Merge pull request #80 from Kwoth/dev

ups
This commit is contained in:
samvaio 2017-02-04 21:30:38 +05:30 committed by GitHub
commit d7612c7298
43 changed files with 884 additions and 720 deletions

@ -1 +1 @@
Subproject commit 80384323790471d254c7db5c237a49dc62624378 Subproject commit 0fd1e70a22612ff9fa697dace96f22780080b01f

View File

@ -18,56 +18,6 @@ You can support the project on patreon: <https://patreon.com/nadekobot> or paypa
### Administration ### Administration
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`.voice+text` `.v+t` | Creates a text channel for each voice channel only users in that voice channel can see.If you are server owner, keep in mind you will see them all the time regardless. **Requires ManageRoles server permission.** **Requires ManageChannels server permission.** | `.voice+text`
`.cleanvplust` `.cv+t` | Deletes all text channels ending in `-voice` for which voicechannels are not found. Use at your own risk. **Requires ManageChannels server permission.** **Requires ManageRoles server permission.** | `.cleanv+t`
`.greetdel` `.grdel` | Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.greetdel 0` or `.greetdel 30`
`.greet` | Toggles anouncements on the current channel when someone joins the server. **Requires ManageServer server permission.** | `.greet`
`.greetmsg` | Sets a new join announcement message which will be shown in the server's channel. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. **Requires ManageServer server permission.** | `.greetmsg Welcome, %user%.`
`.greetdm` | Toggles whether the greet messages will be sent in a DM (This is separate from greet - you can have both, any or neither enabled). **Requires ManageServer server permission.** | `.greetdm`
`.greetdmmsg` | Sets a new join announcement message which will be sent to the user who joined. Type %user% if you want to mention the new member. Using it with no message will show the current DM greet message. **Requires ManageServer server permission.** | `.greetdmmsg Welcome to the server, %user%`.
`.bye` | Toggles anouncements on the current channel when someone leaves the server. **Requires ManageServer server permission.** | `.bye`
`.byemsg` | Sets a new leave announcement message. Type %user% if you want to show the name the user who left. Type %id% to show id. Using this command with no message will show the current bye message. **Requires ManageServer server permission.** | `.byemsg %user% has left.`
`.byedel` | Sets the time it takes (in seconds) for bye messages to be auto-deleted. Set 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.byedel 0` or `.byedel 30`
`.leave` | Makes Nadeko leave the server. Either name or id required. **Bot Owner only.** | `.leave 123123123331`
`.die` | Shuts the bot down. **Bot Owner only.** | `.die`
`.setname` `.newnm` | Gives the bot a new name. **Bot Owner only.** | `.newnm BotName`
`.setstatus` | Sets the bot's status. (Online/Idle/Dnd/Invisible) **Bot Owner only.** | `.setstatus Idle`
`.setavatar` `.setav` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner only.** | `.setav http://i.imgur.com/xTG3a1I.jpg`
`.setgame` | Sets the bots game. **Bot Owner only.** | `.setgame with snakes`
`.setstream` | Sets the bots stream. First argument is the twitch link, second argument is stream name. **Bot Owner only.** | `.setstream TWITCHLINK Hello`
`.send` | Sends a message to someone on a different server through the bot. Separate server and channel/user ids with `|` and prepend channel id with `c:` and user id with `u:`. **Bot Owner only.** | `.send serverid|c:channelid message` or `.send serverid|u:userid message`
`.announce` | Sends a message to all servers' general channel bot is connected to. **Bot Owner only.** | `.announce Useless spam`
`.adsarm` | Toggles the automatic deletion of confirmations for .iam and .iamn commands. **Requires ManageMessages server permission.** | `.adsarm`
`.asar` | Adds a role to the list of self-assignable roles. **Requires ManageRoles server permission.** | `.asar Gamer`
`.rsar` | Removes a specified role from the list of self-assignable roles. **Requires ManageRoles server permission.** | `.rsar`
`.lsar` | Lists all self-assignable roles. | `.lsar`
`.togglexclsar` `.tesar` | Toggles whether the self-assigned roles are exclusive. (So that any person can have only one of the self assignable roles) **Requires ManageRoles server permission.** | `.tesar`
`.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | `.iam Gamer`
`.iamnot` `.iamn` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | `.iamn Gamer`
`.slowmode` | Toggles slowmode. Disable by specifying no parameters. To enable, specify a number of messages each user can send, and an interval in seconds. For example 1 message every 5 seconds. **Requires ManageMessages server permission.** | `.slowmode 1 5` or `.slowmode`
`.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`
`.antispamignore` | Toggles whether antispam ignores current channel. Antispam must be enabled. | `.antispamignore`
`.antilist` `.antilst` | Shows currently enabled protection features. | `.antilist`
`.rotateplaying` `.ropl` | Toggles rotation of playing status of the dynamic strings you previously specified. **Bot Owner only.** | `.ropl`
`.addplaying` `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued% **Bot Owner only.** | `.adpl`
`.listplaying` `.lipl` | Lists all playing statuses with their corresponding number. **Bot Owner only.** | `.lipl`
`.removeplaying` `.rmpl` `.repl` | Removes a playing string on a given number. **Bot Owner only.** | `.rmpl`
`.setmuterole` | Sets a name of the role which will be assigned to people who should be muted. Default is nadeko-mute. **Requires ManageRoles server permission.** | `.setmuterole Silenced`
`.mute` | Mutes a mentioned user both from speaking and chatting. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.mute @Someone`
`.unmute` | Unmutes a mentioned user previously muted with `.mute` command. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.unmute @Someone`
`.chatmute` | Prevents a mentioned user from chatting in text channels. **Requires ManageRoles server permission.** | `.chatmute @Someone`
`.chatunmute` | Removes a mute role previously set on a mentioned user with `.chatmute` which prevented him from chatting in text channels. **Requires ManageRoles server permission.** | `.chatunmute @Someone`
`.voicemute` | Prevents a mentioned user from speaking in voice channels. **Requires MuteMembers server permission.** | `.voicemute @Someone`
`.voiceunmute` | Gives a previously voice-muted user a permission to speak. **Requires MuteMembers server permission.** | `.voiceunmute @Someguy`
`.migratedata` | Migrate data from old bot configuration **Bot Owner only.** | `.migratedata`
`.logserver` | Enables or Disables ALL log events. If enabled, all log events will log to this channel. **Requires Administrator server permission.** **Bot Owner only.** | `.logserver enable` or `.logserver disable`
`.logignore` | Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. **Requires Administrator server permission.** **Bot Owner only.** | `.logignore`
`.logevents` | Shows a list of all events you can subscribe to with `.log` **Requires Administrator server permission.** **Bot Owner only.** | `.logevents`
`.log` | Toggles logging event. Disables it if it's active anywhere on the server. Enables if it's not active. Use `.logevents` to see a list of all events you can subscribe to. **Requires Administrator server permission.** **Bot Owner only.** | `.log userpresence` or `.log userbanned`
`.fwmsgs` | Toggles forwarding of non-command messages sent to bot's DM to the bot owners **Bot Owner only.** | `.fwmsgs`
`.fwtoall` | Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json **Bot Owner only.** | `.fwtoall`
`.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
`.resetperms` | Resets BOT's permissions module on this server to the default value. **Requires Administrator server permission.** | `.resetperms` `.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` `.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. **Requires Administrator server permission.** | `.delmsgoncmd`
`.setrole` `.sr` | Sets a role for a given user. **Requires ManageRoles server permission.** | `.sr @User Guest` `.setrole` `.sr` | Sets a role for a given user. **Requires ManageRoles server permission.** | `.sr @User Guest`
@ -91,6 +41,57 @@ Command and aliases | Description | Usage
`.mentionrole` `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. **Requires MentionEveryone server permission.** | `.menro RoleName` `.mentionrole` `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. **Requires MentionEveryone server permission.** | `.menro RoleName`
`.donators` | List of lovely people who donated to keep this project alive. | `.donators` `.donators` | List of lovely people who donated to keep this project alive. | `.donators`
`.donadd` | Add a donator to the database. **Bot Owner only.** | `.donadd Donate Amount` `.donadd` | Add a donator to the database. **Bot Owner only.** | `.donadd Donate Amount`
`.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`
`.setmuterole` | Sets a name of the role which will be assigned to people who should be muted. Default is nadeko-mute. **Requires ManageRoles server permission.** | `.setmuterole Silenced`
`.mute` | Mutes a mentioned user both from speaking and chatting. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.mute @Someone`
`.unmute` | Unmutes a mentioned user previously muted with `.mute` command. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.unmute @Someone`
`.chatmute` | Prevents a mentioned user from chatting in text channels. **Requires ManageRoles server permission.** | `.chatmute @Someone`
`.chatunmute` | Removes a mute role previously set on a mentioned user with `.chatmute` which prevented him from chatting in text channels. **Requires ManageRoles server permission.** | `.chatunmute @Someone`
`.voicemute` | Prevents a mentioned user from speaking in voice channels. **Requires MuteMembers server permission.** | `.voicemute @Someone`
`.voiceunmute` | Gives a previously voice-muted user a permission to speak. **Requires MuteMembers server permission.** | `.voiceunmute @Someguy`
`.rotateplaying` `.ropl` | Toggles rotation of playing status of the dynamic strings you previously specified. **Bot Owner only.** | `.ropl`
`.addplaying` `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %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`
`.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`
`.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`
`.greet` | Toggles anouncements on the current channel when someone joins the server. **Requires ManageServer server permission.** | `.greet`
`.greetmsg` | Sets a new join announcement message which will be shown in the server's channel. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. **Requires ManageServer server permission.** | `.greetmsg Welcome, %user%.`
`.greetdm` | Toggles whether the greet messages will be sent in a DM (This is separate from greet - you can have both, any or neither enabled). **Requires ManageServer server permission.** | `.greetdm`
`.greetdmmsg` | Sets a new join announcement message which will be sent to the user who joined. Type %user% if you want to mention the new member. Using it with no message will show the current DM greet message. **Requires ManageServer server permission.** | `.greetdmmsg Welcome to the server, %user%`.
`.bye` | Toggles anouncements on the current channel when someone leaves the server. **Requires ManageServer server permission.** | `.bye`
`.byemsg` | Sets a new leave announcement message. Type %user% if you want to show the name the user who left. Type %id% to show id. Using this command with no message will show the current bye message. **Requires ManageServer server permission.** | `.byemsg %user% has left.`
`.byedel` | Sets the time it takes (in seconds) for bye messages to be auto-deleted. Set 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.byedel 0` or `.byedel 30`
`.voice+text` `.v+t` | Creates a text channel for each voice channel only users in that voice channel can see.If you are server owner, keep in mind you will see them all the time regardless. **Requires ManageRoles server permission.** **Requires ManageChannels server permission.** | `.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)
@ -125,24 +126,6 @@ Command and aliases | Description | Usage
### Gambling ### Gambling
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`$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`
`$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`
`$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`
`$flip` | Flips coin(s) - heads or tails, and shows an image. | `$flip` or `$flip 3`
`$betflip` `$bf` | Bet to guess will the result be heads or tails. Guessing awards you 1.8x the currency you've bet. | `$bf 5 heads` or `$bf 3 t`
`$draw` | Draws a card from the deck.If you supply number X, she draws up to 5 cards from the deck. | `$draw` or `$draw 5`
`$shuffle` `$sh` | Reshuffles all cards back into the deck. | `$sh`
`$roll` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. Y can be a letter 'F' if you want to roll fate dice instead of dnd. | `$roll` or `$roll 7` or `$roll 3d5` or `$roll 5dF`
`$rolluo` | Rolls X normal dice (up to 30) unordered. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | `$rolluo` or `$rolluo 7` or `$rolluo 3d5`
`$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15`
`$startevent` | Starts one of the events seen on public nadeko. **Bot Owner only.** | `$startevent flowerreaction`
`$race` | Starts a new animal race. | `$race`
`$joinrace` `$jr` | Joins a new race. You can specify an amount of currency for betting (optional). You will get YourBet*(participants-1) back if you win. | `$jr` or `$jr 5`
`$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. | `$raffle` or `$raffle RoleName` `$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. | `$raffle` or `$raffle RoleName`
`$cash` `$$$` | Check how much currency a person has. (Defaults to yourself) | `$$$` or `$$$ @SomeGuy` `$cash` `$$$` | Check how much currency a person has. (Defaults to yourself) | `$$$` or `$$$ @SomeGuy`
`$give` | Give someone a certain amount of currency. | `$give 1 "@SomeGuy"` `$give` | Give someone a certain amount of currency. | `$give 1 "@SomeGuy"`
@ -150,36 +133,54 @@ Command and aliases | Description | Usage
`$take` | Takes a certain amount of currency from someone. **Bot Owner only.** | `$take 1 "@someguy"` `$take` | Takes a certain amount of currency from someone. **Bot Owner only.** | `$take 1 "@someguy"`
`$betroll` `$br` | Bets a certain amount of currency and rolls a dice. Rolling over 66 yields x2 of your currency, over 90 - x3 and 100 x10. | `$br 5` `$betroll` `$br` | Bets a certain amount of currency and rolls a dice. Rolling over 66 yields x2 of your currency, over 90 - x3 and 100 x10. | `$br 5`
`$leaderboard` `$lb` | Displays bot currency leaderboard. | `$lb` `$leaderboard` `$lb` | Displays bot currency leaderboard. | `$lb`
`$race` | Starts a new animal race. | `$race`
`$joinrace` `$jr` | Joins a new race. You can specify an amount of currency for betting (optional). You will get YourBet*(participants-1) back if you win. | `$jr` or `$jr 5`
`$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`
`$divorce` | Releases your claim on a specific waifu. You will get some of the money you've spent back unless that waifu has an affinity towards you. 6 hours cooldown. | `$divorce @CheatingSloot`
`$affinity` | Sets your affinity towards someone you want to be claimed by. Setting affinity will reduce their `$claim` on you by 20%. You can leave second argument empty to clear your affinity. 30 minutes cooldown. | `$affinity @MyHusband` or `$affinity`
`$waifus` `$waifulb` | Shows top 9 waifus. | `$waifus`
`$waifuinfo` `$waifustats` | Shows waifu stats for a target person. Defaults to you if no user is provided. | `$waifuinfo @MyCrush` or `$waifuinfo`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
### Games ### Games
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`>trivia` `>t` | Starts a game of trivia. You can add nohint to prevent hints.First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question. | `>t` or `>t 5 nohint`
`>tl` | Shows a current trivia leaderboard. | `>tl`
`>tq` | Quits current trivia after current question. | `>tq`
`>typestart` | Starts a typing contest. | `>typestart`
`>typestop` | Stops a typing contest on the current channel. | `>typestop`
`>typeadd` | Adds a new article to the typing contest. **Bot Owner only.** | `>typeadd wordswords`
`>typelist` | Lists added typing articles with their IDs. 15 per page. | `>typelist` or `>typelist 3`
`>typedel` | Deletes a typing article given the ID. **Bot Owner only.** | `>typedel 3`
`>poll` | Creates a poll which requires users to send the number of the voting option to the bot. **Requires ManageMessages server permission.** | `>poll Question?;Answer1;Answ 2;A_3`
`>publicpoll` `>ppoll` | Creates a public poll which requires users to type a number of the voting option in the channel command is ran in. **Requires ManageMessages server permission.** | `>ppoll Question?;Answer1;Answ 2;A_3`
`>pollstats` | Shows the poll results without stopping the poll on this server. **Requires ManageMessages server permission.** | `>pollstats`
`>pollend` | Stops active poll on this server and prints the results in this channel. **Requires ManageMessages server permission.** | `>pollend`
`>pick` | Picks the currency planted in this channel. 60 seconds cooldown. | `>pick`
`>plant` | Spend a unit of currency to plant it in this channel. (If bot is restarted or crashes, the currency will be lost) | `>plant`
`>gencurrency` `>gc` | Toggles currency generation on this channel. Every posted message will have chance to spawn currency. Chance is specified by the Bot Owner. (default is 2%) **Requires ManageMessages server permission.** | `>gc`
`>hangmanlist` | Shows a list of hangman term types. | `> hangmanlist`
`>hangman` | Starts a game of hangman in the channel. Use `>hangmanlist` to see a list of available term types. Defaults to 'all'. | `>hangman` or `>hangman movies`
`>cleverbot` | Toggles cleverbot session. When enabled, the bot will reply to messages starting with bot mention in the server. Custom reactions starting with %mention% won't work if cleverbot is enabled. **Requires ManageMessages server permission.** | `>cleverbot`
`>acrophobia` `>acro` | Starts an Acrophobia game. Second argment is optional round length in seconds. (default is 60) | `>acro` or `>acro 30`
`>choose` | Chooses a thing from a list of things | `>choose Get up;Sleep;Sleep more` `>choose` | Chooses a thing from a list of things | `>choose Get up;Sleep;Sleep more`
`>8ball` | Ask the 8ball a yes/no question. | `>8ball should I do something` `>8ball` | Ask the 8ball a yes/no question. | `>8ball should I do something`
`>rps` | Play a game of rocket paperclip scissors with Nadeko. | `>rps scissors` `>rps` | Play a game of rocket paperclip scissors with Nadeko. | `>rps scissors`
`>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows` `>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows`
`>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | `>leet 3 Hello` `>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | `>leet 3 Hello`
`>acrophobia` `>acro` | Starts an Acrophobia game. Second argment is optional round length in seconds. (default is 60) | `>acro` or `>acro 30`
`>cleverbot` | Toggles cleverbot session. When enabled, the bot will reply to messages starting with bot mention in the server. Custom reactions starting with %mention% won't work if cleverbot is enabled. **Requires ManageMessages server permission.** | `>cleverbot`
`>hangmanlist` | Shows a list of hangman term types. | `> hangmanlist`
`>hangman` | Starts a game of hangman in the channel. Use `>hangmanlist` to see a list of available term types. Defaults to 'all'. | `>hangman` or `>hangman movies`
`>pick` | Picks the currency planted in this channel. 60 seconds cooldown. | `>pick`
`>plant` | Spend an amount of currency to plant it in this channel. Default is 1. (If bot is restarted or crashes, the currency will be lost) | `>plant` or `>plant 5`
`>gencurrency` `>gc` | Toggles currency generation on this channel. Every posted message will have chance to spawn currency. Chance is specified by the Bot Owner. (default is 2%) **Requires ManageMessages server permission.** | `>gc`
`>poll` | Creates a poll which requires users to send the number of the voting option to the bot. **Requires ManageMessages server permission.** | `>poll Question?;Answer1;Answ 2;A_3`
`>publicpoll` `>ppoll` | Creates a public poll which requires users to type a number of the voting option in the channel command is ran in. **Requires ManageMessages server permission.** | `>ppoll Question?;Answer1;Answ 2;A_3`
`>pollstats` | Shows the poll results without stopping the poll on this server. **Requires ManageMessages server permission.** | `>pollstats`
`>pollend` | Stops active poll on this server and prints the results in this channel. **Requires ManageMessages server permission.** | `>pollend`
`>typestart` | Starts a typing contest. | `>typestart`
`>typestop` | Stops a typing contest on the current channel. | `>typestop`
`>typeadd` | Adds a new article to the typing contest. **Bot Owner only.** | `>typeadd wordswords`
`>typelist` | Lists added typing articles with their IDs. 15 per page. | `>typelist` or `>typelist 3`
`>typedel` | Deletes a typing article given the ID. **Bot Owner only.** | `>typedel 3`
`>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)
@ -227,6 +228,7 @@ Command and aliases | Description | Usage
`!!deleteplaylist` `!!delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!!delpls animu-5` `!!deleteplaylist` `!!delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!!delpls animu-5`
`!!goto` | Goes to a specific time in seconds in a song. | `!!goto 30` `!!goto` | Goes to a specific time in seconds in a song. | `!!goto 30`
`!!autoplay` `!!ap` | Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty) | `!!ap` `!!autoplay` `!!ap` | Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty) | `!!ap`
`!!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)
@ -236,12 +238,12 @@ Command and aliases | Description | Usage
`~hentai` | Shows a hentai image from a random website (gelbooru or danbooru or konachan or atfbooru or yandere) with a given tag. Tag is optional but preferred. Only 1 tag allowed. | `~hentai yuri` `~hentai` | Shows a hentai image from a random website (gelbooru or danbooru or konachan or atfbooru or yandere) with a given tag. Tag is optional but preferred. Only 1 tag allowed. | `~hentai yuri`
`~autohentai` | Posts a hentai every X seconds with a random tag from the provided tags. Use `|` to separate tags. 20 seconds minimum. Provide no arguments to disable. **Requires ManageMessages channel permission.** | `~autohentai 30 yuri|tail|long_hair` or `~autohentai` `~autohentai` | Posts a hentai every X seconds with a random tag from the provided tags. Use `|` to separate tags. 20 seconds minimum. Provide no arguments to disable. **Requires ManageMessages channel permission.** | `~autohentai 30 yuri|tail|long_hair` or `~autohentai`
`~hentaibomb` | Shows a total 5 images (from gelbooru, danbooru, konachan, yandere and atfbooru). Tag is optional but preferred. | `~hentaibomb yuri` `~hentaibomb` | Shows a total 5 images (from gelbooru, danbooru, konachan, yandere and atfbooru). Tag is optional but preferred. | `~hentaibomb yuri`
`~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~danbooru yuri+kissing`
`~yandere` | Shows a random image from yandere with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~yandere tag1+tag2` `~yandere` | Shows a random image from yandere with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~yandere tag1+tag2`
`~konachan` | Shows a random hentai image from konachan with a given tag. Tag is optional but preferred. | `~konachan yuri` `~konachan` | Shows a random hentai image from konachan with a given tag. Tag is optional but preferred. | `~konachan yuri`
`~gelbooru` | Shows a random hentai image from gelbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~gelbooru yuri+kissing`
`~rule34` | Shows a random image from rule34.xx with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~rule34 yuri+kissing` `~rule34` | Shows a random image from rule34.xx with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~rule34 yuri+kissing`
`~e621` | Shows a random hentai image from e621.net with a given tag. Tag is optional but preferred. Use spaces for multiple tags. | `~e621 yuri kissing` `~e621` | Shows a random hentai image from e621.net with a given tag. Tag is optional but preferred. Use spaces for multiple tags. | `~e621 yuri kissing`
`~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~danbooru yuri+kissing`
`~gelbooru` | Shows a random hentai image from gelbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~gelbooru yuri+kissing`
`~cp` | We all know where this will lead you to. | `~cp` `~cp` | We all know where this will lead you to. | `~cp`
`~boobs` | Real adult content. | `~boobs` `~boobs` | Real adult content. | `~boobs`
`~butts` `~ass` `~butt` | Real adult content. | `~butts` or `~ass` `~butts` `~ass` `~butt` | Real adult content. | `~butts` or `~ass`
@ -251,18 +253,6 @@ Command and aliases | Description | Usage
### Permissions ### Permissions
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`;srvrfilterinv` `;sfi` | Toggles automatic deleting of invites posted in the server. Does not affect Bot Owner. | `;sfi`
`;chnlfilterinv` `;cfi` | Toggles automatic deleting of invites posted in the channel. Does not negate the ;srvrfilterinv enabled setting. Does not affect Bot Owner. | `;cfi`
`;srvrfilterwords` `;sfw` | Toggles automatic deleting of messages containing forbidden words on the server. Does not affect Bot Owner. | `;sfw`
`;chnlfilterwords` `;cfw` | Toggles automatic deleting of messages containing banned words on the channel. Does not negate the ;srvrfilterwords enabled setting. Does not affect bot owner. | `;cfw`
`;fw` | Adds or removes (if it exists) a word from the list of filtered words. Use`;sfw` or `;cfw` to toggle filtering. | `;fw poop`
`;lstfilterwords` `;lfw` | Shows a list of filtered words. | `;lfw`
`;cmdcosts` | Shows a list of command costs. Paginated with 9 command per page. | `;cmdcosts` or `;cmdcosts 2`
`;cmdcooldown` `;cmdcd` | Sets a cooldown per user for a command. Set to 0 to remove the cooldown. | `;cmdcd "some cmd" 5`
`;allcmdcooldowns` `;acmdcds` | Shows a list of all commands and their respective cooldowns. | `;acmdcds`
`;ubl` | Either [add]s or [rem]oves a user specified by a mention or ID from a blacklist. **Bot Owner only.** | `;ubl add @SomeUser` or `;ubl rem 12312312313`
`;cbl` | Either [add]s or [rem]oves a channel specified by an ID from a blacklist. **Bot Owner only.** | `;cbl rem 12312312312`
`;sbl` | Either [add]s or [rem]oves a server specified by a Name or ID from a blacklist. **Bot Owner only.** | `;sbl add 12312321312` or `;sbl rem SomeTrashServer`
`;verbose` `;v` | Sets whether to show when a command/module is blocked. | `;verbose true` `;verbose` `;v` | Sets whether to show when a command/module is blocked. | `;verbose true`
`;permrole` `;pr` | Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'. | `;pr role` `;permrole` `;pr` | Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'. | `;pr role`
`;listperms` `;lp` | Lists whole permission chain with their indexes. You can specify an optional page number if there are a lot of permissions. | `;lp` or `;lp 3` `;listperms` `;lp` | Lists whole permission chain with their indexes. You can specify an optional page number if there are a lot of permissions. | `;lp` or `;lp 3`
@ -280,6 +270,18 @@ Command and aliases | Description | Usage
`;allrolemdls` `;arm` | Enable or disable all modules for a specific role. | `;arm [enable/disable] MyRole` `;allrolemdls` `;arm` | Enable or disable all modules for a specific role. | `;arm [enable/disable] MyRole`
`;allusrmdls` `;aum` | Enable or disable all modules for a specific user. | `;aum enable @someone` `;allusrmdls` `;aum` | Enable or disable all modules for a specific user. | `;aum enable @someone`
`;allsrvrmdls` `;asm` | Enable or disable all modules for your server. | `;asm [enable/disable]` `;allsrvrmdls` `;asm` | Enable or disable all modules for your server. | `;asm [enable/disable]`
`;ubl` | Either [add]s or [rem]oves a user specified by a mention or ID from a blacklist. **Bot Owner only.** | `;ubl add @SomeUser` or `;ubl rem 12312312313`
`;cbl` | Either [add]s or [rem]oves a channel specified by an ID from a blacklist. **Bot Owner only.** | `;cbl rem 12312312312`
`;sbl` | Either [add]s or [rem]oves a server specified by a Name or ID from a blacklist. **Bot Owner only.** | `;sbl add 12312321312` or `;sbl rem SomeTrashServer`
`;cmdcooldown` `;cmdcd` | Sets a cooldown per user for a command. Set to 0 to remove the cooldown. | `;cmdcd "some cmd" 5`
`;allcmdcooldowns` `;acmdcds` | Shows a list of all commands and their respective cooldowns. | `;acmdcds`
`;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`
`;fw` | Adds or removes (if it exists) a word from the list of filtered words. Use`;sfw` or `;cfw` to toggle filtering. | `;fw poop`
`;lstfilterwords` `;lfw` | Shows a list of filtered words. | `;lfw`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
@ -297,32 +299,6 @@ Command and aliases | Description | Usage
### Searches ### Searches
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`~xkcd` | Shows a XKCD comic. No arguments will retrieve random one. Number argument will retrieve a specific comic, and "latest" will get the latest one. | `~xkcd` or `~xkcd 1400` or `~xkcd latest`
`~translate` `~trans` | Translates from>to text. From the given language to the destination language. | `~trans en>fr Hello`
`~autotrans` `~at` | Starts automatic translation of all messages by users who set their `~atl` in this channel. You can set "del" argument to automatically delete all translated user messages. **Requires Administrator server permission.** **Bot Owner only.** | `~at` or `~at del`
`~autotranslang` `~atl` | `~atl en>fr` | Sets your source and target language to be used with `~at`. Specify no arguments to remove previously set value.
`~translangs` | Lists the valid languages for translation. | `~translangs`
`~hitbox` `~hb` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~hitbox SomeStreamer`
`~twitch` `~tw` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~twitch SomeStreamer`
`~beam` `~bm` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~beam SomeStreamer`
`~liststreams` `~ls` | Lists all streams you are following on this server. | `~ls`
`~removestream` `~rms` | Removes notifications of a certain streamer from a certain platform on this channel. **Requires ManageMessages server permission.** | `~rms Twitch SomeGuy` or `~rms Beam SomeOtherGuy`
`~checkstream` `~cs` | Checks if a user is online on a certain streaming platform. | `~cs twitch MyFavStreamer`
`~pokemon` `~poke` | Searches for a pokemon. | `~poke Sylveon`
`~pokemonability` `~pokeab` | Searches for a pokemon ability. | `~pokeab overgrow`
`~placelist` | Shows the list of available tags for the `~place` command. | `~placelist`
`~place` | Shows a placeholder image of a given tag. Use `~placelist` to see all available tags. You can specify the width and height of the image as the last two optional arguments. | `~place Cage` or `~place steven 500 400`
`~overwatch` `~ow` | Show's basic stats on a player (competitive rank, playtime, level etc) Region codes are: `eu` `us` `cn` `kr` | `~ow us Battletag#1337` or `~overwatch eu Battletag#2016`
`~osu` | Shows osu stats for a player. | `~osu Name` or `~osu Name taiko`
`~osub` | Shows information about an osu beatmap. | `~osub https://osu.ppy.sh/s/127712`
`~osu5` | Displays a user's top 5 plays. | `~osu5 Name`
`~yomama` `~ym` | Shows a random joke from <http://api.yomomma.info/> | `~ym`
`~randjoke` `~rj` | Shows a random joke from <http://tambal.azurewebsites.net/joke/random> | `~rj`
`~chucknorris` `~cn` | Shows a random chucknorris joke from <http://tambal.azurewebsites.net/joke/random> | `~cn`
`~wowjoke` | Get one of Kwoth's penultimate WoW jokes. | `~wowjoke`
`~magicitem` `~mi` | Shows a random magicitem from <https://1d4chan.org/wiki/List_of_/tg/%27s_magic_items> | `~mi`
`~anime` `~ani` `~aq` | Queries anilist for an anime and shows the first result. | `~ani aquarion evol`
`~manga` `~mang` `~mq` | Queries anilist for a manga and shows the first result. | `~mq Shingeki no kyojin`
`~weather` `~we` | Shows weather data for a specified city. You can also specify a country after a comma. | `~we Moscow, RU` `~weather` `~we` | Shows weather data for a specified city. You can also specify a country after a comma. | `~we Moscow, RU`
`~youtube` `~yt` | Searches youtubes and shows the first result | `~yt query` `~youtube` `~yt` | Searches youtubes and shows the first result | `~yt query`
`~imdb` `~omdb` | Queries omdb for movies or series, show first result. | `~imdb Batman vs Superman` `~imdb` `~omdb` | Queries omdb for movies or series, show first result. | `~imdb Batman vs Superman`
@ -353,34 +329,40 @@ Command and aliases | Description | Usage
`~lolban` | Shows top banned champions ordered by ban rate. | `~lolban` `~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` `~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ | `~memelist`
`~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"` `~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"`
`~mal` | Shows basic info from 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`
`~wowjoke` | Get one of Kwoth's penultimate WoW jokes. | `~wowjoke`
`~magicitem` `~mi` | Shows a random magicitem from <https://1d4chan.org/wiki/List_of_/tg/%27s_magic_items> | `~mi`
`~osu` | Shows osu stats for a player. | `~osu Name` or `~osu Name taiko`
`~osub` | Shows information about an osu beatmap. | `~osub https://osu.ppy.sh/s/127712`
`~osu5` | Displays a user's top 5 plays. | `~osu5 Name`
`~overwatch` `~ow` | Show's basic stats on a player (competitive rank, playtime, level etc) Region codes are: `eu` `us` `cn` `kr` | `~ow us Battletag#1337` or `~overwatch eu Battletag#2016`
`~placelist` | Shows the list of available tags for the `~place` command. | `~placelist`
`~place` | Shows a placeholder image of a given tag. Use `~placelist` to see all available tags. You can specify the width and height of the image as the last two optional arguments. | `~place Cage` or `~place steven 500 400`
`~pokemon` `~poke` | Searches for a pokemon. | `~poke Sylveon`
`~pokemonability` `~pokeab` | Searches for a pokemon ability. | `~pokeab overgrow`
`~hitbox` `~hb` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~hitbox SomeStreamer`
`~twitch` `~tw` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~twitch SomeStreamer`
`~beam` `~bm` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~beam SomeStreamer`
`~liststreams` `~ls` | Lists all streams you are following on this server. | `~ls`
`~removestream` `~rms` | Removes notifications of a certain streamer from a certain platform on this channel. **Requires ManageMessages server permission.** | `~rms Twitch SomeGuy` or `~rms Beam SomeOtherGuy`
`~checkstream` `~cs` | Checks if a user is online on a certain streaming platform. | `~cs twitch MyFavStreamer`
`~translate` `~trans` | Translates from>to text. From the given language to the destination language. | `~trans en>fr Hello`
`~autotrans` `~at` | Starts automatic translation of all messages by users who set their `~atl` in this channel. You can set "del" argument to automatically delete all translated user messages. **Requires Administrator server permission.** **Bot Owner only.** | `~at` or `~at del`
`~autotranslang` `~atl` | `~atl en>fr` | Sets your source and target language to be used with `~at`. Specify no arguments to remove previously set value.
`~translangs` | Lists the valid languages for translation. | `~translangs`
`~xkcd` | Shows a XKCD comic. No arguments will retrieve random one. Number argument will retrieve a specific comic, and "latest" will get the latest one. | `~xkcd` or `~xkcd 1400` or `~xkcd latest`
###### [Back to TOC](#table-of-contents) ###### [Back to TOC](#table-of-contents)
### Utility ### Utility
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`.convertlist` | List of the convertible dimensions and currencies. | `.convertlist` `.rotaterolecolor` `.rrc` | Rotates a roles color on an interval with a list of supplied colors. First argument is interval in seconds (Minimum 60). Second argument is a role, followed by a space-separated list of colors in hex. Provide a rolename with a 0 interval to disable. **Requires ManageRoles server permission.** **Bot Owner only.** | `.rrc 60 MyLsdRole #ff0000 #00ff00 #0000ff` or `.rrc 0 MyLsdRole`
`.convert` | Convert quantities. Use `.convertlist` to see supported dimensions and currencies. | `.convert m km 1000`
`.remind` | Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message. | `.remind me 1d5h Do something` or `.remind #general 1m Start now!`
`.remindtemplate` | Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind. **Bot Owner only.** | `.remindtemplate %user%, do %message%!`
`.listquotes` `.liqu` | `.liqu` or `.liqu 3` | Lists all quotes on the server ordered alphabetically. 15 Per page.
`...` | Shows a random quote with a specified name. | `... abc`
`..` | Adds a new quote with the specified name and message. | `.. sayhi Hi`
`.deletequote` `.delq` | Deletes a random quote with the specified keyword. You have to either be server Administrator or the creator of the quote to delete it. | `.delq abc`
`.delallq` `.daq` | Deletes all quotes on a specified keyword. **Requires Administrator server permission.** | `.delallq kek`
`.repeatinvoke` `.repinv` | Immediately shows the repeat message on a certain index and restarts its timer. **Requires ManageMessages server permission.** | `.repinv 1`
`.repeatremove` `.reprm` | Removes a repeating message on a specified index. Use `.repeatlist` to see indexes. **Requires ManageMessages server permission.** | `.reprm 2`
`.repeat` | Repeat a message every X minutes in the current channel. You can have up to 5 repeating messages on the server in total. **Requires ManageMessages server permission.** | `.repeat 5 Hello there`
`.repeatlist` `.replst` | Shows currently repeating messages and their indexes. **Requires ManageMessages server permission.** | `.repeatlist`
`.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`
`.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`
`.calculate` `.calc` | Evaluate a mathematical expression. | `.calc 1+1`
`.calcops` | Shows all available operations in .calc command | `.calcops`
`.rotaterolecolor` `.rrc` | Rotates a roles color on an interval with a list of supplied colors. First argument is interval in seconds (Minimum 60). Second argument is a role, followed by a space-separated list of colors in hex. Provide a rolename with a 0 interval to disable. **Bot Owner only.** | `.rrc 60 MyLsdRole #ff0000 #00ff00 #0000ff` or `.rrc 0 MyLsdRole`
`.togethertube` `.totube` | Creates a new room on <https://togethertube.com> and shows the link in the chat. | `.totube` `.togethertube` `.totube` | Creates a new room on <https://togethertube.com> and shows the link in the chat. | `.totube`
`.whosplaying` `.whpl` | Shows a list of users who are playing the specified game. | `.whpl Overwatch` `.whosplaying` `.whpl` | Shows a list of users who are playing the specified game. | `.whpl Overwatch`
`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. If the list is too long for 1 message, you must have Manage Messages permission. | `.inrole Role` `.inrole` | Lists every person from the 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`
@ -396,3 +378,24 @@ Command and aliases | Description | Usage
`.listservers` | Lists servers the bot is on with some basic info. 15 per page. **Bot Owner only.** | `.listservers 3` `.listservers` | Lists servers the bot is on with some basic info. 15 per page. **Bot Owner only.** | `.listservers 3`
`.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner only.** | `.savechat 150` `.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` `.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`
`.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`
`.serverinfo` `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | `.sinfo Some Server`
`.channelinfo` `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | `.cinfo #some-channel`
`.userinfo` `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | `.uinfo @SomeUser`
`.repeatinvoke` `.repinv` | Immediately shows the repeat message on a certain index and restarts its timer. **Requires ManageMessages server permission.** | `.repinv 1`
`.repeatremove` `.reprm` | Removes a repeating message on a specified index. Use `.repeatlist` to see indexes. **Requires ManageMessages server permission.** | `.reprm 2`
`.repeat` | Repeat a message every X minutes in the current channel. You can have up to 5 repeating messages on the server in total. **Requires ManageMessages server permission.** | `.repeat 5 Hello there`
`.repeatlist` `.replst` | Shows currently repeating messages and their indexes. **Requires ManageMessages server permission.** | `.repeatlist`
`.listquotes` `.liqu` | `.liqu` or `.liqu 3` | Lists all quotes on the server ordered alphabetically. 15 Per page.
`...` | Shows a random quote with a specified name. | `... abc`
`..` | Adds a new quote with the specified name and message. | `.. sayhi Hi`
`.deletequote` `.delq` | Deletes a random quote with the specified keyword. You have to either be server Administrator or the creator of the quote to delete it. | `.delq abc`
`.delallq` `.daq` | Deletes all quotes on a specified keyword. **Requires Administrator server permission.** | `.delallq kek`
`.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

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

