Merge pull request #963 from Kwoth/dev

1.1.0
This commit is contained in:
Master Kwoth 2017-01-11 18:22:34 +01:00 committed by GitHub
commit 17d642805f
66 changed files with 5565 additions and 1366 deletions

@ -1 +1 @@
Subproject commit fa2568bc312ba35f1518e47601c62fccdb949731 Subproject commit b9f767337d2b7c07ed76eb83c3bc5030109d5238

View File

@ -12,8 +12,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "NadekoBot", "src\NadekoBot\NadekoBot.xproj", "{45EC1473-C678-4857-A544-07DFE0D0B478}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "NadekoBot", "src\NadekoBot\NadekoBot.xproj", "{45EC1473-C678-4857-A544-07DFE0D0B478}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.API", "discord.net\src\Discord.Net.API\Discord.Net.API.xproj", "{834C70DF-1230-4AAA-9C13-48AB232E8D76}"
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}" 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 EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Core", "discord.net\src\Discord.Net.Core\Discord.Net.Core.xproj", "{E5F4786F-58F3-469E-8C87-1908A95436B7}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Core", "discord.net\src\Discord.Net.Core\Discord.Net.Core.xproj", "{E5F4786F-58F3-469E-8C87-1908A95436B7}"
@ -35,12 +33,6 @@ Global
{45EC1473-C678-4857-A544-07DFE0D0B478}.GlobalNadeko|Any CPU.Build.0 = Release|Any CPU {45EC1473-C678-4857-A544-07DFE0D0B478}.GlobalNadeko|Any CPU.Build.0 = Release|Any CPU
{45EC1473-C678-4857-A544-07DFE0D0B478}.Release|Any CPU.ActiveCfg = Release|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 {45EC1473-C678-4857-A544-07DFE0D0B478}.Release|Any CPU.Build.0 = Release|Any CPU
{834C70DF-1230-4AAA-9C13-48AB232E8D76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{834C70DF-1230-4AAA-9C13-48AB232E8D76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{834C70DF-1230-4AAA-9C13-48AB232E8D76}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
{834C70DF-1230-4AAA-9C13-48AB232E8D76}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
{834C70DF-1230-4AAA-9C13-48AB232E8D76}.Release|Any CPU.ActiveCfg = Release|Any CPU
{834C70DF-1230-4AAA-9C13-48AB232E8D76}.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.ActiveCfg = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = 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 = Debug|Any CPU {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU

View File

@ -18,8 +18,63 @@ You can support the project on patreon: <https://patreon.com/nadekobot> or paypa
### Administration ### Administration
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`.resetperms` | Resets BOT's permissions module on this server to the default value. **Requires Administrator server permission.** | `.resetperms` `.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. **Requires ManageRoles server permission.** **Requires ManageChannels server permission.** | `.voice+text`
`.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. **Requires Administrator server permission.** | `.delmsgoncmd` `.cleanvplust` `.cv+t` | Deletes all text channels ending in `-voice` for which voicechannels are not found. Use at your own risk. **Requires ManageChannels server permission.** **Requires ManageRoles server permission.** | `.cleanv+t`
`.greetdel` `.grdel` | Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.greetdel 0` or `.greetdel 30`
`.greet` | Toggles anouncements on the current channel when someone joins the server. **Requires ManageServer server permission.** | `.greet`
`.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. **Requires ManageServer server permission.** | `.greetmsg Welcome, %user%.`
`.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). **Requires ManageServer server permission.** | `.greetdm`
`.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. **Requires ManageServer server permission.** | `.greetdmmsg Welcome to the server, %user%`.
`.bye` | Toggles anouncements on the current channel when someone leaves the server. **Requires ManageServer server permission.** | `.bye`
`.byemsg` | Sets a new leave announcement message. Type %user% if you want to show the name the user who left. Type %id% to show id. Using this command with no message will show the current bye message. **Requires ManageServer server permission.** | `.byemsg %user% has left.`
`.byedel` | Sets the time it takes (in seconds) for bye messages to be auto-deleted. Set 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.byedel 0` or `.byedel 30`
`.leave` | Makes Nadeko leave the server. Either name or id required. **Bot Owner only.** | `.leave 123123123331`
`.die` | Shuts the bot down. **Bot Owner only.** | `.die`
`.setname` `.newnm` | Gives the bot a new name. **Bot Owner only.** | `.newnm BotName`
`.setstatus` | Sets the bot's status. (Online/Idle/Dnd/Invisible) **Bot Owner only.** | `.setstatus Idle`
`.setavatar` `.setav` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner only.** | `.setav http://i.imgur.com/xTG3a1I.jpg`
`.setgame` | Sets the bots game. **Bot Owner only.** | `.setgame with snakes`
`.setstream` | Sets the bots stream. First argument is the twitch link, second argument is stream name. **Bot Owner only.** | `.setstream TWITCHLINK Hello`
`.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:`. **Bot Owner only.** | `.send serverid|c:channelid message` or `.send serverid|u:userid message`
`.announce` | Sends a message to all servers' general channel bot is connected to. **Bot Owner only.** | `.announce Useless spam`
`.adsarm` | Toggles the automatic deletion of confirmations for .iam and .iamn commands. **Requires ManageMessages server permission.** | `.adsarm`
`.asar` | Adds a role to the list of self-assignable roles. **Requires ManageRoles server permission.** | `.asar Gamer`
`.rsar` | Removes a specified role from the list of self-assignable roles. **Requires ManageRoles server permission.** | `.rsar`
`.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) **Requires ManageRoles server permission.** | `.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`
`.slowmode` | Toggles slowmode. Disable by specifying no parameters. To enable, specify a number of messages each user can send, and an interval in seconds. For example 1 message every 5 seconds. **Requires ManageMessages server permission.** | `.slowmode 1 5` or `.slowmode`
`.rotateplaying` `.ropl` | Toggles rotation of playing status of the dynamic strings you previously specified. **Bot Owner only.** | `.ropl`
`.addplaying` `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued% **Bot Owner only.** | `.adpl`
`.listplaying` `.lipl` | Lists all playing statuses with their corresponding number. **Bot Owner only.** | `.lipl`
`.removeplaying` `.rmpl` `.repl` | Removes a playing string on a given number. **Bot Owner only.** | `.rmpl`
`.setmuterole` | Sets a name of the role which will be assigned to people who should be muted. Default is nadeko-mute. **Requires ManageRoles server permission.** | `.setmuterole Silenced`
`.mute` | Mutes a mentioned user both from speaking and chatting. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.mute @Someone`
`.unmute` | Unmutes a mentioned user previously muted with `.mute` command. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.unmute @Someone`
`.chatmute` | Prevents a mentioned user from chatting in text channels. **Requires ManageRoles server permission.** | `.chatmute @Someone`
`.chatunmute` | Removes a mute role previously set on a mentioned user with `.chatmute` which prevented him from chatting in text channels. **Requires ManageRoles server permission.** | `.chatunmute @Someone`
`.voicemute` | Prevents a mentioned user from speaking in voice channels. **Requires MuteMembers server permission.** | `.voicemute @Someone`
`.voiceunmute` | Gives a previously voice-muted user a permission to speak. **Requires MuteMembers server permission.** | `.voiceunmute @Someguy`
`.migratedata` | Migrate data from old bot configuration **Bot Owner only.** | `.migratedata`
`.repeatinvoke` `.repinv` | Immediately shows the repeat message on a certain index and restarts its timer. **Requires ManageMessages server permission.** | `.repinv 1`
`.repeatremove` `.reprm` | Removes a repeating message on a specified index. Use `.repeatlist` to see indexes. **Requires ManageMessages server permission.** | `.reprm 2`
`.repeat` | Repeat a message every X minutes in the current channel. **Requires ManageMessages server permission.** | `.repeat 5 Hello there`
`.repeatlist` `.replst` | Shows currently repeating messages and their indexes. **Requires ManageMessages server permission.** | `.repeatlist`
`.logserver` | Enables or Disables ALL log events. If enabled, all log events will log to this channel. **Requires Administrator server permission.** **Bot Owner only.** | `.logserver enable` or `.logserver disable`
`.logignore` | Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. **Requires Administrator server permission.** **Bot Owner only.** | `.logignore`
`.logevents` | Shows a list of all events you can subscribe to with `.log` **Requires Administrator server permission.** **Bot Owner only.** | `.logevents`
`.log` | Toggles logging event. Disables it if it's active anywhere on the server. Enables if it's not active. Use `.logevents` to see a list of all events you can subscribe to. **Requires Administrator server permission.** **Bot Owner only.** | `.log userpresence` or `.log userbanned`
`.fwmsgs` | Toggles forwarding of non-command messages sent to bot's DM to the bot owners **Bot Owner only.** | `.fwmsgs`
`.fwtoall` | Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json **Bot Owner only.** | `.fwtoall`
`.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. **Bot Owner only.** | `.scsc`
`.jcsc` | Joins current channel to an instance of cross server channel using the token. **Requires ManageServer server permission.** | `.jcsc TokenHere`
`.lcsc` | Leaves Cross server channel instance from this channel. **Requires ManageServer server permission.** | `.lcsc`
`.autoassignrole` `.aar` | Automaticaly assigns a specified role to every user who joins the server. **Requires ManageRoles server permission.** | `.aar` to disable, `.aar Role Name` to enable
`.antiraid` | Sets an anti-raid protection on the server. First argument is number of people which will trigger the protection. Second one is a time interval in which that number of people needs to join in order to trigger the protection, and third argument is punishment for those people (Kick, Ban, Mute) **Requires Administrator server permission.** | `.antiraid 5 20 Kick`
`.antispam` | Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders. **Requires Administrator server permission.** | `.antispam 3 Mute` or `.antispam 4 Kick` or `.antispam 6 Ban`
`.resetperms` | Resets BOT's permissions module on this server to the default value. **Requires Administrator server permission.** | `.resetperms`
`.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. **Requires Administrator server permission.** | `.delmsgoncmd`
`.setrole` `.sr` | Sets a role for a given user. **Requires ManageRoles server permission.** | `.sr @User Guest` `.setrole` `.sr` | Sets a role for a given user. **Requires ManageRoles server permission.** | `.sr @User Guest`
`.removerole` `.rr` | Removes a role from a given user. **Requires ManageRoles server permission.** | `.rr @User Admin` `.removerole` `.rr` | Removes a role from a given user. **Requires ManageRoles server permission.** | `.rr @User Admin`
`.renamerole` `.renr` | Renames a role. Roles you are renaming must be lower than bot's highest role. **Requires ManageRoles server permission.** | `.renr "First role" SecondRole` `.renamerole` `.renr` | Renames a role. Roles you are renaming must be lower than bot's highest role. **Requires ManageRoles server permission.** | `.renr "First role" SecondRole`
@ -38,62 +93,10 @@ Command and aliases | Description | Usage
`.settopic` `.st` | Sets a topic on the current channel. **Requires ManageChannels server permission.** | `.st My new topic` `.settopic` `.st` | Sets a topic on the current channel. **Requires ManageChannels server permission.** | `.st My new topic`
`.setchanlname` `.schn` | Changes the name of the current channel. **Requires ManageChannels server permission.** | `.schn NewName` `.setchanlname` `.schn` | Changes the name of the current channel. **Requires ManageChannels server permission.** | `.schn NewName`
`.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` `.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. **Bot Owner only.** | `.die` `.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner only.** | `.savechat 150`
`.setname` `.newnm` | Gives the bot a new name. **Bot Owner only.** | `.newnm BotName`
`.setavatar` `.setav` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner only.** | `.setav http://i.imgur.com/xTG3a1I.jpg`
`.setgame` | Sets the bots game. **Bot Owner only.** | `.setgame with snakes`
`.setstream` | Sets the bots stream. First argument is the twitch link, second argument is stream name. **Bot Owner only.** | `.setstream TWITCHLINK Hello`
`.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:`. **Bot Owner only.** | `.send serverid|c:channelid message` or `.send serverid|u:userid message`
`.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`
`.mentionrole` `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. **Requires MentionEveryone server permission.** | `.menro RoleName` `.mentionrole` `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. **Requires MentionEveryone server permission.** | `.menro RoleName`
`.donators` | List of lovely people who donated to keep this project alive. | `.donators` `.donators` | List of lovely people who donated to keep this project alive. | `.donators`
`.donadd` | Add a donator to the database. **Bot Owner only.** | `.donadd Donate Amount` `.donadd` | Add a donator to the database. **Bot Owner only.** | `.donadd Donate Amount`
`.antiraid` | Sets an anti-raid protection on the server. First argument is number of people which will trigger the protection. Second one is a time interval in which that number of people needs to join in order to trigger the protection, and third argument is punishment for those people (Kick, Ban, Mute) **Requires Administrator server permission.** | `.antiraid 5 20 Kick`
`.antispam` | Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders. **Requires Administrator server permission.** | `.antispam 3 Mute` or `.antispam 4 Kick` or `.antispam 6 Ban`
`.autoassignrole` `.aar` | Automaticaly assigns a specified role to every user who joins the server. **Requires ManageRoles server permission.** | `.aar` to disable, `.aar Role Name` to enable
`.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. **Bot Owner only.** | `.scsc`
`.jcsc` | Joins current channel to an instance of cross server channel using the token. **Requires ManageServer server permission.** | `.jcsc TokenHere`
`.lcsc` | Leaves Cross server channel instance from this channel. **Requires ManageServer server permission.** | `.lcsc`
`.fwmsgs` | Toggles forwarding of non-command messages sent to bot's DM to the bot owners **Bot Owner only.** | `.fwmsgs`
`.fwtoall` | Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json **Bot Owner only.** | `.fwtoall`
`.logserver` | Enables or Disables ALL log events. If enabled, all log events will log to this channel. **Requires Administrator server permission.** **Bot Owner only.** | `.logserver enable` or `.logserver disable`
`.logignore` | Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. **Requires Administrator server permission.** **Bot Owner only.** | `.logignore`
`.logevents` | Shows a list of all events you can subscribe to with `.log` **Requires Administrator server permission.** **Bot Owner only.** | `.logevents`
`.log` | Toggles logging event. Disables it if it's active anywhere on the server. Enables if it's not active. Use `.logevents` to see a list of all events you can subscribe to. **Requires Administrator server permission.** **Bot Owner only.** | `.log userpresence` or `.log userbanned`
`.repeatinvoke` `.repinv` | Immediately shows the repeat message and restarts the timer. **Requires ManageMessages server permission.** | `.repinv`
`.repeat` | Repeat a message every X minutes. If no parameters are specified, repeat is disabled. **Requires ManageMessages server permission.** | `.repeat 5 Hello there`
`.migratedata` | Migrate data from old bot configuration **Bot Owner only.** | `.migratedata`
`.setmuterole` | Sets a name of the role which will be assigned to people who should be muted. Default is nadeko-mute. **Requires ManageRoles server permission.** | `.setmuterole Silenced`
`.mute` | Mutes a mentioned user both from speaking and chatting. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.mute @Someone`
`.unmute` | Unmutes a mentioned user previously muted with `.mute` command. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.unmute @Someone`
`.chatmute` | Prevents a mentioned user from chatting in text channels. **Requires ManageRoles server permission.** | `.chatmute @Someone`
`.chatunmute` | Removes a mute role previously set on a mentioned user with `.chatmute` which prevented him from chatting in text channels. **Requires ManageRoles server permission.** | `.chatunmute @Someone`
`.voicemute` | Prevents a mentioned user from speaking in voice channels. **Requires MuteMembers server permission.** | `.voicemute @Someone`
`.voiceunmute` | Gives a previously voice-muted user a permission to speak. **Requires MuteMembers server permission.** | `.voiceunmute @Someguy`
`.rotateplaying` `.ropl` | Toggles rotation of playing status of the dynamic strings you previously specified. **Bot Owner only.** | `.ropl`
`.addplaying` `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued% **Bot Owner only.** | `.adpl`
`.listplaying` `.lipl` | Lists all playing statuses with their corresponding number. **Bot Owner only.** | `.lipl`
`.removeplaying` `.rmpl` `.repl` | Removes a playing string on a given number. **Bot Owner only.** | `.rmpl`
`.slowmode` | Toggles slowmode. Disable by specifying no parameters. To enable, specify a number of messages each user can send, and an interval in seconds. For example 1 message every 5 seconds. **Requires ManageMessages server permission.** | `.slowmode 1 5` or `.slowmode`
`.adsarm` | Toggles the automatic deletion of confirmations for .iam and .iamn commands. **Requires ManageMessages server permission.** | `.adsarm`
`.asar` | Adds a role to the list of self-assignable roles. **Requires ManageRoles server permission.** | `.asar Gamer`
`.rsar` | Removes a specified role from the list of self-assignable roles. **Requires ManageRoles server permission.** | `.rsar`
`.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) **Requires ManageRoles server permission.** | `.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`
`.leave` | Makes Nadeko leave the server. Either name or id required. **Bot Owner only.** | `.leave 123123123331`
`.greetdel` `.grdel` | Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.greetdel 0` or `.greetdel 30`
`.greet` | Toggles anouncements on the current channel when someone joins the server. **Requires ManageServer server permission.** | `.greet`
`.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. **Requires ManageServer server permission.** | `.greetmsg Welcome, %user%.`
`.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). **Requires ManageServer server permission.** | `.greetdm`
`.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. **Requires ManageServer server permission.** | `.greetdmmsg Welcome to the server, %user%`.
`.bye` | Toggles anouncements on the current channel when someone leaves the server. **Requires ManageServer server permission.** | `.bye`
`.byemsg` | Sets a new leave announcement message. Type %user% if you want to show the name the user who left. Type %id% to show id. Using this command with no message will show the current bye message. **Requires ManageServer server permission.** | `.byemsg %user% has left.`
`.byedel` | Sets the time it takes (in seconds) for bye messages to be auto-deleted. Set 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.byedel 0` or `.byedel 30`
`.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. **Requires ManageRoles server permission.** **Requires ManageChannels server permission.** | `.voice+text`
`.cleanvplust` `.cv+t` | Deletes all text channels ending in `-voice` for which voicechannels are not found. Use at your own risk. **Requires ManageChannels server permission.** **Requires ManageRoles server permission.** | `.cleanv+t`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
@ -120,59 +123,60 @@ Command and aliases | Description | Usage
`.listcustreactg` `.lcrg` | Lists global or server custom reactions (20 commands per page) grouped by trigger, and show a number of responses for each. Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. | `.lcrg 1` `.listcustreactg` `.lcrg` | Lists global or server custom reactions (20 commands per page) grouped by trigger, and show a number of responses for each. Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. | `.lcrg 1`
`.showcustreact` `.scr` | Shows a custom reaction's response on a given ID. | `.scr 1` `.showcustreact` `.scr` | Shows a custom reaction's response on a given ID. | `.scr 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` `.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`
`.crstatsclear` | Resets the counters on `.crstats`. You can specify a trigger to clear stats only for that trigger. | `.crstatsclear` or `.crstatsclear rng` `.crstatsclear` | Resets the counters on `.crstats`. You can specify a trigger to clear stats only for that trigger. **Bot Owner only.** | `.crstatsclear` or `.crstatsclear rng`
`.crstats` | Shows a list of custom reactions and the number of times they have been executed. Paginated with 10 per page. Use `.crstatsclear` to reset the counters. | `.crstats` or `.crstats 3` `.crstats` | Shows a list of custom reactions and the number of times they have been executed. Paginated with 10 per page. Use `.crstatsclear` to reset the counters. | `.crstats` or `.crstats 3`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
### Gambling ### Gambling
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. | `$raffle` or `$raffle RoleName` `$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 awards you 1.8x the currency you've bet. | `$bf 5 heads` or `$bf 3 t`
`$draw` | Draws a card from the deck.If you supply number X, she draws up to 5 cards from the deck. | `$draw` or `$draw 5`
`$shuffle` `$sh` | Reshuffles all cards back into the deck. | `$sh`
`$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. Y can be a letter 'F' if you want to roll fate dice instead of dnd. | `$roll` or `$roll 7` or `$roll 3d5` or `$roll 5dF`
`$rolluo` | 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`
`$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 currency 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`
`$cash` `$$$` | Check how much currency a person has. (Defaults to yourself) | `$$$` or `$$$ @SomeGuy` `$cash` `$$$` | Check how much currency a person has. (Defaults to yourself) | `$$$` or `$$$ @SomeGuy`
`$give` | Give someone a certain amount of currency. | `$give 1 "@SomeGuy"` `$give` | Give someone a certain amount of currency. | `$give 1 "@SomeGuy"`
`$award` | Awards someone a certain amount of currency. You can also specify a role name to award currency to all users in a role. **Bot Owner only.** | `$award 100 @person` or `$award 5 Role Of Gamblers` `$award` | Awards someone a certain amount of currency. You can also specify a role name to award currency to all users in a role. **Bot Owner only.** | `$award 100 @person` or `$award 5 Role Of Gamblers`
`$take` | Takes a certain amount of currency from someone. **Bot Owner only.** | `$take 1 "@someguy"` `$take` | Takes a certain amount of currency from someone. **Bot Owner only.** | `$take 1 "@someguy"`
`$betroll` `$br` | Bets a certain amount of currency and rolls a dice. Rolling over 66 yields x2 of your currency, over 90 - x3 and 100 x10. | `$br 5` `$betroll` `$br` | Bets a certain amount of currency and rolls a dice. Rolling over 66 yields x2 of your currency, over 90 - x3 and 100 x10. | `$br 5`
`$leaderboard` `$lb` | Displays bot currency leaderboard. | `$lb` `$leaderboard` `$lb` | Displays bot currency leaderboard. | `$lb`
`$race` | Starts a new animal race. | `$race`
`$joinrace` `$jr` | Joins a new race. You can specify an amount of currency for betting (optional). You will get YourBet*(participants-1) back if you win. | `$jr` or `$jr 5`
`$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. Y can be a letter 'F' if you want to roll fate dice instead of dnd. | `$roll` or `$roll 7` or `$roll 3d5` or `$roll 5dF`
`$rolluo` | 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`
`$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15`
`$draw` | Draws a card from the deck.If you supply number X, she draws up to 5 cards from the deck. | `$draw` or `$draw 5`
`$shuffle` `$sh` | Reshuffles all cards back into the deck. | `$sh`
`$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 awards you 1.8x the currency you've bet. | `$bf 5 heads` or `$bf 3 t`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
### Games ### Games
Command and aliases | Description | Usage 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 which requires users to send the number of the voting option to the bot. **Requires ManageMessages server permission.** | `>poll Question?;Answer1;Answ 2;A_3`
`>publicpoll` `>ppoll` | Creates a public poll which requires users to type a number of the voting option in the channel command is ran in. **Requires ManageMessages server permission.** | `>ppoll Question?;Answer1;Answ 2;A_3`
`>pollend` | Stops active poll on this server and prints the results in this channel. **Requires ManageMessages server permission.** | `>pollend`
`>acrophobia` `>acro` | Starts an Acrophobia game. Second argment is optional round length in seconds. (default is 60) | `>acro` or `>acro 30`
`>cleverbot` | Toggles cleverbot session. When enabled, the bot will reply to messages starting with bot mention in the server. Custom reactions starting with %mention% won't work if cleverbot is enabled. **Requires ManageMessages server permission.** | `>cleverbot`
`>hangmanlist` | Shows a list of hangman term types. | `> hangmanlist`
`>hangman` | Starts a game of hangman in the channel. Use `>hangmanlist` to see a list of available term types. Defaults to 'all'. | `>hangman` or `>hangman movies`
`>pick` | Picks the currency planted in this channel. 60 seconds cooldown. | `>pick`
`>plant` | Spend a unit of currency to plant it in this channel. (If bot is restarted or crashes, the currency will be lost) | `>plant`
`>gencurrency` `>gc` | Toggles currency generation on this channel. Every posted message will have chance to spawn currency. Chance is specified by the Bot Owner. (default is 2%) **Requires ManageMessages server permission.** | `>gc`
`>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. **Bot Owner only.** | `>typeadd wordswords`
`>typelist` | Lists added typing articles with their IDs. 15 per page. | `>typelist` or `>typelist 3`
`>typedel` | Deletes a typing article given the ID. **Bot Owner only.** | `>typedel 3`
`>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` or `>t 5 nohint` `>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` or `>t 5 nohint`
`>tl` | Shows a current trivia leaderboard. | `>tl` `>tl` | Shows a current trivia leaderboard. | `>tl`
`>tq` | Quits current trivia after current question. | `>tq` `>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. **Bot Owner only.** | `>typeadd wordswords`
`>typelist` | Lists added typing articles with their IDs. 15 per page. | `>typelist` or `>typelist 3`
`>typedel` | Deletes a typing article given the ID. **Bot Owner only.** | `>typedel 3`
`>poll` | Creates a poll which requires users to send the number of the voting option to the bot. **Requires ManageMessages server permission.** | `>poll Question?;Answer1;Answ 2;A_3`
`>publicpoll` `>ppoll` | Creates a public poll which requires users to type a number of the voting option in the channel command is ran in. **Requires ManageMessages server permission.** | `>ppoll Question?;Answer1;Answ 2;A_3`
`>pollstats` | Shows the poll results without stopping the poll on this server. **Requires ManageMessages server permission.** | `>pollstats`
`>pollend` | Stops active poll on this server and prints the results in this channel. **Requires ManageMessages server permission.** | `>pollend`
`>pick` | Picks the currency planted in this channel. 60 seconds cooldown. | `>pick`
`>plant` | Spend a unit of currency to plant it in this channel. (If bot is restarted or crashes, the currency will be lost) | `>plant`
`>gencurrency` `>gc` | Toggles currency generation on this channel. Every posted message will have chance to spawn currency. Chance is specified by the Bot Owner. (default is 2%) **Requires ManageMessages server permission.** | `>gc`
`>hangmanlist` | Shows a list of hangman term types. | `> hangmanlist`
`>hangman` | Starts a game of hangman in the channel. Use `>hangmanlist` to see a list of available term types. Defaults to 'all'. | `>hangman` or `>hangman movies`
`>cleverbot` | Toggles cleverbot session. When enabled, the bot will reply to messages starting with bot mention in the server. Custom reactions starting with %mention% won't work if cleverbot is enabled. **Requires ManageMessages server permission.** | `>cleverbot`
`>acrophobia` `>acro` | Starts an Acrophobia game. Second argment is optional round length in seconds. (default is 60) | `>acro` or `>acro 30`
`>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`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
@ -182,9 +186,9 @@ Command and aliases | Description | Usage
`-modules` `-mdls` | Lists all bot modules. | `-modules` `-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` `-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` `-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. **Bot Owner only.** | `-hgit` `-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` `-readme` `-guide` | Sends a readme and a guide links to the channel. | `-readme` or `-guide`
`-donate` | Instructions for helping the project financially. | `-donate` `-donate` | Instructions for helping the project financially. | `-donate`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
@ -215,11 +219,11 @@ Command and aliases | Description | Usage
`!!setmaxplaytime` `!!smp` | Sets a maximum number of seconds (>14) a song can run before being skipped automatically. Set 0 to have no limit. | `!!smp 0` or `!!smp 270` `!!setmaxplaytime` `!!smp` | Sets a maximum number of seconds (>14) a song can run before being skipped automatically. Set 0 to have no limit. | `!!smp 0` or `!!smp 270`
`!!reptcursong` `!!rcs` | Toggles repeat of current song. | `!!rcs` `!!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` `!!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` `!!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 saved playlist using it's ID. Use `!!pls` to list all saved playlists and !!save to save new ones. | `!!load 5` `!!load` | Loads a saved playlist using it's ID. Use `!!pls` to list all saved playlists and !!save to save new ones. | `!!load 5`
`!!playlists` `!!pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!!pls 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` `!!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` `!!goto` | Goes to a specific time in seconds in a song. | `!!goto 30`
`!!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` `!!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`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
@ -227,17 +231,17 @@ Command and aliases | Description | Usage
### NSFW ### NSFW
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`~hentai` | Shows a hentai image from a random website (gelbooru or danbooru or konachan or atfbooru or yandere) with a given tag. Tag is optional but preferred. Only 1 tag allowed. | `~hentai yuri` `~hentai` | Shows a hentai image from a random website (gelbooru or danbooru or konachan or atfbooru or yandere) with a given tag. Tag is optional but preferred. Only 1 tag allowed. | `~hentai yuri`
`~autohentai` | Posts a hentai every X seconds with a random tag from the provided tags. Use `|` to separate tags. 20 seconds minimum. Provide no arguments to disable. | `~autohentai 30 yuri|tail|long_hair` or `~autohentai` `~autohentai` | Posts a hentai every X seconds with a random tag from the provided tags. Use `|` to separate tags. 20 seconds minimum. Provide no arguments to disable. | `~autohentai 30 yuri|tail|long_hair` or `~autohentai`
`~hentaibomb` | Shows a total 5 images (from gelbooru, danbooru, konachan, yandere and atfbooru). Tag is optional but preferred. | `~hentaibomb yuri` `~hentaibomb` | Shows a total 5 images (from gelbooru, danbooru, konachan, yandere and atfbooru). Tag is optional but preferred. | `~hentaibomb yuri`
`~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~danbooru yuri+kissing` `~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~danbooru yuri+kissing`
`~yandere` | Shows a random image from yandere with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~yandere tag1+tag2` `~yandere` | Shows a random image from yandere with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~yandere tag1+tag2`
`~konachan` | Shows a random hentai image from konachan with a given tag. Tag is optional but preferred. | `~konachan yuri` `~konachan` | Shows a random hentai image from konachan with a given tag. Tag is optional but preferred. | `~konachan yuri`
`~gelbooru` | Shows a random hentai image from gelbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~gelbooru yuri+kissing` `~gelbooru` | Shows a random hentai image from gelbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~gelbooru yuri+kissing`
`~rule34` | Shows a random image from rule34.xx with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~rule34 yuri+kissing` `~rule34` | Shows a random image from rule34.xx with a given tag. Tag is optional but preferred. (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 preferred. Use spaces for multiple tags. | `~e621 yuri kissing` `~e621` | Shows a random hentai image from e621.net with a given tag. Tag is optional but preferred. Use spaces for multiple tags. | `~e621 yuri kissing`
`~cp` | We all know where this will lead you to. | `~cp` `~cp` | We all know where this will lead you to. | `~cp`
`~boobs` | Real adult content. | `~boobs` `~boobs` | Real adult content. | `~boobs`
`~butts` `~ass` `~butt` | Real adult content. | `~butts` or `~ass` `~butts` `~ass` `~butt` | Real adult content. | `~butts` or `~ass`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
@ -245,6 +249,17 @@ Command and aliases | Description | Usage
### Permissions ### Permissions
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`;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`
`;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`
`;ubl` | Either [add]s or [rem]oves a user specified by a mention or ID from a blacklist. **Bot Owner only.** | `;ubl add @SomeUser` or `;ubl rem 12312312313`
`;cbl` | Either [add]s or [rem]oves a channel specified by an ID from a blacklist. **Bot Owner only.** | `;cbl rem 12312312312`
`;sbl` | Either [add]s or [rem]oves a server specified by a Name or ID from a blacklist. **Bot Owner only.** | `;sbl add 12312321312` or `;sbl rem SomeTrashServer`
`;verbose` `;v` | Sets whether to show when a command/module is blocked. | `;verbose true` `;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` `;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` `;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`
@ -262,120 +277,111 @@ Command and aliases | Description | Usage
`;allrolemdls` `;arm` | Enable or disable all modules for a specific role. | `;arm [enable/disable] MyRole` `;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` `;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]` `;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. **Bot Owner only.** | `;ubl add @SomeUser` or `;ubl rem 12312312313`
`;cbl` | Either [add]s or [rem]oves a channel specified by an ID from a blacklist. **Bot Owner only.** | `;cbl rem 12312312312`
`;sbl` | Either [add]s or [rem]oves a server specified by a Name or ID from a blacklist. **Bot Owner only.** | `;sbl add 12312321312` or `;sbl rem SomeTrashServer`
`;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`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
### Pokemon ### Pokemon
Command and aliases | Description | Usage 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` `>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` `>movelist` `>ml` | Lists the moves you are able to use | `>ml`
`>heal` | Heals someone. Revives those who fainted. Costs a NadekoFlower | `>heal @someone` `>heal` | Heals someone. Revives those who fainted. Costs a NadekoFlower | `>heal @someone`
`>type` | Get the poketype of the target. | `>type @someone` `>type` | Get the poketype of the target. | `>type @someone`
`>settype` | Set your poketype. Costs a NadekoFlower. Provide no arguments to see a list of available types. | `>settype fire` or `>settype` `>settype` | Set your poketype. Costs a NadekoFlower. Provide no arguments to see a list of available types. | `>settype fire` or `>settype`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
### Searches ### Searches
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`~weather` `~we` | Shows weather data for a specified city. You can also specify a country after a comma. | `~we Moscow, RU` `~xkcd` | Shows a XKCD comic. No arguments will retrieve random one. Number argument will retrieve a specific comic, and "latest" will get the latest one. | `~xkcd` or `~xkcd 1400` or `~xkcd latest`
`~youtube` `~yt` | Searches youtubes and shows the first result | `~yt query` `~translate` `~trans` | Translates from>to text. From the given language to the destination language. | `~trans en>fr Hello`
`~imdb` `~omdb` | Queries omdb for movies or series, show first result. | `~imdb Batman vs Superman` `~autotrans` `~at` | Starts automatic translation of all messages by users who set their `~atl` in this channel. You can set "del" argument to automatically delete all translated user messages. **Requires Administrator server permission.** **Bot Owner only.** | `~at` or `~at del`
`~randomcat` `~meow` | Shows a random cat image. | `~meow` `~autotranslang` `~atl` | `~atl en>fr` | Sets your source and target language to be used with `~at`. Specify no arguments to remove previously set value.
`~randomdog` `~woof` | Shows a random dog image. | `~woof` `~translangs` | Lists the valid languages for translation. | `~translangs`
`~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`
`~shorten` | Attempts to shorten an URL, if it fails, returns the input URL. | `~shorten https://google.com`
`~google` `~g` | Get a google search link for some terms. | `~google query`
`~magicthegathering` `~mtg` | Searches for a Magic The Gathering card. | `~magicthegathering about face` or `~mtg about face`
`~hearthstone` `~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | `~hs Ysera`
`~yodify` `~yoda` | Translates your normal sentences into Yoda styled sentences! | ~yodify I was once an adventurer like you` or `~yoda my feelings hurt`
`~urbandict` `~ud` | Searches Urban Dictionary for a word. | `~ud Pineapple`
`~define` `~def` | Finds a definition of a word. | `~def heresy`
`~#` | 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 preferred. (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"`
`~avatar` `~av` | Shows a mentioned person's avatar. | `~av "@SomeGuy"`
`~wikia` | Gives you back a wikia link | `~wikia mtg Vigilance` or `~wikia mlp Dashy`
`~minecraftping` `~mcping` | Pings a minecraft server. | `~mcping 127.0.0.1:25565`
`~minecraftquery` `~mcq` | Finds information about a minecraft server. | `~mcq server:ip`
`~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"`
`~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`
`~overwatch` `~ow` | Show's basic stats on a player (competitive rank, playtime, level etc) Region codes are: `eu` `us` `cn` `kr` | `~ow us Battletag#1337` or `~overwatch eu Battletag#2016`
`~placelist` | Shows the list of available tags for the `~place` command. | `~placelist`
`~place` | Shows a placeholder image of a given tag. Use `~placelist` to see all available tags. You can specify the width and height of the image as the last two optional arguments. | `~place Cage` or `~place steven 500 400`
`~pokemon` `~poke` | Searches for a pokemon. | `~poke Sylveon`
`~pokemonability` `~pokeab` | Searches for a pokemon ability. | `~pokeab overgrow`
`~hitbox` `~hb` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~hitbox SomeStreamer` `~hitbox` `~hb` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~hitbox SomeStreamer`
`~twitch` `~tw` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~twitch SomeStreamer` `~twitch` `~tw` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~twitch SomeStreamer`
`~beam` `~bm` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~beam SomeStreamer` `~beam` `~bm` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~beam SomeStreamer`
`~liststreams` `~ls` | Lists all streams you are following on this server. | `~ls` `~liststreams` `~ls` | Lists all streams you are following on this server. | `~ls`
`~removestream` `~rms` | Removes notifications of a certain streamer from a certain platform on this channel. **Requires ManageMessages server permission.** | `~rms Twitch SomeGuy` or `~rms Beam SomeOtherGuy` `~removestream` `~rms` | Removes notifications of a certain streamer from a certain platform on this channel. **Requires ManageMessages server permission.** | `~rms Twitch SomeGuy` or `~rms Beam SomeOtherGuy`
`~checkstream` `~cs` | Checks if a user is online on a certain streaming platform. | `~cs twitch MyFavStreamer` `~checkstream` `~cs` | Checks if a user is online on a certain streaming platform. | `~cs twitch MyFavStreamer`
`~translate` `~trans` | Translates from>to text. From the given language to the destination language. | `~trans en>fr Hello` `~pokemon` `~poke` | Searches for a pokemon. | `~poke Sylveon`
`~autotrans` `~at` | Starts automatic translation of all messages by users who set their `~atl` in this channel. You can set "del" argument to automatically delete all translated user messages. **Requires Administrator server permission.** **Bot Owner only.** | `~at` or `~at del` `~pokemonability` `~pokeab` | Searches for a pokemon ability. | `~pokeab overgrow`
`~autotranslang` `~atl` | `~atl en>fr` | Sets your source and target language to be used with `~at`. Specify no arguments to remove previously set value. `~placelist` | Shows the list of available tags for the `~place` command. | `~placelist`
`~translangs` | Lists the valid languages for translation. | `~translangs` `~place` | Shows a placeholder image of a given tag. Use `~placelist` to see all available tags. You can specify the width and height of the image as the last two optional arguments. | `~place Cage` or `~place steven 500 400`
`~xkcd` | Shows a XKCD comic. No arguments will retrieve random one. Number argument will retrieve a specific comic, and "latest" will get the latest one. | `~xkcd` or `~xkcd 1400` or `~xkcd latest` `~overwatch` `~ow` | Show's basic stats on a player (competitive rank, playtime, level etc) Region codes are: `eu` `us` `cn` `kr` | `~ow us Battletag#1337` or `~overwatch eu Battletag#2016`
`~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`
`~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`
`~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`
`~weather` `~we` | Shows weather data for a specified city. You can also specify a country after a comma. | `~we Moscow, RU`
`~youtube` `~yt` | Searches youtubes and shows the first result | `~yt query`
`~imdb` `~omdb` | Queries omdb 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`
`~image` `~img` | Pulls the first image found using a search parameter. Use ~rimg for different results. | `~img cute kitten`
`~randomimage` `~rimg` | Pulls a random image using a search parameter. | `~rimg cute kitten`
`~lmgtfy` | Google something for an idiot. | `~lmgtfy query`
`~shorten` | Attempts to shorten an URL, if it fails, returns the input URL. | `~shorten https://google.com`
`~google` `~g` | Get a google search link for some terms. | `~google query`
`~magicthegathering` `~mtg` | Searches for a Magic The Gathering card. | `~magicthegathering about face` or `~mtg about face`
`~hearthstone` `~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | `~hs Ysera`
`~yodify` `~yoda` | Translates your normal sentences into Yoda styled sentences! | ~yodify I was once an adventurer like you` or `~yoda my feelings hurt`
`~urbandict` `~ud` | Searches Urban Dictionary for a word. | `~ud Pineapple`
`~define` `~def` | Finds a definition of a word. | `~def heresy`
`~#` | 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 preferred. (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"`
`~avatar` `~av` | Shows a mentioned person's avatar. | `~av "@SomeGuy"`
`~wikia` | Gives you back a wikia link | `~wikia mtg Vigilance` or `~wikia mlp Dashy`
`~minecraftping` `~mcping` | Pings a minecraft server. | `~mcping 127.0.0.1:25565`
`~minecraftquery` `~mcq` | Finds information about a minecraft server. | `~mcq server:ip`
`~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"`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
### Utility ### Utility
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`.togethertube` `.totube` | Creates a new room on <https://togethertube.com> and shows the link in the chat. | `.totube` `.convertlist` | List of the convertible dimensions and currencies. | `.convertlist`
`.whosplaying` `.whpl` | Shows a list of users who are playing the specified game. | `.whpl Overwatch` `.convert` | Convert quantities. Use `.convertlist` to see supported dimensions and currencies. | `.convert m km 1000`
`.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` `.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 1m Start now!`
`.checkmyperms` | Checks your user-specific permissions on this channel. | `.checkmyperms` `.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. **Bot Owner only.** | `.remindtemplate %user%, do %message%!`
`.userid` `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy"` `.listquotes` `.liqu` | `.liqu` or `.liqu 3` | Lists all quotes on the server ordered alphabetically. 15 Per page.
`.channelid` `.cid` | Shows current channel ID. | `.cid` `...` | Shows a random quote with a specified name. | `... abc`
`.serverid` `.sid` | Shows current server ID. | `.sid` `..` | Adds a new quote with the specified name and message. | `.. sayhi Hi`
`.roles` | List roles on this server or a roles of a specific user if specified. Paginated. 20 roles per page. | `.roles 2` or `.roles @Someone` `.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`
`.channeltopic` `.ct` | Sends current channel's topic as a message. | `.ct` `.delallq` `.daq` | Deletes all quotes on a specified keyword. **Requires Administrator server permission.** | `.delallq kek`
`.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 SPECIAL emojis`
`.listservers` | Lists servers the bot is on with some basic info. 15 per page. **Bot Owner only.** | `.listservers 3`
`.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` `.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` `.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` `.userinfo` `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | `.uinfo @SomeUser`
`.activity` | Checks for spammers. **Bot Owner only.** | `.activity` `.calculate` `.calc` | Evaluate a mathematical expression. | `.calc 1+1`
`.listquotes` `.liqu` | `.liqu` or `.liqu 3` | Lists all quotes on the server ordered alphabetically. 15 Per page. `.calcops` | Shows all available operations in .calc command | `.calcops`
`...` | Shows a random quote with a specified name. | `... abc` `.rotaterolecolor` `.rrc` | Rotates a roles color on an interval with a list of supplied colors. First argument is interval in seconds (Minimum 60). Second argument is a role, followed by a space-separated list of colors in hex. Provide a rolename with a 0 interval to disable. **Bot Owner only.** | `.rrc 60 MyLsdRole #ff0000 #00ff00 #0000ff` or `.rrc 0 MyLsdRole`
`..` | Adds a new quote with the specified name and message. | `.. sayhi Hi` `.togethertube` `.totube` | Creates a new room on <https://togethertube.com> and shows the link in the chat. | `.totube`
`.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` `.whosplaying` `.whpl` | Shows a list of users who are playing the specified game. | `.whpl Overwatch`
`.delallq` `.daq` | Deletes all quotes on a specified keyword. **Requires Administrator server permission.** | `.delallq kek` `.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`
`.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 1m Start now!` `.checkmyperms` | Checks your user-specific permissions on this channel. | `.checkmyperms`
`.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. **Bot Owner only.** | `.remindtemplate %user%, do %message%!` `.userid` `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy"`
`.convertlist` | List of the convertible dimensions and currencies. | `.convertlist` `.channelid` `.cid` | Shows current channel ID. | `.cid`
`.convert` | Convert quantities. Use `.convertlist` to see supported dimensions and currencies. | `.convert m km 1000` `.serverid` `.sid` | Shows current server ID. | `.sid`
`.roles` | List roles on this server or a roles of a specific user if specified. Paginated. 20 roles per page. | `.roles 2` or `.roles @Someone`
`.channeltopic` `.ct` | Sends current channel's topic as a message. | `.ct`
`.createinvite` `.crinv` | Creates a new invite which has infinite max uses and never expires. **Requires CreateInstantInvite channel permission.** | `.crinv`
`.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 SPECIAL emojis`
`.listservers` | Lists servers the bot is on with some basic info. 15 per page. **Bot Owner only.** | `.listservers 3`
`.activity` | Checks for spammers. **Bot Owner only.** | `.activity`

View File

@ -40,7 +40,7 @@ There are currently three different placeholders which we will look at, with mor
| Placeholder | Description | Example Usage | Usage | | 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!"| |`%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!"| |`%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. You can also specify a custom range (%rng1-100%) even with negative numbers: `%rng-9--1%` (from -9 to -1) . |`.acr "Random number" %rng%`|Input: "Random number" Output: "2"| |`%rng%`|The `%rng%` placeholder generates a random number between 0 and 10. You can also specify a custom range (%rng1-100%) even with negative numbers: `%rng-9--1%` (from -9 to -1) . |`.acr "Random number" %rng%`|Input: "Random number" Output: "2"|
|`%rnduser%`|The `%rnduser%` placeholder mentions a random user from the server. |`.acr "Random user" %rnduser%`|Input: "Random number" Output: @SomeUser| |`%rnduser%`|The `%rnduser%` placeholder mentions a random user from the server. |`.acr "Random user" %rnduser%`|Input: "Random number" Output: @SomeUser|

View File

@ -43,7 +43,7 @@ Commonly Asked Questions
--------------- ---------------
###How do I create a music DJ? ###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: To allow users to only see the current song and have a DJ role for queuing follow these steps:
1. `;sm Music disable` 1. `;sm Music disable`
@ -53,17 +53,13 @@ To allow users to only see the current song and have a DJ role for queuing follo
* Enables the "nowplaying" command for everyone * Enables the "nowplaying" command for everyone
3. `;sc !!getlink enable` 3. `;sc !!listqueue enable`
* Enables the "getlink" command for everyone
4. `;sc !!listqueue enable`
* Enables the "listqueue" command for everyone * Enables the "listqueue" command for everyone
5. `;rm Music enable DJ` 4. `;rm Music enable DJ`
* Enables all the music commands only for the DJ role * Enables all music commands only for the DJ role
###How do I create a NSFW channel? ###How do I create a NSFW channel?

View File

@ -1,34 +1,58 @@
# NadekoBot a Discord bot # NadekoBot a Discord bot
Nadeko is written in C# and Discord.net for more information visit https://github.com/Kwoth/NadekoBot Nadeko is written in C# and Discord.net for more information visit <https://github.com/Kwoth/NadekoBot>
## Install Docker ## Install Docker
Follow the respective guide for your operating system found here https://docs.docker.com/engine/installation/ Follow the respective guide for your operating system found here [Docker Engine Install Guide](https://docs.docker.com/engine/installation/)
## Nadeko Setup Guide ## Nadeko Setup Guide
For this guide we will be using the folder /nadeko as our config root folder. For this guide we will be using the folder /nadeko as our config root folder.
``` ```bash
docker create --name=nadeko -v /nadeko/data:/opt/NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.0/data -v /nadeko/credentials.json:/opt/NadekoBot/src/NadekoBot/credentials.json kwoth/nadeko:dev docker create --name=nadeko -v /nadeko/data:/opt/NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.0/data -v /nadeko/credentials.json:/opt/NadekoBot/src/NadekoBot/credentials.json uirel/nadeko
``` ```
-If you are coming from a previous version of nadeko (the old docker) make sure your crednetials.json has been copied into this directory and is the only thing in this folder. -If you are coming from a previous version of nadeko (the old docker) make sure your crednetials.json has been copied into this directory and is the only thing in this folder.
-If you are making a fresh install, create your credentials.json from the following guide and palce it in the /nadeko folder -If you are making a fresh install, create your credentials.json from the following guide and palce it in the /nadeko folder [Nadeko JSON Guide](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/)
http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/
Next start the docker up with Next start the docker up with
```docker start nadeko; docker logs -f nadeko``` `docker start nadeko; docker logs -f nadeko`
The docker will start and the log file will start scrolling past. Depending on hardware the bot start can take up to 5 minutes on a small DigitalOcean droplet. The docker will start and the log file will start scrolling past. Depending on hardware the bot start can take up to 5 minutes on a small DigitalOcean droplet.
Once the log ends with "NadekoBot | Starting NadekoBot v1.0-rc2" the bot is ready and can be invited to your server. Ctrl+C at this point to stop viewing the logs. Once the log ends with "NadekoBot | Starting NadekoBot v1.0-rc2" the bot is ready and can be invited to your server. Ctrl+C at this point to stop viewing the logs.
After a few moments you should be able to invite Nadeko to your server. If you cannot check the log file for errors After a few moments you should be able to invite Nadeko to your server. If you cannot check the log file for errors
## Updates / Monitoring ## Monitoring
* Upgrade to the latest version of Nadeko simply `docker restart nadeko`.
* Monitor the logs of the container in realtime `docker logs -f nadeko`. * Monitor the logs of the container in realtime `docker logs -f nadeko`.
## Updates
# Manual
Updates are handled by pulling the new layer of the Docker Container which contains a pre compiled update to Nadeko.
The following commands are required for the default options
`docker pull uirel/nadeko:latest`
`docker stop nadeko; docker rm nadeko`
`docker create --name=nadeko -v /nadeko/data:/opt/NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.0/data -v /nadeko/credentials.json:/opt/NadekoBot/src/NadekoBot/credentials.json uirel/nadeko`
`docker start nadeko`
# Automatic Updates
Automatic update are now handled by watchertower [WatchTower GitHub](https://github.com/CenturyLinkLabs/watchtower)
To setup watchtower to keep Nadeko up-to-date for you with the default settings use the following command
```bash
docker run -d --name watchtower -v /var/run/docker.sock:/var/run/docker.sock centurylink/watchtower --cleanup nadeko
```
This will check for updates to the docker every 5 minutes and update immediately. Alternatively using the `--interval X` command to change the interval, where X is the amount of time in seconds to wait. eg 21600 for 6 hours.
If you have any issues with the docker setup, please ask in #help but indicate you are using the docker. If you have any issues with the docker setup, please ask in #help but indicate you are using the docker.
For information about configuring your bot or its functionality, please check the http://nadekobot.readthedocs.io/en/latest guides. For information about configuring your bot or its functionality, please check the <http://nadekobot.readthedocs.io/en/latest> guides.

View File

@ -11,6 +11,9 @@ NadekoBot is an open source project, and it can be found on our [GitHub][GitHub]
Here you can read current [Issues][Issues]. Here you can read current [Issues][Issues].
If you want to contribute, be sure to PR on the **[dev][dev]** branch. If you want to contribute, be sure to PR on the **[dev][dev]** branch.
**NadekoBot 1.1 release currently does not support x86/32bit architecture.**
##Content ##Content
- [About](about.md) - [About](about.md)
- Guides - Guides
@ -34,14 +37,3 @@ If you want to contribute, be sure to PR on the **[dev][dev]** branch.
[GitHub]: https://github.com/Kwoth/NadekoBot [GitHub]: https://github.com/Kwoth/NadekoBot
[Issues]: https://github.com/Kwoth/NadekoBot/issues [Issues]: https://github.com/Kwoth/NadekoBot/issues
[dev]: https://github.com/Kwoth/NadekoBot/tree/dev [dev]: https://github.com/Kwoth/NadekoBot/tree/dev
[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

@ -0,0 +1,24 @@
using Discord.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static NadekoBot.Modules.Permissions.Permissions;
namespace NadekoBot.DataStructures
{
public struct ExecuteCommandResult
{
public readonly CommandInfo CommandInfo;
public readonly PermissionCache PermissionCache;
public readonly IResult Result;
public ExecuteCommandResult(CommandInfo commandInfo, PermissionCache cache, IResult result)
{
this.CommandInfo = commandInfo;
this.PermissionCache = cache;
this.Result = result;
}
}
}

View File

@ -0,0 +1,834 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations
{
[DbContext(typeof(NadekoContext))]
[Migration("20170110111159_repeater-drop")]
partial class repeaterdrop
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752");
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<ulong>("ItemId");
b.Property<int>("Type");
b.HasKey("Id");
b.HasIndex("BotConfigId");
b.ToTable("BlacklistItem");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("BufferSize");
b.Property<float>("CurrencyGenerationChance");
b.Property<int>("CurrencyGenerationCooldown");
b.Property<string>("CurrencyName");
b.Property<string>("CurrencyPluralName");
b.Property<string>("CurrencySign");
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");
b.HasKey("Id");
b.ToTable("BotConfig");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("BaseDestroyed");
b.Property<string>("CallUser");
b.Property<int>("ClashWarId");
b.Property<int?>("SequenceNumber");
b.Property<int>("Stars");
b.Property<DateTime>("TimeAdded");
b.HasKey("Id");
b.HasIndex("ClashWarId");
b.ToTable("ClashCallers");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<string>("EnemyClan");
b.Property<ulong>("GuildId");
b.Property<int>("Size");
b.Property<DateTime>("StartedAt");
b.Property<int>("WarState");
b.HasKey("Id");
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")
.ValueGeneratedOnAdd();
b.Property<string>("InternalTrigger");
b.Property<decimal>("Modifier");
b.Property<string>("UnitType");
b.HasKey("Id");
b.ToTable("ConversionUnits");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("Amount");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("Currency");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("Amount");
b.Property<string>("Reason");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.ToTable("CurrencyTransactions");
});
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")
.ValueGeneratedOnAdd();
b.Property<int>("Amount");
b.Property<string>("Name");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("Donators");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("Text");
b.HasKey("Id");
b.HasIndex("BotConfigId");
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")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("GuildId");
b.Property<int>("Type");
b.Property<string>("Username");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
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")
.ValueGeneratedOnAdd();
b.Property<ulong>("AutoAssignRoleId");
b.Property<bool>("AutoDeleteByeMessages");
b.Property<int>("AutoDeleteByeMessagesTimer");
b.Property<bool>("AutoDeleteGreetMessages");
b.Property<int>("AutoDeleteGreetMessagesTimer");
b.Property<bool>("AutoDeleteSelfAssignedRoleMessages");
b.Property<ulong>("ByeMessageChannelId");
b.Property<string>("ChannelByeMessageText");
b.Property<string>("ChannelGreetMessageText");
b.Property<bool>("CleverbotEnabled");
b.Property<float>("DefaultMusicVolume");
b.Property<bool>("DeleteMessageOnCommand");
b.Property<string>("DmGreetMessageText");
b.Property<bool>("ExclusiveSelfAssignedRoles");
b.Property<bool>("FilterInvites");
b.Property<bool>("FilterWords");
b.Property<ulong>("GreetMessageChannelId");
b.Property<ulong>("GuildId");
b.Property<int?>("LogSettingId");
b.Property<string>("MuteRoleName");
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");
b.HasIndex("GuildId")
.IsUnique();
b.HasIndex("LogSettingId");
b.HasIndex("RootPermissionId");
b.ToTable("GuildConfigs");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("LogSettingId");
b.HasKey("Id");
b.HasIndex("LogSettingId");
b.ToTable("IgnoredLogChannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("LogSettingId");
b.HasKey("Id");
b.HasIndex("LogSettingId");
b.ToTable("IgnoredVoicePresenceCHannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("ChannelCreated");
b.Property<ulong?>("ChannelCreatedId");
b.Property<bool>("ChannelDestroyed");
b.Property<ulong?>("ChannelDestroyedId");
b.Property<ulong>("ChannelId");
b.Property<bool>("ChannelUpdated");
b.Property<ulong?>("ChannelUpdatedId");
b.Property<bool>("IsLogging");
b.Property<ulong?>("LogOtherId");
b.Property<bool>("LogUserPresence");
b.Property<ulong?>("LogUserPresenceId");
b.Property<bool>("LogVoicePresence");
b.Property<ulong?>("LogVoicePresenceId");
b.Property<ulong?>("LogVoicePresenceTTSId");
b.Property<bool>("MessageDeleted");
b.Property<ulong?>("MessageDeletedId");
b.Property<bool>("MessageUpdated");
b.Property<ulong?>("MessageUpdatedId");
b.Property<bool>("UserBanned");
b.Property<ulong?>("UserBannedId");
b.Property<bool>("UserJoined");
b.Property<ulong?>("UserJoinedId");
b.Property<bool>("UserLeft");
b.Property<ulong?>("UserLeftId");
b.Property<ulong?>("UserMutedId");
b.Property<ulong>("UserPresenceChannelId");
b.Property<bool>("UserUnbanned");
b.Property<ulong?>("UserUnbannedId");
b.Property<bool>("UserUpdated");
b.Property<ulong?>("UserUpdatedId");
b.Property<ulong>("VoicePresenceChannelId");
b.HasKey("Id");
b.ToTable("LogSettings");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("ModuleName");
b.Property<string>("Prefix");
b.HasKey("Id");
b.HasIndex("BotConfigId");
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.MutedUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("GuildConfigId");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("MutedUserId");
});
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")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("Status");
b.HasKey("Id");
b.HasIndex("BotConfigId");
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")
.ValueGeneratedOnAdd();
b.Property<ulong>("AuthorId");
b.Property<string>("AuthorName")
.IsRequired();
b.Property<ulong>("GuildId");
b.Property<string>("Keyword")
.IsRequired();
b.Property<string>("Text")
.IsRequired();
b.HasKey("Id");
b.ToTable("Quotes");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("Icon");
b.Property<string>("Name");
b.HasKey("Id");
b.HasIndex("BotConfigId");
b.ToTable("RaceAnimals");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<bool>("IsPrivate");
b.Property<string>("Message");
b.Property<ulong>("ServerId");
b.Property<ulong>("UserId");
b.Property<DateTime>("When");
b.HasKey("Id");
b.ToTable("Reminders");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("GuildId");
b.Property<ulong>("RoleId");
b.HasKey("Id");
b.HasIndex("GuildId", "RoleId")
.IsUnique();
b.ToTable("SelfAssignableRoles");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("UserId");
b.Property<string>("type");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("PokeGame");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("Blacklist")
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar")
.WithMany("Bases")
.HasForeignKey("ClashWarId")
.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")
.WithMany("EightBallResponses")
.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")
.WithMany("FollowedStreams")
.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 =>
{
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
.WithMany("IgnoredChannels")
.HasForeignKey("LogSettingId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
.WithMany("IgnoredVoicePresenceChannelIds")
.HasForeignKey("LogSettingId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("ModulePrefixes")
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("MutedUsers")
.HasForeignKey("GuildConfigId");
});
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 =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("RotatingStatusMessages")
.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")
.WithMany("RaceAnimals")
.HasForeignKey("BotConfigId");
});
}
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class repeaterdrop : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Repeaters");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Repeaters",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ChannelId = table.Column<ulong>(nullable: false),
GuildId = table.Column<ulong>(nullable: false),
Interval = table.Column<TimeSpan>(nullable: false),
Message = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Repeaters", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Repeaters_ChannelId",
table: "Repeaters",
column: "ChannelId",
unique: true);
}
}
}

View File

@ -0,0 +1,863 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations
{
[DbContext(typeof(NadekoContext))]
[Migration("20170110111302_repeater-new")]
partial class repeaternew
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752");
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<ulong>("ItemId");
b.Property<int>("Type");
b.HasKey("Id");
b.HasIndex("BotConfigId");
b.ToTable("BlacklistItem");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("BufferSize");
b.Property<float>("CurrencyGenerationChance");
b.Property<int>("CurrencyGenerationCooldown");
b.Property<string>("CurrencyName");
b.Property<string>("CurrencyPluralName");
b.Property<string>("CurrencySign");
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");
b.HasKey("Id");
b.ToTable("BotConfig");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("BaseDestroyed");
b.Property<string>("CallUser");
b.Property<int>("ClashWarId");
b.Property<int?>("SequenceNumber");
b.Property<int>("Stars");
b.Property<DateTime>("TimeAdded");
b.HasKey("Id");
b.HasIndex("ClashWarId");
b.ToTable("ClashCallers");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<string>("EnemyClan");
b.Property<ulong>("GuildId");
b.Property<int>("Size");
b.Property<DateTime>("StartedAt");
b.Property<int>("WarState");
b.HasKey("Id");
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")
.ValueGeneratedOnAdd();
b.Property<string>("InternalTrigger");
b.Property<decimal>("Modifier");
b.Property<string>("UnitType");
b.HasKey("Id");
b.ToTable("ConversionUnits");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("Amount");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("Currency");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("Amount");
b.Property<string>("Reason");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.ToTable("CurrencyTransactions");
});
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")
.ValueGeneratedOnAdd();
b.Property<int>("Amount");
b.Property<string>("Name");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("Donators");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("Text");
b.HasKey("Id");
b.HasIndex("BotConfigId");
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")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("GuildId");
b.Property<int>("Type");
b.Property<string>("Username");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
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")
.ValueGeneratedOnAdd();
b.Property<ulong>("AutoAssignRoleId");
b.Property<bool>("AutoDeleteByeMessages");
b.Property<int>("AutoDeleteByeMessagesTimer");
b.Property<bool>("AutoDeleteGreetMessages");
b.Property<int>("AutoDeleteGreetMessagesTimer");
b.Property<bool>("AutoDeleteSelfAssignedRoleMessages");
b.Property<ulong>("ByeMessageChannelId");
b.Property<string>("ChannelByeMessageText");
b.Property<string>("ChannelGreetMessageText");
b.Property<bool>("CleverbotEnabled");
b.Property<float>("DefaultMusicVolume");
b.Property<bool>("DeleteMessageOnCommand");
b.Property<string>("DmGreetMessageText");
b.Property<bool>("ExclusiveSelfAssignedRoles");
b.Property<bool>("FilterInvites");
b.Property<bool>("FilterWords");
b.Property<ulong>("GreetMessageChannelId");
b.Property<ulong>("GuildId");
b.Property<int?>("LogSettingId");
b.Property<string>("MuteRoleName");
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");
b.HasIndex("GuildId")
.IsUnique();
b.HasIndex("LogSettingId");
b.HasIndex("RootPermissionId");
b.ToTable("GuildConfigs");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("GuildId");
b.Property<TimeSpan>("Interval");
b.Property<string>("Message");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("GuildRepeater");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("LogSettingId");
b.HasKey("Id");
b.HasIndex("LogSettingId");
b.ToTable("IgnoredLogChannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("LogSettingId");
b.HasKey("Id");
b.HasIndex("LogSettingId");
b.ToTable("IgnoredVoicePresenceCHannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("ChannelCreated");
b.Property<ulong?>("ChannelCreatedId");
b.Property<bool>("ChannelDestroyed");
b.Property<ulong?>("ChannelDestroyedId");
b.Property<ulong>("ChannelId");
b.Property<bool>("ChannelUpdated");
b.Property<ulong?>("ChannelUpdatedId");
b.Property<bool>("IsLogging");
b.Property<ulong?>("LogOtherId");
b.Property<bool>("LogUserPresence");
b.Property<ulong?>("LogUserPresenceId");
b.Property<bool>("LogVoicePresence");
b.Property<ulong?>("LogVoicePresenceId");
b.Property<ulong?>("LogVoicePresenceTTSId");
b.Property<bool>("MessageDeleted");
b.Property<ulong?>("MessageDeletedId");
b.Property<bool>("MessageUpdated");
b.Property<ulong?>("MessageUpdatedId");
b.Property<bool>("UserBanned");
b.Property<ulong?>("UserBannedId");
b.Property<bool>("UserJoined");
b.Property<ulong?>("UserJoinedId");
b.Property<bool>("UserLeft");
b.Property<ulong?>("UserLeftId");
b.Property<ulong?>("UserMutedId");
b.Property<ulong>("UserPresenceChannelId");
b.Property<bool>("UserUnbanned");
b.Property<ulong?>("UserUnbannedId");
b.Property<bool>("UserUpdated");
b.Property<ulong?>("UserUpdatedId");
b.Property<ulong>("VoicePresenceChannelId");
b.HasKey("Id");
b.ToTable("LogSettings");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("ModuleName");
b.Property<string>("Prefix");
b.HasKey("Id");
b.HasIndex("BotConfigId");
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.MutedUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("GuildConfigId");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("MutedUserId");
});
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")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("Status");
b.HasKey("Id");
b.HasIndex("BotConfigId");
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")
.ValueGeneratedOnAdd();
b.Property<ulong>("AuthorId");
b.Property<string>("AuthorName")
.IsRequired();
b.Property<ulong>("GuildId");
b.Property<string>("Keyword")
.IsRequired();
b.Property<string>("Text")
.IsRequired();
b.HasKey("Id");
b.ToTable("Quotes");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("Icon");
b.Property<string>("Name");
b.HasKey("Id");
b.HasIndex("BotConfigId");
b.ToTable("RaceAnimals");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<bool>("IsPrivate");
b.Property<string>("Message");
b.Property<ulong>("ServerId");
b.Property<ulong>("UserId");
b.Property<DateTime>("When");
b.HasKey("Id");
b.ToTable("Reminders");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("GuildId");
b.Property<ulong>("RoleId");
b.HasKey("Id");
b.HasIndex("GuildId", "RoleId")
.IsUnique();
b.ToTable("SelfAssignableRoles");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("UserId");
b.Property<string>("type");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("PokeGame");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("Blacklist")
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar")
.WithMany("Bases")
.HasForeignKey("ClashWarId")
.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")
.WithMany("EightBallResponses")
.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")
.WithMany("FollowedStreams")
.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.GuildRepeater", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("GuildRepeaters")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
.WithMany("IgnoredChannels")
.HasForeignKey("LogSettingId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
.WithMany("IgnoredVoicePresenceChannelIds")
.HasForeignKey("LogSettingId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("ModulePrefixes")
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("MutedUsers")
.HasForeignKey("GuildConfigId");
});
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 =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("RotatingStatusMessages")
.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")
.WithMany("RaceAnimals")
.HasForeignKey("BotConfigId");
});
}
}
}

View File

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class repeaternew : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "GuildRepeater",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ChannelId = table.Column<ulong>(nullable: false),
GuildConfigId = table.Column<int>(nullable: true),
GuildId = table.Column<ulong>(nullable: false),
Interval = table.Column<TimeSpan>(nullable: false),
Message = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_GuildRepeater", x => x.Id);
table.ForeignKey(
name: "FK_GuildRepeater_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_GuildRepeater_GuildConfigId",
table: "GuildRepeater",
column: "GuildConfigId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GuildRepeater");
}
}
}

View File

@ -0,0 +1,942 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations
{
[DbContext(typeof(NadekoContext))]
[Migration("20170110180534_protection")]
partial class protection
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752");
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Action");
b.Property<int>("GuildConfigId");
b.Property<int>("Seconds");
b.Property<int>("UserThreshold");
b.HasKey("Id");
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiRaidSetting");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("AntiSpamSettingId");
b.Property<ulong>("ChannelId");
b.HasKey("Id");
b.HasIndex("AntiSpamSettingId");
b.ToTable("AntiSpamIgnore");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Action");
b.Property<int>("GuildConfigId");
b.Property<int>("MessageThreshold");
b.HasKey("Id");
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiSpamSetting");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<ulong>("ItemId");
b.Property<int>("Type");
b.HasKey("Id");
b.HasIndex("BotConfigId");
b.ToTable("BlacklistItem");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("BufferSize");
b.Property<float>("CurrencyGenerationChance");
b.Property<int>("CurrencyGenerationCooldown");
b.Property<string>("CurrencyName");
b.Property<string>("CurrencyPluralName");
b.Property<string>("CurrencySign");
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");
b.HasKey("Id");
b.ToTable("BotConfig");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("BaseDestroyed");
b.Property<string>("CallUser");
b.Property<int>("ClashWarId");
b.Property<int?>("SequenceNumber");
b.Property<int>("Stars");
b.Property<DateTime>("TimeAdded");
b.HasKey("Id");
b.HasIndex("ClashWarId");
b.ToTable("ClashCallers");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<string>("EnemyClan");
b.Property<ulong>("GuildId");
b.Property<int>("Size");
b.Property<DateTime>("StartedAt");
b.Property<int>("WarState");
b.HasKey("Id");
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")
.ValueGeneratedOnAdd();
b.Property<string>("InternalTrigger");
b.Property<decimal>("Modifier");
b.Property<string>("UnitType");
b.HasKey("Id");
b.ToTable("ConversionUnits");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("Amount");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("Currency");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("Amount");
b.Property<string>("Reason");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.ToTable("CurrencyTransactions");
});
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")
.ValueGeneratedOnAdd();
b.Property<int>("Amount");
b.Property<string>("Name");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("Donators");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("Text");
b.HasKey("Id");
b.HasIndex("BotConfigId");
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")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("GuildId");
b.Property<int>("Type");
b.Property<string>("Username");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
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")
.ValueGeneratedOnAdd();
b.Property<ulong>("AutoAssignRoleId");
b.Property<bool>("AutoDeleteByeMessages");
b.Property<int>("AutoDeleteByeMessagesTimer");
b.Property<bool>("AutoDeleteGreetMessages");
b.Property<int>("AutoDeleteGreetMessagesTimer");
b.Property<bool>("AutoDeleteSelfAssignedRoleMessages");
b.Property<ulong>("ByeMessageChannelId");
b.Property<string>("ChannelByeMessageText");
b.Property<string>("ChannelGreetMessageText");
b.Property<bool>("CleverbotEnabled");
b.Property<float>("DefaultMusicVolume");
b.Property<bool>("DeleteMessageOnCommand");
b.Property<string>("DmGreetMessageText");
b.Property<bool>("ExclusiveSelfAssignedRoles");
b.Property<bool>("FilterInvites");
b.Property<bool>("FilterWords");
b.Property<ulong>("GreetMessageChannelId");
b.Property<ulong>("GuildId");
b.Property<int?>("LogSettingId");
b.Property<string>("MuteRoleName");
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");
b.HasIndex("GuildId")
.IsUnique();
b.HasIndex("LogSettingId");
b.HasIndex("RootPermissionId");
b.ToTable("GuildConfigs");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("GuildId");
b.Property<TimeSpan>("Interval");
b.Property<string>("Message");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("GuildRepeater");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("LogSettingId");
b.HasKey("Id");
b.HasIndex("LogSettingId");
b.ToTable("IgnoredLogChannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("LogSettingId");
b.HasKey("Id");
b.HasIndex("LogSettingId");
b.ToTable("IgnoredVoicePresenceCHannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("ChannelCreated");
b.Property<ulong?>("ChannelCreatedId");
b.Property<bool>("ChannelDestroyed");
b.Property<ulong?>("ChannelDestroyedId");
b.Property<ulong>("ChannelId");
b.Property<bool>("ChannelUpdated");
b.Property<ulong?>("ChannelUpdatedId");
b.Property<bool>("IsLogging");
b.Property<ulong?>("LogOtherId");
b.Property<bool>("LogUserPresence");
b.Property<ulong?>("LogUserPresenceId");
b.Property<bool>("LogVoicePresence");
b.Property<ulong?>("LogVoicePresenceId");
b.Property<ulong?>("LogVoicePresenceTTSId");
b.Property<bool>("MessageDeleted");
b.Property<ulong?>("MessageDeletedId");
b.Property<bool>("MessageUpdated");
b.Property<ulong?>("MessageUpdatedId");
b.Property<bool>("UserBanned");
b.Property<ulong?>("UserBannedId");
b.Property<bool>("UserJoined");
b.Property<ulong?>("UserJoinedId");
b.Property<bool>("UserLeft");
b.Property<ulong?>("UserLeftId");
b.Property<ulong?>("UserMutedId");
b.Property<ulong>("UserPresenceChannelId");
b.Property<bool>("UserUnbanned");
b.Property<ulong?>("UserUnbannedId");
b.Property<bool>("UserUpdated");
b.Property<ulong?>("UserUpdatedId");
b.Property<ulong>("VoicePresenceChannelId");
b.HasKey("Id");
b.ToTable("LogSettings");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("ModuleName");
b.Property<string>("Prefix");
b.HasKey("Id");
b.HasIndex("BotConfigId");
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.MutedUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("GuildConfigId");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("MutedUserId");
});
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")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("Status");
b.HasKey("Id");
b.HasIndex("BotConfigId");
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")
.ValueGeneratedOnAdd();
b.Property<ulong>("AuthorId");
b.Property<string>("AuthorName")
.IsRequired();
b.Property<ulong>("GuildId");
b.Property<string>("Keyword")
.IsRequired();
b.Property<string>("Text")
.IsRequired();
b.HasKey("Id");
b.ToTable("Quotes");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("Icon");
b.Property<string>("Name");
b.HasKey("Id");
b.HasIndex("BotConfigId");
b.ToTable("RaceAnimals");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<bool>("IsPrivate");
b.Property<string>("Message");
b.Property<ulong>("ServerId");
b.Property<ulong>("UserId");
b.Property<DateTime>("When");
b.HasKey("Id");
b.ToTable("Reminders");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("GuildId");
b.Property<ulong>("RoleId");
b.HasKey("Id");
b.HasIndex("GuildId", "RoleId")
.IsUnique();
b.ToTable("SelfAssignableRoles");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("UserId");
b.Property<string>("type");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("PokeGame");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
.WithOne("AntiRaidSetting")
.HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting")
.WithMany("IgnoredChannels")
.HasForeignKey("AntiSpamSettingId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
.WithOne("AntiSpamSetting")
.HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("Blacklist")
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar")
.WithMany("Bases")
.HasForeignKey("ClashWarId")
.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")
.WithMany("EightBallResponses")
.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")
.WithMany("FollowedStreams")
.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.GuildRepeater", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("GuildRepeaters")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
.WithMany("IgnoredChannels")
.HasForeignKey("LogSettingId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")
.WithMany("IgnoredVoicePresenceChannelIds")
.HasForeignKey("LogSettingId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("ModulePrefixes")
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("MutedUsers")
.HasForeignKey("GuildConfigId");
});
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 =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("RotatingStatusMessages")
.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")
.WithMany("RaceAnimals")
.HasForeignKey("BotConfigId");
});
}
}
}

View File

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class protection : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AntiRaidSetting",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Action = table.Column<int>(nullable: false),
GuildConfigId = table.Column<int>(nullable: false),
Seconds = table.Column<int>(nullable: false),
UserThreshold = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AntiRaidSetting", x => x.Id);
table.ForeignKey(
name: "FK_AntiRaidSetting_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "AntiSpamSetting",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Action = table.Column<int>(nullable: false),
GuildConfigId = table.Column<int>(nullable: false),
MessageThreshold = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AntiSpamSetting", x => x.Id);
table.ForeignKey(
name: "FK_AntiSpamSetting_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "AntiSpamIgnore",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
AntiSpamSettingId = table.Column<int>(nullable: true),
ChannelId = table.Column<ulong>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AntiSpamIgnore", x => x.Id);
table.ForeignKey(
name: "FK_AntiSpamIgnore_AntiSpamSetting_AntiSpamSettingId",
column: x => x.AntiSpamSettingId,
principalTable: "AntiSpamSetting",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_AntiRaidSetting_GuildConfigId",
table: "AntiRaidSetting",
column: "GuildConfigId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AntiSpamIgnore_AntiSpamSettingId",
table: "AntiSpamIgnore",
column: "AntiSpamSettingId");
migrationBuilder.CreateIndex(
name: "IX_AntiSpamSetting_GuildConfigId",
table: "AntiSpamSetting",
column: "GuildConfigId",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AntiRaidSetting");
migrationBuilder.DropTable(
name: "AntiSpamIgnore");
migrationBuilder.DropTable(
name: "AntiSpamSetting");
}
}
}

View File

@ -2,7 +2,10 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {
@ -14,6 +17,62 @@ namespace NadekoBot.Migrations
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752"); .HasAnnotation("ProductVersion", "1.1.0-rtm-22752");
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Action");
b.Property<int>("GuildConfigId");
b.Property<int>("Seconds");
b.Property<int>("UserThreshold");
b.HasKey("Id");
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiRaidSetting");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("AntiSpamSettingId");
b.Property<ulong>("ChannelId");
b.HasKey("Id");
b.HasIndex("AntiSpamSettingId");
b.ToTable("AntiSpamIgnore");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Action");
b.Property<int>("GuildConfigId");
b.Property<int>("MessageThreshold");
b.HasKey("Id");
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiSpamSetting");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -381,6 +440,28 @@ namespace NadekoBot.Migrations
b.ToTable("GuildConfigs"); b.ToTable("GuildConfigs");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("GuildId");
b.Property<TimeSpan>("Interval");
b.Property<string>("Message");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("GuildRepeater");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -663,27 +744,6 @@ namespace NadekoBot.Migrations
b.ToTable("Reminders"); b.ToTable("Reminders");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ChannelId");
b.Property<ulong>("GuildId");
b.Property<TimeSpan>("Interval");
b.Property<string>("Message");
b.HasKey("Id");
b.HasIndex("ChannelId")
.IsUnique();
b.ToTable("Repeaters");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -718,6 +778,29 @@ namespace NadekoBot.Migrations
b.ToTable("PokeGame"); b.ToTable("PokeGame");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
.WithOne("AntiRaidSetting")
.HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting")
.WithMany("IgnoredChannels")
.HasForeignKey("AntiSpamSettingId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
.WithOne("AntiSpamSetting")
.HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b =>
{ {
b.HasOne("NadekoBot.Services.Database.Models.BotConfig") b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
@ -790,6 +873,13 @@ namespace NadekoBot.Migrations
.HasForeignKey("RootPermissionId"); .HasForeignKey("RootPermissionId");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("GuildRepeaters")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b =>
{ {
b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting")

View File

@ -202,7 +202,7 @@ namespace NadekoBot.Modules.Administration
return; return;
} }
var roleName = args[0].ToUpperInvariant(); var roleName = args[0].ToUpperInvariant();
var role = Context.Guild.Roles.Where(r=>r.Name.ToUpperInvariant() == roleName).FirstOrDefault(); var role = Context.Guild.Roles.Where(r => r.Name.ToUpperInvariant() == roleName).FirstOrDefault();
if (role == null) if (role == null)
{ {
@ -236,18 +236,22 @@ namespace NadekoBot.Modules.Administration
{ {
msg = "❗No reason provided."; msg = "❗No reason provided.";
} }
if (Context.User.Id != user.Guild.OwnerId && ((IGuildUser)Context.User).GetRoles().Select(r=>r.Position).Max() >= ((IGuildUser)Context.User).GetRoles().Select(r => r.Position).Max()) if (Context.User.Id != user.Guild.OwnerId && (user.GetRoles().Select(r => r.Position).Max() >= ((IGuildUser)Context.User).GetRoles().Select(r => r.Position).Max()))
{ {
await Context.Channel.SendErrorAsync("⚠️ You can't use this command on users with a role higher or equal to yours in the role hierarchy.").ConfigureAwait(false); await Context.Channel.SendErrorAsync("⚠️ You can't use this command on users with a role higher or equal to yours in the role hierarchy.").ConfigureAwait(false);
return; return;
} }
try if (!string.IsNullOrWhiteSpace(msg))
{ {
await (await user.CreateDMChannelAsync()).SendErrorAsync($"⛔️ **You have been BANNED from `{Context.Guild.Name}` server.**\n" + try
$"⚖ *Reason:* {msg}").ConfigureAwait(false); {
await Task.Delay(2000).ConfigureAwait(false); await (await user.CreateDMChannelAsync()).SendErrorAsync($"⛔️ **You have been BANNED from `{Context.Guild.Name}` server.**\n" +
$"⚖ *Reason:* {msg}").ConfigureAwait(false);
await Task.Delay(2000).ConfigureAwait(false);
}
catch { }
} }
catch { }
try try
{ {
await Context.Guild.AddBanAsync(user, 7).ConfigureAwait(false); await Context.Guild.AddBanAsync(user, 7).ConfigureAwait(false);
@ -275,13 +279,18 @@ namespace NadekoBot.Modules.Administration
await Context.Channel.SendErrorAsync("⚠️ You can't use this command on users with a role higher or equal to yours in the role hierarchy."); await Context.Channel.SendErrorAsync("⚠️ You can't use this command on users with a role higher or equal to yours in the role hierarchy.");
return; return;
} }
try
if (!string.IsNullOrWhiteSpace(msg))
{ {
await user.SendErrorAsync($"☣ **You have been SOFT-BANNED from `{Context.Guild.Name}` server.**\n" + try
$"⚖ *Reason:* {msg}").ConfigureAwait(false); {
await Task.Delay(2000).ConfigureAwait(false); await user.SendErrorAsync($"☣ **You have been SOFT-BANNED from `{Context.Guild.Name}` server.**\n" +
$"⚖ *Reason:* {msg}").ConfigureAwait(false);
await Task.Delay(2000).ConfigureAwait(false);
}
catch { }
} }
catch { }
try try
{ {
await Context.Guild.AddBanAsync(user, 7).ConfigureAwait(false); await Context.Guild.AddBanAsync(user, 7).ConfigureAwait(false);
@ -451,6 +460,9 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(ChannelPermission.ManageMessages)] [RequireUserPermission(ChannelPermission.ManageMessages)]
public async Task Prune(int count) public async Task Prune(int count)
{ {
if (count < 1)
return;
count += 1;
await Context.Message.DeleteAsync().ConfigureAwait(false); await Context.Message.DeleteAsync().ConfigureAwait(false);
int limit = (count < 100) ? count : 100; int limit = (count < 100) ? count : 100;
var enumerable = (await Context.Channel.GetMessagesAsync(limit: limit).Flatten().ConfigureAwait(false)); var enumerable = (await Context.Channel.GetMessagesAsync(limit: limit).Flatten().ConfigureAwait(false));
@ -463,45 +475,17 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(ChannelPermission.ManageMessages)] [RequireUserPermission(ChannelPermission.ManageMessages)]
public async Task Prune(IGuildUser user, int count = 100) public async Task Prune(IGuildUser user, int count = 100)
{ {
if (count < 1)
return;
if (user.Id == Context.User.Id)
count += 1;
int limit = (count < 100) ? count : 100; int limit = (count < 100) ? count : 100;
var enumerable = (await Context.Channel.GetMessagesAsync(limit: limit).Flatten()).Where(m => m.Author == user); var enumerable = (await Context.Channel.GetMessagesAsync(limit: limit).Flatten()).Where(m => m.Author == user);
await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false); await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task SaveChat(int cnt)
{
ulong? lastmsgId = null;
var sb = new StringBuilder();
var msgs = new List<IMessage>(cnt);
while (cnt > 0)
{
var dlcnt = cnt < 100 ? cnt : 100;
IEnumerable<IMessage> dledMsgs;
if (lastmsgId == null)
dledMsgs = await Context.Channel.GetMessagesAsync(cnt).Flatten().ConfigureAwait(false);
else
dledMsgs = await Context.Channel.GetMessagesAsync(lastmsgId.Value, Direction.Before, dlcnt).Flatten().ConfigureAwait(false);
if (!dledMsgs.Any())
break;
msgs.AddRange(dledMsgs);
lastmsgId = msgs[msgs.Count - 1].Id;
cnt -= 100;
}
var title = $"Chatlog-{Context.Guild.Name}/#{Context.Channel.Name}-{DateTime.Now}.txt";
var grouping = msgs.GroupBy(x => $"{x.CreatedAt.Date:dd.MM.yyyy}")
.Select(g => new { date = g.Key, messages = g.OrderBy(x => x.CreatedAt).Select(s => $"【{s.Timestamp:HH:mm:ss}】{s.Author}:" + s.ToString()) });
await (Context.User as IGuildUser).SendFileAsync(
await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream().ConfigureAwait(false),
title, title).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.MentionEveryone)] [RequireUserPermission(GuildPermission.MentionEveryone)]
@ -511,7 +495,7 @@ title, title).ConfigureAwait(false);
foreach (var role in roles) foreach (var role in roles)
{ {
send += $"\n**{role.Name}**\n"; send += $"\n**{role.Name}**\n";
send += string.Join(", ", (await Context.Guild.GetUsersAsync()).Where(u => u.GetRoles().Contains(role)).Distinct().Select(u=>u.Mention)); send += string.Join(", ", (await Context.Guild.GetUsersAsync()).Where(u => u.GetRoles().Contains(role)).Distinct().Select(u => u.Mention));
} }
while (send.Length > 2000) while (send.Length > 2000)

View File

@ -1,272 +0,0 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
public enum PunishmentAction
{
Mute,
Kick,
Ban,
}
public enum ProtectionType
{
Raiding,
Spamming,
}
private class AntiRaidSetting
{
public int UserThreshold { get; set; }
public int Seconds { get; set; }
public PunishmentAction Action { get; set; }
public int UsersCount { get; set; }
public ConcurrentHashSet<IGuildUser> RaidUsers { get; set; } = new ConcurrentHashSet<IGuildUser>();
}
private class AntiSpamSetting
{
public PunishmentAction Action { get; set; }
public int MessageThreshold { get; set; } = 3;
public ConcurrentDictionary<ulong, UserSpamStats> UserStats { get; set; }
= new ConcurrentDictionary<ulong, UserSpamStats>();
}
private class UserSpamStats
{
public int Count { get; set; }
public string LastMessage { get; set; }
public UserSpamStats(string msg)
{
Count = 1;
LastMessage = msg.ToUpperInvariant();
}
public void ApplyNextMessage(string message)
{
var upperMsg = message.ToUpperInvariant();
if (upperMsg == LastMessage)
Count++;
else
{
LastMessage = upperMsg;
Count = 0;
}
}
}
[Group]
public class AntiRaidCommands : ModuleBase
{
private static ConcurrentDictionary<ulong, AntiRaidSetting> antiRaidGuilds =
new ConcurrentDictionary<ulong, AntiRaidSetting>();
// guildId | (userId|messages)
private static ConcurrentDictionary<ulong, AntiSpamSetting> antiSpamGuilds =
new ConcurrentDictionary<ulong, AntiSpamSetting>();
private static Logger _log { get; }
static AntiRaidCommands()
{
_log = LogManager.GetCurrentClassLogger();
NadekoBot.Client.MessageReceived += async (imsg) =>
{
try
{
var msg = imsg as IUserMessage;
if (msg == null || msg.Author.IsBot)
return;
var channel = msg.Channel as ITextChannel;
if (channel == null)
return;
AntiSpamSetting spamSettings;
if (!antiSpamGuilds.TryGetValue(channel.Guild.Id, out spamSettings))
return;
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, new UserSpamStats(msg.Content),
(id, old) => { old.ApplyNextMessage(msg.Content); return old; });
if (stats.Count >= spamSettings.MessageThreshold)
{
if (spamSettings.UserStats.TryRemove(msg.Author.Id, out stats))
{
await PunishUsers(spamSettings.Action, ProtectionType.Spamming, (IGuildUser)msg.Author)
.ConfigureAwait(false);
}
}
}
catch { }
};
NadekoBot.Client.UserJoined += async (usr) =>
{
try
{
if (usr.IsBot)
return;
AntiRaidSetting settings;
if (!antiRaidGuilds.TryGetValue(usr.Guild.Id, out settings))
return;
if (!settings.RaidUsers.Add(usr))
return;
++settings.UsersCount;
if (settings.UsersCount >= settings.UserThreshold)
{
var users = settings.RaidUsers.ToArray();
settings.RaidUsers.Clear();
await PunishUsers(settings.Action, ProtectionType.Raiding, users).ConfigureAwait(false);
}
await Task.Delay(1000 * settings.Seconds).ConfigureAwait(false);
settings.RaidUsers.TryRemove(usr);
--settings.UsersCount;
}
catch { }
};
}
private static async Task PunishUsers(PunishmentAction action, ProtectionType pt, params IGuildUser[] gus)
{
foreach (var gu in gus)
{
switch (action)
{
case PunishmentAction.Mute:
try
{
await MuteCommands.MuteUser(gu).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishement"); }
break;
case PunishmentAction.Kick:
try
{
await gu.Guild.AddBanAsync(gu, 7).ConfigureAwait(false);
try
{
await gu.Guild.RemoveBanAsync(gu).ConfigureAwait(false);
}
catch
{
await gu.Guild.RemoveBanAsync(gu).ConfigureAwait(false);
// try it twice, really don't want to ban user if
// only kick has been specified as the punishement
}
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); }
break;
case PunishmentAction.Ban:
try
{
await gu.Guild.AddBanAsync(gu, 7).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); }
break;
default:
break;
}
}
await LogCommands.TriggeredAntiProtection(gus, action, pt).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task AntiRaid(int userThreshold, int seconds, PunishmentAction action)
{
if (userThreshold < 2 || userThreshold > 30)
{
await Context.Channel.SendErrorAsync("❗User threshold must be between **2** and **30**.").ConfigureAwait(false);
return;
}
if (seconds < 2 || seconds > 300)
{
await Context.Channel.SendErrorAsync("❗Time must be between **2** and **300** seconds.").ConfigureAwait(false);
return;
}
try
{
await MuteCommands.GetMuteRole(Context.Guild).ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendConfirmAsync("⚠️ Failed creating a mute role. Give me ManageRoles permission" +
"or create 'nadeko-mute' role with disabled SendMessages and try again.")
.ConfigureAwait(false);
_log.Warn(ex);
return;
}
var setting = new AntiRaidSetting()
{
Action = action,
Seconds = seconds,
UserThreshold = userThreshold,
};
antiRaidGuilds.AddOrUpdate(Context.Guild.Id, setting, (id, old) => setting);
await Context.Channel.SendConfirmAsync($" {Context.User.Mention} If **{userThreshold}** or more users join within **{seconds}** seconds, I will **{action}** them.")
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task AntiSpam(int messageCount=3, PunishmentAction action = PunishmentAction.Mute)
{
if (messageCount < 2 || messageCount > 10)
return;
AntiSpamSetting throwaway;
if (antiSpamGuilds.TryRemove(Context.Guild.Id, out throwaway))
{
await Context.Channel.SendConfirmAsync("🆗 **Anti-Spam feature** has been **disabled** on this server.").ConfigureAwait(false);
}
else
{
try
{
await MuteCommands.GetMuteRole(Context.Guild).ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync("⚠️ Failed creating a mute role. Give me ManageRoles permission" +
"or create 'nadeko-mute' role with disabled SendMessages and try again.")
.ConfigureAwait(false);
_log.Warn(ex);
return;
}
if (antiSpamGuilds.TryAdd(Context.Guild.Id, new AntiSpamSetting()
{
Action = action,
MessageThreshold = messageCount,
}))
await Context.Channel.SendConfirmAsync("✅ **Anti-Spam feature** has been **enabled** on this server.").ConfigureAwait(false);
}
}
}
}
}

View File

@ -23,14 +23,17 @@ namespace NadekoBot.Modules.Administration
[Group] [Group]
public class LogCommands : ModuleBase public class LogCommands : ModuleBase
{ {
private const string clockEmojiUrl = "https://cdn.discordapp.com/attachments/155726317222887425/258309524966866945/clock.png";
private static ShardedDiscordClient _client { get; } private static ShardedDiscordClient _client { get; }
private static Logger _log { get; } private static Logger _log { get; }
private static string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】"; private static string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】";
private static string currentTime = $"{DateTime.Now:HH:mm:ss}";
public static ConcurrentDictionary<ulong, LogSetting> GuildLogSettings { get; } public static ConcurrentDictionary<ulong, LogSetting> GuildLogSettings { get; }
private static ConcurrentDictionary<ITextChannel, List<string>> UserPresenceUpdates { get; } = new ConcurrentDictionary<ITextChannel, List<string>>(); private static ConcurrentDictionary<ITextChannel, List<string>> PresenceUpdates { get; } = new ConcurrentDictionary<ITextChannel, List<string>>();
private static Timer timerReference { get; } private static Timer timerReference { get; }
private IGoogleApiService _google { get; } private IGoogleApiService _google { get; }
@ -50,20 +53,20 @@ namespace NadekoBot.Modules.Administration
{ {
try try
{ {
var keys = UserPresenceUpdates.Keys.ToList(); var keys = PresenceUpdates.Keys.ToList();
await Task.WhenAll(keys.Select(async key => await Task.WhenAll(keys.Select(async key =>
{ {
List<string> messages; List<string> messages;
if (UserPresenceUpdates.TryRemove(key, out messages)) if (PresenceUpdates.TryRemove(key, out messages))
try { await key.SendMessageAsync(string.Join(Environment.NewLine, messages)); } catch { } try { await key.SendConfirmAsync("Presence Updates", string.Join(Environment.NewLine, messages)); } catch { }
})); }));
} }
catch (Exception ex) catch (Exception ex)
{ {
_log.Warn(ex); _log.Warn(ex);
} }
}, null, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10)); }, null, TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15));
sw.Stop(); sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
@ -78,7 +81,10 @@ namespace NadekoBot.Modules.Administration
_client.UserPresenceUpdated += _client_UserPresenceUpdated; _client.UserPresenceUpdated += _client_UserPresenceUpdated;
_client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated; _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated;
_client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS; _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS;
_client.GuildUserUpdated += _client_GuildUserUpdated;
#if !GLOBAL_NADEKO
_client.UserUpdated += _client_UserUpdated; _client.UserUpdated += _client_UserUpdated;
#endif
_client.ChannelCreated += _client_ChannelCreated; _client.ChannelCreated += _client_ChannelCreated;
_client.ChannelDestroyed += _client_ChannelDestroyed; _client.ChannelDestroyed += _client_ChannelDestroyed;
@ -88,6 +94,74 @@ namespace NadekoBot.Modules.Administration
MuteCommands.UserUnmuted += MuteCommands_UserUnmuted; MuteCommands.UserUnmuted += MuteCommands_UserUnmuted;
} }
private static async void _client_UserUpdated(SocketUser before, SocketUser uAfter)
{
try
{
var after = uAfter as SocketGuildUser;
if (after == null)
return;
var g = after.Guild;
LogSetting logSetting;
if (!GuildLogSettings.TryGetValue(g.Id, out logSetting)
|| (logSetting.UserUpdatedId == null))
return;
ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(g, logSetting, LogType.UserUpdated)) == null)
return;
var embed = new EmbedBuilder();
if (before.Username != after.Username)
{
embed.WithTitle("👥 Username Changed")
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.AddField(fb => fb.WithName("Old Name").WithValue($"{before.Username}").WithIsInline(true))
.AddField(fb => fb.WithName("New Name").WithValue($"{after.Username}").WithIsInline(true))
.WithFooter(fb => fb.WithText(currentTime))
.WithOkColor();
}
else if (before.AvatarUrl != after.AvatarUrl)
{
embed.WithTitle("👥 Avatar Changed")
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithThumbnailUrl(before.AvatarUrl)
.WithImageUrl(after.AvatarUrl)
.WithFooter(fb => fb.WithText(currentTime))
.WithOkColor();
}
else
{
return;
}
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
//var guildsMemberOf = NadekoBot.Client.GetGuilds().Where(g => g.Users.Select(u => u.Id).Contains(before.Id)).ToList();
//foreach (var g in guildsMemberOf)
//{
// LogSetting logSetting;
// if (!GuildLogSettings.TryGetValue(g.Id, out logSetting)
// || (logSetting.UserUpdatedId == null))
// return;
// ITextChannel logChannel;
// if ((logChannel = await TryGetLogChannel(g, logSetting, LogType.UserUpdated)) == null)
// return;
// try { await logChannel.SendMessageAsync(str).ConfigureAwait(false); } catch { }
//}
}
catch
{ }
}
private static async void _client_UserVoiceStateUpdated_TTS(SocketUser iusr, SocketVoiceState before, SocketVoiceState after) private static async void _client_UserVoiceStateUpdated_TTS(SocketUser iusr, SocketVoiceState before, SocketVoiceState after)
{ {
try try
@ -111,7 +185,7 @@ namespace NadekoBot.Modules.Administration
if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.VoicePresenceTTS)) == null) if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.VoicePresenceTTS)) == null)
return; return;
string str = null; var str = "";
if (beforeVch?.Guild == afterVch?.Guild) if (beforeVch?.Guild == afterVch?.Guild)
{ {
str = $"{usr.Username} moved from {beforeVch.Name} to {afterVch.Name}"; str = $"{usr.Username} moved from {beforeVch.Name} to {afterVch.Name}";
@ -155,9 +229,15 @@ namespace NadekoBot.Modules.Administration
mutes = "text and voice chat"; mutes = "text and voice chat";
break; break;
} }
await logChannel.SendMessageAsync($"‼️🕕`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__🔇 **| User muted from the {mutes}. |** 🆔 `{usr.Id}`").ConfigureAwait(false);
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName("🔇 User Muted from " + mutes))
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
.WithFooter(fb => fb.WithText(currentTime))
.WithOkColor();
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch { }
} }
private static async void MuteCommands_UserUnmuted(IGuildUser usr, MuteCommands.MuteType muteType) private static async void MuteCommands_UserUnmuted(IGuildUser usr, MuteCommands.MuteType muteType)
@ -186,9 +266,15 @@ namespace NadekoBot.Modules.Administration
mutes = "text and voice chat"; mutes = "text and voice chat";
break; break;
} }
await logChannel.SendMessageAsync($"‼️🕕`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__🔊 **| User unmuted from the {mutes}. |** 🆔 `{usr.Id}`").ConfigureAwait(false);
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName("🔊 User Unmuted from " + mutes))
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
.WithFooter(fb => fb.WithText($"{currentTime}"))
.WithOkColor();
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch { }
} }
public static async Task TriggeredAntiProtection(IGuildUser[] users, PunishmentAction action, ProtectionType protection) public static async Task TriggeredAntiProtection(IGuildUser[] users, PunishmentAction action, ProtectionType protection)
@ -210,35 +296,31 @@ namespace NadekoBot.Modules.Administration
if (action == PunishmentAction.Mute) if (action == PunishmentAction.Mute)
{ {
punishment = "🔇 MUTED"; punishment = "🔇 MUTED";
//punishment = "MUTED";
} }
else if (action == PunishmentAction.Kick) else if (action == PunishmentAction.Kick)
{ {
punishment = "☣ SOFT-BANNED (KICKED)"; punishment = "☣ SOFT-BANNED (KICKED)";
//punishment = "KICKED";
} }
else if (action == PunishmentAction.Ban) else if (action == PunishmentAction.Ban)
{ {
punishment = "⛔️ BANNED"; punishment = "⛔️ BANNED";
//punishment = "BANNED";
} }
await logChannel.SendMessageAsync(String.Join("\n", users.Select(user => $"‼️ {Format.Bold(user.ToString())} got **{punishment}** due to __**{protection}**__ protection on **{user.Guild.Name}** server.")))
.ConfigureAwait(false); var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName($"🛡 Anti-{protection}"))
.WithTitle($"Users " + punishment)
.WithDescription(String.Join("\n", users.Select(u => u.ToString())))
.WithFooter(fb => fb.WithText($"{currentTime}"))
.WithOkColor();
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch { }
} }
private static async void _client_UserUpdated(SocketUser uBefore, SocketUser uAfter) private static async void _client_GuildUserUpdated(SocketGuildUser before, SocketGuildUser after)
{ {
try try
{ {
var before = uBefore as SocketGuildUser;
if (before == null)
return;
var after = uAfter as SocketGuildUser;
if (after == null)
return;
LogSetting logSetting; LogSetting logSetting;
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out logSetting) if (!GuildLogSettings.TryGetValue(before.Guild.Id, out logSetting)
|| (logSetting.UserUpdatedId == null)) || (logSetting.UserUpdatedId == null))
@ -247,32 +329,35 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel; ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) == null) if ((logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) == null)
return; return;
string str = $"🕔`{prettyCurrentTime}`"; var embed = new EmbedBuilder().WithOkColor().WithFooter(efb => efb.WithText(currentTime))
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}");
if (before.Nickname != after.Nickname)
{
embed.WithAuthor(eab => eab.WithName("👥 Nickname Changed"))
if (before.Username != after.Username) .AddField(efb => efb.WithName("Old Nickname").WithValue($"{before.Nickname}#{before.Discriminator}"))
str += $"👤__**{before.Username}#{before.Discriminator}**__ **| Name Changed |** 🆔 `{before.Id}`\n\t\t`New:` **{after.ToString()}**"; .AddField(efb => efb.WithName("New Nickname").WithValue($"{after.Nickname}#{after.Discriminator}"));
else if (before.Nickname != after.Nickname) }
str += $"👤__**{before.Username}#{before.Discriminator}**__ **| Nickname Changed |** 🆔 `{before.Id}`\n\t\t`Old:` **{before.Nickname}#{before.Discriminator}**\n\t\t`New:` **{after.Nickname}#{after.Discriminator}**";
else if (before.AvatarUrl != after.AvatarUrl)
str += $"👤__**{before.Username}#{before.Discriminator}**__ **| Avatar Changed |** 🆔 `{before.Id}`\n\t🖼 {await NadekoBot.Google.ShortenUrl(before.AvatarUrl)} `=>` {await NadekoBot.Google.ShortenUrl(after.AvatarUrl)}";
else if (!before.RoleIds.SequenceEqual(after.RoleIds)) else if (!before.RoleIds.SequenceEqual(after.RoleIds))
{ {
if (before.RoleIds.Count < after.RoleIds.Count) if (before.RoleIds.Count < after.RoleIds.Count)
{ {
var diffRoles = after.RoleIds.Where(r => !before.RoleIds.Contains(r)).Select(r => "**" + before.Guild.GetRole(r).Name + "**"); var diffRoles = after.RoleIds.Where(r => !before.RoleIds.Contains(r)).Select(r => before.Guild.GetRole(r).Name);
str += $"👤__**{before.ToString()}**__ **| User's Role Added |** 🆔 `{before.Id}`\n\t✅ {string.Join(", ", diffRoles).SanitizeMentions()}\n\t\t⚔ **`{string.Join(", ", after.GetRoles().Select(r => r.Name)).SanitizeMentions()}`** ⚔"; embed.WithAuthor(eab => eab.WithName("⚔ User's Role Added"))
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
} }
else if (before.RoleIds.Count > after.RoleIds.Count) else if (before.RoleIds.Count > after.RoleIds.Count)
{ {
var diffRoles = before.RoleIds.Where(r => !after.RoleIds.Contains(r)).Select(r => "**" + before.Guild.GetRole(r).Name + "**"); var diffRoles = before.RoleIds.Where(r => !after.RoleIds.Contains(r)).Select(r => before.Guild.GetRole(r).Name);
str += $"👤__**{before.ToString()}**__ **| User's Role Removed |** 🆔 `{before.Id}`\n\t🚮 {string.Join(", ", diffRoles).SanitizeMentions()}\n\t\t⚔ **`{string.Join(", ", after.GetRoles().Select(r => r.Name)).SanitizeMentions()}`** ⚔"; embed.WithAuthor(eab => eab.WithName("⚔ User's Role Removed"))
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
} }
} }
else else
return; return;
try { await logChannel.SendMessageAsync(str).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch { }
} }
private static async void _client_ChannelUpdated(IChannel cbefore, IChannel cafter) private static async void _client_ChannelUpdated(IChannel cbefore, IChannel cafter)
@ -293,16 +378,29 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel; ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.ChannelUpdated)) == null) if ((logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.ChannelUpdated)) == null)
return; return;
var embed = new EmbedBuilder().WithOkColor().WithFooter(efb => efb.WithText(currentTime));
var beforeTextChannel = cbefore as ITextChannel;
var afterTextChannel = cafter as ITextChannel;
if (before.Name != after.Name) if (before.Name != after.Name)
await logChannel.SendMessageAsync($@"🕓`{prettyCurrentTime}` **| Channel Name Changed |** #⃣ `{after.Name} ({after.Id})` {
`Old:` {before.Name} embed.WithTitle(" Channel Name Changed")
**`New:`** {after.Name}").ConfigureAwait(false); .WithDescription($"{after} | {after.Id}")
else if ((before as ITextChannel).Topic != (after as ITextChannel).Topic) .AddField(efb => efb.WithName("Old Name").WithValue(before.Name));
await logChannel.SendMessageAsync($@"🕘`{prettyCurrentTime}` **| Channel Topic Changed |** #⃣ `{after.Name} ({after.Id})` }
`Old:` {((ITextChannel)before).Topic} else if (beforeTextChannel?.Topic != afterTextChannel?.Topic)
**`New:`** {((ITextChannel)after).Topic}").ConfigureAwait(false); {
embed.WithTitle(" Channel Topic Changed")
.WithDescription($"{after} | {after.Id}")
.AddField(efb => efb.WithName("Old Topic").WithValue(beforeTextChannel.Topic))
.AddField(efb => efb.WithName("New Topic").WithValue(afterTextChannel.Topic));
}
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch { }
} }
private static async void _client_ChannelDestroyed(IChannel ich) private static async void _client_ChannelDestroyed(IChannel ich)
@ -323,9 +421,13 @@ namespace NadekoBot.Modules.Administration
if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ChannelDestroyed)) == null) if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ChannelDestroyed)) == null)
return; return;
await logChannel.SendMessageAsync($"🕕`{prettyCurrentTime}`🗑 **| {(ch is IVoiceChannel ? "Voice" : "Text")} Channel Deleted #⃣ {ch.Name}** `({ch.Id})`").ConfigureAwait(false); await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle("🆕 " + (ch is IVoiceChannel ? "Voice" : "Text") + " Channel Destroyed")
.WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch { }
} }
private static async void _client_ChannelCreated(IChannel ich) private static async void _client_ChannelCreated(IChannel ich)
@ -345,7 +447,11 @@ namespace NadekoBot.Modules.Administration
if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ChannelCreated)) == null) if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ChannelCreated)) == null)
return; return;
await logChannel.SendMessageAsync($"🕓`{prettyCurrentTime}`🆕 **| {(ch is IVoiceChannel ? "Voice" : "Text")} Channel Created: #⃣ {ch.Name}** `({ch.Id})`").ConfigureAwait(false); await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle("🆕 " + (ch is IVoiceChannel ? "Voice" : "Text") + " Channel Created")
.WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch (Exception ex) { _log.Warn(ex); }
} }
@ -387,19 +493,16 @@ namespace NadekoBot.Modules.Administration
str = $"🎙`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__ has left **{beforeVch.Name}** voice channel."; str = $"🎙`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__ has left **{beforeVch.Name}** voice channel.";
} }
if (str != null) if (str != null)
UserPresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; }); PresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; });
}
catch (Exception ex)
{
_log.Warn(ex);
} }
catch { }
} }
private static async void _client_UserPresenceUpdated(Optional<SocketGuild> optGuild, SocketUser usr, SocketPresence before, SocketPresence after) private static async void _client_UserPresenceUpdated(Optional<SocketGuild> optGuild, SocketUser usr, SocketPresence before, SocketPresence after)
{ {
try try
{ {
var guild = optGuild.IsSpecified ? optGuild.Value : null; var guild = optGuild.GetValueOrDefault() ?? (usr as SocketGuildUser)?.Guild;
if (guild == null) if (guild == null)
return; return;
@ -413,13 +516,18 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel; ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserPresence)) == null) if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserPresence)) == null)
return; return;
string str; string str = "";
if (before.Status != after.Status) if (before.Status != after.Status)
str = $"🔵`{prettyCurrentTime}`👤__**{usr.Username}**__ is now **{after.Status}**."; str = $"🎭`{prettyCurrentTime}`👤__**{usr.Username}**__ is now **{after.Status}**.";
else
str = $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game}**.";
UserPresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; }); //if (before.Game?.Name != after.Game?.Name)
//{
// if (str != "")
// str += "\n";
// str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**.";
//}
PresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; });
} }
catch { } catch { }
} }
@ -436,7 +544,14 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel; ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserLeft)) == null) if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserLeft)) == null)
return; return;
await logChannel.SendMessageAsync($"❗️🕛`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__❌ **| USER LEFT |** 🆔 `{usr.Id}`").ConfigureAwait(false);
await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle("❌ User Left")
.WithThumbnailUrl(usr.AvatarUrl)
.WithDescription(usr.ToString())
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
.WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false);
} }
catch { } catch { }
} }
@ -454,7 +569,13 @@ namespace NadekoBot.Modules.Administration
if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserJoined)) == null) if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserJoined)) == null)
return; return;
await logChannel.SendMessageAsync($"❕🕓`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__✅ **| USER JOINED |** 🆔 `{usr.Id}`").ConfigureAwait(false); await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle("✅ User Joined")
.WithThumbnailUrl(usr.AvatarUrl)
.WithDescription($"{usr}")
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
.WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch (Exception ex) { _log.Warn(ex); }
} }
@ -472,7 +593,13 @@ namespace NadekoBot.Modules.Administration
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserUnbanned)) == null) if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserUnbanned)) == null)
return; return;
await logChannel.SendMessageAsync($"❕🕘`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__♻ **| USER UN-BANNED |** 🆔 `{usr.Id}`").ConfigureAwait(false); await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle("♻️ User Unbanned")
.WithThumbnailUrl(usr.AvatarUrl)
.WithDescription(usr.ToString())
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
.WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch (Exception ex) { _log.Warn(ex); }
} }
@ -489,7 +616,13 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel; ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserBanned)) == null) if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserBanned)) == null)
return; return;
await logChannel.SendMessageAsync($"‼️🕕`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__🚫 **| USER BANNED |** 🆔 `{usr.Id}`").ConfigureAwait(false); await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle("🚫 User Banned")
.WithThumbnailUrl(usr.AvatarUrl)
.WithDescription(usr.ToString())
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
.WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch (Exception ex) { _log.Warn(ex); }
} }
@ -516,13 +649,19 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel; ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(channel.Guild, logSetting, LogType.MessageDeleted)) == null || logChannel.Id == msg.Id) if ((logChannel = await TryGetLogChannel(channel.Guild, logSetting, LogType.MessageDeleted)) == null || logChannel.Id == msg.Id)
return; return;
var str = $@"🕔`{prettyCurrentTime}`👤__**{msg.Author.Username}#{msg.Author.Discriminator}**__ **| Deleted Message |** 🆔 `{msg.Author.Id}` #⃣ `{channel.Name}` var embed = new EmbedBuilder()
🗑 {msg.Resolve(userHandling: TagHandling.FullName)}"; .WithOkColor()
.WithTitle($"🗑 Message Deleted in {((ITextChannel)msg.Channel).Mention}")
.WithDescription($"{msg.Author}")
.AddField(efb => efb.WithName("Content").WithValue(msg.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName("Id").WithValue(msg.Id.ToString()).WithIsInline(false))
.WithFooter(efb => efb.WithText(currentTime));
if (msg.Attachments.Any()) if (msg.Attachments.Any())
str += $"{Environment.NewLine}📎 {string.Join(", ", msg.Attachments.Select(a => a.ProxyUrl))}"; embed.AddField(efb => efb.WithName("Attachments").WithValue(string.Join(", ", msg.Attachments.Select(a => a.ProxyUrl))).WithIsInline(false));
await logChannel.SendMessageAsync(str.SanitizeMentions()).ConfigureAwait(false);
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch { }
} }
private static async void _client_MessageUpdated(Optional<SocketMessage> optmsg, SocketMessage imsg2) private static async void _client_MessageUpdated(Optional<SocketMessage> optmsg, SocketMessage imsg2)
@ -553,11 +692,19 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel; ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(channel.Guild, logSetting, LogType.MessageUpdated)) == null || logChannel.Id == after.Channel.Id) if ((logChannel = await TryGetLogChannel(channel.Guild, logSetting, LogType.MessageUpdated)) == null || logChannel.Id == after.Channel.Id)
return; return;
await logChannel.SendMessageAsync($@"🕔`{prettyCurrentTime}`👤__**{before.Author.Username}#{before.Author.Discriminator}**__ **| 📝 Edited Message |** 🆔 `{before.Author.Id}` #⃣ `{channel.Name}`
`Old:` {before.Resolve(userHandling: TagHandling.FullName).SanitizeMentions()} var embed = new EmbedBuilder()
**`New:`** {after.Resolve(userHandling: TagHandling.FullName).SanitizeMentions()}").ConfigureAwait(false); .WithOkColor()
.WithTitle($"📝 Message Updated in {((ITextChannel)after.Channel).Mention}")
.WithDescription(after.Author.ToString())
.AddField(efb => efb.WithName("Old Message").WithValue(before.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName("New Message").WithValue(after.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName("Id").WithValue(after.Id.ToString()).WithIsInline(false))
.WithFooter(efb => efb.WithText(currentTime));
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch { }
} }
public enum LogType public enum LogType
@ -740,14 +887,15 @@ namespace NadekoBot.Modules.Administration
logSetting.ChannelUpdatedId = logSetting.ChannelUpdatedId =
logSetting.LogUserPresenceId = logSetting.LogUserPresenceId =
logSetting.LogVoicePresenceId = logSetting.LogVoicePresenceId =
logSetting.UserMutedId =
logSetting.LogVoicePresenceTTSId = (action.Value ? channel.Id : (ulong?)null); logSetting.LogVoicePresenceTTSId = (action.Value ? channel.Id : (ulong?)null);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
if (action.Value) if (action.Value)
await channel.SendMessageAsync("✅ Logging all events on this channel.").ConfigureAwait(false); await channel.SendConfirmAsync("Logging all events in this channel.").ConfigureAwait(false);
else else
await channel.SendMessageAsync(" Logging disabled.").ConfigureAwait(false); await channel.SendConfirmAsync("Logging disabled.").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -774,9 +922,9 @@ namespace NadekoBot.Modules.Administration
} }
if (removed == 0) if (removed == 0)
await channel.SendMessageAsync($"🆗 Logging will **now ignore** #⃣ `{channel.Name} ({channel.Id})`").ConfigureAwait(false); await channel.SendConfirmAsync($"Logging will IGNORE **{channel.Mention} ({channel.Id})**").ConfigureAwait(false);
else else
await channel.SendMessageAsync($" Logging will **no longer ignore** #⃣ `{channel.Name} ({channel.Id})`").ConfigureAwait(false); await channel.SendConfirmAsync($"Logging will NOT IGNORE **{channel.Mention} ({channel.Id})**").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -853,9 +1001,9 @@ namespace NadekoBot.Modules.Administration
} }
if (channelId != null) if (channelId != null)
await channel.SendMessageAsync($"✅ Logging `{type}` event in #⃣ `{channel.Name} ({channel.Id})`").ConfigureAwait(false); await channel.SendConfirmAsync($"Logging **{type}** event in this channel.").ConfigureAwait(false);
else else
await channel.SendMessageAsync($" Stopped logging `{type}` event.").ConfigureAwait(false); await channel.SendConfirmAsync($"Stopped logging **{type}** event.").ConfigureAwait(false);
} }
} }
} }

