Merge pull request #20 from Kwoth/dev

ups
This commit is contained in:
samvaio 2017-04-21 11:15:52 +05:30 committed by GitHub
commit 1c55e3b5b2
207 changed files with 82422 additions and 4680 deletions

View File

@ -1,14 +1,12 @@
![img](https://ci.appveyor.com/api/projects/status/gmu6b3ltc80hr3k9?svg=true)
[![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)
# NadekoBot
[![nadeko1](https://cdn.discordapp.com/attachments/155726317222887425/252095170676391936/A1.jpg)](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/)
##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/nadekobot) [![Wiki](https://cdn.discordapp.com/attachments/155726317222887425/252192472849973250/read_the_docs_banner.JPG)](http://nadekobot.readthedocs.io/en/latest/)
[![nadeko0](https://cdn.discordapp.com/attachments/266240393639755778/281920716809699328/part1.png)](http://nadekobot.xyz)
[![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/266240393639755778/281920161311883264/part3.png)](http://nadekobot.readthedocs.io/en/latest/Commands%20List/)
## 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)
- [Administration](#administration)
- [ClashOfClans](#clashofclans)
@ -16,19 +16,18 @@ You can support the project on patreon: <https://patreon.com/nadekobot> or paypa
### Administration
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.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`
`.resetperms` | Resets the bot's permissions module on this server to the default value. **Requires Administrator server permission.** | `.resetperms`
`.resetglobalperms` | Resets global permissions set by bot owner. **Bot owner only** | `.resetglobalperms`
`.delmsgoncmd` | Toggles the automatic deletion of the 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`
`.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`
`.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`
`.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"`
`.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`
@ -37,70 +36,92 @@ Command and aliases | Description | Usage
`.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`
`.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`
`.mentionrole` `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. **Requires MentionEveryone server permission.** | `.menro RoleName`
`.donators` | List of lovely people who donated to keep this project alive. | `.donators`
`.donadd` | Add a donator to the database. **Bot Owner only.** | `.donadd Donate Amount`
`.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`
`.mentionrole` `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. **Requires MentionEveryone server permission.** | `.menro RoleName`
`.donators` | List of the lovely people who donated to keep this project alive. | `.donators`
`.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
`.fwmsgs` | Toggles forwarding of non-command messages sent to bot's DM to the bot owners **Bot Owner only.** | `.fwmsgs`
`.fwtoall` | Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json **Bot Owner only.** | `.fwtoall`
`.logserver` | Enables or Disables ALL log events. If enabled, all log events will log to this channel. **Requires Administrator server permission.** **Bot Owner only.** | `.logserver enable` or `.logserver disable`
`.logignore` | Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. **Requires Administrator server permission.** **Bot Owner only.** | `.logignore`
`.logevents` | Shows a list of all events you can subscribe to with `.log` **Requires Administrator server permission.** **Bot Owner only.** | `.logevents`
`.log` | Toggles logging event. Disables it if it's active anywhere on the server. Enables if it's not active. Use `.logevents` to see a list of all events you can subscribe to. **Requires Administrator server permission.** **Bot Owner only.** | `.log userpresence` or `.log userbanned`
`.migratedata` | Migrate data from old bot configuration **Bot Owner only.** | `.migratedata`
`.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. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.mute @Someone`
`.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`
`.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. **Requires Administrator server permission.** | `.antispam 3 Mute` or `.antispam 4 Kick` or `.antispam 6 Ban`
`.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`
`.adsarm` | Toggles the automatic deletion of confirmations for .iam and .iamn commands. **Requires ManageMessages server permission.** | `.adsarm`
`.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`
`.leave` | Makes Nadeko leave the server. Either name or id required. **Bot Owner only.** | `.leave 123123123331`
`.die` | Shuts the bot down. **Bot Owner only.** | `.die`
`.setname` `.newnm` | Gives the bot a new name. **Bot Owner only.** | `.newnm BotName`
`.setstatus` | Sets the bot's status. (Online/Idle/Dnd/Invisible) **Bot Owner only.** | `.setstatus Idle`
`.setavatar` `.setav` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner only.** | `.setav http://i.imgur.com/xTG3a1I.jpg`
`.setgame` | Sets the bots game. **Bot Owner only.** | `.setgame with snakes`
`.setstream` | Sets the bots stream. First argument is the twitch link, second argument is stream name. **Bot Owner only.** | `.setstream TWITCHLINK Hello`
`.send` | Sends a message to someone on a different server through the bot. Separate server and channel/user ids with `|` and prepend channel id with `c:` and user id with `u:`. **Bot Owner only.** | `.send serverid|c:channelid message` or `.send serverid|u:userid message`
`.announce` | Sends a message to all servers' general channel bot is connected to. **Bot Owner only.** | `.announce Useless spam`
`.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 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.greetdel 0` or `.greetdel 30`
`.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. **Requires ManageServer server permission.** | `.greetmsg Welcome, %user%.`
`.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. **Requires ManageServer server permission.** | `.greetdmmsg Welcome to the server, %user%`.
`.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. **Requires ManageServer server permission.** | `.byemsg %user% has left.`
`.byedel` | Sets the time it takes (in seconds) for bye messages to be auto-deleted. Set 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.byedel 0` or `.byedel 30`
`.voice+text` `.v+t` | Creates a text channel for each voice channel only users in that voice channel can see.If you are server owner, keep in mind you will see them all the time regardless. **Requires ManageRoles server permission.** **Requires ManageChannels server permission.** | `.v+t`
`.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
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`
`,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]`
`,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`
@ -108,61 +129,69 @@ Command and aliases | Description | Usage
`,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]`
###### [Back to TOC](#table-of-contents)
###### [Back to ToC](#table-of-contents)
### 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`
`.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`
`.delcustreact` `.dcr` | Deletes a custom reaction on a specific index. If ran in DM, it is bot owner only and deletes a global custom reaction. If ran in a server, it requires Administration priviledges and removes server custom reaction. | `.dcr 5`
`.crstatsclear` | Resets the counters on `.crstats`. You can specify a trigger to clear stats only for that trigger. **Bot Owner only.** | `.crstatsclear` or `.crstatsclear rng`
`.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`
`.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`
###### [Back to TOC](#table-of-contents)
###### [Back to ToC](#table-of-contents)
### Gambling
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. | `$raffle` or `$raffle RoleName`
`$cash` `$$$` | Check how much currency a person has. (Defaults to yourself) | `$$$` or `$$$ @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`
`$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`
`$leaderboard` `$lb` | Displays bot currency leaderboard. | `$lb`
`$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"`
`$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 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`
`$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`
`$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 999. 3 seconds cooldown per user. | `$slot 5`
`$claimwaifu` `$claim` | Claim a waifu for yourself by spending currency. You must spend atleast 10% more than her current value unless she set `$affinity` towards you. | `$claim 50 @Himesama`
`$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
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`>choose` | Chooses a thing from a list of things | `>choose Get up;Sleep;Sleep more`
`>8ball` | Ask the 8ball a yes/no question. | `>8ball should I do something`
`>rps` | Play a game of rocket paperclip scissors with Nadeko. | `>rps scissors`
`>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`
`>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | `>leet 3 Hello`
`>acrophobia` `>acro` | Starts an Acrophobia game. Second argment is optional round length in seconds. (default is 60) | `>acro` or `>acro 30`
`>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`
@ -175,89 +204,90 @@ Command and aliases | Description | Usage
`>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`
`>typeadd` | Adds a new article to the typing contest. **Bot owner only** | `>typeadd wordswords`
`>typelist` | Lists added typing articles with their IDs. 15 per page. | `>typelist` or `>typelist 3`
`>typedel` | Deletes a typing article given the ID. **Bot Owner only.** | `>typedel 3`
`>trivia` `>t` | Starts a game of trivia. You can add nohint to prevent hints.First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question. | `>t` or `>t 5 nohint`
`>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
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`-modules` `-mdls` | Lists all bot modules. | `-modules`
`-commands` `-cmds` | List all of the bot's commands from a certain module. You can either specify full, or only first few letters of the module name. | `-commands Administration` or `-cmds Admin`
`-help` `-h` | Either shows a help for a single command, or DMs you help link if no arguments are specified. | `-h !!q` or `-h`
`-hgit` | Generates the commandlist.md file. **Bot Owner only.** | `-hgit`
`-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 -cmds` or `-h`
`-hgit` | Generates the commandlist.md file. **Bot owner only** | `-hgit`
`-readme` `-guide` | Sends a readme and a guide links to the channel. | `-readme` or `-guide`
`-donate` | Instructions for helping the project financially. | `-donate`
###### [Back to TOC](#table-of-contents)
###### [Back to ToC](#table-of-contents)
### 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`
`!!stop` `!!s` | Stops the music and clears the playlist. Stays in the channel. | `!!s`
`!!destroy` `!!d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!!d`
`!!pause` `!!p` | Pauses or Unpauses the song. | `!!p`
`!!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`
`!!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`
`!!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`
`!!soundcloudqueue` `!!sq` | Queue a soundcloud song using keywords. Bot will join your voice channel. **You must be in a voice channel**. | `!!sq Dream Of Venice`
`!!listqueue` `!!lq` | Lists 15 currently queued songs per page. Default page is 1. | `!!lq` or `!!lq 2`
`!!nowplaying` `!!np` | Shows the song currently playing. | `!!np`
`!!volume` `!!vol` | Sets the music volume 0-100% | `!!vol 50`
`!!nowplaying` `!!np` | Shows the song that the bot is currently playing. | `!!np`
`!!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`
`!!shuffle` `!!sh` | Shuffles the current playlist. | `!!sh`
`!!playlist` `!!pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `!!pl playlist link or name`
`!!soundcloudpl` `!!scpl` | Queue a soundcloud playlist using a link. | `!!scpl soundcloudseturl`
`!!localplaylst` `!!lopl` | Queues all songs from a directory. **Bot Owner only.** | `!!lopl C:/music/classical`
`!!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`
`!!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`
`!!remove` `!!rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!!rm 5`
`!!movesong` `!!ms` | Moves a song from one position to another. | `!!ms 5>3`
`!!setmaxqueue` `!!smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `!!smq 50` or `!!smq`
`!!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`
`!!rpeatplaylst` `!!rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!!rpl`
`!!save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!!save classical1`
`!!load` | Loads a saved playlist using it's ID. Use `!!pls` to list all saved playlists and !!save to save new ones. | `!!load 5`
`!!playlists` `!!pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!!pls 1`
`!!deleteplaylist` `!!delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!!delpls animu-5`
`!!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 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`
`!!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`
`!!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
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`
`~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`
`~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`
`~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`
`~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`
`~boobs` | Real adult content. | `~boobs`
`~butts` `~ass` `~butt` | Real adult content. | `~butts` or `~ass`
###### [Back to TOC](#table-of-contents)
###### [Back to ToC](#table-of-contents)
### Permissions
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`;verbose` `;v` | Sets whether to show when a command/module is blocked. | `;verbose true`
`;permrole` `;pr` | Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'. | `;pr role`
`;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`
`;removeperm` `;rp` | Removes a permission from a given position in Permissions list. | `;rp 1`
`;moveperm` `;mp` | Moves permission from one position to another in Permissions list. | `;mp 2 4`
`;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 the Permissions list. | `;mp 2 4`
`;srvrcmd` `;sc` | Sets a command's permission at the server level. | `;sc "command name" disable`
`;srvrmdl` `;sm` | Sets a module's permission at the server level. | `;sm ModuleName enable`
`;usrcmd` `;uc` | Sets a command's permission at the user level. | `;uc "command name" enable SomeUsername`
@ -270,73 +300,74 @@ Command and aliases | Description | Usage
`;allrolemdls` `;arm` | Enable or disable all modules for a specific role. | `;arm [enable/disable] MyRole`
`;allusrmdls` `;aum` | Enable or disable all modules for a specific user. | `;aum enable @someone`
`;allsrvrmdls` `;asm` | Enable or disable all modules for your server. | `;asm [enable/disable]`
`;ubl` | Either [add]s or [rem]oves a user specified by a mention or ID from a blacklist. **Bot Owner only.** | `;ubl add @SomeUser` or `;ubl rem 12312312313`
`;cbl` | Either [add]s or [rem]oves a channel specified by an ID from a blacklist. **Bot Owner only.** | `;cbl rem 12312312312`
`;sbl` | Either [add]s or [rem]oves a server specified by a Name or ID from a blacklist. **Bot Owner only.** | `;sbl add 12312321312` or `;sbl rem SomeTrashServer`
`;cmdcooldown` `;cmdcd` | Sets a cooldown per user for a command. Set to 0 to remove the cooldown. | `;cmdcd "some cmd" 5`
`;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`
`;cmdcosts` | Shows a list of command costs. Paginated with 9 command per page. | `;cmdcosts` or `;cmdcosts 2`
`;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`
`;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
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`
`>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`
`>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
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`~weather` `~we` | Shows weather data for a specified city. You can also specify a country after a comma. | `~we Moscow, RU`
`~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`
`~imdb` `~omdb` | Queries omdb for movies or series, show first result. | `~imdb Batman vs Superman`
`~randomcat` `~meow` | Shows a random cat image. | `~meow`
`~randomdog` `~woof` | Shows a random dog image. | `~woof`
`~image` `~img` | Pulls the first image found using a search parameter. Use ~rimg for different results. | `~img cute kitten`
`~image` `~img` | Pulls the first image found using a search parameter. Use `~rimg` for different results. | `~img cute kitten`
`~randomimage` `~rimg` | Pulls a random image using a search parameter. | `~rimg cute kitten`
`~lmgtfy` | Google something for an idiot. | `~lmgtfy query`
`~shorten` | Attempts to shorten an URL, if it fails, returns the input URL. | `~shorten https://google.com`
`~google` `~g` | Get a google search link for some terms. | `~google query`
`~google` `~g` | Get a Google search link for some terms. | `~google query`
`~magicthegathering` `~mtg` | Searches for a Magic The Gathering card. | `~magicthegathering about face` or `~mtg about face`
`~hearthstone` `~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | `~hs Ysera`
`~yodify` `~yoda` | Translates your normal sentences into Yoda styled sentences! | ~yodify I was once an adventurer like you` or `~yoda my feelings hurt`
`~yodify` `~yoda` | Translates your normal sentences into Yoda styled sentences! | `~yoda my feelings hurt`
`~urbandict` `~ud` | Searches Urban Dictionary for a word. | `~ud Pineapple`
`~define` `~def` | Finds a definition of a word. | `~def heresy`
`~#` | Searches Tagdef.com for a hashtag. | `~# ff`
`~catfact` | Shows a random catfact from <http://catfacts-api.appspot.com/api/facts> | `~catfact`
`~revav` | Returns a google reverse image search for someone's avatar. | `~revav "@SomeGuy"`
`~revimg` | Returns a google reverse image search for an image from a link. | `~revimg Image link`
`~revav` | Returns a Google reverse image search for someone's avatar. | `~revav "@SomeGuy"`
`~revimg` | Returns a Google reverse image search for an image from a link. | `~revimg Image link`
`~safebooru` | Shows a random image from safebooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~safebooru yuri+kissing`
`~wikipedia` `~wiki` | Gives you back a wikipedia link | `~wiki query`
`~color` `~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00`
`~videocall` | Creates a private <http://www.appear.in> video call link for you and other mentioned people. The link is sent to mentioned people via a private message. | `~videocall "@SomeGuy"`
`~avatar` `~av` | Shows a mentioned person's avatar. | `~av "@SomeGuy"`
`~wikia` | Gives you back a wikia link | `~wikia mtg Vigilance` or `~wikia mlp Dashy`
`~minecraftping` `~mcping` | Pings a minecraft server. | `~mcping 127.0.0.1:25565`
`~minecraftquery` `~mcq` | Finds information about a minecraft server. | `~mcq server:ip`
`~lolban` | Shows top banned champions ordered by ban rate. | `~lolban`
`~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ | `~memelist`
`~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"`
`~mal` | Shows basic info from myanimelist profile. | `~mal straysocks`
`~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 chucknorris joke from <http://tambal.azurewebsites.net/joke/random> | `~cn`
`~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 magicitem from <https://1d4chan.org/wiki/List_of_/tg/%27s_magic_items> | `~mi`
`~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`
`~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`
@ -352,50 +383,59 @@ Command and aliases | Description | Usage
`~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` | `~atl en>fr` | Sets your source and target language to be used with `~at`. Specify no arguments to remove previously set value.
`~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
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.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`
`.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`
`.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`
`.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`
`.userid` `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy"`
`.channelid` `.cid` | Shows current channel ID. | `.cid`
`.serverid` `.sid` | Shows current server ID. | `.sid`
`.roles` | List 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`
`.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`
`.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`
`.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner only.** | `.savechat 150`
`.activity` | Checks for spammers. **Bot Owner only.** | `.activity`
`.listservers` | Lists servers the bot is on with some basic info. 15 per page. **Bot owner only** | `.listservers 3`
`.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 .calc command | `.calcops`
`.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`
`.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 Cross server channel instance from this channel. **Requires ManageServer server permission.** | `.lcsc`
`.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`
`.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`
`.listquotes` `.liqu` | `.liqu` or `.liqu 3` | Lists all quotes on the server ordered alphabetically. 15 Per page.
`.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 random quote with the specified keyword. You have to either be server Administrator or the creator of the quote to delete it. | `.delq abc`
`.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%!`
`.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

@ -1,18 +1,18 @@
##Setting up NadekoBot on Linux
## Setting up NadekoBot on Linux
####Setting up NadekoBot on Linux Digital Ocean Droplet
#### Setting up NadekoBot on Linux Digital Ocean Droplet
If you want Nadeko to play music for you 24/7 without having to hosting it on your PC and want to keep it cheap, reliable and convenient as possible, you can try Nadeko on Linux Digital Ocean Droplet using the link [DigitalOcean](http://m.do.co/c/46b4d3d44795/) (and using this link will be supporting Nadeko and will give you **$10 credit**)
####Setting up NadekoBot
#### Setting up NadekoBot
Assuming you have followed the link above to setup an account and Droplet with 64bit OS in Digital Ocean and got the `IP address and root password (in email)` to login, its time to get started.
**Go through this whole guide before setting up Nadeko**
####Prerequisites
#### Prerequisites
- Download [PuTTY](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html)
- Download [WinSCP](https://winscp.net/eng/download.php) *(optional)*
####Starting up
#### Starting up
- **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.
@ -24,7 +24,7 @@ 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.**
####Creating and Inviting bot
#### 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)**
@ -37,8 +37,8 @@ If you entered your Droplets IP address correctly, it should show **login as:**
- 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
#### Getting NadekoBot
##### Part I - Downloading the installer
Use the following command to get and run `linuxAIO.sh`
(Remember **Do Not** rename the file **linuxAIO.sh**)
@ -55,7 +55,8 @@ You should see these following options after using the above command:
6. Set up credentials.json (if you have downloaded the bot already)
7. To exit
```
#####Part II (Optional)
##### Part II - Downloading Nadekobot prerequisites
**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.
@ -63,10 +64,11 @@ 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)*
(Optional) **If** you want to install it manually, you can try finding it [here](https://github.com/Kwoth/NadekoBot-BashScript/blob/master/nadekoautoinstaller.sh)
Once *prerequisites* finish installing.
#####Part III
Once *prerequisites* finish installing,
##### Part III - Installing Nadeko
Choose either
`1` to get the **most updated build of NadekoBot**
or
@ -76,8 +78,7 @@ 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)*
##### Part IV - Setting up credentials
- [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)
@ -102,17 +103,17 @@ You will be asked to enter the required informations, just follow the on-screen
(If you want to skip any optional infos, just press `enter` key without typing/pasting anything.)
Once done,
#####Part V
##### Part V - Checking if Nadeko is working
You should see the options again.
Next, press `3` to **Run Nadeko (Normally)**
Next, press `3` to **Run Nadeko (Normally)**.
Check in your discord server if your new bot is working properly.
#####Part VI
##### Part VI - Running Nadeko on tmux
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
#### Running NadekoBot
**Create a new Session:**
@ -147,9 +148,9 @@ See how that happens:
**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.
Next to **move the bot to background** and to do that, press **CTRL+B, release, D** (that will detach the nadeko session using TMUX) and you can finally close **PuTTY** if you want.
####Restarting Nadeko
#### Restarting Nadeko
**Restarting NadekoBot:**
@ -166,7 +167,7 @@ Open **PuTTY** and login as you have before, type `reboot` and hit Enter.
- `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
#### 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)
@ -177,98 +178,21 @@ Open **PuTTY** and login as you have before, type `reboot` and hit Enter.
- 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)
#### Setting up Music
#####Installing Git
To set up Nadeko for music and Google API Keys, follow [Setting up NadekoBot for Music](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-nadekobot-for-music)
![img1](https://cdn.discordapp.com/attachments/251504306010849280/251504416019054592/git.gif)
Once done, go back to **PuTTY**
Ubuntu:
#### Some more Info
`sudo apt-get install git -y`
##### Info about tmux
CentOS:
- If you want to **see the sessions** after logging back again, type `tmux ls`, and that will give you the list of sessions running.
- If you want to **switch to/ see that session**, type `tmux a -t nadeko` (**nadeko** is the name of the session we created before so, replace **“nadeko”** with the session name you created.)
- If you want to **kill** NadekoBot **session**, type `tmux kill-session -t nadeko`
`yum -y install git`
**NOTE:** If the command is not being initiated, hit **Enter**
#####Installing .NET Core SDK
![img2](https://cdn.discordapp.com/attachments/251504306010849280/251504746987388938/dotnet.gif)
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
We'll go over the steps here for Ubuntu 16.04 anyway (these will **only** work on Ubuntu 16.04), accurate as of 3/2/2017
```
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-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)
#####Installing Opus Voice Codec and libsodium
![img3](https://cdn.discordapp.com/attachments/251504306010849280/251505294654308353/libopus.gif)
Ubuntu:
`sudo apt-get install libopus0 opus-tools libopus-dev libsodium-dev -y`
CentOS:
`yum -y install opus opus-devel`
#####Installing FFMPEG
![img4](https://cdn.discordapp.com/attachments/251504306010849280/251505443111829505/ffmpeg.gif)
Ubuntu:
`apt-get install ffmpeg -y`
Centos:
```
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:
```
sudo add-apt-repository ppa:mc3man/trusty-media
sudo apt-get update
sudo apt-get dist-upgrade
```
**Before executing:** `sudo apt-get install ffmpeg`
**NOTE:** If you are running **Debian 8 Jessie**, please, follow these steps:
```
sudo apt-get update
echo "deb http://ftp.debian.org/debian jessie-backports main" | tee /etc/apt/sources.list.d/debian-backports.list
sudo apt-get update && sudo apt-get install ffmpeg -y
```
#####Installing TMUX
![img5](https://cdn.discordapp.com/attachments/251504306010849280/251505519758409728/tmux.gif)
Ubuntu:
`sudo apt-get install tmux -y`
Centos:
`yum -y install tmux`
####Guide for Advance Users (Optional)
#### Guide for Advance Users (Optional)
**Skip this step if you are a Regular User or New to Linux.**
@ -285,7 +209,7 @@ Centos:
- It will then ask "File Name to Write" (rename), just hit `Enter` and Done.
- You can now move to [Running NadekoBot](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#running-nadekobot)
####Setting up SFTP
#### Setting up SFTP
- Open **WinSCP**
- Click on **New Site** (top-left corner).
@ -298,7 +222,7 @@ Centos:
- 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.
####Setting up credentials.json
#### Setting up credentials.json
- 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)
@ -306,39 +230,5 @@ Centos:
- **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/)
####Setting up Music
To set up Nadeko for music and Google API Keys, follow [Setting up NadekoBot for Music](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-nadekobot-for-music)
Once done, go back to **PuTTY**
####Some more Info
#####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 **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`
#####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):
![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`
**OR**
```
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
```
If you are getting error using the above steps try:
```
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
```
[img7]: https://cdn.discordapp.com/attachments/251504306010849280/251505766370902016/setting_up_credentials.gif

View File

@ -6,14 +6,15 @@
- Soundcloud Account (if you want soundcloud support)
- Text Editor (TextWrangler, or equivalent) or outside editor such as [Atom][Atom]
####Installing Homebrew
#### Installing Homebrew
```/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"```
Run `brew update` to fetch the latest package data.
####Installing dependencies
#### Installing dependencies
```
brew install wget
brew install git
brew install ffmpeg
brew update && brew upgrade ffmpeg
@ -26,15 +27,15 @@ brew install libsodium
brew install tmux
```
####Installing .NET Core SDK
#### Installing .NET Core SDK
- `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/`
- 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.
- `ln -s /usr/local/share/dotnet/dotnet /usr/local/bin`
####Check your `FFMPEG`
#### Check your `FFMPEG`
**In case your `FFMPEG` wasnt installed properly (Optional)**
@ -45,7 +46,7 @@ brew install tmux
- `brew doctor` (Check your Homebrew installation for common issues)
- Then try `brew install ffmpeg` again.
####Installing xcode-select
#### Installing xcode-select
Xcode command line tools. You will do this in Terminal.app by running the following command line:
@ -53,7 +54,7 @@ Xcode command line tools. You will do this in Terminal.app by running the follow
A dialog box will open asking if you want to install `xcode-select`. Select install and finish the installation.
####Downloading and building Nadeko
#### Downloading and building Nadeko
Use the following command to get and run `linuxAIO.sh`:
(Remember **DO NOT** rename the file `linuxAIO.sh`)
@ -69,7 +70,7 @@ 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
#### 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*
@ -79,17 +80,17 @@ Next, choose `5` to exit.
- Go to the newly created link and pick the server we created, and click `Authorize`.
- The bot should have been added to your server.
####Setting up Credentials.json file
#### Setting up Credentials.json file
- Open up the `NadekoBot` folder, which should be in your home directory, then `NadekoBot` folder then `src` folder and then the additonal `NadekoBot` folder.
- EDIT it as it is guided here: [Setting up credentials.json](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-credentialsjson-file)
- **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`.
- **If** you have Nadeko 0.9x follow the [Upgrading Guide](http://nadekobot.readthedocs.io/en/latest/guides/Upgrading%20Guide/)
####Setting NadekoBot Music
#### Setting NadekoBot Music
For Music Setup and API keys check [Setting up NadekoBot for Music](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-nadekobot-for-music) and [JSON Explanations](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/).
####Running NadekoBot
#### Running NadekoBot
- Using tmux
@ -123,7 +124,7 @@ Choose `4` To Run the bot with Auto Restart.
Now time to move bot to background and to do that, press CTRL+B+D (this will detach the nadeko session using TMUX)
If you used Screen press CTRL+A+D (this will detach the nadeko screen)
####Updating Nadeko
#### Updating Nadeko
- Connect to the terminal.
- `tmux kill-session -t nadeko` [(don't forget to replace **nadeko** in the command to what ever you named your bot's session)](http://nadekobot.readthedocs.io/en/latest/guides/OSX%20Guide/#some-more-info)
@ -134,7 +135,7 @@ If you used Screen press CTRL+A+D (this will detach the nadeko screen)
- Choose either `3` or `4` to run the bot again with **normally** or **auto restart** respectively.
- Done. You can close terminal now.
####Some more Info
#### Some more Info
**TMUX**
@ -148,7 +149,7 @@ If you used Screen press CTRL+A+D (this will detach the nadeko screen)
- If you want to switch to/ see that screen, type `screen -r nadeko` (nadeko is the name of the screen we created before so, replace `nadeko` with the screen name you created.)
- If you want to kill the NadekoBot screen, type `screen -X -S nadeko quit`
####Alternative Method to Install Nadeko
#### Alternative Method to Install Nadeko
**METHOD I**
@ -166,6 +167,7 @@ If you used Screen press CTRL+A+D (this will detach the nadeko screen)
- `dotnet build --configuration Release`
[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
[Atom]: https://atom.io/
[Invite Guide]: http://discord.kongslien.net/guide.html

View File

@ -1,5 +1,5 @@
________________________________________________________________________________
*Thanks to @Flatbread and Mirai for making this guide*
*Thanks to @Flatbread and @Mirai for making this guide*
________________________________________________________________________________
## Setting Up NadekoBot on Windows
@ -13,7 +13,7 @@ ________________________________________________________________________________
- 6) [Notepad++][Notepad++]
- 7) Windows 8 or later
####Guide
#### Guide
- 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`.
- 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`.)
@ -24,8 +24,9 @@ ________________________________________________________________________________
- Wait a while for the file to finish installing, it'll display it's progress in the command prompt.
- You should now have a new folder named `NadekoBot` inside the `Nadeko` folder we previously created.
- Once Installation is completed, press any key to close the command prompt.
![img1](http://i.imgur.com/O1dY9eW.gif)
####Creating DiscordBot application
#### Creating DiscordBot application
- Go to [the Discord developer application page][DiscordApp].
- Log in with your Discord account.
- On the left side, press `New Application`.
@ -33,8 +34,9 @@ ________________________________________________________________________________
- Create the application.
- Click on `Create a Bot User` and confirm that you do want to add a bot to this app.
- Keep this window open for now.
![img2](http://i.imgur.com/x3jWudH.gif)
####Setting up credentials.json file
#### Setting up credentials.json file
- In our `NadekoBot` folder you should see a `src` folder, then *another* `NadekoBot` folder, in this final folder, you should see a `.json` file named `credentials.json`. (Note: If you do not see a `.json` after `credentials.json`, do not add the `.json`. You most likely have **"Hide file extensions"** enabled.)
- If you mess up the setup of `credentials.json`, rename `credentials_example.json` to `credentials.json`.
- Open the file with [Notepad++][Notepad++].
@ -45,19 +47,21 @@ ________________________________________________________________________________
- 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
- 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`)
- 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.
![img3](http://i.imgur.com/QwKMnTG.gif)
####Inviting your bot to your server
#### Inviting your bot to your server
- [Invite Guide][Invite Guide]
- Copy your `Client ID` from your [applications page][DiscordApp].
- 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.
![img4](http://i.imgur.com/aFK7InR.gif)
####Starting the bot
#### Starting the bot
- Go to the `Nadeko` folder that we have created earlier, and run the `NadekoInstaller.bat` file as Administrator.
- From the options,
- Choose `3` to **run the bot normally**.
@ -65,7 +69,7 @@ ________________________________________________________________________________
- Choose `4` to **run the bot with auto restart**.
(with auto restart the bot will restart itself if it disconnects by the use of `.die` command. Useful if you want to have restart function for any reason.)
####Updating NadekoBot
#### Updating NadekoBot
- Make sure the bot is closed and is not running (Run `.die` in a connected server to ensure it's not running).
- Once that's checked, go to the `Nadeko` folder.
- Run the `NadekoInstaller.bat` file.
@ -106,7 +110,8 @@ In order to have a functioning music module, you need to install ffmpeg and setu
- Follow these steps on how to setup Google API keys:
- Go to [Google Console][Google Console] and log in.
- Create a new project (name does not matter). Once the project is created, go into "Enable and manage APIs."
- Under the "Other Popular APIs" section, enable `URL Shortener API` and `Custom Search Api`. Under the `YouTube APIs` section, enable `YouTube Data API`.
- Under the "Other Popular APIs" section, enable `URL Shortener API` and `Custom Search API`. Under the `YouTube APIs` section, enable `YouTube Data API`.
- Under the "Google Maps APIs" section, enable `Google Maps Geocoding API` and `Google Maps Time Zone API`.
- On the left tab, access `Credentials`. Click `Create Credentials` button. Click on `API Key`. A new window will appear with your `Google API key`.
- Copy the key.
- Open up `credentials.json`.
@ -118,9 +123,11 @@ In order to have a functioning music module, you need to install ffmpeg and setu
- 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**
[.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
[7zip]: http://www.7-zip.org/download.html
[DiscordApp]: https://discordapp.com/developers/applications/me

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,24 +1,25 @@
@ECHO off
TITLE Downloading Latest Build of NadekoBot...
::Setting convenient to read variables which don't delete the windows temp folder
SET root=%~dp0
CD /D %root%
SET rootdir=%cd%
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 build3=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.WebSocket\
SET build4=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Commands\
SET build5=%root%NadekoInstall_Temp\NadekoBot\src\NadekoBot\
SET installtemp=%root%NadekoInstall_Temp\
SET "root=%~dp0"
CD /D "%root%"
SET "rootdir=%cd%"
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 "build3=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.WebSocket\"
SET "build4=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Commands\"
SET "build5=%root%NadekoInstall_Temp\NadekoBot\src\NadekoBot\"
SET "installtemp=%root%NadekoInstall_Temp\"
::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
dotnet --version >nul 2>&1 || GOTO :dotnet
git --version >nul 2>&1 || GOTO :git
::Creates the install directory to work in and get the current directory because spaces ruins everything otherwise
:start
MKDIR NadekoInstall_Temp
CD /D %installtemp%
MKDIR "%root%NadekoInstall_Temp"
CD /D "%installtemp%"
::Downloads the latest version of Nadeko
ECHO Downloading Nadeko...
ECHO.
@ -28,28 +29,28 @@ TITLE Installing NadekoBot, please wait...
ECHO.
ECHO Installing Discord.Net(1/4)...
::Building Nadeko
CD /D %build1%
CD /D "%build1%"
dotnet restore >nul 2>&1
ECHO Installing Discord.Net(2/4)...
CD /D %build2%
CD /D "%build2%"
dotnet restore >nul 2>&1
ECHO Installing Discord.Net(3/4)...
CD /D %build3%
CD /D "%build3%"
dotnet restore >nul 2>&1
ECHO Installing Discord.Net(4/4)...
CD /D %build4%
CD /D "%build4%"
dotnet restore >nul 2>&1
ECHO.
ECHO Discord.Net installation completed successfully...
ECHO.
ECHO Installing NadekoBot...
CD /D %build5%
CD /D "%build5%"
dotnet restore >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
IF EXIST "%root%NadekoBot\" (GOTO :backupinstall)
IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) ELSE (GOTO :freshinstall)
:freshinstall
::Moves the NadekoBot folder to keep things tidy
ECHO.
@ -65,20 +66,23 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall)
ROBOCOPY "%root%NadekoBot" "%root%NadekoBot_Old" /MIR >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
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
COPY "%root%NadekoBot_Old\src\NadekoBot\credentials.json" "%installtemp%NadekoBot\src\NadekoBot\credentials.json" >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
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
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
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
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
ECHO.
ECHO Old data folder copied to new folder
ECHO Old data folder copied...
::Moves the setup Nadeko folder
RMDIR "%root%NadekoBot\" /S /Q >nul 2>&1
ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1
@ -103,7 +107,7 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall)
:giterror
ECHO.
ECHO Git clone failed, trying again
RMDIR %installtemp% /S /Q >nul 2>&1
RMDIR "%installtemp%" /S /Q >nul 2>&1
GOTO :start
:copyerror
::If at any point a copy error is encountered
@ -124,22 +128,39 @@ ECHO.
ECHO Your System Architecture is 32bit...
timeout /t 5
ECHO.
ECHO Downloading libsodium.dll and opus.dll...
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"
bitsadmin.exe /transfer "Downloading libsodium.dll" /priority high https://github.com/Kwoth/NadekoBot/raw/dev/src/NadekoBot/_libs/32/libsodium.dll "%FILENAME%"
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"
bitsadmin.exe /transfer "Downloading opus.dll" /priority high https://github.com/Kwoth/NadekoBot/raw/dev/src/NadekoBot/_libs/32/opus.dll "%FILENAME%"
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
::Normal execution of end of script
TITLE Installation complete!
TITLE NadekoBot Installation complete!
CD /D "%root%"
RMDIR /S /Q "%installtemp%" >nul 2>&1
ECHO.
ECHO Installation complete, press any key to close this window!
ECHO Installation complete!
ECHO.
timeout /t 5
del Latest.bat

View File

@ -2,8 +2,8 @@
@TITLE NadekoBot
SET root=%~dp0
CD /D %root%
SET "root=%~dp0"
CD /D "%root%"
CLS
ECHO Welcome to NadekoBot Auto Restart and Update!
@ -25,32 +25,28 @@ IF ERRORLEVEL 1 GOTO latestar
:latestar
ECHO Auto Restart and Update with Dev Build (latest)
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
ECHO Updating...
timeout /t 3
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.
SET root=%~dp0
CD /D %root%
SET "root=%~dp0"
CD /D "%root%"
CALL Latest.bat
GOTO latestar
:stablear
ECHO Auto Restart and Update with Stable Build
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
ECHO Updating...
timeout /t 3
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.
SET root=%~dp0
CD /D %root%
SET "root=%~dp0"
CD /D "%root%"
CALL Stable.bat
GOTO stablear
@ -58,12 +54,12 @@ GOTO stablear
ECHO Normal Auto Restart
ECHO Bot will not auto update on every restart!
timeout /t 3
CD /D %~dp0NadekoBot\src\NadekoBot
CD /D "%~dp0NadekoBot\src\NadekoBot"
dotnet run --configuration Release
goto autorun
:Exit
SET root=%~dp0
CD /D %root%
SET "root=%~dp0"
CD /D "%root%"
del NadekoAutoRun.bat
CALL NadekoInstaller.bat

View File

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

View File

@ -1,24 +1,25 @@
@ECHO off
TITLE Downloading Stable Build of NadekoBot...
::Setting convenient to read variables which don't delete the windows temp folder
SET root=%~dp0
CD /D %root%
SET rootdir=%cd%
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 build3=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.WebSocket\
SET build4=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Commands\
SET build5=%root%NadekoInstall_Temp\NadekoBot\src\NadekoBot\
SET installtemp=%root%NadekoInstall_Temp\
SET "root=%~dp0"
CD /D "%root%"
SET "rootdir=%cd%"
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 "build3=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.WebSocket\"
SET "build4=%root%NadekoInstall_Temp\NadekoBot\Discord.Net\src\Discord.Net.Commands\"
SET "build5=%root%NadekoInstall_Temp\NadekoBot\src\NadekoBot\"
SET "installtemp=%root%NadekoInstall_Temp\"
::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
dotnet --version >nul 2>&1 || GOTO :dotnet
git --version >nul 2>&1 || GOTO :git
::Creates the install directory to work in and get the current directory because spaces ruins everything otherwise
:start
MKDIR NadekoInstall_Temp
CD /D %installtemp%
MKDIR "%root%NadekoInstall_Temp"
CD /D "%installtemp%"
::Downloads the latest version of Nadeko
ECHO Downloading Nadeko...
ECHO.
@ -28,28 +29,28 @@ TITLE Installing NadekoBot, please wait...
ECHO.
ECHO Installing Discord.Net(1/4)...
::Building Nadeko
CD /D %build1%
CD /D "%build1%"
dotnet restore >nul 2>&1
ECHO Installing Discord.Net(2/4)...
CD /D %build2%
CD /D "%build2%"
dotnet restore >nul 2>&1
ECHO Installing Discord.Net(3/4)...
CD /D %build3%
CD /D "%build3%"
dotnet restore >nul 2>&1
ECHO Installing Discord.Net(4/4)...
CD /D %build4%
CD /D "%build4%"
dotnet restore >nul 2>&1
ECHO.
ECHO Discord.Net installation completed successfully...
ECHO.
ECHO Installing NadekoBot...
CD /D %build5%
CD /D "%build5%"
dotnet restore >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
IF EXIST "%root%NadekoBot\" (GOTO :backupinstall)
IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) ELSE (GOTO :freshinstall)
:freshinstall
::Moves the NadekoBot folder to keep things tidy
ECHO.
@ -65,20 +66,23 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall)
ROBOCOPY "%root%NadekoBot" "%root%NadekoBot_Old" /MIR >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
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
COPY "%root%NadekoBot_Old\src\NadekoBot\credentials.json" "%installtemp%NadekoBot\src\NadekoBot\credentials.json" >nul 2>&1
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
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
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
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
IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror)
ECHO.
ECHO Old data folder copied to new folder
ECHO Old data folder copied...
::Moves the setup Nadeko folder
RMDIR "%root%NadekoBot\" /S /Q >nul 2>&1
ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1
@ -103,7 +107,7 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall)
:giterror
ECHO.
ECHO Git clone failed, trying again
RMDIR %installtemp% /S /Q >nul 2>&1
RMDIR "%installtemp%" /S /Q >nul 2>&1
GOTO :start
:copyerror
::If at any point a copy error is encountered
@ -124,22 +128,39 @@ ECHO.
ECHO Your System Architecture is 32bit...
timeout /t 5
ECHO.
ECHO Downloading libsodium.dll and opus.dll...
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"
bitsadmin.exe /transfer "Downloading libsodium.dll" /priority high https://github.com/Kwoth/NadekoBot/raw/dev/src/NadekoBot/_libs/32/libsodium.dll "%FILENAME%"
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"
bitsadmin.exe /transfer "Downloading opus.dll" /priority high https://github.com/Kwoth/NadekoBot/raw/dev/src/NadekoBot/_libs/32/opus.dll "%FILENAME%"
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
::Normal execution of end of script
TITLE Installation complete!
TITLE NadekoBot Installation complete!
CD /D "%root%"
RMDIR /S /Q "%installtemp%" >nul 2>&1
ECHO.
ECHO Installation complete, press any key to close this window!
ECHO Installation complete!
ECHO.
timeout /t 5
del Stable.bat

View File

@ -6,6 +6,6 @@ namespace NadekoBot.Attributes
public class OwnerOnlyAttribute : PreconditionAttribute
{
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,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;
}
}
}
}
}

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");
}
}
}

View File

@ -24,6 +24,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Action");
b.Property<DateTime?>("DateAdded");
b.Property<int>("GuildConfigId");
b.Property<int>("Seconds");
@ -47,6 +49,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.HasKey("Id");
b.HasIndex("AntiSpamSettingId");
@ -61,6 +65,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Action");
b.Property<DateTime?>("DateAdded");
b.Property<int>("GuildConfigId");
b.Property<int>("MessageThreshold");
@ -80,6 +86,8 @@ namespace NadekoBot.Migrations
b.Property<int?>("BotConfigId");
b.Property<DateTime?>("DateAdded");
b.Property<ulong>("ItemId");
b.Property<int>("Type");
@ -91,6 +99,28 @@ namespace NadekoBot.Migrations
b.ToTable("BlacklistItem");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<int?>("BotConfigId1");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Name");
b.HasKey("Id");
b.HasIndex("BotConfigId");
b.HasIndex("BotConfigId1");
b.ToTable("BlockedCmdOrMdl");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b =>
{
b.Property<int>("Id")
@ -120,6 +150,8 @@ namespace NadekoBot.Migrations
b.Property<string>("DMHelpString");
b.Property<DateTime?>("DateAdded");
b.Property<string>("ErrorColor");
b.Property<bool>("ForwardMessages");
@ -158,6 +190,8 @@ namespace NadekoBot.Migrations
b.Property<int>("ClashWarId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("SequenceNumber");
b.Property<int>("Stars");
@ -178,6 +212,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("EnemyClan");
b.Property<ulong>("GuildId");
@ -193,6 +229,26 @@ namespace NadekoBot.Migrations
b.ToTable("ClashOfClans");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<string>("Mapping");
b.Property<string>("Trigger");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("CommandAlias");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b =>
{
b.Property<int>("Id")
@ -200,6 +256,8 @@ namespace NadekoBot.Migrations
b.Property<string>("CommandName");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<int>("Seconds");
@ -220,6 +278,8 @@ namespace NadekoBot.Migrations
b.Property<string>("CommandName");
b.Property<DateTime?>("DateAdded");
b.Property<int>("Price");
b.HasKey("Id");
@ -237,6 +297,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<string>("InternalTrigger");
b.Property<decimal>("Modifier");
@ -255,6 +317,8 @@ namespace NadekoBot.Migrations
b.Property<long>("Amount");
b.Property<DateTime?>("DateAdded");
b.Property<ulong>("UserId");
b.HasKey("Id");
@ -272,6 +336,8 @@ namespace NadekoBot.Migrations
b.Property<long>("Amount");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Reason");
b.Property<ulong>("UserId");
@ -286,6 +352,12 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("AutoDeleteTrigger");
b.Property<DateTime?>("DateAdded");
b.Property<bool>("DmResponse");
b.Property<ulong?>("GuildId");
b.Property<bool>("IsRegex");
@ -308,6 +380,8 @@ namespace NadekoBot.Migrations
b.Property<string>("AvatarId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Discriminator");
b.Property<ulong>("UserId");
@ -328,6 +402,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Amount");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Name");
b.Property<ulong>("UserId");
@ -347,6 +423,8 @@ namespace NadekoBot.Migrations
b.Property<int?>("BotConfigId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Text");
b.HasKey("Id");
@ -363,6 +441,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<int?>("GuildConfigId1");
@ -381,6 +461,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<string>("Word");
@ -399,6 +481,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("GuildId");
@ -421,6 +505,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.HasKey("Id");
@ -455,6 +541,8 @@ namespace NadekoBot.Migrations
b.Property<bool>("CleverbotEnabled");
b.Property<DateTime?>("DateAdded");
b.Property<float>("DefaultMusicVolume");
b.Property<bool>("DeleteMessageOnCommand");
@ -467,6 +555,8 @@ namespace NadekoBot.Migrations
b.Property<bool>("FilterWords");
b.Property<ulong?>("GameVoiceChannel");
b.Property<ulong>("GreetMessageChannelId");
b.Property<ulong>("GuildId");
@ -493,6 +583,8 @@ namespace NadekoBot.Migrations
b.Property<bool>("VoicePlusTextEnabled");
b.Property<bool>("WarningsInitialized");
b.HasKey("Id");
b.HasIndex("GuildId")
@ -512,6 +604,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("GuildId");
@ -534,6 +628,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("LogSettingId");
b.HasKey("Id");
@ -550,6 +646,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("LogSettingId");
b.HasKey("Id");
@ -578,6 +676,8 @@ namespace NadekoBot.Migrations
b.Property<ulong?>("ChannelUpdatedId");
b.Property<DateTime?>("DateAdded");
b.Property<bool>("IsLogging");
b.Property<ulong?>("LogOtherId");
@ -638,6 +738,8 @@ namespace NadekoBot.Migrations
b.Property<int?>("BotConfigId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("ModuleName");
b.Property<string>("Prefix");
@ -658,6 +760,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("AuthorId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Name");
b.HasKey("Id");
@ -670,6 +774,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("UserId");
@ -686,6 +792,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("NextId");
b.Property<int>("PrimaryTarget");
@ -706,6 +814,34 @@ namespace NadekoBot.Migrations
b.ToTable("Permission");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<int>("Index");
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("GuildConfigId");
b.ToTable("Permissionv2");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b =>
{
b.Property<int>("Id")
@ -713,6 +849,8 @@ namespace NadekoBot.Migrations
b.Property<int?>("BotConfigId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Status");
b.HasKey("Id");
@ -727,6 +865,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("MusicPlaylistId");
b.Property<string>("Provider");
@ -756,6 +896,8 @@ namespace NadekoBot.Migrations
b.Property<string>("AuthorName")
.IsRequired();
b.Property<DateTime?>("DateAdded");
b.Property<ulong>("GuildId");
b.Property<string>("Keyword")
@ -776,6 +918,8 @@ namespace NadekoBot.Migrations
b.Property<int?>("BotConfigId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Icon");
b.Property<string>("Name");
@ -794,6 +938,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<bool>("IsPrivate");
b.Property<string>("Message");
@ -809,11 +955,34 @@ namespace NadekoBot.Migrations
b.ToTable("Reminders");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AmountRewardedThisMonth");
b.Property<DateTime?>("DateAdded");
b.Property<DateTime>("LastReward");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("RewardedUsers");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<ulong>("GuildId");
b.Property<ulong>("RoleId");
@ -826,11 +995,149 @@ namespace NadekoBot.Migrations
b.ToTable("SelfAssignableRoles");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("AuthorId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<int>("Index");
b.Property<string>("Name");
b.Property<int>("Price");
b.Property<ulong>("RoleId");
b.Property<string>("RoleName");
b.Property<int>("Type");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("ShopEntry");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("ShopEntryId");
b.Property<string>("Text");
b.HasKey("Id");
b.HasIndex("ShopEntryId");
b.ToTable("ShopEntryItem");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("RoleId");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("SlowmodeIgnoredRole");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("SlowmodeIgnoredUser");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int?>("BotConfigId");
b.Property<ulong>("ChannelId");
b.Property<string>("ChannelName");
b.Property<string>("CommandText");
b.Property<DateTime?>("DateAdded");
b.Property<ulong?>("GuildId");
b.Property<string>("GuildName");
b.Property<int>("Index");
b.Property<ulong?>("VoiceChannelId");
b.Property<string>("VoiceChannelName");
b.HasKey("Id");
b.HasIndex("BotConfigId");
b.ToTable("StartupCommand");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<DateTime>("UnmuteAt");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("UnmuteTimer");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<ulong>("UserId");
b.Property<string>("type");
@ -843,6 +1150,26 @@ namespace NadekoBot.Migrations
b.ToTable("PokeGame");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("RoleId");
b.Property<ulong>("VoiceChannelId");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("VcRoleInfo");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b =>
{
b.Property<int>("Id")
@ -852,6 +1179,8 @@ namespace NadekoBot.Migrations
b.Property<int?>("ClaimerId");
b.Property<DateTime?>("DateAdded");
b.Property<int>("Price");
b.Property<int>("WaifuId");
@ -873,6 +1202,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("NewId");
b.Property<int?>("OldId");
@ -892,6 +1223,52 @@ namespace NadekoBot.Migrations
b.ToTable("WaifuUpdates");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<bool>("Forgiven");
b.Property<string>("ForgivenBy");
b.Property<ulong>("GuildId");
b.Property<string>("Moderator");
b.Property<string>("Reason");
b.Property<ulong>("UserId");
b.HasKey("Id");
b.ToTable("Warnings");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Count");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<int>("Punishment");
b.Property<int>("Time");
b.HasKey("Id");
b.HasIndex("GuildConfigId");
b.ToTable("WarningPunishment");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
@ -922,6 +1299,17 @@ namespace NadekoBot.Migrations
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("BlockedCommands")
.HasForeignKey("BotConfigId");
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("BlockedModules")
.HasForeignKey("BotConfigId1");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar")
@ -930,6 +1318,13 @@ namespace NadekoBot.Migrations
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("CommandAliases")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
@ -1036,6 +1431,13 @@ namespace NadekoBot.Migrations
.HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("Permissions")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
@ -1058,6 +1460,55 @@ namespace NadekoBot.Migrations
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("ShopEntries")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.ShopEntry")
.WithMany("Items")
.HasForeignKey("ShopEntryId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("SlowmodeIgnoredRoles")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("SlowmodeIgnoredUsers")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.BotConfig")
.WithMany("StartupCommands")
.HasForeignKey("BotConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("UnmuteTimers")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("VcRoleInfos")
.HasForeignKey("GuildConfigId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity")
@ -1089,6 +1540,13 @@ namespace NadekoBot.Migrations
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
.WithMany("WarnPunishments")
.HasForeignKey("GuildConfigId");
});
}
}
}

View File

@ -12,11 +12,12 @@ using NadekoBot.Services.Database.Models;
using static NadekoBot.Modules.Permissions.Permissions;
using System.Collections.Concurrent;
using NLog;
using NadekoBot.Modules.Permissions;
namespace NadekoBot.Modules.Administration
{
[NadekoModule("Administration", ".")]
public partial class Administration : NadekoModule
public partial class Administration : NadekoTopLevelModule
{
private static ConcurrentHashSet<ulong> deleteMessagesOnCommand { get; }
@ -31,7 +32,7 @@ namespace NadekoBot.Modules.Administration
}
private static Task DelMsgOnCmd_Handler(SocketUserMessage msg, CommandInfo cmd)
private static Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
{
var _ = Task.Run(async () =>
{
@ -56,24 +57,33 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.Administrator)]
public async Task ResetPermissions()
{
var channel = (ITextChannel)Context.Channel;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.PermissionsFor(Context.Guild.Id);
config.RootPermission = Permission.GetDefaultRoot();
var toAdd = new PermissionCache()
{
RootPermission = config.RootPermission,
PermRole = config.PermissionRole,
Verbose = config.VerbosePermissions,
};
Cache.AddOrUpdate(channel.Guild.Id,
toAdd, (id, old) => toAdd);
var config = uow.GuildConfigs.GcWithPermissionsv2For(Context.Guild.Id);
config.Permissions = Permissionv2.GetDefaultPermlist;
await uow.CompleteAsync();
UpdateCache(config);
}
await ReplyConfirmLocalized("perms_reset").ConfigureAwait(false);
}
[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]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
@ -106,6 +116,10 @@ namespace NadekoBot.Modules.Administration
[RequireBotPermission(GuildPermission.ManageRoles)]
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
{
await usr.AddRolesAsync(role).ConfigureAwait(false);
@ -125,6 +139,9 @@ namespace NadekoBot.Modules.Administration
[RequireBotPermission(GuildPermission.ManageRoles)]
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
{
await usr.RemoveRolesAsync(role).ConfigureAwait(false);
@ -142,6 +159,9 @@ namespace NadekoBot.Modules.Administration
[RequireBotPermission(GuildPermission.ManageRoles)]
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
{
if (roleToEdit.Position > (await Context.Guild.GetCurrentUserAsync().ConfigureAwait(false)).GetRoles().Max(r => r.Position))
@ -164,9 +184,15 @@ namespace NadekoBot.Modules.Administration
[RequireBotPermission(GuildPermission.ManageRoles)]
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
{
await user.RemoveRolesAsync(user.GetRoles()).ConfigureAwait(false);
await user.RemoveRolesAsync(userRoles).ConfigureAwait(false);
await ReplyConfirmLocalized("rar", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
@ -188,6 +214,19 @@ namespace NadekoBot.Modules.Administration
await ReplyConfirmLocalized("cr", Format.Bold(r.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[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)]
@ -225,101 +264,6 @@ namespace NadekoBot.Modules.Administration
}
}
[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.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);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.DeafenMembers)]
@ -440,6 +384,7 @@ namespace NadekoBot.Modules.Administration
var enumerable = (await Context.Channel.GetMessagesAsync().Flatten()).AsEnumerable();
enumerable = enumerable.Where(x => x.Author.Id == user.Id);
await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false);
Context.Message.DeleteAfter(3);
}
// prune x
@ -447,14 +392,18 @@ namespace NadekoBot.Modules.Administration
[RequireContext(ContextType.Guild)]
[RequireUserPermission(ChannelPermission.ManageMessages)]
[RequireBotPermission(GuildPermission.ManageMessages)]
[Priority(0)]
public async Task Prune(int count)
{
if (count < 1)
return;
count += 1;
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));
if (enumerable.FirstOrDefault()?.Id == Context.Message.Id)
enumerable = enumerable.Skip(1).ToArray();
else
enumerable = enumerable.Take(count);
await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false);
}
@ -463,6 +412,7 @@ namespace NadekoBot.Modules.Administration
[RequireContext(ContextType.Guild)]
[RequireUserPermission(ChannelPermission.ManageMessages)]
[RequireBotPermission(GuildPermission.ManageMessages)]
[Priority(1)]
public async Task Prune(IGuildUser user, int count = 100)
{
if (count < 1)
@ -474,6 +424,8 @@ namespace NadekoBot.Modules.Administration
int limit = (count < 100) ? count : 100;
var enumerable = (await Context.Channel.GetMessagesAsync(limit: limit).Flatten()).Where(m => m.Author == user);
await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false);
Context.Message.DeleteAfter(3);
}
[NadekoCommand, Usage, Description, Aliases]

View File

@ -1,6 +1,7 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NLog;
using System;
@ -48,6 +49,11 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task AutoAssignRole([Remainder] IRole role = null)
{
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())
{
var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set);

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

@ -16,10 +16,30 @@ namespace NadekoBot.Modules.Administration
[Group]
public class LocalizationCommands : NadekoSubmodule
{
//Română, România
//Bahasa Indonesia, Indonesia
private ImmutableDictionary<string, string> supportedLocales { get; } = new Dictionary<string, string>()
{
{"en-US", "English, United States" },
{"sr-cyrl-rs", "Serbian, Cyrillic" }
//{"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]
@ -91,12 +111,12 @@ namespace NadekoBot.Modules.Administration
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task LanguagesList()
{
await ReplyConfirmLocalized("lang_list",
string.Join("\n", supportedLocales.Select(x => $"{Format.Code(x.Key)} => {x.Value}")))
.ConfigureAwait(false);
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}"))));
}
}
}