View File

@ -38,20 +38,24 @@ namespace NadekoBot.Modules.Administration
} }
private static async Task DelMsgOnCmd_Handler(SocketUserMessage msg, CommandInfo cmd) private static Task DelMsgOnCmd_Handler(SocketUserMessage msg, CommandInfo cmd)
{ {
try var _ = Task.Run(async () =>
{ {
var channel = msg.Channel as SocketTextChannel; try
if (channel == null) {
return; var channel = msg.Channel as SocketTextChannel;
if (DeleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune") if (channel == null)
await msg.DeleteAsync().ConfigureAwait(false); return;
} if (DeleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune")
catch (Exception ex) await msg.DeleteAsync().ConfigureAwait(false);
{ }
_log.Warn(ex, "Delmsgoncmd errored..."); catch (Exception ex)
} {
_log.Warn(ex, "Delmsgoncmd errored...");
}
});
return Task.CompletedTask;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]

View File

@ -105,74 +105,85 @@ namespace NadekoBot.Modules.Administration
antiSpamGuilds.TryAdd(gc.GuildId, new AntiSpamStats() { AntiSpamSettings = spam }); antiSpamGuilds.TryAdd(gc.GuildId, new AntiSpamStats() { AntiSpamSettings = spam });
} }
NadekoBot.Client.MessageReceived += async (imsg) => NadekoBot.Client.MessageReceived += (imsg) =>
{ {
var msg = imsg as IUserMessage;
if (msg == null || msg.Author.IsBot)
return Task.CompletedTask;
try var channel = msg.Channel as ITextChannel;
if (channel == null)
return Task.CompletedTask;
var _ = Task.Run(async () =>
{ {
var msg = imsg as IUserMessage; try
if (msg == null || msg.Author.IsBot)
return;
var channel = msg.Channel as ITextChannel;
if (channel == null)
return;
AntiSpamStats spamSettings;
if (!antiSpamGuilds.TryGetValue(channel.Guild.Id, out spamSettings) ||
spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new AntiSpamIgnore()
{
ChannelId = channel.Id
}))
return;
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, new UserSpamStats(msg.Content),
(id, old) => { old.ApplyNextMessage(msg.Content); return old; });
if (stats.Count >= spamSettings.AntiSpamSettings.MessageThreshold)
{ {
if (spamSettings.UserStats.TryRemove(msg.Author.Id, out stats)) AntiSpamStats spamSettings;
if (!antiSpamGuilds.TryGetValue(channel.Guild.Id, out spamSettings) ||
spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new AntiSpamIgnore()
{
ChannelId = channel.Id
}))
return;
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, new UserSpamStats(msg.Content),
(id, old) =>
{
old.ApplyNextMessage(msg.Content); return old;
});
if (stats.Count >= spamSettings.AntiSpamSettings.MessageThreshold)
{ {
await PunishUsers(spamSettings.AntiSpamSettings.Action, ProtectionType.Spamming, (IGuildUser)msg.Author) if (spamSettings.UserStats.TryRemove(msg.Author.Id, out stats))
.ConfigureAwait(false); {
await PunishUsers(spamSettings.AntiSpamSettings.Action, ProtectionType.Spamming, (IGuildUser)msg.Author)
.ConfigureAwait(false);
}
} }
} }
} catch { }
catch { } });
return Task.CompletedTask;
}; };
NadekoBot.Client.UserJoined += async (usr) => NadekoBot.Client.UserJoined += (usr) =>
{ {
try if (usr.IsBot)
return Task.CompletedTask;
AntiRaidStats settings;
if (!antiRaidGuilds.TryGetValue(usr.Guild.Id, out settings))
return Task.CompletedTask;
if (!settings.RaidUsers.Add(usr))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{ {
if (usr.IsBot) try
return;
AntiRaidStats settings;
if (!antiRaidGuilds.TryGetValue(usr.Guild.Id, out settings))
return;
if (!settings.RaidUsers.Add(usr))
return;
++settings.UsersCount;
if (settings.UsersCount >= settings.AntiRaidSettings.UserThreshold)
{ {
var users = settings.RaidUsers.ToArray(); ++settings.UsersCount;
settings.RaidUsers.Clear();
if (settings.UsersCount >= settings.AntiRaidSettings.UserThreshold)
{
var users = settings.RaidUsers.ToArray();
settings.RaidUsers.Clear();
await PunishUsers(settings.AntiRaidSettings.Action, ProtectionType.Raiding, users).ConfigureAwait(false);
}
await Task.Delay(1000 * settings.AntiRaidSettings.Seconds).ConfigureAwait(false);
settings.RaidUsers.TryRemove(usr);
--settings.UsersCount;
await PunishUsers(settings.AntiRaidSettings.Action, ProtectionType.Raiding, users).ConfigureAwait(false);
} }
await Task.Delay(1000 * settings.AntiRaidSettings.Seconds).ConfigureAwait(false); catch { }
});
settings.RaidUsers.TryRemove(usr); return Task.CompletedTask;
--settings.UsersCount;
}
catch { }
}; };
} }
private static async Task PunishUsers(PunishmentAction action, ProtectionType pt, params IGuildUser[] gus) private static async Task PunishUsers(PunishmentAction action, ProtectionType pt, params IGuildUser[] gus)
{ {
_log.Info($"[{pt}] - Punishing [{gus.Length}] users with [{action}] in {gus[0].Guild.Name} guild");
foreach (var gu in gus) foreach (var gu in gus)
{ {
switch (action) switch (action)

View File

@ -3,6 +3,7 @@ using Discord.Commands;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
@ -168,6 +169,14 @@ namespace NadekoBot.Modules.Administration
await Context.Channel.SendConfirmAsync("🆗").ConfigureAwait(false); await Context.Channel.SendConfirmAsync("🆗").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ReloadImages()
{
var time = await NadekoBot.Images.Reload().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"Images loaded after {time.TotalSeconds:F3}s!").ConfigureAwait(false);
}
private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus) private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus)
{ {
switch (sus) switch (sus)
@ -184,6 +193,14 @@ namespace NadekoBot.Modules.Administration
return UserStatus.Online; return UserStatus.Online;
} }
public enum SettableUserStatus
{
Online,
Invisible,
Idle,
Dnd
}
} }
} }
} }