View File

@ -1,173 +0,0 @@
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.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class RepeatCommands : ModuleBase
{
public static ConcurrentDictionary<ulong, RepeatRunner> repeaters { get; }
public class RepeatRunner
{
private Logger _log { get; }
private CancellationTokenSource source { get; set; }
private CancellationToken token { get; set; }
public Repeater Repeater { get; }
public ITextChannel Channel { get; }
public RepeatRunner(Repeater repeater, ITextChannel channel = null)
{
_log = LogManager.GetCurrentClassLogger();
this.Repeater = repeater;
this.Channel = channel ?? NadekoBot.Client.GetGuild(repeater.GuildId)?.GetTextChannelAsync(repeater.ChannelId).GetAwaiter().GetResult();
if (Channel == null)
return;
Task.Run(Run);
}
private async Task Run()
{
source = new CancellationTokenSource();
token = source.Token;
IUserMessage oldMsg = null;
try
{
while (!token.IsCancellationRequested)
{
var toSend = "🔄 " + Repeater.Message;
await Task.Delay(Repeater.Interval, token).ConfigureAwait(false);
//var lastMsgInChannel = (await Channel.GetMessagesAsync(2)).FirstOrDefault();
// if (lastMsgInChannel.Id == oldMsg?.Id) //don't send if it's the same message in the channel
// continue;
if (oldMsg != null)
try { await oldMsg.DeleteAsync(); } catch { }
try { oldMsg = await Channel.SendMessageAsync(toSend).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
}
}
catch (OperationCanceledException) { }
}
public void Reset()
{
source.Cancel();
var t = Task.Run(Run);
}
public void Stop()
{
source.Cancel();
}
}
static RepeatCommands()
{
var _log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
using (var uow = DbHandler.UnitOfWork())
{
repeaters = new ConcurrentDictionary<ulong, RepeatRunner>(uow.Repeaters.GetAll().Select(r => new RepeatRunner(r)).Where(r => r != null).ToDictionary(r => r.Repeater.ChannelId));
}
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task RepeatInvoke()
{
RepeatRunner rep;
if (!repeaters.TryGetValue(Context.Channel.Id, out rep))
{
await Context.Channel.SendErrorAsync(" **No repeating message found on this server.**").ConfigureAwait(false);
return;
}
rep.Reset();
await Context.Channel.SendMessageAsync("🔄 " + rep.Repeater.Message).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task Repeat()
{
RepeatRunner rep;
if (repeaters.TryRemove(Context.Channel.Id, out rep))
{
using (var uow = DbHandler.UnitOfWork())
{
uow.Repeaters.Remove(rep.Repeater);
await uow.CompleteAsync();
}
rep.Stop();
await Context.Channel.SendConfirmAsync("✅ **Stopped repeating a message.**").ConfigureAwait(false);
}
else
await Context.Channel.SendConfirmAsync(" **No message is repeating.**").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task Repeat(int minutes, [Remainder] string message)
{
if (minutes < 1 || minutes > 10080)
return;
if (string.IsNullOrWhiteSpace(message))
return;
RepeatRunner rep;
rep = repeaters.AddOrUpdate(Context.Channel.Id, (cid) =>
{
using (var uow = DbHandler.UnitOfWork())
{
var localRep = new Repeater
{
ChannelId = Context.Channel.Id,
GuildId = Context.Guild.Id,
Interval = TimeSpan.FromMinutes(minutes),
Message = message,
};
uow.Repeaters.Add(localRep);
uow.Complete();
return new RepeatRunner(localRep, (ITextChannel)Context.Channel);
}
}, (cid, old) =>
{
using (var uow = DbHandler.UnitOfWork())
{
old.Repeater.Message = message;
old.Repeater.Interval = TimeSpan.FromMinutes(minutes);
uow.Repeaters.Update(old.Repeater);
uow.Complete();
}
old.Reset();
return old;
});
await Context.Channel.SendConfirmAsync($"🔁 Repeating **\"{rep.Repeater.Message}\"** every `{rep.Repeater.Interval.Days} day(s), {rep.Repeater.Interval.Hours} hour(s) and {rep.Repeater.Interval.Minutes} minute(s)`.").ConfigureAwait(false);
}
}
}
}

View File

@ -71,8 +71,8 @@ namespace NadekoBot.Modules.Administration
public static Dictionary<string, Func<string>> PlayingPlaceholders { get; } = public static Dictionary<string, Func<string>> PlayingPlaceholders { get; } =
new Dictionary<string, Func<string>> { new Dictionary<string, Func<string>> {
{"%servers%", () => NadekoBot.Client.GetGuilds().Count().ToString()}, {"%servers%", () => NadekoBot.Client.GetGuildsCount().ToString()},
{"%users%", () => NadekoBot.Client.GetGuilds().Select(s => s.Users.Count).Sum().ToString()}, {"%users%", () => NadekoBot.Client.GetGuilds().Sum(s => s.Users.Count).ToString()},
{"%playing%", () => { {"%playing%", () => {
var cnt = Music.Music.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null); var cnt = Music.Music.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null);
if (cnt != 1) return cnt.ToString(); if (cnt != 1) return cnt.ToString();

View File

@ -0,0 +1,425 @@
using Discord;
using Discord.Commands;
using Microsoft.EntityFrameworkCore;
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.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
public enum ProtectionType
{
Raiding,
Spamming,
}
public class AntiRaidStats
{
public AntiRaidSetting AntiRaidSettings { get; set; }
public int UsersCount { get; set; } = 0;
public ConcurrentHashSet<IGuildUser> RaidUsers { get; set; } = new ConcurrentHashSet<IGuildUser>();
public override string ToString() =>
$"If **{AntiRaidSettings.UserThreshold}** or more users join within **{AntiRaidSettings.Seconds}** seconds," +
$" I will **{AntiRaidSettings.Action}** them.";
}
public class AntiSpamStats
{
public AntiSpamSetting AntiSpamSettings { get; set; }
public ConcurrentDictionary<ulong, UserSpamStats> UserStats { get; set; }
= new ConcurrentDictionary<ulong, UserSpamStats>();
public override string ToString()
{
var ignoredString = string.Join(", ", AntiSpamSettings.IgnoredChannels.Select(c => $"<#{c.ChannelId}>"));
if (string.IsNullOrWhiteSpace(ignoredString))
ignoredString = "none";
return $"If a user posts **{AntiSpamSettings.MessageThreshold}** same messages in a row, I will **{AntiSpamSettings.Action}** them."
+ $"\n\t__IgnoredChannels__: {ignoredString}";
}
}
public class UserSpamStats
{
public int Count { get; set; }
public string LastMessage { get; set; }
public UserSpamStats(string msg)
{
Count = 1;
LastMessage = msg.ToUpperInvariant();
}
public void ApplyNextMessage(string message)
{
var upperMsg = message.ToUpperInvariant();
if (upperMsg == LastMessage)
Count++;
else
{
LastMessage = upperMsg;
Count = 0;
}
}
}
[Group]
public class ProtectionCommands : ModuleBase
{
private static ConcurrentDictionary<ulong, AntiRaidStats> antiRaidGuilds =
new ConcurrentDictionary<ulong, AntiRaidStats>();
// guildId | (userId|messages)
private static ConcurrentDictionary<ulong, AntiSpamStats> antiSpamGuilds =
new ConcurrentDictionary<ulong, AntiSpamStats>();
private static Logger _log { get; }
static ProtectionCommands()
{
_log = LogManager.GetCurrentClassLogger();
foreach (var gc in NadekoBot.AllGuildConfigs)
{
var raid = gc.AntiRaidSetting;
var spam = gc.AntiSpamSetting;
if (raid != null)
{
var raidStats = new AntiRaidStats() { AntiRaidSettings = raid };
antiRaidGuilds.TryAdd(gc.GuildId, raidStats);
}
if (spam != null)
antiSpamGuilds.TryAdd(gc.GuildId, new AntiSpamStats() { AntiSpamSettings = spam });
}
NadekoBot.Client.MessageReceived += async (imsg) =>
{
try
{
var msg = imsg as IUserMessage;
if (msg == null || msg.Author.IsBot)
return;
var channel = msg.Channel as ITextChannel;
if (channel == null)
return;
AntiSpamStats spamSettings;
if (!antiSpamGuilds.TryGetValue(channel.Guild.Id, out spamSettings) ||
spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new AntiSpamIgnore()
{
ChannelId = channel.Id
}))
return;
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, new UserSpamStats(msg.Content),
(id, old) => { old.ApplyNextMessage(msg.Content); return old; });
if (stats.Count >= spamSettings.AntiSpamSettings.MessageThreshold)
{
if (spamSettings.UserStats.TryRemove(msg.Author.Id, out stats))
{
await PunishUsers(spamSettings.AntiSpamSettings.Action, ProtectionType.Spamming, (IGuildUser)msg.Author)
.ConfigureAwait(false);
}
}
}
catch { }
};
NadekoBot.Client.UserJoined += async (usr) =>
{
try
{
if (usr.IsBot)
return;
AntiRaidStats settings;
if (!antiRaidGuilds.TryGetValue(usr.Guild.Id, out settings))
return;
if (!settings.RaidUsers.Add(usr))
return;
++settings.UsersCount;
if (settings.UsersCount >= settings.AntiRaidSettings.UserThreshold)
{
var users = settings.RaidUsers.ToArray();
settings.RaidUsers.Clear();
await PunishUsers(settings.AntiRaidSettings.Action, ProtectionType.Raiding, users).ConfigureAwait(false);
}
await Task.Delay(1000 * settings.AntiRaidSettings.Seconds).ConfigureAwait(false);
settings.RaidUsers.TryRemove(usr);
--settings.UsersCount;
}
catch { }
};
}
private static async Task PunishUsers(PunishmentAction action, ProtectionType pt, params IGuildUser[] gus)
{
foreach (var gu in gus)
{
switch (action)
{
case PunishmentAction.Mute:
try
{
await MuteCommands.MuteUser(gu).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishement"); }
break;
case PunishmentAction.Kick:
try
{
await gu.Guild.AddBanAsync(gu, 7).ConfigureAwait(false);
try
{
await gu.Guild.RemoveBanAsync(gu).ConfigureAwait(false);
}
catch
{
await gu.Guild.RemoveBanAsync(gu).ConfigureAwait(false);
// try it twice, really don't want to ban user if
// only kick has been specified as the punishement
}
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); }
break;
case PunishmentAction.Ban:
try
{
await gu.Guild.AddBanAsync(gu, 7).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); }
break;
default:
break;
}
}
await LogCommands.TriggeredAntiProtection(gus, action, pt).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task AntiRaid(int userThreshold = 5, int seconds = 10, PunishmentAction action = PunishmentAction.Mute)
{
if (userThreshold < 2 || userThreshold > 30)
{
await Context.Channel.SendErrorAsync("❗User threshold must be between **2** and **30**.").ConfigureAwait(false);
return;
}
if (seconds < 2 || seconds > 300)
{
await Context.Channel.SendErrorAsync("❗Time must be between **2** and **300** seconds.").ConfigureAwait(false);
return;
}
AntiRaidStats throwaway;
if (antiRaidGuilds.TryRemove(Context.Guild.Id, out throwaway))
{
using (var uow = DbHandler.UnitOfWork())
{
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiRaidSetting));
gc.AntiRaidSetting = null;
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("**Anti-Raid** feature has been **disabled** on this server.").ConfigureAwait(false);
return;
}
try
{
await MuteCommands.GetMuteRole(Context.Guild).ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendConfirmAsync("⚠️ Failed creating a mute role. Give me ManageRoles permission" +
"or create 'nadeko-mute' role with disabled SendMessages and try again.")
.ConfigureAwait(false);
_log.Warn(ex);
return;
}
var stats = new AntiRaidStats()
{
AntiRaidSettings = new AntiRaidSetting()
{
Action = action,
Seconds = seconds,
UserThreshold = userThreshold,
}
};
antiRaidGuilds.AddOrUpdate(Context.Guild.Id, stats, (key, old) => stats);
using (var uow = DbHandler.UnitOfWork())
{
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiRaidSetting));
gc.AntiRaidSetting = stats.AntiRaidSettings;
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("Anti-Raid Enabled", $"{Context.User.Mention} {stats.ToString()}")
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task AntiSpam(int messageCount = 3, PunishmentAction action = PunishmentAction.Mute)
{
if (messageCount < 2 || messageCount > 10)
return;
AntiSpamStats throwaway;
if (antiSpamGuilds.TryRemove(Context.Guild.Id, out throwaway))
{
using (var uow = DbHandler.UnitOfWork())
{
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiSpamSetting)
.ThenInclude(x => x.IgnoredChannels));
gc.AntiSpamSetting = null;
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("**Anti-Spam** has been **disabled** on this server.").ConfigureAwait(false);
return;
}
try
{
await MuteCommands.GetMuteRole(Context.Guild).ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync("⚠️ Failed creating a mute role. Give me ManageRoles permission" +
"or create 'nadeko-mute' role with disabled SendMessages and try again.")
.ConfigureAwait(false);
_log.Warn(ex);
return;
}
var stats = new AntiSpamStats
{
AntiSpamSettings = new AntiSpamSetting()
{
Action = action,
MessageThreshold = messageCount,
}
};
antiSpamGuilds.AddOrUpdate(Context.Guild.Id, stats, (key, old) => stats);
using (var uow = DbHandler.UnitOfWork())
{
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiSpamSetting));
gc.AntiSpamSetting = stats.AntiSpamSettings;
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("Anti-Spam Enabled", $"{Context.User.Mention} {stats.ToString()}").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AntispamIgnore()
{
var channel = (ITextChannel)Context.Channel;
var obj = new AntiSpamIgnore()
{
ChannelId = channel.Id
};
bool added;
using (var uow = DbHandler.UnitOfWork())
{
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels));
var spam = gc.AntiSpamSetting;
if (spam == null)
{
return;
}
if (spam.IgnoredChannels.Add(obj))
{
AntiSpamStats temp;
if (antiSpamGuilds.TryGetValue(Context.Guild.Id, out temp))
temp.AntiSpamSettings.IgnoredChannels.Add(obj);
added = true;
}
else
{
spam.IgnoredChannels.Remove(obj);
AntiSpamStats temp;
if (antiSpamGuilds.TryGetValue(Context.Guild.Id, out temp))
temp.AntiSpamSettings.IgnoredChannels.Remove(obj);
added = false;
}
await uow.CompleteAsync().ConfigureAwait(false);
}
if (added)
await Context.Channel.SendConfirmAsync("Anti-Spam will ignore this channel.").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync("Anti-Spam will no longer ignore this channel.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AntiList()
{
var channel = (ITextChannel)Context.Channel;
AntiSpamStats spam;
antiSpamGuilds.TryGetValue(Context.Guild.Id, out spam);
AntiRaidStats raid;
antiRaidGuilds.TryGetValue(Context.Guild.Id, out raid);
if (spam == null && raid == null)
{
await Context.Channel.SendConfirmAsync("No protections enabled.");
return;
}
var embed = new EmbedBuilder().WithOkColor()
.WithTitle("Protections Enabled");
if (spam != null)
embed.AddField(efb => efb.WithName("Anti-Spam")
.WithValue(spam.ToString())
.WithIsInline(true));
if (raid != null)
embed.AddField(efb => efb.WithName("Anti-Raid")
.WithValue(raid.ToString())
.WithIsInline(true));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
}
}
}

View File

@ -20,7 +20,7 @@ namespace NadekoBot.Modules.Administration
public async Task Leave([Remainder] string guildStr) public async Task Leave([Remainder] string guildStr)
{ {
guildStr = guildStr.Trim().ToUpperInvariant(); guildStr = guildStr.Trim().ToUpperInvariant();
var server = NadekoBot.Client.GetGuilds().FirstOrDefault(g => g.Id.ToString().Trim().ToUpperInvariant() == guildStr) ?? var server = NadekoBot.Client.GetGuilds().FirstOrDefault(g => g.Id.ToString() == guildStr) ??
NadekoBot.Client.GetGuilds().FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == guildStr); NadekoBot.Client.GetGuilds().FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == guildStr);
if (server == null) if (server == null)
@ -59,7 +59,16 @@ namespace NadekoBot.Modules.Administration
await NadekoBot.Client.CurrentUser().ModifyAsync(u => u.Username = newName).ConfigureAwait(false); await NadekoBot.Client.CurrentUser().ModifyAsync(u => u.Username = newName).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($" Successfully changed name to **{newName}**").ConfigureAwait(false); await Context.Channel.SendConfirmAsync($"Bot name changed to **{newName}**").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task SetStatus([Remainder] SettableUserStatus status)
{
await NadekoBot.Client.SetStatus(status);
await Context.Channel.SendConfirmAsync($"Bot status changed to **{status}**").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -88,8 +97,6 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task SetGame([Remainder] string game = null) public async Task SetGame([Remainder] string game = null)
{ {
game = game ?? "";
await NadekoBot.Client.SetGame(game).ConfigureAwait(false); await NadekoBot.Client.SetGame(game).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("👾 **New game set.**").ConfigureAwait(false); await Context.Channel.SendConfirmAsync("👾 **New game set.**").ConfigureAwait(false);

View File

@ -145,13 +145,17 @@ namespace NadekoBot.Modules.CustomReactions
if (customReactions == null || !customReactions.Any()) if (customReactions == null || !customReactions.Any())
await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false); await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false);
else else
await Context.Channel.SendConfirmAsync( {
$"Page {page} of custom reactions:", var lastPage = customReactions.Count / 20;
string.Join("\n", customReactions.OrderBy(cr => cr.Trigger) await Context.Channel.SendPaginatedConfirmAsync(page, curPage =>
.Skip((page - 1) * 20) new EmbedBuilder().WithOkColor()
.WithTitle("Custom reactions")
.WithDescription(string.Join("\n", customReactions.OrderBy(cr => cr.Trigger)
.Skip((curPage - 1) * 20)
.Take(20) .Take(20)
.Select(cr => $"`#{cr.Id}` `Trigger:` {cr.Trigger}"))) .Select(cr => $"`#{cr.Id}` `Trigger:` {cr.Trigger}"))), lastPage)
.ConfigureAwait(false); .ConfigureAwait(false);
}
} }
public enum All public enum All
@ -200,14 +204,22 @@ namespace NadekoBot.Modules.CustomReactions
if (customReactions == null || !customReactions.Any()) if (customReactions == null || !customReactions.Any())
await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false); await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false);
else else
await Context.Channel.SendConfirmAsync($"Page {page} of custom reactions (grouped):", {
string.Join("\r\n", customReactions var ordered = customReactions
.GroupBy(cr => cr.Trigger) .GroupBy(cr => cr.Trigger)
.OrderBy(cr => cr.Key) .OrderBy(cr => cr.Key)
.Skip((page - 1) * 20) .ToList();
.Take(20)
.Select(cr => $"**{cr.Key.Trim().ToLowerInvariant()}** `x{cr.Count()}`"))) var lastPage = ordered.Count / 20;
await Context.Channel.SendPaginatedConfirmAsync(page, (curPage) =>
new EmbedBuilder().WithOkColor()
.WithTitle($"Custom Reactions (grouped)")
.WithDescription(string.Join("\r\n", ordered
.Skip((curPage - 1) * 20)
.Take(20)
.Select(cr => $"**{cr.Key.Trim().ToLowerInvariant()}** `x{cr.Count()}`"))), lastPage)
.ConfigureAwait(false); .ConfigureAwait(false);
}
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -300,13 +312,14 @@ namespace NadekoBot.Modules.CustomReactions
{ {
if (page < 1) if (page < 1)
return; return;
await Context.Channel.EmbedAsync(ReactionStats.OrderByDescending(x => x.Value) var ordered = ReactionStats.OrderByDescending(x => x.Value).ToList();
.Skip((page - 1) * 9) var lastPage = ordered.Count / 9;
.Take(9) await Context.Channel.SendPaginatedConfirmAsync(page,
.Aggregate(new EmbedBuilder().WithOkColor().WithTitle($"Custom Reaction stats page #{page}"), (curPage) => ordered.Skip((curPage - 1) * 9)
(agg, cur) => agg.AddField(efb => efb.WithName(cur.Key).WithValue(cur.Value.ToString()).WithIsInline(true))) .Take(9)
) .Aggregate(new EmbedBuilder().WithOkColor().WithTitle($"Custom Reaction Stats"),
.ConfigureAwait(false); (agg, cur) => agg.AddField(efb => efb.WithName(cur.Key).WithValue(cur.Value.ToString()).WithIsInline(true))), lastPage)
.ConfigureAwait(false);
} }
} }
} }