View File

@ -126,7 +126,6 @@ namespace NadekoBot.Modules.Administration
{
embed.WithTitle("👥" + g.GetLogText("avatar_changed"))
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithThumbnailUrl(before.AvatarUrl)
.WithImageUrl(after.AvatarUrl)
.WithFooter(fb => fb.WithText(currentTime))
@ -308,6 +307,9 @@ namespace NadekoBot.Modules.Administration
punishment = "🔇 " + logChannel.Guild.GetLogText("muted_pl").ToUpperInvariant();
break;
case PunishmentAction.Kick:
punishment = "👢 " + logChannel.Guild.GetLogText("kicked_pl").ToUpperInvariant();
break;
case PunishmentAction.Softban:
punishment = "☣ " + logChannel.Guild.GetLogText("soft_banned_pl").ToUpperInvariant();
break;
case PunishmentAction.Ban:
@ -530,7 +532,7 @@ namespace NadekoBot.Modules.Administration
else if (afterVch == null)
{
str = "🎙" + Format.Code(prettyCurrentTime) + logChannel.Guild.GetLogText("user_vleft",
"👤" + Format.Code(prettyCurrentTime), "👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
"👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
Format.Bold(beforeVch.Name ?? ""));
}
if (str != null)
@ -705,17 +707,18 @@ namespace NadekoBot.Modules.Administration
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle("🗑 " + logChannel.Guild.GetLogText("msg_del", ((ITextChannel)msg.Channel).Name))
.WithDescription($"{msg.Author}")
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("content")).WithValue(msg.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.WithDescription(msg.Author.ToString())
.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))
.WithFooter(efb => efb.WithText(currentTime));
if (msg.Attachments.Any())
embed.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("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);
}
catch
catch (Exception ex)
{
_log.Warn(ex);
// ignored
}
}
@ -753,8 +756,8 @@ namespace NadekoBot.Modules.Administration
.WithOkColor()
.WithTitle("📝 " + logChannel.Guild.GetLogText("msg_update", ((ITextChannel)after.Channel).Name))
.WithDescription(after.Author.ToString())
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("old_msg")).WithValue(before.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("new_msg")).WithValue(after.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(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))
.WithFooter(efb => efb.WithText(currentTime));
@ -988,7 +991,9 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task LogEvents()
{
await ReplyConfirmLocalized("log_events", string.Join(", ", Enum.GetNames(typeof(LogType)).Cast<string>())).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(GetText("log_events") + "\n" +
string.Join(", ", Enum.GetNames(typeof(LogType)).Cast<string>()))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -1066,7 +1071,7 @@ namespace NadekoBot.Modules.Administration
public static class GuildExtensions
{
public static string GetLogText(this IGuild guild, string key, params object[] replacements)
=> NadekoModule.GetTextStatic(key,
=> NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(guild),
typeof(Administration).Name.ToLowerInvariant(),
replacements);

View File

@ -1,14 +1,15 @@
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 NLog;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
@ -20,6 +21,8 @@ namespace NadekoBot.Modules.Administration
{
private static ConcurrentDictionary<ulong, string> guildMuteRoles { get; }
private static ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> mutedUsers { get; }
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> UserUnmuted = delegate { };
@ -30,6 +33,9 @@ namespace NadekoBot.Modules.Administration
Chat,
All
}
private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny);
private static readonly new Logger _log = LogManager.GetCurrentClassLogger();
static MuteCommands()
{
@ -43,6 +49,23 @@ namespace NadekoBot.Modules.Administration
v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))
));
foreach (var conf in configs)
{
foreach (var x in conf.UnmuteTimers)
{
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);
}
}
NadekoBot.Client.UserJoined += Client_UserJoined;
}
@ -67,10 +90,15 @@ namespace NadekoBot.Modules.Administration
public static async Task MuteUser(IGuildUser usr)
{
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())
{
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()
{
UserId = usr.Id
@ -78,7 +106,9 @@ namespace NadekoBot.Modules.Administration
ConcurrentHashSet<ulong> muted;
if (mutedUsers.TryGetValue(usr.Guild.Id, out muted))
muted.Add(usr.Id);
config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
UserMuted(usr, MuteType.All);
@ -86,11 +116,13 @@ namespace NadekoBot.Modules.Administration
public static async Task UnmuteUser(IGuildUser usr)
{
await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false);
await usr.RemoveRolesAsync(await GetMuteRole(usr.Guild)).ConfigureAwait(false);
StopUnmuteTimer(usr.GuildId, usr.Id);
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())
{
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()
{
UserId = usr.Id
@ -98,6 +130,9 @@ namespace NadekoBot.Modules.Administration
ConcurrentHashSet<ulong> muted;
if (mutedUsers.TryGetValue(usr.Guild.Id, out muted))
muted.TryRemove(usr.Id);
config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
UserUnmuted(usr, MuteType.All);
@ -121,24 +156,102 @@ namespace NadekoBot.Modules.Administration
muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) ??
await guild.CreateRoleAsync(defaultMuteRoleName, GuildPermissions.None).ConfigureAwait(false);
}
}
foreach (var toOverwrite in (await guild.GetTextChannelsAsync()))
foreach (var toOverwrite in (await guild.GetTextChannelsAsync()))
{
try
{
try
if (!toOverwrite.PermissionOverwrites.Select(x => x.Permissions).Contains(denyOverwrite))
{
await toOverwrite.AddPermissionOverwriteAsync(muteRole, new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny))
await toOverwrite.AddPermissionOverwriteAsync(muteRole, denyOverwrite)
.ConfigureAwait(false);
await Task.Delay(200).ConfigureAwait(false);
}
catch
{
// ignored
}
await Task.Delay(200).ConfigureAwait(false);
}
catch
{
// ignored
}
}
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]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
@ -170,6 +283,7 @@ namespace NadekoBot.Modules.Administration
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireUserPermission(GuildPermission.MuteMembers)]
[Priority(1)]
public async Task Mute(IGuildUser user)
{
try
@ -183,6 +297,27 @@ namespace NadekoBot.Modules.Administration
}
}
[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);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]