View File

@ -7,9 +7,11 @@ using NadekoBot.Services;
using NLog; using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
@ -22,6 +24,8 @@ namespace NadekoBot.Modules.Administration
private static Regex channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled); private static Regex channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled);
private static ConcurrentHashSet<ulong> voicePlusTextCache { get; } private static ConcurrentHashSet<ulong> voicePlusTextCache { get; }
private static ConcurrentDictionary<ulong, SemaphoreSlim> guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>();
static VoicePlusTextCommands() static VoicePlusTextCommands()
{ {
var _log = LogManager.GetCurrentClassLogger(); var _log = LogManager.GetCurrentClassLogger();
@ -36,78 +40,119 @@ namespace NadekoBot.Modules.Administration
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
} }
private static async Task UserUpdatedEventHandler(SocketUser iuser, SocketVoiceState before, SocketVoiceState after) private static Task UserUpdatedEventHandler(SocketUser iuser, SocketVoiceState before, SocketVoiceState after)
{ {
var user = (iuser as SocketGuildUser); var user = (iuser as SocketGuildUser);
var guild = user?.Guild; var guild = user?.Guild;
if (guild == null) if (guild == null)
return; return Task.CompletedTask;
try var botUserPerms = guild.CurrentUser.GuildPermissions;
if (before.VoiceChannel == after.VoiceChannel)
return Task.CompletedTask;
if (!voicePlusTextCache.Contains(guild.Id))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{ {
var botUserPerms = guild.CurrentUser.GuildPermissions; try
if (before.VoiceChannel == after.VoiceChannel) return;
if (!voicePlusTextCache.Contains(guild.Id))
return;
if (!botUserPerms.ManageChannels || !botUserPerms.ManageRoles)
{ {
if (!botUserPerms.ManageChannels || !botUserPerms.ManageRoles)
{
try
{
await guild.Owner.SendErrorAsync(
"⚠️ I don't have **manage server** and/or **manage channels** permission," +
$" so I cannot run `voice+text` on **{guild.Name}** server.").ConfigureAwait(false);
}
catch { }
using (var uow = DbHandler.UnitOfWork())
{
uow.GuildConfigs.For(guild.Id, set => set).VoicePlusTextEnabled = false;
voicePlusTextCache.TryRemove(guild.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
return;
}
var semaphore = guildLockObjects.GetOrAdd(guild.Id, (key) => new SemaphoreSlim(1, 1));
try try
{ {
await guild.Owner.SendErrorAsync( await semaphore.WaitAsync().ConfigureAwait(false);
"⚠️ I don't have **manage server** and/or **manage channels** permission," +
$" so I cannot run `voice+text` on **{guild.Name}** server.").ConfigureAwait(false);
}
catch { }
using (var uow = DbHandler.UnitOfWork())
{
uow.GuildConfigs.For(guild.Id, set => set).VoicePlusTextEnabled = false;
voicePlusTextCache.TryRemove(guild.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
return;
}
var beforeVch = before.VoiceChannel;
if (beforeVch != null)
{
var beforeRoleName = GetRoleName(beforeVch);
var beforeRole = guild.Roles.FirstOrDefault(x => x.Name == beforeRoleName);
if (beforeRole != null)
try
{
_log.Info("Removing role " + beforeRoleName + " from user " + user.Username);
await user.RemoveRolesAsync(beforeRole).ConfigureAwait(false);
await Task.Delay(200).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
var afterVch = after.VoiceChannel;
if (afterVch != null && guild.AFKChannel?.Id != afterVch.Id)
{
var roleName = GetRoleName(afterVch);
IRole roleToAdd = guild.Roles.FirstOrDefault(x => x.Name == roleName);
if (roleToAdd == null)
roleToAdd = await guild.CreateRoleAsync(roleName).ConfigureAwait(false);
var beforeVch = before.VoiceChannel; ITextChannel textChannel = guild.TextChannels
if (beforeVch != null) .Where(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant())
{ .FirstOrDefault();
var textChannel = guild.TextChannels.Where(t => t.Name == GetChannelName(beforeVch.Name).ToLowerInvariant()).FirstOrDefault(); if (textChannel == null)
if (textChannel != null) {
await textChannel.AddPermissionOverwriteAsync(user, var created = (await guild.CreateTextChannelAsync(GetChannelName(afterVch.Name).ToLowerInvariant()).ConfigureAwait(false));
new OverwritePermissions(readMessages: PermValue.Deny,
sendMessages: PermValue.Deny)).ConfigureAwait(false); try { await guild.CurrentUser.AddRolesAsync(roleToAdd).ConfigureAwait(false); } catch { }
} await Task.Delay(50).ConfigureAwait(false);
var afterVch = after.VoiceChannel; await created.AddPermissionOverwriteAsync(roleToAdd, new OverwritePermissions(
if (afterVch != null && guild.AFKChannel?.Id != afterVch.Id) readMessages: PermValue.Allow,
{ sendMessages: PermValue.Allow))
ITextChannel textChannel = guild.TextChannels .ConfigureAwait(false);
.Where(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant()) await Task.Delay(50).ConfigureAwait(false);
.FirstOrDefault(); await created.AddPermissionOverwriteAsync(guild.EveryoneRole, new OverwritePermissions(
if (textChannel == null) readMessages: PermValue.Deny,
{ sendMessages: PermValue.Deny))
textChannel = (await guild.CreateTextChannelAsync(GetChannelName(afterVch.Name).ToLowerInvariant()).ConfigureAwait(false)); .ConfigureAwait(false);
await textChannel.AddPermissionOverwriteAsync(guild.EveryoneRole, await Task.Delay(50).ConfigureAwait(false);
new OverwritePermissions(readMessages: PermValue.Deny, }
sendMessages: PermValue.Deny)).ConfigureAwait(false); _log.Warn("Adding role " + roleToAdd.Name + " to user " + user.Username);
await user.AddRolesAsync(roleToAdd).ConfigureAwait(false);
}
}
finally
{
semaphore.Release();
} }
await textChannel.AddPermissionOverwriteAsync(user,
new OverwritePermissions(readMessages: PermValue.Allow,
sendMessages: PermValue.Allow)).ConfigureAwait(false);
} }
} catch (Exception ex)
catch (Exception ex) {
{ _log.Warn(ex);
Console.WriteLine(ex); }
} });
return Task.CompletedTask;
} }
private static string GetChannelName(string voiceName) => private static string GetChannelName(string voiceName) =>
channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice"; channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice";
private static string GetRoleName(IVoiceChannel ch) =>
"nvoice-" + ch.Id;
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
@ -127,7 +172,7 @@ namespace NadekoBot.Modules.Administration
{ {
try try
{ {
await Context.Channel.SendErrorAsync("⚠️ You are enabling this feature and **I do not have ADMINISTRATOR permissions**. " + await Context.Channel.SendErrorAsync("⚠️ You are enabling/disabling this feature and **I do not have ADMINISTRATOR permissions**. " +
"`This may cause some issues, and you will have to clean up text channels yourself afterwards.`"); "`This may cause some issues, and you will have to clean up text channels yourself afterwards.`");
} }
catch { } catch { }
@ -147,6 +192,13 @@ namespace NadekoBot.Modules.Administration
foreach (var textChannel in (await guild.GetTextChannelsAsync().ConfigureAwait(false)).Where(c => c.Name.EndsWith("-voice"))) foreach (var textChannel in (await guild.GetTextChannelsAsync().ConfigureAwait(false)).Where(c => c.Name.EndsWith("-voice")))
{ {
try { await textChannel.DeleteAsync().ConfigureAwait(false); } catch { } try { await textChannel.DeleteAsync().ConfigureAwait(false); } catch { }
await Task.Delay(500).ConfigureAwait(false);
}
foreach (var role in guild.Roles.Where(c => c.Name.StartsWith("nvoice-")))
{
try { await role.DeleteAsync().ConfigureAwait(false); } catch { }
await Task.Delay(500).ConfigureAwait(false);
} }
await Context.Channel.SendConfirmAsync(" Successfuly **removed** voice + text feature.").ConfigureAwait(false); await Context.Channel.SendConfirmAsync(" Successfuly **removed** voice + text feature.").ConfigureAwait(false);
return; return;
@ -163,7 +215,9 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)] [RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
//[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task CleanVPlusT() public async Task CleanVPlusT()
{ {
var guild = Context.Guild; var guild = Context.Guild;
@ -174,15 +228,27 @@ namespace NadekoBot.Modules.Administration
return; return;
} }
var allTxtChannels = (await guild.GetTextChannelsAsync()).Where(c => c.Name.EndsWith("-voice")); var textChannels = await guild.GetTextChannelsAsync().ConfigureAwait(false);
var validTxtChannelNames = (await guild.GetVoiceChannelsAsync()).Select(c => GetChannelName(c.Name).ToLowerInvariant()); var voiceChannels = await guild.GetVoiceChannelsAsync().ConfigureAwait(false);
var invalidTxtChannels = allTxtChannels.Where(c => !validTxtChannelNames.Contains(c.Name)); var boundTextChannels = textChannels.Where(c => c.Name.EndsWith("-voice"));
var validTxtChannelNames = new HashSet<string>(voiceChannels.Select(c => GetChannelName(c.Name).ToLowerInvariant()));
var invalidTxtChannels = boundTextChannels.Where(c => !validTxtChannelNames.Contains(c.Name));
foreach (var c in invalidTxtChannels) foreach (var c in invalidTxtChannels)
{ {
try { await c.DeleteAsync().ConfigureAwait(false); } catch { } try { await c.DeleteAsync().ConfigureAwait(false); } catch { }
await Task.Delay(500); await Task.Delay(500).ConfigureAwait(false);
}
var boundRoles = guild.Roles.Where(r => r.Name.StartsWith("nvoice-"));
var validRoleNames = new HashSet<string>(voiceChannels.Select(c => GetRoleName(c).ToLowerInvariant()));
var invalidRoles = boundRoles.Where(r => !validRoleNames.Contains(r.Name));
foreach (var r in invalidRoles)
{
try { await r.DeleteAsync().ConfigureAwait(false); } catch { }
await Task.Delay(500).ConfigureAwait(false);
} }
await Context.Channel.SendConfirmAsync("Cleaned v+t.").ConfigureAwait(false); await Context.Channel.SendConfirmAsync("Cleaned v+t.").ConfigureAwait(false);

View File

@ -78,6 +78,7 @@ namespace NadekoBot.Modules.Gambling
_secretCode += _sneakyGameStatusChars[rng.Next(0, _sneakyGameStatusChars.Length)]; _secretCode += _sneakyGameStatusChars[rng.Next(0, _sneakyGameStatusChars.Length)];
} }
var game = NadekoBot.Client.Game?.Name;
await NadekoBot.Client.SetGameAsync($"type {_secretCode} for " + NadekoBot.BotConfig.CurrencyPluralName) await NadekoBot.Client.SetGameAsync($"type {_secretCode} for " + NadekoBot.BotConfig.CurrencyPluralName)
.ConfigureAwait(false); .ConfigureAwait(false);
try try
@ -94,10 +95,11 @@ namespace NadekoBot.Modules.Gambling
await Task.Delay(num * 1000); await Task.Delay(num * 1000);
NadekoBot.Client.MessageReceived -= SneakyGameMessageReceivedEventHandler; NadekoBot.Client.MessageReceived -= SneakyGameMessageReceivedEventHandler;
var cnt = _sneakyGameAwardedUsers.Count;
_sneakyGameAwardedUsers.Clear(); _sneakyGameAwardedUsers.Clear();
_secretCode = String.Empty; _secretCode = String.Empty;
await NadekoBot.Client.SetGameAsync($"SneakyGame event ended.") await NadekoBot.Client.SetGameAsync($"SneakyGame event ended. {cnt} users received a reward.")
.ConfigureAwait(false); .ConfigureAwait(false);
} }

View File

@ -34,15 +34,11 @@ namespace NadekoBot.Modules.Gambling
var num2 = gen % 10; var num2 = gen % 10;
var imageStream = await Task.Run(() => var imageStream = await Task.Run(() =>
{ {
try var ms = new MemoryStream();
{ new[] { GetDice(num1), GetDice(num2) }.Merge().SaveAsPng(ms);
var ms = new MemoryStream(); ms.Position = 0;
new[] { GetDice(num1), GetDice(num2) }.Merge().SaveAsPng(ms); return ms;
ms.Position = 0; }).ConfigureAwait(false);
return ms;
}
catch { return new MemoryStream(); }
});
await Context.Channel.SendFileAsync(imageStream, "dice.png", $"{Context.User.Mention} rolled " + Format.Code(gen.ToString())).ConfigureAwait(false); await Context.Channel.SendFileAsync(imageStream, "dice.png", $"{Context.User.Mention} rolled " + Format.Code(gen.ToString())).ConfigureAwait(false);
} }
@ -82,7 +78,7 @@ namespace NadekoBot.Modules.Gambling
await InternallDndRoll(arg, false).ConfigureAwait(false); await InternallDndRoll(arg, false).ConfigureAwait(false);
} }
private async Task InternalRoll( int num, bool ordered) private async Task InternalRoll(int num, bool ordered)
{ {
if (num < 1 || num > 30) if (num < 1 || num > 30)
{ {
@ -209,20 +205,24 @@ namespace NadekoBot.Modules.Gambling
private Image GetDice(int num) private Image GetDice(int num)
{ {
const string pathToImage = "data/images/dice"; if (num < 0 || num > 10)
if (num != 10) throw new ArgumentOutOfRangeException(nameof(num));
if (num == 10)
{ {
using (var stream = File.OpenRead(Path.Combine(pathToImage, $"{num}.png"))) var images = NadekoBot.Images.Dice;
return new Image(stream); using (var imgOneStream = images[1].Value.ToStream())
using (var imgZeroStream = images[0].Value.ToStream())
{
Image imgOne = new Image(imgOneStream);
Image imgZero = new Image(imgZeroStream);
return new[] { imgOne, imgZero }.Merge();
}
} }
using (var die = NadekoBot.Images.Dice[num].Value.ToStream())
using (var one = File.OpenRead(Path.Combine(pathToImage, "1.png")))
using (var zero = File.OpenRead(Path.Combine(pathToImage, "0.png")))
{ {
Image imgOne = new Image(one); return new Image(die);
Image imgZero = new Image(zero);
return new[] { imgOne, imgZero }.Merge();
} }
} }
} }

View File

@ -5,6 +5,7 @@ using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Image = ImageSharp.Image; using Image = ImageSharp.Image;
@ -16,34 +17,54 @@ namespace NadekoBot.Modules.Gambling
[Group] [Group]
public class FlipCoinCommands : ModuleBase public class FlipCoinCommands : ModuleBase
{ {
private readonly IImagesService _images;
private static NadekoRandom rng { get; } = new NadekoRandom(); private static NadekoRandom rng { get; } = new NadekoRandom();
private const string headsPath = "data/images/coins/heads.png";
private const string tailsPath = "data/images/coins/tails.png"; public FlipCoinCommands()
{
//todo DI in the future, can't atm
this._images = NadekoBot.Images;
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Flip(int count = 1) public async Task Flip(int count = 1)
{ {
if (count == 1) if (count == 1)
{ {
if (rng.Next(0, 2) == 1) if (rng.Next(0, 2) == 1)
await Context.Channel.SendFileAsync(File.Open(headsPath, FileMode.OpenOrCreate), "heads.jpg", $"{Context.User.Mention} flipped " + Format.Code("Heads") + ".").ConfigureAwait(false); {
using (var heads = _images.Heads.ToStream())
{
await Context.Channel.SendFileAsync(heads, "heads.jpg", $"{Context.User.Mention} flipped " + Format.Code("Heads") + ".").ConfigureAwait(false);
}
}
else else
await Context.Channel.SendFileAsync(File.Open(tailsPath, FileMode.OpenOrCreate), "tails.jpg", $"{Context.User.Mention} flipped " + Format.Code("Tails") + ".").ConfigureAwait(false); {
using (var tails = _images.Tails.ToStream())
{
await Context.Channel.SendFileAsync(tails, "tails.jpg", $"{Context.User.Mention} flipped " + Format.Code("Tails") + ".").ConfigureAwait(false);
}
}
return; return;
} }
if (count > 10 || count < 1) if (count > 10 || count < 1)
{ {
await Context.Channel.SendErrorAsync("`Invalid number specified. You can flip 1 to 10 coins.`"); await Context.Channel.SendErrorAsync("`Invalid number specified. You can flip 1 to 10 coins.`").ConfigureAwait(false);
return; return;
} }
var imgs = new Image[count]; var imgs = new Image[count];
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 ? for (var i = 0; i < count; i++)
new Image(File.OpenRead(headsPath)) : {
new Image(File.OpenRead(tailsPath)); imgs[i] = rng.Next(0, 10) < 5 ?
new Image(heads) :
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] [NadekoCommand, Usage, Description, Aliases]
@ -68,17 +89,18 @@ namespace NadekoBot.Modules.Gambling
//heads = true //heads = true
//tails = false //tails = false
//todo this seems stinky, no time to look at it right now
var isHeads = guessStr == "HEADS" || guessStr == "H"; var isHeads = guessStr == "HEADS" || guessStr == "H";
bool result = false; bool result = false;
string imgPathToSend; IEnumerable<byte> imageToSend;
if (rng.Next(0, 2) == 1) if (rng.Next(0, 2) == 1)
{ {
imgPathToSend = headsPath; imageToSend = _images.Heads;
result = true; result = true;
} }
else else
{ {
imgPathToSend = tailsPath; imageToSend = _images.Tails;
} }
string str; string str;
@ -92,8 +114,10 @@ namespace NadekoBot.Modules.Gambling
{ {
str = $"{Context.User.Mention}`Better luck next time.`"; str = $"{Context.User.Mention}`Better luck next time.`";
} }
using (var toSend = imageToSend.ToStream())
await Context.Channel.SendFileAsync(File.Open(imgPathToSend, FileMode.OpenOrCreate), new FileInfo(imgPathToSend).Name, str).ConfigureAwait(false); {
await Context.Channel.SendFileAsync(toSend, "result.png", str).ConfigureAwait(false);
}
} }
} }
} }

View File

@ -21,52 +21,19 @@ namespace NadekoBot.Modules.Gambling
private static int totalBet = 0; private static int totalBet = 0;
private static int totalPaidOut = 0; private static int totalPaidOut = 0;
private const string backgroundPath = "data/slots/background.png";
private static readonly byte[] backgroundBuffer;
private static readonly byte[][] numbersBuffer = new byte[10][];
private static readonly byte[][] emojiBuffer;
const int alphaCutOut = byte.MaxValue / 3; const int alphaCutOut = byte.MaxValue / 3;
static Slots()
{
backgroundBuffer = File.ReadAllBytes(backgroundPath);
for (int i = 0; i < 10; i++)
{
numbersBuffer[i] = File.ReadAllBytes("data/slots/" + i + ".png");
}
int throwaway;
var emojiFiles = Directory.GetFiles("data/slots/emojis/", "*.png")
.Where(f => int.TryParse(Path.GetFileNameWithoutExtension(f), out throwaway))
.OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f)))
.ToArray();
emojiBuffer = new byte[emojiFiles.Length][];
for (int i = 0; i < emojiFiles.Length; i++)
{
emojiBuffer[i] = File.ReadAllBytes(emojiFiles[i]);
}
}
private static MemoryStream InternalGetStream(string path)
{
var ms = new MemoryStream();
using (var fs = File.Open(path, FileMode.Open))
{
fs.CopyTo(ms);
fs.Flush();
}
ms.Position = 0;
return ms;
}
//here is a payout chart //here is a payout chart
//https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg //https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg
//thanks to judge for helping me with this //thanks to judge for helping me with this
private readonly IImagesService _images;
public Slots()
{
this._images = NadekoBot.Images;
}
public class SlotMachine public class SlotMachine
{ {
public const int MaxValue = 5; public const int MaxValue = 5;
@ -154,7 +121,7 @@ namespace NadekoBot.Modules.Gambling
var sb = new StringBuilder(); var sb = new StringBuilder();
const int bet = 1; const int bet = 1;
int payout = 0; int payout = 0;
foreach (var key in dict.Keys.OrderByDescending(x=>x)) foreach (var key in dict.Keys.OrderByDescending(x => x))
{ {
sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%"); sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%");
payout += key * dict[key]; payout += key * dict[key];
@ -164,6 +131,7 @@ namespace NadekoBot.Modules.Gambling
} }
static HashSet<ulong> runningUsers = new HashSet<ulong>(); static HashSet<ulong> runningUsers = new HashSet<ulong>();
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Slot(int amount = 0) public async Task Slot(int amount = 0)
{ {
@ -189,7 +157,7 @@ namespace NadekoBot.Modules.Gambling
return; return;
} }
Interlocked.Add(ref totalBet, amount); Interlocked.Add(ref totalBet, amount);
using (var bgFileStream = new MemoryStream(backgroundBuffer)) using (var bgFileStream = NadekoBot.Images.SlotBackground.ToStream())
{ {
var bgImage = new ImageSharp.Image(bgFileStream); var bgImage = new ImageSharp.Image(bgFileStream);
@ -199,7 +167,7 @@ namespace NadekoBot.Modules.Gambling
{ {
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
{ {
using (var file = new MemoryStream(emojiBuffer[numbers[i]])) using (var file = _images.SlotEmojis[numbers[i]].ToStream())
{ {
var randomImage = new ImageSharp.Image(file); var randomImage = new ImageSharp.Image(file);
using (var toAdd = randomImage.Lock()) using (var toAdd = randomImage.Lock())
@ -226,7 +194,7 @@ namespace NadekoBot.Modules.Gambling
do do
{ {
var digit = printWon % 10; var digit = printWon % 10;
using (var fs = new MemoryStream(numbersBuffer[digit])) using (var fs = NadekoBot.Images.SlotNumbers[digit].ToStream())
{ {
var img = new ImageSharp.Image(fs); var img = new ImageSharp.Image(fs);
using (var pixels = img.Lock()) using (var pixels = img.Lock())
@ -251,7 +219,7 @@ namespace NadekoBot.Modules.Gambling
do do
{ {
var digit = printAmount % 10; var digit = printAmount % 10;
using (var fs = new MemoryStream(numbersBuffer[digit])) using (var fs = _images.SlotNumbers[digit].ToStream())
{ {
var img = new ImageSharp.Image(fs); var img = new ImageSharp.Image(fs);
using (var pixels = img.Lock()) using (var pixels = img.Lock())
@ -301,4 +269,4 @@ namespace NadekoBot.Modules.Gambling
} }
} }
} }
} }

View File

@ -10,6 +10,7 @@ using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -51,62 +52,73 @@ namespace NadekoBot.Modules.Games
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId))); .SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
} }
private static async Task PotentialFlowerGeneration(SocketMessage imsg) private static Task PotentialFlowerGeneration(SocketMessage imsg)
{ {
try var msg = imsg as SocketUserMessage;
if (msg == null || msg.IsAuthor() || msg.Author.IsBot)
return Task.CompletedTask;
var channel = imsg.Channel as ITextChannel;
if (channel == null)
return Task.CompletedTask;
if (!generationChannels.Contains(channel.Id))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{ {
var msg = imsg as SocketUserMessage; try
if (msg == null || msg.IsAuthor() || msg.Author.IsBot)
return;
var channel = imsg.Channel as ITextChannel;
if (channel == null)
return;
if (!generationChannels.Contains(channel.Id))
return;
var lastGeneration = lastGenerations.GetOrAdd(channel.Id, DateTime.MinValue);
var rng = new NadekoRandom();
if (DateTime.Now - TimeSpan.FromSeconds(NadekoBot.BotConfig.CurrencyGenerationCooldown) < lastGeneration) //recently generated in this channel, don't generate again
return;
var num = rng.Next(1, 101) + NadekoBot.BotConfig.CurrencyGenerationChance * 100;
if (num > 100)
{ {
lastGenerations.AddOrUpdate(channel.Id, DateTime.Now, (id, old) => DateTime.Now); var lastGeneration = lastGenerations.GetOrAdd(channel.Id, DateTime.MinValue);
var rng = new NadekoRandom();
var dropAmount = NadekoBot.BotConfig.CurrencyDropAmount; //todo i'm stupid :rofl: wtg kwoth. real async programming :100: :ok_hand: :100: :100: :thumbsup:
if (DateTime.Now - TimeSpan.FromSeconds(NadekoBot.BotConfig.CurrencyGenerationCooldown) < lastGeneration) //recently generated in this channel, don't generate again
return;
if (dropAmount > 0) var num = rng.Next(1, 101) + NadekoBot.BotConfig.CurrencyGenerationChance * 100;
if (num > 100)
{ {
var msgs = new IUserMessage[dropAmount]; lastGenerations.AddOrUpdate(channel.Id, DateTime.Now, (id, old) => DateTime.Now);
string firstPart; var dropAmount = NadekoBot.BotConfig.CurrencyDropAmount;
if (dropAmount == 1)
if (dropAmount > 0)
{ {
firstPart = $"A random { NadekoBot.BotConfig.CurrencyName } appeared!"; var msgs = new IUserMessage[dropAmount];
}
else
{
firstPart = $"{dropAmount} random { NadekoBot.BotConfig.CurrencyPluralName } appeared!";
}
var file = GetRandomCurrencyImagePath();
var sent = await channel.SendFileAsync(
File.Open(file, FileMode.OpenOrCreate),
new FileInfo(file).Name,
$"❗ {firstPart} Pick it up by typing `{NadekoBot.ModulePrefixes[typeof(Games).Name]}pick`")
.ConfigureAwait(false);
msgs[0] = sent; string firstPart;
if (dropAmount == 1)
{
firstPart = $"A random { NadekoBot.BotConfig.CurrencyName } appeared!";
}
else
{
firstPart = $"{dropAmount} random { NadekoBot.BotConfig.CurrencyPluralName } appeared!";
}
var file = GetRandomCurrencyImage();
using (var fileStream = file.Value.ToStream())
{
var sent = await channel.SendFileAsync(
fileStream,
file.Key,
$"❗ {firstPart} Pick it up by typing `{NadekoBot.ModulePrefixes[typeof(Games).Name]}pick`")
.ConfigureAwait(false);
plantedFlowers.AddOrUpdate(channel.Id, msgs.ToList(), (id, old) => { old.AddRange(msgs); return old; }); msgs[0] = sent;
}
plantedFlowers.AddOrUpdate(channel.Id, msgs.ToList(), (id, old) => { old.AddRange(msgs); return old; });
}
} }
} }
} catch (Exception ex)
catch { } {
_log.Warn(ex);
}
});
return Task.CompletedTask;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -159,18 +171,15 @@ namespace NadekoBot.Modules.Games
return; return;
} }
var file = GetRandomCurrencyImagePath(); var imgData = GetRandomCurrencyImage();
IUserMessage msg;
var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.BotConfig.CurrencyName[0]); 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 {NadekoBot.ModulePrefixes[typeof(Games).Name]}pick"; 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 {NadekoBot.ModulePrefixes[typeof(Games).Name]}pick";
if (file == null)
IUserMessage msg;
using (var toSend = imgData.Value.ToStream())
{ {
msg = await Context.Channel.SendConfirmAsync(NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false); msg = await Context.Channel.SendFileAsync(toSend, imgData.Key, msgToSend).ConfigureAwait(false);
}
else
{
msg = await Context.Channel.SendFileAsync(File.Open(file, FileMode.OpenOrCreate), new FileInfo(file).Name, msgToSend).ConfigureAwait(false);
} }
var msgs = new IUserMessage[amount]; var msgs = new IUserMessage[amount];
@ -220,10 +229,12 @@ namespace NadekoBot.Modules.Games
} }
} }
private static string GetRandomCurrencyImagePath() private static KeyValuePair<string, ImmutableArray<byte>> GetRandomCurrencyImage()
{ {
var rng = new NadekoRandom(); var rng = new NadekoRandom();
return Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault(); var images = NadekoBot.Images.Currency;
return images[rng.Next(0, images.Length)];
} }
int GetRandomNumber() int GetRandomNumber()