View File

@ -164,12 +164,15 @@ namespace NadekoBot.Modules.Gambling
var arr = new int[n1]; var arr = new int[n1];
for (int i = 0; i < n1; i++) for (int i = 0; i < n1; i++)
{ {
arr[i] = rng.Next(1, n2 + 1) + add - sub; arr[i] = rng.Next(1, n2 + 1);
} }
var embed = new EmbedBuilder().WithOkColor().WithDescription($"{Context.User.Mention} rolled {n1} {(n1 == 1 ? "die" : "dice")} `1 to {n2}` +`{add}` -`{sub}`") var sum = arr.Sum();
.AddField(efb => efb.WithName(Format.Bold("Result")) var embed = new EmbedBuilder().WithOkColor().WithDescription($"{Context.User.Mention} rolled {n1} {(n1 == 1 ? "die" : "dice")} `1 to {n2}`")
.WithValue(string.Join(" ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => Format.Code(x.ToString()))))); .AddField(efb => efb.WithName(Format.Bold("Rolls"))
.WithValue(string.Join(" ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => Format.Code(x.ToString())))))
.AddField(efb => efb.WithName(Format.Bold("Sum"))
.WithValue(sum + " + " + add + " - " + sub + " = " + (sum + add - sub)));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
} }
} }

View File

@ -26,9 +26,9 @@ namespace NadekoBot.Modules.Gambling
if (count == 1) if (count == 1)
{ {
if (rng.Next(0, 2) == 1) if (rng.Next(0, 2) == 1)
await Context.Channel.SendFileAsync(headsPath, $"{Context.User.Mention} flipped " + Format.Code("Heads") + ".").ConfigureAwait(false); await Context.Channel.SendFileAsync(File.Open(headsPath, FileMode.OpenOrCreate), "heads.jpg", $"{Context.User.Mention} flipped " + Format.Code("Heads") + ".").ConfigureAwait(false);
else else
await Context.Channel.SendFileAsync(tailsPath, $"{Context.User.Mention} flipped " + Format.Code("Tails") + ".").ConfigureAwait(false); await Context.Channel.SendFileAsync(File.Open(tailsPath, FileMode.OpenOrCreate), "tails.jpg", $"{Context.User.Mention} flipped " + Format.Code("Tails") + ".").ConfigureAwait(false);
return; return;
} }
if (count > 10 || count < 1) if (count > 10 || count < 1)
@ -93,7 +93,7 @@ namespace NadekoBot.Modules.Gambling
str = $"{Context.User.Mention}`Better luck next time.`"; str = $"{Context.User.Mention}`Better luck next time.`";
} }
await Context.Channel.SendFileAsync(imgPathToSend, str).ConfigureAwait(false); await Context.Channel.SendFileAsync(File.Open(imgPathToSend, FileMode.OpenOrCreate), "coin.jpg", str).ConfigureAwait(false);
} }
} }
} }