View File

@ -46,16 +46,18 @@ namespace NadekoBot.Modules.Administration
LastMessage = msg.ToUpperInvariant();
}
public void ApplyNextMessage(string message)
public void ApplyNextMessage(IUserMessage message)
{
var upperMsg = message.ToUpperInvariant();
if (upperMsg == LastMessage)
Count++;
else
var upperMsg = message.Content.ToUpperInvariant();
if (upperMsg != LastMessage || (string.IsNullOrWhiteSpace(upperMsg) && message.Attachments.Any()))
{
LastMessage = upperMsg;
Count = 0;
}
else
{
Count++;
}
}
}
@ -113,7 +115,7 @@ namespace NadekoBot.Modules.Administration
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, new UserSpamStats(msg.Content),
(id, old) =>
{
old.ApplyNextMessage(msg.Content); return old;
old.ApplyNextMessage(msg); return old;
});
if (stats.Count >= spamSettings.AntiSpamSettings.MessageThreshold)
@ -186,6 +188,13 @@ namespace NadekoBot.Modules.Administration
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);

View File

@ -1,10 +1,16 @@
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 NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -13,9 +19,12 @@ namespace NadekoBot.Modules.Administration
public partial class Administration
{
[Group]
public class RatelimitCommand : NadekoSubmodule
public class RatelimitCommands : NadekoSubmodule
{
public static ConcurrentDictionary<ulong, Ratelimiter> RatelimitingChannels = new ConcurrentDictionary<ulong, Ratelimiter>();
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
@ -35,10 +44,17 @@ namespace NadekoBot.Modules.Administration
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)
{
HashSet<ulong> ignoreUsers;
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)
if (usr.MessageCount >= MaxMessages)
{
return true;
}
@ -56,16 +72,26 @@ namespace NadekoBot.Modules.Administration
}
}
static RatelimitCommand()
static RatelimitCommands()
{
_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) =>
{
try
{
var usrMsg = umsg as IUserMessage;
var channel = usrMsg?.Channel as ITextChannel;
var usrMsg = umsg as SocketUserMessage;
var channel = usrMsg?.Channel as SocketTextChannel;
if (channel == null || usrMsg.IsAuthor())
return;
@ -73,7 +99,7 @@ namespace NadekoBot.Modules.Administration
if (!RatelimitingChannels.TryGetValue(channel.Id, out limiter))
return;
if (limiter.CheckUserRatelimit(usrMsg.Author.Id))
if (limiter.CheckUserRatelimit(usrMsg.Author.Id, channel.Guild.Id, usrMsg.Author as SocketGuildUser))
await usrMsg.DeleteAsync();
}
catch (Exception ex) { _log.Warn(ex); }
@ -118,6 +144,70 @@ namespace NadekoBot.Modules.Administration
.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

@ -43,6 +43,10 @@ namespace NadekoBot.Modules.Administration
{
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;
var error = false;
using (var uow = DbHandler.UnitOfWork())
@ -75,6 +79,10 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task Rsar([Remainder] IRole role)
{
var guser = (IGuildUser)Context.User;
if (Context.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
return;
bool success;
using (var uow = DbHandler.UnitOfWork())
{
@ -150,11 +158,11 @@ namespace NadekoBot.Modules.Administration
var guildUser = (IGuildUser)Context.User;
GuildConfig conf;
IEnumerable<SelfAssignedRole> roles;
SelfAssignedRole[] roles;
using (var uow = DbHandler.UnitOfWork())
{
conf = uow.GuildConfigs.For(Context.Guild.Id, set => set);
roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id);
roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id).ToArray();
}
if (roles.FirstOrDefault(r=>r.RoleId == role.Id) == null)
{
@ -167,14 +175,21 @@ namespace NadekoBot.Modules.Administration
return;
}
var roleIds = roles.Select(x => x.RoleId).ToArray();
if (conf.ExclusiveSelfAssignedRoles)
{
var sameRoleId = guildUser.RoleIds.FirstOrDefault(r => roles.Select(sar => sar.RoleId).Contains(r));
var sameRole = Context.Guild.GetRole(sameRoleId);
var sameRoleId = guildUser.RoleIds.FirstOrDefault(r => roleIds.Contains(r));
if (sameRoleId != default(ulong))
{
await ReplyErrorLocalized("self_assign_already_excl", Format.Bold(sameRole?.Name)).ConfigureAwait(false);
return;
var sameRole = Context.Guild.GetRole(sameRoleId);
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

View File

@ -10,6 +10,8 @@ using System.Net.Http;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using Microsoft.EntityFrameworkCore;
namespace NadekoBot.Modules.Administration
{
@ -31,6 +33,166 @@ namespace NadekoBot.Modules.Administration
_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]
@ -68,27 +230,46 @@ namespace NadekoBot.Modules.Administration
}
public static async Task HandleDmForwarding(SocketMessage msg, List<IDMChannel> ownerChannels)
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 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, msg.Content))).ConfigureAwait(false);
.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, msg.Content).ConfigureAwait(false); }
{
try
{
await firstOwnerChannel.SendConfirmAsync(title, toSend).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
}
}
@ -138,7 +319,7 @@ namespace NadekoBot.Modules.Administration
else
{
await server.DeleteAsync().ConfigureAwait(false);
await ReplyConfirmLocalized("deleted_server",Format.Bold(server.Name)).ConfigureAwait(false);
await ReplyConfirmLocalized("deleted_server", Format.Bold(server.Name)).ConfigureAwait(false);
}
}
@ -313,4 +494,4 @@ namespace NadekoBot.Modules.Administration
}
}
}
}
}

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

@ -166,7 +166,7 @@ namespace NadekoBot.Modules.Administration
var botUser = await guild.GetCurrentUserAsync().ConfigureAwait(false);
if (!botUser.GuildPermissions.ManageRoles || !botUser.GuildPermissions.ManageChannels)
{
await ReplyErrorLocalized("vt_no_perms").ConfigureAwait(false);
await ReplyErrorLocalized("vt_perms").ConfigureAwait(false);
return;
}

View File

@ -11,15 +11,13 @@ using NadekoBot.Services.Database.Models;
using System.Linq;
using NadekoBot.Extensions;
using System.Threading;
using System.Diagnostics;
using NLog;
namespace NadekoBot.Modules.ClashOfClans
{
[NadekoModule("ClashOfClans", ",")]
public class ClashOfClans : NadekoModule
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; }
@ -82,11 +80,9 @@ namespace NadekoBot.Modules.ClashOfClans
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task CreateWar(int size, [Remainder] string enemyClan = null)
{
if (!(Context.User as IGuildUser).GuildPermissions.ManageChannels)
return;
if (string.IsNullOrWhiteSpace(enemyClan))
return;

View File

@ -135,7 +135,7 @@ namespace NadekoBot.Modules.ClashOfClans
public static string Localize(this ClashWar cw, string key)
{
return NadekoModule.GetTextStatic(key,
return NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(cw.Channel?.GuildId),
typeof(ClashOfClans).Name.ToLowerInvariant());
}

View File

@ -11,13 +11,29 @@ using NLog;
using System.Diagnostics;
using Discord.WebSocket;
using System;
using Newtonsoft.Json;
using NadekoBot.DataStructures;
namespace NadekoBot.Modules.CustomReactions
{
public static class CustomReactionExtensions
{
public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage context)
{
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 : NadekoModule
public class CustomReactions : NadekoTopLevelModule
{
private static CustomReaction[] _globalReactions = new CustomReaction[] { };
public static CustomReaction[] GlobalReactions => _globalReactions;
@ -25,7 +41,7 @@ namespace NadekoBot.Modules.CustomReactions
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()
{
@ -43,11 +59,11 @@ namespace NadekoBot.Modules.CustomReactions
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;
if (channel == null)
return false;
return null;
var content = umsg.Content.Trim().ToLowerInvariant();
CustomReaction[] reactions;
@ -70,26 +86,9 @@ namespace NadekoBot.Modules.CustomReactions
var reaction = rs[new NadekoRandom().Next(0, rs.Length)];
if (reaction != null)
{
if (reaction.Response != "-")
{
CREmbed crembed;
if (CREmbed.TryParse(reaction.Response, out crembed))
{
try { await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "").ConfigureAwait(false); }
catch (Exception ex)
{
_log.Warn("Sending CREmbed failed");
_log.Warn(ex);
}
}
else
{
try { await channel.SendMessageAsync(reaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { }
}
}
ReactionStats.AddOrUpdate(reaction.Trigger, 1, (k, old) => ++old);
return true;
if (reaction.Response == "-")
return null;
return reaction;
}
}
}
@ -103,29 +102,10 @@ namespace NadekoBot.Modules.CustomReactions
return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger);
}).ToArray();
if (grs.Length == 0)
return false;
return null;
var greaction = grs[new NadekoRandom().Next(0, grs.Length)];
if (greaction != null)
{
CREmbed crembed;
if (CREmbed.TryParse(greaction.Response, out crembed))
{
try { await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "").ConfigureAwait(false); }
catch (Exception ex)
{
_log.Warn("Sending CREmbed failed");
_log.Warn(ex);
}
}
else
{
try { await channel.SendMessageAsync(greaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { }
}
ReactionStats.AddOrUpdate(greaction.Trigger, 1, (k, old) => ++old);
return true;
}
return false;
return greaction;
}
[NadekoCommand, Usage, Description, Aliases]
@ -208,7 +188,19 @@ namespace NadekoBot.Modules.CustomReactions
.WithDescription(string.Join("\n", customReactions.OrderBy(cr => cr.Trigger)
.Skip((curPage - 1) * 20)
.Take(20)
.Select(cr => $"`#{cr.Id}` `{GetText("trigger")}:` {cr.Trigger}"))), lastPage)
.Select(cr =>
{
var str = $"`#{cr.Id}` {cr.Trigger}";
if (cr.AutoDeleteTrigger)
{
str = "🗑" + str;
}
if (cr.DmResponse)
{
str = "📪" + str;
}
return str;
}))), lastPage)
.ConfigureAwait(false);
}
@ -359,6 +351,108 @@ namespace NadekoBot.Modules.CustomReactions
}
}
[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]
[OwnerOnly]
public async Task CrStatsClear(string trigger = null)

View File

@ -15,7 +15,7 @@ namespace NadekoBot.Modules.CustomReactions
{
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>>()

View File

@ -28,7 +28,7 @@ namespace NadekoBot.Modules.Gambling
var ar = new AnimalRace(Context.Guild.Id, (ITextChannel)Context.Channel, Prefix);
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]
@ -43,7 +43,7 @@ namespace NadekoBot.Modules.Gambling
AnimalRace 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;
}
await ar.JoinRace(Context.User as IGuildUser, amount);
@ -56,22 +56,22 @@ namespace NadekoBot.Modules.Gambling
public bool Fail { get; set; }
public List<Participant> participants = new List<Participant>();
private ulong serverId;
private int messagesSinceGameStarted = 0;
private readonly List<Participant> _participants = new List<Participant>();
private readonly ulong _serverId;
private int _messagesSinceGameStarted;
private readonly string _prefix;
private Logger _log { get; }
private readonly Logger _log;
public ITextChannel raceChannel { get; set; }
public bool Started { get; private set; } = false;
private readonly ITextChannel _raceChannel;
public bool Started { get; private set; }
public AnimalRace(ulong serverId, ITextChannel ch, string prefix)
{
this._prefix = prefix;
this._log = LogManager.GetCurrentClassLogger();
this.serverId = serverId;
this.raceChannel = ch;
_prefix = prefix;
_log = LogManager.GetCurrentClassLogger();
_serverId = serverId;
_raceChannel = ch;
if (!AnimalRaces.TryAdd(serverId, this))
{
Fail = true;
@ -90,8 +90,8 @@ namespace NadekoBot.Modules.Gambling
{
try
{
await raceChannel.SendConfirmAsync("Animal Race", $"Starting in 20 seconds or when the room is full.",
footer: $"Type {_prefix}jr to join the race.");
await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting"),
footer: GetText("animal_race_join_instr", _prefix));
}
catch (Exception ex)
{
@ -102,16 +102,16 @@ namespace NadekoBot.Modules.Gambling
cancelSource.Cancel();
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
{
try { await raceChannel.SendErrorAsync("Animal Race", "Failed to start since there was not enough participants."); } catch (Exception ex) { _log.Warn(ex); }
var p = participants.FirstOrDefault();
try { await _raceChannel.SendErrorAsync(GetText("animal_race"), GetText("animal_race_failed")); } catch (Exception ex) { _log.Warn(ex); }
var p = _participants.FirstOrDefault();
if (p != null && p.AmountBet > 0)
await CurrencyHandler.AddCurrencyAsync(p.User, "BetRace", p.AmountBet, false).ConfigureAwait(false);
@ -128,7 +128,7 @@ namespace NadekoBot.Modules.Gambling
private void End()
{
AnimalRace throwaway;
AnimalRaces.TryRemove(serverId, out throwaway);
AnimalRaces.TryRemove(_serverId, out throwaway);
}
private async Task StartRace()
@ -136,21 +136,21 @@ namespace NadekoBot.Modules.Gambling
var rng = new NadekoRandom();
Participant winner = null;
IUserMessage msg = null;
int place = 1;
var place = 1;
try
{
NadekoBot.Client.MessageReceived += Client_MessageReceived;
while (!participants.All(p => p.Total >= 60))
while (!_participants.All(p => p.Total >= 60))
{
//update the state
participants.ForEach(p =>
_participants.ForEach(p =>
{
p.Total += 1 + rng.Next(0, 10);
});
participants
_participants
.OrderByDescending(p => p.Total)
.ForEach(p =>
{
@ -170,14 +170,14 @@ namespace NadekoBot.Modules.Gambling
//draw the state
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)
try { await msg.DeleteAsync(); } catch { }
messagesSinceGameStarted = 0;
try { msg = await raceChannel.SendMessageAsync(text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
_messagesSinceGameStarted = 0;
try { msg = await _raceChannel.SendMessageAsync(text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
}
else
{
@ -187,22 +187,33 @@ namespace NadekoBot.Modules.Gambling
await Task.Delay(2500);
}
}
catch { }
catch
{
// ignored
}
finally
{
NadekoBot.Client.MessageReceived -= Client_MessageReceived;
}
if (winner.AmountBet > 0)
if (winner != null)
{
var wonAmount = winner.AmountBet * (participants.Count - 1);
if (winner.AmountBet > 0)
{
var wonAmount = winner.AmountBet * (_participants.Count - 1);
await CurrencyHandler.AddCurrencyAsync(winner.User, "Won a Race", wonAmount, true).ConfigureAwait(false);
await raceChannel.SendConfirmAsync("Animal Race", $"{winner.User.Mention} as {winner.Animal} **Won the race and {wonAmount}{CurrencySign}!**").ConfigureAwait(false);
}
else
{
await raceChannel.SendConfirmAsync("Animal Race", $"{winner.User.Mention} as {winner.Animal} **Won the race!**").ConfigureAwait(false);
await CurrencyHandler.AddCurrencyAsync(winner.User, "Won a Race", wonAmount, true)
.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
{
await _raceChannel.SendConfirmAsync(GetText("animal_race"),
Format.Bold(GetText("animal_race_won", winner.User.Mention, winner.Animal))).ConfigureAwait(false);
}
}
}
@ -212,9 +223,9 @@ namespace NadekoBot.Modules.Gambling
var msg = imsg as SocketUserMessage;
if (msg == null)
return Task.CompletedTask;
if (msg.IsAuthor() || !(imsg.Channel is ITextChannel) || imsg.Channel != raceChannel)
if (msg.IsAuthor() || !(imsg.Channel is ITextChannel) || imsg.Channel != _raceChannel)
return Task.CompletedTask;
messagesSinceGameStarted++;
_messagesSinceGameStarted++;
return Task.CompletedTask;
}
@ -228,51 +239,66 @@ namespace NadekoBot.Modules.Gambling
public async Task JoinRace(IGuildUser u, int amount = 0)
{
var animal = "";
string 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;
}
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;
}
if (Started)
{
await raceChannel.SendErrorAsync($"{u.Mention} `Race is already started`").ConfigureAwait(false);
await _raceChannel.SendErrorAsync(GetText("animal_race_already_started")).ConfigureAwait(false);
return;
}
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 {NadekoBot.BotConfig.CurrencyPluralName}.").ConfigureAwait(false); } catch { }
await _raceChannel.SendErrorAsync(GetText("not_enough", CurrencySign)).ConfigureAwait(false);
return;
}
participants.Add(p);
await raceChannel.SendConfirmAsync("Animal Race", $"{u.Mention} **joined as a {p.Animal}" + (amount > 0 ? $" and bet {amount} {CurrencySign}!**" : "**"))
.ConfigureAwait(false);
_participants.Add(p);
string confStr;
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 IGuildUser User { get; set; }
public string Animal { get; set; }
public int AmountBet { get; set; }
public IGuildUser User { get; }
public string Animal { get; }
public int AmountBet { get; }
public float Coeff { 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)
{
this.User = u;
this.Animal = a;
this.AmountBet = amount;
User = u;
Animal = a;
AmountBet = amount;
}
public override int GetHashCode() => User.GetHashCode();
@ -288,23 +314,13 @@ namespace NadekoBot.Modules.Gambling
var str = new string('‣', Total) + Animal;
if (Place == 0)
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

@ -43,7 +43,7 @@ namespace NadekoBot.Modules.Gambling
switch (e)
{
case CurrencyEvent.FlowerReaction:
await FlowerReactionEvent(Context).ConfigureAwait(false);
await FlowerReactionEvent(Context, arg).ConfigureAwait(false);
break;
case CurrencyEvent.SneakyGameStatus:
await SneakyGameStatusEvent(Context, arg).ConfigureAwait(false);
@ -115,23 +115,26 @@ namespace NadekoBot.Modules.Gambling
return Task.Delay(0);
}
public async Task FlowerReactionEvent(CommandContext context)
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(100.ToString()) + CurrencySign);
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);
await new FlowerReactionEvent().Start(msg, context, amount);
}
}
}
public abstract class CurrencyEvent
{
public abstract Task Start(IUserMessage msg, CommandContext channel);
public abstract Task Start(IUserMessage msg, CommandContext channel, int amount);
}
public class FlowerReactionEvent : CurrencyEvent
@ -172,7 +175,7 @@ namespace NadekoBot.Modules.Gambling
return Task.CompletedTask;
}
public override async Task Start(IUserMessage umsg, CommandContext context)
public override async Task Start(IUserMessage umsg, CommandContext context, int amount)
{
msg = umsg;
NadekoBot.Client.MessageDeleted += MessageDeletedEventHandler;
@ -193,7 +196,7 @@ namespace NadekoBot.Modules.Gambling
{
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", 100, false)
await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", amount, false)
.ConfigureAwait(false);
}
}

View File

@ -1,6 +1,5 @@
using Discord;
using Discord.Commands;
using ImageSharp;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
@ -22,7 +21,7 @@ namespace NadekoBot.Modules.Gambling
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 readonly char[] fateRolls = new[] { '-', ' ', '+' };
private readonly char[] _fateRolls = { '-', ' ', '+' };
[NadekoCommand, Usage, Description, Aliases]
public async Task Roll()
@ -35,12 +34,14 @@ namespace NadekoBot.Modules.Gambling
var imageStream = await Task.Run(() =>
{
var ms = new MemoryStream();
new[] { GetDice(num1), GetDice(num2) }.Merge().SaveAsPng(ms);
new[] { GetDice(num1), GetDice(num2) }.Merge().Save(ms);
ms.Position = 0;
return ms;
}).ConfigureAwait(false);
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
@ -59,7 +60,7 @@ namespace NadekoBot.Modules.Gambling
[NadekoCommand, Usage, Description, Aliases]
[Priority(0)]
public async Task Rolluo(int num)
public async Task Rolluo(int num = 1)
{
await InternalRoll(num, false).ConfigureAwait(false);
}
@ -82,7 +83,7 @@ namespace NadekoBot.Modules.Gambling
{
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;
}
@ -118,9 +119,14 @@ namespace NadekoBot.Modules.Gambling
var bitmap = dice.Merge();
var ms = new MemoryStream();
bitmap.SaveAsPng(ms);
bitmap.Save(ms);
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)
@ -138,9 +144,9 @@ namespace NadekoBot.Modules.Gambling
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"))
.WithValue(string.Join(" ", rolls.Select(c => Format.Code($"[{c}]")))));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -164,7 +170,7 @@ namespace NadekoBot.Modules.Gambling
}
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"))
.WithValue(string.Join(" ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => Format.Code(x.ToString())))))
.AddField(efb => efb.WithName(Format.Bold("Sum"))
@ -177,30 +183,26 @@ namespace NadekoBot.Modules.Gambling
[NadekoCommand, Usage, Description, Aliases]
public async Task NRoll([Remainder] string range)
{
try
int rolled;
if (range.Contains("-"))
{
int rolled;
if (range.Contains("-"))
var arr = range.Split('-')
.Take(2)
.Select(int.Parse)
.ToArray();
if (arr[0] > arr[1])
{
var arr = range.Split('-')
.Take(2)
.Select(int.Parse)
.ToArray();
if (arr[0] > arr[1])
throw new ArgumentException("Second argument must be larger than the first one.");
rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
}
else
{
rolled = new NadekoRandom().Next(0, int.Parse(range) + 1);
await ReplyErrorLocalized("second_larger_than_first").ConfigureAwait(false);
return;
}
rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
}
else
{
rolled = new NadekoRandom().Next(0, int.Parse(range) + 1);
}
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} rolled **{rolled}**.").ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync($":anger: {ex.Message}").ConfigureAwait(false);
}
await ReplyConfirmLocalized("dice_rolled", Format.Bold(rolled.ToString())).ConfigureAwait(false);
}
private Image GetDice(int num)

View File

@ -49,7 +49,7 @@ namespace NadekoBot.Modules.Gambling
images.Add(new Image(stream));
}
MemoryStream bitmapStream = new MemoryStream();
images.Merge().SaveAsPng(bitmapStream);
images.Merge().Save(bitmapStream);
bitmapStream.Position = 0;
var toSend = $"{Context.User.Mention}";
if (cardObjects.Count == 5)

View File

