Merge pull request #7 from Kwoth/1.0

1.0 update
This commit is contained in:
miraai 2016-10-13 10:27:09 +02:00 committed by GitHub
commit 3a88d80ca5
231 changed files with 61804 additions and 14156 deletions

351
.gitignore vendored
View File

@ -1,3 +1,85 @@
#Manually added files
src/NadekoBot/credentials.json
src/NadekoBot/data/NadekoBot.db
src/NadekoBot/musicdata
# Created by https://www.gitignore.io/api/visualstudio,visualstudiocode,windows,linux,macos
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
### Windows ###
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
@ -6,16 +88,154 @@
*.user
*.userosscache
*.sln.docstates
*.pfx
.vs
obj/
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
**/src/NadekoBot/bin
Tests/bin
src/NadekoBot/credentials.json
src/NadekoBot/project.lock.json
src/NadekoBot/data/*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
@ -25,12 +245,109 @@ src/NadekoBot/data/*
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
NadekoBot/bin/Debug/data/nadekobot.sqlite
NadekoBot/bin/Debug/data/config.json
NadekoBot/bin/Debug/data/ServerSpecificConfigs.json
NadekoBot.sln.iml
.idea/workspace.xml
.idea/vcs.xml
.idea/modules.xml
NadekoBot/bin/Debug/data/config_xnaas.json
src/NadekoBot/project.lock.json
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/
### VisualStudio Patch ###
build/

View File

@ -16,29 +16,45 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "discord.net\
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "discord.net\src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "tests", "src\tests\tests.xproj", "{14CBADA0-971C-44E3-B331-C7D01DD74F0B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
GlobalNadeko|Any CPU = GlobalNadeko|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{45EC1473-C678-4857-A544-07DFE0D0B478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{45EC1473-C678-4857-A544-07DFE0D0B478}.Debug|Any CPU.Build.0 = Debug|Any CPU
{45EC1473-C678-4857-A544-07DFE0D0B478}.GlobalNadeko|Any CPU.ActiveCfg = GlobalNadeko|Any CPU
{45EC1473-C678-4857-A544-07DFE0D0B478}.GlobalNadeko|Any CPU.Build.0 = GlobalNadeko|Any CPU
{45EC1473-C678-4857-A544-07DFE0D0B478}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45EC1473-C678-4857-A544-07DFE0D0B478}.Release|Any CPU.Build.0 = Release|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.GlobalNadeko|Any CPU.ActiveCfg = GlobalNadeko|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.GlobalNadeko|Any CPU.Build.0 = GlobalNadeko|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Release|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.GlobalNadeko|Any CPU.ActiveCfg = GlobalNadeko|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.GlobalNadeko|Any CPU.Build.0 = GlobalNadeko|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.Build.0 = Release|Any CPU
{14CBADA0-971C-44E3-B331-C7D01DD74F0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14CBADA0-971C-44E3-B331-C7D01DD74F0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14CBADA0-971C-44E3-B331-C7D01DD74F0B}.GlobalNadeko|Any CPU.ActiveCfg = GlobalNadeko|Any CPU
{14CBADA0-971C-44E3-B331-C7D01DD74F0B}.GlobalNadeko|Any CPU.Build.0 = GlobalNadeko|Any CPU
{14CBADA0-971C-44E3-B331-C7D01DD74F0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14CBADA0-971C-44E3-B331-C7D01DD74F0B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{45EC1473-C678-4857-A544-07DFE0D0B478} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{14CBADA0-971C-44E3-B331-C7D01DD74F0B} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
EndGlobalSection
EndGlobal

19
NuGet.Config Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="Discord myget feed" value="https://www.myget.org/F/discord-net/api/v3/index.json" />
<add key="Image Processor" value="https://www.myget.org/F/imageprocessor/api/v3/index.json" />
</packageSources>
<disabledPackageSources>
<add key="Microsoft and .NET" value="true" />
</disabledPackageSources>
<packageRestore>
<add key="enabled" value="True" />
<add key="automatic" value="True" />
</packageRestore>
<bindingRedirects>
<add key="skip" value="False" />
</bindingRedirects>
</configuration>

@ -1 +1 @@
Subproject commit b51408def863ee5f4273478efa65eb50e4008487
Subproject commit 06e2a2d019bfa7613bea2f0c698d5ff3bf34bbf0

293
docs/Commands List.md Normal file
View File

@ -0,0 +1,293 @@
### Administration
Command and aliases | Description | Usage
----------------|--------------|-------
`.resetperms` | Resets BOT's permissions module on this server to the default value. | `.resetperms` **Requires Administrator server permission.**
`.restart` | Restarts the bot. Might not work. | `.restart` **Bot owner only.**
`.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. **Server Manager Only.** | `.delmsgoncmd` **Requires Administrator server permission.**
`.setrole` `.sr` | Sets a role for a given user. | `.sr @User Guest` **Requires ManageRoles server permission.**
`.removerole` `.rr` | Removes a role from a given user. | `.rr @User Admin` **Requires ManageRoles server permission.**
`.renamerole` `.renr` | Renames a role. Roles you are renaming must be lower than bot's highest role. | `.renr "First role" SecondRole` **Requires ManageRoles server permission.**
`.removeallroles` `.rar` | Removes all roles from a mentioned user. | `.rar @User` **Requires ManageRoles server permission.**
`.createrole` `.cr` | Creates a role with a given name. | `.cr Awesome Role` **Requires ManageRoles server permission.**
`.rolecolor` `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. | `.rc Admin 255 200 100` or `.rc Admin ffba55` **Requires ManageRoles server permission.**
`.ban` `.b` | Bans a user by id or name with an optional message. | `.b "@some Guy" Your behaviour is toxic.` **Requires BanMembers server permission.**
`.softban` `.sb` | Bans and then unbans a user by id or name with an optional message. | `.sb "@some Guy" Your behaviour is toxic.` **Requires BanMembers server permission.**
`.kick` `.k` | Kicks a mentioned user. | `.k "@some Guy" Your behaviour is toxic.` **Requires KickMembers server permission.**
`.mute` | Mutes a mentioned user in a voice channel. | `.mute @Someone` **Requires MuteMembers server permission.**
`.unmute` | Unmutes mentioned user or users. | `.unmute "@Someguy"` or `.unmute "@Someguy" "@Someguy"` **Requires MuteMembers server permission.**
`.deafen` `.deaf` | Deafens mentioned user or users. | `.deaf "@Someguy"` or `.deaf "@Someguy" "@Someguy"` **Requires DeafenMembers server permission.**
`.undeafen` `.undef` | Undeafens mentioned user or users. | `.undef "@Someguy"` or `.undef "@Someguy" "@Someguy"` **Requires DeafenMembers server permission.**
`.delvoichanl` `.dvch` | Deletes a voice channel with a given name. | `.dvch VoiceChannelName` **Requires ManageChannels server permission.**
`.creatvoichanl` `.cvch` | Creates a new voice channel with a given name. | `.cvch VoiceChannelName` **Requires ManageChannels server permission.**
`.deltxtchanl` `.dtch` | Deletes a text channel with a given name. | `.dtch TextChannelName` **Requires ManageChannels server permission.**
`.creatxtchanl` `.ctch` | Creates a new text channel with a given name. | `.ctch TextChannelName` **Requires ManageChannels server permission.**
`.settopic` `.st` | Sets a topic on the current channel. | `.st My new topic` **Requires ManageChannels server permission.**
`.setchanlname` `.schn` | Changed the name of the current channel. | `.schn NewName` **Requires ManageChannels server permission.**
`.prune` `.clr` | `.prune` removes all nadeko's messages in the last 100 messages.`.prune X` removes last X messages from the channel (up to 100)`.prune @Someone` removes all Someone's messages in the last 100 messages.`.prune @Someone X` removes last X 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X`
`.die` | Works only for the owner. Shuts the bot down. | `@NadekoBot die` **Bot owner only.**
`.setname` `.newnm` | Give the bot a new name. | `.newnm BotName` **Bot owner only.**
`.setavatar` `.setav` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. | `.setav http://i.imgur.com/xTG3a1I.jpg` **Bot owner only.**
`.setgame` | Sets the bots game. | `.setgame Playing with kwoth` **Bot owner only.**
`.send` | Sends a message to someone on a different server through the bot. Separate server and channel/user ids with `|` and prepend channel id with `c:` and user id with `u:`. | `.send serverid|c:channelid` or `.send serverid|u:userid` **Bot owner only.**
`.announce` | Sends a message to all servers' general channel bot is connected to. | `.announce Useless spam` **Bot owner only.**
`.savechat` | Saves a number of messages to a text file and sends it to you. | `.savechat 150` **Bot owner only.**
`.mentionrole` `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. | `.menro RoleName` **Requires MentionEveryone server permission.**
`.donators` | List of lovely people who donated to keep this project alive. | `.donators`
`.donadd` | Add a donator to the database. **Kwoth Only** | `.donadd Donate Amount` **Bot owner only.**
`.autoassignrole` `.aar` | Automaticaly assigns a specified role to every user who joins the server. | `.aar` to disable, `.aar Role Name` to enable **Requires ManageRoles server permission.**
`.scsc` | Starts an instance of cross server channel. You will get a token as a DM that other people will use to tune in to the same instance. | `.scsc` **Bot owner only.**
`.jcsc` | Joins current channel to an instance of cross server channel using the token. | `.jcsc TokenHere` **Requires ManageServer server permission.**
`.lcsc` | Leaves Cross server channel instance from this channel. | `.lcsc` **Requires ManageServer server permission.**
`.fwmsgs` | Toggles forwarding of non-command messages sent to bot's DM to the bot owners | `.fwmsgs` **Bot owner only.**
`.fwtoall` | Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json | `.fwtoall` **Bot owner only.**
`.logserver` | Logs server activity in this channel. | `.logserver` **Requires Administrator server permission.** **Bot owner only.**
`.logignore` | Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. | `.logignore` **Requires Administrator server permission.** **Bot owner only.**
`.userpresence` | Starts logging to this channel when someone from the server goes online/offline/idle. | `.userpresence` **Requires Administrator server permission.**
`.voicepresence` | Toggles logging to this channel whenever someone joins or leaves a voice channel you are in right now. | `.voicepresence` **Requires Administrator server permission.**
`.repeatinvoke` `.repinv` | Immediately shows the repeat message and restarts the timer. | `.repinv` **Requires ManageMessages server permission.**
`.repeat` | Repeat a message every X minutes. If no parameters are specified, repeat is disabled. | `.repeat 5 Hello there`
`.migratedata` | Migrate data from old bot configuration | `.migratedata` **Bot owner only.**
`.rotateplaying` `.ropl` | Toggles rotation of playing status of the dynamic strings you specified earlier. | `.ropl` **Bot owner only.**
`.addplaying` `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued% | `.adpl` **Bot owner only.**
`.listplaying` `.lipl` | Lists all playing statuses with their corresponding number. | `.lipl` **Bot owner only.**
`.removeplaying` `.rmpl` `.repl` | Removes a playing string on a given number. | `.rmpl` **Bot owner only.**
`.slowmode` | Toggles slow mode. When ON, users will be able to send only 1 message every 5 seconds. | `.slowmode` **Requires ManageMessages server permission.**
`.asar` | Adds a role, or list of roles separated by whitespace(use quotations for multiword roles) to the list of self-assignable roles. | `.asar Gamer` **Requires ManageRoles server permission.**
`.rsar` | Removes a specified role from the list of self-assignable roles. | `.rsar` **Requires ManageRoles server permission.**
`.lsar` | Lists all self-assignable roles. | `.lsar`
`.togglexclsar` `.tesar` | Toggles whether the self-assigned roles are exclusive. (So that any person can have only one of the self assignable roles) | `.tesar` **Requires ManageRoles server permission.**
`.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | `.iam Gamer`
`.iamnot` `.iamn` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | `.iamn Gamer`
`.leave` | Makes Nadeko leave the server. Either name or id required. | `.leave 123123123331` **Bot owner only.**
`.greetdel` `.grdel` | Toggles automatic deletion of greet messages. | `.greetdel` **Requires ManageServer server permission.**
`.greet` | Toggles anouncements on the current channel when someone joins the server. | `.greet` **Requires ManageServer server permission.**
`.greetmsg` | Sets a new join announcement message which will be shown in the server's channel. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. | `.greetmsg Welcome, %user%.` **Requires ManageServer server permission.**
`.greetdm` | Toggles whether the greet messages will be sent in a DM (This is separate from greet - you can have both, any or neither enabled). | `.greetdm` **Requires ManageServer server permission.**
`.greetdmmsg` | Sets a new join announcement message which will be sent to the user who joined. Type %user% if you want to mention the new member. Using it with no message will show the current DM greet message. | `.greetdmmsg Welcome to the server, %user%`. **Requires ManageServer server permission.**
`.bye` | Toggles anouncements on the current channel when someone leaves the server. | `.bye` **Requires ManageServer server permission.**
`.byemsg` | Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message. | `.byemsg %user% has left.` **Requires ManageServer server permission.**
`.byedel` | Toggles automatic deletion of bye messages. | `.byedel` **Requires ManageServer server permission.**
`.voice+text` `.v+t` | Creates a text channel for each voice channel only users in that voice channel can see.If you are server owner, keep in mind you will see them all the time regardless. | `.voice+text` **Requires ManageRoles server permission.** **Requires ManageChannels server permission.**
`.cleanvplust` `.cv+t` | Deletes all text channels ending in `-voice` for which voicechannels are not found. **Use at your own risk. Needs Manage Roles and Manage Channels Permissions.** | `.cleanv+t` **Requires ManageChannels server permission.** **Requires ManageRoles server permission.**
### ClashOfClans
Command and aliases | Description | Usage
----------------|--------------|-------
`,createwar` `,cw` | Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. | `,cw 15 The Enemy Clan`
`,startwar` `,sw` | Starts a war with a given number. | `,sw 15`
`,listwar` `,lw` | Shows the active war claims by a number. Shows all wars in a short way if no number is specified. | `,lw [war_number] or ,lw`
`,claim` `,call` `,c` | Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. | `,call [war_number] [base_number] [optional_other_name]`
`,claimfinish1` `,cf1` | Finish your claim with 1 star if you destroyed a base. First argument is the war number, optional second argument finishes for someone else. | `,cf1 2 SomeGirl`
`,claimfinish2` `,cf2` | Finish your claim with 2 stars if you destroyed a base. First argument is the war number, optional second argument finishes for someone else. | `,cf2 1 SomeGuy`
`,claimfinish` `,cf` | Finish your claim with 3 stars if you destroyed a base. First argument is the war number, optional second argument finishes for someone else. | `,cf 1 Someone`
`,endwar` `,ew` | Ends the war with a given index. | `,ew [war_number]`
`,unclaim` `,ucall` `,uc` | Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim | `,uc [war_number] [optional_other_name]`
### CustomReactions
Command and aliases | Description | Usage
----------------|--------------|-------
`#addcustreact` `#acr` | Add a custom reaction with a trigger and a response. Running this command in server requires Administration permission. Running this command in DM is Bot Owner only and adds a new global custom reaction. Guide here: <http://nadekobot.readthedocs.io/en/1.0/Custom%20Reactions/> | `.acr "hello" Hi there %user%`
`#listcustreact` `#lcr` | Lists global or server custom reactions (15 commands per page). Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. | `.lcr 1`
`#delcustreact` `#dcr` | Deletes a custom reaction on a specific index. If ran in DM, it is bot owner only and deletes a global custom reaction. If ran in a server, it requires Administration priviledges and removes server custom reaction. | `.dcr 5`
### Gambling
Command and aliases | Description | Usage
----------------|--------------|-------
`xraffle` | Prints a name and ID of a random user from the online list from the (optional) role. | `$raffle` or `$raffle RoleName`
`xcash` `x$$` | Check how much NadekoFlowers a person has. (Defaults to yourself) | `$$$` or `$$$ @SomeGuy`
`xgive` | Give someone a certain amount of currency. | `$give 1 "@SomeGuy"`
`xaward` | Awards someone a certain amount of currency. | `$award 100 @person` **Bot owner only.**
`xtake` | Takes a certain amount of flowers from someone. | `$take 1 "@someguy"` **Bot owner only.**
`xbetroll` `xbr` | Bets a certain amount of NadekoFlowers and rolls a dice. Rolling over 66 yields x2 flowers, over 90 - x3 and 100 x10. | `$br 5`
`xleaderboard` `xlb` | Displays bot currency leaderboard. | `$lb`
`xrace` | Starts a new animal race. | `$race`
`xjoinrace` `xjr` | Joins a new race. You can specify an amount of flowers for betting (optional). You will get YourBet*(participants-1) back if you win. | `$jr` or `$jr 5`
`xroll` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | `$roll` or `$roll 7` or `$roll 3d5`
`xrolluo` | Rolls X normal dice (up to 30) unordered. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | `$rolluo` or `$rolluo 7` or `$rolluo 3d5`
`xnroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15`
`xdraw` | Draws a card from the deck.If you supply number X, she draws up to 5 cards from the deck. | `$draw` or `$draw 5`
`xshuffle` `xsh` | Reshuffles all cards back into the deck. | `$sh`
`xflip` | Flips coin(s) - heads or tails, and shows an image. | `$flip` or `$flip 3`
`xbetflip` `xbf` | Bet to guess will the result be heads or tails. Guessing awards you double flowers you've bet. | `$bf 5 heads` or `$bf 3 t`
### Games
Command and aliases | Description | Usage
----------------|--------------|-------
`>choose` | Chooses a thing from a list of things | `>choose Get up;Sleep;Sleep more`
`>8ball` | Ask the 8ball a yes/no question. | `>8ball should I do something`
`>rps` | Play a game of rocket paperclip scissors with Nadeko. | `>rps scissors`
`>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows`
`>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | `>leet 3 Hello`
`>poll` | Creates a poll, only person who has manage server permission can do it. | `>poll Question?;Answer1;Answ 2;A_3`
`>pollend` | Stops active poll on this server and prints the results in this channel. | `>pollend`
`>pick` | Picks a flower planted in this channel. | `>pick`
`>plant` | Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost) | `>plant`
`>gencurrency` `>gc` | Toggles currency generation on this channel. Every posted message will have chance to spawn a NadekoFlower. Chance is specified by the Bot Owner. (default is 2%) | `>gc` **Requires ManageMessages server permission.**
`>typestart` | Starts a typing contest. | `>typestart`
`>typestop` | Stops a typing contest on the current channel. | `>typestop`
`>typeadd` | Adds a new article to the typing contest. | `>typeadd wordswords` **Bot owner only.**
`>trivia` `>t` | Starts a game of trivia. You can add nohint to prevent hints.First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question. | `>t nohint` or `>t 5 nohint`
`>tl` | Shows a current trivia leaderboard. | `>tl`
`>tq` | Quits current trivia after current question. | `>tq`
### Help
Command and aliases | Description | Usage
----------------|--------------|-------
`-modules` `-mdls` | Lists all bot modules. | `-modules`
`-commands` `-cmds` | List all of the bot's commands from a certain module. You can either specify full, or only first few letters of the module name. | `-commands Administration` or `-cmds Admin`
`-help` `-h` | Either shows a help for a single command, or DMs you help link if no arguments are specified. | `-h !!q` or `-h`
`-hgit` | Generates the commandlist.md file. | `-hgit` **Bot owner only.**
`-readme` `-guide` | Sends a readme and a guide links to the channel. | `-readme` or `-guide`
`-donate` | Instructions for helping the project financially. | `-donate`
### Music
Command and aliases | Description | Usage
----------------|--------------|-------
`!!next` `!!n` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `!!n`
`!!stop` `!!s` | Stops the music and clears the playlist. Stays in the channel. | `!!s`
`!!destroy` `!!d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!!d`
`!!pause` `!!p` | Pauses or Unpauses the song. | `!!p`
`!!queue` `!!q` `!!yq` | Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**. | `!!q Dream Of Venice`
`!!soundcloudqueue` `!!sq` | Queue a soundcloud song using keywords. Bot will join your voice channel.**You must be in a voice channel**. | `!!sq Dream Of Venice`
`!!listqueue` `!!lq` | Lists 15 currently queued songs per page. Default page is 1. | `!!lq` or `!!lq 2`
`!!nowplaying` `!!np` | Shows the song currently playing. | `!!np`
`!!volume` `!!vol` | Sets the music volume 0-100% | `!!vol 50`
`!!defvol` `!!dv` | Sets the default music volume when music playback is started (0-100). Persists through restarts. | `!!dv 80`
`!!shuffle` `!!sh` | Shuffles the current playlist. | `!!sh`
`!!playlist` `!!pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `!!pl playlist link or name`
`!!soundcloudpl` `!!scpl` | Queue a soundcloud playlist using a link. | `!!scpl soundcloudseturl`
`!!localplaylst` `!!lopl` | Queues all songs from a directory. | `!!lopl C:/music/classical` **Bot owner only.**
`!!radio` `!!ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: <https://streamable.com/al54>) | `!!ra radio link here`
`!!local` `!!lo` | Queues a local file by specifying a full path. | `!!lo C:/music/mysong.mp3` **Bot owner only.**
`!!move` `!!mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!!mv`
`!!remove` `!!rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!!rm 5`
`!!movesong` `!!ms` | Moves a song from one position to another. | `!! ms 5>3`
`!!setmaxqueue` `!!smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `!!smq 50` or `!!smq`
`!!reptcursong` `!!rcs` | Toggles repeat of current song. | `!!rcs`
`!!rpeatplaylst` `!!rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!!rpl`
`!!save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!!save classical1`
`!!load` | Loads a playlist under a certain name. | `!!load classical-1`
`!!playlists` `!!pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!!pls 1`
`!!deleteplaylist` `!!delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!!delpls animu-5`
`!!goto` | Goes to a specific time in seconds in a song. | `!!goto 30`
`!!getlink` `!!gl` | Shows a link to the song in the queue by index, or the currently playing song by default. | `!!gl`
`!!autoplay` `!!ap` | Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty) | `!!ap`
### NSFW
Command and aliases | Description | Usage
----------------|--------------|-------
`~hentai` | Shows a 2 random images (from gelbooru and danbooru) with a given tag. Tag is optional but preferred. Only 1 tag allowed. | `~hentai yuri`
`~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~danbooru yuri+kissing`
`~gelbooru` | Shows a random hentai image from gelbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~gelbooru yuri+kissing`
`~rule34` | Shows a random image from rule34.xx with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~rule34 yuri+kissing`
`~e621` | Shows a random hentai image from e621.net with a given tag. Tag is optional but preffered. Use spaces for multiple tags. | `~e621 yuri kissing`
`~cp` | We all know where this will lead you to. | `~cp`
`~boobs` | Real adult content. | `~boobs`
`~butts` `~ass` `~butt` | Real adult content. | `~butts` or `~ass`
### Permissions
Command and aliases | Description | Usage
----------------|--------------|-------
`;verbose` `;v` | Sets whether to show when a command/module is blocked. | `;verbose true`
`;permrole` `;pr` | Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'. | `;pr role`
`;listperms` `;lp` | Lists whole permission chain with their indexes. You can specify an optional page number if there are a lot of permissions. | `;lp` or `;lp 3`
`;removeperm` `;rp` | Removes a permission from a given position | `;rp 1`
`;moveperm` `;mp` | Moves permission from one position to another. | `;mp 2 4`
`;srvrcmd` `;sc` | Sets a command's permission at the server level. | `;sc "command name" disable`
`;srvrmdl` `;sm` | Sets a module's permission at the server level. | `;sm "module name" enable`
`;usrcmd` `;uc` | Sets a command's permission at the user level. | `;uc "command name" enable SomeUsername`
`;usrmdl` `;um` | Sets a module's permission at the user level. | `;um "module name" enable SomeUsername`
`;rolecmd` `;rc` | Sets a command's permission at the role level. | `;rc "command name" disable MyRole`
`;rolemdl` `;rm` | Sets a module's permission at the role level. | `;rm "module name" enable MyRole`
`;chnlcmd` `;cc` | Sets a command's permission at the channel level. | `;cc "command name" enable SomeChannel`
`;chnlmdl` `;cm` | Sets a module's permission at the channel level. | `;cm "module name" enable SomeChannel`
`;allchnlmdls` `;acm` | Enable or disable all modules in a specified channel. | `;acm enable #SomeChannel`
`;allrolemdls` `;arm` | Enable or disable all modules for a specific role. | `;arm [enable/disable] MyRole`
`;allusrmdls` `;aum` | Enable or disable all modules for a specific user. | `;aum enable @someone`
`;allsrvrmdls` `;asm` | Enable or disable all modules for your server. | `;asm [enable/disable]`
`;ubl` | Either [add]s or [rem]oves a user specified by a mention or ID from a blacklist. | `;ubl add @SomeUser` or `;ubl rem 12312312313` **Bot owner only.**
`;cbl` | Either [add]s or [rem]oves a channel specified by an ID from a blacklist. | `;cbl rem 12312312312` **Bot owner only.**
`;sbl` | Either [add]s or [rem]oves a server specified by a Name or ID from a blacklist. | `;sbl add 12312321312` or `;sbl rem SomeTrashServer` **Bot owner only.**
`;cmdcooldown` `;cmdcd` | Sets a cooldown per user for a command. Set to 0 to remove the cooldown. | `;cmdcd "some cmd" 5`
`;allcmdcooldowns` `;acmdcds` | Shows a list of all commands and their respective cooldowns. | `;acmdcds`
`;srvrfilterinv` `;sfi` | Toggles automatic deleting of invites posted in the server. Does not affect Bot Owner. | `;sfi`
`;chnlfilterinv` `;cfi` | Toggles automatic deleting of invites posted in the channel. Does not negate the .srvrfilterinv enabled setting. Does not affect Bot Owner. | `;cfi`
`;srvrfilterwords` `;sfw` | Toggles automatic deleting of messages containing forbidden words on the server. Does not affect Bot Owner. | `;sfw`
`;chnlfilterwords` `;cfw` | Toggles automatic deleting of messages containing banned words on the channel. Does not negate the .srvrfilterwords enabled setting. Does not affect bot owner. | `;cfw`
`;fw` | Adds or removes (if it exists) a word from the list of filtered words. Use` ;sfw` or `;cfw` to toggle filtering. | `;fw poop`
`;lstfilterwords` `;lfw` | Shows a list of filtered words. | `;lfw`
### Searches
Command and aliases | Description | Usage
----------------|--------------|-------
`~weather` `~we` | Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | `~we Moscow RF`
`~youtube` `~yt` | Searches youtubes and shows the first result | `~yt query`
`~imdb` | Queries imdb for movies or series, show first result. | `~imdb Batman vs Superman`
`~randomcat` `~meow` | Shows a random cat image. | `~meow`
`~randomdog` `~woof` | Shows a random dog image. | `~woof`
`~img` `~i` | Pulls the first image found using a search parameter. Use ~ir for different results. | `~i cute kitten`
`~ir` | Pulls a random image using a search parameter. | `~ir cute kitten`
`~lmgtfy` | Google something for an idiot. | `~lmgtfy query`
`~google` `~g` | Get a google search link for some terms. | `~google query`
`~hearthstone` `~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | `~hs Ysera`
`~urbandict` `~ud` | Searches Urban Dictionary for a word. | `~ud Pineapple`
`~#` | Searches Tagdef.com for a hashtag. | `~# ff`
`~catfact` | Shows a random catfact from <http://catfacts-api.appspot.com/api/facts> | `~catfact`
`~revav` | Returns a google reverse image search for someone's avatar. | `~revav "@SomeGuy"`
`~revimg` | Returns a google reverse image search for an image from a link. | `~revimg Image link`
`~safebooru` | Shows a random image from safebooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~safebooru yuri+kissing`
`~wikipedia` `~wiki` | Gives you back a wikipedia link | `~wiki query`
`~color` `~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00`
`~videocall` | Creates a private <http://www.appear.in> video call link for you and other mentioned people. The link is sent to mentioned people via a private message. | `~videocall "@SomeGuy"`
`~av` `~avatar` | Shows a mentioned person's avatar. | `~av "@SomeGuy"`
`~lolban` | Shows top banned champions ordered by ban rate. | `~lolban`
`~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ | `~memelist`
`~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"`
`~translate` `~trans` | Translates from>to text. From the given language to the destination language. | `~trans en>fr Hello`
`~translangs` | Lists the valid languages for translation. | `~translangs`
`~anime` `~ani` `~aq` | Queries anilist for an anime and shows the first result. | `~ani aquarion evol`
`~manga` `~mang` `~mq` | Queries anilist for a manga and shows the first result. | `~mq Shingeki no kyojin`
`~yomama` `~ym` | Shows a random joke from <http://api.yomomma.info/> | `~ym`
`~randjoke` `~rj` | Shows a random joke from <http://tambal.azurewebsites.net/joke/random> | `~rj`
`~chucknorris` `~cn` | Shows a random chucknorris joke from <http://tambal.azurewebsites.net/joke/random> | `~cn`
`~wowjoke` | Get one of Kwoth's penultimate WoW jokes. | `~wowjoke`
`~magicitem` `~mi` | Shows a random magicitem from <https://1d4chan.org/wiki/List_of_/tg/%27s_magic_items> | `~mi`
`~osu` | Shows osu stats for a player. | `~osu Name` or `~osu Name taiko`
`~osub` | Shows information about an osu beatmap. | `~osub https://osu.ppy.sh/s/127712`
`~osu5` | Displays a user's top 5 plays. | `~osu5 Name`
`~pokemon` `~poke` | Searches for a pokemon. | `~poke Sylveon`
`~pokemonability` `~pokeab` | Searches for a pokemon ability. | `~pokeab "water gun"`
`~hitbox` `~hb` | Notifies this channel when a certain user starts streaming. | `~hitbox SomeStreamer` **Requires ManageMessages server permission.**
`~twitch` `~tw` | Notifies this channel when a certain user starts streaming. | `~twitch SomeStreamer` **Requires ManageMessages server permission.**
`~beam` `~bm` | Notifies this channel when a certain user starts streaming. | `~beam SomeStreamer` **Requires ManageMessages server permission.**
`~liststreams` `~ls` | Lists all streams you are following on this server. | `~ls`
`~removestream` `~rms` | Removes notifications of a certain streamer on this channel. | `~rms SomeGuy` **Requires ManageMessages server permission.**
`~checkstream` `~cs` | Checks if a user is online on a certain streaming platform. | `~cs twitch MyFavStreamer`
### Utility
Command and aliases | Description | Usage
----------------|--------------|-------
`.whosplaying` `.whpl` | Shows a list of users who are playing the specified game. | `.whpl Overwatch`
`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. If the list is too long for 1 message, you must have Manage Messages permission. | `.inrole Role`
`.checkmyperms` | Checks your userspecific permissions on this channel. | `.checkmyperms`
`.userid` `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy"`
`.channelid` `.cid` | Shows current channel ID. | `.cid`
`.serverid` `.sid` | Shows current server ID. | `.sid`
`.roles` | List all roles on this server or a single user if specified. | `.roles`
`.channeltopic` `.ct` | Sends current channel's topic as a message. | `.ct`
`.stats` | Shows some basic stats for Nadeko. | `.stats`
`.showemojis` `.se` | Shows a name and a link to every SPECIAL emoji in the message. | `.se A message full of SPECIALemojis`
`.calculate` `.calc` | Evaluate a mathematical expression. | `.calc 1+1`
`.calcops` | Shows all available operations in .calc command | `.calcops`
`.serverinfo` `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | `.sinfo Some Server`
`.channelinfo` `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | `.cinfo #some-channel`
`.userinfo` `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | `.uinfo @SomeUser`
`...` | Shows a random quote with a specified name. | `... abc`
`..` | Adds a new quote with the specified name and message. | `.. sayhi Hi`
`.deletequote` `.delq` | Deletes a random quote with the specified keyword. You have to either be server Administrator or the creator of the quote to delete it. | `.delq abc`
`.delallq` `.daq` | Deletes all quotes on a specified keyword. | `.delallq kek` **Requires Administrator server permission.**
`.remind` | Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message. | `.remind me 1d5h Do something` or `.remind #general Start now!`
`.remindtemplate` | Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind. | `.remindtemplate %user%, you gotta do %message%!` **Bot owner only.**
`.convertlist` | List of the convertible dimensions and currencies. | `.convertlist`
`.convert` | Convert quantities. Use `.convertlist` to see supported dimensions and currencies. | `.convert m km 1000`

View File

@ -0,0 +1,7 @@
### How to contribute
1. Make Pull Requests to the **1.0 BRANCH**
2. Keep 1 Pull Request to a single feature
3. Explain what you did in the PR message
Thanks for all the help ^_^

41
docs/Custom Reactions.md Normal file
View File

@ -0,0 +1,41 @@
##Custom Reactions
###Important
* For modifying **global** custom reactions, the ones which will work across all the servers your bot is connected to, you **must** be a Bot Owner.
You must also use the commands for adding, deleting and listing these reactions in a direct message with the bot.
* For modifying **local** custom reactions, the ones which will only work on the server that they are added on, require you to have the **Administrator** permission.
You must also use the commands for adding, deleting and listing these reactions in the server you want the custom reactions to work on.
###Commands and Their Use
| Command Name | Description | Example |
|:------------:|-------------|---------|
|`.acr`|Add a custom reaction with a trigger and a response. Running this command in a server requries the Administrator permission. Running this command in DM is Bot Owner only, and adds a new global custom reaction. Guide [here](http://nadekobot.readthedocs.io/en/1.0/Custom%20Reactions/)|`.acr "hello" Hi there, %user%!`|
|`.lcr`|Lists a page of global or server custom reactions (15 reactions per page). Running this command in a DM will list the global custom reactions, while running it in a server will list that server's custom reactions.|`.lcr 1`|
|`.dcr`|Deletes a custom reaction based on the provided index. Running this command in a server requires the Administrator permission. Running this command in DM is Bot Owner only, and will delete a global custom reaction.|`.dcr 5`|
####Now that we know the commands let's take a look at an example of adding a command with `.acr`,
`.acr "Nice Weather" It sure is, %user%!`
This command can be split into two different arguments:
* The trigger, `"Nice Weather"`
* And the response, `It sure is, %user%!`
An important thing to note about the triger is that, to be more than one word, we had to wrap it with quotation marks, `"Like this"` otherwise, only the first word would have been recognised as the trigger, and the second word would have been recognised as part of the response.
There's no special requirement for the formatting of the response, so we could just write it in exactly the same way we want it to respond, albeit with a placeholder - which will be explained in this next section.
Now, if that command was ran in a server, anyone on that server can make the bot mention them, saying `It sure is, @Username` anytime they say "Nice Weather". If the command is ran in a direct message with the bot, then the custom reaction can be used on every server the bot is connected to.
###Placeholders!
There are currently three different placeholders which we will look at, with more placeholders potentially coming in the future.
| Placeholder | Description | Example Usage | Usage |
|:-----------:|-------------|---------------|-------|
|`%mention`|The `%mention%` placeholder is triggered when you type `@BotName` - It's important to note that if you've given the bot a custom nickname, this trigger won't work!|```.acr "Hello %mention%" I, %mention%, also say hello!```|Input: "Hello @BotName" Output: "I, @BotName, also say hello!"|
|`%user%`|The `%user%` placeholder mentions the person who said the command|`.acr "Who am I?" You are %user%!`|Input: "Who am I?" Output: "You are @Username!"|
|`%rng%`|The `%rng%` placeholder generates a random number between 0 and 10|`.acr "Random number" %rng%`|Input: "Random number" Output: "2"|
[//]: # (|`%target%`|The `%target%` placeholder is used to make Nadeko Mention another person or phrase|`.acr "Say this: " %target%|Input: "Say this: I, @BotName, am a parrot!". Output: "I, @BotName, am a parrot!".|)
Thanks to Nekai for being creative. <3

17
docs/Donate.md Normal file
View File

@ -0,0 +1,17 @@
##Donate to NadekoBot
If you want to help Nadeko and Nadeko's team by donating, you can do that in the two of the following ways:
###Patreon
You can donate over [Patreon][Patreon] and support the project.
[![img][img]](https://www.patreon.com/nadekobot)
###PayPal
If you wish to donate over PayPal, you can send your donations to: `nadekodiscordbot@gmail.com`
[Patreon]: https://www.patreon.com/nadekobot
[img]: http://www.mister-and-me.com/wp-content/plugins/patron-button-and-widgets-by-codebard/images/patreon-medium-button.png

View File

@ -0,0 +1,77 @@
#Frequently Asked Questions
###Question 1: How do I get Nadeko to join my server?
----
**Answer:** Simply send Nadeko a Direct Message with -h and follow the link. **Only People with the Manage Server permission can add the bot to the server**
###Question 2: I want to change permissions, but it isn't working!
----
**Answer:** You must have the ;permrole (by default this is the "Nadeko" role, for more details on permissions check [here](http://nadekobot.readthedocs.io/en/1.0/Permissions%20System/ "Permissions"). If you have a role called `Nadeko` but can't assign it, just create a new Role and assign that instead.)
###Question 3: I want to disable NSFW on my server.
----
**Answer:** To disable the NSFW Module for your server type, `;sm NSFW disable`. If this does not work refer to Question 2.
###Question 4: How do I get NadekoFlowers/Currency?
----
**Answer:** You can get NadekoFlowers by picking them up after they have been generated with `>gc`, which you can then either plant (give away to a channel so that someone can pick it), or gamble with for potentinal profit with `$betflip`, `$betroll` and `$jr`
###Question 5: I have an issue/bug/suggestion, where do I put it so it gets noticed?
-----------
**Answer:** First, check [issues](https://github.com/Kwoth/NadekoBot/issues "GitHub NadekoBot Issues"), then check the `#suggestions` channel in the Nadeko [help server](https://discord.gg/0ehQwTK2RBjAxzEY).
If your problem or suggestion is not there, feel free to request/notify us about it either in the Issues section of GitHub for issues or in the `#suggestions` channel on the Nadeko help server for suggestions.
###Question 6: How do I use this command?
--------
**Answer:** You can see the description and usage of certain commands by using `-h command` **i.e** `-h ;sm`.
The whole list of commands can be found [here](http://nadekobot.readthedocs.io/en/1.0/Commands%20List/ "Command List")
###Question 7: Music isn't working?
----
**Answer:** Music is disabled on public Nadeko due to large hosting costs, it will be re-enabled later in the future for donators.
**If you would like music in the meantime, you must host Nadeko yourself**. Be sure you have FFMPEG installed correctly, and have followed the [guide](http://nadekobot.readthedocs.io/en/1.0/guides/Windows%20Guide/) carefully.
###Question 8: My music is still not working/very laggy?
----
**Answer:** Try changing your discord [location][1], if this doesn't work be sure you have enabled the correct permissions for Nadeko and rebooted since installing FFMPEG.
[1]: https://support.discordapp.com/hc/en-us/articles/216661717-How-do-I-change-my-Voice-Server-Region-
###Question 9: I want to change data in the database (like NadekoFlowers or the pokemontypes of users, but how?
----
**Answer:** Open `/data/NadekoBot.db` using sqlitebrowser (or some alternative), Browse Data, select relevant table, change data, Write changes
###Question 10: The .greet and .bye commands doesn't work, but everything else is (From @Kong)
-----
**Answer:** Set a greeting message by using `.greetmsg YourMessageHere` and a bye-message by using `.byemsg YourMessageHere`. Don't forget that `.greet` and `.bye` only apply to users joining a server, not coming online/offline.
###Question 11: How do I import certs on linux?
-------
**Answer:**
`certmgr -ssl https://discordapp.com`
`certmgr -ssl https://gateway.discord.gg`
###Question 12: I made an application, but I can't add that new bot to my server, how do I invite it to my server?
----
**Answer:** You need to use oauth link to add it to you server, just copy your CLIENT ID (that's in the same Developer page where you brought your token) and replace `12345678` in the link below: https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303
Follow this Detailed [Guide](http://discord.kongslien.net/guide.html) if you do not understand.
###Question 13: I'm building NadekoBot from source, but I get hundreds of (namespace) errors without changing anything!?
-----
**Answer:** Using Visual Studio, you can solve these errors by going to `Tools` -> `NuGet Package Manager` -> `Manage NuGet Packages for Solution`. Go to the Installed tab, select the Packages that were missing (usually `Newtonsoft.json` and `RestSharp`) and install them for all projects
###Question 14: My bot has all permissions but it's still saying, "Failed to add roles. Bot has insufficient permissions.". How do I fix this?
----------
**Answer:** Discord has added a few new features and roles now follow hierarchy. This means you need to place your bot's role above every-other role your server has. [Here's](https://support.discordapp.com/hc/en-us/articles/214836687-Role-Management-101) a link to Discords role management 101.
###Question 15: I've broken permissions and am stuck, can I reset permissions?
----------
**Answer:** Yes, there is a way, in one easy command! Just run `.resetperms`
**Please Note:** *The bot can only set/add all roles below its own highest role. It can not assign it's "highest role" to anyone else.*

60
docs/JSON Explanations.md Normal file
View File

@ -0,0 +1,60 @@
###Setting up your Credentials
If you do not see `credentials.json` you will need to rename `credentials_example.json` to `credentials.json`.
**This is how the unedited credentials look:**
```json
{
"Token": "",
"ClientId": "116275390695079945",
"BotId": 1231231231231,
"OwnerIds": [
123123123123,
5675675679845
],
"GoogleAPIKey": "",
"SoundCloudClientID": "",
"MashapeKey": "",
"LOLAPIKEY": "",
"TrelloAPPKey": "",
"OsuAPIKey": "",
"CarbonKey": "",
}
```
####Required Parts
+ **Token** - Required to log in. Refer to this [guide](http://discord.kongslien.net/guide.html)
+ **OwnerIds** - Required for the **Owner-Only** commands. Seperate multiple Id's with a comma.
+ **BotId** - Required for custom reactions and conversation commands to work.
+ **Important : Bot ID and Client ID are the same in newer bot accounts due to recent Discord API changes.**
_BotId and the OwnerIds are **NOT** the names of the owner and the bot. If you do not know the id of your bot, keep the two random numbers in those fields and
run the bot then do `.uid @MyBotName` - this will give you your bot_id.
Do the same for yourself with `.uid @MyName` Put these numbers in their respective field of the credentials._
Setting up your API keys
====================
####This part is completely optional, **However it is necessary for music to work properly**
+ **GoogleAPIKey** - Required for Youtube Song Search, Playlist queuing, and URL Shortener. `~i` and `~img`.
+ You can get this api Key [here](https://console.developers.google.com/apis)
+ **SoundCloudClientID** - Required to queue soundloud songs from sc links.
+ You will need to create a new app [here](http://soundcloud.com/you/apps). **Please note you must be logged into SoundCloud**
+ You should come to a page that looks like this ![Imgur](http://i.imgur.com/RAZ2HDM.png)
+ Simply click Register a new application and enter a name.
+ After naming your app you will be brought to this page: ![Imgur](http://i.imgur.com/GH1gjKK.png) Copy the Client ID and click "save app" then paste the Client Id it into your `credentials.json`
+ **MashapeKey** - Required for Urban Disctionary, Hashtag search, and Hearthstone cards.
+ You need to create an account on their [api marketplace](https://market.mashape.com/), after that go to `market.mashape.com/YOURNAMEHERE/applications/default-application` and press **Get the keys** in the top right corner.
+ Copy the key and paste it into `credentials.json`
+ **LOLAPIKey** - Required for all League of Legends commands.
+ You can get this key [here](http://api.champion.gg/)
+ **TrelloAppKey** - Required for the trello commands.
+ You can get this key [here](https://trello.com/app-key) **Be sure you are logged into Trello first**
+ **OsuAPIKey** - Required for Osu commands
+ You can get this key [here](https://osu.ppy.sh/p/api) **You will need to log in and like the soundcloud it may take a few tries**
+ **CarbonKey** -This key is for Carobnitex.net stats.
+ Most likely unnecessary **Needed only if your bot is listed on Carbonitex.net**
Config.json
===========
In the folder where `NadekoBot.exe` is located you should also see a `Data` folder. In this folder you will find `config.json` among other files.
`config.json` contains user specific commands, such as: if DM's sent to the bot are forwarded to you, Blacklisted Ids, Servers, and channels...etc.
**If you do not see** `config.json` **you need to rename** `config_example.json` **to** `config.json`

View File

@ -0,0 +1,84 @@
Permissions Overview
===================
Have you ever felt confused or even overwhelmed when trying to set Nadeko's permissions? In this guide we will be explaining how to use the
permission commands correctly and even cover a few common questions! Every command we discuss here can be found in the [Commands List](http://nadekobot.readthedocs.io/en/1.0/Commands%20List/#permissions).
**To see the old guide for versions 0.9 and below, see [here](http://nadekobot.readthedocs.io/en/latest/Permissions%20System/)**
Why do we use the Permissions Commands?
------------------------------
Permissions are very handy at setting who can use what commands in a server. By default, the NSFW module is blocked, but nothing else is. If something is a bot owner only command, it can only be ran by the bot owner, the person who is running the bot, or has their id in [`credentials.json`](http://nadekobot.readthedocs.io/en/1.0/JSON%20Explanations/ "Setting up your credentials").
The administration module still requires that you have the correct permissions on discord to be able to use these commands, so for users to be able to use commands like `.kick` and `.prune`, they need kick and mange messages permissions respectively.
With the permissions system it possible to restrict who can skip the current song, pick NadekoFlowers or use the NSFW module.
First Time Setup
------------------
To change permissions you **must** meet the following requirement:
**Have the role specified by `;permrole` (By default, this is Nadeko)**
If you have an existing role called "Nadeko" but can't assign it to yourself, create a new role called "Nadeko" and assign that to yourself.
If you would like to set a different role, such as "Admins", to be the role required to edit permissions, do `;permrole Admins` (you must have the current permission role to be able to do this).
Basics & Hierarchy
-----
The [Commands List](http://nadekobot.readthedocs.io/en/1.0/Commands%20List/#permissions) is a great resource which lists **all** the available commands, however we'll go over a few commands here.
Firstly, let's explain how the permissions system works - It's simple once you figure out how each command works!
The permissions system works as a chain, everytime a command is used, the permissions chain is checked. Starting from the top of the chain, the command is compared to a rule, if it isn't either allowed or disallowed by that rule it proceeds to check the next rule all the way till it reaches the bottom rule, which allows all commands.
To view this permissions chain, do `;listperms`, with the top of the chain being rule number 1, shown at the top of the message.
If you want to remove a permission from the chain of permissions, do `;removeperm X` to remove rule number X and similarly, do `;moveperm X Y` to move rule number X to number Y (moving, not swapping!).
As an example, if you wanted to enable NSFW for a certain role, say "Lewd", you could do `;rolemdl NSFW enable Lewd`.
This adds the rule to the top of the permissions chain so even if the default `;sm NSFW disabled` rule exists, the "Lewd" role will be able to use the NSFW module.
If you want the bot to notify users why they can't use a command or module, use `;verbose true` and Nadeko will tell you what rule is preventing the command.
Commonly Asked Questions
---------------
###How do I create a music DJ?
To allow users to only see the current song and have a DJ role for queuing follow these five steps:
1. `;sm Music disable`
* Disables music commands for everybody
2. `;sc !!nowplaying enable`
* Enables the "nowplaying" command for everyone
3. `;sc !!getlink enable`
* Enables the "getlink" command for everyone
4. `;sc !!listqueue enable`
* Enables the "listqueue" command for everyone
5. `;rm Music enable DJ`
* Enables all the music commands only for the DJ role
###How do I create a NSFW channel?
Say you want to only enable NSFW commands in the #NSFW channel, just do the following two steps.
1. `;sm NSFW disable`
* Disables the NSFW module from being used
2. `;cm NSFW enable #NSFW`
* Enables the NSFW module for use in the #NSFW channel
###I've broken permissions and am stuck, can I reset permissions?
Yes, there is a way, in one easy command!
1. `.resetperms`
* This resets the permission chain back to default, with only NSFW disabled
_-- Thanks to @applemac for providing the template for this guide_

29
docs/Readme.md Normal file
View File

@ -0,0 +1,29 @@
##Readme for Commands List
###Bot Owner Only
- *Bot Owner Only* commands refer to the commands only the **owner** of the bot can use.
- *Bot Owner Only* commands do **not** refer to the owner of the **server**, just the owner of the **bot**.
- *Owner of the bot* is a person who is **hosting** their own bot, and their **ID** is inside of **credentials.json** file.
- You are **not** the bot **owner** if you invited the bot using **Carbonitex** or other invitation links.
###Music on the public Nadeko
- In case you got Nadeko in your server by the invitation from **Carbonitex**, our **GitHub** invite or **help (-h)**, music is disabled.
- Music is **disabled** due to large maintenance expenses, unless Kwoth is **testing** music module.
- If you want to have music module on your server, you will have to **host** the bot on your PC, or any of the external servers.
- How to **host** the bot, check the **guides** on the left side.
###NadekoFlowers
- NadekoFlowers is the **currency** of the public Nadeko.
- NadekoFlowers can be `>pick`ed after Nadeko plants a flower randomly after `>gc` has been used on a channel
- You can give NadekoFlowers to other users, using the command `$give X @person`.
- You can only give flowers you **own**.
- If you want to have **unlimited** number of flowers, you will have to **host** the bot.
- Commands `$award X @person` and `$take X @person` can only be used by the *bot owner*.
- If you `>plant` the flower, flower will be avaliable for everyone to `>pick` it. In that case you will **lose** the flower.
###Manage Permissions
**These permissions refer to the permissions you can set in Discord settings for individual users or roles.**

13
docs/about.md Normal file
View File

@ -0,0 +1,13 @@
## Terms of Use
The MIT License (MIT)
Copyright (c) 2016 NadekoBot Team
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,363 +0,0 @@
######For more information and how to setup your own NadekoBot, go to: **http://github.com/Kwoth/NadekoBot/**
######You can donate on patreon: `https://patreon.com/nadekobot`
######or paypal: `nadekodiscordbot@gmail.com`
#NadekoBot List Of Commands
Version: `NadekoBot v0.9.6054.4837`
### Help
Command and aliases | Description | Usage
----------------|--------------|-------
`-h`, `-help`, `@BotName help`, `@BotName h`, `~h` | Either shows a help for a single command, or PMs you help link if no arguments are specified. | `-h !m q` or just `-h`
`-hgit` | Generates the commandlist.md file. **Bot Owner Only!** | `-hgit`
`-readme`, `-guide` | Sends a readme and a guide links to the channel. | `-readme` or `-guide`
`-donate`, `~donate` | Instructions for helping the project! | `{Prefix}donate` or `~donate`
`-modules`, `.modules` | List all bot modules. | `{Prefix}modules` or `.modules`
`-commands`, `.commands` | List all of the bot's commands from a certain module. | `{Prefix}commands` or `.commands`
### Administration
Command and aliases | Description | Usage
----------------|--------------|-------
`.grdel` | Toggles automatic deletion of greet and bye messages. | `.grdel`
`.greet` | Toggles anouncements on the current channel when someone joins the server. | `.greet`
`.greetmsg` | Sets a new join announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. | `.greetmsg Welcome to the server, %user%.`
`.bye` | Toggles anouncements on the current channel when someone leaves the server. | `.bye`
`.byemsg` | Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message. | `.byemsg %user% has left the server.`
`.byepm` | Toggles whether the good bye messages will be sent in a PM or in the text channel. | `.byepm`
`.greetpm` | Toggles whether the greet messages will be sent in a PM or in the text channel. | `.greetpm`
`.spmom` | Toggles whether mentions of other offline users on your server will send a pm to them. | `.spmom`
`.logserver` | Toggles logging in this channel. Logs every message sent/deleted/edited on the server. **Bot Owner Only!** | `.logserver`
`.logignore` | Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. | `.logignore`
`.userpresence` | Starts logging to this channel when someone from the server goes online/offline/idle. | `.userpresence`
`.voicepresence` | Toggles logging to this channel whenever someone joins or leaves a voice channel you are in right now. | `{Prefix}voicerpresence`
`.repeatinvoke`, `.repinv` | Immediately shows the repeat message and restarts the timer. | `{Prefix}repinv`
`.repeat` | Repeat a message every X minutes. If no parameters are specified, repeat is disabled. Requires manage messages. | `.repeat 5 Hello there`
`.rotateplaying`, `.ropl` | Toggles rotation of playing status of the dynamic strings you specified earlier. | `.ropl`
`.addplaying`, `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %trivia% | `.adpl`
`.listplaying`, `.lipl` | Lists all playing statuses with their corresponding number. | `.lipl`
`.removeplaying`, `.repl`, `.rmpl` | Removes a playing string on a given number. | `.rmpl`
`.slowmode` | Toggles slow mode. When ON, users will be able to send only 1 message every 5 seconds. | `.slowmode`
`.cleanv+t`, `.cv+t` | Deletes all text channels ending in `-voice` for which voicechannels are not found. **Use at your own risk.** | `.cleanv+t`
`.voice+text`, `.v+t` | Creates a text channel for each voice channel only users in that voice channel can see.If you are server owner, keep in mind you will see them all the time regardless. | `.voice+text`
`.scsc` | Starts an instance of cross server channel. You will get a token as a DM that other people will use to tune in to the same instance. | `.scsc`
`.jcsc` | Joins current channel to an instance of cross server channel using the token. | `.jcsc`
`.lcsc` | Leaves Cross server channel instance from this channel. | `.lcsc`
`.asar` | Adds a role, or list of roles separated by whitespace(use quotations for multiword roles) to the list of self-assignable roles. | .asar Gamer
`.rsar` | Removes a specified role from the list of self-assignable roles. | `.rsar`
`.lsar` | Lists all self-assignable roles. | `.lsar`
`.togglexclsar`, `.tesar` | toggle whether the self-assigned roles should be exclusive | `.tesar`
`.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | .iam Gamer
`.iamnot`, `.iamn` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | .iamn Gamer
`.addcustreact`, `.acr` | Add a custom reaction. Guide here: <https://github.com/Kwoth/NadekoBot/wiki/Custom-Reactions> **Bot Owner Only!** | `.acr "hello" I love saying hello to %user%`
`.listcustreact`, `.lcr` | Lists custom reactions (paginated with 30 commands per page). Use 'all' instead of page number to get all custom reactions DM-ed to you. | `.lcr 1`
`.showcustreact`, `.scr` | Shows all possible responses from a single custom reaction. | `.scr %mention% bb`
`.editcustreact`, `.ecr` | Edits a custom reaction, arguments are custom reactions name, index to change, and a (multiword) message **Bot Owner Only** | `.ecr "%mention% disguise" 2 Test 123`
`.delcustreact`, `.dcr` | Deletes a custom reaction with given name (and index). | `.dcr index`
`.autoassignrole`, `.aar` | Automaticaly assigns a specified role to every user who joins the server. Type `.aar` to disable, `.aar Role Name` to enable
`.leave` | Makes Nadeko leave the server. Either name or id required. | `.leave 123123123331`
`.listincidents`, `.lin` | List all UNREAD incidents and flags them as read. | `.lin`
`.listallincidents`, `.lain` | Sends you a file containing all incidents and flags them as read. | `.lain`
`.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. Server Manager Only. | `.delmsgoncmd`
`.restart` | Restarts the bot. Might not work. **Bot Owner Only** | `.restart`
`.setrole`, `.sr` | Sets a role for a given user. | `.sr @User Guest`
`.removerole`, `.rr` | Removes a role from a given user. | `.rr @User Admin`
`.renamerole`, `.renr` | Renames a role. Role you are renaming must be lower than bot's highest role. | `.renr "First role" SecondRole`
`.removeallroles`, `.rar` | Removes all roles from a mentioned user. | `.rar @User`
`.createrole`, `.cr` | Creates a role with a given name. | `.cr Awesome Role`
`.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. | `.rc Admin 255 200 100` or `.rc Admin ffba55`
`.ban`, `.b` | Bans a user by id or name with an optional message. | `.b "@some Guy" Your behaviour is toxic.`
`.softban`, `.sb` | Bans and then unbans a user by id or name with an optional message. | `.sb "@some Guy" Your behaviour is toxic.`
`.kick`, `.k` | Kicks a mentioned user. | `.k "@some Guy" Your behaviour is toxic.`
`.mute` | Mutes mentioned user or users. | `.mute "@Someguy"` or `.mute "@Someguy" "@Someguy"`
`.unmute` | Unmutes mentioned user or users. | `.unmute "@Someguy"` or `.unmute "@Someguy" "@Someguy"`
`.deafen`, `.deaf` | Deafens mentioned user or users | `.deaf "@Someguy"` or `.deaf "@Someguy" "@Someguy"`
`.undeafen`, `.undef` | Undeafens mentioned user or users | `.undef "@Someguy"` or `.undef "@Someguy" "@Someguy"`
`.delvoichanl`, `.dvch` | Deletes a voice channel with a given name. | `.dvch VoiceChannelName`
`.creatvoichanl`, `.cvch` | Creates a new voice channel with a given name. | `.cvch VoiceChannelName`
`.deltxtchanl`, `.dtch` | Deletes a text channel with a given name. | `.dtch TextChannelName`
`.creatxtchanl`, `.ctch` | Creates a new text channel with a given name. | `.ctch TextChannelName`
`.settopic`, `.st` | Sets a topic on the current channel. | `.st My new topic`
`.setchanlname`, `.schn` | Changed the name of the current channel.| `.schn NewName`
`.heap` | Shows allocated memory - **Bot Owner Only!** | `.heap`
`.prune`, `.clr` | `.prune` removes all nadeko's messages in the last 100 messages.`.prune X` removes last X messages from the channel (up to 100)`.prune @Someone` removes all Someone's messages in the last 100 messages.`.prune @Someone X` removes last X 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X`
`.die` | Shuts the bot down and notifies users about the restart. **Bot Owner Only!** | `.die`
`.setname`, `.newnm` | Give the bot a new name. **Bot Owner Only!** | .newnm BotName
`.newavatar`, `.setavatar` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner Only!** | `.setavatar https://i.ytimg.com/vi/WDudkR1eTMM/maxresdefault.jpg`
`.setgame` | Sets the bots game. **Bot Owner Only!** | `.setgame Playing with kwoth`
`.send` | Send a message to someone on a different server through the bot. **Bot Owner Only!** | `.send serverid|u:user_id Send this to a user!` or `.send serverid|c:channel_id Send this to a channel!`
`.mentionrole`, `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. | `.menro RoleName`
`.unstuck` | Clears the message queue. **Bot Owner Only!** | `.unstuck`
`.donators` | List of lovely people who donated to keep this project alive.
`.donadd` | Add a donator to the database. | `.donadd Donate Amount`
`.announce` | Sends a message to all servers' general channel bot is connected to.**Bot Owner Only!** | `.announce Useless spam`
`.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.savechat 150`
### Utility
Command and aliases | Description | Usage
----------------|--------------|-------
`.remind` | Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message. | `.remind me 1d5h Do something` or `.remind #general Start now!`
`.remindmsg` | Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind. **Bot Owner Only!** | `.remindmsg do something else`
`.serverinfo`, `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | `.sinfo Some Server`
`.channelinfo`, `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | `.cinfo #some-channel`
`.userinfo`, `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | `.uinfo @SomeUser`
`.whoplays` | Shows a list of users who are playing the specified game. | `.whoplays Overwatch`
`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. If the list is too long for 1 message, you must have Manage Messages permission. | `.inrole Role`
`.checkmyperms` | Checks your userspecific permissions on this channel. | `.checkmyperms`
`.stats` | Shows some basic stats for Nadeko. | `.stats`
`.dysyd` | Shows some basic stats for Nadeko. | `.dysyd`
`.userid`, `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy"`
`.channelid`, `.cid` | Shows current channel ID. | `.cid`
`.serverid`, `.sid` | Shows current server ID. | `.sid`
`.roles` | List all roles on this server or a single user if specified.
`.channeltopic`, `.ct` | Sends current channel's topic as a message. | `.ct`
### Permissions
Command and aliases | Description | Usage
----------------|--------------|-------
`;chnlfilterinv`, `;cfi` | Enables or disables automatic deleting of invites on the channel.If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once. | ;cfi enable #general-chat
`;srvrfilterinv`, `;sfi` | Enables or disables automatic deleting of invites on the server. | ;sfi disable
`;chnlfilterwords`, `;cfw` | Enables or disables automatic deleting of messages containing banned words on the channel.If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once. | ;cfw enable #general-chat
`;addfilterword`, `;afw` | Adds a new word to the list of filtered words | ;afw poop
`;rmvfilterword`, `;rfw` | Removes the word from the list of filtered words | ;rw poop
`;lstfilterwords`, `;lfw` | Shows a list of filtered words | ;lfw
`;srvrfilterwords`, `;sfw` | Enables or disables automatic deleting of messages containing forbidden words on the server. | ;sfw disable
`;permrole`, `;pr` | Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'. | `;pr role`
`;rolepermscopy`, `;rpc` | Copies BOT PERMISSIONS (not discord permissions) from one role to another. | `;rpc Some Role ~ Some other role`
`;chnlpermscopy`, `;cpc` | Copies BOT PERMISSIONS (not discord permissions) from one channel to another. | `;cpc Some Channel ~ Some other channel`
`;usrpermscopy`, `;upc` | Copies BOT PERMISSIONS (not discord permissions) from one role to another. | `;upc @SomeUser ~ @SomeOtherUser`
`;verbose`, `;v` | Sets whether to show when a command/module is blocked. | `;verbose true`
`;srvrperms`, `;sp` | Shows banned permissions for this server. | `;sp`
`;roleperms`, `;rp` | Shows banned permissions for a certain role. No argument means for everyone. | `;rp AwesomeRole`
`;chnlperms`, `;cp` | Shows banned permissions for a certain channel. No argument means for this channel. | `;cp #dev`
`;userperms`, `;up` | Shows banned permissions for a certain user. No argument means for yourself. | `;up Kwoth`
`;srvrmdl`, `;sm` | Sets a module's permission at the server level. | `;sm "module name" enable`
`;srvrcmd`, `;sc` | Sets a command's permission at the server level. | `;sc "command name" disable`
`;rolemdl`, `;rm` | Sets a module's permission at the role level. | `;rm "module name" enable MyRole`
`;rolecmd`, `;rc` | Sets a command's permission at the role level. | `;rc "command name" disable MyRole`
`;chnlmdl`, `;cm` | Sets a module's permission at the channel level. | `;cm "module name" enable SomeChannel`
`;chnlcmd`, `;cc` | Sets a command's permission at the channel level. | `;cc "command name" enable SomeChannel`
`;usrmdl`, `;um` | Sets a module's permission at the user level. | `;um "module name" enable SomeUsername`
`;usrcmd`, `;uc` | Sets a command's permission at the user level. | `;uc "command name" enable SomeUsername`
`;allsrvrmdls`, `;asm` | Sets permissions for all modules at the server level. | `;asm [enable/disable]`
`;allsrvrcmds`, `;asc` | Sets permissions for all commands from a certain module at the server level. | `;asc "module name" [enable/disable]`
`;allchnlmdls`, `;acm` | Sets permissions for all modules at the channel level. | `;acm [enable/disable] SomeChannel`
`;allchnlcmds`, `;acc` | Sets permissions for all commands from a certain module at the channel level. | `;acc "module name" [enable/disable] SomeChannel`
`;allrolemdls`, `;arm` | Sets permissions for all modules at the role level. | `;arm [enable/disable] MyRole`
`;allrolecmds`, `;arc` | Sets permissions for all commands from a certain module at the role level. | `;arc "module name" [enable/disable] MyRole`
`;ubl` | Blacklists a mentioned user. | `;ubl [user_mention]`
`;uubl` | Unblacklists a mentioned user. | `;uubl [user_mention]`
`;cbl` | Blacklists a mentioned channel (#general for example). | `;cbl #some_channel`
`;cubl` | Unblacklists a mentioned channel (#general for example). | `;cubl #some_channel`
`;sbl` | Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY** | `;sbl [servername/serverid]`
`;cmdcooldown`, `;cmdcd` | Sets a cooldown per user for a command. Set 0 to clear. | `;cmdcd "some cmd" 5`
`;allcmdcooldowns`, `;acmdcds` | Shows a list of all commands and their respective cooldowns.
### Conversations
Command and aliases | Description | Usage
----------------|--------------|-------
`..` | Adds a new quote with the specified name (single word) and message (no limit). | `.. abc My message`
`...` | Shows a random quote with a specified name. | `... abc`
`..qdel`, `..quotedelete` | Deletes all quotes with the specified keyword. You have to either be bot owner or the creator of the quote to delete it. | `..qdel abc`
`@BotName rip` | Shows a grave image of someone with a start year | @NadekoBot rip @Someone 2000
`@BotName die` | Works only for the owner. Shuts the bot down. | `@NadekoBot die`
`@BotName do you love me` | Replies with positive answer only to the bot owner. | `@NadekoBot do you love me`
`@BotName how are you`, `@BotName how are you?` | Replies positive only if bot owner is online. | `@NadekoBot how are you`
`@BotName fire` | Shows a unicode fire message. Optional parameter [x] tells her how many times to repeat the fire. | `@NadekoBot fire [x]`
`@BotName dump` | Dumps all of the invites it can to dump.txt.** Owner Only.** | `@NadekoBot dump`
`@BotName ab` | Try to get 'abalabahaha'| `@NadekoBot ab`
### Gambling
Command and aliases | Description | Usage
----------------|--------------|-------
`$draw` | Draws a card from the deck.If you supply number [x], she draws up to 5 cards from the deck. | `$draw [x]`
`$shuffle`, `$sh` | Reshuffles all cards back into the deck.|`$shuffle`
`$flip` | Flips coin(s) - heads or tails, and shows an image. | `$flip` or `$flip 3`
`$betflip`, `$bf` | Bet to guess will the result be heads or tails. Guessing award you double flowers you've bet. | `$bf 5 heads` or `$bf 3 t`
`$roll` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | `$roll` or `$roll 7` or `$roll 3d5`
`$rolluo` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice (unordered). If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | `$roll` or `$roll` 7 or `$roll 3d5`
`$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15`
`$race` | Starts a new animal race. | `$race`
`$joinrace`, `$jr` | Joins a new race. You can specify an amount of flowers for betting (optional). You will get YourBet*(participants-1) back if you win. | `$jr` or `$jr 5`
`$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. | `$raffle` or `$raffle RoleName`
`$$$` | Check how much NadekoFlowers a person has. (Defaults to yourself) | `$$$` or `$$$ @Someone`
`$give` | Give someone a certain amount of NadekoFlowers|`$give 1 "@SomeGuy"`
`$award` | Gives someone a certain amount of flowers. **Bot Owner Only!** | `$award 100 @person`
`$take` | Takes a certain amount of flowers from someone. **Bot Owner Only!** | `$take 1 "@someguy"`
`$betroll`, `$br` | Bets a certain amount of NadekoFlowers and rolls a dice. Rolling over 66 yields x2 flowers, over 90 - x3 and 100 x10. | `$br 5`
`$leaderboard`, `$lb` | Displays bot currency leaderboard | $lb
### Games
Command and aliases | Description | Usage
----------------|--------------|-------
`>t` | Starts a game of trivia. You can add nohint to prevent hints.First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question. | `>t nohint` or `>t 5 nohint`
`>tl` | Shows a current trivia leaderboard. | `>tl`
`>tq` | Quits current trivia after current question. | `>tq`
`>typestart` | Starts a typing contest. | `>typestart`
`>typestop` | Stops a typing contest on the current channel. | `>typestop`
`>typeadd` | Adds a new article to the typing contest. Owner only. | `>typeadd wordswords`
`>poll` | Creates a poll, only person who has manage server permission can do it. | `>poll Question?;Answer1;Answ 2;A_3`
`>pollend` | Stops active poll on this server and prints the results in this channel. | `>pollend`
`>pick` | Picks a flower planted in this channel. | `>pick`
`>plant` | Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost) | `>plant`
`>gencurrency`, `>gc` | Toggles currency generation on this channel. Every posted message will have 2% chance to spawn a NadekoFlower. Optional parameter cooldown time in minutes, 5 minutes by default. Requires Manage Messages permission. | `>gc` or `>gc 60`
`>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | `>leet 3 Hello`
`>choose` | Chooses a thing from a list of things | `>choose Get up;Sleep;Sleep more`
`>8ball` | Ask the 8ball a yes/no question. | `>8ball should i do something`
`>rps` | Play a game of rocket paperclip scissors with Nadeko. | `>rps scissors`
`>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows`
### Music
Command and aliases | Description | Usage
----------------|--------------|-------
`!!next`, `!!n`, `!!skip` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `!!n`
`!!stop`, `!!s` | Stops the music and clears the playlist. Stays in the channel. | `!!s`
`!!destroy`, `!!d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!!d`
`!!pause`, `!!p` | Pauses or Unpauses the song. | `!!p`
`!!queue`, `!!q`, `!!yq` | Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**. | `!!q Dream Of Venice`
`!!soundcloudqueue`, `!!sq` | Queue a soundcloud song using keywords. Bot will join your voice channel.**You must be in a voice channel**. | `!!sq Dream Of Venice`
`!!listqueue`, `!!lq` | Lists 15 currently queued songs per page. Default page is 1. | `!!lq` or `!!lq 2`
`!!nowplaying`, `!!np` | Shows the song currently playing. | `!!np`
`!!volume`, `!!vol` | Sets the music volume 0-100% | `!!vol 50`
`!!defvol`, `!!dv` | Sets the default music volume when music playback is started (0-100). Persists through restarts. | `!!dv 80`
`!!mute`, `!!min` | Sets the music volume to 0% | `!!min`
`!!max` | Sets the music volume to 100%. | `!!max`
`!!half` | Sets the music volume to 50%. | `!!half`
`!!shuffle`, `!!sh` | Shuffles the current playlist. | `!!sh`
`!!playlist`, `!!pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `!!pl playlist link or name`
`!!soundcloudpl`, `!!scpl` | Queue a soundcloud playlist using a link. | `!!scpl https://soundcloud.com/saratology/sets/symphony`
`!!localplaylst`, `!!lopl` | Queues all songs from a directory. **Bot Owner Only!** | `!!lopl C:/music/classical`
`!!radio`, `!!ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: <https://streamable.com/al54>) | `!!ra radio link here`
`!!local`, `!!lo` | Queues a local file by specifying a full path. **Bot Owner Only!** | `!!lo C:/music/mysong.mp3`
`!!move`, `!!mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!!mv`
`!!remove`, `!!rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!!rm 5`
`!!movesong`, `!!ms` | Moves a song from one position to another. | `!! ms` 5>3
`!!setmaxqueue`, `!!smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `!!smq` 50 or `!!smq`
`!!cleanup` | Cleans up hanging voice connections. **Bot Owner Only!** | `!!cleanup`
`!!reptcursong`, `!!rcs` | Toggles repeat of current song. | `!!rcs`
`!!rpeatplaylst`, `!!rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!!rpl`
`!!save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!!save classical1`
`!!load` | Loads a playlist under a certain name. | `!!load classical-1`
`!!playlists`, `!!pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!!pls 1`
`!!deleteplaylist`, `!!delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!!delpls animu-5`
`!!goto` | Goes to a specific time in seconds in a song. | `!!goto 30`
`!!getlink`, `!!gl` | Shows a link to the currently playing song.
`!!autoplay`, `!!ap` | Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty)
### Searches
Command and aliases | Description | Usage
----------------|--------------|-------
`~lolchamp` | Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role. | `~lolchamp Riven` or `~lolchamp Annie sup`
`~lolban` | Shows top 6 banned champions ordered by ban rate. Ban these champions and you will be Plat 5 in no time. | `~lolban`
`~hitbox`, `~hb` | Notifies this channel when a certain user starts streaming. | `~hitbox SomeStreamer`
`~twitch`, `~tw` | Notifies this channel when a certain user starts streaming. | `~twitch SomeStreamer`
`~beam`, `~bm` | Notifies this channel when a certain user starts streaming. | `~beam SomeStreamer`
`~checkhitbox`, `~chhb` | Checks if a certain user is streaming on the hitbox platform. | `~chhb SomeStreamer`
`~checktwitch`, `~chtw` | Checks if a certain user is streaming on the twitch platform. | `~chtw SomeStreamer`
`~checkbeam`, `~chbm` | Checks if a certain user is streaming on the beam platform. | `~chbm SomeStreamer`
`~removestream`, `~rms` | Removes notifications of a certain streamer on this channel. | `~rms SomeGuy`
`~liststreams`, `~ls` | Lists all streams you are following on this server. | `~ls`
`~convert` | Convert quantities from>to. | `~convert m>km 1000`
`~convertlist` | List of the convertable dimensions and currencies.
`~wowjoke` | Get one of Kwoth's penultimate WoW jokes. | `~wowjoke`
`~calculate`, `~calc` | Evaluate a mathematical expression. | ~calc 1+1
`~osu` | Shows osu stats for a player. | `~osu Name` or `~osu Name taiko`
`~osu b` | Shows information about an osu beatmap. | `~osu b` https://osu.ppy.sh/s/127712`
`~osu top5` | Displays a user's top 5 plays. | ~osu top5 Name
`~pokemon`, `~poke` | Searches for a pokemon. | `~poke Sylveon`
`~pokemonability`, `~pokeab` | Searches for a pokemon ability. | `~pokeab "water gun"`
`~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ | `~memelist`
`~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"`
`~we` | Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | `~we Moscow RF`
`~yt` | Searches youtubes and shows the first result | `~yt query`
`~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. | `~aq aquerion evol`
`~imdb` | Queries imdb for movies or series, show first result. | `~imdb query`
`~mang`, `~manga`, `~mq` | Queries anilist for a manga and shows the first result. | `~mq query`
`~randomcat`, `~meow` | Shows a random cat image.
`~randomdog`, `~woof` | Shows a random dog image.
`~i` | Pulls the first image found using a search parameter. Use ~ir for different results. | `~i cute kitten`
`~ir` | Pulls a random image using a search parameter. | `~ir cute kitten`
`~lmgtfy` | Google something for an idiot. | `~lmgtfy query`
`~google`, `~g` | Get a google search link for some terms. | `~google query`
`~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | `~hs Ysera`
`~ud` | Searches Urban Dictionary for a word. | `~ud Pineapple`
`~#` | Searches Tagdef.com for a hashtag. | `~# ff`
`~quote` | Shows a random quote. | `~quote`
`~catfact` | Shows a random catfact from <http://catfacts-api.appspot.com/api/facts> | `~catfact`
`~yomama`, `~ym` | Shows a random joke from <http://api.yomomma.info/> | `~ym`
`~randjoke`, `~rj` | Shows a random joke from <http://tambal.azurewebsites.net/joke/random> | `~rj`
`~chucknorris`, `~cn` | Shows a random chucknorris joke from <http://tambal.azurewebsites.net/joke/random> | `~cn`
`~magicitem`, `~mi` | Shows a random magicitem from <https://1d4chan.org/wiki/List_of_/tg/%27s_magic_items> | `~mi`
`~revav` | Returns a google reverse image search for someone's avatar. | `~revav "@SomeGuy"`
`~revimg` | Returns a google reverse image search for an image from a link. | `~revav Image link`
`~safebooru` | Shows a random image from safebooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~safebooru yuri+kissing`
`~wiki` | Gives you back a wikipedia link | `~wiki query`
`~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00`
`~videocall` | Creates a private <http://www.appear.in> video call link for you and other mentioned people. The link is sent to mentioned people via a private message. | `~videocall "@SomeGuy"`
`~av`, `~avatar` | Shows a mentioned person's avatar. | `~av @X`
### NSFW
Command and aliases | Description | Usage
----------------|--------------|-------
`~hentai` | Shows a random NSFW hentai image from gelbooru and danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~hentai yuri+kissing`
`~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~danbooru yuri+kissing`
`~gelbooru` | Shows a random hentai image from gelbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~gelbooru yuri+kissing`
`~rule34` | Shows a random image from rule34.xx with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~rule34 yuri+kissing`
`~e621` | Shows a random hentai image from e621.net with a given tag. Tag is optional but preffered. Use spaces for multiple tags. | `~e621 yuri kissing`
`~cp` | We all know where this will lead you to. | `~cp`
`~boobs` | Real adult content. | `~boobs`
`~butts`, `~ass`, `~butt` | Real adult content. | `~butts` or `~ass`
### ClashOfClans
Command and aliases | Description | Usage
----------------|--------------|-------
`,createwar`, `,cw` | Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. | ,cw 15 The Enemy Clan
`,startwar`, `,sw` | Starts a war with a given number.
`,listwar`, `,lw` | Shows the active war claims by a number. Shows all wars in a short way if no number is specified. | ,lw [war_number] or ,lw
`,claim`, `,call`, `,c` | Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. | ,call [war_number] [base_number] [optional_other_name]
`,claimfinish`, `,cf`, `,cf3`, `,claimfinish3` | Finish your claim with 3 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name]
`,claimfinish2`, `,cf2` | Finish your claim with 2 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name]
`,claimfinish1`, `,cf1` | Finish your claim with 1 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name]
`,unclaim`, `,uncall`, `,uc` | Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim | ,uc [war_number] [optional_other_name]
`,endwar`, `,ew` | Ends the war with a given index. | ,ew [war_number]
### Pokegame
Command and aliases | Description | Usage
----------------|--------------|-------
`>attack` | Attacks a target with the given move. Use `>movelist` to see a list of moves your type can use. | `>attack "vine whip" @someguy`
`>movelist`, `>ml` | Lists the moves you are able to use | `>ml`
`>heal` | Heals someone. Revives those who fainted. Costs a NadekoFlower | `>heal @someone`
`>type` | Get the poketype of the target. | `>type @someone`
`>settype` | Set your poketype. Costs a NadekoFlower. | `>settype fire`
### Translator
Command and aliases | Description | Usage
----------------|--------------|-------
`~translate`, `~trans` | Translates from>to text. From the given language to the destiation language. | `~trans en>fr Hello`
`~translangs` | List the valid languages for translation. | `{Prefix}translangs` or `{Prefix}translangs language`
### Customreactions
Command and aliases | Description | Usage
----------------|--------------|-------
`\o\` | Custom reaction. | \o\
`/o/` | Custom reaction. | /o/
`moveto` | Custom reaction. | moveto
`comeatmebro` | Custom reaction. | comeatmebro
`e` | Custom reaction. | e
`@BotName insult`, `<@!116275390695079945> insult` | Custom reaction. | %mention% insult
`@BotName praise`, `<@!116275390695079945> praise` | Custom reaction. | %mention% praise
`@BotName pat`, `<@!116275390695079945> pat` | Custom reaction. | %mention% pat
`@BotName cry`, `<@!116275390695079945> cry` | Custom reaction. | %mention% cry
`@BotName are you real?`, `<@!116275390695079945> are you real?` | Custom reaction. | %mention% are you real?
`@BotName are you there?`, `<@!116275390695079945> are you there?` | Custom reaction. | %mention% are you there?
`@BotName draw`, `<@!116275390695079945> draw` | Custom reaction. | %mention% draw
`@BotName bb`, `<@!116275390695079945> bb` | Custom reaction. | %mention% bb
`@BotName call`, `<@!116275390695079945> call` | Custom reaction. | %mention% call
`@BotName disguise`, `<@!116275390695079945> disguise` | Custom reaction. | %mention% disguise
`~hentai` | Custom reaction. | ~hentai
### Trello
Command and aliases | Description | Usage
----------------|--------------|-------
`trello bind` | Bind a trello bot to a single channel. You will receive notifications from your board when something is added or edited. | `trello bind [board_id]`
`trello unbind` | Unbinds a bot from the channel and board.
`trello lists`, `trello list` | Lists all lists yo ;)
`trello cards` | Lists all cards from the supplied list. You can supply either a name or an index. | `trello cards index`

View File

@ -0,0 +1,8 @@
###Building from Source
For easy setup and no programming knowledge, you can use [Updater](https://github.com/Kwoth/NadekoUpdater/releases/latest) or download release from [Releases](https://github.com/Kwoth/NadekoBot/releases) and follow the [Windows Guide](Windows Guide.md)
In your bin/debug folder (or next to your exe if you are using release version), you must have a file called 'credentials.json' in which you will store all the necessary data to make the bot know who the owner is, and your api keys.
When you clone the project, make sure to run `git submodule init` and `git submodule update` to get the correct discord.net version
Make sure you've read ComprehensiveGuide to get a grasp of basic config/credentials setup and then look at "Credentials and config" chapter.

View File

@ -1,6 +1,6 @@
## Docker guide with digitalocean
# Docker Guide with DigitalOcean
#####Prerequisites:
#####Prerequisites
- Digital ocean account (you can use my [reflink][reflink] to support the project and get 10$ after you register)
- [PuTTY][PuTTY]
- A bot account - follow this [guide][guide]

View File

@ -1,9 +1,9 @@
#Setting up NadekoBot on Linux
##Setting up NadekoBot on Linux
####Setting up NadekoBot on Linux Digital Ocean Droplet
######If you want Nadeko to play music for you 24/7 without having to hosting it on your PC and want to keep it cheap, reliable and convenient as possible, you can try Nadeko on Linux Digital Ocean Droplet using the link [DigitalOcean][DigitalOcean] (and using this link will be supporting Nadeko and will give you **$10 credit**)
If you want Nadeko to play music for you 24/7 without having to hosting it on your PC and want to keep it cheap, reliable and convenient as possible, you can try Nadeko on Linux Digital Ocean Droplet using the link [DigitalOcean][DigitalOcean] (and using this link will be supporting Nadeko and will give you **$10 credit**)
######Keep this helpful video handy [Linux Setup Video][Linux Setup Video] (thanks to klincheR) it contains how to set up the Digital Ocean droplet aswell.
Keep this helpful video handy [Linux Setup Video][Linux Setup Video] (thanks to klincheR) it contains how to set up the Digital Ocean droplet aswell.
####Setting up NadekoBot
Assuming you have followed the link above to created an account in Digital Ocean and video to set up the bot until you get the `IP address and root password (in email)` to login, its time to begin.
@ -24,47 +24,52 @@ If you entered your Droplets IP address correctly, it should show **login as:**
**Copy and just paste** using **mouse right-click** (it should paste automatically)
######MONO (Source: [Mono Source][Mono Source])
####Installing Mono
MONO (Source: [Mono Source][Mono Source])
**1) Installing Mono**
**1)**
`sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF`
`echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list`
`sudo apt-get update`
Note if the command is not being initiated, hit **Enter**
**2)**
`echo "deb http://download.mono-project.com/repo/debian wheezy-apache24-compat main" | sudo tee -a /etc/apt/sources.list.d/mono-xamarin.list`
`echo "deb http://download.mono-project.com/repo/debian wheezy-apache24-compat main" | sudo
tee -a /etc/apt/sources.list.d/mono-xamarin.list`
####Mono on Debian 8 and later
**2.5)**
*ONLY DEBIAN 8 and later*
`echo "deb http://download.mono-project.com/repo/debian wheezy-libjpeg62-compat main" | sudo tee -a /etc/apt/sources.list.d/mono-xamarin.list`
`echo "deb http://download.mono-project.com/repo/debian wheezy-libjpeg62-compat main" | sudo
tee -a /etc/apt/sources.list.d/mono-xamarin.list`
####Mono on CentOS 7, Fedora 19 (and later) and later
**2.6)**
*ONLY CentOS 7, Fedora 19 (and later)*
`yum install yum-util`
`rpm --import "http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF"`
`yum-config-manager --add-repo http://download.mono-project.com/repo/centos/`
####Mono Devel
**3)**
*Mono Devel*
`apt-get install mono-devel`
**Type** `y` **hit Enter**
####Mono Fix
**In case you are having issues with Mono where you get a random string and the bot won't run, do this:**
`sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF`
`echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list`
`apt-get install ca-certificates-mono`
`mozroots --import --sync`
####Installing Opus Voice Codec
**4)**
Opus Voice Codec
`sudo apt-get install libopus0 opus-tools`
**Type** `y` **hit Enter**
@ -72,17 +77,6 @@ Opus Voice Codec
**5)**
`sudo apt-get install libopus-dev`
**In case you are having issues with Mono where you get a random string and the bot won't run, do this:**
`sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF`
`echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list`
`apt-get install ca-certificates-mono`
`mozroots --import --sync`
####FFMPEG
**6)**
@ -95,9 +89,7 @@ NOTE: if its "not installing" then, follow the guide here: [FFMPEG Help Guide][F
**All you need to do, if you are running UBUNTU 14.04 is initiate these:**
`sudo add-apt-repository ppa:mc3man/trusty-media`
`sudo apt-get update`
`sudo apt-get dist-upgrade`
*Before executing* `sudo apt-get install ffmpeg`
@ -109,11 +101,8 @@ NOTE: if its "not installing" then, follow the guide here: [FFMPEG Help Guide][F
In case you are not able to install it with installer ^up there, follow these steps:
`sudo apt-get update`
`echo "deb http://ftp.debian.org/debian jessie-backports main" | tee /etc/apt/sources.list.d/debian-backports.list`
`sudo apt-get update`
`sudo apt-get install ffmpeg -y`
####Uncomplicated Firewall UFW
@ -134,30 +123,25 @@ In case you are not able to install it with installer ^up there, follow these st
**10)**
`sudo ufw allow ssh`
####Installing Unzip
**11)**
Unzip
`apt-get install unzip`
####Installing TMUX
**12)**
TMUX
`apt-get install tmux`
**Type** `y` **hit Enter**
####NOW WE NEED TO IMPORT SOME DISCORD CERTS
####Importing Discord certs
**13)**
`certmgr -ssl https://discordapp.com`
**14)**
`certmgr -ssl https://gateway.discord.gg`
Type `yes` and hit Enter **(three times - as it will ask for three times)**
####Creating Nadeko folder
**15)**
Create a new folder “nadeko” or anything you prefer
@ -168,7 +152,7 @@ Move to “nadeko” folder (note `cd --` to go back the directory)
`cd nadeko`
**NOW WE NEED TO GET NADEKO FROM RELEASES**
####Getting NadekoBot from Releases
Go to this link: [Releases][Releases] and **copy the zip file address** of the lalest version available,
it should look like `https://github.com/Kwoth/NadekoBot/releases/download/vx.xx/NadekoBot.vx.x.zip`
@ -181,14 +165,13 @@ Get the correct link, type `wget`, then *paste the link*, then hit **Enter**.
**^Do not copy-paste it**
**18)**
Now we need to `unzip` the downloaded zip file and to do that, type the file name as it showed in your screen or just copy from the screen, should be like ` NadekoBot.vx.x.zip`
`unzip NadekoBot.vx.x.zip`
**^Do not copy-paste it**
#####NOW TO SETUP NADEKO
####Setting up NadekoBot
- Open **CyberDuck**
- Click on **Open Connection** (top-left corner), a new window should appear.
@ -201,15 +184,16 @@ Now we need to `unzip` the downloaded zip file and to do that, type the file nam
- It should show you the new folder you created.
- Open it.
#####MAKE SURE YOU READ THE README BEFORE PROCEEDING
####Renaming Credentials.json
- Copy the `credentials_example.json` to desktop
- EDIT it as it is guided here: [Readme][Readme]
- EDIT it as it is guided here: [Setting up Credentials.json](Windows Guide.md#setting-up-credentialsjson-file)
- Read here how to [Create DiscordBot application](https://github.com/miraai/NadekoBot/blob/dev/docs/guides/Windows%20Guide.md#creating-discordbot-application)
- Rename it to `credentials.json` and paste/put it back in the folder. `(Yes, using CyberDuck)`
- You should see two files `credentials_example.json` and `credentials.json`
- Also if you already have nadeko setup and have `credentials.json`, `config.json`, `nadekobot.sqlite`, and `"permissions" folder`, you can just copy and paste it to the Droplets folder using CyberDuck.
######TIME TO RUN
####Running NadekoBot
Go back to **PuTTY**, `(hope its still running xD)`
@ -218,7 +202,8 @@ Type/ Copy and hit **Enter**.
`tmux new -s nadeko`
**^this will create a new session named “nadeko”** `(you can replace “nadeko” with anything you prefer and remember its your session name) so you can run the bot in background without having to keep running PuTTY in the background.`
**^this will create a new session named “nadeko”** `(you can replace “nadeko” with anything you prefer and remember
its your session name) so you can run the bot in background without having to keep running PuTTY in the background.`
`cd nadeko`
@ -227,33 +212,33 @@ Type/ Copy and hit **Enter**.
**CHECK THE BOT IN DISCORD, IF EVERYTHING IS WORKING**
Now time to **move bot to background** and to do that, press **CTRL+B+D** (this will ditach the nadeko session using TMUX), and you can finally close PuTTY now.
####Setting up Nadeko Music
For how to set up Nadeko for music and Google API Keys, follow [Setting up NadekoBot for Music](Windows Guide.md#setting-up-nadekobot-for-music)
Now time to **move bot to background** and to do that, press **CTRL+B+D** (this will detach the nadeko session using TMUX), and you can finally close PuTTY now.
Copy your CLIENT ID (that's in the same Developer page where you brought your token) and replace `12345678` in this link: `https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303` with it. Go to that link and you will be able to add your bot to your server.
**NOW YOU HAVE YOUR OWN NADEKO BOT** `Thanks to Kwoth <3`
######SOME MORE INFO (JUST TO KNOW):
####Some more Info (just in case)
-If you want to **see the sessions** after logging back again, type `tmux ls`, and that will give you the list of sessions running.
-If you want to **switch to/ see that session**, type `tmux a -t nadeko` (**nadeko** is the name of the session we created before so, replace **“nadeko”** with the session name you created.)
**21)**
-If you want to **kill** NadekoBot **session**, type `tmux kill-session -t nadeko`
######TO RESTART YOUR BOT ALONG WITH THE WHOLE SERVER (for science):
**22)**
####Restarting Nadeko with the Server
Open **PuTTY** and login as you have before, type `reboot` and hit Enter.
######IF YOU WANT TO UPDATE YOUR BOT
####Updating Nadeko
**FOLLOW THESE STEPS SERIALLY**
- **-21 OR 22**
- **-19**
- **-16**
- **-17**
- **-18**
- **-19**
- **-20**
HIT **CTRL+B+D** and close **PuTTY**

167
docs/guides/OSX Guide.md Normal file
View File

@ -0,0 +1,167 @@
### Setting Up NadekoBot on OSX
#### Prerequisites
- 1) [Homebrew][Homebrew]
- 2) Mono
- 3) Google Account
- 4) Soundcloud Account (if you want soundcloud support)
- 5) Text Editor (TextWrangler, or equivalent) or outside editor such as [Atom][Atom]
####Installing Homebrew
`/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
Run `brew update` to fetch the latest package data.
####Installing dependencies
```
brew install git
brew install ffmpeg
brew update && brew upgrade ffmpeg
brew install opus
brew install opus-tools
brew install opusfile
brew install libffi
brew install libsodium
brew install tmux
```
####Check your `FFMPEG`
**In case your `FFMPEG` wasnt installed properly**
- `brew options ffmpeg`
- `brew install ffmpeg --with-x --with-y --with-z` etc.
- `brew update && brew upgrade` (Update formulae and Homebrew itself && Install newer versions of outdated packages)
- `brew prune` (Remove dead symlinks from Homebrews prefix)
- `brew doctor` (Check your Homebrew installation for common issues)
- Then try `brew install ffmpeg` again.
####Installing xcode-select
Xcode command line tools. You will do this in Terminal.app by running the following command line:
`xcode-select --install`
A dialog box will open asking if you want to install `xcode-select`. Select install and finish the installation.
####Installing Mono
- Building Mono dependencies:
`brew install autoconf automake libtool pkg-config`
- Building Mono from Source:
To build Mono from a Git Source Code checkout, you will want to have the official Mono installed on the system, as the build requires a working C# compiler to run. Once you do this, run the following commands, remember to replace PREFIX with your installation prefix that you selected:
```
PATH=$PREFIX/bin:$PATH
git clone https://github.com/mono/mono.git
cd mono
CC='cc -m32' ./autogen.sh --prefix=$PREFIX --disable-nls --build=i386-apple-darwin11.2.0
make
make install
```
To build Mono in 64 bit mode instead use this to configure the build:
`./autogen.sh --prefix=$PREFIX --disable-nls`
####Nadeko Setup
- Create a new folder and name it `Nadeko`.
- Move to our `Nadeko` folder
`cd Nadeko`
- Go to [Releases][Releases] and copy the zip file address of the lalest version available, it should look like `https://github.com/Kwoth/NadekoBot/releases/download/vx.xx/NadekoBot.vx.x.zip`
- Get the correct link, type `curl -O` and past the link, then hit `Enter`
- It should be something like this:
`curl -O https://github.com/Kwoth/NadekoBot/releases/download/vx.xx/NadekoBot.vx.x.zip`
^ do not copy-paste it
- Unzip the downloaded file in our `Nadeko` folder
####Creating DiscordBot application
- Go to [DiscordApp][DiscordApp].
- Log in with your Discord account.
- On the left side, press `New Application`.
- Fill out the `App Name` (your bot's name, in this case), put the image you want, and add an app description(optional).
- Create the application.
- Once the application is created, click on `Create a Bot User` and confirm it.
- Keep this window open for now.
####Setting up Credentials.json file
- In our `NadekoBot` folder you should have `.json` file named `credentials_example.json`. (Note: If you do not see a **.json** after `credentials_example.json `, do not add the `**.json**`. You most likely have `"Hide file extensions"` enabled.)
- Rename `credentials_example.json` to `credentials.json`.
- Open the file with your Text editor.
- In there you will see fields like `Token`, `ClientId`, `BotId`, `OwnerIDs`.
- In your [DiscordApp][DiscordApp], under `Bot User` part, you will see the `Token:click to reveal` part, click to reveal it.
- Copy your bot's token, and put it between `" "` in your `credentials.json` file.
- Copy `Client ID` and replace it with the example one in your `credentials.json` in `Client ID` **and** `BotID` field.
- Save your `credentials.json` but keep it open. We need to put your `User ID` and owner.
####Running NadekoBot
- Copy/past and hit `Enter`
`tmux new -s nadeko`
^this will create a new session named “nadeko” `(you can replace “nadeko” with anything you prefer and remember its your
session name)`.
or if you want to use Screen, run:
`screen -S nadeko`
^this will create a new screen named “nadeko” `(you can replace “nadeko” with anything you prefer and remember its your
screen name)`.
`cd nadeko`
- Start NadekoBot.exe using Mono:
`mono NadekoBot.exe`
CHECK THE BOT IN DISCORD, IF EVERYTHING IS WORKING
Now time to move bot to background and to do that, press CTRL+B+D (this will ditach the nadeko session using TMUX)
*if you used Screen press CTRL+A+D (this will detach the nadeko screen)*
####Inviting your bot to your server - [Invite Guide][Invite Guide]
- Create a new server in Discord.
- Copy your `Client ID` from your [DiscordApp][DiscordApp].
- Replace `12345678` in this link `https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303` with your `Client ID`.
- Link should look like this: `https://discordapp.com/oauth2/authorize?client_id=**YOUR_CLENT_ID**&scope=bot&permissions=66186303`.
- Go to newly created link and pick the server we created, and click `Authorize`.
- Bot should be added to your server.
####Setting up OwnerIds
- In the server where your bot is, in a text channel, type `.uid`
- Your `User ID` should show, copy it.
- Close `NadekoBot.exe`
- Replace your `User ID` in the `credentials.json` between `[ ]` and save the changes.
- Run `NadekoBot.exe` again.
- Now you are the bot owner.
- You can add `User IDs` from the other users by separating IDs with a comma if you want to have more owners.
####Setting NadekoBot Music
For Music Setup and API keys check [Setting up NadekoBot for Music](Windows Guide.md#setting-up-nadekobot-for-music) and [JSON Explanations](JSON Explanations.md).
####Some more Info - TMUX
-If you want to see the sessions after logging back again, type `tmux ls`, and that will give you the list of sessions running.
-If you want to switch to/ see that session, type `tmux a -t nadeko` (nadeko is the name of the session we created before so, replace `“nadeko”` with the session name you created.)
-If you want to kill NadekoBot session, type `tmux kill-session -t nadeko`
####Some more Info - Screen
-If you want to see the sessions after logging back again, type `screen -ls`, and that will give you the list of screens.
-If you want to switch to/ see that screen, type `screen -r nadeko` (nadeko is the name of the screen we created before so, replace `“nadeko”` with the screen name you created.)
-If you want to kill the NadekoBot screen, type `screen -X -S nadeko quit`
[Homebrew]: http://brew.sh/
[Mono]: http://www.mono-project.com/docs/compiling-mono/mac/
[Releases]: https://github.com/Kwoth/NadekoBot/releases
[DiscordApp]: https://discordapp.com/developers/applications/me
[Atom]: https://atom.io/
[Invite Guide]: http://discord.kongslien.net/guide.html
[Google Console]: https://console.developers.google.com
[Soundcloud]: https://soundcloud.com/you/apps/new

View File

@ -11,10 +11,10 @@ ________________________________________________________________________________
- 5) [7zip][7zip] (or whatever you are using, WinRar)
- 6) [Notepad++][Notepad++]
####Guide:
####Guide
- Create a folder, name it `Nadeko`.
- Head to [Releases][Releases]* and download `WINDOWS.-.nadeupdater.7z`.
- Head to [Updater Releases Page][Updater] and download `WINDOWS.-.nadeupdater.7z`.
- Copy `WINDOWS.-.nadeupdater.7z` to the `Nadeko` (folder we created before) and extract everything.
- You will see a file `NadekoUpdater.bat ` and a folder `publish ` after extraction.
- Run/Launch/Open the file `NadekoUpdater.bat ` and you will see it running in cmd.exe asking you with **3 options** *1-3*.
@ -40,11 +40,11 @@ ________________________________________________________________________________
- In there you will see fields like `Token`, `ClientId`, `BotId`, `OwnerIDs`.
- In your [DiscordApp][DiscordApp], under `Bot User` part, you will see the `Token:click to reveal` part, click to reveal it.
- Copy your bot's token, and put it between `" "` in your `credentials.json` file.
- Copy `Client ID` and replace it with the example one in your `credentials.json`.
- Copy `Bot ID` and replace it with the example one in your `credentials.json`.
- Copy `Client ID` and replace it with the example one in your `credentials.json` in `Client ID` **and** `BotID` field.
- Save your `credentials.json` but keep it open. We need to put your `User ID` and owner.
####Inviting your bot to your server [Invite Guide][Invite Guide]
####Inviting your bot to your server
- [Invite Guide][Invite Guide]
- Create a new server in Discord.
- Copy your `Client ID` from your [DiscordApp][DiscordApp].
- Replace `12345678` in this link `https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303` with your `Client ID`.
@ -58,7 +58,7 @@ ________________________________________________________________________________
- Your bot should now be online in the server we added him to.
- Note: Your bot will be offline in case you close `NadekoBot.exe`.
####Setting up OwnerIds:
####Setting up OwnerIds
- In the server where your bot is, in a text channel, type `.uid`
- Your `User ID` should show, copy it.
- Close `NadekoBot.exe`
@ -67,7 +67,10 @@ ________________________________________________________________________________
- Now you are the bot owner.
- You can add `User IDs` from the other users by separating IDs with a comma if you want to have more owners.
`*Alternatively, you can download nadekobot from [Releases][Releases] and extract the zip yourself. That is what updater does, except it makes it easier for you to update because it doesn't overwrite important files. If you are downloading releases you will have to be careful about your config, credentials, and other files you edited in order to preserve your data every time you update.`
`*Alternatively, you can download nadekobot from` [Releases][Releases] `and extract the zip yourself.
That is what updater does, except it makes it easier for you to update because it doesn't overwrite
important files.If you are downloading releases you will have to be careful about your config,
credentials, and other files you edited in order to preserve your data every time you update.`
________________________________________________________________________________
@ -80,7 +83,7 @@ ________________________________________________________________________________
- Go to [Google Console][Google Console] and log in.
- Create a new project (name does not matter). Once the project is created, go into "Enable and manage APIs."
- Under the "Other Popular APIs" section, enable `URL Shortener API` and `Custom Search Api`. Under the `YouTube APIs` section, enable `YouTube Data API`.
- On the left tab, access `Credentials`. Click `Create Credentials` button. Click on `API Key`, and then `Server Key` in the new window that appears. Enter in a name for the `Server Key`. A new window will appear with your `Google API key`.
- On the left tab, access `Credentials`. Click `Create Credentials` button. Click on `API Key`. A new window will appear with your `Google API key`.
- Copy the key.
- Open up `credentials.json`.
- For `"GoogleAPIKey"`, fill in with the new key we copied.
@ -91,8 +94,9 @@ ________________________________________________________________________________
- In `credentials.json`, fill in `"SoundcloudClientID"` with the copied ID.
- Restart your computer.
##### Prerequisites for manual `ffmpeg` setup:
####Manual `ffmpeg` setup
**Do this step in case you were not able to install `ffmpeg` with the installer.**
- Create a folder named `ffmpeg` in your main Windows directory. We will use **C:\ffmpeg** (for our guide)
- Download FFMPEG through the link https://ffmpeg.zeranoe.com/builds/ (download static build)
- Extract it using `7zip` and place the folder `ffmpeg-xxxxx-git-xxxxx-xxxx-static` inside **C:\ffmpeg**
@ -104,7 +108,8 @@ ________________________________________________________________________________
[NET Framework]: https://www.microsoft.com/en-us/download/details.aspx?id=48130
[FFMPEG]: https://github.com/Soundofdarkness/FFMPEG-Installer
[7zip]: http://www.7-zip.org/download.html
[Releases]: //github.com/Kwoth/NadekoUpdater/releases/tag/v1.0
[Updater]: https://github.com/Kwoth/NadekoUpdater/releases
[Releases]: https://github.com/Kwoth/NadekoBot/releases
[DiscordApp]: https://discordapp.com/developers/applications/me
[Notepad++]: https://notepad-plus-plus.org/
[Invite Guide]: http://discord.kongslien.net/guide.html

2
docs/guides/mii-chan.md Normal file
View File

@ -0,0 +1,2 @@
Docs are in the air.
Kwoth is magic.

View File

@ -1 +1,47 @@
Hai, this will be docs of nakeda
#NadekoBot Documentation
To invite NadekoBot to your server, click on the image bellow:
[![img][img]](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303)
In case you need any help, hop on the [NadekoBot Server][NadekoBot Server], where we can provide support.
NadekoBot is an open source project, and it can be found on our [GitHub][GitHub] page.
Here you can read current [Issues][Issues].
If you want to contribute, be sure to PR on the **[1.0][1.0]** branch.
##Content
- [About](about.md)
- Guides
- [Windows Guide](guides/Windows Guide.md)
- [Linux Guide](guides/Linux Guide.md)
- [OSX Guide](guides/OSX Guide.md)
- [Building from Source](guides/Building from Source.md)
- [Docker Guide](guides/Docker Guide.md)
- Commands
- [Readme](Readme.md)
- [Commands List](Commands List.md)
- [Permissions System](Permissions System.md)
- [JSON Explanations](JSON Explanations.md)
- [Custom Reactions](Custom Reactions.md)
- [Frequently Asked Questions](Frequently Asked Questions.md)
- [Contribution Guide](Contribution Guide.md)
- [Donate](Donate.md)
[img]: https://discordcdn.com/attachments/202743183774318593/210580315381563392/discord.png
[NadekoBot Server]: https://discord.gg/0ehQwTK2RBjAxzEY
[GitHub]: https://github.com/Kwoth/NadekoBot
[Issues]: https://github.com/Kwoth/NadekoBot/issues
[1.0]: https://github.com/Kwoth/NadekoBot/tree/1.0
[Italian]: http://i.imgur.com/SsaTwOF.png?1
[Russian]: http://i.imgur.com/wf9bc5G.png?1
[German]: http://i.imgur.com/EM5qPzf.png?1
[Chinese]: http://i.imgur.com/MVCNOjT.png?1
[English]: http://i.imgur.com/jHTyZFS.png?1
[Spanish]: http://i.imgur.com/9BsusB6.png?1
[French]: http://i.imgur.com/g2ARPF6.png?1
[Dutch]: http://i.imgur.com/SadddLj.png?1
[Norwegian]: http://i.imgur.com/TCVa0V8.png?1
[Serbian]: http://i.imgur.com/5evoUbU.png

View File

@ -1 +0,0 @@
Hai, this will be docs of nakeda

View File

@ -1,19 +0,0 @@
Copyright (c) 2015 Master Kwoth
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -9,9 +9,9 @@ using System.Threading.Tasks;
namespace NadekoBot.Attributes
{
public class LocalizedAliasAttribute : AliasAttribute
public class Aliases : AliasAttribute
{
public LocalizedAliasAttribute([CallerMemberName] string memberName = "") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_text").Split(' ').Skip(1).ToArray())
public Aliases([CallerMemberName] string memberName = "") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_cmd").Split(' ').Skip(1).ToArray())
{
}
}

View File

@ -0,0 +1,14 @@
using Discord.Commands;
using NadekoBot.Services;
using System.Runtime.CompilerServices;
namespace NadekoBot.Attributes
{
public class Description : SummaryAttribute
{
public Description([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_desc"))
{
}
}
}

View File

@ -1,15 +0,0 @@
using Discord.Commands;
using NadekoBot.Services;
using System.Linq;
using System.Runtime.CompilerServices;
namespace NadekoBot.Attributes
{
public class LocalizedCommandAttribute : CommandAttribute
{
public LocalizedCommandAttribute([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_text").Split(' ')[0])
{
}
}
}

View File

@ -1,14 +0,0 @@
using Discord.Commands;
using NadekoBot.Services;
using System.Runtime.CompilerServices;
namespace NadekoBot.Attributes
{
public class LocalizedDescriptionAttribute : DescriptionAttribute
{
public LocalizedDescriptionAttribute([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant()+"_desc"))
{
}
}
}

View File

@ -1,14 +0,0 @@
using Discord.Commands;
using NadekoBot.Services;
using System.Runtime.CompilerServices;
namespace NadekoBot.Attributes
{
public class LocalizedSummaryAttribute : SummaryAttribute
{
public LocalizedSummaryAttribute([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_summary"))
{
}
}
}

View File

@ -0,0 +1,15 @@
using Discord.Commands;
using NadekoBot.Services;
using System.Linq;
using System.Runtime.CompilerServices;
namespace NadekoBot.Attributes
{
public class NadekoCommand : CommandAttribute
{
public NadekoCommand([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_cmd").Split(' ')[0])
{
}
}
}

View File

@ -30,22 +30,22 @@ namespace NadekoBot.Attributes
}
}
public NadekoModuleAttribute(string moduleName, string defaultPrefix) : base(GetModulePrefix(moduleName) ?? defaultPrefix)
public NadekoModuleAttribute(string moduleName, string defaultPrefix) : base(GetModulePrefix(moduleName, defaultPrefix))
{
AppendSpace = false;
}
private static string GetModulePrefix(string moduleName)
private static string GetModulePrefix(string moduleName, string defaultPrefix)
{
string prefix;
if (ModulePrefixes.TryGetValue(moduleName, out prefix))
string prefix = null;
if (!ModulePrefixes.TryGetValue(moduleName, out prefix))
{
Console.WriteLine("Cache hit");
return prefix;
NadekoBot.ModulePrefixes.TryAdd(moduleName, defaultPrefix);
NLog.LogManager.GetCurrentClassLogger().Warn("Prefix not found for {0}. Will use default one: {1}", moduleName, defaultPrefix);
}
Console.WriteLine("Cache not hit for " + moduleName);
return null;
return prefix ?? defaultPrefix;
}
}
}

View File

@ -1,11 +1,12 @@
//using System.Threading.Tasks;
//using Discord.Commands;
//using Discord;
using System.Threading.Tasks;
using Discord.Commands;
using Discord;
//namespace NadekoBot.Attributes {
// public class OwnerOnlyAttribute : PreconditionAttribute
// {
// public override Task<PreconditionResult> CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance) =>
// Task.FromResult((NadekoBot.Credentials.IsOwner(context.Author) ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
// }
//}
namespace NadekoBot.Attributes
{
public class OwnerOnlyAttribute : PreconditionAttribute
{
public override Task<PreconditionResult> CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance) =>
Task.FromResult((NadekoBot.Credentials.IsOwner(context.Author) ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
}
}

View File

@ -0,0 +1,14 @@
using Discord.Commands;
using NadekoBot.Services;
using System.Runtime.CompilerServices;
namespace NadekoBot.Attributes
{
public class Usage : RemarksAttribute
{
public Usage([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant()+"_usage"))
{
}
}
}

View File

@ -0,0 +1,774 @@
// License MIT
// Source: https://github.com/i3arnon/ConcurrentHashSet
using ConcurrentCollections;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
namespace System.Collections.Concurrent
{
/// <summary>
/// Represents a thread-safe hash-based unique collection.
/// </summary>
/// <typeparam name="T">The type of the items in the collection.</typeparam>
/// <remarks>
/// All public members of <see cref="ConcurrentHashSet{T}"/> are thread-safe and may be used
/// concurrently from multiple threads.
/// </remarks>
[DebuggerDisplay("Count = {Count}")]
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T>
{
private const int DefaultCapacity = 31;
private const int MaxLockNumber = 1024;
private readonly IEqualityComparer<T> _comparer;
private readonly bool _growLockArray;
private int _budget;
private volatile Tables _tables;
private static int DefaultConcurrencyLevel => PlatformHelper.ProcessorCount;
/// <summary>
/// Gets the number of items contained in the <see
/// cref="ConcurrentHashSet{T}"/>.
/// </summary>
/// <value>The number of items contained in the <see
/// cref="ConcurrentHashSet{T}"/>.</value>
/// <remarks>Count has snapshot semantics and represents the number of items in the <see
/// cref="ConcurrentHashSet{T}"/>
/// at the moment when Count was accessed.</remarks>
public int Count {
get {
var count = 0;
var acquiredLocks = 0;
try
{
AcquireAllLocks(ref acquiredLocks);
for (var i = 0; i < _tables.CountPerLock.Length; i++)
{
count += _tables.CountPerLock[i];
}
}
finally
{
ReleaseLocks(0, acquiredLocks);
}
return count;
}
}
/// <summary>
/// Gets a value that indicates whether the <see cref="ConcurrentHashSet{T}"/> is empty.
/// </summary>
/// <value>true if the <see cref="ConcurrentHashSet{T}"/> is empty; otherwise,
/// false.</value>
public bool IsEmpty {
get {
var acquiredLocks = 0;
try
{
AcquireAllLocks(ref acquiredLocks);
for (var i = 0; i < _tables.CountPerLock.Length; i++)
{
if (_tables.CountPerLock[i] != 0)
{
return false;
}
}
}
finally
{
ReleaseLocks(0, acquiredLocks);
}
return true;
}
}
/// <summary>
/// Initializes a new instance of the <see
/// cref="ConcurrentHashSet{T}"/>
/// class that is empty, has the default concurrency level, has the default initial capacity, and
/// uses the default comparer for the item type.
/// </summary>
public ConcurrentHashSet()
: this(DefaultConcurrencyLevel, DefaultCapacity, true, EqualityComparer<T>.Default)
{
}
/// <summary>
/// Initializes a new instance of the <see
/// cref="ConcurrentHashSet{T}"/>
/// class that is empty, has the specified concurrency level and capacity, and uses the default
/// comparer for the item type.
/// </summary>
/// <param name="concurrencyLevel">The estimated number of threads that will update the
/// <see cref="ConcurrentHashSet{T}"/> concurrently.</param>
/// <param name="capacity">The initial number of elements that the <see
/// cref="ConcurrentHashSet{T}"/>
/// can contain.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is
/// less than 1.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException"> <paramref name="capacity"/> is less than
/// 0.</exception>
public ConcurrentHashSet(int concurrencyLevel, int capacity)
: this(concurrencyLevel, capacity, false, EqualityComparer<T>.Default)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
/// class that contains elements copied from the specified <see
/// cref="T:System.Collections.IEnumerable{T}"/>, has the default concurrency
/// level, has the default initial capacity, and uses the default comparer for the item type.
/// </summary>
/// <param name="collection">The <see
/// cref="T:System.Collections.IEnumerable{T}"/> whose elements are copied to
/// the new
/// <see cref="ConcurrentHashSet{T}"/>.</param>
/// <exception cref="T:System.ArgumentNullException"><paramref name="collection"/> is a null reference.</exception>
public ConcurrentHashSet(IEnumerable<T> collection)
: this(collection, EqualityComparer<T>.Default)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
/// class that is empty, has the specified concurrency level and capacity, and uses the specified
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>.
/// </summary>
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>
/// implementation to use when comparing items.</param>
/// <exception cref="T:System.ArgumentNullException"><paramref name="comparer"/> is a null reference.</exception>
public ConcurrentHashSet(IEqualityComparer<T> comparer)
: this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
/// class that contains elements copied from the specified <see
/// cref="T:System.Collections.IEnumerable"/>, has the default concurrency level, has the default
/// initial capacity, and uses the specified
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>.
/// </summary>
/// <param name="collection">The <see
/// cref="T:System.Collections.IEnumerable{T}"/> whose elements are copied to
/// the new
/// <see cref="ConcurrentHashSet{T}"/>.</param>
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>
/// implementation to use when comparing items.</param>
/// <exception cref="T:System.ArgumentNullException"><paramref name="collection"/> is a null reference
/// (Nothing in Visual Basic). -or-
/// <paramref name="comparer"/> is a null reference (Nothing in Visual Basic).
/// </exception>
public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
: this(comparer)
{
if (collection == null) throw new ArgumentNullException(nameof(collection));
InitializeFromCollection(collection);
}
/// <summary>
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
/// class that contains elements copied from the specified <see cref="T:System.Collections.IEnumerable"/>,
/// has the specified concurrency level, has the specified initial capacity, and uses the specified
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>.
/// </summary>
/// <param name="concurrencyLevel">The estimated number of threads that will update the
/// <see cref="ConcurrentHashSet{T}"/> concurrently.</param>
/// <param name="collection">The <see cref="T:System.Collections.IEnumerable{T}"/> whose elements are copied to the new
/// <see cref="ConcurrentHashSet{T}"/>.</param>
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/> implementation to use
/// when comparing items.</param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="collection"/> is a null reference.
/// -or-
/// <paramref name="comparer"/> is a null reference.
/// </exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="concurrencyLevel"/> is less than 1.
/// </exception>
public ConcurrentHashSet(int concurrencyLevel, IEnumerable<T> collection, IEqualityComparer<T> comparer)
: this(concurrencyLevel, DefaultCapacity, false, comparer)
{
if (collection == null) throw new ArgumentNullException(nameof(collection));
if (comparer == null) throw new ArgumentNullException(nameof(comparer));
InitializeFromCollection(collection);
}
/// <summary>
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
/// class that is empty, has the specified concurrency level, has the specified initial capacity, and
/// uses the specified <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>.
/// </summary>
/// <param name="concurrencyLevel">The estimated number of threads that will update the
/// <see cref="ConcurrentHashSet{T}"/> concurrently.</param>
/// <param name="capacity">The initial number of elements that the <see
/// cref="ConcurrentHashSet{T}"/>
/// can contain.</param>
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>
/// implementation to use when comparing items.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="concurrencyLevel"/> is less than 1. -or-
/// <paramref name="capacity"/> is less than 0.
/// </exception>
/// <exception cref="T:System.ArgumentNullException"><paramref name="comparer"/> is a null reference.</exception>
public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer<T> comparer)
: this(concurrencyLevel, capacity, false, comparer)
{
}
private ConcurrentHashSet(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer<T> comparer)
{
if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(nameof(concurrencyLevel));
if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity));
if (comparer == null) throw new ArgumentNullException(nameof(comparer));
// The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard
// any buckets.
if (capacity < concurrencyLevel)
{
capacity = concurrencyLevel;
}
var locks = new object[concurrencyLevel];
for (var i = 0; i < locks.Length; i++)
{
locks[i] = new object();
}
var countPerLock = new int[locks.Length];
var buckets = new Node[capacity];
_tables = new Tables(buckets, locks, countPerLock);
_growLockArray = growLockArray;
_budget = buckets.Length / locks.Length;
_comparer = comparer;
}
/// <summary>
/// Adds the specified item to the <see cref="ConcurrentHashSet{T}"/>.
/// </summary>
/// <param name="item">The item to add.</param>
/// <returns>true if the items was added to the <see cref="ConcurrentHashSet{T}"/>
/// successfully; false if it already exists.</returns>
/// <exception cref="T:System.OverflowException">The <see cref="ConcurrentHashSet{T}"/>
/// contains too many items.</exception>
public bool Add(T item) =>
AddInternal(item, _comparer.GetHashCode(item), true);
/// <summary>
/// Removes all items from the <see cref="ConcurrentHashSet{T}"/>.
/// </summary>
public void Clear()
{
var locksAcquired = 0;
try
{
AcquireAllLocks(ref locksAcquired);
var newTables = new Tables(new Node[DefaultCapacity], _tables.Locks, new int[_tables.CountPerLock.Length]);
_tables = newTables;
_budget = Math.Max(1, newTables.Buckets.Length / newTables.Locks.Length);
}
finally
{
ReleaseLocks(0, locksAcquired);
}
}
/// <summary>
/// Determines whether the <see cref="ConcurrentHashSet{T}"/> contains the specified
/// item.
/// </summary>
/// <param name="item">The item to locate in the <see cref="ConcurrentHashSet{T}"/>.</param>
/// <returns>true if the <see cref="ConcurrentHashSet{T}"/> contains the item; otherwise, false.</returns>
public bool Contains(T item)
{
var hashcode = _comparer.GetHashCode(item);
// We must capture the _buckets field in a local variable. It is set to a new table on each table resize.
var tables = _tables;
var bucketNo = GetBucket(hashcode, tables.Buckets.Length);
// We can get away w/out a lock here.
// The Volatile.Read ensures that the load of the fields of 'n' doesn't move before the load from buckets[i].
var current = Volatile.Read(ref tables.Buckets[bucketNo]);
while (current != null)
{
if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item))
{
return true;
}
current = current.Next;
}
return false;
}
/// <summary>
/// Attempts to remove the item from the <see cref="ConcurrentHashSet{T}"/>.
/// </summary>
/// <param name="item">The item to remove.</param>
/// <returns>true if an item was removed successfully; otherwise, false.</returns>
public bool TryRemove(T item)
{
var hashcode = _comparer.GetHashCode(item);
while (true)
{
var tables = _tables;
int bucketNo, lockNo;
GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.Buckets.Length, tables.Locks.Length);
lock (tables.Locks[lockNo])
{
// If the table just got resized, we may not be holding the right lock, and must retry.
// This should be a rare occurrence.
if (tables != _tables)
{
continue;
}
Node previous = null;
for (var current = tables.Buckets[bucketNo]; current != null; current = current.Next)
{
Debug.Assert((previous == null && current == tables.Buckets[bucketNo]) || previous.Next == current);
if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item))
{
if (previous == null)
{
Volatile.Write(ref tables.Buckets[bucketNo], current.Next);
}
else
{
previous.Next = current.Next;
}
tables.CountPerLock[lockNo]--;
return true;
}
previous = current;
}
}
return false;
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>Returns an enumerator that iterates through the <see
/// cref="ConcurrentHashSet{T}"/>.</summary>
/// <returns>An enumerator for the <see cref="ConcurrentHashSet{T}"/>.</returns>
/// <remarks>
/// The enumerator returned from the collection is safe to use concurrently with
/// reads and writes to the collection, however it does not represent a moment-in-time snapshot
/// of the collection. The contents exposed through the enumerator may contain modifications
/// made to the collection after <see cref="GetEnumerator"/> was called.
/// </remarks>
public IEnumerator<T> GetEnumerator()
{
var buckets = _tables.Buckets;
for (var i = 0; i < buckets.Length; i++)
{
// The Volatile.Read ensures that the load of the fields of 'current' doesn't move before the load from buckets[i].
var current = Volatile.Read(ref buckets[i]);
while (current != null)
{
yield return current.Item;
current = current.Next;
}
}
}
void ICollection<T>.Add(T item) => Add(item);
bool ICollection<T>.IsReadOnly => false;
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException(nameof(array));
if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex));
var locksAcquired = 0;
try
{
AcquireAllLocks(ref locksAcquired);
var count = 0;
for (var i = 0; i < _tables.Locks.Length && count >= 0; i++)
{
count += _tables.CountPerLock[i];
}
if (array.Length - count < arrayIndex || count < 0) //"count" itself or "count + arrayIndex" can overflow
{
throw new ArgumentException("The index is equal to or greater than the length of the array, or the number of elements in the set is greater than the available space from index to the end of the destination array.");
}
CopyToItems(array, arrayIndex);
}
finally
{
ReleaseLocks(0, locksAcquired);
}
}
bool ICollection<T>.Remove(T item) => TryRemove(item);
private void InitializeFromCollection(IEnumerable<T> collection)
{
foreach (var item in collection)
{
AddInternal(item, _comparer.GetHashCode(item), false);
}
if (_budget == 0)
{
_budget = _tables.Buckets.Length / _tables.Locks.Length;
}
}
private bool AddInternal(T item, int hashcode, bool acquireLock)
{
while (true)
{
int bucketNo, lockNo;
var tables = _tables;
GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.Buckets.Length, tables.Locks.Length);
var resizeDesired = false;
var lockTaken = false;
try
{
if (acquireLock)
Monitor.Enter(tables.Locks[lockNo], ref lockTaken);
// If the table just got resized, we may not be holding the right lock, and must retry.
// This should be a rare occurrence.
if (tables != _tables)
{
continue;
}
// Try to find this item in the bucket
Node previous = null;
for (var current = tables.Buckets[bucketNo]; current != null; current = current.Next)
{
Debug.Assert((previous == null && current == tables.Buckets[bucketNo]) || previous.Next == current);
if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item))
{
return false;
}
previous = current;
}
// The item was not found in the bucket. Insert the new item.
Volatile.Write(ref tables.Buckets[bucketNo], new Node(item, hashcode, tables.Buckets[bucketNo]));
checked
{
tables.CountPerLock[lockNo]++;
}
//
// If the number of elements guarded by this lock has exceeded the budget, resize the bucket table.
// It is also possible that GrowTable will increase the budget but won't resize the bucket table.
// That happens if the bucket table is found to be poorly utilized due to a bad hash function.
//
if (tables.CountPerLock[lockNo] > _budget)
{
resizeDesired = true;
}
}
finally
{
if (lockTaken)
Monitor.Exit(tables.Locks[lockNo]);
}
//
// The fact that we got here means that we just performed an insertion. If necessary, we will grow the table.
//
// Concurrency notes:
// - Notice that we are not holding any locks at when calling GrowTable. This is necessary to prevent deadlocks.
// - As a result, it is possible that GrowTable will be called unnecessarily. But, GrowTable will obtain lock 0
// and then verify that the table we passed to it as the argument is still the current table.
//
if (resizeDesired)
{
GrowTable(tables);
}
return true;
}
}
private static int GetBucket(int hashcode, int bucketCount)
{
var bucketNo = (hashcode & 0x7fffffff) % bucketCount;
Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount);
return bucketNo;
}
private static void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount)
{
bucketNo = (hashcode & 0x7fffffff) % bucketCount;
lockNo = bucketNo % lockCount;
Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount);
Debug.Assert(lockNo >= 0 && lockNo < lockCount);
}
private void GrowTable(Tables tables)
{
const int maxArrayLength = 0X7FEFFFFF;
var locksAcquired = 0;
try
{
// The thread that first obtains _locks[0] will be the one doing the resize operation
AcquireLocks(0, 1, ref locksAcquired);
// Make sure nobody resized the table while we were waiting for lock 0:
if (tables != _tables)
{
// We assume that since the table reference is different, it was already resized (or the budget
// was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons,
// we will have to revisit this logic.
return;
}
// Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow.
long approxCount = 0;
for (var i = 0; i < tables.CountPerLock.Length; i++)
{
approxCount += tables.CountPerLock[i];
}
//
// If the bucket array is too empty, double the budget instead of resizing the table
//
if (approxCount < tables.Buckets.Length / 4)
{
_budget = 2 * _budget;
if (_budget < 0)
{
_budget = int.MaxValue;
}
return;
}
// Compute the new table size. We find the smallest integer larger than twice the previous table size, and not divisible by
// 2,3,5 or 7. We can consider a different table-sizing policy in the future.
var newLength = 0;
var maximizeTableSize = false;
try
{
checked
{
// Double the size of the buckets table and add one, so that we have an odd integer.
newLength = tables.Buckets.Length * 2 + 1;
// Now, we only need to check odd integers, and find the first that is not divisible
// by 3, 5 or 7.
while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0)
{
newLength += 2;
}
Debug.Assert(newLength % 2 != 0);
if (newLength > maxArrayLength)
{
maximizeTableSize = true;
}
}
}
catch (OverflowException)
{
maximizeTableSize = true;
}
if (maximizeTableSize)
{
newLength = maxArrayLength;
// We want to make sure that GrowTable will not be called again, since table is at the maximum size.
// To achieve that, we set the budget to int.MaxValue.
//
// (There is one special case that would allow GrowTable() to be called in the future:
// calling Clear() on the ConcurrentHashSet will shrink the table and lower the budget.)
_budget = int.MaxValue;
}
// Now acquire all other locks for the table
AcquireLocks(1, tables.Locks.Length, ref locksAcquired);
var newLocks = tables.Locks;
// Add more locks
if (_growLockArray && tables.Locks.Length < MaxLockNumber)
{
newLocks = new object[tables.Locks.Length * 2];
Array.Copy(tables.Locks, 0, newLocks, 0, tables.Locks.Length);
for (var i = tables.Locks.Length; i < newLocks.Length; i++)
{
newLocks[i] = new object();
}
}
var newBuckets = new Node[newLength];
var newCountPerLock = new int[newLocks.Length];
// Copy all data into a new table, creating new nodes for all elements
for (var i = 0; i < tables.Buckets.Length; i++)
{
var current = tables.Buckets[i];
while (current != null)
{
var next = current.Next;
int newBucketNo, newLockNo;
GetBucketAndLockNo(current.Hashcode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length);
newBuckets[newBucketNo] = new Node(current.Item, current.Hashcode, newBuckets[newBucketNo]);
checked
{
newCountPerLock[newLockNo]++;
}
current = next;
}
}
// Adjust the budget
_budget = Math.Max(1, newBuckets.Length / newLocks.Length);
// Replace tables with the new versions
_tables = new Tables(newBuckets, newLocks, newCountPerLock);
}
finally
{
// Release all locks that we took earlier
ReleaseLocks(0, locksAcquired);
}
}
public int RemoveWhere(Func<T, bool> predicate)
{
var elems = this.Where(predicate);
var removed = 0;
foreach (var elem in elems)
{
if (this.TryRemove(elem))
removed++;
}
return removed;
}
private void AcquireAllLocks(ref int locksAcquired)
{
// First, acquire lock 0
AcquireLocks(0, 1, ref locksAcquired);
// Now that we have lock 0, the _locks array will not change (i.e., grow),
// and so we can safely read _locks.Length.
AcquireLocks(1, _tables.Locks.Length, ref locksAcquired);
Debug.Assert(locksAcquired == _tables.Locks.Length);
}
private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired)
{
Debug.Assert(fromInclusive <= toExclusive);
var locks = _tables.Locks;
for (var i = fromInclusive; i < toExclusive; i++)
{
var lockTaken = false;
try
{
Monitor.Enter(locks[i], ref lockTaken);
}
finally
{
if (lockTaken)
{
locksAcquired++;
}
}
}
}
private void ReleaseLocks(int fromInclusive, int toExclusive)
{
Debug.Assert(fromInclusive <= toExclusive);
for (var i = fromInclusive; i < toExclusive; i++)
{
Monitor.Exit(_tables.Locks[i]);
}
}
private void CopyToItems(T[] array, int index)
{
var buckets = _tables.Buckets;
for (var i = 0; i < buckets.Length; i++)
{
for (var current = buckets[i]; current != null; current = current.Next)
{
array[index] = current.Item;
index++; //this should never flow, CopyToItems is only called when there's no overflow risk
}
}
}
private sealed class Tables
{
public readonly Node[] Buckets;
public readonly object[] Locks;
public volatile int[] CountPerLock;
public Tables(Node[] buckets, object[] locks, int[] countPerLock)
{
Buckets = buckets;
Locks = locks;
CountPerLock = countPerLock;
}
}
private sealed class Node
{
public readonly T Item;
public readonly int Hashcode;
public volatile Node Next;
public Node(T item, int hashcode, Node next)
{
Item = item;
Hashcode = hashcode;
Next = next;
}
}
}
}

View File

@ -0,0 +1,25 @@
using System;
namespace ConcurrentCollections
{
internal static class PlatformHelper
{
private const int ProcessorCountRefreshIntervalMs = 30000;
private static volatile int _processorCount;
private static volatile int _lastProcessorCountRefreshTicks;
internal static int ProcessorCount {
get {
var now = Environment.TickCount;
if (_processorCount == 0 || (now - _lastProcessorCountRefreshTicks) >= ProcessorCountRefreshIntervalMs)
{
_processorCount = Environment.ProcessorCount;
_lastProcessorCountRefreshTicks = now;
}
return _processorCount;
}
}
}
}

View File

@ -8,7 +8,7 @@ using NadekoBot.Services.Database.Impl;
namespace NadekoBot.Migrations
{
[DbContext(typeof(NadekoSqliteContext))]
[Migration("20160910180231_first")]
[Migration("20161011200458_first")]
partial class first
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -25,6 +25,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ItemId");
b.Property<int>("Type");
b.HasKey("Id");
b.HasIndex("BotConfigId");
@ -49,12 +51,16 @@ namespace NadekoBot.Migrations
b.Property<string>("CurrencySign");
b.Property<bool>("DontJoinServers");
b.Property<string>("DMHelpString");
b.Property<bool>("ForwardMessages");
b.Property<bool>("ForwardToAllOwners");
b.Property<string>("HelpString");
b.Property<int>("MigrationVersion");
b.Property<string>("RemindMessageFormat");
b.Property<bool>("RotatingStatuses");
@ -108,6 +114,24 @@ namespace NadekoBot.Migrations
b.ToTable("ClashOfClans");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("CommandName");
b.Property<int?>("GuildConfigId");
b.Property<int>("Seconds");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("CommandCooldown");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b =>
{
b.Property<int>("Id")
@ -141,6 +165,26 @@ namespace NadekoBot.Migrations
b.ToTable("Currency");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong?>("GuildId");
b.Property<bool>("IsRegex");
b.Property<bool>("OwnerOnly");
b.Property<string>("Response");
b.Property<string>("Trigger");
b.HasKey("Id");
b.ToTable("CustomReactions");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b =>
{
b.Property<int>("Id")
@ -176,6 +220,42 @@ namespace NadekoBot.Migrations
b.ToTable("EightBallResponses");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("GuildConfigId");
b.Property<int?>("GuildConfigId1");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.HasIndex("GuildConfigId1");
b.ToTable("FilterChannelId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("GuildConfigId");
b.Property<string>("Word");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("FilteredWord");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b =>
{
b.Property<int>("Id")
@ -187,8 +267,6 @@ namespace NadekoBot.Migrations
b.Property<ulong>("GuildId");
b.Property<bool>("LastStatus");
b.Property<int>("Type");
b.Property<string>("Username");
@ -200,6 +278,22 @@ namespace NadekoBot.Migrations
b.ToTable("FollowedStream");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("GuildConfigId");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("GCChannelId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b =>
{
b.Property<int>("Id")
@ -229,7 +323,9 @@ namespace NadekoBot.Migrations
b.Property<bool>("ExclusiveSelfAssignedRoles");
b.Property<ulong?>("GenerateCurrencyChannelId");
b.Property<bool>("FilterInvites");
b.Property<bool>("FilterWords");
b.Property<ulong>("GreetMessageChannelId");
@ -237,12 +333,18 @@ namespace NadekoBot.Migrations
b.Property<int?>("LogSettingId");
b.Property<string>("PermissionRole");
b.Property<int?>("RootPermissionId");
b.Property<bool>("SendChannelByeMessage");
b.Property<bool>("SendChannelGreetMessage");
b.Property<bool>("SendDmGreetMessage");
b.Property<bool>("VerbosePermissions");
b.Property<bool>("VoicePlusTextEnabled");
b.HasKey("Id");
@ -252,6 +354,8 @@ namespace NadekoBot.Migrations
b.HasIndex("LogSettingId");
b.HasIndex("RootPermissionId");
b.ToTable("GuildConfigs");
});
@ -308,8 +412,6 @@ namespace NadekoBot.Migrations
b.Property<bool>("MessageDeleted");
b.Property<bool>("MessageReceived");
b.Property<bool>("MessageUpdated");
b.Property<bool>("UserBanned");
@ -336,7 +438,7 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("BotConfigId");
b.Property<int?>("BotConfigId");
b.Property<string>("ModuleName");
@ -349,6 +451,47 @@ namespace NadekoBot.Migrations
b.ToTable("ModulePrefixes");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Author");
b.Property<ulong>("AuthorId");
b.Property<string>("Name");
b.HasKey("Id");
b.ToTable("MusicPlaylists");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("NextId");
b.Property<int>("PrimaryTarget");
b.Property<ulong>("PrimaryTargetId");
b.Property<int>("SecondaryTarget");
b.Property<string>("SecondaryTargetName");
b.Property<bool>("State");
b.HasKey("Id");
b.HasIndex("NextId")
.IsUnique();
b.ToTable("Permission");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b =>
{
b.Property<int>("Id")
@ -365,6 +508,30 @@ namespace NadekoBot.Migrations
b.ToTable("PlayingStatus");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("MusicPlaylistId");
b.Property<string>("Provider");
b.Property<int>("ProviderType");
b.Property<string>("Query");
b.Property<string>("Title");
b.Property<string>("Uri");
b.HasKey("Id");
b.HasIndex("MusicPlaylistId");
b.ToTable("PlaylistSong");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b =>
{
b.Property<int>("Id")
@ -466,20 +633,6 @@ namespace NadekoBot.Migrations
b.ToTable("SelfAssignableRoles");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.TypingArticle", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Author");
b.Property<string>("Text");
b.HasKey("Id");
b.ToTable("TypingArticles");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
@ -495,6 +648,13 @@ namespace NadekoBot.Migrations
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("CommandCooldowns")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
@ -502,6 +662,24 @@ namespace NadekoBot.Migrations
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("FilterInvitesChannelIds")
.HasForeignKey("GuildConfigId");
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("FilterWordsChannelIds")
.HasForeignKey("GuildConfigId1");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("FilteredWords")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
@ -509,11 +687,22 @@ namespace NadekoBot.Migrations
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("GenerateCurrencyChannelIds")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
.WithMany()
.HasForeignKey("LogSettingId");
b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission")
.WithMany()
.HasForeignKey("RootPermissionId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
@ -532,10 +721,16 @@ namespace NadekoBot.Migrations
modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig", "BotConfig")
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("ModulePrefixes")
.HasForeignKey("BotConfigId")
.OnDelete(DeleteBehavior.Cascade);
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next")
.WithOne("Previous")
.HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b =>
@ -545,6 +740,14 @@ namespace NadekoBot.Migrations
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist")
.WithMany("Songs")
.HasForeignKey("MusicPlaylistId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")

View File

@ -20,9 +20,11 @@ namespace NadekoBot.Migrations
CurrencyName = table.Column<string>(nullable: true),
CurrencyPluralName = table.Column<string>(nullable: true),
CurrencySign = table.Column<string>(nullable: true),
DontJoinServers = table.Column<bool>(nullable: false),
DMHelpString = table.Column<string>(nullable: true),
ForwardMessages = table.Column<bool>(nullable: false),
ForwardToAllOwners = table.Column<bool>(nullable: false),
HelpString = table.Column<string>(nullable: true),
MigrationVersion = table.Column<int>(nullable: false),
RemindMessageFormat = table.Column<string>(nullable: true),
RotatingStatuses = table.Column<bool>(nullable: false)
},
@ -78,6 +80,23 @@ namespace NadekoBot.Migrations
table.PrimaryKey("PK_Currency", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CustomReactions",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
GuildId = table.Column<ulong>(nullable: true),
IsRegex = table.Column<bool>(nullable: false),
OwnerOnly = table.Column<bool>(nullable: false),
Response = table.Column<string>(nullable: true),
Trigger = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CustomReactions", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Donators",
columns: table => new
@ -107,7 +126,6 @@ namespace NadekoBot.Migrations
LogUserPresence = table.Column<bool>(nullable: false),
LogVoicePresence = table.Column<bool>(nullable: false),
MessageDeleted = table.Column<bool>(nullable: false),
MessageReceived = table.Column<bool>(nullable: false),
MessageUpdated = table.Column<bool>(nullable: false),
UserBanned = table.Column<bool>(nullable: false),
UserJoined = table.Column<bool>(nullable: false),
@ -122,6 +140,45 @@ namespace NadekoBot.Migrations
table.PrimaryKey("PK_LogSettings", x => x.Id);
});
migrationBuilder.CreateTable(
name: "MusicPlaylists",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
Author = table.Column<string>(nullable: true),
AuthorId = table.Column<ulong>(nullable: false),
Name = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_MusicPlaylists", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Permission",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
NextId = table.Column<int>(nullable: true),
PrimaryTarget = table.Column<int>(nullable: false),
PrimaryTargetId = table.Column<ulong>(nullable: false),
SecondaryTarget = table.Column<int>(nullable: false),
SecondaryTargetName = table.Column<string>(nullable: true),
State = table.Column<bool>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Permission", x => x.Id);
table.ForeignKey(
name: "FK_Permission_Permission_NextId",
column: x => x.NextId,
principalTable: "Permission",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Quotes",
columns: table => new
@ -187,20 +244,6 @@ namespace NadekoBot.Migrations
table.PrimaryKey("PK_SelfAssignableRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "TypingArticles",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
Author = table.Column<string>(nullable: true),
Text = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TypingArticles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "BlacklistItem",
columns: table => new
@ -208,7 +251,8 @@ namespace NadekoBot.Migrations
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
BotConfigId = table.Column<int>(nullable: true),
ItemId = table.Column<ulong>(nullable: false)
ItemId = table.Column<ulong>(nullable: false),
Type = table.Column<int>(nullable: false)
},
constraints: table =>
{
@ -247,7 +291,7 @@ namespace NadekoBot.Migrations
{
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
BotConfigId = table.Column<int>(nullable: false),
BotConfigId = table.Column<int>(nullable: true),
ModuleName = table.Column<string>(nullable: true),
Prefix = table.Column<string>(nullable: true)
},
@ -259,7 +303,7 @@ namespace NadekoBot.Migrations
column: x => x.BotConfigId,
principalTable: "BotConfig",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
@ -326,44 +370,6 @@ namespace NadekoBot.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "GuildConfigs",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
AutoAssignRoleId = table.Column<ulong>(nullable: false),
AutoDeleteByeMessages = table.Column<bool>(nullable: false),
AutoDeleteGreetMessages = table.Column<bool>(nullable: false),
AutoDeleteGreetMessagesTimer = table.Column<int>(nullable: false),
AutoDeleteSelfAssignedRoleMessages = table.Column<bool>(nullable: false),
ByeMessageChannelId = table.Column<ulong>(nullable: false),
ChannelByeMessageText = table.Column<string>(nullable: true),
ChannelGreetMessageText = table.Column<string>(nullable: true),
DefaultMusicVolume = table.Column<float>(nullable: false),
DeleteMessageOnCommand = table.Column<bool>(nullable: false),
DmGreetMessageText = table.Column<string>(nullable: true),
ExclusiveSelfAssignedRoles = table.Column<bool>(nullable: false),
GenerateCurrencyChannelId = table.Column<ulong>(nullable: true),
GreetMessageChannelId = table.Column<ulong>(nullable: false),
GuildId = table.Column<ulong>(nullable: false),
LogSettingId = table.Column<int>(nullable: true),
SendChannelByeMessage = table.Column<bool>(nullable: false),
SendChannelGreetMessage = table.Column<bool>(nullable: false),
SendDmGreetMessage = table.Column<bool>(nullable: false),
VoicePlusTextEnabled = table.Column<bool>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GuildConfigs", x => x.Id);
table.ForeignKey(
name: "FK_GuildConfigs_LogSettings_LogSettingId",
column: x => x.LogSettingId,
principalTable: "LogSettings",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "IgnoredLogChannels",
columns: table => new
@ -404,6 +410,146 @@ namespace NadekoBot.Migrations
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "PlaylistSong",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
MusicPlaylistId = table.Column<int>(nullable: true),
Provider = table.Column<string>(nullable: true),
ProviderType = table.Column<int>(nullable: false),
Query = table.Column<string>(nullable: true),
Title = table.Column<string>(nullable: true),
Uri = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PlaylistSong", x => x.Id);
table.ForeignKey(
name: "FK_PlaylistSong_MusicPlaylists_MusicPlaylistId",
column: x => x.MusicPlaylistId,
principalTable: "MusicPlaylists",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "GuildConfigs",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
AutoAssignRoleId = table.Column<ulong>(nullable: false),
AutoDeleteByeMessages = table.Column<bool>(nullable: false),
AutoDeleteGreetMessages = table.Column<bool>(nullable: false),
AutoDeleteGreetMessagesTimer = table.Column<int>(nullable: false),
AutoDeleteSelfAssignedRoleMessages = table.Column<bool>(nullable: false),
ByeMessageChannelId = table.Column<ulong>(nullable: false),
ChannelByeMessageText = table.Column<string>(nullable: true),
ChannelGreetMessageText = table.Column<string>(nullable: true),
DefaultMusicVolume = table.Column<float>(nullable: false),
DeleteMessageOnCommand = table.Column<bool>(nullable: false),
DmGreetMessageText = table.Column<string>(nullable: true),
ExclusiveSelfAssignedRoles = table.Column<bool>(nullable: false),
FilterInvites = table.Column<bool>(nullable: false),
FilterWords = table.Column<bool>(nullable: false),
GreetMessageChannelId = table.Column<ulong>(nullable: false),
GuildId = table.Column<ulong>(nullable: false),
LogSettingId = table.Column<int>(nullable: true),
PermissionRole = table.Column<string>(nullable: true),
RootPermissionId = table.Column<int>(nullable: true),
SendChannelByeMessage = table.Column<bool>(nullable: false),
SendChannelGreetMessage = table.Column<bool>(nullable: false),
SendDmGreetMessage = table.Column<bool>(nullable: false),
VerbosePermissions = table.Column<bool>(nullable: false),
VoicePlusTextEnabled = table.Column<bool>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GuildConfigs", x => x.Id);
table.ForeignKey(
name: "FK_GuildConfigs_LogSettings_LogSettingId",
column: x => x.LogSettingId,
principalTable: "LogSettings",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_GuildConfigs_Permission_RootPermissionId",
column: x => x.RootPermissionId,
principalTable: "Permission",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "CommandCooldown",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
CommandName = table.Column<string>(nullable: true),
GuildConfigId = table.Column<int>(nullable: true),
Seconds = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CommandCooldown", x => x.Id);
table.ForeignKey(
name: "FK_CommandCooldown_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "FilterChannelId",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
ChannelId = table.Column<ulong>(nullable: false),
GuildConfigId = table.Column<int>(nullable: true),
GuildConfigId1 = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_FilterChannelId", x => x.Id);
table.ForeignKey(
name: "FK_FilterChannelId_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_FilterChannelId_GuildConfigs_GuildConfigId1",
column: x => x.GuildConfigId1,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "FilteredWord",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
GuildConfigId = table.Column<int>(nullable: true),
Word = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_FilteredWord", x => x.Id);
table.ForeignKey(
name: "FK_FilteredWord_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "FollowedStream",
columns: table => new
@ -413,7 +559,6 @@ namespace NadekoBot.Migrations
ChannelId = table.Column<ulong>(nullable: false),
GuildConfigId = table.Column<int>(nullable: true),
GuildId = table.Column<ulong>(nullable: false),
LastStatus = table.Column<bool>(nullable: false),
Type = table.Column<int>(nullable: false),
Username = table.Column<string>(nullable: true)
},
@ -428,6 +573,26 @@ namespace NadekoBot.Migrations
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "GCChannelId",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Autoincrement", true),
ChannelId = table.Column<ulong>(nullable: false),
GuildConfigId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_GCChannelId", x => x.Id);
table.ForeignKey(
name: "FK_GCChannelId_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_BlacklistItem_BotConfigId",
table: "BlacklistItem",
@ -438,6 +603,11 @@ namespace NadekoBot.Migrations
table: "ClashCallers",
column: "ClashWarId");
migrationBuilder.CreateIndex(
name: "IX_CommandCooldown_GuildConfigId",
table: "CommandCooldown",
column: "GuildConfigId");
migrationBuilder.CreateIndex(
name: "IX_Currency_UserId",
table: "Currency",
@ -455,11 +625,31 @@ namespace NadekoBot.Migrations
table: "EightBallResponses",
column: "BotConfigId");
migrationBuilder.CreateIndex(
name: "IX_FilterChannelId_GuildConfigId",
table: "FilterChannelId",
column: "GuildConfigId");
migrationBuilder.CreateIndex(
name: "IX_FilterChannelId_GuildConfigId1",
table: "FilterChannelId",
column: "GuildConfigId1");
migrationBuilder.CreateIndex(
name: "IX_FilteredWord_GuildConfigId",
table: "FilteredWord",
column: "GuildConfigId");
migrationBuilder.CreateIndex(
name: "IX_FollowedStream_GuildConfigId",
table: "FollowedStream",
column: "GuildConfigId");
migrationBuilder.CreateIndex(
name: "IX_GCChannelId_GuildConfigId",
table: "GCChannelId",
column: "GuildConfigId");
migrationBuilder.CreateIndex(
name: "IX_GuildConfigs_GuildId",
table: "GuildConfigs",
@ -471,6 +661,11 @@ namespace NadekoBot.Migrations
table: "GuildConfigs",
column: "LogSettingId");
migrationBuilder.CreateIndex(
name: "IX_GuildConfigs_RootPermissionId",
table: "GuildConfigs",
column: "RootPermissionId");
migrationBuilder.CreateIndex(
name: "IX_IgnoredLogChannels_LogSettingId",
table: "IgnoredLogChannels",
@ -486,11 +681,22 @@ namespace NadekoBot.Migrations
table: "ModulePrefixes",
column: "BotConfigId");
migrationBuilder.CreateIndex(
name: "IX_Permission_NextId",
table: "Permission",
column: "NextId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_PlayingStatus_BotConfigId",
table: "PlayingStatus",
column: "BotConfigId");
migrationBuilder.CreateIndex(
name: "IX_PlaylistSong_MusicPlaylistId",
table: "PlaylistSong",
column: "MusicPlaylistId");
migrationBuilder.CreateIndex(
name: "IX_RaceAnimals_BotConfigId",
table: "RaceAnimals",
@ -517,21 +723,36 @@ namespace NadekoBot.Migrations
migrationBuilder.DropTable(
name: "ClashCallers");
migrationBuilder.DropTable(
name: "CommandCooldown");
migrationBuilder.DropTable(
name: "ConversionUnits");
migrationBuilder.DropTable(
name: "Currency");
migrationBuilder.DropTable(
name: "CustomReactions");
migrationBuilder.DropTable(
name: "Donators");
migrationBuilder.DropTable(
name: "EightBallResponses");
migrationBuilder.DropTable(
name: "FilterChannelId");
migrationBuilder.DropTable(
name: "FilteredWord");
migrationBuilder.DropTable(
name: "FollowedStream");
migrationBuilder.DropTable(
name: "GCChannelId");
migrationBuilder.DropTable(
name: "IgnoredLogChannels");
@ -544,6 +765,9 @@ namespace NadekoBot.Migrations
migrationBuilder.DropTable(
name: "PlayingStatus");
migrationBuilder.DropTable(
name: "PlaylistSong");
migrationBuilder.DropTable(
name: "Quotes");
@ -559,20 +783,23 @@ namespace NadekoBot.Migrations
migrationBuilder.DropTable(
name: "SelfAssignableRoles");
migrationBuilder.DropTable(
name: "TypingArticles");
migrationBuilder.DropTable(
name: "ClashOfClans");
migrationBuilder.DropTable(
name: "GuildConfigs");
migrationBuilder.DropTable(
name: "MusicPlaylists");
migrationBuilder.DropTable(
name: "BotConfig");
migrationBuilder.DropTable(
name: "LogSettings");
migrationBuilder.DropTable(
name: "Permission");
}
}
}

View File

@ -24,6 +24,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ItemId");
b.Property<int>("Type");
b.HasKey("Id");
b.HasIndex("BotConfigId");
@ -48,12 +50,16 @@ namespace NadekoBot.Migrations
b.Property<string>("CurrencySign");
b.Property<bool>("DontJoinServers");
b.Property<string>("DMHelpString");
b.Property<bool>("ForwardMessages");
b.Property<bool>("ForwardToAllOwners");
b.Property<string>("HelpString");
b.Property<int>("MigrationVersion");
b.Property<string>("RemindMessageFormat");
b.Property<bool>("RotatingStatuses");
@ -107,6 +113,24 @@ namespace NadekoBot.Migrations
b.ToTable("ClashOfClans");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("CommandName");
b.Property<int?>("GuildConfigId");
b.Property<int>("Seconds");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("CommandCooldown");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b =>
{
b.Property<int>("Id")
@ -140,6 +164,26 @@ namespace NadekoBot.Migrations
b.ToTable("Currency");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong?>("GuildId");
b.Property<bool>("IsRegex");
b.Property<bool>("OwnerOnly");
b.Property<string>("Response");
b.Property<string>("Trigger");
b.HasKey("Id");
b.ToTable("CustomReactions");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b =>
{
b.Property<int>("Id")
@ -175,6 +219,42 @@ namespace NadekoBot.Migrations
b.ToTable("EightBallResponses");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("GuildConfigId");
b.Property<int?>("GuildConfigId1");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.HasIndex("GuildConfigId1");
b.ToTable("FilterChannelId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("GuildConfigId");
b.Property<string>("Word");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("FilteredWord");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b =>
{
b.Property<int>("Id")
@ -186,8 +266,6 @@ namespace NadekoBot.Migrations
b.Property<ulong>("GuildId");
b.Property<bool>("LastStatus");
b.Property<int>("Type");
b.Property<string>("Username");
@ -199,6 +277,22 @@ namespace NadekoBot.Migrations
b.ToTable("FollowedStream");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("GuildConfigId");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("GCChannelId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b =>
{
b.Property<int>("Id")
@ -228,7 +322,9 @@ namespace NadekoBot.Migrations
b.Property<bool>("ExclusiveSelfAssignedRoles");
b.Property<ulong?>("GenerateCurrencyChannelId");
b.Property<bool>("FilterInvites");
b.Property<bool>("FilterWords");
b.Property<ulong>("GreetMessageChannelId");
@ -236,12 +332,18 @@ namespace NadekoBot.Migrations
b.Property<int?>("LogSettingId");
b.Property<string>("PermissionRole");
b.Property<int?>("RootPermissionId");
b.Property<bool>("SendChannelByeMessage");
b.Property<bool>("SendChannelGreetMessage");
b.Property<bool>("SendDmGreetMessage");
b.Property<bool>("VerbosePermissions");
b.Property<bool>("VoicePlusTextEnabled");
b.HasKey("Id");
@ -251,6 +353,8 @@ namespace NadekoBot.Migrations
b.HasIndex("LogSettingId");
b.HasIndex("RootPermissionId");
b.ToTable("GuildConfigs");
});
@ -307,8 +411,6 @@ namespace NadekoBot.Migrations
b.Property<bool>("MessageDeleted");
b.Property<bool>("MessageReceived");
b.Property<bool>("MessageUpdated");
b.Property<bool>("UserBanned");
@ -335,7 +437,7 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("BotConfigId");
b.Property<int?>("BotConfigId");
b.Property<string>("ModuleName");
@ -348,6 +450,47 @@ namespace NadekoBot.Migrations
b.ToTable("ModulePrefixes");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Author");
b.Property<ulong>("AuthorId");
b.Property<string>("Name");
b.HasKey("Id");
b.ToTable("MusicPlaylists");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("NextId");
b.Property<int>("PrimaryTarget");
b.Property<ulong>("PrimaryTargetId");
b.Property<int>("SecondaryTarget");
b.Property<string>("SecondaryTargetName");
b.Property<bool>("State");
b.HasKey("Id");
b.HasIndex("NextId")
.IsUnique();
b.ToTable("Permission");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b =>
{
b.Property<int>("Id")
@ -364,6 +507,30 @@ namespace NadekoBot.Migrations
b.ToTable("PlayingStatus");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("MusicPlaylistId");
b.Property<string>("Provider");
b.Property<int>("ProviderType");
b.Property<string>("Query");
b.Property<string>("Title");
b.Property<string>("Uri");
b.HasKey("Id");
b.HasIndex("MusicPlaylistId");
b.ToTable("PlaylistSong");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b =>
{
b.Property<int>("Id")
@ -465,20 +632,6 @@ namespace NadekoBot.Migrations
b.ToTable("SelfAssignableRoles");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.TypingArticle", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Author");
b.Property<string>("Text");
b.HasKey("Id");
b.ToTable("TypingArticles");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
@ -494,6 +647,13 @@ namespace NadekoBot.Migrations
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("CommandCooldowns")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
@ -501,6 +661,24 @@ namespace NadekoBot.Migrations
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("FilterInvitesChannelIds")
.HasForeignKey("GuildConfigId");
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("FilterWordsChannelIds")
.HasForeignKey("GuildConfigId1");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("FilteredWords")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
@ -508,11 +686,22 @@ namespace NadekoBot.Migrations
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("GenerateCurrencyChannelIds")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
.WithMany()
.HasForeignKey("LogSettingId");
b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission")
.WithMany()
.HasForeignKey("RootPermissionId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
@ -531,10 +720,16 @@ namespace NadekoBot.Migrations
modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig", "BotConfig")
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("ModulePrefixes")
.HasForeignKey("BotConfigId")
.OnDelete(DeleteBehavior.Cascade);
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next")
.WithOne("Previous")
.HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b =>
@ -544,6 +739,14 @@ namespace NadekoBot.Migrations
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist")
.WithMany("Songs")
.HasForeignKey("MusicPlaylistId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")

View File

@ -13,13 +13,16 @@ using System.Text.RegularExpressions;
using Discord.WebSocket;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using System.Net.Http;
using ImageProcessorCore;
using System.IO;
namespace NadekoBot.Modules.Administration
{
[NadekoModule("Administration", ".")]
public partial class Administration : DiscordModule
{
public Administration(ILocalization loc, CommandService cmds, DiscordSocketClient client) : base(loc, cmds, client)
public Administration(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client)
{
NadekoBot.CommandHandler.CommandExecuted += DelMsgOnCmd_Handler;
}
@ -39,7 +42,7 @@ namespace NadekoBot.Modules.Administration
}
if (shouldDelete)
await e.Message.DeleteAsync();
await e.Message.DeleteAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
@ -47,20 +50,40 @@ namespace NadekoBot.Modules.Administration
}
}
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Restart(IUserMessage umsg)
//{
// var channel = (ITextChannel)umsg.Channel;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.Administrator)]
public async Task ResetPermissions(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
// await channel.SendMessageAsync("`Restarting in 2 seconds...`");
// await Task.Delay(2000);
// System.Diagnostics.Process.Start(System.Reflection.Assembly.GetEntryAssembly().Location);
// Environment.Exit(0);
//}
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.PermissionsFor(channel.Guild.Id);
config.RootPermission = Permission.GetDefaultRoot();
await uow.CompleteAsync();
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
await channel.SendMessageAsync($"{imsg.Author.Mention} :ok: `Permissions for this server are reset`");
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Restart(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
await channel.SendMessageAsync("`Restarting in 2 seconds...`").ConfigureAwait(false);
await Task.Delay(2000).ConfigureAwait(false);
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
Arguments = "dotnet " + System.Reflection.Assembly.GetEntryAssembly().Location
});
Environment.Exit(0);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.Administrator)]
public async Task Delmsgoncmd(IUserMessage umsg)
@ -75,12 +98,12 @@ namespace NadekoBot.Modules.Administration
await uow.CompleteAsync();
}
if (conf.DeleteMessageOnCommand)
await channel.SendMessageAsync("❗`Now automatically deleting successfull command invokations.`");
await channel.SendMessageAsync("❗`Now automatically deleting successfull command invokations.`").ConfigureAwait(false);
else
await channel.SendMessageAsync("❗`Stopped automatic deletion of successfull command invokations.`");
await channel.SendMessageAsync("❗`Stopped automatic deletion of successfull command invokations.`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageRoles)]
public async Task Setrole(IUserMessage umsg, IGuildUser usr, [Remainder] IRole role)
@ -98,7 +121,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageRoles)]
public async Task Removerole(IUserMessage umsg, IGuildUser usr, [Remainder] IRole role)
@ -115,7 +138,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageRoles)]
public async Task RenameRole(IUserMessage umsg, IRole roleToEdit, string newname)
@ -137,7 +160,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageRoles)]
public async Task RemoveAllRoles(IUserMessage umsg, [Remainder] IGuildUser user)
@ -155,7 +178,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageRoles)]
public async Task CreateRole(IUserMessage umsg, [Remainder] string roleName = null)
@ -176,7 +199,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageRoles)]
public async Task RoleColor(IUserMessage umsg, params string[] args)
@ -205,7 +228,7 @@ namespace NadekoBot.Modules.Administration
var green = Convert.ToByte(rgb ? int.Parse(args[2]) : Convert.ToInt32(arg1.Substring(2, 2), 16));
var blue = Convert.ToByte(rgb ? int.Parse(args[3]) : Convert.ToInt32(arg1.Substring(4, 2), 16));
await role.ModifyAsync(r => r.Color = new Color(red, green, blue).RawValue).ConfigureAwait(false);
await role.ModifyAsync(r => r.Color = new Discord.Color(red, green, blue).RawValue).ConfigureAwait(false);
await channel.SendMessageAsync($"Role {role.Name}'s color has been changed.").ConfigureAwait(false);
}
catch (Exception)
@ -214,7 +237,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.BanMembers)]
public async Task Ban(IUserMessage umsg, IGuildUser user)
@ -241,7 +264,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.BanMembers)]
public async Task Softban(IUserMessage umsg, IGuildUser user, [Remainder] string msg = null)
@ -267,8 +290,9 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.KickMembers)]
public async Task Kick(IUserMessage umsg, IGuildUser user, [Remainder] string msg = null)
{
var channel = (ITextChannel)umsg.Channel;
@ -295,7 +319,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.MuteMembers)]
public async Task Mute(IUserMessage umsg, params IGuildUser[] users)
@ -318,7 +342,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.MuteMembers)]
public async Task Unmute(IUserMessage umsg, params IGuildUser[] users)
@ -341,7 +365,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.DeafenMembers)]
public async Task Deafen(IUserMessage umsg, params IGuildUser[] users)
@ -364,7 +388,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.DeafenMembers)]
public async Task UnDeafen(IUserMessage umsg, params IGuildUser[] users)
@ -387,7 +411,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageChannels)]
public async Task DelVoiChanl(IUserMessage umsg, [Remainder] IVoiceChannel voiceChannel)
@ -396,7 +420,7 @@ namespace NadekoBot.Modules.Administration
await umsg.Channel.SendMessageAsync($"Removed channel **{voiceChannel.Name}**.").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageChannels)]
public async Task CreatVoiChanl(IUserMessage umsg, [Remainder] string channelName)
@ -406,7 +430,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync($"Created voice channel **{ch.Name}**, id `{ch.Id}`.").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageChannels)]
public async Task DelTxtChanl(IUserMessage umsg, [Remainder] ITextChannel channel)
@ -415,7 +439,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync($"Removed text channel **{channel.Name}**, id `{channel.Id}`.").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageChannels)]
public async Task CreaTxtChanl(IUserMessage umsg, [Remainder] string channelName)
@ -425,18 +449,18 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync($"Added text channel **{txtCh.Name}**, id `{txtCh.Id}`.").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageChannels)]
public async Task SetTopic(IUserMessage umsg, [Remainder] string topic = null)
{
var channel = (ITextChannel)umsg.Channel;
topic = topic ?? "";
await (channel as ITextChannel).ModifyAsync(c => c.Topic = topic);
await channel.ModifyAsync(c => c.Topic = topic);
await channel.SendMessageAsync(":ok: **New channel topic set.**").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageChannels)]
public async Task SetChanlName(IUserMessage umsg, [Remainder] string name)
@ -449,193 +473,204 @@ namespace NadekoBot.Modules.Administration
//delets her own messages, no perm required
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Prune(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
var user = await channel.Guild.GetCurrentUserAsync();
var user = channel.Guild.GetCurrentUser();
var enumerable = (await umsg.Channel.GetMessagesAsync()).Where(x => x.Author.Id == user.Id);
await umsg.Channel.DeleteMessagesAsync(enumerable);
}
// prune x
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(ChannelPermission.ManageMessages)]
public async Task Prune(IUserMessage msg, int count)
{
var channel = msg.Channel as ITextChannel;
var channel = (ITextChannel)msg.Channel;
await (msg as IUserMessage).DeleteAsync();
while (count > 0)
{
int limit = (count < 100) ? count : 100;
var enumerable = (await msg.Channel.GetMessagesAsync(limit: limit));
await msg.Channel.DeleteMessagesAsync(enumerable);
await Task.Delay(1000); // there is a 1 per second per guild ratelimit for deletemessages
if (enumerable.Count < limit) break;
count -= limit;
}
}
//prune @user [x]
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(ChannelPermission.ManageMessages)]
public async Task Prune(IUserMessage msg, IGuildUser user, int count = 100)
{
var channel = msg.Channel as ITextChannel;
var channel = (ITextChannel)msg.Channel;
int limit = (count < 100) ? count : 100;
var enumerable = (await msg.Channel.GetMessagesAsync(limit: limit)).Where(m => m.Author == user);
await msg.Channel.DeleteMessagesAsync(enumerable);
}
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Die(IUserMessage umsg)
//{
// var channel = (ITextChannel)umsg.Channel;
// await channel.SendMessageAsync("`Shutting down.`").ConfigureAwait(false);
// await Task.Delay(2000).ConfigureAwait(false);
// Environment.Exit(0);
//}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Die(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Setname(IUserMessage umsg, [Remainder] string newName = null)
//{
// var channel = (ITextChannel)umsg.Channel;
try { await channel.SendMessageAsync("`Shutting down.`").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
await Task.Delay(2000).ConfigureAwait(false);
Environment.Exit(0);
}
//}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Setname(IUserMessage umsg, [Remainder] string newName)
{
var channel = (ITextChannel)umsg.Channel;
if (string.IsNullOrWhiteSpace(newName))
return;
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task NewAvatar(IUserMessage umsg, [Remainder] string img = null)
//{
// var channel = (ITextChannel)umsg.Channel;
await NadekoBot.Client.GetCurrentUser().ModifyAsync(u => u.Username = newName).ConfigureAwait(false);
// if (string.IsNullOrWhiteSpace(img))
// return;
// // Gather user provided URL.
// var avatarAddress = img;
// var imageStream = await SearchHelper.GetResponseStreamAsync(avatarAddress).ConfigureAwait(false);
// var image = System.Drawing.Image.FromStream(imageStream);
// await client.CurrentUser.Edit("", avatar: image.ToStream()).ConfigureAwait(false);
await channel.SendMessageAsync($"Successfully changed name to {newName}").ConfigureAwait(false);
}
// // Send confirm.
// await channel.SendMessageAsync("New avatar set.").ConfigureAwait(false);
//}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task SetAvatar(IUserMessage umsg, [Remainder] string img = null)
{
var channel = (ITextChannel)umsg.Channel;
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task SetGame(IUserMessage umsg, [Remainder] string game = null)
//{
// var channel = (ITextChannel)umsg.Channel;
if (string.IsNullOrWhiteSpace(img))
return;
// game = game ?? "";
using (var http = new HttpClient())
{
using (var sr = await http.GetStreamAsync(img))
{
var imgStream = new MemoryStream();
await sr.CopyToAsync(imgStream);
imgStream.Position = 0;
// client.SetGame(set_game);
//}
await NadekoBot.Client.GetCurrentUser().ModifyAsync(u => u.Avatar = imgStream).ConfigureAwait(false);
}
}
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Send(IUserMessage umsg, string where, [Remainder] string msg = null)
//{
// var channel = (ITextChannel)umsg.Channel;
await channel.SendMessageAsync("New avatar set.").ConfigureAwait(false);
}
// if (string.IsNullOrWhiteSpace(msg))
// return;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task SetGame(IUserMessage umsg, [Remainder] string game = null)
{
var channel = (ITextChannel)umsg.Channel;
// var ids = where.Split('|');
// if (ids.Length != 2)
// return;
// var sid = ulong.Parse(ids[0]);
// var server = NadekoBot.Client.Servers.Where(s => s.Id == sid).FirstOrDefault();
game = game ?? "";
// if (server == null)
// return;
await NadekoBot.Client.GetCurrentUser().ModifyStatusAsync(u => u.Game = new Game(game)).ConfigureAwait(false);
// if (ids[1].ToUpperInvariant().StartsWith("C:"))
// {
// var cid = ulong.Parse(ids[1].Substring(2));
// var channel = server.TextChannels.Where(c => c.Id == cid).FirstOrDefault();
// if (channel == null)
// {
// return;
// }
// await channel.SendMessageAsync(msg);
// }
// else if (ids[1].ToUpperInvariant().StartsWith("U:"))
// {
// var uid = ulong.Parse(ids[1].Substring(2));
// var user = server.Users.Where(u => u.Id == uid).FirstOrDefault();
// if (user == null)
// {
// return;
// }
// await user.SendMessageAsync(msg);
// }
// else
// {
// await channel.SendMessageAsync("`Invalid format.`");
// }
//}
await channel.SendMessageAsync("New game set.").ConfigureAwait(false);
}
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Announce(IUserMessage umsg, [Remainder] string message)
//{
// var channel = (ITextChannel)umsg.Channel;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Send(IUserMessage umsg, string where, [Remainder] string msg = null)
{
var channel = (ITextChannel)umsg.Channel;
// foreach (var ch in (await _client.GetGuildsAsync().ConfigureAwait(false)).Select(async g => await g.GetDefaultChannelAsync().ConfigureAwait(false)))
// {
// await channel.SendMessageAsync(message).ConfigureAwait(false);
// }
if (string.IsNullOrWhiteSpace(msg))
return;
// await channel.SendMessageAsync(":ok:").ConfigureAwait(false);
//}
var ids = where.Split('|');
if (ids.Length != 2)
return;
var sid = ulong.Parse(ids[0]);
var server = NadekoBot.Client.GetGuilds().Where(s => s.Id == sid).FirstOrDefault();
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task SaveChat(IUserMessage umsg, int cnt)
//{
// var channel = (ITextChannel)umsg.Channel;
if (server == null)
return;
// ulong? lastmsgId = null;
// var sb = new StringBuilder();
// var msgs = new List<IUserMessage>(cnt);
// while (cnt > 0)
// {
// var dlcnt = cnt < 100 ? cnt : 100;
// IReadOnlyCollection<IUserMessage> dledMsgs;
// if (lastmsgId == null)
// dledMsgs = await umsg.Channel.GetMessagesAsync(cnt).ConfigureAwait(false);
// else
// dledMsgs = await umsg.Channel.GetMessagesAsync(lastmsgId.Value, Direction.Before, dlcnt);
if (ids[1].ToUpperInvariant().StartsWith("C:"))
{
var cid = ulong.Parse(ids[1].Substring(2));
var ch = server.GetTextChannels().Where(c => c.Id == cid).FirstOrDefault();
if (ch == null)
{
return;
}
await ch.SendMessageAsync(msg).ConfigureAwait(false);
}
else if (ids[1].ToUpperInvariant().StartsWith("U:"))
{
var uid = ulong.Parse(ids[1].Substring(2));
var user = server.GetUsers().Where(u => u.Id == uid).FirstOrDefault();
if (user == null)
{
return;
}
await user.SendMessageAsync(msg).ConfigureAwait(false);
}
else
{
await channel.SendMessageAsync("`Invalid format.`").ConfigureAwait(false);
}
}
// if (!dledMsgs.Any())
// break;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Announce(IUserMessage umsg, [Remainder] string message)
{
var channel = (ITextChannel)umsg.Channel;
// msgs.AddRange(dledMsgs);
// lastmsgId = msgs[msgs.Count - 1].Id;
// cnt -= 100;
// }
// var title = $"Chatlog-{channel.Guild.Name}/#{channel.Name}-{DateTime.Now}.txt";
// await (umsg.Author as IGuildUser).SendFileAsync(
// await JsonConvert.SerializeObject(new { Messages = msgs.Select(s => s.ToString()) }, Formatting.Indented).ToStream().ConfigureAwait(false),
// title, title).ConfigureAwait(false);
//}
var channels = await Task.WhenAll(_client.GetGuilds().Select(g =>
g.GetDefaultChannelAsync()
)).ConfigureAwait(false);
await Task.WhenAll(channels.Select(c => c.SendMessageAsync($"`Message from {umsg.Author} (Bot Owner):` " + message)))
.ConfigureAwait(false);
await channel.SendMessageAsync(":ok:").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task SaveChat(IUserMessage umsg, int cnt)
{
var channel = (ITextChannel)umsg.Channel;
ulong? lastmsgId = null;
var sb = new StringBuilder();
var msgs = new List<IMessage>(cnt);
while (cnt > 0)
{
var dlcnt = cnt < 100 ? cnt : 100;
IReadOnlyCollection<IMessage> dledMsgs;
if (lastmsgId == null)
dledMsgs = await umsg.Channel.GetMessagesAsync(cnt).ConfigureAwait(false);
else
dledMsgs = await umsg.Channel.GetMessagesAsync(lastmsgId.Value, Direction.Before, dlcnt);
if (!dledMsgs.Any())
break;
msgs.AddRange(dledMsgs);
lastmsgId = msgs[msgs.Count - 1].Id;
cnt -= 100;
}
var title = $"Chatlog-{channel.Guild.Name}/#{channel.Name}-{DateTime.Now}.txt";
await (umsg.Author as IGuildUser).SendFileAsync(
await JsonConvert.SerializeObject(new { Messages = msgs.Select(s => s.ToString()) }, Formatting.Indented).ToStream().ConfigureAwait(false),
title, title).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.MentionEveryone)]
public async Task MentionRole(IUserMessage umsg, params IRole[] roles)
@ -660,7 +695,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync(send).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Donators(IUserMessage umsg)
{
@ -676,8 +711,9 @@ namespace NadekoBot.Modules.Administration
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Donadd(IUserMessage umsg, IUser donator, int amount)
{
var channel = (ITextChannel)umsg.Channel;

View File

@ -42,7 +42,7 @@ namespace NadekoBot.Modules.Administration
};
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageRoles)]
public async Task AutoAssignRole(IUserMessage umsg, [Remainder] IRole role = null)

View File

@ -3,6 +3,8 @@ using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -18,8 +20,12 @@ namespace NadekoBot.Modules.Administration
{
public CrossServerTextChannel()
{
_log = LogManager.GetCurrentClassLogger();
NadekoBot.Client.MessageReceived += (imsg) =>
{
if (imsg.Author.IsBot)
return Task.CompletedTask;
var msg = imsg as IUserMessage;
if (msg == null)
return Task.CompletedTask;
@ -29,8 +35,6 @@ namespace NadekoBot.Modules.Administration
return Task.CompletedTask;
Task.Run(async () =>
{
try
{
if (msg.Author.Id == NadekoBot.Client.GetCurrentUser().Id) return;
foreach (var subscriber in Subscribers)
@ -40,11 +44,9 @@ namespace NadekoBot.Modules.Administration
continue;
foreach (var chan in set.Except(new[] { channel }))
{
await chan.SendMessageAsync(GetText(channel.Guild, channel, (IGuildUser)msg.Author, msg)).ConfigureAwait(false);
try { await chan.SendMessageAsync(GetText(channel.Guild, channel, (IGuildUser)msg.Author, msg)).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
}
}
}
catch { }
});
return Task.CompletedTask;
};
@ -53,38 +55,39 @@ namespace NadekoBot.Modules.Administration
private string GetText(IGuild server, ITextChannel channel, IGuildUser user, IUserMessage message) =>
$"**{server.Name} | {channel.Name}** `{user.Username}`: " + message.Content;
public static readonly ConcurrentDictionary<int, HashSet<ITextChannel>> Subscribers = new ConcurrentDictionary<int, HashSet<ITextChannel>>();
public static readonly ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>> Subscribers = new ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>>();
private Logger _log { get; }
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Scsc(IUserMessage msg)
//{
// var channel = (ITextChannel)msg.Channel;
// var token = new NadekoRandom().Next();
// var set = new HashSet<ITextChannel>();
// if (Subscribers.TryAdd(token, set))
// {
// set.Add(channel);
// await ((IGuildUser)msg.Author).SendMessageAsync("This is your CSC token:" + token.ToString()).ConfigureAwait(false);
// }
//}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Scsc(IUserMessage msg)
{
var channel = (ITextChannel)msg.Channel;
var token = new NadekoRandom().Next();
var set = new ConcurrentHashSet<ITextChannel>();
if (Subscribers.TryAdd(token, set))
{
set.Add(channel);
await ((IGuildUser)msg.Author).SendMessageAsync("This is your CSC token:" + token.ToString()).ConfigureAwait(false);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageGuild)]
public async Task Jcsc(IUserMessage imsg, int token)
{
var channel = (ITextChannel)imsg.Channel;
HashSet<ITextChannel> set;
ConcurrentHashSet<ITextChannel> set;
if (!Subscribers.TryGetValue(token, out set))
return;
set.Add(channel);
await channel.SendMessageAsync(":ok:").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageGuild)]
public async Task Lcsc(IUserMessage imsg)
@ -93,7 +96,7 @@ namespace NadekoBot.Modules.Administration
foreach (var subscriber in Subscribers)
{
subscriber.Value.Remove(channel);
subscriber.Value.TryRemove(channel);
}
await channel.SendMessageAsync(":ok:").ConfigureAwait(false);
}

View File

@ -0,0 +1,89 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class DMForwardCommands
{
private static bool ForwardDMs { get; set; }
private static bool ForwardDMsToAllOwners { get; set; }
static DMForwardCommands()
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
ForwardDMs = config.ForwardMessages;
ForwardDMsToAllOwners = config.ForwardToAllOwners;
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ForwardMessages(IUserMessage imsg)
{
var channel = imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
ForwardDMs = config.ForwardMessages = !config.ForwardMessages;
uow.Complete();
}
if (ForwardDMs)
await channel.SendMessageAsync("`I will forward DMs from now on.`").ConfigureAwait(false);
else
await channel.SendMessageAsync("`I will stop forwarding DMs.`").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ForwardToAll(IUserMessage imsg)
{
var channel = imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
ForwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners;
uow.Complete();
}
if (ForwardDMsToAllOwners)
await channel.SendMessageAsync("`I will forward DMs to all owners.`").ConfigureAwait(false);
else
await channel.SendMessageAsync("`I will forward DMs only to the first owner.`").ConfigureAwait(false);
}
public static async Task HandleDMForwarding(IMessage msg, List<IDMChannel> ownerChannels)
{
if (ForwardDMs && ownerChannels.Any())
{
var toSend = $"`I received a message from {msg.Author} ({msg.Author.Id})`: {msg.Content}";
if (ForwardDMsToAllOwners)
{
var msgs = await Task.WhenAll(ownerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id)
.Select(ch => ch.SendMessageAsync(toSend))).ConfigureAwait(false);
}
else
{
var firstOwnerChannel = ownerChannels.First();
if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
try { await firstOwnerChannel.SendMessageAsync(msg.Content).ConfigureAwait(false); } catch { }
}
}
}
}
}
}

View File

@ -22,7 +22,7 @@ namespace NadekoBot.Modules.Administration
[Group]
public class LogCommands
{
private DiscordSocketClient _client { get; }
private ShardedDiscordClient _client { get; }
private Logger _log { get; }
private string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】";
@ -33,7 +33,7 @@ namespace NadekoBot.Modules.Administration
private Timer t;
private IGoogleApiService _google { get; }
public LogCommands(DiscordSocketClient client, IGoogleApiService google)
public LogCommands(ShardedDiscordClient client, IGoogleApiService google)
{
_client = client;
_google = google;
@ -60,7 +60,7 @@ namespace NadekoBot.Modules.Administration
}, null, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
_client.MessageReceived += _client_MessageReceived;
//_client.MessageReceived += _client_MessageReceived;
_client.MessageUpdated += _client_MessageUpdated;
_client.MessageDeleted += _client_MessageDeleted;
_client.UserBanned += _client_UserBanned;
@ -168,7 +168,7 @@ namespace NadekoBot.Modules.Administration
var task = Task.Run(async () =>
{
await logChannel.SendMessageAsync($"❗`{prettyCurrentTime}` `{(ch is IVoiceChannel ? "Voice" : "Text")} Channel Deleted:` **#{ch.Name}** ({ch.Id})").ConfigureAwait(false);
try { await logChannel.SendMessageAsync($"❗`{prettyCurrentTime}` `{(ch is IVoiceChannel ? "Voice" : "Text")} Channel Deleted:` **#{ch.Name}** ({ch.Id})").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
});
return Task.CompletedTask;
@ -192,7 +192,7 @@ namespace NadekoBot.Modules.Administration
var task = Task.Run(async () =>
{
await logChannel.SendMessageAsync($"`{prettyCurrentTime}`🆕`{(ch is IVoiceChannel ? "Voice" : "Text")} Channel Created:` **#{ch.Name}** ({ch.Id})").ConfigureAwait(false);
try { await logChannel.SendMessageAsync($"`{prettyCurrentTime}`🆕`{(ch is IVoiceChannel ? "Voice" : "Text")} Channel Created:` **#{ch.Name}** ({ch.Id})").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
});
return Task.CompletedTask;
@ -275,7 +275,7 @@ namespace NadekoBot.Modules.Administration
var task = Task.Run(async () =>
{
await logChannel.SendMessageAsync($"`{prettyCurrentTime}`❗`User left:` **{usr.Username}** ({usr.Id})").ConfigureAwait(false);
try { await logChannel.SendMessageAsync($"`{prettyCurrentTime}`❗`User left:` **{usr.Username}** ({usr.Id})").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
});
return Task.CompletedTask;
@ -295,7 +295,7 @@ namespace NadekoBot.Modules.Administration
var task = Task.Run(async () =>
{
await logChannel.SendMessageAsync($"`{prettyCurrentTime}`❗`User joined:` **{usr.Username}** ({usr.Id})").ConfigureAwait(false);
try { await logChannel.SendMessageAsync($"`{prettyCurrentTime}`❗`User joined:` **{usr.Username}** ({usr.Id})").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
});
return Task.CompletedTask;
@ -315,7 +315,7 @@ namespace NadekoBot.Modules.Administration
var task = Task.Run(async () =>
{
await logChannel.SendMessageAsync($"`{prettyCurrentTime}`♻`User unbanned:` **{usr.Username}** ({usr.Id})").ConfigureAwait(false);
try { await logChannel.SendMessageAsync($"`{prettyCurrentTime}`♻`User unbanned:` **{usr.Username}** ({usr.Id})").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
});
return Task.CompletedTask;
@ -335,7 +335,7 @@ namespace NadekoBot.Modules.Administration
var task = Task.Run(async () =>
{
await logChannel.SendMessageAsync($"❗`{prettyCurrentTime}`❌`User banned:` **{usr.Username}** ({usr.Id})").ConfigureAwait(false);
try { await logChannel.SendMessageAsync($"❗`{prettyCurrentTime}`❌`User banned:` **{usr.Username}** ({usr.Id})").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
});
return Task.CompletedTask;
@ -399,46 +399,46 @@ namespace NadekoBot.Modules.Administration
var task = Task.Run(async () =>
{
await logChannel.SendMessageAsync($@"🕔`{prettyCurrentTime}` **Message** 📝 `#{channel.Name}`
try { await logChannel.SendMessageAsync($@"🕔`{prettyCurrentTime}` **Message** 📝 `#{channel.Name}`
👤`{before.Author.Username}`
`Old:` {before.Resolve(userHandling: UserMentionHandling.NameAndDiscriminator)}
`New:` {after.Resolve(userHandling: UserMentionHandling.NameAndDiscriminator)}").ConfigureAwait(false);
`New:` {after.Resolve(userHandling: UserMentionHandling.NameAndDiscriminator)}").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
});
return Task.CompletedTask;
}
private Task _client_MessageReceived(IMessage imsg)
{
var msg = imsg as IUserMessage;
if (msg == null || msg.IsAuthor())
return Task.CompletedTask;
// private Task _client_MessageReceived(IMessage imsg)
// {
// var msg = imsg as IUserMessage;
// if (msg == null || msg.IsAuthor())
// return Task.CompletedTask;
var channel = msg.Channel as ITextChannel;
if (channel == null)
return Task.CompletedTask;
// var channel = msg.Channel as ITextChannel;
// if (channel == null)
// return Task.CompletedTask;
LogSetting logSetting;
if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out logSetting)
|| !logSetting.IsLogging
|| !logSetting.MessageReceived)
return Task.CompletedTask;
// LogSetting logSetting;
// if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out logSetting)
// || !logSetting.IsLogging
// || !logSetting.MessageReceived)
// return Task.CompletedTask;
ITextChannel logChannel;
if ((logChannel = TryGetLogChannel(channel.Guild, logSetting)) == null || logChannel.Id == imsg.Channel.Id)
return Task.CompletedTask;
// ITextChannel logChannel;
// if ((logChannel = TryGetLogChannel(channel.Guild, logSetting)) == null || logChannel.Id == imsg.Channel.Id)
// return Task.CompletedTask;
var task = Task.Run(async () =>
{
var str = $@"🕔`{prettyCurrentTime}` **New Message** `#{channel.Name}`
👤`{msg.Author.Username}`: {msg.Resolve(userHandling: UserMentionHandling.NameAndDiscriminator)}";
if (msg.Attachments.Any())
str += $"{Environment.NewLine}`Attachements`: {string.Join(", ", msg.Attachments.Select(a => a.ProxyUrl))}";
await logChannel.SendMessageAsync(str).ConfigureAwait(false);
});
// var task = Task.Run(async () =>
// {
// var str = $@"🕔`{prettyCurrentTime}` **New Message** `#{channel.Name}`
//👤`{msg.Author.Username}`: {msg.Resolve(userHandling: UserMentionHandling.NameAndDiscriminator)}";
// if (msg.Attachments.Any())
// str += $"{Environment.NewLine}`Attachements`: {string.Join(", ", msg.Attachments.Select(a => a.ProxyUrl))}";
// await logChannel.SendMessageAsync(str).ConfigureAwait(false);
// });
return Task.CompletedTask;
}
// return Task.CompletedTask;
// }
private enum LogChannelType { Text, Voice, UserPresence };
private ITextChannel TryGetLogChannel(IGuild guild, LogSetting logSetting, LogChannelType logChannelType = LogChannelType.Text)
@ -482,8 +482,10 @@ namespace NadekoBot.Modules.Administration
return channel;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.Administrator)]
[OwnerOnly]
public async Task LogServer(IUserMessage msg)
{
var channel = (ITextChannel)msg.Channel;
@ -504,8 +506,10 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("`Logging disabled.`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.Administrator)]
[OwnerOnly]
public async Task LogIgnore(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
@ -527,40 +531,41 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync($"`Logging will no longer ignore {channel.Name} ({channel.Id}) channel.`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[RequireContext(ContextType.Guild)]
public async Task LogAdd(IUserMessage msg, [Remainder] string eventName)
{
var channel = (ITextChannel)msg.Channel;
//eventName = eventName?.Replace(" ","").ToLowerInvariant();
//[LocalizedCommand, LocalizedRemarks, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//[OwnerOnly]
//public async Task LogAdd(IUserMessage msg, [Remainder] string eventName)
//{
// var channel = (ITextChannel)msg.Channel;
// //eventName = eventName?.Replace(" ","").ToLowerInvariant();
switch (eventName.ToLowerInvariant())
{
case "messagereceived":
case "messageupdated":
case "messagedeleted":
case "userjoined":
case "userleft":
case "userbanned":
case "userunbanned":
case "channelcreated":
case "channeldestroyed":
case "channelupdated":
using (var uow = DbHandler.UnitOfWork())
{
var logSetting = uow.GuildConfigs.For(channel.Guild.Id).LogSetting;
GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting);
var prop = logSetting.GetType().GetProperty(eventName);
prop.SetValue(logSetting, true);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"`Now logging {eventName} event.`").ConfigureAwait(false);
break;
default:
await channel.SendMessageAsync($"`Event \"{eventName}\" not found.`").ConfigureAwait(false);
break;
}
}
// switch (eventName.ToLowerInvariant())
// {
// case "messagereceived":
// case "messageupdated":
// case "messagedeleted":
// case "userjoined":
// case "userleft":
// case "userbanned":
// case "userunbanned":
// case "channelcreated":
// case "channeldestroyed":
// case "channelupdated":
// using (var uow = DbHandler.UnitOfWork())
// {
// var logSetting = uow.GuildConfigs.For(channel.Guild.Id).LogSetting;
// GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting);
// var prop = logSetting.GetType().GetProperty(eventName);
// prop.SetValue(logSetting, true);
// await uow.CompleteAsync().ConfigureAwait(false);
// }
// await channel.SendMessageAsync($"`Now logging {eventName} event.`").ConfigureAwait(false);
// break;
// default:
// await channel.SendMessageAsync($"`Event \"{eventName}\" not found.`").ConfigureAwait(false);
// break;
// }
//}
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
@ -597,8 +602,9 @@ namespace NadekoBot.Modules.Administration
// }
//}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.Administrator)]
public async Task UserPresence(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
@ -619,8 +625,9 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync($"`Stopped logging user presence updates.`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.Administrator)]
public async Task VoicePresence(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;

View File

@ -5,6 +5,7 @@ using NadekoBot.Attributes;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -23,6 +24,8 @@ namespace NadekoBot.Modules.Administration
public class RepeatRunner
{
private Logger _log { get; }
private CancellationTokenSource source { get; set; }
private CancellationToken token { get; set; }
public Repeater Repeater { get; }
@ -30,6 +33,7 @@ namespace NadekoBot.Modules.Administration
public RepeatRunner(Repeater repeater, ITextChannel channel = null)
{
_log = LogManager.GetCurrentClassLogger();
this.Repeater = repeater;
this.Channel = channel ?? NadekoBot.Client.GetGuild(repeater.GuildId)?.GetTextChannel(repeater.ChannelId);
if (Channel == null)
@ -42,12 +46,15 @@ namespace NadekoBot.Modules.Administration
{
source = new CancellationTokenSource();
token = source.Token;
IUserMessage oldMsg = null;
try
{
while (!token.IsCancellationRequested)
{
await Task.Delay(Repeater.Interval, token).ConfigureAwait(false);
await Channel.SendMessageAsync("🔄 " + Repeater.Message).ConfigureAwait(false);
if (oldMsg != null)
try { await oldMsg.DeleteAsync(); } catch { }
try { oldMsg = await Channel.SendMessageAsync("🔄 " + Repeater.Message).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); try { source.Cancel(); } catch { } }
}
}
catch (OperationCanceledException) { }
@ -73,7 +80,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageMessages)]
public async Task RepeatInvoke(IUserMessage imsg)
@ -90,7 +97,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("🔄 " + rep.Repeater.Message).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Repeat(IUserMessage imsg)
{
@ -110,7 +117,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("`No message is repeating.`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Repeat(IUserMessage imsg, int minutes, [Remainder] string message)
{

View File

@ -0,0 +1,426 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using Newtonsoft.Json;
using NLog;
using NadekoBot.Modules.Administration.Commands.Migration;
using System.Collections.Concurrent;
using NadekoBot.Extensions;
using NadekoBot.Services.Database;
using Microsoft.Data.Sqlite;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class Migration
{
private const int CURRENT_VERSION = 1;
private Logger _log { get; }
public Migration()
{
_log = LogManager.GetCurrentClassLogger();
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task MigrateData(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
var version = 0;
using (var uow = DbHandler.UnitOfWork())
{
version = uow.BotConfig.GetOrCreate().MigrationVersion;
}
try
{
for (var i = version; i < CURRENT_VERSION; i++)
{
switch (i)
{
case 0:
Migrate0_9To1_0();
break;
}
}
await umsg.Channel.SendMessageAsync("Migration done.").ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Error(ex);
await umsg.Channel.SendMessageAsync(":warning: Error while migrating, check logs for more informations.").ConfigureAwait(false);
}
}
private void Migrate0_9To1_0()
{
using (var uow = DbHandler.UnitOfWork())
{
var botConfig = uow.BotConfig.GetOrCreate();
MigrateConfig0_9(uow, botConfig);
MigratePermissions0_9(uow);
MigrateServerSpecificConfigs0_9(uow);
MigrateDb0_9(uow);
//NOW save it
botConfig.MigrationVersion = 1;
_log.Warn("Writing to disc");
uow.Complete();
}
}
private void MigrateDb0_9(IUnitOfWork uow)
{
var db = new SqliteConnection("Data Source=data/nadekobot.sqlite");
if (!File.Exists("data/nadekobot.sqlite"))
{
_log.Warn("No data from the old database will be migrated.");
return;
}
db.Open();
var com = db.CreateCommand();
com.CommandText = "SELECT * FROM Announcement";
var reader = com.ExecuteReader();
var i = 0;
while (reader.Read())
{
var gid = (ulong)(long)reader["ServerId"];
var greet = (long)reader["Greet"] == 1;
var greetDM = (long)reader["GreetPM"] == 1;
var greetChannel = (ulong)(long)reader["GreetChannelId"];
var greetMsg = (string)reader["GreetText"];
var bye = (long)reader["Bye"] == 1;
var byeDM = (long)reader["ByePM"] == 1;
var byeChannel = (ulong)(long)reader["ByeChannelId"];
var byeMsg = (string)reader["ByeText"];
var grdel = false;
var byedel = grdel;
var gc = uow.GuildConfigs.For(gid);
if (greetDM)
gc.SendDmGreetMessage = greet;
else
gc.SendChannelGreetMessage = greet;
gc.GreetMessageChannelId = greetChannel;
gc.ChannelGreetMessageText = greetMsg;
gc.SendChannelByeMessage = bye;
gc.ByeMessageChannelId = byeChannel;
gc.ChannelByeMessageText = byeMsg;
gc.AutoDeleteByeMessages = gc.AutoDeleteGreetMessages = grdel;
_log.Info(++i);
}
var com2 = db.CreateCommand();
com.CommandText = "SELECT * FROM CurrencyState";
i = 0;
var reader2 = com.ExecuteReader();
while (reader2.Read())
{
_log.Info(++i);
uow.Currency.Add(new Currency()
{
Amount = (long)reader2["Value"],
UserId = (ulong)(long)reader2["UserId"]
});
}
db.Close();
try { File.Move("data/nadekobot.sqlite", "data/DELETE_ME_nadekobot.sqlite"); } catch { }
}
private void MigrateServerSpecificConfigs0_9(IUnitOfWork uow)
{
const string specificConfigsPath = "data/ServerSpecificConfigs.json";
if (!File.Exists(specificConfigsPath))
{
_log.Warn($"No data from {specificConfigsPath} will be migrated.");
return;
}
var configs = new ConcurrentDictionary<ulong, ServerSpecificConfig>();
try
{
configs = JsonConvert
.DeserializeObject<ConcurrentDictionary<ulong, ServerSpecificConfig>>(
File.ReadAllText(specificConfigsPath), new JsonSerializerSettings()
{
Error = (s, e) =>
{
if (e.ErrorContext.Member.ToString() == "GenerateCurrencyChannels")
{
e.ErrorContext.Handled = true;
}
}
});
}
catch (Exception ex)
{
_log.Warn(ex, "ServerSpecificConfig deserialization failed");
return;
}
var i = 0;
var selfAssRoles = new ConcurrentHashSet<SelfAssignedRole>();
configs
.Select(p => new { data = p.Value, gconfig = uow.GuildConfigs.For(p.Key) })
.AsParallel()
.ForAll(config =>
{
try
{
var guildConfig = config.gconfig;
var data = config.data;
guildConfig.AutoAssignRoleId = data.AutoAssignedRole;
guildConfig.DeleteMessageOnCommand = data.AutoDeleteMessagesOnCommand;
guildConfig.DefaultMusicVolume = data.DefaultMusicVolume;
guildConfig.ExclusiveSelfAssignedRoles = data.ExclusiveSelfAssignedRoles;
guildConfig.GenerateCurrencyChannelIds = new HashSet<GCChannelId>(data.GenerateCurrencyChannels.Select(gc => new GCChannelId() { ChannelId = gc.Key }));
selfAssRoles.AddRange(data.ListOfSelfAssignableRoles.Select(r => new SelfAssignedRole() { GuildId = guildConfig.GuildId, RoleId = r }).ToArray());
var logSetting = guildConfig.LogSetting;
guildConfig.LogSetting.IsLogging = data.LogChannel != null;
guildConfig.LogSetting.ChannelId = data.LogChannel ?? 0;
guildConfig.LogSetting.IgnoredChannels = new HashSet<IgnoredLogChannel>(data.LogserverIgnoreChannels.Select(id => new IgnoredLogChannel() { ChannelId = id }));
guildConfig.LogSetting.LogUserPresence = data.LogPresenceChannel != null;
guildConfig.LogSetting.UserPresenceChannelId = data.LogPresenceChannel ?? 0;
guildConfig.FollowedStreams = new HashSet<FollowedStream>(data.ObservingStreams.Select(x =>
{
FollowedStream.FollowedStreamType type = FollowedStream.FollowedStreamType.Twitch;
switch (x.Type)
{
case StreamNotificationConfig0_9.StreamType.Twitch:
type = FollowedStream.FollowedStreamType.Twitch;
break;
case StreamNotificationConfig0_9.StreamType.Beam:
type = FollowedStream.FollowedStreamType.Beam;
break;
case StreamNotificationConfig0_9.StreamType.Hitbox:
type = FollowedStream.FollowedStreamType.Hitbox;
break;
default:
break;
}
return new FollowedStream()
{
ChannelId = x.ChannelId,
GuildId = guildConfig.GuildId,
Username = x.Username.ToLowerInvariant(),
Type = type
};
}));
guildConfig.VoicePlusTextEnabled = data.VoicePlusTextEnabled;
_log.Info("Migrating SpecificConfig for {0} done ({1})", guildConfig.GuildId, ++i);
}
catch (Exception ex)
{
_log.Error(ex);
}
});
uow.SelfAssignedRoles.AddRange(selfAssRoles.ToArray());
try { File.Move("data/ServerSpecificConfigs.json", "data/DELETE_ME_ServerSpecificCOnfigs.json"); } catch { }
}
private void MigratePermissions0_9(IUnitOfWork uow)
{
var PermissionsDict = new ConcurrentDictionary<ulong, ServerPermissions0_9>();
if (!Directory.Exists("data/permissions/"))
{
_log.Warn("No data from permissions will be migrated.");
return;
}
foreach (var file in Directory.EnumerateFiles("data/permissions/"))
{
try
{
var strippedFileName = Path.GetFileNameWithoutExtension(file);
if (string.IsNullOrWhiteSpace(strippedFileName)) continue;
var id = ulong.Parse(strippedFileName);
var data = JsonConvert.DeserializeObject<ServerPermissions0_9>(File.ReadAllText(file));
PermissionsDict.TryAdd(id, data);
}
catch { }
}
var i = 0;
PermissionsDict
.Select(p => new { data = p.Value, gconfig = uow.GuildConfigs.For(p.Key) })
.AsParallel()
.ForAll(perms =>
{
try
{
var data = perms.data;
var gconfig = perms.gconfig;
gconfig.PermissionRole = data.PermissionsControllerRole;
gconfig.VerbosePermissions = data.Verbose;
gconfig.FilteredWords = new HashSet<FilteredWord>(data.Words.Select(w => w.ToLowerInvariant())
.Distinct()
.Select(w => new FilteredWord() { Word = w }));
gconfig.FilterWords = data.Permissions.FilterWords;
gconfig.FilterInvites = data.Permissions.FilterInvites;
gconfig.FilterInvitesChannelIds = new HashSet<FilterChannelId>();
gconfig.FilterInvitesChannelIds.AddRange(data.ChannelPermissions.Where(kvp => kvp.Value.FilterInvites)
.Select(cp => new FilterChannelId()
{
ChannelId = cp.Key
}));
gconfig.FilterWordsChannelIds = new HashSet<FilterChannelId>();
gconfig.FilterWordsChannelIds.AddRange(data.ChannelPermissions.Where(kvp => kvp.Value.FilterWords)
.Select(cp => new FilterChannelId()
{
ChannelId = cp.Key
}));
gconfig.CommandCooldowns = new HashSet<CommandCooldown>(data.CommandCooldowns
.Where(cc => !string.IsNullOrWhiteSpace(cc.Key) && cc.Value > 0)
.Select(cc => new CommandCooldown()
{
CommandName = cc.Key,
Seconds = cc.Value
}));
_log.Info("Migrating data from permissions folder for {0} done ({1})", gconfig.GuildId, ++i);
}
catch (Exception ex)
{
_log.Error(ex);
}
});
try { Directory.Move("data/permissions", "data/DELETE_ME_permissions"); } catch { }
}
private void MigrateConfig0_9(IUnitOfWork uow, BotConfig botConfig)
{
Config0_9 oldConfig;
const string configPath = "data/config.json";
try
{
oldConfig = JsonConvert.DeserializeObject<Config0_9>(File.ReadAllText(configPath));
}
catch (FileNotFoundException)
{
_log.Warn("config.json not found");
return;
}
catch (Exception)
{
_log.Error("Unknown error while deserializing file config.json, pls check its integrity, aborting migration");
throw new MigrationException();
}
//Basic
botConfig.ForwardMessages = oldConfig.ForwardMessages;
botConfig.ForwardToAllOwners = oldConfig.ForwardToAllOwners;
botConfig.BufferSize = (ulong)oldConfig.BufferSize;
botConfig.RemindMessageFormat = oldConfig.RemindMessageFormat;
botConfig.CurrencySign = oldConfig.CurrencySign;
botConfig.CurrencyName = oldConfig.CurrencyName;
botConfig.DMHelpString = oldConfig.DMHelpString;
botConfig.HelpString = oldConfig.HelpString;
//messages
botConfig.RotatingStatuses = oldConfig.IsRotatingStatus;
var messages = new List<PlayingStatus>();
oldConfig.RotatingStatuses.ForEach(i => messages.Add(new PlayingStatus { Status = i }));
botConfig.RotatingStatusMessages = messages;
//Prefix
botConfig.ModulePrefixes.Clear();
botConfig.ModulePrefixes.AddRange(new HashSet<ModulePrefix>
{
new ModulePrefix()
{
ModuleName = "Administration",
Prefix = oldConfig.CommandPrefixes.Administration
},
new ModulePrefix()
{
ModuleName = "Searches",
Prefix = oldConfig.CommandPrefixes.Searches
},
new ModulePrefix() {ModuleName = "NSFW", Prefix = oldConfig.CommandPrefixes.NSFW},
new ModulePrefix()
{
ModuleName = "Conversations",
Prefix = oldConfig.CommandPrefixes.Conversations
},
new ModulePrefix()
{
ModuleName = "ClashOfClans",
Prefix = oldConfig.CommandPrefixes.ClashOfClans
},
new ModulePrefix() {ModuleName = "Help", Prefix = oldConfig.CommandPrefixes.Help},
new ModulePrefix() {ModuleName = "Music", Prefix = oldConfig.CommandPrefixes.Music},
new ModulePrefix() {ModuleName = "Trello", Prefix = oldConfig.CommandPrefixes.Trello},
new ModulePrefix() {ModuleName = "Games", Prefix = oldConfig.CommandPrefixes.Games},
new ModulePrefix()
{
ModuleName = "Gambling",
Prefix = oldConfig.CommandPrefixes.Gambling
},
new ModulePrefix()
{
ModuleName = "Permissions",
Prefix = oldConfig.CommandPrefixes.Permissions
},
new ModulePrefix()
{
ModuleName = "Programming",
Prefix = oldConfig.CommandPrefixes.Programming
},
new ModulePrefix() {ModuleName = "Pokemon", Prefix = oldConfig.CommandPrefixes.Pokemon},
new ModulePrefix() {ModuleName = "Utility", Prefix = oldConfig.CommandPrefixes.Utility}
});
//Blacklist
var blacklist = new HashSet<BlacklistItem>(oldConfig.ServerBlacklist.Select(server => new BlacklistItem() { ItemId = server, Type = BlacklistItem.BlacklistType.Server }));
blacklist.AddRange(oldConfig.ChannelBlacklist.Select(channel => new BlacklistItem() { ItemId = channel, Type = BlacklistItem.BlacklistType.Channel }));
blacklist.AddRange(oldConfig.UserBlacklist.Select(user => new BlacklistItem() { ItemId = user, Type = BlacklistItem.BlacklistType.User }));
botConfig.Blacklist = blacklist;
//Eightball
botConfig.EightBallResponses = new HashSet<EightBallResponse>(oldConfig._8BallResponses.Select(response => new EightBallResponse() { Text = response }));
//customreactions
uow.CustomReactions.AddRange(oldConfig.CustomReactions.SelectMany(cr =>
{
return cr.Value.Select(res => new CustomReaction()
{
GuildId = 0,
IsRegex = false,
OwnerOnly = false,
Response = res,
Trigger = cr.Key,
});
}).ToArray());
try { File.Move(configPath, "./data/DELETE_ME_config.json"); } catch { }
}
}
}
}

View File

@ -0,0 +1,198 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration.Commands.Migration
{
public class CommandPrefixes0_9
{
public string Administration { get; set; }
public string Searches { get; set; }
public string NSFW { get; set; }
public string Conversations { get; set; }
public string ClashOfClans { get; set; }
public string Help { get; set; }
public string Music { get; set; }
public string Trello { get; set; }
public string Games { get; set; }
public string Gambling { get; set; }
public string Permissions { get; set; }
public string Programming { get; set; }
public string Pokemon { get; set; }
public string Utility { get; set; }
}
public class Config0_9
{
public bool DontJoinServers { get; set; }
public bool ForwardMessages { get; set; }
public bool ForwardToAllOwners { get; set; }
public bool IsRotatingStatus { get; set; }
public int BufferSize { get; set; }
public List<string> RaceAnimals { get; set; }
public string RemindMessageFormat { get; set; }
public Dictionary<string, List<string>> CustomReactions { get; set; }
public List<string> RotatingStatuses { get; set; }
public CommandPrefixes0_9 CommandPrefixes { get; set; }
public List<ulong> ServerBlacklist { get; set; }
public List<ulong> ChannelBlacklist { get; set; }
public List<ulong> UserBlacklist { get; set; }
public List<string> _8BallResponses { get; set; }
public string CurrencySign { get; set; }
public string CurrencyName { get; set; }
public string DMHelpString { get; set; }
public string HelpString { get; set; }
}
/// <summary>
/// Holds a permission list
/// </summary>
public class Permissions
{
/// <summary>
/// Name of the parent object whose permissions these are
/// </summary>
public string Name { get; set; }
/// <summary>
/// Module name with allowed/disallowed
/// </summary>
public ConcurrentDictionary<string, bool> Modules { get; set; }
/// <summary>
/// Command name with allowed/disallowed
/// </summary>
public ConcurrentDictionary<string, bool> Commands { get; set; }
/// <summary>
/// Should the bot filter invites to other discord servers (and ref links in the future)
/// </summary>
public bool FilterInvites { get; set; }
/// <summary>
/// Should the bot filter words which are specified in the Words hashset
/// </summary>
public bool FilterWords { get; set; }
public Permissions(string name)
{
Name = name;
Modules = new ConcurrentDictionary<string, bool>();
Commands = new ConcurrentDictionary<string, bool>();
FilterInvites = false;
FilterWords = false;
}
public void CopyFrom(Permissions other)
{
Modules.Clear();
foreach (var mp in other.Modules)
Modules.AddOrUpdate(mp.Key, mp.Value, (s, b) => mp.Value);
Commands.Clear();
foreach (var cp in other.Commands)
Commands.AddOrUpdate(cp.Key, cp.Value, (s, b) => cp.Value);
FilterInvites = other.FilterInvites;
FilterWords = other.FilterWords;
}
public override string ToString()
{
var toReturn = "";
var bannedModules = Modules.Where(kvp => kvp.Value == false);
var bannedModulesArray = bannedModules as KeyValuePair<string, bool>[] ?? bannedModules.ToArray();
if (bannedModulesArray.Any())
{
toReturn += "`Banned Modules:`\n";
toReturn = bannedModulesArray.Aggregate(toReturn, (current, m) => current + $"\t`[x] {m.Key}`\n");
}
var bannedCommands = Commands.Where(kvp => kvp.Value == false);
var bannedCommandsArr = bannedCommands as KeyValuePair<string, bool>[] ?? bannedCommands.ToArray();
if (bannedCommandsArr.Any())
{
toReturn += "`Banned Commands:`\n";
toReturn = bannedCommandsArr.Aggregate(toReturn, (current, c) => current + $"\t`[x] {c.Key}`\n");
}
return toReturn;
}
}
public class ServerPermissions0_9
{
/// <summary>
/// The guy who can edit the permissions
/// </summary>
public string PermissionsControllerRole { get; set; }
/// <summary>
/// Does it print the error when a restriction occurs
/// </summary>
public bool Verbose { get; set; }
/// <summary>
/// The id of the thing (user/server/channel)
/// </summary>
public ulong Id { get; set; } //a string because of the role name.
/// <summary>
/// Permission object bound to the id of something/role name
/// </summary>
public Permissions Permissions { get; set; }
/// <summary>
/// Banned words, usually profanities, like word "java"
/// </summary>
public HashSet<string> Words { get; set; }
public Dictionary<ulong, Permissions> UserPermissions { get; set; }
public Dictionary<ulong, Permissions> ChannelPermissions { get; set; }
public Dictionary<ulong, Permissions> RolePermissions { get; set; }
/// <summary>
/// Dictionary of command names with their respective cooldowns
/// </summary>
public ConcurrentDictionary<string, int> CommandCooldowns { get; set; }
public ServerPermissions0_9(ulong id, string name)
{
Id = id;
PermissionsControllerRole = "Nadeko";
Verbose = true;
Permissions = new Permissions(name);
Permissions.Modules.TryAdd("NSFW", false);
UserPermissions = new Dictionary<ulong, Permissions>();
ChannelPermissions = new Dictionary<ulong, Permissions>();
RolePermissions = new Dictionary<ulong, Permissions>();
CommandCooldowns = new ConcurrentDictionary<string, int>();
Words = new HashSet<string>();
}
}
internal class ServerSpecificConfig
{
public bool VoicePlusTextEnabled { get; set; }
public bool SendPrivateMessageOnMention { get; set; }
public ulong? LogChannel { get; set; } = null;
public ulong? LogPresenceChannel { get; set; } = null;
public HashSet<ulong> LogserverIgnoreChannels { get; set; }
public ConcurrentDictionary<ulong, ulong> VoiceChannelLog { get; set; }
public HashSet<ulong> ListOfSelfAssignableRoles { get; set; }
public ulong AutoAssignedRole { get; set; }
public ConcurrentDictionary<ulong, int> GenerateCurrencyChannels { get; set; }
public bool AutoDeleteMessagesOnCommand { get; set; }
public bool ExclusiveSelfAssignedRoles { get; set; }
public float DefaultMusicVolume { get; set; }
public HashSet<StreamNotificationConfig0_9> ObservingStreams { get; set; }
}
public class StreamNotificationConfig0_9
{
public string Username { get; set; }
public StreamType Type { get; set; }
public ulong ServerId { get; set; }
public ulong ChannelId { get; set; }
public bool LastStatus { get; set; }
public enum StreamType
{
Twitch,
Beam,
Hitbox,
YoutubeGaming
}
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration.Commands.Migration
{
public class MigrationException : Exception
{
}
}

View File

@ -13,7 +13,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
//todo owner only
namespace NadekoBot.Modules.Administration
{
public partial class Administration
@ -86,8 +85,9 @@ namespace NadekoBot.Modules.Administration
{"%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()}
};
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task RotatePlaying(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
@ -106,8 +106,9 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("`Rotating playing status disabled.`");
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task AddPlaying(IUserMessage umsg, [Remainder] string status)
{
var channel = (ITextChannel)umsg.Channel;
@ -122,8 +123,9 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("`Added.`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task ListPlaying(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
@ -144,8 +146,9 @@ namespace NadekoBot.Modules.Administration
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task RemovePlaying(IUserMessage umsg, int index)
{
var channel = (ITextChannel)umsg.Channel;

View File

@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Administration
{
public static ConcurrentDictionary<ulong, Ratelimiter> RatelimitingChannels = new ConcurrentDictionary<ulong, Ratelimiter>();
private DiscordSocketClient _client { get; }
private ShardedDiscordClient _client { get; }
public class Ratelimiter
{
@ -84,8 +84,9 @@ namespace NadekoBot.Modules.Administration
};
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageMessages)]
public async Task Slowmode(IUserMessage umsg, int msg = 1, int perSec = 5)
{
var channel = (ITextChannel)umsg.Channel;

View File

@ -6,6 +6,7 @@ using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -19,7 +20,7 @@ namespace NadekoBot.Modules.Administration
public class SelfAssignedRolesCommands
{
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageRoles)]
public async Task Asar(IUserMessage umsg, [Remainder] IRole role)
@ -49,7 +50,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync(msg.ToString()).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageRoles)]
public async Task Rsar(IUserMessage umsg, [Remainder] IRole role)
@ -70,13 +71,13 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync($":ok:**{role.Name}** has been removed from the list of self-assignable roles").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Lsar(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
var toRemove = new HashSet<SelfAssignedRole>();
var toRemove = new ConcurrentHashSet<SelfAssignedRole>();
var removeMsg = new StringBuilder();
var msg = new StringBuilder();
using (var uow = DbHandler.UnitOfWork())
@ -105,7 +106,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync(msg.ToString() + "\n\n" + removeMsg.ToString()).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageRoles)]
public async Task Tesar(IUserMessage umsg)
@ -124,7 +125,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("Self assigned roles are now " + exl);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Iam(IUserMessage umsg, [Remainder] IRole role)
{
@ -182,7 +183,7 @@ namespace NadekoBot.Modules.Administration
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Iamnot(IUserMessage umsg, [Remainder] IRole role)
{

View File

@ -1,50 +1,51 @@
//using Discord;
//using Discord.Commands;
//using Discord.WebSocket;
//using NadekoBot.Attributes;
//using System.Linq;
//using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using System.Linq;
using System.Threading.Tasks;
////todo owner only
//namespace NadekoBot.Modules.Administration
//{
// public partial class Administration
// {
// [Group]
// class SelfCommands
// {
// private DiscordSocketClient _client;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
class SelfCommands
{
private ShardedDiscordClient _client;
// public SelfCommands(DiscordSocketClient client)
// {
// this._client = client;
// }
public SelfCommands(ShardedDiscordClient client)
{
this._client = client;
}
// [LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
// [RequireContext(ContextType.Guild)]
// public async Task Leave(IUserMessage umsg, [Remainder] string guildStr)
// {
// var channel = (ITextChannel)umsg.Channel;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Leave(IUserMessage umsg, [Remainder] string guildStr)
{
var channel = (ITextChannel)umsg.Channel;
// guildStr = guildStr.ToUpperInvariant();
// var server = _client.GetGuilds().FirstOrDefault(g => g.Id.ToString() == guildStr) ?? _client.GetGuilds().FirstOrDefault(g => g.Name.ToUpperInvariant() == guildStr);
guildStr = guildStr.Trim().ToUpperInvariant();
var server = _client.GetGuilds().FirstOrDefault(g => g.Id.ToString().Trim().ToUpperInvariant() == guildStr) ??
_client.GetGuilds().FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == guildStr);
// if (server == null)
// {
// await channel.SendMessageAsync("Cannot find that server").ConfigureAwait(false);
// return;
// }
// if (server.OwnerId != _client.GetCurrentUser().Id)
// {
// await server.LeaveAsync().ConfigureAwait(false);
// await channel.SendMessageAsync("Left server " + server.Name).ConfigureAwait(false);
// }
// else
// {
// await server.DeleteAsync().ConfigureAwait(false);
// await channel.SendMessageAsync("Deleted server " + server.Name).ConfigureAwait(false);
// }
// }
// }
// }
//}
if (server == null)
{
await channel.SendMessageAsync("Cannot find that server").ConfigureAwait(false);
return;
}
if (server.OwnerId != _client.GetCurrentUser().Id)
{
await server.LeaveAsync().ConfigureAwait(false);
await channel.SendMessageAsync("Left server " + server.Name).ConfigureAwait(false);
}
else
{
await server.DeleteAsync().ConfigureAwait(false);
await channel.SendMessageAsync("Deleted server " + server.Name).ConfigureAwait(false);
}
}
}
}
}

View File

@ -1,9 +1,11 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
@ -41,11 +43,12 @@ namespace NadekoBot.Modules.Administration
if (channel == null) //maybe warn the server owner that the channel is missing
return;
var msg = conf.ChannelByeMessageText.Replace("%user%", "**" + user.Username + "**");
var msg = conf.ChannelByeMessageText.Replace("%user%", user.Username + $" ({user.Id})").Replace("%server%", user.Guild.Name);
if (string.IsNullOrWhiteSpace(msg))
return;
var toDelete = await channel.SendMessageAsync(msg).ConfigureAwait(false);
try
{
var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false);
if (conf.AutoDeleteByeMessages)
{
var t = Task.Run(async () =>
@ -54,6 +57,8 @@ namespace NadekoBot.Modules.Administration
await toDelete.DeleteAsync().ConfigureAwait(false);
});
}
}
catch (Exception ex) { _log.Warn(ex); }
});
return Task.CompletedTask;
}
@ -73,10 +78,12 @@ namespace NadekoBot.Modules.Administration
var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.GreetMessageChannelId);
if (channel != null) //maybe warn the server owner that the channel is missing
{
var msg = conf.ChannelGreetMessageText.Replace("%user%", user.Username).Replace("%server%", user.Guild.Name);
var msg = conf.ChannelGreetMessageText.Replace("%user%", user.Mention).Replace("%server%", user.Guild.Name);
if (!string.IsNullOrWhiteSpace(msg))
{
var toDelete = await channel.SendMessageAsync(msg).ConfigureAwait(false);
try
{
var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false);
if (conf.AutoDeleteGreetMessages)
{
var t = Task.Run(async () =>
@ -86,6 +93,8 @@ namespace NadekoBot.Modules.Administration
});
}
}
catch (Exception ex) { _log.Warn(ex); }
}
}
}
@ -106,7 +115,7 @@ namespace NadekoBot.Modules.Administration
return Task.CompletedTask;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageGuild)]
public async Task GreetDel(IUserMessage umsg)
@ -128,7 +137,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("`Automatic deletion of greet messages has been disabled.`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageGuild)]
public async Task Greet(IUserMessage umsg)
@ -151,10 +160,10 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("Greet announcements disabled.").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageGuild)]
public async Task GreetMsg(IUserMessage umsg, [Remainder] string text)
public async Task GreetMsg(IUserMessage umsg, [Remainder] string text = null)
{
var channel = (ITextChannel)umsg.Channel;
@ -164,7 +173,7 @@ namespace NadekoBot.Modules.Administration
conf = uow.GuildConfigs.For(channel.Guild.Id);
if (!string.IsNullOrWhiteSpace(text))
{
conf.ChannelGreetMessageText = text;
conf.ChannelGreetMessageText = text.SanitizeMentions();
uow.GuildConfigs.Update(conf);
await uow.CompleteAsync();
}
@ -172,7 +181,7 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(text))
{
await channel.SendMessageAsync("`Current greet message:` " + conf.ChannelGreetMessageText);
await channel.SendMessageAsync("`Current greet message:` " + conf.ChannelGreetMessageText.SanitizeMentions());
return;
}
await channel.SendMessageAsync("New greet message set.").ConfigureAwait(false);
@ -180,7 +189,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("Enable greet messsages by typing `.greet`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageGuild)]
public async Task GreetDm(IUserMessage umsg)
@ -202,10 +211,10 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("Greet announcements disabled.").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageGuild)]
public async Task GreetDmMsg(IUserMessage umsg, [Remainder] string text)
public async Task GreetDmMsg(IUserMessage umsg, [Remainder] string text = null)
{
var channel = (ITextChannel)umsg.Channel;
@ -231,7 +240,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("Enable DM greet messsages by typing `.greetdm`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageGuild)]
public async Task Bye(IUserMessage umsg)
@ -254,10 +263,10 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("Bye announcements disabled.").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageGuild)]
public async Task ByeMsg(IUserMessage umsg, [Remainder] string text)
public async Task ByeMsg(IUserMessage umsg, [Remainder] string text = null)
{
var channel = (ITextChannel)umsg.Channel;
@ -267,7 +276,7 @@ namespace NadekoBot.Modules.Administration
conf = uow.GuildConfigs.For(channel.Guild.Id);
if (!string.IsNullOrWhiteSpace(text))
{
conf.ChannelByeMessageText = text;
conf.ChannelByeMessageText = text.SanitizeMentions();
uow.GuildConfigs.Update(conf);
await uow.CompleteAsync();
}
@ -275,7 +284,7 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(text))
{
await channel.SendMessageAsync("`Current bye message:` " + conf.ChannelGreetMessageText);
await channel.SendMessageAsync("`Current bye message:` " + conf.ChannelByeMessageText.SanitizeMentions());
return;
}
await channel.SendMessageAsync("New bye message set.").ConfigureAwait(false);
@ -283,7 +292,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync("Enable bye messsages by typing `.bye`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageGuild)]
public async Task ByeDel(IUserMessage umsg)

View File

@ -97,7 +97,7 @@ namespace NadekoBot.Modules.Administration
private string GetChannelName(string voiceName) =>
channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice";
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageRoles)]
[RequirePermission(GuildPermission.ManageChannels)]
@ -138,7 +138,7 @@ namespace NadekoBot.Modules.Administration
await channel.SendMessageAsync(ex.ToString()).ConfigureAwait(false);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageChannels)]
[RequirePermission(GuildPermission.ManageRoles)]
@ -148,7 +148,7 @@ namespace NadekoBot.Modules.Administration
var guild = channel.Guild;
if (!guild.GetCurrentUser().GuildPermissions.ManageChannels)
{
await channel.SendMessageAsync("`I have insufficient permission to do that.`");
await channel.SendMessageAsync("`I have insufficient permission to do that.`").ConfigureAwait(false);
return;
}
@ -163,7 +163,7 @@ namespace NadekoBot.Modules.Administration
await Task.Delay(500);
}
await channel.SendMessageAsync("`Done.`");
await channel.SendMessageAsync("`Done.`").ConfigureAwait(false);
}
}
}

View File

@ -19,25 +19,28 @@ namespace NadekoBot.Modules.ClashOfClans
{
public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; } = new ConcurrentDictionary<ulong, List<ClashWar>>();
public ClashOfClans(ILocalization loc, CommandService cmds, DiscordSocketClient client) : base(loc, cmds, client)
static ClashOfClans()
{
using (var uow = DbHandler.UnitOfWork())
{
ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>(
uow.ClashOfClans
.GetAll()
.Select(cw => {
cw.Channel = NadekoBot.Client.GetGuilds()
.FirstOrDefault(s => s.Id == cw.GuildId)?
.GetChannels()
.FirstOrDefault(c => c.Id == cw.ChannelId)
as ITextChannel;
cw.Bases.Capacity = cw.Size;
return cw;
})
.GroupBy(cw => cw.GuildId)
.ToDictionary(g => g.Key, g => g.ToList()));
//using (var uow = DbHandler.UnitOfWork())
//{
// ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>(
// uow.ClashOfClans
// .GetAllWars()
// .Select(cw => {
// if (cw == null || cw.Bases == null)
// return null;
// cw.Channel = NadekoBot.Client.GetGuild(cw.GuildId)
// ?.GetTextChannel(cw.ChannelId);
// cw.Bases.Capacity = cw.Size;
// return cw;
// })
// .Where(cw => cw?.Channel != null)
// .GroupBy(cw => cw.GuildId)
// .ToDictionary(g => g.Key, g => g.ToList()));
//}
}
public ClashOfClans(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client)
{
}
private static async Task CheckWar(TimeSpan callExpire, ClashWar war)
@ -48,13 +51,13 @@ namespace NadekoBot.Modules.ClashOfClans
if (Bases[i] == null) continue;
if (!Bases[i].BaseDestroyed && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire)
{
await war.Channel.SendMessageAsync($"❗🔰**Claim from @{Bases[i].CallUser} for a war against {war.ShortPrint()} has expired.**").ConfigureAwait(false);
Bases[i] = null;
try { await war.Channel.SendMessageAsync($"❗🔰**Claim from @{Bases[i].CallUser} for a war against {war.ShortPrint()} has expired.**").ConfigureAwait(false); } catch { }
}
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task CreateWar(IUserMessage umsg, int size, [Remainder] string enemyClan = null)
{
@ -87,7 +90,7 @@ namespace NadekoBot.Modules.ClashOfClans
await channel.SendMessageAsync($"❗🔰**CREATED CLAN WAR AGAINST {cw.ShortPrint()}**").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task StartWar(IUserMessage umsg, [Remainder] string number = null)
{
@ -115,7 +118,7 @@ namespace NadekoBot.Modules.ClashOfClans
SaveWar(war);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ListWar(IUserMessage umsg, [Remainder] string number = null)
{
@ -158,7 +161,7 @@ namespace NadekoBot.Modules.ClashOfClans
await channel.SendMessageAsync(warsInfo.Item1[warsInfo.Item2].ToPrettyString()).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Claim(IUserMessage umsg, int number, int baseNumber, [Remainder] string other_name = null)
{
@ -186,7 +189,7 @@ namespace NadekoBot.Modules.ClashOfClans
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ClaimFinish1(IUserMessage umsg, int number, int baseNumber, [Remainder] string other_name = null)
{
@ -194,7 +197,7 @@ namespace NadekoBot.Modules.ClashOfClans
await FinishClaim(umsg, number, baseNumber, other_name, 1);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ClaimFinish2(IUserMessage umsg, int number, int baseNumber, [Remainder] string other_name = null)
{
@ -202,7 +205,7 @@ namespace NadekoBot.Modules.ClashOfClans
await FinishClaim(umsg, number, baseNumber, other_name, 2);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ClaimFinish(IUserMessage umsg, int number, int baseNumber, [Remainder] string other_name = null)
{
@ -210,7 +213,7 @@ namespace NadekoBot.Modules.ClashOfClans
await FinishClaim(umsg, number, baseNumber, other_name);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task EndWar(IUserMessage umsg, int number)
{
@ -231,7 +234,7 @@ namespace NadekoBot.Modules.ClashOfClans
warsInfo.Item1.RemoveAt(warsInfo.Item2);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Unclaim(IUserMessage umsg, int number, [Remainder] string otherName = null)
{
@ -318,11 +321,8 @@ namespace NadekoBot.Modules.ClashOfClans
Bases = new List<ClashCaller>(size),
GuildId = serverId,
ChannelId = channelId,
Channel = NadekoBot.Client.GetGuilds()
.FirstOrDefault(s => s.Id == serverId)?
.GetChannels()
.FirstOrDefault(c => c.Id == channelId)
as ITextChannel
Channel = NadekoBot.Client.GetGuild(serverId)
?.GetTextChannel(channelId)
};
uow.ClashOfClans.Add(cw);
await uow.CompleteAsync();

View File

@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using NadekoBot.Services;
using NadekoBot.Attributes;
using NadekoBot.Services.Database;
using System.Collections.Concurrent;
using NadekoBot.Services.Database.Models;
using Discord;
using NadekoBot.Extensions;
namespace NadekoBot.Modules.CustomReactions
{
[NadekoModule("CustomReactions",".")]
public class CustomReactions : DiscordModule
{
public static ConcurrentHashSet<CustomReaction> GlobalReactions { get; } = new ConcurrentHashSet<CustomReaction>();
public static ConcurrentDictionary<ulong, ConcurrentHashSet<CustomReaction>> GuildReactions { get; } = new ConcurrentDictionary<ulong, ConcurrentHashSet<CustomReaction>>();
static CustomReactions()
{
using (var uow = DbHandler.UnitOfWork())
{
var items = uow.CustomReactions.GetAll();
GuildReactions = new ConcurrentDictionary<ulong, ConcurrentHashSet<CustomReaction>>(items.Where(g => g.GuildId != null).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => new ConcurrentHashSet<CustomReaction>(g)));
GlobalReactions = new ConcurrentHashSet<CustomReaction>(items.Where(g => g.GuildId == null));
}
}
public CustomReactions(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client)
{
client.MessageReceived += (imsg) =>
{
var umsg = imsg as IUserMessage;
if (umsg == null)
return Task.CompletedTask;
var channel = umsg.Channel as ITextChannel;
if (channel == null)
return Task.CompletedTask;
var t = Task.Run(async () =>
{
var content = umsg.Content.ToLowerInvariant();
ConcurrentHashSet<CustomReaction> reactions;
GuildReactions.TryGetValue(channel.Guild.Id, out reactions);
if (reactions != null && reactions.Any())
{
var reaction = reactions.Where(cr => cr.TriggerWithContext(umsg) == content).Shuffle().FirstOrDefault();
if (reaction != null)
{
try { await channel.SendMessageAsync(reaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { }
return;
}
}
var greaction = GlobalReactions.Where(cr => cr.TriggerWithContext(umsg) == content).Shuffle().FirstOrDefault();
if (greaction != null)
{
try { await channel.SendMessageAsync(greaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { }
return;
}
});
return Task.CompletedTask;
};
}
[NadekoCommand, Usage, Description, Aliases]
public async Task AddCustReact(IUserMessage imsg, string key, [Remainder] string message)
{
var channel = imsg.Channel as ITextChannel;
if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(key))
return;
key = key.ToLowerInvariant();
if ((channel == null && !NadekoBot.Credentials.IsOwner(imsg.Author)) || (channel != null && !((IGuildUser)imsg.Author).GuildPermissions.Administrator))
{
try { await imsg.Channel.SendMessageAsync("Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for guild custom reactions."); } catch { }
return;
}
var cr = new CustomReaction()
{
GuildId = channel?.Guild.Id,
IsRegex = false,
Trigger = key,
Response = message,
};
using (var uow = DbHandler.UnitOfWork())
{
uow.CustomReactions.Add(cr);
await uow.CompleteAsync().ConfigureAwait(false);
}
if (channel == null)
{
GlobalReactions.Add(cr);
}
else
{
var reactions = GuildReactions.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CustomReaction>());
reactions.Add(cr);
}
await imsg.Channel.SendMessageAsync($"`Added new custom reaction:`\n\t`Trigger:` {key}\n\t`Response:` {message}").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ListCustReact(IUserMessage imsg,int page = 1)
{
var channel = imsg.Channel as ITextChannel;
if (page < 1 || page > 1000)
return;
ConcurrentHashSet<CustomReaction> customReactions;
if (channel == null)
customReactions = GlobalReactions;
else
customReactions = GuildReactions.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CustomReaction>());
if (customReactions == null || !customReactions.Any())
await imsg.Channel.SendMessageAsync("`No custom reactions found`").ConfigureAwait(false);
else
await imsg.Channel.SendMessageAsync(string.Join("\n", customReactions.OrderBy(cr => cr.Trigger).Skip((page - 1) * 10).Take(10)))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task DelCustReact(IUserMessage imsg, int id)
{
var channel = imsg.Channel as ITextChannel;
if ((channel == null && !NadekoBot.Credentials.IsOwner(imsg.Author)) || (channel != null && !((IGuildUser)imsg.Author).GuildPermissions.Administrator))
{
try { await imsg.Channel.SendMessageAsync("Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for guild custom reactions."); } catch { }
return;
}
var success = false;
CustomReaction toDelete;
using (var uow = DbHandler.UnitOfWork())
{
toDelete = uow.CustomReactions.Get(id);
if (toDelete == null) //not found
return;
if (toDelete.GuildId == null && channel == null)
{
uow.CustomReactions.Remove(toDelete);
GlobalReactions.RemoveWhere(cr => cr.Id == toDelete.Id);
success = true;
}
else if (toDelete.GuildId != null && channel?.Guild.Id == toDelete.GuildId)
{
uow.CustomReactions.Remove(toDelete);
GuildReactions.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CustomReaction>()).RemoveWhere(cr=>cr.Id == toDelete.Id);
success = true;
}
if(success)
await uow.CompleteAsync().ConfigureAwait(false);
}
if (success)
await imsg.Channel.SendMessageAsync("**Successfully deleted custom reaction** " + toDelete.ToString()).ConfigureAwait(false);
else
await imsg.Channel.SendMessageAsync("`Failed to find that custom reaction.`").ConfigureAwait(false);
}
}
}

View File

@ -0,0 +1,38 @@
using Discord;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.CustomReactions
{
public static class Extensions
{
public static Dictionary<string, Func<IUserMessage, string>> placeholders = new Dictionary<string, Func<IUserMessage, string>>()
{
{"%mention%", (ctx) => { return $"<@{NadekoBot.Client.GetCurrentUser().Id}>"; } },
{"%user%", (ctx) => { return ctx.Author.Mention; } },
{"%target%", (ctx) => { return ctx.MentionedUsers.Shuffle().FirstOrDefault()?.Mention ?? "Nobody"; } },
{"%rng%", (ctx) => { return new NadekoRandom().Next(0,10).ToString(); } }
};
private static string ResolveCRString(this string str, IUserMessage ctx)
{
foreach (var ph in placeholders)
{
str = str.Replace(ph.Key, ph.Value(ctx));
}
return str;
}
public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx)
=> cr.Trigger.ResolveCRString(ctx);
public static string ResponseWithContext(this CustomReaction cr, IUserMessage ctx)
=> cr.Response.ResolveCRString(ctx);
}
}

View File

@ -10,11 +10,18 @@ namespace NadekoBot.Modules
{
protected ILocalization _l { get; }
protected CommandService _commands { get; }
protected DiscordSocketClient _client { get; }
protected ShardedDiscordClient _client { get; }
protected Logger _log { get; }
private string _prefix { get; }
public DiscordModule(ILocalization loc, CommandService cmds, DiscordSocketClient client)
public DiscordModule(ILocalization loc, CommandService cmds, ShardedDiscordClient client)
{
string prefix;
if (NadekoBot.ModulePrefixes.TryGetValue(this.GetType().Name, out prefix))
_prefix = prefix;
else
_prefix = "?missing_prefix?";
_l = loc;
_commands = cmds;
_client = client;

View File

@ -4,6 +4,7 @@ using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -18,9 +19,13 @@ namespace NadekoBot.Modules.Gambling
[Group]
public class AnimalRacing
{
public AnimalRacing()
{
}
public static ConcurrentDictionary<ulong, AnimalRace> AnimalRaces = new ConcurrentDictionary<ulong, AnimalRace>();
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Race(IUserMessage umsg)
{
@ -32,7 +37,7 @@ namespace NadekoBot.Modules.Gambling
await channel.SendMessageAsync("🏁 `Failed starting a race. Another race is probably running.`");
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task JoinRace(IUserMessage umsg, int amount = 0)
{
@ -43,7 +48,10 @@ namespace NadekoBot.Modules.Gambling
if (amount > 0)
if (!await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)umsg.Author, "BetRace", amount, true).ConfigureAwait(false))
await channel.SendMessageAsync($"{umsg.Author.Mention} You don't have enough {Gambling.CurrencyName}s.").ConfigureAwait(false);
{
try { await channel.SendMessageAsync($"{umsg.Author.Mention} You don't have enough {Gambling.CurrencyName}s.").ConfigureAwait(false); } catch { }
return;
}
AnimalRace ar;
@ -65,12 +73,14 @@ namespace NadekoBot.Modules.Gambling
public List<Participant> participants = new List<Participant>();
private ulong serverId;
private int messagesSinceGameStarted = 0;
private Logger _log { get; }
public ITextChannel raceChannel { get; set; }
public bool Started { get; private set; } = false;
public AnimalRace(ulong serverId, ITextChannel ch)
{
this._log = LogManager.GetCurrentClassLogger();
this.serverId = serverId;
this.raceChannel = ch;
if (!AnimalRaces.TryAdd(serverId, this))
@ -92,22 +102,21 @@ namespace NadekoBot.Modules.Gambling
{
try
{
//todo Commmand prefixes from config
await raceChannel.SendMessageAsync($"🏁`Race is starting in 20 seconds or when the room is full. Type $jr to join the race.`");
try { await raceChannel.SendMessageAsync($"🏁`Race is starting in 20 seconds or when the room is full. Type {NadekoBot.ModulePrefixes[typeof(Gambling).Name]}jr to join the race.`"); } catch (Exception ex) { _log.Warn(ex); }
var t = await Task.WhenAny(Task.Delay(20000, token), fullgame);
Started = true;
cancelSource.Cancel();
if (t == fullgame)
{
await raceChannel.SendMessageAsync("🏁`Race full, starting right now!`");
try { await raceChannel.SendMessageAsync("🏁`Race full, starting right now!`"); } catch (Exception ex) { _log.Warn(ex); }
}
else if (participants.Count > 1)
{
await raceChannel.SendMessageAsync("🏁`Game starting with " + participants.Count + " participants.`");
try { await raceChannel.SendMessageAsync("🏁`Game starting with " + participants.Count + " participants.`"); } catch (Exception ex) { _log.Warn(ex); }
}
else
{
await raceChannel.SendMessageAsync("🏁`Race failed to start since there was not enough participants.`");
try { await raceChannel.SendMessageAsync("🏁`Race failed to start since there was not enough participants.`"); } catch (Exception ex) { _log.Warn(ex); }
var p = participants.FirstOrDefault();
if (p != null)
@ -166,11 +175,13 @@ namespace NadekoBot.Modules.Gambling
{
if (msg != null)
try { await msg.DeleteAsync(); } catch { }
msg = await raceChannel.SendMessageAsync(text).ConfigureAwait(false);
messagesSinceGameStarted = 0;
try { msg = await raceChannel.SendMessageAsync(text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
}
else
await msg.ModifyAsync(m => m.Content = text).ConfigureAwait(false);
{
try { await msg.ModifyAsync(m => m.Content = text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
}
await Task.Delay(2500);
}

View File

@ -1,119 +1,223 @@
using Discord;
using Discord.Commands;
using ImageProcessorCore;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Resources;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling
{
public partial class Gambling
{
[Group]
public class DriceRollCommands
{
private Regex dndRegex { get; } = new Regex(@"(?<n1>\d+)d(?<n2>\d+)", RegexOptions.Compiled);
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public Task Roll(IUserMessage umsg, [Remainder] string arg = null) =>
publicRoll(umsg, arg, true);
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[RequireContext(ContextType.Guild)]
public Task Rolluo(IUserMessage umsg, [Remainder] string arg = null) =>
publicRoll(umsg, arg, false);
//todo drawing
private async Task publicRoll(IUserMessage umsg, string arg, bool ordered)
public async Task Roll(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
var r = new NadekoRandom();
//if (string.IsNullOrWhiteSpace(arg))
//{
// var gen = r.Next(0, 101);
if (channel == null)
return;
var rng = new NadekoRandom();
var gen = rng.Next(1, 101);
// var num1 = gen / 10;
// var num2 = gen % 10;
var num1 = gen / 10;
var num2 = gen % 10;
var imageStream = await Task.Run(() =>
{
var ms = new MemoryStream();
new[] { GetDice(num1), GetDice(num2) }.Merge().SaveAsPng(ms);
ms.Position = 0;
return ms;
});
// var imageStream = await new Image[2] { GetDice(num1), GetDice(num2) }.Merge().ToStream(ImageFormat.Png);
await channel.SendFileAsync(imageStream, "dice.png", $"{umsg.Author.Mention} rolled " + Format.Code(gen.ToString())).ConfigureAwait(false);
}
//todo merge into internallDndRoll and internalRoll
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task Roll(IUserMessage umsg, string arg)
{
var channel = (ITextChannel)umsg.Channel;
if (channel == null)
return;
// await channel.SendFileAsync(imageStream, "dice.png").ConfigureAwait(false);
// return;
//}
Match m;
if ((m = dndRegex.Match(arg)).Length != 0)
var ordered = true;
var rng = new NadekoRandom();
Match match;
if ((match = dndRegex.Match(arg)).Length != 0)
{
int n1;
int n2;
if (int.TryParse(m.Groups["n1"].ToString(), out n1) &&
int.TryParse(m.Groups["n2"].ToString(), out n2) &&
if (int.TryParse(match.Groups["n1"].ToString(), out n1) &&
int.TryParse(match.Groups["n2"].ToString(), out n2) &&
n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0)
{
var arr = new int[n1];
for (int i = 0; i < n1; i++)
{
arr[i] = r.Next(1, n2 + 1);
arr[i] = rng.Next(1, n2 + 1);
}
var elemCnt = 0;
await channel.SendMessageAsync($"`Rolled {n1} {(n1 == 1 ? "die" : "dice")} 1-{n2}.`\n`Result:` " + string.Join(", ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => elemCnt++ % 2 == 0 ? $"**{x}**" : x.ToString()))).ConfigureAwait(false);
await channel.SendMessageAsync($"{umsg.Author.Mention} rolled {n1} {(n1 == 1 ? "die" : "dice")} 1-{n2}.\n`Result:` " + string.Join(", ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => elemCnt++ % 2 == 0 ? $"**{x}**" : x.ToString()))).ConfigureAwait(false);
}
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(2)]
public async Task Roll(IUserMessage umsg, int num)
{
var channel = (ITextChannel)umsg.Channel;
if (channel == null)
return;
var ordered = true;
if (num < 1 || num > 30)
{
await channel.SendMessageAsync("Invalid number specified. You can roll up to 1-30 dice at a time.").ConfigureAwait(false);
return;
}
//try
//{
// var num = int.Parse(e.Args[0]);
// if (num < 1) num = 1;
// if (num > 30)
// {
// await channel.SendMessageAsync("You can roll up to 30 dice at a time.").ConfigureAwait(false);
// num = 30;
// }
// var dices = new List<Image>(num);
// var values = new List<int>(num);
// for (var i = 0; i < num; i++)
// {
// var randomNumber = r.Next(1, 7);
// var toInsert = dices.Count;
// if (ordered)
// {
// if (randomNumber == 6 || dices.Count == 0)
// toInsert = 0;
// else if (randomNumber != 1)
// for (var j = 0; j < dices.Count; j++)
// {
// if (values[j] < randomNumber)
// {
// toInsert = j;
// break;
// }
// }
// }
// else
// {
// toInsert = dices.Count;
// }
// dices.Insert(toInsert, GetDice(randomNumber));
// values.Insert(toInsert, randomNumber);
// }
// var bitmap = dices.Merge();
// await channel.SendMessageAsync(values.Count + " Dice rolled. Total: **" + values.Sum() + "** Average: **" + (values.Sum() / (1.0f * values.Count)).ToString("N2") + "**").ConfigureAwait(false);
// await channel.SendFileAsync("dice.png", bitmap.ToStream(ImageFormat.Png)).ConfigureAwait(false);
//}
//catch
//{
// await channel.SendMessageAsync("Please enter a number of dice to roll.").ConfigureAwait(false);
//}
var rng = new NadekoRandom();
var dice = new List<Image>(num);
var values = new List<int>(num);
for (var i = 0; i < num; i++)
{
var randomNumber = rng.Next(1, 7);
var toInsert = dice.Count;
if (ordered)
{
if (randomNumber == 6 || dice.Count == 0)
toInsert = 0;
else if (randomNumber != 1)
for (var j = 0; j < dice.Count; j++)
{
if (values[j] < randomNumber)
{
toInsert = j;
break;
}
}
}
else
{
toInsert = dice.Count;
}
dice.Insert(toInsert, GetDice(randomNumber));
values.Insert(toInsert, randomNumber);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
var bitmap = dice.Merge();
var ms = new MemoryStream();
bitmap.SaveAsPng(ms);
ms.Position = 0;
await channel.SendFileAsync(ms, "dice.png", $"{umsg.Author.Mention} rolled {values.Count} {(values.Count == 1 ? "die" : "dice")}. Total: **{values.Sum()}** Average: **{(values.Sum() / (1.0f * values.Count)).ToString("N2")}**").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Rolluo(IUserMessage umsg, string arg)
{
var channel = (ITextChannel)umsg.Channel;
if (channel == null)
return;
var ordered = false;
var rng = new NadekoRandom();
Match match;
if ((match = dndRegex.Match(arg)).Length != 0)
{
int n1;
int n2;
if (int.TryParse(match.Groups["n1"].ToString(), out n1) &&
int.TryParse(match.Groups["n2"].ToString(), out n2) &&
n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0)
{
var arr = new int[n1];
for (int i = 0; i < n1; i++)
{
arr[i] = rng.Next(1, n2 + 1);
}
var elemCnt = 0;
await channel.SendMessageAsync($"`{umsg.Author.Mention} rolled {n1} {(n1 == 1 ? "die" : "dice")} 1-{n2}.`\n`Result:` " + string.Join(", ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => elemCnt++ % 2 == 0 ? $"**{x}**" : x.ToString()))).ConfigureAwait(false);
}
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Rolluo(IUserMessage umsg, int num)
{
var channel = (ITextChannel)umsg.Channel;
if (channel == null)
return;
var ordered = false;
if (num < 1 || num > 30)
{
await channel.SendMessageAsync("Invalid number specified. You can roll up to 1-30 dice at a time.").ConfigureAwait(false);
return;
}
var rng = new NadekoRandom();
var dice = new List<Image>(num);
var values = new List<int>(num);
for (var i = 0; i < num; i++)
{
var randomNumber = rng.Next(1, 7);
var toInsert = dice.Count;
if (ordered)
{
if (randomNumber == 6 || dice.Count == 0)
toInsert = 0;
else if (randomNumber != 1)
for (var j = 0; j < dice.Count; j++)
{
if (values[j] < randomNumber)
{
toInsert = j;
break;
}
}
}
else
{
toInsert = dice.Count;
}
dice.Insert(toInsert, GetDice(randomNumber));
values.Insert(toInsert, randomNumber);
}
var bitmap = dice.Merge();
var ms = new MemoryStream();
bitmap.SaveAsPng(ms);
ms.Position = 0;
await channel.SendFileAsync(ms, "dice.png", $"{umsg.Author.Mention} rolled {values.Count} {(values.Count == 1 ? "die" : "dice")}. Total: **{values.Sum()}** Average: **{(values.Sum() / (1.0f * values.Count)).ToString("N2")}**").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task NRoll(IUserMessage umsg, [Remainder] string range)
{
var channel = (ITextChannel)umsg.Channel;
try
{
int rolled;
@ -140,14 +244,24 @@ namespace NadekoBot.Modules.Gambling
}
}
private Image GetDice(int num)
{
const string pathToImage = "data/images/dice";
if (num != 10)
{
using (var stream = File.OpenRead(Path.Combine(pathToImage, $"{num}.png")))
return new Image(stream);
}
////todo drawing
//private Image GetDice(int num) => num != 10
// ? Properties.Resources.ResourceManager.GetObject("_" + num) as Image
// : new[]
// {
// (Properties.Resources.ResourceManager.GetObject("_" + 1) as Image),
// (Properties.Resources.ResourceManager.GetObject("_" + 0) as Image),
// }.Merge();
using (var one = File.OpenRead(Path.Combine(pathToImage, "1.png")))
using (var zero = File.OpenRead(Path.Combine(pathToImage, "0.png")))
{
Image imgOne = new Image(one);
Image imgZero = new Image(zero);
return new[] { imgOne, imgZero }.Merge();
}
}
}
}
}

View File

@ -1,92 +1,82 @@
//using Discord.Commands;
//using NadekoBot.Classes;
//using NadekoBot.Extensions;
//using NadekoBot.Modules.Gambling.Helpers;
//using System;
//using System.Collections.Concurrent;
//using System.Collections.Generic;
//using System.Drawing;
//using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using ImageProcessorCore;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
////todo drawing
//namespace NadekoBot.Modules.Gambling
//{
// public class DrawCommand : DiscordCommand
// {
// public DrawCommand(DiscordModule module) : base(module) { }
namespace NadekoBot.Modules.Gambling
{
public partial class Gambling
{
[Group]
public class DrawCommands
{
private static readonly ConcurrentDictionary<IGuild, Cards> AllDecks = new ConcurrentDictionary<IGuild, Cards>();
// public override void Init(CommandGroupBuilder cgb)
// {
// cgb.CreateCommand(Module.Prefix + "draw")
// .Description($"Draws a card from the deck.If you supply number [x], she draws up to 5 cards from the deck. | `{Prefix}draw [x]`")
// .Parameter("count", ParameterType.Optional)
// .Do(DrawCardFunc());
// cgb.CreateCommand(Module.Prefix + "shuffle")
// .Alias(Module.Prefix + "sh")
// .Description($"Reshuffles all cards back into the deck.|`{Prefix}shuffle`")
// .Do(ReshuffleTask());
// }
public DrawCommands()
{
_log = LogManager.GetCurrentClassLogger();
}
// private static readonly ConcurrentDictionary<Discord.Server, Cards> AllDecks = new ConcurrentDictionary<Discord.Server, Cards>();
private const string cardsPath = "data/images/cards";
private Logger _log { get; }
// private static Func<CommandEventArgs, Task> ReshuffleTask()
// {
// return async e =>
// {
// AllDecks.AddOrUpdate(e.Server,
// (s) => new Cards(),
// (s, c) =>
// {
// c.Restart();
// return c;
// });
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Draw(IUserMessage msg, int num = 1)
{
var channel = (ITextChannel)msg.Channel;
var cards = AllDecks.GetOrAdd(channel.Guild, (s) => new Cards());
var images = new List<Image>();
var cardObjects = new List<Cards.Card>();
if (num > 5) num = 5;
for (var i = 0; i < num; i++)
{
if (cards.CardPool.Count == 0 && i != 0)
{
try { await channel.SendMessageAsync("No more cards in a deck.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
break;
}
var currentCard = cards.DrawACard();
cardObjects.Add(currentCard);
using (var stream = File.OpenRead(Path.Combine(cardsPath, currentCard.ToString().ToLowerInvariant()+ ".jpg").Replace(' ','_')))
images.Add(new Image(stream));
}
MemoryStream bitmapStream = new MemoryStream();
images.Merge().SaveAsPng(bitmapStream);
bitmapStream.Position = 0;
//todo CARD NAMES?
var toSend = $"{msg.Author.Mention}";
if (cardObjects.Count == 5)
toSend += $" drew `{Cards.GetHandValue(cardObjects)}`";
// await channel.SendMessageAsync("Deck reshuffled.").ConfigureAwait(false);
// };
// }
await channel.SendFileAsync(bitmapStream, images.Count + " cards.jpg", toSend).ConfigureAwait(false);
}
// private Func<CommandEventArgs, Task> DrawCardFunc() => async (e) =>
// {
// var cards = AllDecks.GetOrAdd(e.Server, (s) => new Cards());
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ShuffleDeck(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
// try
// {
// var num = 1;
// var isParsed = int.TryParse(count, out num);
// if (!isParsed || num < 2)
// {
// var c = cards.DrawACard();
// await e.Channel.SendFile(c.Name + ".jpg", (Properties.Resources.ResourceManager.GetObject(c.Name) as Image).ToStream()).ConfigureAwait(false);
// return;
// }
// if (num > 5)
// num = 5;
AllDecks.AddOrUpdate(channel.Guild,
(g) => new Cards(),
(g, c) =>
{
c.Restart();
return c;
});
// var images = new List<Image>();
// var cardObjects = new List<Cards.Card>();
// for (var i = 0; i < num; i++)
// {
// if (cards.CardPool.Count == 0 && i != 0)
// {
// await channel.SendMessageAsync("No more cards in a deck.").ConfigureAwait(false);
// break;
// }
// var currentCard = cards.DrawACard();
// cardObjects.Add(currentCard);
// images.Add(Properties.Resources.ResourceManager.GetObject(currentCard.Name) as Image);
// }
// var bitmap = images.Merge();
// await e.Channel.SendFile(images.Count + " cards.jpg", bitmap.ToStream()).ConfigureAwait(false);
// if (cardObjects.Count == 5)
// {
// await channel.SendMessageAsync($"{umsg.Author.Mention} `{Cards.GetHandValue(cardObjects)}`").ConfigureAwait(false);
// }
// }
// catch (Exception ex)
// {
// Console.WriteLine("Error drawing (a) card(s) " + ex.ToString());
// }
// };
// }
//}
await channel.SendMessageAsync("`Deck reshuffled.`").ConfigureAwait(false);
}
}
}
}

View File

@ -1,93 +1,107 @@
using Discord;
using Discord.Commands;
using ImageProcessorCore;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using System.IO;
using System.Threading.Tasks;
//todo drawing
namespace NadekoBot.Modules.Gambling
{
public partial class Gambling
{
[Group]
public class FlipCoinCommands
{
NadekoRandom rng { get; } = new NadekoRandom();
private const string headsPath = "data/images/coins/heads.png";
private const string tailsPath = "data/images/coins/tails.png";
public FlipCoinCommands() { }
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Flip(IUserMessage imsg, int count = 1)
{
var channel = (ITextChannel)imsg.Channel;
if (count == 1)
{
if (rng.Next(0, 2) == 1)
await channel.SendFileAsync(headsPath, $"{imsg.Author.Mention} rolled " + Format.Code("Heads") + ".").ConfigureAwait(false);
else
await channel.SendFileAsync(tailsPath, $"{imsg.Author.Mention} rolled " + Format.Code("Tails") + ".").ConfigureAwait(false);
return;
}
if (count > 10 || count < 1)
{
await channel.SendMessageAsync("`Invalid number specified. You can flip 1 to 10 coins.`");
return;
}
var imgs = new Image[count];
for (var i = 0; i < count; i++)
{
imgs[i] = rng.Next(0, 10) < 5 ?
new Image(File.OpenRead(headsPath)) :
new Image(File.OpenRead(tailsPath));
}
await channel.SendFileAsync(imgs.Merge().ToStream(), $"{count} coins.png").ConfigureAwait(false);
}
////todo drawing
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Flip(IUserMessage imsg, int count = 0)
//{
// var channel = (ITextChannel)imsg.Channel;
// if (count == 0)
// {
// if (rng.Next(0, 2) == 1)
// await channel.SendFileAsync("heads.png", ).ConfigureAwait(false);
// else
// await channel.SendFileAsync("tails.png", ).ConfigureAwait(false);
// return;
// }
// if (result > 10)
// result = 10;
// var imgs = new Image[result];
// for (var i = 0; i < result; i++)
// {
// imgs[i] = rng.Next(0, 2) == 0 ?
// Properties.Resources.tails :
// Properties.Resources.heads;
// }
// await channel.SendFile($"{result} coins.png", imgs.Merge().ToStream(System.Drawing.Imaging.ImageFormat.Png)).ConfigureAwait(false);
// return;
// await channel.SendMessageAsync("Invalid number").ConfigureAwait(false);
//}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Betflip(IUserMessage umsg, int amount, string guess)
{
var channel = (ITextChannel)umsg.Channel;
var guildUser = (IGuildUser)umsg.Author;
var guessStr = guess.Trim().ToUpperInvariant();
if (guessStr != "H" && guessStr != "T" && guessStr != "HEADS" && guessStr != "TAILS")
return;
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Betflip(IUserMessage umsg, int amount, string guess)
//{
// var channel = (ITextChannel)umsg.Channel;
// var guildUser = (IGuildUser)umsg.Author;
// var guessStr = guess.Trim().ToUpperInvariant();
// if (guessStr != "H" && guessStr != "T" && guessStr != "HEADS" && guessStr != "TAILS")
// return;
if (amount < 1)
return;
// todo update this
long userFlowers;
using (var uow = DbHandler.UnitOfWork())
{
userFlowers = uow.Currency.GetOrCreate(umsg.Author.Id).Amount;
}
// if (amount < 1)
// return;
if (userFlowers < amount)
{
await channel.SendMessageAsync($"{umsg.Author.Mention} You don't have enough {Gambling.CurrencyPluralName}. You only have {userFlowers}{Gambling.CurrencySign}.").ConfigureAwait(false);
return;
}
// var userFlowers = Gambling.GetUserFlowers(umsg.Author.Id);
await CurrencyHandler.RemoveCurrencyAsync(guildUser, "Betflip Gamble", amount, false).ConfigureAwait(false);
//heads = true
//tails = false
// if (userFlowers < amount)
// {
// await channel.SendMessageAsync($"{umsg.Author.Mention} You don't have enough {Gambling.CurrencyName}s. You only have {userFlowers}{Gambling.CurrencySign}.").ConfigureAwait(false);
// return;
// }
var isHeads = guessStr == "HEADS" || guessStr == "H";
bool result = false;
string imgPathToSend;
if (rng.Next(0, 2) == 1)
{
imgPathToSend = headsPath;
result = true;
}
else
{
imgPathToSend = tailsPath;
}
// await CurrencyHandler.RemoveCurrencyAsync(guildUser, "Betflip Gamble", amount, false).ConfigureAwait(false);
// //heads = true
// //tails = false
string str;
if (isHeads == result)
{
str = $"{umsg.Author.Mention}`You guessed it!` You won {amount * 2}{Gambling.CurrencySign}";
await CurrencyHandler.AddCurrencyAsync((IGuildUser)umsg.Author, "Betflip Gamble", amount * 2, false).ConfigureAwait(false);
}
else
{
str = $"{umsg.Author.Mention}`Better luck next time.`";
}
// var isHeads = guessStr == "HEADS" || guessStr == "H";
// bool result = false;
// var rng = new NadekoRandom();
// if (rng.Next(0, 2) == 1)
// {
// await channel.SendFileAsync("heads.png", Properties.Resources.heads.ToStream(System.Drawing.Imaging.ImageFormat.Png)).ConfigureAwait(false);
// result = true;
// }
// else
// {
// await channel.SendFileAsync("tails.png", Properties.Resources.tails.ToStream(System.Drawing.Imaging.ImageFormat.Png)).ConfigureAwait(false);
// }
// string str;
// if (isHeads == result)
// {
// str = $"{umsg.Author.Mention}`You guessed it!` You won {amount * 2}{Gambling.CurrencySign}";
// await CurrencyHandler.AddCurrencyAsync((IGuildUser)umsg.Author, "Betflip Gamble", amount * 2, false).ConfigureAwait(false);
// }
// else
// str = $"{umsg.Author.Mention}`More luck next time.`";
// await channel.SendMessageAsync(str).ConfigureAwait(false);
//}
await channel.SendFileAsync(imgPathToSend, str).ConfigureAwait(false);
}
}
}
}

View File

@ -21,7 +21,7 @@ namespace NadekoBot.Modules.Gambling
public static string CurrencyPluralName { get; set; }
public static string CurrencySign { get; set; }
public Gambling(ILocalization loc, CommandService cmds, DiscordSocketClient client) : base(loc, cmds, client)
public Gambling(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client)
{
using (var uow = DbHandler.UnitOfWork())
{
@ -33,7 +33,7 @@ namespace NadekoBot.Modules.Gambling
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Raffle(IUserMessage umsg, [Remainder] IRole role = null)
{
@ -47,11 +47,11 @@ namespace NadekoBot.Modules.Gambling
await channel.SendMessageAsync($"**Raffled user:** {usr.Username} (id: {usr.Id})").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[RequireContext(ContextType.Guild)]
[NadekoCommand, Usage, Description, Aliases]
[Priority(1)]
public async Task Cash(IUserMessage umsg, [Remainder] IUser user = null)
{
var channel = (ITextChannel)umsg.Channel;
var channel = umsg.Channel;
user = user ?? umsg.Author;
long amount;
@ -65,7 +65,24 @@ namespace NadekoBot.Modules.Gambling
await channel.SendMessageAsync($"{user.Username} has {amount} {config.CurrencySign}").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[Priority(0)]
public async Task Cash(IUserMessage umsg, ulong userId)
{
var channel = umsg.Channel;
long amount;
BotConfig config;
using (var uow = DbHandler.UnitOfWork())
{
amount = uow.Currency.GetUserCurrency(userId);
config = uow.BotConfig.GetOrCreate();
}
await channel.SendMessageAsync($"`{userId}` has {amount} {config.CurrencySign}").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Give(IUserMessage umsg, long amount, [Remainder] IGuildUser receiver)
{
@ -82,47 +99,59 @@ namespace NadekoBot.Modules.Gambling
await channel.SendMessageAsync($"{umsg.Author.Mention} successfully sent {amount} {Gambling.CurrencyPluralName}s to {receiver.Mention}!").ConfigureAwait(false);
}
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public Task Award(IUserMessage umsg, long amount, [Remainder] IGuildUser usr) =>
// Award(umsg, amount, usr.Id);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public Task Award(IUserMessage umsg, long amount, [Remainder] IGuildUser usr) =>
Award(umsg, amount, usr.Id);
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Award(IUserMessage umsg, long amount, [Remainder] ulong usrId)
//{
// var channel = (ITextChannel)umsg.Channel;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Award(IUserMessage umsg, long amount, [Remainder] ulong usrId)
{
var channel = (ITextChannel)umsg.Channel;
// if (amount <= 0)
// return;
if (amount <= 0)
return;
// await CurrencyHandler.AddFlowersAsync(usrId, $"Awarded by bot owner. ({umsg.Author.Username}/{umsg.Author.Id})", (int)amount).ConfigureAwait(false);
await CurrencyHandler.AddCurrencyAsync(usrId, $"Awarded by bot owner. ({umsg.Author.Username}/{umsg.Author.Id})", (int)amount).ConfigureAwait(false);
// await channel.SendMessageAsync($"{umsg.Author.Mention} successfully awarded {amount} {Gambling.CurrencyName}s to <@{usrId}>!").ConfigureAwait(false);
//}
await channel.SendMessageAsync($"{umsg.Author.Mention} successfully awarded {amount} {Gambling.CurrencyName}s to <@{usrId}>!").ConfigureAwait(false);
}
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public Task Take(IUserMessage umsg, long amount, [Remainder] IGuildUser user) =>
// Take(umsg, amount, user.Id);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Take(IUserMessage umsg, long amount, [Remainder] IGuildUser user)
{
var channel = (ITextChannel)umsg.Channel;
if (amount <= 0)
return;
//todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Take(IUserMessage umsg, long amount, [Remainder] ulong usrId)
//{
// var channel = (ITextChannel)umsg.Channel;
// if (amount <= 0)
// return;
if(await CurrencyHandler.RemoveCurrencyAsync(user, $"Taken by bot owner.({umsg.Author.Username}/{umsg.Author.Id})", amount, true).ConfigureAwait(false))
await channel.SendMessageAsync($"{umsg.Author.Mention} successfully took {amount} {Gambling.CurrencyPluralName} from {user}!").ConfigureAwait(false);
else
await channel.SendMessageAsync($"{umsg.Author.Mention} was unable to take {amount} {Gambling.CurrencyPluralName} from {user} because the user doesn't have that much {Gambling.CurrencyPluralName}!").ConfigureAwait(false);
}
// await CurrencyHandler.RemoveFlowers(usrId, $"Taken by bot owner.({umsg.Author.Username}/{umsg.Author.Id})", (int)amount).ConfigureAwait(false);
// await channel.SendMessageAsync($"{umsg.Author.Mention} successfully took {amount} {Gambling.CurrencyName}s from <@{usrId}>!").ConfigureAwait(false);
//}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Take(IUserMessage umsg, long amount, [Remainder] ulong usrId)
{
var channel = (ITextChannel)umsg.Channel;
if (amount <= 0)
return;
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
if(await CurrencyHandler.RemoveCurrencyAsync(usrId, $"Taken by bot owner.({umsg.Author.Username}/{umsg.Author.Id})", amount).ConfigureAwait(false))
await channel.SendMessageAsync($"{umsg.Author.Mention} successfully took {amount} {Gambling.CurrencyName}s from <@{usrId}>!").ConfigureAwait(false);
else
await channel.SendMessageAsync($"{umsg.Author.Mention} was unable to take {amount} {Gambling.CurrencyPluralName} from `{usrId}` because the user doesn't have that much {Gambling.CurrencyPluralName}!").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task BetRoll(IUserMessage umsg, long amount)
{
@ -172,7 +201,7 @@ namespace NadekoBot.Modules.Gambling
await channel.SendMessageAsync(str).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Leaderboard(IUserMessage umsg)
{
@ -188,13 +217,12 @@ namespace NadekoBot.Modules.Gambling
await channel.SendMessageAsync(
richest.Aggregate(new StringBuilder(
$@"```xl
Id $$$
"),
(cur, cs) => cur.AppendLine(
$@"┣━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━┫
{(channel.Guild.GetUser(cs.UserId)?.Username.TrimTo(18, true) ?? cs.UserId.ToString()),-20} {cs,5} ")
).ToString() + "┗━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━┛```").ConfigureAwait(false);
(cur, cs) => cur.AppendLine($@"┣━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━┫
{(channel.Guild.GetUser(cs.UserId)?.Username.TrimTo(18, true) ?? cs.UserId.ToString()),-20} {cs.Amount,6} ")
).ToString() + "┗━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━┛```").ConfigureAwait(false);
}
}
}

View File

@ -12,7 +12,7 @@ namespace NadekoBot.Modules.Games
{
public partial class Games
{
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Leet(IUserMessage umsg, int level, [Remainder] string text = null)
{

View File

@ -4,11 +4,11 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services.Database.Models
namespace NadekoBot.Modules.Games.Commands.Models
{
public class TypingArticle : DbEntity
public class TypingArticle
{
public string Author { get; set; }
public string Title { get; set; }
public string Text { get; set; }
}
}

View File

@ -5,6 +5,8 @@ using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -14,9 +16,10 @@ using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
//todo rewrite
namespace NadekoBot.Modules.Games
{
//todo make currency generation change and cooldown modifyable
//only by bot owner through commands
public partial class Games
{
/// <summary>
@ -29,10 +32,9 @@ namespace NadekoBot.Modules.Games
[Group]
public class PlantPickCommands
{
private Random rng;
private ConcurrentDictionary<ulong, bool> generationChannels = new ConcurrentDictionary<ulong, bool>();
private ConcurrentHashSet<ulong> generationChannels = new ConcurrentHashSet<ulong>();
//channelid/message
private ConcurrentDictionary<ulong, List<IUserMessage>> plantedFlowers = new ConcurrentDictionary<ulong, List<IUserMessage>>();
//channelId/last generation
@ -40,9 +42,11 @@ namespace NadekoBot.Modules.Games
private float chance;
private int cooldown;
private Logger _log { get; }
public PlantPickCommands()
{
_log = LogManager.GetCurrentClassLogger();
NadekoBot.Client.MessageReceived += PotentialFlowerGeneration;
rng = new NadekoRandom();
@ -50,9 +54,8 @@ namespace NadekoBot.Modules.Games
{
var conf = uow.BotConfig.GetOrCreate();
var x =
generationChannels = new ConcurrentDictionary<ulong, bool>(uow.GuildConfigs.GetAll()
.Where(c => c.GenerateCurrencyChannelId != null)
.ToDictionary(c => c.GenerateCurrencyChannelId.Value, c => true));
generationChannels = new ConcurrentHashSet<ulong>(uow.GuildConfigs.GetAll()
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj=>obj.ChannelId)));
chance = conf.CurrencyGenerationChance;
cooldown = conf.CurrencyGenerationCooldown;
}
@ -61,15 +64,14 @@ namespace NadekoBot.Modules.Games
private Task PotentialFlowerGeneration(IMessage imsg)
{
var msg = imsg as IUserMessage;
if (msg == null || msg.IsAuthor())
if (msg == null || msg.IsAuthor() || msg.Author.IsBot)
return Task.CompletedTask;
var channel = imsg.Channel as ITextChannel;
if (channel == null)
return Task.CompletedTask;
bool shouldGenerate;
if (!generationChannels.TryGetValue(channel.Id, out shouldGenerate) || !shouldGenerate)
if (!generationChannels.Contains(channel.Id))
return Task.CompletedTask;
var t = Task.Run(async () =>
@ -84,12 +86,11 @@ namespace NadekoBot.Modules.Games
if (num > 100)
{
lastGenerations.AddOrUpdate(channel.Id, DateTime.Now, (id, old) => DateTime.Now);
//todo get prefix
try
{
var sent = await channel.SendFileAsync(
GetRandomCurrencyImagePath(),
$"❗ A random { Gambling.Gambling.CurrencyName } appeared! Pick it up by typing `>pick`")
$"❗ A random { Gambling.Gambling.CurrencyName } appeared! Pick it up by typing `{NadekoBot.ModulePrefixes[typeof(Games).Name]}pick`")
.ConfigureAwait(false);
plantedFlowers.AddOrUpdate(channel.Id, new List<IUserMessage>() { sent }, (id, old) => { old.Add(sent); return old; });
}
@ -99,7 +100,7 @@ namespace NadekoBot.Modules.Games
});
return Task.CompletedTask;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Pick(IUserMessage imsg)
{
@ -122,23 +123,17 @@ namespace NadekoBot.Modules.Games
await CurrencyHandler.AddCurrencyAsync((IGuildUser)imsg.Author, "Picked flower(s).", msgs.Count, false).ConfigureAwait(false);
var msg = await channel.SendMessageAsync($"**{imsg.Author.Username}** picked {msgs.Count}{Gambling.Gambling.CurrencySign}!").ConfigureAwait(false);
var t = Task.Run(async () =>
{
try
{
await Task.Delay(10000).ConfigureAwait(false);
await msg.DeleteAsync().ConfigureAwait(false);
}
catch { }
try { await msg.DeleteAsync().ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
});
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Plant(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
if (channel == null)
return;
var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)imsg.Author, "Planted a flower.", 1, false).ConfigureAwait(false);
if (!removed)
@ -150,44 +145,42 @@ namespace NadekoBot.Modules.Games
var file = GetRandomCurrencyImagePath();
IUserMessage msg;
var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(Gambling.Gambling.CurrencyName[0]);
var msgToSend = $"Oh how Nice! **{imsg.Author.Username}** planted {(vowelFirst ? "an" : "a")} {Gambling.Gambling.CurrencyName}. Pick it using >pick";
var msgToSend = $"Oh how Nice! **{imsg.Author.Username}** planted {(vowelFirst ? "an" : "a")} {Gambling.Gambling.CurrencyName}. Pick it using {NadekoBot.ModulePrefixes[typeof(Games).Name]}pick";
if (file == null)
{
msg = await channel.SendMessageAsync(Gambling.Gambling.CurrencySign).ConfigureAwait(false);
}
else
{
//todo add prefix
msg = await channel.SendFileAsync(file, msgToSend).ConfigureAwait(false);
}
plantedFlowers.AddOrUpdate(channel.Id, new List<IUserMessage>() { msg }, (id, old) => { old.Add(msg); return old; });
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageMessages)]
public async Task Gencurrency(IUserMessage imsg)
public async Task GenCurrency(IUserMessage imsg)
{
var channel = imsg.Channel as ITextChannel;
if (channel == null)
return;
var channel = (ITextChannel)imsg.Channel;
bool enabled;
using (var uow = DbHandler.UnitOfWork())
{
var guildConfig = uow.GuildConfigs.For(channel.Id);
if (guildConfig.GenerateCurrencyChannelId == null)
var toAdd = new GCChannelId() { ChannelId = channel.Id };
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
{
guildConfig.GenerateCurrencyChannelId = channel.Id;
generationChannels.TryAdd(channel.Id, true);
guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
generationChannels.Add(channel.Id);
enabled = true;
}
else
{
guildConfig.GenerateCurrencyChannelId = null;
bool throwaway;
generationChannels.TryRemove(channel.Id, out throwaway);
guildConfig.GenerateCurrencyChannelIds.Remove(toAdd);
generationChannels.TryRemove(channel.Id);
enabled = false;
}
await uow.CompleteAsync();

View File

@ -14,7 +14,7 @@ namespace NadekoBot.Modules.Games
{
public static ConcurrentDictionary<IGuild, Poll> ActivePolls = new ConcurrentDictionary<IGuild, Poll>();
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Poll(IUserMessage umsg, [Remainder] string arg = null)
{
@ -35,7 +35,7 @@ namespace NadekoBot.Modules.Games
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Pollend(IUserMessage umsg)
{
@ -111,7 +111,7 @@ namespace NadekoBot.Modules.Games
{
// has to be a user message
var msg = imsg as IUserMessage;
if (msg == null)
if (msg == null || msg.Author.IsBot)
return Task.CompletedTask;
// channel must be private
IPrivateChannel ch;
@ -124,17 +124,12 @@ namespace NadekoBot.Modules.Games
var t = Task.Run(async () =>
{
try
{
if (vote < 1 || vote > answers.Length)
return;
if (participants.TryAdd(msg.Author, vote))
{
await (ch as ITextChannel).SendMessageAsync($"Thanks for voting **{msg.Author.Username}**.").ConfigureAwait(false);
try { await (ch as ITextChannel).SendMessageAsync($"Thanks for voting **{msg.Author.Username}**.").ConfigureAwait(false); } catch { }
}
}
catch { }
});
return Task.CompletedTask;
}

View File

@ -3,12 +3,17 @@ using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Commands.Models;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@ -24,9 +29,11 @@ namespace NadekoBot.Modules.Games
public bool IsActive;
private readonly Stopwatch sw;
private readonly List<ulong> finishedUserIds;
private Logger _log { get; }
public TypingGame(ITextChannel channel)
{
_log = LogManager.GetCurrentClassLogger();
this.channel = channel;
IsActive = false;
sw = new Stopwatch();
@ -43,28 +50,33 @@ namespace NadekoBot.Modules.Games
IsActive = false;
sw.Stop();
sw.Reset();
await channel.SendMessageAsync("Typing contest stopped").ConfigureAwait(false);
try { await channel.SendMessageAsync("Typing contest stopped").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
return true;
}
public async Task Start()
{
while (true)
{
if (IsActive) return; // can't start running game
IsActive = true;
CurrentSentence = GetRandomSentence();
var i = (int)(CurrentSentence.Length / WORD_VALUE * 1.7f);
await channel.SendMessageAsync($":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.").ConfigureAwait(false);
try
{
await channel.SendMessageAsync($@":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.").ConfigureAwait(false);
var msg = await channel.SendMessageAsync("Starting new typing contest in **3**...").ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
try
{
await msg.ModifyAsync(m => m.Content = "Starting new typing contest in **2**...").ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
await msg.ModifyAsync(m => m.Content = "Starting new typing contest in **1**...").ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
await msg.ModifyAsync(m => m.Content = $":book:**{CurrentSentence.Replace(" ", " \x200B")}**:book:").ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
await msg.ModifyAsync(m => m.Content = Format.Bold(Format.Sanitize(CurrentSentence.Replace(" ", " \x200B")).SanitizeMentions())).ConfigureAwait(false);
sw.Start();
HandleAnswers();
@ -76,16 +88,20 @@ namespace NadekoBot.Modules.Games
return;
}
}
catch { }
finally
{
await Stop().ConfigureAwait(false);
}
}
public string GetRandomSentence()
{
using (var uow = DbHandler.UnitOfWork())
{
return uow.TypingArticles.GetRandom()?.Text ?? "No typing articles found. Use `>typeadd` command to add a new article for typing.";
}
if (SpeedTypingCommands.TypingArticles.Any())
return SpeedTypingCommands.TypingArticles[new NadekoRandom().Next(0, SpeedTypingCommands.TypingArticles.Count)].Text;
else
return $"No typing articles found. Use {NadekoBot.ModulePrefixes[typeof(Games).Name]}typeadd command to add a new article for typing.";
}
@ -96,6 +112,8 @@ namespace NadekoBot.Modules.Games
private Task AnswerReceived(IMessage imsg)
{
if (imsg.Author.IsBot)
return Task.CompletedTask;
var msg = imsg as IUserMessage;
if (msg == null)
return Task.CompletedTask;
@ -115,7 +133,7 @@ namespace NadekoBot.Modules.Games
await channel.SendMessageAsync($"{msg.Author.Mention} finished in **{sw.Elapsed.Seconds}** seconds with { distance } errors, **{ CurrentSentence.Length / WORD_VALUE / sw.Elapsed.Seconds * 60 }** WPM!").ConfigureAwait(false);
if (finishedUserIds.Count % 2 == 0)
{
await channel.SendMessageAsync($":exclamation: `A lot of people finished, here is the text for those still typing:`\n\n:book:**{CurrentSentence}**:book:").ConfigureAwait(false);
await channel.SendMessageAsync($":exclamation: `A lot of people finished, here is the text for those still typing:`\n\n**{Format.Sanitize(CurrentSentence.Replace(" ", " \x200B")).SanitizeMentions()}**").ConfigureAwait(false);
}
}
}
@ -132,6 +150,14 @@ namespace NadekoBot.Modules.Games
public class SpeedTypingCommands
{
public static List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>();
const string typingArticlesPath = "data/typing_articles.json";
static SpeedTypingCommands()
{
try { TypingArticles = JsonConvert.DeserializeObject<List<TypingArticle>>(File.ReadAllText(typingArticlesPath)); } catch { }
}
public static ConcurrentDictionary<ulong, TypingGame> RunningContests;
public SpeedTypingCommands()
@ -139,7 +165,7 @@ namespace NadekoBot.Modules.Games
RunningContests = new ConcurrentDictionary<ulong, TypingGame>();
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task TypeStart(IUserMessage msg)
{
@ -160,7 +186,7 @@ namespace NadekoBot.Modules.Games
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task TypeStop(IUserMessage imsg)
{
@ -174,24 +200,24 @@ namespace NadekoBot.Modules.Games
await channel.SendMessageAsync("No contest to stop on this channel.").ConfigureAwait(false);
}
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Typeadd(IUserMessage imsg, [Remainder] string text)
//{
// var channel = (ITextChannel)imsg.Channel;
// using (var uow = DbHandler.UnitOfWork())
// {
// uow.TypingArticles.Add(new Services.Database.Models.TypingArticle
// {
// Author = imsg.Author.Username,
// Text = text
// });
// }
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Typeadd(IUserMessage imsg, [Remainder] string text)
{
var channel = (ITextChannel)imsg.Channel;
// await channel.SendMessageAsync("Added new article for typing game.").ConfigureAwait(false);
//}
TypingArticles.Add(new TypingArticle
{
Title = $"Text added on {DateTime.UtcNow} by {imsg.Author}",
Text = text.SanitizeMentions(),
});
File.WriteAllText(typingArticlesPath, JsonConvert.SerializeObject(TypingArticles));
await channel.SendMessageAsync("Added new article for typing game.").ConfigureAwait(false);
}
}
}
}

View File

@ -1,5 +1,8 @@
using Discord;
using Discord.Net;
using NadekoBot.Extensions;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@ -12,9 +15,10 @@ namespace NadekoBot.Modules.Games.Trivia
public class TriviaGame
{
private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1);
private Logger _log { get; }
private IGuild guild { get; }
private ITextChannel channel { get; }
public IGuild guild { get; }
public ITextChannel channel { get; }
private int QuestionDurationMiliseconds { get; } = 30000;
private int HintTimeoutMiliseconds { get; } = 6000;
@ -33,6 +37,7 @@ namespace NadekoBot.Modules.Games.Trivia
public TriviaGame(IGuild guild, ITextChannel channel, bool showHints, int winReq = 10)
{
_log = LogManager.GetCurrentClassLogger();
ShowHints = showHints;
this.guild = guild;
this.channel = channel;
@ -51,13 +56,18 @@ namespace NadekoBot.Modules.Games.Trivia
CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(oldQuestions);
if (CurrentQuestion == null)
{
await channel.SendMessageAsync($":exclamation: Failed loading a trivia question").ConfigureAwait(false);
try { await channel.SendMessageAsync($":exclamation: Failed loading a trivia question").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
await End().ConfigureAwait(false);
return;
}
oldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again
//sendquestion
await channel.SendMessageAsync($":question: **{CurrentQuestion.Question}**").ConfigureAwait(false);
try { await channel.SendMessageAsync($":question: **{CurrentQuestion.Question}**").ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
break;
}
catch (Exception ex) { _log.Warn(ex); }
//receive messages
NadekoBot.Client.MessageReceived += PotentialGuess;
@ -70,7 +80,7 @@ namespace NadekoBot.Modules.Games.Trivia
//hint
await Task.Delay(HintTimeoutMiliseconds, token).ConfigureAwait(false);
if (ShowHints)
await channel.SendMessageAsync($":exclamation:**Hint:** {CurrentQuestion.GetHint()}").ConfigureAwait(false);
try { await channel.SendMessageAsync($":exclamation:**Hint:** {CurrentQuestion.GetHint()}").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
//timeout
await Task.Delay(QuestionDurationMiliseconds - HintTimeoutMiliseconds, token).ConfigureAwait(false);
@ -79,29 +89,33 @@ namespace NadekoBot.Modules.Games.Trivia
catch (TaskCanceledException) { } //means someone guessed the answer
GameActive = false;
if (!triviaCancelSource.IsCancellationRequested)
await channel.SendMessageAsync($":clock2: :question: **Time's up!** The correct answer was **{CurrentQuestion.Answer}**").ConfigureAwait(false);
try { await channel.SendMessageAsync($":clock2: :question: **Time's up!** The correct answer was **{CurrentQuestion.Answer}**").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
NadekoBot.Client.MessageReceived -= PotentialGuess;
// load next question if game is still running
await Task.Delay(2000).ConfigureAwait(false);
}
try { NadekoBot.Client.MessageReceived -= PotentialGuess; } catch { }
GameActive = false;
await End().ConfigureAwait(false);
}
private async Task End()
{
ShouldStopGame = true;
await channel.SendMessageAsync("**Trivia game ended**\n" + GetLeaderboard()).ConfigureAwait(false);
try { await channel.SendMessageAsync("**Trivia game ended**\n" + GetLeaderboard()).ConfigureAwait(false); } catch { }
}
public async Task StopGame()
{
if (!ShouldStopGame)
await channel.SendMessageAsync(":exclamation: Trivia will stop after this question.").ConfigureAwait(false);
try { await channel.SendMessageAsync(":exclamation: Trivia will stop after this question.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
ShouldStopGame = true;
}
private Task PotentialGuess(IMessage imsg)
{
if (imsg.Author.IsBot)
return Task.CompletedTask;
var umsg = imsg as IUserMessage;
if (umsg == null)
return Task.CompletedTask;
@ -111,7 +125,7 @@ namespace NadekoBot.Modules.Games.Trivia
{
if (!(umsg.Channel is IGuildChannel && umsg.Channel is ITextChannel)) return;
if ((umsg.Channel as ITextChannel).Guild != guild) return;
if (umsg.Author.Id == (await NadekoBot.Client.GetCurrentUserAsync()).Id) return;
if (umsg.Author.Id == NadekoBot.Client.GetCurrentUser().Id) return;
var guildUser = umsg.Author as IGuildUser;
@ -121,19 +135,19 @@ namespace NadekoBot.Modules.Games.Trivia
{
if (GameActive && CurrentQuestion.IsAnswerCorrect(umsg.Content) && !triviaCancelSource.IsCancellationRequested)
{
Users.AddOrUpdate(guildUser, 0, (gu, old) => old++);
Users.AddOrUpdate(guildUser, 1, (gu, old) => ++old);
guess = true;
}
}
finally { _guessLock.Release(); }
if (!guess) return;
triviaCancelSource.Cancel();
await channel.SendMessageAsync($"☑️ {guildUser.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false);
try { await channel.SendMessageAsync($"☑️ {guildUser.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
if (Users[guildUser] != WinRequirement) return;
ShouldStopGame = true;
await channel.SendMessageAsync($":exclamation: We have a winner! It's {guildUser.Mention}.").ConfigureAwait(false);
}
catch { }
catch (Exception ex) { _log.Warn(ex); }
});
return Task.CompletedTask;
}
@ -146,7 +160,7 @@ namespace NadekoBot.Modules.Games.Trivia
var sb = new StringBuilder();
sb.Append("**Leaderboard:**\n-----------\n");
foreach (var kvp in Users.OrderBy(kvp => kvp.Value))
foreach (var kvp in Users.OrderByDescending(kvp => kvp.Value))
{
sb.AppendLine($"**{kvp.Key.Username}** has {kvp.Value} points".ToString().SnPl(kvp.Value));
}

View File

@ -1,6 +1,8 @@
using NadekoBot.Services;
using NadekoBot.Extensions;
using NadekoBot.Services;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -10,7 +12,7 @@ namespace NadekoBot.Modules.Games.Trivia
public class TriviaQuestionPool
{
public static TriviaQuestionPool Instance { get; } = new TriviaQuestionPool();
public HashSet<TriviaQuestion> pool = new HashSet<TriviaQuestion>();
public ConcurrentHashSet<TriviaQuestion> pool = new ConcurrentHashSet<TriviaQuestion>();
private Random rng { get; } = new NadekoRandom();
@ -30,15 +32,15 @@ namespace NadekoBot.Modules.Games.Trivia
public void Reload()
{
var arr = JArray.Parse(File.ReadAllText("data/triviaquestions.json"));
var arr = JArray.Parse(File.ReadAllText("data/questions.json"));
foreach (var item in arr)
{
var tq = new TriviaQuestion(item["Question"].ToString(), item["Answer"].ToString(), item["Category"]?.ToString());
var tq = new TriviaQuestion(item["Question"].ToString().SanitizeMentions(), item["Answer"].ToString().SanitizeMentions(), item["Category"]?.ToString());
pool.Add(tq);
}
var r = new NadekoRandom();
pool = new HashSet<TriviaQuestion>(pool.OrderBy(x => r.Next()));
pool = new ConcurrentHashSet<TriviaQuestion>(pool.OrderBy(x => r.Next()));
}
}
}

View File

@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Games
{
public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias = new ConcurrentDictionary<ulong, TriviaGame>();
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Trivia(IUserMessage umsg, params string[] args)
{
@ -34,7 +34,7 @@ namespace NadekoBot.Modules.Games
}).Where(t => t.Item1).Select(t => t.Item2).FirstOrDefault();
if (number < 0)
return;
var triviaGame = new TriviaGame(channel.Guild, umsg.Channel as ITextChannel, showHints, number == 0 ? 10 : number);
var triviaGame = new TriviaGame(channel.Guild, (ITextChannel)umsg.Channel, showHints, number == 0 ? 10 : number);
if (RunningTrivias.TryAdd(channel.Guild.Id, triviaGame))
await channel.SendMessageAsync($"**Trivia game started! {triviaGame.WinRequirement} points needed to win.**").ConfigureAwait(false);
else
@ -44,7 +44,7 @@ namespace NadekoBot.Modules.Games
await channel.SendMessageAsync("Trivia game is already running on this server.\n" + trivia.CurrentQuestion).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Tl(IUserMessage umsg)
{
@ -57,7 +57,7 @@ namespace NadekoBot.Modules.Games
await channel.SendMessageAsync("No trivia is running on this server.").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Tq(IUserMessage umsg)
{

View File

@ -23,11 +23,11 @@ namespace NadekoBot.Modules.Games
}
}
}
public Games(ILocalization loc, CommandService cmds, DiscordSocketClient client) : base(loc, cmds, client)
public Games(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client)
{
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Choose(IUserMessage umsg, [Remainder] string list = null)
{
@ -41,7 +41,7 @@ namespace NadekoBot.Modules.Games
await channel.SendMessageAsync(listArr[rng.Next(0, listArr.Length)]).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task _8Ball(IUserMessage umsg, [Remainder] string question = null)
{
@ -54,7 +54,7 @@ namespace NadekoBot.Modules.Games
🎱 `8Ball Answers` __**{_8BallResponses.Shuffle().FirstOrDefault()}**__").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Rps(IUserMessage umsg, string input)
{
@ -97,14 +97,14 @@ namespace NadekoBot.Modules.Games
else if ((pick == 0 && nadekoPick == 1) ||
(pick == 1 && nadekoPick == 2) ||
(pick == 2 && nadekoPick == 0))
msg = $"{(await NadekoBot.Client.GetCurrentUserAsync()).Mention} won! :{GetRPSPick(nadekoPick)}: beats :{GetRPSPick(pick)}:";
msg = $"{NadekoBot.Client.GetCurrentUser().Mention} won! :{GetRPSPick(nadekoPick)}: beats :{GetRPSPick(pick)}:";
else
msg = $"{umsg.Author.Mention} won! :{GetRPSPick(pick)}: beats :{GetRPSPick(nadekoPick)}:";
await channel.SendMessageAsync(msg).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Linux(IUserMessage umsg, string guhnoo, string loonix)
{

View File

@ -9,44 +9,56 @@ using System;
using System.IO;
using System.Text;
using Discord.WebSocket;
using System.Collections;
using System.Collections.Generic;
using NadekoBot.Services.Database;
using System.Threading;
namespace NadekoBot.Modules.Help
{
[NadekoModule("Help", "-")]
public partial class Help : DiscordModule
{
public string HelpString {
get {
var str = "To add me to your server, use this link -> <https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=66186303>\n";
return str + String.Format(str, NadekoBot.Credentials.ClientId);
private static string helpString { get; }
public static string HelpString => String.Format(helpString, NadekoBot.Credentials.ClientId, NadekoBot.ModulePrefixes[typeof(Help).Name]);
public static string DMHelpString { get; }
static Help()
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
helpString = config.HelpString;
DMHelpString = config.DMHelpString;
}
}
public Help(ILocalization loc, CommandService cmds, DiscordSocketClient client) : base(loc, cmds, client)
public Help(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client)
{
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[RequireContext(ContextType.Guild)]
[NadekoCommand, Usage, Description, Aliases]
public async Task Modules(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
await channel.SendMessageAsync("`List of modules:` \n• " + string.Join("\n• ", _commands.Modules.Select(m => m.Name)) + $"\n`Type \"-commands module_name\" to get a list of commands in that module.`")
await umsg.Channel.SendMessageAsync("`List of modules:` ```xl\n• " + string.Join("\n• ", _commands.Modules.Select(m => m.Name)) + $"\n``` `Type \"-commands module_name\" to get a list of commands in that module.`")
.ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[RequireContext(ContextType.Guild)]
[NadekoCommand, Usage, Description, Aliases]
public async Task Commands(IUserMessage umsg, [Remainder] string module = null)
{
var channel = (ITextChannel)umsg.Channel;
var channel = umsg.Channel;
module = module?.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(module))
return;
var cmds = _commands.Commands.Where(c => c.Module.Name.ToUpperInvariant().StartsWith(module))
.OrderBy(c => c.Text)
.Distinct(new CommandTextEqualityComparer())
.AsEnumerable();
var cmdsArray = cmds as Command[] ?? cmds.ToArray();
if (!cmdsArray.Any())
{
@ -61,19 +73,19 @@ namespace NadekoBot.Modules.Help
{
await channel.SendMessageAsync("`List Of Commands:`\n• " + string.Join("\n• ", cmdsArray.Select(c => $"{c.Text}")));
}
await channel.SendMessageAsync($"`You can type \"-h command_name\" to see the help about that specific command.`").ConfigureAwait(false);
await channel.SendMessageAsync($"`You can type \"{NadekoBot.ModulePrefixes[typeof(Help).Name]}h CommandName\" to see the help about that specific command.`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[RequireContext(ContextType.Guild)]
[NadekoCommand, Usage, Description, Aliases]
public async Task H(IUserMessage umsg, [Remainder] string comToFind = null)
{
var channel = (ITextChannel)umsg.Channel;
var channel = umsg.Channel;
comToFind = comToFind?.ToLowerInvariant();
if (string.IsNullOrWhiteSpace(comToFind))
{
await (await (umsg.Author as IGuildUser).CreateDMChannelAsync()).SendMessageAsync(HelpString).ConfigureAwait(false);
IMessageChannel ch = channel is ITextChannel ? await ((IGuildUser)umsg.Author).CreateDMChannelAsync() : channel;
await ch.SendMessageAsync(HelpString).ConfigureAwait(false);
return;
}
var com = _commands.Commands.FirstOrDefault(c => c.Text.ToLowerInvariant() == comToFind || c.Aliases.Select(a=>a.ToLowerInvariant()).Contains(comToFind));
@ -88,18 +100,34 @@ namespace NadekoBot.Modules.Help
if (alias != null)
str += $" / `{ alias }`";
if (com != null)
await channel.SendMessageAsync(str + $@"{Environment.NewLine}**Desc:** {com.Description}
**Usage:** {com.Summary}").ConfigureAwait(false);
await channel.SendMessageAsync(str + $@"{Environment.NewLine}**Desc:** {com.Summary} {GetCommandRequirements(com)}
**Usage:** {com.Remarks}").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
private string GetCommandRequirements(Command cmd)
{
return String.Join(" ", cmd.Source.CustomAttributes
.Where(ca => ca.AttributeType == typeof(OwnerOnlyAttribute) || ca.AttributeType == typeof(RequirePermissionAttribute))
.Select(ca =>
{
if (ca.AttributeType == typeof(OwnerOnlyAttribute))
return "**Bot owner only.**";
else if (ca.AttributeType == typeof(RequirePermissionAttribute))
return $"**Requires {(GuildPermission)ca.ConstructorArguments.FirstOrDefault().Value} server permission.**".Replace("Guild", "Server");
else
return $"**Requires {(GuildPermission)ca.ConstructorArguments.FirstOrDefault().Value} channel permission.**".Replace("Guild", "Server");
}));
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Hgit(IUserMessage umsg)
[OwnerOnly]
public Task Hgit(IUserMessage umsg)
{
var helpstr = new StringBuilder();
var lastModule = "";
foreach (var com in _commands.Commands)
foreach (var com in _commands.Commands.OrderBy(com=>com.Module.Name).GroupBy(c=>c.Text).Select(g=>g.First()))
{
if (com.Module.Name != lastModule)
{
@ -108,39 +136,44 @@ namespace NadekoBot.Modules.Help
helpstr.AppendLine("----------------|--------------|-------");
lastModule = com.Module.Name;
}
helpstr.AppendLine($"`{com.Text}` {string.Join(" ", com.Aliases.Skip(1).Select(a=>"`"+a+"`"))} | {com.Description} | {com.Summary}");
helpstr.AppendLine($"`{com.Text}` {string.Join(" ", com.Aliases.Skip(1).Select(a=>"`"+a+"`"))} | {com.Summary} | {com.Remarks} {GetCommandRequirements(com)}");
}
helpstr = helpstr.Replace((await NadekoBot.Client.GetCurrentUserAsync()).Username , "@BotName");
#if DEBUG
File.WriteAllText("../../../../../docs/Commands List.md", helpstr.ToString());
#else
File.WriteAllText("commandlist.md", helpstr.ToString());
#endif
helpstr = helpstr.Replace(NadekoBot.Client.GetCurrentUser().Username , "@BotName");
File.WriteAllText("../../docs/Commands List.md", helpstr.ToString());
return Task.CompletedTask;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Guide(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
await channel.SendMessageAsync(
@"**LIST OF COMMANDS**: <http://nadekobot.readthedocs.io/en/latest/Commands%20List/>
**Hosting Guides and docs can be found here**: <http://nadekobot.rtfd.io>").ConfigureAwait(false);
@"**LIST OF COMMANDS**: <http://nadekobot.readthedocs.io/en/1.0/Commands%20List/>
**Hosting Guides and docs can be found here**: <http://nadekobot.readthedocs.io/en/1.0/>").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Donate(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
await channel.SendMessageAsync(
$@"You can support the project on patreon. <https://patreon.com/nadekobot> or
$@"You can support the NadekoBot project on patreon. <https://patreon.com/nadekobot> or
You can send donations to `nadekodiscordbot@gmail.com`
Don't forget to leave your discord name or id in the message.
**Thank you** ").ConfigureAwait(false);
}
}
public class CommandTextEqualityComparer : IEqualityComparer<Command>
{
public bool Equals(Command x, Command y) => x.Text == y.Text;
public int GetHashCode(Command obj) => obj.Text.GetHashCode();
}
}

View File

@ -14,10 +14,11 @@ using System.Net.Http;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Music
{
[NadekoModule("ClashOfClans", "!!")]
[NadekoModule("Music", "!!", AutoLoad = false)]
public partial class Music : DiscordModule
{
public static ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers = new ConcurrentDictionary<ulong, MusicPlayer>();
@ -25,7 +26,7 @@ namespace NadekoBot.Modules.Music
public const string MusicDataPath = "data/musicdata";
private IGoogleApiService _google;
public Music(ILocalization loc, CommandService cmds, DiscordSocketClient client, IGoogleApiService google) : base(loc, cmds, client)
public Music(ILocalization loc, CommandService cmds, ShardedDiscordClient client, IGoogleApiService google) : base(loc, cmds, client)
{
//it can fail if its currenctly opened or doesn't exist. Either way i don't care
try { Directory.Delete(MusicDataPath, true); } catch { }
@ -35,7 +36,7 @@ namespace NadekoBot.Modules.Music
_google = google;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public Task Next(IUserMessage umsg)
{
@ -48,7 +49,7 @@ namespace NadekoBot.Modules.Music
return Task.CompletedTask;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public Task Stop(IUserMessage umsg)
{
@ -64,7 +65,7 @@ namespace NadekoBot.Modules.Music
return Task.CompletedTask;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public Task Destroy(IUserMessage umsg)
{
@ -77,7 +78,7 @@ namespace NadekoBot.Modules.Music
return Task.CompletedTask;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Pause(IUserMessage umsg)
{
@ -94,7 +95,7 @@ namespace NadekoBot.Modules.Music
await channel.SendMessageAsync("🎵`Music Player unpaused.`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Queue(IUserMessage umsg, [Remainder] string query)
{
@ -108,7 +109,7 @@ namespace NadekoBot.Modules.Music
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task SoundCloudQueue(IUserMessage umsg, [Remainder] string query)
{
@ -122,7 +123,7 @@ namespace NadekoBot.Modules.Music
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ListQueue(IUserMessage umsg, int page = 1)
{
@ -155,7 +156,7 @@ namespace NadekoBot.Modules.Music
await channel.SendMessageAsync(toSend + string.Join("\n", musicPlayer.Playlist.Skip(startAt).Take(15).Select(v => $"`{number++}.` {v.PrettyName}"))).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task NowPlaying(IUserMessage umsg)
{
@ -170,7 +171,7 @@ namespace NadekoBot.Modules.Music
$"{currentSong.PrettyCurrentTime()}").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Volume(IUserMessage umsg, int val)
{
@ -186,7 +187,7 @@ namespace NadekoBot.Modules.Music
await channel.SendMessageAsync($"🎵 `Volume set to {volume}%`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Defvol(IUserMessage umsg, [Remainder] int val)
{
@ -205,37 +206,9 @@ namespace NadekoBot.Modules.Music
await channel.SendMessageAsync($"🎵 `Default volume set to {val}%`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public Task Mute(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer))
return Task.CompletedTask;
if (((IGuildUser)umsg.Author).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return Task.CompletedTask;
musicPlayer.SetVolume(0);
return Task.CompletedTask;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[RequireContext(ContextType.Guild)]
public Task Max(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer))
return Task.CompletedTask;
if (((IGuildUser)umsg.Author).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return Task.CompletedTask;
musicPlayer.SetVolume(100);
return Task.CompletedTask;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[RequireContext(ContextType.Guild)]
public async Task Shuffle(IUserMessage umsg)
public async Task ShufflePlaylist(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
MusicPlayer musicPlayer;
@ -253,7 +226,7 @@ namespace NadekoBot.Modules.Music
await channel.SendMessageAsync("🎵 `Songs shuffled.`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Playlist(IUserMessage umsg, [Remainder] string playlist)
{
@ -295,7 +268,7 @@ namespace NadekoBot.Modules.Music
await msg.ModifyAsync(m => m.Content = "🎵 `Playlist queue complete.`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task SoundCloudPl(IUserMessage umsg, [Remainder] string pl)
{
@ -332,8 +305,9 @@ namespace NadekoBot.Modules.Music
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task LocalPl(IUserMessage umsg, [Remainder] string directory)
{
var channel = (ITextChannel)umsg.Channel;
@ -361,7 +335,7 @@ namespace NadekoBot.Modules.Music
catch { }
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Radio(IUserMessage umsg, string radio_link)
{
@ -379,8 +353,9 @@ namespace NadekoBot.Modules.Music
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Local(IUserMessage umsg, [Remainder] string path)
{
var channel = (ITextChannel)umsg.Channel;
@ -391,7 +366,7 @@ namespace NadekoBot.Modules.Music
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Move(IUserMessage umsg)
{
@ -403,7 +378,7 @@ namespace NadekoBot.Modules.Music
await musicPlayer.MoveToVoiceChannel(voiceChannel);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Remove(IUserMessage umsg, int num)
{
@ -423,7 +398,7 @@ namespace NadekoBot.Modules.Music
await channel.SendMessageAsync($"🎵**Track {song.PrettyName} at position `#{num}` has been removed.**").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Remove(IUserMessage umsg, string all)
{
@ -438,7 +413,7 @@ namespace NadekoBot.Modules.Music
return;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task MoveSong(IUserMessage umsg, [Remainder] string fromto)
{
@ -474,7 +449,7 @@ namespace NadekoBot.Modules.Music
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task SetMaxQueue(IUserMessage umsg, uint size)
{
@ -488,7 +463,7 @@ namespace NadekoBot.Modules.Music
await channel.SendMessageAsync($"🎵 `Max queue set to {(size == 0 ? ("unlimited") : size + " tracks")}`");
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ReptCurSong(IUserMessage umsg)
{
@ -506,7 +481,7 @@ namespace NadekoBot.Modules.Music
.ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task RepeatPl(IUserMessage umsg)
{
@ -518,40 +493,135 @@ namespace NadekoBot.Modules.Music
await channel.SendMessageAsync($"🎵🔁`Repeat playlist {(currentValue ? "enabled" : "disabled")}`").ConfigureAwait(false);
}
///
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Save(IUserMessage umsg, [Remainder] string name)
//{
// var channel = (ITextChannel)umsg.Channel;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Save(IUserMessage umsg, [Remainder] string name)
{
var channel = (ITextChannel)umsg.Channel;
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer))
return;
//}
var curSong = musicPlayer.CurrentSong;
var songs = musicPlayer.Playlist.Append(curSong)
.Select(s=> new PlaylistSong() {
Provider = s.SongInfo.Provider,
ProviderType = s.SongInfo.ProviderType,
Title = s.SongInfo.Title,
Uri = s.SongInfo.Uri,
Query = s.SongInfo.Query,
}).ToList();
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Load(IUserMessage umsg, [Remainder] string name)
//{
// var channel = (ITextChannel)umsg.Channel;
MusicPlaylist playlist;
using (var uow = DbHandler.UnitOfWork())
{
playlist = new MusicPlaylist
{
Name = name,
Author = umsg.Author.Username,
AuthorId = umsg.Author.Id,
Songs = songs,
};
uow.MusicPlaylists.Add(playlist);
await uow.CompleteAsync().ConfigureAwait(false);
}
//}
await channel.SendMessageAsync(($"🎵 `Saved playlist as {name}.` `Id: {playlist.Id}`")).ConfigureAwait(false);
}
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Playlists(IUserMessage umsg, [Remainder] string num)
//{
// var channel = (ITextChannel)umsg.Channel;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Load(IUserMessage umsg, [Remainder] int id)
{
var channel = (ITextChannel)umsg.Channel;
//}
MusicPlaylist mpl;
using (var uow = DbHandler.UnitOfWork())
{
mpl = uow.MusicPlaylists.GetWithSongs(id);
}
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task DeletePlaylist(IUserMessage umsg, [Remainder] string pl)
//{
// var channel = (ITextChannel)umsg.Channel;
if (mpl == null)
{
await channel.SendMessageAsync("`Can't find playlist with that ID`").ConfigureAwait(false);
return;
}
IUserMessage msg = null;
try { msg = await channel.SendMessageAsync($"`Attempting to load {mpl.Songs.Count} songs...`").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
foreach (var item in mpl.Songs)
{
var usr = (IGuildUser)umsg.Author;
try
{
await QueueSong(usr, channel, usr.VoiceChannel, item.Query, true, item.ProviderType).ConfigureAwait(false);
}
catch { break; }
}
if (msg != null)
await msg.ModifyAsync(m => m.Content = $"`Done loading playlist {mpl.Name}.`").ConfigureAwait(false);
}
//}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Playlists(IUserMessage umsg, [Remainder] int num = 1)
{
var channel = (ITextChannel)umsg.Channel;
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
if (num <= 0)
return;
List<MusicPlaylist> playlists;
using (var uow = DbHandler.UnitOfWork())
{
playlists = uow.MusicPlaylists.GetPlaylistsOnPage(num);
}
await channel.SendMessageAsync($@"`Page {num} of saved playlists`
" + string.Join("\n", playlists.Select(r => $"`#{r.Id}` - `{r.Name}` by {r.Author} - **{r.Songs.Count}** songs"))).ConfigureAwait(false);
}
//todo only author or owner
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task DeletePlaylist(IUserMessage umsg, [Remainder] int id)
{
var channel = (ITextChannel)umsg.Channel;
bool success = false;
MusicPlaylist pl = null;
try
{
using (var uow = DbHandler.UnitOfWork())
{
pl = uow.MusicPlaylists.Get(id);
if (pl != null)
{
if (NadekoBot.Credentials.IsOwner(umsg.Author) || pl.AuthorId == umsg.Author.Id)
{
uow.MusicPlaylists.Remove(pl);
await uow.CompleteAsync().ConfigureAwait(false);
success = true;
}
else
success = false;
}
}
if (!success)
await channel.SendMessageAsync("Failed to delete that playlist. It either doesn't exist, or you are not its author.").ConfigureAwait(false);
else
await channel.SendMessageAsync("`Playlist successfully deleted.`").ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Goto(IUserMessage umsg, int time)
{
@ -588,7 +658,7 @@ namespace NadekoBot.Modules.Music
await channel.SendMessageAsync($"`Skipped to {minutes}:{seconds}`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task GetLink(IUserMessage umsg, int index = 0)
{
@ -623,7 +693,7 @@ namespace NadekoBot.Modules.Music
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Autoplay(IUserMessage umsg)
{
@ -671,16 +741,13 @@ namespace NadekoBot.Modules.Music
await lastFinishedMessage.DeleteAsync().ConfigureAwait(false);
if (playingMessage != null)
await playingMessage.DeleteAsync().ConfigureAwait(false);
lastFinishedMessage = await textCh.SendMessageAsync($"🎵`Finished`{song.PrettyName}").ConfigureAwait(false);
try { lastFinishedMessage = await textCh.SendMessageAsync($"🎵`Finished`{song.PrettyName}").ConfigureAwait(false); } catch { }
if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.Provider == "YouTube")
{
await QueueSong(queuer.Guild.GetCurrentUser(), textCh, voiceCh, (await NadekoBot.Google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList().Shuffle().FirstOrDefault(), silent, musicType).ConfigureAwait(false);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
catch { }
}
};
mp.OnStarted += async (s, song) =>
@ -691,13 +758,8 @@ namespace NadekoBot.Modules.Music
if (sender == null)
return;
try
{
var msgTxt = $"🎵`Playing`{song.PrettyName} `Vol: {(int)(sender.Volume * 100)}%`";
playingMessage = await textCh.SendMessageAsync(msgTxt).ConfigureAwait(false);
}
catch { }
try { playingMessage = await textCh.SendMessageAsync(msgTxt).ConfigureAwait(false); } catch { }
}
};
return mp;
@ -712,14 +774,15 @@ namespace NadekoBot.Modules.Music
}
catch (PlaylistFullException)
{
await textCh.SendMessageAsync($"🎵 `Queue is full at {musicPlayer.MaxQueueSize}/{musicPlayer.MaxQueueSize}.` ");
try { await textCh.SendMessageAsync($"🎵 `Queue is full at {musicPlayer.MaxQueueSize}/{musicPlayer.MaxQueueSize}.` "); } catch { }
throw;
}
if (!silent)
{
try
{
var queuedMessage = await textCh.SendMessageAsync($"🎵`Queued`{resolvedSong.PrettyName} **at** `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false);
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(async () =>
var t = Task.Run(async () =>
{
await Task.Delay(10000).ConfigureAwait(false);
try
@ -728,7 +791,8 @@ namespace NadekoBot.Modules.Music
}
catch { }
}).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
catch { } // if queued message sending fails, don't attempt to delete it
}
}
}

View File

@ -18,11 +18,11 @@ namespace NadekoBot.Modules.NSFW
[NadekoModule("NSFW", "~")]
public class NSFW : DiscordModule
{
public NSFW(ILocalization loc, CommandService cmds, DiscordSocketClient client) : base(loc, cmds, client)
public NSFW(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client)
{
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Hentai(IUserMessage umsg, [Remainder] string tag = null)
{
@ -41,7 +41,7 @@ namespace NadekoBot.Modules.NSFW
await channel.SendMessageAsync(String.Join("\n\n", links)).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Danbooru(IUserMessage umsg, [Remainder] string tag = null)
{
@ -55,26 +55,12 @@ namespace NadekoBot.Modules.NSFW
await channel.SendMessageAsync(link).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Gelbooru(IUserMessage umsg, [Remainder] string tag = null)
{
var channel = (ITextChannel)umsg.Channel;
tag = tag?.Trim() ?? "";
var link = await GetRule34ImageLink(tag).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(link))
await channel.SendMessageAsync("Search yielded no results ;(");
else
await channel.SendMessageAsync(link).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[RequireContext(ContextType.Guild)]
public async Task Rule34(IUserMessage umsg, [Remainder] string tag = null)
{
var channel = (ITextChannel)umsg.Channel;
tag = tag?.Trim() ?? "";
var link = await GetGelbooruImageLink(tag).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(link))
@ -83,7 +69,21 @@ namespace NadekoBot.Modules.NSFW
await channel.SendMessageAsync(link).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Rule34(IUserMessage umsg, [Remainder] string tag = null)
{
var channel = (ITextChannel)umsg.Channel;
tag = tag?.Trim() ?? "";
var link = await GetRule34ImageLink(tag).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(link))
await channel.SendMessageAsync("Search yielded no results ;(");
else
await channel.SendMessageAsync(link).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task E621(IUserMessage umsg, [Remainder] string tag = null)
{
@ -97,7 +97,7 @@ namespace NadekoBot.Modules.NSFW
await channel.SendMessageAsync(link).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Cp(IUserMessage umsg)
{
@ -106,7 +106,7 @@ namespace NadekoBot.Modules.NSFW
await channel.SendMessageAsync("http://i.imgur.com/MZkY1md.jpg").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Boobs(IUserMessage umsg)
{
@ -126,7 +126,7 @@ namespace NadekoBot.Modules.NSFW
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Butts(IUserMessage umsg)
{

View File

@ -0,0 +1,115 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Modules.Games.Trivia;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static NadekoBot.Services.Database.Models.BlacklistItem;
namespace NadekoBot.Modules.Permissions
{
public partial class Permissions
{
public enum AddRemove
{
Add,
Rem
}
[Group]
public class BlacklistCommands
{
public static ConcurrentHashSet<BlacklistItem> BlacklistedItems { get; set; } = new ConcurrentHashSet<BlacklistItem>();
static BlacklistCommands()
{
using (var uow = DbHandler.UnitOfWork())
{
BlacklistedItems = new ConcurrentHashSet<BlacklistItem>(uow.BotConfig.GetOrCreate().Blacklist);
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task UserBlacklist(IUserMessage imsg, AddRemove action, ulong id)
=> Blacklist(imsg, action, id, BlacklistType.User);
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task UserBlacklist(IUserMessage imsg, AddRemove action, IUser usr)
=> Blacklist(imsg, action, usr.Id, BlacklistType.User);
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task ChannelBlacklist(IUserMessage imsg, AddRemove action, ulong id)
=> Blacklist(imsg, action, id, BlacklistType.Channel);
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task ServerBlacklist(IUserMessage imsg, AddRemove action, ulong id)
=> Blacklist(imsg, action, id, BlacklistType.Server);
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task ServerBlacklist(IUserMessage imsg, AddRemove action, IGuild guild)
=> Blacklist(imsg, action, guild.Id, BlacklistType.Server);
private async Task Blacklist(IUserMessage imsg, AddRemove action, ulong id, BlacklistType type)
{
var channel = imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
if (action == AddRemove.Add)
{
var item = new BlacklistItem { ItemId = id, Type = type };
uow.BotConfig.GetOrCreate().Blacklist.Add(item);
BlacklistedItems.Add(item);
}
else
{
uow.BotConfig.GetOrCreate().Blacklist.RemoveWhere(bi => bi.ItemId == id && bi.Type == type);
BlacklistedItems.RemoveWhere(bi => bi.ItemId == id && bi.Type == type);
}
await uow.CompleteAsync().ConfigureAwait(false);
}
if (action == AddRemove.Rem)
{
TriviaGame tg;
switch (type)
{
case BlacklistType.Server:
Games.Games.TriviaCommands.RunningTrivias.TryRemove(id, out tg);
if (tg != null)
{
await tg.StopGame().ConfigureAwait(false);
}
break;
case BlacklistType.Channel:
var item = Games.Games.TriviaCommands.RunningTrivias.FirstOrDefault(kvp => kvp.Value.channel.Id == id);
Games.Games.TriviaCommands.RunningTrivias.TryRemove(item.Key, out tg);
if (tg != null)
{
await tg.StopGame().ConfigureAwait(false);
}
break;
case BlacklistType.User:
break;
default:
break;
}
}
await channel.SendMessageAsync(":ok:").ConfigureAwait(false);
}
}
}
}

View File

@ -0,0 +1,124 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Permissions
{
public partial class Permissions
{
public class ActiveCooldown
{
public string Command { get; set; }
public ulong UserId { get; set; }
}
[Group]
public class CmdCdsCommands
{
public static ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>> commandCooldowns { get; }
private static ConcurrentDictionary<ulong, ConcurrentHashSet<ActiveCooldown>> activeCooldowns = new ConcurrentDictionary<ulong, ConcurrentHashSet<ActiveCooldown>>();
static CmdCdsCommands()
{
using (var uow = DbHandler.UnitOfWork())
{
var configs = uow.GuildConfigs.GetAll();
commandCooldowns = new ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>>(configs.ToDictionary(k => k.GuildId, v => new ConcurrentHashSet<CommandCooldown>(v.CommandCooldowns)));
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task CmdCooldown(IUserMessage imsg, Command command, int secs)
{
var channel = (ITextChannel)imsg.Channel;
if (secs < 0 || secs > 3600)
{
await channel.SendMessageAsync("Invalid second parameter. (Must be a number between 0 and 3600)").ConfigureAwait(false);
return;
}
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(channel.Guild.Id);
var localSet = commandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CommandCooldown>());
config.CommandCooldowns.RemoveWhere(cc => cc.CommandName == command.Text.ToLowerInvariant());
localSet.RemoveWhere(cc => cc.CommandName == command.Text.ToLowerInvariant());
if (secs != 0)
{
var cc = new CommandCooldown()
{
CommandName = command.Text.ToLowerInvariant(),
Seconds = secs,
};
config.CommandCooldowns.Add(cc);
localSet.Add(cc);
}
await uow.CompleteAsync().ConfigureAwait(false);
}
if (secs == 0)
{
var activeCds = activeCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<ActiveCooldown>());
activeCds.RemoveWhere(ac => ac.Command == command.Text.ToLowerInvariant());
await channel.SendMessageAsync($"Command **{command}** has no coooldown now and all existing cooldowns have been cleared.").ConfigureAwait(false);
}
else
await channel.SendMessageAsync($"Command **{command}** now has a **{secs} {(secs == 1 ? "second" : "seconds")}** cooldown.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AllCmdCooldowns(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
var localSet = commandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CommandCooldown>());
if (!localSet.Any())
await channel.SendMessageAsync("`No command cooldowns set.`").ConfigureAwait(false);
else
await channel.SendTableAsync("", localSet.Select(c => c.CommandName + ": " + c.Seconds + " secs"), s => $"{s,-30}", 2).ConfigureAwait(false);
}
public static bool HasCooldown(Command cmd, IGuild guild, IUser user)
{
if (guild == null)
return false;
var cmdcds = CmdCdsCommands.commandCooldowns.GetOrAdd(guild.Id, new ConcurrentHashSet<CommandCooldown>());
CommandCooldown cdRule;
if ((cdRule = cmdcds.FirstOrDefault(cc => cc.CommandName == cmd.Text.ToLowerInvariant())) != null)
{
var activeCdsForGuild = activeCooldowns.GetOrAdd(guild.Id, new ConcurrentHashSet<ActiveCooldown>());
if (activeCdsForGuild.FirstOrDefault(ac => ac.UserId == user.Id && ac.Command == cmd.Text.ToLowerInvariant()) != null)
{
return true;
}
else
{
activeCdsForGuild.Add(new ActiveCooldown()
{
UserId = user.Id,
Command = cmd.Text.ToLowerInvariant(),
});
var t = Task.Run(async () =>
{
await Task.Delay(cdRule.Seconds * 1000);
activeCdsForGuild.RemoveWhere(ac => ac.Command == cmd.Text.ToLowerInvariant() && ac.UserId == user.Id);
});
}
}
return false;
}
}
}
}

View File

@ -0,0 +1,238 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Permissions
{
public partial class Permissions
{
[Group]
public class FilterCommands
{
public static ConcurrentHashSet<ulong> InviteFilteringChannels { get; set; }
public static ConcurrentHashSet<ulong> InviteFilteringServers { get; set; }
//serverid, filteredwords
private static ConcurrentDictionary<ulong, ConcurrentHashSet<string>> ServerFilteredWords { get; set; }
public static ConcurrentHashSet<ulong> WordFilteringChannels { get; set; }
public static ConcurrentHashSet<ulong> WordFilteringServers { get; set; }
public static ConcurrentHashSet<string> FilteredWordsForChannel(ulong channelId, ulong guildId)
{
ConcurrentHashSet<string> words = new ConcurrentHashSet<string>();
if(WordFilteringChannels.Contains(channelId))
ServerFilteredWords.TryGetValue(guildId, out words);
return words;
}
public static ConcurrentHashSet<string> FilteredWordsForServer(ulong guildId)
{
var words = new ConcurrentHashSet<string>();
if(WordFilteringServers.Contains(guildId))
ServerFilteredWords.TryGetValue(guildId, out words);
return words;
}
static FilterCommands()
{
using (var uow = DbHandler.UnitOfWork())
{
var guildConfigs = uow.GuildConfigs.GetAll();
InviteFilteringServers = new ConcurrentHashSet<ulong>(guildConfigs.Where(gc => gc.FilterInvites).Select(gc => gc.GuildId));
InviteFilteringChannels = new ConcurrentHashSet<ulong>(guildConfigs.SelectMany(gc => gc.FilterInvitesChannelIds.Select(fci => fci.ChannelId)));
var dict = guildConfigs.ToDictionary(gc => gc.GuildId, gc => new ConcurrentHashSet<string>(gc.FilteredWords.Select(fw => fw.Word)));
ServerFilteredWords = new ConcurrentDictionary<ulong, ConcurrentHashSet<string>>(dict);
var serverFiltering = guildConfigs.Where(gc => gc.FilterWords);
WordFilteringServers = new ConcurrentHashSet<ulong>(serverFiltering.Select(gc => gc.GuildId));
WordFilteringChannels = new ConcurrentHashSet<ulong>(guildConfigs.SelectMany(gc => gc.FilterWordsChannelIds.Select(fwci => fwci.ChannelId)));
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task SrvrFilterInv(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
bool enabled;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(channel.Guild.Id);
enabled = config.FilterInvites = !config.FilterInvites;
await uow.CompleteAsync().ConfigureAwait(false);
}
if (enabled)
{
InviteFilteringServers.Add(channel.Guild.Id);
await channel.SendMessageAsync("`Invite filtering enabled on this server.`").ConfigureAwait(false);
}
else
{
InviteFilteringServers.TryRemove(channel.Guild.Id);
await channel.SendMessageAsync("`Invite filtering disabled on this server.`").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ChnlFilterInv(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
int removed;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(channel.Guild.Id);
removed = config.FilterInvitesChannelIds.RemoveWhere(fc => fc.ChannelId == channel.Id);
if (removed == 0)
{
config.FilterInvitesChannelIds.Add(new Services.Database.Models.FilterChannelId()
{
ChannelId = channel.Id
});
}
await uow.CompleteAsync().ConfigureAwait(false);
}
if (removed == 0)
{
InviteFilteringChannels.Add(channel.Id);
await channel.SendMessageAsync("`Invite filtering enabled on this channel.`").ConfigureAwait(false);
}
else
{
InviteFilteringChannels.TryRemove(channel.Id);
await channel.SendMessageAsync("`Invite filtering disabled on this channel.`").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task SrvrFilterWords(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
bool enabled;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(channel.Guild.Id);
enabled = config.FilterWords = !config.FilterWords;
await uow.CompleteAsync().ConfigureAwait(false);
}
if (enabled)
{
WordFilteringServers.Add(channel.Guild.Id);
await channel.SendMessageAsync("`Word filtering enabled on this server.`").ConfigureAwait(false);
}
else
{
WordFilteringServers.TryRemove(channel.Guild.Id);
await channel.SendMessageAsync("`Word filtering disabled on this server.`").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ChnlFilterWords(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
int removed;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(channel.Guild.Id);
removed = config.FilterWordsChannelIds.RemoveWhere(fc => fc.ChannelId == channel.Id);
if (removed == 0)
{
config.FilterWordsChannelIds.Add(new Services.Database.Models.FilterChannelId()
{
ChannelId = channel.Id
});
}
await uow.CompleteAsync().ConfigureAwait(false);
}
if (removed == 0)
{
WordFilteringChannels.Add(channel.Id);
await channel.SendMessageAsync("`Word filtering enabled on this channel.`").ConfigureAwait(false);
}
else
{
WordFilteringChannels.TryRemove(channel.Id);
await channel.SendMessageAsync("`Word filtering disabled on this channel.`").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task FilterWord(IUserMessage imsg, [Remainder] string word)
{
var channel = (ITextChannel)imsg.Channel;
word = word?.Trim().ToLowerInvariant();
if (string.IsNullOrWhiteSpace(word))
return;
int removed;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(channel.Guild.Id);
removed = config.FilteredWords.RemoveWhere(fw => fw.Word == word);
if (removed == 0)
config.FilteredWords.Add(new Services.Database.Models.FilteredWord() { Word = word });
await uow.CompleteAsync().ConfigureAwait(false);
}
var filteredWords = ServerFilteredWords.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<string>());
if (removed == 0)
{
filteredWords.Add(word);
await channel.SendMessageAsync($"Word `{word}` successfully added to the list of filtered words.")
.ConfigureAwait(false);
}
else
{
filteredWords.TryRemove(word);
await channel.SendMessageAsync($"Word `{word}` removed from the list of filtered words.")
.ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task LstFilterWords(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
ConcurrentHashSet<string> filteredWords;
ServerFilteredWords.TryGetValue(channel.Guild.Id, out filteredWords);
await channel.SendMessageAsync($"`List of banned words:`\n" + string.Join(",\n", filteredWords))
.ConfigureAwait(false);
}
}
}
}

View File

@ -0,0 +1,35 @@
using Discord.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord;
namespace NadekoBot.Modules.Permissions
{
public class PermissionAction
{
public static PermissionAction Enable => new PermissionAction(true);
public static PermissionAction Disable => new PermissionAction(false);
public bool Value { get; }
public PermissionAction(bool value)
{
this.Value = value;
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
return this.Value == ((PermissionAction)obj).Value;
}
public override int GetHashCode() => Value.GetHashCode();
}
}

View File

@ -0,0 +1,242 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Permissions
{
public static class PermissionExtensions
{
public static bool CheckPermissions(this IEnumerable<Permission> permsEnumerable, IUserMessage message, Command command)
{
var perms = permsEnumerable as List<Permission> ?? permsEnumerable.ToList();
int throwaway;
return perms.CheckPermissions(message, command.Name, command.Module.Name, out throwaway);
}
public static bool CheckPermissions(this IEnumerable<Permission> permsEnumerable, IUserMessage message, string commandName, string moduleName)
{
var perms = permsEnumerable as List<Permission> ?? permsEnumerable.ToList();
int throwaway;
return perms.CheckPermissions(message, commandName, moduleName, out throwaway);
}
public static bool CheckPermissions(this IEnumerable<Permission> permsEnumerable, IUserMessage message, string commandName, string moduleName, out int permIndex)
{
var perms = permsEnumerable as List<Permission> ?? permsEnumerable.ToList();
for (int i = 0; i < perms.Count; i++)
{
var perm = perms[i];
var result = perm.CheckPermission(message, commandName, moduleName);
if (result == null)
{
continue;
}
else
{
permIndex = i;
return result.Value;
}
}
permIndex = -1; //defaut behaviour
return true;
}
//null = not applicable
//true = applicable, allowed
//false = applicable, not allowed
public static bool? CheckPermission(this Permission perm, IUserMessage message, string commandName, string moduleName)
{
if (!((perm.SecondaryTarget == SecondaryPermissionType.Command &&
perm.SecondaryTargetName.ToLowerInvariant() == commandName.ToLowerInvariant()) ||
(perm.SecondaryTarget == SecondaryPermissionType.Module &&
perm.SecondaryTargetName.ToLowerInvariant() == moduleName.ToLowerInvariant()) ||
perm.SecondaryTarget == SecondaryPermissionType.AllModules))
return null;
var guildUser = message.Author as IGuildUser;
switch (perm.PrimaryTarget)
{
case PrimaryPermissionType.User:
if (perm.PrimaryTargetId == message.Author.Id)
return perm.State;
break;
case PrimaryPermissionType.Channel:
if (perm.PrimaryTargetId == message.Channel.Id)
return perm.State;
break;
case PrimaryPermissionType.Role:
if (guildUser == null)
break;
if (guildUser.Roles.Any(r => r.Id == perm.PrimaryTargetId))
return perm.State;
break;
case PrimaryPermissionType.Server:
if (guildUser == null)
break;
return perm.State;
}
return null;
}
public static string GetCommand(this Permission perm, IGuild guild = null)
{
var com = "";
switch (perm.PrimaryTarget)
{
case PrimaryPermissionType.User:
com += "u";
break;
case PrimaryPermissionType.Channel:
com += "c";
break;
case PrimaryPermissionType.Role:
com += "r";
break;
case PrimaryPermissionType.Server:
com += "s";
break;
}
switch (perm.SecondaryTarget)
{
case SecondaryPermissionType.Module:
com += "m";
break;
case SecondaryPermissionType.Command:
com += "c";
break;
case SecondaryPermissionType.AllModules:
com = "a" + com + "m";
break;
}
com += " " + (perm.SecondaryTargetName != "*" ? perm.SecondaryTargetName + " " : "") + (perm.State ? "enable" : "disable") + " ";
switch (perm.PrimaryTarget)
{
case PrimaryPermissionType.User:
if (guild == null)
com += $"<@{perm.PrimaryTargetId}>";
else
com += guild.GetUser(perm.PrimaryTargetId).ToString() ?? $"<@{perm.PrimaryTargetId}>";
break;
case PrimaryPermissionType.Channel:
com += $"<#{perm.PrimaryTargetId}>";
break;
case PrimaryPermissionType.Role:
com += $"<@&{perm.PrimaryTargetId}>";
break;
case PrimaryPermissionType.Server:
break;
}
return NadekoBot.ModulePrefixes[typeof(Permissions).Name] + com;
}
public static void Prepend(this Permission perm, Permission toAdd)
{
perm = perm.GetRoot();
perm.Previous = toAdd;
toAdd.Next = perm;
}
/* /this can't work if index < 0 and perm isn't roo
public static void Insert(this Permission perm, int index, Permission toAdd)
{
if (index < 0)
throw new IndexOutOfRangeException();
if (index == 0)
{
perm.Prepend(toAdd);
return;
}
var atIndex = perm;
var i = 0;
while (i != index)
{
atIndex = atIndex.Next;
i++;
if (atIndex == null)
throw new IndexOutOfRangeException();
}
var previous = atIndex.Previous;
//connect right side
atIndex.Previous = toAdd;
toAdd.Next = atIndex;
//connect left side
toAdd.Previous = previous;
previous.Next = toAdd;
}
*/
public static Permission RemoveAt(this Permission perm, int index)
{
if (index <= 0) //can't really remove at 0, that means deleting the element right now. Just use perm.Next if its 0
throw new IndexOutOfRangeException();
var toRemove = perm;
var i = 0;
while (i != index)
{
toRemove = toRemove.Next;
i++;
if (toRemove == null)
throw new IndexOutOfRangeException();
}
toRemove.Previous.Next = toRemove.Next;
if (toRemove.Next != null)
toRemove.Next.Previous = toRemove.Previous;
return toRemove;
}
public static Permission GetAt(this Permission perm, int index)
{
if (index < 0)
throw new IndexOutOfRangeException();
var temp = perm;
while (index > 0) { temp = temp?.Next; index--; }
if (temp == null)
throw new IndexOutOfRangeException();
return temp;
}
public static int Count(this Permission perm)
{
var i = 1;
var temp = perm;
while ((temp = temp.Next) != null) { i++; }
return i;
}
public static IEnumerable<Permission> AsEnumerable(this Permission perm)
{
do yield return perm;
while ((perm = perm.Next) != null);
}
public static Permission GetRoot(this Permission perm)
{
Permission toReturn;
do toReturn = perm;
while ((perm = perm.Previous) != null);
return toReturn;
}
}
}

View File

@ -0,0 +1,511 @@
using NadekoBot.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Services;
using Discord;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using Discord.API;
namespace NadekoBot.Modules.Permissions
{
[NadekoModule("Permissions", ";")]
public partial class Permissions : DiscordModule
{
public Permissions(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client)
{
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Verbose(IUserMessage msg, PermissionAction action)
{
var channel = (ITextChannel)msg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(channel.Guild.Id);
config.VerbosePermissions = action.Value;
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync("I will " + (action.Value ? "now" : "no longer") + " show permission warnings.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task PermRole(IUserMessage msg, [Remainder] IRole role = null)
{
var channel = (ITextChannel)msg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(channel.Guild.Id);
if (role == null)
{
await channel.SendMessageAsync($"Current permission role is **{config.PermissionRole}**.").ConfigureAwait(false);
return;
}
else {
config.PermissionRole = role.Name.Trim();
await uow.CompleteAsync().ConfigureAwait(false);
}
}
await channel.SendMessageAsync($"Users now require **{role.Name}** role in order to edit permissions.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ListPerms(IUserMessage msg, int page = 1)
{
var channel = (ITextChannel)msg.Channel;
if (page < 1 || page > 4)
return;
string toSend = "";
using (var uow = DbHandler.UnitOfWork())
{
var perms = uow.GuildConfigs.PermissionsFor(channel.Guild.Id).RootPermission;
var i = 1;
toSend = Format.Code($"Permissions page {page}") + "\n\n" + String.Join("\n", perms.AsEnumerable().Skip((page - 1) * 20).Take(20).Select(p => $"`{(i++)}.` {(p.Next == null ? Format.Bold(p.GetCommand(channel.Guild) + " [uneditable]") : (p.GetCommand(channel.Guild)))}"));
}
if (string.IsNullOrWhiteSpace(toSend))
await channel.SendMessageAsync("`No permissions set.`").ConfigureAwait(false);
else
await channel.SendMessageAsync(toSend).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task RemovePerm(IUserMessage imsg, int index)
{
var channel = (ITextChannel)imsg.Channel;
index -= 1;
try
{
Permission p;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.PermissionsFor(channel.Guild.Id);
var perms = config.RootPermission;
if (index == perms.Count() - 1)
{
return;
}
else if (index == 0)
{
p = perms;
config.RootPermission = perms.Next;
}
else
{
p = perms.RemoveAt(index);
}
await uow.CompleteAsync().ConfigureAwait(false);
}
using (var uow2 = DbHandler.UnitOfWork())
{
uow2._context.Remove<Permission>(p);
uow2._context.SaveChanges();
}
await channel.SendMessageAsync($"{imsg.Author.Mention} removed permission **{p.GetCommand(channel.Guild)}** from position #{index + 1}.").ConfigureAwait(false);
}
catch (ArgumentOutOfRangeException)
{
await channel.SendMessageAsync("`No command on that index found.`").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task MovePerm(IUserMessage imsg, int from, int to)
{
from -= 1;
to -= 1;
var channel = (ITextChannel)imsg.Channel;
if (!(from == to || from < 0 || to < 0))
{
try
{
Permission fromPerm = null;
Permission toPerm = null;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.PermissionsFor(channel.Guild.Id);
var perms = config.RootPermission;
var root = perms;
var index = 0;
var fromFound = false;
var toFound = false;
while ((!toFound || !fromFound) && perms != null)
{
if (index == from)
{
fromPerm = perms;
fromFound = true;
}
if (index == to)
{
toPerm = perms;
toFound = true;
}
if (!toFound)
{
toPerm = perms; //In case of to > size
}
perms = perms.Next;
index++;
}
if (perms == null)
{
if (!fromFound)
{
await channel.SendMessageAsync($"`Can't find permission at index `#{++from}`").ConfigureAwait(false);
return;
}
if (!toFound)
{
await channel.SendMessageAsync($"`Can't find permission at index `#{++to}`").ConfigureAwait(false);
return;
}
}
//Change chain for from indx
var next = fromPerm.Next;
var pre = fromPerm.Previous;
if (pre != null)
pre.Next = next;
if (fromPerm.Next == null || toPerm.Next == null)
{
throw new IndexOutOfRangeException();
}
next.Previous = pre;
if (from == 0)
{
root = next;
}
await uow.CompleteAsync().ConfigureAwait(false);
//Inserting
if (to > from)
{
fromPerm.Previous = toPerm;
fromPerm.Next = toPerm.Next;
toPerm.Next.Previous = fromPerm;
toPerm.Next = fromPerm;
}
else
{
pre = toPerm.Previous;
fromPerm.Next = toPerm;
fromPerm.Previous = pre;
toPerm.Previous = fromPerm;
if (pre != null)
pre.Next = fromPerm;
}
config.RootPermission = fromPerm.GetRoot();
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"`Moved permission:` \"{fromPerm.GetCommand(channel.Guild)}\" `from #{++from} to #{++to}.`").ConfigureAwait(false);
return;
}
catch (Exception e) when (e is ArgumentOutOfRangeException || e is IndexOutOfRangeException)
{
}
}
await channel.SendMessageAsync("`Invalid index(es) specified.`").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task SrvrCmd(IUserMessage imsg, Command command, PermissionAction action)
{
var channel = (ITextChannel)imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var newPerm = new Permission
{
PrimaryTarget = PrimaryPermissionType.Server,
PrimaryTargetId = 0,
SecondaryTarget = SecondaryPermissionType.Command,
SecondaryTargetName = command.Text.ToLowerInvariant(),
State = action.Value,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"{(action.Value ? "Allowed" : "Denied")} usage of `{command.Text}` command on this server.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task SrvrMdl(IUserMessage imsg, Module module, PermissionAction action)
{
var channel = (ITextChannel)imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var newPerm = new Permission
{
PrimaryTarget = PrimaryPermissionType.Server,
PrimaryTargetId = 0,
SecondaryTarget = SecondaryPermissionType.Module,
SecondaryTargetName = module.Name.ToLowerInvariant(),
State = action.Value,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"{(action.Value ? "Allowed" : "Denied")} usage of `{module.Name}` module on this server.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task UsrCmd(IUserMessage imsg, Command command, PermissionAction action, [Remainder] IGuildUser user)
{
var channel = (ITextChannel)imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var newPerm = new Permission
{
PrimaryTarget = PrimaryPermissionType.User,
PrimaryTargetId = user.Id,
SecondaryTarget = SecondaryPermissionType.Command,
SecondaryTargetName = command.Text.ToLowerInvariant(),
State = action.Value,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"{(action.Value ? "Allowed" : "Denied")} usage of `{command.Text}` command for `{user}` user.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task UsrMdl(IUserMessage imsg, Module module, PermissionAction action, [Remainder] IGuildUser user)
{
var channel = (ITextChannel)imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var newPerm = new Permission
{
PrimaryTarget = PrimaryPermissionType.User,
PrimaryTargetId = user.Id,
SecondaryTarget = SecondaryPermissionType.Module,
SecondaryTargetName = module.Name.ToLowerInvariant(),
State = action.Value,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"{(action.Value ? "Allowed" : "Denied")} usage of `{module.Name}` module for `{user}` user.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task RoleCmd(IUserMessage imsg, Command command, PermissionAction action, [Remainder] IRole role)
{
var channel = (ITextChannel)imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var newPerm = new Permission
{
PrimaryTarget = PrimaryPermissionType.Role,
PrimaryTargetId = role.Id,
SecondaryTarget = SecondaryPermissionType.Command,
SecondaryTargetName = command.Text.ToLowerInvariant(),
State = action.Value,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"{(action.Value ? "Allowed" : "Denied")} usage of `{command.Text}` command for `{role}` role.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task RoleMdl(IUserMessage imsg, Module module, PermissionAction action, [Remainder] IRole role)
{
var channel = (ITextChannel)imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var newPerm = new Permission
{
PrimaryTarget = PrimaryPermissionType.Role,
PrimaryTargetId = role.Id,
SecondaryTarget = SecondaryPermissionType.Module,
SecondaryTargetName = module.Name.ToLowerInvariant(),
State = action.Value,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"{(action.Value ? "Allowed" : "Denied")} usage of `{module.Name}` module for `{role}` role.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ChnlCmd(IUserMessage imsg, Command command, PermissionAction action, [Remainder] ITextChannel chnl)
{
var channel = (ITextChannel)imsg.Channel;
try
{
using (var uow = DbHandler.UnitOfWork())
{
var newPerm = new Permission
{
PrimaryTarget = PrimaryPermissionType.Channel,
PrimaryTargetId = chnl.Id,
SecondaryTarget = SecondaryPermissionType.Command,
SecondaryTargetName = command.Text.ToLowerInvariant(),
State = action.Value,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm);
await uow.CompleteAsync().ConfigureAwait(false);
}
}
catch (Exception ex) {
Console.WriteLine(ex);
}
await channel.SendMessageAsync($"{(action.Value ? "Allowed" : "Denied")} usage of `{command.Text}` command for `{chnl}` channel.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ChnlMdl(IUserMessage imsg, Module module, PermissionAction action, [Remainder] ITextChannel chnl)
{
var channel = (ITextChannel)imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var newPerm = new Permission
{
PrimaryTarget = PrimaryPermissionType.Channel,
PrimaryTargetId = chnl.Id,
SecondaryTarget = SecondaryPermissionType.Module,
SecondaryTargetName = module.Name.ToLowerInvariant(),
State = action.Value,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"{(action.Value ? "Allowed" : "Denied")} usage of `{module.Name}` module for `{chnl}` channel.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AllChnlMdls(IUserMessage imsg, PermissionAction action, [Remainder] ITextChannel chnl)
{
var channel = (ITextChannel)imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var newPerm = new Permission
{
PrimaryTarget = PrimaryPermissionType.Channel,
PrimaryTargetId = chnl.Id,
SecondaryTarget = SecondaryPermissionType.AllModules,
SecondaryTargetName = "*",
State = action.Value,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"{(action.Value ? "Allowed" : "Denied")} usage of `ALL MODULES` for `{chnl}` channel.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AllRoleMdls(IUserMessage imsg, PermissionAction action, [Remainder] IRole role)
{
var channel = (ITextChannel)imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var newPerm = new Permission
{
PrimaryTarget = PrimaryPermissionType.Role,
PrimaryTargetId = role.Id,
SecondaryTarget = SecondaryPermissionType.AllModules,
SecondaryTargetName = "*",
State = action.Value,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"{(action.Value ? "Allowed" : "Denied")} usage of `ALL MODULES` for `{role}` role.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AllUsrMdls(IUserMessage imsg, PermissionAction action, [Remainder] IUser user)
{
var channel = (ITextChannel)imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var newPerm = new Permission
{
PrimaryTarget = PrimaryPermissionType.User,
PrimaryTargetId = user.Id,
SecondaryTarget = SecondaryPermissionType.AllModules,
SecondaryTargetName = "*",
State = action.Value,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"{(action.Value ? "Allowed" : "Denied")} usage of `ALL MODULES` for `{user}` user.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AllSrvrMdls(IUserMessage imsg, PermissionAction action)
{
var channel = (ITextChannel)imsg.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var newPerm = new Permission
{
PrimaryTarget = PrimaryPermissionType.Server,
PrimaryTargetId = 0,
SecondaryTarget = SecondaryPermissionType.AllModules,
SecondaryTargetName = "*",
State = action.Value,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm);
var allowUser = new Permission
{
PrimaryTarget = PrimaryPermissionType.User,
PrimaryTargetId = imsg.Author.Id,
SecondaryTarget = SecondaryPermissionType.AllModules,
SecondaryTargetName = "*",
State = true,
};
uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, allowUser);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync($"{(action.Value ? "Allowed" : "Denied")} usage of `ALL MODULES` on this server.").ConfigureAwait(false);
}
}
}

View File

@ -1,27 +0,0 @@
using Discord.Commands;
using Discord;
using NadekoBot.Attributes;
using System.Threading.Tasks;
using NadekoBot.Services;
using Discord.WebSocket;
namespace NadekoBot.Modules.Games
{
[Module(">", AppendSpace = false)]
public partial class Pokemon : DiscordModule
{
public Pokemon(ILocalization loc, CommandService cmds, DiscordSocketClient client) : base(loc, cmds, client)
{
}
//todo Dragon should PR this in
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[RequireContext(ContextType.Guild)]
public async Task Poke(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
}
}
}

View File

@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Searches
_log = LogManager.GetCurrentClassLogger();
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Anime(IUserMessage umsg, [Remainder] string query)
{
@ -43,7 +43,7 @@ namespace NadekoBot.Modules.Searches
await channel.SendMessageAsync(result.ToString() ?? "`No anime found.`").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Manga(IUserMessage umsg, [Remainder] string query)
{

View File

@ -1,172 +0,0 @@
//using Discord.Commands;
//using NadekoBot.Classes;
//using ScaredFingers.UnitsConversion;
//using System;
//using System.Collections.Generic;
//using System.Globalization;
//using System.Threading;
//using System.Threading.Tasks;
////todo Unit Conversion lib
//namespace NadekoBot.Modules.Searches
//{
// class ConverterCommand : DiscordCommand
// {
// public ConverterCommand(DiscordModule module) : base(module)
// {
// if (unitTables == null)
// {
// CultureInfo ci = new CultureInfo("en-US");
// Thread.CurrentThread.CurrentCulture = ci;
// unitTables = new List<UnitTable>();
// unitTables.Add(UnitTable.LengthTable);
// unitTables.Add(UnitTable.TemperatureTable);
// unitTables.Add(UnitTable.VolumeTable);
// unitTables.Add(UnitTable.WeightTable);
// reInitCurrencyConverterTable();
// }
// }
// public override void Init(CommandGroupBuilder cgb)
// {
// cgb.CreateCommand(Module.Prefix + "convert")
// .Description($"Convert quantities from>to. | `{Prefix}convert m>km 1000`")
// .Parameter("from-to", ParameterType.Required)
// .Parameter("quantity", ParameterType.Optional)
// .Do(ConvertFunc());
// cgb.CreateCommand(Module.Prefix + "convertlist")
// .Description("List of the convertable dimensions and currencies.")
// .Do(ConvertListFunc());
// }
// private Func<CommandEventArgs, Task> ConvertListFunc() =>
// async e =>
// {
// reInitCurrencyConverterTable();
// string msg = "";
// foreach (var tmpTable in unitTables)
// {
// int i = 1;
// while (tmpTable.IsKnownUnit(i))
// {
// msg += tmpTable.GetUnitName(i) + " (" + tmpTable.GetUnitSymbol(i) + "); ";
// i++;
// }
// msg += "\n";
// }
// foreach (var curr in exchangeRateProvider.Currencies)
// {
// msg += curr + "; ";
// }
// await channel.SendMessageAsync(msg).ConfigureAwait(false);
// };
// private Func<CommandEventArgs, Task> ConvertFunc() =>
// async e =>
// {
// try
// {
// await e.Channel.SendIsTyping().ConfigureAwait(false);
// string from = from-to.ToLowerInvariant().Split('>')[0];
// string to = from-to.ToLowerInvariant().Split('>')[1];
// float quantity = 1.0f;
// if (!float.TryParse(quantity, out quantity))
// {
// quantity = 1.0f;
// }
// int fromCode, toCode = 0;
// UnitTable table = null;
// ResolveUnitCodes(from, to, out table, out fromCode, out toCode);
// if (table != null)
// {
// Unit inUnit = new Unit(fromCode, quantity, table);
// Unit outUnit = inUnit.Convert(toCode);
// await channel.SendMessageAsync(inUnit.ToString() + " = " + outUnit.ToString()).ConfigureAwait(false);
// }
// else
// {
// CultureInfo ci = new CultureInfo("en-US");
// Thread.CurrentThread.CurrentCulture = ci;
// reInitCurrencyConverterTable();
// Unit inUnit = currTable.CreateUnit(quantity, from.ToUpperInvariant());
// Unit outUnit = inUnit.Convert(currTable.CurrencyCode(to.ToUpperInvariant()));
// await channel.SendMessageAsync(inUnit.ToString() + " = " + outUnit.ToString()).ConfigureAwait(false);
// }
// }
// catch //(Exception ex)
// {
// //Console.WriteLine(ex.ToString());
// await channel.SendMessageAsync("Bad input format, or sth went wrong... Try to list them with `" + Module.Prefix + "`convertlist").ConfigureAwait(false);
// }
// };
// private void reInitCurrencyConverterTable()
// {
// if (lastChanged == null || lastChanged.DayOfYear != DateTime.Now.DayOfYear)
// {
// try
// {
// exchangeRateProvider = new WebExchangeRatesProvider();
// currTable = new CurrencyExchangeTable(exchangeRateProvider);
// lastChanged = DateTime.Now;
// }
// catch
// {
// Console.WriteLine("Error with the currency download.");
// }
// }
// }
// private void ResolveUnitCodes(string from, string to, out UnitTable table, out int fromCode, out int toCode)
// {
// foreach (var tmpTable in unitTables)
// {
// int f = LookupUnit(tmpTable, from);
// int t = LookupUnit(tmpTable, to);
// if (f > 0 && t > 0)
// {
// table = tmpTable;
// fromCode = f;
// toCode = t;
// return;
// }
// }
// table = null;
// fromCode = 0;
// toCode = 0;
// }
// private int LookupUnit(UnitTable table, string lookup)
// {
// string wellformedLookup = lookup.ToLowerInvariant().Replace("°", "");
// int i = 1;
// while (table.IsKnownUnit(i))
// {
// if (wellformedLookup == table.GetUnitName(i).ToLowerInvariant().Replace("°", "") ||
// wellformedLookup == table.GetUnitPlural(i).ToLowerInvariant().Replace("°", "") ||
// wellformedLookup == table.GetUnitSymbol(i).ToLowerInvariant().Replace("°", ""))
// {
// return i;
// }
// i++;
// }
// return 0;
// }
// private static List<UnitTable> unitTables;
// private static CurrencyExchangeRatesProvider exchangeRateProvider;
// private static CurrencyExchangeTable currTable;
// private static DateTime lastChanged;
// }
//}

View File

@ -5,7 +5,7 @@ using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Translator
namespace NadekoBot.Modules.Searches
{
public class GoogleTranslator
{

View File

@ -1,4 +1,5 @@
using NadekoBot.Modules.Searches.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Searches.Models;
using System;
using System.Collections.Generic;
using System.Linq;
@ -71,7 +72,7 @@ namespace NadekoBot.Modules.Searches.IMDB
mov.Status = true;
mov.Title = Match(@"<title>(IMDb \- )*(.*?) \(.*?</title>", html, 2);
mov.OriginalTitle = Match(@"title-extra"">(.*?)<", html);
mov.Year = Match(@"<title>.*?\(.*?(\d{4}).*?).*?</title>", Match(@"(<title>.*?</title>)", html));
mov.Year = Match(@"<title>.*?\(.*?(\d{4}).*?\).*?</title>", Match(@"(<title>.*?</title>)", html));
mov.Rating = Match(@"<b>(\d.\d)/10</b>", html);
mov.Genres = MatchAll(@"<a.*?>(.*?)</a>", Match(@"Genre.?:(.*?)(</div>|See more)", html)).Cast<string>().ToList();
mov.Plot = Match(@"Plot:</h5>.*?<div class=""info-content"">(.*?)(<a|</div)", html);
@ -140,16 +141,13 @@ namespace NadekoBot.Modules.Searches.IMDB
List<string> list = new List<string>();
string recUrl = "http://www.imdb.com/widget/recommendations/_ajax/get_more_recs?specs=p13nsims%3A" + mov.Id;
string json = await GetUrlDataAsync(recUrl);
list = MatchAll(@"title=\\""(.*?)\\""", json);
HashSet<String> set = new HashSet<string>();
foreach (String rec in list) set.Add(rec);
return new List<string>(set.ToList());
return MatchAll(@"title=\\""(.*?)\\""", json);
}
/*******************************[ Helper Methods ]********************************/
//Match single instance
private static string Match(string regex, string html, int i = 1)
{
return new Regex(regex, RegexOptions.Multiline).Match(html).Groups[i].Value.Trim();
return new Regex(regex, RegexOptions.Multiline | RegexOptions.IgnoreCase).Match(html).Groups[i].ToString().Trim();
}
//Match all instances and return as List<string>
private static List<string> MatchAll(string regex, string html, int i = 1)
@ -165,12 +163,12 @@ namespace NadekoBot.Modules.Searches.IMDB
return Regex.Replace(inputString, @"<.*?>", string.Empty);
}
//Get URL Data
private static Task<string> GetUrlDataAsync(string url)
private static async Task<string> GetUrlDataAsync(string url)
{
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1");
return http.GetStringAsync(url);
http.AddFakeHeaders();
return await http.GetStringAsync(url);
}
}
}

View File

@ -42,7 +42,7 @@ namespace NadekoBot.Modules.Searches
_log.Warn("data/magicitems.json is missing. Magic items are not loaded.");
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Yomama(IUserMessage umsg)
{
@ -54,7 +54,7 @@ namespace NadekoBot.Modules.Searches
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Randjoke(IUserMessage umsg)
{
@ -66,7 +66,7 @@ namespace NadekoBot.Modules.Searches
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ChuckNorris(IUserMessage umsg)
{
@ -78,7 +78,7 @@ namespace NadekoBot.Modules.Searches
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WowJoke(IUserMessage umsg)
{
@ -90,7 +90,7 @@ namespace NadekoBot.Modules.Searches
await channel.SendMessageAsync(wowJokes[new NadekoRandom().Next(0, wowJokes.Count)].ToString());
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task MagicItem(IUserMessage umsg)
{

View File

@ -32,7 +32,7 @@ namespace NadekoBot.Modules.Searches
"Doesn't matter what you ban really. Enemy will ban your main and you will lose." };
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Lolban(IUserMessage umsg)
{

View File

@ -15,21 +15,35 @@ namespace NadekoBot.Modules.Searches
{
public partial class Searches
{
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Memelist(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
using (var http = new HttpClient())
HttpClientHandler handler = new HttpClientHandler();
handler.AllowAutoRedirect = false;
using (var http = new HttpClient(handler))
{
var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(await http.GetStringAsync("http://memegen.link/templates/"))
http.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
string rawJson = "";
try
{
rawJson = await http.GetStringAsync("https://memegen.link/api/templates/").ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(rawJson)
.Select(kvp => Path.GetFileName(kvp.Value));
await channel.SendTableAsync(data, x => $"{x,-17}", 3);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Memegen(IUserMessage umsg, string meme, string topText, string botText)
{

View File

@ -25,9 +25,9 @@ namespace NadekoBot.Modules.Searches
{
_log = LogManager.GetCurrentClassLogger();
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Osu(IUserMessage umsg, string usr, string mode)
public async Task Osu(IUserMessage umsg, string usr, [Remainder] string mode = null)
{
var channel = (ITextChannel)umsg.Channel;
@ -46,9 +46,10 @@ namespace NadekoBot.Modules.Searches
http.AddFakeHeaders();
var res = await http.GetStreamAsync(new Uri($"http://lemmmy.pw/osusig/sig.php?uname={ usr }&flagshadow&xpbar&xpbarhex&pp=2&mode={m}")).ConfigureAwait(false);
res.Position = 0;
await channel.SendFileAsync(res, $"{usr}.png").ConfigureAwait(false);
await channel.SendMessageAsync($"`Profile Link:`https://osu.ppy.sh/u/{Uri.EscapeDataString(usr)}\n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false);
MemoryStream ms = new MemoryStream();
res.CopyTo(ms);
ms.Position = 0;
await channel.SendFileAsync(ms, $"{usr}.png", $"`Profile Link:`https://osu.ppy.sh/u/{Uri.EscapeDataString(usr)}\n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false);
}
catch (Exception ex)
{
@ -58,7 +59,7 @@ namespace NadekoBot.Modules.Searches
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Osub(IUserMessage umsg, [Remainder] string map)
{
@ -95,9 +96,9 @@ namespace NadekoBot.Modules.Searches
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Osu5(IUserMessage umsg, string user, [Remainder] string mode)
public async Task Osu5(IUserMessage umsg, string user, [Remainder] string mode = null)
{
var channel = (ITextChannel)umsg.Channel;
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.OsuApiKey))

View File

@ -38,7 +38,7 @@ namespace NadekoBot.Modules.Searches
_log.Warn(PokemonAbilitiesFile + " is missing. Pokemon abilities not loaded.");
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Pokemon(IUserMessage umsg, [Remainder] string pokemon = null)
{
@ -59,7 +59,7 @@ namespace NadekoBot.Modules.Searches
await channel.SendMessageAsync("`No pokemon found.`");
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task PokemonAbility(IUserMessage umsg, [Remainder] string ability = null)
{

View File

@ -18,18 +18,33 @@ namespace NadekoBot.Modules.Searches
{
public partial class Searches
{
public class StreamStatus
{
public StreamStatus(string link, bool isLive, string views)
{
Link = link;
IsLive = isLive;
Views = views;
}
public bool IsLive { get; set; }
public string Link { get; set; }
public string Views { get; set; }
}
[Group]
public class StreamNotificationCommands
{
private Timer checkTimer { get; }
private ConcurrentDictionary<string, Tuple<bool, string>> cachedStatuses = new ConcurrentDictionary<string, Tuple<bool, string>>();
private ConcurrentDictionary<string, StreamStatus> oldCachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
private ConcurrentDictionary<string, StreamStatus> cachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
private bool FirstPass { get; set; } = true;
public StreamNotificationCommands()
{
checkTimer = new Timer(async (state) =>
{
cachedStatuses.Clear();
oldCachedStatuses = new ConcurrentDictionary<string, StreamStatus>(cachedStatuses);
cachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
try
{
IEnumerable<FollowedStream> streams;
@ -39,19 +54,23 @@ namespace NadekoBot.Modules.Searches
}
foreach (var stream in streams)
{
Tuple<bool, string> data;
StreamStatus data;
try
{
data = await GetStreamStatus(stream).ConfigureAwait(false);
if (data == null)
return;
}
catch
{
continue;
}
if (data.Item1 != stream.LastStatus)
StreamStatus oldData;
oldCachedStatuses.TryGetValue(data.Link, out oldData);
if (oldData == null || data.IsLive != oldData.IsLive)
{
stream.LastStatus = data.Item1;
if (FirstPass)
continue;
var server = NadekoBot.Client.GetGuild(stream.GuildId);
@ -59,32 +78,30 @@ namespace NadekoBot.Modules.Searches
if (channel == null)
continue;
var msg = $"`{stream.Username}`'s stream is now " +
$"**{(data.Item1 ? "ONLINE" : "OFFLINE")}** with " +
$"**{data.Item2}** viewers.";
if (stream.LastStatus)
$"**{(data.IsLive ? "ONLINE" : "OFFLINE")}** with " +
$"**{data.Views}** viewers.";
if (data.IsLive)
if (stream.Type == FollowedStream.FollowedStreamType.Hitbox)
msg += $"\n`Here is the Link:`【 http://www.hitbox.tv/{stream.Username}/ 】";
else if (stream.Type == FollowedStream.FollowedStreamType.Twitch)
msg += $"\n`Here is the Link:`【 http://www.twitch.tv/{stream.Username}/ 】";
else if (stream.Type == FollowedStream.FollowedStreamType.Beam)
msg += $"\n`Here is the Link:`【 http://www.beam.pro/{stream.Username}/ 】";
//else if (stream.Type == FollowedStream.FollowedStreamType.YoutubeGaming)
// msg += $"\n`Here is the Link:`【 not implemented yet - {stream.Username} 】";
await channel.SendMessageAsync(msg).ConfigureAwait(false);
try { await channel.SendMessageAsync(msg).ConfigureAwait(false); } catch { }
}
}
FirstPass = false;
}
catch { }
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(15));
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(60));
}
private async Task<Tuple<bool, string>> GetStreamStatus(FollowedStream stream, bool checkCache = true)
private async Task<StreamStatus> GetStreamStatus(FollowedStream stream, bool checkCache = true)
{
bool isLive;
string response;
JObject data;
Tuple<bool, string> result;
StreamStatus result;
switch (stream.Type)
{
case FollowedStream.FollowedStreamType.Hitbox:
@ -97,11 +114,11 @@ namespace NadekoBot.Modules.Searches
}
data = JObject.Parse(response);
isLive = data["media_is_live"].ToString() == "1";
result = new Tuple<bool, string>(isLive, data["media_views"].ToString());
result = new StreamStatus(hitboxUrl, isLive, data["media_views"].ToString());
cachedStatuses.TryAdd(hitboxUrl, result);
return result;
case FollowedStream.FollowedStreamType.Twitch:
var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username)}";
var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username)}?client_id=67w6z9i09xv2uoojdm9l0wsyph4hxo6";
if (checkCache && cachedStatuses.TryGetValue(twitchUrl, out result))
return result;
using (var http = new HttpClient())
@ -110,7 +127,7 @@ namespace NadekoBot.Modules.Searches
}
data = JObject.Parse(response);
isLive = !string.IsNullOrWhiteSpace(data["stream"].ToString());
result = new Tuple<bool, string>(isLive, isLive ? data["stream"]["viewers"].ToString() : "0");
result = new StreamStatus(twitchUrl, isLive, isLive ? data["stream"]["viewers"].ToString() : "0");
cachedStatuses.TryAdd(twitchUrl, result);
return result;
case FollowedStream.FollowedStreamType.Beam:
@ -123,37 +140,37 @@ namespace NadekoBot.Modules.Searches
}
data = JObject.Parse(response);
isLive = data["online"].ToObject<bool>() == true;
result = new Tuple<bool, string>(isLive, data["viewersCurrent"].ToString());
result = new StreamStatus(beamUrl, isLive, data["viewersCurrent"].ToString());
cachedStatuses.TryAdd(beamUrl, result);
return result;
default:
break;
}
return new Tuple<bool, string>(false, "0");
return null;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageMessages)]
public async Task Hitbox(IUserMessage msg, [Remainder] string username) =>
await TrackStream((ITextChannel)msg.Channel, username, FollowedStream.FollowedStreamType.Hitbox)
.ConfigureAwait(false);
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageMessages)]
public async Task Twitch(IUserMessage msg, [Remainder] string username) =>
await TrackStream((ITextChannel)msg.Channel, username, FollowedStream.FollowedStreamType.Twitch)
.ConfigureAwait(false);
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageMessages)]
public async Task Beam(IUserMessage msg, [Remainder] string username) =>
await TrackStream((ITextChannel)msg.Channel, username, FollowedStream.FollowedStreamType.Beam)
.ConfigureAwait(false);
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ListStreams(IUserMessage imsg)
{
@ -179,23 +196,24 @@ namespace NadekoBot.Modules.Searches
await channel.SendMessageAsync($"You are following **{streams.Count()}** streams on this server.\n\n" + text).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageMessages)]
public async Task RemoveStream(IUserMessage msg, [Remainder] string username)
{
var channel = (ITextChannel)msg.Channel;
username = username.ToUpperInvariant().Trim();
username = username.ToLowerInvariant().Trim();
FollowedStream toRemove;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(channel.Guild.Id);
var streams = config.FollowedStreams;
toRemove = streams.Where(fs => fs.ChannelId == channel.Id && fs.Username.ToUpperInvariant() == username).FirstOrDefault();
toRemove = streams.Where(fs => fs.ChannelId == channel.Id && fs.Username.ToLowerInvariant() == username).FirstOrDefault();
if (toRemove != null)
{
config.FollowedStreams = streams.Except(new[] { toRemove }).ToList();
config.FollowedStreams = new HashSet<FollowedStream>(streams.Except(new[] { toRemove }));
await uow.CompleteAsync();
}
}
@ -207,7 +225,7 @@ namespace NadekoBot.Modules.Searches
await channel.SendMessageAsync($":ok: Removed `{toRemove.Username}`'s stream ({toRemove.Type}) from notifications.").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task CheckStream(IUserMessage imsg, FollowedStream.FollowedStreamType platform, [Remainder] string username)
{
@ -223,9 +241,13 @@ namespace NadekoBot.Modules.Searches
Username = stream,
Type = platform
}));
if (streamStatus.Item1)
if (streamStatus.IsLive)
{
await channel.SendMessageAsync($"`Streamer {streamStatus.Item2} is online.`");
await channel.SendMessageAsync($"`Streamer {username} is online with {streamStatus.Views} viewers.`");
}
else
{
await channel.SendMessageAsync($"`Streamer {username} is offline.`");
}
}
catch
@ -236,7 +258,7 @@ namespace NadekoBot.Modules.Searches
private async Task TrackStream(ITextChannel channel, string username, FollowedStream.FollowedStreamType type)
{
username = username.ToUpperInvariant().Trim();
username = username.ToLowerInvariant().Trim();
var stream = new FollowedStream
{
GuildId = channel.Guild.Id,
@ -247,14 +269,14 @@ namespace NadekoBot.Modules.Searches
bool exists;
using (var uow = DbHandler.UnitOfWork())
{
exists = uow.GuildConfigs.For(channel.Guild.Id).FollowedStreams.Where(fs => fs.ChannelId == channel.Id && fs.Username.ToUpperInvariant().Trim() == username).Any();
exists = uow.GuildConfigs.For(channel.Guild.Id).FollowedStreams.Where(fs => fs.ChannelId == channel.Id && fs.Username.ToLowerInvariant().Trim() == username).Any();
}
if (exists)
{
await channel.SendMessageAsync($":anger: I am already following `{username}` ({type}) stream on this channel.").ConfigureAwait(false);
return;
}
Tuple<bool, string> data;
StreamStatus data;
try
{
data = await GetStreamStatus(stream).ConfigureAwait(false);
@ -264,17 +286,14 @@ namespace NadekoBot.Modules.Searches
await channel.SendMessageAsync(":anger: Stream probably doesn't exist.").ConfigureAwait(false);
return;
}
var msg = $"Stream is currently **{(data.Item1 ? "ONLINE" : "OFFLINE")}** with **{data.Item2}** viewers";
if (data.Item1)
var msg = $"Stream is currently **{(data.IsLive ? "ONLINE" : "OFFLINE")}** with **{data.Views}** viewers";
if (data.IsLive)
if (type == FollowedStream.FollowedStreamType.Hitbox)
msg += $"\n`Here is the Link:`【 http://www.hitbox.tv/{stream.Username}/ 】";
else if (type == FollowedStream.FollowedStreamType.Twitch)
msg += $"\n`Here is the Link:`【 http://www.twitch.tv/{stream.Username}/ 】";
else if (type == FollowedStream.FollowedStreamType.Beam)
msg += $"\n`Here is the Link:`【 https://beam.pro/{stream.Username}/ 】";
//else if (type == FollowedStream.FollowedStreamType.YoutubeGaming)
// msg += $"\n`Here is the Link:` not implemented yet - {stream.Username}";
stream.LastStatus = data.Item1;
using (var uow = DbHandler.UnitOfWork())
{
uow.GuildConfigs.For(channel.Guild.Id).FollowedStreams.Add(stream);

View File

@ -7,16 +7,11 @@ using System.Threading.Tasks;
using NadekoBot.Services;
using Discord.WebSocket;
namespace NadekoBot.Modules.Translator
namespace NadekoBot.Modules.Searches
{
[NadekoModule("Translator", "~")]
public class Translator : DiscordModule
public partial class Searches
{
public Translator(ILocalization loc, CommandService cmds, DiscordSocketClient client) : base(loc, cmds, client)
{
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Translate(IUserMessage umsg, string langs, [Remainder] string text = null)
{
@ -44,7 +39,7 @@ namespace NadekoBot.Modules.Translator
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Translangs(IUserMessage umsg)
{

View File

@ -13,6 +13,10 @@ using System.Net;
using Discord.WebSocket;
using NadekoBot.Modules.Searches.Models;
using NadekoBot.Modules.Searches.IMDB;
using System.Collections.Generic;
using ImageProcessorCore;
using NadekoBot.Extensions;
using System.IO;
namespace NadekoBot.Modules.Searches
{
@ -21,12 +25,12 @@ namespace NadekoBot.Modules.Searches
{
private IGoogleApiService _google { get; }
public Searches(ILocalization loc, CommandService cmds, DiscordSocketClient client, IGoogleApiService youtube) : base(loc, cmds, client)
public Searches(ILocalization loc, CommandService cmds, ShardedDiscordClient client, IGoogleApiService youtube) : base(loc, cmds, client)
{
_google = youtube;
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Weather(IUserMessage umsg, string city, string country)
{
@ -35,7 +39,7 @@ namespace NadekoBot.Modules.Searches
country = city.Replace(" ", "");
string response;
using (var http = new HttpClient())
response = await http.GetStringAsync($"http://api.lawlypopzz.xyz/nadekobot/weather/?city={city}&country={country}").ConfigureAwait(false);
response = await http.GetStringAsync($"http://api.ninetales.us/nadekobot/weather/?city={city}&country={country}").ConfigureAwait(false);
var obj = JObject.Parse(response)["weather"];
@ -47,12 +51,12 @@ $@"🌍 **Weather for** 【{obj["target"]}】
🌄 **Sunrise:** {obj["sunrise"]} 🌇 **Sunset:** {obj["sunset"]}").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Youtube(IUserMessage umsg, [Remainder] string query = null)
{
var channel = (ITextChannel)umsg.Channel;
if (!(await ValidateQuery(umsg.Channel as ITextChannel, query).ConfigureAwait(false))) return;
if (!(await ValidateQuery(channel, query).ConfigureAwait(false))) return;
var result = (await _google.GetVideosByKeywordsAsync(query, 1)).FirstOrDefault();
if (string.IsNullOrWhiteSpace(result))
{
@ -62,13 +66,13 @@ $@"🌍 **Weather for** 【{obj["target"]}】
await channel.SendMessageAsync(result).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Imdb(IUserMessage umsg, [Remainder] string query = null)
{
var channel = (ITextChannel)umsg.Channel;
if (!(await ValidateQuery(umsg.Channel as ITextChannel, query).ConfigureAwait(false))) return;
if (!(await ValidateQuery(channel, query).ConfigureAwait(false))) return;
await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false);
string result;
try
@ -77,16 +81,17 @@ $@"🌍 **Weather for** 【{obj["target"]}】
if (movie.Status) result = movie.ToString();
else result = "Failed to find that movie.";
}
catch
catch (Exception ex)
{
await channel.SendMessageAsync("Failed to find that movie.").ConfigureAwait(false);
_log.Warn(ex);
return;
}
await channel.SendMessageAsync(result.ToString()).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task RandomCat(IUserMessage umsg)
{
@ -99,7 +104,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task RandomDog(IUserMessage umsg)
{
@ -110,7 +115,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task I(IUserMessage umsg, [Remainder] string query = null)
{
@ -140,7 +145,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Ir(IUserMessage umsg, [Remainder] string query = null)
{
@ -172,7 +177,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Lmgtfy(IUserMessage umsg, [Remainder] string ffs = null)
{
@ -186,7 +191,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】
.ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Google(IUserMessage umsg, [Remainder] string terms = null)
{
@ -199,55 +204,62 @@ $@"🌍 **Weather for** 【{obj["target"]}】
await channel.SendMessageAsync($"https://google.com/search?q={ WebUtility.UrlEncode(terms).Replace(' ', '+') }")
.ConfigureAwait(false);
}
////todo drawing
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Hearthstone(IUserMessage umsg, [Remainder] string name = null)
//{
// var channel = (ITextChannel)umsg.Channel;
// var arg = name;
// if (string.IsNullOrWhiteSpace(arg))
// {
// await channel.SendMessageAsync("💢 Please enter a card name to search for.").ConfigureAwait(false);
// return;
// }
// await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false);
// string response = "";
// using (var http = new HttpClient())
// {
// http.DefaultRequestHeaders.Clear();
// http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey);
// response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.mashape.com/cards/search/{Uri.EscapeUriString(arg)}", headers)
// .ConfigureAwait(false);
// try
// {
// var items = JArray.Parse(response).Shuffle().ToList();
// var images = new List<Image>();
// if (items == null)
// throw new KeyNotFoundException("Cannot find a card by that name");
// var cnt = 0;
// foreach (var item in items.TakeWhile(item => cnt++ < 4).Where(item => item.HasValues && item["img"] != null))
// {
// images.Add(
// Image.FromStream(await http.GetStreamAsync(item["img"].ToString()).ConfigureAwait(false)));
// }
// if (items.Count > 4)
// {
// await channel.SendMessageAsync("⚠ Found over 4 images. Showing random 4.").ConfigureAwait(false);
// }
// await channel.SendMessageAsync(arg + ".png", (await images.MergeAsync()).ToStream(System.Drawing.Imaging.ImageFormat.Png))
// .ConfigureAwait(false);
// }
// catch (Exception ex)
// {
// await channel.SendMessageAsync($"💢 Error {ex.Message}").ConfigureAwait(false);
// }
// }
//}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Ud(IUserMessage umsg, [Remainder] string query = null)
public async Task Hearthstone(IUserMessage umsg, [Remainder] string name = null)
{
var channel = (ITextChannel)umsg.Channel;
var arg = name;
if (string.IsNullOrWhiteSpace(arg))
{
await channel.SendMessageAsync("💢 Please enter a card name to search for.").ConfigureAwait(false);
return;
}
await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false);
string response = "";
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey);
response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.mashape.com/cards/search/{Uri.EscapeUriString(arg)}")
.ConfigureAwait(false);
try
{
var items = JArray.Parse(response).Shuffle().ToList();
var images = new List<Image>();
if (items == null)
throw new KeyNotFoundException("Cannot find a card by that name");
foreach (var item in items.Where(item => item.HasValues && item["img"] != null).Take(4))
{
using (var sr =await http.GetStreamAsync(item["img"].ToString()))
{
var imgStream = new MemoryStream();
await sr.CopyToAsync(imgStream);
imgStream.Position = 0;
images.Add(new Image(imgStream));
}
}
string msg = null;
if (items.Count > 4)
{
msg = "⚠ Found over 4 images. Showing random 4.";
}
var ms = new MemoryStream();
images.Merge().SaveAsPng(ms);
ms.Position = 0;
await channel.SendFileAsync(ms, arg + ".png", msg).ConfigureAwait(false);
}
catch (Exception ex)
{
await channel.SendMessageAsync($"💢 Error {ex.Message}").ConfigureAwait(false);
}
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task UrbanDict(IUserMessage umsg, [Remainder] string query = null)
{
var channel = (ITextChannel)umsg.Channel;
@ -279,7 +291,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Hashtag(IUserMessage umsg, [Remainder] string query = null)
{
@ -314,7 +326,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Catfact(IUserMessage umsg)
{
@ -328,7 +340,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Revav(IUserMessage umsg, [Remainder] string arg = null)
{
@ -345,7 +357,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】
await channel.SendMessageAsync($"https://images.google.com/searchbyimage?image_url={usr.AvatarUrl}").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Revimg(IUserMessage umsg, [Remainder] string imageLink = null)
{
@ -357,7 +369,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】
await channel.SendMessageAsync($"https://images.google.com/searchbyimage?image_url={imageLink}").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Safebooru(IUserMessage umsg, [Remainder] string tag = null)
{
@ -371,7 +383,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】
await channel.SendMessageAsync(link).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Wiki(IUserMessage umsg, [Remainder] string query = null)
{
@ -391,33 +403,27 @@ $@"🌍 **Weather for** 【{obj["target"]}】
}
}
////todo drawing
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task Clr(IUserMessage umsg, [Remainder] string color = null)
//{
// var channel = (ITextChannel)umsg.Channel;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Color(IUserMessage umsg, [Remainder] string color = null)
{
var channel = (ITextChannel)umsg.Channel;
// color = color?.Trim().Replace("#", "");
// if (string.IsNullOrWhiteSpace((string)color))
// return;
// var img = new Bitmap(50, 50);
color = color?.Trim().Replace("#", "");
if (string.IsNullOrWhiteSpace((string)color))
return;
var img = new Image(50, 50);
// var red = Convert.ToInt32(color.Substring(0, 2), 16);
// var green = Convert.ToInt32(color.Substring(2, 2), 16);
// var blue = Convert.ToInt32(color.Substring(4, 2), 16);
// var brush = new SolidBrush(System.Drawing.Color.FromArgb(red, green, blue));
var red = Convert.ToInt32(color.Substring(0, 2), 16);
var green = Convert.ToInt32(color.Substring(2, 2), 16);
var blue = Convert.ToInt32(color.Substring(4, 2), 16);
// using (Graphics g = Graphics.FromImage(img))
// {
// g.FillRectangle(brush, 0, 0, 50, 50);
// g.Flush();
// }
img.BackgroundColor(new ImageProcessorCore.Color(color));
// await channel.SendFileAsync("arg1.png", img.ToStream());
//}
await channel.SendFileAsync(img.ToStream(), $"{color}.png");
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Videocall(IUserMessage umsg, [Remainder] string arg = null)
{
@ -440,7 +446,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Avatar(IUserMessage umsg, [Remainder] string mention = null)
{

View File

@ -9,12 +9,12 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Searches
namespace NadekoBot.Modules.Utility
{
[Group]
public partial class Searches
public partial class Utility
{
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public static async Task Calculate(IUserMessage msg, [Remainder] string expression)
{
@ -41,7 +41,7 @@ namespace NadekoBot.Modules.Searches
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task CalcOps(IUserMessage msg)
{

View File

@ -12,11 +12,11 @@ namespace NadekoBot.Modules.Utility
{
partial class Utility : DiscordModule
{
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ServerInfo(IUserMessage msg, string guild = null)
{
var channel = msg.Channel as ITextChannel;
var channel = (ITextChannel)msg.Channel;
guild = guild?.ToUpperInvariant();
IGuild server;
if (guild == null)
@ -47,11 +47,11 @@ namespace NadekoBot.Modules.Utility
await msg.Reply(sb.ToString()).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ChannelInfo(IUserMessage msg, ITextChannel channel = null)
{
var ch = channel ?? msg.Channel as ITextChannel;
var ch = channel ?? (ITextChannel)msg.Channel;
if (ch == null)
return;
var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(ch.Id >> 22);
@ -63,11 +63,11 @@ namespace NadekoBot.Modules.Utility
await msg.Reply(toReturn).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task UserInfo(IUserMessage msg, IGuildUser usr = null)
{
var channel = msg.Channel as ITextChannel;
var channel = (ITextChannel)msg.Channel;
var user = usr ?? msg.Author as IGuildUser;
if (user == null)
return;
@ -77,7 +77,7 @@ namespace NadekoBot.Modules.Utility
toReturn += $@"`Id:` **{user.Id}**
`Current Game:` **{(user.Game?.Name == null ? "-" : user.Game.Name)}**
`Joined At:` **{user.JoinedAt}**
`Roles:` **({user.Roles.Count()}) - {string.Join(", ", user.Roles.Select(r => r.Name))}**
`Roles:` **({user.Roles.Count()}) - {string.Join(", ", user.Roles.Select(r => r.Name)).SanitizeMentions()}**
`AvatarUrl:` **{user.AvatarUrl}**";
await msg.Reply(toReturn).ConfigureAwait(false);
}

View File

@ -1,6 +1,7 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
@ -14,11 +15,11 @@ namespace NadekoBot.Modules.Utility
{
public partial class Utility
{
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ShowQuote(IUserMessage umsg, string keyword)
public async Task ShowQuote(IUserMessage umsg, [Remainder] string keyword)
{
var channel = umsg.Channel as ITextChannel;
var channel = (ITextChannel)umsg.Channel;
if (string.IsNullOrWhiteSpace(keyword))
return;
@ -34,14 +35,14 @@ namespace NadekoBot.Modules.Utility
if (quote == null)
return;
await channel.SendMessageAsync("📣 " + quote.Text);
await channel.SendMessageAsync("📣 " + quote.Text.SanitizeMentions());
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AddQuote(IUserMessage umsg, string keyword, [Remainder] string text)
{
var channel = umsg.Channel as ITextChannel;
var channel = (ITextChannel)umsg.Channel;
if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text))
return;
@ -59,42 +60,48 @@ namespace NadekoBot.Modules.Utility
Text = text,
});
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync("`Quote added.`").ConfigureAwait(false);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task DeleteQuote(IUserMessage umsg, string keyword)
public async Task DeleteQuote(IUserMessage umsg, [Remainder] string keyword)
{
var channel = umsg.Channel as ITextChannel;
var channel = (ITextChannel)umsg.Channel;
if (string.IsNullOrWhiteSpace(keyword))
return;
keyword = keyword.ToUpperInvariant();
var isAdmin = ((IGuildUser)umsg.Author).GuildPermissions.Administrator;
keyword = keyword.ToUpperInvariant();
string response;
using (var uow = DbHandler.UnitOfWork())
{
var q = await uow.Quotes.GetRandomQuoteByKeywordAsync(channel.Guild.Id, keyword);
var qs = uow.Quotes.GetAllQuotesByKeyword(channel.Guild.Id, keyword);
if (q == null)
if (qs==null || !qs.Any())
{
await channel.SendMessageAsync("`No quotes found.`");
response = "`No quotes found.`";
return;
}
var q = qs.Shuffle().FirstOrDefault(elem => isAdmin || elem.AuthorId == umsg.Author.Id);
uow.Quotes.Remove(q);
await uow.CompleteAsync();
await uow.CompleteAsync().ConfigureAwait(false);
response = "`Deleted a random quote`";
}
await channel.SendMessageAsync("`Deleted a random quote.`");
await channel.SendMessageAsync(response);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task DelAllQuotes(IUserMessage umsg, string keyword)
[RequirePermission(GuildPermission.Administrator)]
public async Task DelAllQuotes(IUserMessage umsg, [Remainder] string keyword)
{
var channel = umsg.Channel as ITextChannel;
var channel = (ITextChannel)umsg.Channel;
if (string.IsNullOrWhiteSpace(keyword))
return;
@ -103,7 +110,7 @@ namespace NadekoBot.Modules.Utility
using (var uow = DbHandler.UnitOfWork())
{
var quotes = uow.Quotes.GetAllQuotesByKeyword(keyword);
var quotes = uow.Quotes.GetAllQuotesByKeyword(channel.Guild.Id, keyword);
uow.Quotes.RemoveRange(quotes.ToArray());//wtf?!

View File

@ -2,9 +2,11 @@
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
@ -31,9 +33,11 @@ namespace NadekoBot.Modules.Utility
{ "%user%", (r) => $"<@!{r.UserId}>" },
{ "%target%", (r) => r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>"}
};
private Logger _log { get; }
public RemindCommands()
{
_log = LogManager.GetCurrentClassLogger();
List<Reminder> reminders;
using (var uow = DbHandler.UnitOfWork())
{
@ -44,7 +48,7 @@ namespace NadekoBot.Modules.Utility
foreach (var r in reminders)
{
var t = StartReminder(r);
try { var t = StartReminder(r); } catch (Exception ex) { _log.Warn(ex); }
}
}
@ -75,13 +79,10 @@ namespace NadekoBot.Modules.Utility
await ch.SendMessageAsync(
replacements.Aggregate(RemindMessageFormat,
(cur, replace) => cur.Replace(replace.Key, replace.Value(r)))
.SanitizeMentions()
).ConfigureAwait(false); //it works trust me
}
catch (Exception ex)
{
Console.WriteLine($"Timer error! {ex}");
}
catch (Exception ex) { _log.Warn(ex); }
finally
{
using (var uow = DbHandler.UnitOfWork())
@ -92,7 +93,7 @@ namespace NadekoBot.Modules.Utility
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Remind(IUserMessage umsg, string meorchannel, string timeStr, [Remainder] string message)
{
@ -180,25 +181,27 @@ namespace NadekoBot.Modules.Utility
await uow.CompleteAsync();
}
await channel.SendMessageAsync($"⏰ I will remind \"{(ch is ITextChannel ? ((ITextChannel)ch).Name : umsg.Author.Username)}\" to \"{message.ToString()}\" in {output}. ({time:d.M.yyyy.} at {time:HH:mm})").ConfigureAwait(false);
try { await channel.SendMessageAsync($"⏰ I will remind \"{(ch is ITextChannel ? ((ITextChannel)ch).Name : umsg.Author.Username)}\" to \"{message.SanitizeMentions()}\" in {output}. ({time:d.M.yyyy.} at {time:HH:mm})").ConfigureAwait(false); } catch { }
await StartReminder(rem);
}
////todo owner only
//[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
//[RequireContext(ContextType.Guild)]
//public async Task RemindTemplate(IUserMessage umsg, [Remainder] string arg)
//{
// var channel = (ITextChannel)umsg.Channel;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task RemindTemplate(IUserMessage umsg, [Remainder] string arg)
{
var channel = (ITextChannel)umsg.Channel;
if (string.IsNullOrWhiteSpace(arg))
return;
// arg = arg?.Trim();
// if (string.IsNullOrWhiteSpace(arg))
// return;
// NadekoBot.Config.RemindMessageFormat = arg;
// await channel.SendMessageAsync("`New remind message set.`");
//}
using (var uow = DbHandler.UnitOfWork())
{
uow.BotConfig.GetOrCreate().RemindMessageFormat = arg.Trim();
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendMessageAsync("`New remind template set.`");
}
}
}
}

View File

@ -2,7 +2,7 @@
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Searches.Commands.Models;
using NadekoBot.Modules.Utility.Commands.Models;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using Newtonsoft.Json;
@ -17,105 +17,86 @@ using System.Threading;
using System.Threading.Tasks;
using System.Xml;
namespace NadekoBot.Modules.Searches
namespace NadekoBot.Modules.Utility
{
public partial class Searches
public partial class Utility
{
[Group]
public class UnitConverterCommands
{
private Logger _log;
public static List<ConvertUnit> Units { get; set; } = new List<ConvertUnit>();
private static Logger _log;
private static Timer _timer;
public static TimeSpan Span = new TimeSpan(12, 0, 0);
public UnitConverterCommands()
private static TimeSpan updateInterval = new TimeSpan(12, 0, 0);
static UnitConverterCommands()
{
_log = LogManager.GetCurrentClassLogger();
try
{
using (var uow = DbHandler.UnitOfWork())
{
//need to do this the first time
if (uow.ConverterUnits.Empty())
{
var content = JsonConvert.DeserializeObject<List<MeasurementUnit>>(File.ReadAllText("units.json")).Select(u => new ConvertUnit()
var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit()
{
Modifier = u.Modifier,
UnitType = u.UnitType,
InternalTrigger = string.Join("|", u.Triggers)
});
}).ToArray();
uow.ConverterUnits.AddRange(content.ToArray());
using (var uow = DbHandler.UnitOfWork())
{
if (uow.ConverterUnits.Empty())
{
uow.ConverterUnits.AddRange(data);
uow.Complete();
}
Units = uow.ConverterUnits.GetAll().ToList();
}
Units = data.ToList();
}
catch (Exception e)
{
_log.Warn("Could not load units: " + e.Message);
}
}
_timer = new Timer(new TimerCallback(UpdateCurrency), null, 0,(int)Span.TotalMilliseconds);
public UnitConverterCommands()
{
_timer = new Timer(async (obj) => await UpdateCurrency(), null, (int)updateInterval.TotalMilliseconds, (int)updateInterval.TotalMilliseconds);
}
public void UpdateCurrency(object stateInfo)
public async Task UpdateCurrency()
{
var currencyRates = UpdateCurrencyRates().Result;
var currencyRates = await UpdateCurrencyRates();
var unitTypeString = "currency";
using (var uow = DbHandler.UnitOfWork())
{
var toRemove = Units.Where(u => u.UnitType == unitTypeString);
Units.RemoveAll(u => u.UnitType == unitTypeString);
uow.ConverterUnits.RemoveRange(toRemove.ToArray());
var baseType = new ConvertUnit()
{
Triggers = new[] { currencyRates.Base },
Modifier = decimal.One,
UnitType = unitTypeString
};
uow.ConverterUnits.Add(baseType);
Units.Add(baseType);
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
{
InternalTrigger = u.Key,
Modifier = u.Value,
UnitType = unitTypeString
}).ToArray();
uow.ConverterUnits.AddRange(range);
Units.AddRange(range);
var baseType = new ConvertUnit()
{
Triggers = new[] { currencyRates.Base },
Modifier = decimal.One,
UnitType = unitTypeString
};
var toRemove = Units.Where(u => u.UnitType == unitTypeString);
uow.Complete();
using (var uow = DbHandler.UnitOfWork())
{
uow.ConverterUnits.RemoveRange(toRemove.ToArray());
uow.ConverterUnits.Add(baseType);
uow.ConverterUnits.AddRange(range);
await uow.CompleteAsync().ConfigureAwait(false);
}
Units.RemoveAll(u => u.UnitType == unitTypeString);
Units.Add(baseType);
Units.AddRange(range);
_log.Info("Updated Currency");
}
public List<ConvertUnit> Units { get; set; }
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[RequireContext(ContextType.Guild)]
public async Task ConvertListE(IUserMessage msg) //extended and bugged list
{
var channel = msg.Channel as IGuildChannel;
var sb = new StringBuilder("Units that can be used by the converter: \n");
var res = Units.GroupBy(x => x.UnitType);
foreach (var group in res)
{
sb.AppendLine($"{group.Key}: ```xl");
foreach (var el in group)
{
sb.Append($" [{string.Join(",", el.Triggers)}] ");
}
sb.AppendLine("```");
}
await msg.ReplyLong(sb.ToString(), breakOn: new[] { "```xl", "\n" });
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ConvertList(IUserMessage msg)
{
@ -129,7 +110,7 @@ namespace NadekoBot.Modules.Searches
}
await msg.ReplyLong(sb.ToString(), breakOn: new[] { "```xl\n", "\n" });
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
public async Task Convert(IUserMessage msg, string origin, string target, decimal value)
{
var originUnit = Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(origin.ToLowerInvariant()));
@ -152,10 +133,10 @@ namespace NadekoBot.Modules.Searches
switch (originUnit.Triggers.First().ToUpperInvariant())
{
case "C":
res = value + (decimal)273.15; //celcius!
res = value + 273.15m; //celcius!
break;
case "F":
res = (value + (decimal)459.67) * ((decimal)5 / 9);
res = (value + 459.67m) * (5m / 9m);
break;
default:
res = value;
@ -165,10 +146,10 @@ namespace NadekoBot.Modules.Searches
switch (targetUnit.Triggers.First())
{
case "C":
res = value - (decimal)273.15; //celcius!
res = value - 273.15m; //celcius!
break;
case "F":
res = res * ((decimal)9 / 5) - (decimal)458.67;
res = res * (9m / 5m) - 459.67m;
break;
default:
break;
@ -176,7 +157,6 @@ namespace NadekoBot.Modules.Searches
}
else
{
//I just love currency
if (originUnit.UnitType == "currency")
{
res = (value * targetUnit.Modifier) / originUnit.Modifier;
@ -184,8 +164,9 @@ namespace NadekoBot.Modules.Searches
else
res = (value * originUnit.Modifier) / targetUnit.Modifier;
}
res = Math.Round(res, 2);
await msg.Reply(string.Format("{0} {1} is equal to {2} {3}", value, originUnit.Triggers.First(), res, targetUnit.Triggers.First()));
res = Math.Round(res, 4);
await msg.Reply(string.Format("{0} {1} is equal to {2} {3}", value, (originUnit.Triggers.First() + "s").SnPl(value.IsInteger() ? (int)value : 2), res, (targetUnit.Triggers.First() + "s").SnPl(res.IsInteger() ? (int)res : 2)));
}
}

View File

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace NadekoBot.Modules.Searches.Commands.Models
namespace NadekoBot.Modules.Utility.Commands.Models
{
public class MeasurementUnit
{

View File

@ -2,7 +2,7 @@
using System;
using System.Collections.Generic;
namespace NadekoBot.Modules.Searches.Commands.Models
namespace NadekoBot.Modules.Utility.Commands.Models
{
public class Rates
{

View File

@ -18,12 +18,12 @@ namespace NadekoBot.Modules.Utility
[NadekoModule("Utility", ".")]
public partial class Utility : DiscordModule
{
public Utility(ILocalization loc, CommandService cmds, DiscordSocketClient client) : base(loc, cmds, client)
public Utility(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client)
{
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WhosPlaying(IUserMessage umsg, [Remainder] string game = null)
{
@ -43,9 +43,9 @@ namespace NadekoBot.Modules.Utility
await channel.SendMessageAsync("```xl\n" + string.Join("\n", arr.GroupBy(item => (i++) / 3).Select(ig => string.Concat(ig.Select(el => $"• {el,-35}")))) + "\n```").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task InRole(IUserMessage umsg, [Remainder] string roles = null)
public async Task InRole(IUserMessage umsg, [Remainder] string roles)
{
if (string.IsNullOrWhiteSpace(roles))
return;
@ -76,14 +76,14 @@ namespace NadekoBot.Modules.Utility
await channel.SendMessageAsync(send).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task CheckMyPerms(IUserMessage msg)
{
StringBuilder builder = new StringBuilder("```\n");
var user = msg.Author as IGuildUser;
var perms = user.GetPermissions(msg.Channel as ITextChannel);
var perms = user.GetPermissions((ITextChannel)msg.Channel);
foreach (var p in perms.GetType().GetProperties().Where(p => !p.GetGetMethod().GetParameters().Any()))
{
builder.AppendLine($"{p.Name} : {p.GetValue(perms, null).ToString()}");
@ -93,43 +93,54 @@ namespace NadekoBot.Modules.Utility
await msg.Reply(builder.ToString());
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task UserId(IUserMessage msg, IGuildUser target = null)
{
var usr = target ?? msg.Author;
await msg.Reply($"Id of the user { usr.Username } is { usr.Id })").ConfigureAwait(false);
await msg.Reply($"Id of the user { usr.Username } is { usr.Id }").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
public async Task ChannelId(IUserMessage msg)
{
await msg.Reply($"This Channel's ID is {msg.Channel.Id}").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ServerId(IUserMessage msg)
{
await msg.Reply($"This server's ID is {(msg.Channel as ITextChannel).Guild.Id}").ConfigureAwait(false);
await msg.Reply($"This server's ID is {((ITextChannel)msg.Channel).Guild.Id}").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Roles(IUserMessage msg, IGuildUser target = null)
public async Task Roles(IUserMessage msg, IGuildUser target, int page = 1)
{
var guild = (msg.Channel as ITextChannel).Guild;
var channel = (ITextChannel)msg.Channel;
var guild = channel.Guild;
const int RolesPerPage = 20;
if (page < 1 || page > 100)
return;
if (target != null)
{
await msg.Reply($"`List of roles for **{target.Username}**:` \n• " + string.Join("\n• ", target.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => r.Position)));
await msg.Reply($"`Page #{page} of roles for **{target.Username}**:` \n• " + string.Join("\n• ", target.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => r.Position).Skip((page - 1) * RolesPerPage).Take(RolesPerPage)).SanitizeMentions());
}
else
{
await msg.Reply("`List of roles:` \n• " + string.Join("\n• ", (msg.Channel as ITextChannel).Guild.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r=>r.Position)));
await msg.Reply($"`Page #{page} of all roles on this server:` \n• " + string.Join("\n• ", guild.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => r.Position).Skip((page - 1) * RolesPerPage).Take(RolesPerPage)).SanitizeMentions());
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public Task Roles(IUserMessage msg, int page = 1) =>
Roles(msg, null, page);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ChannelTopic(IUserMessage umsg)
{
@ -142,14 +153,27 @@ namespace NadekoBot.Modules.Utility
await channel.SendMessageAsync("`Topic:` " + topic);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias]
[RequireContext(ContextType.Guild)]
[NadekoCommand, Usage, Description, Aliases]
public async Task Stats(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
var channel = umsg.Channel;
await channel.SendMessageAsync(await NadekoBot.Stats.Print());
}
private Regex emojiFinder { get; } = new Regex(@"<:(?<name>.+?):(?<id>\d*)>", RegexOptions.Compiled);
[NadekoCommand, Usage, Description, Aliases]
public async Task Showemojis(IUserMessage msg, [Remainder] string emojis)
{
var matches = emojiFinder.Matches(emojis);
var result = string.Join("\n", matches.Cast<Match>()
.Select(m => $"`Name:` {m.Groups["name"]} `Link:` http://discordapp.com/api/emojis/{m.Groups["id"]}.png"));
await msg.Channel.SendMessageAsync(result).ConfigureAwait(false);
}
}
}

Some files were not shown because too many files have changed in this diff Show More