View File

@ -99,7 +99,7 @@ namespace NadekoBot.Modules.Gambling
await CurrencyHandler.AddCurrencyAsync(usrId, $"Awarded by bot owner. ({Context.User.Username}/{Context.User.Id})", amount).ConfigureAwait(false); await CurrencyHandler.AddCurrencyAsync(usrId, $"Awarded by bot owner. ({Context.User.Username}/{Context.User.Id})", amount).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} successfully awarded {amount} {(amount == 1 ? Gambling.CurrencyName : Gambling.CurrencyPluralName)} to <@{usrId}>!").ConfigureAwait(false); await Context.Channel.SendConfirmAsync($"{Context.User.Mention} awarded {amount} {(amount == 1 ? Gambling.CurrencyName : Gambling.CurrencyPluralName)} to <@{usrId}>!").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]

View File

@ -91,7 +91,7 @@ namespace NadekoBot.Modules.Games
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(ChannelPermission.ManageMessages)] [RequireUserPermission(GuildPermission.ManageMessages)]
public async Task Cleverbot() public async Task Cleverbot()
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;

View File

@ -12,37 +12,15 @@ using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Commands.Hangman namespace NadekoBot.Modules.Games.Commands.Hangman
{ {
public class HangmanModel
{
public List<HangmanObject> All { get; set; }
public List<HangmanObject> Animals { get; set; }
public List<HangmanObject> Countries { get; set; }
public List<HangmanObject> Movies { get; set; }
public List<HangmanObject> Things { get; set; }
}
public class HangmanTermPool public class HangmanTermPool
{ {
public enum HangmanTermType
{
All,
Animals,
Countries,
Movies,
Things
}
const string termsPath = "data/hangman.json"; const string termsPath = "data/hangman.json";
public static HangmanModel data { get; } public static IReadOnlyDictionary<string, HangmanObject[]> data { get; }
static HangmanTermPool() static HangmanTermPool()
{ {
try try
{ {
data = JsonConvert.DeserializeObject<HangmanModel>(File.ReadAllText(termsPath)); data = JsonConvert.DeserializeObject<Dictionary<string, HangmanObject[]>>(File.ReadAllText(termsPath));
data.All = data.Animals.Concat(data.Countries)
.Concat(data.Movies)
.Concat(data.Things)
.ToList();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -50,27 +28,31 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
} }
} }
public static HangmanObject GetTerm(HangmanTermType type) public static HangmanObject GetTerm(string type)
{ {
if (string.IsNullOrWhiteSpace(type))
throw new ArgumentNullException(nameof(type));
type = type.Trim();
var rng = new NadekoRandom(); var rng = new NadekoRandom();
switch (type)
{ if (type == "All") {
case HangmanTermType.Animals: var keys = data.Keys.ToArray();
return data.Animals[rng.Next(0, data.Animals.Count)]; type = keys[rng.Next(0, keys.Length)];
case HangmanTermType.Countries:
return data.Countries[rng.Next(0, data.Countries.Count)];
case HangmanTermType.Movies:
return data.Movies[rng.Next(0, data.Movies.Count)];
case HangmanTermType.Things:
return data.Things[rng.Next(0, data.Things.Count)];
default:
return data.All[rng.Next(0, data.All.Count)];
} }
HangmanObject[] termTypes;
data.TryGetValue(type, out termTypes);
if (termTypes.Length == 0)
return null;
return termTypes[rng.Next(0, termTypes.Length)];
} }
} }
public class HangmanGame public class HangmanGame: IDisposable
{ {
private readonly Logger _log; private readonly Logger _log;
@ -95,20 +77,23 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
public bool GuessedAll => Guesses.IsSupersetOf(Term.Word.ToUpperInvariant() public bool GuessedAll => Guesses.IsSupersetOf(Term.Word.ToUpperInvariant()
.Where(c => char.IsLetter(c) || char.IsDigit(c))); .Where(c => char.IsLetter(c) || char.IsDigit(c)));
public HangmanTermPool.HangmanTermType TermType { get; } public string TermType { get; }
public event Action<HangmanGame> OnEnded; public event Action<HangmanGame> OnEnded;
public HangmanGame(IMessageChannel channel, HangmanTermPool.HangmanTermType type) public HangmanGame(IMessageChannel channel, string type)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
this.GameChannel = channel; this.GameChannel = channel;
this.TermType = type; this.TermType = type.ToTitleCase();
} }
public void Start() public void Start()
{ {
this.Term = HangmanTermPool.GetTerm(TermType); this.Term = HangmanTermPool.GetTerm(TermType);
if (this.Term == null)
throw new KeyNotFoundException("Can't find a term with that type. Use hangmanlist command.");
// start listening for answers when game starts // start listening for answers when game starts
NadekoBot.Client.MessageReceived += PotentialGuess; NadekoBot.Client.MessageReceived += PotentialGuess;
} }
@ -211,5 +196,11 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
{(Errors > 1 ? "/" : " ")} {(Errors > 2 ? "|" : " ")} {(Errors > 3 ? "\\" : " ")} | {(Errors > 1 ? "/" : " ")} {(Errors > 2 ? "|" : " ")} {(Errors > 3 ? "\\" : " ")} |
{(Errors > 4 ? "/" : " ")} {(Errors > 5 ? "\\" : " ")} | {(Errors > 4 ? "/" : " ")} {(Errors > 5 ? "\\" : " ")} |
/-\"; /-\";
public void Dispose()
{
NadekoBot.Client.MessageReceived -= PotentialGuess;
OnEnded = null;
}
} }
} }