@ -52,17 +52,22 @@ namespace NadekoBot.Modules.Gambling
return;
}
var imgs = new Image[count];
using (var heads = _images.Heads.ToStream())
using(var tails = _images.Tails.ToStream())
for (var i = 0; i < count; i++)
{
for (var i = 0; i < count; i++)
using (var heads = _images.Heads.ToStream())
using (var tails = _images.Tails.ToStream())
{
imgs[i] = rng.Next(0, 10) < 5 ?
new Image(heads) :
new Image(tails);
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);
}
[NadekoCommand, Usage, Description, Aliases]
@ -105,7 +110,7 @@ namespace NadekoBot.Modules.Gambling
{
var toWin = (int)Math.Round(amount * NadekoBot.BotConfig.BetflipMultiplier);
str = Context.User.Mention + " " + GetText("flip_guess", toWin + CurrencySign);
await CurrencyHandler.AddCurrencyAsync(Context.User, GetText("betflip_gamble"), toWin, false).ConfigureAwait(false);
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betflip Gamble", toWin, false).ConfigureAwait(false);
}
else
{

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

@ -1,5 +1,6 @@
using Discord;
using Discord.Commands;
using ImageSharp;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
@ -143,11 +144,11 @@ namespace NadekoBot.Modules.Gambling
await ReplyErrorLocalized("min_bet_limit", 1 + CurrencySign).ConfigureAwait(false);
return;
}
if (amount > 999)
const int maxAmount = 9999;
if (amount > maxAmount)
{
GetText("slot_maxbet", 999 + CurrencySign);
await ReplyErrorLocalized("max_bet_limit", 999 + CurrencySign).ConfigureAwait(false);
GetText("slot_maxbet", maxAmount + CurrencySign);
await ReplyErrorLocalized("max_bet_limit", maxAmount + CurrencySign).ConfigureAwait(false);
return;
}
@ -163,83 +164,43 @@ namespace NadekoBot.Modules.Gambling
var result = SlotMachine.Pull();
int[] numbers = result.Numbers;
using (var bgPixels = bgImage.Lock())
for (int i = 0; i < 3; i++)
{
for (int i = 0; i < 3; i++)
using (var file = _images.SlotEmojis[numbers[i]].ToStream())
using (var randomImage = new ImageSharp.Image(file))
{
using (var file = _images.SlotEmojis[numbers[i]].ToStream())
{
var randomImage = new ImageSharp.Image(file);
using (var toAdd = randomImage.Lock())
{
for (int j = 0; j < toAdd.Width; j++)
{
for (int k = 0; k < toAdd.Height; k++)
{
var x = 95 + 142 * i + j;
int y = 330 + k;
var toSet = toAdd[j, k];
if (toSet.A < _alphaCutOut)
continue;
bgPixels[x, y] = toAdd[j, k];
}
}
}
}
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())
{
var img = new ImageSharp.Image(fs);
using (var pixels = img.Lock())
{
for (int i = 0; i < pixels.Width; i++)
{
for (int j = 0; j < pixels.Height; j++)
{
if (pixels[i, j].A < _alphaCutOut)
continue;
var x = 230 - n * 16 + i;
bgPixels[x, 462 + j] = pixels[i, j];
}
}
}
}
n++;
} while ((printWon /= 10) != 0);
var printAmount = amount;
n = 0;
do
{
var digit = printAmount % 10;
using (var fs = _images.SlotNumbers[digit].ToStream())
{
var img = new ImageSharp.Image(fs);
using (var pixels = img.Lock())
{
for (int i = 0; i < pixels.Width; i++)
{
for (int j = 0; j < pixels.Height; j++)
{
if (pixels[i, j].A < _alphaCutOut)
continue;
var x = 395 - n * 16 + i;
bgPixels[x, 462 + j] = pixels[i, j];
}
}
}
}
n++;
} while ((printAmount /= 10) != 0);
}
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)
{
@ -262,7 +223,7 @@ namespace NadekoBot.Modules.Gambling
{
var _ = Task.Run(async () =>
{
await Task.Delay(2000);
await Task.Delay(1500);
_runningUsers.Remove(Context.User.Id);
});
}

View File

@ -3,13 +3,11 @@ using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling
@ -49,8 +47,8 @@ namespace NadekoBot.Modules.Gambling
[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>();
private static ConcurrentDictionary<ulong, DateTime> divorceCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
private static ConcurrentDictionary<ulong, DateTime> affinityCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
enum WaifuClaimResult
{
@ -65,20 +63,19 @@ namespace NadekoBot.Modules.Gambling
{
if (amount < 50)
{
await Context.Channel.SendErrorAsync($"{Context.User.Mention} No waifu is that cheap. You must pay at least 50{NadekoBot.BotConfig.CurrencySign} to get a waifu, even if their actual value is lower.").ConfigureAwait(false);
await ReplyErrorLocalized("waifu_isnt_cheap", 50 + CurrencySign).ConfigureAwait(false);
return;
}
if (target.Id == Context.User.Id)
{
await Context.Channel.SendErrorAsync(Context.User.Mention + " You can't claim yourself.").ConfigureAwait(false);
await ReplyErrorLocalized("waifu_not_yourself").ConfigureAwait(false);
return;
}
WaifuClaimResult result = WaifuClaimResult.NotEnoughFunds;
int? oldPrice = null;
WaifuClaimResult result;
WaifuInfo w;
var isAffinity = false;
bool isAffinity;
using (var uow = DbHandler.UnitOfWork())
{
w = uow.Waifus.ByWaifuUserId(target.Id);
@ -120,7 +117,6 @@ namespace NadekoBot.Modules.Gambling
{
var oldClaimer = w.Claimer;
w.Claimer = uow.DiscordUsers.GetOrCreate(Context.User);
oldPrice = w.Price;
w.Price = amount + (amount / 4);
result = WaifuClaimResult.Success;
@ -143,7 +139,6 @@ namespace NadekoBot.Modules.Gambling
{
var oldClaimer = w.Claimer;
w.Claimer = uow.DiscordUsers.GetOrCreate(Context.User);
oldPrice = w.Price;
w.Price = amount;
result = WaifuClaimResult.Success;
@ -165,22 +160,20 @@ namespace NadekoBot.Modules.Gambling
if (result == WaifuClaimResult.InsufficientAmount)
{
await Context.Channel.SendErrorAsync($"{Context.User.Mention} You must pay {Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f))} or more to claim that waifu!").ConfigureAwait(false);
await ReplyErrorLocalized("waifu_not_enough", Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f))).ConfigureAwait(false);
return;
}
else if (result == WaifuClaimResult.NotEnoughFunds)
if (result == WaifuClaimResult.NotEnoughFunds)
{
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} you don't have {amount}{NadekoBot.BotConfig.CurrencySign}!")
.ConfigureAwait(false);
}
else
{
var msg = $"{Context.User.Mention} claimed {target.Mention} as their waifu for {amount}{NadekoBot.BotConfig.CurrencySign}!";
if (w.Affinity?.UserId == Context.User.Id)
msg += $"\n🎉 Their love is fulfilled! 🎉\n**{target}'s** new value is {w.Price}{NadekoBot.BotConfig.CurrencySign}!";
await Context.Channel.SendConfirmAsync(msg)
.ConfigureAwait(false);
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
@ -192,7 +185,7 @@ namespace NadekoBot.Modules.Gambling
}
private static readonly TimeSpan DivorceLimit = TimeSpan.FromHours(6);
private static readonly TimeSpan _divorceLimit = TimeSpan.FromHours(6);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Divorce([Remainder]IUser target)
@ -200,19 +193,19 @@ namespace NadekoBot.Modules.Gambling
if (target.Id == Context.User.Id)
return;
var result = DivorceResult.NotYourWife;
TimeSpan difference = TimeSpan.Zero;
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 == null || w.Claimer == null || w.Claimer.UserId != Context.User.Id)
if (w?.Claimer == null || w.Claimer.UserId != Context.User.Id)
result = DivorceResult.NotYourWife;
else if (_divorceCooldowns.AddOrUpdate(Context.User.Id,
else if (divorceCooldowns.AddOrUpdate(Context.User.Id,
now,
(key, old) => ((difference = now.Subtract(old)) > DivorceLimit) ? now : old) != now)
(key, old) => ((difference = now.Subtract(old)) > _divorceLimit) ? now : old) != now)
{
result = DivorceResult.Cooldown;
}
@ -249,37 +242,39 @@ namespace NadekoBot.Modules.Gambling
if (result == DivorceResult.SucessWithPenalty)
{
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} You have divorced a waifu who likes you. You heartless monster.\n{w.Waifu} received {amount}{NadekoBot.BotConfig.CurrencySign} as a compensation.").ConfigureAwait(false);
await ReplyConfirmLocalized("waifu_divorced_like", Format.Bold(w.Waifu.ToString()), amount + CurrencySign).ConfigureAwait(false);
}
else if (result == DivorceResult.Success)
{
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} You have divorced a waifu who doesn't like you. You received {amount}{NadekoBot.BotConfig.CurrencySign} back.").ConfigureAwait(false);
await ReplyConfirmLocalized("waifu_divorced_notlike", amount + CurrencySign).ConfigureAwait(false);
}
else if (result == DivorceResult.NotYourWife)
{
await Context.Channel.SendErrorAsync($"{Context.User.Mention} That waifu is not yours.").ConfigureAwait(false);
await ReplyErrorLocalized("waifu_not_yours").ConfigureAwait(false);
}
else
{
var remaining = DivorceLimit.Subtract(difference);
await Context.Channel.SendErrorAsync($"{Context.User.Mention} You divorced recently. You must wait **{remaining.Hours} hours and {remaining.Minutes} minutes** to divorce again.").ConfigureAwait(false);
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);
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 Context.Channel.SendErrorAsync($"{Context.User.Mention} you can't set affinity to yourself, you egomaniac.").ConfigureAwait(false);
await ReplyErrorLocalized("waifu_egomaniac").ConfigureAwait(false);
return;
}
DiscordUser oldAff = null;
var sucess = false;
var cooldown = false;
TimeSpan difference = TimeSpan.Zero;
var difference = TimeSpan.Zero;
using (var uow = DbHandler.UnitOfWork())
{
var w = uow.Waifus.ByWaifuUserId(Context.User.Id);
@ -287,13 +282,11 @@ namespace NadekoBot.Modules.Gambling
var now = DateTime.UtcNow;
if (w?.Affinity?.UserId == u?.Id)
{
sucess = false;
}
else if (_affinityCooldowns.AddOrUpdate(Context.User.Id,
else if (affinityCooldowns.AddOrUpdate(Context.User.Id,
now,
(key, old) => ((difference = now.Subtract(old)) > AffinityLimit) ? now : old) != now)
(key, old) => ((difference = now.Subtract(old)) > _affinityLimit) ? now : old) != now)
{
sucess = false;
cooldown = true;
}
else if (w == null)
@ -338,19 +331,29 @@ namespace NadekoBot.Modules.Gambling
{
if (cooldown)
{
var remaining = AffinityLimit.Subtract(difference);
await Context.Channel.SendErrorAsync($"{Context.User.Mention} You must wait **{remaining.Hours} hours and {remaining.Minutes} minutes** in order to change your affinity again.").ConfigureAwait(false);
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 Context.Channel.SendErrorAsync($"{Context.User.Mention} your affinity is already set to that waifu or you're trying to remove your affinity while not having one.").ConfigureAwait(false);
{
await ReplyErrorLocalized("waifu_affinity_already").ConfigureAwait(false);
}
return;
}
if (u == null)
await Context.Channel.SendConfirmAsync("Affinity Reset", $"{Context.User.Mention} Your affinity is reset. You no longer have a person you like.").ConfigureAwait(false);
{
await ReplyConfirmLocalized("waifu_affinity_reset").ConfigureAwait(false);
}
else if (oldAff == null)
await Context.Channel.SendConfirmAsync("Affinity Set", $"{Context.User.Mention} wants to be {u.Mention}'s waifu. Aww <3").ConfigureAwait(false);
{
await ReplyConfirmLocalized("waifu_affinity_set", Format.Bold(u.ToString())).ConfigureAwait(false);
}
else
await Context.Channel.SendConfirmAsync("Affinity Changed", $"{Context.User.Mention} changed their affinity from {oldAff} to {u.Mention}.\n\n*This is morally questionable.*🤔").ConfigureAwait(false);
{
await ReplyConfirmLocalized("waifu_affinity_changed", Format.Bold(oldAff.ToString()), Format.Bold(u.ToString())).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -365,19 +368,20 @@ namespace NadekoBot.Modules.Gambling
if (waifus.Count == 0)
{
await Context.Channel.SendConfirmAsync("No waifus have been claimed yet.").ConfigureAwait(false);
await ReplyConfirmLocalized("waifus_none").ConfigureAwait(false);
return;
}
var embed = new EmbedBuilder()
.WithTitle("Top Waifus")
.WithTitle(GetText("waifus_top_waifus"))
.WithOkColor();
for (int i = 0; i < waifus.Count; i++)
for (var i = 0; i < waifus.Count; i++)
{
var w = waifus[i];
embed.AddField(efb => efb.WithName("#" + (i + 1) + " - " + w.Price + NadekoBot.BotConfig.CurrencySign).WithValue(w.ToString()).WithIsInline(false));
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);
@ -391,7 +395,7 @@ namespace NadekoBot.Modules.Gambling
target = Context.User;
WaifuInfo w;
IList<WaifuInfo> claims;
int divorces = 0;
int divorces;
using (var uow = DbHandler.UnitOfWork())
{
w = uow.Waifus.ByWaifuUserId(target.Id);
@ -419,15 +423,18 @@ namespace NadekoBot.Modules.Gambling
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("Price").WithValue(w.Price.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("Claimed by").WithValue(w.Claimer?.ToString() ?? "No one").WithIsInline(true))
.AddField(efb => efb.WithName("Likes").WithValue(w.Affinity?.ToString() ?? "Nobody").WithIsInline(true))
.AddField(efb => efb.WithName("Changes Of Heart").WithValue($"{affInfo.Count} - \"the {affInfo.Title}\"").WithIsInline(true))
.AddField(efb => efb.WithName("Divorces").WithValue(divorces.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName($"Waifus ({claims.Count})").WithValue(claims.Count == 0 ? "Nobody" : string.Join("\n", claims.Select(x => x.Waifu))).WithIsInline(true));
.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);
}
@ -447,13 +454,13 @@ namespace NadekoBot.Modules.Gambling
private static WaifuProfileTitle GetClaimTitle(ulong userId)
{
int count = 0;
int count;
using (var uow = DbHandler.UnitOfWork())
{
count = uow.Waifus.ByClaimerUserId(userId).Count;
}
ClaimTitles title = ClaimTitles.Lonely;
ClaimTitles title;
if (count == 0)
title = ClaimTitles.Lonely;
else if (count == 1)
@ -484,13 +491,16 @@ namespace NadekoBot.Modules.Gambling
private static WaifuProfileTitle GetAffinityTitle(ulong userId)
{
int count = 0;
int count;
using (var uow = DbHandler.UnitOfWork())
{
count = uow._context.WaifuUpdates.Count(w => w.User.UserId == userId && w.UpdateType == WaifuUpdateType.AffinityChanged);
count = uow._context.WaifuUpdates
.Where(w => w.User.UserId == userId && w.UpdateType == WaifuUpdateType.AffinityChanged && w.New != null)
.GroupBy(x => x.New)
.Count();
}
AffinityTitles title = AffinityTitles.Pure;
AffinityTitles title;
if (count < 1)
title = AffinityTitles.Pure;
else if (count < 2)

View File

@ -12,7 +12,7 @@ using System.Collections.Generic;
namespace NadekoBot.Modules.Gambling
{
[NadekoModule("Gambling", "$")]
public partial class Gambling : NadekoModule
public partial class Gambling : NadekoTopLevelModule
{
public static string CurrencyName { get; set; }
public static string CurrencyPluralName { get; set; }
@ -49,8 +49,10 @@ namespace NadekoBot.Modules.Gambling
[Priority(0)]
public async Task Cash([Remainder] IUser user = null)
{
user = user ?? Context.User;
await ReplyConfirmLocalized("has", Format.Bold(user.ToString()), $"{GetCurrency(user.Id)} {CurrencySign}").ConfigureAwait(false);
if(user == null)
await ConfirmLocalized("has", Format.Bold(Context.User.ToString()), $"{GetCurrency(Context.User.Id)} {CurrencySign}").ConfigureAwait(false);
else
await ReplyConfirmLocalized("has", Format.Bold(user.ToString()), $"{GetCurrency(user.Id)} {CurrencySign}").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -238,25 +240,33 @@ namespace NadekoBot.Modules.Gambling
(int) (amount * NadekoBot.BotConfig.Betroll100Multiplier), false).ConfigureAwait(false);
}
}
Console.WriteLine("started sending");
await Context.Channel.SendConfirmAsync(str).ConfigureAwait(false);
Console.WriteLine("done sending");
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Leaderboard()
public async Task Leaderboard(int page = 1)
{
if (page < 1)
return;
List<Currency> richest;
using (var uow = DbHandler.UnitOfWork())
{
richest = uow.Currency.GetTopRichest(9).ToList();
richest = uow.Currency.GetTopRichest(9, 9 * (page - 1)).ToList();
}
if (!richest.Any())
return;
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle(NadekoBot.BotConfig.CurrencySign + " " + GetText("leaderboard"));
.WithTitle(NadekoBot.BotConfig.CurrencySign +
" " + GetText("leaderboard"))
.WithFooter(efb => efb.WithText(GetText("page", page)));
if (!richest.Any())
{
embed.WithDescription(GetText("no_users_found"));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
return;
}
for (var i = 0; i < richest.Count; i++)
{
@ -267,7 +277,7 @@ namespace NadekoBot.Modules.Gambling
: usr.Username?.TrimTo(20, true);
var j = i;
embed.AddField(efb => efb.WithName("#" + (j + 1) + " " + usrStr)
embed.AddField(efb => efb.WithName("#" + (9 * (page - 1) + j + 1) + " " + usrStr)
.WithValue(x.Amount.ToString() + " " + NadekoBot.BotConfig.CurrencySign)
.WithIsInline(true));
}

View File

@ -19,7 +19,7 @@ namespace NadekoBot.Modules.Games
public partial class Games
{
[Group]
public class Acropobia : ModuleBase
public class Acropobia : NadekoSubmodule
{
//channelId, game
public static ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>();
@ -28,6 +28,8 @@ namespace NadekoBot.Modules.Games
[RequireContext(ContextType.Guild)]
public async Task Acro(int time = 60)
{
if (time < 10 || time > 120)
return;
var channel = (ITextChannel)Context.Channel;
var game = new AcrophobiaGame(channel, time);
@ -45,7 +47,7 @@ namespace NadekoBot.Modules.Games
}
else
{
await channel.SendErrorAsync("Acrophobia game is already running in this channel.").ConfigureAwait(false);
await ReplyErrorLocalized("acro_running").ConfigureAwait(false);
}
}
}
@ -59,44 +61,44 @@ namespace NadekoBot.Modules.Games
public class AcrophobiaGame
{
private readonly ITextChannel channel;
private readonly int time;
private readonly NadekoRandom rng;
private readonly ImmutableArray<char> startingLetters;
private readonly CancellationTokenSource source;
private readonly ITextChannel _channel;
private readonly int _time;
private readonly NadekoRandom _rng;
private readonly ImmutableArray<char> _startingLetters;
private readonly CancellationTokenSource _source;
private AcroPhase phase { get; set; } = AcroPhase.Submitting;
private readonly ConcurrentDictionary<string, IGuildUser> submissions = new ConcurrentDictionary<string, IGuildUser>();
public IReadOnlyDictionary<string, IGuildUser> Submissions => submissions;
private readonly ConcurrentDictionary<string, IGuildUser> _submissions = new ConcurrentDictionary<string, IGuildUser>();
public IReadOnlyDictionary<string, IGuildUser> Submissions => _submissions;
private readonly ConcurrentHashSet<ulong> usersWhoSubmitted = new ConcurrentHashSet<ulong>();
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
private readonly ConcurrentDictionary<string, int> votes = new ConcurrentDictionary<string, int>();
private readonly ConcurrentDictionary<string, int> _votes = new ConcurrentDictionary<string, int>();
private readonly Logger _log;
public AcrophobiaGame(ITextChannel channel, int time)
{
this._log = LogManager.GetCurrentClassLogger();
_log = LogManager.GetCurrentClassLogger();
this.channel = channel;
this.time = time;
this.source = new CancellationTokenSource();
_channel = channel;
_time = time;
_source = new CancellationTokenSource();
this.rng = new NadekoRandom();
var wordCount = rng.Next(3, 6);
_rng = new NadekoRandom();
var wordCount = _rng.Next(3, 6);
var lettersArr = new char[wordCount];
for (int i = 0; i < wordCount; i++)
{
var randChar = (char)rng.Next(65, 91);
lettersArr[i] = randChar == 'X' ? (char)rng.Next(65, 88) : randChar;
var randChar = (char)_rng.Next(65, 91);
lettersArr[i] = randChar == 'X' ? (char)_rng.Next(65, 88) : randChar;
}
startingLetters = lettersArr.ToImmutableArray();
_startingLetters = lettersArr.ToImmutableArray();
}
private EmbedBuilder GetEmbed()
@ -104,19 +106,19 @@ namespace NadekoBot.Modules.Games
var i = 0;
return phase == AcroPhase.Submitting
? new EmbedBuilder().WithOkColor()
.WithTitle("Acrophobia")
.WithDescription($"Game started. Create a sentence with the following acronym: **{string.Join(".", startingLetters)}.**\n")
.WithFooter(efb => efb.WithText("You have " + this.time + " seconds to make a submission."))
? new EmbedBuilder().WithOkColor()
.WithTitle(GetText("acrophobia"))
.WithDescription(GetText("acro_started", Format.Bold(string.Join(".", _startingLetters))))
.WithFooter(efb => efb.WithText(GetText("acro_started_footer", _time)))
: new EmbedBuilder()
.WithOkColor()
.WithTitle("Acrophobia - Submissions Closed")
.WithDescription($@"Acronym was **{string.Join(".", startingLetters)}.**
--
{this.submissions.Aggregate("", (agg, cur) => agg + $"`{++i}.` **{cur.Key.ToLowerInvariant().ToTitleCase()}**\n")}
--")
.WithFooter(efb => efb.WithText("Vote by typing a number of the submission"));
: new EmbedBuilder()
.WithOkColor()
.WithTitle(GetText("acrophobia") + " - " + GetText("submissions_closed"))
.WithDescription(GetText("acro_nym_was", Format.Bold(string.Join(".", _startingLetters)) + "\n" +
$@"--
{_submissions.Aggregate("",(agg, cur) => agg + $"`{++i}.` **{cur.Key.ToLowerInvariant().ToTitleCase()}**\n")}
--"))
.WithFooter(efb => efb.WithText(GetText("acro_vote")));
}
public async Task Run()
@ -125,10 +127,10 @@ namespace NadekoBot.Modules.Games
var embed = GetEmbed();
//SUBMISSIONS PHASE
await channel.EmbedAsync(embed).ConfigureAwait(false);
await _channel.EmbedAsync(embed).ConfigureAwait(false);
try
{
await Task.Delay(time * 1000, source.Token).ConfigureAwait(false);
await Task.Delay(_time * 1000, _source.Token).ConfigureAwait(false);
phase = AcroPhase.Idle;
}
catch (OperationCanceledException)
@ -137,30 +139,32 @@ namespace NadekoBot.Modules.Games
}
//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;
}
else if (submissions.Count == 1)
if (_submissions.Count == 1)
{
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription($"{submissions.First().Value.Mention} is the winner for being the only user who made a submission!")
.WithFooter(efb => efb.WithText(submissions.First().Key.ToLowerInvariant().ToTitleCase())))
.ConfigureAwait(false);
await _channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription(
GetText("acro_winner_only",
Format.Bold(_submissions.First().Value.ToString())))
.WithFooter(efb => efb.WithText(_submissions.First().Key.ToLowerInvariant().ToTitleCase())))
.ConfigureAwait(false);
return;
}
var submissionClosedEmbed = GetEmbed();
await channel.EmbedAsync(submissionClosedEmbed).ConfigureAwait(false);
await _channel.EmbedAsync(submissionClosedEmbed).ConfigureAwait(false);
//VOTING PHASE
this.phase = AcroPhase.Voting;
phase = AcroPhase.Voting;
try
{
//30 secondds for voting
await Task.Delay(30000, source.Token).ConfigureAwait(false);
this.phase = AcroPhase.Idle;
await Task.Delay(30000, _source.Token).ConfigureAwait(false);
phase = AcroPhase.Idle;
}
catch (OperationCanceledException)
{
@ -174,10 +178,10 @@ namespace NadekoBot.Modules.Games
try
{
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;
++spamCount;
++_spamCount;
var guildUser = (IGuildUser)msg.Author;
@ -185,37 +189,39 @@ namespace NadekoBot.Modules.Games
if (phase == AcroPhase.Submitting)
{
if (spamCount > 10)
if (_spamCount > 10)
{
spamCount = 0;
try { await channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); }
_spamCount = 0;
try { await _channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); }
catch { }
}
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;
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
return;
}
if (!usersWhoSubmitted.Add(guildUser.Id))
if (!_usersWhoSubmitted.Add(guildUser.Id))
return;
//try adding it to the list of answers
if (!submissions.TryAdd(input, guildUser))
if (!_submissions.TryAdd(input, guildUser))
{
usersWhoSubmitted.TryRemove(guildUser.Id);
_usersWhoSubmitted.TryRemove(guildUser.Id);
return;
}
// 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
{
await msg.DeleteAsync();
@ -227,14 +233,13 @@ namespace NadekoBot.Modules.Games
}
else if (phase == AcroPhase.Voting)
{
if (spamCount > 10)
if (_spamCount > 10)
{
spamCount = 0;
try { await channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); }
_spamCount = 0;
try { await _channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); }
catch { }
}
IGuildUser usr;
//if (submissions.TryGetValue(input, out usr) && usr.Id != guildUser.Id)
//{
// if (!usersWhoVoted.Add(guildUser.Id))
@ -246,17 +251,17 @@ namespace NadekoBot.Modules.Games
//}
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();
usr = kvp.Value;
var kvp = _submissions.Skip(num - 1).First();
var usr = kvp.Value;
//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;
votes.AddOrUpdate(kvp.Key, 1, (key, old) => ++old);
await channel.SendConfirmAsync("Acrophobia", $"{guildUser.Mention} cast their vote!").ConfigureAwait(false);
_votes.AddOrUpdate(kvp.Key, 1, (key, old) => ++old);
await _channel.SendConfirmAsync(GetText("acrophobia"),
GetText("acro_vote_cast", Format.Bold(guildUser.ToString()))).ConfigureAwait(false);
await msg.DeleteAsync().ConfigureAwait(false);
return;
}
}
@ -269,27 +274,34 @@ namespace NadekoBot.Modules.Games
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;
}
var table = votes.OrderByDescending(v => v.Value);
var table = _votes.OrderByDescending(v => v.Value);
var winner = table.First();
var embed = new EmbedBuilder().WithOkColor()
.WithTitle("Acrophobia")
.WithDescription($"Winner is {submissions[winner.Key].Mention} with {winner.Value} points.\n")
.WithTitle(GetText("acrophobia"))
.WithDescription(GetText("acro_winner", Format.Bold(_submissions[winner.Key].ToString()),
Format.Bold(winner.Value.ToString())))
.WithFooter(efb => efb.WithText(winner.Key.ToLowerInvariant().ToTitleCase()));
await channel.EmbedAsync(embed).ConfigureAwait(false);
await _channel.EmbedAsync(embed).ConfigureAwait(false);
}
public void EnsureStopped()
{
NadekoBot.Client.MessageReceived -= PotentialAcro;
if (!source.IsCancellationRequested)
source.Cancel();
if (!_source.IsCancellationRequested)
_source.Cancel();
}
private string GetText(string key, params object[] replacements)
=> GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(_channel.Guild),
typeof(Games).Name.ToLowerInvariant(),
replacements);
}
}
}

View File

@ -1,54 +1,55 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NLog;
using Services.CleverBotApi;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
[Group]
public class CleverBotCommands : ModuleBase
public class CleverBotCommands : NadekoSubmodule
{
private static Logger _log { get; }
private new static Logger _log { get; }
public static ConcurrentDictionary<ulong, Lazy<ChatterBotSession>> CleverbotGuilds { get; } = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>();
public static ConcurrentDictionary<ulong, Lazy<ChatterBotSession>> CleverbotGuilds { get; }
static CleverBotCommands()
{
_log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
var bot = ChatterBotFactory.Create(ChatterBotType.CLEVERBOT);
CleverbotGuilds = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>(
NadekoBot.AllGuildConfigs
.Where(gc => gc.CleverbotEnabled)
.ToDictionary(gc => gc.GuildId, gc => new Lazy<ChatterBotSession>(() => bot.CreateSession(), true)));
.ToDictionary(gc => gc.GuildId, gc => new Lazy<ChatterBotSession>(() => new ChatterBotSession(gc.GuildId), true)));
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
}
public static async Task<bool> TryAsk(SocketUserMessage msg)
public static string PrepareMessage(IUserMessage msg, out ChatterBotSession cleverbot)
{
var channel = msg.Channel as ITextChannel;
cleverbot = null;
if (channel == null)
return false;
return null;
Lazy<ChatterBotSession> cleverbot;
if (!CleverbotGuilds.TryGetValue(channel.Guild.Id, out cleverbot))
return false;
Lazy<ChatterBotSession> lazyCleverbot;
if (!CleverbotGuilds.TryGetValue(channel.Guild.Id, out lazyCleverbot))
return null;
cleverbot = lazyCleverbot.Value;
var nadekoId = NadekoBot.Client.CurrentUser.Id;
var normalMention = $"<@{nadekoId}> ";
@ -64,19 +65,24 @@ namespace NadekoBot.Modules.Games
}
else
{
return false;
return null;
}
await msg.Channel.TriggerTypingAsync().ConfigureAwait(false);
return message;
}
var response = await cleverbot.Value.Think(message).ConfigureAwait(false);
public static async Task<bool> TryAsk(ChatterBotSession cleverbot, ITextChannel channel, string message)
{
await channel.TriggerTypingAsync().ConfigureAwait(false);
var response = await cleverbot.Think(message).ConfigureAwait(false);
try
{
await msg.Channel.SendConfirmAsync(response.SanitizeMentions()).ConfigureAwait(false);
await channel.SendConfirmAsync(response.SanitizeMentions()).ConfigureAwait(false);
}
catch
{
await msg.Channel.SendConfirmAsync(response.SanitizeMentions()).ConfigureAwait(false); // try twice :\
await channel.SendConfirmAsync(response.SanitizeMentions()).ConfigureAwait(false); // try twice :\
}
return true;
}
@ -96,13 +102,11 @@ namespace NadekoBot.Modules.Games
uow.GuildConfigs.SetCleverbotEnabled(Context.Guild.Id, false);
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} Disabled cleverbot on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("cleverbot_disabled").ConfigureAwait(false);
return;
}
var cleverbot = ChatterBotFactory.Create(ChatterBotType.CLEVERBOT);
CleverbotGuilds.TryAdd(channel.Guild.Id, new Lazy<ChatterBotSession>(() => cleverbot.CreateSession(), true));
CleverbotGuilds.TryAdd(channel.Guild.Id, new Lazy<ChatterBotSession>(() => new ChatterBotSession(Context.Guild.Id), true));
using (var uow = DbHandler.UnitOfWork())
{
@ -110,8 +114,45 @@ namespace NadekoBot.Modules.Games
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} Enabled cleverbot on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("cleverbot_enabled").ConfigureAwait(false);
}
}
public class ChatterBotSession
{
private static NadekoRandom rng { get; } = new NadekoRandom();
public string ChatterbotId { get; }
public string ChannelId { get; }
private int _botId = 6;
public ChatterBotSession(ulong channelId)
{
ChannelId = channelId.ToString().ToBase64();
ChatterbotId = rng.Next(0, 1000000).ToString().ToBase64();
}
private string apiEndpoint => "http://api.program-o.com/v2/chatbot/" +
$"?bot_id={_botId}&" +
"say={0}&" +
$"convo_id=nadekobot_{ChatterbotId}_{ChannelId}&" +
"format=json";
public async Task<string> Think(string message)
{
using (var http = new HttpClient())
{
var res = await http.GetStringAsync(string.Format(apiEndpoint, message)).ConfigureAwait(false);
var cbr = JsonConvert.DeserializeObject<ChatterBotResponse>(res);
//Console.WriteLine(cbr.Convo_id);
return cbr.BotSay.Replace("<br/>", "\n");
}
}
}
public class ChatterBotResponse
{
public string Convo_id { get; set; }
public string BotSay { get; set; }
}
}
}