View File

@ -140,24 +140,6 @@ namespace NadekoBot.Modules.NSFW
} }
} }
[NadekoCommand, Usage, Description, Aliases]
public async Task Danbooru([Remainder] string tag = null)
{
tag = tag?.Trim() ?? "";
var url = await GetDanbooruImageLink(tag).ConfigureAwait(false);
if (url == null)
await Context.Channel.SendErrorAsync(Context.User.Mention + " No results.").ConfigureAwait(false);
else
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription(Context.User.Mention + " " + tag)
.WithImageUrl(url)
.WithFooter(efb => efb.WithText("Danbooru")))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public Task Yandere([Remainder] string tag = null) public Task Yandere([Remainder] string tag = null)
=> Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Yandere); => Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Yandere);
@ -166,10 +148,6 @@ namespace NadekoBot.Modules.NSFW
public Task Konachan([Remainder] string tag = null) public Task Konachan([Remainder] string tag = null)
=> Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Konachan); => Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Konachan);
[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] [NadekoCommand, Usage, Description, Aliases]
public Task Rule34([Remainder] string tag = null) public Task Rule34([Remainder] string tag = null)
=> Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Rule34); => Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Rule34);
@ -191,6 +169,49 @@ namespace NadekoBot.Modules.NSFW
.ConfigureAwait(false); .ConfigureAwait(false);
} }
#endif #endif
[NadekoCommand, Usage, Description, Aliases]
public async Task Danbooru([Remainder] string tag = null)
{
tag = tag?.Trim() ?? "";
var url = await GetDanbooruImageLink(tag).ConfigureAwait(false);
if (url == null)
await Context.Channel.SendErrorAsync(Context.User.Mention + " No results.").ConfigureAwait(false);
else
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription(Context.User.Mention + " " + tag)
.WithImageUrl(url)
.WithFooter(efb => efb.WithText("Danbooru")))
.ConfigureAwait(false);
}
public static Task<string> GetDanbooruImageLink(string tag) => Task.Run(async () =>
{
try
{
using (var http = new HttpClient())
{
http.AddFakeHeaders();
var data = await http.GetStreamAsync("https://danbooru.donmai.us/posts.xml?limit=100&tags=" + tag).ConfigureAwait(false);
var doc = new XmlDocument();
doc.Load(data);
var nodes = doc.GetElementsByTagName("file-url");
var node = nodes[new NadekoRandom().Next(0, nodes.Count)];
return "https://danbooru.donmai.us" + node.InnerText;
}
}
catch
{
return null;
}
});
[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] [NadekoCommand, Usage, Description, Aliases]
public async Task Cp() public async Task Cp()
{ {
@ -233,27 +254,6 @@ namespace NadekoBot.Modules.NSFW
} }
} }
#if !GLOBAL_NADEKO #if !GLOBAL_NADEKO
public static Task<string> GetDanbooruImageLink(string tag) => Task.Run(async () =>
{
try
{
using (var http = new HttpClient())
{
http.AddFakeHeaders();
var data = await http.GetStreamAsync("https://danbooru.donmai.us/posts.xml?limit=100&tags=" + tag).ConfigureAwait(false);
var doc = new XmlDocument();
doc.Load(data);
var nodes = doc.GetElementsByTagName("file-url");
var node = nodes[new NadekoRandom().Next(0, nodes.Count)];
return "https://danbooru.donmai.us" + node.InnerText;
}
}
catch
{
return null;
}
});
public static Task<string> GetE621ImageLink(string tag) => Task.Run(async () => public static Task<string> GetE621ImageLink(string tag) => Task.Run(async () =>

View File

@ -33,6 +33,7 @@ namespace NadekoBot
public static GoogleApiService Google { get; private set; } public static GoogleApiService Google { get; private set; }
public static StatsService Stats { get; private set; } public static StatsService Stats { get; private set; }
public static IImagesService Images { get; private set; }
public static ConcurrentDictionary<string, string> ModulePrefixes { get; private set; } public static ConcurrentDictionary<string, string> ModulePrefixes { get; private set; }
public static bool Ready { get; private set; } public static bool Ready { get; private set; }
@ -67,8 +68,12 @@ namespace NadekoBot
MessageCacheSize = 10, MessageCacheSize = 10,
LogLevel = LogSeverity.Warning, LogLevel = LogSeverity.Warning,
TotalShards = Credentials.TotalShards, TotalShards = Credentials.TotalShards,
ConnectionTimeout = int.MaxValue ConnectionTimeout = int.MaxValue,
#if !GLOBAL_NADEKO
//AlwaysDownloadUsers = true,
#endif
}); });
#if GLOBAL_NADEKO #if GLOBAL_NADEKO
Client.Log += Client_Log; Client.Log += Client_Log;
#endif #endif
@ -81,6 +86,7 @@ namespace NadekoBot
Google = new GoogleApiService(); Google = new GoogleApiService();
CommandHandler = new CommandHandler(Client, CommandService); CommandHandler = new CommandHandler(Client, CommandService);
Stats = new StatsService(Client, CommandHandler); Stats = new StatsService(Client, CommandHandler);
Images = await ImagesService.Create().ConfigureAwait(false);
////setup DI ////setup DI
//var depMap = new DependencyMap(); //var depMap = new DependencyMap();
@ -112,7 +118,7 @@ namespace NadekoBot
await CommandHandler.StartHandling().ConfigureAwait(false); await CommandHandler.StartHandling().ConfigureAwait(false);
await CommandService.AddModulesAsync(this.GetType().GetTypeInfo().Assembly).ConfigureAwait(false); var _ = await Task.Run(() => CommandService.AddModulesAsync(this.GetType().GetTypeInfo().Assembly)).ConfigureAwait(false);
#if !GLOBAL_NADEKO #if !GLOBAL_NADEKO
await CommandService.AddModuleAsync<Music>().ConfigureAwait(false); await CommandService.AddModuleAsync<Music>().ConfigureAwait(false);
#endif #endif