View File

@ -23,7 +23,7 @@ namespace NadekoBot.Modules.Games
static HangmanCommands() static HangmanCommands()
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
typesStr = $"`List of \"{NadekoBot.ModulePrefixes[typeof(Games).Name]}hangman\" term types:`\n" + String.Join(", ", Enum.GetNames(typeof(HangmanTermPool.HangmanTermType))); typesStr = $"`List of \"{NadekoBot.ModulePrefixes[typeof(Games).Name]}hangman\" term types:`\n" + String.Join(", ", HangmanTermPool.data.Keys);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -33,7 +33,7 @@ namespace NadekoBot.Modules.Games
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Hangman(HangmanTermPool.HangmanTermType type = HangmanTermPool.HangmanTermType.All) public async Task Hangman([Remainder]string type = "All")
{ {
var hm = new HangmanGame(Context.Channel, type); var hm = new HangmanGame(Context.Channel, type);
@ -48,7 +48,18 @@ namespace NadekoBot.Modules.Games
HangmanGame throwaway; HangmanGame throwaway;
HangmanGames.TryRemove(g.GameChannel.Id, out throwaway); HangmanGames.TryRemove(g.GameChannel.Id, out throwaway);
}; };
hm.Start(); try
{
hm.Start();
}
catch (Exception ex)
{
try { await Context.Channel.SendErrorAsync($"Starting errored: {ex.Message}").ConfigureAwait(false); } catch { }
HangmanGame throwaway;
HangmanGames.TryRemove(Context.Channel.Id, out throwaway);
throwaway.Dispose();
return;
}
await Context.Channel.SendConfirmAsync("Hangman game started", hm.ScrambledWord + "\n" + hm.GetHangman() + "\n" + hm.ScrambledWord); await Context.Channel.SendConfirmAsync("Hangman game started", hm.ScrambledWord + "\n" + hm.GetHangman() + "\n" + hm.ScrambledWord);
} }

View File

@ -46,9 +46,12 @@ namespace NadekoBot.Modules.Games
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
#if !GLOBAL_NADEKO #if !GLOBAL_NADEKO
NadekoBot.Client.MessageReceived += PotentialFlowerGeneration; NadekoBot.Client.MessageReceived += PotentialFlowerGeneration;
#endif #endif
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var conf = uow.BotConfig.GetOrCreate(); var conf = uow.BotConfig.GetOrCreate();
@ -91,7 +94,8 @@ namespace NadekoBot.Modules.Games
lastGenerations.AddOrUpdate(channel.Id, DateTime.Now, (id, old) => DateTime.Now); lastGenerations.AddOrUpdate(channel.Id, DateTime.Now, (id, old) => DateTime.Now);
var sent = await channel.SendFileAsync( var sent = await channel.SendFileAsync(
GetRandomCurrencyImagePath(), File.Open(GetRandomCurrencyImagePath(), FileMode.OpenOrCreate),
"RandomFlower.jpg",
$"❗ A random { Gambling.Gambling.CurrencyName } appeared! Pick it up by typing `{NadekoBot.ModulePrefixes[typeof(Games).Name]}pick`") $"❗ A random { Gambling.Gambling.CurrencyName } appeared! Pick it up by typing `{NadekoBot.ModulePrefixes[typeof(Games).Name]}pick`")
.ConfigureAwait(false); .ConfigureAwait(false);
plantedFlowers.AddOrUpdate(channel.Id, new List<IUserMessage>() { sent }, (id, old) => { old.Add(sent); return old; }); plantedFlowers.AddOrUpdate(channel.Id, new List<IUserMessage>() { sent }, (id, old) => { old.Add(sent); return old; });
@ -101,16 +105,19 @@ namespace NadekoBot.Modules.Games
} }
catch { } catch { }
} }
#if !GLOBAL_NADEKO
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Pick() public async Task Pick()
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
if (!(await channel.Guild.GetCurrentUserAsync()).GetPermissions(channel).ManageMessages || !usersRecentlyPicked.Add(Context.User.Id)) if (!(await channel.Guild.GetCurrentUserAsync()).GetPermissions(channel).ManageMessages)
return; return;
#if GLOBAL_NADEKO
if (!usersRecentlyPicked.Add(Context.User.Id))
return;
#endif
try try
{ {
@ -122,14 +129,16 @@ namespace NadekoBot.Modules.Games
await Task.WhenAll(msgs.Select(toDelete => toDelete.DeleteAsync())).ConfigureAwait(false); await Task.WhenAll(msgs.Select(toDelete => toDelete.DeleteAsync())).ConfigureAwait(false);
await CurrencyHandler.AddCurrencyAsync((IGuildUser)Context.User, "Picked flower(s).", msgs.Count, false).ConfigureAwait(false); await CurrencyHandler.AddCurrencyAsync((IGuildUser)Context.User, $"Picked {Gambling.Gambling.CurrencyPluralName}", msgs.Count, false).ConfigureAwait(false);
var msg = await channel.SendConfirmAsync($"**{Context.User}** picked {msgs.Count}{Gambling.Gambling.CurrencySign}!").ConfigureAwait(false); var msg = await channel.SendConfirmAsync($"**{Context.User}** picked {msgs.Count}{Gambling.Gambling.CurrencySign}!").ConfigureAwait(false);
msg.DeleteAfter(10); msg.DeleteAfter(10);
} }
finally finally
{ {
#if GLOBAL_NADEKO
await Task.Delay(60000); await Task.Delay(60000);
usersRecentlyPicked.TryRemove(Context.User.Id); usersRecentlyPicked.TryRemove(Context.User.Id);
#endif
} }
} }
@ -137,7 +146,7 @@ namespace NadekoBot.Modules.Games
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Plant() public async Task Plant()
{ {
var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, "Planted a flower.", 1, false).ConfigureAwait(false); var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Planted a {Gambling.Gambling.CurrencyName}", 1, false).ConfigureAwait(false);
if (!removed) if (!removed)
{ {
await Context.Channel.SendErrorAsync($"You don't have any {Gambling.Gambling.CurrencyPluralName}.").ConfigureAwait(false); await Context.Channel.SendErrorAsync($"You don't have any {Gambling.Gambling.CurrencyPluralName}.").ConfigureAwait(false);
@ -155,11 +164,11 @@ namespace NadekoBot.Modules.Games
} }
else else
{ {
msg = await Context.Channel.SendFileAsync(file, msgToSend).ConfigureAwait(false); msg = await Context.Channel.SendFileAsync(File.Open(file, FileMode.OpenOrCreate), "plant.jpg", msgToSend).ConfigureAwait(false);
} }
plantedFlowers.AddOrUpdate(Context.Channel.Id, new List<IUserMessage>() { msg }, (id, old) => { old.Add(msg); return old; }); plantedFlowers.AddOrUpdate(Context.Channel.Id, new List<IUserMessage>() { msg }, (id, old) => { old.Add(msg); return old; });
} }
#endif
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)] [RequireUserPermission(GuildPermission.ManageMessages)]

View File

@ -7,6 +7,7 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -17,7 +18,7 @@ namespace NadekoBot.Modules.Games
[Group] [Group]
public class PollCommands : ModuleBase public class PollCommands : ModuleBase
{ {
public static ConcurrentDictionary<IGuild, Poll> ActivePolls = new ConcurrentDictionary<IGuild, Poll>(); public static ConcurrentDictionary<ulong, Poll> ActivePolls = new ConcurrentDictionary<ulong, Poll>();
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)] [RequireUserPermission(GuildPermission.ManageMessages)]
@ -31,6 +32,18 @@ namespace NadekoBot.Modules.Games
public Task PublicPoll([Remainder] string arg = null) public Task PublicPoll([Remainder] string arg = null)
=> InternalStartPoll(arg, isPublic: true); => InternalStartPoll(arg, isPublic: true);
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public async Task PollStats()
{
Games.Poll poll;
if (!ActivePolls.TryGetValue(Context.Guild.Id, out poll))
return;
await Context.Channel.EmbedAsync(poll.GetStats("Current Poll Results"));
}
private async Task InternalStartPoll(string arg, bool isPublic = false) private async Task InternalStartPoll(string arg, bool isPublic = false)
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
@ -44,7 +57,7 @@ namespace NadekoBot.Modules.Games
return; return;
var poll = new Poll(Context.Message, data[0], data.Skip(1), isPublic: isPublic); var poll = new Poll(Context.Message, data[0], data.Skip(1), isPublic: isPublic);
if (ActivePolls.TryAdd(channel.Guild, poll)) if (ActivePolls.TryAdd(channel.Guild.Id, poll))
{ {
await poll.StartPoll().ConfigureAwait(false); await poll.StartPoll().ConfigureAwait(false);
} }
@ -60,7 +73,7 @@ namespace NadekoBot.Modules.Games
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
Poll poll; Poll poll;
ActivePolls.TryRemove(channel.Guild, out poll); ActivePolls.TryRemove(channel.Guild.Id, out poll);
await poll.StopPoll().ConfigureAwait(false); await poll.StopPoll().ConfigureAwait(false);
} }
} }
@ -69,20 +82,55 @@ namespace NadekoBot.Modules.Games
{ {
private readonly IUserMessage originalMessage; private readonly IUserMessage originalMessage;
private readonly IGuild guild; private readonly IGuild guild;
private readonly string[] answers; private string[] Answers { get; }
private ConcurrentDictionary<ulong, int> participants = new ConcurrentDictionary<ulong, int>(); private ConcurrentDictionary<ulong, int> participants = new ConcurrentDictionary<ulong, int>();
private readonly string question; private readonly string question;
private DateTime started; private DateTime started;
private CancellationTokenSource pollCancellationSource = new CancellationTokenSource(); private CancellationTokenSource pollCancellationSource = new CancellationTokenSource();
private readonly bool isPublic; public bool IsPublic { get; }
public Poll(IUserMessage umsg, string question, IEnumerable<string> enumerable, bool isPublic = false) public Poll(IUserMessage umsg, string question, IEnumerable<string> enumerable, bool isPublic = false)
{ {
this.originalMessage = umsg; this.originalMessage = umsg;
this.guild = ((ITextChannel)umsg.Channel).Guild; this.guild = ((ITextChannel)umsg.Channel).Guild;
this.question = question; this.question = question;
this.answers = enumerable as string[] ?? enumerable.ToArray(); this.Answers = enumerable as string[] ?? enumerable.ToArray();
this.isPublic = isPublic; this.IsPublic = isPublic;
}
public EmbedBuilder GetStats(string title)
{
var results = participants.GroupBy(kvp => kvp.Value)
.ToDictionary(x => x.Key, x => x.Sum(kvp => 1))
.OrderByDescending(kvp => kvp.Value)
.ToArray();
var eb = new EmbedBuilder().WithTitle(title);
var sb = new StringBuilder()
.AppendLine(Format.Bold(question))
.AppendLine();
var totalVotesCast = 0;
if (results.Length == 0)
{
sb.AppendLine("No votes cast.");
}
else
{
for (int i = 0; i < results.Length; i++)
{
var result = results[i];
sb.AppendLine($"`{i + 1}.` {Format.Bold(Answers[result.Key - 1])} with {Format.Bold(result.Value.ToString())} votes.");
totalVotesCast += result.Value;
}
}
eb.WithDescription(sb.ToString())
.WithFooter(efb => efb.WithText(totalVotesCast + " total votes cast."));
return eb;
} }
public async Task StartPoll() public async Task StartPoll()
@ -91,8 +139,8 @@ namespace NadekoBot.Modules.Games
NadekoBot.Client.MessageReceived += Vote; NadekoBot.Client.MessageReceived += Vote;
var msgToSend = $"📃**{originalMessage.Author.Username}** has created a poll which requires your attention:\n\n**{question}**\n"; var msgToSend = $"📃**{originalMessage.Author.Username}** has created a poll which requires your attention:\n\n**{question}**\n";
var num = 1; var num = 1;
msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n"); msgToSend = Answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n");
if (!isPublic) if (!IsPublic)
msgToSend += "\n**Private Message me with the corresponding number of the answer.**"; msgToSend += "\n**Private Message me with the corresponding number of the answer.**";
else else
msgToSend += "\n**Send a Message here with the corresponding number of the answer.**"; msgToSend += "\n**Send a Message here with the corresponding number of the answer.**";
@ -102,30 +150,7 @@ namespace NadekoBot.Modules.Games
public async Task StopPoll() public async Task StopPoll()
{ {
NadekoBot.Client.MessageReceived -= Vote; NadekoBot.Client.MessageReceived -= Vote;
try await originalMessage.Channel.EmbedAsync(GetStats("POLL CLOSED")).ConfigureAwait(false);
{
var results = participants.GroupBy(kvp => kvp.Value)
.ToDictionary(x => x.Key, x => x.Sum(kvp => 1))
.OrderByDescending(kvp => kvp.Value);
var totalVotesCast = results.Sum(kvp => kvp.Value);
if (totalVotesCast == 0)
{
await originalMessage.Channel.SendMessageAsync("📄 **No votes have been cast.**").ConfigureAwait(false);
return;
}
var closeMessage = $"--------------**POLL CLOSED**--------------\n" +
$"📄 , here are the results:\n";
closeMessage = results.Aggregate(closeMessage, (current, kvp) => current + $"`{kvp.Key}.` **[{answers[kvp.Key - 1]}]**" +
$" has {kvp.Value} votes." +
$"({kvp.Value * 1.0f / totalVotesCast * 100}%)\n");
await originalMessage.Channel.SendConfirmAsync($"📄 **Total votes cast**: {totalVotesCast}\n{closeMessage}").ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine($"Error in poll game {ex}");
}
} }
private async void Vote(SocketMessage imsg) private async void Vote(SocketMessage imsg)
@ -141,11 +166,11 @@ namespace NadekoBot.Modules.Games
int vote; int vote;
if (!int.TryParse(imsg.Content, out vote)) if (!int.TryParse(imsg.Content, out vote))
return; return;
if (vote < 1 || vote > answers.Length) if (vote < 1 || vote > Answers.Length)
return; return;
IMessageChannel ch; IMessageChannel ch;
if (isPublic) if (IsPublic)
{ {
//if public, channel must be the same the poll started in //if public, channel must be the same the poll started in
if (originalMessage.Channel.Id != imsg.Channel.Id) if (originalMessage.Channel.Id != imsg.Channel.Id)
@ -167,7 +192,7 @@ namespace NadekoBot.Modules.Games
//user can vote only once //user can vote only once
if (participants.TryAdd(msg.Author.Id, vote)) if (participants.TryAdd(msg.Author.Id, vote))
{ {
if (!isPublic) if (!IsPublic)
{ {
await ch.SendConfirmAsync($"Thanks for voting **{msg.Author.Username}**.").ConfigureAwait(false); await ch.SendConfirmAsync($"Thanks for voting **{msg.Author.Username}**.").ConfigureAwait(false);
} }

View File

@ -122,17 +122,23 @@ namespace NadekoBot.Modules.Help
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[OwnerOnly] [OwnerOnly]
public Task Hgit() public async Task Hgit()
{ {
var helpstr = new StringBuilder(); var helpstr = new StringBuilder();
helpstr.AppendLine("You can support the project on patreon: <https://patreon.com/nadekobot> or paypal: <https://www.paypal.me/Kwoth>\n"); helpstr.AppendLine("You can support the project on patreon: <https://patreon.com/nadekobot> or paypal: <https://www.paypal.me/Kwoth>\n");
helpstr.AppendLine("##Table Of Contents"); helpstr.AppendLine("##Table Of Contents");
helpstr.AppendLine(string.Join("\n", NadekoBot.CommandService.Modules.Where(m => m.Name.ToLowerInvariant() != "help").OrderBy(m => m.Name).Prepend(NadekoBot.CommandService.Modules.FirstOrDefault(m=>m.Name.ToLowerInvariant()=="help")).Select(m => $"- [{m.Name}](#{m.Name.ToLowerInvariant()})"))); helpstr.AppendLine(string.Join("\n", NadekoBot.CommandService.Modules.Where(m => m.GetTopLevelModule().Name.ToLowerInvariant() != "help")
.Select(m => m.GetTopLevelModule().Name)
.Distinct()
.OrderBy(m => m)
.Prepend("Help")
.Select(m => $"- [{m}](#{m.ToLowerInvariant()})")));
helpstr.AppendLine(); helpstr.AppendLine();
string lastModule = null; string lastModule = null;
foreach (var com in NadekoBot.CommandService.Commands.OrderBy(com => com.Module.Name).GroupBy(c => c.Aliases.First()).Select(g => g.First())) foreach (var com in NadekoBot.CommandService.Commands.OrderBy(com => com.Module.GetTopLevelModule().Name).GroupBy(c => c.Aliases.First()).Select(g => g.First()))
{ {
if (com.Module.Name != lastModule) var module = com.Module.GetTopLevelModule();
if (module.Name != lastModule)
{ {
if (lastModule != null) if (lastModule != null)
{ {
@ -140,16 +146,16 @@ namespace NadekoBot.Modules.Help
helpstr.AppendLine("###### [Back to TOC](#table-of-contents)"); helpstr.AppendLine("###### [Back to TOC](#table-of-contents)");
} }
helpstr.AppendLine(); helpstr.AppendLine();
helpstr.AppendLine("### " + com.Module.Name + " "); helpstr.AppendLine("### " + module.Name + " ");
helpstr.AppendLine("Command and aliases | Description | Usage"); helpstr.AppendLine("Command and aliases | Description | Usage");
helpstr.AppendLine("----------------|--------------|-------"); helpstr.AppendLine("----------------|--------------|-------");
lastModule = com.Module.Name; lastModule = module.Name;
} }
helpstr.AppendLine($"{string.Join(" ", com.Aliases.Select(a => "`" + a + "`"))} | {string.Format(com.Summary, com.Module.GetPrefix())} {GetCommandRequirements(com)} | {string.Format(com.Remarks, com.Module.GetPrefix())}"); helpstr.AppendLine($"{string.Join(" ", com.Aliases.Select(a => "`" + a + "`"))} | {string.Format(com.Summary, com.Module.GetPrefix())} {GetCommandRequirements(com)} | {string.Format(com.Remarks, com.Module.GetPrefix())}");
} }
helpstr = helpstr.Replace(NadekoBot.Client.CurrentUser().Username , "@BotName"); helpstr = helpstr.Replace(NadekoBot.Client.CurrentUser().Username , "@BotName");
File.WriteAllText("../../docs/Commands List.md", helpstr.ToString()); File.WriteAllText("../../docs/Commands List.md", helpstr.ToString());
return Task.CompletedTask; await Context.Channel.SendConfirmAsync("Commandlist Regenerated").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]

View File

@ -77,7 +77,7 @@ namespace NadekoBot.Modules.Music.Classes
public string PrettyVolume => $"🔉 {(int)(Volume * 100)}%"; public string PrettyVolume => $"🔉 {(int)(Volume * 100)}%";
public event Action<Song> SongRemoved = delegate { }; public event Action<Song, int> SongRemoved = delegate { };
public MusicPlayer(IVoiceChannel startingVoiceChannel, float? defaultVolume) public MusicPlayer(IVoiceChannel startingVoiceChannel, float? defaultVolume)
{ {
@ -137,7 +137,7 @@ namespace NadekoBot.Modules.Music.Classes
var index = playlist.IndexOf(CurrentSong); var index = playlist.IndexOf(CurrentSong);
if (index != -1) if (index != -1)
RemoveSongAt(index); RemoveSongAt(index, true);
OnStarted(this, CurrentSong); OnStarted(this, CurrentSong);
await CurrentSong.Play(audioClient, cancelToken); await CurrentSong.Play(audioClient, cancelToken);
@ -273,16 +273,16 @@ namespace NadekoBot.Modules.Music.Classes
}); });
} }
public void RemoveSongAt(int index) public void RemoveSongAt(int index, bool silent = false)
{ {
actionQueue.Enqueue(() => actionQueue.Enqueue(() =>
{ {
if (index < 0 || index >= playlist.Count) if (index < 0 || index >= playlist.Count)
return; return;
var song = playlist.ElementAtOrDefault(index); var song = playlist.ElementAtOrDefault(index);
if (playlist.Remove(song)) if (playlist.Remove(song) && !silent)
{ {
SongRemoved(song); SongRemoved(song, index);
} }
}); });

View File

@ -62,7 +62,15 @@ namespace NadekoBot.Modules.Music.Classes
else if (TotalTime == TimeSpan.MaxValue) else if (TotalTime == TimeSpan.MaxValue)
return "∞"; return "∞";
else else
return TotalTime.ToString(@"mm\:ss"); {
var time = TotalTime.ToString(@"mm\:ss");
var hrs = (int)TotalTime.TotalHours;
if (hrs > 0)
return hrs + ":" + time;
else
return time;
}
} }
} }