View File

@ -9,12 +9,13 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Modules.Games.Commands.Hangman;
namespace NadekoBot.Modules.Games.Commands.Hangman
namespace NadekoBot.Modules.Games.Hangman
{
public class HangmanTermPool
{
const string termsPath = "data/hangman.json";
const string termsPath = "data/hangman2.json";
public static IReadOnlyDictionary<string, HangmanObject[]> data { get; }
static HangmanTermPool()
{
@ -70,7 +71,7 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
return $" {c}";
c = char.ToUpperInvariant(c);
return Guesses.Contains(c) ? $" {c}" : " _";
return Guesses.Contains(c) ? $" {c}" : " ";
})) + "`";
public bool GuessedAll => Guesses.IsSupersetOf(Term.Word.ToUpperInvariant()
@ -145,7 +146,7 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
MessagesSinceLastPost = 0;
++Errors;
if (Errors < MaxErrors)
await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author.Mention} Letter `{guess}` has already been used.\n" + ScrambledWord + "\n" + GetHangman(),
await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author} Letter `{guess}` has already been used.\n" + ScrambledWord + "\n" + GetHangman(),
footer: string.Join(" ", Guesses)).ConfigureAwait(false);
else
await End().ConfigureAwait(false);
@ -158,7 +159,7 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
{
if (GuessedAll)
{
try { await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author.Mention} guessed a letter `{guess}`!").ConfigureAwait(false); } catch { }
try { await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author} guessed a letter `{guess}`!").ConfigureAwait(false); } catch { }
await End().ConfigureAwait(false);
return;
@ -166,7 +167,7 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
MessagesSinceLastPost = 0;
try
{
await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author.Mention} guessed a letter `{guess}`!\n" + ScrambledWord + "\n" + GetHangman(),
await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author} guessed a letter `{guess}`!\n" + ScrambledWord + "\n" + GetHangman(),
footer: string.Join(" ", Guesses)).ConfigureAwait(false);
}
catch { }
@ -177,7 +178,7 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
MessagesSinceLastPost = 0;
++Errors;
if (Errors < MaxErrors)
await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author.Mention} Letter `{guess}` does not exist.\n" + ScrambledWord + "\n" + GetHangman(),
await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author} Letter `{guess}` does not exist.\n" + ScrambledWord + "\n" + GetHangman(),
footer: string.Join(" ", Guesses)).ConfigureAwait(false);
else
await End().ConfigureAwait(false);

View File

@ -1,36 +1,25 @@
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Commands.Hangman;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using NadekoBot.Modules.Games.Hangman;
using Discord;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
[Group]
public class HangmanCommands : ModuleBase
public class HangmanCommands : NadekoSubmodule
{
private static Logger _log { get; }
//channelId, game
public static ConcurrentDictionary<ulong, HangmanGame> HangmanGames { get; } = new ConcurrentDictionary<ulong, HangmanGame>();
private static string typesStr { get; } = "";
static HangmanCommands()
{
_log = LogManager.GetCurrentClassLogger();
typesStr =
string.Format("`List of \"{0}hangman\" term types:`\n", NadekoBot.ModulePrefixes[typeof(Games).Name]) + String.Join(", ", HangmanTermPool.data.Keys);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Hangmanlist()
{
await Context.Channel.SendConfirmAsync(typesStr);
await Context.Channel.SendConfirmAsync(Format.Code(GetText("hangman_types", Prefix)) + "\n" + string.Join(", ", HangmanTermPool.data.Keys));
}
[NadekoCommand, Usage, Description, Aliases]
@ -40,11 +29,11 @@ namespace NadekoBot.Modules.Games
if (!HangmanGames.TryAdd(Context.Channel.Id, hm))
{
await Context.Channel.SendErrorAsync("Hangman game already running on this channel.").ConfigureAwait(false);
await ReplyErrorLocalized("hangman_running").ConfigureAwait(false);
return;
}
hm.OnEnded += (g) =>
hm.OnEnded += g =>
{
HangmanGame throwaway;
HangmanGames.TryRemove(g.GameChannel.Id, out throwaway);
@ -55,14 +44,14 @@ namespace NadekoBot.Modules.Games
}
catch (Exception ex)
{
try { await Context.Channel.SendErrorAsync($"Starting errored: {ex.Message}").ConfigureAwait(false); } catch { }
try { await Context.Channel.SendErrorAsync(GetText("hangman_start_errored") + " " + ex.Message).ConfigureAwait(false); } catch { }
HangmanGame throwaway;
HangmanGames.TryRemove(Context.Channel.Id, out throwaway);
throwaway.Dispose();
return;
}
await Context.Channel.SendConfirmAsync("Hangman game started", hm.ScrambledWord + "\n" + hm.GetHangman());
await Context.Channel.SendConfirmAsync(GetText("hangman_game_started"), hm.ScrambledWord + "\n" + hm.GetHangman());
}
}
}

View File

@ -11,10 +11,7 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games
@ -31,7 +28,7 @@ namespace NadekoBot.Modules.Games
[Group]
public class PlantPickCommands : NadekoSubmodule
{
private static ConcurrentHashSet<ulong> generationChannels { get; } = new ConcurrentHashSet<ulong>();
private static ConcurrentHashSet<ulong> generationChannels { get; }
//channelid/message
private static ConcurrentDictionary<ulong, List<IUserMessage>> plantedFlowers { get; } = new ConcurrentDictionary<ulong, List<IUserMessage>>();
//channelId/last generation
@ -82,25 +79,19 @@ namespace NadekoBot.Modules.Games
if (dropAmount > 0)
{
var msgs = new IUserMessage[dropAmount];
string firstPart;
if (dropAmount == 1)
{
firstPart = $"A random { NadekoBot.BotConfig.CurrencyName } appeared!";
}
else
{
firstPart = $"{dropAmount} random { NadekoBot.BotConfig.CurrencyPluralName } appeared!";
}
var prefix = NadekoBot.ModulePrefixes[typeof(Games).Name];
var toSend = dropAmount == 1
? GetLocalText(channel, "curgen_sn", NadekoBot.BotConfig.CurrencySign)
+ GetLocalText(channel, "pick_sn", prefix)
: GetLocalText(channel, "curgen_pl", dropAmount, NadekoBot.BotConfig.CurrencySign)
+ GetLocalText(channel, "pick_pl", prefix);
var file = GetRandomCurrencyImage();
using (var fileStream = file.Value.ToStream())
{
var sent = await channel.SendFileAsync(
fileStream,
file.Key,
string.Format("❗ {0} Pick it up by typing `{1}pick`", firstPart,
NadekoBot.ModulePrefixes[typeof(Games).Name]))
.ConfigureAwait(false);
toSend).ConfigureAwait(false);
msgs[0] = sent;
}
@ -117,6 +108,12 @@ namespace NadekoBot.Modules.Games
return Task.CompletedTask;
}
public static string GetLocalText(ITextChannel channel, string key, params object[] replacements) =>
NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(channel.GuildId),
typeof(Games).Name.ToLowerInvariant(),
replacements);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Pick()
@ -135,7 +132,8 @@ namespace NadekoBot.Modules.Games
await Task.WhenAll(msgs.Where(m => m != null).Select(toDelete => toDelete.DeleteAsync())).ConfigureAwait(false);
await CurrencyHandler.AddCurrencyAsync((IGuildUser)Context.User, $"Picked {NadekoBot.BotConfig.CurrencyPluralName}", msgs.Count, false).ConfigureAwait(false);
var msg = await channel.SendConfirmAsync($"**{Context.User}** picked {msgs.Count}{NadekoBot.BotConfig.CurrencySign}!").ConfigureAwait(false);
var msg = await ReplyConfirmLocalized("picked", msgs.Count + NadekoBot.BotConfig.CurrencySign)
.ConfigureAwait(false);
msg.DeleteAfter(10);
}
@ -149,14 +147,24 @@ namespace NadekoBot.Modules.Games
var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Planted a {NadekoBot.BotConfig.CurrencyName}", amount, false).ConfigureAwait(false);
if (!removed)
{
await Context.Channel.SendErrorAsync($"You don't have enough {NadekoBot.BotConfig.CurrencyPluralName}.").ConfigureAwait(false);
await ReplyErrorLocalized("not_enough", NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
return;
}
var imgData = GetRandomCurrencyImage();
var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.BotConfig.CurrencyName[0]);
var msgToSend = $"Oh how Nice! **{Context.User.Username}** planted {(amount == 1 ? (vowelFirst ? "an" : "a") : amount.ToString())} {(amount > 1 ? NadekoBot.BotConfig.CurrencyPluralName : NadekoBot.BotConfig.CurrencyName)}. Pick it using {Prefix}pick";
//todo upload all currency images to transfer.sh and use that one as cdn
//and then
var msgToSend = GetText("planted",
Format.Bold(Context.User.ToString()),
amount + NadekoBot.BotConfig.CurrencySign,
Prefix);
if (amount > 1)
msgToSend += " " + GetText("pick_pl", Prefix);
else
msgToSend += " " + GetText("pick_sn", Prefix);
IUserMessage msg;
using (var toSend = imgData.Value.ToStream())
@ -173,7 +181,7 @@ namespace NadekoBot.Modules.Games
return old;
});
}
#if !GLOBAL_NADEKO
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
@ -203,13 +211,14 @@ namespace NadekoBot.Modules.Games
}
if (enabled)
{
await channel.SendConfirmAsync("Currency generation enabled on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("curgen_enabled").ConfigureAwait(false);
}
else
{
await channel.SendConfirmAsync("Currency generation disabled on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("curgen_disabled").ConfigureAwait(false);
}
}
#endif
private static KeyValuePair<string, ImmutableArray<byte>> GetRandomCurrencyImage()
{

View File

@ -1,22 +1,22 @@
using Discord;
using System;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ImageSharp.Processing;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
[Group]
public class PollCommands : ModuleBase
public class PollCommands : NadekoSubmodule
{
public static ConcurrentDictionary<ulong, Poll> ActivePolls = new ConcurrentDictionary<ulong, Poll>();
@ -24,32 +24,30 @@ namespace NadekoBot.Modules.Games
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public Task Poll([Remainder] string arg = null)
=> InternalStartPoll(arg, isPublic: false);
=> InternalStartPoll(arg, false);
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public Task PublicPoll([Remainder] string arg = null)
=> InternalStartPoll(arg, isPublic: true);
=> InternalStartPoll(arg, true);
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public async Task PollStats()
{
Games.Poll poll;
Poll poll;
if (!ActivePolls.TryGetValue(Context.Guild.Id, out poll))
return;
await Context.Channel.EmbedAsync(poll.GetStats("Current Poll Results"));
await Context.Channel.EmbedAsync(poll.GetStats(GetText("current_poll_results")));
}
private async Task InternalStartPoll(string arg, bool isPublic = false)
{
var channel = (ITextChannel)Context.Channel;
if (!(Context.User as IGuildUser).GuildPermissions.ManageChannels)
return;
if (string.IsNullOrWhiteSpace(arg) || !arg.Contains(";"))
return;
var data = arg.Split(';');
@ -62,7 +60,7 @@ namespace NadekoBot.Modules.Games
await poll.StartPoll().ConfigureAwait(false);
}
else
await channel.SendErrorAsync("Poll is already running on this server.").ConfigureAwait(false);
await ReplyErrorLocalized("poll_already_running").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -80,27 +78,25 @@ namespace NadekoBot.Modules.Games
public class Poll
{
private readonly IUserMessage originalMessage;
private readonly IGuild guild;
private string[] Answers { get; }
private ConcurrentDictionary<ulong, int> participants = new ConcurrentDictionary<ulong, int>();
private readonly string question;
private DateTime started;
private CancellationTokenSource pollCancellationSource = new CancellationTokenSource();
private readonly IUserMessage _originalMessage;
private readonly IGuild _guild;
private string[] answers { get; }
private readonly ConcurrentDictionary<ulong, int> _participants = new ConcurrentDictionary<ulong, int>();
private readonly string _question;
public bool IsPublic { get; }
public Poll(IUserMessage umsg, string question, IEnumerable<string> enumerable, bool isPublic = false)
{
this.originalMessage = umsg;
this.guild = ((ITextChannel)umsg.Channel).Guild;
this.question = question;
this.Answers = enumerable as string[] ?? enumerable.ToArray();
this.IsPublic = isPublic;
_originalMessage = umsg;
_guild = ((ITextChannel)umsg.Channel).Guild;
_question = question;
answers = enumerable as string[] ?? enumerable.ToArray();
IsPublic = isPublic;
}
public EmbedBuilder GetStats(string title)
{
var results = participants.GroupBy(kvp => kvp.Value)
var results = _participants.GroupBy(kvp => kvp.Value)
.ToDictionary(x => x.Key, x => x.Sum(kvp => 1))
.OrderByDescending(kvp => kvp.Value)
.ToArray();
@ -108,49 +104,51 @@ namespace NadekoBot.Modules.Games
var eb = new EmbedBuilder().WithTitle(title);
var sb = new StringBuilder()
.AppendLine(Format.Bold(question))
.AppendLine(Format.Bold(_question))
.AppendLine();
var totalVotesCast = 0;
if (results.Length == 0)
{
sb.AppendLine("No votes cast.");
sb.AppendLine(GetText("no_votes_cast"));
}
else
{
for (int i = 0; i < results.Length; i++)
{
var result = results[i];
sb.AppendLine($"`{i + 1}.` {Format.Bold(Answers[result.Key - 1])} with {Format.Bold(result.Value.ToString())} votes.");
sb.AppendLine(GetText("poll_result",
result.Key,
Format.Bold(answers[result.Key - 1]),
Format.Bold(result.Value.ToString())));
totalVotesCast += result.Value;
}
}
eb.WithDescription(sb.ToString())
.WithFooter(efb => efb.WithText(totalVotesCast + " total votes cast."));
.WithFooter(efb => efb.WithText(GetText("x_votes_cast", totalVotesCast)));
return eb;
}
public async Task StartPoll()
{
started = DateTime.Now;
NadekoBot.Client.MessageReceived += Vote;
var msgToSend = $"📃**{originalMessage.Author.Username}** has created a poll which requires your attention:\n\n**{question}**\n";
var msgToSend = GetText("poll_created", Format.Bold(_originalMessage.Author.Username)) + "\n\n" + Format.Bold(_question) + "\n";
var num = 1;
msgToSend = Answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n");
msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n");
if (!IsPublic)
msgToSend += "\n**Private Message me with the corresponding number of the answer.**";
msgToSend += "\n" + Format.Bold(GetText("poll_vote_private"));
else
msgToSend += "\n**Send a Message here with the corresponding number of the answer.**";
await originalMessage.Channel.SendConfirmAsync(msgToSend).ConfigureAwait(false);
msgToSend += "\n" + Format.Bold(GetText("poll_vote_public"));
await _originalMessage.Channel.SendConfirmAsync(msgToSend).ConfigureAwait(false);
}
public async Task StopPoll()
{
NadekoBot.Client.MessageReceived -= Vote;
await originalMessage.Channel.EmbedAsync(GetStats("POLL CLOSED")).ConfigureAwait(false);
await _originalMessage.Channel.EmbedAsync(GetStats("POLL CLOSED")).ConfigureAwait(false);
}
private async Task Vote(SocketMessage imsg)
@ -166,14 +164,14 @@ namespace NadekoBot.Modules.Games
int vote;
if (!int.TryParse(imsg.Content, out vote))
return;
if (vote < 1 || vote > Answers.Length)
if (vote < 1 || vote > answers.Length)
return;
IMessageChannel ch;
if (IsPublic)
{
//if public, channel must be the same the poll started in
if (originalMessage.Channel.Id != imsg.Channel.Id)
if (_originalMessage.Channel.Id != imsg.Channel.Id)
return;
ch = imsg.Channel;
}
@ -184,27 +182,33 @@ namespace NadekoBot.Modules.Games
return;
// user must be a member of the guild this poll is in
var guildUsers = await guild.GetUsersAsync().ConfigureAwait(false);
if (!guildUsers.Any(u => u.Id == imsg.Author.Id))
var guildUsers = await _guild.GetUsersAsync().ConfigureAwait(false);
if (guildUsers.All(u => u.Id != imsg.Author.Id))
return;
}
//user can vote only once
if (participants.TryAdd(msg.Author.Id, vote))
if (_participants.TryAdd(msg.Author.Id, vote))
{
if (!IsPublic)
{
await ch.SendConfirmAsync($"Thanks for voting **{msg.Author.Username}**.").ConfigureAwait(false);
await ch.SendConfirmAsync(GetText("thanks_for_voting", Format.Bold(msg.Author.Username))).ConfigureAwait(false);
}
else
{
var toDelete = await ch.SendConfirmAsync($"{msg.Author.Mention} cast their vote.").ConfigureAwait(false);
var toDelete = await ch.SendConfirmAsync(GetText("poll_voted", Format.Bold(msg.Author.ToString()))).ConfigureAwait(false);
toDelete.DeleteAfter(5);
}
}
}
catch { }
}
private string GetText(string key, params object[] replacements)
=> NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(_guild.Id),
typeof(Games).Name.ToLowerInvariant(),
replacements);
}
}
}

View File