View File

@ -5621,6 +5621,33 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to reloadimages.
/// </summary>
public static string reloadimages_cmd {
get {
return ResourceManager.GetString("reloadimages_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reloads images bot is using. Safe to use even when bot is being used heavily..
/// </summary>
public static string reloadimages_desc {
get {
return ResourceManager.GetString("reloadimages_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}reloadimages`.
/// </summary>
public static string reloadimages_usage {
get {
return ResourceManager.GetString("reloadimages_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to remind. /// Looks up a localized string similar to remind.
/// </summary> /// </summary>

View File

@ -3042,4 +3042,13 @@
<data name="setmusicchannel_usage" xml:space="preserve"> <data name="setmusicchannel_usage" xml:space="preserve">
<value>`{0}smch`</value> <value>`{0}smch`</value>
</data> </data>
<data name="reloadimages_cmd" xml:space="preserve">
<value>reloadimages</value>
</data>
<data name="reloadimages_desc" xml:space="preserve">
<value>Reloads images bot is using. Safe to use even when bot is being used heavily.</value>
</data>
<data name="reloadimages_usage" xml:space="preserve">
<value>`{0}reloadimages`</value>
</data>
</root> </root>

View File

@ -105,9 +105,8 @@ namespace NadekoBot.Services
BlacklistCommands.BlacklistedUsers.Contains(usrMsg.Author.Id); BlacklistCommands.BlacklistedUsers.Contains(usrMsg.Author.Id);
const float oneThousandth = 1.0f / 1000; const float oneThousandth = 1.0f / 1000;
private async Task LogSuccessfulExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, int ticks) private Task LogSuccessfulExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, int ticks)
{ {
await CommandExecuted(usrMsg, exec.CommandInfo).ConfigureAwait(false);
_log.Info("Command Executed after {4}s\n\t" + _log.Info("Command Executed after {4}s\n\t" +
"User: {0}\n\t" + "User: {0}\n\t" +
"Server: {1}\n\t" + "Server: {1}\n\t" +
@ -118,6 +117,7 @@ namespace NadekoBot.Services
(channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2}
usrMsg.Content, // {3} usrMsg.Content, // {3}
ticks * oneThousandth); ticks * oneThousandth);
return Task.CompletedTask;
} }
private void LogErroredExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, int ticks) private void LogErroredExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, int ticks)
@ -184,93 +184,98 @@ namespace NadekoBot.Services
return false; return false;
} }
private async Task MessageReceivedHandler(SocketMessage msg) private Task MessageReceivedHandler(SocketMessage msg)
{ {
try var _ = Task.Run(async () =>
{ {
if (msg.Author.IsBot || !NadekoBot.Ready) //no bots, wait until bot connected and initialized try
return; {
if (msg.Author.IsBot || !NadekoBot.Ready) //no bots, wait until bot connected and initialized
return;
var execTime = Environment.TickCount; var execTime = Environment.TickCount;
var usrMsg = msg as SocketUserMessage; var usrMsg = msg as SocketUserMessage;
if (usrMsg == null) //has to be an user message, not system/other messages. if (usrMsg == null) //has to be an user message, not system/other messages.
return; return;
#if !GLOBAL_NADEKO #if !GLOBAL_NADEKO
// track how many messagges each user is sending // track how many messagges each user is sending
UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old); UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old);
#endif #endif
var channel = msg.Channel as SocketTextChannel; var channel = msg.Channel as SocketTextChannel;
var guild = channel?.Guild; var guild = channel?.Guild;
if (guild != null && guild.OwnerId != msg.Author.Id) if (guild != null && guild.OwnerId != msg.Author.Id)
{ {
if (await InviteFiltered(guild, usrMsg).ConfigureAwait(false)) if (await InviteFiltered(guild, usrMsg).ConfigureAwait(false))
return;
if (await WordFiltered(guild, usrMsg).ConfigureAwait(false))
return;
}
if (IsBlacklisted(guild, usrMsg))
return; return;
if (await WordFiltered(guild, usrMsg).ConfigureAwait(false)) var cleverBotRan = await Task.Run(() => TryRunCleverbot(usrMsg, guild)).ConfigureAwait(false);
if (cleverBotRan)
return; return;
}
if (IsBlacklisted(guild, usrMsg)) // maybe this message is a custom reaction
return; // todo log custom reaction executions. return struct with info
var crExecuted = await Task.Run(() => CustomReactions.TryExecuteCustomReaction(usrMsg)).ConfigureAwait(false);
if (crExecuted) //if it was, don't execute the command
return;
var cleverBotRan = await Task.Run(() => TryRunCleverbot(usrMsg, guild)).ConfigureAwait(false); string messageContent = usrMsg.Content;
if (cleverBotRan)
return;
// maybe this message is a custom reaction // execute the command and measure the time it took
// todo log custom reaction executions. return struct with info var exec = await Task.Run(() => ExecuteCommand(new CommandContext(_client, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best)).ConfigureAwait(false);
var crExecuted = await Task.Run(() => CustomReactions.TryExecuteCustomReaction(usrMsg)).ConfigureAwait(false); execTime = Environment.TickCount - execTime;
if (crExecuted) //if it was, don't execute the command
return;
string messageContent = usrMsg.Content; if (exec.Result.IsSuccess)
// execute the command and measure the time it took
var exec = await Task.Run(() => ExecuteCommand(new CommandContext(_client, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best)).ConfigureAwait(false);
execTime = Environment.TickCount - execTime;
if (exec.Result.IsSuccess)
{
await LogSuccessfulExecution(usrMsg, exec, channel, execTime).ConfigureAwait(false);
}
else if (!exec.Result.IsSuccess && exec.Result.Error != CommandError.UnknownCommand)
{
LogErroredExecution(usrMsg, exec, channel, execTime);
if (guild != null && exec.CommandInfo != null && exec.Result.Error == CommandError.Exception)
{ {
if (exec.PermissionCache != null && exec.PermissionCache.Verbose) await CommandExecuted(usrMsg, exec.CommandInfo).ConfigureAwait(false);
try { await msg.Channel.SendMessageAsync("⚠️ " + exec.Result.ErrorReason).ConfigureAwait(false); } catch { } await LogSuccessfulExecution(usrMsg, exec, channel, execTime).ConfigureAwait(false);
}
else if (!exec.Result.IsSuccess && exec.Result.Error != CommandError.UnknownCommand)
{
LogErroredExecution(usrMsg, exec, channel, execTime);
if (guild != null && exec.CommandInfo != null && exec.Result.Error == CommandError.Exception)
{
if (exec.PermissionCache != null && exec.PermissionCache.Verbose)
try { await msg.Channel.SendMessageAsync("⚠️ " + exec.Result.ErrorReason).ConfigureAwait(false); } catch { }
}
}
else
{
if (msg.Channel is IPrivateChannel)
{
// rofl, gotta do this to prevent dm help message being sent to
// users who are voting on private polls (sending a number in a DM)
int vote;
if (int.TryParse(msg.Content, out vote)) return;
await msg.Channel.SendMessageAsync(Help.DMHelpString).ConfigureAwait(false);
await DMForwardCommands.HandleDMForwarding(msg, ownerChannels).ConfigureAwait(false);
}
} }
} }
else catch (Exception ex)
{ {
if (msg.Channel is IPrivateChannel) _log.Warn("Error in CommandHandler");
_log.Warn(ex);
if (ex.InnerException != null)
{ {
// rofl, gotta do this to prevent dm help message being sent to _log.Warn("Inner Exception of the error in CommandHandler");
// users who are voting on private polls (sending a number in a DM) _log.Warn(ex.InnerException);
int vote;
if (int.TryParse(msg.Content, out vote)) return;
await msg.Channel.SendMessageAsync(Help.DMHelpString).ConfigureAwait(false);
await DMForwardCommands.HandleDMForwarding(msg, ownerChannels).ConfigureAwait(false);
} }
} }
} });
catch (Exception ex) return Task.CompletedTask;
{
_log.Warn("Error in CommandHandler");
_log.Warn(ex);
if (ex.InnerException != null)
{
_log.Warn("Inner Exception of the error in CommandHandler");
_log.Warn(ex.InnerException);
}
}
} }
public Task<ExecuteCommandResult> ExecuteCommandAsync(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) public Task<ExecuteCommandResult> ExecuteCommandAsync(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)

View File

@ -0,0 +1,26 @@
using NadekoBot.DataStructures;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services
{
public interface IImagesService
{
ImmutableArray<byte> Heads { get; }
ImmutableArray<byte> Tails { get; }
ImmutableArray<KeyValuePair<string, ImmutableArray<byte>>> Currency { get; }
ImmutableArray<KeyValuePair<string, ImmutableArray<byte>>> Dice { get; }
ImmutableArray<byte> SlotBackground { get; }
ImmutableArray<ImmutableArray<byte>> SlotEmojis { get; }
ImmutableArray<ImmutableArray<byte>> SlotNumbers { get; }
Task<TimeSpan> Reload();
}
}

View File

@ -0,0 +1,99 @@
using NadekoBot.DataStructures;
using NLog;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services.Impl
{
public class ImagesService : IImagesService
{
private readonly Logger _log;
private const string basePath = "data/images/";
private const string headsPath = basePath + "coins/heads.png";
private const string tailsPath = basePath + "coins/tails.png";
private const string currencyImagesPath = basePath + "currency";
private const string diceImagesPath = basePath + "dice";
private const string slotBackgroundPath = basePath + "slots/background.png";
private const string slotNumbersPath = basePath + "slots/numbers/";
private const string slotEmojisPath = basePath + "slots/emojis/";
public ImmutableArray<byte> Heads { get; private set; }
public ImmutableArray<byte> Tails { get; private set; }
//todo C#7
public ImmutableArray<KeyValuePair<string, ImmutableArray<byte>>> Currency { get; private set; }
public ImmutableArray<KeyValuePair<string, ImmutableArray<byte>>> Dice { get; private set; }
public ImmutableArray<byte> SlotBackground { get; private set; }
public ImmutableArray<ImmutableArray<byte>> SlotNumbers { get; private set; }
public ImmutableArray<ImmutableArray<byte>> SlotEmojis { get; private set; }
private ImagesService()
{
_log = LogManager.GetCurrentClassLogger();
}
public static async Task<IImagesService> Create()
{
var srvc = new ImagesService();
await srvc.Reload().ConfigureAwait(false);
return srvc;
}
public Task<TimeSpan> Reload() => Task.Run(() =>
{
try
{
_log.Info("Loading images...");
var sw = Stopwatch.StartNew();
Heads = File.ReadAllBytes(headsPath).ToImmutableArray();
Tails = File.ReadAllBytes(tailsPath).ToImmutableArray();
Currency = Directory.GetFiles(currencyImagesPath)
.Select(x => new KeyValuePair<string, ImmutableArray<byte>>(
Path.GetFileName(x),
File.ReadAllBytes(x).ToImmutableArray()))
.ToImmutableArray();
Dice = Directory.GetFiles(diceImagesPath)
.OrderBy(x => int.Parse(Path.GetFileNameWithoutExtension(x)))
.Select(x => new KeyValuePair<string, ImmutableArray<byte>>(x,
File.ReadAllBytes(x).ToImmutableArray()))
.ToImmutableArray();
SlotBackground = File.ReadAllBytes(slotBackgroundPath).ToImmutableArray();
SlotNumbers = Directory.GetFiles(slotNumbersPath)
.OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f)))
.Select(x => File.ReadAllBytes(x).ToImmutableArray())
.ToImmutableArray();
SlotEmojis = Directory.GetFiles(slotEmojisPath)
.Select(x => File.ReadAllBytes(x).ToImmutableArray())
.ToImmutableArray();
sw.Stop();
_log.Info($"Images loaded after {sw.Elapsed.TotalSeconds:F2}s!");
return sw.Elapsed;
}
catch (Exception ex)
{
_log.Error(ex);
throw;
}
});
}
}