View File

@ -99,16 +99,16 @@ namespace NadekoBot.Modules.Music
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Destroy() public Task Destroy()
{ {
await Context.Channel.SendErrorAsync("This command is temporarily disabled.").ConfigureAwait(false); MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) return Task.CompletedTask;
/*MusicPlayer musicPlayer; if (((IGuildUser)Context.User).VoiceChannel == musicPlayer.PlaybackVoiceChannel)
if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) return Task.CompletedTask; if (MusicPlayers.TryRemove(Context.Guild.Id, out musicPlayer))
if (((IGuildUser)umsg.Author).VoiceChannel == musicPlayer.PlaybackVoiceChannel)
if(MusicPlayers.TryRemove(channel.Guild.Id, out musicPlayer))
musicPlayer.Destroy(); musicPlayer.Destroy();
return Task.CompletedTask;*/
return Task.CompletedTask;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -183,36 +183,41 @@ namespace NadekoBot.Modules.Music
try { await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { } try { await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { }
const int itemsPerPage = 10; const int itemsPerPage = 10;
int startAt = itemsPerPage * (page - 1);
var number = 0 + startAt;
var total = musicPlayer.TotalPlaytime; var total = musicPlayer.TotalPlaytime;
var maxPlaytime = musicPlayer.MaxPlaytimeSeconds; var maxPlaytime = musicPlayer.MaxPlaytimeSeconds;
var embed = new EmbedBuilder() var lastPage = musicPlayer.Playlist.Count / itemsPerPage;
.WithAuthor(eab => eab.WithName($"Player Queue - Page {page}") Func<int, EmbedBuilder> printAction = (curPage) =>
.WithMusicIcon()) {
.WithDescription(string.Join("\n", musicPlayer.Playlist int startAt = itemsPerPage * (curPage - 1);
.Skip(startAt) var number = 0 + startAt;
.Take(10) var embed = new EmbedBuilder()
.Select(v => $"`{++number}.` {v.PrettyFullName}"))) .WithAuthor(eab => eab.WithName($"Player Queue")
.WithFooter(ef => ef.WithText($"{musicPlayer.PrettyVolume} | {musicPlayer.Playlist.Count} " + .WithMusicIcon())
$"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {(int)total.TotalHours}h {total.Minutes}m {total.Seconds}s | " + .WithDescription(string.Join("\n", musicPlayer.Playlist
(musicPlayer.FairPlay ? "✔fairplay" : "✖fairplay") + $" | " + (maxPlaytime == 0 ? "unlimited" : $"{maxPlaytime}s limit"))) .Skip(startAt)
.WithOkColor(); .Take(itemsPerPage)
.Select(v => $"`{++number}.` {v.PrettyFullName}")))
.WithFooter(ef => ef.WithText($"{musicPlayer.PrettyVolume} | {musicPlayer.Playlist.Count} " +
$"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {(int)total.TotalHours}h {total.Minutes}m {total.Seconds}s | " +
(musicPlayer.FairPlay ? "✔fairplay" : "✖fairplay") + $" | " + (maxPlaytime == 0 ? "unlimited" : $"{maxPlaytime}s limit")))
.WithOkColor();
if (musicPlayer.RepeatSong) if (musicPlayer.RepeatSong)
{ {
embed.WithTitle($"🔂 Repeating Song: {currentSong.SongInfo.Title} | {currentSong.PrettyFullTime}"); embed.WithTitle($"🔂 Repeating Song: {currentSong.SongInfo.Title} | {currentSong.PrettyFullTime}");
} }
else if (musicPlayer.RepeatPlaylist) else if (musicPlayer.RepeatPlaylist)
{ {
embed.WithTitle("🔁 Repeating Playlist"); embed.WithTitle("🔁 Repeating Playlist");
} }
if (musicPlayer.MaxQueueSize != 0 && musicPlayer.Playlist.Count >= musicPlayer.MaxQueueSize) if (musicPlayer.MaxQueueSize != 0 && musicPlayer.Playlist.Count >= musicPlayer.MaxQueueSize)
{ {
embed.WithTitle("🎵 Song queue is full!"); embed.WithTitle("🎵 Song queue is full!");
} }
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); return embed;
};
await Context.Channel.SendPaginatedConfirmAsync(page, printAction, lastPage).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -300,7 +305,7 @@ $"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {(int)total.TotalHours}h {tota
return; return;
if (((IGuildUser)Context.User).VoiceChannel?.Guild != Context.Guild) if (((IGuildUser)Context.User).VoiceChannel?.Guild != Context.Guild)
{ {
await Context.Channel.SendErrorAsync("💢 You need to be in a **voice channel** on this server.\n If you are already in a voice (ITextChannel)Context.Channel, try rejoining it.").ConfigureAwait(false); await Context.Channel.SendErrorAsync($"💢 You need to be in a **voice channel** on this server.").ConfigureAwait(false);
return; return;
} }
var plId = (await NadekoBot.Google.GetPlaylistIdsByKeywordsAsync(arg).ConfigureAwait(false)).FirstOrDefault(); var plId = (await NadekoBot.Google.GetPlaylistIdsByKeywordsAsync(arg).ConfigureAwait(false)).FirstOrDefault();
@ -458,31 +463,16 @@ $"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {(int)total.TotalHours}h {tota
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[Priority(0)] [Priority(0)]
public async Task Remove(int num) public Task Remove(int num)
{ {
MusicPlayer musicPlayer; MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
return; return Task.CompletedTask;
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel) if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return; return Task.CompletedTask;
musicPlayer.SongRemoved += async (song) =>
{
try
{
var embed = new EmbedBuilder()
.WithAuthor(eab => eab.WithName("Removed song #" + num).WithMusicIcon())
.WithDescription(song.PrettyName)
.WithFooter(ef => ef.WithText(song.PrettyInfo))
.WithErrorColor();
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
catch { }
};
musicPlayer.RemoveSongAt(num - 1); musicPlayer.RemoveSongAt(num - 1);
return Task.CompletedTask;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -490,8 +480,6 @@ $"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {(int)total.TotalHours}h {tota
[Priority(1)] [Priority(1)]
public async Task Remove(string all) public async Task Remove(string all)
{ {
if (all.Trim().ToUpperInvariant() != "ALL") if (all.Trim().ToUpperInvariant() != "ALL")
return; return;
MusicPlayer musicPlayer; MusicPlayer musicPlayer;
@ -857,13 +845,34 @@ $"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {(int)total.TotalHours}h {tota
{ {
try try
{ {
IUserMessage msg;
if (paused) if (paused)
await textCh.SendConfirmAsync("🎵 Music playback **paused**.").ConfigureAwait(false); msg = await textCh.SendConfirmAsync("🎵 Music playback **paused**.").ConfigureAwait(false);
else else
await textCh.SendConfirmAsync("🎵 Music playback **resumed**.").ConfigureAwait(false); msg = await textCh.SendConfirmAsync("🎵 Music playback **resumed**.").ConfigureAwait(false);
if (msg != null)
msg.DeleteAfter(10);
} }
catch { } catch { }
}; };
mp.SongRemoved += async (song, index) =>
{
try
{
var embed = new EmbedBuilder()
.WithAuthor(eab => eab.WithName("Removed song #" + (index + 1)).WithMusicIcon())
.WithDescription(song.PrettyName)
.WithFooter(ef => ef.WithText(song.PrettyInfo))
.WithErrorColor();
await textCh.EmbedAsync(embed).ConfigureAwait(false);
}
catch { }
};
return mp; return mp;
}); });
Song resolvedSong; Song resolvedSong;

View File

@ -49,7 +49,7 @@ namespace NadekoBot.Modules.NSFW
var link = await provider.ConfigureAwait(false); var link = await provider.ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(link)) if (string.IsNullOrWhiteSpace(link))
{ {
if (noError) if (!noError)
await channel.SendErrorAsync("No results found.").ConfigureAwait(false); await channel.SendErrorAsync("No results found.").ConfigureAwait(false);
return; return;
} }

View File

@ -23,13 +23,18 @@ namespace NadekoBot.Modules.Permissions
[Group] [Group]
public class BlacklistCommands : ModuleBase public class BlacklistCommands : ModuleBase
{ {
public static ConcurrentHashSet<BlacklistItem> BlacklistedItems { get; set; } = new ConcurrentHashSet<BlacklistItem>(); public static ConcurrentHashSet<ulong> BlacklistedUsers { get; set; } = new ConcurrentHashSet<ulong>();
public static ConcurrentHashSet<ulong> BlacklistedGuilds { get; set; } = new ConcurrentHashSet<ulong>();
public static ConcurrentHashSet<ulong> BlacklistedChannels { get; set; } = new ConcurrentHashSet<ulong>();
static BlacklistCommands() static BlacklistCommands()
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
BlacklistedItems = new ConcurrentHashSet<BlacklistItem>(uow.BotConfig.GetOrCreate().Blacklist); var blacklist = uow.BotConfig.GetOrCreate().Blacklist;
BlacklistedUsers = new ConcurrentHashSet<ulong>(blacklist.Where(bi => bi.Type == BlacklistType.User).Select(c => c.ItemId));
BlacklistedGuilds = new ConcurrentHashSet<ulong>(blacklist.Where(bi => bi.Type == BlacklistType.Server).Select(c => c.ItemId));
BlacklistedChannels = new ConcurrentHashSet<ulong>(blacklist.Where(bi => bi.Type == BlacklistType.Channel).Select(c => c.ItemId));
} }
} }
@ -66,12 +71,34 @@ namespace NadekoBot.Modules.Permissions
{ {
var item = new BlacklistItem { ItemId = id, Type = type }; var item = new BlacklistItem { ItemId = id, Type = type };
uow.BotConfig.GetOrCreate().Blacklist.Add(item); uow.BotConfig.GetOrCreate().Blacklist.Add(item);
BlacklistedItems.Add(item); if (type == BlacklistType.Server)
{
BlacklistedGuilds.Add(id);
}
else if (type == BlacklistType.Channel)
{
BlacklistedChannels.Add(id);
}
else if (type == BlacklistType.User)
{
BlacklistedUsers.Add(id);
}
} }
else else
{ {
uow.BotConfig.GetOrCreate().Blacklist.RemoveWhere(bi => bi.ItemId == id && bi.Type == type); uow.BotConfig.GetOrCreate().Blacklist.RemoveWhere(bi => bi.ItemId == id && bi.Type == type);
BlacklistedItems.RemoveWhere(bi => bi.ItemId == id && bi.Type == type); if (type == BlacklistType.Server)
{
BlacklistedGuilds.TryRemove(id);
}
else if (type == BlacklistType.Channel)
{
BlacklistedChannels.TryRemove(id);
}
else if (type == BlacklistType.User)
{
BlacklistedUsers.TryRemove(id);
}
} }
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }

View File