@ -147,11 +147,11 @@ namespace NadekoBot.Modules.Games
}
[Group]
public class SpeedTypingCommands : ModuleBase
public class SpeedTypingCommands : NadekoSubmodule
{
public static List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>();
private const string _typingArticlesPath = "data/typing_articles.json";
private const string _typingArticlesPath = "data/typing_articles2.json";
static SpeedTypingCommands()
{

View File

@ -4,9 +4,7 @@ using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -15,21 +13,13 @@ namespace NadekoBot.Modules.Games
{
public partial class Games
{
//todo timeout
[Group]
public class TicTacToeCommands : ModuleBase
public class TicTacToeCommands : NadekoSubmodule
{
//channelId/game
private static readonly Dictionary<ulong, TicTacToe> _games = new Dictionary<ulong, TicTacToe>();
private readonly Logger _log;
public TicTacToeCommands()
{
_log = LogManager.GetCurrentClassLogger();
}
private readonly SemaphoreSlim sem = new SemaphoreSlim(1, 1);
private readonly object tttLockObj = new object();
private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
@ -37,7 +27,7 @@ namespace NadekoBot.Modules.Games
{
var channel = (ITextChannel)Context.Channel;
await sem.WaitAsync(1000);
await _sem.WaitAsync(1000);
try
{
TicTacToe game;
@ -51,7 +41,7 @@ namespace NadekoBot.Modules.Games
}
game = new TicTacToe(channel, (IGuildUser)Context.User);
_games.Add(channel.Id, game);
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} Created a TicTacToe game.").ConfigureAwait(false);
await ReplyConfirmLocalized("ttt_created").ConfigureAwait(false);
game.OnEnded += (g) =>
{
@ -60,7 +50,7 @@ namespace NadekoBot.Modules.Games
}
finally
{
sem.Release();
_sem.Release();
}
}
}
@ -75,46 +65,49 @@ namespace NadekoBot.Modules.Games
}
private readonly ITextChannel _channel;
private readonly Logger _log;
private readonly IGuildUser[] _users;
private readonly int?[,] _state;
private Phase _phase;
int curUserIndex = 0;
private readonly SemaphoreSlim moveLock;
private int _curUserIndex;
private readonly SemaphoreSlim _moveLock;
private IGuildUser _winner = null;
private IGuildUser _winner;
private readonly string[] numbers = { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" };
private readonly string[] _numbers = { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" };
public Action<TicTacToe> OnEnded;
private IUserMessage previousMessage = null;
private Timer timeoutTimer;
private IUserMessage _previousMessage;
private Timer _timeoutTimer;
public TicTacToe(ITextChannel channel, IGuildUser firstUser)
{
_channel = channel;
_users = new IGuildUser[2] { firstUser, null };
_state = new int?[3, 3] {
_users = new[] { firstUser, null };
_state = new int?[,] {
{ null, null, null },
{ null, null, null },
{ null, null, null },
};
_log = LogManager.GetCurrentClassLogger();
_log.Warn($"User {firstUser} created a TicTacToe game.");
_phase = Phase.Starting;
moveLock = new SemaphoreSlim(1, 1);
_moveLock = new SemaphoreSlim(1, 1);
}
private string GetText(string key, params object[] replacements) =>
NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(_channel.GuildId),
typeof(Games).Name.ToLowerInvariant(),
replacements);
public string GetState()
{
var sb = new StringBuilder();
for (int i = 0; i < _state.GetLength(0); i++)
for (var i = 0; i < _state.GetLength(0); i++)
{
for (int j = 0; j < _state.GetLength(1); j++)
for (var j = 0; j < _state.GetLength(1); j++)
{
sb.Append(_state[i, j] == null ? numbers[i * 3 + j] : GetIcon(_state[i, j]));
sb.Append(_state[i, j] == null ? _numbers[i * 3 + j] : GetIcon(_state[i, j]));
if (j < _state.GetLength(1) - 1)
sb.Append("┃");
}
@ -130,7 +123,7 @@ namespace NadekoBot.Modules.Games
var embed = new EmbedBuilder()
.WithOkColor()
.WithDescription(Environment.NewLine + GetState())
.WithAuthor(eab => eab.WithName($"{_users[0]} vs {_users[1]}"));
.WithAuthor(eab => eab.WithName(GetText("vs", _users[0], _users[1])));
if (!string.IsNullOrWhiteSpace(title))
embed.WithTitle(title);
@ -138,12 +131,12 @@ namespace NadekoBot.Modules.Games
if (_winner == null)
{
if (_phase == Phase.Ended)
embed.WithFooter(efb => efb.WithText($"No moves left!"));
embed.WithFooter(efb => efb.WithText(GetText("ttt_no_moves")));
else
embed.WithFooter(efb => efb.WithText($"{_users[curUserIndex]}'s move"));
embed.WithFooter(efb => efb.WithText(GetText("ttt_users_move", _users[_curUserIndex])));
}
else
embed.WithFooter(efb => efb.WithText($"{_winner} Won!"));
embed.WithFooter(efb => efb.WithText(GetText("ttt_has_won", _winner)));
return embed;
}
@ -169,23 +162,22 @@ namespace NadekoBot.Modules.Games
{
if (_phase == Phase.Started || _phase == Phase.Ended)
{
await _channel.SendErrorAsync(user.Mention + " TicTacToe Game is already running in this channel.").ConfigureAwait(false);
await _channel.SendErrorAsync(user.Mention + GetText("ttt_already_running")).ConfigureAwait(false);
return;
}
else if (_users[0] == user)
{
await _channel.SendErrorAsync(user.Mention + " You can't play against yourself.").ConfigureAwait(false);
await _channel.SendErrorAsync(user.Mention + GetText("ttt_against_yourself")).ConfigureAwait(false);
return;
}
_users[1] = user;
_log.Warn($"User {user} joined a TicTacToe game.");
_phase = Phase.Started;
timeoutTimer = new Timer(async (_) =>
_timeoutTimer = new Timer(async (_) =>
{
await moveLock.WaitAsync();
await _moveLock.WaitAsync();
try
{
if (_phase == Phase.Ended)
@ -194,12 +186,13 @@ namespace NadekoBot.Modules.Games
_phase = Phase.Ended;
if (_users[1] != null)
{
_winner = _users[curUserIndex ^= 1];
var del = previousMessage?.DeleteAsync();
_winner = _users[_curUserIndex ^= 1];
var del = _previousMessage?.DeleteAsync();
try
{
await _channel.EmbedAsync(GetEmbed("Time Expired!")).ConfigureAwait(false);
await del.ConfigureAwait(false);
await _channel.EmbedAsync(GetEmbed(GetText("ttt_time_expired"))).ConfigureAwait(false);
if (del != null)
await del.ConfigureAwait(false);
}
catch { }
}
@ -209,21 +202,21 @@ namespace NadekoBot.Modules.Games
catch { }
finally
{
moveLock.Release();
_moveLock.Release();
}
}, null, 15000, Timeout.Infinite);
NadekoBot.Client.MessageReceived += Client_MessageReceived;
previousMessage = await _channel.EmbedAsync(GetEmbed("Game Started")).ConfigureAwait(false);
_previousMessage = await _channel.EmbedAsync(GetEmbed(GetText("game_started"))).ConfigureAwait(false);
}
private bool IsDraw()
{
for (int i = 0; i < 3; i++)
for (var i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
for (var j = 0; j < 3; j++)
{
if (_state[i, j] == null)
return false;
@ -236,10 +229,10 @@ namespace NadekoBot.Modules.Games
{
var _ = Task.Run(async () =>
{
await moveLock.WaitAsync().ConfigureAwait(false);
await _moveLock.WaitAsync().ConfigureAwait(false);
try
{
var curUser = _users[curUserIndex];
var curUser = _users[_curUserIndex];
if (_phase == Phase.Ended || msg.Author?.Id != curUser.Id)
return;
@ -249,53 +242,53 @@ namespace NadekoBot.Modules.Games
index <= 9 &&
_state[index / 3, index % 3] == null)
{
_state[index / 3, index % 3] = curUserIndex;
_state[index / 3, index % 3] = _curUserIndex;
// i'm lazy
if (_state[index / 3, 0] == _state[index / 3, 1] && _state[index / 3, 1] == _state[index / 3, 2])
{
_state[index / 3, 0] = curUserIndex + 2;
_state[index / 3, 1] = curUserIndex + 2;
_state[index / 3, 2] = curUserIndex + 2;
_state[index / 3, 0] = _curUserIndex + 2;
_state[index / 3, 1] = _curUserIndex + 2;
_state[index / 3, 2] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (_state[0, index % 3] == _state[1, index % 3] && _state[1, index % 3] == _state[2, index % 3])
{
_state[0, index % 3] = curUserIndex + 2;
_state[1, index % 3] = curUserIndex + 2;
_state[2, index % 3] = curUserIndex + 2;
_state[0, index % 3] = _curUserIndex + 2;
_state[1, index % 3] = _curUserIndex + 2;
_state[2, index % 3] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (curUserIndex == _state[0, 0] && _state[0, 0] == _state[1, 1] && _state[1, 1] == _state[2, 2])
else if (_curUserIndex == _state[0, 0] && _state[0, 0] == _state[1, 1] && _state[1, 1] == _state[2, 2])
{
_state[0, 0] = curUserIndex + 2;
_state[1, 1] = curUserIndex + 2;
_state[2, 2] = curUserIndex + 2;
_state[0, 0] = _curUserIndex + 2;
_state[1, 1] = _curUserIndex + 2;
_state[2, 2] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (curUserIndex == _state[0, 2] && _state[0, 2] == _state[1, 1] && _state[1, 1] == _state[2, 0])
else if (_curUserIndex == _state[0, 2] && _state[0, 2] == _state[1, 1] && _state[1, 1] == _state[2, 0])
{
_state[0, 2] = curUserIndex + 2;
_state[1, 1] = curUserIndex + 2;
_state[2, 0] = curUserIndex + 2;
_state[0, 2] = _curUserIndex + 2;
_state[1, 1] = _curUserIndex + 2;
_state[2, 0] = _curUserIndex + 2;
_phase = Phase.Ended;
}
string reason = "";
var reason = "";
if (_phase == Phase.Ended) // if user won, stop receiving moves
{
reason = "Matched three!";
_winner = _users[curUserIndex];
reason = GetText("ttt_matched_three");
_winner = _users[_curUserIndex];
NadekoBot.Client.MessageReceived -= Client_MessageReceived;
OnEnded?.Invoke(this);
}
else if (IsDraw())
{
reason = "A draw!";
reason = GetText("ttt_a_draw");
_phase = Phase.Ended;
NadekoBot.Client.MessageReceived -= Client_MessageReceived;
OnEnded?.Invoke(this);
@ -304,19 +297,19 @@ namespace NadekoBot.Modules.Games
var sendstate = Task.Run(async () =>
{
var del1 = msg.DeleteAsync();
var del2 = previousMessage?.DeleteAsync();
try { previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); } catch { }
var del2 = _previousMessage?.DeleteAsync();
try { _previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); } catch { }
try { await del1; } catch { }
try { if (del2 != null) await del2; } catch { }
});
curUserIndex ^= 1;
_curUserIndex ^= 1;
timeoutTimer.Change(15000, Timeout.Infinite);
_timeoutTimer.Change(15000, Timeout.Infinite);
}
}
finally
{
moveLock.Release();
_moveLock.Release();
}
});

View File

@ -17,36 +17,44 @@ namespace NadekoBot.Modules.Games.Trivia
public class TriviaGame
{
private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1);
private Logger _log { get; }
private readonly Logger _log;
public IGuild guild { get; }
public ITextChannel channel { get; }
public IGuild Guild { get; }
public ITextChannel Channel { get; }
private int QuestionDurationMiliseconds { get; } = 30000;
private int HintTimeoutMiliseconds { get; } = 6000;
public bool ShowHints { get; } = true;
private int questionDurationMiliseconds { get; } = 30000;
private int hintTimeoutMiliseconds { get; } = 6000;
public bool ShowHints { get; }
public bool IsPokemon { get; }
private CancellationTokenSource triviaCancelSource { get; set; }
public TriviaQuestion CurrentQuestion { get; private set; }
public HashSet<TriviaQuestion> oldQuestions { get; } = new HashSet<TriviaQuestion>();
public HashSet<TriviaQuestion> OldQuestions { get; } = new HashSet<TriviaQuestion>();
public ConcurrentDictionary<IGuildUser, int> Users { get; } = new ConcurrentDictionary<IGuildUser, int>();
public bool GameActive { get; private set; } = false;
public bool GameActive { get; private set; }
public bool ShouldStopGame { get; private set; }
public int WinRequirement { get; } = 10;
public int WinRequirement { get; }
public TriviaGame(IGuild guild, ITextChannel channel, bool showHints, int winReq)
public TriviaGame(IGuild guild, ITextChannel channel, bool showHints, int winReq, bool isPokemon)
{
this._log = LogManager.GetCurrentClassLogger();
_log = LogManager.GetCurrentClassLogger();
this.ShowHints = showHints;
this.guild = guild;
this.channel = channel;
this.WinRequirement = winReq;
ShowHints = showHints;
Guild = guild;
Channel = channel;
WinRequirement = winReq;
IsPokemon = isPokemon;
}
private string GetText(string key, params object[] replacements) =>
NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(Channel.GuildId),
typeof(Games).Name.ToLowerInvariant(),
replacements);
public async Task StartGame()
{
while (!ShouldStopGame)
@ -55,26 +63,25 @@ namespace NadekoBot.Modules.Games.Trivia
triviaCancelSource = new CancellationTokenSource();
// load question
CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(oldQuestions);
if (CurrentQuestion == null ||
string.IsNullOrWhiteSpace(CurrentQuestion.Answer) ||
string.IsNullOrWhiteSpace(CurrentQuestion.Question))
CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(OldQuestions, IsPokemon);
if (string.IsNullOrWhiteSpace(CurrentQuestion?.Answer) || string.IsNullOrWhiteSpace(CurrentQuestion.Question))
{
await channel.SendErrorAsync("Trivia Game", "Failed loading a question.").ConfigureAwait(false);
await Channel.SendErrorAsync(GetText("trivia_game"), GetText("failed_loading_question")).ConfigureAwait(false);
return;
}
oldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again
OldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again
EmbedBuilder questionEmbed;
IUserMessage questionMessage;
try
{
questionEmbed = new EmbedBuilder().WithOkColor()
.WithTitle("Trivia Game")
.AddField(eab => eab.WithName("Category").WithValue(CurrentQuestion.Category))
.AddField(eab => eab.WithName("Question").WithValue(CurrentQuestion.Question));
.WithTitle(GetText("trivia_game"))
.AddField(eab => eab.WithName(GetText("category")).WithValue(CurrentQuestion.Category))
.AddField(eab => eab.WithName(GetText("question")).WithValue(CurrentQuestion.Question))
.WithImageUrl(CurrentQuestion.ImageUrl);
questionMessage = await channel.EmbedAsync(questionEmbed).ConfigureAwait(false);
questionMessage = await Channel.EmbedAsync(questionEmbed).ConfigureAwait(false);
}
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound ||
ex.HttpCode == System.Net.HttpStatusCode.Forbidden ||
@ -99,7 +106,7 @@ namespace NadekoBot.Modules.Games.Trivia
try
{
//hint
await Task.Delay(HintTimeoutMiliseconds, triviaCancelSource.Token).ConfigureAwait(false);
await Task.Delay(hintTimeoutMiliseconds, triviaCancelSource.Token).ConfigureAwait(false);
if (ShowHints)
try
{
@ -113,7 +120,7 @@ namespace NadekoBot.Modules.Games.Trivia
catch (Exception ex) { _log.Warn(ex); }
//timeout
await Task.Delay(QuestionDurationMiliseconds - HintTimeoutMiliseconds, triviaCancelSource.Token).ConfigureAwait(false);
await Task.Delay(questionDurationMiliseconds - hintTimeoutMiliseconds, triviaCancelSource.Token).ConfigureAwait(false);
}
catch (TaskCanceledException) { } //means someone guessed the answer
@ -124,8 +131,21 @@ namespace NadekoBot.Modules.Games.Trivia
NadekoBot.Client.MessageReceived -= PotentialGuess;
}
if (!triviaCancelSource.IsCancellationRequested)
try { await channel.SendErrorAsync("Trivia Game", $"**Time's up!** The correct answer was **{CurrentQuestion.Answer}**").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
await Task.Delay(2000).ConfigureAwait(false);
{
try
{
await Channel.EmbedAsync(new EmbedBuilder().WithErrorColor()
.WithTitle(GetText("trivia_game"))
.WithDescription(GetText("trivia_times_up", Format.Bold(CurrentQuestion.Answer)))
.WithImageUrl(CurrentQuestion.AnswerImageUrl))
.ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
await Task.Delay(5000).ConfigureAwait(false);
}
}
@ -133,7 +153,7 @@ namespace NadekoBot.Modules.Games.Trivia
{
ShouldStopGame = true;
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
await Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName("Trivia Game Ended"))
.WithTitle("Final Results")
.WithDescription(GetLeaderboard())).ConfigureAwait(false);
@ -144,7 +164,7 @@ namespace NadekoBot.Modules.Games.Trivia
var old = ShouldStopGame;
ShouldStopGame = true;
if (!old)
try { await channel.SendConfirmAsync("Trivia Game", "Stopping after this question.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
try { await Channel.SendConfirmAsync(GetText("trivia_game"), GetText("trivia_stopping")).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
}
private async Task PotentialGuess(SocketMessage imsg)
@ -155,11 +175,9 @@ namespace NadekoBot.Modules.Games.Trivia
return;
var umsg = imsg as SocketUserMessage;
if (umsg == null)
return;
var textChannel = umsg.Channel as ITextChannel;
if (textChannel == null || textChannel.Guild != guild)
var textChannel = umsg?.Channel as ITextChannel;
if (textChannel == null || textChannel.Guild != Guild)
return;
var guildUser = (IGuildUser)umsg.Author;
@ -182,14 +200,31 @@ namespace NadekoBot.Modules.Games.Trivia
if (Users[guildUser] == WinRequirement)
{
ShouldStopGame = true;
try { await channel.SendConfirmAsync("Trivia Game", $"{guildUser.Mention} guessed it and WON the game! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false); } catch { }
try
{
await Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("trivia_game"))
.WithDescription(GetText("trivia_win",
guildUser.Mention,
Format.Bold(CurrentQuestion.Answer)))
.WithImageUrl(CurrentQuestion.AnswerImageUrl))
.ConfigureAwait(false);
}
catch
{
// ignored
}
var reward = NadekoBot.BotConfig.TriviaCurrencyReward;
if (reward > 0)
await CurrencyHandler.AddCurrencyAsync(guildUser, "Won trivia", reward, true).ConfigureAwait(false);
return;
}
await channel.SendConfirmAsync("Trivia Game", $"{guildUser.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false);
await Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("trivia_game"))
.WithDescription(GetText("trivia_guess", guildUser.Mention, Format.Bold(CurrentQuestion.Answer)))
.WithImageUrl(CurrentQuestion.AnswerImageUrl))
.ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
}
@ -197,13 +232,13 @@ namespace NadekoBot.Modules.Games.Trivia
public string GetLeaderboard()
{
if (Users.Count == 0)
return "No results.";
return GetText("no_results");
var sb = new StringBuilder();
foreach (var kvp in Users.OrderByDescending(kvp => kvp.Value))
{
sb.AppendLine($"**{kvp.Key.Username}** has {kvp.Value} points".ToString().SnPl(kvp.Value));
sb.AppendLine(GetText("trivia_points", Format.Bold(kvp.Key.ToString()), kvp.Value).SnPl(kvp.Value));
}
return sb.ToString();

View File

@ -20,13 +20,17 @@ namespace NadekoBot.Modules.Games.Trivia
public string Category { get; set; }
public string Question { get; set; }
public string ImageUrl { get; set; }
public string AnswerImageUrl { get; set; }
public string Answer { get; set; }
public TriviaQuestion(string q, string a, string c)
public TriviaQuestion(string q, string a, string c, string img = null, string answerImage = null)
{
this.Question = q;
this.Answer = a;
this.Category = c;
this.ImageUrl = img;
this.AnswerImageUrl = answerImage ?? img;
}
public string GetHint() => Scramble(Answer);

View File

@ -1,10 +1,9 @@
using NadekoBot.Extensions;
using NadekoBot.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
@ -12,27 +11,50 @@ namespace NadekoBot.Modules.Games.Trivia
{
public class TriviaQuestionPool
{
public class PokemonNameId
{
public int Id { get; set; }
public string Name { get; set; }
}
private static TriviaQuestionPool _instance;
public static TriviaQuestionPool Instance { get; } = _instance ?? (_instance = new TriviaQuestionPool());
private const string questionsFile = "data/trivia_questions.json";
private const string pokemonMapPath = "data/pokemon/name-id_map4.json";
private readonly int maxPokemonId;
private Random rng { get; } = new NadekoRandom();
private TriviaQuestion[] pool { get; }
private ImmutableDictionary<int, string> map { get; }
static TriviaQuestionPool() { }
private TriviaQuestionPool()
{
pool = JsonConvert.DeserializeObject<TriviaQuestion[]>(File.ReadAllText(questionsFile));
map = JsonConvert.DeserializeObject<PokemonNameId[]>(File.ReadAllText(pokemonMapPath))
.ToDictionary(x => x.Id, x => x.Name)
.ToImmutableDictionary();
maxPokemonId = 721; //xd
}
public TriviaQuestion GetRandomQuestion(HashSet<TriviaQuestion> exclude)
public TriviaQuestion GetRandomQuestion(HashSet<TriviaQuestion> exclude, bool isPokemon)
{
if (pool.Length == 0)
return null;
if (isPokemon)
{
var num = rng.Next(1, maxPokemonId + 1);
return new TriviaQuestion("Who's That Pokémon?",
map[num].ToTitleCase(),
"Pokemon",
$@"http://nadekobot.xyz/images/pokemon/shadows/{num}.png",
$@"http://nadekobot.xyz/images/pokemon/real/{num}.png");
}
TriviaQuestion randomQuestion;
while (exclude.Contains(randomQuestion = pool[rng.Next(0, pool.Length)])) ;

View File

@ -3,9 +3,7 @@ using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Trivia;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
@ -14,24 +12,30 @@ namespace NadekoBot.Modules.Games
public partial class Games
{
[Group]
public class TriviaCommands : ModuleBase
public class TriviaCommands : NadekoSubmodule
{
public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public Task Trivia([Remainder] string additionalArgs = "")
=> Trivia(10, additionalArgs);
=> InternalTrivia(10, additionalArgs);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Trivia(int winReq = 10, [Remainder] string additionalArgs = "")
public Task Trivia(int winReq = 10, [Remainder] string additionalArgs = "")
=> InternalTrivia(winReq, additionalArgs);
public async Task InternalTrivia(int winReq, string additionalArgs = "")
{
var channel = (ITextChannel)Context.Channel;
var showHints = !additionalArgs.Contains("nohint");
additionalArgs = additionalArgs?.Trim()?.ToLowerInvariant();
TriviaGame trivia = new TriviaGame(channel.Guild, channel, showHints, winReq);
var showHints = !additionalArgs.Contains("nohint");
var isPokemon = additionalArgs.Contains("pokemon");
var trivia = new TriviaGame(channel.Guild, channel, showHints, winReq, isPokemon);
if (RunningTrivias.TryAdd(channel.Guild.Id, trivia))
{
try
@ -43,10 +47,11 @@ namespace NadekoBot.Modules.Games
RunningTrivias.TryRemove(channel.Guild.Id, out trivia);
await trivia.EnsureStopped().ConfigureAwait(false);
}
return;
return;
}
else
await Context.Channel.SendErrorAsync("Trivia game is already running on this server.\n" + trivia.CurrentQuestion).ConfigureAwait(false);
await Context.Channel.SendErrorAsync(GetText("trivia_already_running") + "\n" + trivia.CurrentQuestion)
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -58,11 +63,11 @@ namespace NadekoBot.Modules.Games
TriviaGame trivia;
if (RunningTrivias.TryGetValue(channel.Guild.Id, out trivia))
{
await channel.SendConfirmAsync("Leaderboard", trivia.GetLeaderboard()).ConfigureAwait(false);
await channel.SendConfirmAsync(GetText("leaderboard"), trivia.GetLeaderboard()).ConfigureAwait(false);
return;
}
await channel.SendErrorAsync("No trivia is running on this server.").ConfigureAwait(false);
await ReplyErrorLocalized("trivia_none").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -78,7 +83,7 @@ namespace NadekoBot.Modules.Games
return;
}
await channel.SendErrorAsync("No trivia is running on this server.").ConfigureAwait(false);
await ReplyErrorLocalized("trivia_none").ConfigureAwait(false);
}
}
}

View File

@ -4,16 +4,29 @@ using NadekoBot.Services;
using System.Threading.Tasks;
using NadekoBot.Attributes;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Threading;
using NadekoBot.Extensions;
using System.Net.Http;
using ImageSharp;
using NadekoBot.DataStructures;
using NLog;
namespace NadekoBot.Modules.Games
{
[NadekoModule("Games", ">")]
public partial class Games : NadekoModule
public partial class Games : NadekoTopLevelModule
{
private static string[] _8BallResponses { get; } = NadekoBot.BotConfig.EightBallResponses.Select(ebr => ebr.Text).ToArray();
private static readonly ImmutableArray<string> _8BallResponses = NadekoBot.BotConfig.EightBallResponses.Select(ebr => ebr.Text).ToImmutableArray();
private static readonly Timer _t = new Timer((_) =>
{
_girlRatings.Clear();
}, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1));
[NadekoCommand, Usage, Description, Aliases]
public async Task Choose([Remainder] string list = null)
@ -21,7 +34,7 @@ namespace NadekoBot.Modules.Games
if (string.IsNullOrWhiteSpace(list))
return;
var listArr = list.Split(';');
if (listArr.Count() < 2)
if (listArr.Length < 2)
return;
var rng = new NadekoRandom();
await Context.Channel.SendConfirmAsync("🤔", listArr[rng.Next(0, listArr.Length)]).ConfigureAwait(false);
@ -34,21 +47,24 @@ namespace NadekoBot.Modules.Games
return;
await Context.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
.AddField(efb => efb.WithName("❓ Question").WithValue(question).WithIsInline(false))
.AddField(efb => efb.WithName("🎱 8Ball").WithValue(_8BallResponses[new NadekoRandom().Next(0, _8BallResponses.Length)]).WithIsInline(false)));
.AddField(efb => efb.WithName("❓ " + GetText("question") ).WithValue(question).WithIsInline(false))
.AddField(efb => efb.WithName("🎱 " + GetText("8ball")).WithValue(_8BallResponses[new NadekoRandom().Next(0, _8BallResponses.Length)]).WithIsInline(false)));
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Rps(string input)
{
Func<int,string> GetRPSPick = (p) =>
Func<int,string> getRpsPick = (p) =>
{
if (p == 0)
return "🚀";
else if (p == 1)
return "📎";
else
return "✂️";
switch (p)
{
case 0:
return "🚀";
case 1:
return "📎";
default:
return "✂️";
}
};
int pick;
@ -72,19 +88,176 @@ namespace NadekoBot.Modules.Games
return;
}
var nadekoPick = new NadekoRandom().Next(0, 3);
var msg = "";
string msg;
if (pick == nadekoPick)
msg = $"It's a draw! Both picked {GetRPSPick(pick)}";
msg = GetText("rps_draw", getRpsPick(pick));
else if ((pick == 0 && nadekoPick == 1) ||
(pick == 1 && nadekoPick == 2) ||
(pick == 2 && nadekoPick == 0))
msg = $"{NadekoBot.Client.CurrentUser.Mention} won! {GetRPSPick(nadekoPick)} beats {GetRPSPick(pick)}";
msg = GetText("rps_win", NadekoBot.Client.CurrentUser.Mention,
getRpsPick(nadekoPick), getRpsPick(pick));
else
msg = $"{Context.User.Mention} won! {GetRPSPick(pick)} beats {GetRPSPick(nadekoPick)}";
msg = GetText("rps_win", Context.User.Mention, getRpsPick(pick),
getRpsPick(nadekoPick));
await Context.Channel.SendConfirmAsync(msg).ConfigureAwait(false);
}
private static readonly ConcurrentDictionary<ulong, GirlRating> _girlRatings = new ConcurrentDictionary<ulong, GirlRating>();
public class GirlRating
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
public double Crazy { get; }
public double Hot { get; }
public int Roll { get; }
public string Advice { get; }
public AsyncLazy<string> Url { get; }
public GirlRating(double crazy, double hot, int roll, string advice)
{
Crazy = crazy;
Hot = hot;
Roll = roll;
Advice = advice; // convenient to have it here, even though atm there are only few different ones.
Url = new AsyncLazy<string>(async () =>
{
try
{
using (var ms = new MemoryStream(NadekoBot.Images.WifeMatrix.ToArray(), false))
using (var img = new ImageSharp.Image(ms))
{
const int minx = 35;
const int miny = 385;
const int length = 345;
var pointx = (int)(minx + length * (Hot / 10));
var pointy = (int)(miny - length * ((Crazy - 4) / 6));
using (var pointMs = new MemoryStream(NadekoBot.Images.RategirlDot.ToArray(), false))
using (var pointImg = new ImageSharp.Image(pointMs))
{
img.DrawImage(pointImg, 100, default(Size), new Point(pointx - 10, pointy - 10));
}
string url;
using (var http = new HttpClient())
using (var imgStream = new MemoryStream())
{
img.Save(imgStream);
var byteContent = new ByteArrayContent(imgStream.ToArray());
http.AddFakeHeaders();
var reponse = await http.PutAsync("https://transfer.sh/img.png", byteContent);
url = await reponse.Content.ReadAsStringAsync();
}
return url;
}
}
catch (Exception ex)
{
_log.Warn(ex);
return null;
}
});
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task RateGirl(IGuildUser usr)
{
var gr = _girlRatings.GetOrAdd(usr.Id, GetGirl);
var img = await gr.Url;
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle("Girl Rating For " + usr)
.AddField(efb => efb.WithName("Hot").WithValue(gr.Hot.ToString("F2")).WithIsInline(true))
.AddField(efb => efb.WithName("Crazy").WithValue(gr.Crazy.ToString("F2")).WithIsInline(true))
.AddField(efb => efb.WithName("Advice").WithValue(gr.Advice).WithIsInline(false))
.WithImageUrl(img)).ConfigureAwait(false);
}
private double NextDouble(double x, double y)
{
var rng = new Random();
return rng.NextDouble() * (y - x) + x;
}
private GirlRating GetGirl(ulong uid)
{
var rng = new NadekoRandom();
var roll = rng.Next(1, 1001);
if ((uid == 185968432783687681 ||
uid == 265642040950390784) && roll >= 900)
roll = 1000;
double hot;
double crazy;
string advice;
if (roll < 500)
{
hot = NextDouble(0, 5);
crazy = NextDouble(4, 10);
advice =
"This is your NO-GO ZONE. We do not hang around, and date, and marry women who are atleast, in our mind, a 5. " +
"So, this is your no-go zone. You don't go here. You just rule this out. Life is better this way, that's the way it is.";
}
else if (roll < 750)
{
hot = NextDouble(5, 8);
crazy = NextDouble(4, .6 * hot + 4);
advice = "Above a 5, and to about an 8, and below the crazy line - this is your FUN ZONE. You can " +
"hang around here, and meet these girls and spend time with them. Keep in mind, while you're " +
"in the fun zone, you want to move OUT of the fun zone to a more permanent location. " +
"These girls are most of the time not crazy.";
}
else if (roll < 900)
{
hot = NextDouble(5, 10);
crazy = NextDouble(.61 * hot + 4, 10);
advice = "Above the crazy line - it's the DANGER ZONE. This is redheads, strippers, anyone named Tiffany, " +
"hairdressers... This is where your car gets keyed, you get bunny in the pot, your tires get slashed, " +
"and you wind up in jail.";
}
else if (roll < 951)
{
hot = NextDouble(8, 10);
crazy = NextDouble(7, .6 * hot + 4);
advice = "Below the crazy line, above an 8 hot, but still about 7 crazy. This is your DATE ZONE. " +
"You can stay in the date zone indefinitely. These are the girls you introduce to your friends and your family. " +
"They're good looking, and they're reasonably not crazy most of the time. You can stay here indefinitely.";
}
else if (roll < 990)
{
hot = NextDouble(8, 10);
crazy = NextDouble(5, 7);
advice = "Above an 8 hot, and between about 7 and a 5 crazy - this is WIFE ZONE. If you meet this girl, you should consider long-term " +
"relationship. Rare.";
}
else if (roll < 999)
{
hot = NextDouble(8, 10);
crazy = NextDouble(2, 3.99d);
advice = "You've met a girl she's above 8 hot, and not crazy at all (below 4)... totally cool?" +
" You should be careful. That's a dude. You're talking to a tranny!";
}
else
{
hot = NextDouble(8, 10);
crazy = NextDouble(4, 5);
advice = "Below 5 crazy, and above 8 hot, this is the UNICORN ZONE, these things don't exist." +
"If you find a unicorn, please capture it safely, keep it alive, we'd like to study it, " +
"and maybe look at how to replicate that.";
}
return new GirlRating(crazy, hot, roll, advice);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Linux(string guhnoo, string loonix)
{

View File

@ -13,7 +13,7 @@ using System.Collections.Generic;
namespace NadekoBot.Modules.Help
{
[NadekoModule("Help", "-")]
public class Help : NadekoModule
public class Help : NadekoTopLevelModule
{
private static string helpString { get; } = NadekoBot.BotConfig.HelpString;
public static string HelpString => String.Format(helpString, NadekoBot.Credentials.ClientId, NadekoBot.ModulePrefixes[typeof(Help).Name]);
@ -31,6 +31,7 @@ namespace NadekoBot.Modules.Help
.WithTitle(GetText("list_of_modules"))
.WithDescription(string.Join("\n",
NadekoBot.CommandService.Modules.GroupBy(m => m.GetTopLevelModule())
.Where(m => !Permissions.Permissions.GlobalPermissionCommands.BlockedModules.Contains(m.Key.Name.ToLowerInvariant()))
.Select(m => "• " + m.Key.Name)
.OrderBy(s => s)));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -45,6 +46,7 @@ namespace NadekoBot.Modules.Help
if (string.IsNullOrWhiteSpace(module))
return;
var cmds = NadekoBot.CommandService.Commands.Where(c => c.Module.GetTopLevelModule().Name.ToUpperInvariant().StartsWith(module))
.Where(c => !Permissions.Permissions.GlobalPermissionCommands.BlockedCommands.Contains(c.Aliases.First().ToLowerInvariant()))
.OrderBy(c => c.Aliases.First())
.Distinct(new CommandTextEqualityComparer())
.AsEnumerable();
@ -55,8 +57,14 @@ namespace NadekoBot.Modules.Help
await ReplyErrorLocalized("module_not_found").ConfigureAwait(false);
return;
}
var j = 0;
var groups = cmdsArray.GroupBy(x => j++ / 48).ToArray();
await channel.SendTableAsync($"📃 **{GetText("list_of_commands")}**\n", cmdsArray, el => $"{el.Aliases.First(),-15} {"["+el.Aliases.Skip(1).FirstOrDefault()+"]",-8}").ConfigureAwait(false);
for (int i = 0; i < groups.Count(); i++)
{
await channel.SendTableAsync(i == 0 ? $"📃 **{GetText("list_of_commands")}**\n" : "", groups.ElementAt(i), el => $"{el.Aliases.First(),-15} {"[" + el.Aliases.Skip(1).FirstOrDefault() + "]",-8}").ConfigureAwait(false);
}
await ConfirmLocalized("commands_instr", Prefix).ConfigureAwait(false);
}

View File

@ -77,10 +77,10 @@ namespace NadekoBot.Modules.Music.Classes
public IVoiceChannel PlaybackVoiceChannel { get; private set; }
public ITextChannel OutputTextChannel { get; set; }
private bool destroyed { get; set; } = false;
public bool RepeatSong { get; private set; } = false;
public bool RepeatPlaylist { get; private set; } = false;
public bool Autoplay { get; set; } = false;
private bool destroyed { get; set; }
public bool RepeatSong { get; private set; }
public bool RepeatPlaylist { get; private set; }
public bool Autoplay { get; set; }
public uint MaxQueueSize { get; set; } = 0;
private ConcurrentQueue<Action> actionQueue { get; } = new ConcurrentQueue<Action>();
@ -163,7 +163,7 @@ namespace NadekoBot.Modules.Music.Classes
}
if (RepeatPlaylist)
if (RepeatPlaylist & !RepeatSong)
AddSong(CurrentSong, CurrentSong.QueuerName);
if (RepeatSong)

View File

@ -5,12 +5,9 @@ using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using VideoLibrary;
using System.Net;
namespace NadekoBot.Modules.Music.Classes
@ -32,22 +29,22 @@ namespace NadekoBot.Modules.Music.Classes
public string QueuerName { get; set; }
public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
public TimeSpan CurrentTime => TimeSpan.FromSeconds(bytesSent / frameBytes / (1000 / milliseconds));
public TimeSpan CurrentTime => TimeSpan.FromSeconds(bytesSent / (float)_frameBytes / (1000 / (float)_milliseconds));
const int milliseconds = 20;
const int samplesPerFrame = (48000 / 1000) * milliseconds;
const int frameBytes = 3840; //16-bit, 2 channels
private const int _milliseconds = 20;
private const int _samplesPerFrame = (48000 / 1000) * _milliseconds;
private const int _frameBytes = 3840; //16-bit, 2 channels
private ulong bytesSent { get; set; } = 0;
private ulong bytesSent { get; set; }
//pwetty
public string PrettyProvider =>
$"{(SongInfo.Provider ?? "No Provider")}";
$"{(SongInfo.Provider ?? "???")}";
public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime;
public string PrettyName => $"**[{SongInfo.Title.TrimTo(65)}]({songUrl})**";
public string PrettyName => $"**[{SongInfo.Title.TrimTo(65)}]({SongUrl})**";
public string PrettyInfo => $"{MusicPlayer.PrettyVolume} | {PrettyTotalTime} | {PrettyProvider} | {QueuerName}";
@ -65,22 +62,19 @@ namespace NadekoBot.Modules.Music.Classes
}
}
private string PrettyTotalTime {
get {
public string PrettyTotalTime {
get
{
if (TotalTime == TimeSpan.Zero)
return "(?)";
else if (TotalTime == TimeSpan.MaxValue)
if (TotalTime == TimeSpan.MaxValue)
return "∞";
else
{
var time = TotalTime.ToString(@"mm\:ss");
var hrs = (int)TotalTime.TotalHours;
var time = TotalTime.ToString(@"mm\:ss");
var hrs = (int)TotalTime.TotalHours;
if (hrs > 0)
return hrs + ":" + time;
else
return time;
}
if (hrs > 0)
return hrs + ":" + time;
return time;
}
}
@ -89,13 +83,13 @@ namespace NadekoBot.Modules.Music.Classes
switch (SongInfo.ProviderType)
{
case MusicType.Radio:
return $"https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links
return "https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links
case MusicType.Normal:
//todo have videoid in songinfo from the start
var videoId = Regex.Match(SongInfo.Query, "<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+");
return $"https://img.youtube.com/vi/{ videoId }/0.jpg";
case MusicType.Local:
return $"https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png"; //test links
return "https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png"; //test links
case MusicType.Soundcloud:
return SongInfo.AlbumArt;
default:
@ -104,7 +98,7 @@ namespace NadekoBot.Modules.Music.Classes
}
}
private string songUrl {
public string SongUrl {
get {
switch (SongInfo.ProviderType)
{
@ -122,36 +116,32 @@ namespace NadekoBot.Modules.Music.Classes
}
}
private int skipTo = 0;
public int SkipTo {
get { return skipTo; }
set {
skipTo = value;
bytesSent = (ulong)skipTo * 3840 * 50;
}
}
public int SkipTo { get; set; }
private readonly Logger _log;
public Song(SongInfo songInfo)
{
this.SongInfo = songInfo;
this._log = LogManager.GetCurrentClassLogger();
SongInfo = songInfo;
_log = LogManager.GetCurrentClassLogger();
}
public Song Clone()
{
var s = new Song(SongInfo);
s.MusicPlayer = MusicPlayer;
s.QueuerName = QueuerName;
var s = new Song(SongInfo)
{
MusicPlayer = MusicPlayer,
QueuerName = QueuerName
};
return s;
}
public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
{
bytesSent = (ulong) SkipTo * 3840 * 50;
var filename = Path.Combine(Music.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
SongBuffer inStream = new SongBuffer(MusicPlayer, filename, SongInfo, skipTo, frameBytes * 100);
var inStream = new SongBuffer(MusicPlayer, filename, SongInfo, SkipTo, _frameBytes * 100);
var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false);
try
@ -200,22 +190,22 @@ namespace NadekoBot.Modules.Music.Classes
var outStream = voiceClient.CreatePCMStream(960);
int nextTime = Environment.TickCount + milliseconds;
int nextTime = Environment.TickCount + _milliseconds;
byte[] buffer = new byte[frameBytes];
byte[] buffer = new byte[_frameBytes];
while (!cancelToken.IsCancellationRequested && //song canceled for whatever reason
!(MusicPlayer.MaxPlaytimeSeconds != 0 && CurrentTime.TotalSeconds >= MusicPlayer.MaxPlaytimeSeconds)) // or exceedded max playtime
{
//Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------");
var read = await inStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
//await inStream.CopyToAsync(voiceClient.OutputStream);
if (read < frameBytes)
if (read < _frameBytes)
_log.Debug("read {0}", read);
unchecked
{
bytesSent += (ulong)read;
}
if (read < frameBytes)
if (read < _frameBytes)
{
if (read == 0)
{
@ -231,12 +221,12 @@ namespace NadekoBot.Modules.Music.Classes
_log.Warn("Slow connection has disrupted music, waiting a bit for buffer");
await Task.Delay(1000, cancelToken).ConfigureAwait(false);
nextTime = Environment.TickCount + milliseconds;
nextTime = Environment.TickCount + _milliseconds;
}
else
{
await Task.Delay(100, cancelToken).ConfigureAwait(false);
nextTime = Environment.TickCount + milliseconds;
nextTime = Environment.TickCount + _milliseconds;
}
}
else
@ -245,16 +235,16 @@ namespace NadekoBot.Modules.Music.Classes
else
attempt = 0;
while (this.MusicPlayer.Paused)
while (MusicPlayer.Paused)
{
await Task.Delay(200, cancelToken).ConfigureAwait(false);
nextTime = Environment.TickCount + milliseconds;
nextTime = Environment.TickCount + _milliseconds;
}
buffer = AdjustVolume(buffer, MusicPlayer.Volume);
if (read != frameBytes) continue;
nextTime = unchecked(nextTime + milliseconds);
if (read != _frameBytes) continue;
nextTime = unchecked(nextTime + _milliseconds);
int delayMillis = unchecked(nextTime - Environment.TickCount);
if (delayMillis > 0)
await Task.Delay(delayMillis, cancelToken).ConfigureAwait(false);
@ -264,8 +254,7 @@ namespace NadekoBot.Modules.Music.Classes
finally
{
await bufferTask;
if (inStream != null)
inStream.Dispose();
inStream.Dispose();
}
}
@ -279,25 +268,20 @@ namespace NadekoBot.Modules.Music.Classes
}
//aidiakapi ftw
public unsafe static byte[] AdjustVolume(byte[] audioSamples, float volume)
public static unsafe byte[] AdjustVolume(byte[] audioSamples, float volume)
{
Contract.Requires(audioSamples != null);
Contract.Requires(audioSamples.Length % 2 == 0);
Contract.Requires(volume >= 0f && volume <= 1f);
Contract.Assert(BitConverter.IsLittleEndian);
if (Math.Abs(volume - 1f) < 0.0001f) return audioSamples;
// 16-bit precision for the multiplication
int volumeFixed = (int)Math.Round(volume * 65536d);
var volumeFixed = (int)Math.Round(volume * 65536d);
int count = audioSamples.Length / 2;
var count = audioSamples.Length / 2;
fixed (byte* srcBytes = audioSamples)
{
short* src = (short*)srcBytes;
var src = (short*)srcBytes;
for (int i = count; i != 0; i--, src++)
for (var i = count; i != 0; i--, src++)
*src = (short)(((*src) * volumeFixed) >> 16);
}

View File

@ -99,8 +99,8 @@ namespace NadekoBot.Modules.Music.Classes
Console.WriteLine(@"You have not properly installed or configured FFMPEG.
Please install and configure FFMPEG to play music.
Check the guides for your platform on how to setup ffmpeg correctly:
Windows Guide: https://goo.gl/SCv72y
Linux Guide: https://goo.gl/rRhjCp");
Windows Guide: https://goo.gl/OjKk8F
Linux Guide: https://goo.gl/ShjCUo");
Console.ForegroundColor = oldclr;
}
catch (Exception ex)

View File

@ -6,12 +6,14 @@ using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NLog;
using VideoLibrary;
namespace NadekoBot.Modules.Music.Classes
{
public static class SongHandler
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
public static async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal)
{
if (string.IsNullOrWhiteSpace(query))
@ -106,7 +108,8 @@ namespace NadekoBot.Modules.Music.Classes
}
catch (Exception ex)
{
Console.WriteLine($"Failed resolving the link.{ex.Message}");
_log.Warn($"Failed resolving the link.{ex.Message}");
_log.Warn(ex);
return null;
}
}
@ -137,7 +140,7 @@ namespace NadekoBot.Modules.Music.Classes
}
catch
{
Console.WriteLine($"Failed reading .pls:\n{file}");
_log.Warn($"Failed reading .pls:\n{file}");
return null;
}
}
@ -156,7 +159,7 @@ namespace NadekoBot.Modules.Music.Classes
}
catch
{
Console.WriteLine($"Failed reading .m3u:\n{file}");
_log.Warn($"Failed reading .m3u:\n{file}");
return null;
}
@ -172,7 +175,7 @@ namespace NadekoBot.Modules.Music.Classes
}
catch
{
Console.WriteLine($"Failed reading .asx:\n{file}");
_log.Warn($"Failed reading .asx:\n{file}");
return null;
}
}
@ -192,7 +195,7 @@ namespace NadekoBot.Modules.Music.Classes
}
catch
{
Console.WriteLine($"Failed reading .xspf:\n{file}");
_log.Warn($"Failed reading .xspf:\n{file}");
return null;
}
}

View File

@ -14,14 +14,13 @@ using System.Net.Http;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using NadekoBot.Services.Database.Models;
using System.Text.RegularExpressions;
using System.Threading;
namespace NadekoBot.Modules.Music
{
[NadekoModule("Music", "!!")]
[DontAutoLoad]
public partial class Music : NadekoModule
public class Music : NadekoTopLevelModule
{
public static ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers { get; } = new ConcurrentDictionary<ulong, MusicPlayer>();
@ -78,7 +77,10 @@ namespace NadekoBot.Modules.Music
}
}
catch { }
catch
{
// ignored
}
return Task.CompletedTask;
}
@ -153,7 +155,14 @@ namespace NadekoBot.Modules.Music
return;
var val = musicPlayer.FairPlay = !musicPlayer.FairPlay;
await channel.SendConfirmAsync("Fair play " + (val ? "enabled" : "disabled") + ".").ConfigureAwait(false);
if (val)
{
await ReplyConfirmLocalized("fp_enabled").ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("fp_disabled").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -182,57 +191,56 @@ namespace NadekoBot.Modules.Music
[RequireContext(ContextType.Guild)]
public async Task ListQueue(int page = 1)
{
Song currentSong;
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer) ||
(currentSong = musicPlayer?.CurrentSong) == null)
{
await Context.Channel.SendErrorAsync("🎵 No active music player.").ConfigureAwait(false);
await ReplyErrorLocalized("no_player").ConfigureAwait(false);
return;
}
if (page <= 0)
return;
var currentSong = musicPlayer.CurrentSong;
if (currentSong == null)
{
await Context.Channel.SendErrorAsync("🎵 No active music player.").ConfigureAwait(false);
return;
}
try { await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { }
const int itemsPerPage = 10;
var total = musicPlayer.TotalPlaytime;
var totalStr = total == TimeSpan.MaxValue ? "∞" : $"{(int)total.TotalHours}h {total.Minutes}m {total.Seconds}s";
var totalStr = total == TimeSpan.MaxValue ? "∞" : GetText("time_format",
(int) total.TotalHours,
total.Minutes,
total.Seconds);
var maxPlaytime = musicPlayer.MaxPlaytimeSeconds;
var lastPage = musicPlayer.Playlist.Count / itemsPerPage;
Func<int, EmbedBuilder> printAction = (curPage) =>
Func<int, EmbedBuilder> printAction = curPage =>
{
int startAt = itemsPerPage * (curPage - 1);
var startAt = itemsPerPage * (curPage - 1);
var number = 0 + startAt;
var desc = string.Join("\n", musicPlayer.Playlist
.Skip(startAt)
.Take(itemsPerPage)
.Select(v => $"`{++number}.` {v.PrettyFullName}"));
if (currentSong != null)
desc = $"`🔊` {currentSong.PrettyFullName}\n\n" + desc;
desc = $"`🔊` {currentSong.PrettyFullName}\n\n" + desc;
if (musicPlayer.RepeatSong)
desc = "🔂 Repeating Current Song\n\n" + desc;
desc = "🔂 " + GetText("repeating_cur_song") +"\n\n" + desc;
else if (musicPlayer.RepeatPlaylist)
desc = "🔁 Repeating Playlist\n\n" + desc;
desc = "🔁 " + GetText("repeating_playlist")+"\n\n" + desc;
var embed = new EmbedBuilder()
.WithAuthor(eab => eab.WithName($"Player Queue - Page {curPage}/{lastPage + 1}")
.WithMusicIcon())
.WithAuthor(eab => eab.WithName(GetText("player_queue", curPage, lastPage + 1))
.WithMusicIcon())
.WithDescription(desc)
.WithFooter(ef => ef.WithText($"{musicPlayer.PrettyVolume} | {musicPlayer.Playlist.Count} " +
$"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {totalStr} | " +
(musicPlayer.FairPlay ? "✔fairplay" : "✖fairplay") + $" | " + (maxPlaytime == 0 ? "unlimited" : $"{maxPlaytime}s limit")))
$"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {totalStr} | " +
(musicPlayer.FairPlay
? "✔️" + GetText("fairplay")
: "✖️" + GetText("fairplay")) + " | " +
(maxPlaytime == 0 ? "unlimited" : GetText("play_limit", maxPlaytime))))
.WithOkColor();
return embed;
@ -253,7 +261,7 @@ namespace NadekoBot.Modules.Music
try { await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { }
var embed = new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName("Now Playing").WithMusicIcon())
.WithAuthor(eab => eab.WithName(GetText("now_playing")).WithMusicIcon())
.WithDescription(currentSong.PrettyName)
.WithThumbnailUrl(currentSong.Thumbnail)
.WithFooter(ef => ef.WithText(musicPlayer.PrettyVolume + " | " + currentSong.PrettyFullTime + $" | {currentSong.PrettyProvider} | {currentSong.QueuerName}"));
@ -270,21 +278,22 @@ namespace NadekoBot.Modules.Music
return;
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return;
if (val < 0)
if (val < 0 || val > 100)
{
await ReplyErrorLocalized("volume_input_invalid").ConfigureAwait(false);
return;
}
var volume = musicPlayer.SetVolume(val);
await Context.Channel.SendConfirmAsync($"🎵 Volume set to {volume}%").ConfigureAwait(false);
await ReplyConfirmLocalized("volume_set", volume).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Defvol([Remainder] int val)
{
if (val < 0 || val > 100)
{
await Context.Channel.SendErrorAsync("Volume number invalid. Must be between 0 and 100").ConfigureAwait(false);
await ReplyErrorLocalized("volume_input_invalid").ConfigureAwait(false);
return;
}
using (var uow = DbHandler.UnitOfWork())
@ -292,7 +301,7 @@ namespace NadekoBot.Modules.Music
uow.GuildConfigs.For(Context.Guild.Id, set => set).DefaultMusicVolume = val / 100.0f;
uow.Complete();
}
await Context.Channel.SendConfirmAsync($"🎵 Default volume set to {val}%").ConfigureAwait(false);
await ReplyConfirmLocalized("defvol_set", val).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -306,13 +315,10 @@ namespace NadekoBot.Modules.Music
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return;
if (musicPlayer.Playlist.Count < 2)
{
await Context.Channel.SendErrorAsync("💢 Not enough songs in order to perform the shuffle.").ConfigureAwait(false);
return;
}
musicPlayer.Shuffle();
await Context.Channel.SendConfirmAsync("🎵 Songs shuffled.").ConfigureAwait(false);
await ReplyConfirmLocalized("songs_shuffled").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -325,29 +331,29 @@ namespace NadekoBot.Modules.Music
return;
if (((IGuildUser)Context.User).VoiceChannel?.Guild != Context.Guild)
{
await Context.Channel.SendErrorAsync($"💢 You need to be in a **voice channel** on this server.").ConfigureAwait(false);
await ReplyErrorLocalized("must_be_in_voice").ConfigureAwait(false);
return;
}
var plId = (await NadekoBot.Google.GetPlaylistIdsByKeywordsAsync(arg).ConfigureAwait(false)).FirstOrDefault();
if (plId == null)
{
await Context.Channel.SendErrorAsync("No search results for that query.");
await ReplyErrorLocalized("no_search_results").ConfigureAwait(false);
return;
}
var ids = await NadekoBot.Google.GetPlaylistTracksAsync(plId, 500).ConfigureAwait(false);
if (!ids.Any())
{
await Context.Channel.SendErrorAsync($"🎵 Failed to find any songs.").ConfigureAwait(false);
await ReplyErrorLocalized("no_search_results").ConfigureAwait(false);
return;
}
var count = ids.Count();
var msg = await Context.Channel.SendMessageAsync($"🎵 Attempting to queue **{count}** songs".SnPl(count) + "...").ConfigureAwait(false);
var msg = await Context.Channel.SendMessageAsync("🎵 " + GetText("attempting_to_queue",
Format.Bold(count.ToString()))).ConfigureAwait(false);
var cancelSource = new CancellationTokenSource();
var gusr = (IGuildUser)Context.User;
//todo use grouping
while (ids.Any() && !cancelSource.IsCancellationRequested)
{
var tasks = Task.WhenAll(ids.Take(5).Select(async id =>
@ -366,7 +372,7 @@ namespace NadekoBot.Modules.Music
ids = ids.Skip(5);
}
await msg.ModifyAsync(m => m.Content = "✅ Playlist queue complete.").ConfigureAwait(false);
await msg.ModifyAsync(m => m.Content = "✅ " + Format.Bold(GetText("playlist_queue_complete"))).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -392,7 +398,7 @@ namespace NadekoBot.Modules.Music
{
try
{
mp.AddSong(new Song(new Classes.SongInfo
mp.AddSong(new Song(new SongInfo
{
Title = svideo.FullName,
Provider = "SoundCloud",
@ -434,20 +440,20 @@ namespace NadekoBot.Modules.Music
// ignored
}
}
await Context.Channel.SendConfirmAsync("🎵 Directory queue complete.").ConfigureAwait(false);
await ReplyConfirmLocalized("dir_queue_complete").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Radio(string radio_link)
public async Task Radio(string radioLink)
{
if (((IGuildUser)Context.User).VoiceChannel?.Guild != Context.Guild)
{
await Context.Channel.SendErrorAsync("💢 You need to be in a voice channel on this server.\n If you are already in a voice (ITextChannel)Context.Channel, try rejoining it.").ConfigureAwait(false);
await ReplyErrorLocalized("must_be_in_voice").ConfigureAwait(false);
return;
}
await QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, radio_link, musicType: MusicType.Radio).ConfigureAwait(false);
await QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, radioLink, musicType: MusicType.Radio).ConfigureAwait(false);
if ((await Context.Guild.GetCurrentUserAsync()).GetPermissions((IGuildChannel)Context.Channel).ManageMessages)
{
Context.Message.DeleteAfter(10);
@ -504,20 +510,20 @@ namespace NadekoBot.Modules.Music
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) return;
musicPlayer.ClearQueue();
await Context.Channel.SendConfirmAsync($"🎵 Queue cleared!").ConfigureAwait(false);
return;
await ReplyConfirmLocalized("queue_cleared").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task MoveSong([Remainder] string fromto)
{
if (string.IsNullOrWhiteSpace(fromto))
return;
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
{
return;
}
fromto = fromto?.Trim();
var fromtoArr = fromto.Split('>');
@ -530,7 +536,7 @@ namespace NadekoBot.Modules.Music
!int.TryParse(fromtoArr[1], out n2) || n1 < 1 || n2 < 1 || n1 == n2 ||
n1 > playlist.Count || n2 > playlist.Count)
{
await Context.Channel.SendErrorAsync("Invalid input.").ConfigureAwait(false);
await ReplyConfirmLocalized("invalid_input").ConfigureAwait(false);
return;
}
@ -541,10 +547,10 @@ namespace NadekoBot.Modules.Music
var embed = new EmbedBuilder()
.WithTitle($"{s.SongInfo.Title.TrimTo(70)}")
.WithUrl($"{s.SongInfo.Query}")
.WithAuthor(eab => eab.WithName("Song Moved").WithIconUrl("https://cdn.discordapp.com/attachments/155726317222887425/258605269972549642/music1.png"))
.AddField(fb => fb.WithName("**From Position**").WithValue($"#{n1}").WithIsInline(true))
.AddField(fb => fb.WithName("**To Position**").WithValue($"#{n2}").WithIsInline(true))
.WithUrl(s.SongUrl)
.WithAuthor(eab => eab.WithName(GetText("song_moved")).WithIconUrl("https://cdn.discordapp.com/attachments/155726317222887425/258605269972549642/music1.png"))
.AddField(fb => fb.WithName(GetText("from_position")).WithValue($"#{n1}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("to_position")).WithValue($"#{n2}").WithIsInline(true))
.WithColor(NadekoBot.OkColor);
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -562,7 +568,11 @@ namespace NadekoBot.Modules.Music
return;
musicPlayer.MaxQueueSize = size;
await Context.Channel.SendConfirmAsync($"🎵 Max queue set to {(size == 0 ? ("unlimited") : size + " tracks")}.");
if(size == 0)
await ReplyConfirmLocalized("max_queue_unlimited").ConfigureAwait(false);
else
await ReplyConfirmLocalized("max_queue_x", size).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -578,9 +588,9 @@ namespace NadekoBot.Modules.Music
return;
musicPlayer.MaxPlaytimeSeconds = seconds;
if (seconds == 0)
await channel.SendConfirmAsync($"🎵 Max playtime has no limit now.");
await ReplyConfirmLocalized("max_playtime_none").ConfigureAwait(false);
else
await channel.SendConfirmAsync($"🎵 Max playtime set to {seconds} seconds.");
await ReplyConfirmLocalized("max_playtime_set", seconds).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -599,11 +609,11 @@ namespace NadekoBot.Modules.Music
if (currentValue)
await Context.Channel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithAuthor(eab => eab.WithMusicIcon().WithName("🔂 Repeating track"))
.WithAuthor(eab => eab.WithMusicIcon().WithName("🔂 " + GetText("repeating_track")))
.WithDescription(currentSong.PrettyName)
.WithFooter(ef => ef.WithText(currentSong.PrettyInfo))).ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync($"🔂 Current track repeat stopped.")
await Context.Channel.SendConfirmAsync("🔂 " + GetText("repeating_track_stopped"))
.ConfigureAwait(false);
}
@ -616,7 +626,10 @@ namespace NadekoBot.Modules.Music
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
return;
var currentValue = musicPlayer.ToggleRepeatPlaylist();
await Context.Channel.SendConfirmAsync($"🔁 Repeat playlist {(currentValue ? "**enabled**." : "**disabled**.")}").ConfigureAwait(false);
if(currentValue)
await ReplyConfirmLocalized("rpl_enabled").ConfigureAwait(false);
else
await ReplyConfirmLocalized("rpl_disabled").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -653,7 +666,10 @@ namespace NadekoBot.Modules.Music
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync(($"🎵 Saved playlist as **{name}**, ID: {playlist.Id}.")).ConfigureAwait(false);
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("playlist_saved"))
.AddField(efb => efb.WithName(GetText("name")).WithValue(name))
.AddField(efb => efb.WithName(GetText("id")).WithValue(playlist.Id.ToString())));
}
[NadekoCommand, Usage, Description, Aliases]
@ -668,11 +684,11 @@ namespace NadekoBot.Modules.Music
if (mpl == null)
{
await Context.Channel.SendErrorAsync("Can't find playlist with that ID.").ConfigureAwait(false);
await ReplyErrorLocalized("playlist_id_not_found").ConfigureAwait(false);
return;
}
IUserMessage msg = null;
try { msg = await Context.Channel.SendMessageAsync($"🎶 Attempting to load **{mpl.Songs.Count}** songs...").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
try { msg = await Context.Channel.SendMessageAsync(GetText("attempting_to_queue", Format.Bold(mpl.Songs.Count.ToString()))).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
foreach (var item in mpl.Songs)
{
var usr = (IGuildUser)Context.User;
@ -684,15 +700,13 @@ namespace NadekoBot.Modules.Music
catch { break; }
}
if (msg != null)
await msg.ModifyAsync(m => m.Content = $"✅ Done loading playlist **{mpl.Name}**.").ConfigureAwait(false);
await msg.ModifyAsync(m => m.Content = GetText("playlist_queue_complete")).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Playlists([Remainder] int num = 1)
{
if (num <= 0)
return;
@ -704,8 +718,9 @@ namespace NadekoBot.Modules.Music
}
var embed = new EmbedBuilder()
.WithAuthor(eab => eab.WithName($"Page {num} of Saved Playlists").WithMusicIcon())
.WithDescription(string.Join("\n", playlists.Select(r => $"`#{r.Id}` - **{r.Name}** by *{r.Author}* ({r.Songs.Count} songs)")))
.WithAuthor(eab => eab.WithName(GetText("playlists_page", num)).WithMusicIcon())
.WithDescription(string.Join("\n", playlists.Select(r =>
GetText("playlists", r.Id, r.Name, r.Author, r.Songs.Count))))
.WithOkColor();
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -715,13 +730,12 @@ namespace NadekoBot.Modules.Music
[RequireContext(ContextType.Guild)]
public async Task DeletePlaylist([Remainder] int id)
{
bool success = false;
MusicPlaylist pl = null;
var success = false;
try
{
using (var uow = DbHandler.UnitOfWork())
{
pl = uow.MusicPlaylists.Get(id);
var pl = uow.MusicPlaylists.Get(id);
if (pl != null)
{
@ -731,15 +745,13 @@ namespace NadekoBot.Modules.Music
await uow.CompleteAsync().ConfigureAwait(false);
success = true;
}
else
success = false;
}
}
if (!success)
await Context.Channel.SendErrorAsync("Failed to delete that playlist. It either doesn't exist, or you are not its author.").ConfigureAwait(false);
await ReplyErrorLocalized("playlist_delete_fail").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync("🗑 Playlist successfully **deleted**.").ConfigureAwait(false);
await ReplyConfirmLocalized("playlist_deleted").ConfigureAwait(false);
}
catch (Exception ex)
{
@ -779,7 +791,7 @@ namespace NadekoBot.Modules.Music
if (seconds.Length == 1)
seconds = "0" + seconds;
await Context.Channel.SendConfirmAsync($"Skipped to `{minutes}:{seconds}`").ConfigureAwait(false);
await ReplyConfirmLocalized("skipped_to", minutes, seconds).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -791,9 +803,9 @@ namespace NadekoBot.Modules.Music
return;
if (!musicPlayer.ToggleAutoplay())
await Context.Channel.SendConfirmAsync("❌ Autoplay disabled.").ConfigureAwait(false);
await ReplyConfirmLocalized("autoplay_disabled").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync("✅ Autoplay enabled.").ConfigureAwait(false);
await ReplyConfirmLocalized("autoplay_enabled").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -804,29 +816,29 @@ namespace NadekoBot.Modules.Music
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
{
await Context.Channel.SendErrorAsync("Music must be playing before you set an ouput channel.").ConfigureAwait(false);
await ReplyErrorLocalized("no_player").ConfigureAwait(false);
return;
}
musicPlayer.OutputTextChannel = (ITextChannel)Context.Channel;
await Context.Channel.SendConfirmAsync("I will now output playing, finished, paused and removed songs in this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("set_music_channel").ConfigureAwait(false);
}
public static async Task QueueSong(IGuildUser queuer, ITextChannel textCh, IVoiceChannel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal)
public async Task QueueSong(IGuildUser queuer, ITextChannel textCh, IVoiceChannel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal)
{
if (voiceCh == null || voiceCh.Guild != textCh.Guild)
{
if (!silent)
await textCh.SendErrorAsync($"💢 You need to be in a voice channel on this server.").ConfigureAwait(false);
await textCh.SendErrorAsync(GetText("must_be_in_voice")).ConfigureAwait(false);
throw new ArgumentNullException(nameof(voiceCh));
}
if (string.IsNullOrWhiteSpace(query) || query.Length < 3)
throw new ArgumentException("💢 Invalid query for queue song.", nameof(query));
throw new ArgumentException("Invalid song query.", nameof(query));
var musicPlayer = MusicPlayers.GetOrAdd(textCh.Guild.Id, server =>
{
float vol = 1;// SpecificConfigurations.Default.Of(server.Id).DefaultMusicVolume;
float vol;// SpecificConfigurations.Default.Of(server.Id).DefaultMusicVolume;
using (var uow = DbHandler.UnitOfWork())
{
vol = uow.GuildConfigs.For(textCh.Guild.Id, set => set).DefaultMusicVolume;
@ -843,7 +855,7 @@ namespace NadekoBot.Modules.Music
try
{
lastFinishedMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName("Finished Song").WithMusicIcon())
.WithAuthor(eab => eab.WithName(GetText("finished_song")).WithMusicIcon())
.WithDescription(song.PrettyName)
.WithFooter(ef => ef.WithText(song.PrettyInfo)))
.ConfigureAwait(false);
@ -861,17 +873,23 @@ namespace NadekoBot.Modules.Music
textCh,
voiceCh,
relatedVideos[new NadekoRandom().Next(0, relatedVideos.Count)],
true,
musicType).ConfigureAwait(false);
true).ConfigureAwait(false);
}
}
catch { }
catch
{
// ignored
}
};
mp.OnStarted += async (player, song) =>
{
try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { }
var sender = player as MusicPlayer;
try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); }
catch
{
// ignored
}
var sender = player;
if (sender == null)
return;
try
@ -879,12 +897,15 @@ namespace NadekoBot.Modules.Music
playingMessage?.DeleteAfter(0);
playingMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName("Playing Song").WithMusicIcon())
.WithAuthor(eab => eab.WithName(GetText("playing_song")).WithMusicIcon())
.WithDescription(song.PrettyName)
.WithFooter(ef => ef.WithText(song.PrettyInfo)))
.ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
};
mp.OnPauseChanged += async (paused) =>
{
@ -892,13 +913,16 @@ namespace NadekoBot.Modules.Music
{
IUserMessage msg;
if (paused)
msg = await mp.OutputTextChannel.SendConfirmAsync("🎵 Music playback **paused**.").ConfigureAwait(false);
msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("paused")).ConfigureAwait(false);
else
msg = await mp.OutputTextChannel.SendConfirmAsync("🎵 Music playback **resumed**.").ConfigureAwait(false);
msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("resumed")).ConfigureAwait(false);
msg?.DeleteAfter(10);
}
catch { }
catch
{
// ignored
}
};
mp.SongRemoved += async (song, index) =>
@ -906,7 +930,7 @@ namespace NadekoBot.Modules.Music
try
{
var embed = new EmbedBuilder()
.WithAuthor(eab => eab.WithName("Removed song #" + (index + 1)).WithMusicIcon())
.WithAuthor(eab => eab.WithName(GetText("removed_song") + " #" + (index + 1)).WithMusicIcon())
.WithDescription(song.PrettyName)
.WithFooter(ef => ef.WithText(song.PrettyInfo))
.WithErrorColor();
@ -914,7 +938,10 @@ namespace NadekoBot.Modules.Music
await mp.OutputTextChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
};
return mp;
});
@ -931,7 +958,14 @@ namespace NadekoBot.Modules.Music
}
catch (PlaylistFullException)
{
try { await textCh.SendConfirmAsync($"🎵 Queue is full at **{musicPlayer.MaxQueueSize}/{musicPlayer.MaxQueueSize}**."); } catch { }
try
{
await textCh.SendConfirmAsync(GetText("queue_full", musicPlayer.MaxQueueSize));
}
catch
{
// ignored
}
throw;
}
if (!silent)
@ -940,15 +974,17 @@ namespace NadekoBot.Modules.Music
{
//var queuedMessage = await textCh.SendConfirmAsync($"🎵 Queued **{resolvedSong.SongInfo.Title}** at `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false);
var queuedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName("Queued Song #" + (musicPlayer.Playlist.Count + 1)).WithMusicIcon())
.WithDescription($"{resolvedSong.PrettyName}\nQueue ")
.WithAuthor(eab => eab.WithName(GetText("queued_song") + " #" + (musicPlayer.Playlist.Count + 1)).WithMusicIcon())
.WithDescription($"{resolvedSong.PrettyName}\n{GetText("queue")} ")
.WithThumbnailUrl(resolvedSong.Thumbnail)
.WithFooter(ef => ef.WithText(resolvedSong.PrettyProvider)))
.ConfigureAwait(false);
if (queuedMessage != null)
queuedMessage.DeleteAfter(10);
queuedMessage?.DeleteAfter(10);
}
catch { } // if queued message sending fails, don't attempt to delete it
catch
{
// ignored
} // if queued message sending fails, don't attempt to delete it
}
}
}