View File

@ -15,7 +15,7 @@ namespace NadekoBot.Services.Impl
private DiscordShardedClient client; private DiscordShardedClient client;
private DateTime started; private DateTime started;
public const string BotVersion = "1.1.5a"; public const string BotVersion = "1.1.6";
public string Author => "Kwoth#2560"; public string Author => "Kwoth#2560";
public string Library => "Discord.Net"; public string Library => "Discord.Net";

View File

@ -1,199 +0,0 @@
using Discord;
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NLog;
using System.Diagnostics;
namespace NadekoBot
{
public class ShardedDiscordClient
{
private DiscordSocketConfig discordSocketConfig;
private Logger _log { get; }
public event Action<SocketGuildUser> UserJoined = delegate { };
public event Action<SocketMessage> MessageReceived = delegate { };
public event Action<SocketGuildUser> UserLeft = delegate { };
public event Action<SocketUser, SocketUser> UserUpdated = delegate { };
public event Action<SocketGuildUser, SocketGuildUser> GuildUserUpdated = delegate { };
public event Action<Optional<SocketMessage>, SocketMessage> MessageUpdated = delegate { };
public event Action<ulong, Optional<SocketMessage>> MessageDeleted = delegate { };
public event Action<SocketUser, SocketGuild> UserBanned = delegate { };
public event Action<SocketUser, SocketGuild> UserUnbanned = delegate { };
public event Action<Optional<SocketGuild>, SocketUser, SocketPresence, SocketPresence> UserPresenceUpdated = delegate { };
public event Action<SocketUser, SocketVoiceState, SocketVoiceState> UserVoiceStateUpdated = delegate { };
public event Action<SocketChannel> ChannelCreated = delegate { };
public event Action<SocketChannel> ChannelDestroyed = delegate { };
public event Action<SocketChannel, SocketChannel> ChannelUpdated = delegate { };
public event Action<ulong, Optional<SocketUserMessage>, SocketReaction> ReactionAdded = delegate { };
public event Action<ulong, Optional<SocketUserMessage>, SocketReaction> ReactionRemoved = delegate { };
public event Action<ulong, Optional<SocketUserMessage>> ReactionsCleared = delegate { };
public event Action<SocketGuild> JoinedGuild = delegate { };
public event Action<SocketGuild> LeftGuild = delegate { };
public event Action<Exception> Disconnected = delegate { };
public event Action Connected = delegate { };
private uint _connectedCount = 0;
private uint _downloadedCount = 0;
private int _guildCount = 0;
private IReadOnlyList<DiscordSocketClient> Clients { get; }
public ShardedDiscordClient(DiscordSocketConfig discordSocketConfig)
{
_log = LogManager.GetCurrentClassLogger();
this.discordSocketConfig = discordSocketConfig;
var clientList = new List<DiscordSocketClient>();
for (int i = 0; i < discordSocketConfig.TotalShards; i++)
{
discordSocketConfig.ShardId = i;
var client = new DiscordSocketClient(discordSocketConfig);
clientList.Add(client);
client.UserJoined += arg1 => { UserJoined(arg1); return Task.CompletedTask; };
client.MessageReceived += arg1 =>
{
if (arg1.Author == null || arg1.Author.IsBot)
return Task.CompletedTask;
MessageReceived(arg1);
return Task.CompletedTask;
};
client.UserLeft += arg1 => { UserLeft(arg1); return Task.CompletedTask; };
client.UserUpdated += (arg1, gu2) => { UserUpdated(arg1, gu2); return Task.CompletedTask; };
client.GuildMemberUpdated += (arg1, arg2) => { GuildUserUpdated(arg1, arg2); return Task.CompletedTask; };
client.MessageUpdated += (arg1, m2) => { MessageUpdated(arg1, m2); return Task.CompletedTask; };
client.MessageDeleted += (arg1, arg2) => { MessageDeleted(arg1, arg2); return Task.CompletedTask; };
client.UserBanned += (arg1, arg2) => { UserBanned(arg1, arg2); return Task.CompletedTask; };
client.UserUnbanned += (arg1, arg2) => { UserUnbanned(arg1, arg2); return Task.CompletedTask; };
client.UserPresenceUpdated += (arg1, arg2, arg3, arg4) => { UserPresenceUpdated(arg1, arg2, arg3, arg4); return Task.CompletedTask; };
client.UserVoiceStateUpdated += (arg1, arg2, arg3) => { UserVoiceStateUpdated(arg1, arg2, arg3); return Task.CompletedTask; };
client.ChannelCreated += arg => { ChannelCreated(arg); return Task.CompletedTask; };
client.ChannelDestroyed += arg => { ChannelDestroyed(arg); return Task.CompletedTask; };
client.ChannelUpdated += (arg1, arg2) => { ChannelUpdated(arg1, arg2); return Task.CompletedTask; };
client.JoinedGuild += (arg1) => { JoinedGuild(arg1); ++_guildCount; return Task.CompletedTask; };
client.LeftGuild += (arg1) => { LeftGuild(arg1); --_guildCount; return Task.CompletedTask; };
client.ReactionAdded += (arg1, arg2, arg3) => { ReactionAdded(arg1, arg2, arg3); return Task.CompletedTask; };
client.ReactionRemoved += (arg1, arg2, arg3) => { ReactionRemoved(arg1, arg2, arg3); return Task.CompletedTask; };
client.ReactionsCleared += (arg1, arg2) => { ReactionsCleared(arg1, arg2); return Task.CompletedTask; };
_log.Info($"Shard #{i} initialized.");
#if GLOBAL_NADEKO
client.Log += Client_Log;
#endif
var j = i;
client.Disconnected += (ex) =>
{
_log.Error("Shard #{0} disconnected", j);
_log.Error(ex, ex?.Message ?? "No error");
return Task.CompletedTask;
};
}
Clients = clientList.AsReadOnly();
}
private Task Client_Log(LogMessage arg)
{
_log.Warn(arg.Message);
_log.Error(arg.Exception);
return Task.CompletedTask;
}
public DiscordSocketClient MainClient =>
Clients[0];
public SocketSelfUser CurrentUser =>
Clients[0].CurrentUser;
public IEnumerable<SocketGuild> GetGuilds() =>
Clients.SelectMany(c => c.Guilds);
public int GetGuildsCount() =>
_guildCount;
public SocketGuild GetGuild(ulong id)
{
foreach (var c in Clients)
{
var g = c.GetGuild(id);
if (g != null)
return g;
}
return null;
}
public Task<IDMChannel> GetDMChannelAsync(ulong channelId) =>
Clients[0].GetDMChannelAsync(channelId);
internal async Task LoginAsync(TokenType tokenType, string token)
{
foreach (var c in Clients)
{
await c.LoginAsync(tokenType, token).ConfigureAwait(false);
_log.Info($"Shard #{c.ShardId} logged in.");
}
}
internal async Task ConnectAsync()
{
foreach (var c in Clients)
{
try
{
var sw = Stopwatch.StartNew();
await c.ConnectAsync().ConfigureAwait(false);
sw.Stop();
_log.Info($"Shard #{c.ShardId} connected after {sw.Elapsed.TotalSeconds:F2}s ({++_connectedCount}/{Clients.Count})");
_guildCount += c.Guilds.Count;
}
catch
{
_log.Error($"Shard #{c.ShardId} FAILED CONNECTING.");
try { await c.ConnectAsync().ConfigureAwait(false); }
catch (Exception ex2)
{
_log.Error($"Shard #{c.ShardId} FAILED CONNECTING TWICE.");
_log.Error(ex2);
}
}
}
Connected();
}
internal Task DownloadAllUsersAsync() =>
Task.WhenAll(Clients.Select(async c =>
{
var sw = Stopwatch.StartNew();
await c.DownloadAllUsersAsync().ConfigureAwait(false);
sw.Stop();
_log.Info($"Shard #{c.ShardId} downloaded {c.Guilds.Sum(g => g.Users.Count)} users after {sw.Elapsed.TotalSeconds:F2}s ({++_downloadedCount}/{Clients.Count}).");
}));
public Task SetGame(string game) => Task.WhenAll(Clients.Select(ms => ms.SetGameAsync(game)));
public Task SetStream(string name, string url) => Task.WhenAll(Clients.Select(ms => ms.SetGameAsync(name, url, StreamType.Twitch)));
//public Task SetStatus(SettableUserStatus status) => Task.WhenAll(Clients.Select(ms => ms.SetStatusAsync(SettableUserStatusToUserStatus(status))));
}
public enum SettableUserStatus
{
Online = 1,
On = 1,
Invisible = 2,
Invis = 2,
Idle = 3,
Afk = 3,
Dnd = 4,
DoNotDisturb = 4,
Busy = 4,
}
}