@ -73,6 +73,9 @@ namespace NadekoBot.Modules.Permissions
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task PermRole([Remainder] IRole role = null) public async Task PermRole([Remainder] IRole role = null)
{ {
if (role != null && role == role.Guild.EveryoneRole)
return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set); var config = uow.GuildConfigs.For(Context.Guild.Id, set => set);
@ -379,6 +382,9 @@ namespace NadekoBot.Modules.Permissions
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task RoleCmd(CommandInfo command, PermissionAction action, [Remainder] IRole role) public async Task RoleCmd(CommandInfo command, PermissionAction action, [Remainder] IRole role)
{ {
if (role == role.Guild.EveryoneRole)
return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var newPerm = new Permission var newPerm = new Permission
@ -405,6 +411,9 @@ namespace NadekoBot.Modules.Permissions
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task RoleMdl(ModuleInfo module, PermissionAction action, [Remainder] IRole role) public async Task RoleMdl(ModuleInfo module, PermissionAction action, [Remainder] IRole role)
{ {
if (role == role.Guild.EveryoneRole)
return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var newPerm = new Permission var newPerm = new Permission
@ -515,6 +524,9 @@ namespace NadekoBot.Modules.Permissions
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task AllRoleMdls(PermissionAction action, [Remainder] IRole role) public async Task AllRoleMdls(PermissionAction action, [Remainder] IRole role)
{ {
if (role == role.Guild.EveryoneRole)
return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var newPerm = new Permission var newPerm = new Permission

View File

@ -23,8 +23,8 @@ namespace NadekoBot.Modules.Searches.Commands.Models
public class Main public class Main
{ {
public double temp { get; set; } public double temp { get; set; }
public int pressure { get; set; } public float pressure { get; set; }
public int humidity { get; set; } public float humidity { get; set; }
public double temp_min { get; set; } public double temp_min { get; set; }
public double temp_max { get; set; } public double temp_max { get; set; }
} }

View File

@ -18,7 +18,8 @@ namespace NadekoBot.Modules.Searches.Commands.OMDB
{ {
var res = await http.GetStringAsync(String.Format(queryUrl,name.Trim().Replace(' ','+'))).ConfigureAwait(false); var res = await http.GetStringAsync(String.Format(queryUrl,name.Trim().Replace(' ','+'))).ConfigureAwait(false);
var movie = JsonConvert.DeserializeObject<OmdbMovie>(res); var movie = JsonConvert.DeserializeObject<OmdbMovie>(res);
if (movie?.Title == null)
return null;
movie.Poster = await NadekoBot.Google.ShortenUrl(movie.Poster); movie.Poster = await NadekoBot.Google.ShortenUrl(movie.Poster);
return movie; return movie;
} }

View File

@ -109,61 +109,75 @@ namespace NadekoBot.Modules.Searches
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task I([Remainder] string query = null) public async Task Image([Remainder] string terms = null)
{ {
if (string.IsNullOrWhiteSpace(query)) terms = terms?.Trim();
if (string.IsNullOrWhiteSpace(terms))
return; return;
try
{ terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
using (var http = new HttpClient())
{ var fullQueryLink = $"http://imgur.com/search?q={ terms }";
var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(query)}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&fields=items%2Flink&key={NadekoBot.Credentials.GoogleApiKey}"; var config = Configuration.Default.WithDefaultLoader();
var obj = JObject.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false)); var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
await Context.Channel.SendMessageAsync(obj["items"][0]["link"].ToString()).ConfigureAwait(false);
} var elems = document.QuerySelectorAll("a.image-list-link");
}
catch (HttpRequestException exception) if (!elems.Any())
{ return;
if (exception.Message.Contains("403 (Forbidden)"))
{ var img = (elems.FirstOrDefault()?.Children?.FirstOrDefault() as IHtmlImageElement);
await Context.Channel.SendErrorAsync("Daily limit reached!");
} if (img?.Source == null)
else return;
{
await Context.Channel.SendErrorAsync("Something went wrong."); var source = img.Source.Replace("b.", ".");
_log.Error(exception);
} var embed = new EmbedBuilder()
} .WithOkColor()
.WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50))
.WithUrl(fullQueryLink)
.WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?"))
.WithDescription(source)
.WithImageUrl(source)
.WithTitle(Context.User.Mention);
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Ir([Remainder] string query = null) public async Task RandomImage([Remainder] string terms = null)
{ {
if (string.IsNullOrWhiteSpace(query)) terms = terms?.Trim();
if (string.IsNullOrWhiteSpace(terms))
return; return;
try
{ terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
using (var http = new HttpClient())
{ var fullQueryLink = $"http://imgur.com/search?q={ terms }";
var rng = new NadekoRandom(); var config = Configuration.Default.WithDefaultLoader();
var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(query)}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&start={ rng.Next(1, 50) }&fields=items%2Flink&key={NadekoBot.Credentials.GoogleApiKey}"; var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
var obj = JObject.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false));
var items = obj["items"] as JArray; var elems = document.QuerySelectorAll("a.image-list-link").ToList();
await Context.Channel.SendMessageAsync(items[0]["link"].ToString()).ConfigureAwait(false);
} if (!elems.Any())
} return;
catch (HttpRequestException exception)
{ var img = (elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Count))?.Children?.FirstOrDefault() as IHtmlImageElement);
if (exception.Message.Contains("403 (Forbidden)"))
{ if (img?.Source == null)
await Context.Channel.SendErrorAsync("Daily limit reached!"); return;
}
else var source = img.Source.Replace("b.", ".");
{
await Context.Channel.SendErrorAsync("Something went wrong."); var embed = new EmbedBuilder()
_log.Error(exception); .WithOkColor()
} .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50))
} .WithUrl(fullQueryLink)
.WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?"))
.WithDescription(source)
.WithImageUrl(source)
.WithTitle(Context.User.Mention);
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -568,14 +582,10 @@ namespace NadekoBot.Modules.Searches
public async Task Color([Remainder] string color = null) public async Task Color([Remainder] string color = null)
{ {
color = color?.Trim().Replace("#", ""); color = color?.Trim().Replace("#", "");
if (string.IsNullOrWhiteSpace((string)color)) if (string.IsNullOrWhiteSpace(color))
return; return;
var img = new ImageSharp.Image(50, 50); var img = new ImageSharp.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);
img.BackgroundColor(new ImageSharp.Color(color)); img.BackgroundColor(new ImageSharp.Color(color));
await Context.Channel.SendFileAsync(img.ToStream(), $"{color}.png"); await Context.Channel.SendFileAsync(img.ToStream(), $"{color}.png");

View File

@ -9,9 +9,9 @@ using System.Collections.Concurrent;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Utility
{ {
public partial class Administration public partial class Utility
{ {
[Group] [Group]
public class CrossServerTextChannel : ModuleBase public class CrossServerTextChannel : ModuleBase

View File

@ -44,7 +44,7 @@ namespace NadekoBot.Modules.Utility
.AddField(fb => fb.WithName("**Voice Channels**").WithValue(voicechn.ToString()).WithIsInline(true)) .AddField(fb => fb.WithName("**Voice Channels**").WithValue(voicechn.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Created At**").WithValue($"{createdAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) .AddField(fb => fb.WithName("**Created At**").WithValue($"{createdAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true))
.AddField(fb => fb.WithName("**Region**").WithValue(guild.VoiceRegionId.ToString()).WithIsInline(true)) .AddField(fb => fb.WithName("**Region**").WithValue(guild.VoiceRegionId.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Roles**").WithValue(guild.Roles.Count().ToString()).WithIsInline(true)) .AddField(fb => fb.WithName("**Roles**").WithValue((guild.Roles.Count - 1).ToString()).WithIsInline(true))
.WithImageUrl(guild.IconUrl) .WithImageUrl(guild.IconUrl)
.WithColor(NadekoBot.OkColor); .WithColor(NadekoBot.OkColor);
if (guild.Emojis.Count() > 0) if (guild.Emojis.Count() > 0)
@ -92,8 +92,7 @@ namespace NadekoBot.Modules.Utility
embed.AddField(fb => fb.WithName("**ID**").WithValue(user.Id.ToString()).WithIsInline(true)) embed.AddField(fb => fb.WithName("**ID**").WithValue(user.Id.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Joined Server**").WithValue($"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) .AddField(fb => fb.WithName("**Joined Server**").WithValue($"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true))
.AddField(fb => fb.WithName("**Joined Discord**").WithValue($"{user.CreatedAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) .AddField(fb => fb.WithName("**Joined Discord**").WithValue($"{user.CreatedAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true))
.AddField(fb => fb.WithName("**Current Game**").WithValue($"{(user.Game?.Name == null ? "-" : user.Game.Value.Name)}").WithIsInline(true)) .AddField(fb => fb.WithName("**Roles**").WithValue($"**({user.RoleIds.Count})** - {string.Join(", ", user.GetRoles().Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true))
.AddField(fb => fb.WithName("**Roles**").WithValue($"**({user.RoleIds.Count})** - {string.Join(", ", user.GetRoles().Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true))
.WithThumbnailUrl(user.AvatarUrl) .WithThumbnailUrl(user.AvatarUrl)
.WithColor(NadekoBot.OkColor); .WithColor(NadekoBot.OkColor);
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -103,7 +102,7 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[OwnerOnly] [OwnerOnly]
public async Task Activity(IUserMessage imsg, int page = 1) public async Task Activity(int page = 1)
{ {
const int activityPerPage = 15; const int activityPerPage = 15;
page -= 1; page -= 1;
@ -119,7 +118,7 @@ namespace NadekoBot.Modules.Utility
str.AppendLine($"`{++startCount}.` **{kvp.Key}** [{kvp.Value/NadekoBot.Stats.GetUptime().TotalSeconds:F2}/s] - {kvp.Value} total"); str.AppendLine($"`{++startCount}.` **{kvp.Key}** [{kvp.Value/NadekoBot.Stats.GetUptime().TotalSeconds:F2}/s] - {kvp.Value} total");
} }
await imsg.Channel.EmbedAsync(new EmbedBuilder().WithTitle($"Activity Page #{page}") await Context.Channel.EmbedAsync(new EmbedBuilder().WithTitle($"Activity Page #{page}")
.WithOkColor() .WithOkColor()
.WithFooter(efb => efb.WithText($"{NadekoBot.CommandHandler.UserMessagesSent.Count} users total.")) .WithFooter(efb => efb.WithText($"{NadekoBot.CommandHandler.UserMessagesSent.Count} users total."))
.WithDescription(str.ToString())); .WithDescription(str.ToString()));

View File

@ -0,0 +1,238 @@
using Discord;
using Discord.Commands;
using Microsoft.EntityFrameworkCore;
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;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Utility
{
public partial class Utility
{
[Group]
public class RepeatCommands : ModuleBase
{
//guildid/RepeatRunners
public static ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> repeaters { get; }
public class RepeatRunner
{
private Logger _log { get; }
private CancellationTokenSource source { get; set; }
private CancellationToken token { get; set; }
public Repeater Repeater { get; }
public ITextChannel Channel { get; }
public RepeatRunner(Repeater repeater, ITextChannel channel = null)
{
_log = LogManager.GetCurrentClassLogger();
this.Repeater = repeater;
this.Channel = channel ?? NadekoBot.Client.GetGuild(repeater.GuildId)?.GetTextChannelAsync(repeater.ChannelId).GetAwaiter().GetResult();
if (Channel == null)
return;
Task.Run(Run);
}
private async Task Run()
{
source = new CancellationTokenSource();
token = source.Token;
IUserMessage oldMsg = null;
try
{
while (!token.IsCancellationRequested)
{
var toSend = "🔄 " + Repeater.Message;
await Task.Delay(Repeater.Interval, token).ConfigureAwait(false);
//var lastMsgInChannel = (await Channel.GetMessagesAsync(2)).FirstOrDefault();
// if (lastMsgInChannel.Id == oldMsg?.Id) //don't send if it's the same message in the channel
// continue;
if (oldMsg != null)
try { await oldMsg.DeleteAsync(); } catch { }
try { oldMsg = await Channel.SendMessageAsync(toSend).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
}
}
catch (OperationCanceledException) { }
}
public void Reset()
{
source.Cancel();
var t = Task.Run(Run);
}
public void Stop()
{
source.Cancel();
}
public override string ToString()
{
return $"{this.Channel.Mention} | {(int)this.Repeater.Interval.TotalHours}:{this.Repeater.Interval:mm} | {this.Repeater.Message.TrimTo(33)}";
}
}
static RepeatCommands()
{
var _log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
repeaters = new ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>>(NadekoBot.AllGuildConfigs
.ToDictionary(gc => gc.GuildId,
gc => new ConcurrentQueue<RepeatRunner>(gc.GuildRepeaters.Select(gr => new RepeatRunner(gr))
.Where(gr => gr.Channel != null))));
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task RepeatInvoke(int index)
{
index -= 1;
ConcurrentQueue<RepeatRunner> rep;
if (!repeaters.TryGetValue(Context.Guild.Id, out rep))
{
await Context.Channel.SendErrorAsync(" **No repeating message found on this server.**").ConfigureAwait(false);
return;
}
var repList = rep.ToList();
if (index >= repList.Count)
{
await Context.Channel.SendErrorAsync("Index out of range.").ConfigureAwait(false);
return;
}
var repeater = repList[index].Repeater;
await Context.Channel.SendMessageAsync("🔄 " + repeater.Message).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
[Priority(0)]
public async Task RepeatRemove(int index)
{
if (index < 1)
return;
index -= 1;
ConcurrentQueue<RepeatRunner> rep;
if (!repeaters.TryGetValue(Context.Guild.Id, out rep))
return;
var repeaterList = rep.ToList();
if (index >= repeaterList.Count)
{
await Context.Channel.SendErrorAsync("Index out of range.").ConfigureAwait(false);
return;
}
var repeater = repeaterList[index];
repeater.Stop();
repeaterList.RemoveAt(index);
using (var uow = DbHandler.UnitOfWork())
{
var guildConfig = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(gc => gc.GuildRepeaters));
guildConfig.GuildRepeaters.RemoveWhere(r => r.Id == repeater.Repeater.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
if (repeaters.TryUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(repeaterList), rep))
await Context.Channel.SendConfirmAsync("Message Repeater",$"#{index+1} stopped.\n\n{repeater.ToString()}").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
[Priority(1)]
public async Task Repeat(int minutes, [Remainder] string message)
{
if (minutes < 1 || minutes > 10080)
return;
if (string.IsNullOrWhiteSpace(message))
return;
var toAdd = new GuildRepeater()
{
ChannelId = Context.Channel.Id,
GuildId = Context.Guild.Id,
Interval = TimeSpan.FromMinutes(minutes),
Message = message
};
using (var uow = DbHandler.UnitOfWork())
{
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.GuildRepeaters));
if (gc.GuildRepeaters.Count >= 5)
return;
gc.GuildRepeaters.Add(toAdd);
await uow.CompleteAsync().ConfigureAwait(false);
}
var rep = new RepeatRunner(toAdd, (ITextChannel)Context.Channel);
repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] { rep }), (key, old) =>
{
old.Enqueue(rep);
return old;
});
await Context.Channel.SendConfirmAsync($"🔁 Repeating **\"{rep.Repeater.Message}\"** every `{rep.Repeater.Interval.Days} day(s), {rep.Repeater.Interval.Hours} hour(s) and {rep.Repeater.Interval.Minutes} minute(s)`.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task RepeatList()
{
ConcurrentQueue<RepeatRunner> repRunners;
if (!repeaters.TryGetValue(Context.Guild.Id, out repRunners))
{
await Context.Channel.SendConfirmAsync("No repeaters running on this server.").ConfigureAwait(false);
return;
}
var replist = repRunners.ToList();
var sb = new StringBuilder();
for (int i = 0; i < replist.Count; i++)
{
var rep = replist[i];
sb.AppendLine($"`{i + 1}.` {rep.ToString()}");
}
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle("List Of Repeaters")
.WithDescription(sb.ToString()))
.ConfigureAwait(false);
}
}
}
}

View File

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Utility namespace NadekoBot.Modules.Utility

View File

@ -10,12 +10,84 @@ using System.Text.RegularExpressions;
using System.Reflection; using System.Reflection;
using NadekoBot.Services.Impl; using NadekoBot.Services.Impl;
using System.Net.Http; using System.Net.Http;
using System.Collections.Concurrent;
using System.Threading;
using ImageSharp;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NadekoBot.Modules.Utility namespace NadekoBot.Modules.Utility
{ {
[NadekoModule("Utility", ".")] [NadekoModule("Utility", ".")]
public partial class Utility : DiscordModule public partial class Utility : DiscordModule
{ {
private static ConcurrentDictionary<ulong, Timer> rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>();
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task RotateRoleColor(int timeout, IRole role, params string[] hexes)
{
var channel = (ITextChannel)Context.Channel;
if ((timeout < 60 && timeout != 0) || timeout > 3600)
return;
Timer t;
if (timeout == 0 || hexes.Length == 0)
{
if (rotatingRoleColors.TryRemove(role.Id, out t))
{
t.Change(Timeout.Infinite, Timeout.Infinite);
await channel.SendConfirmAsync($"Stopped rotating colors for the **{role.Name}** role").ConfigureAwait(false);
}
return;
}
var hexColors = hexes.Select(hex =>
{
try { return (ImageSharp.Color?)new ImageSharp.Color(hex.Replace("#", "")); } catch { return null; }
})
.Where(c => c != null)
.Select(c => c.Value)
.ToArray();
if (!hexColors.Any())
{
await channel.SendMessageAsync("No colors are in the correct format. Use `#00ff00` for example.").ConfigureAwait(false);
return;
}
var images = hexColors.Select(color =>
{
var img = new ImageSharp.Image(50, 50);
img.BackgroundColor(color);
return img;
}).Merge().ToStream();
var i = 0;
t = new Timer(async (_) =>
{
try
{
var color = hexColors[i];
await role.ModifyAsync(r => r.Color = new Discord.Color(color.R, color.G, color.B)).ConfigureAwait(false);
++i;
if (i >= hexColors.Length)
i = 0;
}
catch { }
}, null, 0, timeout * 1000);
rotatingRoleColors.AddOrUpdate(role.Id, t, (key, old) =>
{
old.Change(Timeout.Infinite, Timeout.Infinite);
return t;
});
await channel.SendFileAsync(images, "magicalgirl.jpg", $"Rotating **{role.Name}** role's color.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task TogetherTube() public async Task TogetherTube()
{ {
@ -170,15 +242,27 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task ChannelTopic() public async Task ChannelTopic([Remainder]ITextChannel channel = null)
{ {
var channel = (ITextChannel)Context.Channel; if (channel == null)
channel = (ITextChannel)Context.Channel;
var topic = channel.Topic; var topic = channel.Topic;
if (string.IsNullOrWhiteSpace(topic)) if (string.IsNullOrWhiteSpace(topic))
await channel.SendErrorAsync("No topic set."); await Context.Channel.SendErrorAsync("No topic set.").ConfigureAwait(false);
else else
await channel.SendConfirmAsync("Channel topic", topic); await Context.Channel.SendConfirmAsync("Channel topic", topic).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireBotPermission(ChannelPermission.CreateInstantInvite)]
[RequireUserPermission(ChannelPermission.CreateInstantInvite)]
public async Task CreateInvite()
{
var invite = await ((ITextChannel)Context.Channel).CreateInviteAsync(0, null, isUnique: true);
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} https://discord.gg/{invite.Code}");
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -199,7 +283,7 @@ namespace NadekoBot.Modules.Utility
.AddField(efb => efb.WithName(Format.Bold("Memory")).WithValue($"{stats.Heap} MB").WithIsInline(true)) .AddField(efb => efb.WithName(Format.Bold("Memory")).WithValue($"{stats.Heap} MB").WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Owner ID(s)")).WithValue(stats.OwnerIds).WithIsInline(true)) .AddField(efb => efb.WithName(Format.Bold("Owner ID(s)")).WithValue(stats.OwnerIds).WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Uptime")).WithValue(stats.GetUptimeString("\n")).WithIsInline(true)) .AddField(efb => efb.WithName(Format.Bold("Uptime")).WithValue(stats.GetUptimeString("\n")).WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Presence")).WithValue($"{NadekoBot.Client.GetGuilds().Count} Servers\n{stats.TextChannels} Text Channels\n{stats.VoiceChannels} Voice Channels").WithIsInline(true)) .AddField(efb => efb.WithName(Format.Bold("Presence")).WithValue($"{NadekoBot.Client.GetGuildsCount()} Servers\n{stats.TextChannels} Text Channels\n{stats.VoiceChannels} Voice Channels").WithIsInline(true))
#if !GLOBAL_NADEKO #if !GLOBAL_NADEKO
.WithFooter(efb => efb.WithText($"Playing {Music.Music.MusicPlayers.Where(mp => mp.Value.CurrentSong != null).Count()} songs, {Music.Music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count)} queued.")) .WithFooter(efb => efb.WithText($"Playing {Music.Music.MusicPlayers.Where(mp => mp.Value.CurrentSong != null).Count()} songs, {Music.Music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count)} queued."))
#endif #endif
@ -230,7 +314,7 @@ namespace NadekoBot.Modules.Utility
if (page < 0) if (page < 0)
return; return;
var guilds = NadekoBot.Client.GetGuilds().OrderBy(g => g.Name).Skip((page - 1) * 15).Take(15); var guilds = await Task.Run(() => NadekoBot.Client.GetGuilds().OrderBy(g => g.Name).Skip((page - 1) * 15).Take(15)).ConfigureAwait(false);
if (!guilds.Any()) if (!guilds.Any())
{ {
@ -244,5 +328,22 @@ namespace NadekoBot.Modules.Utility
.WithIsInline(false)))) .WithIsInline(false))))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task SaveChat(int cnt)
{
var sb = new StringBuilder();
var msgs = new List<IMessage>(cnt);
await Context.Channel.GetMessagesAsync(cnt).ForEachAsync(dled => msgs.AddRange(dled)).ConfigureAwait(false);
var title = $"Chatlog-{Context.Guild.Name}/#{Context.Channel.Name}-{DateTime.Now}.txt";
var grouping = msgs.GroupBy(x => $"{x.CreatedAt.Date:dd.MM.yyyy}")
.Select(g => new { date = g.Key, messages = g.OrderBy(x => x.CreatedAt).Select(s => $"【{s.Timestamp:HH:mm:ss}】{s.Author}:" + s.ToString()) });
await Context.User.SendFileAsync(
await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream().ConfigureAwait(false), title, title).ConfigureAwait(false);
}
} }
} }

View File

@ -91,7 +91,9 @@ namespace NadekoBot
//connect //connect
await Client.LoginAsync(TokenType.Bot, Credentials.Token).ConfigureAwait(false); await Client.LoginAsync(TokenType.Bot, Credentials.Token).ConfigureAwait(false);
await Client.ConnectAsync().ConfigureAwait(false); await Client.ConnectAsync().ConfigureAwait(false);
//await Client.DownloadAllUsersAsync().ConfigureAwait(false); #if !GLOBAL_NADEKO
await Client.DownloadAllUsersAsync().ConfigureAwait(false);
#endif
_log.Info("Connected"); _log.Info("Connected");

View File

@ -437,6 +437,33 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to antilist antilst.
/// </summary>
public static string antilist_cmd {
get {
return ResourceManager.GetString("antilist_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shows currently enabled protection features..
/// </summary>
public static string antilist_desc {
get {
return ResourceManager.GetString("antilist_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}antilist`.
/// </summary>
public static string antilist_usage {
get {
return ResourceManager.GetString("antilist_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to antiraid. /// Looks up a localized string similar to antiraid.
/// </summary> /// </summary>
@ -491,6 +518,33 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to antispamignore.
/// </summary>
public static string antispamignore_cmd {
get {
return ResourceManager.GetString("antispamignore_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Toggles whether antispam ignores current channel. Antispam must be enabled..
/// </summary>
public static string antispamignore_desc {
get {
return ResourceManager.GetString("antispamignore_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}antispamignore`.
/// </summary>
public static string antispamignore_usage {
get {
return ResourceManager.GetString("antispamignore_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to asar. /// Looks up a localized string similar to asar.
/// </summary> /// </summary>
@ -1841,6 +1895,33 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to createinvite crinv.
/// </summary>
public static string createinvite_cmd {
get {
return ResourceManager.GetString("createinvite_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Creates a new invite which has infinite max uses and never expires..
/// </summary>
public static string createinvite_desc {
get {
return ResourceManager.GetString("createinvite_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}crinv`.
/// </summary>
public static string createinvite_usage {
get {
return ResourceManager.GetString("createinvite_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to createrole cr. /// Looks up a localized string similar to createrole cr.
/// </summary> /// </summary>
@ -3245,33 +3326,6 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to img i.
/// </summary>
public static string i_cmd {
get {
return ResourceManager.GetString("i_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Pulls the first image found using a search parameter. Use {0}ir for different results..
/// </summary>
public static string i_desc {
get {
return ResourceManager.GetString("i_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}i cute kitten`.
/// </summary>
public static string i_usage {
get {
return ResourceManager.GetString("i_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to iam. /// Looks up a localized string similar to iam.
/// </summary> /// </summary>
@ -3326,6 +3380,33 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to image img.
/// </summary>
public static string image_cmd {
get {
return ResourceManager.GetString("image_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Pulls the first image found using a search parameter. Use {0}rimg for different results..
/// </summary>
public static string image_desc {
get {
return ResourceManager.GetString("image_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}img cute kitten`.
/// </summary>
public static string image_usage {
get {
return ResourceManager.GetString("image_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to imdb omdb. /// Looks up a localized string similar to imdb omdb.
/// </summary> /// </summary>
@ -3380,33 +3461,6 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to ir.
/// </summary>
public static string ir_cmd {
get {
return ResourceManager.GetString("ir_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Pulls a random image using a search parameter..
/// </summary>
public static string ir_desc {
get {
return ResourceManager.GetString("ir_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}ir cute kitten`.
/// </summary>
public static string ir_usage {
get {
return ResourceManager.GetString("ir_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to jcsc. /// Looks up a localized string similar to jcsc.
/// </summary> /// </summary>
@ -5162,6 +5216,33 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to pollstats.
/// </summary>
public static string pollstats_cmd {
get {
return ResourceManager.GetString("pollstats_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shows the poll results without stopping the poll on this server..
/// </summary>
public static string pollstats_desc {
get {
return ResourceManager.GetString("pollstats_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}pollstats`.
/// </summary>
public static string pollstats_usage {
get {
return ResourceManager.GetString("pollstats_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to prune clr. /// Looks up a localized string similar to prune clr.
/// </summary> /// </summary>
@ -5405,6 +5486,33 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to randomimage rimg.
/// </summary>
public static string randomimage_cmd {
get {
return ResourceManager.GetString("randomimage_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Pulls a random image using a search parameter..
/// </summary>
public static string randomimage_desc {
get {
return ResourceManager.GetString("randomimage_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}rimg cute kitten`.
/// </summary>
public static string randomimage_usage {
get {
return ResourceManager.GetString("randomimage_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to remind. /// Looks up a localized string similar to remind.
/// </summary> /// </summary>
@ -5658,7 +5766,7 @@ namespace NadekoBot.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Repeat a message every X minutes. If no parameters are specified, repeat is disabled.. /// Looks up a localized string similar to Repeat a message every X minutes in the current channel. You can have up to 5 repeating messages on the server in total..
/// </summary> /// </summary>
public static string repeat_desc { public static string repeat_desc {
get { get {
@ -5685,7 +5793,7 @@ namespace NadekoBot.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Immediately shows the repeat message and restarts the timer.. /// Looks up a localized string similar to Immediately shows the repeat message on a certain index and restarts its timer..
/// </summary> /// </summary>
public static string repeatinvoke_desc { public static string repeatinvoke_desc {
get { get {
@ -5694,7 +5802,7 @@ namespace NadekoBot.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to `{0}repinv`. /// Looks up a localized string similar to `{0}repinv 1`.
/// </summary> /// </summary>
public static string repeatinvoke_usage { public static string repeatinvoke_usage {
get { get {
@ -5702,6 +5810,33 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to repeatlist replst.
/// </summary>
public static string repeatlist_cmd {
get {
return ResourceManager.GetString("repeatlist_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shows currently repeating messages and their indexes..
/// </summary>
public static string repeatlist_desc {
get {
return ResourceManager.GetString("repeatlist_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}repeatlist`.
/// </summary>
public static string repeatlist_usage {
get {
return ResourceManager.GetString("repeatlist_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to rpeatplaylst rpl. /// Looks up a localized string similar to rpeatplaylst rpl.
/// </summary> /// </summary>
@ -5729,6 +5864,33 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to repeatremove reprm.
/// </summary>
public static string repeatremove_cmd {
get {
return ResourceManager.GetString("repeatremove_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Removes a repeating message on a specified index. Use `{0}repeatlist` to see indexes..
/// </summary>
public static string repeatremove_desc {
get {
return ResourceManager.GetString("repeatremove_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}reprm 2`.
/// </summary>
public static string repeatremove_usage {
get {
return ResourceManager.GetString("repeatremove_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to reptcursong rcs. /// Looks up a localized string similar to reptcursong rcs.
/// </summary> /// </summary>
@ -6053,6 +6215,33 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to rotaterolecolor rrc.
/// </summary>
public static string rotaterolecolor_cmd {
get {
return ResourceManager.GetString("rotaterolecolor_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Rotates a roles color on an interval with a list of supplied colors. First argument is interval in seconds (Minimum 60). Second argument is a role, followed by a space-separated list of colors in hex. Provide a rolename with a 0 interval to disable..
/// </summary>
public static string rotaterolecolor_desc {
get {
return ResourceManager.GetString("rotaterolecolor_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}rrc 60 MyLsdRole #ff0000 #00ff00 #0000ff` or `{0}rrc 0 MyLsdRole`.
/// </summary>
public static string rotaterolecolor_usage {
get {
return ResourceManager.GetString("rotaterolecolor_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to rps. /// Looks up a localized string similar to rps.
/// </summary> /// </summary>
@ -6566,6 +6755,33 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to setstatus.
/// </summary>
public static string setstatus_cmd {
get {
return ResourceManager.GetString("setstatus_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Sets the bot&apos;s status. (Online/Idle/Dnd/Invisible).
/// </summary>
public static string setstatus_desc {
get {
return ResourceManager.GetString("setstatus_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}setstatus Idle`.
/// </summary>
public static string setstatus_usage {
get {
return ResourceManager.GetString("setstatus_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to setstream. /// Looks up a localized string similar to setstream.
/// </summary> /// </summary>

View File

@ -265,16 +265,16 @@
<value>repeatinvoke repinv</value> <value>repeatinvoke repinv</value>
</data> </data>
<data name="repeatinvoke_desc" xml:space="preserve"> <data name="repeatinvoke_desc" xml:space="preserve">
<value>Immediately shows the repeat message and restarts the timer.</value> <value>Immediately shows the repeat message on a certain index and restarts its timer.</value>
</data> </data>
<data name="repeatinvoke_usage" xml:space="preserve"> <data name="repeatinvoke_usage" xml:space="preserve">
<value>`{0}repinv`</value> <value>`{0}repinv 1`</value>
</data> </data>
<data name="repeat_cmd" xml:space="preserve"> <data name="repeat_cmd" xml:space="preserve">
<value>repeat</value> <value>repeat</value>
</data> </data>
<data name="repeat_desc" xml:space="preserve"> <data name="repeat_desc" xml:space="preserve">
<value>Repeat a message every X minutes. If no parameters are specified, repeat is disabled.</value> <value>Repeat a message every X minutes in the current channel. You can have up to 5 repeating messages on the server in total.</value>
</data> </data>
<data name="repeat_usage" xml:space="preserve"> <data name="repeat_usage" xml:space="preserve">
<value>`{0}repeat 5 Hello there`</value> <value>`{0}repeat 5 Hello there`</value>
@ -1926,23 +1926,23 @@
<data name="randomdog_usage" xml:space="preserve"> <data name="randomdog_usage" xml:space="preserve">
<value>`{0}woof`</value> <value>`{0}woof`</value>
</data> </data>
<data name="i_cmd" xml:space="preserve"> <data name="image_cmd" xml:space="preserve">
<value>img i</value> <value>image img</value>
</data> </data>
<data name="i_desc" xml:space="preserve"> <data name="image_desc" xml:space="preserve">
<value>Pulls the first image found using a search parameter. Use {0}ir for different results.</value> <value>Pulls the first image found using a search parameter. Use {0}rimg for different results.</value>
</data> </data>
<data name="i_usage" xml:space="preserve"> <data name="image_usage" xml:space="preserve">
<value>`{0}i cute kitten`</value> <value>`{0}img cute kitten`</value>
</data> </data>
<data name="ir_cmd" xml:space="preserve"> <data name="randomimage_cmd" xml:space="preserve">
<value>ir</value> <value>randomimage rimg</value>
</data> </data>
<data name="ir_desc" xml:space="preserve"> <data name="randomimage_desc" xml:space="preserve">
<value>Pulls a random image using a search parameter.</value> <value>Pulls a random image using a search parameter.</value>
</data> </data>
<data name="ir_usage" xml:space="preserve"> <data name="randomimage_usage" xml:space="preserve">
<value>`{0}ir cute kitten`</value> <value>`{0}rimg cute kitten`</value>
</data> </data>
<data name="lmgtfy_cmd" xml:space="preserve"> <data name="lmgtfy_cmd" xml:space="preserve">
<value>lmgtfy</value> <value>lmgtfy</value>
@ -2853,4 +2853,76 @@
<data name="autohentai_usage" xml:space="preserve"> <data name="autohentai_usage" xml:space="preserve">
<value>`{0}autohentai 30 yuri|tail|long_hair` or `{0}autohentai`</value> <value>`{0}autohentai 30 yuri|tail|long_hair` or `{0}autohentai`</value>
</data> </data>
<data name="setstatus_cmd" xml:space="preserve">
<value>setstatus</value>
</data>
<data name="setstatus_desc" xml:space="preserve">
<value>Sets the bot's status. (Online/Idle/Dnd/Invisible)</value>
</data>
<data name="setstatus_usage" xml:space="preserve">
<value>`{0}setstatus Idle`</value>
</data>
<data name="rotaterolecolor_cmd" xml:space="preserve">
<value>rotaterolecolor rrc</value>
</data>
<data name="rotaterolecolor_desc" xml:space="preserve">
<value>Rotates a roles color on an interval with a list of supplied colors. First argument is interval in seconds (Minimum 60). Second argument is a role, followed by a space-separated list of colors in hex. Provide a rolename with a 0 interval to disable.</value>
</data>
<data name="rotaterolecolor_usage" xml:space="preserve">
<value>`{0}rrc 60 MyLsdRole #ff0000 #00ff00 #0000ff` or `{0}rrc 0 MyLsdRole`</value>
</data>
<data name="createinvite_cmd" xml:space="preserve">
<value>createinvite crinv</value>
</data>
<data name="createinvite_desc" xml:space="preserve">
<value>Creates a new invite which has infinite max uses and never expires.</value>
</data>
<data name="createinvite_usage" xml:space="preserve">
<value>`{0}crinv`</value>
</data>
<data name="pollstats_cmd" xml:space="preserve">
<value>pollstats</value>
</data>
<data name="pollstats_desc" xml:space="preserve">
<value>Shows the poll results without stopping the poll on this server.</value>
</data>
<data name="pollstats_usage" xml:space="preserve">
<value>`{0}pollstats`</value>
</data>
<data name="repeatlist_cmd" xml:space="preserve">
<value>repeatlist replst</value>
</data>
<data name="repeatlist_desc" xml:space="preserve">
<value>Shows currently repeating messages and their indexes.</value>
</data>
<data name="repeatlist_usage" xml:space="preserve">
<value>`{0}repeatlist`</value>
</data>
<data name="repeatremove_cmd" xml:space="preserve">
<value>repeatremove reprm</value>
</data>
<data name="repeatremove_desc" xml:space="preserve">
<value>Removes a repeating message on a specified index. Use `{0}repeatlist` to see indexes.</value>
</data>
<data name="repeatremove_usage" xml:space="preserve">
<value>`{0}reprm 2`</value>
</data>
<data name="antilist_cmd" xml:space="preserve">
<value>antilist antilst</value>
</data>
<data name="antilist_desc" xml:space="preserve">
<value>Shows currently enabled protection features.</value>
</data>
<data name="antilist_usage" xml:space="preserve">
<value>`{0}antilist`</value>
</data>
<data name="antispamignore_cmd" xml:space="preserve">
<value>antispamignore</value>
</data>
<data name="antispamignore_desc" xml:space="preserve">
<value>Toggles whether antispam ignores current channel. Antispam must be enabled.</value>
</data>
<data name="antispamignore_usage" xml:space="preserve">
<value>`{0}antispamignore`</value>
</data>
</root> </root>

View File

@ -29,10 +29,16 @@ namespace Services.CleverBotApi
public static ChatterBot Create(ChatterBotType type, object arg) public static ChatterBot Create(ChatterBotType type, object arg)
{ {
#if GLOBAL_NADEKO
var url = "http://www.cleverbot.com/webservicemin?uc=3210&botapi=nadekobot";
#else
var url = "http://www.cleverbot.com/webservicemin?uc=3210";
#endif
switch (type) switch (type)
{ {
case ChatterBotType.CLEVERBOT: case ChatterBotType.CLEVERBOT:
return new Cleverbot("http://www.cleverbot.com/", "http://www.cleverbot.com/webservicemin?uc=321", 26); return new Cleverbot("http://www.cleverbot.com/", url, 26);
case ChatterBotType.JABBERWACKY: case ChatterBotType.JABBERWACKY:
return new Cleverbot("http://jabberwacky.com", "http://jabberwacky.com/webservicemin", 20); return new Cleverbot("http://jabberwacky.com", "http://jabberwacky.com/webservicemin", 20);
case ChatterBotType.PANDORABOTS: case ChatterBotType.PANDORABOTS:

View File

@ -46,7 +46,7 @@ namespace Services.CleverBotApi
private readonly int endIndex; private readonly int endIndex;
private readonly string url; private readonly string url;
private readonly IDictionary<string, string> vars; private readonly IDictionary<string, string> vars;
private readonly CookieCollection cookies; private readonly CookieCollection cookies;
public CleverbotSession(string baseUrl, string url, int endIndex) public CleverbotSession(string baseUrl, string url, int endIndex)
{ {
@ -60,7 +60,7 @@ namespace Services.CleverBotApi
//vars["fno"] = "0"; //vars["fno"] = "0";
//vars["sub"] = "Say"; //vars["sub"] = "Say";
//vars["cleanslate"] = "false"; //vars["cleanslate"] = "false";
cookies = Utils.GetCookies(baseUrl); cookies = Utils.GetCookies(baseUrl);
} }
public async Task<ChatterBotThought> Think(ChatterBotThought thought) public async Task<ChatterBotThought> Think(ChatterBotThought thought)

View File

@ -17,6 +17,8 @@ using static NadekoBot.Modules.Administration.Administration;
using NadekoBot.Modules.CustomReactions; using NadekoBot.Modules.CustomReactions;
using NadekoBot.Modules.Games; using NadekoBot.Modules.Games;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading;
using NadekoBot.DataStructures;
namespace NadekoBot.Services namespace NadekoBot.Services
{ {
@ -28,6 +30,8 @@ namespace NadekoBot.Services
} }
public class CommandHandler public class CommandHandler
{ {
public const int GlobalCommandsCooldown = 1500;
private ShardedDiscordClient _client; private ShardedDiscordClient _client;
private CommandService _commandService; private CommandService _commandService;
private Logger _log; private Logger _log;
@ -39,11 +43,19 @@ namespace NadekoBot.Services
//userid/msg count //userid/msg count
public ConcurrentDictionary<ulong, uint> UserMessagesSent { get; } = new ConcurrentDictionary<ulong, uint>(); public ConcurrentDictionary<ulong, uint> UserMessagesSent { get; } = new ConcurrentDictionary<ulong, uint>();
public ConcurrentHashSet<ulong> UsersOnShortCooldown { get; } = new ConcurrentHashSet<ulong>();
private Timer clearUsersOnShortCooldown { get; }
public CommandHandler(ShardedDiscordClient client, CommandService commandService) public CommandHandler(ShardedDiscordClient client, CommandService commandService)
{ {
_client = client; _client = client;
_commandService = commandService; _commandService = commandService;
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
clearUsersOnShortCooldown = new Timer((_) =>
{
UsersOnShortCooldown.Clear();
}, null, GlobalCommandsCooldown, GlobalCommandsCooldown);
} }
public async Task StartHandling() public async Task StartHandling()
{ {
@ -62,158 +74,202 @@ namespace NadekoBot.Services
_client.MessageReceived += MessageReceivedHandler; _client.MessageReceived += MessageReceivedHandler;
} }
private async void MessageReceivedHandler(SocketMessage msg) private async Task<bool> TryRunCleverbot(SocketUserMessage usrMsg, IGuild guild)
{ {
if (guild == null)
return false;
try try
{ {
var cleverbotExecuted = await Games.CleverBotCommands.TryAsk(usrMsg).ConfigureAwait(false);
var usrMsg = msg as SocketUserMessage; if (cleverbotExecuted)
if (usrMsg == null)
return;
if (!usrMsg.IsAuthor())
UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old);
if (msg.Author.IsBot || !NadekoBot.Ready) //no bots
return;
var guild = (msg.Channel as SocketTextChannel)?.Guild;
if (guild != null && guild.OwnerId != msg.Author.Id)
{ {
//todo split checks into their own modules _log.Info($@"CleverBot Executed
if (Permissions.FilterCommands.InviteFilteringChannels.Contains(msg.Channel.Id) || Server: {guild.Name} [{guild.Id}]
Permissions.FilterCommands.InviteFilteringServers.Contains(guild.Id)) Channel: {usrMsg.Channel?.Name} [{usrMsg.Channel?.Id}]
{ UserId: {usrMsg.Author} [{usrMsg.Author.Id}]
if (usrMsg.Content.IsDiscordInvite()) Message: {usrMsg.Content}");
{ return true;
try
{
await usrMsg.DeleteAsync().ConfigureAwait(false);
return;
}
catch (HttpException ex)
{
_log.Warn("I do not have permission to filter invites in channel with id " + msg.Channel.Id, ex);
}
}
}
var filteredWords = Permissions.FilterCommands.FilteredWordsForChannel(msg.Channel.Id, guild.Id).Concat(Permissions.FilterCommands.FilteredWordsForServer(guild.Id));
var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' ');
if (filteredWords.Any(w => wordsInMessage.Contains(w)))
{
try
{
await usrMsg.DeleteAsync().ConfigureAwait(false);
return;
}
catch (HttpException ex)
{
_log.Warn("I do not have permission to filter words in channel with id " + msg.Channel.Id, ex);
}
}
} }
}
catch (Exception ex) { _log.Warn(ex, "Error in cleverbot"); }
return false;
}
BlacklistItem blacklistedItem; private bool IsBlacklisted(IGuild guild, SocketUserMessage usrMsg) =>
if ((blacklistedItem = Permissions.BlacklistCommands.BlacklistedItems.FirstOrDefault(bi => (guild != null && BlacklistCommands.BlacklistedGuilds.Contains(guild.Id)) ||
(bi.Type == BlacklistItem.BlacklistType.Server && bi.ItemId == guild?.Id) || BlacklistCommands.BlacklistedChannels.Contains(usrMsg.Channel.Id) ||
(bi.Type == BlacklistItem.BlacklistType.Channel && bi.ItemId == msg.Channel.Id) || BlacklistCommands.BlacklistedUsers.Contains(usrMsg.Author.Id);
(bi.Type == BlacklistItem.BlacklistType.User && bi.ItemId == msg.Author.Id))) != null)
{
return;
}
#if !GLOBAL_NADEKO
try
{
var cleverbotExecuted = await Games.CleverBotCommands.TryAsk(usrMsg);
if (cleverbotExecuted)
return;
}
catch (Exception ex) { _log.Warn(ex, "Error in cleverbot"); }
#endif private async Task LogSuccessfulExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, Stopwatch sw)
try {
{ await CommandExecuted(usrMsg, exec.CommandInfo).ConfigureAwait(false);
// maybe this message is a custom reaction _log.Info("Command Executed after {4}s\n\t" +
var crExecuted = await CustomReactions.TryExecuteCustomReaction(usrMsg).ConfigureAwait(false); "User: {0}\n\t" +
"Server: {1}\n\t" +
"Channel: {2}\n\t" +
"Message: {3}",
usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0}
(channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1}
(channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2}
usrMsg.Content, // {3}
sw.Elapsed.TotalSeconds);
}
//if it was, don't execute the command private void LogErroredExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, Stopwatch sw)
if (crExecuted) {
return; _log.Warn("Command Errored after {5}s\n\t" +
}
catch { }
string messageContent = usrMsg.Content;
var sw = new Stopwatch();
sw.Start();
var exec = await ExecuteCommand(new CommandContext(_client.MainClient, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best);
var command = exec.CommandInfo;
var permCache = exec.PermissionCache;
var result = exec.Result;
sw.Stop();
var channel = (msg.Channel as ITextChannel);
if (result.IsSuccess)
{
await CommandExecuted(usrMsg, command);
_log.Info("Command Executed after {4}s\n\t" +
"User: {0}\n\t" +
"Server: {1}\n\t" +
"Channel: {2}\n\t" +
"Message: {3}",
msg.Author + " [" + msg.Author.Id + "]", // {0}
(channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1}
(channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2}
usrMsg.Content, // {3}
sw.Elapsed.TotalSeconds // {4}
);
}
else if (!result.IsSuccess && result.Error != CommandError.UnknownCommand)
{
_log.Warn("Command Errored after {5}s\n\t" +
"User: {0}\n\t" + "User: {0}\n\t" +
"Server: {1}\n\t" + "Server: {1}\n\t" +
"Channel: {2}\n\t" + "Channel: {2}\n\t" +
"Message: {3}\n\t" + "Message: {3}\n\t" +
"Error: {4}", "Error: {4}",
msg.Author + " [" + msg.Author.Id + "]", // {0} usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0}
(channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1} (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1}
(channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2}
usrMsg.Content,// {3} usrMsg.Content,// {3}
result.ErrorReason, // {4} exec.Result.ErrorReason, // {4}
sw.Elapsed.TotalSeconds // {5} sw.Elapsed.TotalSeconds // {5}
); );
if (guild != null && command != null && result.Error == CommandError.Exception) }
private async Task<bool> InviteFiltered(IGuild guild, SocketUserMessage usrMsg)
{
if ((Permissions.FilterCommands.InviteFilteringChannels.Contains(usrMsg.Channel.Id) ||
Permissions.FilterCommands.InviteFilteringServers.Contains(guild.Id)) &&
usrMsg.Content.IsDiscordInvite())
{
try
{
await usrMsg.DeleteAsync().ConfigureAwait(false);
return true;
}
catch (HttpException ex)
{
_log.Warn("I do not have permission to filter invites in channel with id " + usrMsg.Channel.Id, ex);
return true;
}
}
return false;
}
private async Task<bool> WordFiltered(IGuild guild, SocketUserMessage usrMsg)
{
var filteredChannelWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id);
var filteredServerWords = Permissions.FilterCommands.FilteredWordsForServer(guild.Id);
var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' ');
if (filteredChannelWords.Count != 0 || filteredServerWords.Count != 0)
{
foreach (var word in wordsInMessage)
{
if (filteredChannelWords.Contains(word) ||
filteredServerWords.Contains(word))
{ {
if (permCache != null && permCache.Verbose) try
try { await msg.Channel.SendMessageAsync("⚠️ " + result.ErrorReason).ConfigureAwait(false); } catch { } {
await usrMsg.DeleteAsync().ConfigureAwait(false);
}
catch (HttpException ex)
{
_log.Warn("I do not have permission to filter words in channel with id " + usrMsg.Channel.Id, ex);
}
return true;
}
}
}
return false;
}
private async void MessageReceivedHandler(SocketMessage msg)
{
try
{
if (msg.Author.IsBot || !NadekoBot.Ready) //no bots, wait until bot connected and initialized
return;
var usrMsg = msg as SocketUserMessage;
if (usrMsg == null) //has to be an user message, not system/other messages.
return;
// track how many messagges each user is sending
UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old);
// Bot will ignore commands which are ran more often than what specified by
// GlobalCommandsCooldown constant (miliseconds)
if (!UsersOnShortCooldown.Add(usrMsg.Author.Id))
return;
var channel = msg.Channel as SocketTextChannel;
var guild = channel?.Guild;
if (guild != null && guild.OwnerId != msg.Author.Id)
{
if (await InviteFiltered(guild, usrMsg).ConfigureAwait(false))
return;
if (await WordFiltered(guild, usrMsg).ConfigureAwait(false))
return;
}
if (IsBlacklisted(guild, usrMsg))
return;
var cleverBotRan = await TryRunCleverbot(usrMsg, guild).ConfigureAwait(false);
if (cleverBotRan)
return;
// maybe this message is a custom reaction
var crExecuted = await CustomReactions.TryExecuteCustomReaction(usrMsg).ConfigureAwait(false);
if (crExecuted) //if it was, don't execute the command
return;
string messageContent = usrMsg.Content;
// execute the command and measure the time it took
var sw = Stopwatch.StartNew();
var exec = await ExecuteCommand(new CommandContext(_client.MainClient, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best);
sw.Stop();
if (exec.Result.IsSuccess)
{
await LogSuccessfulExecution(usrMsg, exec, channel, sw).ConfigureAwait(false);
}
else if (!exec.Result.IsSuccess && exec.Result.Error != CommandError.UnknownCommand)
{
LogErroredExecution(usrMsg, exec, channel, sw);
if (guild != null && exec.CommandInfo != null && exec.Result.Error == CommandError.Exception)
{
if (exec.PermissionCache != null && exec.PermissionCache.Verbose)
try { await msg.Channel.SendMessageAsync("⚠️ " + exec.Result.ErrorReason).ConfigureAwait(false); } catch { }
} }
} }
else else
{ {
if (msg.Channel is IPrivateChannel) if (msg.Channel is IPrivateChannel)
{ {
//rofl, gotta do this to prevent this message from occuring on polls // rofl, gotta do this to prevent dm help message being sent to
// users who are voting on private polls (sending a number in a DM)
int vote; int vote;
if (int.TryParse(msg.Content, out vote)) return; if (int.TryParse(msg.Content, out vote)) return;
await msg.Channel.SendMessageAsync(Help.DMHelpString).ConfigureAwait(false); await msg.Channel.SendMessageAsync(Help.DMHelpString).ConfigureAwait(false);
await DMForwardCommands.HandleDMForwarding(msg, ownerChannels); await DMForwardCommands.HandleDMForwarding(msg, ownerChannels).ConfigureAwait(false);
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
_log.Warn(ex, "Error in CommandHandler"); _log.Warn("Error in CommandHandler");
_log.Warn(ex);
if (ex.InnerException != null) if (ex.InnerException != null)
_log.Warn(ex.InnerException, "Inner Exception of the error in CommandHandler"); {
_log.Warn("Inner Exception of the error in CommandHandler");
_log.Warn(ex.InnerException);
}
} }
return;
} }
public Task<ExecuteCommandResult> ExecuteCommandAsync(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) public Task<ExecuteCommandResult> ExecuteCommandAsync(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
=> ExecuteCommand(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); => ExecuteCommand(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling);
@ -308,19 +364,5 @@ namespace NadekoBot.Services
return new ExecuteCommandResult(null, null, SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload.")); return new ExecuteCommandResult(null, null, SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."));
} }
public struct ExecuteCommandResult
{
public readonly CommandInfo CommandInfo;
public readonly PermissionCache PermissionCache;
public readonly IResult Result;
public ExecuteCommandResult(CommandInfo commandInfo, PermissionCache cache, IResult result)
{
this.CommandInfo = commandInfo;
this.PermissionCache = cache;
this.Result = result;
}
}
} }
} }

View File

@ -15,7 +15,6 @@ namespace NadekoBot.Services.Database
IReminderRepository Reminders { get; } IReminderRepository Reminders { get; }
ISelfAssignedRolesRepository SelfAssignedRoles { get; } ISelfAssignedRolesRepository SelfAssignedRoles { get; }
IBotConfigRepository BotConfig { get; } IBotConfigRepository BotConfig { get; }
IRepeaterRepository Repeaters { get; }
IUnitConverterRepository ConverterUnits { get; } IUnitConverterRepository ConverterUnits { get; }
ICustomReactionRepository CustomReactions { get; } ICustomReactionRepository CustomReactions { get; }
ICurrencyRepository Currency { get; } ICurrencyRepository Currency { get; }

View File

@ -0,0 +1,53 @@
using Discord;
using NadekoBot.Services.Database.Models;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace NadekoBot.Services.Database.Models
{
public class AntiRaidSetting : DbEntity
{
public int GuildConfigId { get; set; }
public GuildConfig GuildConfig { get; set; }
public int UserThreshold { get; set; }
public int Seconds { get; set; }
public PunishmentAction Action { get; set; }
}
public class AntiSpamSetting : DbEntity
{
public int GuildConfigId { get; set; }
public GuildConfig GuildConfig { get; set; }
public PunishmentAction Action { get; set; }
public int MessageThreshold { get; set; } = 3;
public HashSet<AntiSpamIgnore> IgnoredChannels { get; set; } = new HashSet<AntiSpamIgnore>();
}
public enum PunishmentAction
{
Mute,
Kick,
Ban,
}
public class AntiSpamIgnore : DbEntity
{
public ulong ChannelId { get; set; }
public override int GetHashCode() => ChannelId.GetHashCode();
public override bool Equals(object obj)
{
var inst = obj as AntiSpamIgnore;
if (inst == null)
return false;
return inst.ChannelId == ChannelId;
}
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using static NadekoBot.Modules.Administration.Administration;
namespace NadekoBot.Services.Database.Models namespace NadekoBot.Services.Database.Models
{ {
@ -58,6 +59,12 @@ namespace NadekoBot.Services.Database.Models
public string MuteRoleName { get; set; } public string MuteRoleName { get; set; }
public bool CleverbotEnabled { get; set; } public bool CleverbotEnabled { get; set; }
public HashSet<GuildRepeater> GuildRepeaters { get; set; } = new HashSet<GuildRepeater>();
public AntiRaidSetting AntiRaidSetting { get; set; }
public AntiSpamSetting AntiSpamSetting { get; set; }
//public List<ProtectionIgnoredChannel> ProtectionIgnoredChannels { get; set; } = new List<ProtectionIgnoredChannel>();
} }
public class FilterChannelId : DbEntity public class FilterChannelId : DbEntity

View File

@ -2,11 +2,16 @@
namespace NadekoBot.Services.Database.Models namespace NadekoBot.Services.Database.Models
{ {
public class Repeater :DbEntity public class Repeater : DbEntity
{ {
public ulong GuildId { get; set; } public ulong GuildId { get; set; }
public ulong ChannelId { get; set; } public ulong ChannelId { get; set; }
public string Message { get; set; } public string Message { get; set; }
public TimeSpan Interval { get; set; } public TimeSpan Interval { get; set; }
} }
public class GuildRepeater : Repeater
{
}
} }

View File

@ -16,7 +16,6 @@ namespace NadekoBot.Services.Database
public DbSet<Reminder> Reminders { get; set; } public DbSet<Reminder> Reminders { get; set; }
public DbSet<SelfAssignedRole> SelfAssignableRoles { get; set; } public DbSet<SelfAssignedRole> SelfAssignableRoles { get; set; }
public DbSet<BotConfig> BotConfig { get; set; } public DbSet<BotConfig> BotConfig { get; set; }
public DbSet<Repeater> Repeaters { get; set; }
public DbSet<Currency> Currency { get; set; } public DbSet<Currency> Currency { get; set; }
public DbSet<ConvertUnit> ConversionUnits { get; set; } public DbSet<ConvertUnit> ConversionUnits { get; set; }
public DbSet<MusicPlaylist> MusicPlaylists { get; set; } public DbSet<MusicPlaylist> MusicPlaylists { get; set; }
@ -44,6 +43,7 @@ namespace NadekoBot.Services.Database
this.Database.Migrate(); this.Database.Migrate();
EnsureSeedData(); EnsureSeedData();
} }
////Uncomment this to db initialisation with dotnet ef migration add [module] ////Uncomment this to db initialisation with dotnet ef migration add [module]
//protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) //protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
//{ //{
@ -142,6 +142,17 @@ namespace NadekoBot.Services.Database
.HasIndex(c => c.GuildId) .HasIndex(c => c.GuildId)
.IsUnique(); .IsUnique();
modelBuilder.Entity<AntiSpamSetting>()
.HasOne(x => x.GuildConfig)
.WithOne(x => x.AntiSpamSetting);
modelBuilder.Entity<AntiRaidSetting>()
.HasOne(x => x.GuildConfig)
.WithOne(x => x.AntiRaidSetting);
//modelBuilder.Entity<ProtectionIgnoredChannel>()
// .HasAlternateKey(c => new { c.ChannelId, c.ProtectionType });
#endregion #endregion
#region BotConfig #region BotConfig
@ -172,16 +183,6 @@ namespace NadekoBot.Services.Database
#endregion #endregion
#region Repeater
var repeaterEntity = modelBuilder.Entity<Repeater>();
repeaterEntity
.HasIndex(r => r.ChannelId)
.IsUnique();
#endregion
#region Currency #region Currency
var currencyEntity = modelBuilder.Entity<Currency>(); var currencyEntity = modelBuilder.Entity<Currency>();
@ -231,6 +232,11 @@ namespace NadekoBot.Services.Database
.IsUnique(); .IsUnique();
#endregion
#region Protection
#endregion #endregion
} }
} }

View File

@ -1,9 +0,0 @@
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services.Database.Repositories
{
public interface IRepeaterRepository : IRepository<Repeater>
{
}
}

View File

@ -26,6 +26,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl
.Include(gc => gc.FilterWordsChannelIds) .Include(gc => gc.FilterWordsChannelIds)
.Include(gc => gc.FilteredWords) .Include(gc => gc.FilteredWords)
.Include(gc => gc.CommandCooldowns) .Include(gc => gc.CommandCooldowns)
.Include(gc => gc.GuildRepeaters)
.Include(gc => gc.AntiRaidSetting)
.Include(gc => gc.AntiSpamSetting)
.ThenInclude(x => x.IgnoredChannels)
.ToList(); .ToList();
/// <summary> /// <summary>

View File

@ -1,12 +0,0 @@
using NadekoBot.Services.Database.Models;
using Microsoft.EntityFrameworkCore;
namespace NadekoBot.Services.Database.Repositories.Impl
{
public class RepeaterRepository : Repository<Repeater>, IRepeaterRepository
{
public RepeaterRepository(DbContext context) : base(context)
{
}
}
}

View File

@ -30,9 +30,6 @@ namespace NadekoBot.Services.Database
private IBotConfigRepository _botConfig; private IBotConfigRepository _botConfig;
public IBotConfigRepository BotConfig => _botConfig ?? (_botConfig = new BotConfigRepository(_context)); public IBotConfigRepository BotConfig => _botConfig ?? (_botConfig = new BotConfigRepository(_context));
private IRepeaterRepository _repeaters;
public IRepeaterRepository Repeaters => _repeaters ?? (_repeaters = new RepeaterRepository(_context));
private ICurrencyRepository _currency; private ICurrencyRepository _currency;
public ICurrencyRepository Currency => _currency ?? (_currency = new CurrencyRepository(_context)); public ICurrencyRepository Currency => _currency ?? (_currency = new CurrencyRepository(_context));

View File

@ -0,0 +1,78 @@
using Discord;
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services.Discord
{
public class ReactionEventWrapper : IDisposable
{
public IUserMessage Message { get; }
public event Action<SocketReaction> OnReactionAdded = delegate { };
public event Action<SocketReaction> OnReactionRemoved = delegate { };
public event Action OnReactionsCleared = delegate { };
public ReactionEventWrapper(IUserMessage msg)
{
if (msg == null)
throw new ArgumentNullException(nameof(msg));
Message = msg;
NadekoBot.Client.ReactionAdded += Discord_ReactionAdded;
NadekoBot.Client.ReactionRemoved += Discord_ReactionRemoved;
NadekoBot.Client.ReactionsCleared += Discord_ReactionsCleared;
}
private void Discord_ReactionsCleared(ulong messageId, Optional<SocketUserMessage> reaction)
{
try
{
if (messageId == Message.Id)
OnReactionsCleared?.Invoke();
}
catch { }
}
private void Discord_ReactionRemoved(ulong messageId, Optional<SocketUserMessage> arg2, SocketReaction reaction)
{
try
{
if (messageId == Message.Id)
OnReactionRemoved?.Invoke(reaction);
}
catch { }
}
private void Discord_ReactionAdded(ulong messageId, Optional<SocketUserMessage> message, SocketReaction reaction)
{
try
{
if (messageId == Message.Id)
OnReactionAdded?.Invoke(reaction);
}
catch { }
}
public void UnsubAll()
{
NadekoBot.Client.ReactionAdded -= Discord_ReactionAdded;
NadekoBot.Client.ReactionRemoved -= Discord_ReactionRemoved;
NadekoBot.Client.ReactionsCleared -= Discord_ReactionsCleared;
OnReactionAdded = null;
OnReactionRemoved = null;
OnReactionsCleared = null;
}
private bool disposing = false;
public void Dispose()
{
if (disposing)
return;
disposing = true;
UnsubAll();
}
}
}

View File

@ -51,7 +51,7 @@ namespace NadekoBot.Services.Impl
return (await query.ExecuteAsync()).Items.Select(i => i.Id.PlaylistId); return (await query.ExecuteAsync()).Items.Select(i => i.Id.PlaylistId);
} }
private readonly Regex YtVideoIdRegex = new Regex("(?:youtu\\.be\\/|v\\/|u\\/\\w\\/|embed\\/|watch\\?v=|\\&v=)(?<id>[^#\\&\\?]*)", RegexOptions.Compiled); private readonly Regex YtVideoIdRegex = new Regex(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)([a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled);
public async Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1) public async Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1)
{ {
@ -149,7 +149,7 @@ namespace NadekoBot.Services.Impl
return toReturn; return toReturn;
} }
//todo AsyncEnumerable
public async Task<IReadOnlyDictionary<string,TimeSpan>> GetVideoDurationsAsync(IEnumerable<string> videoIds) public async Task<IReadOnlyDictionary<string,TimeSpan>> GetVideoDurationsAsync(IEnumerable<string> videoIds)
{ {
var videoIdsList = videoIds as List<string> ?? videoIds.ToList(); var videoIdsList = videoIds as List<string> ?? videoIds.ToList();

View File

@ -14,16 +14,19 @@ namespace NadekoBot.Services.Impl
private ShardedDiscordClient client; private ShardedDiscordClient client;
private DateTime started; private DateTime started;
public const string BotVersion = "1.1.0-beta"; public const string BotVersion = "1.1.0";
public string Author => "Kwoth#2560"; public string Author => "Kwoth#2560";
public string Library => "Discord.Net"; public string Library => "Discord.Net";
public int MessageCounter { get; private set; } = 0; public int MessageCounter { get; private set; } = 0;
public int CommandsRan { get; private set; } = 0; public int CommandsRan { get; private set; } = 0;
public string Heap => Math.Round((double)GC.GetTotalMemory(false) / 1.MiB(), 2).ToString(); public string Heap =>
public double MessagesPerSecond => MessageCounter / (double)GetUptime().TotalSeconds; Math.Round((double)GC.GetTotalMemory(false) / 1.MiB(), 2).ToString();
public int TextChannels => client.GetGuilds().SelectMany(g => g.Channels.Where(c => c is ITextChannel)).Count(); public double MessagesPerSecond => MessageCounter / GetUptime().TotalSeconds;
public int VoiceChannels => client.GetGuilds().SelectMany(g => g.Channels.Where(c => c is IVoiceChannel)).Count(); private int _textChannels = 0;
public int TextChannels => _textChannels;
private int _voiceChannels = 0;
public int VoiceChannels => _voiceChannels;
public string OwnerIds => string.Join(", ", NadekoBot.Credentials.OwnerIds); public string OwnerIds => string.Join(", ", NadekoBot.Credentials.OwnerIds);
Timer carbonitexTimer { get; } Timer carbonitexTimer { get; }
@ -39,17 +42,56 @@ namespace NadekoBot.Services.Impl
this.client.Disconnected += _ => Reset(); this.client.Disconnected += _ => Reset();
this.client.Connected += () =>
{
var guilds = this.client.GetGuilds();
_textChannels = guilds.Sum(g => g.Channels.Where(cx => cx is ITextChannel).Count());
_voiceChannels = guilds.Sum(g => g.Channels.Count) - _textChannels;
};
this.client.ChannelCreated += (c) =>
{
if (c is ITextChannel)
++_textChannels;
else if (c is IVoiceChannel)
++_voiceChannels;
};
this.client.ChannelDestroyed += (c) =>
{
if (c is ITextChannel)
--_textChannels;
else if (c is IVoiceChannel)
--_voiceChannels;
};
this.client.JoinedGuild += (g) =>
{
var tc = g.Channels.Where(cx => cx is ITextChannel).Count();
var vc = g.Channels.Count - tc;
_textChannels += tc;
_voiceChannels += vc;
};
this.client.LeftGuild += (g) =>
{
var tc = g.Channels.Where(cx => cx is ITextChannel).Count();
var vc = g.Channels.Count - tc;
_textChannels -= tc;
_voiceChannels -= vc;
};
this.carbonitexTimer = new Timer(async (state) => this.carbonitexTimer = new Timer(async (state) =>
{ {
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.CarbonKey)) if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.CarbonKey))
return; return;
try try
{ {
using (var http = new HttpClient()) using (var http = new HttpClient())
{ {
using (var content = new FormUrlEncodedContent( using (var content = new FormUrlEncodedContent(
new Dictionary<string, string> { new Dictionary<string, string> {
{ "servercount", this.client.GetGuilds().Count.ToString() }, { "servercount", this.client.GetGuildsCount().ToString() },
{ "key", NadekoBot.Credentials.CarbonKey }})) { "key", NadekoBot.Credentials.CarbonKey }}))
{ {
content.Headers.Clear(); content.Headers.Clear();
@ -71,7 +113,7 @@ Bot Version: [{BotVersion}]
Bot ID: {curUser.Id} Bot ID: {curUser.Id}
Owner ID(s): {OwnerIds} Owner ID(s): {OwnerIds}
Uptime: {GetUptimeString()} Uptime: {GetUptimeString()}
Servers: {client.GetGuilds().Count} | TextChannels: {TextChannels} | VoiceChannels: {VoiceChannels} Servers: {client.GetGuildsCount()} | TextChannels: {TextChannels} | VoiceChannels: {VoiceChannels}
Commands Ran this session: {CommandsRan} Commands Ran this session: {CommandsRan}
Messages: {MessageCounter} [{MessagesPerSecond:F2}/sec] Heap: [{Heap} MB]"); Messages: {MessageCounter} [{MessagesPerSecond:F2}/sec] Heap: [{Heap} MB]");
} }

View File

@ -18,6 +18,7 @@ namespace NadekoBot
public event Action<SocketMessage> MessageReceived = delegate { }; public event Action<SocketMessage> MessageReceived = delegate { };
public event Action<SocketGuildUser> UserLeft = delegate { }; public event Action<SocketGuildUser> UserLeft = delegate { };
public event Action<SocketUser, SocketUser> UserUpdated = delegate { }; public event Action<SocketUser, SocketUser> UserUpdated = delegate { };
public event Action<SocketGuildUser, SocketGuildUser> GuildUserUpdated = delegate { };
public event Action<Optional<SocketMessage>, SocketMessage> MessageUpdated = delegate { }; public event Action<Optional<SocketMessage>, SocketMessage> MessageUpdated = delegate { };
public event Action<ulong, Optional<SocketMessage>> MessageDeleted = delegate { }; public event Action<ulong, Optional<SocketMessage>> MessageDeleted = delegate { };
public event Action<SocketUser, SocketGuild> UserBanned = delegate { }; public event Action<SocketUser, SocketGuild> UserBanned = delegate { };
@ -27,11 +28,21 @@ namespace NadekoBot
public event Action<SocketChannel> ChannelCreated = delegate { }; public event Action<SocketChannel> ChannelCreated = delegate { };
public event Action<SocketChannel> ChannelDestroyed = delegate { }; public event Action<SocketChannel> ChannelDestroyed = delegate { };
public event Action<SocketChannel, SocketChannel> ChannelUpdated = delegate { }; public event Action<SocketChannel, SocketChannel> ChannelUpdated = delegate { };
public event Action<ulong, Optional<SocketUserMessage>, SocketReaction> ReactionAdded = delegate { };
public event Action<ulong, Optional<SocketUserMessage>, SocketReaction> ReactionRemoved = delegate { };
public event Action<ulong, Optional<SocketUserMessage>> ReactionsCleared = delegate { };
public event Action<SocketGuild> JoinedGuild = delegate { };
public event Action<SocketGuild> LeftGuild = delegate { };
public event Action<Exception> Disconnected = delegate { }; public event Action<Exception> Disconnected = delegate { };
public event Action Connected = delegate { };
private uint _connectedCount = 0; private uint _connectedCount = 0;
private uint _downloadedCount = 0; private uint _downloadedCount = 0;
private int _guildCount = 0;
private IReadOnlyList<DiscordSocketClient> Clients { get; } private IReadOnlyList<DiscordSocketClient> Clients { get; }
public ShardedDiscordClient(DiscordSocketConfig discordSocketConfig) public ShardedDiscordClient(DiscordSocketConfig discordSocketConfig)
@ -54,6 +65,7 @@ namespace NadekoBot
}; };
client.UserLeft += arg1 => { UserLeft(arg1); return Task.CompletedTask; }; client.UserLeft += arg1 => { UserLeft(arg1); return Task.CompletedTask; };
client.UserUpdated += (arg1, gu2) => { UserUpdated(arg1, gu2); return Task.CompletedTask; }; client.UserUpdated += (arg1, gu2) => { UserUpdated(arg1, gu2); return Task.CompletedTask; };
client.GuildMemberUpdated += (arg1, arg2) => { GuildUserUpdated(arg1, arg2); return Task.CompletedTask; };
client.MessageUpdated += (arg1, m2) => { MessageUpdated(arg1, m2); return Task.CompletedTask; }; client.MessageUpdated += (arg1, m2) => { MessageUpdated(arg1, m2); return Task.CompletedTask; };
client.MessageDeleted += (arg1, arg2) => { MessageDeleted(arg1, arg2); return Task.CompletedTask; }; client.MessageDeleted += (arg1, arg2) => { MessageDeleted(arg1, arg2); return Task.CompletedTask; };
client.UserBanned += (arg1, arg2) => { UserBanned(arg1, arg2); return Task.CompletedTask; }; client.UserBanned += (arg1, arg2) => { UserBanned(arg1, arg2); return Task.CompletedTask; };
@ -63,30 +75,69 @@ namespace NadekoBot
client.ChannelCreated += arg => { ChannelCreated(arg); return Task.CompletedTask; }; client.ChannelCreated += arg => { ChannelCreated(arg); return Task.CompletedTask; };
client.ChannelDestroyed += arg => { ChannelDestroyed(arg); return Task.CompletedTask; }; client.ChannelDestroyed += arg => { ChannelDestroyed(arg); return Task.CompletedTask; };
client.ChannelUpdated += (arg1, arg2) => { ChannelUpdated(arg1, arg2); return Task.CompletedTask; }; client.ChannelUpdated += (arg1, arg2) => { ChannelUpdated(arg1, arg2); return Task.CompletedTask; };
client.JoinedGuild += (arg1) => { JoinedGuild(arg1); ++_guildCount; return Task.CompletedTask; };
client.LeftGuild += (arg1) => { LeftGuild(arg1); --_guildCount; return Task.CompletedTask; };
client.ReactionAdded += (arg1, arg2, arg3) => { ReactionAdded(arg1, arg2, arg3); return Task.CompletedTask; };
client.ReactionRemoved += (arg1, arg2, arg3) => { ReactionRemoved(arg1, arg2, arg3); return Task.CompletedTask; };
client.ReactionsCleared += (arg1, arg2) => { ReactionsCleared(arg1, arg2); return Task.CompletedTask; };
_log.Info($"Shard #{i} initialized."); _log.Info($"Shard #{i} initialized.");
#if GLOBAL_NADEKO
client.Log += Client_Log;
#endif
var j = i;
client.Disconnected += (ex) =>
{
_log.Error("Shard #{0} disconnected", j);
_log.Error(ex, ex?.Message ?? "No error");
return Task.CompletedTask;
};
} }
Clients = clientList.AsReadOnly(); Clients = clientList.AsReadOnly();
} }
private Task Client_Log(LogMessage arg)
{
_log.Warn(arg.Message);
_log.Error(arg.Exception);
return Task.CompletedTask;
}
public DiscordSocketClient MainClient => public DiscordSocketClient MainClient =>
Clients[0]; Clients[0];
public SocketSelfUser CurrentUser() => public SocketSelfUser CurrentUser() =>
Clients[0].CurrentUser; Clients[0].CurrentUser;
public IReadOnlyCollection<SocketGuild> GetGuilds() => public IEnumerable<SocketGuild> GetGuilds() =>
Clients.SelectMany(c => c.Guilds).ToList(); Clients.SelectMany(c => c.Guilds);
public SocketGuild GetGuild(ulong id) => public int GetGuildsCount() =>
Clients.Select(c => c.GetGuild(id)).FirstOrDefault(g => g != null); _guildCount;
public SocketGuild GetGuild(ulong id)
{
foreach (var c in Clients)
{
var g = c.GetGuild(id);
if (g != null)
return g;
}
return null;
}
public Task<IDMChannel> GetDMChannelAsync(ulong channelId) => public Task<IDMChannel> GetDMChannelAsync(ulong channelId) =>
Clients[0].GetDMChannelAsync(channelId); Clients[0].GetDMChannelAsync(channelId);
internal Task LoginAsync(TokenType tokenType, string token) => internal async Task LoginAsync(TokenType tokenType, string token)
Task.WhenAll(Clients.Select(async c => { await c.LoginAsync(tokenType, token).ConfigureAwait(false); _log.Info($"Shard #{c.ShardId} logged in."); })); {
foreach (var c in Clients)
{
await c.LoginAsync(tokenType, token).ConfigureAwait(false);
_log.Info($"Shard #{c.ShardId} logged in.");
}
}
internal async Task ConnectAsync() internal async Task ConnectAsync()
{ {
@ -99,6 +150,7 @@ namespace NadekoBot
await c.ConnectAsync().ConfigureAwait(false); await c.ConnectAsync().ConfigureAwait(false);
sw.Stop(); sw.Stop();
_log.Info($"Shard #{c.ShardId} connected after {sw.Elapsed.TotalSeconds:F2}s ({++_connectedCount}/{Clients.Count})"); _log.Info($"Shard #{c.ShardId} connected after {sw.Elapsed.TotalSeconds:F2}s ({++_connectedCount}/{Clients.Count})");
_guildCount += c.Guilds.Count;
} }
catch catch
{ {
@ -111,6 +163,7 @@ namespace NadekoBot
} }
} }
} }
Connected();
} }
internal Task DownloadAllUsersAsync() => internal Task DownloadAllUsersAsync() =>
@ -126,5 +179,37 @@ namespace NadekoBot
public Task SetStream(string name, string url) => Task.WhenAll(Clients.Select(ms => ms.SetGameAsync(name, url, StreamType.NotStreaming))); public Task SetStream(string name, string url) => Task.WhenAll(Clients.Select(ms => ms.SetGameAsync(name, url, StreamType.NotStreaming)));
public Task SetStatus(SettableUserStatus status) => Task.WhenAll(Clients.Select(ms => ms.SetStatusAsync(SettableUserStatusToUserStatus(status))));
private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus)
{
switch (sus)
{
case SettableUserStatus.Online:
return UserStatus.Online;
case SettableUserStatus.Invisible:
return UserStatus.Invisible;
case SettableUserStatus.Idle:
return UserStatus.AFK;
case SettableUserStatus.Dnd:
return UserStatus.DoNotDisturb;
}
return UserStatus.Online;
}
}
public enum SettableUserStatus
{
Online = 1,
On = 1,
Invisible = 2,
Invis = 2,
Idle = 3,
Afk = 3,
Dnd = 4,
DoNotDisturb = 4,
Busy = 4,
} }
} }

View File

@ -1,6 +1,8 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using ImageSharp; using ImageSharp;
using NadekoBot.Services.Discord;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -16,6 +18,73 @@ namespace NadekoBot.Extensions
{ {
public static class Extensions public static class Extensions
{ {
private const string arrow_left = "⬅";
private const string arrow_right = "➡";
/// <summary>
/// danny kamisama
/// </summary>
public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, int currentPage, Func<int, EmbedBuilder> pageFunc, int? lastPage = null)
{
lastPage += 1;
var embed = pageFunc(currentPage).AddPaginatedFooter(currentPage, lastPage);
var msg = await channel.EmbedAsync(embed) as IUserMessage;
if (currentPage >= lastPage && lastPage == 1)
return;
await msg.AddReactionAsync(arrow_left).ConfigureAwait(false);
await msg.AddReactionAsync(arrow_right).ConfigureAwait(false);
await Task.Delay(2000).ConfigureAwait(false);
Action<SocketReaction> changePage = async r =>
{
try
{
if (r.Emoji.Name == arrow_left)
{
if (currentPage == 1)
return;
await msg.ModifyAsync(x => x.Embed = pageFunc(--currentPage).AddPaginatedFooter(currentPage, lastPage).Build()).ConfigureAwait(false);
}
else if (r.Emoji.Name == arrow_right)
{
if (lastPage == null || lastPage > currentPage)
await msg.ModifyAsync(x => x.Embed = pageFunc(++currentPage).AddPaginatedFooter(currentPage, lastPage).Build()).ConfigureAwait(false);
}
}
catch (Exception ex) { Console.WriteLine(ex); }
};
using (msg.OnReaction(changePage, changePage))
{
await Task.Delay(30000).ConfigureAwait(false);
}
await msg.RemoveAllReactionsAsync().ConfigureAwait(false);
}
private static EmbedBuilder AddPaginatedFooter(this EmbedBuilder embed, int curPage, int? lastPage)
{
if (lastPage != null)
return embed.WithFooter(efb => efb.WithText($"page {curPage} / {lastPage}"));
else
return embed.WithFooter(efb => efb.WithText($"page {curPage}"));
}
public static ReactionEventWrapper OnReaction(this IUserMessage msg, Action<SocketReaction> reactionAdded, Action<SocketReaction> reactionRemoved = null)
{
if (reactionRemoved == null)
reactionRemoved = delegate { };
var wrap = new ReactionEventWrapper(msg);
wrap.OnReactionAdded += reactionAdded;
wrap.OnReactionRemoved += reactionRemoved;
return wrap;
}
public static void AddFakeHeaders(this HttpClient http) public static void AddFakeHeaders(this HttpClient http)
{ {
http.DefaultRequestHeaders.Clear(); http.DefaultRequestHeaders.Clear();
@ -44,7 +113,8 @@ namespace NadekoBot.Extensions
public static string GetPrefix(this ModuleInfo module) => NadekoBot.ModulePrefixes[module.GetTopLevelModule().Name]; public static string GetPrefix(this ModuleInfo module) => NadekoBot.ModulePrefixes[module.GetTopLevelModule().Name];
public static ModuleInfo GetTopLevelModule(this ModuleInfo module) { public static ModuleInfo GetTopLevelModule(this ModuleInfo module)
{
while (module.Parent != null) while (module.Parent != null)
{ {
module = module.Parent; module = module.Parent;
@ -105,7 +175,7 @@ namespace NadekoBot.Extensions
=> await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithColor(NadekoBot.OkColor).WithDescription(text)); => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithColor(NadekoBot.OkColor).WithDescription(text));
public static async Task<IUserMessage> SendConfirmAsync(this IUser user, string title, string text, string url = null) public static async Task<IUserMessage> SendConfirmAsync(this IUser user, string title, string text, string url = null)
=> await(await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithColor(NadekoBot.OkColor).WithDescription(text) => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithColor(NadekoBot.OkColor).WithDescription(text)
.WithTitle(title).WithUrl(url)); .WithTitle(title).WithUrl(url));
public static async Task<IUserMessage> SendErrorAsync(this IUser user, string title, string error, string url = null) public static async Task<IUserMessage> SendErrorAsync(this IUser user, string title, string error, string url = null)
@ -317,7 +387,7 @@ namespace NadekoBot.Extensions
var canvasPixels = canvas.Lock(); var canvasPixels = canvas.Lock();
int offsetX = 0; int offsetX = 0;
foreach (var img in imgList.Select(img=>img.Lock())) foreach (var img in imgList.Select(img => img.Lock()))
{ {
for (int i = 0; i < img.Width; i++) for (int i = 0; i < img.Width; i++)
{ {