View File

@ -7,8 +7,6 @@ using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Services;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using NadekoBot.Extensions;
using System.Xml;
using System.Threading;
@ -17,11 +15,11 @@ using System.Collections.Concurrent;
namespace NadekoBot.Modules.NSFW
{
[NadekoModule("NSFW", "~")]
public class NSFW : NadekoModule
public class NSFW : NadekoTopLevelModule
{
private static readonly ConcurrentDictionary<ulong, Timer> AutoHentaiTimers = new ConcurrentDictionary<ulong, Timer>();
private static readonly ConcurrentHashSet<ulong> HentaiBombBlacklist = new ConcurrentHashSet<ulong>();
private static readonly ConcurrentDictionary<ulong, Timer> _autoHentaiTimers = new ConcurrentDictionary<ulong, Timer>();
private static readonly ConcurrentHashSet<ulong> _hentaiBombBlacklist = new ConcurrentHashSet<ulong>();
private async Task InternalHentai(IMessageChannel channel, string tag, bool noError)
{
@ -72,7 +70,7 @@ namespace NadekoBot.Modules.NSFW
if (interval == 0)
{
if (!AutoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return;
if (!_autoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return;
t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer
await ReplyConfirmLocalized("autohentai_stopped").ConfigureAwait(false);
@ -99,7 +97,7 @@ namespace NadekoBot.Modules.NSFW
}
}, null, interval * 1000, interval * 1000);
AutoHentaiTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) =>
_autoHentaiTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) =>
{
old.Change(Timeout.Infinite, Timeout.Infinite);
return t;
@ -114,7 +112,7 @@ namespace NadekoBot.Modules.NSFW
[NadekoCommand, Usage, Description, Aliases]
public async Task HentaiBomb([Remainder] string tag = null)
{
if (!HentaiBombBlacklist.Add(Context.User.Id))
if (!_hentaiBombBlacklist.Add(Context.User.Id))
return;
try
{
@ -138,17 +136,17 @@ namespace NadekoBot.Modules.NSFW
finally
{
await Task.Delay(5000).ConfigureAwait(false);
HentaiBombBlacklist.TryRemove(Context.User.Id);
_hentaiBombBlacklist.TryRemove(Context.User.Id);
}
}
#endif
[NadekoCommand, Usage, Description, Aliases]
public Task Yandere([Remainder] string tag = null)
=> Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Yandere);
=> InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Yandere);
[NadekoCommand, Usage, Description, Aliases]
public Task Konachan([Remainder] string tag = null)
=> Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Konachan);
=> InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Konachan);
[NadekoCommand, Usage, Description, Aliases]
public async Task E621([Remainder] string tag = null)
@ -169,7 +167,7 @@ namespace NadekoBot.Modules.NSFW
[NadekoCommand, Usage, Description, Aliases]
public Task Rule34([Remainder] string tag = null)
=> Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Rule34);
=> InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Rule34);
[NadekoCommand, Usage, Description, Aliases]
public async Task Danbooru([Remainder] string tag = null)
@ -212,13 +210,7 @@ namespace NadekoBot.Modules.NSFW
[NadekoCommand, Usage, Description, Aliases]
public Task Gelbooru([Remainder] string tag = null)
=> Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Gelbooru);
[NadekoCommand, Usage, Description, Aliases]
public async Task Cp()
{
await Context.Channel.SendMessageAsync("http://i.imgur.com/MZkY1md.jpg").ConfigureAwait(false);
}
=> InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Gelbooru);
[NadekoCommand, Usage, Description, Aliases]
public async Task Boobs()
@ -289,5 +281,20 @@ namespace NadekoBot.Modules.NSFW
public static Task<string> GetGelbooruImageLink(string tag) =>
Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Gelbooru);
public async Task InternalDapiCommand(string tag, Searches.Searches.DapiSearchType type)
{
tag = tag?.Trim() ?? "";
var url = await Searches.Searches.InternalDapiSearch(tag, type).ConfigureAwait(false);
if (url == null)
await ReplyErrorLocalized("not_found").ConfigureAwait(false);
else
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription(Context.User + " " + tag)
.WithImageUrl(url)
.WithFooter(efb => efb.WithText(type.ToString()))).ConfigureAwait(false);
}
}
}