View File

@ -20,7 +20,10 @@ namespace NadekoBot.Extensions
{ {
private const string arrow_left = "⬅"; private const string arrow_left = "⬅";
private const string arrow_right = "➡"; private const string arrow_right = "➡";
public static Stream ToStream(this IEnumerable<byte> bytes, bool canWrite = false)
=> new MemoryStream(bytes as byte[] ?? bytes.ToArray(), canWrite);
/// <summary> /// <summary>
/// danny kamisama /// danny kamisama
/// </summary> /// </summary>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

View File

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 551 B

After

Width:  |  Height:  |  Size: 551 B

View File

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

View File

Before

Width:  |  Height:  |  Size: 553 B

After

Width:  |  Height:  |  Size: 553 B

View File

Before

Width:  |  Height:  |  Size: 539 B

After

Width:  |  Height:  |  Size: 539 B

View File

Before

Width:  |  Height:  |  Size: 471 B

After

Width:  |  Height:  |  Size: 471 B

View File

Before

Width:  |  Height:  |  Size: 542 B

After

Width:  |  Height:  |  Size: 542 B

View File

Before

Width:  |  Height:  |  Size: 562 B

After

Width:  |  Height:  |  Size: 562 B

View File

Before

Width:  |  Height:  |  Size: 417 B

After

Width:  |  Height:  |  Size: 417 B

View File

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 597 B

View File

Before

Width:  |  Height:  |  Size: 578 B

After

Width:  |  Height:  |  Size: 578 B