Merge pull request #10 from Kwoth/dev

merge
This commit is contained in:
Mirai 2017-04-15 00:32:23 +02:00 committed by GitHub
commit 40de846414
280 changed files with 101012 additions and 6368 deletions

@ -1 +1 @@
Subproject commit b9f767337d2b7c07ed76eb83c3bc5030109d5238 Subproject commit d2229228b92117899d65cd549a1f2853057b255b

View File

@ -2,5 +2,6 @@
<configuration> <configuration>
<packageSources> <packageSources>
<add key="ImageSharp" value="https://www.myget.org/F/imagesharp/api/v3/index.json" /> <add key="ImageSharp" value="https://www.myget.org/F/imagesharp/api/v3/index.json" />
<add key="Kwoth" value="https://www.myget.org/F/kwoth/api/v3/index.json" />
</packageSources> </packageSources>
</configuration> </configuration>

View File

@ -1,14 +1,12 @@
![img](https://ci.appveyor.com/api/projects/status/gmu6b3ltc80hr3k9?svg=true) ![img](https://ci.appveyor.com/api/projects/status/gmu6b3ltc80hr3k9?svg=true)
[![Discord](https://discordapp.com/api/guilds/117523346618318850/widget.png)](https://discord.gg/0ehQwTK2RBjAxzEY) [![Discord](https://discordapp.com/api/guilds/117523346618318850/widget.png)](https://discord.gg/nadekobot)
[![Documentation Status](https://readthedocs.org/projects/nadekobot/badge/?version=latest)](http://nadekobot.readthedocs.io/en/latest/?badge=latest) [![Documentation Status](https://readthedocs.org/projects/nadekobot/badge/?version=latest)](http://nadekobot.readthedocs.io/en/latest/?badge=latest)
# NadekoBot [![nadeko0](https://cdn.discordapp.com/attachments/266240393639755778/281920716809699328/part1.png)](http://nadekobot.xyz)
[![nadeko1](https://cdn.discordapp.com/attachments/155726317222887425/252095170676391936/A1.jpg)](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303) [![nadeko1](https://cdn.discordapp.com/attachments/266240393639755778/281920134967328768/part2.png)](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303)
[![nadeko2](https://cdn.discordapp.com/attachments/155726317222887425/252095207514832896/A2.jpg)](http://nadekobot.readthedocs.io/en/latest/Commands%20List/) [![nadeko2](https://cdn.discordapp.com/attachments/266240393639755778/281920161311883264/part3.png)](http://nadekobot.readthedocs.io/en/latest/Commands%20List/)
##For Update, Help and Guidlines
`Follow me on twitter for updates. | Join my Discord server if you need help. | Read the Docs for hosting guides.`
[![twitter](https://cdn.discordapp.com/attachments/155726317222887425/252192520094613504/twiter_banner.JPG)](https://twitter.com/TheNadekoBot) [![discord](https://cdn.discordapp.com/attachments/155726317222887425/252192415673221122/discord_banner.JPG)](https://discord.gg/0ehQwTK2RBjAxzEY) [![Wiki](https://cdn.discordapp.com/attachments/155726317222887425/252192472849973250/read_the_docs_banner.JPG)](http://nadekobot.readthedocs.io/en/latest/)
## For Updates, Help and Guidelines
| [![twitter](https://cdn.discordapp.com/attachments/155726317222887425/252192520094613504/twiter_banner.JPG)](https://twitter.com/TheNadekoBot) | [![discord](https://cdn.discordapp.com/attachments/266240393639755778/281920766490968064/discord.png)](https://discord.gg/nadekobot) | [![Wiki](https://cdn.discordapp.com/attachments/266240393639755778/281920793330581506/datcord.png)](http://nadekobot.readthedocs.io/en/latest/)
| --- | --- | --- |
| **Follow me on Twitter.** | **Join my Discord server for help.** | **Read the Docs for self-hosting.** |

View File

@ -1,6 +1,6 @@
You can support the project on patreon: <https://patreon.com/nadekobot> or paypal: <https://www.paypal.me/Kwoth> You can support the project on patreon: <https://patreon.com/nadekobot> or paypal: <https://paypal.me/Kwoth>
##Table Of Contents ##Table of contents
- [Help](#help) - [Help](#help)
- [Administration](#administration) - [Administration](#administration)
- [ClashOfClans](#clashofclans) - [ClashOfClans](#clashofclans)
@ -16,71 +16,18 @@ You can support the project on patreon: <https://patreon.com/nadekobot> or paypa
### Administration ### Administration
Command and aliases | Description | Usage Commands and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`.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` `.resetperms` | Resets the bot's permissions module on this server to the default value. **Requires Administrator server permission.** | `.resetperms`
`.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` `.resetglobalperms` | Resets global permissions set by bot owner. **Bot owner only** | `.resetglobalperms`
`.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` `.delmsgoncmd` | Toggles the automatic deletion of the user's successful command message to prevent chat flood. **Requires Administrator server permission.** | `.delmsgoncmd`
`.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`
`.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 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`
`.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. The role you are renaming must be lower than bot's highest role. **Requires ManageRoles server permission.** | `.renr "First role" SecondRole`
`.removeallroles` `.rar` | Removes all roles from a mentioned user. **Requires ManageRoles server permission.** | `.rar @User` `.removeallroles` `.rar` | Removes all roles from a mentioned user. **Requires ManageRoles server permission.** | `.rar @User`
`.createrole` `.cr` | Creates a role with a given name. **Requires ManageRoles server permission.** | `.cr Awesome Role` `.createrole` `.cr` | Creates a role with a given name. **Requires ManageRoles server permission.** | `.cr Awesome Role`
`.rolehoist` `.rh` | Toggles if this role is displayed in the sidebar or not **Requires ManageRoles server permission.** | `.rh Guests true` or `.rh "Space Wizards" true
`.rolecolor` `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. **Requires ManageRoles server permission.** | `.rc Admin 255 200 100` or `.rc Admin ffba55` `.rolecolor` `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. **Requires ManageRoles server permission.** | `.rc Admin 255 200 100` or `.rc Admin ffba55`
`.ban` `.b` | Bans a user by ID or name with an optional message. **Requires BanMembers server permission.** | `.b "@some Guy" Your behaviour is toxic.`
`.softban` `.sb` | Bans and then unbans a user by ID or name with an optional message. **Requires KickMembers server permission.** **Requires ManageMessages server permission.** | `.sb "@some Guy" Your behaviour is toxic.`
`.kick` `.k` | Kicks a mentioned user. **Requires KickMembers server permission.** | `.k "@some Guy" Your behaviour is toxic.`
`.deafen` `.deaf` | Deafens mentioned user or users. **Requires DeafenMembers server permission.** | `.deaf "@Someguy"` or `.deaf "@Someguy" "@Someguy"` `.deafen` `.deaf` | Deafens mentioned user or users. **Requires DeafenMembers server permission.** | `.deaf "@Someguy"` or `.deaf "@Someguy" "@Someguy"`
`.undeafen` `.undef` | Undeafens mentioned user or users. **Requires DeafenMembers server permission.** | `.undef "@Someguy"` or `.undef "@Someguy" "@Someguy"` `.undeafen` `.undef` | Undeafens mentioned user or users. **Requires DeafenMembers server permission.** | `.undef "@Someguy"` or `.undef "@Someguy" "@Someguy"`
`.delvoichanl` `.dvch` | Deletes a voice channel with a given name. **Requires ManageChannels server permission.** | `.dvch VoiceChannelName` `.delvoichanl` `.dvch` | Deletes a voice channel with a given name. **Requires ManageChannels server permission.** | `.dvch VoiceChannelName`
@ -89,20 +36,92 @@ Command and aliases | Description | Usage
`.creatxtchanl` `.ctch` | Creates a new text channel with a given name. **Requires ManageChannels server permission.** | `.ctch TextChannelName` `.creatxtchanl` `.ctch` | Creates a new text channel with a given name. **Requires ManageChannels server permission.** | `.ctch TextChannelName`
`.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` number of 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` number of 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X`
`.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 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 the 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` `.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
`.gvc` | Toggles game voice channel feature in the voice channel you're currently in. Users who join the game voice channel will get automatically redirected to the voice channel with the name of their current game, if it exists. Can't move users to channels that the bot has no connect permission for. One per server. **Requires Administrator server permission.** | `.gvc`
`.languageset` `.langset` | Sets this server's response language. If bot's response strings have been translated to that language, bot will use that language in this server. Reset by using `default` as the locale name. Provide no arguments to see currently set language. | `.langset de-DE ` or `.langset default`
`.langsetdefault` `.langsetd` | Sets the bot's default response language. All servers which use a default locale will use this one. Setting to `default` will use the host's current culture. Provide no arguments to see currently set language. | `.langsetd en-US` or `.langsetd default`
`.languageslist` `.langli` | List of languages for which translation (or part of it) exist atm. | `.langli`
`.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 is active anywhere on the server. Enables if it isn't 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`
`.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. You can also specify time in minutes (up to 1440) for how long the user should be muted. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.mute @Someone` or `.mute 30 @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%`, `%time%`, `%shardid%`, `%shardcount%`, `%shardguilds%`. **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`
`.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. Max message count is 10. **Requires Administrator server permission.** | `.antispam 3 Mute` or `.antispam 4 Kick` or `.antispam 6 Ban`
`.antispamignore` | Toggles whether antispam ignores current channel. Antispam must be enabled. | `.antispamignore`
`.antilist` `.antilst` | Shows currently enabled protection features. | `.antilist`
`.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`
`.slowmodewl` | Ignores a role or a user from the slowmode feature. **Requires ManageMessages server permission.** | `.slowmodewl SomeRole` or `.slowmodewl AdminDude`
`.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`
`.scadd` | Adds a command to the list of commands which will be executed automatically in the current channel, in the order they were added in, by the bot when it startups up. **Bot owner only** | `.scadd .stats`
`.sclist` | Lists all startup commands in the order they will be executed in. **Bot owner only** | `.sclist`
`.wait` | Used only as a startup command. Waits a certain number of miliseconds before continuing the execution of the following startup commands. **Bot owner only** | `.wait 3000`
`.scrm` | Removes a startup command with the provided command text. **Bot owner only** | `.scrm .stats`
`.scclr` | Removes all startup commands. **Bot owner only** | `.scclr`
`.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 file **Bot owner only** | `.fwtoall`
`.connectshard` | Try (re)connecting a shard with a certain shardid when it dies. No one knows will it work. Keep an eye on the console for errors. **Bot owner only** | `.connectshard 2`
`.leave` | Makes Nadeko leave the server. Either server name or server ID is 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 prefix the channel id with `c:` and the 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' default channel that bot is connected to. **Bot owner only** | `.announce Useless spam`
`.reloadimages` | Reloads images bot is using. Safe to use even when bot is being used heavily. **Bot owner only** | `.reloadimages`
`.greetdel` `.grdel` | Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set it to 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. You can use embed json from <http://nadekobot.xyz/embedbuilder/> instead of a regular text, if you want the message to be embedded. **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. You can use embed json from <http://nadekobot.xyz/embedbuilder/> instead of a regular text, if you want the message to be embedded. **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. You can use embed json from <http://nadekobot.xyz/embedbuilder/> instead of a regular text, if you want the message to be embedded. **Requires ManageServer server permission.** | `.byemsg %user% has left.`
`.byedel` | Sets the time it takes (in seconds) for bye messages to be auto-deleted. Set it to `0` to disable automatic deletion. **Requires ManageServer server permission.** | `.byedel 0` or `.byedel 30`
`.warn` | Warns a user. **Requires BanMembers server permission.** | `.warn @b1nzy Very rude person`
`.warnlog` | See a list of warnings of a certain user. **Requires BanMembers server permission.** | `.warnlog @b1nzy`
`.warnclear` `.warnc` | Clears all warnings from a certain user. **Requires BanMembers server permission.** | `.warnclear @PoorDude`
`.warnpunish` `.warnp` | Sets a punishment for a certain number of warnings. Provide no punishment to remove. **Requires BanMembers server permission.** | `.warnpunish 5 Ban` or `.warnpunish 3`
`.warnpunishlist` `.warnpl` | Lists punishments for warnings. | `.warnpunishlist`
`.ban` `.b` | Bans a user by ID or name with an optional message. **Requires BanMembers server permission.** | `.b "@some Guy" Your behaviour is toxic.`
`.unban` | Unbans a user with the provided user#discrim or id. **Requires BanMembers server permission.** | `.unban kwoth#1234` or `.unban 123123123`
`.softban` `.sb` | Bans and then unbans a user by ID or name with an optional message. **Requires KickMembers server permission.** **Requires ManageMessages server permission.** | `.sb "@some Guy" Your behaviour is toxic.`
`.kick` `.k` | Kicks a mentioned user. **Requires KickMembers server permission.** | `.k "@some Guy" Your behaviour is toxic.`
`.vcrole` | Sets or resets a role which will be given to users who join the voice channel you're in when you run this command. Provide no role name to disable. You must be in a voice channel to run this command. **Requires ManageRoles server permission.** **Requires ManageChannels server permission.** | `.vcrole SomeRole` or `.vcrole`
`.vcrolelist` | Shows a list of currently set voice channel roles. | `.vcrolelist`
`.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.** | `.v+t`
`.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)
### ClashOfClans ### ClashOfClans
Command and aliases | Description | Usage Commands and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`,createwar` `,cw` | Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. | `,cw 15 The Enemy Clan` `,createwar` `,cw` | Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. **Requires ManageMessages server permission.** | `,cw 15 The Enemy Clan`
`,startwar` `,sw` | Starts a war with a given number. | `,sw 15` `,startwar` `,sw` | Starts a war with a given number. | `,sw 15`
`,listwar` `,lw` | Shows the active war claims by a number. Shows all wars in a short way if no number is specified. | `,lw [war_number] or ,lw` `,listwar` `,lw` | Shows the active war claims by a number. Shows all wars in a short way if no number is specified. | `,lw [war_number]` or `,lw`
`,claim` `,call` `,c` | Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. | `,call [war_number] [base_number] [optional_other_name]` `,claim` `,call` `,c` | Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. | `,call [war_number] [base_number] [optional_other_name]`
`,claimfinish1` `,cf1` | Finish your claim with 1 star if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. | `,cf1 1` or `,cf1 1 5` `,claimfinish1` `,cf1` | Finish your claim with 1 star if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. | `,cf1 1` or `,cf1 1 5`
`,claimfinish2` `,cf2` | Finish your claim with 2 stars if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. | `,cf2 1` or `,cf2 1 5` `,claimfinish2` `,cf2` | Finish your claim with 2 stars if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. | `,cf2 1` or `,cf2 1 5`
@ -110,157 +129,165 @@ Command and aliases | Description | Usage
`,endwar` `,ew` | Ends the war with a given index. | `,ew [war_number]` `,endwar` `,ew` | Ends the war with a given index. | `,ew [war_number]`
`,unclaim` `,ucall` `,uc` | Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim | `,uc [war_number] [optional_other_name]` `,unclaim` `,ucall` `,uc` | Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim | `,uc [war_number] [optional_other_name]`
###### [Back to TOC](#table-of-contents) ###### [Back to ToC](#table-of-contents)
### CustomReactions ### CustomReactions
Command and aliases | Description | Usage Commands and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`.addcustreact` `.acr` | Add a custom reaction with a trigger and a response. Running this command in server requires Administration permission. Running this command in DM is Bot Owner only and adds a new global custom reaction. Guide here: <http://nadekobot.readthedocs.io/en/latest/Custom%20Reactions/> | `.acr "hello" Hi there %user%` `.addcustreact` `.acr` | Add a custom reaction with a trigger and a response. Running this command in server requires the Administration permission. Running this command in DM is Bot Owner only and adds a new global custom reaction. Guide here: <http://nadekobot.readthedocs.io/en/latest/Custom%20Reactions/> | `.acr "hello" Hi there %user%`
`.listcustreact` `.lcr` | Lists global or server custom reactions (20 commands per page). Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. Specifying `all` argument instead of the number will DM you a text file with a list of all custom reactions. | `.lcr 1` or `.lcr all` `.listcustreact` `.lcr` | Lists global or server custom reactions (20 commands per page). Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. Specifying `all` argument instead of the number will DM you a text file with a list of all custom reactions. | `.lcr 1` or `.lcr all`
`.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 privileges 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. **Bot Owner only.** | `.crstatsclear` or `.crstatsclear rng` `.crdm` | Toggles whether the response message of the custom reaction will be sent as a direct message. | `.crdm 44`
`.crad` | Toggles whether the message triggering the custom reaction will be automatically deleted. | `.crad 59`
`.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 Commands and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`$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` `$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 - x4 and 100 x10. | `$br 5`
`$leaderboard` `$lb` | Displays bot currency leaderboard. | `$lb` `$leaderboard` `$lb` | Displays the bot's 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`
`$startevent` | Starts one of the events seen on public nadeko. **Bot owner only** | `$startevent flowerreaction`
`$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.95x the currency you've bet (rounded up). Multiplier can be changed by the bot owner. | `$bf 5 heads` or `$bf 3 t`
`$shop` | Lists this server's administrators' shop. Paginated. | `$shop` or `$shop 2`
`$buy` | Buys an item from the shop on a given index. If buying items, make sure that the bot can DM you. | `$buy 2`
`$shopadd` | Adds an item to the shop by specifying type price and name. Available types are role and list. **Requires Administrator server permission.** | `$shopadd role 1000 Rich`
`$shoplistadd` | Adds an item to the list of items for sale in the shop entry given the index. You usually want to run this command in the secret channel, so that the unique items are not leaked. **Requires Administrator server permission.** | `$shoplistadd 1 Uni-que-Steam-Key`
`$shoprem` `$shoprm` | Removes an item from the shop by its color. **Requires Administrator server permission.** | `$shoprm 1`
`$slotstats` | Shows the total stats of the slot command for this bot's session. **Bot owner only** | `$slotstats`
`$slottest` | Tests to see how much slots payout for X number of plays. **Bot owner only** | `$slottest 1000`
`$slot` | Play Nadeko slots. Max bet is 9999. 1.5 second cooldown per user. | `$slot 5`
`$claimwaifu` `$claim` | Claim a waifu for yourself by spending currency. You must spend at least 10% more than her current value unless she set `$affinity` towards you. | `$claim 50 @Himesama`
`$divorce` | Releases your claim on a specific waifu. You will get some of the money you've spent back unless that waifu has an affinity towards you. 6 hours cooldown. | `$divorce @CheatingSloot`
`$affinity` | Sets your affinity towards someone you want to be claimed by. Setting affinity will reduce their `$claim` on you by 20%. You can leave second argument empty to clear your affinity. 30 minutes cooldown. | `$affinity @MyHusband` or `$affinity`
`$waifus` `$waifulb` | Shows top 9 waifus. | `$waifus`
`$waifuinfo` `$waifustats` | Shows waifu stats for a target person. Defaults to you if no user is provided. | `$waifuinfo @MyCrush` or `$waifuinfo`
###### [Back to TOC](#table-of-contents) ###### [Back to ToC](#table-of-contents)
### Games ### Games
Command and aliases | Description | Usage Commands and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`>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`
`>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`
`>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 channel 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` `>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` `>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` `>rps` | Play a game of Rocket-Paperclip-Scissors with Nadeko. | `>rps scissors`
`>rategirl` | Use the universal hot-crazy wife zone matrix to determine the girl's worth. It is everything young men need to know about women. At any moment in time, any woman you have previously located on this chart can vanish from that location and appear anywhere else on the chart. | `>rategirl @SomeGurl`
`>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows` `>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows`
`>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | `>leet 3 Hello` `>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | `>leet 3 Hello`
`>acrophobia` `>acro` | Starts an Acrophobia game. Second argument 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 an amount of currency to plant it in this channel. Default is 1. (If bot is restarted or crashes, the currency will be lost) | `>plant` or `>plant 5`
`>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`
`>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`
`>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`
`>tictactoe` `>ttt` | Starts a game of tic tac toe. Another user must run the command in the same channel in order to accept the challenge. Use numbers 1-9 to play. 15 seconds per move. | >ttt
`>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`
`>tq` | Quits current trivia after current question. | `>tq`
###### [Back to TOC](#table-of-contents) ###### [Back to ToC](#table-of-contents)
### Help ### Help
Command and aliases | Description | Usage Commands 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 the full name or only the 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 -cmds` 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)
### Music ### Music
Command and aliases | Description | Usage Commands and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`!!next` `!!n` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. You can skip multiple songs, but in that case songs will not be requeued if !!rcs or !!rpl is enabled. | `!!n` or `!!n 5` `!!next` `!!n` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. You can skip multiple songs, but in that case songs will not be requeued if !!rcs or !!rpl is enabled. | `!!n` or `!!n 5`
`!!stop` `!!s` | Stops the music and clears the playlist. Stays in the channel. | `!!s` `!!stop` `!!s` | Stops the music and clears the playlist. Stays in the channel. | `!!s`
`!!destroy` `!!d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!!d` `!!destroy` `!!d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!!d`
`!!pause` `!!p` | Pauses or Unpauses the song. | `!!p` `!!pause` `!!p` | Pauses or Unpauses the song. | `!!p`
`!!fairplay` `!!fp` | Toggles fairplay. While enabled, music player will prioritize songs from users who didn't have their song recently played instead of the song's position in the queue. | `!!fp` `!!fairplay` `!!fp` | Toggles fairplay. While enabled, the bot will prioritize songs from users who didn't have their song recently played instead of the song's position in the queue. | `!!fp`
`!!queue` `!!q` `!!yq` | Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**. | `!!q Dream Of Venice` `!!queue` `!!q` `!!yq` | Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**. | `!!q Dream Of Venice`
`!!soundcloudqueue` `!!sq` | Queue a soundcloud song using keywords. Bot will join your voice channel.**You must be in a voice channel**. | `!!sq Dream Of Venice` `!!soundcloudqueue` `!!sq` | Queue a soundcloud song using keywords. Bot will join your voice channel. **You must be in a voice channel**. | `!!sq Dream Of Venice`
`!!listqueue` `!!lq` | Lists 15 currently queued songs per page. Default page is 1. | `!!lq` or `!!lq 2` `!!listqueue` `!!lq` | Lists 15 currently queued songs per page. Default page is 1. | `!!lq` or `!!lq 2`
`!!nowplaying` `!!np` | Shows the song currently playing. | `!!np` `!!nowplaying` `!!np` | Shows the song that the bot is currently playing. | `!!np`
`!!volume` `!!vol` | Sets the music volume 0-100% | `!!vol 50` `!!volume` `!!vol` | Sets the music playback volume (0-100%) | `!!vol 50`
`!!defvol` `!!dv` | Sets the default music volume when music playback is started (0-100). Persists through restarts. | `!!dv 80` `!!defvol` `!!dv` | Sets the default music volume when music playback is started (0-100). Persists through restarts. | `!!dv 80`
`!!shuffle` `!!sh` | Shuffles the current playlist. | `!!sh` `!!shuffle` `!!sh` | Shuffles the current playlist. | `!!sh`
`!!playlist` `!!pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `!!pl playlist link or name` `!!playlist` `!!pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `!!pl playlist link or name`
`!!soundcloudpl` `!!scpl` | Queue a soundcloud playlist using a link. | `!!scpl soundcloudseturl` `!!soundcloudpl` `!!scpl` | Queue a Soundcloud playlist using a link. | `!!scpl soundcloudseturl`
`!!localplaylst` `!!lopl` | Queues all songs from a directory. **Bot Owner only.** | `!!lopl C:/music/classical` `!!localplaylst` `!!lopl` | Queues all songs from a directory. **Bot owner only** | `!!lopl C:/music/classical`
`!!radio` `!!ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: <https://streamable.com/al54>) | `!!ra radio link here` `!!radio` `!!ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: <https://streamable.com/al54>) | `!!ra radio link here`
`!!local` `!!lo` | Queues a local file by specifying a full path. **Bot Owner only.** | `!!lo C:/music/mysong.mp3` `!!local` `!!lo` | Queues a local file by specifying a full path. **Bot owner only** | `!!lo C:/music/mysong.mp3`
`!!move` `!!mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!!mv`
`!!remove` `!!rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!!rm 5` `!!remove` `!!rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!!rm 5`
`!!movesong` `!!ms` | Moves a song from one position to another. | `!!ms 5>3` `!!movesong` `!!ms` | Moves a song from one position to another. | `!!ms 5>3`
`!!setmaxqueue` `!!smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `!!smq 50` or `!!smq` `!!setmaxqueue` `!!smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `!!smq 50` or `!!smq`
`!!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. Playlist name must be no longer than 20 characters and must not 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 its 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. Works 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`
`!!setmusicchannel` `!!smch` | Sets the current channel as the default music output channel. This will output playing, finished, paused and removed songs to that channel instead of the channel where the first song was queued in. **Requires ManageMessages server permission.** | `!!smch`
###### [Back to TOC](#table-of-contents) ###### [Back to ToC](#table-of-contents)
### NSFW ### NSFW
Command and aliases | Description | Usage Commands 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. **Requires ManageMessages channel permission.** | `~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`
`~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`
`~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`
`~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`
`~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`
`~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`
`~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)
### Permissions ### Permissions
Command and aliases | Description | Usage Commands 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. Supply no parameters to see the current one. Default 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`
`;removeperm` `;rp` | Removes a permission from a given position in Permissions list. | `;rp 1` `;removeperm` `;rp` | Removes a permission from a given position in the Permissions list. | `;rp 1`
`;moveperm` `;mp` | Moves permission from one position to another in Permissions list. | `;mp 2 4` `;moveperm` `;mp` | Moves permission from one position to another in the Permissions list. | `;mp 2 4`
`;srvrcmd` `;sc` | Sets a command's permission at the server level. | `;sc "command name" disable` `;srvrcmd` `;sc` | Sets a command's permission at the server level. | `;sc "command name" disable`
`;srvrmdl` `;sm` | Sets a module's permission at the server level. | `;sm ModuleName enable` `;srvrmdl` `;sm` | Sets a module's permission at the server level. | `;sm ModuleName enable`
`;usrcmd` `;uc` | Sets a command's permission at the user level. | `;uc "command name" enable SomeUsername` `;usrcmd` `;uc` | Sets a command's permission at the user level. | `;uc "command name" enable SomeUsername`
@ -273,109 +300,142 @@ 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 an 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 an 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 it 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 deletion of invites posted in the server. Does not affect the Bot Owner. | `;sfi`
`;chnlfilterinv` `;cfi` | Toggles automatic deletion of invites posted in the channel. Does not negate the `;srvrfilterinv` enabled setting. Does not affect the Bot Owner. | `;cfi`
`;srvrfilterwords` `;sfw` | Toggles automatic deletion of messages containing filtered words on the server. Does not affect the Bot Owner. | `;sfw`
`;chnlfilterwords` `;cfw` | Toggles automatic deletion of messages containing filtered words on the channel. Does not negate the `;srvrfilterwords` enabled setting. Does not affect the 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`
`;listglobalperms` `;lgp` | Lists global permissions set by the bot owner. **Bot owner only** | `;lgp`
`;globalmodule` `;gmod` | Enable or disable a module from use on all servers. **Bot owner only** | `;gmod nsfw disable`
`;globalcommand` `;gcmd` | Enables or disables a command from use on all servers. **Bot owner only** | `;gcmd `
###### [Back to TOC](#table-of-contents) ###### [Back to ToC](#table-of-contents)
### Pokemon ### Pokemon
Command and aliases | Description | Usage Commands 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 Commands and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`~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`
`~translate` `~trans` | Translates from>to text. From the given language to the destination language. | `~trans en>fr Hello`
`~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`
`~autotranslang` `~atl` | `~atl en>fr` | Sets your source and target language to be used with `~at`. Specify no arguments to remove previously set value.
`~translangs` | Lists the valid languages for translation. | `~translangs`
`~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`
`~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`
`~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`
`~pokemon` `~poke` | Searches for a pokemon. | `~poke Sylveon`
`~pokemonability` `~pokeab` | Searches for a pokemon ability. | `~pokeab overgrow`
`~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`
`~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` `~weather` `~we` | Shows weather data for a specified city. You can also specify a country after a comma. | `~we Moscow, RU`
`~time` | Shows the current time and timezone in the specified location. | `~time London, UK`
`~youtube` `~yt` | Searches youtubes and shows the first result | `~yt query` `~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` `~imdb` `~omdb` | Queries omdb for movies or series, show first result. | `~imdb Batman vs Superman`
`~randomcat` `~meow` | Shows a random cat image. | `~meow` `~randomcat` `~meow` | Shows a random cat image. | `~meow`
`~randomdog` `~woof` | Shows a random dog image. | `~woof` `~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` `~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` `~randomimage` `~rimg` | Pulls a random image using a search parameter. | `~rimg cute kitten`
`~lmgtfy` | Google something for an idiot. | `~lmgtfy query` `~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` `~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` `~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` `~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` `~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` `~yodify` `~yoda` | Translates your normal sentences into Yoda styled sentences! | `~yoda my feelings hurt`
`~urbandict` `~ud` | Searches Urban Dictionary for a word. | `~ud Pineapple` `~urbandict` `~ud` | Searches Urban Dictionary for a word. | `~ud Pineapple`
`~define` `~def` | Finds a definition of a word. | `~def heresy` `~define` `~def` | Finds a definition of a word. | `~def heresy`
`~#` | Searches Tagdef.com for a hashtag. | `~# ff` `~#` | Searches Tagdef.com for a hashtag. | `~# ff`
`~catfact` | Shows a random catfact from <http://catfacts-api.appspot.com/api/facts> | `~catfact` `~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"` `~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` `~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` `~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` `~wikipedia` `~wiki` | Gives you back a wikipedia link | `~wiki query`
`~color` `~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00` `~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"` `~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"` `~avatar` `~av` | Shows a mentioned person's avatar. | `~av "@SomeGuy"`
`~wikia` | Gives you back a wikia link | `~wikia mtg Vigilance` or `~wikia mlp Dashy` `~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` `~lolban` | Shows top banned champions ordered by ban rate. | `~lolban`
`~mal` | Shows basic info from a MyAnimeList profile. | `~mal straysocks`
`~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 Chuck Norris joke from <http://tambal.azurewebsites.net/joke/random> | `~cn`
`~wowjoke` | Get one of Kwoth's penultimate WoW jokes. | `~wowjoke`
`~magicitem` `~mi` | Shows a random magic item from <https://1d4chan.org/wiki/List_of_/tg/%27s_magic_items> | `~mi`
`~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ | `~memelist` `~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"` `~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"`
`~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`
`~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`
`~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`
`~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`
`~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`
`~autotranslang` `~atl` | Sets your source and target language to be used with `~at`. Specify no arguments to remove previously set value. | `~atl en>fr`
`~translangs` | Lists the valid languages for translation. | `~translangs`
`~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`
###### [Back to TOC](#table-of-contents) ###### [Back to ToC](#table-of-contents)
### Utility ### Utility
Command and aliases | Description | Usage Commands and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`.convertlist` | List of the convertible dimensions and currencies. | `.convertlist` `.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. **Requires ManageRoles server permission.** **Bot owner only** | `.rrc 60 MyLsdRole #ff0000 #00ff00 #0000ff` or `.rrc 0 MyLsdRole`
`.convert` | Convert quantities. Use `.convertlist` to see supported dimensions and currencies. | `.convert m km 1000`
`.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!`
`.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%!`
`.listquotes` `.liqu` | `.liqu` or `.liqu 3` | Lists all quotes on the server ordered alphabetically. 15 Per page.
`...` | Shows a random quote with a specified name. | `... abc`
`..` | Adds a new quote with the specified name and message. | `.. sayhi Hi`
`.deletequote` `.delq` | Deletes a random quote with the specified keyword. You have to either be server Administrator or the creator of the quote to delete it. | `.delq abc`
`.delallq` `.daq` | Deletes all quotes on a specified keyword. **Requires Administrator server permission.** | `.delallq kek`
`.serverinfo` `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | `.sinfo Some Server`
`.channelinfo` `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | `.cinfo #some-channel`
`.userinfo` `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | `.uinfo @SomeUser`
`.calculate` `.calc` | Evaluate a mathematical expression. | `.calc 1+1`
`.calcops` | Shows all available operations in .calc command | `.calcops`
`.togethertube` `.totube` | Creates a new room on <https://togethertube.com> and shows the link in the chat. | `.totube` `.togethertube` `.totube` | Creates a new room on <https://togethertube.com> and shows the link in the chat. | `.totube`
`.whosplaying` `.whpl` | Shows a list of users who are playing the specified game. | `.whpl Overwatch` `.whosplaying` `.whpl` | Shows a list of users who are playing the specified game. | `.whpl Overwatch`
`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. If the list is too long for 1 message, you must have Manage Messages permission. | `.inrole Role` `.inrole` | Lists every person from the specified role on this server. You can use role ID, role name. | `.inrole Some Role`
`.checkmyperms` | Checks your user-specific permissions on this channel. | `.checkmyperms` `.checkmyperms` | Checks your user-specific permissions on this channel. | `.checkmyperms`
`.userid` `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy"` `.userid` `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy"`
`.channelid` `.cid` | Shows current channel ID. | `.cid` `.channelid` `.cid` | Shows current channel ID. | `.cid`
`.serverid` `.sid` | Shows current server ID. | `.sid` `.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` `.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` `.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`
`.shardstats` | Stats for shards. Paginated with 25 shards per page. | `.shardstats` or `.shardstats 2`
`.shardid` | Shows which shard is a certain guild on, by guildid. | `.shardid 117523346618318850`
`.stats` | Shows some basic stats for Nadeko. | `.stats` `.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` `.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` `.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` `.savechat` | Saves a number of messages to a text file and sends it to you. **Bot owner only** | `.savechat 150`
`.ping` | Ping the bot to see if there are latency issues. | `.ping`
`.activity` | Checks for spammers. **Bot owner only** | `.activity`
`.calculate` `.calc` | Evaluate a mathematical expression. | `.calc 1+1`
`.calcops` | Shows all available operations in the `.calc` command | `.calcops`
`.alias` `.cmdmap` | Create a custom alias for a certain Nadeko command. Provide no alias to remove the existing one. **Requires Administrator server permission.** | `.alias allin $bf 100 h` or `.alias "linux thingy" >loonix Spyware Windows`
`.aliaslist` `.cmdmaplist` `.aliases` | Shows the list of currently set aliases. Paginated. | `.aliaslist` or `.aliaslist 3`
`.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 a cross server channel instance from this channel. **Requires ManageServer server permission.** | `.lcsc`
`.serverinfo` `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | `.sinfo Some Server`
`.channelinfo` `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | `.cinfo #some-channel`
`.userinfo` `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | `.uinfo @SomeUser`
`.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. You can have up to 5 repeating messages on the server in total. **Requires ManageMessages server permission.** | `.repeat 5 Hello there`
`.repeatlist` `.replst` | Shows currently repeating messages and their indexes. **Requires ManageMessages server permission.** | `.repeatlist`
`.parewrel` | Forces the update of the list of patrons who are eligible for the reward. **Bot owner only** | `.parewrel`
`.clparew` | Claim patreon rewards. If you're subscribed to bot owner's patreon you can use this command to claim your rewards - assuming bot owner did setup has their patreon key. | `.clparew`
`.listquotes` `.liqu` | Lists all quotes on the server ordered alphabetically. 15 Per page. | `.liqu` or `.liqu 3`
`...` | Shows a random quote with a specified name. | `... abc`
`.qsearch` | Shows a random quote for a keyword that contains any text specified in the search. | `.qsearch keyword text`
`.quoteid` `.qid` | Displays the quote with the specified ID number. Quote ID numbers can be found by typing `.liqu [num]` where `[num]` is a number of a page which contains 15 quotes. | `.qid 123456`
`..` | Adds a new quote with the specified name and message. | `.. sayhi Hi`
`.deletequote` `.delq` | Deletes a quote with the specified ID. You have to be either server Administrator or the creator of the quote to delete it. | `.delq 123456`
`.delallq` `.daq` | Deletes all quotes on a specified keyword. **Requires Administrator server permission.** | `.delallq kek`
`.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!`
`.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%!`
`.convertlist` | List of the convertible dimensions and currencies. | `.convertlist`
`.convert` | Convert quantities. Use `.convertlist` to see supported dimensions and currencies. | `.convert m km 1000`

View File

@ -19,7 +19,7 @@
###Question 5: I have an issue/bug/suggestion, where do I put it so it gets noticed? ###Question 5: I have an issue/bug/suggestion, where do I put it so it gets noticed?
----------- -----------
**Answer:** First, check [issues](https://github.com/Kwoth/NadekoBot/issues "GitHub NadekoBot Issues"), then check the `#suggestions` channel in the Nadeko [help server](https://discord.gg/0ehQwTK2RBjAxzEY). **Answer:** First, check [issues](https://github.com/Kwoth/NadekoBot/issues "GitHub NadekoBot Issues"), then check the `#suggestions` channel in the Nadeko [help server](https://discord.gg/nadekobot).
If your problem or suggestion is not there, feel free to request/notify us about it either in the Issues section of GitHub for issues or in the `#suggestions` channel on the Nadeko help server for suggestions. If your problem or suggestion is not there, feel free to request/notify us about it either in the Issues section of GitHub for issues or in the `#suggestions` channel on the Nadeko help server for suggestions.

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

@ -8,11 +8,11 @@ Assuming you have followed the link above to setup an account and Droplet with 6
**Go through this whole guide before setting up Nadeko** **Go through this whole guide before setting up Nadeko**
#### Prerequisites ####Prerequisites
- Download [PuTTY](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html) - Download [PuTTY](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html)
- Download [CyberDuck](https://cyberduck.io) or [WinSCP](https://winscp.net/eng/download.php) - Download [WinSCP](https://winscp.net/eng/download.php) *(optional)*
#### Follow these steps ####Starting up
- **Open PuTTY.exe** that you downloaded before, and paste or enter your `IP address` and then click **Open**. - **Open PuTTY.exe** that you downloaded before, and paste or enter your `IP address` and then click **Open**.
If you entered your Droplets IP address correctly, it should show **login as:** in a newly opened window. If you entered your Droplets IP address correctly, it should show **login as:** in a newly opened window.
@ -24,46 +24,254 @@ If you entered your Droplets IP address correctly, it should show **login as:**
**NOTE:** Copy the commands, and just paste them using **mouse single right-click.** **NOTE:** Copy the commands, and just paste them using **mouse single right-click.**
####Installing Git ####Creating and Inviting bot
- Read here how to [create a DiscordBot application](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#creating-discordbot-application)
- [Visual Invite Guide](http://discord.kongslien.net/guide.html) **(Note: Client ID is your Bot ID)**
- Copy your `Client ID` from your [applications page](https://discordapp.com/developers/applications/me).
- Replace the **12345678** in this link:
`https://discordapp.com/oauth2/authorize?client_id=`12345678`&scope=bot&permissions=66186303`
with your `Client ID`
- The link should now look like this:
`https://discordapp.com/oauth2/authorize?client_id=`**YOUR_CLENT_ID_HERE**`&scope=bot&permissions=66186303`
- Go to the newly created link and pick the server we created, and click `Authorize`
- The bot should have been added to your server.
####Getting NadekoBot
#####Part I
Use the following command to get and run `linuxAIO.sh`
(Remember **Do Not** rename the file **linuxAIO.sh**)
`cd ~ && wget -N https://github.com/Kwoth/NadekoBot-BashScript/raw/master/linuxAIO.sh && bash linuxAIO.sh`
You should see these following options after using the above command:
```
1. Download Dev Build (Latest)
2. Download Stable Build
3. Run Nadeko (Normally)
4. Run Nadeko with Auto Restart (Run Nadeko normally before using this.)
5. Auto-Install Prerequisites (for Ubuntu, Debian and CentOS)
6. Set up credentials.json (if you have downloaded the bot already)
7. To exit
```
#####Part II (Optional)
**If** you are running NadekoBot for the first time on your system and never had any *prerequisites* installed and have Ubuntu, Debian or CentOS, Press `5` and `enter` key, then `y` when you see the following:
```
Welcome to NadekoBot Auto Prerequisites Installer.
Would you like to continue?
```
That will install all the prerequisites your system need to run NadekoBot.
If you prefer to install them [manually](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#installing-manually-optional), click on the link. *(Optional)*
Once *prerequisites* finish installing.
#####Part III
Choose either
`1` to get the **most updated build of NadekoBot**
or
`2` to get the **previously stable build of NadekoBot**
and then press `enter` key.
Once Installation is completed you should see the options again.
Next, check out:
#####Part IV (Optional)
If you prefer to skip this step and want to do it [manually](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#setting-up-sftp) or already have the `credentials.json` file, click on the link. *(Optional)*
- [1. Setting up credentials.json](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#setting-up-credentialsjson)
- [2. To Get the Google API](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-nadekobot-for-music)
- [3. JSON Explanations for other APIs](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/)
You will need the following for the next step:
![botimg](https://cdn.discordapp.com/attachments/251504306010849280/276455844223123457/Capture.PNG)
- **Bot's Client ID** and **Bot's ID** (both are same) [(*required)](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-credentialsjson-file)
- **Bot's Token** (not client secret) [(*required)](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-credentialsjson-file)
- Your **Discord userID** [(*required)](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-credentialsjson-file)
- **Google Api Key** [(optional)](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-nadekobot-for-music)
- **LoL Api Key** [(optional)](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/)
- **Mashape Key** [(optional)](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/)
- **Osu Api Key** [(optional)](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/)
- **Sound Cloud Client Id** [(optional)](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/)
Once you have acquired them, press `6` to **Set up credentials.json**
You will be asked to enter the required informations, just follow the on-screen instructions and enter the required information.
*i.e* If you are asked **Bot's Token**, then just copy and paste or type the **Bot's Token** and press `enter` key.
(If you want to skip any optional infos, just press `enter` key without typing/pasting anything.)
Once done,
#####Part V
You should see the options again.
Next, press `3` to **Run Nadeko (Normally)**
Check in your discord server if your new bot is working properly.
#####Part VI
If your bot is working properly in your server, type `.die` to **shut down the bot**, then press `7` to **exit**.
Next, [Run your bot again with **tmux**.](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#running-nadekobot)
[Check this when you need to **restart** your **NadekoBot** anytime later along with tmux session.](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#restarting-nadeko)
####Running NadekoBot
**Create a new Session:**
- `tmux new -s nadeko`
The above command will create a new session named **nadeko** *(you can replace “nadeko” with anything you prefer and remember its your session name)* so you can run the bot in background without having to keep the PuTTY running.
**Next, we need to run `linuxAIO.sh` in order to get the latest running scripts with patches:**
- `cd ~ && bash linuxAIO.sh`
**From the options,**
Choose `3` to **Run NadekoBot normally.**
**NOTE:** With option `3` (Running Normally), if you use `.die` [command](http://nadekobot.readthedocs.io/en/latest/Commands%20List/#administration) in discord. The bot will shut down and will stay offline until you manually run it again. (best if you want to check the bot.)
Choose `4` to **Run NadekoBot with Auto Restart.**
It will show you more options:
```
1. Run Auto Restart normally without Updating.
2. Auto Restart and Update with Dev Build (latest)
3. Auto Restart and Update with Stable Build
4. Exit
```
**NOTE:** With option `4` (Running with Auto Restart), bot will auto run if you use `.die` [command](http://nadekobot.readthedocs.io/en/latest/Commands%20List/#administration) making the command `.die` to function as restart.
See how that happens:
![img9](https://cdn.discordapp.com/attachments/251504306010849280/251506312893038592/die_explaination.gif)
**Remember** that, while running with Auto Restart, you will need to [close the tmux session](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#restarting-nadeko) to stop the bot completely.
**Now check your Discord, the bot should be online**
Next to **move the bot to background** and to do that, press **CTRL+B+D** (that will detach the nadeko session using TMUX) and you can finally close **PuTTY** if you want.
####Restarting Nadeko
**Restarting NadekoBot:**
**If** you have chosen option `4` to **Run Nadeko with Auto Restart** from Nadeko's `linuxAIO.sh` *[(you got it from this step)](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#getting-nadekobot)*
You can simply type `.die` in the server you have your NadekoBot to make her restart.
**Restarting Nadeko with the Server:**
Open **PuTTY** and login as you have before, type `reboot` and hit Enter.
**Restarting Manually:**
- Kill your previous session, check with `tmux ls`
- `tmux kill-session -t nadeko` (don't forget to replace "nadeko" to what ever you named your bot's session)
- [Run the bot again.](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#running-nadekobot)
####Updating Nadeko
- Connect to the terminal through **PuTTY**.
- `tmux kill-session -t nadeko` (don't forget to replace **nadeko** in the command with the name of your bot's session)
- Make sure the bot is **not** running.
- `tmux new -s nadeko` (**nadeko** is the name of the session)
- `cd ~ && bash linuxAIO.sh`
- Choose either `1` or `2` to update the bot with **latest build** or **stable build** respectively.
- Choose either `3` or `4` to run the bot again with **normally** or **auto restart** respectively.
- Done. You can close **PuTTY** now.
####Installing Manually (Optional)
#####Installing Git
![img1](https://cdn.discordapp.com/attachments/251504306010849280/251504416019054592/git.gif) ![img1](https://cdn.discordapp.com/attachments/251504306010849280/251504416019054592/git.gif)
Ubuntu:
`sudo apt-get install git -y` `sudo apt-get install git -y`
CentOS:
`yum -y install git`
**NOTE:** If the command is not being initiated, hit **Enter** **NOTE:** If the command is not being initiated, hit **Enter**
####Installing .NET Core SDK #####Installing .NET Core SDK
![img2](https://cdn.discordapp.com/attachments/251504306010849280/251504746987388938/dotnet.gif) ![img2](https://cdn.discordapp.com/attachments/251504306010849280/251504746987388938/dotnet.gif)
Go to [this link](https://www.microsoft.com/net/core#ubuntu) provided by microsoft for instructions on how to get the most up to date version of the dotnet core sdk! Go to [this link](https://www.microsoft.com/net/core#ubuntu) (for Ubuntu) or to [this link](https://www.microsoft.com/net/core#linuxcentos) (for CentOS) provided by microsoft for instructions on how to get the most up to date version of the dotnet core sdk!
Make sure that you're on the correct page for your distribution of linux as the guides are different for the various distributions Make sure that you're on the correct page for your distribution of linux as the guides are different for the various distributions.
Install the **currently supported version** `1.0.0-preview2-1-003177`.
You can find it [here](https://github.com/dotnet/core/blob/master/release-notes/download-archives/1.1-preview2.1-download.md) if you prefer manual installing `dpkg` files.
We'll go over the steps here for Ubuntu 16.04 anyway (these will **only** work on Ubuntu 16.04), accurate as of 25/11/2016 We'll go over the steps here for few linux distributions, accurate as of March 08, 2017:
**NOTE:** .NET CORE SDK only supports 64-bit Linux Operating Systems (Raspberry Pis are not supported because of this)
**Ubuntu x64 17.04 & 16.10**
```sh
sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ yakkety main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 417A0893
sudo apt-get update && sudo apt-get install dotnet-dev-1.0.0-preview2.1-003177 -y
``` ```
**Ubuntu x64 16.04**
```sh
sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ xenial main" > /etc/apt/sources.list.d/dotnetdev.list' sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ xenial main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 417A0893 sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 417A0893
sudo apt-get update && sudo apt-get install dotnet-dev-1.0.0-preview2.1-003177 -y sudo apt-get update && sudo apt-get install dotnet-dev-1.0.0-preview2.1-003177 -y
``` ```
**NOTE:** .NET CORE SDK only supports 64-bit Linux Operating Systems (Raspberry Pis are not supported because of this) **Ubuntu x64 14.04**
```sh
sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 417A0893
sudo apt-get update && sudo apt-get install dotnet-dev-1.0.0-preview2.1-003177 -y
```
####Installing Opus Voice Codec and libsodium **Debian 8 x64**
```sh
sudo apt-get install curl libunwind8 gettext -y
curl -sSL -o dotnet.tar.gz https://go.microsoft.com/fwlink/?LinkID=835021
sudo mkdir -p /opt/dotnet && sudo tar zxf dotnet.tar.gz -C /opt/dotnet
sudo ln -s /opt/dotnet/dotnet /usr/local/bin
```
**CentOS 7 x64**
```sh
sudo yum install libunwind libicu -y
curl -sSL -o dotnet.tar.gz https://go.microsoft.com/fwlink/?LinkID=835019
sudo mkdir -p /opt/dotnet && sudo tar zxf dotnet.tar.gz -C /opt/dotnet
sudo ln -s /opt/dotnet/dotnet /usr/local/bin
```
#####Installing Opus Voice Codec and libsodium
![img3](https://cdn.discordapp.com/attachments/251504306010849280/251505294654308353/libopus.gif) ![img3](https://cdn.discordapp.com/attachments/251504306010849280/251505294654308353/libopus.gif)
Ubuntu:
`sudo apt-get install libopus0 opus-tools libopus-dev libsodium-dev -y` `sudo apt-get install libopus0 opus-tools libopus-dev libsodium-dev -y`
####Installing FFMPEG CentOS:
`yum -y install opus opus-devel`
#####Installing FFMPEG
![img4](https://cdn.discordapp.com/attachments/251504306010849280/251505443111829505/ffmpeg.gif) ![img4](https://cdn.discordapp.com/attachments/251504306010849280/251505443111829505/ffmpeg.gif)
Ubuntu:
`apt-get install ffmpeg -y` `apt-get install ffmpeg -y`
Centos:
```sh
yum -y install http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm epel-release
yum -y install ffmpeg
```
**NOTE:** If you are running **UBUNTU 14.04**, you must run these first: **NOTE:** If you are running **UBUNTU 14.04**, you must run these first:
``` ```sh
sudo add-apt-repository ppa:mc3man/trusty-media sudo add-apt-repository ppa:mc3man/trusty-media
sudo apt-get update sudo apt-get update
sudo apt-get dist-upgrade sudo apt-get dist-upgrade
@ -74,49 +282,25 @@ sudo apt-get dist-upgrade
**NOTE:** If you are running **Debian 8 Jessie**, please, follow these steps: **NOTE:** If you are running **Debian 8 Jessie**, please, follow these steps:
`wget http://luxcaeli.de/installer.sh && sudo bash installer.sh` *Thanks to Eleria <3* ```sh
In case you are not able to install it with **installer**, follow these steps:
```
sudo apt-get update sudo apt-get update
echo "deb http://ftp.debian.org/debian jessie-backports main" | tee /etc/apt/sources.list.d/debian-backports.list echo "deb http://ftp.debian.org/debian jessie-backports main" | tee /etc/apt/sources.list.d/debian-backports.list
sudo apt-get update && sudo apt-get install ffmpeg -y sudo apt-get update && sudo apt-get install ffmpeg -y
``` ```
####Installing TMUX #####Installing TMUX
![img5](https://cdn.discordapp.com/attachments/251504306010849280/251505519758409728/tmux.gif) ![img5](https://cdn.discordapp.com/attachments/251504306010849280/251505519758409728/tmux.gif)
Ubuntu:
`sudo apt-get install tmux -y` `sudo apt-get install tmux -y`
####Getting NadekoBot Centos:
Use the following command to get and run `linuxAIO.sh`: `yum -y install tmux`
(Remember **DO NOT** rename the file `linuxAIO.sh`)
`cd ~ && wget -N https://github.com/Kwoth/NadekoBot-BashScript/raw/master/linuxAIO.sh && bash linuxAIO.sh` ####Guide for Advance Users (Optional)
Follow the on screen instructions:
1. To Get the latest build. (most recent updates)
2. To Get the stable build.
Choose either `1` or `2` then press `enter` key.
Once Installation is completed you should see the options again.
Next, choose `5` to exit.
####Creating and Inviting bot
- Read here how to [create a DiscordBot application](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#creating-discordbot-application)
- [Visual Invite Guide](http://discord.kongslien.net/guide.html) *NOTE: Client ID is your Bot ID*
- Copy your `Client ID` from your [applications page](https://discordapp.com/developers/applications/me).
- Replace the `12345678` in this link `https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303` with your `Client ID`.
- The link should now look like this: `https://discordapp.com/oauth2/authorize?client_id=**YOUR_CLENT_ID_HERE**&scope=bot&permissions=66186303`.
- Go to the newly created link and pick the server we created, and click `Authorize`.
- The bot should have been added to your server.
####Guide for Advance Users
**Skip this step if you are a Regular User or New to Linux.** **Skip this step if you are a Regular User or New to Linux.**
@ -135,23 +319,23 @@ Next, choose `5` to exit.
####Setting up SFTP ####Setting up SFTP
- Open **CyberDuck** - Open **WinSCP**
- Click on **Open Connection** (top-left corner), a new window should appear. - Click on **New Site** (top-left corner).
- You should see **FTP (File Transfer Protocol)** in drop-down. - On the right-hand side, you should see **File Protocol** above a drop-down selection menu.
- Change it to **SFTP (SSH File Transfer Protocol)** - Select **SFTP** *(SSH File Transfer Protocol)* if its not already selected.
- Now, in **Server:** paste or type in your `Digital Ocean Droplets IP address`, leave `Port: 22` (no need to change it) - Now, in **Host name:** paste or type in your `Digital Ocean Droplets IP address` and leave `Port: 22` (no need to change it).
- In **Username:** type `root` - In **Username:** type `root`
- In **Password:** type `the new root password (you changed at the start)` - In **Password:** type `the new root password (you changed at the start)`
- Click on **Connect** - Click on **Login**, it should connect.
- It should show you the NadekoBot folder which was created by git earlier - It should show you the NadekoBot folder which was created by git earlier on the right-hand side window.
- Open that folder, then open the `src` folder, followed by another `NadekoBot` folder and you should see `credentials.json` there. - Open that folder, then open the `src` folder, followed by another `NadekoBot` folder and you should see `credentials.json` there.
####Setting up credentials.json ####Setting up credentials.json
- Copy the `credentials.json` to desktop - Copy the `credentials.json` to desktop
- EDIT it as it is guided here: [Setting up credentials.json](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-credentialsjson-file) - EDIT it as it is guided here: [Setting up credentials.json](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-credentialsjson-file)
- Paste/put it back in the folder once done. `(Using CyberDuck/WinSCP)` - Paste/put it back in the folder once done. `(Using WinSCP)`
- **If** you already have Nadeko 1.0 setup and have `credentials.json` and `NadekoBot.db`, you can just copy and paste the `credentials.json` to `NadekoBot/src/NadekoBot` and `NadekoBot.db` to `NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.0/data` using CyberDuck. - **If** you already have Nadeko 1.0 setup and have `credentials.json` and `NadekoBot.db`, you can just copy and paste the `credentials.json` to `NadekoBot/src/NadekoBot` and `NadekoBot.db` to `NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.0/data` using WinSCP.
- **If** you have Nadeko 0.9x follow the [Upgrading Guide](http://nadekobot.readthedocs.io/en/latest/guides/Upgrading%20Guide/) - **If** you have Nadeko 0.9x follow the [Upgrading Guide](http://nadekobot.readthedocs.io/en/latest/guides/Upgrading%20Guide/)
####Setting up Music ####Setting up Music
@ -160,94 +344,32 @@ To set up Nadeko for music and Google API Keys, follow [Setting up NadekoBot for
Once done, go back to **PuTTY** Once done, go back to **PuTTY**
####Running NadekoBot ####Some more Info
**Create a new Session:** #####Info about tmux
- `tmux new -s nadeko`
The above command will create a new session named **nadeko** *(you can replace “nadeko” with anything you prefer and remember its your session name)* so you can run the bot in background without having to keep the PuTTY running.
**Next, we need to run `linuxAIO.sh` in order to get the latest running scripts with patches:**
- `cd ~ && bash linuxAIO.sh`
From the options,
Choose `3` To Run the bot normally.
**NOTE:** With option `3` (Running Normally), if you use `.die` [command](http://nadekobot.readthedocs.io/en/latest/Commands%20List/#administration) in discord. The bot will shut down and will stay offline until you manually run it again. (best if you want to check the bot.)
Choose `4` To Run the bot with Auto Restart.
**NOTE:** With option `4` (Running with Auto Restart), bot will auto run if you use `.die` [command](http://nadekobot.readthedocs.io/en/latest/Commands%20List/#administration) making the command `.die` to function as restart.
See how that happens:
![img9](https://cdn.discordapp.com/attachments/251504306010849280/251506312893038592/die_explaination.gif)
**Remember** that, while running with Auto Restart, you will need to [close the tmux session](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#restarting-nadeko) to stop the bot completely.
**Now check your Discord, the bot should be online**
Next to **move the bot to background** and to do that, press **CTRL+B+D** (this will detach the nadeko session using TMUX), and you can finally close PuTTY now.
####Some more Info (just in case)
**Info about tmux:**
- If you want to **see the sessions** after logging back again, type `tmux ls`, and that will give you the list of sessions running. - If you want to **see the sessions** after logging back again, type `tmux ls`, and that will give you the list of sessions running.
- If you want to **switch to/ see that session**, type `tmux a -t nadeko` (**nadeko** is the name of the session we created before so, replace **“nadeko”** with the session name you created.) - If you want to **switch to/ see that session**, type `tmux a -t nadeko` (**nadeko** is the name of the session we created before so, replace **“nadeko”** with the session name you created.)
- If you want to **kill** NadekoBot **session**, type `tmux kill-session -t nadeko` - If you want to **kill** NadekoBot **session**, type `tmux kill-session -t nadeko`
**If you are running Ubuntu 16.10, and having trouble installing .NET Core:** #####Alternative way to Install
- Go to [Download Page for libicu55_55.1-7_amd64.deb](http://packages.ubuntu.com/en/xenial/amd64/libicu55/download)
- Copy the link with a download option closest to you
- `wget <copied link>` *e.g.* `wget http://mirrors.kernel.org/ubuntu/pool/main/i/icu/libicu55_55.1-7_amd64.deb` (make sure it is downloaded)
- Install with: `dpkg i libicu55_55.1-7_amd64.deb`
- Now go back and install the .NET Core
####Restarting Nadeko
**Restarting Nadeko with the Server:**
Open **PuTTY** and login as you have before, type `reboot` and hit Enter.
**Restarting Manually:**
- Kill your previous session, check with `tmux ls`
- `tmux kill-session -t nadeko` (don't forget to replace "nadeko" to what ever you named your bot's session)
- [Run the bot again.](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#running-nadekobot)
####Updating Nadeko
- Connect to the terminal through PuTTY.
- `tmux kill-session -t nadeko` (don't forget to replace **nadeko** in the command with the name of your bot's session)
- Make sure the bot is **not** running.
- `tmux new -s nadeko` (**nadeko** is the name of the session)
- `cd ~ && bash linuxAIO.sh`
- Choose either `1` or `2` to update the bot with **latest build** or **stable build** respectively.
- Choose either `3` or `4` to run the bot again with **normally** or **auto restart** respectively.
- Done. You can close PuTTY now.
####Alternative way to Install
If the [Nadeko installer](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#getting-nadekobot) shows any kind error, check if you have the `linuxAIO.sh` file and make sure its not renamed or if you want to manually install the bot. Use the following command(s): If the [Nadeko installer](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#getting-nadekobot) shows any kind error, check if you have the `linuxAIO.sh` file and make sure its not renamed or if you want to manually install the bot. Use the following command(s):
![img6](https://cdn.discordapp.com/attachments/251504306010849280/251505587089571850/getting_nadeko.gif) ![img6](https://cdn.discordapp.com/attachments/251504306010849280/251505587089571850/getting_nadeko.gif)
`cd ~ && curl -L https://github.com/Kwoth/NadekoBot-BashScript/raw/master/nadeko_installer.sh | sh` `cd ~ && curl -L https://github.com/Kwoth/NadekoBot-BashScript/raw/master/nadeko_installer.sh | sh`
**OR** **OR**
``` ```sh
cd ~ && git clone -b 1.0 --recursive --depth 1 https://github.com/Kwoth/NadekoBot.git cd ~ && git clone -b dev --recursive --depth 1 https://github.com/Kwoth/NadekoBot.git
cd ~/NadekoBot/discord.net/src/Discord.Net && dotnet restore && cd ../Discord.Net.Commands && dotnet restore && cd ../../../src/NadekoBot/ && dotnet restore && dotnet build --configuration Release cd ~/NadekoBot/discord.net/src/Discord.Net && dotnet restore && cd ../Discord.Net.Commands && dotnet restore && cd ../../../src/NadekoBot/ && dotnet restore && dotnet build --configuration Release
``` ```
If you are getting error using the above steps try: If you are getting error using the above steps try:
``` ```sh
cd ~/NadekoBot/discord.net && dotnet restore -s https://dotnet.myget.org/F/dotnet-core/api/v3/index.json && dotnet restore cd ~/NadekoBot/discord.net && dotnet restore -s https://dotnet.myget.org/F/dotnet-core/api/v3/index.json && dotnet restore
cd ~/NadekoBot/src/NadekoBot/ && dotnet restore && dotnet build --configuration Release cd ~/NadekoBot/src/NadekoBot/ && dotnet restore && dotnet build --configuration Release
``` ```

View File

@ -30,7 +30,7 @@ brew install tmux
- `ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/` - `ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/`
- `ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/` - `ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/`
- Download the [.NET Core SDK](https://www.microsoft.com/net/core#macos), found [here.](https://go.microsoft.com/fwlink/?LinkID=835011) - Download the [.NET Core SDK][.NET Core SDK]
- Open the `.pkg` file you downloaded and install it. - Open the `.pkg` file you downloaded and install it.
- `ln -s /usr/local/share/dotnet/dotnet /usr/local/bin` - `ln -s /usr/local/share/dotnet/dotnet /usr/local/bin`
@ -166,6 +166,7 @@ If you used Screen press CTRL+A+D (this will detach the nadeko screen)
- `dotnet build --configuration Release` - `dotnet build --configuration Release`
[Homebrew]: http://brew.sh/ [Homebrew]: http://brew.sh/
[.NET Core SDK]: https://github.com/dotnet/core/blob/master/release-notes/download-archives/1.1-preview2.1-download.md
[DiscordApp]: https://discordapp.com/developers/applications/me [DiscordApp]: https://discordapp.com/developers/applications/me
[Atom]: https://atom.io/ [Atom]: https://atom.io/
[Invite Guide]: http://discord.kongslien.net/guide.html [Invite Guide]: http://discord.kongslien.net/guide.html

View File

@ -7,18 +7,17 @@ ________________________________________________________________________________
#### Prerequisites #### Prerequisites
- 1) [.NET Core SDK][.NET Core SDK] - 1) [.NET Core SDK][.NET Core SDK]
- 2) [Git][Git] - 2) [Git][Git]
- 3) [FFMPEG][FFMPEG] - 3) Google Account
- 4) Google Account - 4) Soundcloud Account (if you want soundcloud support)
- 5) Soundcloud Account (if you want soundcloud support) - 5) [7zip][7zip] (or whatever you are using, WinRar)
- 6) [7zip][7zip] (or whatever you are using, WinRar) - 6) [Notepad++][Notepad++]
- 7) [Notepad++][Notepad++] - 7) Windows 8 or later
- 8) Windows 8 or later
####Guide ####Guide
- Make sure you have installed both [Git][Git] and the [.NET Core SDK][.NET Core SDK]. - Make sure you have installed both [Git][Git] and the [.NET Core SDK][.NET Core SDK].
- Create a **new folder** anywhere you like and name it `Nadeko`. - Create a **new folder** anywhere you like and name it `Nadeko`.
- Next, [Right-Click on this link](https://github.com/Kwoth/NadekoBotInstallerWin/raw/master/NadekoInstaller.bat) and select **Save link as** and save the file `NadekoInstaller.bat` inside the `Nadeko` folder that we created earlier. (**DO NOT** rename the file `NadekoInstaller.bat`) - Next, [Right-Click on this link](https://github.com/Kwoth/NadekoBotInstallerWin/raw/master/NadekoInstaller.bat) and select **Save link as** and save the file `NadekoInstaller.bat` inside the `Nadeko` folder that we created earlier. (Please **DO NOT** rename the file `NadekoInstaller.bat`.)
- Once that's done, double-click on `NadekoInstaller.bat` to run it. - Once that's done, right-click on `NadekoInstaller.bat` to run it as Administrator.
- From the options, - From the options,
- Choose `1` to get the **most recent build**. - Choose `1` to get the **most recent build**.
- Choose `2` to get the **stable build**. - Choose `2` to get the **stable build**.
@ -46,7 +45,7 @@ ________________________________________________________________________________
- Again, copy the same `Client ID` and replace the `null` part of the `BotId` line with it. - Again, copy the same `Client ID` and replace the `null` part of the `BotId` line with it.
- Go to a server on discord and attempt to mention yourself, but put a backslash at the start like shown below - Go to a server on discord and attempt to mention yourself, but put a backslash at the start like shown below
- So the message `\@fearnlj01#3535` will appears as `<@145521851676884992>` after you send the message (to make it slightly easier, add the backslash after you type the mention out) - So the message `\@fearnlj01#3535` will appears as `<@145521851676884992>` after you send the message (to make it slightly easier, add the backslash after you type the mention out)
- The message will appear as a mention if done correctly, copy the numbers from the message you sent (`145521851676884992`) and replace the `0` on the `OwnerIds` section with your user ID shown earlier. - The message will appear as a mention if done correctly, copy the numbers from the message you sent (`145521851676884992`) and replace the ID (By default, the ID is `105635576866156544`) on the `OwnerIds` section with your user ID shown earlier.
- Save `credentials.json` (make sure you aren't saving it as `credentials.json.txt`) - Save `credentials.json` (make sure you aren't saving it as `credentials.json.txt`)
- If done correctly, you are now the bot owner. You can add multiple owners by seperating each owner ID with a comma within the square brackets. - If done correctly, you are now the bot owner. You can add multiple owners by seperating each owner ID with a comma within the square brackets.
@ -59,7 +58,7 @@ ________________________________________________________________________________
- The bot should have been added to your server. - The bot should have been added to your server.
####Starting the bot ####Starting the bot
- Go to the `Nadeko` folder that we have created earlier, and run the `NadekoInstaller.bat` file. - Go to the `Nadeko` folder that we have created earlier, and run the `NadekoInstaller.bat` file as Administrator.
- From the options, - From the options,
- Choose `3` to **run the bot normally**. - Choose `3` to **run the bot normally**.
(with normal-run the bot will shutdown and will stay offline if it disconnects by the use of `.die` command until you manually run it again. Useful if you want to test the bot.) (with normal-run the bot will shutdown and will stay offline if it disconnects by the use of `.die` command until you manually run it again. Useful if you want to test the bot.)
@ -80,11 +79,30 @@ ________________________________________________________________________________
- You've updated and are running again, easy as that! - You've updated and are running again, easy as that!
________________________________________________________________________________ ________________________________________________________________________________
#### Setting Up NadekoBot For Music ### Setting Up NadekoBot For Music
##### Prerequisites
- 1) [FFMPEG][FFMPEG] installed.
- 2) Setting up API keys.
In order to have a functioning music module, you need to install ffmpeg and setup api keys.
#### Setting up `ffmpeg` using NadekoBot Client!
- Go to the `Nadeko` folder that we have created earlier, and run the `NadekoInstaller.bat` file as Administrator.
- From the options select `6` Install ffmpeg (for music)
- Next, **Press Any Key** if you are running as Administrator or just close and relaunch it as Administrator using mouse right-click.
- Wait for it to finish installing and backing up existing.
- Once done, you should see "ffmpeg Installation complete!".
- Next, **Press Any Key** to go back to NadekoBot Client.
- Press `3` to run the bot normally just to test music. (optional)
- `ffmpeg` installation for Music is now complete.
#### Manual `ffmpeg` setup
- Create a folder named `ffmpeg` in your main Windows directory. We will use **C:\ffmpeg** (for our guide)
- Download FFMPEG through the link https://ffmpeg.zeranoe.com/builds/ (download static build)
- EXTRACT it using `7zip` and place the FOLDER `ffmpeg-xxxxx-git-xxxxx-xxxx-static` inside **C:\ffmpeg**
- Before proceeding, check out this gif to set up `ffmpeg` PATH correctly ![LINK TO gif](http://i.imgur.com/aR5l1Hn.gif) *(thanks to PooPeePants#7135)*
- Go to My Computer, right click and select Properties. On the left tab, select Advanced System Settings. Under the Advanced tab, select Environmental Variables near the bottom. One of the variables should be called "Path". Add a semi-colon (;) to the end followed by your FFMPEG's **bin** install location (**for example C:\ffmpeg\ffmpeg-xxxxx-git-xxxxx-xxxx-static\bin**). Save and close.
- Setup your API keys as explained above.
- **Restart your computer**
#### Api keys setup
- Follow these steps on how to setup Google API keys: - Follow these steps on how to setup Google API keys:
- Go to [Google Console][Google Console] and log in. - Go to [Google Console][Google Console] and log in.
- Create a new project (name does not matter). Once the project is created, go into "Enable and manage APIs." - Create a new project (name does not matter). Once the project is created, go into "Enable and manage APIs."
@ -98,22 +116,12 @@ ________________________________________________________________________________
- Enter a name for the app and create it. - Enter a name for the app and create it.
- You will need to fill out an application form to request access to the Soundcloud API. - You will need to fill out an application form to request access to the Soundcloud API.
- All requests for an API key must go through the review process, where applications will be reviewed on a case by case basis, in line with Soundcloud API Terms of Use. If your application is successful, you will receive an API key. - All requests for an API key must go through the review process, where applications will be reviewed on a case by case basis, in line with Soundcloud API Terms of Use. If your application is successful, you will receive an API key.
- **Restart your computer**. - **Restart your computer**
####Manual `ffmpeg` setup
**Do this step in case you were not able to install `ffmpeg` with the installer.**
- Create a folder named `ffmpeg` in your main Windows directory. We will use **C:\ffmpeg** (for our guide)
- Download FFMPEG through the link https://ffmpeg.zeranoe.com/builds/ (download static build)
- Extract it using `7zip` and place the folder `ffmpeg-xxxxx-git-xxxxx-xxxx-static` inside **C:\ffmpeg**
- Before proceeding, check out this gif to set up `ffmpeg` PATH correctly ![LINK TO gif](http://i.imgur.com/aR5l1Hn.gif) *(thanks to PooPeePants#7135)*
- Go to My Computer, right click and select Properties. On the left tab, select Advanced System Settings. Under the Advanced tab, select Environmental Variables near the bottom. One of the variables should be called "Path". Add a semi-colon (;) to the end followed by your FFMPEG's **bin** install location (**for example C:\ffmpeg\ffmpeg-xxxxx-git-xxxxx-xxxx-static\bin**). Save and close.
- Setup your API keys as explained above.
- Restart your computer.
[.NET Core SDK]: https://www.microsoft.com/net/core#windowscmd [.NET Core SDK]: https://github.com/dotnet/core/blob/master/release-notes/download-archives/1.1-preview2.1-download.md
[Git]: https://git-scm.com/download/win [Git]: https://git-scm.com/download/win
[FFMPEG]: https://github.com/Soundofdarkness/FFMPEG-Inst/releases
[7zip]: http://www.7-zip.org/download.html [7zip]: http://www.7-zip.org/download.html
[DiscordApp]: https://discordapp.com/developers/applications/me [DiscordApp]: https://discordapp.com/developers/applications/me
[Notepad++]: https://notepad-plus-plus.org/ [Notepad++]: https://notepad-plus-plus.org/

View File

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

View File

@ -33,7 +33,7 @@ If you want to contribute, be sure to PR on the **[dev][dev]** branch.
- [Donate](Donate.md) - [Donate](Donate.md)
[img]: https://cdn.discordapp.com/attachments/202743183774318593/210580315381563392/discord.png [img]: https://cdn.discordapp.com/attachments/202743183774318593/210580315381563392/discord.png
[NadekoBot Server]: https://discord.gg/0ehQwTK2RBjAxzEY [NadekoBot Server]: https://discord.gg/nadekobot
[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

View File

@ -1,3 +1,6 @@
{ {
"projects": [ "Discord.Net/src", "src" ] "projects": [ "Discord.Net/src", "src" ],
"sdk": {
"version": "1.0.0-preview2-1-003177"
}
} }

View File

@ -1,79 +1,93 @@
@ECHO off @ECHO off
TITLE Downloading NadekoBot, please wait TITLE Downloading Latest Build of NadekoBot...
::Setting convenient to read variables which don't delete the windows temp folder ::Setting convenient to read variables which don't delete the windows temp folder
SET root=%~dp0 SET "root=%~dp0"
CD /D %root% CD /D "%root%"
SET rootdir=%cd% SET "rootdir=%cd%"
SET build1=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Core\ SET "build1=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Core\"
SET build2=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Rest\ SET "build2=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Rest\"
SET build3=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.WebSocket\ SET "build3=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.WebSocket\"
SET build4=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Commands\ SET "build4=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Commands\"
SET build5=%root%NadekoInstall_Temp\NadekoBot\src\NadekoBot\ SET "build5=%root%NadekoInstall_Temp\NadekoBot\src\NadekoBot\"
SET installtemp=%root%NadekoInstall_Temp\ SET "installtemp=%root%NadekoInstall_Temp\"
::Deleting traces of last setup for the sake of clean folders, if by some miracle it still exists ::Deleting traces of last setup for the sake of clean folders, if by some miracle it still exists
IF EXIST %installtemp% ( RMDIR %installtemp% /S /Q >nul 2>&1) IF EXIST "%installtemp%" ( RMDIR "%installtemp%" /S /Q >nul 2>&1)
timeout /t 5
::Checks that both git and dotnet are installed ::Checks that both git and dotnet are installed
dotnet --version >nul 2>&1 || GOTO :dotnet dotnet --version >nul 2>&1 || GOTO :dotnet
git --version >nul 2>&1 || GOTO :git git --version >nul 2>&1 || GOTO :git
::Creates the install directory to work in and get the current directory because spaces ruins everything otherwise ::Creates the install directory to work in and get the current directory because spaces ruins everything otherwise
:start :start
MKDIR NadekoInstall_Temp MKDIR "%root%NadekoInstall_Temp"
CD /D %installtemp% CD /D "%installtemp%"
::Downloads the latest version of Nadeko ::Downloads the latest version of Nadeko
ECHO Downloading Nadeko... ECHO Downloading Nadeko...
ECHO. ECHO.
git clone -b dev --recursive --depth 1 --progress https://github.com/Kwoth/NadekoBot.git >nul git clone -b dev --recursive --depth 1 --progress https://github.com/Kwoth/NadekoBot.git >nul
IF %ERRORLEVEL% EQU 128 (GOTO :giterror) IF %ERRORLEVEL% EQU 128 (GOTO :giterror)
TITLE Installing NadekoBot, please wait TITLE Installing NadekoBot, please wait...
ECHO. ECHO.
ECHO Installing... ECHO Installing Discord.Net(1/4)...
::Building Nadeko ::Building Nadeko
CD /D %build1% CD /D "%build1%"
dotnet restore >nul 2>&1 dotnet restore >nul 2>&1
CD /D %build2% ECHO Installing Discord.Net(2/4)...
CD /D "%build2%"
dotnet restore >nul 2>&1 dotnet restore >nul 2>&1
CD /D %build3% ECHO Installing Discord.Net(3/4)...
CD /D "%build3%"
dotnet restore >nul 2>&1 dotnet restore >nul 2>&1
CD /D %build4% ECHO Installing Discord.Net(4/4)...
CD /D "%build4%"
dotnet restore >nul 2>&1 dotnet restore >nul 2>&1
CD /D %build5% ECHO.
ECHO Discord.Net installation completed successfully...
ECHO.
ECHO Installing NadekoBot...
CD /D "%build5%"
dotnet restore >nul 2>&1 dotnet restore >nul 2>&1
dotnet build --configuration Release >nul 2>&1 dotnet build --configuration Release >nul 2>&1
ECHO.
ECHO NadekoBot installation completed successfully...
::Attempts to backup old files if they currently exist in the same folder as the batch file ::Attempts to backup old files if they currently exist in the same folder as the batch file
IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) ELSE (GOTO :freshinstall)
:freshinstall :freshinstall
::Moves the NadekoBot folder to keep things tidy ::Moves the NadekoBot folder to keep things tidy
ECHO.
ECHO Moving files, Please wait...
ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1 ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
GOTO :end IF EXIST "%PROGRAMFILES(X86)%" (GOTO 64BIT) ELSE (GOTO 32BIT)
:backupinstall :backupinstall
TITLE Backing up old files TITLE Backing up old files...
ECHO. ECHO.
ECHO Make sure to close any files such as NadekoBot.db before PRESSing ANY KEY TO CONTINUE to prevent data loss ECHO Moving and Backing up old files...
PAUSE >nul 2>&1
::Recursively copies all files and folders from NadekoBot to NadekoBot_Old ::Recursively copies all files and folders from NadekoBot to NadekoBot_Old
ROBOCOPY "%root%NadekoBot" "%root%NadekoBot_Old" /MIR >nul 2>&1 ROBOCOPY "%root%NadekoBot" "%root%NadekoBot_Old" /MIR >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
ECHO. ECHO.
ECHO Old files backed up to NadekoBot_Old ECHO Old files backed up to NadekoBot_Old...
::Copies the credentials and database from the backed up data to the new folder ::Copies the credentials and database from the backed up data to the new folder
COPY "%root%NadekoBot_Old\src\NadekoBot\credentials.json" "%installtemp%NadekoBot\src\NadekoBot\credentials.json" >nul 2>&1 COPY "%root%NadekoBot_Old\src\NadekoBot\credentials.json" "%installtemp%NadekoBot\src\NadekoBot\credentials.json" >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
ECHO. ECHO.
ECHO credentials.json copied to new folder ECHO credentials.json copied...
ROBOCOPY "%root%NadekoBot_Old\src\NadekoBot\bin" "%installtemp%NadekoBot\src\NadekoBot\bin" /E >nul 2>&1 ROBOCOPY "%root%NadekoBot_Old\src\NadekoBot\bin" "%installtemp%NadekoBot\src\NadekoBot\bin" /E >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
ECHO. ECHO.
ECHO Old bin folder copied to new folder ECHO bin folder copied...
RD /S /Q "%root%NadekoBot_Old\src\NadekoBot\data\musicdata"
ECHO.
ECHO music cache cleared...
ROBOCOPY "%root%NadekoBot_Old\src\NadekoBot\data" "%installtemp%NadekoBot\src\NadekoBot\data" /E >nul 2>&1 ROBOCOPY "%root%NadekoBot_Old\src\NadekoBot\data" "%installtemp%NadekoBot\src\NadekoBot\data" /E >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
ECHO. ECHO.
ECHO Old data folder copied to new folder ECHO Old data folder copied...
::Moves the setup Nadeko folder ::Moves the setup Nadeko folder
RMDIR "%root%NadekoBot\" /S /Q >nul 2>&1 RMDIR "%root%NadekoBot\" /S /Q >nul 2>&1
ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1 ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
GOTO :end IF EXIST "%PROGRAMFILES(X86)%" (GOTO 64BIT) ELSE (GOTO 32BIT)
:dotnet :dotnet
::Terminates the batch script if it can't run dotnet --version ::Terminates the batch script if it can't run dotnet --version
TITLE Error! TITLE Error!
@ -93,7 +107,7 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall)
:giterror :giterror
ECHO. ECHO.
ECHO Git clone failed, trying again ECHO Git clone failed, trying again
RMDIR %installtemp% /S /Q >nul 2>&1 RMDIR "%installtemp%" /S /Q >nul 2>&1
GOTO :start GOTO :start
:copyerror :copyerror
::If at any point a copy error is encountered ::If at any point a copy error is encountered
@ -105,12 +119,48 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall)
PAUSE >nul 2>&1 PAUSE >nul 2>&1
CD /D "%root%" CD /D "%root%"
GOTO :EOF GOTO :EOF
:64BIT
ECHO.
ECHO Your System Architecture is 64bit...
GOTO end
:32BIT
ECHO.
ECHO Your System Architecture is 32bit...
timeout /t 5
ECHO.
ECHO Getting 32bit libsodium.dll and opus.dll...
IF EXIST "%root%NadekoBot\src\NadekoBot\_libs\32\libsodium.dll" (GOTO copysodium) ELSE (GOTO downloadsodium)
:copysodium
del "%root%NadekoBot\src\NadekoBot\libsodium.dll"
copy "%root%NadekoBot\src\NadekoBot\_libs\32\libsodium.dll" "%root%NadekoBot\src\NadekoBot\libsodium.dll"
ECHO libsodium.dll copied.
ECHO.
timeout /t 5
IF EXIST "%root%NadekoBot\src\NadekoBot\_libs\32\opus.dll" (GOTO copyopus) ELSE (GOTO downloadopus)
:downloadsodium
SET "FILENAME=%~dp0\NadekoBot\src\NadekoBot\libsodium.dll"
powershell -Command "Invoke-WebRequest https://github.com/Kwoth/NadekoBot/raw/dev/src/NadekoBot/_libs/32/libsodium.dll -OutFile '%FILENAME%'"
ECHO libsodium.dll downloaded.
ECHO.
timeout /t 5
IF EXIST "%root%NadekoBot\src\NadekoBot\_libs\32\opus.dll" (GOTO copyopus) ELSE (GOTO downloadopus)
:copyopus
del "%root%NadekoBot\src\NadekoBot\opus.dll"
copy "%root%NadekoBot\src\NadekoBot\_libs\32\opus.dll" "%root%NadekoBot\src\NadekoBot\opus.dll"
ECHO opus.dll copied.
GOTO end
:downloadopus
SET "FILENAME=%~dp0\NadekoBot\src\NadekoBot\opus.dll"
powershell -Command "Invoke-WebRequest https://github.com/Kwoth/NadekoBot/raw/dev/src/NadekoBot/_libs/32/opus.dll -OutFile '%FILENAME%'"
ECHO opus.dll downloaded.
GOTO end
:end :end
::Normal execution of end of script ::Normal execution of end of script
TITLE Installation complete! TITLE NadekoBot Installation complete!
CD /D "%root%" CD /D "%root%"
RMDIR /S /Q "%installtemp%" >nul 2>&1 RMDIR /S /Q "%installtemp%" >nul 2>&1
ECHO. ECHO.
ECHO Installation complete, press any key to close this window! ECHO Installation complete!
PAUSE >nul 2>&1 ECHO.
PAUSE
del Latest.bat del Latest.bat

View File

@ -2,8 +2,8 @@
@TITLE NadekoBot @TITLE NadekoBot
SET root=%~dp0 SET "root=%~dp0"
CD /D %root% CD /D "%root%"
CLS CLS
ECHO Welcome to NadekoBot Auto Restart and Update! ECHO Welcome to NadekoBot Auto Restart and Update!
@ -25,32 +25,28 @@ IF ERRORLEVEL 1 GOTO latestar
:latestar :latestar
ECHO Auto Restart and Update with Dev Build (latest) ECHO Auto Restart and Update with Dev Build (latest)
ECHO Bot will auto update on every restart! ECHO Bot will auto update on every restart!
timeout /t 3 CD /D "%~dp0NadekoBot\src\NadekoBot"
CD /D %~dp0NadekoBot\src\NadekoBot
dotnet run --configuration Release dotnet run --configuration Release
ECHO Updating... ECHO Updating...
timeout /t 3
SET "FILENAME=%~dp0\Latest.bat" SET "FILENAME=%~dp0\Latest.bat"
bitsadmin.exe /transfer "Downloading Nadeko (Latest)" /priority high https://github.com/Kwoth/NadekoBot/raw/master/scripts/Latest.bat "%FILENAME%" powershell -Command "Invoke-WebRequest https://github.com/Kwoth/NadekoBot/raw/dev/scripts/Latest.bat -OutFile '%FILENAME%'"
ECHO NadekoBot Dev Build (latest) downloaded. ECHO NadekoBot Dev Build (latest) downloaded.
SET root=%~dp0 SET "root=%~dp0"
CD /D %root% CD /D "%root%"
CALL Latest.bat CALL Latest.bat
GOTO latestar GOTO latestar
:stablear :stablear
ECHO Auto Restart and Update with Stable Build ECHO Auto Restart and Update with Stable Build
ECHO Bot will auto update on every restart! ECHO Bot will auto update on every restart!
timeout /t 3 CD /D "%~dp0NadekoBot\src\NadekoBot"
CD /D %~dp0NadekoBot\src\NadekoBot
dotnet run --configuration Release dotnet run --configuration Release
ECHO Updating... ECHO Updating...
timeout /t 3
SET "FILENAME=%~dp0\Stable.bat" SET "FILENAME=%~dp0\Stable.bat"
bitsadmin.exe /transfer "Downloading Nadeko (Stable)" /priority high https://github.com/Kwoth/NadekoBot/raw/master/scripts/Stable.bat "%FILENAME%" powershell -Command "Invoke-WebRequest https://github.com/Kwoth/NadekoBot/raw/dev/scripts/Stable.bat -OutFile '%FILENAME%'"
ECHO NadekoBot Stable build downloaded. ECHO NadekoBot Stable build downloaded.
SET root=%~dp0 SET "root=%~dp0"
CD /D %root% CD /D "%root%"
CALL Stable.bat CALL Stable.bat
GOTO stablear GOTO stablear
@ -58,12 +54,12 @@ GOTO stablear
ECHO Normal Auto Restart ECHO Normal Auto Restart
ECHO Bot will not auto update on every restart! ECHO Bot will not auto update on every restart!
timeout /t 3 timeout /t 3
CD /D %~dp0NadekoBot\src\NadekoBot CD /D "%~dp0NadekoBot\src\NadekoBot"
dotnet run --configuration Release dotnet run --configuration Release
goto autorun goto autorun
:Exit :Exit
SET root=%~dp0 SET "root=%~dp0"
CD /D %root% CD /D "%root%"
del NadekoAutoRun.bat del NadekoAutoRun.bat
CALL NadekoInstaller.bat CALL NadekoInstaller.bat

View File

@ -1,9 +1,9 @@
@ECHO off @ECHO off
@TITLE NadekoBot @TITLE NadekoBot
CD /D %~dp0NadekoBot\src\NadekoBot CD /D "%~dp0NadekoBot\src\NadekoBot"
dotnet run --configuration Release dotnet run --configuration Release
ECHO NadekoBot has been succesfully stopped, press any key to close this window. ECHO NadekoBot has been succesfully stopped, press any key to close this window.
TITLE NadekoBot - Stopped TITLE NadekoBot - Stopped
CD /D %~dp0 CD /D "%~dp0"
PAUSE >nul 2>&1 PAUSE >nul 2>&1
del NadekoRunNormal.bat del NadekoRunNormal.bat

View File

@ -1,79 +1,93 @@
@ECHO off @ECHO off
TITLE Downloading NadekoBot, please wait TITLE Downloading Stable Build of NadekoBot...
::Setting convenient to read variables which don't delete the windows temp folder ::Setting convenient to read variables which don't delete the windows temp folder
SET root=%~dp0 SET "root=%~dp0"
CD /D %root% CD /D "%root%"
SET rootdir=%cd% SET "rootdir=%cd%"
SET build1=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Core\ SET "build1=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Core\"
SET build2=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Rest\ SET "build2=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Rest\"
SET build3=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.WebSocket\ SET "build3=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.WebSocket\"
SET build4=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Commands\ SET "build4=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Commands\"
SET build5=%root%NadekoInstall_Temp\NadekoBot\src\NadekoBot\ SET "build5=%root%NadekoInstall_Temp\NadekoBot\src\NadekoBot\"
SET installtemp=%root%NadekoInstall_Temp\ SET "installtemp=%root%NadekoInstall_Temp\"
::Deleting traces of last setup for the sake of clean folders, if by some miracle it still exists ::Deleting traces of last setup for the sake of clean folders, if by some miracle it still exists
IF EXIST %installtemp% ( RMDIR %installtemp% /S /Q >nul 2>&1) IF EXIST "%installtemp%" ( RMDIR "%installtemp%" /S /Q >nul 2>&1)
timeout /t 5
::Checks that both git and dotnet are installed ::Checks that both git and dotnet are installed
dotnet --version >nul 2>&1 || GOTO :dotnet dotnet --version >nul 2>&1 || GOTO :dotnet
git --version >nul 2>&1 || GOTO :git git --version >nul 2>&1 || GOTO :git
::Creates the install directory to work in and get the current directory because spaces ruins everything otherwise ::Creates the install directory to work in and get the current directory because spaces ruins everything otherwise
:start :start
MKDIR NadekoInstall_Temp MKDIR "%root%NadekoInstall_Temp"
CD /D %installtemp% CD /D "%installtemp%"
::Downloads the latest version of Nadeko ::Downloads the latest version of Nadeko
ECHO Downloading Nadeko... ECHO Downloading Nadeko...
ECHO. ECHO.
git clone -b master --recursive --depth 1 --progress https://github.com/Kwoth/NadekoBot.git >nul git clone -b master --recursive --depth 1 --progress https://github.com/Kwoth/NadekoBot.git >nul
IF %ERRORLEVEL% EQU 128 (GOTO :giterror) IF %ERRORLEVEL% EQU 128 (GOTO :giterror)
TITLE Installing NadekoBot, please wait TITLE Installing NadekoBot, please wait...
ECHO. ECHO.
ECHO Installing... ECHO Installing Discord.Net(1/4)...
::Building Nadeko ::Building Nadeko
CD /D %build1% CD /D "%build1%"
dotnet restore >nul 2>&1 dotnet restore >nul 2>&1
CD /D %build2% ECHO Installing Discord.Net(2/4)...
CD /D "%build2%"
dotnet restore >nul 2>&1 dotnet restore >nul 2>&1
CD /D %build3% ECHO Installing Discord.Net(3/4)...
CD /D "%build3%"
dotnet restore >nul 2>&1 dotnet restore >nul 2>&1
CD /D %build4% ECHO Installing Discord.Net(4/4)...
CD /D "%build4%"
dotnet restore >nul 2>&1 dotnet restore >nul 2>&1
CD /D %build5% ECHO.
ECHO Discord.Net installation completed successfully...
ECHO.
ECHO Installing NadekoBot...
CD /D "%build5%"
dotnet restore >nul 2>&1 dotnet restore >nul 2>&1
dotnet build --configuration Release >nul 2>&1 dotnet build --configuration Release >nul 2>&1
ECHO.
ECHO NadekoBot installation completed successfully...
::Attempts to backup old files if they currently exist in the same folder as the batch file ::Attempts to backup old files if they currently exist in the same folder as the batch file
IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) ELSE (GOTO :freshinstall)
:freshinstall :freshinstall
::Moves the NadekoBot folder to keep things tidy ::Moves the NadekoBot folder to keep things tidy
ECHO.
ECHO Moving files, Please wait...
ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1 ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
GOTO :end IF EXIST "%PROGRAMFILES(X86)%" (GOTO 64BIT) ELSE (GOTO 32BIT)
:backupinstall :backupinstall
TITLE Backing up old files TITLE Backing up old files...
ECHO. ECHO.
ECHO Make sure to close any files such as NadekoBot.db before PRESSing ANY KEY TO CONTINUE to prevent data loss ECHO Moving and Backing up old files...
PAUSE >nul 2>&1
::Recursively copies all files and folders from NadekoBot to NadekoBot_Old ::Recursively copies all files and folders from NadekoBot to NadekoBot_Old
ROBOCOPY "%root%NadekoBot" "%root%NadekoBot_Old" /MIR >nul 2>&1 ROBOCOPY "%root%NadekoBot" "%root%NadekoBot_Old" /MIR >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
ECHO. ECHO.
ECHO Old files backed up to NadekoBot_Old ECHO Old files backed up to NadekoBot_Old...
::Copies the credentials and database from the backed up data to the new folder ::Copies the credentials and database from the backed up data to the new folder
COPY "%root%NadekoBot_Old\src\NadekoBot\credentials.json" "%installtemp%NadekoBot\src\NadekoBot\credentials.json" >nul 2>&1 COPY "%root%NadekoBot_Old\src\NadekoBot\credentials.json" "%installtemp%NadekoBot\src\NadekoBot\credentials.json" >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
ECHO. ECHO.
ECHO credentials.json copied to new folder ECHO credentials.json copied...
ROBOCOPY "%root%NadekoBot_Old\src\NadekoBot\bin" "%installtemp%NadekoBot\src\NadekoBot\bin" /E >nul 2>&1 ROBOCOPY "%root%NadekoBot_Old\src\NadekoBot\bin" "%installtemp%NadekoBot\src\NadekoBot\bin" /E >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
ECHO. ECHO.
ECHO Old bin folder copied to new folder ECHO bin folder copied...
RD /S /Q "%root%NadekoBot_Old\src\NadekoBot\data\musicdata"
ECHO.
ECHO music cache cleared...
ROBOCOPY "%root%NadekoBot_Old\src\NadekoBot\data" "%installtemp%NadekoBot\src\NadekoBot\data" /E >nul 2>&1 ROBOCOPY "%root%NadekoBot_Old\src\NadekoBot\data" "%installtemp%NadekoBot\src\NadekoBot\data" /E >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
ECHO. ECHO.
ECHO Old data folder copied to new folder ECHO Old data folder copied...
::Moves the setup Nadeko folder ::Moves the setup Nadeko folder
RMDIR "%root%NadekoBot\" /S /Q >nul 2>&1 RMDIR "%root%NadekoBot\" /S /Q >nul 2>&1
ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1 ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
GOTO :end IF EXIST "%PROGRAMFILES(X86)%" (GOTO 64BIT) ELSE (GOTO 32BIT)
:dotnet :dotnet
::Terminates the batch script if it can't run dotnet --version ::Terminates the batch script if it can't run dotnet --version
TITLE Error! TITLE Error!
@ -93,7 +107,7 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall)
:giterror :giterror
ECHO. ECHO.
ECHO Git clone failed, trying again ECHO Git clone failed, trying again
RMDIR %installtemp% /S /Q >nul 2>&1 RMDIR "%installtemp%" /S /Q >nul 2>&1
GOTO :start GOTO :start
:copyerror :copyerror
::If at any point a copy error is encountered ::If at any point a copy error is encountered
@ -105,12 +119,48 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall)
PAUSE >nul 2>&1 PAUSE >nul 2>&1
CD /D "%root%" CD /D "%root%"
GOTO :EOF GOTO :EOF
:64BIT
ECHO.
ECHO Your System Architecture is 64bit...
GOTO end
:32BIT
ECHO.
ECHO Your System Architecture is 32bit...
timeout /t 5
ECHO.
ECHO Getting 32bit libsodium.dll and opus.dll...
IF EXIST "%root%NadekoBot\src\NadekoBot\_libs\32\libsodium.dll" (GOTO copysodium) ELSE (GOTO downloadsodium)
:copysodium
del "%root%NadekoBot\src\NadekoBot\libsodium.dll"
copy "%root%NadekoBot\src\NadekoBot\_libs\32\libsodium.dll" "%root%NadekoBot\src\NadekoBot\libsodium.dll"
ECHO libsodium.dll copied.
ECHO.
timeout /t 5
IF EXIST "%root%NadekoBot\src\NadekoBot\_libs\32\opus.dll" (GOTO copyopus) ELSE (GOTO downloadopus)
:downloadsodium
SET "FILENAME=%~dp0\NadekoBot\src\NadekoBot\libsodium.dll"
powershell -Command "Invoke-WebRequest https://github.com/Kwoth/NadekoBot/raw/dev/src/NadekoBot/_libs/32/libsodium.dll -OutFile '%FILENAME%'"
ECHO libsodium.dll downloaded.
ECHO.
timeout /t 5
IF EXIST "%root%NadekoBot\src\NadekoBot\_libs\32\opus.dll" (GOTO copyopus) ELSE (GOTO downloadopus)
:copyopus
del "%root%NadekoBot\src\NadekoBot\opus.dll"
copy "%root%NadekoBot\src\NadekoBot\_libs\32\opus.dll" "%root%NadekoBot\src\NadekoBot\opus.dll"
ECHO opus.dll copied.
GOTO end
:downloadopus
SET "FILENAME=%~dp0\NadekoBot\src\NadekoBot\opus.dll"
powershell -Command "Invoke-WebRequest https://github.com/Kwoth/NadekoBot/raw/dev/src/NadekoBot/_libs/32/opus.dll -OutFile '%FILENAME%'"
ECHO opus.dll downloaded.
GOTO end
:end :end
::Normal execution of end of script ::Normal execution of end of script
TITLE Installation complete! TITLE NadekoBot Installation complete!
CD /D "%root%" CD /D "%root%"
RMDIR /S /Q "%installtemp%" >nul 2>&1 RMDIR /S /Q "%installtemp%" >nul 2>&1
ECHO. ECHO.
ECHO Installation complete, press any key to close this window! ECHO Installation complete!
PAUSE >nul 2>&1 ECHO.
PAUSE
del Stable.bat del Stable.bat

View File

@ -6,6 +6,6 @@ namespace NadekoBot.Attributes
public class OwnerOnlyAttribute : PreconditionAttribute public class OwnerOnlyAttribute : PreconditionAttribute
{ {
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo executingCommand,IDependencyMap depMap) => public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo executingCommand,IDependencyMap depMap) =>
Task.FromResult((NadekoBot.Credentials.IsOwner(context.User) ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner"))); Task.FromResult((NadekoBot.Credentials.IsOwner(context.User) || NadekoBot.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
} }
} }

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.DataStructures
{
public class AsyncLazy<T> : Lazy<Task<T>>
{
public AsyncLazy(Func<T> valueFactory) :
base(() => Task.Factory.StartNew(valueFactory))
{ }
public AsyncLazy(Func<Task<T>> taskFactory) :
base(() => Task.Factory.StartNew(taskFactory).Unwrap())
{ }
public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
}
}

View File

@ -0,0 +1,91 @@
using Discord;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.DataStructures
{
public class CREmbed
{
private static readonly Logger _log;
public string PlainText { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public CREmbedFooter Footer { get; set; }
public string Thumbnail { get; set; }
public string Image { get; set; }
public CREmbedField[] Fields { get; set; }
public uint Color { get; set; } = 7458112;
static CREmbed()
{
_log = LogManager.GetCurrentClassLogger();
}
public bool IsValid =>
!string.IsNullOrWhiteSpace(Title) ||
!string.IsNullOrWhiteSpace(Description) ||
!string.IsNullOrWhiteSpace(Thumbnail) ||
!string.IsNullOrWhiteSpace(Image) ||
(Footer != null && (!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl))) ||
(Fields != null && Fields.Length > 0);
public EmbedBuilder ToEmbed()
{
var embed = new EmbedBuilder()
.WithTitle(Title)
.WithDescription(Description)
.WithColor(new Discord.Color(Color));
if (Footer != null)
embed.WithFooter(efb => efb.WithIconUrl(Footer.IconUrl).WithText(Footer.Text));
embed.WithThumbnailUrl(Thumbnail)
.WithImageUrl(Image);
if (Fields != null)
foreach (var f in Fields)
{
embed.AddField(efb => efb.WithName(f.Name).WithValue(f.Value).WithIsInline(f.Inline));
}
return embed;
}
public static bool TryParse(string input, out CREmbed embed)
{
embed = null;
if (string.IsNullOrWhiteSpace(input))
return false;
try
{
var crembed = JsonConvert.DeserializeObject<CREmbed>(input);
if (!crembed.IsValid)
return false;
embed = crembed;
return true;
}
catch
{
return false;
}
}
}
public class CREmbedField
{
public string Name { get; set; }
public string Value { get; set; }
public bool Inline { get; set; }
}
public class CREmbedFooter {
public string Text { get; set; }
public string IconUrl { get; set; }
}
}

View File

@ -0,0 +1,82 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.DataStructures
{
public static class DisposableReadOnlyListExtensions
{
public static IDisposableReadOnlyList<T> AsDisposable<T>(this IReadOnlyList<T> arr) where T : IDisposable
=> new DisposableReadOnlyList<T>(arr);
public static IDisposableReadOnlyList<KeyValuePair<TKey, TValue>> AsDisposable<TKey, TValue>(this IReadOnlyList<KeyValuePair<TKey, TValue>> arr) where TValue : IDisposable
=> new DisposableReadOnlyList<TKey, TValue>(arr);
}
public interface IDisposableReadOnlyList<T> : IReadOnlyList<T>, IDisposable
{
}
public class DisposableReadOnlyList<T> : IDisposableReadOnlyList<T>
where T : IDisposable
{
private readonly IReadOnlyList<T> _arr;
public int Count => _arr.Count;
public T this[int index] => _arr[index];
public DisposableReadOnlyList(IReadOnlyList<T> arr)
{
this._arr = arr;
}
public IEnumerator<T> GetEnumerator()
=> _arr.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> _arr.GetEnumerator();
public void Dispose()
{
foreach (var item in _arr)
{
item.Dispose();
}
}
}
public class DisposableReadOnlyList<T, U> : IDisposableReadOnlyList<KeyValuePair<T, U>>
where U : IDisposable
{
private readonly IReadOnlyList<KeyValuePair<T, U>> _arr;
public int Count => _arr.Count;
KeyValuePair<T, U> IReadOnlyList<KeyValuePair<T, U>>.this[int index] => _arr[index];
public DisposableReadOnlyList(IReadOnlyList<KeyValuePair<T, U>> arr)
{
this._arr = arr;
}
public IEnumerator<KeyValuePair<T, U>> GetEnumerator() =>
_arr.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() =>
_arr.GetEnumerator();
public void Dispose()
{
foreach (var item in _arr)
{
item.Value.Dispose();
}
}
}
}

View File

@ -0,0 +1,128 @@
using NadekoBot.Services.Database.Models;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace NadekoBot.DataStructures
{
public class IndexedCollection<T> : IList<T> where T : IIndexed
{
public List<T> Source { get; }
private readonly object _locker = new object();
public IndexedCollection(IEnumerable<T> source)
{
lock (_locker)
{
Source = source.OrderBy(x => x.Index).ToList();
for (var i = 0; i < Source.Count; i++)
{
if (Source[i].Index != i)
Source[i].Index = i;
}
}
}
public static implicit operator List<T>(IndexedCollection<T> x) =>
x.Source;
public IEnumerator<T> GetEnumerator() =>
Source.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() =>
Source.GetEnumerator();
public void Add(T item)
{
lock (_locker)
{
item.Index = Source.Count;
Source.Add(item);
}
}
public virtual void Clear()
{
lock (_locker)
{
Source.Clear();
}
}
public bool Contains(T item)
{
lock (_locker)
{
return Source.Contains(item);
}
}
public void CopyTo(T[] array, int arrayIndex)
{
lock (_locker)
{
Source.CopyTo(array, arrayIndex);
}
}
public virtual bool Remove(T item)
{
bool removed;
lock (_locker)
{
if (removed = Source.Remove(item))
{
for (int i = 0; i < Source.Count; i++)
{
// hm, no idea how ef works, so I don't want to set if it's not changed,
// maybe it will try to update db?
// But most likely it just compares old to new values, meh.
if (Source[i].Index != i)
Source[i].Index = i;
}
}
}
return removed;
}
public int Count => Source.Count;
public bool IsReadOnly => false;
public int IndexOf(T item) => item.Index;
public virtual void Insert(int index, T item)
{
lock (_locker)
{
Source.Insert(index, item);
for (int i = index; i < Source.Count; i++)
{
Source[i].Index = i;
}
}
}
public virtual void RemoveAt(int index)
{
lock (_locker)
{
Source.RemoveAt(index);
for (int i = index; i < Source.Count; i++)
{
Source[i].Index = i;
}
}
}
public virtual T this[int index] {
get { return Source[index]; }
set {
lock (_locker)
{
value.Index = index;
Source[index] = value;
}
}
}
}
}

View File

@ -0,0 +1,75 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.DataStructures
{
public class PermissionsCollection<T> : IndexedCollection<T> where T : IIndexed
{
private readonly object _localLocker = new object();
public PermissionsCollection(IEnumerable<T> source) : base(source)
{
}
public static implicit operator List<T>(PermissionsCollection<T> x) =>
x.Source;
public override void Clear()
{
lock (_localLocker)
{
var first = Source[0];
base.Clear();
Source[0] = first;
}
}
public override bool Remove(T item)
{
bool removed;
lock (_localLocker)
{
if(Source.IndexOf(item) == 0)
throw new ArgumentException("You can't remove first permsission (allow all)");
removed = base.Remove(item);
}
return removed;
}
public override void Insert(int index, T item)
{
lock (_localLocker)
{
if(index == 0) // can't insert on first place. Last item is always allow all.
throw new IndexOutOfRangeException(nameof(index));
base.Insert(index, item);
}
}
public override void RemoveAt(int index)
{
lock (_localLocker)
{
if(index == 0) // you can't remove first permission (allow all)
throw new IndexOutOfRangeException(nameof(index));
base.RemoveAt(index);
}
}
public override T this[int index] {
get { return Source[index]; }
set {
lock (_localLocker)
{
if(index == 0) // can't set first element. It's always allow all
throw new IndexOutOfRangeException(nameof(index));
base[index] = value;
}
}
}
}
}

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

@ -0,0 +1,984 @@
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("20170112185538_currency-modifications")]
partial class currencymodifications
{
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<float>("BetflipMultiplier");
b.Property<float>("Betroll100Multiplier");
b.Property<float>("Betroll67Multiplier");
b.Property<float>("Betroll91Multiplier");
b.Property<ulong>("BufferSize");
b.Property<int>("CurrencyDropAmount");
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<int>("MinimumBetAmount");
b.Property<string>("RemindMessageFormat");
b.Property<bool>("RotatingStatuses");
b.Property<int>("TriviaCurrencyReward");
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.CommandPrice", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("CommandName");
b.Property<int>("Price");
b.HasKey("Id");
b.HasIndex("BotConfigId");
b.HasIndex("Price")
.IsUnique();
b.ToTable("CommandPrice");
});
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.CommandPrice", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("CommandPrices")
.HasForeignKey("BotConfigId");
});
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,120 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class currencymodifications : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<float>(
name: "BetflipMultiplier",
table: "BotConfig",
nullable: false,
defaultValue: 1.95f);
migrationBuilder.AddColumn<float>(
name: "Betroll100Multiplier",
table: "BotConfig",
nullable: false,
defaultValue: 10f);
migrationBuilder.AddColumn<float>(
name: "Betroll67Multiplier",
table: "BotConfig",
nullable: false,
defaultValue: 2f);
migrationBuilder.AddColumn<float>(
name: "Betroll91Multiplier",
table: "BotConfig",
nullable: false,
defaultValue: 4f);
migrationBuilder.AddColumn<int>(
name: "CurrencyDropAmount",
table: "BotConfig",
nullable: false,
defaultValue: 1);
migrationBuilder.AddColumn<int>(
name: "MinimumBetAmount",
table: "BotConfig",
nullable: false,
defaultValue: 2);
migrationBuilder.AddColumn<int>(
name: "TriviaCurrencyReward",
table: "BotConfig",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "CommandPrice",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
BotConfigId = table.Column<int>(nullable: true),
CommandName = table.Column<string>(nullable: true),
Price = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CommandPrice", x => x.Id);
table.ForeignKey(
name: "FK_CommandPrice_BotConfig_BotConfigId",
column: x => x.BotConfigId,
principalTable: "BotConfig",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_CommandPrice_BotConfigId",
table: "CommandPrice",
column: "BotConfigId");
migrationBuilder.CreateIndex(
name: "IX_CommandPrice_Price",
table: "CommandPrice",
column: "Price",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CommandPrice");
migrationBuilder.DropColumn(
name: "BetflipMultiplier",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "Betroll100Multiplier",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "Betroll67Multiplier",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "Betroll91Multiplier",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "CurrencyDropAmount",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "MinimumBetAmount",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "TriviaCurrencyReward",
table: "BotConfig");
}
}
}

View File

@ -0,0 +1,988 @@
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("20170118202307_ok-error-colors")]
partial class okerrorcolors
{
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<float>("BetflipMultiplier");
b.Property<float>("Betroll100Multiplier");
b.Property<float>("Betroll67Multiplier");
b.Property<float>("Betroll91Multiplier");
b.Property<ulong>("BufferSize");
b.Property<int>("CurrencyDropAmount");
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<string>("ErrorColor");
b.Property<bool>("ForwardMessages");
b.Property<bool>("ForwardToAllOwners");
b.Property<string>("HelpString");
b.Property<int>("MigrationVersion");
b.Property<int>("MinimumBetAmount");
b.Property<string>("OkColor");
b.Property<string>("RemindMessageFormat");
b.Property<bool>("RotatingStatuses");
b.Property<int>("TriviaCurrencyReward");
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.CommandPrice", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<string>("CommandName");
b.Property<int>("Price");
b.HasKey("Id");
b.HasIndex("BotConfigId");
b.HasIndex("Price")
.IsUnique();
b.ToTable("CommandPrice");
});
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.CommandPrice", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("CommandPrices")
.HasForeignKey("BotConfigId");
});
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,35 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class okerrorcolors : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "ErrorColor",
table: "BotConfig",
nullable: false,
defaultValue: "ee281f");
migrationBuilder.AddColumn<string>(
name: "OkColor",
table: "BotConfig",
nullable: false,
defaultValue: "71cd40");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ErrorColor",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "OkColor",
table: "BotConfig");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class waifus : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "DiscordUser",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
AvatarId = table.Column<string>(nullable: true),
Discriminator = table.Column<string>(nullable: true),
UserId = table.Column<ulong>(nullable: false),
Username = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_DiscordUser", x => x.Id);
table.UniqueConstraint("AK_DiscordUser_UserId", x => x.UserId);
});
migrationBuilder.CreateTable(
name: "WaifuInfo",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
AffinityId = table.Column<int>(nullable: true),
ClaimerId = table.Column<int>(nullable: true),
Price = table.Column<int>(nullable: false),
WaifuId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_WaifuInfo", x => x.Id);
table.ForeignKey(
name: "FK_WaifuInfo_DiscordUser_AffinityId",
column: x => x.AffinityId,
principalTable: "DiscordUser",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WaifuInfo_DiscordUser_ClaimerId",
column: x => x.ClaimerId,
principalTable: "DiscordUser",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WaifuInfo_DiscordUser_WaifuId",
column: x => x.WaifuId,
principalTable: "DiscordUser",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "WaifuUpdates",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
NewId = table.Column<int>(nullable: true),
OldId = table.Column<int>(nullable: true),
UpdateType = table.Column<int>(nullable: false),
UserId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_WaifuUpdates", x => x.Id);
table.ForeignKey(
name: "FK_WaifuUpdates_DiscordUser_NewId",
column: x => x.NewId,
principalTable: "DiscordUser",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WaifuUpdates_DiscordUser_OldId",
column: x => x.OldId,
principalTable: "DiscordUser",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WaifuUpdates_DiscordUser_UserId",
column: x => x.UserId,
principalTable: "DiscordUser",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_WaifuInfo_AffinityId",
table: "WaifuInfo",
column: "AffinityId");
migrationBuilder.CreateIndex(
name: "IX_WaifuInfo_ClaimerId",
table: "WaifuInfo",
column: "ClaimerId");
migrationBuilder.CreateIndex(
name: "IX_WaifuInfo_WaifuId",
table: "WaifuInfo",
column: "WaifuId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_WaifuUpdates_NewId",
table: "WaifuUpdates",
column: "NewId");
migrationBuilder.CreateIndex(
name: "IX_WaifuUpdates_OldId",
table: "WaifuUpdates",
column: "OldId");
migrationBuilder.CreateIndex(
name: "IX_WaifuUpdates_UserId",
table: "WaifuUpdates",
column: "UserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "WaifuInfo");
migrationBuilder.DropTable(
name: "WaifuUpdates");
migrationBuilder.DropTable(
name: "DiscordUser");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class guildtimezoneandlocale : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Locale",
table: "GuildConfigs",
nullable: true,
defaultValue: null);
migrationBuilder.AddColumn<string>(
name: "TimeZoneId",
table: "GuildConfigs",
nullable: true,
defaultValue: null);
migrationBuilder.AddColumn<string>(
name: "Locale",
table: "BotConfig",
nullable: true,
defaultValue: null);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Locale",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "TimeZoneId",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "Locale",
table: "BotConfig");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,357 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class dateadded : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "WaifuUpdates",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "WaifuInfo",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "PokeGame",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "SelfAssignableRoles",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "Reminders",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "RaceAnimals",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "Quotes",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "PlaylistSong",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "PlayingStatus",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "Permission",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "MutedUserId",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "MusicPlaylists",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "ModulePrefixes",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "LogSettings",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "IgnoredVoicePresenceCHannels",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "IgnoredLogChannels",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "GuildRepeater",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "GuildConfigs",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "GCChannelId",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "FollowedStream",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "FilteredWord",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "FilterChannelId",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "EightBallResponses",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "Donators",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "DiscordUser",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "CustomReactions",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "CurrencyTransactions",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "Currency",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "ConversionUnits",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "CommandPrice",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "CommandCooldown",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "ClashOfClans",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "ClashCallers",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "BotConfig",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "BlacklistItem",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "AntiSpamSetting",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "AntiSpamIgnore",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "AntiRaidSetting",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DateAdded",
table: "WaifuUpdates");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "WaifuInfo");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "PokeGame");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "SelfAssignableRoles");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "Reminders");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "RaceAnimals");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "Quotes");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "PlaylistSong");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "PlayingStatus");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "Permission");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "MutedUserId");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "MusicPlaylists");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "ModulePrefixes");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "LogSettings");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "IgnoredVoicePresenceCHannels");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "IgnoredLogChannels");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "GuildRepeater");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "GCChannelId");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "FollowedStream");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "FilteredWord");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "FilterChannelId");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "EightBallResponses");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "Donators");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "DiscordUser");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "CustomReactions");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "CurrencyTransactions");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "Currency");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "ConversionUnits");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "CommandPrice");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "CommandCooldown");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "ClashOfClans");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "ClashCallers");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "BlacklistItem");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "AntiSpamSetting");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "AntiSpamIgnore");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "AntiRaidSetting");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class permsv2 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Permissionv2",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
GuildConfigId = table.Column<int>(nullable: true),
Index = table.Column<int>(nullable: false),
PrimaryTarget = table.Column<int>(nullable: false),
PrimaryTargetId = table.Column<ulong>(nullable: false),
SecondaryTarget = table.Column<int>(nullable: false),
SecondaryTargetName = table.Column<string>(nullable: true),
State = table.Column<bool>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Permissionv2", x => x.Id);
table.ForeignKey(
name: "FK_Permissionv2_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Permissionv2_GuildConfigId",
table: "Permissionv2",
column: "GuildConfigId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Permissionv2");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class unmutetimers : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "UnmuteTimer",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
GuildConfigId = table.Column<int>(nullable: true),
UnmuteAt = table.Column<DateTime>(nullable: false),
UserId = table.Column<ulong>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UnmuteTimer", x => x.Id);
table.ForeignKey(
name: "FK_UnmuteTimer_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_UnmuteTimer_GuildConfigId",
table: "UnmuteTimer",
column: "GuildConfigId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "UnmuteTimer");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class vcrole : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "VcRoleInfo",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
GuildConfigId = table.Column<int>(nullable: true),
RoleId = table.Column<ulong>(nullable: false),
VoiceChannelId = table.Column<ulong>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_VcRoleInfo", x => x.Id);
table.ForeignKey(
name: "FK_VcRoleInfo_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_VcRoleInfo_GuildConfigId",
table: "VcRoleInfo",
column: "GuildConfigId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "VcRoleInfo");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class cradandcrdm : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "AutoDeleteTrigger",
table: "CustomReactions",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "DmResponse",
table: "CustomReactions",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AutoDeleteTrigger",
table: "CustomReactions");
migrationBuilder.DropColumn(
name: "DmResponse",
table: "CustomReactions");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class commandaliasing : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "CommandAlias",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
GuildConfigId = table.Column<int>(nullable: true),
Mapping = table.Column<string>(nullable: true),
Trigger = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CommandAlias", x => x.Id);
table.ForeignKey(
name: "FK_CommandAlias_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_CommandAlias_GuildConfigId",
table: "CommandAlias",
column: "GuildConfigId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CommandAlias");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class warningcommands : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "WarningsInitialized",
table: "GuildConfigs",
nullable: false,
defaultValue: false);
migrationBuilder.CreateTable(
name: "Warnings",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
Forgiven = table.Column<bool>(nullable: false),
ForgivenBy = table.Column<string>(nullable: true),
GuildId = table.Column<ulong>(nullable: false),
Moderator = table.Column<string>(nullable: true),
Reason = table.Column<string>(nullable: true),
UserId = table.Column<ulong>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Warnings", x => x.Id);
});
migrationBuilder.CreateTable(
name: "WarningPunishment",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Count = table.Column<int>(nullable: false),
DateAdded = table.Column<DateTime>(nullable: true),
GuildConfigId = table.Column<int>(nullable: true),
Punishment = table.Column<int>(nullable: false),
Time = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_WarningPunishment", x => x.Id);
table.ForeignKey(
name: "FK_WarningPunishment_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_WarningPunishment_GuildConfigId",
table: "WarningPunishment",
column: "GuildConfigId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Warnings");
migrationBuilder.DropTable(
name: "WarningPunishment");
migrationBuilder.DropColumn(
name: "WarningsInitialized",
table: "GuildConfigs");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class startupcommands : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "StartupCommand",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
BotConfigId = table.Column<int>(nullable: true),
ChannelId = table.Column<ulong>(nullable: false),
ChannelName = table.Column<string>(nullable: true),
CommandText = table.Column<string>(nullable: true),
DateAdded = table.Column<DateTime>(nullable: true),
GuildId = table.Column<ulong>(nullable: true),
GuildName = table.Column<string>(nullable: true),
Index = table.Column<int>(nullable: false),
VoiceChannelId = table.Column<ulong>(nullable: true),
VoiceChannelName = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_StartupCommand", x => x.Id);
table.ForeignKey(
name: "FK_StartupCommand_BotConfig_BotConfigId",
column: x => x.BotConfigId,
principalTable: "BotConfig",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_StartupCommand_BotConfigId",
table: "StartupCommand",
column: "BotConfigId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "StartupCommand");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class slowmodewhitelist : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "SlowmodeIgnoredRole",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
GuildConfigId = table.Column<int>(nullable: true),
RoleId = table.Column<ulong>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SlowmodeIgnoredRole", x => x.Id);
table.ForeignKey(
name: "FK_SlowmodeIgnoredRole_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "SlowmodeIgnoredUser",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
GuildConfigId = table.Column<int>(nullable: true),
UserId = table.Column<ulong>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SlowmodeIgnoredUser", x => x.Id);
table.ForeignKey(
name: "FK_SlowmodeIgnoredUser_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_SlowmodeIgnoredRole_GuildConfigId",
table: "SlowmodeIgnoredRole",
column: "GuildConfigId");
migrationBuilder.CreateIndex(
name: "IX_SlowmodeIgnoredUser_GuildConfigId",
table: "SlowmodeIgnoredUser",
column: "GuildConfigId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "SlowmodeIgnoredRole");
migrationBuilder.DropTable(
name: "SlowmodeIgnoredUser");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class patreonrewards : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "RewardedUsers",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
AmountRewardedThisMonth = table.Column<int>(nullable: false),
DateAdded = table.Column<DateTime>(nullable: true),
LastReward = table.Column<DateTime>(nullable: false),
UserId = table.Column<ulong>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RewardedUsers", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_RewardedUsers_UserId",
table: "RewardedUsers",
column: "UserId",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "RewardedUsers");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class flowershop : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ShopEntry",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
AuthorId = table.Column<ulong>(nullable: false),
DateAdded = table.Column<DateTime>(nullable: true),
GuildConfigId = table.Column<int>(nullable: true),
Index = table.Column<int>(nullable: false),
Name = table.Column<string>(nullable: true),
Price = table.Column<int>(nullable: false),
RoleId = table.Column<ulong>(nullable: false),
RoleName = table.Column<string>(nullable: true),
Type = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ShopEntry", x => x.Id);
table.ForeignKey(
name: "FK_ShopEntry_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "ShopEntryItem",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
ShopEntryId = table.Column<int>(nullable: true),
Text = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ShopEntryItem", x => x.Id);
table.ForeignKey(
name: "FK_ShopEntryItem_ShopEntry_ShopEntryId",
column: x => x.ShopEntryId,
principalTable: "ShopEntry",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_ShopEntry_GuildConfigId",
table: "ShopEntry",
column: "GuildConfigId");
migrationBuilder.CreateIndex(
name: "IX_ShopEntryItem_ShopEntryId",
table: "ShopEntryItem",
column: "ShopEntryId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ShopEntryItem");
migrationBuilder.DropTable(
name: "ShopEntry");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class gamevoicechannel : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<ulong>(
name: "GameVoiceChannel",
table: "GuildConfigs",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GameVoiceChannel",
table: "GuildConfigs");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class gmodandcmod : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BlockedCmdOrMdl",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
BotConfigId = table.Column<int>(nullable: true),
BotConfigId1 = table.Column<int>(nullable: true),
DateAdded = table.Column<DateTime>(nullable: true),
Name = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_BlockedCmdOrMdl", x => x.Id);
table.ForeignKey(
name: "FK_BlockedCmdOrMdl_BotConfig_BotConfigId",
column: x => x.BotConfigId,
principalTable: "BotConfig",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_BlockedCmdOrMdl_BotConfig_BotConfigId1",
column: x => x.BotConfigId1,
principalTable: "BotConfig",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_BlockedCmdOrMdl_BotConfigId",
table: "BlockedCmdOrMdl",
column: "BotConfigId");
migrationBuilder.CreateIndex(
name: "IX_BlockedCmdOrMdl_BotConfigId1",
table: "BlockedCmdOrMdl",
column: "BotConfigId1");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "BlockedCmdOrMdl");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +1,55 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System.Net.Http;
using System.IO;
using static NadekoBot.Modules.Permissions.Permissions; using static NadekoBot.Modules.Permissions.Permissions;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using NLog; using NLog;
using NadekoBot.Modules.Permissions;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
{ {
[NadekoModule("Administration", ".")] [NadekoModule("Administration", ".")]
public partial class Administration : DiscordModule public partial class Administration : NadekoTopLevelModule
{ {
private static ConcurrentHashSet<ulong> deleteMessagesOnCommand { get; }
private static ConcurrentDictionary<ulong, string> GuildMuteRoles { get; } = new ConcurrentDictionary<ulong, string>(); private new static readonly Logger _log;
private static ConcurrentHashSet<ulong> DeleteMessagesOnCommand { get; } = new ConcurrentHashSet<ulong>();
private new static Logger _log { get; }
static Administration() static Administration()
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
NadekoBot.CommandHandler.CommandExecuted += DelMsgOnCmd_Handler; NadekoBot.CommandHandler.CommandExecuted += DelMsgOnCmd_Handler;
DeleteMessagesOnCommand = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId)); deleteMessagesOnCommand = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId));
} }
private static async Task DelMsgOnCmd_Handler(SocketUserMessage msg, CommandInfo cmd) private static Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
{
var _ = Task.Run(async () =>
{ {
try try
{ {
var channel = msg.Channel as SocketTextChannel; var channel = msg.Channel as SocketTextChannel;
if (channel == null) if (channel == null)
return; return;
if (DeleteMessagesOnCommand.Contains(channel.Guild.Id)) if (deleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune")
await msg.DeleteAsync().ConfigureAwait(false); await msg.DeleteAsync().ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
_log.Warn(ex, "Delmsgoncmd errored..."); _log.Warn(ex, "Delmsgoncmd errored...");
} }
});
return Task.CompletedTask;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -59,28 +57,37 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.Administrator)] [RequireUserPermission(GuildPermission.Administrator)]
public async Task ResetPermissions() public async Task ResetPermissions()
{ {
var channel = (ITextChannel)Context.Channel;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var config = uow.GuildConfigs.PermissionsFor(Context.Guild.Id); var config = uow.GuildConfigs.GcWithPermissionsv2For(Context.Guild.Id);
config.RootPermission = Permission.GetDefaultRoot(); config.Permissions = Permissionv2.GetDefaultPermlist;
var toAdd = new PermissionCache()
{
RootPermission = config.RootPermission,
PermRole = config.PermissionRole,
Verbose = config.VerbosePermissions,
};
Permissions.Permissions.Cache.AddOrUpdate(channel.Guild.Id,
toAdd, (id, old) => toAdd);
await uow.CompleteAsync(); await uow.CompleteAsync();
UpdateCache(config);
}
await ReplyConfirmLocalized("perms_reset").ConfigureAwait(false);
} }
await channel.SendConfirmAsync($"{Context.Message.Author.Mention} 🆗 **Permissions for this server are reset.**"); [NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ResetGlobalPermissions()
{
using (var uow = DbHandler.UnitOfWork())
{
var gc = uow.BotConfig.GetOrCreate();
gc.BlockedCommands.Clear();
gc.BlockedModules.Clear();
GlobalPermissionCommands.BlockedCommands.Clear();
GlobalPermissionCommands.BlockedModules.Clear();
await uow.CompleteAsync();
}
await ReplyConfirmLocalized("global_perms_reset").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)] [RequireUserPermission(GuildPermission.Administrator)]
[RequireBotPermission(GuildPermission.ManageMessages)]
public async Task Delmsgoncmd() public async Task Delmsgoncmd()
{ {
bool enabled; bool enabled;
@ -93,29 +100,35 @@ namespace NadekoBot.Modules.Administration
} }
if (enabled) if (enabled)
{ {
DeleteMessagesOnCommand.Add(Context.Guild.Id); deleteMessagesOnCommand.Add(Context.Guild.Id);
await Context.Channel.SendConfirmAsync("✅ **Now automatically deleting successful command invokations.**").ConfigureAwait(false); await ReplyConfirmLocalized("delmsg_on").ConfigureAwait(false);
} }
else else
{ {
DeleteMessagesOnCommand.TryRemove(Context.Guild.Id); deleteMessagesOnCommand.TryRemove(Context.Guild.Id);
await Context.Channel.SendConfirmAsync("❗**Stopped automatic deletion of successful command invokations.**").ConfigureAwait(false); await ReplyConfirmLocalized("delmsg_off").ConfigureAwait(false);
} }
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task Setrole(IGuildUser usr, [Remainder] IRole role) public async Task Setrole(IGuildUser usr, [Remainder] IRole role)
{ {
var guser = (IGuildUser)Context.User;
var maxRole = guser.GetRoles().Max(x => x.Position);
if (maxRole < role.Position || maxRole <= usr.GetRoles().Max(x => x.Position))
return;
try try
{ {
await usr.AddRolesAsync(role).ConfigureAwait(false); await usr.AddRolesAsync(role).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($" Successfully added role **{role.Name}** to user **{usr.Username}**").ConfigureAwait(false); await ReplyConfirmLocalized("setrole", Format.Bold(role.Name), Format.Bold(usr.ToString()))
.ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
await Context.Channel.SendErrorAsync("⚠️ Failed to add role. **Bot has insufficient permissions.**\n").ConfigureAwait(false); await ReplyErrorLocalized("setrole_err").ConfigureAwait(false);
Console.WriteLine(ex.ToString()); Console.WriteLine(ex.ToString());
} }
} }
@ -123,95 +136,119 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task Removerole(IGuildUser usr, [Remainder] IRole role) public async Task Removerole(IGuildUser usr, [Remainder] IRole role)
{ {
var guser = (IGuildUser)Context.User;
if (Context.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= usr.GetRoles().Max(x => x.Position))
return;
try try
{ {
await usr.RemoveRolesAsync(role).ConfigureAwait(false); await usr.RemoveRolesAsync(role).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($" Successfully removed role **{role.Name}** from user **{usr.Username}**").ConfigureAwait(false); await ReplyConfirmLocalized("remrole", Format.Bold(role.Name), Format.Bold(usr.ToString())).ConfigureAwait(false);
} }
catch catch
{ {
await Context.Channel.SendErrorAsync("⚠️ Failed to remove role. Most likely reason: **Insufficient permissions.**").ConfigureAwait(false); await ReplyErrorLocalized("remrole_err").ConfigureAwait(false);
} }
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task RenameRole(IRole roleToEdit, string newname) public async Task RenameRole(IRole roleToEdit, string newname)
{ {
var guser = (IGuildUser)Context.User;
if (Context.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= roleToEdit.Position)
return;
try try
{ {
if (roleToEdit.Position > (await Context.Guild.GetCurrentUserAsync().ConfigureAwait(false)).GetRoles().Max(r => r.Position)) if (roleToEdit.Position > (await Context.Guild.GetCurrentUserAsync().ConfigureAwait(false)).GetRoles().Max(r => r.Position))
{ {
await Context.Channel.SendErrorAsync("🚫 You can't edit roles higher than your highest role.").ConfigureAwait(false); await ReplyErrorLocalized("renrole_perms").ConfigureAwait(false);
return; return;
} }
await roleToEdit.ModifyAsync(g => g.Name = newname).ConfigureAwait(false); await roleToEdit.ModifyAsync(g => g.Name = newname).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("✅ Role renamed.").ConfigureAwait(false); await ReplyConfirmLocalized("renrole").ConfigureAwait(false);
} }
catch (Exception) catch (Exception)
{ {
await Context.Channel.SendErrorAsync("⚠️ Failed to rename role. Probably **insufficient permissions.**").ConfigureAwait(false); await ReplyErrorLocalized("renrole_err").ConfigureAwait(false);
} }
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task RemoveAllRoles([Remainder] IGuildUser user) public async Task RemoveAllRoles([Remainder] IGuildUser user)
{ {
var guser = (IGuildUser)Context.User;
var userRoles = user.GetRoles();
if (guser.Id != Context.Guild.OwnerId &&
(user.Id == Context.Guild.OwnerId || guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position)))
return;
try try
{ {
await user.RemoveRolesAsync(user.GetRoles()).ConfigureAwait(false); await user.RemoveRolesAsync(userRoles).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"🗑 Successfully removed **all** roles from user **{user.Username}**").ConfigureAwait(false); await ReplyConfirmLocalized("rar", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch
{ {
await Context.Channel.SendErrorAsync("⚠️ Failed to remove roles. Most likely reason: **Insufficient permissions.**").ConfigureAwait(false); await ReplyErrorLocalized("rar_err").ConfigureAwait(false);
} }
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task CreateRole([Remainder] string roleName = null) public async Task CreateRole([Remainder] string roleName = null)
{ {
if (string.IsNullOrWhiteSpace(roleName)) if (string.IsNullOrWhiteSpace(roleName))
return; return;
try
{
var r = await Context.Guild.CreateRoleAsync(roleName).ConfigureAwait(false); var r = await Context.Guild.CreateRoleAsync(roleName).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"✅ Successfully created role **{r.Name}**.").ConfigureAwait(false); await ReplyConfirmLocalized("cr", Format.Bold(r.Name)).ConfigureAwait(false);
}
catch (Exception)
{
await Context.Channel.SendErrorAsync("⚠️ Unspecified error.").ConfigureAwait(false);
}
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task RoleHoist(string roleSearchName, PermissionAction targetState)
{
var roleName = roleSearchName.ToUpperInvariant();
var role = Context.Guild.Roles.FirstOrDefault(r => r.Name.ToUpperInvariant() == roleName);
await role.ModifyAsync(r => r.Hoist = targetState.Value).ConfigureAwait(false);
await ReplyConfirmLocalized("rh", Format.Bold(role.Name), Format.Bold(targetState.Value.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task RoleColor(params string[] args) public async Task RoleColor(params string[] args)
{ {
if (args.Count() != 2 && args.Count() != 4) if (args.Length != 2 && args.Length != 4)
{ {
await Context.Channel.SendErrorAsync("❌ The parameters specified are **invalid.**").ConfigureAwait(false); await ReplyErrorLocalized("rc_params").ConfigureAwait(false);
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.FirstOrDefault(r => r.Name.ToUpperInvariant() == roleName);
if (role == null) if (role == null)
{ {
await Context.Channel.SendErrorAsync("🚫 That role **does not exist.**").ConfigureAwait(false); await ReplyErrorLocalized("rc_not_exist").ConfigureAwait(false);
return; return;
} }
try try
{ {
var rgb = args.Count() == 4; var rgb = args.Length == 4;
var arg1 = args[1].Replace("#", ""); var arg1 = args[1].Replace("#", "");
var red = Convert.ToByte(rgb ? int.Parse(arg1) : Convert.ToInt32(arg1.Substring(0, 2), 16)); var red = Convert.ToByte(rgb ? int.Parse(arg1) : Convert.ToInt32(arg1.Substring(0, 2), 16));
@ -219,226 +256,121 @@ namespace NadekoBot.Modules.Administration
var blue = Convert.ToByte(rgb ? int.Parse(args[3]) : Convert.ToInt32(arg1.Substring(4, 2), 16)); var blue = Convert.ToByte(rgb ? int.Parse(args[3]) : Convert.ToInt32(arg1.Substring(4, 2), 16));
await role.ModifyAsync(r => r.Color = new Color(red, green, blue)).ConfigureAwait(false); await role.ModifyAsync(r => r.Color = new Color(red, green, blue)).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"☑️ Role **{role.Name}'s** color has been changed.").ConfigureAwait(false); await ReplyConfirmLocalized("rc", Format.Bold(role.Name)).ConfigureAwait(false);
} }
catch (Exception) catch (Exception)
{ {
await Context.Channel.SendErrorAsync("⚠️ Error occured, most likely **invalid parameters** or **insufficient permissions.**").ConfigureAwait(false); await ReplyErrorLocalized("rc_perms").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
public async Task Ban(IGuildUser user, [Remainder] string msg = null)
{
if (string.IsNullOrWhiteSpace(msg))
{
msg = "❗No reason provided.";
}
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);
return;
}
if (!string.IsNullOrWhiteSpace(msg))
{
try
{
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 { }
}
try
{
await Context.Guild.AddBanAsync(user, 7).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("⛔️ **Banned** user **" + user.Username + "** ID: `" + user.Id + "`").ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ **Error.** Most likely I don't have sufficient permissions.").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.KickMembers)]
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task Softban(IGuildUser user, [Remainder] string msg = null)
{
if (string.IsNullOrWhiteSpace(msg))
{
msg = "❗No reason provided.";
}
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.");
return;
}
if (!string.IsNullOrWhiteSpace(msg))
{
try
{
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 { }
}
try
{
await Context.Guild.AddBanAsync(user, 7).ConfigureAwait(false);
try { await Context.Guild.RemoveBanAsync(user).ConfigureAwait(false); }
catch { await Context.Guild.RemoveBanAsync(user).ConfigureAwait(false); }
await Context.Channel.SendConfirmAsync("☣ **Soft-Banned** user **" + user.Username + "** ID: `" + user.Id + "`").ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ Error. Most likely I don't have sufficient permissions.").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.KickMembers)]
public async Task Kick(IGuildUser user, [Remainder] string msg = null)
{
if (user == null)
{
await Context.Channel.SendErrorAsync("❗User not found.").ConfigureAwait(false);
return;
}
if (Context.Message.Author.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.");
return;
}
if (!string.IsNullOrWhiteSpace(msg))
{
try
{
await user.SendErrorAsync($"‼️**You have been KICKED from `{Context.Guild.Name}` server.**\n" +
$"⚖ *Reason:* {msg}").ConfigureAwait(false);
await Task.Delay(2000).ConfigureAwait(false);
}
catch { }
}
try
{
await user.KickAsync().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("‼️**Kicked** user **" + user.Username + "** ID: `" + user.Id + "`").ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ Error. Most likely I don't have sufficient permissions.").ConfigureAwait(false);
} }
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.DeafenMembers)] [RequireUserPermission(GuildPermission.DeafenMembers)]
[RequireBotPermission(GuildPermission.DeafenMembers)]
public async Task Deafen(params IGuildUser[] users) public async Task Deafen(params IGuildUser[] users)
{ {
if (!users.Any()) if (!users.Any())
return; return;
try
{
foreach (var u in users) foreach (var u in users)
{
try
{ {
await u.ModifyAsync(usr => usr.Deaf = true).ConfigureAwait(false); await u.ModifyAsync(usr => usr.Deaf = true).ConfigureAwait(false);
} }
await Context.Channel.SendConfirmAsync("🔇 **Deafen** successful.").ConfigureAwait(false);
}
catch catch
{ {
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); // ignored
} }
}
await ReplyConfirmLocalized("deafen").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.DeafenMembers)] [RequireUserPermission(GuildPermission.DeafenMembers)]
[RequireBotPermission(GuildPermission.DeafenMembers)]
public async Task UnDeafen(params IGuildUser[] users) public async Task UnDeafen(params IGuildUser[] users)
{ {
if (!users.Any()) if (!users.Any())
return; return;
try
{
foreach (var u in users) foreach (var u in users)
{
try
{ {
await u.ModifyAsync(usr => usr.Deaf = false).ConfigureAwait(false); await u.ModifyAsync(usr => usr.Deaf = false).ConfigureAwait(false);
} }
await Context.Channel.SendConfirmAsync("🔊 **Undeafen** successful.").ConfigureAwait(false);
}
catch catch
{ {
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); // ignored
} }
} }
await ReplyConfirmLocalized("undeafen").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)] [RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
public async Task DelVoiChanl([Remainder] IVoiceChannel voiceChannel) public async Task DelVoiChanl([Remainder] IVoiceChannel voiceChannel)
{ {
await voiceChannel.DeleteAsync().ConfigureAwait(false); await voiceChannel.DeleteAsync().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"🗑 Removed voice channel **{voiceChannel.Name}** successfully.").ConfigureAwait(false); await ReplyConfirmLocalized("delvoich", Format.Bold(voiceChannel.Name)).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)] [RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
public async Task CreatVoiChanl([Remainder] string channelName) public async Task CreatVoiChanl([Remainder] string channelName)
{ {
var ch = await Context.Guild.CreateVoiceChannelAsync(channelName).ConfigureAwait(false); var ch = await Context.Guild.CreateVoiceChannelAsync(channelName).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"✅ Created voice channel **{ch.Name}**. ID: `{ch.Id}`").ConfigureAwait(false); await ReplyConfirmLocalized("createvoich",Format.Bold(ch.Name)).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)] [RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
public async Task DelTxtChanl([Remainder] ITextChannel toDelete) public async Task DelTxtChanl([Remainder] ITextChannel toDelete)
{ {
await toDelete.DeleteAsync().ConfigureAwait(false); await toDelete.DeleteAsync().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"🗑 Removed text channel **{toDelete.Name}**. ID: `{toDelete.Id}`").ConfigureAwait(false); await ReplyConfirmLocalized("deltextchan", Format.Bold(toDelete.Name)).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)] [RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
public async Task CreaTxtChanl([Remainder] string channelName) public async Task CreaTxtChanl([Remainder] string channelName)
{ {
var txtCh = await Context.Guild.CreateTextChannelAsync(channelName).ConfigureAwait(false); var txtCh = await Context.Guild.CreateTextChannelAsync(channelName).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"✅ Added text channel **{txtCh.Name}**. ID: `{txtCh.Id}`").ConfigureAwait(false); await ReplyConfirmLocalized("createtextchan", Format.Bold(txtCh.Name)).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)] [RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
public async Task SetTopic([Remainder] string topic = null) public async Task SetTopic([Remainder] string topic = null)
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
topic = topic ?? ""; topic = topic ?? "";
await channel.ModifyAsync(c => c.Topic = topic); await channel.ModifyAsync(c => c.Topic = topic);
await channel.SendConfirmAsync("🆗 **New channel topic set.**").ConfigureAwait(false); await ReplyConfirmLocalized("set_topic").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)] [RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
public async Task SetChanlName([Remainder] string name) public async Task SetChanlName([Remainder] string name)
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
await channel.ModifyAsync(c => c.Name = name).ConfigureAwait(false); await channel.ModifyAsync(c => c.Name = name).ConfigureAwait(false);
await channel.SendConfirmAsync("🆗 **New channel name set.**").ConfigureAwait(false); await ReplyConfirmLocalized("set_channel_name").ConfigureAwait(false);
} }
@ -452,17 +384,26 @@ namespace NadekoBot.Modules.Administration
var enumerable = (await Context.Channel.GetMessagesAsync().Flatten()).AsEnumerable(); var enumerable = (await Context.Channel.GetMessagesAsync().Flatten()).AsEnumerable();
enumerable = enumerable.Where(x => x.Author.Id == user.Id); enumerable = enumerable.Where(x => x.Author.Id == user.Id);
await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false); await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false);
Context.Message.DeleteAfter(3);
} }
// prune x // prune x
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(ChannelPermission.ManageMessages)] [RequireUserPermission(ChannelPermission.ManageMessages)]
[RequireBotPermission(GuildPermission.ManageMessages)]
[Priority(0)]
public async Task Prune(int count) public async Task Prune(int count)
{ {
if (count < 1)
return;
await Context.Message.DeleteAsync().ConfigureAwait(false); await Context.Message.DeleteAsync().ConfigureAwait(false);
int limit = (count < 100) ? count : 100; int limit = (count < 100) ? count + 1 : 100;
var enumerable = (await Context.Channel.GetMessagesAsync(limit: limit).Flatten().ConfigureAwait(false)); var enumerable = (await Context.Channel.GetMessagesAsync(limit: limit).Flatten().ConfigureAwait(false));
if (enumerable.FirstOrDefault()?.Id == Context.Message.Id)
enumerable = enumerable.Skip(1).ToArray();
else
enumerable = enumerable.Take(count);
await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false); await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false);
} }
@ -470,42 +411,33 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(ChannelPermission.ManageMessages)] [RequireUserPermission(ChannelPermission.ManageMessages)]
[RequireBotPermission(GuildPermission.ManageMessages)]
[Priority(1)]
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);
Context.Message.DeleteAfter(3);
} }
[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 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)]
public async Task MentionRole(params IRole[] roles) public async Task MentionRole(params IRole[] roles)
{ {
string send = $"❕{Context.User.Mention} has invoked a mention on the following roles ❕"; string send = "❕" +GetText("menrole",Context.User.Mention);
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)).Take(50).Select(u => u.Mention));
} }
while (send.Length > 2000) while (send.Length > 2000)
@ -519,7 +451,7 @@ title, title).ConfigureAwait(false);
await Context.Channel.SendMessageAsync(send).ConfigureAwait(false); await Context.Channel.SendMessageAsync(send).ConfigureAwait(false);
} }
IGuild nadekoSupportServer; private IGuild _nadekoSupportServer;
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Donators() public async Task Donators()
{ {
@ -529,18 +461,15 @@ title, title).ConfigureAwait(false);
{ {
donatorsOrdered = uow.Donators.GetDonatorsOrdered(); donatorsOrdered = uow.Donators.GetDonatorsOrdered();
} }
await Context.Channel.SendConfirmAsync("Thanks to the people listed below for making this project happen!", string.Join("⭐", donatorsOrdered.Select(d => d.Name))).ConfigureAwait(false); await Context.Channel.SendConfirmAsync(GetText("donators"), string.Join("⭐", donatorsOrdered.Select(d => d.Name))).ConfigureAwait(false);
nadekoSupportServer = nadekoSupportServer ?? NadekoBot.Client.GetGuild(117523346618318850); _nadekoSupportServer = _nadekoSupportServer ?? NadekoBot.Client.GetGuild(117523346618318850);
if (nadekoSupportServer == null) var patreonRole = _nadekoSupportServer?.GetRole(236667642088259585);
return;
var patreonRole = nadekoSupportServer.GetRole(236667642088259585);
if (patreonRole == null) if (patreonRole == null)
return; return;
var usrs = (await nadekoSupportServer.GetUsersAsync()).Where(u => u.RoleIds.Contains(236667642088259585u)); var usrs = (await _nadekoSupportServer.GetUsersAsync()).Where(u => u.RoleIds.Contains(236667642088259585u));
await Context.Channel.SendConfirmAsync("Patreon supporters", string.Join("⭐", usrs.Select(d => d.Username))).ConfigureAwait(false); await Context.Channel.SendConfirmAsync("Patreon supporters", string.Join("⭐", usrs.Select(d => d.Username))).ConfigureAwait(false);
} }
@ -555,8 +484,47 @@ title, title).ConfigureAwait(false);
don = uow.Donators.AddOrUpdateDonator(donator.Id, donator.Username, amount); don = uow.Donators.AddOrUpdateDonator(donator.Id, donator.Username, amount);
await uow.CompleteAsync(); await uow.CompleteAsync();
} }
await ReplyConfirmLocalized("donadd", don.Amount).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"Successfuly added a new donator. Total donated amount from this user: {don.Amount} 👑").ConfigureAwait(false);
} }
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//public async Task Timezones(int page = 1)
//{
// page -= 1;
// if (page < 0 || page > 20)
// return;
// var timezones = TimeZoneInfo.GetSystemTimeZones();
// var timezonesPerPage = 20;
// await Context.Channel.SendPaginatedConfirmAsync(page + 1, (curPage) => new EmbedBuilder()
// .WithOkColor()
// .WithTitle("Available Timezones")
// .WithDescription(string.Join("\n", timezones.Skip((curPage - 1) * timezonesPerPage).Take(timezonesPerPage).Select(x => $"`{x.Id,-25}` UTC{x.BaseUtcOffset:hhmm}"))),
// timezones.Count / timezonesPerPage);
//}
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//public async Task Timezone([Remainder] string id)
//{
// TimeZoneInfo tz;
// try
// {
// tz = TimeZoneInfo.FindSystemTimeZoneById(id);
// if (tz != null)
// await Context.Channel.SendConfirmAsync(tz.ToString()).ConfigureAwait(false);
// }
// catch (Exception ex)
// {
// tz = null;
// _log.Warn(ex);
// }
// if (tz == null)
// await Context.Channel.SendErrorAsync("Timezone not found. You should specify one of the timezones listed in the 'timezones' command.").ConfigureAwait(false);
//}
} }
} }

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

@ -3,11 +3,9 @@ using Discord.Commands;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NLog; using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -16,25 +14,23 @@ namespace NadekoBot.Modules.Administration
public partial class Administration public partial class Administration
{ {
[Group] [Group]
public class AutoAssignRoleCommands : ModuleBase public class AutoAssignRoleCommands : NadekoSubmodule
{ {
private static Logger _log { get; }
//guildid/roleid //guildid/roleid
private static ConcurrentDictionary<ulong, ulong> AutoAssignedRoles { get; } private static ConcurrentDictionary<ulong, ulong> autoAssignedRoles { get; }
static AutoAssignRoleCommands() static AutoAssignRoleCommands()
{ {
_log = LogManager.GetCurrentClassLogger(); var log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
AutoAssignedRoles = new ConcurrentDictionary<ulong, ulong>(NadekoBot.AllGuildConfigs.Where(x => x.AutoAssignRoleId != 0) autoAssignedRoles = new ConcurrentDictionary<ulong, ulong>(NadekoBot.AllGuildConfigs.Where(x => x.AutoAssignRoleId != 0)
.ToDictionary(k => k.GuildId, v => v.AutoAssignRoleId)); .ToDictionary(k => k.GuildId, v => v.AutoAssignRoleId));
NadekoBot.Client.UserJoined += async (user) => NadekoBot.Client.UserJoined += async (user) =>
{ {
try try
{ {
ulong roleId = 0; ulong roleId;
AutoAssignedRoles.TryGetValue(user.Guild.Id, out roleId); autoAssignedRoles.TryGetValue(user.Guild.Id, out roleId);
if (roleId == 0) if (roleId == 0)
return; return;
@ -44,11 +40,8 @@ namespace NadekoBot.Modules.Administration
if (role != null) if (role != null)
await user.AddRolesAsync(role).ConfigureAwait(false); await user.AddRolesAsync(role).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch (Exception ex) { log.Warn(ex); }
}; };
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -56,20 +49,24 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
public async Task AutoAssignRole([Remainder] IRole role = null) public async Task AutoAssignRole([Remainder] IRole role = null)
{ {
GuildConfig conf; var guser = (IGuildUser)Context.User;
if (role != null)
if (Context.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
conf = uow.GuildConfigs.For(Context.Guild.Id, set => set); var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set);
if (role == null) if (role == null)
{ {
conf.AutoAssignRoleId = 0; conf.AutoAssignRoleId = 0;
ulong throwaway; ulong throwaway;
AutoAssignedRoles.TryRemove(Context.Guild.Id, out throwaway); autoAssignedRoles.TryRemove(Context.Guild.Id, out throwaway);
} }
else else
{ {
conf.AutoAssignRoleId = role.Id; conf.AutoAssignRoleId = role.Id;
AutoAssignedRoles.AddOrUpdate(Context.Guild.Id, role.Id, (key, val) => role.Id); autoAssignedRoles.AddOrUpdate(Context.Guild.Id, role.Id, (key, val) => role.Id);
} }
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
@ -77,11 +74,11 @@ namespace NadekoBot.Modules.Administration
if (role == null) if (role == null)
{ {
await Context.Channel.SendConfirmAsync("🆗 **Auto assign role** on user join is now **disabled**.").ConfigureAwait(false); await ReplyConfirmLocalized("aar_disabled").ConfigureAwait(false);
return; return;
} }
await Context.Channel.SendConfirmAsync("✅ **Auto assign role** on user join is now **enabled**.").ConfigureAwait(false); await ReplyConfirmLocalized("aar_enabled").ConfigureAwait(false);
} }
} }
} }

View File

@ -1,97 +0,0 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class CrossServerTextChannel : ModuleBase
{
static CrossServerTextChannel()
{
_log = LogManager.GetCurrentClassLogger();
NadekoBot.Client.MessageReceived += async (imsg) =>
{
try
{
if (imsg.Author.IsBot)
return;
var msg = imsg as IUserMessage;
if (msg == null)
return;
var channel = imsg.Channel as ITextChannel;
if (channel == null)
return;
if (msg.Author.Id == NadekoBot.Client.CurrentUser().Id) return;
foreach (var subscriber in Subscribers)
{
var set = subscriber.Value;
if (!set.Contains(channel))
continue;
foreach (var chan in set.Except(new[] { channel }))
{
try { await chan.SendMessageAsync(GetText(channel.Guild, channel, (IGuildUser)msg.Author, msg)).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
}
}
}
catch (Exception ex) {
_log.Warn(ex);
}
};
}
private static string GetText(IGuild server, ITextChannel channel, IGuildUser user, IUserMessage message) =>
$"**{server.Name} | {channel.Name}** `{user.Username}`: " + message.Content;
public static readonly ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>> Subscribers = new ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>>();
private static Logger _log { get; }
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Scsc()
{
var token = new NadekoRandom().Next();
var set = new ConcurrentHashSet<ITextChannel>();
if (Subscribers.TryAdd(token, set))
{
set.Add((ITextChannel)Context.Channel);
await ((IGuildUser)Context.User).SendConfirmAsync("This is your CSC token", token.ToString()).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageGuild)]
public async Task Jcsc(int token)
{
ConcurrentHashSet<ITextChannel> set;
if (!Subscribers.TryGetValue(token, out set))
return;
set.Add((ITextChannel)Context.Channel);
await Context.Channel.SendConfirmAsync("Joined cross server channel.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageGuild)]
public async Task Lcsc()
{
foreach (var subscriber in Subscribers)
{
subscriber.Value.TryRemove((ITextChannel)Context.Channel);
}
await Context.Channel.SendMessageAsync("Left cross server channel.").ConfigureAwait(false);
}
}
}
}

View File

@ -1,90 +0,0 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NLog;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class DMForwardCommands : ModuleBase
{
private static bool ForwardDMs { get; set; }
private static bool ForwardDMsToAllOwners { get; set; }
private static readonly Logger _log;
static DMForwardCommands()
{
_log = LogManager.GetCurrentClassLogger();
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
ForwardDMs = config.ForwardMessages;
ForwardDMsToAllOwners = config.ForwardToAllOwners;
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ForwardMessages()
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
ForwardDMs = config.ForwardMessages = !config.ForwardMessages;
uow.Complete();
}
if (ForwardDMs)
await Context.Channel.SendConfirmAsync("✅ **I will forward DMs from now on.**").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync("🆗 **I will stop forwarding DMs from now on.**").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ForwardToAll()
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
ForwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners;
uow.Complete();
}
if (ForwardDMsToAllOwners)
await Context.Channel.SendConfirmAsync(" **I will forward DMs to all owners.**").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync(" **I will forward DMs only to the first owner.**").ConfigureAwait(false);
}
public static async Task HandleDMForwarding(SocketMessage msg, List<IDMChannel> ownerChannels)
{
if (ForwardDMs && ownerChannels.Any())
{
var title = $"DM from [{msg.Author}]({msg.Author.Id})";
if (ForwardDMsToAllOwners)
{
var msgs = await Task.WhenAll(ownerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id)
.Select(ch => ch.SendConfirmAsync(title, msg.Content))).ConfigureAwait(false);
}
else
{
var firstOwnerChannel = ownerChannels.First();
if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
try { await firstOwnerChannel.SendConfirmAsync(title, msg.Content).ConfigureAwait(false); } catch { }
}
}
}
}
}
}

View File

@ -0,0 +1,131 @@
using Discord;
using Discord.Commands;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Discord.WebSocket;
using NLog;
using NadekoBot.Extensions;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class GameChannelCommands : NadekoSubmodule
{
//private static readonly Timer _t;
private static readonly ConcurrentHashSet<ulong> gameVoiceChannels = new ConcurrentHashSet<ulong>();
private static new readonly Logger _log;
static GameChannelCommands()
{
//_t = new Timer(_ => {
//}, null, );
_log = LogManager.GetCurrentClassLogger();
gameVoiceChannels = new ConcurrentHashSet<ulong>(
NadekoBot.AllGuildConfigs.Where(gc => gc.GameVoiceChannel != null)
.Select(gc => gc.GameVoiceChannel.Value));
NadekoBot.Client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
}
private static Task Client_UserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState, SocketVoiceState newState)
{
var _ = Task.Run(async () =>
{
try
{
var gUser = usr as SocketGuildUser;
if (gUser == null)
return;
var game = gUser.Game?.Name.TrimTo(50).ToLowerInvariant();
if (oldState.VoiceChannel == newState.VoiceChannel ||
newState.VoiceChannel == null)
return;
if (!gameVoiceChannels.Contains(newState.VoiceChannel.Id) ||
string.IsNullOrWhiteSpace(game))
return;
var vch = gUser.Guild.VoiceChannels
.FirstOrDefault(x => x.Name.ToLowerInvariant() == game);
if (vch == null)
return;
await Task.Delay(1000).ConfigureAwait(false);
await gUser.ModifyAsync(gu => gu.Channel = vch).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
});
return Task.CompletedTask;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
[RequireBotPermission(GuildPermission.MoveMembers)]
public async Task GameVoiceChannel()
{
var vch = ((IGuildUser)Context.User).VoiceChannel;
if (vch == null)
{
await ReplyErrorLocalized("not_in_voice").ConfigureAwait(false);
return;
}
ulong? id;
using (var uow = DbHandler.UnitOfWork())
{
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set);
if (gc.GameVoiceChannel == vch.Id)
{
gameVoiceChannels.TryRemove(vch.Id);
id = gc.GameVoiceChannel = null;
}
else
{
if(gc.GameVoiceChannel != null)
gameVoiceChannels.TryRemove(gc.GameVoiceChannel.Value);
gameVoiceChannels.Add(vch.Id);
id = gc.GameVoiceChannel = vch.Id;
}
uow.Complete();
}
if (id == null)
{
await ReplyConfirmLocalized("gvc_disabled").ConfigureAwait(false);
}
else
{
gameVoiceChannels.Add(vch.Id);
await ReplyConfirmLocalized("gvc_enabled", Format.Bold(vch.Name)).ConfigureAwait(false);
}
}
}
}
}

View File

@ -0,0 +1,254 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class LocalizationCommands : NadekoSubmodule
{
//Română, România
//Bahasa Indonesia, Indonesia
private ImmutableDictionary<string, string> supportedLocales { get; } = new Dictionary<string, string>()
{
//{"ar", "العربية" },
{"zh-TW", "繁體中文, 台灣" },
{"zh-CN", "简体中文, 中华人民共和国"},
{"nl-NL", "Nederlands, Nederland"},
{"en-US", "English, United States"},
{"fr-FR", "Français, France"},
{"de-DE", "Deutsch, Deutschland"},
{"he-IL", "עברית, ישראל"},
{"id-ID", "Bahasa Indonesia, Indonesia" },
{"it-IT", "Italiano, Italia" },
//{"ja-JP", "日本語, 日本"},
{"ko-KR", "한국어, 대한민국" },
{"nb-NO", "Norsk, Norge"},
{"pl-PL", "Polski, Polska" },
{"pt-BR", "Português Brasileiro, Brasil"},
{"ru-RU", "Русский, Россия"},
{"sr-Cyrl-RS", "Српски, Србија"},
{"es-ES", "Español, España"},
{"sv-SE", "Svenska, Sverige"},
{"tr-TR", "Türkçe, Türkiye"}
}.ToImmutableDictionary();
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task LanguageSet()
{
var cul = NadekoBot.Localization.GetCultureInfo(Context.Guild);
await ReplyConfirmLocalized("lang_set_show", Format.Bold(cul.ToString()), Format.Bold(cul.NativeName))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task LanguageSet(string name)
{
try
{
CultureInfo ci;
if (name.Trim().ToLowerInvariant() == "default")
{
NadekoBot.Localization.RemoveGuildCulture(Context.Guild);
ci = NadekoBot.Localization.DefaultCultureInfo;
}
else
{
ci = new CultureInfo(name);
NadekoBot.Localization.SetGuildCulture(Context.Guild, ci);
}
await ReplyConfirmLocalized("lang_set", Format.Bold(ci.ToString()), Format.Bold(ci.NativeName)).ConfigureAwait(false);
}
catch(Exception)
{
await ReplyErrorLocalized("lang_set_fail").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task LanguageSetDefault()
{
var cul = NadekoBot.Localization.DefaultCultureInfo;
await ReplyConfirmLocalized("lang_set_bot_show", cul, cul.NativeName).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task LanguageSetDefault(string name)
{
try
{
CultureInfo ci;
if (name.Trim().ToLowerInvariant() == "default")
{
NadekoBot.Localization.ResetDefaultCulture();
ci = NadekoBot.Localization.DefaultCultureInfo;
}
else
{
ci = new CultureInfo(name);
NadekoBot.Localization.SetDefaultCulture(ci);
}
await ReplyConfirmLocalized("lang_set_bot", Format.Bold(ci.ToString()), Format.Bold(ci.NativeName)).ConfigureAwait(false);
}
catch (Exception)
{
await ReplyErrorLocalized("lang_set_fail").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task LanguagesList()
{
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("lang_list"))
.WithDescription(string.Join("\n",
supportedLocales.Select(x => $"{Format.Code(x.Key), -10} => {x.Value}"))));
}
}
}
}
/* list of language codes for reference.
* taken from https://github.com/dotnet/coreclr/blob/ee5862c6a257e60e263537d975ab6c513179d47f/src/mscorlib/src/System/Globalization/CultureData.cs#L192
{ "029", "en-029" },
{ "AE", "ar-AE" },
{ "AF", "prs-AF" },
{ "AL", "sq-AL" },
{ "AM", "hy-AM" },
{ "AR", "es-AR" },
{ "AT", "de-AT" },
{ "AU", "en-AU" },
{ "AZ", "az-Cyrl-AZ" },
{ "BA", "bs-Latn-BA" },
{ "BD", "bn-BD" },
{ "BE", "nl-BE" },
{ "BG", "bg-BG" },
{ "BH", "ar-BH" },
{ "BN", "ms-BN" },
{ "BO", "es-BO" },
{ "BR", "pt-BR" },
{ "BY", "be-BY" },
{ "BZ", "en-BZ" },
{ "CA", "en-CA" },
{ "CH", "it-CH" },
{ "CL", "es-CL" },
{ "CN", "zh-CN" },
{ "CO", "es-CO" },
{ "CR", "es-CR" },
{ "CS", "sr-Cyrl-CS" },
{ "CZ", "cs-CZ" },
{ "DE", "de-DE" },
{ "DK", "da-DK" },
{ "DO", "es-DO" },
{ "DZ", "ar-DZ" },
{ "EC", "es-EC" },
{ "EE", "et-EE" },
{ "EG", "ar-EG" },
{ "ES", "es-ES" },
{ "ET", "am-ET" },
{ "FI", "fi-FI" },
{ "FO", "fo-FO" },
{ "FR", "fr-FR" },
{ "GB", "en-GB" },
{ "GE", "ka-GE" },
{ "GL", "kl-GL" },
{ "GR", "el-GR" },
{ "GT", "es-GT" },
{ "HK", "zh-HK" },
{ "HN", "es-HN" },
{ "HR", "hr-HR" },
{ "HU", "hu-HU" },
{ "ID", "id-ID" },
{ "IE", "en-IE" },
{ "IL", "he-IL" },
{ "IN", "hi-IN" },
{ "IQ", "ar-IQ" },
{ "IR", "fa-IR" },
{ "IS", "is-IS" },
{ "IT", "it-IT" },
{ "IV", "" },
{ "JM", "en-JM" },
{ "JO", "ar-JO" },
{ "JP", "ja-JP" },
{ "KE", "sw-KE" },
{ "KG", "ky-KG" },
{ "KH", "km-KH" },
{ "KR", "ko-KR" },
{ "KW", "ar-KW" },
{ "KZ", "kk-KZ" },
{ "LA", "lo-LA" },
{ "LB", "ar-LB" },
{ "LI", "de-LI" },
{ "LK", "si-LK" },
{ "LT", "lt-LT" },
{ "LU", "lb-LU" },
{ "LV", "lv-LV" },
{ "LY", "ar-LY" },
{ "MA", "ar-MA" },
{ "MC", "fr-MC" },
{ "ME", "sr-Latn-ME" },
{ "MK", "mk-MK" },
{ "MN", "mn-MN" },
{ "MO", "zh-MO" },
{ "MT", "mt-MT" },
{ "MV", "dv-MV" },
{ "MX", "es-MX" },
{ "MY", "ms-MY" },
{ "NG", "ig-NG" },
{ "NI", "es-NI" },
{ "NL", "nl-NL" },
{ "NO", "nn-NO" },
{ "NP", "ne-NP" },
{ "NZ", "en-NZ" },
{ "OM", "ar-OM" },
{ "PA", "es-PA" },
{ "PE", "es-PE" },
{ "PH", "en-PH" },
{ "PK", "ur-PK" },
{ "PL", "pl-PL" },
{ "PR", "es-PR" },
{ "PT", "pt-PT" },
{ "PY", "es-PY" },
{ "QA", "ar-QA" },
{ "RO", "ro-RO" },
{ "RS", "sr-Latn-RS" },
{ "RU", "ru-RU" },
{ "RW", "rw-RW" },
{ "SA", "ar-SA" },
{ "SE", "sv-SE" },
{ "SG", "zh-SG" },
{ "SI", "sl-SI" },
{ "SK", "sk-SK" },
{ "SN", "wo-SN" },
{ "SV", "es-SV" },
{ "SY", "ar-SY" },
{ "TH", "th-TH" },
{ "TJ", "tg-Cyrl-TJ" },
{ "TM", "tk-TM" },
{ "TN", "ar-TN" },
{ "TR", "tr-TR" },
{ "TT", "en-TT" },
{ "TW", "zh-TW" },
{ "UA", "uk-UA" },
{ "US", "en-US" },
{ "UY", "es-UY" },
{ "UZ", "uz-Cyrl-UZ" },
{ "VE", "es-VE" },
{ "VN", "vi-VN" },
{ "YE", "ar-YE" },
{ "ZA", "af-ZA" },
{ "ZW", "en-ZW" }
*/

View File

@ -1,7 +1,6 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket; using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Permissions; using NadekoBot.Modules.Permissions;
@ -21,45 +20,43 @@ namespace NadekoBot.Modules.Administration
public partial class Administration public partial class Administration
{ {
[Group] [Group]
public class LogCommands : ModuleBase public class LogCommands : NadekoSubmodule
{ {
private const string clockEmojiUrl = "https://cdn.discordapp.com/attachments/155726317222887425/258309524966866945/clock.png"; private static DiscordShardedClient client { get; }
private new static Logger _log { get; }
private static ShardedDiscordClient _client { 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}"; 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>> PresenceUpdates { 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 readonly Timer _timerReference;
private IGoogleApiService _google { get; }
static LogCommands() static LogCommands()
{ {
_client = NadekoBot.Client; client = NadekoBot.Client;
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
using (var uow = DbHandler.UnitOfWork())
{
GuildLogSettings = new ConcurrentDictionary<ulong, LogSetting>(NadekoBot.AllGuildConfigs GuildLogSettings = new ConcurrentDictionary<ulong, LogSetting>(NadekoBot.AllGuildConfigs
.ToDictionary(g => g.GuildId, g => g.LogSetting)); .ToDictionary(g => g.GuildId, g => g.LogSetting));
}
timerReference = new Timer(async (state) => _timerReference = new Timer(async (state) =>
{ {
try try
{ {
var keys = PresenceUpdates.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 (PresenceUpdates.TryRemove(key, out messages)) if (presenceUpdates.TryRemove(key, out messages))
try { await key.SendConfirmAsync("Presence Updates", string.Join(Environment.NewLine, messages)); } catch { } try { await key.SendConfirmAsync(key.Guild.GetLogText("presence_updates"), string.Join(Environment.NewLine, messages)); }
catch
{
// ignored
}
})); }));
} }
catch (Exception ex) catch (Exception ex)
@ -72,29 +69,28 @@ namespace NadekoBot.Modules.Administration
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
//_client.MessageReceived += _client_MessageReceived; //_client.MessageReceived += _client_MessageReceived;
_client.MessageUpdated += _client_MessageUpdated; client.MessageUpdated += _client_MessageUpdated;
_client.MessageDeleted += _client_MessageDeleted; client.MessageDeleted += _client_MessageDeleted;
_client.UserBanned += _client_UserBanned; client.UserBanned += _client_UserBanned;
_client.UserUnbanned += _client_UserUnbanned; client.UserUnbanned += _client_UserUnbanned;
_client.UserJoined += _client_UserJoined; client.UserJoined += _client_UserJoined;
_client.UserLeft += _client_UserLeft; client.UserLeft += _client_UserLeft;
_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; client.GuildMemberUpdated += _client_GuildUserUpdated;
#if !GLOBAL_NADEKO #if !GLOBAL_NADEKO
_client.UserUpdated += _client_UserUpdated; client.UserUpdated += _client_UserUpdated;
#endif #endif
client.ChannelCreated += _client_ChannelCreated;
_client.ChannelCreated += _client_ChannelCreated; client.ChannelDestroyed += _client_ChannelDestroyed;
_client.ChannelDestroyed += _client_ChannelDestroyed; client.ChannelUpdated += _client_ChannelUpdated;
_client.ChannelUpdated += _client_ChannelUpdated;
MuteCommands.UserMuted += MuteCommands_UserMuted; MuteCommands.UserMuted += MuteCommands_UserMuted;
MuteCommands.UserUnmuted += MuteCommands_UserUnmuted; MuteCommands.UserUnmuted += MuteCommands_UserUnmuted;
} }
private static async void _client_UserUpdated(SocketUser before, SocketUser uAfter) private static async Task _client_UserUpdated(SocketUser before, SocketUser uAfter)
{ {
try try
{ {
@ -119,7 +115,7 @@ namespace NadekoBot.Modules.Administration
if (before.Username != after.Username) if (before.Username != after.Username)
{ {
embed.WithTitle("👥 Username Changed") embed.WithTitle("👥 " + g.GetLogText("username_changed"))
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}") .WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.AddField(fb => fb.WithName("Old Name").WithValue($"{before.Username}").WithIsInline(true)) .AddField(fb => fb.WithName("Old Name").WithValue($"{before.Username}").WithIsInline(true))
.AddField(fb => fb.WithName("New Name").WithValue($"{after.Username}").WithIsInline(true)) .AddField(fb => fb.WithName("New Name").WithValue($"{after.Username}").WithIsInline(true))
@ -128,9 +124,8 @@ namespace NadekoBot.Modules.Administration
} }
else if (before.AvatarUrl != after.AvatarUrl) else if (before.AvatarUrl != after.AvatarUrl)
{ {
embed.WithTitle("👥 Avatar Changed") embed.WithTitle("👥" + g.GetLogText("avatar_changed"))
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}") .WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithThumbnailUrl(before.AvatarUrl) .WithThumbnailUrl(before.AvatarUrl)
.WithImageUrl(after.AvatarUrl) .WithImageUrl(after.AvatarUrl)
.WithFooter(fb => fb.WithText(currentTime)) .WithFooter(fb => fb.WithText(currentTime))
@ -159,10 +154,12 @@ namespace NadekoBot.Modules.Administration
//} //}
} }
catch catch
{ } {
// ignored
}
} }
private static async void _client_UserVoiceStateUpdated_TTS(SocketUser iusr, SocketVoiceState before, SocketVoiceState after) private static async Task _client_UserVoiceStateUpdated_TTS(SocketUser iusr, SocketVoiceState before, SocketVoiceState after)
{ {
try try
{ {
@ -188,20 +185,23 @@ namespace NadekoBot.Modules.Administration
var str = ""; var str = "";
if (beforeVch?.Guild == afterVch?.Guild) if (beforeVch?.Guild == afterVch?.Guild)
{ {
str = $"{usr.Username} moved from {beforeVch.Name} to {afterVch.Name}"; str = logChannel.Guild.GetLogText("moved", usr.Username, beforeVch?.Name, afterVch?.Name);
} }
else if (beforeVch == null) else if (beforeVch == null)
{ {
str = $"{usr.Username} has joined {afterVch.Name}"; str = logChannel.Guild.GetLogText("joined", usr.Username, afterVch.Name);
} }
else if (afterVch == null) else if (afterVch == null)
{ {
str = $"{usr.Username} has left {beforeVch.Name}"; str = logChannel.Guild.GetLogText("left", usr.Username, beforeVch.Name);
} }
var toDelete = await logChannel.SendMessageAsync(str, true).ConfigureAwait(false); var toDelete = await logChannel.SendMessageAsync(str, true).ConfigureAwait(false);
toDelete.DeleteAfter(5); toDelete.DeleteAfter(5);
} }
catch { } catch
{
// ignored
}
} }
private static async void MuteCommands_UserMuted(IGuildUser usr, MuteCommands.MuteType muteType) private static async void MuteCommands_UserMuted(IGuildUser usr, MuteCommands.MuteType muteType)
@ -216,28 +216,32 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel; ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserMuted)) == null) if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserMuted)) == null)
return; return;
string mutes = ""; var mutes = "";
var mutedLocalized = logChannel.Guild.GetLogText("muted_sn");
switch (muteType) switch (muteType)
{ {
case MuteCommands.MuteType.Voice: case MuteCommands.MuteType.Voice:
mutes = "voice chat"; mutes = "🔇 " + logChannel.Guild.GetLogText("xmuted_voice", mutedLocalized);
break; break;
case MuteCommands.MuteType.Chat: case MuteCommands.MuteType.Chat:
mutes = "text chat"; mutes = "🔇 " + logChannel.Guild.GetLogText("xmuted_text", mutedLocalized);
break; break;
case MuteCommands.MuteType.All: case MuteCommands.MuteType.All:
mutes = "text and voice chat"; mutes = "🔇 " + logChannel.Guild.GetLogText("xmuted_text_and_voice", mutedLocalized);
break; break;
} }
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName("🔇 User Muted from " + mutes)) var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName(mutes))
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}") .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
.WithFooter(fb => fb.WithText(currentTime)) .WithFooter(fb => fb.WithText(currentTime))
.WithOkColor(); .WithOkColor();
await logChannel.EmbedAsync(embed).ConfigureAwait(false); await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch { } catch
{
// ignored
}
} }
private static async void MuteCommands_UserUnmuted(IGuildUser usr, MuteCommands.MuteType muteType) private static async void MuteCommands_UserUnmuted(IGuildUser usr, MuteCommands.MuteType muteType)
@ -253,28 +257,32 @@ namespace NadekoBot.Modules.Administration
if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserMuted)) == null) if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserMuted)) == null)
return; return;
string mutes = ""; var mutes = "";
var unmutedLocalized = logChannel.Guild.GetLogText("unmuted_sn");
switch (muteType) switch (muteType)
{ {
case MuteCommands.MuteType.Voice: case MuteCommands.MuteType.Voice:
mutes = "voice chat"; mutes = "🔊 " + logChannel.Guild.GetLogText("xmuted_voice", unmutedLocalized);
break; break;
case MuteCommands.MuteType.Chat: case MuteCommands.MuteType.Chat:
mutes = "text chat"; mutes = "🔊 " + logChannel.Guild.GetLogText("xmuted_text", unmutedLocalized);
break; break;
case MuteCommands.MuteType.All: case MuteCommands.MuteType.All:
mutes = "text and voice chat"; mutes = "🔊 " + logChannel.Guild.GetLogText("xmuted_text_and_voice", unmutedLocalized);
break; break;
} }
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName("🔊 User Unmuted from " + mutes)) var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName(mutes))
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}") .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
.WithFooter(fb => fb.WithText($"{currentTime}")) .WithFooter(fb => fb.WithText($"{currentTime}"))
.WithOkColor(); .WithOkColor();
await logChannel.EmbedAsync(embed).ConfigureAwait(false); await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch { } catch
{
// ignored
}
} }
public static async Task TriggeredAntiProtection(IGuildUser[] users, PunishmentAction action, ProtectionType protection) public static async Task TriggeredAntiProtection(IGuildUser[] users, PunishmentAction action, ProtectionType protection)
@ -293,31 +301,37 @@ namespace NadekoBot.Modules.Administration
return; return;
var punishment = ""; var punishment = "";
if (action == PunishmentAction.Mute) switch (action)
{ {
punishment = "🔇 MUTED"; case PunishmentAction.Mute:
} punishment = "🔇 " + logChannel.Guild.GetLogText("muted_pl").ToUpperInvariant();
else if (action == PunishmentAction.Kick) break;
{ case PunishmentAction.Kick:
punishment = "☣ SOFT-BANNED (KICKED)"; punishment = "👢 " + logChannel.Guild.GetLogText("kicked_pl").ToUpperInvariant();
} break;
else if (action == PunishmentAction.Ban) case PunishmentAction.Softban:
{ punishment = "☣ " + logChannel.Guild.GetLogText("soft_banned_pl").ToUpperInvariant();
punishment = "⛔️ BANNED"; break;
case PunishmentAction.Ban:
punishment = "⛔️ " + logChannel.Guild.GetLogText("banned_pl").ToUpperInvariant();
break;
} }
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName($"🛡 Anti-{protection}")) var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName($"🛡 Anti-{protection}"))
.WithTitle($"Users " + punishment) .WithTitle(logChannel.Guild.GetLogText("users") + " " + punishment)
.WithDescription(String.Join("\n", users.Select(u => u.ToString()))) .WithDescription(string.Join("\n", users.Select(u => u.ToString())))
.WithFooter(fb => fb.WithText($"{currentTime}")) .WithFooter(fb => fb.WithText($"{currentTime}"))
.WithOkColor(); .WithOkColor();
await logChannel.EmbedAsync(embed).ConfigureAwait(false); await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch { } catch
{
// ignored
}
} }
private static async void _client_GuildUserUpdated(SocketGuildUser before, SocketGuildUser after) private static async Task _client_GuildUserUpdated(SocketGuildUser before, SocketGuildUser after)
{ {
try try
{ {
@ -333,23 +347,23 @@ namespace NadekoBot.Modules.Administration
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}"); .WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}");
if (before.Nickname != after.Nickname) if (before.Nickname != after.Nickname)
{ {
embed.WithAuthor(eab => eab.WithName("👥 Nickname Changed")) embed.WithAuthor(eab => eab.WithName("👥 " + logChannel.Guild.GetLogText("nick_change")))
.AddField(efb => efb.WithName("Old Nickname").WithValue($"{before.Nickname}#{before.Discriminator}")) .AddField(efb => efb.WithName(logChannel.Guild.GetLogText("old_nick")).WithValue($"{before.Nickname}#{before.Discriminator}"))
.AddField(efb => efb.WithName("New Nickname").WithValue($"{after.Nickname}#{after.Discriminator}")); .AddField(efb => efb.WithName(logChannel.Guild.GetLogText("new_nick")).WithValue($"{after.Nickname}#{after.Discriminator}"));
} }
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);
embed.WithAuthor(eab => eab.WithName("⚔ User's Role Added")) embed.WithAuthor(eab => eab.WithName("⚔ " + logChannel.Guild.GetLogText("user_role_add")))
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); .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);
embed.WithAuthor(eab => eab.WithName("⚔ User's Role Removed")) embed.WithAuthor(eab => eab.WithName("⚔ " + logChannel.Guild.GetLogText("user_role_rem")))
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); .WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
} }
} }
@ -357,10 +371,13 @@ namespace NadekoBot.Modules.Administration
return; return;
await logChannel.EmbedAsync(embed).ConfigureAwait(false); await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch { } catch
{
// ignored
}
} }
private static async void _client_ChannelUpdated(IChannel cbefore, IChannel cafter) private static async Task _client_ChannelUpdated(IChannel cbefore, IChannel cafter)
{ {
try try
{ {
@ -386,24 +403,29 @@ namespace NadekoBot.Modules.Administration
if (before.Name != after.Name) if (before.Name != after.Name)
{ {
embed.WithTitle(" Channel Name Changed") embed.WithTitle(" " + logChannel.Guild.GetLogText("ch_name_change"))
.WithDescription($"{after} | {after.Id}") .WithDescription($"{after} | {after.Id}")
.AddField(efb => efb.WithName("Old Name").WithValue(before.Name)); .AddField(efb => efb.WithName(logChannel.Guild.GetLogText("ch_old_name")).WithValue(before.Name));
} }
else if (beforeTextChannel?.Topic != afterTextChannel?.Topic) else if (beforeTextChannel?.Topic != afterTextChannel?.Topic)
{ {
embed.WithTitle(" Channel Topic Changed") embed.WithTitle(" " + logChannel.Guild.GetLogText("ch_topic_change"))
.WithDescription($"{after} | {after.Id}") .WithDescription($"{after} | {after.Id}")
.AddField(efb => efb.WithName("Old Topic").WithValue(beforeTextChannel.Topic)) .AddField(efb => efb.WithName(logChannel.Guild.GetLogText("old_topic")).WithValue(beforeTextChannel?.Topic ?? "-"))
.AddField(efb => efb.WithName("New Topic").WithValue(afterTextChannel.Topic)); .AddField(efb => efb.WithName(logChannel.Guild.GetLogText("new_topic")).WithValue(afterTextChannel?.Topic ?? "-"));
} }
else
return;
await logChannel.EmbedAsync(embed).ConfigureAwait(false); await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch { } catch
{
// ignored
}
} }
private static async void _client_ChannelDestroyed(IChannel ich) private static async Task _client_ChannelDestroyed(IChannel ich)
{ {
try try
{ {
@ -420,17 +442,26 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel; ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ChannelDestroyed)) == null) if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ChannelDestroyed)) == null)
return; return;
string title;
if (ch is IVoiceChannel)
{
title = logChannel.Guild.GetLogText("voice_chan_destroyed");
}
else
title = logChannel.Guild.GetLogText("text_chan_destroyed");
await logChannel.EmbedAsync(new EmbedBuilder() await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithTitle("🆕 " + (ch is IVoiceChannel ? "Voice" : "Text") + " Channel Destroyed") .WithTitle("🆕 " + title)
.WithDescription($"{ch.Name} | {ch.Id}") .WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false); .WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false);
} }
catch { } catch
{
// ignored
}
} }
private static async void _client_ChannelCreated(IChannel ich) private static async Task _client_ChannelCreated(IChannel ich)
{ {
try try
{ {
@ -446,17 +477,23 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel; ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ChannelCreated)) == null) if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ChannelCreated)) == null)
return; return;
string title;
if (ch is IVoiceChannel)
{
title = logChannel.Guild.GetLogText("voice_chan_created");
}
else
title = logChannel.Guild.GetLogText("text_chan_created");
await logChannel.EmbedAsync(new EmbedBuilder() await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithTitle("🆕 " + (ch is IVoiceChannel ? "Voice" : "Text") + " Channel Created") .WithTitle("🆕 " + title)
.WithDescription($"{ch.Name} | {ch.Id}") .WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false); .WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch (Exception ex) { _log.Warn(ex); }
} }
private static async void _client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState before, SocketVoiceState after) private static async Task _client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState before, SocketVoiceState after)
{ {
try try
{ {
@ -482,23 +519,32 @@ namespace NadekoBot.Modules.Administration
string str = null; string str = null;
if (beforeVch?.Guild == afterVch?.Guild) if (beforeVch?.Guild == afterVch?.Guild)
{ {
str = $"🎙`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__ moved from **{beforeVch.Name}** to **{afterVch.Name}** voice channel."; str = "🎙" + Format.Code(prettyCurrentTime) + logChannel.Guild.GetLogText("user_vmoved",
"👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
Format.Bold(beforeVch?.Name ?? ""), Format.Bold(afterVch?.Name ?? ""));
} }
else if (beforeVch == null) else if (beforeVch == null)
{ {
str = $"🎙`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__ has joined **{afterVch.Name}** voice channel."; str = "🎙" + Format.Code(prettyCurrentTime) + logChannel.Guild.GetLogText("user_vjoined",
"👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
Format.Bold(afterVch.Name ?? ""));
} }
else if (afterVch == null) else if (afterVch == null)
{ {
str = $"🎙`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__ has left **{beforeVch.Name}** voice channel."; str = "🎙" + Format.Code(prettyCurrentTime) + logChannel.Guild.GetLogText("user_vleft",
"👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
Format.Bold(beforeVch.Name ?? ""));
} }
if (str != null) if (str != null)
PresenceUpdates.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
{
// ignored
} }
catch { }
} }
private static async void _client_UserPresenceUpdated(Optional<SocketGuild> optGuild, SocketUser usr, SocketPresence before, SocketPresence after) private static async Task _client_UserPresenceUpdated(Optional<SocketGuild> optGuild, SocketUser usr, SocketPresence before, SocketPresence after)
{ {
try try
{ {
@ -518,7 +564,10 @@ namespace NadekoBot.Modules.Administration
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 = "🎭" + Format.Code(prettyCurrentTime) +
logChannel.Guild.GetLogText("user_status_change",
"👤" + Format.Bold(usr.Username),
Format.Bold(after.Status.ToString()));
//if (before.Game?.Name != after.Game?.Name) //if (before.Game?.Name != after.Game?.Name)
//{ //{
@ -527,12 +576,15 @@ namespace NadekoBot.Modules.Administration
// str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**."; // str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**.";
//} //}
PresenceUpdates.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
{
// ignored
} }
catch { }
} }
private static async void _client_UserLeft(IGuildUser usr) private static async Task _client_UserLeft(IGuildUser usr)
{ {
try try
{ {
@ -547,16 +599,19 @@ namespace NadekoBot.Modules.Administration
await logChannel.EmbedAsync(new EmbedBuilder() await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithTitle("❌ User Left") .WithTitle("❌ " + logChannel.Guild.GetLogText("user_left"))
.WithThumbnailUrl(usr.AvatarUrl) .WithThumbnailUrl(usr.AvatarUrl)
.WithDescription(usr.ToString()) .WithDescription(usr.ToString())
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString())) .AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
.WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false); .WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false);
} }
catch { } catch
{
// ignored
}
} }
private static async void _client_UserJoined(IGuildUser usr) private static async Task _client_UserJoined(IGuildUser usr)
{ {
try try
{ {
@ -571,7 +626,7 @@ namespace NadekoBot.Modules.Administration
await logChannel.EmbedAsync(new EmbedBuilder() await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithTitle("✅ User Joined") .WithTitle("✅ " + logChannel.Guild.GetLogText("user_joined"))
.WithThumbnailUrl(usr.AvatarUrl) .WithThumbnailUrl(usr.AvatarUrl)
.WithDescription($"{usr}") .WithDescription($"{usr}")
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString())) .AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
@ -580,7 +635,7 @@ namespace NadekoBot.Modules.Administration
catch (Exception ex) { _log.Warn(ex); } catch (Exception ex) { _log.Warn(ex); }
} }
private static async void _client_UserUnbanned(IUser usr, IGuild guild) private static async Task _client_UserUnbanned(IUser usr, IGuild guild)
{ {
try try
{ {
@ -595,7 +650,7 @@ namespace NadekoBot.Modules.Administration
await logChannel.EmbedAsync(new EmbedBuilder() await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithTitle("♻️ User Unbanned") .WithTitle("♻️ " + logChannel.Guild.GetLogText("user_unbanned"))
.WithThumbnailUrl(usr.AvatarUrl) .WithThumbnailUrl(usr.AvatarUrl)
.WithDescription(usr.ToString()) .WithDescription(usr.ToString())
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString())) .AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
@ -604,7 +659,7 @@ namespace NadekoBot.Modules.Administration
catch (Exception ex) { _log.Warn(ex); } catch (Exception ex) { _log.Warn(ex); }
} }
private static async void _client_UserBanned(IUser usr, IGuild guild) private static async Task _client_UserBanned(IUser usr, IGuild guild)
{ {
try try
{ {
@ -618,7 +673,7 @@ namespace NadekoBot.Modules.Administration
return; return;
await logChannel.EmbedAsync(new EmbedBuilder() await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithTitle("🚫 User Banned") .WithTitle("🚫 " + logChannel.Guild.GetLogText("user_banned"))
.WithThumbnailUrl(usr.AvatarUrl) .WithThumbnailUrl(usr.AvatarUrl)
.WithDescription(usr.ToString()) .WithDescription(usr.ToString())
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString())) .AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
@ -627,7 +682,7 @@ namespace NadekoBot.Modules.Administration
catch (Exception ex) { _log.Warn(ex); } catch (Exception ex) { _log.Warn(ex); }
} }
private static async void _client_MessageDeleted(ulong arg1, Optional<SocketMessage> imsg) private static async Task _client_MessageDeleted(ulong arg1, Optional<SocketMessage> imsg)
{ {
try try
@ -651,20 +706,24 @@ namespace NadekoBot.Modules.Administration
return; return;
var embed = new EmbedBuilder() var embed = new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithTitle($"🗑 Message Deleted in {((ITextChannel)msg.Channel).Mention}") .WithTitle("🗑 " + logChannel.Guild.GetLogText("msg_del", ((ITextChannel)msg.Channel).Name))
.WithDescription($"{msg.Author}") .WithDescription(msg.Author.ToString())
.AddField(efb => efb.WithName("Content").WithValue(msg.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false)) .AddField(efb => efb.WithName(logChannel.Guild.GetLogText("content")).WithValue(string.IsNullOrWhiteSpace(msg.Content) ? "-" : msg.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName("Id").WithValue(msg.Id.ToString()).WithIsInline(false)) .AddField(efb => efb.WithName("Id").WithValue(msg.Id.ToString()).WithIsInline(false))
.WithFooter(efb => efb.WithText(currentTime)); .WithFooter(efb => efb.WithText(currentTime));
if (msg.Attachments.Any()) if (msg.Attachments.Any())
embed.AddField(efb => efb.WithName("Attachments").WithValue(string.Join(", ", msg.Attachments.Select(a => a.ProxyUrl))).WithIsInline(false)); embed.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("attachments")).WithValue(string.Join(", ", msg.Attachments.Select(a => a.Url))).WithIsInline(false));
await logChannel.EmbedAsync(embed).ConfigureAwait(false); await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch { } catch (Exception ex)
{
_log.Warn(ex);
// ignored
}
} }
private static async void _client_MessageUpdated(Optional<SocketMessage> optmsg, SocketMessage imsg2) private static async Task _client_MessageUpdated(Optional<SocketMessage> optmsg, SocketMessage imsg2)
{ {
try try
{ {
@ -695,16 +754,19 @@ namespace NadekoBot.Modules.Administration
var embed = new EmbedBuilder() var embed = new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithTitle($"📝 Message Updated in {((ITextChannel)after.Channel).Mention}") .WithTitle("📝 " + logChannel.Guild.GetLogText("msg_update", ((ITextChannel)after.Channel).Name))
.WithDescription(after.Author.ToString()) .WithDescription(after.Author.ToString())
.AddField(efb => efb.WithName("Old Message").WithValue(before.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false)) .AddField(efb => efb.WithName(logChannel.Guild.GetLogText("old_msg")).WithValue(string.IsNullOrWhiteSpace(before.Content) ? "-" : 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(logChannel.Guild.GetLogText("new_msg")).WithValue(string.IsNullOrWhiteSpace(after.Content) ? "-" : after.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName("Id").WithValue(after.Id.ToString()).WithIsInline(false)) .AddField(efb => efb.WithName("Id").WithValue(after.Id.ToString()).WithIsInline(false))
.WithFooter(efb => efb.WithText(currentTime)); .WithFooter(efb => efb.WithText(currentTime));
await logChannel.EmbedAsync(embed).ConfigureAwait(false); await logChannel.EmbedAsync(embed).ConfigureAwait(false);
} }
catch { } catch
{
// ignored
}
} }
public enum LogType public enum LogType
@ -776,8 +838,6 @@ namespace NadekoBot.Modules.Administration
case LogType.UserMuted: case LogType.UserMuted:
id = logSetting.UserMutedId; id = logSetting.UserMutedId;
break; break;
default:
break;
} }
if (!id.HasValue) if (!id.HasValue)
@ -848,8 +908,6 @@ namespace NadekoBot.Modules.Administration
case LogType.VoicePresenceTTS: case LogType.VoicePresenceTTS:
newLogSetting.LogVoicePresenceTTSId = null; newLogSetting.LogVoicePresenceTTSId = null;
break; break;
default:
break;
} }
GuildLogSettings.AddOrUpdate(guildId, newLogSetting, (gid, old) => newLogSetting); GuildLogSettings.AddOrUpdate(guildId, newLogSetting, (gid, old) => newLogSetting);
uow.Complete(); uow.Complete();
@ -893,9 +951,9 @@ namespace NadekoBot.Modules.Administration
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
if (action.Value) if (action.Value)
await channel.SendConfirmAsync("Logging all events in this channel.").ConfigureAwait(false); await ReplyConfirmLocalized("log_all").ConfigureAwait(false);
else else
await channel.SendConfirmAsync("Logging disabled.").ConfigureAwait(false); await ReplyConfirmLocalized("log_disabled").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -922,9 +980,9 @@ namespace NadekoBot.Modules.Administration
} }
if (removed == 0) if (removed == 0)
await channel.SendConfirmAsync($"Logging will IGNORE **{channel.Mention} ({channel.Id})**").ConfigureAwait(false); await ReplyConfirmLocalized("log_ignore", Format.Bold(channel.Mention + "(" + channel.Id + ")")).ConfigureAwait(false);
else else
await channel.SendConfirmAsync($"Logging will NOT IGNORE **{channel.Mention} ({channel.Id})**").ConfigureAwait(false); await ReplyConfirmLocalized("log_not_ignore", Format.Bold(channel.Mention + "(" + channel.Id + ")")).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -933,7 +991,9 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task LogEvents() public async Task LogEvents()
{ {
await Context.Channel.SendConfirmAsync("Log events you can subscribe to:", String.Join(", ", Enum.GetNames(typeof(LogType)).Cast<string>())); await Context.Channel.SendConfirmAsync(GetText("log_events") + "\n" +
string.Join(", ", Enum.GetNames(typeof(LogType)).Cast<string>()))
.ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -1001,10 +1061,19 @@ namespace NadekoBot.Modules.Administration
} }
if (channelId != null) if (channelId != null)
await channel.SendConfirmAsync($"Logging **{type}** event in this channel.").ConfigureAwait(false); await ReplyConfirmLocalized("log", Format.Bold(type.ToString())).ConfigureAwait(false);
else else
await channel.SendConfirmAsync($"Stopped logging **{type}** event.").ConfigureAwait(false); await ReplyConfirmLocalized("log_stop", Format.Bold(type.ToString())).ConfigureAwait(false);
} }
} }
} }
public static class GuildExtensions
{
public static string GetLogText(this IGuild guild, string key, params object[] replacements)
=> NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(guild),
typeof(Administration).Name.ToLowerInvariant(),
replacements);
}
} }

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

@ -20,11 +20,11 @@ namespace NadekoBot.Modules.Administration
public partial class Administration public partial class Administration
{ {
[Group] [Group]
public class Migration : ModuleBase public class Migration : NadekoSubmodule
{ {
private const int CURRENT_VERSION = 1; private const int CURRENT_VERSION = 1;
private static Logger _log { get; } private new static readonly Logger _log;
static Migration() static Migration()
{ {
@ -51,12 +51,12 @@ namespace NadekoBot.Modules.Administration
break; break;
} }
} }
await Context.Channel.SendMessageAsync("🆙 **Migration done.**").ConfigureAwait(false); await ReplyConfirmLocalized("migration_done").ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
_log.Error(ex); _log.Error(ex);
await Context.Channel.SendMessageAsync("⚠️ **Error while migrating, check `logs` for more informations.**").ConfigureAwait(false); await ReplyErrorLocalized("migration_error").ConfigureAwait(false);
} }
} }
@ -89,10 +89,12 @@ namespace NadekoBot.Modules.Administration
db.Open(); db.Open();
var com = db.CreateCommand(); var com = db.CreateCommand();
var i = 0;
try
{
com.CommandText = "SELECT * FROM Announcement"; com.CommandText = "SELECT * FROM Announcement";
var reader = com.ExecuteReader(); var reader = com.ExecuteReader();
var i = 0;
while (reader.Read()) while (reader.Read())
{ {
var gid = (ulong)(long)reader["ServerId"]; var gid = (ulong)(long)reader["ServerId"];
@ -101,11 +103,8 @@ namespace NadekoBot.Modules.Administration
var greetChannel = (ulong)(long)reader["GreetChannelId"]; var greetChannel = (ulong)(long)reader["GreetChannelId"];
var greetMsg = (string)reader["GreetText"]; var greetMsg = (string)reader["GreetText"];
var bye = (long)reader["Bye"] == 1; var bye = (long)reader["Bye"] == 1;
var byeDM = (long)reader["ByePM"] == 1;
var byeChannel = (ulong)(long)reader["ByeChannelId"]; var byeChannel = (ulong)(long)reader["ByeChannelId"];
var byeMsg = (string)reader["ByeText"]; var byeMsg = (string)reader["ByeText"];
var grdel = false;
var byedel = grdel;
var gc = uow.GuildConfigs.For(gid, set => set); var gc = uow.GuildConfigs.For(gid, set => set);
if (greetDM) if (greetDM)
@ -119,15 +118,19 @@ namespace NadekoBot.Modules.Administration
gc.ByeMessageChannelId = byeChannel; gc.ByeMessageChannelId = byeChannel;
gc.ChannelByeMessageText = byeMsg; gc.ChannelByeMessageText = byeMsg;
gc.AutoDeleteGreetMessagesTimer = gc.AutoDeleteByeMessagesTimer = grdel ? 30 : 0;
_log.Info(++i); _log.Info(++i);
} }
}
catch {
_log.Warn("Greet/bye messages won't be migrated");
}
var com2 = db.CreateCommand(); var com2 = db.CreateCommand();
com.CommandText = "SELECT * FROM CurrencyState GROUP BY UserId"; com2.CommandText = "SELECT * FROM CurrencyState GROUP BY UserId";
i = 0; i = 0;
var reader2 = com.ExecuteReader(); try
{
var reader2 = com2.ExecuteReader();
while (reader2.Read()) while (reader2.Read())
{ {
_log.Info(++i); _log.Info(++i);
@ -138,6 +141,11 @@ namespace NadekoBot.Modules.Administration
}; };
uow.Currency.Add(curr); uow.Currency.Add(curr);
} }
}
catch
{
_log.Warn("Currency won't be migrated");
}
db.Close(); db.Close();
try { File.Move("data/nadekobot.sqlite", "data/DELETE_ME_nadekobot.sqlite"); } catch { } try { File.Move("data/nadekobot.sqlite", "data/DELETE_ME_nadekobot.sqlite"); } catch { }
} }
@ -191,7 +199,6 @@ namespace NadekoBot.Modules.Administration
guildConfig.ExclusiveSelfAssignedRoles = data.ExclusiveSelfAssignedRoles; guildConfig.ExclusiveSelfAssignedRoles = data.ExclusiveSelfAssignedRoles;
guildConfig.GenerateCurrencyChannelIds = new HashSet<GCChannelId>(data.GenerateCurrencyChannels.Select(gc => new GCChannelId() { ChannelId = gc.Key })); guildConfig.GenerateCurrencyChannelIds = new HashSet<GCChannelId>(data.GenerateCurrencyChannels.Select(gc => new GCChannelId() { ChannelId = gc.Key }));
selfAssRoles.AddRange(data.ListOfSelfAssignableRoles.Select(r => new SelfAssignedRole() { GuildId = guildConfig.GuildId, RoleId = r }).ToArray()); selfAssRoles.AddRange(data.ListOfSelfAssignableRoles.Select(r => new SelfAssignedRole() { GuildId = guildConfig.GuildId, RoleId = r }).ToArray());
var logSetting = guildConfig.LogSetting;
guildConfig.LogSetting.IgnoredChannels = new HashSet<IgnoredLogChannel>(data.LogserverIgnoreChannels.Select(id => new IgnoredLogChannel() { ChannelId = id })); guildConfig.LogSetting.IgnoredChannels = new HashSet<IgnoredLogChannel>(data.LogserverIgnoreChannels.Select(id => new IgnoredLogChannel() { ChannelId = id }));
guildConfig.LogSetting.LogUserPresenceId = data.LogPresenceChannel; guildConfig.LogSetting.LogUserPresenceId = data.LogPresenceChannel;
@ -237,7 +244,7 @@ namespace NadekoBot.Modules.Administration
private void MigratePermissions0_9(IUnitOfWork uow) private void MigratePermissions0_9(IUnitOfWork uow)
{ {
var PermissionsDict = new ConcurrentDictionary<ulong, ServerPermissions0_9>(); var permissionsDict = new ConcurrentDictionary<ulong, ServerPermissions0_9>();
if (!Directory.Exists("data/permissions/")) if (!Directory.Exists("data/permissions/"))
{ {
_log.Warn("No data from permissions will be migrated."); _log.Warn("No data from permissions will be migrated.");
@ -251,12 +258,15 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(strippedFileName)) continue; if (string.IsNullOrWhiteSpace(strippedFileName)) continue;
var id = ulong.Parse(strippedFileName); var id = ulong.Parse(strippedFileName);
var data = JsonConvert.DeserializeObject<ServerPermissions0_9>(File.ReadAllText(file)); var data = JsonConvert.DeserializeObject<ServerPermissions0_9>(File.ReadAllText(file));
PermissionsDict.TryAdd(id, data); permissionsDict.TryAdd(id, data);
}
catch
{
// ignored
} }
catch { }
} }
var i = 0; var i = 0;
PermissionsDict permissionsDict
.Select(p => new { data = p.Value, gconfig = uow.GuildConfigs.For(p.Key) }) .Select(p => new { data = p.Value, gconfig = uow.GuildConfigs.For(p.Key) })
.AsParallel() .AsParallel()
.ForAll(perms => .ForAll(perms =>

View File

@ -1,16 +1,15 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NLog; using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
@ -18,11 +17,12 @@ namespace NadekoBot.Modules.Administration
public partial class Administration public partial class Administration
{ {
[Group] [Group]
public class MuteCommands : ModuleBase public class MuteCommands : NadekoSubmodule
{ {
private static ConcurrentDictionary<ulong, string> GuildMuteRoles { get; } = new ConcurrentDictionary<ulong, string>(); private static ConcurrentDictionary<ulong, string> guildMuteRoles { get; }
private static ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> mutedUsers { get; }
private static ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; } = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(); private static ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>> unmuteTimers { get; }
= new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>>();
public static event Action<IGuildUser, MuteType> UserMuted = delegate { }; public static event Action<IGuildUser, MuteType> UserMuted = delegate { };
public static event Action<IGuildUser, MuteType> UserUnmuted = delegate { }; public static event Action<IGuildUser, MuteType> UserUnmuted = delegate { };
@ -33,43 +33,56 @@ namespace NadekoBot.Modules.Administration
Chat, Chat,
All All
} }
private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny);
private static readonly new Logger _log = LogManager.GetCurrentClassLogger();
static MuteCommands() static MuteCommands()
{ {
var _log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
var configs = NadekoBot.AllGuildConfigs; var configs = NadekoBot.AllGuildConfigs;
GuildMuteRoles = new ConcurrentDictionary<ulong, string>(configs guildMuteRoles = new ConcurrentDictionary<ulong, string>(configs
.Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName)) .Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName))
.ToDictionary(c => c.GuildId, c => c.MuteRoleName)); .ToDictionary(c => c.GuildId, c => c.MuteRoleName));
MutedUsers = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(configs.ToDictionary( mutedUsers = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(configs.ToDictionary(
k => k.GuildId, k => k.GuildId,
v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId)) v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))
)); ));
NadekoBot.Client.UserJoined += Client_UserJoined; foreach (var conf in configs)
{
sw.Stop(); foreach (var x in conf.UnmuteTimers)
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); {
TimeSpan after;
if (x.UnmuteAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
{
after = TimeSpan.FromMinutes(2);
}
else
{
after = x.UnmuteAt - DateTime.UtcNow;
}
StartUnmuteTimer(conf.GuildId, x.UserId, after);
}
} }
private static async void Client_UserJoined(IGuildUser usr) NadekoBot.Client.UserJoined += Client_UserJoined;
}
private static async Task Client_UserJoined(IGuildUser usr)
{ {
try try
{ {
ConcurrentHashSet<ulong> muted; ConcurrentHashSet<ulong> muted;
MutedUsers.TryGetValue(usr.Guild.Id, out muted); mutedUsers.TryGetValue(usr.Guild.Id, out muted);
if (muted == null || !muted.Contains(usr.Id)) if (muted == null || !muted.Contains(usr.Id))
return; return;
else
await MuteUser(usr).ConfigureAwait(false); await MuteUser(usr).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
_log.Warn(ex); LogManager.GetCurrentClassLogger().Warn(ex);
} }
} }
@ -77,18 +90,25 @@ namespace NadekoBot.Modules.Administration
public static async Task MuteUser(IGuildUser usr) public static async Task MuteUser(IGuildUser usr)
{ {
await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false); await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false);
await usr.AddRolesAsync(await GetMuteRole(usr.Guild)).ConfigureAwait(false); var muteRole = await GetMuteRole(usr.Guild);
if (!usr.RoleIds.Contains(muteRole.Id))
await usr.AddRolesAsync(muteRole).ConfigureAwait(false);
StopUnmuteTimer(usr.GuildId, usr.Id);
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var config = uow.GuildConfigs.For(usr.Guild.Id, set => set.Include(gc => gc.MutedUsers)); var config = uow.GuildConfigs.For(usr.Guild.Id,
set => set.Include(gc => gc.MutedUsers)
.Include(gc => gc.UnmuteTimers));
config.MutedUsers.Add(new MutedUserId() config.MutedUsers.Add(new MutedUserId()
{ {
UserId = usr.Id UserId = usr.Id
}); });
ConcurrentHashSet<ulong> muted; ConcurrentHashSet<ulong> muted;
if (MutedUsers.TryGetValue(usr.Guild.Id, out muted)) if (mutedUsers.TryGetValue(usr.Guild.Id, out muted))
muted.Add(usr.Id); muted.Add(usr.Id);
config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
UserMuted(usr, MuteType.All); UserMuted(usr, MuteType.All);
@ -96,28 +116,33 @@ namespace NadekoBot.Modules.Administration
public static async Task UnmuteUser(IGuildUser usr) public static async Task UnmuteUser(IGuildUser usr)
{ {
await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false); StopUnmuteTimer(usr.GuildId, usr.Id);
await usr.RemoveRolesAsync(await GetMuteRole(usr.Guild)).ConfigureAwait(false); try { await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false); } catch { }
try { await usr.RemoveRolesAsync(await GetMuteRole(usr.Guild)).ConfigureAwait(false); } catch { /*ignore*/ }
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var config = uow.GuildConfigs.For(usr.Guild.Id, set => set.Include(gc => gc.MutedUsers)); var config = uow.GuildConfigs.For(usr.Guild.Id, set => set.Include(gc => gc.MutedUsers)
.Include(gc => gc.UnmuteTimers));
config.MutedUsers.Remove(new MutedUserId() config.MutedUsers.Remove(new MutedUserId()
{ {
UserId = usr.Id UserId = usr.Id
}); });
ConcurrentHashSet<ulong> muted; ConcurrentHashSet<ulong> muted;
if (MutedUsers.TryGetValue(usr.Guild.Id, out muted)) if (mutedUsers.TryGetValue(usr.Guild.Id, out muted))
muted.TryRemove(usr.Id); muted.TryRemove(usr.Id);
config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
UserUnmuted(usr, MuteType.All); UserUnmuted(usr, MuteType.All);
} }
public static async Task<IRole> GetMuteRole(IGuild guild) public static async Task<IRole>GetMuteRole(IGuild guild)
{ {
const string defaultMuteRoleName = "nadeko-mute"; const string defaultMuteRoleName = "nadeko-mute";
var muteRoleName = GuildMuteRoles.GetOrAdd(guild.Id, defaultMuteRoleName); var muteRoleName = guildMuteRoles.GetOrAdd(guild.Id, defaultMuteRoleName);
var muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName); var muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName);
if (muteRole == null) if (muteRole == null)
@ -131,28 +156,108 @@ namespace NadekoBot.Modules.Administration
muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) ?? muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) ??
await guild.CreateRoleAsync(defaultMuteRoleName, GuildPermissions.None).ConfigureAwait(false); await guild.CreateRoleAsync(defaultMuteRoleName, GuildPermissions.None).ConfigureAwait(false);
} }
}
foreach (var toOverwrite in (await guild.GetTextChannelsAsync())) foreach (var toOverwrite in (await guild.GetTextChannelsAsync()))
{ {
try try
{ {
await toOverwrite.AddPermissionOverwriteAsync(muteRole, new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny)) if (!toOverwrite.PermissionOverwrites.Select(x => x.Permissions).Contains(denyOverwrite))
{
await toOverwrite.AddPermissionOverwriteAsync(muteRole, denyOverwrite)
.ConfigureAwait(false); .ConfigureAwait(false);
}
catch { }
await Task.Delay(200).ConfigureAwait(false); await Task.Delay(200).ConfigureAwait(false);
} }
} }
catch
{
// ignored
}
}
return muteRole; return muteRole;
} }
public static async Task TimedMute(IGuildUser user, TimeSpan after)
{
await MuteUser(user).ConfigureAwait(false); // mute the user. This will also remove any previous unmute timers
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(user.GuildId, set => set.Include(x => x.UnmuteTimers));
config.UnmuteTimers.Add(new UnmuteTimer()
{
UserId = user.Id,
UnmuteAt = DateTime.UtcNow + after,
}); // add teh unmute timer to the database
uow.Complete();
}
StartUnmuteTimer(user.GuildId, user.Id, after); // start the timer
}
public static void StartUnmuteTimer(ulong guildId, ulong userId, TimeSpan after)
{
//load the unmute timers for this guild
var userUnmuteTimers = unmuteTimers.GetOrAdd(guildId, new ConcurrentDictionary<ulong, Timer>());
//unmute timer to be added
var toAdd = new Timer(async _ =>
{
try
{
var guild = NadekoBot.Client.GetGuild(guildId); // load the guild
if (guild == null)
{
RemoveUnmuteTimerFromDb(guildId, userId);
return; // if guild can't be found, just remove the timer from db
}
// unmute the user, this will also remove the timer from the db
await UnmuteUser(guild.GetUser(userId)).ConfigureAwait(false);
}
catch (Exception ex)
{
RemoveUnmuteTimerFromDb(guildId, userId); // if unmute errored, just remove unmute from db
Administration._log.Warn("Couldn't unmute user {0} in guild {1}", userId, guildId);
Administration._log.Warn(ex);
}
}, null, after, Timeout.InfiniteTimeSpan);
//add it, or stop the old one and add this one
userUnmuteTimers.AddOrUpdate(userId, (key) => toAdd, (key, old) =>
{
old.Change(Timeout.Infinite, Timeout.Infinite);
return toAdd;
});
}
public static void StopUnmuteTimer(ulong guildId, ulong userId)
{
ConcurrentDictionary<ulong, Timer> userUnmuteTimers;
if (!unmuteTimers.TryGetValue(guildId, out userUnmuteTimers)) return;
Timer removed;
if(userUnmuteTimers.TryRemove(userId, out removed))
{
removed.Change(Timeout.Infinite, Timeout.Infinite);
}
}
private static void RemoveUnmuteTimerFromDb(ulong guildId, ulong userId)
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(guildId, set => set.Include(x => x.UnmuteTimers));
config.UnmuteTimers.RemoveWhere(x => x.UserId == userId);
uow.Complete();
}
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
[Priority(1)] [Priority(1)]
public async Task SetMuteRole([Remainder] string name) public async Task SetMuteRole([Remainder] string name)
{ {
//var channel = (ITextChannel)Context.Channel;
name = name.Trim(); name = name.Trim();
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(name))
return; return;
@ -161,10 +266,10 @@ namespace NadekoBot.Modules.Administration
{ {
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set); var config = uow.GuildConfigs.For(Context.Guild.Id, set => set);
config.MuteRoleName = name; config.MuteRoleName = name;
GuildMuteRoles.AddOrUpdate(Context.Guild.Id, name, (id, old) => name); guildMuteRoles.AddOrUpdate(Context.Guild.Id, name, (id, old) => name);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
await Context.Channel.SendConfirmAsync("☑️ **New mute role set.**").ConfigureAwait(false); await ReplyConfirmLocalized("mute_role_set").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -178,16 +283,38 @@ namespace NadekoBot.Modules.Administration
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
[RequireUserPermission(GuildPermission.MuteMembers)] [RequireUserPermission(GuildPermission.MuteMembers)]
[Priority(1)]
public async Task Mute(IGuildUser user) public async Task Mute(IGuildUser user)
{ {
try try
{ {
await MuteUser(user).ConfigureAwait(false); await MuteUser(user).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"🔇 **{user}** has been **muted** from text and voice chat.").ConfigureAwait(false); await ReplyConfirmLocalized("user_muted", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch
{ {
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireUserPermission(GuildPermission.MuteMembers)]
[Priority(0)]
public async Task Mute(int minutes, IGuildUser user)
{
if (minutes < 1 || minutes > 1440)
return;
try
{
await TimedMute(user, TimeSpan.FromMinutes(minutes)).ConfigureAwait(false);
await ReplyConfirmLocalized("user_muted_time", Format.Bold(user.ToString()), minutes).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
} }
} }
@ -200,11 +327,11 @@ namespace NadekoBot.Modules.Administration
try try
{ {
await UnmuteUser(user).ConfigureAwait(false); await UnmuteUser(user).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"🔉 **{user}** has been **unmuted** from text and voice chat.").ConfigureAwait(false); await ReplyConfirmLocalized("user_unmuted", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch
{ {
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
} }
} }
@ -217,11 +344,11 @@ namespace NadekoBot.Modules.Administration
{ {
await user.AddRolesAsync(await GetMuteRole(Context.Guild).ConfigureAwait(false)).ConfigureAwait(false); await user.AddRolesAsync(await GetMuteRole(Context.Guild).ConfigureAwait(false)).ConfigureAwait(false);
UserMuted(user, MuteType.Chat); UserMuted(user, MuteType.Chat);
await Context.Channel.SendConfirmAsync($"✏️🚫 **{user}** has been **muted** from chatting.").ConfigureAwait(false); await ReplyConfirmLocalized("user_chat_mute", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch
{ {
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
} }
} }
@ -234,11 +361,11 @@ namespace NadekoBot.Modules.Administration
{ {
await user.RemoveRolesAsync(await GetMuteRole(Context.Guild).ConfigureAwait(false)).ConfigureAwait(false); await user.RemoveRolesAsync(await GetMuteRole(Context.Guild).ConfigureAwait(false)).ConfigureAwait(false);
UserUnmuted(user, MuteType.Chat); UserUnmuted(user, MuteType.Chat);
await Context.Channel.SendConfirmAsync($"✏️✅ **{user}** has been **unmuted** from chatting.").ConfigureAwait(false); await ReplyConfirmLocalized("user_chat_unmute", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch
{ {
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
} }
} }
@ -251,11 +378,11 @@ namespace NadekoBot.Modules.Administration
{ {
await user.ModifyAsync(usr => usr.Mute = true).ConfigureAwait(false); await user.ModifyAsync(usr => usr.Mute = true).ConfigureAwait(false);
UserMuted(user, MuteType.Voice); UserMuted(user, MuteType.Voice);
await Context.Channel.SendConfirmAsync($"🎙🚫 **{user}** has been **voice muted**.").ConfigureAwait(false); await ReplyConfirmLocalized("user_voice_mute", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch
{ {
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
} }
} }
@ -268,11 +395,11 @@ namespace NadekoBot.Modules.Administration
{ {
await user.ModifyAsync(usr => usr.Mute = false).ConfigureAwait(false); await user.ModifyAsync(usr => usr.Mute = false).ConfigureAwait(false);
UserUnmuted(user, MuteType.Voice); UserUnmuted(user, MuteType.Voice);
await Context.Channel.SendConfirmAsync($"🎙✅ **{user}** has been **voice unmuted**.").ConfigureAwait(false); await ReplyConfirmLocalized("user_voice_unmute", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch
{ {
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
} }
} }
} }

View File

@ -1,5 +1,5 @@
using Discord; using Discord.Commands;
using Discord.Commands; using Discord.WebSocket;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
@ -7,8 +7,8 @@ using NadekoBot.Services.Database.Models;
using NLog; using NLog;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
@ -16,64 +16,66 @@ namespace NadekoBot.Modules.Administration
public partial class Administration public partial class Administration
{ {
[Group] [Group]
public class PlayingRotateCommands : ModuleBase public class PlayingRotateCommands : NadekoSubmodule
{ {
private static Logger _log { get; }
public static List<PlayingStatus> RotatingStatusMessages { get; } public static List<PlayingStatus> RotatingStatusMessages { get; }
public static bool RotatingStatuses { get; private set; } = false; public static volatile bool RotatingStatuses;
private readonly object _locker = new object();
private new static Logger _log { get; }
private static readonly Timer _t;
private class TimerState
{
public int Index { get; set; }
}
//todo wtf is with this while(true) in constructor
static PlayingRotateCommands() static PlayingRotateCommands()
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
using (var uow = DbHandler.UnitOfWork()) RotatingStatusMessages = NadekoBot.BotConfig.RotatingStatusMessages;
{ RotatingStatuses = NadekoBot.BotConfig.RotatingStatuses;
var conf = uow.BotConfig.GetOrCreate();
RotatingStatusMessages = conf.RotatingStatusMessages;
RotatingStatuses = conf.RotatingStatuses;
}
var t = Task.Run(async () => _t = new Timer(async (objState) =>
{
var index = 0;
do
{ {
try try
{ {
var state = (TimerState)objState;
if (!RotatingStatuses) if (!RotatingStatuses)
continue; return;
else if (state.Index >= RotatingStatusMessages.Count)
{ state.Index = 0;
if (index >= RotatingStatusMessages.Count)
index = 0;
if (!RotatingStatusMessages.Any()) if (!RotatingStatusMessages.Any())
continue; return;
var status = RotatingStatusMessages[index++].Status; var status = RotatingStatusMessages[state.Index++].Status;
if (string.IsNullOrWhiteSpace(status)) if (string.IsNullOrWhiteSpace(status))
continue; return;
PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value())); PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value()));
await NadekoBot.Client.SetGame(status); var shards = NadekoBot.Client.Shards;
for (int i = 0; i < shards.Count; i++)
{
var curShard = shards.ElementAt(i);
ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(curShard)));
try { await shards.ElementAt(i).SetGameAsync(status).ConfigureAwait(false); }
catch (Exception ex)
{
_log.Warn(ex);
}
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
_log.Warn("Rotating playing status errored.\n" + ex); _log.Warn("Rotating playing status errored.\n" + ex);
} }
finally }, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
{
await Task.Delay(TimeSpan.FromMinutes(1));
}
} while (true);
});
} }
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.GetGuildsCount().ToString()}, { "%servers%", () => NadekoBot.Client.GetGuildCount().ToString()},
{"%users%", () => NadekoBot.Client.GetGuilds().Sum(s => s.Users.Count).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();
try { try {
@ -85,24 +87,35 @@ namespace NadekoBot.Modules.Administration
} }
} }
}, },
{"%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()} { "%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()},
{ "%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) },
{ "%shardcount%", () => NadekoBot.Client.Shards.Count.ToString() },
};
public static Dictionary<string, Func<DiscordSocketClient, string>> ShardSpecificPlaceholders { get; } =
new Dictionary<string, Func<DiscordSocketClient, string>> {
{ "%shardid%", (client) => client.ShardId.ToString()},
{ "%shardguilds%", (client) => client.Guilds.Count.ToString()},
}; };
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[OwnerOnly] [OwnerOnly]
public async Task RotatePlaying() public async Task RotatePlaying()
{
lock (_locker)
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var config = uow.BotConfig.GetOrCreate(); var config = uow.BotConfig.GetOrCreate();
RotatingStatuses = config.RotatingStatuses = !config.RotatingStatuses; RotatingStatuses = config.RotatingStatuses = !config.RotatingStatuses;
await uow.CompleteAsync(); uow.Complete();
}
} }
if (RotatingStatuses) if (RotatingStatuses)
await Context.Channel.SendConfirmAsync("🆗 **Rotating playing status enabled.**").ConfigureAwait(false); await ReplyConfirmLocalized("ropl_enabled").ConfigureAwait(false);
else else
await Context.Channel.SendConfirmAsync(" **Rotating playing status disabled.**").ConfigureAwait(false); await ReplyConfirmLocalized("ropl_disabled").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -118,7 +131,7 @@ namespace NadekoBot.Modules.Administration
await uow.CompleteAsync(); await uow.CompleteAsync();
} }
await Context.Channel.SendConfirmAsync("✅ **Added.**").ConfigureAwait(false); await ReplyConfirmLocalized("ropl_added").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -126,11 +139,13 @@ namespace NadekoBot.Modules.Administration
public async Task ListPlaying() public async Task ListPlaying()
{ {
if (!RotatingStatusMessages.Any()) if (!RotatingStatusMessages.Any())
await Context.Channel.SendErrorAsync("❎ **No rotating playing statuses set.**"); await ReplyErrorLocalized("ropl_not_set").ConfigureAwait(false);
else else
{ {
var i = 1; var i = 1;
await Context.Channel.SendConfirmAsync($" {Context.User.Mention} `Here is a list of rotating statuses:`\n\n\t" + string.Join("\n\t", RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}"))); await ReplyConfirmLocalized("ropl_list",
string.Join("\n\t", RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}")))
.ConfigureAwait(false);
} }
} }
@ -141,7 +156,7 @@ namespace NadekoBot.Modules.Administration
{ {
index -= 1; index -= 1;
string msg = ""; string msg;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var config = uow.BotConfig.GetOrCreate(); var config = uow.BotConfig.GetOrCreate();
@ -153,7 +168,7 @@ namespace NadekoBot.Modules.Administration
RotatingStatusMessages.RemoveAt(index); RotatingStatusMessages.RemoveAt(index);
await uow.CompleteAsync(); await uow.CompleteAsync();
} }
await Context.Channel.SendConfirmAsync($"🗑 **Removed the the playing message:** {msg}").ConfigureAwait(false); await ReplyConfirmLocalized("reprm", msg).ConfigureAwait(false);
} }
} }
} }

View File

@ -0,0 +1,443 @@
using Discord;
using Discord.Commands;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
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; }
public ConcurrentHashSet<IGuildUser> RaidUsers { get; set; } = new ConcurrentHashSet<IGuildUser>();
}
public class AntiSpamStats
{
public AntiSpamSetting AntiSpamSettings { get; set; }
public ConcurrentDictionary<ulong, UserSpamStats> UserStats { get; set; }
= new ConcurrentDictionary<ulong, UserSpamStats>();
}
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(IUserMessage message)
{
var upperMsg = message.Content.ToUpperInvariant();
if (upperMsg != LastMessage || (string.IsNullOrWhiteSpace(upperMsg) && message.Attachments.Any()))
{
LastMessage = upperMsg;
Count = 0;
}
else
{
Count++;
}
}
}
[Group]
public class ProtectionCommands : NadekoSubmodule
{
private static readonly ConcurrentDictionary<ulong, AntiRaidStats> _antiRaidGuilds =
new ConcurrentDictionary<ulong, AntiRaidStats>();
// guildId | (userId|messages)
private static readonly ConcurrentDictionary<ulong, AntiSpamStats> _antiSpamGuilds =
new ConcurrentDictionary<ulong, AntiSpamStats>();
private new static readonly Logger _log;
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 += (imsg) =>
{
var msg = imsg as IUserMessage;
if (msg == null || msg.Author.IsBot)
return Task.CompletedTask;
var channel = msg.Channel as ITextChannel;
if (channel == null)
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
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); 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
{
// ignored
}
});
return Task.CompletedTask;
};
NadekoBot.Client.UserJoined += (usr) =>
{
if (usr.IsBot)
return Task.CompletedTask;
AntiRaidStats settings;
if (!_antiRaidGuilds.TryGetValue(usr.Guild.Id, out settings))
return Task.CompletedTask;
if (!settings.RaidUsers.Add(usr))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
++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
{
// ignored
}
});
return Task.CompletedTask;
};
}
private static async Task PunishUsers(PunishmentAction action, ProtectionType pt, params IGuildUser[] gus)
{
_log.Info($"[{pt}] - Punishing [{gus.Length}] users with [{action}] in {gus[0].Guild.Name} guild");
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.KickAsync().ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishement"); }
break;
case PunishmentAction.Softban:
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;
}
}
await LogCommands.TriggeredAntiProtection(gus, action, pt).ConfigureAwait(false);
}
private string GetAntiSpamString(AntiSpamStats stats)
{
var ignoredString = string.Join(", ", stats.AntiSpamSettings.IgnoredChannels.Select(c => $"<#{c.ChannelId}>"));
if (string.IsNullOrWhiteSpace(ignoredString))
ignoredString = "none";
return GetText("spam_stats",
Format.Bold(stats.AntiSpamSettings.MessageThreshold.ToString()),
Format.Bold(stats.AntiSpamSettings.Action.ToString()),
ignoredString);
}
private string GetAntiRaidString(AntiRaidStats stats) => GetText("raid_stats",
Format.Bold(stats.AntiRaidSettings.UserThreshold.ToString()),
Format.Bold(stats.AntiRaidSettings.Seconds.ToString()),
Format.Bold(stats.AntiRaidSettings.Action.ToString()));
[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 ReplyErrorLocalized("raid_cnt", 2, 30).ConfigureAwait(false);
return;
}
if (seconds < 2 || seconds > 300)
{
await ReplyErrorLocalized("raid_time", 2, 300).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 ReplyConfirmLocalized("prot_disable", "Anti-Raid").ConfigureAwait(false);
return;
}
try
{
await MuteCommands.GetMuteRole(Context.Guild).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
await ReplyErrorLocalized("prot_error").ConfigureAwait(false);
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(GetText("prot_enable", "Anti-Raid"), $"{Context.User.Mention} {GetAntiRaidString(stats)}")
.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 ReplyConfirmLocalized("prot_disable", "Anti-Spam").ConfigureAwait(false);
return;
}
try
{
await MuteCommands.GetMuteRole(Context.Guild).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
await ReplyErrorLocalized("prot_error").ConfigureAwait(false);
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(GetText("prot_enable", "Anti-Spam"), $"{Context.User.Mention} {GetAntiSpamString(stats)}").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 ReplyConfirmLocalized("spam_ignore", "Anti-Spam").ConfigureAwait(false);
else
await ReplyConfirmLocalized("spam_not_ignore", "Anti-Spam").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AntiList()
{
AntiSpamStats spam;
_antiSpamGuilds.TryGetValue(Context.Guild.Id, out spam);
AntiRaidStats raid;
_antiRaidGuilds.TryGetValue(Context.Guild.Id, out raid);
if (spam == null && raid == null)
{
await ReplyConfirmLocalized("prot_none").ConfigureAwait(false);
return;
}
var embed = new EmbedBuilder().WithOkColor()
.WithTitle(GetText("prot_active"));
if (spam != null)
embed.AddField(efb => efb.WithName("Anti-Spam")
.WithValue(GetAntiSpamString(spam))
.WithIsInline(true));
if (raid != null)
embed.AddField(efb => efb.WithName("Anti-Raid")
.WithValue(GetAntiRaidString(raid))
.WithIsInline(true));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
}
}
}

View File

@ -1,10 +1,16 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NLog; using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -13,10 +19,13 @@ namespace NadekoBot.Modules.Administration
public partial class Administration public partial class Administration
{ {
[Group] [Group]
public class RatelimitCommand : ModuleBase public class RatelimitCommands : NadekoSubmodule
{ {
public static ConcurrentDictionary<ulong, Ratelimiter> RatelimitingChannels = new ConcurrentDictionary<ulong, Ratelimiter>(); public static ConcurrentDictionary<ulong, Ratelimiter> RatelimitingChannels = new ConcurrentDictionary<ulong, Ratelimiter>();
private static Logger _log { get; } public static ConcurrentDictionary<ulong, HashSet<ulong>> IgnoredRoles = new ConcurrentDictionary<ulong, HashSet<ulong>>();
public static ConcurrentDictionary<ulong, HashSet<ulong>> IgnoredUsers = new ConcurrentDictionary<ulong, HashSet<ulong>>();
private new static readonly Logger _log;
public class Ratelimiter public class Ratelimiter
{ {
@ -35,17 +44,22 @@ namespace NadekoBot.Modules.Administration
public ConcurrentDictionary<ulong, RatelimitedUser> Users { get; set; } = new ConcurrentDictionary<ulong, RatelimitedUser>(); public ConcurrentDictionary<ulong, RatelimitedUser> Users { get; set; } = new ConcurrentDictionary<ulong, RatelimitedUser>();
public bool CheckUserRatelimit(ulong id) public bool CheckUserRatelimit(ulong id, ulong guildId, SocketGuildUser optUser)
{ {
RatelimitedUser usr = Users.GetOrAdd(id, (key) => new RatelimitedUser() { UserId = id }); HashSet<ulong> ignoreUsers;
if (usr.MessageCount == MaxMessages) HashSet<ulong> ignoreRoles;
if ((IgnoredUsers.TryGetValue(guildId, out ignoreUsers) && ignoreUsers.Contains(id)) ||
(optUser != null && IgnoredRoles.TryGetValue(guildId, out ignoreRoles) && optUser.RoleIds.Any(x => ignoreRoles.Contains(x))))
return false;
var usr = Users.GetOrAdd(id, (key) => new RatelimitedUser() { UserId = id });
if (usr.MessageCount >= MaxMessages)
{ {
return true; return true;
} }
else
{
usr.MessageCount++; usr.MessageCount++;
var t = Task.Run(async () => var _ = Task.Run(async () =>
{ {
try try
{ {
@ -56,22 +70,28 @@ namespace NadekoBot.Modules.Administration
}); });
return false; return false;
} }
}
} }
static RatelimitCommand() static RatelimitCommands()
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
IgnoredRoles = new ConcurrentDictionary<ulong, HashSet<ulong>>(
NadekoBot.AllGuildConfigs
.ToDictionary(x => x.GuildId,
x => new HashSet<ulong>(x.SlowmodeIgnoredRoles.Select(y => y.RoleId))));
IgnoredUsers = new ConcurrentDictionary<ulong, HashSet<ulong>>(
NadekoBot.AllGuildConfigs
.ToDictionary(x => x.GuildId,
x => new HashSet<ulong>(x.SlowmodeIgnoredUsers.Select(y => y.UserId))));
NadekoBot.Client.MessageReceived += async (umsg) => NadekoBot.Client.MessageReceived += async (umsg) =>
{ {
try try
{ {
var usrMsg = umsg as IUserMessage; var usrMsg = umsg as SocketUserMessage;
if (usrMsg == null) var channel = usrMsg?.Channel as SocketTextChannel;
return;
var channel = usrMsg.Channel as ITextChannel;
if (channel == null || usrMsg.IsAuthor()) if (channel == null || usrMsg.IsAuthor())
return; return;
@ -79,7 +99,7 @@ namespace NadekoBot.Modules.Administration
if (!RatelimitingChannels.TryGetValue(channel.Id, out limiter)) if (!RatelimitingChannels.TryGetValue(channel.Id, out limiter))
return; return;
if (limiter.CheckUserRatelimit(usrMsg.Author.Id)) if (limiter.CheckUserRatelimit(usrMsg.Author.Id, channel.Guild.Id, usrMsg.Author as SocketGuildUser))
await usrMsg.DeleteAsync(); await usrMsg.DeleteAsync();
} }
catch (Exception ex) { _log.Warn(ex); } catch (Exception ex) { _log.Warn(ex); }
@ -95,8 +115,7 @@ namespace NadekoBot.Modules.Administration
if (RatelimitingChannels.TryRemove(Context.Channel.Id, out throwaway)) if (RatelimitingChannels.TryRemove(Context.Channel.Id, out throwaway))
{ {
throwaway.cancelSource.Cancel(); throwaway.cancelSource.Cancel();
await Context.Channel.SendConfirmAsync(" Slow mode disabled.").ConfigureAwait(false); await ReplyConfirmLocalized("slowmode_disabled").ConfigureAwait(false);
return;
} }
} }
@ -109,7 +128,7 @@ namespace NadekoBot.Modules.Administration
if (msg < 1 || perSec < 1 || msg > 100 || perSec > 3600) if (msg < 1 || perSec < 1 || msg > 100 || perSec > 3600)
{ {
await Context.Channel.SendErrorAsync("⚠️ Invalid parameters."); await ReplyErrorLocalized("invalid_params").ConfigureAwait(false);
return; return;
} }
var toAdd = new Ratelimiter() var toAdd = new Ratelimiter()
@ -120,11 +139,75 @@ namespace NadekoBot.Modules.Administration
}; };
if(RatelimitingChannels.TryAdd(Context.Channel.Id, toAdd)) if(RatelimitingChannels.TryAdd(Context.Channel.Id, toAdd))
{ {
await Context.Channel.SendConfirmAsync("Slow mode initiated", await Context.Channel.SendConfirmAsync(GetText("slowmode_init"),
$"Users can't send more than `{toAdd.MaxMessages} message(s)` every `{toAdd.PerSeconds} second(s)`.") GetText("slowmode_desc", Format.Bold(toAdd.MaxMessages.ToString()), Format.Bold(toAdd.PerSeconds.ToString())))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
} }
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
[Priority(1)]
public async Task SlowmodeWhitelist(IUser user)
{
var siu = new SlowmodeIgnoredUser
{
UserId = user.Id
};
HashSet<SlowmodeIgnoredUser> usrs;
bool removed;
using (var uow = DbHandler.UnitOfWork())
{
usrs = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.SlowmodeIgnoredUsers))
.SlowmodeIgnoredUsers;
if (!(removed = usrs.Remove(siu)))
usrs.Add(siu);
await uow.CompleteAsync().ConfigureAwait(false);
}
IgnoredUsers.AddOrUpdate(Context.Guild.Id, new HashSet<ulong>(usrs.Select(x => x.UserId)), (key, old) => new HashSet<ulong>(usrs.Select(x => x.UserId)));
if(removed)
await ReplyConfirmLocalized("slowmodewl_user_stop", Format.Bold(user.ToString())).ConfigureAwait(false);
else
await ReplyConfirmLocalized("slowmodewl_user_start", Format.Bold(user.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
[Priority(0)]
public async Task SlowmodeWhitelist(IRole role)
{
var sir = new SlowmodeIgnoredRole
{
RoleId = role.Id
};
HashSet<SlowmodeIgnoredRole> roles;
bool removed;
using (var uow = DbHandler.UnitOfWork())
{
roles = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.SlowmodeIgnoredRoles))
.SlowmodeIgnoredRoles;
if (!(removed = roles.Remove(sir)))
roles.Add(sir);
await uow.CompleteAsync().ConfigureAwait(false);
}
IgnoredRoles.AddOrUpdate(Context.Guild.Id, new HashSet<ulong>(roles.Select(x => x.RoleId)), (key, old) => new HashSet<ulong>(roles.Select(x => x.RoleId)));
if (removed)
await ReplyConfirmLocalized("slowmodewl_role_stop", Format.Bold(role.ToString())).ConfigureAwait(false);
else
await ReplyConfirmLocalized("slowmodewl_role_start", Format.Bold(role.ToString())).ConfigureAwait(false);
}
} }
} }
} }

View File

@ -16,7 +16,7 @@ namespace NadekoBot.Modules.Administration
public partial class Administration public partial class Administration
{ {
[Group] [Group]
public class SelfAssignedRolesCommands : ModuleBase public class SelfAssignedRolesCommands : NadekoSubmodule
{ {
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -43,26 +43,35 @@ namespace NadekoBot.Modules.Administration
{ {
IEnumerable<SelfAssignedRole> roles; IEnumerable<SelfAssignedRole> roles;
var guser = (IGuildUser)Context.User;
if (Context.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
return;
string msg; string msg;
var error = false;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id); roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id);
if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id)) if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
{ {
await Context.Channel.SendMessageAsync($"💢 Role **{role.Name}** is already in the list.").ConfigureAwait(false); msg = GetText("role_in_list", Format.Bold(role.Name));
return; error = true;
} }
else else
{ {
uow.SelfAssignedRoles.Add(new SelfAssignedRole { uow.SelfAssignedRoles.Add(new SelfAssignedRole
{
RoleId = role.Id, RoleId = role.Id,
GuildId = role.Guild.Id GuildId = role.Guild.Id
}); });
await uow.CompleteAsync(); await uow.CompleteAsync();
msg = $"🆗 Role **{role.Name}** added to the list."; msg = GetText("role_added", Format.Bold(role.Name));
} }
} }
await Context.Channel.SendConfirmAsync(msg.ToString()).ConfigureAwait(false); if (error)
await Context.Channel.SendErrorAsync(msg).ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync(msg).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -70,7 +79,9 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
public async Task Rsar([Remainder] IRole role) public async Task Rsar([Remainder] IRole role)
{ {
//var channel = (ITextChannel)Context.Channel; var guser = (IGuildUser)Context.User;
if (Context.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
return;
bool success; bool success;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
@ -80,18 +91,16 @@ namespace NadekoBot.Modules.Administration
} }
if (!success) if (!success)
{ {
await Context.Channel.SendErrorAsync("❎ That role is not self-assignable.").ConfigureAwait(false); await ReplyErrorLocalized("self_assign_not").ConfigureAwait(false);
return; return;
} }
await Context.Channel.SendConfirmAsync($"🗑 **{role.Name}** has been removed from the list of self-assignable roles.").ConfigureAwait(false); await ReplyConfirmLocalized("self_assign_rem", Format.Bold(role.Name)).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Lsar() public async Task Lsar()
{ {
//var channel = (ITextChannel)Context.Channel;
var toRemove = new ConcurrentHashSet<SelfAssignedRole>(); var toRemove = new ConcurrentHashSet<SelfAssignedRole>();
var removeMsg = new StringBuilder(); var removeMsg = new StringBuilder();
var msg = new StringBuilder(); var msg = new StringBuilder();
@ -116,11 +125,11 @@ namespace NadekoBot.Modules.Administration
} }
foreach (var role in toRemove) foreach (var role in toRemove)
{ {
removeMsg.AppendLine($"`{role.RoleId} not found. Cleaned up.`"); removeMsg.AppendLine(GetText("role_clean", role.RoleId));
} }
await uow.CompleteAsync(); await uow.CompleteAsync();
} }
await Context.Channel.SendConfirmAsync($" There are `{roleCnt}` self assignable roles:", msg.ToString() + "\n\n" + removeMsg.ToString()).ConfigureAwait(false); await Context.Channel.SendConfirmAsync(GetText("self_assign_list", roleCnt), msg + "\n\n" + removeMsg).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -128,8 +137,6 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
public async Task Tesar() public async Task Tesar()
{ {
//var channel = (ITextChannel)Context.Channel;
bool areExclusive; bool areExclusive;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
@ -138,44 +145,51 @@ namespace NadekoBot.Modules.Administration
areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles; areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles;
await uow.CompleteAsync(); await uow.CompleteAsync();
} }
string exl = areExclusive ? "**exclusive**." : "**not exclusive**."; if(areExclusive)
await Context.Channel.SendConfirmAsync(" Self assigned roles are now " + exl); await ReplyConfirmLocalized("self_assign_excl").ConfigureAwait(false);
else
await ReplyConfirmLocalized("self_assign_no_excl").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Iam([Remainder] IRole role) public async Task Iam([Remainder] IRole role)
{ {
//var channel = (ITextChannel)Context.Channel;
var guildUser = (IGuildUser)Context.User; var guildUser = (IGuildUser)Context.User;
GuildConfig conf; GuildConfig conf;
IEnumerable<SelfAssignedRole> roles; SelfAssignedRole[] roles;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
conf = uow.GuildConfigs.For(Context.Guild.Id, set => set); conf = uow.GuildConfigs.For(Context.Guild.Id, set => set);
roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id); roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id).ToArray();
} }
SelfAssignedRole roleModel; if (roles.FirstOrDefault(r=>r.RoleId == role.Id) == null)
if ((roleModel = roles.FirstOrDefault(r=>r.RoleId == role.Id)) == null)
{ {
await Context.Channel.SendErrorAsync("That role is not self-assignable.").ConfigureAwait(false); await ReplyErrorLocalized("self_assign_not").ConfigureAwait(false);
return; return;
} }
if (guildUser.RoleIds.Contains(role.Id)) if (guildUser.RoleIds.Contains(role.Id))
{ {
await Context.Channel.SendErrorAsync($"You already have **{role.Name}** role.").ConfigureAwait(false); await ReplyErrorLocalized("self_assign_already", Format.Bold(role.Name)).ConfigureAwait(false);
return; return;
} }
var roleIds = roles.Select(x => x.RoleId).ToArray();
if (conf.ExclusiveSelfAssignedRoles) if (conf.ExclusiveSelfAssignedRoles)
{ {
var sameRoleId = guildUser.RoleIds.Where(r => roles.Select(sar => sar.RoleId).Contains(r)).FirstOrDefault(); var sameRoleId = guildUser.RoleIds.FirstOrDefault(r => roleIds.Contains(r));
var sameRole = Context.Guild.GetRole(sameRoleId);
if (sameRoleId != default(ulong)) if (sameRoleId != default(ulong))
{ {
await Context.Channel.SendErrorAsync($"You already have **{sameRole?.Name}** `exclusive self-assigned` role.").ConfigureAwait(false); var sameRole = Context.Guild.GetRole(sameRoleId);
return; if (sameRole != null)
{
await guildUser.RemoveRolesAsync(sameRole).ConfigureAwait(false);
await Task.Delay(500).ConfigureAwait(false);
}
//await ReplyErrorLocalized("self_assign_already_excl", Format.Bold(sameRole?.Name)).ConfigureAwait(false);
//return;
} }
} }
try try
@ -184,11 +198,11 @@ namespace NadekoBot.Modules.Administration
} }
catch (Exception ex) catch (Exception ex)
{ {
await Context.Channel.SendErrorAsync($"⚠️ I am unable to add that role to you. `I can't add roles to owners or other roles higher than my role in the role hierarchy.`").ConfigureAwait(false); await ReplyErrorLocalized("self_assign_perms").ConfigureAwait(false);
Console.WriteLine(ex); Console.WriteLine(ex);
return; return;
} }
var msg = await Context.Channel.SendConfirmAsync($"🆗 You now have **{role.Name}** role.").ConfigureAwait(false); var msg = await ReplyConfirmLocalized("self_assign_success",Format.Bold(role.Name)).ConfigureAwait(false);
if (conf.AutoDeleteSelfAssignedRoleMessages) if (conf.AutoDeleteSelfAssignedRoleMessages)
{ {
@ -210,15 +224,14 @@ namespace NadekoBot.Modules.Administration
autoDeleteSelfAssignedRoleMessages = uow.GuildConfigs.For(Context.Guild.Id, set => set).AutoDeleteSelfAssignedRoleMessages; autoDeleteSelfAssignedRoleMessages = uow.GuildConfigs.For(Context.Guild.Id, set => set).AutoDeleteSelfAssignedRoleMessages;
roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id); roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id);
} }
SelfAssignedRole roleModel; if (roles.FirstOrDefault(r => r.RoleId == role.Id) == null)
if ((roleModel = roles.FirstOrDefault(r => r.RoleId == role.Id)) == null)
{ {
await Context.Channel.SendErrorAsync("💢 That role is not self-assignable.").ConfigureAwait(false); await ReplyErrorLocalized("self_assign_not").ConfigureAwait(false);
return; return;
} }
if (!guildUser.RoleIds.Contains(role.Id)) if (!guildUser.RoleIds.Contains(role.Id))
{ {
await Context.Channel.SendErrorAsync($"❎ You don't have **{role.Name}** role.").ConfigureAwait(false); await ReplyErrorLocalized("self_assign_not_have",Format.Bold(role.Name)).ConfigureAwait(false);
return; return;
} }
try try
@ -227,10 +240,10 @@ namespace NadekoBot.Modules.Administration
} }
catch (Exception) catch (Exception)
{ {
await Context.Channel.SendErrorAsync($"⚠️ I am unable to add that role to you. `I can't remove roles to owners or other roles higher than my role in the role hierarchy.`").ConfigureAwait(false); await ReplyErrorLocalized("self_assign_perms").ConfigureAwait(false);
return; return;
} }
var msg = await Context.Channel.SendConfirmAsync($"🆗 You no longer have **{role.Name}** role.").ConfigureAwait(false); var msg = await ReplyConfirmLocalized("self_assign_remove", Format.Bold(role.Name)).ConfigureAwait(false);
if (autoDeleteSelfAssignedRoleMessages) if (autoDeleteSelfAssignedRoleMessages)
{ {

View File

@ -3,18 +3,301 @@ using Discord.Commands;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using Microsoft.EntityFrameworkCore;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
{ {
public partial class Administration public partial class Administration
{ {
[Group] [Group]
class SelfCommands : ModuleBase public class SelfCommands : NadekoSubmodule
{ {
private static volatile bool _forwardDMs;
private static volatile bool _forwardDMsToAllOwners;
private static readonly object _locker = new object();
static SelfCommands()
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
_forwardDMs = config.ForwardMessages;
_forwardDMsToAllOwners = config.ForwardToAllOwners;
}
var _ = Task.Run(async () =>
{
while(!NadekoBot.Ready)
await Task.Delay(1000);
foreach (var cmd in NadekoBot.BotConfig.StartupCommands)
{
if (cmd.GuildId != null)
{
var guild = NadekoBot.Client.GetGuild(cmd.GuildId.Value);
var channel = guild?.GetChannel(cmd.ChannelId) as SocketTextChannel;
if (channel == null)
continue;
try
{
IUserMessage msg = await channel.SendMessageAsync(cmd.CommandText).ConfigureAwait(false);
msg = (IUserMessage)await channel.GetMessageAsync(msg.Id).ConfigureAwait(false);
await NadekoBot.CommandHandler.TryRunCommand(guild, channel, msg).ConfigureAwait(false);
//msg.DeleteAfter(5);
}
catch { }
}
await Task.Delay(400).ConfigureAwait(false);
}
});
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartupCommandAdd([Remainder] string cmdText)
{
var guser = ((IGuildUser)Context.User);
var cmd = new StartupCommand()
{
CommandText = cmdText,
ChannelId = Context.Channel.Id,
ChannelName = Context.Channel.Name,
GuildId = Context.Guild?.Id,
GuildName = Context.Guild?.Name,
VoiceChannelId = guser.VoiceChannel?.Id,
VoiceChannelName = guser.VoiceChannel?.Name,
};
using (var uow = DbHandler.UnitOfWork())
{
uow.BotConfig
.GetOrCreate(set => set.Include(x => x.StartupCommands))
.StartupCommands.Add(cmd);
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("scadd"))
.AddField(efb => efb.WithName(GetText("server"))
.WithValue(cmd.GuildId == null ? $"-" : $"{cmd.GuildName}/{cmd.GuildId}").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("channel"))
.WithValue($"{cmd.ChannelName}/{cmd.ChannelId}").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("command_text"))
.WithValue(cmdText).WithIsInline(false)));
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartupCommands(int page = 1)
{
if (page < 1)
return;
page -= 1;
IEnumerable<StartupCommand> scmds;
using (var uow = DbHandler.UnitOfWork())
{
scmds = uow.BotConfig
.GetOrCreate(set => set.Include(x => x.StartupCommands))
.StartupCommands
.OrderBy(x => x.Id)
.ToArray();
}
scmds = scmds.Skip(page * 5).Take(5);
if (!scmds.Any())
{
await ReplyErrorLocalized("startcmdlist_none").ConfigureAwait(false);
}
else
{
await Context.Channel.SendConfirmAsync("", string.Join("\n--\n", scmds.Select(x =>
{
string str = Format.Code(GetText("server")) + ": " + (x.GuildId == null ? "-" : x.GuildName + "/" + x.GuildId);
str += $@"
{Format.Code(GetText("channel"))}: {x.ChannelName}/{x.ChannelId}
{Format.Code(GetText("command_text"))}: {x.CommandText}";
return str;
})),footer: GetText("page", page + 1))
.ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task Wait(int miliseconds)
{
if (miliseconds <= 0)
return;
Context.Message.DeleteAfter(0);
try
{
var msg = await Context.Channel.SendConfirmAsync($"⏲ {miliseconds}ms")
.ConfigureAwait(false);
msg.DeleteAfter(miliseconds / 1000);
}
catch { }
await Task.Delay(miliseconds);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartupCommandRemove([Remainder] string cmdText)
{
StartupCommand cmd;
using (var uow = DbHandler.UnitOfWork())
{
var cmds = uow.BotConfig
.GetOrCreate(set => set.Include(x => x.StartupCommands))
.StartupCommands;
cmd = cmds
.FirstOrDefault(x => x.CommandText.ToLowerInvariant() == cmdText.ToLowerInvariant());
if (cmd != null)
{
cmds.Remove(cmd);
await uow.CompleteAsync().ConfigureAwait(false);
}
}
if(cmd == null)
await ReplyErrorLocalized("scrm_fail").ConfigureAwait(false);
else
await ReplyConfirmLocalized("scrm").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartupCommandsClear()
{
using (var uow = DbHandler.UnitOfWork())
{
uow.BotConfig
.GetOrCreate(set => set.Include(x => x.StartupCommands))
.StartupCommands
.Clear();
uow.Complete();
}
await ReplyConfirmLocalized("startcmds_cleared").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ForwardMessages()
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
lock (_locker)
_forwardDMs = config.ForwardMessages = !config.ForwardMessages;
uow.Complete();
}
if (_forwardDMs)
await ReplyConfirmLocalized("fwdm_start").ConfigureAwait(false);
else
await ReplyConfirmLocalized("fwdm_stop").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ForwardToAll()
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
lock (_locker)
_forwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners;
uow.Complete();
}
if (_forwardDMsToAllOwners)
await ReplyConfirmLocalized("fwall_start").ConfigureAwait(false);
else
await ReplyConfirmLocalized("fwall_stop").ConfigureAwait(false);
}
public static async Task HandleDmForwarding(IUserMessage msg, List<IDMChannel> ownerChannels)
{
if (_forwardDMs && ownerChannels.Any())
{
var title = GetTextStatic("dm_from",
NadekoBot.Localization.DefaultCultureInfo,
typeof(Administration).Name.ToLowerInvariant()) +
$" [{msg.Author}]({msg.Author.Id})";
var attachamentsTxt = GetTextStatic("attachments",
NadekoBot.Localization.DefaultCultureInfo,
typeof(Administration).Name.ToLowerInvariant());
var toSend = msg.Content;
if (msg.Attachments.Count > 0)
{
toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n" +
string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl));
}
if (_forwardDMsToAllOwners)
{
await Task.WhenAll(ownerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id)
.Select(ch => ch.SendConfirmAsync(title, toSend))).ConfigureAwait(false);
}
else
{
var firstOwnerChannel = ownerChannels.First();
if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
{
try
{
await firstOwnerChannel.SendConfirmAsync(title, toSend).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ConnectShard(int shardid)
{
var shard = NadekoBot.Client.GetShard(shardid);
if (shard == null)
{
await ReplyErrorLocalized("no_shard_id").ConfigureAwait(false);
return;
}
try
{
await ReplyConfirmLocalized("shard_reconnecting", Format.Bold("#" + shardid)).ConfigureAwait(false);
await shard.ConnectAsync().ConfigureAwait(false);
await ReplyConfirmLocalized("shard_reconnected", Format.Bold("#" + shardid)).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[OwnerOnly] [OwnerOnly]
public async Task Leave([Remainder] string guildStr) public async Task Leave([Remainder] string guildStr)
@ -25,18 +308,18 @@ namespace NadekoBot.Modules.Administration
if (server == null) if (server == null)
{ {
await Context.Channel.SendErrorAsync("⚠️ Cannot find that server").ConfigureAwait(false); await ReplyErrorLocalized("no_server").ConfigureAwait(false);
return; return;
} }
if (server.OwnerId != NadekoBot.Client.CurrentUser().Id) if (server.OwnerId != NadekoBot.Client.CurrentUser.Id)
{ {
await server.LeaveAsync().ConfigureAwait(false); await server.LeaveAsync().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("✅ Left server " + server.Name).ConfigureAwait(false); await ReplyConfirmLocalized("left_server", Format.Bold(server.Name)).ConfigureAwait(false);
} }
else else
{ {
await server.DeleteAsync().ConfigureAwait(false); await server.DeleteAsync().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("Deleted server " + server.Name).ConfigureAwait(false); await ReplyConfirmLocalized("deleted_server", Format.Bold(server.Name)).ConfigureAwait(false);
} }
} }
@ -45,7 +328,14 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task Die() public async Task Die()
{ {
try { await Context.Channel.SendConfirmAsync(" **Shutting down.**").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } try
{
await ReplyConfirmLocalized("shutting_down").ConfigureAwait(false);
}
catch
{
// ignored
}
await Task.Delay(2000).ConfigureAwait(false); await Task.Delay(2000).ConfigureAwait(false);
Environment.Exit(0); Environment.Exit(0);
} }
@ -57,18 +347,18 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(newName)) if (string.IsNullOrWhiteSpace(newName))
return; return;
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($"Bot name changed to **{newName}**").ConfigureAwait(false); await ReplyConfirmLocalized("bot_name", Format.Bold(newName)).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[OwnerOnly] [OwnerOnly]
public async Task SetStatus([Remainder] SettableUserStatus status) public async Task SetStatus([Remainder] SettableUserStatus status)
{ {
await NadekoBot.Client.SetStatus(status); await NadekoBot.Client.SetStatusAsync(SettableUserStatusToUserStatus(status)).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"Bot status changed to **{status}**").ConfigureAwait(false); await ReplyConfirmLocalized("bot_status", Format.Bold(status.ToString())).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -86,20 +376,20 @@ namespace NadekoBot.Modules.Administration
await sr.CopyToAsync(imgStream); await sr.CopyToAsync(imgStream);
imgStream.Position = 0; imgStream.Position = 0;
await NadekoBot.Client.CurrentUser().ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false); await NadekoBot.Client.CurrentUser.ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false);
} }
} }
await Context.Channel.SendConfirmAsync("🆒 **New avatar set.**").ConfigureAwait(false); await ReplyConfirmLocalized("set_avatar").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[OwnerOnly] [OwnerOnly]
public async Task SetGame([Remainder] string game = null) public async Task SetGame([Remainder] string game = null)
{ {
await NadekoBot.Client.SetGame(game).ConfigureAwait(false); await NadekoBot.Client.SetGameAsync(game).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("👾 **New game set.**").ConfigureAwait(false); await ReplyConfirmLocalized("set_game").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -108,9 +398,9 @@ namespace NadekoBot.Modules.Administration
{ {
name = name ?? ""; name = name ?? "";
await NadekoBot.Client.SetStream(name, url).ConfigureAwait(false); await NadekoBot.Client.SetGameAsync(name, url, StreamType.Twitch).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(" **New stream set.**").ConfigureAwait(false); await ReplyConfirmLocalized("set_stream").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -124,7 +414,7 @@ namespace NadekoBot.Modules.Administration
if (ids.Length != 2) if (ids.Length != 2)
return; return;
var sid = ulong.Parse(ids[0]); var sid = ulong.Parse(ids[0]);
var server = NadekoBot.Client.GetGuilds().Where(s => s.Id == sid).FirstOrDefault(); var server = NadekoBot.Client.GetGuilds().FirstOrDefault(s => s.Id == sid);
if (server == null) if (server == null)
return; return;
@ -132,7 +422,7 @@ namespace NadekoBot.Modules.Administration
if (ids[1].ToUpperInvariant().StartsWith("C:")) if (ids[1].ToUpperInvariant().StartsWith("C:"))
{ {
var cid = ulong.Parse(ids[1].Substring(2)); var cid = ulong.Parse(ids[1].Substring(2));
var ch = (await server.GetTextChannelsAsync()).Where(c => c.Id == cid).FirstOrDefault(); var ch = server.TextChannels.FirstOrDefault(c => c.Id == cid);
if (ch == null) if (ch == null)
{ {
return; return;
@ -142,7 +432,7 @@ namespace NadekoBot.Modules.Administration
else if (ids[1].ToUpperInvariant().StartsWith("U:")) else if (ids[1].ToUpperInvariant().StartsWith("U:"))
{ {
var uid = ulong.Parse(ids[1].Substring(2)); var uid = ulong.Parse(ids[1].Substring(2));
var user = server.Users.Where(u => u.Id == uid).FirstOrDefault(); var user = server.Users.FirstOrDefault(u => u.Id == uid);
if (user == null) if (user == null)
{ {
return; return;
@ -151,23 +441,56 @@ namespace NadekoBot.Modules.Administration
} }
else else
{ {
await Context.Channel.SendErrorAsync("⚠️ Invalid format.").ConfigureAwait(false); await ReplyErrorLocalized("invalid_format").ConfigureAwait(false);
return;
} }
await ReplyConfirmLocalized("message_sent").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[OwnerOnly] [OwnerOnly]
public async Task Announce([Remainder] string message) public async Task Announce([Remainder] string message)
{ {
var channels = await Task.WhenAll(NadekoBot.Client.GetGuilds().Select(g => var channels = NadekoBot.Client.GetGuilds().Select(g => g.DefaultChannel).ToArray();
g.GetDefaultChannelAsync()
)).ConfigureAwait(false);
if (channels == null) if (channels == null)
return; return;
await Task.WhenAll(channels.Where(c => c != null).Select(c => c.SendConfirmAsync($"🆕 Message from {Context.User} `[Bot Owner]`:", message))) await Task.WhenAll(channels.Where(c => c != null).Select(c => c.SendConfirmAsync(GetText("message_from_bo", Context.User.ToString()), message)))
.ConfigureAwait(false); .ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("🆗").ConfigureAwait(false); await ReplyConfirmLocalized("message_sent").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ReloadImages()
{
var time = await NadekoBot.Images.Reload().ConfigureAwait(false);
await ReplyConfirmLocalized("images_loaded", time.TotalSeconds.ToString("F3")).ConfigureAwait(false);
}
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,
Invisible,
Idle,
Dnd
} }
} }
} }

View File

@ -1,12 +1,13 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.DataStructures;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NLog; using NLog;
using System; using System;
using System.Collections.Concurrent;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -15,33 +16,103 @@ namespace NadekoBot.Modules.Administration
public partial class Administration public partial class Administration
{ {
[Group] [Group]
public class ServerGreetCommands : ModuleBase public class ServerGreetCommands : NadekoSubmodule
{ {
private static Logger _log { get; } //make this to a field in the guildconfig table
private class GreetSettings
{
public int AutoDeleteGreetMessagesTimer { get; set; }
public int AutoDeleteByeMessagesTimer { get; set; }
public ulong GreetMessageChannelId { get; set; }
public ulong ByeMessageChannelId { get; set; }
public bool SendDmGreetMessage { get; set; }
public string DmGreetMessageText { get; set; }
public bool SendChannelGreetMessage { get; set; }
public string ChannelGreetMessageText { get; set; }
public bool SendChannelByeMessage { get; set; }
public string ChannelByeMessageText { get; set; }
public static GreetSettings Create(GuildConfig g) => new GreetSettings()
{
AutoDeleteByeMessagesTimer = g.AutoDeleteByeMessagesTimer,
AutoDeleteGreetMessagesTimer = g.AutoDeleteGreetMessagesTimer,
GreetMessageChannelId = g.GreetMessageChannelId,
ByeMessageChannelId = g.ByeMessageChannelId,
SendDmGreetMessage = g.SendDmGreetMessage,
DmGreetMessageText = g.DmGreetMessageText,
SendChannelGreetMessage = g.SendChannelGreetMessage,
ChannelGreetMessageText = g.ChannelGreetMessageText,
SendChannelByeMessage = g.SendChannelByeMessage,
ChannelByeMessageText = g.ChannelByeMessageText,
};
}
private new static Logger _log { get; }
private static ConcurrentDictionary<ulong, GreetSettings> guildConfigsCache { get; }
static ServerGreetCommands() static ServerGreetCommands()
{ {
NadekoBot.Client.UserJoined += UserJoined; NadekoBot.Client.UserJoined += UserJoined;
NadekoBot.Client.UserLeft += UserLeft; NadekoBot.Client.UserLeft += UserLeft;
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
guildConfigsCache = new ConcurrentDictionary<ulong, GreetSettings>(NadekoBot.AllGuildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create));
} }
//todo optimize ASAP
private static async void UserLeft(IGuildUser user) private static GreetSettings GetOrAddSettingsForGuild(ulong guildId)
{
GreetSettings settings;
guildConfigsCache.TryGetValue(guildId, out settings);
if (settings != null)
return settings;
using (var uow = DbHandler.UnitOfWork())
{
var gc = uow.GuildConfigs.For(guildId, set => set);
settings = GreetSettings.Create(gc);
}
guildConfigsCache.TryAdd(guildId, settings);
return settings;
}
private static Task UserLeft(IGuildUser user)
{
var _ = Task.Run(async () =>
{ {
try try
{ {
GuildConfig conf; var conf = GetOrAddSettingsForGuild(user.GuildId);
using (var uow = DbHandler.UnitOfWork())
{
conf = uow.GuildConfigs.For(user.Guild.Id, set => set);
}
if (!conf.SendChannelByeMessage) return; if (!conf.SendChannelByeMessage) return;
var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.ByeMessageChannelId); var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.ByeMessageChannelId);
if (channel == null) //maybe warn the server owner that the channel is missing if (channel == null) //maybe warn the server owner that the channel is missing
return; return;
CREmbed embedData;
if (CREmbed.TryParse(conf.ChannelByeMessageText, out embedData))
{
embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Description = embedData.Description?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Title = embedData.Title?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
try
{
var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
if (conf.AutoDeleteByeMessagesTimer > 0)
{
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
}
}
catch (Exception ex) { _log.Warn(ex); }
}
else
{
var msg = conf.ChannelByeMessageText.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); var msg = conf.ChannelByeMessageText.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
if (string.IsNullOrWhiteSpace(msg)) if (string.IsNullOrWhiteSpace(msg))
return; return;
@ -55,23 +126,46 @@ namespace NadekoBot.Modules.Administration
} }
catch (Exception ex) { _log.Warn(ex); } catch (Exception ex) { _log.Warn(ex); }
} }
catch { } }
catch
{
// ignored
}
});
return Task.CompletedTask;
} }
private static async void UserJoined(IGuildUser user) private static Task UserJoined(IGuildUser user)
{
var _ = Task.Run(async () =>
{ {
try try
{ {
GuildConfig conf; var conf = GetOrAddSettingsForGuild(user.GuildId);
using (var uow = DbHandler.UnitOfWork())
{
conf = uow.GuildConfigs.For(user.Guild.Id, set => set);
}
if (conf.SendChannelGreetMessage) if (conf.SendChannelGreetMessage)
{ {
var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.GreetMessageChannelId); var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.GreetMessageChannelId);
if (channel != null) //maybe warn the server owner that the channel is missing if (channel != null) //maybe warn the server owner that the channel is missing
{
CREmbed embedData;
if (CREmbed.TryParse(conf.ChannelGreetMessageText, out embedData))
{
embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Description = embedData.Description?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
try
{
var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
if (conf.AutoDeleteGreetMessagesTimer > 0)
{
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
}
}
catch (Exception ex) { _log.Warn(ex); }
}
else
{ {
var msg = conf.ChannelGreetMessageText.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); var msg = conf.ChannelGreetMessageText.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
if (!string.IsNullOrWhiteSpace(msg)) if (!string.IsNullOrWhiteSpace(msg))
@ -88,6 +182,7 @@ namespace NadekoBot.Modules.Administration
} }
} }
} }
}
if (conf.SendDmGreetMessage) if (conf.SendDmGreetMessage)
{ {
@ -95,7 +190,21 @@ namespace NadekoBot.Modules.Administration
if (channel != null) if (channel != null)
{ {
var msg = conf.DmGreetMessageText.Replace("%user%", user.Username).Replace("%server%", user.Guild.Name); CREmbed embedData;
if (CREmbed.TryParse(conf.ChannelGreetMessageText, out embedData))
{
embedData.PlainText = embedData.PlainText?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Description = embedData.Description?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
try
{
await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
}
else
{
var msg = conf.DmGreetMessageText.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
if (!string.IsNullOrWhiteSpace(msg)) if (!string.IsNullOrWhiteSpace(msg))
{ {
await channel.SendConfirmAsync(msg).ConfigureAwait(false); await channel.SendConfirmAsync(msg).ConfigureAwait(false);
@ -103,7 +212,13 @@ namespace NadekoBot.Modules.Administration
} }
} }
} }
catch { } }
catch
{
// ignored
}
});
return Task.CompletedTask;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -111,16 +226,15 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageGuild)] [RequireUserPermission(GuildPermission.ManageGuild)]
public async Task GreetDel(int timer = 30) public async Task GreetDel(int timer = 30)
{ {
var channel = (ITextChannel)Context.Channel;
if (timer < 0 || timer > 600) if (timer < 0 || timer > 600)
return; return;
await ServerGreetCommands.SetGreetDel(Context.Guild.Id, timer).ConfigureAwait(false); await ServerGreetCommands.SetGreetDel(Context.Guild.Id, timer).ConfigureAwait(false);
if (timer > 0) if (timer > 0)
await Context.Channel.SendConfirmAsync($"🆗 Greet messages **will be deleted** after `{timer} seconds`.").ConfigureAwait(false); await ReplyConfirmLocalized("greetdel_on", timer).ConfigureAwait(false);
else else
await Context.Channel.SendConfirmAsync(" Automatic deletion of greet messages has been **disabled**.").ConfigureAwait(false); await ReplyConfirmLocalized("greetdel_off").ConfigureAwait(false);
} }
private static async Task SetGreetDel(ulong id, int timer) private static async Task SetGreetDel(ulong id, int timer)
@ -133,6 +247,9 @@ namespace NadekoBot.Modules.Administration
var conf = uow.GuildConfigs.For(id, set => set); var conf = uow.GuildConfigs.For(id, set => set);
conf.AutoDeleteGreetMessagesTimer = timer; conf.AutoDeleteGreetMessagesTimer = timer;
var toAdd = GreetSettings.Create(conf);
guildConfigsCache.AddOrUpdate(id, toAdd, (key, old) => toAdd);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
} }
@ -145,9 +262,9 @@ namespace NadekoBot.Modules.Administration
var enabled = await ServerGreetCommands.SetGreet(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false); var enabled = await ServerGreetCommands.SetGreet(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false);
if (enabled) if (enabled)
await Context.Channel.SendConfirmAsync("✅ Greeting messages **enabled** on this channel.").ConfigureAwait(false); await ReplyConfirmLocalized("greet_on").ConfigureAwait(false);
else else
await Context.Channel.SendConfirmAsync(" Greeting messages **disabled**.").ConfigureAwait(false); await ReplyConfirmLocalized("greet_off").ConfigureAwait(false);
} }
private static async Task<bool> SetGreet(ulong guildId, ulong channelId, bool? value = null) private static async Task<bool> SetGreet(ulong guildId, ulong channelId, bool? value = null)
@ -159,6 +276,9 @@ namespace NadekoBot.Modules.Administration
enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage; enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage;
conf.GreetMessageChannelId = channelId; conf.GreetMessageChannelId = channelId;
var toAdd = GreetSettings.Create(conf);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
return enabled; return enabled;
@ -176,15 +296,15 @@ namespace NadekoBot.Modules.Administration
{ {
channelGreetMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelGreetMessageText; channelGreetMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelGreetMessageText;
} }
await Context.Channel.SendConfirmAsync("Current greet message: ", channelGreetMessageText?.SanitizeMentions()); await ReplyConfirmLocalized("greetmsg_cur", channelGreetMessageText?.SanitizeMentions()).ConfigureAwait(false);
return; return;
} }
var sendGreetEnabled = ServerGreetCommands.SetGreetMessage(Context.Guild.Id, ref text); var sendGreetEnabled = ServerGreetCommands.SetGreetMessage(Context.Guild.Id, ref text);
await Context.Channel.SendConfirmAsync("🆗 New greet message **set**.").ConfigureAwait(false); await ReplyConfirmLocalized("greetmsg_new").ConfigureAwait(false);
if (!sendGreetEnabled) if (!sendGreetEnabled)
await Context.Channel.SendConfirmAsync(" Enable greet messsages by typing `.greet`").ConfigureAwait(false); await ReplyConfirmLocalized("greetmsg_enable", $"`{Prefix}greet`").ConfigureAwait(false);
} }
public static bool SetGreetMessage(ulong guildId, ref string message) public static bool SetGreetMessage(ulong guildId, ref string message)
@ -201,6 +321,9 @@ namespace NadekoBot.Modules.Administration
conf.ChannelGreetMessageText = message; conf.ChannelGreetMessageText = message;
greetMsgEnabled = conf.SendChannelGreetMessage; greetMsgEnabled = conf.SendChannelGreetMessage;
var toAdd = GreetSettings.Create(conf);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
uow.Complete(); uow.Complete();
} }
return greetMsgEnabled; return greetMsgEnabled;
@ -214,9 +337,9 @@ namespace NadekoBot.Modules.Administration
var enabled = await ServerGreetCommands.SetGreetDm(Context.Guild.Id).ConfigureAwait(false); var enabled = await ServerGreetCommands.SetGreetDm(Context.Guild.Id).ConfigureAwait(false);
if (enabled) if (enabled)
await Context.Channel.SendConfirmAsync("🆗 DM Greet announcements **enabled**.").ConfigureAwait(false); await ReplyConfirmLocalized("greetdm_on").ConfigureAwait(false);
else else
await Context.Channel.SendConfirmAsync(" Greet announcements **disabled**.").ConfigureAwait(false); await ReplyConfirmLocalized("greetdm_off").ConfigureAwait(false);
} }
private static async Task<bool> SetGreetDm(ulong guildId, bool? value = null) private static async Task<bool> SetGreetDm(ulong guildId, bool? value = null)
@ -227,6 +350,9 @@ namespace NadekoBot.Modules.Administration
var conf = uow.GuildConfigs.For(guildId, set => set); var conf = uow.GuildConfigs.For(guildId, set => set);
enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage; enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage;
var toAdd = GreetSettings.Create(conf);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
return enabled; return enabled;
@ -244,15 +370,15 @@ namespace NadekoBot.Modules.Administration
{ {
config = uow.GuildConfigs.For(Context.Guild.Id); config = uow.GuildConfigs.For(Context.Guild.Id);
} }
await Context.Channel.SendConfirmAsync(" Current **DM greet** message: `" + config.DmGreetMessageText?.SanitizeMentions() + "`"); await ReplyConfirmLocalized("greetdmmsg_cur", config.DmGreetMessageText?.SanitizeMentions()).ConfigureAwait(false);
return; return;
} }
var sendGreetEnabled = ServerGreetCommands.SetGreetDmMessage(Context.Guild.Id, ref text); var sendGreetEnabled = ServerGreetCommands.SetGreetDmMessage(Context.Guild.Id, ref text);
await Context.Channel.SendConfirmAsync("🆗 New DM greet message **set**.").ConfigureAwait(false); await ReplyConfirmLocalized("greetdmmsg_new").ConfigureAwait(false);
if (!sendGreetEnabled) if (!sendGreetEnabled)
await Context.Channel.SendConfirmAsync($" Enable DM greet messsages by typing `{NadekoBot.ModulePrefixes[typeof(Administration).Name]}greetdm`").ConfigureAwait(false); await ReplyConfirmLocalized("greetdmmsg_enable", $"`{Prefix}greetdm`").ConfigureAwait(false);
} }
public static bool SetGreetDmMessage(ulong guildId, ref string message) public static bool SetGreetDmMessage(ulong guildId, ref string message)
@ -269,6 +395,9 @@ namespace NadekoBot.Modules.Administration
conf.DmGreetMessageText = message; conf.DmGreetMessageText = message;
greetMsgEnabled = conf.SendDmGreetMessage; greetMsgEnabled = conf.SendDmGreetMessage;
var toAdd = GreetSettings.Create(conf);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
uow.Complete(); uow.Complete();
} }
return greetMsgEnabled; return greetMsgEnabled;
@ -282,9 +411,9 @@ namespace NadekoBot.Modules.Administration
var enabled = await ServerGreetCommands.SetBye(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false); var enabled = await ServerGreetCommands.SetBye(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false);
if (enabled) if (enabled)
await Context.Channel.SendConfirmAsync("✅ Bye announcements **enabled** on this channel.").ConfigureAwait(false); await ReplyConfirmLocalized("bye_on").ConfigureAwait(false);
else else
await Context.Channel.SendConfirmAsync(" Bye announcements **disabled**.").ConfigureAwait(false); await ReplyConfirmLocalized("bye_off").ConfigureAwait(false);
} }
private static async Task<bool> SetBye(ulong guildId, ulong channelId, bool? value = null) private static async Task<bool> SetBye(ulong guildId, ulong channelId, bool? value = null)
@ -296,6 +425,9 @@ namespace NadekoBot.Modules.Administration
enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage; enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage;
conf.ByeMessageChannelId = channelId; conf.ByeMessageChannelId = channelId;
var toAdd = GreetSettings.Create(conf);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
await uow.CompleteAsync(); await uow.CompleteAsync();
} }
return enabled; return enabled;
@ -313,15 +445,15 @@ namespace NadekoBot.Modules.Administration
{ {
byeMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelByeMessageText; byeMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelByeMessageText;
} }
await Context.Channel.SendConfirmAsync(" Current **bye** message: `" + byeMessageText?.SanitizeMentions() + "`"); await ReplyConfirmLocalized("byemsg_cur", byeMessageText?.SanitizeMentions()).ConfigureAwait(false);
return; return;
} }
var sendByeEnabled = ServerGreetCommands.SetByeMessage(Context.Guild.Id, ref text); var sendByeEnabled = ServerGreetCommands.SetByeMessage(Context.Guild.Id, ref text);
await Context.Channel.SendConfirmAsync("🆗 New bye message **set**.").ConfigureAwait(false); await ReplyConfirmLocalized("byemsg_new").ConfigureAwait(false);
if (!sendByeEnabled) if (!sendByeEnabled)
await Context.Channel.SendConfirmAsync($" Enable bye messsages by typing `{NadekoBot.ModulePrefixes[typeof(Administration).Name]}bye`").ConfigureAwait(false); await ReplyConfirmLocalized("byemsg_enable", $"`{Prefix}bye`").ConfigureAwait(false);
} }
public static bool SetByeMessage(ulong guildId, ref string message) public static bool SetByeMessage(ulong guildId, ref string message)
@ -338,6 +470,9 @@ namespace NadekoBot.Modules.Administration
conf.ChannelByeMessageText = message; conf.ChannelByeMessageText = message;
byeMsgEnabled = conf.SendChannelByeMessage; byeMsgEnabled = conf.SendChannelByeMessage;
var toAdd = GreetSettings.Create(conf);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
uow.Complete(); uow.Complete();
} }
return byeMsgEnabled; return byeMsgEnabled;
@ -351,21 +486,24 @@ namespace NadekoBot.Modules.Administration
await ServerGreetCommands.SetByeDel(Context.Guild.Id, timer).ConfigureAwait(false); await ServerGreetCommands.SetByeDel(Context.Guild.Id, timer).ConfigureAwait(false);
if (timer > 0) if (timer > 0)
await Context.Channel.SendConfirmAsync($"🆗 Bye messages **will be deleted** after `{timer} seconds`.").ConfigureAwait(false); await ReplyConfirmLocalized("byedel_on", timer).ConfigureAwait(false);
else else
await Context.Channel.SendConfirmAsync(" Automatic deletion of bye messages has been **disabled**.").ConfigureAwait(false); await ReplyConfirmLocalized("byedel_off").ConfigureAwait(false);
} }
private static async Task SetByeDel(ulong id, int timer) private static async Task SetByeDel(ulong guildId, int timer)
{ {
if (timer < 0 || timer > 600) if (timer < 0 || timer > 600)
return; return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var conf = uow.GuildConfigs.For(id, set => set); var conf = uow.GuildConfigs.For(guildId, set => set);
conf.AutoDeleteByeMessagesTimer = timer; conf.AutoDeleteByeMessagesTimer = timer;
var toAdd = GreetSettings.Create(conf);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
} }

View File

@ -0,0 +1,436 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class UserPunishCommands : NadekoSubmodule
{
private async Task<PunishmentAction?> InternalWarn(IGuild guild, ulong userId, string modName, string reason)
{
if (string.IsNullOrWhiteSpace(reason))
reason = "-";
var guildId = guild.Id;
var warn = new Warning()
{
UserId = userId,
GuildId = guildId,
Forgiven = false,
Reason = reason,
Moderator = modName,
};
int warnings = 1;
List<WarningPunishment> ps;
using (var uow = DbHandler.UnitOfWork())
{
ps = uow.GuildConfigs.For(guildId, set => set.Include(x => x.WarnPunishments))
.WarnPunishments;
warnings += uow.Warnings
.For(guildId, userId)
.Where(w => !w.Forgiven && w.UserId == userId)
.Count();
uow.Warnings.Add(warn);
uow.Complete();
}
var p = ps.FirstOrDefault(x => x.Count == warnings);
if (p != null)
{
var user = await guild.GetUserAsync(userId);
if (user == null)
return null;
switch (p.Punishment)
{
case PunishmentAction.Mute:
if (p.Time == 0)
await MuteCommands.MuteUser(user).ConfigureAwait(false);
else
await MuteCommands.TimedMute(user, TimeSpan.FromMinutes(p.Time)).ConfigureAwait(false);
break;
case PunishmentAction.Kick:
await user.KickAsync().ConfigureAwait(false);
break;
case PunishmentAction.Ban:
await guild.AddBanAsync(user).ConfigureAwait(false);
break;
case PunishmentAction.Softban:
await guild.AddBanAsync(user).ConfigureAwait(false);
try
{
await guild.RemoveBanAsync(user).ConfigureAwait(false);
}
catch
{
await guild.RemoveBanAsync(user).ConfigureAwait(false);
}
break;
default:
break;
}
return p.Punishment;
}
return null;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
public async Task Warn(IGuildUser user, [Remainder] string reason = null)
{
try
{
await (await user.CreateDMChannelAsync()).EmbedAsync(new EmbedBuilder().WithErrorColor()
.WithDescription(GetText("warned_on", Context.Guild.ToString()))
.AddField(efb => efb.WithName(GetText("moderator")).WithValue(Context.User.ToString()))
.AddField(efb => efb.WithName(GetText("reason")).WithValue(reason ?? "-")))
.ConfigureAwait(false);
}
catch { }
var punishment = await InternalWarn(Context.Guild, user.Id, Context.User.ToString(), reason).ConfigureAwait(false);
if (punishment == null)
{
await ReplyConfirmLocalized("user_warned", Format.Bold(user.ToString())).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("user_warned_and_punished", Format.Bold(user.ToString()), Format.Bold(punishment.ToString())).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
public Task Warnlog(int page, IGuildUser user)
=> Warnlog(page, user.Id);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
public Task Warnlog(IGuildUser user)
=> Warnlog(user.Id);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
public Task Warnlog(int page, ulong userId)
=> InternalWarnlog(userId, page - 1);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
public Task Warnlog(ulong userId)
=> InternalWarnlog(userId, 0);
private async Task InternalWarnlog(ulong userId, int page)
{
if (page < 0)
return;
Warning[] warnings;
using (var uow = DbHandler.UnitOfWork())
{
warnings = uow.Warnings.For(Context.Guild.Id, userId);
}
warnings = warnings.Skip(page * 9)
.Take(9)
.ToArray();
var embed = new EmbedBuilder().WithOkColor()
.WithTitle(GetText("warnlog_for", (Context.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString()))
.WithFooter(efb => efb.WithText(GetText("page", page + 1)));
if (!warnings.Any())
{
embed.WithDescription(GetText("warnings_none"));
}
else
{
foreach (var w in warnings)
{
var name = GetText("warned_on_by", w.DateAdded.Value.ToString("dd.MM.yyy"), w.DateAdded.Value.ToString("HH:mm"), w.Moderator);
if (w.Forgiven)
name = Format.Strikethrough(name) + " " + GetText("warn_cleared_by", w.ForgivenBy);
embed.AddField(x => x
.WithName(name)
.WithValue(w.Reason));
}
}
await Context.Channel.EmbedAsync(embed);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
public Task Warnclear(IGuildUser user)
=> Warnclear(user.Id);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
public async Task Warnclear(ulong userId)
{
using (var uow = DbHandler.UnitOfWork())
{
await uow.Warnings.ForgiveAll(Context.Guild.Id, userId, Context.User.ToString()).ConfigureAwait(false);
uow.Complete();
}
await ReplyConfirmLocalized("warnings_cleared",
Format.Bold((Context.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
public async Task WarnPunish(int number, PunishmentAction punish, int time = 0)
{
if (punish != PunishmentAction.Mute && time != 0)
return;
if (number <= 0)
return;
using (var uow = DbHandler.UnitOfWork())
{
var ps = uow.GuildConfigs.For(Context.Guild.Id).WarnPunishments;
var p = ps.FirstOrDefault(x => x.Count == number);
if (p == null)
{
ps.Add(new WarningPunishment()
{
Count = number,
Punishment = punish,
Time = time,
});
}
else
{
p.Count = number;
p.Punishment = punish;
p.Time = time;
uow._context.Update(p);
}
uow.Complete();
}
await ReplyConfirmLocalized("warn_punish_set",
Format.Bold(punish.ToString()),
Format.Bold(number.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
public async Task WarnPunish(int number)
{
if (number <= 0)
return;
using (var uow = DbHandler.UnitOfWork())
{
var ps = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
var p = ps.FirstOrDefault(x => x.Count == number);
if (p != null)
{
uow._context.Remove(p);
uow.Complete();
}
}
await ReplyConfirmLocalized("warn_punish_rem",
Format.Bold(number.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WarnPunishList()
{
WarningPunishment[] ps;
using (var uow = DbHandler.UnitOfWork())
{
ps = uow.GuildConfigs.For(Context.Guild.Id, gc => gc.Include(x => x.WarnPunishments))
.WarnPunishments
.OrderBy(x => x.Count)
.ToArray();
}
string list;
if (ps.Any())
{
list = string.Join("\n", ps.Select(x => $"{x.Count} -> {x.Punishment}"));
}
else
{
list = GetText("warnpl_none");
}
await Context.Channel.SendConfirmAsync(
GetText("warn_punish_list"),
list).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
[RequireBotPermission(GuildPermission.BanMembers)]
public async Task Ban(IGuildUser user, [Remainder] string msg = null)
{
if (Context.User.Id != user.Guild.OwnerId && (user.GetRoles().Select(r => r.Position).Max() >= ((IGuildUser)Context.User).GetRoles().Select(r => r.Position).Max()))
{
await ReplyErrorLocalized("hierarchy").ConfigureAwait(false);
return;
}
if (!string.IsNullOrWhiteSpace(msg))
{
try
{
await user.SendErrorAsync(GetText("bandm", Format.Bold(Context.Guild.Name), msg));
}
catch
{
// ignored
}
}
await Context.Guild.AddBanAsync(user, 7).ConfigureAwait(false);
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle("⛔️ " + GetText("banned_user"))
.AddField(efb => efb.WithName(GetText("username")).WithValue(user.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("ID").WithValue(user.Id.ToString()).WithIsInline(true)))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
[RequireBotPermission(GuildPermission.BanMembers)]
public async Task Unban([Remainder]string user)
{
var bans = await Context.Guild.GetBansAsync();
var bun = bans.FirstOrDefault(x => x.User.ToString().ToLowerInvariant() == user.ToLowerInvariant());
if (bun == null)
{
await ReplyErrorLocalized("user_not_found").ConfigureAwait(false);
return;
}
await UnbanInternal(bun.User).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
[RequireBotPermission(GuildPermission.BanMembers)]
public async Task Unban(ulong userId)
{
var bans = await Context.Guild.GetBansAsync();
var bun = bans.FirstOrDefault(x => x.User.Id == userId);
if (bun == null)
{
await ReplyErrorLocalized("user_not_found").ConfigureAwait(false);
return;
}
await UnbanInternal(bun.User).ConfigureAwait(false);
}
private async Task UnbanInternal(IUser user)
{
await Context.Guild.RemoveBanAsync(user).ConfigureAwait(false);
await ReplyConfirmLocalized("unbanned_user", Format.Bold(user.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.KickMembers)]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireBotPermission(GuildPermission.BanMembers)]
public async Task Softban(IGuildUser user, [Remainder] string msg = null)
{
if (Context.User.Id != user.Guild.OwnerId && user.GetRoles().Select(r => r.Position).Max() >= ((IGuildUser)Context.User).GetRoles().Select(r => r.Position).Max())
{
await ReplyErrorLocalized("hierarchy").ConfigureAwait(false);
return;
}
if (!string.IsNullOrWhiteSpace(msg))
{
try
{
await user.SendErrorAsync(GetText("sbdm", Format.Bold(Context.Guild.Name), msg));
}
catch
{
// ignored
}
}
await Context.Guild.AddBanAsync(user, 7).ConfigureAwait(false);
try { await Context.Guild.RemoveBanAsync(user).ConfigureAwait(false); }
catch { await Context.Guild.RemoveBanAsync(user).ConfigureAwait(false); }
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle("☣ " + GetText("sb_user"))
.AddField(efb => efb.WithName(GetText("username")).WithValue(user.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("ID").WithValue(user.Id.ToString()).WithIsInline(true)))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.KickMembers)]
[RequireBotPermission(GuildPermission.KickMembers)]
public async Task Kick(IGuildUser user, [Remainder] string msg = null)
{
if (Context.Message.Author.Id != user.Guild.OwnerId && user.GetRoles().Select(r => r.Position).Max() >= ((IGuildUser)Context.User).GetRoles().Select(r => r.Position).Max())
{
await ReplyErrorLocalized("hierarchy").ConfigureAwait(false);
return;
}
if (!string.IsNullOrWhiteSpace(msg))
{
try
{
await user.SendErrorAsync(GetText("kickdm", Format.Bold(Context.Guild.Name), msg));
}
catch { }
}
await user.KickAsync().ConfigureAwait(false);
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("kicked_user"))
.AddField(efb => efb.WithName(GetText("username")).WithValue(user.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("ID").WithValue(user.Id.ToString()).WithIsInline(true)))
.ConfigureAwait(false);
}
}
}
}

View File

@ -0,0 +1,186 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using System.Threading.Tasks;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class VcRoleCommands : NadekoSubmodule
{
private static ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> vcRoles { get; }
static VcRoleCommands()
{
NadekoBot.Client.UserVoiceStateUpdated += ClientOnUserVoiceStateUpdated;
vcRoles = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>>();
foreach (var gconf in NadekoBot.AllGuildConfigs)
{
var g = NadekoBot.Client.GetGuild(gconf.GuildId);
if (g == null)
continue; //todo delete everything from db if guild doesn't exist?
var infos = new ConcurrentDictionary<ulong, IRole>();
vcRoles.TryAdd(gconf.GuildId, infos);
foreach (var ri in gconf.VcRoleInfos)
{
var role = g.GetRole(ri.RoleId);
if (role == null)
continue; //todo remove this entry from db
infos.TryAdd(ri.VoiceChannelId, role);
}
}
}
private static Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState,
SocketVoiceState newState)
{
var gusr = usr as SocketGuildUser;
if (gusr == null)
return Task.CompletedTask;
var oldVc = oldState.VoiceChannel;
var newVc = newState.VoiceChannel;
var _ = Task.Run(async () =>
{
try
{
if (oldVc != newVc)
{
ulong guildId;
guildId = newVc?.Guild.Id ?? oldVc.Guild.Id;
ConcurrentDictionary<ulong, IRole> guildVcRoles;
if (vcRoles.TryGetValue(guildId, out guildVcRoles))
{
IRole role;
//remove old
if (oldVc != null && guildVcRoles.TryGetValue(oldVc.Id, out role))
{
if (gusr.RoleIds.Contains(role.Id))
{
try
{
await gusr.RemoveRolesAsync(role).ConfigureAwait(false);
await Task.Delay(500).ConfigureAwait(false);
}
catch
{
await Task.Delay(200).ConfigureAwait(false);
await gusr.RemoveRolesAsync(role).ConfigureAwait(false);
await Task.Delay(500).ConfigureAwait(false);
}
}
}
//add new
if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role))
{
if (!gusr.RoleIds.Contains(role.Id))
await gusr.AddRolesAsync(role).ConfigureAwait(false);
}
}
}
}
catch (Exception ex)
{
Administration._log.Warn(ex);
}
});
return Task.CompletedTask;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageRoles)]
// todo wait for the fix [RequireBotPermission(GuildPermission.ManageChannels)]
[RequireContext(ContextType.Guild)]
public async Task VcRole([Remainder]IRole role = null)
{
var user = (IGuildUser) Context.User;
var vc = user.VoiceChannel;
if (vc == null || vc.GuildId != user.GuildId)
{
await ReplyErrorLocalized("must_be_in_voice").ConfigureAwait(false);
return;
}
var guildVcRoles = vcRoles.GetOrAdd(user.GuildId, new ConcurrentDictionary<ulong, IRole>());
if (role == null)
{
if (guildVcRoles.TryRemove(vc.Id, out role))
{
await ReplyConfirmLocalized("vcrole_removed", Format.Bold(vc.Name)).ConfigureAwait(false);
using (var uow = DbHandler.UnitOfWork())
{
var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.VcRoleInfos));
conf.VcRoleInfos.RemoveWhere(x => x.VoiceChannelId == vc.Id);
uow.Complete();
}
}
}
else
{
guildVcRoles.AddOrUpdate(vc.Id, role, (key, old) => role);
using (var uow = DbHandler.UnitOfWork())
{
var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.VcRoleInfos));
conf.VcRoleInfos.RemoveWhere(x => x.VoiceChannelId == vc.Id); // remove old one
conf.VcRoleInfos.Add(new VcRoleInfo()
{
VoiceChannelId = vc.Id,
RoleId = role.Id,
}); // add new one
uow.Complete();
}
await ReplyConfirmLocalized("vcrole_added", Format.Bold(vc.Name), Format.Bold(role.Name)).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task VcRoleList()
{
var guild = (SocketGuild) Context.Guild;
string text;
ConcurrentDictionary<ulong, IRole> roles;
if (vcRoles.TryGetValue(Context.Guild.Id, out roles))
{
if (!roles.Any())
{
text = GetText("no_vcroles");
}
else
{
text = string.Join("\n", roles.Select(x =>
$"{Format.Bold(guild.GetVoiceChannel(x.Key)?.Name ?? x.Key.ToString())} => {x.Value}"));
}
}
else
{
text = GetText("no_vcroles");
}
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("vc_role_list"))
.WithDescription(text))
.ConfigureAwait(false);
}
}
}
}

View File

@ -7,9 +7,11 @@ using NadekoBot.Services;
using NLog; using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
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.Administration namespace NadekoBot.Modules.Administration
@ -17,96 +19,141 @@ namespace NadekoBot.Modules.Administration
public partial class Administration public partial class Administration
{ {
[Group] [Group]
public class VoicePlusTextCommands : ModuleBase public class VoicePlusTextCommands : NadekoSubmodule
{ {
private static Regex channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled); private new static readonly Logger _log;
private static ConcurrentHashSet<ulong> voicePlusTextCache { get; } private static readonly Regex _channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled);
private static readonly ConcurrentHashSet<ulong> _voicePlusTextCache;
private static readonly ConcurrentDictionary<ulong, SemaphoreSlim> _guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>();
static VoicePlusTextCommands() static VoicePlusTextCommands()
{ {
var _log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
using (var uow = DbHandler.UnitOfWork())
{ _voicePlusTextCache = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.VoicePlusTextEnabled).Select(g => g.GuildId));
voicePlusTextCache = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.VoicePlusTextEnabled).Select(g => g.GuildId));
}
NadekoBot.Client.UserVoiceStateUpdated += UserUpdatedEventHandler; NadekoBot.Client.UserVoiceStateUpdated += UserUpdatedEventHandler;
sw.Stop(); sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
} }
private static async void UserUpdatedEventHandler(SocketUser iuser, SocketVoiceState before, SocketVoiceState after) private static Task UserUpdatedEventHandler(SocketUser iuser, SocketVoiceState before, SocketVoiceState after)
{ {
var user = (iuser as SocketGuildUser); var user = (iuser as SocketGuildUser);
var guild = user?.Guild; var guild = user?.Guild;
if (guild == null) if (guild == null)
return; return Task.CompletedTask;
try
{
var botUserPerms = guild.CurrentUser.GuildPermissions; var botUserPerms = guild.CurrentUser.GuildPermissions;
if (before.VoiceChannel == after.VoiceChannel) return; if (before.VoiceChannel == after.VoiceChannel)
return Task.CompletedTask;
if (!voicePlusTextCache.Contains(guild.Id)) if (!_voicePlusTextCache.Contains(guild.Id))
return; return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
if (!botUserPerms.ManageChannels || !botUserPerms.ManageRoles) if (!botUserPerms.ManageChannels || !botUserPerms.ManageRoles)
{ {
try try
{ {
await (await guild.GetOwnerAsync()).SendErrorAsync( await guild.Owner.SendErrorAsync(
"⚠️ I don't have **manage server** and/or **manage channels** permission," + GetTextStatic("vt_exit",
$" so I cannot run `voice+text` on **{guild.Name}** server.").ConfigureAwait(false); NadekoBot.Localization.GetCultureInfo(guild),
typeof(Administration).Name.ToLowerInvariant(),
Format.Bold(guild.Name))).ConfigureAwait(false);
}
catch
{
// ignored
} }
catch { }
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
uow.GuildConfigs.For(guild.Id, set => set).VoicePlusTextEnabled = false; uow.GuildConfigs.For(guild.Id, set => set).VoicePlusTextEnabled = false;
voicePlusTextCache.TryRemove(guild.Id); _voicePlusTextCache.TryRemove(guild.Id);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
return; return;
} }
var semaphore = _guildLockObjects.GetOrAdd(guild.Id, (key) => new SemaphoreSlim(1, 1));
try
{
await semaphore.WaitAsync().ConfigureAwait(false);
var beforeVch = before.VoiceChannel; var beforeVch = before.VoiceChannel;
if (beforeVch != null) if (beforeVch != null)
{ {
var textChannel = (await guild.GetTextChannelsAsync()).Where(t => t.Name == GetChannelName(beforeVch.Name).ToLowerInvariant()).FirstOrDefault(); var beforeRoleName = GetRoleName(beforeVch);
if (textChannel != null) var beforeRole = guild.Roles.FirstOrDefault(x => x.Name == beforeRoleName);
await textChannel.AddPermissionOverwriteAsync(user, if (beforeRole != null)
new OverwritePermissions(readMessages: PermValue.Deny, try
sendMessages: PermValue.Deny)).ConfigureAwait(false); {
_log.Info("Removing role " + beforeRoleName + " from user " + user.Username);
await user.RemoveRolesAsync(beforeRole).ConfigureAwait(false);
await Task.Delay(200).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
} }
var afterVch = after.VoiceChannel; var afterVch = after.VoiceChannel;
if (afterVch != null && guild.AFKChannelId != afterVch.Id) if (afterVch != null && guild.AFKChannel?.Id != afterVch.Id)
{ {
var textChannel = (await guild.GetTextChannelsAsync()) var roleName = GetRoleName(afterVch);
.Where(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant()) var roleToAdd = guild.Roles.FirstOrDefault(x => x.Name == roleName) ??
.FirstOrDefault(); (IRole) await guild.CreateRoleAsync(roleName, GuildPermissions.None).ConfigureAwait(false);
ITextChannel textChannel = guild.TextChannels
.FirstOrDefault(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant());
if (textChannel == null) if (textChannel == null)
{ {
textChannel = (await guild.CreateTextChannelAsync(GetChannelName(afterVch.Name).ToLowerInvariant()).ConfigureAwait(false)); var created = (await guild.CreateTextChannelAsync(GetChannelName(afterVch.Name).ToLowerInvariant()).ConfigureAwait(false));
await textChannel.AddPermissionOverwriteAsync(guild.EveryoneRole,
new OverwritePermissions(readMessages: PermValue.Deny, try { await guild.CurrentUser.AddRolesAsync(roleToAdd).ConfigureAwait(false); } catch {/*ignored*/}
sendMessages: PermValue.Deny)).ConfigureAwait(false); await Task.Delay(50).ConfigureAwait(false);
await created.AddPermissionOverwriteAsync(roleToAdd, new OverwritePermissions(
readMessages: PermValue.Allow,
sendMessages: PermValue.Allow))
.ConfigureAwait(false);
await Task.Delay(50).ConfigureAwait(false);
await created.AddPermissionOverwriteAsync(guild.EveryoneRole, new OverwritePermissions(
readMessages: PermValue.Deny,
sendMessages: PermValue.Deny))
.ConfigureAwait(false);
await Task.Delay(50).ConfigureAwait(false);
} }
await textChannel.AddPermissionOverwriteAsync(user, _log.Warn("Adding role " + roleToAdd.Name + " to user " + user.Username);
new OverwritePermissions(readMessages: PermValue.Allow, await user.AddRolesAsync(roleToAdd).ConfigureAwait(false);
sendMessages: PermValue.Allow)).ConfigureAwait(false); }
}
finally
{
semaphore.Release();
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex); _log.Warn(ex);
} }
});
return Task.CompletedTask;
} }
private static string GetChannelName(string voiceName) => private static string GetChannelName(string voiceName) =>
channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice"; _channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice";
private static string GetRoleName(IVoiceChannel ch) =>
"nvoice-" + ch.Id;
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
@ -119,7 +166,7 @@ namespace NadekoBot.Modules.Administration
var botUser = await guild.GetCurrentUserAsync().ConfigureAwait(false); var botUser = await guild.GetCurrentUserAsync().ConfigureAwait(false);
if (!botUser.GuildPermissions.ManageRoles || !botUser.GuildPermissions.ManageChannels) if (!botUser.GuildPermissions.ManageRoles || !botUser.GuildPermissions.ManageChannels)
{ {
await Context.Channel.SendErrorAsync("I require atleast **manage roles** and **manage channels permissions** to enable this feature. `(preffered Administration permission)`"); await ReplyErrorLocalized("vt_perms").ConfigureAwait(false);
return; return;
} }
@ -127,10 +174,12 @@ namespace NadekoBot.Modules.Administration
{ {
try try
{ {
await Context.Channel.SendErrorAsync("⚠️ You are enabling this feature and **I do not have ADMINISTRATOR permissions**. " + await ReplyErrorLocalized("vt_no_admin").ConfigureAwait(false);
"`This may cause some issues, and you will have to clean up text channels yourself afterwards.`"); }
catch
{
// ignored
} }
catch { }
} }
try try
{ {
@ -143,16 +192,23 @@ namespace NadekoBot.Modules.Administration
} }
if (!isEnabled) if (!isEnabled)
{ {
voicePlusTextCache.TryRemove(guild.Id); _voicePlusTextCache.TryRemove(guild.Id);
foreach (var textChannel in (await guild.GetTextChannelsAsync().ConfigureAwait(false)).Where(c => c.Name.EndsWith("-voice"))) foreach (var textChannel in (await guild.GetTextChannelsAsync().ConfigureAwait(false)).Where(c => c.Name.EndsWith("-voice")))
{ {
try { await textChannel.DeleteAsync().ConfigureAwait(false); } catch { } try { await textChannel.DeleteAsync().ConfigureAwait(false); } catch { }
await Task.Delay(500).ConfigureAwait(false);
} }
await Context.Channel.SendConfirmAsync(" Successfuly **removed** voice + text feature.").ConfigureAwait(false);
foreach (var role in guild.Roles.Where(c => c.Name.StartsWith("nvoice-")))
{
try { await role.DeleteAsync().ConfigureAwait(false); } catch { }
await Task.Delay(500).ConfigureAwait(false);
}
await ReplyConfirmLocalized("vt_disabled").ConfigureAwait(false);
return; return;
} }
voicePlusTextCache.Add(guild.Id); _voicePlusTextCache.Add(guild.Id);
await Context.Channel.SendConfirmAsync("🆗 Successfuly **enabled** voice + text feature.").ConfigureAwait(false); await ReplyConfirmLocalized("vt_enabled").ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
@ -163,29 +219,43 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)] [RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
//[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task CleanVPlusT() public async Task CleanVPlusT()
{ {
var guild = Context.Guild; var guild = Context.Guild;
var botUser = await guild.GetCurrentUserAsync().ConfigureAwait(false); var botUser = await guild.GetCurrentUserAsync().ConfigureAwait(false);
if (!botUser.GuildPermissions.Administrator) if (!botUser.GuildPermissions.Administrator)
{ {
await Context.Channel.SendErrorAsync("I need **Administrator permission** to do that.").ConfigureAwait(false); await ReplyErrorLocalized("need_admin").ConfigureAwait(false);
return; return;
} }
var allTxtChannels = (await guild.GetTextChannelsAsync()).Where(c => c.Name.EndsWith("-voice")); var textChannels = await guild.GetTextChannelsAsync().ConfigureAwait(false);
var validTxtChannelNames = (await guild.GetVoiceChannelsAsync()).Select(c => GetChannelName(c.Name).ToLowerInvariant()); var voiceChannels = await guild.GetVoiceChannelsAsync().ConfigureAwait(false);
var invalidTxtChannels = allTxtChannels.Where(c => !validTxtChannelNames.Contains(c.Name)); var boundTextChannels = textChannels.Where(c => c.Name.EndsWith("-voice"));
var validTxtChannelNames = new HashSet<string>(voiceChannels.Select(c => GetChannelName(c.Name).ToLowerInvariant()));
var invalidTxtChannels = boundTextChannels.Where(c => !validTxtChannelNames.Contains(c.Name));
foreach (var c in invalidTxtChannels) foreach (var c in invalidTxtChannels)
{ {
try { await c.DeleteAsync().ConfigureAwait(false); } catch { } try { await c.DeleteAsync().ConfigureAwait(false); } catch { }
await Task.Delay(500); await Task.Delay(500).ConfigureAwait(false);
} }
await Context.Channel.SendConfirmAsync("Cleaned v+t.").ConfigureAwait(false); var boundRoles = guild.Roles.Where(r => r.Name.StartsWith("nvoice-"));
var validRoleNames = new HashSet<string>(voiceChannels.Select(c => GetRoleName(c).ToLowerInvariant()));
var invalidRoles = boundRoles.Where(r => !validRoleNames.Contains(r.Name));
foreach (var r in invalidRoles)
{
try { await r.DeleteAsync().ConfigureAwait(false); } catch { }
await Task.Delay(500).ConfigureAwait(false);
}
await ReplyConfirmLocalized("cleaned_up").ConfigureAwait(false);
} }
} }
} }

View File

@ -11,24 +11,18 @@ using NadekoBot.Services.Database.Models;
using System.Linq; using System.Linq;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using System.Threading; using System.Threading;
using System.Diagnostics;
using NLog;
namespace NadekoBot.Modules.ClashOfClans namespace NadekoBot.Modules.ClashOfClans
{ {
[NadekoModule("ClashOfClans", ",")] [NadekoModule("ClashOfClans", ",")]
public class ClashOfClans : DiscordModule public class ClashOfClans : NadekoTopLevelModule
{ {
public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; } = new ConcurrentDictionary<ulong, List<ClashWar>>(); public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; }
private static Timer checkWarTimer { get; } private static Timer checkWarTimer { get; }
private static new readonly Logger _log;
static ClashOfClans() static ClashOfClans()
{ {
_log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>( ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>(
@ -36,10 +30,8 @@ namespace NadekoBot.Modules.ClashOfClans
.GetAllWars() .GetAllWars()
.Select(cw => .Select(cw =>
{ {
cw.Channel = NadekoBot.Client.GetGuild(cw.GuildId) cw.Channel = NadekoBot.Client.GetGuild(cw.GuildId)?
?.GetTextChannelAsync(cw.ChannelId) .GetTextChannel(cw.ChannelId);
.GetAwaiter()
.GetResult();
return cw; return cw;
}) })
.Where(cw => cw.Channel != null) .Where(cw => cw.Channel != null)
@ -75,7 +67,11 @@ namespace NadekoBot.Modules.ClashOfClans
try try
{ {
SaveWar(war); SaveWar(war);
await war.Channel.SendErrorAsync($"❗🔰**Claim from @{Bases[i].CallUser} for a war against {war.ShortPrint()} has expired.**").ConfigureAwait(false); await war.Channel.SendErrorAsync(GetTextStatic("claim_expired",
NadekoBot.Localization.GetCultureInfo(war.Channel.GuildId),
typeof(ClashOfClans).Name.ToLowerInvariant(),
Format.Bold(Bases[i].CallUser),
war.ShortPrint()));
} }
catch { } catch { }
} }
@ -84,17 +80,15 @@ namespace NadekoBot.Modules.ClashOfClans
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task CreateWar(int size, [Remainder] string enemyClan = null) public async Task CreateWar(int size, [Remainder] string enemyClan = null)
{ {
if (!(Context.User as IGuildUser).GuildPermissions.ManageChannels)
return;
if (string.IsNullOrWhiteSpace(enemyClan)) if (string.IsNullOrWhiteSpace(enemyClan))
return; return;
if (size < 10 || size > 50 || size % 5 != 0) if (size < 10 || size > 50 || size % 5 != 0)
{ {
await Context.Channel.SendErrorAsync("🔰 Not a Valid war size").ConfigureAwait(false); await ReplyErrorLocalized("invalid_size").ConfigureAwait(false);
return; return;
} }
List<ClashWar> wars; List<ClashWar> wars;
@ -109,7 +103,7 @@ namespace NadekoBot.Modules.ClashOfClans
var cw = await CreateWar(enemyClan, size, Context.Guild.Id, Context.Channel.Id); var cw = await CreateWar(enemyClan, size, Context.Guild.Id, Context.Channel.Id);
wars.Add(cw); wars.Add(cw);
await Context.Channel.SendConfirmAsync($"❗🔰**CREATED CLAN WAR AGAINST {cw.ShortPrint()}**").ConfigureAwait(false); await ReplyErrorLocalized("war_created", cw.ShortPrint()).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -122,18 +116,18 @@ namespace NadekoBot.Modules.ClashOfClans
var warsInfo = GetWarInfo(Context.Guild, num); var warsInfo = GetWarInfo(Context.Guild, num);
if (warsInfo == null) if (warsInfo == null)
{ {
await Context.Channel.SendErrorAsync("🔰 **That war does not exist.**").ConfigureAwait(false); await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return; return;
} }
var war = warsInfo.Item1[warsInfo.Item2]; var war = warsInfo.Item1[warsInfo.Item2];
try try
{ {
war.Start(); war.Start();
await Context.Channel.SendConfirmAsync($"🔰**STARTED WAR AGAINST {war.ShortPrint()}**").ConfigureAwait(false); await ReplyConfirmLocalized("war_started", war.ShortPrint()).ConfigureAwait(false);
} }
catch catch
{ {
await Context.Channel.SendErrorAsync($"🔰**WAR AGAINST {war.ShortPrint()} HAS ALREADY STARTED**").ConfigureAwait(false); await ReplyErrorLocalized("war_already_started", war.ShortPrint()).ConfigureAwait(false);
} }
SaveWar(war); SaveWar(war);
} }
@ -151,22 +145,20 @@ namespace NadekoBot.Modules.ClashOfClans
ClashWars.TryGetValue(Context.Guild.Id, out wars); ClashWars.TryGetValue(Context.Guild.Id, out wars);
if (wars == null || wars.Count == 0) if (wars == null || wars.Count == 0)
{ {
await Context.Channel.SendErrorAsync("🔰 **No active wars.**").ConfigureAwait(false); await ReplyErrorLocalized("no_active_wars").ConfigureAwait(false);
return; return;
} }
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine("🔰 **LIST OF ACTIVE WARS**");
sb.AppendLine("**-------------------------**"); sb.AppendLine("**-------------------------**");
for (var i = 0; i < wars.Count; i++) for (var i = 0; i < wars.Count; i++)
{ {
sb.AppendLine($"**#{i + 1}.** `Enemy:` **{wars[i].EnemyClan}**"); sb.AppendLine($"**#{i + 1}.** `{GetText("enemy")}:` **{wars[i].EnemyClan}**");
sb.AppendLine($"\t\t`Size:` **{wars[i].Size} v {wars[i].Size}**"); sb.AppendLine($"\t\t`{GetText("size")}:` **{wars[i].Size} v {wars[i].Size}**");
sb.AppendLine("**-------------------------**"); sb.AppendLine("**-------------------------**");
} }
await Context.Channel.SendConfirmAsync(sb.ToString()).ConfigureAwait(false); await Context.Channel.SendConfirmAsync(GetText("list_active_wars"), sb.ToString()).ConfigureAwait(false);
return; return;
} }
var num = 0; var num = 0;
int.TryParse(number, out num); int.TryParse(number, out num);
@ -174,10 +166,11 @@ namespace NadekoBot.Modules.ClashOfClans
var warsInfo = GetWarInfo(Context.Guild, num); var warsInfo = GetWarInfo(Context.Guild, num);
if (warsInfo == null) if (warsInfo == null)
{ {
await Context.Channel.SendErrorAsync("🔰 **That war does not exist.**").ConfigureAwait(false); await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return; return;
} }
await Context.Channel.SendConfirmAsync(warsInfo.Item1[warsInfo.Item2].ToPrettyString()).ConfigureAwait(false); var war = warsInfo.Item1[warsInfo.Item2];
await Context.Channel.SendConfirmAsync(war.Localize("info_about_war", $"`{war.EnemyClan}` ({war.Size} v {war.Size})"), war.ToPrettyString()).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -187,7 +180,7 @@ namespace NadekoBot.Modules.ClashOfClans
var warsInfo = GetWarInfo(Context.Guild, number); var warsInfo = GetWarInfo(Context.Guild, number);
if (warsInfo == null || warsInfo.Item1.Count == 0) if (warsInfo == null || warsInfo.Item1.Count == 0)
{ {
await Context.Channel.SendErrorAsync("🔰 **That war does not exist.**").ConfigureAwait(false); await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return; return;
} }
var usr = var usr =
@ -199,11 +192,11 @@ namespace NadekoBot.Modules.ClashOfClans
var war = warsInfo.Item1[warsInfo.Item2]; var war = warsInfo.Item1[warsInfo.Item2];
war.Call(usr, baseNumber - 1); war.Call(usr, baseNumber - 1);
SaveWar(war); SaveWar(war);
await Context.Channel.SendConfirmAsync($"🔰**{usr}** claimed a base #{baseNumber} for a war against {war.ShortPrint()}").ConfigureAwait(false); await ConfirmLocalized("claimed_base", Format.Bold(usr.ToString()), baseNumber, war.ShortPrint()).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
await Context.Channel.SendErrorAsync($"🔰 {ex.Message}").ConfigureAwait(false); await Context.Channel.SendErrorAsync(ex.Message).ConfigureAwait(false);
} }
} }
@ -235,15 +228,14 @@ namespace NadekoBot.Modules.ClashOfClans
var warsInfo = GetWarInfo(Context.Guild, number); var warsInfo = GetWarInfo(Context.Guild, number);
if (warsInfo == null) if (warsInfo == null)
{ {
await Context.Channel.SendErrorAsync("🔰 That war does not exist.").ConfigureAwait(false); await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return; return;
} }
var war = warsInfo.Item1[warsInfo.Item2]; var war = warsInfo.Item1[warsInfo.Item2];
war.End(); war.End();
SaveWar(war); SaveWar(war);
await Context.Channel.SendConfirmAsync($"❗🔰**War against {warsInfo.Item1[warsInfo.Item2].ShortPrint()} ended.**").ConfigureAwait(false); await ReplyConfirmLocalized("war_ended", warsInfo.Item1[warsInfo.Item2].ShortPrint()).ConfigureAwait(false);
var size = warsInfo.Item1[warsInfo.Item2].Size;
warsInfo.Item1.RemoveAt(warsInfo.Item2); warsInfo.Item1.RemoveAt(warsInfo.Item2);
} }
@ -254,7 +246,7 @@ namespace NadekoBot.Modules.ClashOfClans
var warsInfo = GetWarInfo(Context.Guild, number); var warsInfo = GetWarInfo(Context.Guild, number);
if (warsInfo == null || warsInfo.Item1.Count == 0) if (warsInfo == null || warsInfo.Item1.Count == 0)
{ {
await Context.Channel.SendErrorAsync("🔰 **That war does not exist.**").ConfigureAwait(false); await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return; return;
} }
var usr = var usr =
@ -266,11 +258,11 @@ namespace NadekoBot.Modules.ClashOfClans
var war = warsInfo.Item1[warsInfo.Item2]; var war = warsInfo.Item1[warsInfo.Item2];
var baseNumber = war.Uncall(usr); var baseNumber = war.Uncall(usr);
SaveWar(war); SaveWar(war);
await Context.Channel.SendConfirmAsync($"🔰 @{usr} has **UNCLAIMED** a base #{baseNumber + 1} from a war against {war.ShortPrint()}").ConfigureAwait(false); await ReplyConfirmLocalized("base_unclaimed", usr, baseNumber + 1, war.ShortPrint()).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
await Context.Channel.SendErrorAsync($"🔰 {ex.Message}").ConfigureAwait(false); await Context.Channel.SendErrorAsync(ex.Message).ConfigureAwait(false);
} }
} }
@ -279,7 +271,7 @@ namespace NadekoBot.Modules.ClashOfClans
var warInfo = GetWarInfo(Context.Guild, number); var warInfo = GetWarInfo(Context.Guild, number);
if (warInfo == null || warInfo.Item1.Count == 0) if (warInfo == null || warInfo.Item1.Count == 0)
{ {
await Context.Channel.SendErrorAsync("🔰 **That war does not exist.**").ConfigureAwait(false); await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return; return;
} }
var war = warInfo.Item1[warInfo.Item2]; var war = warInfo.Item1[warInfo.Item2];
@ -294,7 +286,7 @@ namespace NadekoBot.Modules.ClashOfClans
{ {
war.FinishClaim(baseNumber, stars); war.FinishClaim(baseNumber, stars);
} }
await Context.Channel.SendConfirmAsync($"❗🔰{Context.User.Mention} **DESTROYED** a base #{baseNumber + 1} in a war against {war.ShortPrint()}").ConfigureAwait(false); await ReplyConfirmLocalized("base_destroyed", baseNumber +1, war.ShortPrint()).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -322,7 +314,7 @@ namespace NadekoBot.Modules.ClashOfClans
public static async Task<ClashWar> CreateWar(string enemyClan, int size, ulong serverId, ulong channelId) public static async Task<ClashWar> CreateWar(string enemyClan, int size, ulong serverId, ulong channelId)
{ {
var channel = await NadekoBot.Client.GetGuild(serverId)?.GetTextChannelAsync(channelId); var channel = NadekoBot.Client.GetGuild(serverId)?.GetTextChannel(channelId);
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var cw = new ClashWar var cw = new ClashWar

View File

@ -27,13 +27,13 @@ namespace NadekoBot.Modules.ClashOfClans
public static void Call(this ClashWar cw, string u, int baseNumber) public static void Call(this ClashWar cw, string u, int baseNumber)
{ {
if (baseNumber < 0 || baseNumber >= cw.Bases.Count) if (baseNumber < 0 || baseNumber >= cw.Bases.Count)
throw new ArgumentException("Invalid base number"); throw new ArgumentException(cw.Localize("invalid_base_number"));
if (cw.Bases[baseNumber].CallUser != null && cw.Bases[baseNumber].Stars == 3) if (cw.Bases[baseNumber].CallUser != null && cw.Bases[baseNumber].Stars == 3)
throw new ArgumentException("That base is already destroyed."); throw new ArgumentException(cw.Localize("base_already_claimed"));
for (var i = 0; i < cw.Bases.Count; i++) for (var i = 0; i < cw.Bases.Count; i++)
{ {
if (cw.Bases[i]?.BaseDestroyed == false && cw.Bases[i]?.CallUser == u) if (cw.Bases[i]?.BaseDestroyed == false && cw.Bases[i]?.CallUser == u)
throw new ArgumentException($"@{u} You already claimed base #{i + 1}. You can't claim a new one."); throw new ArgumentException(cw.Localize("claimed_other", u, i + 1));
} }
var cc = cw.Bases[baseNumber]; var cc = cw.Bases[baseNumber];
@ -45,7 +45,7 @@ namespace NadekoBot.Modules.ClashOfClans
public static void Start(this ClashWar cw) public static void Start(this ClashWar cw)
{ {
if (cw.WarState == StateOfWar.Started) if (cw.WarState == StateOfWar.Started)
throw new InvalidOperationException("War already started"); throw new InvalidOperationException("war_already_started");
//if (Started) //if (Started)
// throw new InvalidOperationException(); // throw new InvalidOperationException();
//Started = true; //Started = true;
@ -66,7 +66,7 @@ namespace NadekoBot.Modules.ClashOfClans
cw.Bases[i].CallUser = null; cw.Bases[i].CallUser = null;
return i; return i;
} }
throw new InvalidOperationException("You are not participating in that war."); throw new InvalidOperationException(cw.Localize("not_partic"));
} }
public static string ShortPrint(this ClashWar cw) => public static string ShortPrint(this ClashWar cw) =>
@ -76,7 +76,6 @@ namespace NadekoBot.Modules.ClashOfClans
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine($"🔰**WAR AGAINST `{cw.EnemyClan}` ({cw.Size} v {cw.Size}) INFO:**");
if (cw.WarState == StateOfWar.Created) if (cw.WarState == StateOfWar.Created)
sb.AppendLine("`not started`"); sb.AppendLine("`not started`");
var twoHours = new TimeSpan(2, 0, 0); var twoHours = new TimeSpan(2, 0, 0);
@ -84,7 +83,7 @@ namespace NadekoBot.Modules.ClashOfClans
{ {
if (cw.Bases[i].CallUser == null) if (cw.Bases[i].CallUser == null)
{ {
sb.AppendLine($"`{i + 1}.` ❌*unclaimed*"); sb.AppendLine($"`{i + 1}.` ❌*{cw.Localize("not_claimed")}*");
} }
else else
{ {
@ -120,7 +119,7 @@ namespace NadekoBot.Modules.ClashOfClans
cw.Bases[i].Stars = stars; cw.Bases[i].Stars = stars;
return i; return i;
} }
throw new InvalidOperationException($"@{user} You are either not participating in that war, or you already destroyed a base."); throw new InvalidOperationException(cw.Localize("not_partic_or_destroyed", user));
} }
public static void FinishClaim(this ClashWar cw, int index, int stars = 3) public static void FinishClaim(this ClashWar cw, int index, int stars = 3)
@ -128,10 +127,22 @@ namespace NadekoBot.Modules.ClashOfClans
if (index < 0 || index > cw.Bases.Count) if (index < 0 || index > cw.Bases.Count)
throw new ArgumentOutOfRangeException(nameof(index)); throw new ArgumentOutOfRangeException(nameof(index));
var toFinish = cw.Bases[index]; var toFinish = cw.Bases[index];
if (toFinish.BaseDestroyed != false) throw new InvalidOperationException("That base is already destroyed."); if (toFinish.BaseDestroyed != false) throw new InvalidOperationException(cw.Localize("base_already_destroyed"));
if (toFinish.CallUser == null) throw new InvalidOperationException("That base is unclaimed."); if (toFinish.CallUser == null) throw new InvalidOperationException(cw.Localize("base_already_unclaimed"));
toFinish.BaseDestroyed = true; toFinish.BaseDestroyed = true;
toFinish.Stars = stars; toFinish.Stars = stars;
} }
public static string Localize(this ClashWar cw, string key)
{
return NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(cw.Channel?.GuildId),
typeof(ClashOfClans).Name.ToLowerInvariant());
}
public static string Localize(this ClashWar cw, string key, params object[] replacements)
{
return string.Format(cw.Localize(key), replacements);
}
} }
} }

View File

@ -10,18 +10,38 @@ using NadekoBot.Extensions;
using NLog; using NLog;
using System.Diagnostics; using System.Diagnostics;
using Discord.WebSocket; using Discord.WebSocket;
using System;
using NadekoBot.DataStructures;
namespace NadekoBot.Modules.CustomReactions namespace NadekoBot.Modules.CustomReactions
{ {
[NadekoModule("CustomReactions", ".")] public static class CustomReactionExtensions
public class CustomReactions : DiscordModule
{ {
public static ConcurrentHashSet<CustomReaction> GlobalReactions { get; } = new ConcurrentHashSet<CustomReaction>(); public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage context)
public static ConcurrentDictionary<ulong, ConcurrentHashSet<CustomReaction>> GuildReactions { get; } = new ConcurrentDictionary<ulong, ConcurrentHashSet<CustomReaction>>(); {
var channel = cr.DmResponse ? await context.Author.CreateDMChannelAsync() : context.Channel;
CustomReactions.ReactionStats.AddOrUpdate(cr.Trigger, 1, (k, old) => ++old);
CREmbed crembed;
if (CREmbed.TryParse(cr.Response, out crembed))
{
return await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "");
}
return await channel.SendMessageAsync(cr.ResponseWithContext(context).SanitizeMentions());
}
}
[NadekoModule("CustomReactions", ".")]
public class CustomReactions : NadekoTopLevelModule
{
private static CustomReaction[] _globalReactions = new CustomReaction[] { };
public static CustomReaction[] GlobalReactions => _globalReactions;
public static ConcurrentDictionary<ulong, CustomReaction[]> GuildReactions { get; } = new ConcurrentDictionary<ulong, CustomReaction[]>();
public static ConcurrentDictionary<string, uint> ReactionStats { get; } = new ConcurrentDictionary<string, uint>(); public static ConcurrentDictionary<string, uint> ReactionStats { get; } = new ConcurrentDictionary<string, uint>();
private static new readonly Logger _log; private new static readonly Logger _log;
static CustomReactions() static CustomReactions()
{ {
@ -30,8 +50,8 @@ namespace NadekoBot.Modules.CustomReactions
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
var items = uow.CustomReactions.GetAll(); var items = uow.CustomReactions.GetAll();
GuildReactions = new ConcurrentDictionary<ulong, ConcurrentHashSet<CustomReaction>>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => new ConcurrentHashSet<CustomReaction>(g))); GuildReactions = new ConcurrentDictionary<ulong, CustomReaction[]>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray()));
GlobalReactions = new ConcurrentHashSet<CustomReaction>(items.Where(g => g.GuildId == null || g.GuildId == 0)); _globalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray();
} }
sw.Stop(); sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
@ -39,47 +59,53 @@ namespace NadekoBot.Modules.CustomReactions
public void ClearStats() => ReactionStats.Clear(); public void ClearStats() => ReactionStats.Clear();
public static async Task<bool> TryExecuteCustomReaction(SocketUserMessage umsg) public static CustomReaction TryGetCustomReaction(IUserMessage umsg)
{ {
var channel = umsg.Channel as SocketTextChannel; var channel = umsg.Channel as SocketTextChannel;
if (channel == null) if (channel == null)
return false; return null;
var content = umsg.Content.Trim().ToLowerInvariant(); var content = umsg.Content.Trim().ToLowerInvariant();
ConcurrentHashSet<CustomReaction> reactions; CustomReaction[] reactions;
GuildReactions.TryGetValue(channel.Guild.Id, out reactions); GuildReactions.TryGetValue(channel.Guild.Id, out reactions);
if (reactions != null && reactions.Any()) if (reactions != null && reactions.Any())
{ {
var reaction = reactions.Where(cr => var rs = reactions.Where(cr =>
{ {
if (cr == null)
return false;
var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%"); var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%");
var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant(); var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant();
return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger); return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger);
}).Shuffle().FirstOrDefault(); }).ToArray();
if (rs.Length != 0)
{
var reaction = rs[new NadekoRandom().Next(0, rs.Length)];
if (reaction != null) if (reaction != null)
{ {
if (reaction.Response != "-") if (reaction.Response == "-")
try { await channel.SendMessageAsync(reaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { } return null;
return reaction;
}
}
}
ReactionStats.AddOrUpdate(reaction.Trigger, 1, (k, old) => ++old); var grs = GlobalReactions.Where(cr =>
return true;
}
}
var greaction = GlobalReactions.Where(cr =>
{ {
if (cr == null)
return false;
var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%"); var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%");
var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant(); var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant();
return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger); return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger);
}).Shuffle().FirstOrDefault(); }).ToArray();
if (grs.Length == 0)
return null;
var greaction = grs[new NadekoRandom().Next(0, grs.Length)];
if (greaction != null) return greaction;
{
try { await channel.SendMessageAsync(greaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { }
ReactionStats.AddOrUpdate(greaction.Trigger, 1, (k, old) => ++old);
return true;
}
return false;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -93,7 +119,7 @@ namespace NadekoBot.Modules.CustomReactions
if ((channel == null && !NadekoBot.Credentials.IsOwner(Context.User)) || (channel != null && !((IGuildUser)Context.User).GuildPermissions.Administrator)) if ((channel == null && !NadekoBot.Credentials.IsOwner(Context.User)) || (channel != null && !((IGuildUser)Context.User).GuildPermissions.Administrator))
{ {
try { await Context.Channel.SendErrorAsync("Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for guild custom reactions."); } catch { } await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false);
return; return;
} }
@ -114,19 +140,26 @@ namespace NadekoBot.Modules.CustomReactions
if (channel == null) if (channel == null)
{ {
GlobalReactions.Add(cr); Array.Resize(ref _globalReactions, _globalReactions.Length + 1);
_globalReactions[_globalReactions.Length - 1] = cr;
} }
else else
{ {
var reactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet<CustomReaction>()); GuildReactions.AddOrUpdate(Context.Guild.Id,
reactions.Add(cr); new CustomReaction[] { cr },
(k, old) =>
{
Array.Resize(ref old, old.Length + 1);
old[old.Length - 1] = cr;
return old;
});
} }
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle("New Custom Reaction") .WithTitle(GetText("new_cust_react"))
.WithDescription($"#{cr.Id}") .WithDescription($"#{cr.Id}")
.AddField(efb => efb.WithName("Trigger").WithValue(key)) .AddField(efb => efb.WithName(GetText("trigger")).WithValue(key))
.AddField(efb => efb.WithName("Response").WithValue(message)) .AddField(efb => efb.WithName(GetText("response")).WithValue(message))
).ConfigureAwait(false); ).ConfigureAwait(false);
} }
@ -136,21 +169,38 @@ namespace NadekoBot.Modules.CustomReactions
{ {
if (page < 1 || page > 1000) if (page < 1 || page > 1000)
return; return;
ConcurrentHashSet<CustomReaction> customReactions; CustomReaction[] customReactions;
if (Context.Guild == null) if (Context.Guild == null)
customReactions = GlobalReactions; customReactions = GlobalReactions.Where(cr => cr != null).ToArray();
else else
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet<CustomReaction>()); customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, Array.Empty<CustomReaction>()).Where(cr => cr != null).ToArray();
if (customReactions == null || !customReactions.Any()) if (customReactions == null || !customReactions.Any())
await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false); {
else await ReplyErrorLocalized("no_found").ConfigureAwait(false);
await Context.Channel.SendConfirmAsync( return;
$"Page {page} of custom reactions:", }
string.Join("\n", customReactions.OrderBy(cr => cr.Trigger)
.Skip((page - 1) * 20) var lastPage = customReactions.Length / 20;
await Context.Channel.SendPaginatedConfirmAsync(page, curPage =>
new EmbedBuilder().WithOkColor()
.WithTitle(GetText("name"))
.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 =>
{
var str = $"`#{cr.Id}` {cr.Trigger}";
if (cr.AutoDeleteTrigger)
{
str = "🗑" + str;
}
if (cr.DmResponse)
{
str = "📪" + str;
}
return str;
}))), lastPage)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
@ -163,27 +213,29 @@ namespace NadekoBot.Modules.CustomReactions
[Priority(1)] [Priority(1)]
public async Task ListCustReact(All x) public async Task ListCustReact(All x)
{ {
ConcurrentHashSet<CustomReaction> customReactions; CustomReaction[] customReactions;
if (Context.Guild == null) if (Context.Guild == null)
customReactions = GlobalReactions; customReactions = GlobalReactions.Where(cr => cr != null).ToArray();
else else
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet<CustomReaction>()); customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray();
if (customReactions == null || !customReactions.Any()) if (customReactions == null || !customReactions.Any())
await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false);
else
{ {
await ReplyErrorLocalized("no_found").ConfigureAwait(false);
return;
}
var txtStream = await customReactions.GroupBy(cr => cr.Trigger) var txtStream = await customReactions.GroupBy(cr => cr.Trigger)
.OrderBy(cr => cr.Key) .OrderBy(cr => cr.Key)
.Select(cr => new { Trigger = cr.Key, Responses = cr.Select(y => new { id = y.Id, text = y.Response }).ToList() }) .Select(cr => new { Trigger = cr.Key, Responses = cr.Select(y => new { id = y.Id, text = y.Response }).ToList() })
.ToJson() .ToJson()
.ToStream() .ToStream()
.ConfigureAwait(false); .ConfigureAwait(false);
if (Context.Guild == null) // its a private one, just send back if (Context.Guild == null) // its a private one, just send back
await Context.Channel.SendFileAsync(txtStream, "customreactions.txt", "List of all custom reactions").ConfigureAwait(false); await Context.Channel.SendFileAsync(txtStream, "customreactions.txt", GetText("list_all")).ConfigureAwait(false);
else else
await ((IGuildUser)Context.User).SendFileAsync(txtStream, "customreactions.txt", "List of all custom reactions").ConfigureAwait(false); await ((IGuildUser)Context.User).SendFileAsync(txtStream, "customreactions.txt", GetText("list_all")).ConfigureAwait(false);
}
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -191,44 +243,57 @@ namespace NadekoBot.Modules.CustomReactions
{ {
if (page < 1 || page > 10000) if (page < 1 || page > 10000)
return; return;
ConcurrentHashSet<CustomReaction> customReactions; CustomReaction[] customReactions;
if (Context.Guild == null) if (Context.Guild == null)
customReactions = GlobalReactions; customReactions = GlobalReactions.Where(cr => cr != null).ToArray();
else else
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet<CustomReaction>()); customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray();
if (customReactions == null || !customReactions.Any()) if (customReactions == null || !customReactions.Any())
await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false); {
await ReplyErrorLocalized("no_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();
var lastPage = ordered.Count / 20;
await Context.Channel.SendPaginatedConfirmAsync(page, (curPage) =>
new EmbedBuilder().WithOkColor()
.WithTitle(GetText("name"))
.WithDescription(string.Join("\r\n", ordered
.Skip((curPage - 1) * 20)
.Take(20) .Take(20)
.Select(cr => $"**{cr.Key.Trim().ToLowerInvariant()}** `x{cr.Count()}`"))) .Select(cr => $"**{cr.Key.Trim().ToLowerInvariant()}** `x{cr.Count()}`"))), lastPage)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task ShowCustReact(int id) public async Task ShowCustReact(int id)
{ {
ConcurrentHashSet<CustomReaction> customReactions; CustomReaction[] customReactions;
if (Context.Guild == null) if (Context.Guild == null)
customReactions = GlobalReactions; customReactions = GlobalReactions;
else else
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet<CustomReaction>()); customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ });
var found = customReactions.FirstOrDefault(cr => cr.Id == id); var found = customReactions.FirstOrDefault(cr => cr?.Id == id);
if (found == null) if (found == null)
await Context.Channel.SendErrorAsync("No custom reaction found with that id.").ConfigureAwait(false); {
await ReplyErrorLocalized("no_found_id").ConfigureAwait(false);
return;
}
else else
{ {
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription($"#{id}") .WithDescription($"#{id}")
.AddField(efb => efb.WithName("Trigger").WithValue(found.Trigger)) .AddField(efb => efb.WithName(GetText("trigger")).WithValue(found.Trigger))
.AddField(efb => efb.WithName("Response").WithValue(found.Response + "\n```css\n" + found.Response + "```")) .AddField(efb => efb.WithName(GetText("response")).WithValue(found.Response + "\n```css\n" + found.Response + "```"))
).ConfigureAwait(false); ).ConfigureAwait(false);
} }
} }
@ -238,7 +303,7 @@ namespace NadekoBot.Modules.CustomReactions
{ {
if ((Context.Guild == null && !NadekoBot.Credentials.IsOwner(Context.User)) || (Context.Guild != null && !((IGuildUser)Context.User).GuildPermissions.Administrator)) if ((Context.Guild == null && !NadekoBot.Credentials.IsOwner(Context.User)) || (Context.Guild != null && !((IGuildUser)Context.User).GuildPermissions.Administrator))
{ {
try { await Context.Channel.SendErrorAsync("Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for guild custom reactions."); } catch { } await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false);
return; return;
} }
@ -248,28 +313,144 @@ namespace NadekoBot.Modules.CustomReactions
{ {
toDelete = uow.CustomReactions.Get(id); toDelete = uow.CustomReactions.Get(id);
if (toDelete == null) //not found if (toDelete == null) //not found
return; success = false;
else
{
if ((toDelete.GuildId == null || toDelete.GuildId == 0) && Context.Guild == null) if ((toDelete.GuildId == null || toDelete.GuildId == 0) && Context.Guild == null)
{ {
uow.CustomReactions.Remove(toDelete); uow.CustomReactions.Remove(toDelete);
GlobalReactions.RemoveWhere(cr => cr.Id == toDelete.Id); //todo i can dramatically improve performance of this, if Ids are ordered.
_globalReactions = GlobalReactions.Where(cr => cr?.Id != toDelete.Id).ToArray();
success = true; success = true;
} }
else if ((toDelete.GuildId != null && toDelete.GuildId != 0) && Context.Guild.Id == toDelete.GuildId) else if ((toDelete.GuildId != null && toDelete.GuildId != 0) && Context.Guild.Id == toDelete.GuildId)
{ {
uow.CustomReactions.Remove(toDelete); uow.CustomReactions.Remove(toDelete);
GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet<CustomReaction>()).RemoveWhere(cr => cr.Id == toDelete.Id); GuildReactions.AddOrUpdate(Context.Guild.Id, new CustomReaction[] { }, (key, old) =>
{
return old.Where(cr => cr?.Id != toDelete.Id).ToArray();
});
success = true; success = true;
} }
if (success) if (success)
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
}
if (success) if (success)
await Context.Channel.SendConfirmAsync("Deleted custom reaction", toDelete.ToString()).ConfigureAwait(false); {
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("deleted"))
.WithDescription("#" + toDelete.Id)
.AddField(efb => efb.WithName(GetText("trigger")).WithValue(toDelete.Trigger))
.AddField(efb => efb.WithName(GetText("response")).WithValue(toDelete.Response)));
}
else else
await Context.Channel.SendErrorAsync("Failed to find that custom reaction.").ConfigureAwait(false); {
await ReplyErrorLocalized("no_found_id").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task CrDm(int id)
{
if ((Context.Guild == null && !NadekoBot.Credentials.IsOwner(Context.User)) ||
(Context.Guild != null && !((IGuildUser)Context.User).GuildPermissions.Administrator))
{
await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false);
return;
}
CustomReaction[] reactions = new CustomReaction[0];
if (Context.Guild == null)
reactions = GlobalReactions;
else
{
GuildReactions.TryGetValue(Context.Guild.Id, out reactions);
}
if (reactions.Any())
{
var reaction = reactions.FirstOrDefault(x => x.Id == id);
if (reaction == null)
{
await ReplyErrorLocalized("no_found_id").ConfigureAwait(false);
return;
}
var setValue = reaction.DmResponse = !reaction.DmResponse;
using (var uow = DbHandler.UnitOfWork())
{
uow.CustomReactions.Get(id).DmResponse = setValue;
uow.Complete();
}
if (setValue)
{
await ReplyConfirmLocalized("crdm_enabled", Format.Code(reaction.Id.ToString())).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("crdm_disabled", Format.Code(reaction.Id.ToString())).ConfigureAwait(false);
}
}
else
{
await ReplyErrorLocalized("no_found").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task CrAd(int id)
{
if ((Context.Guild == null && !NadekoBot.Credentials.IsOwner(Context.User)) ||
(Context.Guild != null && !((IGuildUser)Context.User).GuildPermissions.Administrator))
{
await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false);
return;
}
CustomReaction[] reactions = new CustomReaction[0];
if (Context.Guild == null)
reactions = GlobalReactions;
else
{
GuildReactions.TryGetValue(Context.Guild.Id, out reactions);
}
if (reactions.Any())
{
var reaction = reactions.FirstOrDefault(x => x.Id == id);
if (reaction == null)
{
await ReplyErrorLocalized("no_found_id").ConfigureAwait(false);
return;
}
var setValue = reaction.AutoDeleteTrigger = !reaction.AutoDeleteTrigger;
using (var uow = DbHandler.UnitOfWork())
{
uow.CustomReactions.Get(id).AutoDeleteTrigger = setValue;
uow.Complete();
}
if (setValue)
{
await ReplyConfirmLocalized("crad_enabled", Format.Code(reaction.Id.ToString())).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("crad_disabled", Format.Code(reaction.Id.ToString())).ConfigureAwait(false);
}
}
else
{
await ReplyErrorLocalized("no_found").ConfigureAwait(false);
}
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -279,18 +460,18 @@ namespace NadekoBot.Modules.CustomReactions
if (string.IsNullOrWhiteSpace(trigger)) if (string.IsNullOrWhiteSpace(trigger))
{ {
ClearStats(); ClearStats();
await Context.Channel.SendConfirmAsync($"Custom reaction stats cleared.").ConfigureAwait(false); await ReplyConfirmLocalized("all_stats_cleared").ConfigureAwait(false);
} }
else else
{ {
uint throwaway; uint throwaway;
if (ReactionStats.TryRemove(trigger, out throwaway)) if (ReactionStats.TryRemove(trigger, out throwaway))
{ {
await Context.Channel.SendConfirmAsync($"Stats cleared for `{trigger}` custom reaction.").ConfigureAwait(false); await ReplyErrorLocalized("stats_cleared", Format.Bold(trigger)).ConfigureAwait(false);
} }
else else
{ {
await Context.Channel.SendErrorAsync("No stats for that trigger found, no action taken.").ConfigureAwait(false); await ReplyErrorLocalized("stats_not_found").ConfigureAwait(false);
} }
} }
} }
@ -300,12 +481,15 @@ 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).ToArray();
.Skip((page - 1) * 9) if (!ordered.Any())
return;
var lastPage = ordered.Length / 9;
await Context.Channel.SendPaginatedConfirmAsync(page,
(curPage) => ordered.Skip((curPage - 1) * 9)
.Take(9) .Take(9)
.Aggregate(new EmbedBuilder().WithOkColor().WithTitle($"Custom Reaction stats page #{page}"), .Aggregate(new EmbedBuilder().WithOkColor().WithTitle(GetText("stats")),
(agg, cur) => agg.AddField(efb => efb.WithName(cur.Key).WithValue(cur.Value.ToString()).WithIsInline(true))) (agg, cur) => agg.AddField(efb => efb.WithName(cur.Key).WithValue(cur.Value.ToString()).WithIsInline(true))), lastPage)
)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
} }

View File

@ -1,9 +1,11 @@
using Discord; using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@ -13,21 +15,32 @@ namespace NadekoBot.Modules.CustomReactions
{ {
public static Dictionary<string, Func<IUserMessage, string, string>> responsePlaceholders = new Dictionary<string, Func<IUserMessage, string, string>>() public static Dictionary<string, Func<IUserMessage, string, string>> responsePlaceholders = new Dictionary<string, Func<IUserMessage, string, string>>()
{ {
{"%target%", (ctx, trigger) => { return ctx.Content.Substring(trigger.Length).Trim(); } } {"%target%", (ctx, trigger) => { return ctx.Content.Substring(trigger.Length).Trim().SanitizeMentions(); } }
}; };
public static Dictionary<string, Func<IUserMessage, string>> placeholders = new Dictionary<string, Func<IUserMessage, string>>() public static Dictionary<string, Func<IUserMessage, string>> placeholders = new Dictionary<string, Func<IUserMessage, string>>()
{ {
{"%mention%", (ctx) => { return $"<@{NadekoBot.Client.CurrentUser().Id}>"; } }, {"%mention%", (ctx) => { return $"<@{NadekoBot.Client.CurrentUser.Id}>"; } },
{"%user%", (ctx) => { return ctx.Author.Mention; } }, {"%user%", (ctx) => { return ctx.Author.Mention; } },
{"%rnduser%", (ctx) => { {"%rnduser%", (ctx) => {
var ch = ctx.Channel as ITextChannel; //var ch = ctx.Channel as ITextChannel;
if(ch == null) //if(ch == null)
return ""; // return "";
var usrs = (ch.Guild.GetUsersAsync().GetAwaiter().GetResult()); //var g = ch.Guild as SocketGuild;
//if(g == null)
// return "";
//try {
// var usr = g.Users.Skip(new NadekoRandom().Next(0, g.Users.Count)).FirstOrDefault();
// return usr.Mention;
//}
//catch {
return "[%rnduser% is temp. disabled]";
//}
return usrs.Skip(new NadekoRandom().Next(0,usrs.Count-1)).Shuffle().FirstOrDefault()?.Mention ?? ""; //var users = g.Users.ToArray();
//return users[new NadekoRandom().Next(0, users.Length-1)].Mention;
} } } }
//{"%rng%", (ctx) => { return new NadekoRandom().Next(0,10).ToString(); } } //{"%rng%", (ctx) => { return new NadekoRandom().Next(0,10).ToString(); } }
}; };

View File

@ -1,22 +0,0 @@
using Discord.Commands;
using NLog;
namespace NadekoBot.Modules
{
public abstract class DiscordModule : ModuleBase
{
protected Logger _log { get; }
protected string _prefix { get; }
public DiscordModule()
{
string prefix;
if (NadekoBot.ModulePrefixes.TryGetValue(this.GetType().Name, out prefix))
_prefix = prefix;
else
_prefix = "?missing_prefix?";
_log = LogManager.GetCurrentClassLogger();
}
}
}

View File

@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Gambling
public partial class Gambling public partial class Gambling
{ {
[Group] [Group]
public class AnimalRacing : ModuleBase public class AnimalRacing : NadekoSubmodule
{ {
public static ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>(); public static ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
@ -25,10 +25,10 @@ namespace NadekoBot.Modules.Gambling
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Race() public async Task Race()
{ {
var ar = new AnimalRace(Context.Guild.Id, (ITextChannel)Context.Channel); var ar = new AnimalRace(Context.Guild.Id, (ITextChannel)Context.Channel, Prefix);
if (ar.Fail) if (ar.Fail)
await Context.Channel.SendErrorAsync("🏁 `Failed starting a race. Another race is probably running.`").ConfigureAwait(false); await ReplyErrorLocalized("race_failed_starting").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -43,7 +43,7 @@ namespace NadekoBot.Modules.Gambling
AnimalRace ar; AnimalRace ar;
if (!AnimalRaces.TryGetValue(Context.Guild.Id, out ar)) if (!AnimalRaces.TryGetValue(Context.Guild.Id, out ar))
{ {
await Context.Channel.SendErrorAsync("No race exists on this server").ConfigureAwait(false); await ReplyErrorLocalized("race_not_exist").ConfigureAwait(false);
return; return;
} }
await ar.JoinRace(Context.User as IGuildUser, amount); await ar.JoinRace(Context.User as IGuildUser, amount);
@ -56,29 +56,29 @@ namespace NadekoBot.Modules.Gambling
public bool Fail { get; set; } public bool Fail { get; set; }
public List<Participant> participants = new List<Participant>(); private readonly List<Participant> _participants = new List<Participant>();
private ulong serverId; private readonly ulong _serverId;
private int messagesSinceGameStarted = 0; private int _messagesSinceGameStarted;
private Logger _log { get; } private readonly string _prefix;
public ITextChannel raceChannel { get; set; } private readonly Logger _log;
public bool Started { get; private set; } = false;
public AnimalRace(ulong serverId, ITextChannel ch) private readonly ITextChannel _raceChannel;
public bool Started { get; private set; }
public AnimalRace(ulong serverId, ITextChannel ch, string prefix)
{ {
this._log = LogManager.GetCurrentClassLogger(); _prefix = prefix;
this.serverId = serverId; _log = LogManager.GetCurrentClassLogger();
this.raceChannel = ch; _serverId = serverId;
_raceChannel = ch;
if (!AnimalRaces.TryAdd(serverId, this)) if (!AnimalRaces.TryAdd(serverId, this))
{ {
Fail = true; Fail = true;
return; return;
} }
using (var uow = DbHandler.UnitOfWork()) animals = new ConcurrentQueue<string>(NadekoBot.BotConfig.RaceAnimals.Select(ra => ra.Icon).Shuffle());
{
animals = new ConcurrentQueue<string>(uow.BotConfig.GetOrCreate().RaceAnimals.Select(ra => ra.Icon).Shuffle());
}
var cancelSource = new CancellationTokenSource(); var cancelSource = new CancellationTokenSource();
@ -90,8 +90,8 @@ namespace NadekoBot.Modules.Gambling
{ {
try try
{ {
await raceChannel.SendConfirmAsync("Animal Race", $"Starting in 20 seconds or when the room is full.", await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting"),
footer: $"Type {NadekoBot.ModulePrefixes[typeof(Gambling).Name]}jr to join the race."); footer: GetText("animal_race_join_instr", _prefix));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -102,16 +102,16 @@ namespace NadekoBot.Modules.Gambling
cancelSource.Cancel(); cancelSource.Cancel();
if (t == fullgame) if (t == fullgame)
{ {
try { await raceChannel.SendConfirmAsync("Animal Race", "Full! Starting immediately."); } catch (Exception ex) { _log.Warn(ex); } try { await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_full") ); } catch (Exception ex) { _log.Warn(ex); }
} }
else if (participants.Count > 1) else if (_participants.Count > 1)
{ {
try { await raceChannel.SendConfirmAsync("Animal Race", "Starting with " + participants.Count + " participants."); } catch (Exception ex) { _log.Warn(ex); } try { await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting_with_x", _participants.Count)); } catch (Exception ex) { _log.Warn(ex); }
} }
else else
{ {
try { await raceChannel.SendErrorAsync("Animal Race", "Failed to start since there was not enough participants."); } catch (Exception ex) { _log.Warn(ex); } try { await _raceChannel.SendErrorAsync(GetText("animal_race"), GetText("animal_race_failed")); } catch (Exception ex) { _log.Warn(ex); }
var p = participants.FirstOrDefault(); var p = _participants.FirstOrDefault();
if (p != null && p.AmountBet > 0) if (p != null && p.AmountBet > 0)
await CurrencyHandler.AddCurrencyAsync(p.User, "BetRace", p.AmountBet, false).ConfigureAwait(false); await CurrencyHandler.AddCurrencyAsync(p.User, "BetRace", p.AmountBet, false).ConfigureAwait(false);
@ -128,7 +128,7 @@ namespace NadekoBot.Modules.Gambling
private void End() private void End()
{ {
AnimalRace throwaway; AnimalRace throwaway;
AnimalRaces.TryRemove(serverId, out throwaway); AnimalRaces.TryRemove(_serverId, out throwaway);
} }
private async Task StartRace() private async Task StartRace()
@ -136,21 +136,21 @@ namespace NadekoBot.Modules.Gambling
var rng = new NadekoRandom(); var rng = new NadekoRandom();
Participant winner = null; Participant winner = null;
IUserMessage msg = null; IUserMessage msg = null;
int place = 1; var place = 1;
try try
{ {
NadekoBot.Client.MessageReceived += Client_MessageReceived; NadekoBot.Client.MessageReceived += Client_MessageReceived;
while (!participants.All(p => p.Total >= 60)) while (!_participants.All(p => p.Total >= 60))
{ {
//update the state //update the state
participants.ForEach(p => _participants.ForEach(p =>
{ {
p.Total += 1 + rng.Next(0, 10); p.Total += 1 + rng.Next(0, 10);
}); });
participants _participants
.OrderByDescending(p => p.Total) .OrderByDescending(p => p.Total)
.ForEach(p => .ForEach(p =>
{ {
@ -170,14 +170,14 @@ namespace NadekoBot.Modules.Gambling
//draw the state //draw the state
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚| var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
{String.Join("\n", participants.Select(p => $"{(int)(p.Total / 60f * 100),-2}%|{p.ToString()}"))} {String.Join("\n", _participants.Select(p => $"{(int)(p.Total / 60f * 100),-2}%|{p.ToString()}"))}
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|"; |🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
if (msg == null || messagesSinceGameStarted >= 10) // also resend the message if channel was spammed if (msg == null || _messagesSinceGameStarted >= 10) // also resend the message if channel was spammed
{ {
if (msg != null) if (msg != null)
try { await msg.DeleteAsync(); } catch { } try { await msg.DeleteAsync(); } catch { }
messagesSinceGameStarted = 0; _messagesSinceGameStarted = 0;
try { msg = await raceChannel.SendMessageAsync(text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } try { msg = await _raceChannel.SendMessageAsync(text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
} }
else else
{ {
@ -187,35 +187,46 @@ namespace NadekoBot.Modules.Gambling
await Task.Delay(2500); await Task.Delay(2500);
} }
} }
catch { } catch
{
// ignored
}
finally finally
{ {
NadekoBot.Client.MessageReceived -= Client_MessageReceived; NadekoBot.Client.MessageReceived -= Client_MessageReceived;
} }
if (winner != null)
{
if (winner.AmountBet > 0) if (winner.AmountBet > 0)
{ {
var wonAmount = winner.AmountBet * (participants.Count - 1); var wonAmount = winner.AmountBet * (_participants.Count - 1);
await CurrencyHandler.AddCurrencyAsync(winner.User, "Won a Race", wonAmount, true).ConfigureAwait(false); await CurrencyHandler.AddCurrencyAsync(winner.User, "Won a Race", wonAmount, true)
await raceChannel.SendConfirmAsync("Animal Race", $"{winner.User.Mention} as {winner.Animal} **Won the race and {wonAmount}{CurrencySign}!**").ConfigureAwait(false); .ConfigureAwait(false);
await _raceChannel.SendConfirmAsync(GetText("animal_race"),
Format.Bold(GetText("animal_race_won_money", winner.User.Mention,
winner.Animal, wonAmount + CurrencySign)))
.ConfigureAwait(false);
} }
else else
{ {
await raceChannel.SendConfirmAsync("Animal Race", $"{winner.User.Mention} as {winner.Animal} **Won the race!**").ConfigureAwait(false); await _raceChannel.SendConfirmAsync(GetText("animal_race"),
Format.Bold(GetText("animal_race_won", winner.User.Mention, winner.Animal))).ConfigureAwait(false);
}
} }
} }
private void Client_MessageReceived(SocketMessage imsg) private Task Client_MessageReceived(SocketMessage imsg)
{ {
var msg = imsg as SocketUserMessage; var msg = imsg as SocketUserMessage;
if (msg == null) if (msg == null)
return; return Task.CompletedTask;
if (msg.IsAuthor() || !(imsg.Channel is ITextChannel) || imsg.Channel != raceChannel) if (msg.IsAuthor() || !(imsg.Channel is ITextChannel) || imsg.Channel != _raceChannel)
return; return Task.CompletedTask;
messagesSinceGameStarted++; _messagesSinceGameStarted++;
return; return Task.CompletedTask;
} }
private async Task CheckForFullGameAsync(CancellationToken cancelToken) private async Task CheckForFullGameAsync(CancellationToken cancelToken)
@ -228,51 +239,66 @@ namespace NadekoBot.Modules.Gambling
public async Task JoinRace(IGuildUser u, int amount = 0) public async Task JoinRace(IGuildUser u, int amount = 0)
{ {
var animal = ""; string animal;
if (!animals.TryDequeue(out animal)) if (!animals.TryDequeue(out animal))
{ {
await raceChannel.SendErrorAsync($"{u.Mention} `There is no running race on this server.`").ConfigureAwait(false); await _raceChannel.SendErrorAsync(GetText("animal_race_no_race")).ConfigureAwait(false);
return; return;
} }
var p = new Participant(u, animal, amount); var p = new Participant(u, animal, amount);
if (participants.Contains(p)) if (_participants.Contains(p))
{ {
await raceChannel.SendErrorAsync($"{u.Mention} `You already joined this race.`").ConfigureAwait(false); await _raceChannel.SendErrorAsync(GetText("animal_race_already_in")).ConfigureAwait(false);
return; return;
} }
if (Started) if (Started)
{ {
await raceChannel.SendErrorAsync($"{u.Mention} `Race is already started`").ConfigureAwait(false); await _raceChannel.SendErrorAsync(GetText("animal_race_already_started")).ConfigureAwait(false);
return; return;
} }
if (amount > 0) if (amount > 0)
if (!await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)u, "BetRace", amount, false).ConfigureAwait(false)) if (!await CurrencyHandler.RemoveCurrencyAsync(u, "BetRace", amount, false).ConfigureAwait(false))
{ {
try { await raceChannel.SendErrorAsync($"{u.Mention} You don't have enough {Gambling.CurrencyName}s.").ConfigureAwait(false); } catch { } await _raceChannel.SendErrorAsync(GetText("not_enough", CurrencySign)).ConfigureAwait(false);
return; return;
} }
participants.Add(p); _participants.Add(p);
await raceChannel.SendConfirmAsync("Animal Race", $"{u.Mention} **joined as a {p.Animal}" + (amount > 0 ? $" and bet {amount} {CurrencySign}!**" : "**")) string confStr;
.ConfigureAwait(false); if (amount > 0)
confStr = GetText("animal_race_join_bet", u.Mention, p.Animal, amount + CurrencySign);
else
confStr = GetText("animal_race_join", u.Mention, p.Animal);
await _raceChannel.SendConfirmAsync(GetText("animal_race"), Format.Bold(confStr)).ConfigureAwait(false);
} }
private string GetText(string text)
=> NadekoTopLevelModule.GetTextStatic(text,
NadekoBot.Localization.GetCultureInfo(_raceChannel.Guild),
typeof(Gambling).Name.ToLowerInvariant());
private string GetText(string text, params object[] replacements)
=> NadekoTopLevelModule.GetTextStatic(text,
NadekoBot.Localization.GetCultureInfo(_raceChannel.Guild),
typeof(Gambling).Name.ToLowerInvariant(),
replacements);
} }
public class Participant public class Participant
{ {
public IGuildUser User { get; set; } public IGuildUser User { get; }
public string Animal { get; set; } public string Animal { get; }
public int AmountBet { get; set; } public int AmountBet { get; }
public float Coeff { get; set; } public float Coeff { get; set; }
public int Total { get; set; } public int Total { get; set; }
public int Place { get; set; } = 0; public int Place { get; set; }
public Participant(IGuildUser u, string a, int amount) public Participant(IGuildUser u, string a, int amount)
{ {
this.User = u; User = u;
this.Animal = a; Animal = a;
this.AmountBet = amount; AmountBet = amount;
} }
public override int GetHashCode() => User.GetHashCode(); public override int GetHashCode() => User.GetHashCode();
@ -280,9 +306,7 @@ namespace NadekoBot.Modules.Gambling
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
var p = obj as Participant; var p = obj as Participant;
return p == null ? return p != null && p.User == User;
false :
p.User == User;
} }
public override string ToString() public override string ToString()
@ -290,23 +314,13 @@ namespace NadekoBot.Modules.Gambling
var str = new string('‣', Total) + Animal; var str = new string('‣', Total) + Animal;
if (Place == 0) if (Place == 0)
return str; return str;
if (Place == 1)
{
return str + "🏆";
}
else if (Place == 2)
{
return str + "`2nd`";
}
else if (Place == 3)
{
return str + "`3rd`";
}
else
{
return str + $"`{Place}th`";
}
str += $"`#{Place}`";
if (Place == 1)
str += "🏆";
return str;
} }
} }
} }

View File

@ -0,0 +1,226 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using Discord.WebSocket;
using System.Threading;
using NLog;
namespace NadekoBot.Modules.Gambling
{
public partial class Gambling
{
[Group]
public class CurrencyEvents : NadekoSubmodule
{
public enum CurrencyEvent
{
FlowerReaction,
SneakyGameStatus
}
//flower reaction event
private static readonly ConcurrentHashSet<ulong> _sneakyGameAwardedUsers = new ConcurrentHashSet<ulong>();
private static readonly char[] _sneakyGameStatusChars = Enumerable.Range(48, 10)
.Concat(Enumerable.Range(65, 26))
.Concat(Enumerable.Range(97, 26))
.Select(x => (char)x)
.ToArray();
private static string _secretCode = string.Empty;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartEvent(CurrencyEvent e, int arg = -1)
{
switch (e)
{
case CurrencyEvent.FlowerReaction:
await FlowerReactionEvent(Context, arg).ConfigureAwait(false);
break;
case CurrencyEvent.SneakyGameStatus:
await SneakyGameStatusEvent(Context, arg).ConfigureAwait(false);
break;
}
}
public async Task SneakyGameStatusEvent(CommandContext context, int? arg)
{
int num;
if (arg == null || arg < 5)
num = 60;
else
num = arg.Value;
if (_secretCode != string.Empty)
return;
var rng = new NadekoRandom();
for (var i = 0; i < 5; i++)
{
_secretCode += _sneakyGameStatusChars[rng.Next(0, _sneakyGameStatusChars.Length)];
}
await NadekoBot.Client.SetGameAsync($"type {_secretCode} for " + NadekoBot.BotConfig.CurrencyPluralName)
.ConfigureAwait(false);
try
{
var title = GetText("sneakygamestatus_title");
var desc = GetText("sneakygamestatus_desc", Format.Bold(100.ToString()) + CurrencySign, Format.Bold(num.ToString()));
await context.Channel.SendConfirmAsync(title, desc).ConfigureAwait(false);
}
catch
{
// ignored
}
NadekoBot.Client.MessageReceived += SneakyGameMessageReceivedEventHandler;
await Task.Delay(num * 1000);
NadekoBot.Client.MessageReceived -= SneakyGameMessageReceivedEventHandler;
var cnt = _sneakyGameAwardedUsers.Count;
_sneakyGameAwardedUsers.Clear();
_secretCode = string.Empty;
await NadekoBot.Client.SetGameAsync(GetText("sneakygamestatus_end", cnt))
.ConfigureAwait(false);
}
private static Task SneakyGameMessageReceivedEventHandler(SocketMessage arg)
{
if (arg.Content == _secretCode &&
_sneakyGameAwardedUsers.Add(arg.Author.Id))
{
var _ = Task.Run(async () =>
{
await CurrencyHandler.AddCurrencyAsync(arg.Author, "Sneaky Game Event", 100, false)
.ConfigureAwait(false);
try { await arg.DeleteAsync(new RequestOptions() { RetryMode = RetryMode.AlwaysFail }).ConfigureAwait(false); }
catch
{
// ignored
}
});
}
return Task.Delay(0);
}
public async Task FlowerReactionEvent(CommandContext context, int amount)
{
if (amount <= 0)
amount = 100;
var title = GetText("flowerreaction_title");
var desc = GetText("flowerreaction_desc", "🌸", Format.Bold(amount.ToString()) + CurrencySign);
var footer = GetText("flowerreaction_footer", 24);
var msg = await context.Channel.SendConfirmAsync(title,
desc, footer: footer)
.ConfigureAwait(false);
await new FlowerReactionEvent().Start(msg, context, amount);
}
}
}
public abstract class CurrencyEvent
{
public abstract Task Start(IUserMessage msg, CommandContext channel, int amount);
}
public class FlowerReactionEvent : CurrencyEvent
{
private readonly ConcurrentHashSet<ulong> _flowerReactionAwardedUsers = new ConcurrentHashSet<ulong>();
private readonly Logger _log;
private IUserMessage msg { get; set; }
private CancellationTokenSource source { get; }
private CancellationToken cancelToken { get; }
public FlowerReactionEvent()
{
_log = LogManager.GetCurrentClassLogger();
source = new CancellationTokenSource();
cancelToken = source.Token;
}
private async Task End()
{
if(msg != null)
await msg.DeleteAsync().ConfigureAwait(false);
if(!source.IsCancellationRequested)
source.Cancel();
NadekoBot.Client.MessageDeleted -= MessageDeletedEventHandler;
}
private Task MessageDeletedEventHandler(ulong id, Optional<SocketMessage> _) {
if (msg?.Id == id)
{
_log.Warn("Stopping flower reaction event because message is deleted.");
var __ = Task.Run(End);
}
return Task.CompletedTask;
}
public override async Task Start(IUserMessage umsg, CommandContext context, int amount)
{
msg = umsg;
NadekoBot.Client.MessageDeleted += MessageDeletedEventHandler;
try { await msg.AddReactionAsync("🌸").ConfigureAwait(false); }
catch
{
try { await msg.AddReactionAsync("🌸").ConfigureAwait(false); }
catch
{
try { await msg.DeleteAsync().ConfigureAwait(false); }
catch { return; }
}
}
using (msg.OnReaction(async (r) =>
{
try
{
if (r.Emoji.Name == "🌸" && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _flowerReactionAwardedUsers.Add(r.User.Value.Id))
{
await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", amount, false)
.ConfigureAwait(false);
}
}
catch
{
// ignored
}
}))
{
try
{
await Task.Delay(TimeSpan.FromHours(24), cancelToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
if (cancelToken.IsCancellationRequested)
return;
_log.Warn("Stopping flower reaction event because it expired.");
await End();
}
}
}
}

View File

@ -1,6 +1,5 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using ImageSharp;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
@ -17,12 +16,12 @@ namespace NadekoBot.Modules.Gambling
public partial class Gambling public partial class Gambling
{ {
[Group] [Group]
public class DriceRollCommands : ModuleBase public class DriceRollCommands : NadekoSubmodule
{ {
private Regex dndRegex { get; } = new Regex(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$", RegexOptions.Compiled); private Regex dndRegex { get; } = new Regex(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$", RegexOptions.Compiled);
private Regex fudgeRegex { get; } = new Regex(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled); private Regex fudgeRegex { get; } = new Regex(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
private readonly char[] fateRolls = new[] { '-', ' ', '+' }; private readonly char[] _fateRolls = { '-', ' ', '+' };
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Roll() public async Task Roll()
@ -33,18 +32,16 @@ namespace NadekoBot.Modules.Gambling
var num1 = gen / 10; var num1 = gen / 10;
var num2 = gen % 10; var num2 = gen % 10;
var imageStream = await Task.Run(() => var imageStream = await Task.Run(() =>
{
try
{ {
var ms = new MemoryStream(); var ms = new MemoryStream();
new[] { GetDice(num1), GetDice(num2) }.Merge().SaveAsPng(ms); new[] { GetDice(num1), GetDice(num2) }.Merge().Save(ms);
ms.Position = 0; ms.Position = 0;
return ms; return ms;
} }).ConfigureAwait(false);
catch { return new MemoryStream(); }
});
await Context.Channel.SendFileAsync(imageStream, "dice.png", $"{Context.User.Mention} rolled " + Format.Code(gen.ToString())).ConfigureAwait(false); await Context.Channel.SendFileAsync(imageStream,
"dice.png",
Context.User.Mention + " " + GetText("dice_rolled", Format.Code(gen.ToString()))).ConfigureAwait(false);
} }
public enum RollOrderType public enum RollOrderType
@ -63,7 +60,7 @@ namespace NadekoBot.Modules.Gambling
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[Priority(0)] [Priority(0)]
public async Task Rolluo(int num) public async Task Rolluo(int num = 1)
{ {
await InternalRoll(num, false).ConfigureAwait(false); await InternalRoll(num, false).ConfigureAwait(false);
} }
@ -82,11 +79,11 @@ namespace NadekoBot.Modules.Gambling
await InternallDndRoll(arg, false).ConfigureAwait(false); await InternallDndRoll(arg, false).ConfigureAwait(false);
} }
private async Task InternalRoll( int num, bool ordered) private async Task InternalRoll(int num, bool ordered)
{ {
if (num < 1 || num > 30) if (num < 1 || num > 30)
{ {
await Context.Channel.SendErrorAsync("Invalid number specified. You can roll up to 1-30 dice at a time.").ConfigureAwait(false); await ReplyErrorLocalized("dice_invalid_number", 1, 30).ConfigureAwait(false);
return; return;
} }
@ -122,9 +119,14 @@ namespace NadekoBot.Modules.Gambling
var bitmap = dice.Merge(); var bitmap = dice.Merge();
var ms = new MemoryStream(); var ms = new MemoryStream();
bitmap.SaveAsPng(ms); bitmap.Save(ms);
ms.Position = 0; ms.Position = 0;
await Context.Channel.SendFileAsync(ms, "dice.png", $"{Context.User.Mention} rolled {values.Count} {(values.Count == 1 ? "die" : "dice")}. Total: **{values.Sum()}** Average: **{(values.Sum() / (1.0f * values.Count)).ToString("N2")}**").ConfigureAwait(false); await Context.Channel.SendFileAsync(ms, "dice.png",
Context.User.Mention + " " +
GetText("dice_rolled_num", Format.Bold(values.Count.ToString())) +
" " + GetText("total_average",
Format.Bold(values.Sum().ToString()),
Format.Bold((values.Sum() / (1.0f * values.Count)).ToString("N2")))).ConfigureAwait(false);
} }
private async Task InternallDndRoll(string arg, bool ordered) private async Task InternallDndRoll(string arg, bool ordered)
@ -142,9 +144,9 @@ namespace NadekoBot.Modules.Gambling
for (int i = 0; i < n1; i++) for (int i = 0; i < n1; i++)
{ {
rolls.Add(fateRolls[rng.Next(0, fateRolls.Length)]); rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
} }
var embed = new EmbedBuilder().WithOkColor().WithDescription($"{Context.User.Mention} rolled {n1} fate {(n1 == 1 ? "die" : "dice")}.") var embed = new EmbedBuilder().WithOkColor().WithDescription(Context.User.Mention + " " + GetText("dice_rolled_num", Format.Bold(n1.ToString())))
.AddField(efb => efb.WithName(Format.Bold("Result")) .AddField(efb => efb.WithName(Format.Bold("Result"))
.WithValue(string.Join(" ", rolls.Select(c => Format.Code($"[{c}]"))))); .WithValue(string.Join(" ", rolls.Select(c => Format.Code($"[{c}]")))));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -168,7 +170,7 @@ namespace NadekoBot.Modules.Gambling
} }
var sum = arr.Sum(); var sum = arr.Sum();
var embed = new EmbedBuilder().WithOkColor().WithDescription($"{Context.User.Mention} rolled {n1} {(n1 == 1 ? "die" : "dice")} `1 to {n2}`") var embed = new EmbedBuilder().WithOkColor().WithDescription(Context.User.Mention + " " +GetText("dice_rolled_num", n1) + $"`1 - {n2}`")
.AddField(efb => efb.WithName(Format.Bold("Rolls")) .AddField(efb => efb.WithName(Format.Bold("Rolls"))
.WithValue(string.Join(" ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => Format.Code(x.ToString()))))) .WithValue(string.Join(" ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => Format.Code(x.ToString())))))
.AddField(efb => efb.WithName(Format.Bold("Sum")) .AddField(efb => efb.WithName(Format.Bold("Sum"))
@ -180,8 +182,6 @@ namespace NadekoBot.Modules.Gambling
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task NRoll([Remainder] string range) public async Task NRoll([Remainder] string range)
{
try
{ {
int rolled; int rolled;
if (range.Contains("-")) if (range.Contains("-"))
@ -191,7 +191,10 @@ namespace NadekoBot.Modules.Gambling
.Select(int.Parse) .Select(int.Parse)
.ToArray(); .ToArray();
if (arr[0] > arr[1]) if (arr[0] > arr[1])
throw new ArgumentException("Second argument must be larger than the first one."); {
await ReplyErrorLocalized("second_larger_than_first").ConfigureAwait(false);
return;
}
rolled = new NadekoRandom().Next(arr[0], arr[1] + 1); rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
} }
else else
@ -199,32 +202,31 @@ namespace NadekoBot.Modules.Gambling
rolled = new NadekoRandom().Next(0, int.Parse(range) + 1); rolled = new NadekoRandom().Next(0, int.Parse(range) + 1);
} }
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} rolled **{rolled}**.").ConfigureAwait(false); await ReplyConfirmLocalized("dice_rolled", Format.Bold(rolled.ToString())).ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync($":anger: {ex.Message}").ConfigureAwait(false);
}
} }
private Image GetDice(int num) private Image GetDice(int num)
{ {
const string pathToImage = "data/images/dice"; if (num < 0 || num > 10)
if (num != 10) throw new ArgumentOutOfRangeException(nameof(num));
{
using (var stream = File.OpenRead(Path.Combine(pathToImage, $"{num}.png")))
return new Image(stream);
}
using (var one = File.OpenRead(Path.Combine(pathToImage, "1.png"))) if (num == 10)
using (var zero = File.OpenRead(Path.Combine(pathToImage, "0.png")))
{ {
Image imgOne = new Image(one); var images = NadekoBot.Images.Dice;
Image imgZero = new Image(zero); using (var imgOneStream = images[1].Value.ToStream())
using (var imgZeroStream = images[0].Value.ToStream())
{
Image imgOne = new Image(imgOneStream);
Image imgZero = new Image(imgZeroStream);
return new[] { imgOne, imgZero }.Merge(); return new[] { imgOne, imgZero }.Merge();
} }
} }
using (var die = NadekoBot.Images.Dice[num].Value.ToStream())
{
return new Image(die);
}
}
} }
} }
} }

View File

@ -4,8 +4,6 @@ using ImageSharp;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Models; using NadekoBot.Modules.Gambling.Models;
using NLog;
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -17,17 +15,17 @@ namespace NadekoBot.Modules.Gambling
public partial class Gambling public partial class Gambling
{ {
[Group] [Group]
public class DrawCommands : ModuleBase public class DrawCommands : NadekoSubmodule
{ {
private static readonly ConcurrentDictionary<IGuild, Cards> AllDecks = new ConcurrentDictionary<IGuild, Cards>(); private static readonly ConcurrentDictionary<IGuild, Cards> _allDecks = new ConcurrentDictionary<IGuild, Cards>();
private const string cardsPath = "data/images/cards"; private const string _cardsPath = "data/images/cards";
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Draw(int num = 1) public async Task Draw(int num = 1)
{ {
var cards = AllDecks.GetOrAdd(Context.Guild, (s) => new Cards()); var cards = _allDecks.GetOrAdd(Context.Guild, (s) => new Cards());
var images = new List<Image>(); var images = new List<Image>();
var cardObjects = new List<Cards.Card>(); var cardObjects = new List<Cards.Card>();
if (num > 5) num = 5; if (num > 5) num = 5;
@ -35,16 +33,23 @@ namespace NadekoBot.Modules.Gambling
{ {
if (cards.CardPool.Count == 0 && i != 0) if (cards.CardPool.Count == 0 && i != 0)
{ {
try { await Context.Channel.SendErrorAsync("No more cards in a deck.").ConfigureAwait(false); } catch { } try
{
await ReplyErrorLocalized("no_more_cards").ConfigureAwait(false);
}
catch
{
// ignored
}
break; break;
} }
var currentCard = cards.DrawACard(); var currentCard = cards.DrawACard();
cardObjects.Add(currentCard); cardObjects.Add(currentCard);
using (var stream = File.OpenRead(Path.Combine(cardsPath, currentCard.ToString().ToLowerInvariant()+ ".jpg").Replace(' ','_'))) using (var stream = File.OpenRead(Path.Combine(_cardsPath, currentCard.ToString().ToLowerInvariant()+ ".jpg").Replace(' ','_')))
images.Add(new Image(stream)); images.Add(new Image(stream));
} }
MemoryStream bitmapStream = new MemoryStream(); MemoryStream bitmapStream = new MemoryStream();
images.Merge().SaveAsPng(bitmapStream); images.Merge().Save(bitmapStream);
bitmapStream.Position = 0; bitmapStream.Position = 0;
var toSend = $"{Context.User.Mention}"; var toSend = $"{Context.User.Mention}";
if (cardObjects.Count == 5) if (cardObjects.Count == 5)
@ -59,7 +64,7 @@ namespace NadekoBot.Modules.Gambling
{ {
//var channel = (ITextChannel)Context.Channel; //var channel = (ITextChannel)Context.Channel;
AllDecks.AddOrUpdate(Context.Guild, _allDecks.AddOrUpdate(Context.Guild,
(g) => new Cards(), (g) => new Cards(),
(g, c) => (g, c) =>
{ {
@ -67,7 +72,7 @@ namespace NadekoBot.Modules.Gambling
return c; return c;
}); });
await Context.Channel.SendConfirmAsync("Deck reshuffled.").ConfigureAwait(false); await ReplyConfirmLocalized("deck_reshuffled").ConfigureAwait(false);
} }
} }
} }

View File

@ -1,11 +1,10 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using ImageSharp;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using System; using System;
using System.IO; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Image = ImageSharp.Image; using Image = ImageSharp.Image;
@ -14,11 +13,17 @@ namespace NadekoBot.Modules.Gambling
public partial class Gambling public partial class Gambling
{ {
[Group] [Group]
public class FlipCoinCommands : ModuleBase public class FlipCoinCommands : NadekoSubmodule
{ {
private readonly IImagesService _images;
private static NadekoRandom rng { get; } = new NadekoRandom(); private static NadekoRandom rng { get; } = new NadekoRandom();
private const string headsPath = "data/images/coins/heads.png";
private const string tailsPath = "data/images/coins/tails.png"; public FlipCoinCommands()
{
//todo DI in the future, can't atm
_images = NadekoBot.Images;
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Flip(int count = 1) public async Task Flip(int count = 1)
@ -26,22 +31,41 @@ 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(File.Open(headsPath, FileMode.OpenOrCreate), "heads.jpg", $"{Context.User.Mention} flipped " + Format.Code("Heads") + ".").ConfigureAwait(false); {
using (var heads = _images.Heads.ToStream())
{
await Context.Channel.SendFileAsync(heads, "heads.jpg", Context.User.Mention + " " + GetText("flipped", Format.Bold(GetText("heads"))) + ".").ConfigureAwait(false);
}
}
else else
await Context.Channel.SendFileAsync(File.Open(tailsPath, FileMode.OpenOrCreate), "tails.jpg", $"{Context.User.Mention} flipped " + Format.Code("Tails") + ".").ConfigureAwait(false); {
using (var tails = _images.Tails.ToStream())
{
await Context.Channel.SendFileAsync(tails, "tails.jpg", Context.User.Mention + " " + GetText("flipped", Format.Bold(GetText("tails"))) + ".").ConfigureAwait(false);
}
}
return; return;
} }
if (count > 10 || count < 1) if (count > 10 || count < 1)
{ {
await Context.Channel.SendErrorAsync("`Invalid number specified. You can flip 1 to 10 coins.`"); await ReplyErrorLocalized("flip_invalid", 10).ConfigureAwait(false);
return; return;
} }
var imgs = new Image[count]; var imgs = new Image[count];
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
imgs[i] = rng.Next(0, 10) < 5 ? using (var heads = _images.Heads.ToStream())
new Image(File.OpenRead(headsPath)) : using (var tails = _images.Tails.ToStream())
new Image(File.OpenRead(tailsPath)); {
if (rng.Next(0, 10) < 5)
{
imgs[i] = new Image(heads);
}
else
{
imgs[i] = new Image(tails);
}
}
} }
await Context.Channel.SendFileAsync(imgs.Merge().ToStream(), $"{count} coins.png").ConfigureAwait(false); await Context.Channel.SendFileAsync(imgs.Merge().ToStream(), $"{count} coins.png").ConfigureAwait(false);
} }
@ -53,47 +77,49 @@ namespace NadekoBot.Modules.Gambling
if (guessStr != "H" && guessStr != "T" && guessStr != "HEADS" && guessStr != "TAILS") if (guessStr != "H" && guessStr != "T" && guessStr != "HEADS" && guessStr != "TAILS")
return; return;
if (amount < 3) if (amount < NadekoBot.BotConfig.MinimumBetAmount)
{ {
await Context.Channel.SendErrorAsync($"You can't bet less than 3{Gambling.CurrencySign}.") await ReplyErrorLocalized("min_bet_limit", NadekoBot.BotConfig.MinimumBetAmount + CurrencySign).ConfigureAwait(false);
.ConfigureAwait(false);
return; return;
} }
var removed = await CurrencyHandler.RemoveCurrencyAsync(Context.User, "Betflip Gamble", amount, false).ConfigureAwait(false); var removed = await CurrencyHandler.RemoveCurrencyAsync(Context.User, "Betflip Gamble", amount, false).ConfigureAwait(false);
if (!removed) if (!removed)
{ {
await Context.Channel.SendErrorAsync($"{Context.User.Mention} You don't have enough {Gambling.CurrencyPluralName}.").ConfigureAwait(false); await ReplyErrorLocalized("not_enough", CurrencyPluralName).ConfigureAwait(false);
return; return;
} }
//heads = true //heads = true
//tails = false //tails = false
//todo this seems stinky, no time to look at it right now
var isHeads = guessStr == "HEADS" || guessStr == "H"; var isHeads = guessStr == "HEADS" || guessStr == "H";
bool result = false; var result = false;
string imgPathToSend; IEnumerable<byte> imageToSend;
if (rng.Next(0, 2) == 1) if (rng.Next(0, 2) == 1)
{ {
imgPathToSend = headsPath; imageToSend = _images.Heads;
result = true; result = true;
} }
else else
{ {
imgPathToSend = tailsPath; imageToSend = _images.Tails;
} }
string str; string str;
if (isHeads == result) if (isHeads == result)
{ {
var toWin = (int)Math.Round(amount * 1.8); var toWin = (int)Math.Round(amount * NadekoBot.BotConfig.BetflipMultiplier);
str = $"{Context.User.Mention}`You guessed it!` You won {toWin}{Gambling.CurrencySign}"; str = Context.User.Mention + " " + GetText("flip_guess", toWin + CurrencySign);
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betflip Gamble", toWin, false).ConfigureAwait(false); await CurrencyHandler.AddCurrencyAsync(Context.User, "Betflip Gamble", toWin, false).ConfigureAwait(false);
} }
else else
{ {
str = $"{Context.User.Mention}`Better luck next time.`"; str = Context.User.Mention + " " + GetText("better_luck");
}
using (var toSend = imageToSend.ToStream())
{
await Context.Channel.SendFileAsync(toSend, "result.png", str).ConfigureAwait(false);
} }
await Context.Channel.SendFileAsync(File.Open(imgPathToSend, FileMode.OpenOrCreate), "coin.jpg", str).ConfigureAwait(false);
} }
} }
} }

View File

@ -0,0 +1,349 @@
using Discord;
using Discord.Commands;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes;
using NadekoBot.DataStructures;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling
{
public partial class Gambling
{
[Group]
public class FlowerShop : NadekoSubmodule
{
public enum Role
{
Role
}
public enum List
{
List
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Shop(int page = 1)
{
if (page <= 0)
return;
page -= 1;
List<ShopEntry> entries;
using (var uow = DbHandler.UnitOfWork())
{
entries = new IndexedCollection<ShopEntry>(uow.GuildConfigs.For(Context.Guild.Id,
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items)).ShopEntries);
}
await Context.Channel.SendPaginatedConfirmAsync(page + 1, (curPage) =>
{
var theseEntries = entries.Skip((curPage - 1) * 9).Take(9);
if (!theseEntries.Any())
return new EmbedBuilder().WithErrorColor()
.WithDescription(GetText("shop_none"));
var embed = new EmbedBuilder().WithOkColor()
.WithTitle(GetText("shop", CurrencySign));
for (int i = 0; i < entries.Count; i++)
{
var entry = entries[i];
embed.AddField(efb => efb.WithName($"#{i + 1} - {entry.Price}{CurrencySign}").WithValue(EntryToString(entry)).WithIsInline(true));
}
return embed;
}, entries.Count / 9, true);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Buy(int index, [Remainder]string message = null)
{
index -= 1;
if (index < 0)
return;
ShopEntry entry;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set
.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items));
var entries = new IndexedCollection<ShopEntry>(config.ShopEntries);
entry = entries.ElementAtOrDefault(index);
uow.Complete();
}
if (entry == null)
{
await ReplyErrorLocalized("shop_item_not_found").ConfigureAwait(false);
return;
}
if (entry.Type == ShopEntryType.Role)
{
var guser = (IGuildUser)Context.User;
var role = Context.Guild.GetRole(entry.RoleId);
if (role == null)
{
await ReplyErrorLocalized("shop_role_not_found").ConfigureAwait(false);
return;
}
if (await CurrencyHandler.RemoveCurrencyAsync(Context.User.Id, $"Shop purchase - {entry.Type}", entry.Price))
{
try
{
await guser.AddRolesAsync(role).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
await CurrencyHandler.AddCurrencyAsync(Context.User.Id, $"Shop error refund", entry.Price);
await ReplyErrorLocalized("shop_role_purchase_error").ConfigureAwait(false);
return;
}
await CurrencyHandler.AddCurrencyAsync(entry.AuthorId, $"Shop sell item - {entry.Type}", GetProfitAmount(entry.Price));
await ReplyConfirmLocalized("shop_role_purchase", Format.Bold(role.Name)).ConfigureAwait(false);
return;
}
else
{
await ReplyErrorLocalized("not_enough", CurrencySign).ConfigureAwait(false);
return;
}
}
else if (entry.Type == ShopEntryType.List)
{
if (entry.Items.Count == 0)
{
await ReplyErrorLocalized("out_of_stock").ConfigureAwait(false);
return;
}
var item = entry.Items.ToArray()[new NadekoRandom().Next(0, entry.Items.Count)];
if (await CurrencyHandler.RemoveCurrencyAsync(Context.User.Id, $"Shop purchase - {entry.Type}", entry.Price))
{
int removed;
using (var uow = DbHandler.UnitOfWork())
{
var x = uow._context.Set<ShopEntryItem>().Remove(item);
removed = uow.Complete();
}
try
{
await (await Context.User.CreateDMChannelAsync())
.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("shop_purchase", Context.Guild.Name))
.AddField(efb => efb.WithName(GetText("item")).WithValue(item.Text).WithIsInline(false))
.AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("name")).WithValue(entry.Name).WithIsInline(true)))
.ConfigureAwait(false);
await CurrencyHandler.AddCurrencyAsync(entry.AuthorId,
$"Shop sell item - {entry.Name}",
GetProfitAmount(entry.Price)).ConfigureAwait(false);
}
catch
{
using (var uow = DbHandler.UnitOfWork())
{
uow._context.Set<ShopEntryItem>().Add(item);
uow.Complete();
await CurrencyHandler.AddCurrencyAsync(Context.User.Id,
$"Shop error refund - {entry.Name}",
entry.Price,
uow).ConfigureAwait(false);
}
await ReplyErrorLocalized("shop_buy_error").ConfigureAwait(false);
return;
}
await ReplyConfirmLocalized("shop_item_purchase").ConfigureAwait(false);
}
else
{
await ReplyErrorLocalized("not_enough", CurrencySign).ConfigureAwait(false);
return;
}
}
}
private long GetProfitAmount(int price) =>
(int)(Math.Ceiling(0.90 * price));
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task ShopAdd(Role _, int price, [Remainder] IRole role)
{
var entry = new ShopEntry()
{
Name = "-",
Price = price,
Type = ShopEntryType.Role,
AuthorId = Context.User.Id,
RoleId = role.Id,
RoleName = role.Name
};
using (var uow = DbHandler.UnitOfWork())
{
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigs.For(Context.Guild.Id,
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items)).ShopEntries);
entries.Add(entry);
uow.GuildConfigs.For(Context.Guild.Id, set => set).ShopEntries = entries;
uow.Complete();
}
await Context.Channel.EmbedAsync(EntryToEmbed(entry)
.WithTitle(GetText("shop_item_add")));
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task ShopAdd(List _, int price, [Remainder]string name)
{
var entry = new ShopEntry()
{
Name = name.TrimTo(100),
Price = price,
Type = ShopEntryType.List,
AuthorId = Context.User.Id,
Items = new HashSet<ShopEntryItem>(),
};
using (var uow = DbHandler.UnitOfWork())
{
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigs.For(Context.Guild.Id,
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items)).ShopEntries);
entries.Add(entry);
uow.GuildConfigs.For(Context.Guild.Id, set => set).ShopEntries = entries;
uow.Complete();
}
await Context.Channel.EmbedAsync(EntryToEmbed(entry)
.WithTitle(GetText("shop_item_add")));
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task ShopListAdd(int index, [Remainder] string itemText)
{
index -= 1;
if (index < 0)
return;
var item = new ShopEntryItem()
{
Text = itemText
};
ShopEntry entry;
bool rightType = false;
bool added = false;
using (var uow = DbHandler.UnitOfWork())
{
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigs.For(Context.Guild.Id,
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items)).ShopEntries);
entry = entries.ElementAtOrDefault(index);
if (entry != null && (rightType = (entry.Type == ShopEntryType.List)))
{
if (added = entry.Items.Add(item))
{
uow.Complete();
}
}
}
if (entry == null)
await ReplyErrorLocalized("shop_item_not_found").ConfigureAwait(false);
else if (!rightType)
await ReplyErrorLocalized("shop_item_wrong_type").ConfigureAwait(false);
else if (added == false)
await ReplyErrorLocalized("shop_list_item_not_unique").ConfigureAwait(false);
else
await ReplyConfirmLocalized("shop_list_item_added").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task ShopRemove(int index)
{
index -= 1;
if (index < 0)
return;
ShopEntry removed;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set
.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items));
var entries = new IndexedCollection<ShopEntry>(config.ShopEntries);
removed = entries.ElementAtOrDefault(index);
if (removed != null)
{
entries.Remove(removed);
config.ShopEntries = entries;
uow.Complete();
}
}
if (removed == null)
await ReplyErrorLocalized("shop_item_not_found").ConfigureAwait(false);
else
await Context.Channel.EmbedAsync(EntryToEmbed(removed)
.WithTitle(GetText("shop_item_rm")));
}
public EmbedBuilder EntryToEmbed(ShopEntry entry)
{
var embed = new EmbedBuilder().WithOkColor();
if (entry.Type == ShopEntryType.Role)
return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(GetText("shop_role", Format.Bold(entry.RoleName))).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("type")).WithValue(entry.Type.ToString()).WithIsInline(true));
else if (entry.Type == ShopEntryType.List)
return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(entry.Name).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("type")).WithValue(GetText("random_unique_item")).WithIsInline(true));
//else if (entry.Type == ShopEntryType.Infinite_List)
// return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(GetText("shop_role", Format.Bold(entry.RoleName))).WithIsInline(true))
// .AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true))
// .AddField(efb => efb.WithName(GetText("type")).WithValue(entry.Type.ToString()).WithIsInline(true));
else return null;
}
public string EntryToString(ShopEntry entry)
{
if (entry.Type == ShopEntryType.Role)
{
return GetText("shop_role", Format.Bold(entry.RoleName));
}
else if (entry.Type == ShopEntryType.List)
{
return GetText("unique_items_left", entry.Items.Count) + "\n" + entry.Name;
}
//else if (entry.Type == ShopEntryType.Infinite_List)
//{
//}
return "";
}
}
}
}

View File

@ -0,0 +1,197 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using Discord;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
namespace NadekoBot.Modules.Gambling
{
//public partial class Gambling
//{
// [Group]
// public class Lucky7Commands : NadekoSubmodule
// {
// [NadekoCommand, Usage, Description, Aliases]
// [RequireContext(ContextType.Guild)]
// [OwnerOnly]
// public async Task Lucky7Test(uint tests)
// {
// if (tests <= 0)
// return;
// var dict = new Dictionary<float, int>();
// var totalWon = 0;
// for (var i = 0; i < tests; i++)
// {
// var g = new Lucky7Game(10);
// while (!g.Ended)
// {
// if (g.CurrentPosition == 0)
// g.Stay();
// else
// g.Move();
// }
// totalWon += (int)(g.CurrentMultiplier * g.Bet);
// if (!dict.ContainsKey(g.CurrentMultiplier))
// dict.Add(g.CurrentMultiplier, 0);
// dict[g.CurrentMultiplier] ++;
// }
// await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
// .WithTitle("Move Or Stay test")
// .WithDescription(string.Join("\n",
// dict.Select(x => $"x{x.Key} occured {x.Value} times {x.Value * 1.0f / tests * 100:F2}%")))
// .WithFooter(
// efb => efb.WithText($"Total Bet: {tests * 10} | Payout: {totalWon} | {totalWon *1.0f / tests * 10}%")));
// }
// private static readonly ConcurrentDictionary<ulong, Lucky7Game> _games =
// new ConcurrentDictionary<ulong, Lucky7Game>();
// [NadekoCommand, Usage, Description, Aliases]
// [RequireContext(ContextType.Guild)]
// public async Task Lucky7(int bet)
// {
// if (bet < 4)
// return;
// var game = new Lucky7Game(bet);
// if (!_games.TryAdd(Context.User.Id, game))
// {
// await ReplyAsync("You're already betting on move or stay.").ConfigureAwait(false);
// return;
// }
// if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User, "MoveOrStay bet", bet, false))
// {
// _games.TryRemove(Context.User.Id, out game);
// await ReplyConfirmLocalized("not_enough", CurrencySign).ConfigureAwait(false);
// return;
// }
// await Context.Channel.EmbedAsync(GetGameState(game),
// string.Format("{0} rolled {1}.", Context.User, game.Rolled)).ConfigureAwait(false);
// }
// public enum MoveOrStay
// {
// Move = 1,
// M = 1,
// Stay = 2,
// S = 2
// }
// [NadekoCommand, Usage, Description, Aliases]
// [RequireContext(ContextType.Guild)]
// public async Task Lucky7(MoveOrStay action)
// {
// Lucky7Game game;
// if (!_games.TryGetValue(Context.User.Id, out game))
// {
// await ReplyAsync("You're not betting on move or stay.").ConfigureAwait(false);
// return;
// }
// if (action == MoveOrStay.Move)
// {
// game.Move();
// await Context.Channel.EmbedAsync(GetGameState(game),
// string.Format("{0} rolled {1}.", Context.User, game.Rolled)).ConfigureAwait(false);
// if (game.Ended)
// _games.TryRemove(Context.User.Id, out game);
// }
// else if (action == MoveOrStay.Stay)
// {
// var won = game.Stay();
// await CurrencyHandler.AddCurrencyAsync(Context.User, "MoveOrStay stay", won, false)
// .ConfigureAwait(false);
// _games.TryRemove(Context.User.Id, out game);
// await ReplyAsync(string.Format("You've finished with {0}",
// won + CurrencySign))
// .ConfigureAwait(false);
// }
// }
// private EmbedBuilder GetGameState(Lucky7Game game)
// {
// var arr = Lucky7Game.Winnings.ToArray();
// var sb = new StringBuilder();
// for (var i = 0; i < arr.Length; i++)
// {
// if (i == game.CurrentPosition)
// {
// sb.Append("[" + arr[i] + "]");
// }
// else
// {
// sb.Append(arr[i].ToString());
// }
// if (i != arr.Length - 1)
// sb.Append(' ');
// }
// return new EmbedBuilder().WithOkColor()
// .WithTitle("Lucky7")
// .WithDescription(sb.ToString())
// .AddField(efb => efb.WithName("Bet")
// .WithValue(game.Bet.ToString())
// .WithIsInline(true))
// .AddField(efb => efb.WithName("Current Value")
// .WithValue((game.CurrentMultiplier * game.Bet).ToString(_cultureInfo))
// .WithIsInline(true));
// }
// }
// public class Lucky7Game
// {
// public int Bet { get; }
// public bool Ended { get; private set; }
// public int PreviousPosition { get; private set; }
// public int CurrentPosition { get; private set; } = -1;
// public int Rolled { get; private set; }
// public float CurrentMultiplier => Winnings[CurrentPosition];
// private readonly NadekoRandom _rng = new NadekoRandom();
// public static readonly ImmutableArray<float> Winnings = new[]
// {
// 1.2f, 0.8f, 0.75f, 0.90f, 0.7f, 0.5f, 1.8f, 0f, 0f
// }.ToImmutableArray();
// public Lucky7Game(int bet)
// {
// Bet = bet;
// Move();
// }
// public void Move()
// {
// if (Ended)
// return;
// PreviousPosition = CurrentPosition;
// Rolled = _rng.Next(1, 4);
// CurrentPosition += Rolled;
// if (CurrentPosition >= 6)
// Ended = true;
// }
// public int Stay()
// {
// if (Ended)
// return 0;
// Ended = true;
// return (int) (CurrentMultiplier * Bet);
// }
// }
//}
}

View File

@ -0,0 +1,233 @@
using Discord;
using Discord.Commands;
using ImageSharp;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling
{
public partial class Gambling
{
[Group]
public class Slots : NadekoSubmodule
{
private static int _totalBet;
private static int _totalPaidOut;
private const int _alphaCutOut = byte.MaxValue / 3;
//here is a payout chart
//https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg
//thanks to judge for helping me with this
private readonly IImagesService _images;
public Slots()
{
_images = NadekoBot.Images;
}
public class SlotMachine
{
public const int MaxValue = 5;
static readonly List<Func<int[], int>> _winningCombos = new List<Func<int[], int>>()
{
//three flowers
(arr) => arr.All(a=>a==MaxValue) ? 30 : 0,
//three of the same
(arr) => !arr.Any(a => a != arr[0]) ? 10 : 0,
//two flowers
(arr) => arr.Count(a => a == MaxValue) == 2 ? 4 : 0,
//one flower
(arr) => arr.Any(a => a == MaxValue) ? 1 : 0,
};
public static SlotResult Pull()
{
var numbers = new int[3];
for (var i = 0; i < numbers.Length; i++)
{
numbers[i] = new NadekoRandom().Next(0, MaxValue + 1);
}
var multi = 0;
foreach (var t in _winningCombos)
{
multi = t(numbers);
if (multi != 0)
break;
}
return new SlotResult(numbers, multi);
}
public struct SlotResult
{
public int[] Numbers { get; }
public int Multiplier { get; }
public SlotResult(int[] nums, int multi)
{
Numbers = nums;
Multiplier = multi;
}
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task SlotStats()
{
//i remembered to not be a moron
var paid = _totalPaidOut;
var bet = _totalBet;
if (bet <= 0)
bet = 1;
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle("Slot Stats")
.AddField(efb => efb.WithName("Total Bet").WithValue(bet.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("Paid Out").WithValue(paid.ToString()).WithIsInline(true))
.WithFooter(efb => efb.WithText($"Payout Rate: {paid * 1.0 / bet * 100:f4}%"));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task SlotTest(int tests = 1000)
{
if (tests <= 0)
return;
//multi vs how many times it occured
var dict = new Dictionary<int, int>();
for (int i = 0; i < tests; i++)
{
var res = SlotMachine.Pull();
if (dict.ContainsKey(res.Multiplier))
dict[res.Multiplier] += 1;
else
dict.Add(res.Multiplier, 1);
}
var sb = new StringBuilder();
const int bet = 1;
int payout = 0;
foreach (var key in dict.Keys.OrderByDescending(x => x))
{
sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%");
payout += key * dict[key];
}
await Context.Channel.SendConfirmAsync("Slot Test Results", sb.ToString(),
footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%");
}
private static readonly HashSet<ulong> _runningUsers = new HashSet<ulong>();
[NadekoCommand, Usage, Description, Aliases]
public async Task Slot(int amount = 0)
{
if (!_runningUsers.Add(Context.User.Id))
return;
try
{
if (amount < 1)
{
await ReplyErrorLocalized("min_bet_limit", 1 + CurrencySign).ConfigureAwait(false);
return;
}
const int maxAmount = 9999;
if (amount > maxAmount)
{
GetText("slot_maxbet", maxAmount + CurrencySign);
await ReplyErrorLocalized("max_bet_limit", maxAmount + CurrencySign).ConfigureAwait(false);
return;
}
if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User, "Slot Machine", amount, false))
{
await ReplyErrorLocalized("not_enough", CurrencySign).ConfigureAwait(false);
return;
}
Interlocked.Add(ref _totalBet, amount);
using (var bgFileStream = NadekoBot.Images.SlotBackground.ToStream())
{
var bgImage = new ImageSharp.Image(bgFileStream);
var result = SlotMachine.Pull();
int[] numbers = result.Numbers;
for (int i = 0; i < 3; i++)
{
using (var file = _images.SlotEmojis[numbers[i]].ToStream())
using (var randomImage = new ImageSharp.Image(file))
{
bgImage.DrawImage(randomImage, 100, default(Size), new Point(95 + 142 * i, 330));
}
}
var won = amount * result.Multiplier;
var printWon = won;
var n = 0;
do
{
var digit = printWon % 10;
using (var fs = NadekoBot.Images.SlotNumbers[digit].ToStream())
using (var img = new ImageSharp.Image(fs))
{
bgImage.DrawImage(img, 100, default(Size), new Point(230 - n * 16, 462));
}
n++;
} while ((printWon /= 10) != 0);
var printAmount = amount;
n = 0;
do
{
var digit = printAmount % 10;
using (var fs = _images.SlotNumbers[digit].ToStream())
using (var img = new ImageSharp.Image(fs))
{
bgImage.DrawImage(img, 100, default(Size), new Point(395 - n * 16, 462));
}
n++;
} while ((printAmount /= 10) != 0);
var msg = GetText("better_luck");
if (result.Multiplier != 0)
{
await CurrencyHandler.AddCurrencyAsync(Context.User, $"Slot Machine x{result.Multiplier}", amount * result.Multiplier, false);
Interlocked.Add(ref _totalPaidOut, amount * result.Multiplier);
if (result.Multiplier == 1)
msg = GetText("slot_single", CurrencySign, 1);
else if (result.Multiplier == 4)
msg = GetText("slot_two", CurrencySign, 4);
else if (result.Multiplier == 10)
msg = GetText("slot_three", 10);
else if (result.Multiplier == 30)
msg = GetText("slot_jackpot", 30);
}
await Context.Channel.SendFileAsync(bgImage.ToStream(), "result.png", Context.User.Mention + " " + msg + $"\n`{GetText("slot_bet")}:`{amount} `{GetText("slot_won")}:` {amount * result.Multiplier}{NadekoBot.BotConfig.CurrencySign}").ConfigureAwait(false);
}
}
finally
{
var _ = Task.Run(async () =>
{
await Task.Delay(1500);
_runningUsers.Remove(Context.User.Id);
});
}
}
}
}
}

View File

@ -0,0 +1,529 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling
{
public partial class Gambling
{
public enum ClaimTitles
{
Lonely,
Devoted,
Rookie,
Schemer,
Dilettante,
Intermediate,
Seducer,
Expert,
Veteran,
Incubis,
Harem_King,
Harem_God,
}
public enum AffinityTitles
{
Pure,
Faithful,
Defiled,
Cheater,
Tainted,
Corrupted,
Lewd,
Sloot,
Depraved,
Harlot
}
[Group]
public class WaifuClaimCommands : NadekoSubmodule
{
private static ConcurrentDictionary<ulong, DateTime> divorceCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
private static ConcurrentDictionary<ulong, DateTime> affinityCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
enum WaifuClaimResult
{
Success,
NotEnoughFunds,
InsufficientAmount
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WaifuClaim(int amount, [Remainder]IUser target)
{
if (amount < 50)
{
await ReplyErrorLocalized("waifu_isnt_cheap", 50 + CurrencySign).ConfigureAwait(false);
return;
}
if (target.Id == Context.User.Id)
{
await ReplyErrorLocalized("waifu_not_yourself").ConfigureAwait(false);
return;
}
WaifuClaimResult result;
WaifuInfo w;
bool isAffinity;
using (var uow = DbHandler.UnitOfWork())
{
w = uow.Waifus.ByWaifuUserId(target.Id);
isAffinity = (w?.Affinity?.UserId == Context.User.Id);
if (w == null)
{
var claimer = uow.DiscordUsers.GetOrCreate(Context.User);
var waifu = uow.DiscordUsers.GetOrCreate(target);
if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User.Id, "Claimed Waifu", amount, uow).ConfigureAwait(false))
{
result = WaifuClaimResult.NotEnoughFunds;
}
else
{
uow.Waifus.Add(w = new WaifuInfo()
{
Waifu = waifu,
Claimer = claimer,
Affinity = null,
Price = amount
});
uow._context.WaifuUpdates.Add(new WaifuUpdate()
{
User = waifu,
Old = null,
New = claimer,
UpdateType = WaifuUpdateType.Claimed
});
result = WaifuClaimResult.Success;
}
}
else if (isAffinity && amount > w.Price * 0.88f)
{
if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User.Id, "Claimed Waifu", amount, uow).ConfigureAwait(false))
{
result = WaifuClaimResult.NotEnoughFunds;
}
else
{
var oldClaimer = w.Claimer;
w.Claimer = uow.DiscordUsers.GetOrCreate(Context.User);
w.Price = amount + (amount / 4);
result = WaifuClaimResult.Success;
uow._context.WaifuUpdates.Add(new WaifuUpdate()
{
User = w.Waifu,
Old = oldClaimer,
New = w.Claimer,
UpdateType = WaifuUpdateType.Claimed
});
}
}
else if (amount >= w.Price * 1.1f) // if no affinity
{
if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User.Id, "Claimed Waifu", amount, uow).ConfigureAwait(false))
{
result = WaifuClaimResult.NotEnoughFunds;
}
else
{
var oldClaimer = w.Claimer;
w.Claimer = uow.DiscordUsers.GetOrCreate(Context.User);
w.Price = amount;
result = WaifuClaimResult.Success;
uow._context.WaifuUpdates.Add(new WaifuUpdate()
{
User = w.Waifu,
Old = oldClaimer,
New = w.Claimer,
UpdateType = WaifuUpdateType.Claimed
});
}
}
else
result = WaifuClaimResult.InsufficientAmount;
await uow.CompleteAsync().ConfigureAwait(false);
}
if (result == WaifuClaimResult.InsufficientAmount)
{
await ReplyErrorLocalized("waifu_not_enough", Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f))).ConfigureAwait(false);
return;
}
if (result == WaifuClaimResult.NotEnoughFunds)
{
await ReplyErrorLocalized("not_enough", CurrencySign).ConfigureAwait(false);
return;
}
var msg = GetText("waifu_claimed",
Format.Bold(target.ToString()),
amount + CurrencySign);
if (w.Affinity?.UserId == Context.User.Id)
msg += "\n" + GetText("waifu_fulfilled", target, w.Price + CurrencySign);
await Context.Channel.SendConfirmAsync(Context.User.Mention + msg).ConfigureAwait(false);
}
public enum DivorceResult
{
Success,
SucessWithPenalty,
NotYourWife,
Cooldown
}
private static readonly TimeSpan _divorceLimit = TimeSpan.FromHours(6);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Divorce([Remainder]IUser target)
{
if (target.Id == Context.User.Id)
return;
DivorceResult result;
var difference = TimeSpan.Zero;
var amount = 0;
WaifuInfo w = null;
using (var uow = DbHandler.UnitOfWork())
{
w = uow.Waifus.ByWaifuUserId(target.Id);
var now = DateTime.UtcNow;
if (w?.Claimer == null || w.Claimer.UserId != Context.User.Id)
result = DivorceResult.NotYourWife;
else if (divorceCooldowns.AddOrUpdate(Context.User.Id,
now,
(key, old) => ((difference = now.Subtract(old)) > _divorceLimit) ? now : old) != now)
{
result = DivorceResult.Cooldown;
}
else
{
amount = w.Price / 2;
if (w.Affinity?.UserId == Context.User.Id)
{
await CurrencyHandler.AddCurrencyAsync(w.Waifu.UserId, "Waifu Compensation", amount, uow).ConfigureAwait(false);
w.Price = (int)Math.Floor(w.Price * 0.75f);
result = DivorceResult.SucessWithPenalty;
}
else
{
await CurrencyHandler.AddCurrencyAsync(Context.User.Id, "Waifu Refund", amount, uow).ConfigureAwait(false);
result = DivorceResult.Success;
}
var oldClaimer = w.Claimer;
w.Claimer = null;
uow._context.WaifuUpdates.Add(new WaifuUpdate()
{
User = w.Waifu,
Old = oldClaimer,
New = null,
UpdateType = WaifuUpdateType.Claimed
});
}
await uow.CompleteAsync().ConfigureAwait(false);
}
if (result == DivorceResult.SucessWithPenalty)
{
await ReplyConfirmLocalized("waifu_divorced_like", Format.Bold(w.Waifu.ToString()), amount + CurrencySign).ConfigureAwait(false);
}
else if (result == DivorceResult.Success)
{
await ReplyConfirmLocalized("waifu_divorced_notlike", amount + CurrencySign).ConfigureAwait(false);
}
else if (result == DivorceResult.NotYourWife)
{
await ReplyErrorLocalized("waifu_not_yours").ConfigureAwait(false);
}
else
{
var remaining = _divorceLimit.Subtract(difference);
await ReplyErrorLocalized("waifu_recent_divorce",
Format.Bold(((int)remaining.TotalHours).ToString()),
Format.Bold(remaining.Minutes.ToString())).ConfigureAwait(false);
}
}
private static readonly TimeSpan _affinityLimit = TimeSpan.FromMinutes(30);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WaifuClaimerAffinity([Remainder]IUser u = null)
{
if (u?.Id == Context.User.Id)
{
await ReplyErrorLocalized("waifu_egomaniac").ConfigureAwait(false);
return;
}
DiscordUser oldAff = null;
var sucess = false;
var cooldown = false;
var difference = TimeSpan.Zero;
using (var uow = DbHandler.UnitOfWork())
{
var w = uow.Waifus.ByWaifuUserId(Context.User.Id);
var newAff = u == null ? null : uow.DiscordUsers.GetOrCreate(u);
var now = DateTime.UtcNow;
if (w?.Affinity?.UserId == u?.Id)
{
}
else if (affinityCooldowns.AddOrUpdate(Context.User.Id,
now,
(key, old) => ((difference = now.Subtract(old)) > _affinityLimit) ? now : old) != now)
{
cooldown = true;
}
else if (w == null)
{
var thisUser = uow.DiscordUsers.GetOrCreate(Context.User);
uow.Waifus.Add(new WaifuInfo()
{
Affinity = newAff,
Waifu = thisUser,
Price = 1,
Claimer = null
});
sucess = true;
uow._context.WaifuUpdates.Add(new WaifuUpdate()
{
User = thisUser,
Old = null,
New = newAff,
UpdateType = WaifuUpdateType.AffinityChanged
});
}
else
{
if (w.Affinity != null)
oldAff = w.Affinity;
w.Affinity = newAff;
sucess = true;
uow._context.WaifuUpdates.Add(new WaifuUpdate()
{
User = w.Waifu,
Old = oldAff,
New = newAff,
UpdateType = WaifuUpdateType.AffinityChanged
});
}
await uow.CompleteAsync().ConfigureAwait(false);
}
if (!sucess)
{
if (cooldown)
{
var remaining = _affinityLimit.Subtract(difference);
await ReplyErrorLocalized("waifu_affinity_cooldown",
Format.Bold(((int)remaining.TotalHours).ToString()),
Format.Bold(remaining.Minutes.ToString())).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalized("waifu_affinity_already").ConfigureAwait(false);
}
return;
}
if (u == null)
{
await ReplyConfirmLocalized("waifu_affinity_reset").ConfigureAwait(false);
}
else if (oldAff == null)
{
await ReplyConfirmLocalized("waifu_affinity_set", Format.Bold(u.ToString())).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("waifu_affinity_changed", Format.Bold(oldAff.ToString()), Format.Bold(u.ToString())).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WaifuLeaderboard()
{
IList<WaifuInfo> waifus;
using (var uow = DbHandler.UnitOfWork())
{
waifus = uow.Waifus.GetTop(9);
}
if (waifus.Count == 0)
{
await ReplyConfirmLocalized("waifus_none").ConfigureAwait(false);
return;
}
var embed = new EmbedBuilder()
.WithTitle(GetText("waifus_top_waifus"))
.WithOkColor();
for (var i = 0; i < waifus.Count; i++)
{
var w = waifus[i];
var j = i;
embed.AddField(efb => efb.WithName("#" + (j + 1) + " - " + w.Price + NadekoBot.BotConfig.CurrencySign).WithValue(w.ToString()).WithIsInline(false));
}
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WaifuInfo([Remainder]IUser target = null)
{
if (target == null)
target = Context.User;
WaifuInfo w;
IList<WaifuInfo> claims;
int divorces;
using (var uow = DbHandler.UnitOfWork())
{
w = uow.Waifus.ByWaifuUserId(target.Id);
claims = uow.Waifus.ByClaimerUserId(target.Id);
divorces = uow._context.WaifuUpdates.Count(x => x.Old != null &&
x.Old.UserId == target.Id &&
x.UpdateType == WaifuUpdateType.Claimed &&
x.New == null);
if (w == null)
{
uow.Waifus.Add(w = new WaifuInfo()
{
Affinity = null,
Claimer = null,
Price = 1,
Waifu = uow.DiscordUsers.GetOrCreate(target),
});
}
w.Waifu.Username = target.Username;
w.Waifu.Discriminator = target.Discriminator;
await uow.CompleteAsync().ConfigureAwait(false);
}
var claimInfo = GetClaimTitle(target.Id);
var affInfo = GetAffinityTitle(target.Id);
var rng = new NadekoRandom();
var nobody = GetText("nobody");
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle("Waifu " + w.Waifu + " - \"the " + claimInfo.Title + "\"")
.AddField(efb => efb.WithName(GetText("price")).WithValue(w.Price.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("claimed_by")).WithValue(w.Claimer?.ToString() ?? nobody).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("likes")).WithValue(w.Affinity?.ToString() ?? nobody).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("changes_of_heart")).WithValue($"{affInfo.Count} - \"the {affInfo.Title}\"").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("divorces")).WithValue(divorces.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName($"Waifus ({claims.Count})").WithValue(claims.Count == 0 ? nobody : string.Join("\n", claims.OrderBy(x => rng.Next()).Take(30).Select(x => x.Waifu))).WithIsInline(true));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
public struct WaifuProfileTitle
{
public int Count { get; }
public string Title { get; }
public WaifuProfileTitle(int count, string title)
{
Count = count;
Title = title;
}
}
private static WaifuProfileTitle GetClaimTitle(ulong userId)
{
int count;
using (var uow = DbHandler.UnitOfWork())
{
count = uow.Waifus.ByClaimerUserId(userId).Count;
}
ClaimTitles title;
if (count == 0)
title = ClaimTitles.Lonely;
else if (count == 1)
title = ClaimTitles.Devoted;
else if (count < 4)
title = ClaimTitles.Rookie;
else if (count < 6)
title = ClaimTitles.Schemer;
else if (count < 8)
title = ClaimTitles.Dilettante;
else if (count < 10)
title = ClaimTitles.Intermediate;
else if (count < 12)
title = ClaimTitles.Seducer;
else if (count < 15)
title = ClaimTitles.Expert;
else if (count < 17)
title = ClaimTitles.Veteran;
else if (count < 25)
title = ClaimTitles.Incubis;
else if (count < 50)
title = ClaimTitles.Harem_King;
else
title = ClaimTitles.Harem_God;
return new WaifuProfileTitle(count, title.ToString().Replace('_', ' '));
}
private static WaifuProfileTitle GetAffinityTitle(ulong userId)
{
int count;
using (var uow = DbHandler.UnitOfWork())
{
count = uow._context.WaifuUpdates
.Where(w => w.User.UserId == userId && w.UpdateType == WaifuUpdateType.AffinityChanged && w.New != null)
.GroupBy(x => x.New)
.Count();
}
AffinityTitles title;
if (count < 1)
title = AffinityTitles.Pure;
else if (count < 2)
title = AffinityTitles.Faithful;
else if (count < 4)
title = AffinityTitles.Defiled;
else if (count < 7)
title = AffinityTitles.Cheater;
else if (count < 9)
title = AffinityTitles.Tainted;
else if (count < 11)
title = AffinityTitles.Corrupted;
else if (count < 13)
title = AffinityTitles.Lewd;
else if (count < 15)
title = AffinityTitles.Sloot;
else if (count < 17)
title = AffinityTitles.Depraved;
else
title = AffinityTitles.Harlot;
return new WaifuProfileTitle(count, title.ToString().Replace('_', ' '));
}
}
}
}

View File

@ -1,9 +1,9 @@
using Discord; using System;
using Discord;
using Discord.Commands; using Discord.Commands;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
@ -12,7 +12,7 @@ using System.Collections.Generic;
namespace NadekoBot.Modules.Gambling namespace NadekoBot.Modules.Gambling
{ {
[NadekoModule("Gambling", "$")] [NadekoModule("Gambling", "$")]
public partial class Gambling : DiscordModule public partial class Gambling : NadekoTopLevelModule
{ {
public static string CurrencyName { get; set; } public static string CurrencyName { get; set; }
public static string CurrencyPluralName { get; set; } public static string CurrencyPluralName { get; set; }
@ -20,14 +20,9 @@ namespace NadekoBot.Modules.Gambling
static Gambling() static Gambling()
{ {
using (var uow = DbHandler.UnitOfWork()) CurrencyName = NadekoBot.BotConfig.CurrencyName;
{ CurrencyPluralName = NadekoBot.BotConfig.CurrencyPluralName;
var conf = uow.BotConfig.GetOrCreate(); CurrencySign = NadekoBot.BotConfig.CurrencySign;
CurrencyName = conf.CurrencyName;
CurrencySign = conf.CurrencySign;
CurrencyPluralName = conf.CurrencyPluralName;
}
} }
public static long GetCurrency(ulong id) public static long GetCurrency(ulong id)
@ -47,23 +42,24 @@ namespace NadekoBot.Modules.Gambling
var members = role.Members().Where(u => u.Status != UserStatus.Offline && u.Status != UserStatus.Unknown); var members = role.Members().Where(u => u.Status != UserStatus.Offline && u.Status != UserStatus.Unknown);
var membersArray = members as IUser[] ?? members.ToArray(); var membersArray = members as IUser[] ?? members.ToArray();
var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)]; var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)];
await Context.Channel.SendConfirmAsync("🎟 Raffled user", $"**{usr.Username}#{usr.Discriminator}** ID: `{usr.Id}`").ConfigureAwait(false); await Context.Channel.SendConfirmAsync("🎟 "+ GetText("raffled_user"), $"**{usr.Username}#{usr.Discriminator}**", footer: $"ID: {usr.Id}").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[Priority(0)] [Priority(0)]
public async Task Cash([Remainder] IUser user = null) public async Task Cash([Remainder] IUser user = null)
{ {
user = user ?? Context.User; if(user == null)
await ConfirmLocalized("has", Format.Bold(Context.User.ToString()), $"{GetCurrency(Context.User.Id)} {CurrencySign}").ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"{user.Username} has {GetCurrency(user.Id)} {CurrencySign}").ConfigureAwait(false); else
await ReplyConfirmLocalized("has", Format.Bold(user.ToString()), $"{GetCurrency(user.Id)} {CurrencySign}").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[Priority(1)] [Priority(1)]
public async Task Cash(ulong userId) public async Task Cash(ulong userId)
{ {
await Context.Channel.SendConfirmAsync($"`{userId}` has {GetCurrency(userId)} {CurrencySign}").ConfigureAwait(false); await ReplyConfirmLocalized("has", Format.Code(userId.ToString()), $"{GetCurrency(userId)} {CurrencySign}").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -72,14 +68,15 @@ namespace NadekoBot.Modules.Gambling
{ {
if (amount <= 0 || Context.User.Id == receiver.Id) if (amount <= 0 || Context.User.Id == receiver.Id)
return; return;
var success = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount, true).ConfigureAwait(false); var success = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount, false).ConfigureAwait(false);
if (!success) if (!success)
{ {
await Context.Channel.SendErrorAsync($"{Context.User.Mention} You don't have enough {Gambling.CurrencyPluralName}.").ConfigureAwait(false); await ReplyErrorLocalized("not_enough", CurrencyPluralName).ConfigureAwait(false);
return; return;
} }
await CurrencyHandler.AddCurrencyAsync(receiver, $"Gift from {Context.User.Username} ({Context.User.Id}).", amount, true).ConfigureAwait(false); await CurrencyHandler.AddCurrencyAsync(receiver, $"Gift from {Context.User.Username} ({Context.User.Id}).", amount, true).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} successfully sent {amount} {(amount == 1 ? Gambling.CurrencyName : Gambling.CurrencyPluralName)} to {receiver.Mention}!").ConfigureAwait(false); await ReplyConfirmLocalized("gifted", amount + CurrencySign, Format.Bold(receiver.ToString()))
.ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -98,8 +95,7 @@ namespace NadekoBot.Modules.Gambling
return; return;
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 ReplyConfirmLocalized("awarded", amount + CurrencySign, $"<@{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]
@ -108,7 +104,6 @@ namespace NadekoBot.Modules.Gambling
[Priority(0)] [Priority(0)]
public async Task Award(int amount, [Remainder] IRole role) public async Task Award(int amount, [Remainder] IRole role)
{ {
var channel = (ITextChannel)Context.Channel;
var users = (await Context.Guild.GetUsersAsync()) var users = (await Context.Guild.GetUsersAsync())
.Where(u => u.GetRoles().Contains(role)) .Where(u => u.GetRoles().Contains(role))
.ToList(); .ToList();
@ -117,9 +112,10 @@ namespace NadekoBot.Modules.Gambling
amount))) amount)))
.ConfigureAwait(false); .ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"Awarded `{amount}` {Gambling.CurrencyPluralName} to `{users.Count}` users from `{role.Name}` role.") await ReplyConfirmLocalized("mass_award",
.ConfigureAwait(false); amount + CurrencySign,
Format.Bold(users.Count.ToString()),
Format.Bold(role.Name)).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -131,9 +127,9 @@ namespace NadekoBot.Modules.Gambling
return; return;
if (await CurrencyHandler.RemoveCurrencyAsync(user, $"Taken by bot owner.({Context.User.Username}/{Context.User.Id})", amount, true).ConfigureAwait(false)) if (await CurrencyHandler.RemoveCurrencyAsync(user, $"Taken by bot owner.({Context.User.Username}/{Context.User.Id})", amount, true).ConfigureAwait(false))
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} successfully took {amount} {(amount == 1 ? Gambling.CurrencyName : Gambling.CurrencyPluralName)} from {user}!").ConfigureAwait(false); await ReplyConfirmLocalized("take", amount+CurrencySign, Format.Bold(user.ToString())).ConfigureAwait(false);
else else
await Context.Channel.SendErrorAsync($"{Context.User.Mention} was unable to take {amount} {(amount == 1 ? Gambling.CurrencyName : Gambling.CurrencyPluralName)} from {user} because the user doesn't have that much {Gambling.CurrencyPluralName}!").ConfigureAwait(false); await ReplyErrorLocalized("take_fail", amount + CurrencySign, Format.Bold(user.ToString()), CurrencyPluralName).ConfigureAwait(false);
} }
@ -145,75 +141,148 @@ namespace NadekoBot.Modules.Gambling
return; return;
if (await CurrencyHandler.RemoveCurrencyAsync(usrId, $"Taken by bot owner.({Context.User.Username}/{Context.User.Id})", amount).ConfigureAwait(false)) if (await CurrencyHandler.RemoveCurrencyAsync(usrId, $"Taken by bot owner.({Context.User.Username}/{Context.User.Id})", amount).ConfigureAwait(false))
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} successfully took {amount} {(amount == 1 ? Gambling.CurrencyName : Gambling.CurrencyPluralName)} from <@{usrId}>!").ConfigureAwait(false); await ReplyConfirmLocalized("take", amount + CurrencySign, $"<@{usrId}>").ConfigureAwait(false);
else else
await Context.Channel.SendErrorAsync($"{Context.User.Mention} was unable to take {amount} {(amount == 1 ? Gambling.CurrencyName : Gambling.CurrencyPluralName)} from `{usrId}` because the user doesn't have that much {Gambling.CurrencyPluralName}!").ConfigureAwait(false); await ReplyErrorLocalized("take_fail", amount + CurrencySign, Format.Code(usrId.ToString()), CurrencyPluralName).ConfigureAwait(false);
} }
//[NadekoCommand, Usage, Description, Aliases]
//[OwnerOnly]
//public Task BrTest(int tests = 1000)
//{
// var t = Task.Run(async () =>
// {
// if (tests <= 0)
// return;
// //multi vs how many times it occured
// var dict = new Dictionary<int, int>();
// var generator = new NadekoRandom();
// for (int i = 0; i < tests; i++)
// {
// var rng = generator.Next(0, 101);
// var mult = 0;
// if (rng < 67)
// {
// mult = 0;
// }
// else if (rng < 91)
// {
// mult = 2;
// }
// else if (rng < 100)
// {
// mult = 4;
// }
// else
// mult = 10;
// if (dict.ContainsKey(mult))
// dict[mult] += 1;
// else
// dict.Add(mult, 1);
// }
// var sb = new StringBuilder();
// const int bet = 1;
// int payout = 0;
// foreach (var key in dict.Keys.OrderByDescending(x => x))
// {
// sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%");
// payout += key * dict[key];
// }
// try
// {
// await Context.Channel.SendConfirmAsync("BetRoll Test Results", sb.ToString(),
// footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%");
// }
// catch { }
// });
// return Task.CompletedTask;
//}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task BetRoll(long amount) public async Task BetRoll(long amount)
{ {
if (amount < 1) if (amount < 1)
return; return;
long userFlowers; if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User, "Betroll Gamble", amount, false).ConfigureAwait(false))
using (var uow = DbHandler.UnitOfWork())
{ {
userFlowers = uow.Currency.GetOrCreate(Context.User.Id).Amount; await ReplyErrorLocalized("not_enough", CurrencyPluralName).ConfigureAwait(false);
}
if (userFlowers < amount)
{
await Context.Channel.SendErrorAsync($"{Context.User.Mention} You don't have enough {Gambling.CurrencyPluralName}. You only have {userFlowers}{Gambling.CurrencySign}.").ConfigureAwait(false);
return; return;
} }
await CurrencyHandler.RemoveCurrencyAsync(Context.User, "Betroll Gamble", amount, false).ConfigureAwait(false); var rnd = new NadekoRandom().Next(0, 101);
var str = Context.User.Mention + Format.Code(GetText("roll", rnd));
var rng = new NadekoRandom().Next(0, 101); if (rnd < 67)
var str = $"{Context.User.Mention} `You rolled {rng}.` ";
if (rng < 67)
{ {
str += "Better luck next time."; str += GetText("better_luck");
}
else if (rng < 91)
{
str += $"Congratulations! You won {amount * 2}{Gambling.CurrencySign} for rolling above 66";
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betroll Gamble", amount * 2, false).ConfigureAwait(false);
}
else if (rng < 100)
{
str += $"Congratulations! You won {amount * 3}{Gambling.CurrencySign} for rolling above 90.";
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betroll Gamble", amount * 3, false).ConfigureAwait(false);
} }
else else
{ {
str += $"👑 Congratulations! You won {amount * 10}{Gambling.CurrencySign} for rolling **100**. 👑"; if (rnd < 91)
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betroll Gamble", amount * 10, false).ConfigureAwait(false); {
str += GetText("br_win", (amount * NadekoBot.BotConfig.Betroll67Multiplier) + CurrencySign, 66);
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betroll Gamble",
(int) (amount * NadekoBot.BotConfig.Betroll67Multiplier), false).ConfigureAwait(false);
}
else if (rnd < 100)
{
str += GetText("br_win", (amount * NadekoBot.BotConfig.Betroll91Multiplier) + CurrencySign, 90);
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betroll Gamble",
(int) (amount * NadekoBot.BotConfig.Betroll91Multiplier), false).ConfigureAwait(false);
}
else
{
str += GetText("br_win", (amount * NadekoBot.BotConfig.Betroll100Multiplier) + CurrencySign, 100) + " 👑";
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betroll Gamble",
(int) (amount * NadekoBot.BotConfig.Betroll100Multiplier), false).ConfigureAwait(false);
}
} }
await Context.Channel.SendConfirmAsync(str).ConfigureAwait(false); await Context.Channel.SendConfirmAsync(str).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Leaderboard() public async Task Leaderboard(int page = 1)
{ {
IEnumerable<Currency> richest = new List<Currency>(); if (page < 1)
return;
List<Currency> richest;
using (var uow = DbHandler.UnitOfWork()) using (var uow = DbHandler.UnitOfWork())
{ {
richest = uow.Currency.GetTopRichest(10); richest = uow.Currency.GetTopRichest(9, 9 * (page - 1)).ToList();
} }
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle(NadekoBot.BotConfig.CurrencySign +
" " + GetText("leaderboard"))
.WithFooter(efb => efb.WithText(GetText("page", page)));
if (!richest.Any()) if (!richest.Any())
{
embed.WithDescription(GetText("no_users_found"));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
return; return;
await Context.Channel.SendMessageAsync( }
richest.Aggregate(new StringBuilder(
$@"```xl for (var i = 0; i < richest.Count; i++)
{
Id $$$ var x = richest[i];
"), var usr = await Context.Guild.GetUserAsync(x.UserId).ConfigureAwait(false);
(cur, cs) => cur.AppendLine($@"┣━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━┫ var usrStr = usr == null
{(Context.Guild.GetUserAsync(cs.UserId).GetAwaiter().GetResult()?.Username?.TrimTo(18, true) ?? cs.UserId.ToString()),-20} {cs.Amount,6} ") ? x.UserId.ToString()
).ToString() + "┗━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━┛```").ConfigureAwait(false); : usr.Username?.TrimTo(20, true);
var j = i;
embed.AddField(efb => efb.WithName("#" + (9 * (page - 1) + j + 1) + " " + usrStr)
.WithValue(x.Amount.ToString() + " " + NadekoBot.BotConfig.CurrencySign)
.WithIsInline(true));
}
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
} }
} }
} }

View File

@ -19,7 +19,7 @@ namespace NadekoBot.Modules.Games
public partial class Games public partial class Games
{ {
[Group] [Group]
public class Acropobia : ModuleBase public class Acropobia : NadekoSubmodule
{ {
//channelId, game //channelId, game
public static ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>(); public static ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>();
@ -28,6 +28,8 @@ namespace NadekoBot.Modules.Games
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Acro(int time = 60) public async Task Acro(int time = 60)
{ {
if (time < 10 || time > 120)
return;
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
var game = new AcrophobiaGame(channel, time); var game = new AcrophobiaGame(channel, time);
@ -45,7 +47,7 @@ namespace NadekoBot.Modules.Games
} }
else else
{ {
await channel.SendErrorAsync("Acrophobia game is already running in this channel.").ConfigureAwait(false); await ReplyErrorLocalized("acro_running").ConfigureAwait(false);
} }
} }
} }
@ -59,43 +61,44 @@ namespace NadekoBot.Modules.Games
public class AcrophobiaGame public class AcrophobiaGame
{ {
private readonly ITextChannel channel; private readonly ITextChannel _channel;
private readonly int time; private readonly int _time;
private readonly NadekoRandom rng; private readonly NadekoRandom _rng;
private readonly ImmutableArray<char> startingLetters; private readonly ImmutableArray<char> _startingLetters;
private readonly CancellationTokenSource source; private readonly CancellationTokenSource _source;
private AcroPhase phase { get; set; } = AcroPhase.Submitting; private AcroPhase phase { get; set; } = AcroPhase.Submitting;
private readonly ConcurrentDictionary<string, IGuildUser> submissions = new ConcurrentDictionary<string, IGuildUser>(); private readonly ConcurrentDictionary<string, IGuildUser> _submissions = new ConcurrentDictionary<string, IGuildUser>();
public IReadOnlyDictionary<string, IGuildUser> Submissions => submissions; public IReadOnlyDictionary<string, IGuildUser> Submissions => _submissions;
private readonly ConcurrentHashSet<ulong> usersWhoVoted = new ConcurrentHashSet<ulong>(); private readonly ConcurrentHashSet<ulong> _usersWhoSubmitted = new ConcurrentHashSet<ulong>();
private readonly ConcurrentHashSet<ulong> _usersWhoVoted = new ConcurrentHashSet<ulong>();
private int spamCount = 0; private int _spamCount;
//text, votes //text, votes
private readonly ConcurrentDictionary<string, int> votes = new ConcurrentDictionary<string, int>(); private readonly ConcurrentDictionary<string, int> _votes = new ConcurrentDictionary<string, int>();
private readonly Logger _log; private readonly Logger _log;
public AcrophobiaGame(ITextChannel channel, int time) public AcrophobiaGame(ITextChannel channel, int time)
{ {
this._log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
this.channel = channel; _channel = channel;
this.time = time; _time = time;
this.source = new CancellationTokenSource(); _source = new CancellationTokenSource();
this.rng = new NadekoRandom(); _rng = new NadekoRandom();
var wordCount = rng.Next(3, 6); var wordCount = _rng.Next(3, 6);
var lettersArr = new char[wordCount]; var lettersArr = new char[wordCount];
for (int i = 0; i < wordCount; i++) for (int i = 0; i < wordCount; i++)
{ {
var randChar = (char)rng.Next(65, 91); var randChar = (char)_rng.Next(65, 91);
lettersArr[i] = randChar == 'X' ? (char)rng.Next(65, 88) : randChar; lettersArr[i] = randChar == 'X' ? (char)_rng.Next(65, 88) : randChar;
} }
startingLetters = lettersArr.ToImmutableArray(); _startingLetters = lettersArr.ToImmutableArray();
} }
private EmbedBuilder GetEmbed() private EmbedBuilder GetEmbed()
@ -104,18 +107,18 @@ namespace NadekoBot.Modules.Games
return phase == AcroPhase.Submitting return phase == AcroPhase.Submitting
? new EmbedBuilder().WithOkColor() ? new EmbedBuilder().WithOkColor()
.WithTitle("Acrophobia") .WithTitle(GetText("acrophobia"))
.WithDescription($"Game started. Create a sentence with the following acronym: **{string.Join(".", startingLetters)}.**\n") .WithDescription(GetText("acro_started", Format.Bold(string.Join(".", _startingLetters))))
.WithFooter(efb => efb.WithText("You have " + this.time + " seconds to make a submission.")) .WithFooter(efb => efb.WithText(GetText("acro_started_footer", _time)))
: new EmbedBuilder() : new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithTitle("Acrophobia - Submissions Closed") .WithTitle(GetText("acrophobia") + " - " + GetText("submissions_closed"))
.WithDescription($@"Acronym was **{string.Join(".", startingLetters)}.** .WithDescription(GetText("acro_nym_was", Format.Bold(string.Join(".", _startingLetters)) + "\n" +
-- $@"--
{this.submissions.Aggregate("", (agg, cur) => agg + $"`{++i}.` **{cur.Key.ToLowerInvariant().ToTitleCase()}**\n")} {_submissions.Aggregate("",(agg, cur) => agg + $"`{++i}.` **{cur.Key.ToLowerInvariant().ToTitleCase()}**\n")}
--") --"))
.WithFooter(efb => efb.WithText("Vote by typing a number of the submission")); .WithFooter(efb => efb.WithText(GetText("acro_vote")));
} }
public async Task Run() public async Task Run()
@ -124,10 +127,10 @@ namespace NadekoBot.Modules.Games
var embed = GetEmbed(); var embed = GetEmbed();
//SUBMISSIONS PHASE //SUBMISSIONS PHASE
await channel.EmbedAsync(embed).ConfigureAwait(false); await _channel.EmbedAsync(embed).ConfigureAwait(false);
try try
{ {
await Task.Delay(time * 1000, source.Token).ConfigureAwait(false); await Task.Delay(_time * 1000, _source.Token).ConfigureAwait(false);
phase = AcroPhase.Idle; phase = AcroPhase.Idle;
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@ -136,30 +139,32 @@ namespace NadekoBot.Modules.Games
} }
//var i = 0; //var i = 0;
if (submissions.Count == 0) if (_submissions.Count == 0)
{ {
await channel.SendErrorAsync("Acrophobia", "Game ended with no submissions."); await _channel.SendErrorAsync(GetText("acrophobia"), GetText("acro_ended_no_sub"));
return; return;
} }
else if (submissions.Count == 1) if (_submissions.Count == 1)
{ {
await channel.EmbedAsync(new EmbedBuilder().WithOkColor() await _channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription($"{submissions.First().Value.Mention} is the winner for being the only user who made a submission!") .WithDescription(
.WithFooter(efb => efb.WithText(submissions.First().Key.ToLowerInvariant().ToTitleCase()))) GetText("acro_winner_only",
Format.Bold(_submissions.First().Value.ToString())))
.WithFooter(efb => efb.WithText(_submissions.First().Key.ToLowerInvariant().ToTitleCase())))
.ConfigureAwait(false); .ConfigureAwait(false);
return; return;
} }
var submissionClosedEmbed = GetEmbed(); var submissionClosedEmbed = GetEmbed();
await channel.EmbedAsync(submissionClosedEmbed).ConfigureAwait(false); await _channel.EmbedAsync(submissionClosedEmbed).ConfigureAwait(false);
//VOTING PHASE //VOTING PHASE
this.phase = AcroPhase.Voting; phase = AcroPhase.Voting;
try try
{ {
//30 secondds for voting //30 secondds for voting
await Task.Delay(30000, source.Token).ConfigureAwait(false); await Task.Delay(30000, _source.Token).ConfigureAwait(false);
this.phase = AcroPhase.Idle; phase = AcroPhase.Idle;
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -168,15 +173,15 @@ namespace NadekoBot.Modules.Games
await End().ConfigureAwait(false); await End().ConfigureAwait(false);
} }
private async void PotentialAcro(SocketMessage arg) private async Task PotentialAcro(SocketMessage arg)
{ {
try try
{ {
var msg = arg as SocketUserMessage; var msg = arg as SocketUserMessage;
if (msg == null || msg.Author.IsBot || msg.Channel.Id != channel.Id) if (msg == null || msg.Author.IsBot || msg.Channel.Id != _channel.Id)
return; return;
++spamCount; ++_spamCount;
var guildUser = (IGuildUser)msg.Author; var guildUser = (IGuildUser)msg.Author;
@ -184,35 +189,39 @@ namespace NadekoBot.Modules.Games
if (phase == AcroPhase.Submitting) if (phase == AcroPhase.Submitting)
{ {
if (spamCount > 10) if (_spamCount > 10)
{ {
spamCount = 0; _spamCount = 0;
try { await channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); } try { await _channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); }
catch { } catch { }
} }
//user didn't input something already
IGuildUser throwaway;
if (submissions.TryGetValue(input, out throwaway))
return;
var inputWords = input.Split(' '); //get all words var inputWords = input.Split(' '); //get all words
if (inputWords.Length != startingLetters.Length) // number of words must be the same as the number of the starting letters if (inputWords.Length != _startingLetters.Length) // number of words must be the same as the number of the starting letters
return; return;
for (int i = 0; i < startingLetters.Length; i++) for (int i = 0; i < _startingLetters.Length; i++)
{ {
var letter = startingLetters[i]; var letter = _startingLetters[i];
if (!inputWords[i].StartsWith(letter.ToString())) // all first letters must match if (!inputWords[i].StartsWith(letter.ToString())) // all first letters must match
return; return;
} }
//try adding it to the list of answers
if (!submissions.TryAdd(input, guildUser)) if (!_usersWhoSubmitted.Add(guildUser.Id))
return; return;
//try adding it to the list of answers
if (!_submissions.TryAdd(input, guildUser))
{
_usersWhoSubmitted.TryRemove(guildUser.Id);
return;
}
// all good. valid input. answer recorded // all good. valid input. answer recorded
await channel.SendConfirmAsync("Acrophobia", $"{guildUser.Mention} submitted their sentence. ({submissions.Count} total)"); await _channel.SendConfirmAsync(GetText("acrophobia"),
GetText("acro_submit", guildUser.Mention,
_submissions.Count));
try try
{ {
await msg.DeleteAsync(); await msg.DeleteAsync();
@ -224,14 +233,13 @@ namespace NadekoBot.Modules.Games
} }
else if (phase == AcroPhase.Voting) else if (phase == AcroPhase.Voting)
{ {
if (spamCount > 10) if (_spamCount > 10)
{ {
spamCount = 0; _spamCount = 0;
try { await channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); } try { await _channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); }
catch { } catch { }
} }
IGuildUser usr;
//if (submissions.TryGetValue(input, out usr) && usr.Id != guildUser.Id) //if (submissions.TryGetValue(input, out usr) && usr.Id != guildUser.Id)
//{ //{
// if (!usersWhoVoted.Add(guildUser.Id)) // if (!usersWhoVoted.Add(guildUser.Id))
@ -243,17 +251,17 @@ namespace NadekoBot.Modules.Games
//} //}
int num; int num;
if (int.TryParse(input, out num) && num > 0 && num <= submissions.Count) if (int.TryParse(input, out num) && num > 0 && num <= _submissions.Count)
{ {
var kvp = submissions.Skip(num - 1).First(); var kvp = _submissions.Skip(num - 1).First();
usr = kvp.Value; var usr = kvp.Value;
//can't vote for yourself, can't vote multiple times //can't vote for yourself, can't vote multiple times
if (usr.Id == guildUser.Id || !usersWhoVoted.Add(guildUser.Id)) if (usr.Id == guildUser.Id || !_usersWhoVoted.Add(guildUser.Id))
return; return;
votes.AddOrUpdate(kvp.Key, 1, (key, old) => ++old); _votes.AddOrUpdate(kvp.Key, 1, (key, old) => ++old);
await channel.SendConfirmAsync("Acrophobia", $"{guildUser.Mention} cast their vote!").ConfigureAwait(false); await _channel.SendConfirmAsync(GetText("acrophobia"),
GetText("acro_vote_cast", Format.Bold(guildUser.ToString()))).ConfigureAwait(false);
await msg.DeleteAsync().ConfigureAwait(false); await msg.DeleteAsync().ConfigureAwait(false);
return;
} }
} }
@ -266,27 +274,34 @@ namespace NadekoBot.Modules.Games
public async Task End() public async Task End()
{ {
if (!votes.Any()) if (!_votes.Any())
{ {
await channel.SendErrorAsync("Acrophobia", "No votes cast. Game ended with no winner.").ConfigureAwait(false); await _channel.SendErrorAsync(GetText("acrophobia"), GetText("acro_no_votes_cast")).ConfigureAwait(false);
return; return;
} }
var table = votes.OrderByDescending(v => v.Value); var table = _votes.OrderByDescending(v => v.Value);
var winner = table.First(); var winner = table.First();
var embed = new EmbedBuilder().WithOkColor() var embed = new EmbedBuilder().WithOkColor()
.WithTitle("Acrophobia") .WithTitle(GetText("acrophobia"))
.WithDescription($"Winner is {submissions[winner.Key].Mention} with {winner.Value} points.\n") .WithDescription(GetText("acro_winner", Format.Bold(_submissions[winner.Key].ToString()),
Format.Bold(winner.Value.ToString())))
.WithFooter(efb => efb.WithText(winner.Key.ToLowerInvariant().ToTitleCase())); .WithFooter(efb => efb.WithText(winner.Key.ToLowerInvariant().ToTitleCase()));
await channel.EmbedAsync(embed).ConfigureAwait(false); await _channel.EmbedAsync(embed).ConfigureAwait(false);
} }
public void EnsureStopped() public void EnsureStopped()
{ {
NadekoBot.Client.MessageReceived -= PotentialAcro; NadekoBot.Client.MessageReceived -= PotentialAcro;
if (!source.IsCancellationRequested) if (!_source.IsCancellationRequested)
source.Cancel(); _source.Cancel();
} }
private string GetText(string key, params object[] replacements)
=> GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(_channel.Guild),
typeof(Games).Name.ToLowerInvariant(),
replacements);
} }
} }
} }

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