View File

@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace NadekoBot.Modules
{
public abstract class NadekoModule : ModuleBase
public abstract class NadekoTopLevelModule : ModuleBase
{
protected readonly Logger _log;
protected CultureInfo _cultureInfo;
@ -17,7 +17,7 @@ namespace NadekoBot.Modules
public readonly string ModuleTypeName;
public readonly string LowerModuleTypeName;
protected NadekoModule(bool isTopLevelModule = true)
protected NadekoTopLevelModule(bool isTopLevelModule = true)
{
//if it's top level module
ModuleTypeName = isTopLevelModule ? this.GetType().Name : this.GetType().DeclaringType.Name;
@ -69,7 +69,7 @@ namespace NadekoBot.Modules
LogManager.GetCurrentClassLogger().Warn(lowerModuleTypeName + "_" + key + " key is missing from " + cultureInfo + " response strings. PLEASE REPORT THIS.");
text = NadekoBot.ResponsesResourceManager.GetString(lowerModuleTypeName + "_" + key, _usCultureInfo) ?? $"Error: dkey {lowerModuleTypeName + "_" + key} not found!";
if (string.IsNullOrWhiteSpace(text))
return "I cant tell if you command is executed, because there was an error printing out the response. Key '" +
return "I can't tell you if the command is executed, because there was an error printing out the response. Key '" +
lowerModuleTypeName + "_" + key + "' " + "is missing from resources. Please report this.";
}
return text;
@ -84,7 +84,7 @@ namespace NadekoBot.Modules
}
catch (FormatException)
{
return "I cant tell if you command is executed, because there was an error printing out the response. Key '" +
return "I can't tell you if the command is executed, because there was an error printing out the response. Key '" +
lowerModuleTypeName + "_" + key + "' " + "is not properly formatted. Please report this.";
}
}
@ -120,7 +120,7 @@ namespace NadekoBot.Modules
}
}
public abstract class NadekoSubmodule : NadekoModule
public abstract class NadekoSubmodule : NadekoTopLevelModule
{
protected NadekoSubmodule() : base(false)
{

View File

@ -21,11 +21,11 @@ namespace NadekoBot.Modules.Permissions
}
[Group]
public class BlacklistCommands : ModuleBase
public class BlacklistCommands : NadekoSubmodule
{
public static ConcurrentHashSet<ulong> BlacklistedUsers { get; set; } = new ConcurrentHashSet<ulong>();
public static ConcurrentHashSet<ulong> BlacklistedGuilds { get; set; } = new ConcurrentHashSet<ulong>();
public static ConcurrentHashSet<ulong> BlacklistedChannels { get; set; } = new ConcurrentHashSet<ulong>();
public static ConcurrentHashSet<ulong> BlacklistedUsers { get; set; }
public static ConcurrentHashSet<ulong> BlacklistedGuilds { get; set; }
public static ConcurrentHashSet<ulong> BlacklistedChannels { get; set; }
static BlacklistCommands()
{
@ -115,7 +115,7 @@ namespace NadekoBot.Modules.Permissions
}
break;
case BlacklistType.Channel:
var item = Games.Games.TriviaCommands.RunningTrivias.FirstOrDefault(kvp => kvp.Value.channel.Id == id);
var item = Games.Games.TriviaCommands.RunningTrivias.FirstOrDefault(kvp => kvp.Value.Channel.Id == id);
Games.Games.TriviaCommands.RunningTrivias.TryRemove(item.Key, out tg);
if (tg != null)
{
@ -124,16 +124,14 @@ namespace NadekoBot.Modules.Permissions
break;
case BlacklistType.User:
break;
default:
break;
}
}
if(action == AddRemove.Add)
await Context.Channel.SendConfirmAsync($"Blacklisted a `{type}` with id `{id}`").ConfigureAwait(false);
await ReplyConfirmLocalized("blacklisted", Format.Code(type.ToString()), Format.Code(id.ToString())).ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync($"Unblacklisted a `{type}` with id `{id}`").ConfigureAwait(false);
await ReplyConfirmLocalized("unblacklisted", Format.Code(type.ToString()), Format.Code(id.ToString())).ConfigureAwait(false);
}
}
}

View File

@ -21,15 +21,15 @@ namespace NadekoBot.Modules.Permissions
}
[Group]
public class CmdCdsCommands : ModuleBase
public class CmdCdsCommands : NadekoSubmodule
{
public static ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>> commandCooldowns { get; }
public static ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>> CommandCooldowns { get; }
private static ConcurrentDictionary<ulong, ConcurrentHashSet<ActiveCooldown>> activeCooldowns { get; } = new ConcurrentDictionary<ulong, ConcurrentHashSet<ActiveCooldown>>();
static CmdCdsCommands()
{
var configs = NadekoBot.AllGuildConfigs;
commandCooldowns = new ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>>(configs.ToDictionary(k => k.GuildId, v => new ConcurrentHashSet<CommandCooldown>(v.CommandCooldowns)));
CommandCooldowns = new ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>>(configs.ToDictionary(k => k.GuildId, v => new ConcurrentHashSet<CommandCooldown>(v.CommandCooldowns)));
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
@ -38,14 +38,14 @@ namespace NadekoBot.Modules.Permissions
var channel = (ITextChannel)Context.Channel;
if (secs < 0 || secs > 3600)
{
await channel.SendErrorAsync("Invalid second parameter. (Must be a number between 0 and 3600)").ConfigureAwait(false);
await ReplyErrorLocalized("invalid_second_param_between", 0, 3600).ConfigureAwait(false);
return;
}
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.CommandCooldowns));
var localSet = commandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CommandCooldown>());
var localSet = CommandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CommandCooldown>());
config.CommandCooldowns.RemoveWhere(cc => cc.CommandName == command.Aliases.First().ToLowerInvariant());
localSet.RemoveWhere(cc => cc.CommandName == command.Aliases.First().ToLowerInvariant());
@ -65,13 +65,14 @@ namespace NadekoBot.Modules.Permissions
{
var activeCds = activeCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<ActiveCooldown>());
activeCds.RemoveWhere(ac => ac.Command == command.Aliases.First().ToLowerInvariant());
await channel.SendConfirmAsync($"🚮 Command **{command.Aliases.First()}** has no coooldown now and all existing cooldowns have been cleared.")
.ConfigureAwait(false);
await ReplyConfirmLocalized("cmdcd_cleared",
Format.Bold(command.Aliases.First())).ConfigureAwait(false);
}
else
{
await channel.SendConfirmAsync($"✅ Command **{command.Aliases.First()}** now has a **{secs} {"seconds".SnPl(secs)}** cooldown.")
.ConfigureAwait(false);
await ReplyConfirmLocalized("cmdcd_add",
Format.Bold(command.Aliases.First()),
Format.Bold(secs.ToString())).ConfigureAwait(false);
}
}
@ -80,19 +81,19 @@ namespace NadekoBot.Modules.Permissions
public async Task AllCmdCooldowns()
{
var channel = (ITextChannel)Context.Channel;
var localSet = commandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CommandCooldown>());
var localSet = CommandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CommandCooldown>());
if (!localSet.Any())
await channel.SendConfirmAsync(" `No command cooldowns set.`").ConfigureAwait(false);
await ReplyConfirmLocalized("cmdcd_none").ConfigureAwait(false);
else
await channel.SendTableAsync("", localSet.Select(c => c.CommandName + ": " + c.Seconds + " secs"), s => $"{s,-30}", 2).ConfigureAwait(false);
await channel.SendTableAsync("", localSet.Select(c => c.CommandName + ": " + c.Seconds + GetText("sec")), s => $"{s,-30}", 2).ConfigureAwait(false);
}
public static bool HasCooldown(CommandInfo cmd, IGuild guild, IUser user)
{
if (guild == null)
return false;
var cmdcds = CmdCdsCommands.commandCooldowns.GetOrAdd(guild.Id, new ConcurrentHashSet<CommandCooldown>());
var cmdcds = CmdCdsCommands.CommandCooldowns.GetOrAdd(guild.Id, new ConcurrentHashSet<CommandCooldown>());
CommandCooldown cdRule;
if ((cdRule = cmdcds.FirstOrDefault(cc => cc.CommandName == cmd.Aliases.First().ToLowerInvariant())) != null)
{

View File

@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Permissions
public partial class Permissions
{
[Group]
public class CommandCostCommands : ModuleBase
public class CommandCostCommands : NadekoSubmodule
{
private static readonly ConcurrentDictionary<string, int> _commandCosts = new ConcurrentDictionary<string, int>();
public static IReadOnlyDictionary<string, int> CommandCosts => _commandCosts;
@ -29,29 +29,29 @@ namespace NadekoBot.Modules.Permissions
// x => x.Cost));
}
[NadekoCommand, Usage, Description, Aliases]
public async Task CmdCosts(int page = 1)
{
var prices = _commandCosts.ToList();
//[NadekoCommand, Usage, Description, Aliases]
//public async Task CmdCosts(int page = 1)
//{
// var prices = _commandCosts.ToList();
if (!prices.Any())
{
await Context.Channel.SendConfirmAsync("No costs set.").ConfigureAwait(false);
return;
}
// if (!prices.Any())
// {
// await Context.Channel.SendConfirmAsync(GetText("no_costs")).ConfigureAwait(false);
// return;
// }
await Context.Channel.SendPaginatedConfirmAsync(page, (curPage) => {
var embed = new EmbedBuilder().WithOkColor()
.WithTitle("Command Costs");
var current = prices.Skip((curPage - 1) * 9)
.Take(9);
foreach (var price in current)
{
embed.AddField(efb => efb.WithName(price.Key).WithValue(price.Value.ToString()).WithIsInline(true));
}
return embed;
}, prices.Count / 9).ConfigureAwait(false);
}
// await Context.Channel.SendPaginatedConfirmAsync(page, (curPage) => {
// var embed = new EmbedBuilder().WithOkColor()
// .WithTitle(GetText("command_costs"));
// var current = prices.Skip((curPage - 1) * 9)
// .Take(9);
// foreach (var price in current)
// {
// embed.AddField(efb => efb.WithName(price.Key).WithValue(price.Value.ToString()).WithIsInline(true));
// }
// return embed;
// }, prices.Count / 9).ConfigureAwait(false);
//}
//[NadekoCommand, Usage, Description, Aliases]
//public async Task CommandCost(int cost, CommandInfo cmd)

View File

@ -13,13 +13,13 @@ namespace NadekoBot.Modules.Permissions
public partial class Permissions
{
[Group]
public class FilterCommands : ModuleBase
public class FilterCommands : NadekoSubmodule
{
public static ConcurrentHashSet<ulong> InviteFilteringChannels { get; }
public static ConcurrentHashSet<ulong> InviteFilteringServers { get; }
//serverid, filteredwords
private static ConcurrentDictionary<ulong, ConcurrentHashSet<string>> ServerFilteredWords { get; }
private static ConcurrentDictionary<ulong, ConcurrentHashSet<string>> serverFilteredWords { get; }
public static ConcurrentHashSet<ulong> WordFilteringChannels { get; }
public static ConcurrentHashSet<ulong> WordFilteringServers { get; }
@ -28,7 +28,7 @@ namespace NadekoBot.Modules.Permissions
{
ConcurrentHashSet<string> words = new ConcurrentHashSet<string>();
if(WordFilteringChannels.Contains(channelId))
ServerFilteredWords.TryGetValue(guildId, out words);
serverFilteredWords.TryGetValue(guildId, out words);
return words;
}
@ -36,7 +36,7 @@ namespace NadekoBot.Modules.Permissions
{
var words = new ConcurrentHashSet<string>();
if(WordFilteringServers.Contains(guildId))
ServerFilteredWords.TryGetValue(guildId, out words);
serverFilteredWords.TryGetValue(guildId, out words);
return words;
}
@ -49,7 +49,7 @@ namespace NadekoBot.Modules.Permissions
var dict = guildConfigs.ToDictionary(gc => gc.GuildId, gc => new ConcurrentHashSet<string>(gc.FilteredWords.Select(fw => fw.Word)));
ServerFilteredWords = new ConcurrentDictionary<ulong, ConcurrentHashSet<string>>(dict);
serverFilteredWords = new ConcurrentDictionary<ulong, ConcurrentHashSet<string>>(dict);
var serverFiltering = guildConfigs.Where(gc => gc.FilterWords);
WordFilteringServers = new ConcurrentHashSet<ulong>(serverFiltering.Select(gc => gc.GuildId));
@ -74,12 +74,12 @@ namespace NadekoBot.Modules.Permissions
if (enabled)
{
InviteFilteringServers.Add(channel.Guild.Id);
await channel.SendConfirmAsync("Invite filtering enabled on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("invite_filter_server_on").ConfigureAwait(false);
}
else
{
InviteFilteringServers.TryRemove(channel.Guild.Id);
await channel.SendConfirmAsync("Invite filtering disabled on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("invite_filter_server_off").ConfigureAwait(false);
}
}
@ -107,12 +107,11 @@ namespace NadekoBot.Modules.Permissions
if (removed == 0)
{
InviteFilteringChannels.Add(channel.Id);
await channel.SendConfirmAsync("Invite filtering enabled on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("invite_filter_channel_on").ConfigureAwait(false);
}
else
{
InviteFilteringChannels.TryRemove(channel.Id);
await channel.SendConfirmAsync("Invite filtering disabled on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("invite_filter_channel_off").ConfigureAwait(false);
}
}
@ -133,12 +132,12 @@ namespace NadekoBot.Modules.Permissions
if (enabled)
{
WordFilteringServers.Add(channel.Guild.Id);
await channel.SendConfirmAsync("Word filtering enabled on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("word_filter_server_on").ConfigureAwait(false);
}
else
{
WordFilteringServers.TryRemove(channel.Guild.Id);
await channel.SendConfirmAsync("Word filtering disabled on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("word_filter_server_off").ConfigureAwait(false);
}
}
@ -166,12 +165,12 @@ namespace NadekoBot.Modules.Permissions
if (removed == 0)
{
WordFilteringChannels.Add(channel.Id);
await channel.SendConfirmAsync("Word filtering enabled on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("word_filter_channel_on").ConfigureAwait(false);
}
else
{
WordFilteringChannels.TryRemove(channel.Id);
await channel.SendConfirmAsync("Word filtering disabled on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("word_filter_channel_off").ConfigureAwait(false);
}
}
@ -199,19 +198,17 @@ namespace NadekoBot.Modules.Permissions
await uow.CompleteAsync().ConfigureAwait(false);
}
var filteredWords = ServerFilteredWords.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<string>());
var filteredWords = serverFilteredWords.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<string>());
if (removed == 0)
{
filteredWords.Add(word);
await channel.SendConfirmAsync($"Word `{word}` successfully added to the list of filtered words.")
.ConfigureAwait(false);
await ReplyConfirmLocalized("filter_word_add", Format.Code(word)).ConfigureAwait(false);
}
else
{
filteredWords.TryRemove(word);
await channel.SendConfirmAsync($"Word `{word}` removed from the list of filtered words.")
.ConfigureAwait(false);
await ReplyConfirmLocalized("filter_word_remove", Format.Code(word)).ConfigureAwait(false);
}
}
@ -222,9 +219,9 @@ namespace NadekoBot.Modules.Permissions
var channel = (ITextChannel)Context.Channel;
ConcurrentHashSet<string> filteredWords;
ServerFilteredWords.TryGetValue(channel.Guild.Id, out filteredWords);
serverFilteredWords.TryGetValue(channel.Guild.Id, out filteredWords);
await channel.SendConfirmAsync($"List of filtered words", string.Join("\n", filteredWords))
await channel.SendConfirmAsync(GetText("filter_word_list"), string.Join("\n", filteredWords))
.ConfigureAwait(false);
}
}

View File

@ -0,0 +1,118 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.DataStructures;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.TypeReaders;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Permissions
{
public partial class Permissions
{
[Group]
public class GlobalPermissionCommands : NadekoSubmodule
{
public static readonly ConcurrentHashSet<string> BlockedModules;
public static readonly ConcurrentHashSet<string> BlockedCommands;
static GlobalPermissionCommands()
{
BlockedModules = new ConcurrentHashSet<string>(NadekoBot.BotConfig.BlockedModules.Select(x => x.Name));
BlockedCommands = new ConcurrentHashSet<string>(NadekoBot.BotConfig.BlockedCommands.Select(x => x.Name));
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task Lgp()
{
if (!BlockedModules.Any() && !BlockedCommands.Any())
{
await ReplyErrorLocalized("lgp_none").ConfigureAwait(false);
return;
}
var embed = new EmbedBuilder().WithOkColor();
if (BlockedModules.Any())
embed.AddField(efb => efb.WithName(GetText("blocked_modules")).WithValue(string.Join("\n", BlockedModules)).WithIsInline(false));
if (BlockedCommands.Any())
embed.AddField(efb => efb.WithName(GetText("blocked_commands")).WithValue(string.Join("\n", BlockedCommands)).WithIsInline(false));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task Gmod(ModuleOrCrInfo module)
{
var moduleName = module.Name.ToLowerInvariant();
if (BlockedModules.Add(moduleName))
{
using (var uow = DbHandler.UnitOfWork())
{
var bc = uow.BotConfig.GetOrCreate();
bc.BlockedModules.Add(new Services.Database.Models.BlockedCmdOrMdl
{
Name = moduleName,
});
uow.Complete();
}
await ReplyConfirmLocalized("gmod_add", Format.Bold(module.Name)).ConfigureAwait(false);
return;
}
else if (BlockedModules.TryRemove(moduleName))
{
using (var uow = DbHandler.UnitOfWork())
{
var bc = uow.BotConfig.GetOrCreate();
bc.BlockedModules.RemoveWhere(x => x.Name == moduleName);
uow.Complete();
}
await ReplyConfirmLocalized("gmod_remove", Format.Bold(module.Name)).ConfigureAwait(false);
return;
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task Gcmd(CommandOrCrInfo cmd)
{
var commandName = cmd.Name.ToLowerInvariant();
if (BlockedCommands.Add(commandName))
{
using (var uow = DbHandler.UnitOfWork())
{
var bc = uow.BotConfig.GetOrCreate();
bc.BlockedCommands.Add(new Services.Database.Models.BlockedCmdOrMdl
{
Name = commandName,
});
uow.Complete();
}
await ReplyConfirmLocalized("gcmd_add", Format.Bold(cmd.Name)).ConfigureAwait(false);
return;
}
else if (BlockedCommands.TryRemove(commandName))
{
using (var uow = DbHandler.UnitOfWork())
{
var bc = uow.BotConfig.GetOrCreate();
bc.BlockedCommands.RemoveWhere(x => x.Name == commandName);
uow.Complete();
}
await ReplyConfirmLocalized("gcmd_remove", Format.Bold(cmd.Name)).ConfigureAwait(false);
return;
}
}
}
}
}

View File

@ -10,25 +10,12 @@ namespace NadekoBot.Modules.Permissions
{
public static class PermissionExtensions
{
public static bool CheckPermissions(this IEnumerable<Permission> permsEnumerable, IUserMessage message, CommandInfo command)
public static bool CheckPermissions(this IEnumerable<Permissionv2> permsEnumerable, IUserMessage message,
string commandName, string moduleName, out int permIndex)
{
var perms = permsEnumerable as List<Permission> ?? permsEnumerable.ToList();
int throwaway;
return perms.CheckPermissions(message, command.Name, command.Module.Name, out throwaway);
}
var perms = permsEnumerable as List<Permissionv2> ?? permsEnumerable.ToList();
public static bool CheckPermissions(this IEnumerable<Permission> permsEnumerable, IUserMessage message, string commandName, string moduleName)
{
var perms = permsEnumerable as List<Permission> ?? permsEnumerable.ToList();
int throwaway;
return perms.CheckPermissions(message, commandName, moduleName, out throwaway);
}
public static bool CheckPermissions(this IEnumerable<Permission> permsEnumerable, IUserMessage message, string commandName, string moduleName, out int permIndex)
{
var perms = permsEnumerable as List<Permission> ?? permsEnumerable.ToList();
for (int i = 0; i < perms.Count; i++)
for (int i = perms.Count - 1; i >= 0; i--)
{
var perm = perms[i];
@ -38,11 +25,8 @@ namespace NadekoBot.Modules.Permissions
{
continue;
}
else
{
permIndex = i;
return result.Value;
}
permIndex = i;
return result.Value;
}
permIndex = -1; //defaut behaviour
return true;
@ -51,7 +35,7 @@ namespace NadekoBot.Modules.Permissions
//null = not applicable
//true = applicable, allowed
//false = applicable, not allowed
public static bool? CheckPermission(this Permission perm, IUserMessage message, string commandName, string moduleName)
public static bool? CheckPermission(this Permissionv2 perm, IUserMessage message, string commandName, string moduleName)
{
if (!((perm.SecondaryTarget == SecondaryPermissionType.Command &&
perm.SecondaryTargetName.ToLowerInvariant() == commandName.ToLowerInvariant()) ||
@ -86,7 +70,7 @@ namespace NadekoBot.Modules.Permissions
return null;
}
public static string GetCommand(this Permission perm, SocketGuild guild = null)
public static string GetCommand(this Permissionv2 perm, SocketGuild guild = null)
{
var com = "";
switch (perm.PrimaryTarget)
@ -122,19 +106,13 @@ namespace NadekoBot.Modules.Permissions
switch (perm.PrimaryTarget)
{
case PrimaryPermissionType.User:
if (guild == null)
com += $"<@{perm.PrimaryTargetId}>";
else
com += guild.GetUser(perm.PrimaryTargetId).ToString() ?? $"<@{perm.PrimaryTargetId}>";
com += guild?.GetUser(perm.PrimaryTargetId)?.ToString() ?? $"<@{perm.PrimaryTargetId}>";
break;
case PrimaryPermissionType.Channel:
com += $"<#{perm.PrimaryTargetId}>";
break;
case PrimaryPermissionType.Role:
if(guild == null)
com += $"<@&{perm.PrimaryTargetId}>";
else
com += guild.GetRole(perm.PrimaryTargetId).ToString() ?? $"<@{perm.PrimaryTargetId}>";
com += guild?.GetRole(perm.PrimaryTargetId)?.ToString() ?? $"<@&{perm.PrimaryTargetId}>";
break;
case PrimaryPermissionType.Server:
break;
@ -143,98 +121,10 @@ namespace NadekoBot.Modules.Permissions
return NadekoBot.ModulePrefixes[typeof(Permissions).Name] + com;
}
public static void Prepend(this Permission perm, Permission toAdd)
{
perm = perm.GetRoot();
perm.Previous = toAdd;
toAdd.Next = perm;
}
/* /this can't work if index < 0 and perm isn't roo
public static void Insert(this Permission perm, int index, Permission toAdd)
{
if (index < 0)
throw new IndexOutOfRangeException();
if (index == 0)
{
perm.Prepend(toAdd);
return;
}
var atIndex = perm;
var i = 0;
while (i != index)
{
atIndex = atIndex.Next;
i++;
if (atIndex == null)
throw new IndexOutOfRangeException();
}
var previous = atIndex.Previous;
//connect right side
atIndex.Previous = toAdd;
toAdd.Next = atIndex;
//connect left side
toAdd.Previous = previous;
previous.Next = toAdd;
}
*/
public static Permission RemoveAt(this Permission perm, int index)
{
if (index <= 0) //can't really remove at 0, that means deleting the element right now. Just use perm.Next if its 0
throw new IndexOutOfRangeException();
var toRemove = perm;
var i = 0;
while (i != index)
{
toRemove = toRemove.Next;
i++;
if (toRemove == null)
throw new IndexOutOfRangeException();
}
toRemove.Previous.Next = toRemove.Next;
if (toRemove.Next != null)
toRemove.Next.Previous = toRemove.Previous;
return toRemove;
}
public static Permission GetAt(this Permission perm, int index)
{
if (index < 0)
throw new IndexOutOfRangeException();
var temp = perm;
while (index > 0) { temp = temp?.Next; index--; }
if (temp == null)
throw new IndexOutOfRangeException();
return temp;
}
public static int Count(this Permission perm)
{
var i = 1;
var temp = perm;
while ((temp = temp.Next) != null) { i++; }
return i;
}
public static IEnumerable<Permission> AsEnumerable(this Permission perm)
{
do yield return perm;
while ((perm = perm.Next) != null);
}
public static Permission GetRoot(this Permission perm)
{
Permission toReturn;
do toReturn = perm;
while ((perm = perm.Previous) != null);
return toReturn;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@ using System.Collections.Concurrent;
namespace NadekoBot.Modules.Pokemon
{
[NadekoModule("Pokemon", ">")]
public class Pokemon : NadekoModule
public class Pokemon : NadekoTopLevelModule
{
private static readonly List<PokemonType> _pokemonTypes = new List<PokemonType>();
private static readonly ConcurrentDictionary<ulong, PokeStats> _stats = new ConcurrentDictionary<ulong, PokeStats>();

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