diff --git a/.gitignore b/.gitignore index 0fa1ef5e..5169035d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ #Manually added files +patreon_rewards.json command_errors*.txt src/NadekoBot/Command Errors*.txt diff --git a/NadekoBot.sln b/NadekoBot.sln index a80335e2..42b343e5 100644 --- a/NadekoBot.sln +++ b/NadekoBot.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26430.6 +VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}" EndProject @@ -33,4 +33,7 @@ Global GlobalSection(NestedProjects) = preSolution {45EC1473-C678-4857-A544-07DFE0D0B478} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4} + EndGlobalSection EndGlobal diff --git a/README.md b/README.md index ae4aa579..825366c3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![img](https://ci.appveyor.com/api/projects/status/gmu6b3ltc80hr3k9?svg=true) [![Discord](https://discordapp.com/api/guilds/117523346618318850/widget.png)](https://discord.gg/nadekobot) [![Documentation Status](https://readthedocs.org/projects/nadekobot/badge/?version=latest)](http://nadekobot.readthedocs.io/en/latest/?badge=latest) -[![nadeko0](https://cdn.discordapp.com/attachments/266240393639755778/281920716809699328/part1.png)](http://nadekobot.xyz) +[![nadeko0](https://cdn.discordapp.com/attachments/266240393639755778/281920716809699328/part1.png)](https://nadekobot.me) [![nadeko1](https://cdn.discordapp.com/attachments/266240393639755778/281920134967328768/part2.png)](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303) [![nadeko2](https://cdn.discordapp.com/attachments/266240393639755778/281920161311883264/part3.png)](http://nadekobot.readthedocs.io/en/latest/Commands%20List/) diff --git a/docs/Commands List.md b/docs/Commands List.md index d90dd900..6d6a6d9a 100644 --- a/docs/Commands List.md +++ b/docs/Commands List.md @@ -3,7 +3,6 @@ You can support the project on patreon: or paypa ##Table of contents - [Help](#help) - [Administration](#administration) -- [ClashOfClans](#clashofclans) - [CustomReactions](#customreactions) - [Gambling](#gambling) - [Games](#games) @@ -13,6 +12,7 @@ You can support the project on patreon: or paypa - [Pokemon](#pokemon) - [Searches](#searches) - [Utility](#utility) +- [Xp](#xp) ### Administration @@ -34,7 +34,6 @@ Commands and aliases | Description | Usage `.creatxtchanl` `.ctch` | Creates a new text channel with a given name. **Requires ManageChannels server permission.** | `.ctch TextChannelName` `.settopic` `.st` | Sets a topic on the current channel. **Requires ManageChannels server permission.** | `.st My new topic` `.setchanlname` `.schn` | Changes the name of the current channel. **Requires ManageChannels server permission.** | `.schn NewName` -`.prune` `.clear` | `.prune` removes all Nadeko's messages in the last 100 messages. `.prune X` removes last `X` number of messages from the channel (up to 100). `.prune @Someone` removes all Someone's messages in the last 100 messages. `.prune @Someone X` removes last `X` number of 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X` `.mentionrole` `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. **Requires MentionEveryone server permission.** | `.menro RoleName` `.donators` | List of the lovely people who donated to keep this project alive. | `.donators` `.donadd` | Add a donator to the database. **Bot owner only** | `.donadd Donate Amount` @@ -65,6 +64,7 @@ Commands and aliases | Description | Usage `.antispam` | Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders. Max message count is 10. **Requires Administrator server permission.** | `.antispam 3 Mute` or `.antispam 4 Kick` or `.antispam 6 Ban` `.antispamignore` | Toggles whether antispam ignores current channel. Antispam must be enabled. | `.antispamignore` `.antilist` `.antilst` | Shows currently enabled protection features. | `.antilist` +`.prune` `.clear` | `.prune` removes all Nadeko's messages in the last 100 messages. `.prune X` removes last `X` number of messages from the channel (up to 100). `.prune @Someone` removes all Someone's messages in the last 100 messages. `.prune @Someone X` removes last `X` number of 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X` `.slowmode` | Toggles slowmode. Disable by specifying no parameters. To enable, specify a number of messages each user can send, and an interval in seconds. For example 1 message every 5 seconds. **Requires ManageMessages server permission.** | `.slowmode 1 5` or `.slowmode` `.slowmodewl` | Ignores a role or a user from the slowmode feature. **Requires ManageMessages server permission.** | `.slowmodewl SomeRole` or `.slowmodewl AdminDude` `.adsarm` | Toggles the automatic deletion of confirmations for `.iam` and `.iamn` commands. **Requires ManageMessages server permission.** | `.adsarm` @@ -81,7 +81,6 @@ Commands and aliases | Description | Usage `.scclr` | Removes all startup commands. **Bot owner only** | `.scclr` `.fwmsgs` | Toggles forwarding of non-command messages sent to bot's DM to the bot owners **Bot owner only** | `.fwmsgs` `.fwtoall` | Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json file **Bot owner only** | `.fwtoall` -`.connectshard` | Try (re)connecting a shard with a certain shardid when it dies. No one knows will it work. Keep an eye on the console for errors. **Bot owner only** | `.connectshard 2` `.leave` | Makes Nadeko leave the server. Either server name or server ID is required. **Bot owner only** | `.leave 123123123331` `.die` | Shuts the bot down. **Bot owner only** | `.die` `.setname` `.newnm` | Gives the bot a new name. **Bot owner only** | `.newnm BotName` @@ -91,7 +90,6 @@ Commands and aliases | Description | Usage `.setgame` | Sets the bots game. **Bot owner only** | `.setgame with snakes` `.setstream` | Sets the bots stream. First argument is the twitch link, second argument is stream name. **Bot owner only** | `.setstream TWITCHLINK Hello` `.send` | Sends a message to someone on a different server through the bot. Separate server and channel/user ids with `|` and prefix the channel id with `c:` and the user id with `u:`. **Bot owner only** | `.send serverid|c:channelid message` or `.send serverid|u:userid message` -`.announce` | Sends a message to all servers' default channel that bot is connected to. **Bot owner only** | `.announce Useless spam` `.reloadimages` | Reloads images bot is using. Safe to use even when bot is being used heavily. **Bot owner only** | `.reloadimages` `.greetdel` `.grdel` | Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set it to 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.greetdel 0` or `.greetdel 30` `.greet` | Toggles anouncements on the current channel when someone joins the server. **Requires ManageServer server permission.** | `.greet` @@ -105,6 +103,7 @@ Commands and aliases | Description | Usage `.timezone` | Sets this guilds timezone. This affects bot's time output in this server (logs, etc..) | `.timezone` or `.timezone GMT Standard Time` `.warn` | Warns a user. **Requires BanMembers server permission.** | `.warn @b1nzy Very rude person` `.warnlog` | See a list of warnings of a certain user. **Requires BanMembers server permission.** | `.warnlog @b1nzy` +`.warnlogall` | See a list of all warnings on the server. 15 users per page. **Requires BanMembers server permission.** | `.warnlogall` or `.warnlogall 2` `.warnclear` `.warnc` | Clears all warnings from a certain user. **Requires BanMembers server permission.** | `.warnclear @PoorDude` `.warnpunish` `.warnp` | Sets a punishment for a certain number of warnings. Provide no punishment to remove. **Requires BanMembers server permission.** | `.warnpunish 5 Ban` or `.warnpunish 3` `.warnpunishlist` `.warnpl` | Lists punishments for warnings. | `.warnpunishlist` @@ -119,21 +118,6 @@ Commands and aliases | Description | Usage ###### [Back to ToC](#table-of-contents) -### ClashOfClans -Commands and aliases | Description | Usage -----------------|--------------|------- -`.createwar` `.cw` | Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. **Requires ManageMessages server permission.** | `.cw 15 The Enemy Clan` -`.startwar` `.sw` | Starts a war with a given number. | `.sw 15` -`.listwar` `.lw` | Shows the active war claims by a number. Shows all wars in a short way if no number is specified. | `.lw [war_number]` or `.lw` -`.basecall` | Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. | `.basecall [war_number] [base_number] [optional_other_name]` -`.callfinish1` `.cf1` | Finish your claim with 1 star if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. | `.cf1 1` or `.cf1 1 5` -`.callfinish2` `.cf2` | Finish your claim with 2 stars if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. | `.cf2 1` or `.cf2 1 5` -`.callfinish` `.cf` | Finish your claim with 3 stars if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. | `.cf 1` or `.cf 1 5` -`.endwar` `.ew` | Ends the war with a given index. | `.ew [war_number]` -`.uncall` | Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim | `.uc [war_number] [optional_other_name]` - -###### [Back to ToC](#table-of-contents) - ### CustomReactions Commands and aliases | Description | Usage ----------------|--------------|------- @@ -142,6 +126,7 @@ Commands and aliases | Description | Usage `.listcustreactg` `.lcrg` | Lists global or server custom reactions (20 commands per page) grouped by trigger, and show a number of responses for each. Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. | `.lcrg 1` `.showcustreact` `.scr` | Shows a custom reaction's response on a given ID. | `.scr 1` `.delcustreact` `.dcr` | Deletes a custom reaction on a specific index. If ran in DM, it is bot owner only and deletes a global custom reaction. If ran in a server, it requires Administration privileges and removes server custom reaction. | `.dcr 5` +`.crca` | Toggles whether the custom reaction will trigger if the triggering message contains the keyword (instead of only starting with it). | `.crca 44` `.crdm` | Toggles whether the response message of the custom reaction will be sent as a direct message. | `.crdm 44` `.crad` | Toggles whether the message triggering the custom reaction will be automatically deleted. | `.crad 59` `.crstatsclear` | Resets the counters on `.crstats`. You can specify a trigger to clear stats only for that trigger. **Bot owner only** | `.crstatsclear` or `.crstatsclear rng` @@ -183,27 +168,31 @@ Commands and aliases | Description | Usage `.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. You can specify another page to show other waifus. | `.waifus` or `.waifulb 3` `.waifuinfo` `.waifustats` | Shows waifu stats for a target person. Defaults to you if no user is provided. | `.waifuinfo @MyCrush` or `.waifuinfo` +`.waifugift` `.gift` `.gifts` | Gift an item to someone. This will increase their waifu value by 50% of the gifted item's value if they don't have affinity set towards you, or 100% if they do. Provide no arguments to see a list of items that you can gift. | `.gifts` or `.gift Rose @Himesama` +`.wheeloffortune` `.wheel` | Bets a certain amount of currency on the wheel of fortune. Wheel can stop on one of many different multipliers. Won amount is rounded down to the nearest whole number. | `.wheel 10` ###### [Back to ToC](#table-of-contents) ### Games Commands and aliases | Description | Usage ----------------|--------------|------- -`.leet` | Converts a text to leetspeak with 6 (1-6) severity levels | `.leet 3 Hello` `.choose` | Chooses a thing from a list of things | `.choose Get up;Sleep;Sleep more` `.8ball` | Ask the 8ball a yes/no question. | `.8ball should I do something` `.rps` | Play a game of Rocket-Paperclip-Scissors with Nadeko. | `.rps scissors` `.rategirl` | Use the universal hot-crazy wife zone matrix to determine the girl's worth. It is everything young men need to know about women. At any moment in time, any woman you have previously located on this chart can vanish from that location and appear anywhere else on the chart. | `.rategirl @SomeGurl` `.linux` | Prints a customizable Linux interjection | `.linux Spyware Windows` +`.leet` | Converts a text to leetspeak with 6 (1-6) severity levels | `.leet 3 Hello` `.acrophobia` `.acro` | Starts an Acrophobia game. Second argument is optional round length in seconds. (default is 60) | `.acro` or `.acro 30` `.cleverbot` | Toggles cleverbot session. When enabled, the bot will reply to messages starting with bot mention in the server. Custom reactions starting with %mention% won't work if cleverbot is enabled. **Requires ManageMessages server permission.** | `.cleverbot` +`.connect4` `.con4` | Creates or joins an existing connect4 game. 2 players are required for the game. Objective of the game is to get 4 of your pieces next to each other in a vertical, horizontal or diagonal line. | `.connect4` `.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` +`.hangmanstop` | Stops the active hangman game on this channel if it exists. | `.hangmanstop` +`.nunchi` | Creates or joins an existing nunchi game. Users have to count up by 1 from the starting number shown by the bot. If someone makes a mistake (types an incorrent number, or repeats the same number) they are out of the game and a new round starts without them. Minimum 3 users required. | `.nunchi` `.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` +`.poll` `.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` @@ -211,7 +200,7 @@ Commands and aliases | Description | Usage `.typeadd` | Adds a new article to the typing contest. **Bot owner only** | `.typeadd wordswords` `.typelist` | Lists added typing articles with their IDs. 15 per page. | `.typelist` or `.typelist 3` `.typedel` | Deletes a typing article given the ID. **Bot owner only** | `.typedel 3` -`.tictactoe` `.ttt` | Starts a game of tic tac toe. Another user must run the command in the same channel in order to accept the challenge. Use numbers 1-9 to play. 15 seconds per move. | >ttt +`.tictactoe` `.ttt` | Starts a game of tic tac toe. Another user must run the command in the same channel in order to accept the challenge. Use numbers 1-9 to play. 15 seconds per move. | .ttt `.trivia` `.t` | Starts a game of trivia. You can add `nohint` to prevent hints. First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question. | `.t` or `.t 5 nohint` `.tl` | Shows a current trivia leaderboard. | `.tl` `.tq` | Quits current trivia after current question. | `.tq` @@ -233,35 +222,38 @@ Commands and aliases | Description | Usage ### Music Commands and aliases | Description | Usage ----------------|--------------|------- +`.play` `.start` | If no arguments are specified, acts as `.next 1` command. If you specify a song number, it will jump to that song. If you specify a search query, acts as a `.q` command | `.play` or `.play 5` or `.play Dream Of Venice` +`.queue` `.q` `.yq` | Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**. | `.q Dream Of Venice` +`.queuenext` `.qn` | Works the same as `.queue` command, except it enqueues the new song after the current one. **You must be in a voice channel**. | `.qn Dream Of Venice` +`.queuesearch` `.qs` `.yqs` | Search for top 5 youtube song result using keywords, and type the index of the song to play that song. Bot will join your voice channel. **You must be in a voice channel**. | `.qs Dream Of Venice` +`.listqueue` `.lq` | Lists 10 currently queued songs per page. Default page is 1. | `.lq` or `.lq 2` `.next` `.n` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. You can skip multiple songs, but in that case songs will not be requeued if .rcs or .rpl is enabled. | `.n` or `.n 5` -`.stop` `.s` | Stops the music and clears the playlist. Stays in the channel. | `.s` +`.stop` `.s` | Stops the music and preserves the current song index. Stays in the channel. | `.s` `.destroy` `.d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `.d` `.pause` `.p` | Pauses or Unpauses the song. | `.p` -`.fairplay` `.fp` | Toggles fairplay. While enabled, the bot will prioritize songs from users who didn't have their song recently played instead of the song's position in the queue. | `.fp` -`.queue` `.q` `.yq` | Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**. | `.q Dream Of Venice` -`.queuesearch` `.qs` `.yqs` | Search for top 5 youtube song result using keywords, and type the index of the song to play that song. Bot will join your voice channel. **You must be in a voice channel**. | `.qs Dream Of Venice` -`.soundcloudqueue` `.sq` | Queue a soundcloud song using keywords. Bot will join your voice channel. **You must be in a voice channel**. | `.sq Dream Of Venice` -`.listqueue` `.lq` | Lists 15 currently queued songs per page. Default page is 1. | `.lq` or `.lq 2` -`.nowplaying` `.np` | Shows the song that the bot is currently playing. | `.np` `.volume` `.vol` | Sets the music playback volume (0-100%) | `.vol 50` `.defvol` `.dv` | Sets the default music volume when music playback is started (0-100). Persists through restarts. | `.dv 80` -`.playlistshuffle` `.plsh` | Shuffles the current playlist. | `.plsh` -`.playlist` `.pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `.pl playlist link or name` +`.songremove` `.srm` | Remove a song by its # in the queue, or 'all' to remove all songs from the queue and reset the song index. | `.srm 5` +`.playlists` `.pls` | Lists all playlists. Paginated, 20 per page. Default page is 0. | `.pls 1` +`.deleteplaylist` `.delpls` | Deletes a saved playlist. Works only if you made it or if you are the bot owner. | `.delpls animu-5` +`.save` | Saves a playlist under a certain name. Playlist name must be no longer than 20 characters and must not contain dashes. | `.save classical1` +`.load` | Loads a saved playlist using its ID. Use `.pls` to list all saved playlists and `.save` to save new ones. | `.load 5` +`.fairplay` `.fp` | Toggles fairplay. While enabled, the bot will prioritize songs from users who didn't have their song recently played instead of the song's position in the queue. | `.fp` +`.songautodelete` `.sad` | Toggles whether the song should be automatically removed from the music queue when it finishes playing. | `.sad` +`.soundcloudqueue` `.sq` | Queue a soundcloud song using keywords. Bot will join your voice channel. **You must be in a voice channel**. | `.sq Dream Of Venice` `.soundcloudpl` `.scpl` | Queue a Soundcloud playlist using a link. | `.scpl soundcloudseturl` -`.localplaylst` `.lopl` | Queues all songs from a directory. **Bot owner only** | `.lopl C:/music/classical` +`.nowplaying` `.np` | Shows the song that the bot is currently playing. | `.np` +`.shuffle` `.sh` `.plsh` | Shuffles the current playlist. | `.plsh` +`.playlist` `.pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `.pl playlist link or name` `.radio` `.ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: ) | `.ra radio link here` `.local` `.lo` | Queues a local file by specifying a full path. **Bot owner only** | `.lo C:/music/mysong.mp3` -`.songremove` `.srm` | Remove a song by its # in the queue, or 'all' to remove all songs from the queue. | `.srm 5` +`.localplaylst` `.lopl` | Queues all songs from a directory. **Bot owner only** | `.lopl C:/music/classical` +`.move` `.mv` | Moves the bot to your voice channel. (works only if music is already playing) | `.mv` `.movesong` `.ms` | Moves a song from one position to another. | `.ms 5>3` `.setmaxqueue` `.smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `.smq 50` or `.smq` `.setmaxplaytime` `.smp` | Sets a maximum number of seconds (>14) a song can run before being skipped automatically. Set 0 to have no limit. | `.smp 0` or `.smp 270` `.reptcursong` `.rcs` | Toggles repeat of current song. | `.rcs` `.rpeatplaylst` `.rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `.rpl` -`.save` | Saves a playlist under a certain name. Playlist name must be no longer than 20 characters and must not contain dashes. | `.save classical1` -`.load` | Loads a saved playlist using its ID. Use `.pls` to list all saved playlists and `.save` to save new ones. | `.load 5` -`.playlists` `.pls` | Lists all playlists. Paginated, 20 per page. Default page is 0. | `.pls 1` -`.deleteplaylist` `.delpls` | Deletes a saved playlist. Works only if you made it or if you are the bot owner. | `.delpls animu-5` -`.goto` | Goes to a specific time in seconds in a song. | `.goto 30` `.autoplay` `.ap` | Toggles autoplay - When the song is finished, automatically queue a related Youtube song. (Works only for Youtube songs and when queue is empty) | `.ap` `.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` @@ -281,6 +273,8 @@ Commands and aliases | Description | Usage `.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` `.boobs` | Real adult content. | `.boobs` `.butts` `.ass` `.butt` | Real adult content. | `.butts` or `.ass` +`.nsfwtagbl` `.nsfwtbl` | Toggles whether the tag is blacklisted or not in nsfw searches. Provide no parameters to see the list of blacklisted tags. | `.nsfwtbl poop` +`.nsfwcc` | Clears nsfw cache. **Bot owner only** | `.nsfwcc` ###### [Back to ToC](#table-of-contents) @@ -382,11 +376,11 @@ Commands and aliases | Description | Usage `.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` +`.smashcast` `.hb` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `.smashcast 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` +`.mixer` `.bm` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `.mixer 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` +`.removestream` `.rms` | Removes notifications of a certain streamer from a certain platform on this channel. **Requires ManageMessages server permission.** | `.rms Twitch SomeGuy` or `.rms mixer 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` @@ -399,7 +393,6 @@ Commands and aliases | Description | Usage ### Utility Commands and aliases | Description | Usage ----------------|--------------|------- -`.rotaterolecolor` `.rrc` | Rotates a roles color on an interval with a list of supplied colors. First argument is interval in seconds (Minimum 60). Second argument is a role, followed by a space-separated list of colors in hex. Provide a rolename with a 0 interval to disable. **Requires ManageRoles server permission.** **Bot owner only** | `.rrc 60 MyLsdRole #ff0000 #00ff00 #0000ff` or `.rrc 0 MyLsdRole` `.togethertube` `.totube` | Creates a new room on and shows the link in the chat. | `.totube` `.whosplaying` `.whpl` | Shows a list of users who are playing the specified game. | `.whpl Overwatch` `.inrole` | Lists every person from the specified role on this server. You can use role ID, role name. | `.inrole Some Role` @@ -411,20 +404,17 @@ Commands and aliases | Description | Usage `.channeltopic` `.ct` | Sends current channel's topic as a message. | `.ct` `.createinvite` `.crinv` | Creates a new invite which has infinite max uses and never expires. **Requires CreateInstantInvite channel permission.** | `.crinv` `.shardstats` | Stats for shards. Paginated with 25 shards per page. | `.shardstats` or `.shardstats 2` -`.shardid` | Shows which shard is a certain guild on, by guildid. | `.shardid 117523346618318850` `.stats` | Shows some basic stats for Nadeko. | `.stats` `.showemojis` `.se` | Shows a name and a link to every SPECIAL emoji in the message. | `.se A message full of SPECIAL emojis` `.listservers` | Lists servers the bot is on with some basic info. 15 per page. **Bot owner only** | `.listservers 3` `.savechat` | Saves a number of messages to a text file and sends it to you. **Bot owner only** | `.savechat 150` `.ping` | Ping the bot to see if there are latency issues. | `.ping` +`.botconfigedit` `.bce` | Sets one of available bot config settings to a specified value. Use the command without any parameters to get a list of available settings. **Bot owner only** | `.bce CurrencyName b1nzy` or `.bce` `.calculate` `.calc` | Evaluate a mathematical expression. | `.calc 1+1` `.calcops` | Shows all available operations in the `.calc` command | `.calcops` `.alias` `.cmdmap` | Create a custom alias for a certain Nadeko command. Provide no alias to remove the existing one. **Requires Administrator server permission.** | `.alias allin $bf 100 h` or `.alias "linux thingy" >loonix Spyware Windows` `.aliaslist` `.cmdmaplist` `.aliases` | Shows the list of currently set aliases. Paginated. | `.aliaslist` or `.aliaslist 3` -`.scsc` | Starts an instance of cross server channel. You will get a token as a DM that other people will use to tune in to the same instance. **Bot owner only** | `.scsc` -`.jcsc` | Joins current channel to an instance of cross server channel using the token. **Requires ManageServer server permission.** | `.jcsc TokenHere` -`.lcsc` | Leaves a cross server channel instance from this channel. **Requires ManageServer server permission.** | `.lcsc` -`.serverinfo` `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | `.sinfo Some Server` +`.serverinfo` `.sinfo` | Shows info about the server the bot is on. If no server 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` `.activity` | Checks for spammers. **Bot owner only** | `.activity` @@ -443,6 +433,39 @@ Commands and aliases | Description | Usage `.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 instead specify time of day for the message to be repeated at daily (make sure you've set your server's timezone). You can have up to 5 repeating messages on the server in total. **Requires ManageMessages server permission.** | `.repeat 5 Hello there` or `.repeat 17:30 tea time` `.repeatlist` `.replst` | Shows currently repeating messages and their indexes. **Requires ManageMessages server permission.** | `.repeatlist` +`.streamrole` | Sets a role which is monitored for streamers (FromRole), and a role to add if a user from 'FromRole' is streaming (AddRole). When a user from 'FromRole' starts streaming, they will receive an 'AddRole'. Provide no arguments to disable **Requires ManageRoles server permission.** | `.streamrole "Eligible Streamers" "Featured Streams"` +`.streamrolekw` `.srkw` | Sets keyword which is required in the stream's title in order for the streamrole to apply. Provide no keyword in order to reset. **Requires ManageRoles server permission.** | `.srkw` or `.srkw PUBG` +`.streamrolebl` `.srbl` | Adds or removes a blacklisted user. Blacklisted users will never receive the stream role. **Requires ManageRoles server permission.** | `.srbl add @b1nzy#1234` or `.srbl rem @b1nzy#1234` +`.streamrolewl` `.srwl` | Adds or removes a whitelisted user. Whitelisted users will receive the stream role even if they don't have the specified keyword in their stream title. **Requires ManageRoles server permission.** | `.srwl add @b1nzy#1234` or `.srwl rem @b1nzy#1234` `.convertlist` | List of the convertible dimensions and currencies. | `.convertlist` `.convert` | Convert quantities. Use `.convertlist` to see supported dimensions and currencies. | `.convert m km 1000` `.verboseerror` `.ve` | Toggles whether the bot should print command errors when a command is incorrectly used. **Requires ManageMessages server permission.** | `.ve` + +###### [Back to ToC](#table-of-contents) + +### Xp +Commands and aliases | Description | Usage +----------------|--------------|------- +`.experience` `.xp` | Shows your xp stats. Specify the user to show that user's stats instead. | `.xp` +`.xprolerewards` `.xprrs` | Shows currently set role rewards. | `.xprrs` +`.xprolereward` `.xprr` | Sets a role reward on a specified level. Provide no role name in order to remove the role reward. **Requires ManageRoles server permission.** | `.xprr 3 Social` +`.xpnotify` `.xpn` | Sets how the bot should notify you when you get a `server` or `global` level. You can set `dm` (for the bot to send a direct message), `channel` (to get notified in the channel you sent the last message in) or `none` to disable. | `.xpn global dm` `.xpn server channel` +`.xpexclude` `.xpex` | Exclude a user or a role from the xp system, or whole current server. **Requires Administrator server permission.** | `.xpex Role Excluded-Role` `.xpex Server` +`.xpexclusionlist` `.xpexl` | Shows the roles and channels excluded from the XP system on this server, as well as whether the whole server is excluded. | `.xpexl` +`.xpleaderboard` `.xplb` | Shows current server's xp leaderboard. | `.xplb` +`.xpgleaderboard` `.xpglb` | Shows the global xp leaderboard. | `.xpglb` +`.xpadd` | Adds xp to a user on the server. This does not affect their global ranking. You can use negative values. **Requires Administrator server permission.** | `.xpadd 100 @b1nzy` +`.clubcreate` | Creates a club. You must be atleast level 5 and not be in the club already. | `.clubcreate b1nzy's friends` +`.clubicon` | Sets the club icon. | `.clubicon https://i.imgur.com/htfDMfU.png` +`.clubinfo` | Shows information about the club. | `.clubinfo b1nzy's friends#123` +`.clubbans` | Shows the list of users who have banned from your club. Paginated. You must be club owner to use this command. | `.clubbans 2` +`.clubapps` | Shows the list of users who have applied to your club. Paginated. You must be club owner to use this command. | `.clubapps 2` +`.clubapply` | Apply to join a club. You must meet that club's minimum level requirement, and not be on its ban list. | `.clubapply b1nzy's friends#123` +`.clubaccept` | Accept a user who applied to your club. | `.clubaccept b1nzy#1337` +`.clubleave` | Leaves the club you're currently in. | `.clubleave` +`.clubkick` | Kicks the user from the club. You must be the club owner. They will be able to apply again. | `.clubkick b1nzy#1337` +`.clubban` | Bans the user from the club. You must be the club owner. They will not be able to apply again. | `.clubban b1nzy#1337` +`.clubunban` | Unbans the previously banned user from the club. You must be the club owner. | `.clubunban b1nzy#1337` +`.clublevelreq` | Sets the club required level to apply to join the club. You must be club owner. You can't set this number below 5. | `.clublevelreq 7` +`.clubdisband` | Disbands the club you're the owner of. This action is irreversible. | `.clubdisband` +`.clublb` | Shows club rankings on the specified page. | `.clublb 2` diff --git a/docs/Contribution Guide.md b/docs/Contribution Guide.md index 3dff9b35..ca0cd94f 100644 --- a/docs/Contribution Guide.md +++ b/docs/Contribution Guide.md @@ -1,6 +1,6 @@ ### How to contribute -1. Make Pull Requests to the [**dev BRANCH**](https://github.com/Kwoth/NadekoBot/tree/dev). +1. Make Pull Requests to the [**1.9 BRANCH**](https://github.com/Kwoth/NadekoBot/tree/1.9). 2. Keep 1 Pull Request to a single feature. 3. Explain what you did in the PR message. diff --git a/docs/Custom Reactions.md b/docs/Custom Reactions.md index 8d41afca..7926028d 100644 --- a/docs/Custom Reactions.md +++ b/docs/Custom Reactions.md @@ -36,14 +36,4 @@ For example: Now if you try to trigger `/o/`, it won't print anything. ###Placeholders! -There are currently three different placeholders which we will look at, with more placeholders potentially coming in the future. - -| Placeholder | Description | Example Usage | Usage | -|:-----------:|-------------|---------------|-------| -|`%mention%`|The `%mention%` placeholder is triggered when you type `@BotName` - It's important to note that if you've given the bot a custom nickname, this trigger won't work!|```.acr "Hello %mention%" I, %mention%, also say hello!```|Input: "Hello @BotName" Output: "I, @BotName, also say hello!"| -|`%user%`|The `%user%` placeholder mentions the person who said the command|`.acr "Who am I?" You are %user%!`|Input: "Who am I?" Output: "You are @Username!"| -|`%rng%`|The `%rng%` placeholder generates a random number between 0 and 10. You can also specify a custom range (%rng1-100%) even with negative numbers: `%rng-9--1%` (from -9 to -1) . |`.acr "Random number" %rng%`|Input: "Random number" Output: "2"| -|`%rnduser%`|The `%rnduser%` placeholder mentions a random user from the server. |`.acr "Random user" %rnduser%`|Input: "Random number" Output: @SomeUser| -|`%target%`|The `%target%` placeholder is used to make Nadeko Mention another person or phrase, it is only supported as part of the response|`.acr "Say this: " %target%`|Input: "Say this: I, @BotName, am a parrot!". Output: "I, @BotName, am a parrot!".| - - Thanks to Nekai for being creative. <3 +To learn about placeholders, go [here](Placeholders.md) diff --git a/docs/JSON Explanations.md b/docs/JSON Explanations.md index 2ac629e8..5a5e00e3 100644 --- a/docs/JSON Explanations.md +++ b/docs/JSON Explanations.md @@ -16,9 +16,14 @@ If you do not see `credentials.json` you will need to rename `credentials_exampl "GoogleApiKey": "AIzaSyDSci1sdlWQOWNVj1vlXxxxxxbk0oWMEzM", "MashapeKey": "4UrKpcWXc2mshS8RKi00000y8Kf5p1Q8kI6jsn32bmd8oVWiY7", "OsuApiKey": "4c8c8fdff8e1234581725db27fd140a7d93320d6", + "CleverbotApiKey": "", "PatreonAccessToken": "", + "PatreonCampaignId": "334038", "Db": null, - "TotalShards": 1 + "TotalShards": 1, + "ShardRunCommand": "", + "ShardRunArguments": "", + "ShardRunPort": null } ``` ----- @@ -43,7 +48,7 @@ If you do not see `credentials.json` you will need to rename `credentials_exampl - Replace the **`12345678`** in this link: `https://discordapp.com/oauth2/authorize?client_id=`**`12345678`**`&scope=bot&permissions=66186303` with your `Client ID`. - The link should now look like this: -`https://discordapp.com/oauth2/authorize?client_id=`**`YOUR_CLENT_ID_HERE`**`&scope=bot&permissions=66186303` +`https://discordapp.com/oauth2/authorize?client_id=`**`YOUR_CLIENT_ID_HERE`**`&scope=bot&permissions=66186303` - Go to the newly created link and pick the server we created, and click `Authorize`. - The bot should have been added to your server. @@ -144,25 +149,30 @@ It should look like: - Required for Urban Disctionary, Hashtag search, and Hearthstone cards. - You need to create an account on their [api marketplace](https://market.mashape.com/), after that go to `market.mashape.com/YOURNAMEHERE/applications/default-application` and press **Get the keys** in the top right corner. - Copy the key and paste it into `credentials.json` -- **LOLAPIKey** +- **LoLApiKey** - Required for all League of Legends commands. - - You can get this key [here](http://api.champion.gg/) -- **OsuAPIKey** + - You can get this key [here.](http://api.champion.gg/) +- **OsuApiKey** - Required for Osu commands - - You can get this key [here.](https://osu.ppy.sh/p/api) + - You can get this key [here.](https://osu.ppy.sh/p/api) +- **CleverbotApiKey** + - Required if you want to use official cleverobot, instead of program-o + - you can get this key [here.](http://www.cleverbot.com/api/) - **PatreonAccessToken** - For Patreon creators only. +- **PatreonCampaignId** + - For Patreon creators only. Id of your campaign. - **TotalShards** - Required if the bot will be connected to more than 1500 servers. - - Most likely unnecessary to change until your bot is added to more than 1500 servers. - + - Most likely unnecessary to change until your bot is added to more than 1500 servers. ----- ## DB files Nadeko saves all the settings and infomations in `NadekoBot.db` file here: `NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data/NadekoBot.db` (macOS and Linux) -`NadekoBot\system\data` (Windows) +`NadekoBot\system\data` (Windows) + in order to open the database file you will need [DB Browser for SQLite](http://sqlitebrowser.org/). *NOTE: You don't have to worry if you don't have `NadekoBot.db` file, it gets auto created once you run the bot successfully.* @@ -179,11 +189,29 @@ in order to open the database file you will need [DB Browser for SQLite](http:// - click on **Apply** - click on **Write Changes** +![nadekodb](https://cdn.discordapp.com/attachments/251504306010849280/254067055240806400/nadekodb.gif) + and that will save all the changes. +## Sharding your bot + +- **ShardRunCommand** + - Command with which to run shards 1+ + - Required if you're sharding your bot on windows using .exe, or in a custom way. + - This internally defaults to `dotnet` + - For example, if you want to shard your NadekoBot which you installed using windows installer, you would want to set it to something like this: `C:\Program Files\NadekoBot\system\NadekoBot.exe` +- **ShardRunArguments** + - Arguments to the shard run command + - Required if you're sharding your bot on windows using .exe, or in a custom way. + - This internally defaults to `run -c Release -- {0} {1} {2}` which will be enough to run linux and other 'from source' setups + - {0} will be replaced by the `shard ID` of the shard being ran, {1} by the shard 0's process id, and {2} by the port shard communication is happening on + - If shard0 (main window) is closed, all other shards will close too + - For example, if you want to shard your NadekoBot which you installed using windows installer, you would want to set it to `{0} {1} {2}` +- **ShardRunPort** + - Bot uses a random UDP port in [5000, 6000) range for communication between shards + -![nadekodb](https://cdn.discordapp.com/attachments/251504306010849280/254067055240806400/nadekodb.gif) [Google Console]: https://console.developers.google.com [DiscordApp]: https://discordapp.com/developers/applications/me -[Invite Guide]: http://discord.kongslien.net/guide.html \ No newline at end of file +[Invite Guide]: http://discord.kongslien.net/guide.html diff --git a/docs/Permissions System.md b/docs/Permissions System.md index 64b76207..32ad3407 100644 --- a/docs/Permissions System.md +++ b/docs/Permissions System.md @@ -49,11 +49,11 @@ To allow users to only see the current song and have a DJ role for queuing follo * Disables music commands for everybody -2. `.sc !!nowplaying enable` +2. `.sc .nowplaying enable` * Enables the "nowplaying" command for everyone -3. `.sc !!listqueue enable` +3. `.sc .listqueue enable` * Enables the "listqueue" command for everyone diff --git a/docs/Placeholders.md b/docs/Placeholders.md new file mode 100644 index 00000000..88146afb --- /dev/null +++ b/docs/Placeholders.md @@ -0,0 +1,25 @@ +Placeholders are used in Quotes, Custom Reactions, Greet/bye messages, playing statuses, and a few other places. + +They can be used to make the message more user friendly, generate random numbers or pictures, etc... + +Some features have their own specific placeholders which are noted in that feature's command help. Some placeholders are not available in certain features because they don't make sense there. + +### Here is a list of the usual placeholders: +- `%mention%` - Mention the bot +- `%shardid%` - Shard id +- `%server%` - Server name +- `%sid%` - Server Id +- `%channel%` - Channel mention +- `%chname%` - Channel mention +- `%cid%` - Channel Id +- `%user%` - User mention +- `%id%` or `%uid%` - User Id +- `%userfull%` - Username#discriminator +- `%userdiscrim%` - discriminator (for example 1234) +- `%rngX-Y%` - Replace X and Y with the range (for example `%rng5-10%` - random between 5 and 10) +- `%time%` - Bot time +- `%server_time%` - Time on this server, set with `.timezone` command + +**If you're using placeholders in embeds, don't use %user% and in titles, footers and field names. They will not show properly.** + +![img](http://i.imgur.com/lNNNfs1.png) \ No newline at end of file diff --git a/docs/guides/Docker Guide.md b/docs/guides/Docker Guide.md index 8969e373..5c50e73d 100644 --- a/docs/guides/Docker Guide.md +++ b/docs/guides/Docker Guide.md @@ -1,5 +1,5 @@ # NadekoBot a Discord bot -Nadeko is written in C# and Discord.net for more information visit +Nadeko is written in C# and Discord.Net for more information visit ## Install Docker Follow the respective guide for your operating system found here [Docker Engine Install Guide](https://docs.docker.com/engine/installation/) @@ -8,20 +8,18 @@ Follow the respective guide for your operating system found here [Docker Engine For this guide we will be using the folder /nadeko as our config root folder. ```bash -docker create --name=nadeko -v /nadeko/:/root/nadeko uirel/nadeko:1.4 +docker create --name=nadeko -v /nadeko/conf/:/root/nadeko -v /nadeko/data:/opt/NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data uirel/nadeko:1.4 ``` --If you are coming from a previous version of nadeko (the old docker) make sure your crednetials.json has been copied into this directory and is the only thing in this folder. +-If you are coming from a previous version of nadeko (the old docker) make sure your credentials.json has been copied into this directory and is the only thing in this folder. -If you are making a fresh install, create your credentials.json from the following guide and place it in the /nadeko folder [Nadeko JSON Guide](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/) -Next start the docker up with - -`docker start nadeko; docker logs -f nadeko` +Next start the docker up with `docker start nadeko; docker logs -f nadeko` The docker will start and the log file will start scrolling past. Depending on hardware the bot start can take up to 5 minutes on a small DigitalOcean droplet. Once the log ends with "NadekoBot | Starting NadekoBot v1.0-rc2" the bot is ready and can be invited to your server. Ctrl+C at this point to stop viewing the logs. -After a few moments you should be able to invite Nadeko to your server. If you cannot check the log file for errors +After a few moments you should be able to invite Nadeko to your server. If you cannot, check the log file for errors. ## Monitoring @@ -37,22 +35,24 @@ The following commands are required for the default options `docker stop nadeko; docker rm nadeko` -`docker create --name=nadeko -v /nadeko/:/root/nadeko uirel/nadeko:1.4` +``` +docker create --name=nadeko -v /nadeko/conf/:/root/nadeko -v /nadeko/data:/opt/NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data uirel/nadeko:1.4 +``` `docker start nadeko` # Automatic Updates -Automatic update are now handled by watchertower [WatchTower GitHub](https://github.com/CenturyLinkLabs/watchtower) -To setup watchtower to keep Nadeko up-to-date for you with the default settings use the following command +Automatic update are now handled by WatchTower [WatchTower GitHub](https://github.com/CenturyLinkLabs/watchtower) +To setup WatchTower to keep Nadeko up-to-date for you with the default settings, use the following command ```bash docker run -d --name watchtower -v /var/run/docker.sock:/var/run/docker.sock centurylink/watchtower --cleanup nadeko ``` -This will check for updates to the docker every 5 minutes and update immediately. Alternatively using the `--interval X` command to change the interval, where X is the amount of time in seconds to wait. eg 21600 for 6 hours. +This will check for updates to the docker every 5 minutes and update immediately. Alternatively using the `--interval X` command to change the interval, where X is the amount of time in seconds to wait. e.g 21600 for 6 hours. -If you have any issues with the docker setup, please ask in #help but indicate you are using the docker. +If you have any issues with the docker setup, please ask in #help channel on our [Discord server](https://discordapp.com/invite/nadekobot) but indicate you are using the docker. -For information about configuring your bot or its functionality, please check the guides. +For information about configuring your bot or its functionality, please check the [documentation](http://nadekobot.readthedocs.io/en/latest). diff --git a/docs/guides/OSX Guide.md b/docs/guides/OSX Guide.md index b9256356..a9d53736 100644 --- a/docs/guides/OSX Guide.md +++ b/docs/guides/OSX Guide.md @@ -24,6 +24,8 @@ brew install opusfile brew install libffi brew install libsodium brew install tmux +brew install python +brew install youtube-dl ``` #### Installing .NET Core SDK diff --git a/docs/guides/Upgrading Guide.md b/docs/guides/Upgrading Guide.md index 70fc6e80..f853267c 100644 --- a/docs/guides/Upgrading Guide.md +++ b/docs/guides/Upgrading Guide.md @@ -1,9 +1,9 @@ #### If you have NadekoBot 1.x on Windows +- Go to `NadekoBot\src\NadekoBot` and backup your `credentials.json` file; then go to `NadekoBot\src\NadekoBot\bin\Release\netcoreapp1.0` and backup your `data` folder. - Follow the [Windows Guide](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/) and install the latest version of **NadekoBot**. -- Navigate to your **old** `Nadeko` folder and copy your `credentials.json` file and the `data` folder. -- Paste credentials into the **NadekoBot 1.4x+** `C:\Program Files\NadekoBot\system` folder. -- Paste your **old** `Nadeko` data folder into **NadekoBot 1.4x+** `C:\Program Files\NadekoBot\system` folder. +- Paste your `credentials.json` file into the `C:\Program Files\NadekoBot\system` folder. +- Paste your `data` folder into `C:\Program Files\NadekoBot\system` folder. - If it asks you to overwrite files, it is fine to do so. - Next launch your **new** Nadeko as the guide describes, if it is not already running. @@ -27,4 +27,4 @@ - **For Ubuntu, Debian and CentOS Users Only:** use the option `4. Auto-Install Prerequisites` to install the latest version of .NET Core SDK. - Use option `1. Download NadekoBot` to update your NadekoBot to 1.4.x. - Next, just [run your NadekoBot.](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#running-nadekobot) -- *NOTE: 1.4.x uses `NadekoBot.db` file from `NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data` folder.* \ No newline at end of file +- *NOTE: 1.4.x uses `NadekoBot.db` file from `NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data` folder.* diff --git a/docs/guides/Windows Guide.md b/docs/guides/Windows Guide.md index 91b4d45b..fe344a96 100644 --- a/docs/guides/Windows Guide.md +++ b/docs/guides/Windows Guide.md @@ -3,12 +3,11 @@ #### Prerequisites - [Notepad++][Notepad++] (or some other decent text editor) - Windows 8 or later -- [.NET Core SDK (Command line / other)][.NET Core SDK] - [Create Discord Bot application](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/#creating-discord-bot-application) and [Invite the bot to your server](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/#inviting-your-bot-to-your-server). #### Guide - Download and run the [NadekoBot Updater.][Updater] -- Press **`Install ffmpeg`** button if you want music features. +- Press **`Install ffmpeg`** and **`Install youtube-dl`** if you want music features. ***NOTE:** RESTART YOUR PC IF YOU DO.* - Press **`Update`** and go through the installation wizard. ***NOTE:** If you're upgrading from 1.3, DO NOT select your old nadekobot folder. Install it in a separate directory and read the [upgrading guide](http://nadekobot.readthedocs.io/en/latest/guides/Upgrading%20Guide/).* diff --git a/docs/index.md b/docs/index.md index 5e67f8dc..b7bd5ed4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,6 +28,7 @@ If you want to contribute, be sure to PR on the **[1.4][1.4]** branch. - [Permissions System](Permissions System.md) - [JSON Explanations](JSON Explanations.md) - [Custom Reactions](Custom Reactions.md) + - [Placeholders](Placeholders.md) - [Frequently Asked Questions](Frequently Asked Questions.md) - [Contribution Guide](Contribution Guide.md) - [Donate](Donate.md) diff --git a/global.json b/global.json index 3b965cc4..68be1221 100644 --- a/global.json +++ b/global.json @@ -1,3 +1,3 @@ { - "sdk": { "version": "1.0.1" } + "sdk": { "version": "2.0.0" } } \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index e8ccbbad..0ab5e11b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,9 +14,10 @@ pages: - Readme: Readme.md - Commands List: Commands List.md - Features Explained: - - Permissions System: Permissions System.md - JSON Explanations: JSON Explanations.md + - Permissions System: Permissions System.md - Custom Reactions: Custom Reactions.md + - Placeholders: Placeholders.md - Frequently Asked Questions: Frequently Asked Questions.md - Contribution Guide: Contribution Guide.md - ❤ Donate ❤: Donate.md diff --git a/src/NadekoBot/Attributes/Aliases.cs b/src/NadekoBot/Attributes/Aliases.cs deleted file mode 100644 index af3e98fb..00000000 --- a/src/NadekoBot/Attributes/Aliases.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Discord.Commands; -using NadekoBot.Services; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace NadekoBot.Attributes -{ - public class Aliases : AliasAttribute - { - public Aliases([CallerMemberName] string memberName = "") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_cmd").Split(' ').Skip(1).ToArray()) - { - } - } -} diff --git a/src/NadekoBot/Attributes/Description.cs b/src/NadekoBot/Attributes/Description.cs deleted file mode 100644 index a6f32c74..00000000 --- a/src/NadekoBot/Attributes/Description.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Discord.Commands; -using NadekoBot.Services; -using System.Runtime.CompilerServices; - -namespace NadekoBot.Attributes -{ - public class Description : SummaryAttribute - { - public Description([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_desc")) - { - - } - } -} diff --git a/src/NadekoBot/Attributes/NadekoCommand.cs b/src/NadekoBot/Attributes/NadekoCommand.cs deleted file mode 100644 index 3c8010a9..00000000 --- a/src/NadekoBot/Attributes/NadekoCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Discord.Commands; -using NadekoBot.Services; -using System.Runtime.CompilerServices; - -namespace NadekoBot.Attributes -{ - public class NadekoCommand : CommandAttribute - { - public NadekoCommand([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_cmd").Split(' ')[0]) - { - - } - } -} diff --git a/src/NadekoBot/Attributes/Usage.cs b/src/NadekoBot/Attributes/Usage.cs deleted file mode 100644 index b3e18519..00000000 --- a/src/NadekoBot/Attributes/Usage.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Discord.Commands; -using NadekoBot.Services; -using System.Runtime.CompilerServices; - -namespace NadekoBot.Attributes -{ - public class Usage : RemarksAttribute - { - public Usage([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant()+"_usage")) - { - - } - } -} diff --git a/src/NadekoBot/DataStructures/AsyncLazy.cs b/src/NadekoBot/Common/AsyncLazy.cs similarity index 93% rename from src/NadekoBot/DataStructures/AsyncLazy.cs rename to src/NadekoBot/Common/AsyncLazy.cs index f739968f..09d5c989 100644 --- a/src/NadekoBot/DataStructures/AsyncLazy.cs +++ b/src/NadekoBot/Common/AsyncLazy.cs @@ -2,7 +2,7 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; -namespace NadekoBot.DataStructures +namespace NadekoBot.Common { public class AsyncLazy : Lazy> { diff --git a/src/NadekoBot/Common/Attributes/Aliases.cs b/src/NadekoBot/Common/Attributes/Aliases.cs new file mode 100644 index 00000000..e6c95f67 --- /dev/null +++ b/src/NadekoBot/Common/Attributes/Aliases.cs @@ -0,0 +1,13 @@ +using System.Linq; +using System.Runtime.CompilerServices; +using Discord.Commands; +using NadekoBot.Services.Impl; +namespace NadekoBot.Common.Attributes +{ + public class Aliases : AliasAttribute + { + public Aliases([CallerMemberName] string memberName = "") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Cmd.Split(' ').Skip(1).ToArray()) + { + } + } +} diff --git a/src/NadekoBot/Common/Attributes/Description.cs b/src/NadekoBot/Common/Attributes/Description.cs new file mode 100644 index 00000000..7ebbac47 --- /dev/null +++ b/src/NadekoBot/Common/Attributes/Description.cs @@ -0,0 +1,14 @@ +using System.Runtime.CompilerServices; +using Discord.Commands; +using NadekoBot.Services.Impl; + +namespace NadekoBot.Common.Attributes +{ + public class Description : SummaryAttribute + { + public Description([CallerMemberName] string memberName="") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Desc) + { + + } + } +} diff --git a/src/NadekoBot/Common/Attributes/NadekoCommand.cs b/src/NadekoBot/Common/Attributes/NadekoCommand.cs new file mode 100644 index 00000000..ee8b9d58 --- /dev/null +++ b/src/NadekoBot/Common/Attributes/NadekoCommand.cs @@ -0,0 +1,14 @@ +using System.Runtime.CompilerServices; +using Discord.Commands; +using NadekoBot.Services.Impl; + +namespace NadekoBot.Common.Attributes +{ + public class NadekoCommand : CommandAttribute + { + public NadekoCommand([CallerMemberName] string memberName="") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Cmd.Split(' ')[0]) + { + + } + } +} diff --git a/src/NadekoBot/Attributes/NadekoModuleAttribute.cs b/src/NadekoBot/Common/Attributes/NadekoModuleAttribute.cs similarity index 73% rename from src/NadekoBot/Attributes/NadekoModuleAttribute.cs rename to src/NadekoBot/Common/Attributes/NadekoModuleAttribute.cs index dc0d21b7..31ba1730 100644 --- a/src/NadekoBot/Attributes/NadekoModuleAttribute.cs +++ b/src/NadekoBot/Common/Attributes/NadekoModuleAttribute.cs @@ -1,7 +1,7 @@ -using Discord.Commands; -using System; +using System; +using Discord.Commands; -namespace NadekoBot.Attributes +namespace NadekoBot.Common.Attributes { [AttributeUsage(AttributeTargets.Class)] sealed class NadekoModuleAttribute : GroupAttribute diff --git a/src/NadekoBot/Attributes/OwnerOnlyAttribute.cs b/src/NadekoBot/Common/Attributes/OwnerOnlyAttribute.cs similarity index 87% rename from src/NadekoBot/Attributes/OwnerOnlyAttribute.cs rename to src/NadekoBot/Common/Attributes/OwnerOnlyAttribute.cs index 9799dbcd..c5227daa 100644 --- a/src/NadekoBot/Attributes/OwnerOnlyAttribute.cs +++ b/src/NadekoBot/Common/Attributes/OwnerOnlyAttribute.cs @@ -1,9 +1,9 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Discord.Commands; -using System; using NadekoBot.Services; -namespace NadekoBot.Attributes +namespace NadekoBot.Common.Attributes { public class OwnerOnlyAttribute : PreconditionAttribute { diff --git a/src/NadekoBot/Common/Attributes/Usage.cs b/src/NadekoBot/Common/Attributes/Usage.cs new file mode 100644 index 00000000..391de638 --- /dev/null +++ b/src/NadekoBot/Common/Attributes/Usage.cs @@ -0,0 +1,24 @@ +using System.Runtime.CompilerServices; +using Discord.Commands; +using NadekoBot.Services.Impl; +using System.Linq; +using Discord; + +namespace NadekoBot.Common.Attributes +{ + public class Usage : RemarksAttribute + { + public Usage([CallerMemberName] string memberName="") : base(Usage.GetUsage(memberName)) + { + + } + + public static string GetUsage(string memberName) + { + var usage = Localization.LoadCommand(memberName.ToLowerInvariant()).Usage; + return string.Join(" or ", usage + .Select(x => Format.Code(x))); + + } + } +} diff --git a/src/NadekoBot/Common/BotConfigEditType.cs b/src/NadekoBot/Common/BotConfigEditType.cs new file mode 100644 index 00000000..92be166a --- /dev/null +++ b/src/NadekoBot/Common/BotConfigEditType.cs @@ -0,0 +1,26 @@ +namespace NadekoBot.Common +{ + public enum BotConfigEditType + { + BetflipMultiplier, + Betroll100Multiplier, + Betroll67Multiplier, + Betroll91Multiplier, + CurrencyGenerationChance, + CurrencyGenerationCooldown, + CurrencyName, + CurrencyPluralName, + CurrencySign, + DmHelpString, + HelpString, + CurrencyDropAmount, + CurrencyDropAmountMax, + MinimumBetAmount, + TriviaCurrencyReward, + XpPerMessage, + XpMinutesTimeout, + + //ErrorColor, //after i fix the nadekobot.cs static variables + //OkColor + } +} diff --git a/src/NadekoBot/DataStructures/CREmbed.cs b/src/NadekoBot/Common/CREmbed.cs similarity index 59% rename from src/NadekoBot/DataStructures/CREmbed.cs rename to src/NadekoBot/Common/CREmbed.cs index c0bb37f9..8ef9d30d 100644 --- a/src/NadekoBot/DataStructures/CREmbed.cs +++ b/src/NadekoBot/Common/CREmbed.cs @@ -1,8 +1,10 @@ -using Discord; +using System; +using Discord; +using NadekoBot.Extensions; using Newtonsoft.Json; using NLog; -namespace NadekoBot.DataStructures +namespace NadekoBot.Common { public class CREmbed { @@ -31,19 +33,31 @@ namespace NadekoBot.DataStructures public EmbedBuilder ToEmbed() { - var embed = new EmbedBuilder() - .WithTitle(Title) - .WithDescription(Description) - .WithColor(new Discord.Color(Color)); + var embed = new EmbedBuilder(); + + if (!string.IsNullOrWhiteSpace(Title)) + embed.WithTitle(Title); + if (!string.IsNullOrWhiteSpace(Description)) + embed.WithDescription(Description); + embed.WithColor(new Discord.Color(Color)); if (Footer != null) - embed.WithFooter(efb => efb.WithIconUrl(Footer.IconUrl).WithText(Footer.Text)); - embed.WithThumbnailUrl(Thumbnail) - .WithImageUrl(Image); + embed.WithFooter(efb => + { + efb.WithText(Footer.Text); + if (Uri.IsWellFormedUriString(Footer.IconUrl, UriKind.Absolute)) + efb.WithIconUrl(Footer.IconUrl); + }); + + if (Thumbnail != null && Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute)) + embed.WithThumbnailUrl(Thumbnail); + if(Image != null && Uri.IsWellFormedUriString(Image, UriKind.Absolute)) + embed.WithImageUrl(Image); if (Fields != null) foreach (var f in Fields) { - embed.AddField(efb => efb.WithName(f.Name).WithValue(f.Value).WithIsInline(f.Inline)); + if(!string.IsNullOrWhiteSpace(f.Name) && !string.IsNullOrWhiteSpace(f.Value)) + embed.AddField(efb => efb.WithName(f.Name).WithValue(f.Value).WithIsInline(f.Inline)); } return embed; @@ -58,7 +72,13 @@ namespace NadekoBot.DataStructures try { var crembed = JsonConvert.DeserializeObject(input); - + + if(crembed.Fields != null && crembed.Fields.Length > 0) + foreach (var f in crembed.Fields) + { + f.Name = f.Name.TrimTo(256); + f.Value = f.Value.TrimTo(1024); + } if (!crembed.IsValid) return false; diff --git a/src/NadekoBot/DataStructures/ConcurrentHashSet.cs b/src/NadekoBot/Common/Collections/ConcurrentHashSet.cs similarity index 99% rename from src/NadekoBot/DataStructures/ConcurrentHashSet.cs rename to src/NadekoBot/Common/Collections/ConcurrentHashSet.cs index 2a2ae1bf..c7b84515 100644 --- a/src/NadekoBot/DataStructures/ConcurrentHashSet.cs +++ b/src/NadekoBot/Common/Collections/ConcurrentHashSet.cs @@ -1,13 +1,14 @@ // License MIT // Source: https://github.com/i3arnon/ConcurrentHashSet -using ConcurrentCollections; +using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; -namespace System.Collections.Concurrent +namespace NadekoBot.Common.Collections { /// /// Represents a thread-safe hash-based unique collection. diff --git a/src/NadekoBot/DataStructures/DisposableImutableList.cs b/src/NadekoBot/Common/Collections/DisposableImutableList.cs similarity index 98% rename from src/NadekoBot/DataStructures/DisposableImutableList.cs rename to src/NadekoBot/Common/Collections/DisposableImutableList.cs index 391365e5..b97a2cc9 100644 --- a/src/NadekoBot/DataStructures/DisposableImutableList.cs +++ b/src/NadekoBot/Common/Collections/DisposableImutableList.cs @@ -2,9 +2,8 @@ using System.Collections; using System.Collections.Generic; -namespace NadekoBot.DataStructures +namespace NadekoBot.Common.Collections { - public static class DisposableReadOnlyListExtensions { public static IDisposableReadOnlyList AsDisposable(this IReadOnlyList arr) where T : IDisposable diff --git a/src/NadekoBot/DataStructures/IndexedCollection.cs b/src/NadekoBot/Common/Collections/IndexedCollection.cs similarity index 94% rename from src/NadekoBot/DataStructures/IndexedCollection.cs rename to src/NadekoBot/Common/Collections/IndexedCollection.cs index 72b4f343..6ef057c8 100644 --- a/src/NadekoBot/DataStructures/IndexedCollection.cs +++ b/src/NadekoBot/Common/Collections/IndexedCollection.cs @@ -1,11 +1,11 @@ -using NadekoBot.Services.Database.Models; -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Linq; +using NadekoBot.Services.Database.Models; -namespace NadekoBot.DataStructures +namespace NadekoBot.Common.Collections { - public class IndexedCollection : IList where T : IIndexed + public class IndexedCollection : IList where T : class, IIndexed { public List Source { get; } private readonly object _locker = new object(); diff --git a/src/NadekoBot/Common/Collections/PoopyRingBuffer.cs b/src/NadekoBot/Common/Collections/PoopyRingBuffer.cs new file mode 100644 index 00000000..d1cf34c0 --- /dev/null +++ b/src/NadekoBot/Common/Collections/PoopyRingBuffer.cs @@ -0,0 +1,106 @@ +using System; +using System.Threading; + +namespace NadekoBot.Common.Collections +{ + public class PoopyRingBuffer : IDisposable + { + // readpos == writepos means empty + // writepos == readpos - 1 means full + + private byte[] _buffer; + public int Capacity { get; } + + private int ReadPos { get; set; } = 0; + private int WritePos { get; set; } = 0; + + public int Length => ReadPos <= WritePos + ? WritePos - ReadPos + : Capacity - (ReadPos - WritePos); + + public int RemainingCapacity + { + get => Capacity - Length - 1; + } + + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1); + + public PoopyRingBuffer(int capacity = 81920 * 100) + { + this.Capacity = capacity + 1; + this._buffer = new byte[this.Capacity]; + } + + public int Read(byte[] b, int offset, int toRead) + { + if (WritePos == ReadPos) + return 0; + + if (toRead > Length) + toRead = Length; + + if (WritePos > ReadPos) + { + Array.Copy(_buffer, ReadPos, b, offset, toRead); + ReadPos += toRead; + } + else + { + var toEnd = Capacity - ReadPos; + var firstRead = toRead > toEnd ? + toEnd : + toRead; + Array.Copy(_buffer, ReadPos, b, offset, firstRead); + ReadPos += firstRead; + var secondRead = toRead - firstRead; + if (secondRead > 0) + { + Array.Copy(_buffer, 0, b, offset + firstRead, secondRead); + ReadPos = secondRead; + } + } + return toRead; + } + + public bool Write(byte[] b, int offset, int toWrite) + { + while (toWrite > RemainingCapacity) + return false; + + if (toWrite == 0) + return true; + + if (WritePos < ReadPos) + { + Array.Copy(b, offset, _buffer, WritePos, toWrite); + WritePos += toWrite; + } + else + { + var toEnd = Capacity - WritePos; + var firstWrite = toWrite > toEnd ? + toEnd : + toWrite; + Array.Copy(b, offset, _buffer, WritePos, firstWrite); + var secondWrite = toWrite - firstWrite; + if (secondWrite > 0) + { + Array.Copy(b, offset + firstWrite, _buffer, 0, secondWrite); + WritePos = secondWrite; + } + else + { + WritePos += firstWrite; + if (WritePos == Capacity) + WritePos = 0; + } + } + return true; + } + + public void Dispose() + { + _buffer = null; + } + } +} diff --git a/src/NadekoBot/Common/CommandData.cs b/src/NadekoBot/Common/CommandData.cs new file mode 100644 index 00000000..b53ab2ea --- /dev/null +++ b/src/NadekoBot/Common/CommandData.cs @@ -0,0 +1,9 @@ +namespace NadekoBot.Common +{ + public class CommandData + { + public string Cmd { get; set; } + public string Desc { get; set; } + public string[] Usage { get; set; } + } +} diff --git a/src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyBlocker.cs b/src/NadekoBot/Common/ModuleBehaviors/IEarlyBlocker.cs similarity index 70% rename from src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyBlocker.cs rename to src/NadekoBot/Common/ModuleBehaviors/IEarlyBlocker.cs index 9c10e910..5d69e7b2 100644 --- a/src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyBlocker.cs +++ b/src/NadekoBot/Common/ModuleBehaviors/IEarlyBlocker.cs @@ -1,7 +1,7 @@ -using Discord; -using System.Threading.Tasks; +using System.Threading.Tasks; +using Discord; -namespace NadekoBot.DataStructures.ModuleBehaviors +namespace NadekoBot.Common.ModuleBehaviors { /// /// Implemented by modules which block execution before anything is executed diff --git a/src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyBlockingExecutor.cs b/src/NadekoBot/Common/ModuleBehaviors/IEarlyBlockingExecutor.cs similarity index 68% rename from src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyBlockingExecutor.cs rename to src/NadekoBot/Common/ModuleBehaviors/IEarlyBlockingExecutor.cs index f28eaf4f..536d829e 100644 --- a/src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyBlockingExecutor.cs +++ b/src/NadekoBot/Common/ModuleBehaviors/IEarlyBlockingExecutor.cs @@ -1,8 +1,8 @@ -using Discord; +using System.Threading.Tasks; +using Discord; using Discord.WebSocket; -using System.Threading.Tasks; -namespace NadekoBot.DataStructures.ModuleBehaviors +namespace NadekoBot.Common.ModuleBehaviors { /// /// Implemented by modules which can execute something and prevent further commands from being executed. @@ -13,6 +13,6 @@ namespace NadekoBot.DataStructures.ModuleBehaviors /// Try to execute some logic within some module's service. /// /// Whether it should block other command executions after it. - Task TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage msg); + Task TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg); } } diff --git a/src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyExecutor.cs b/src/NadekoBot/Common/ModuleBehaviors/IEarlyExecutor.cs similarity index 53% rename from src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyExecutor.cs rename to src/NadekoBot/Common/ModuleBehaviors/IEarlyExecutor.cs index f761ef85..2f86be12 100644 --- a/src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyExecutor.cs +++ b/src/NadekoBot/Common/ModuleBehaviors/IEarlyExecutor.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.DataStructures.ModuleBehaviors +namespace NadekoBot.Common.ModuleBehaviors { public interface IEarlyExecutor { diff --git a/src/NadekoBot/DataStructures/ModuleBehaviors/IINputTransformer.cs b/src/NadekoBot/Common/ModuleBehaviors/IINputTransformer.cs similarity index 61% rename from src/NadekoBot/DataStructures/ModuleBehaviors/IINputTransformer.cs rename to src/NadekoBot/Common/ModuleBehaviors/IINputTransformer.cs index 3dd96464..8f4be470 100644 --- a/src/NadekoBot/DataStructures/ModuleBehaviors/IINputTransformer.cs +++ b/src/NadekoBot/Common/ModuleBehaviors/IINputTransformer.cs @@ -1,7 +1,7 @@ -using Discord; -using System.Threading.Tasks; +using System.Threading.Tasks; +using Discord; -namespace NadekoBot.DataStructures.ModuleBehaviors +namespace NadekoBot.Common.ModuleBehaviors { public interface IInputTransformer { diff --git a/src/NadekoBot/Common/ModuleBehaviors/ILateBlocker.cs b/src/NadekoBot/Common/ModuleBehaviors/ILateBlocker.cs new file mode 100644 index 00000000..58299714 --- /dev/null +++ b/src/NadekoBot/Common/ModuleBehaviors/ILateBlocker.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; + +namespace NadekoBot.Common.ModuleBehaviors +{ + public interface ILateBlocker + { + Task TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild, + IMessageChannel channel, IUser user, string moduleName, string commandName); + } +} diff --git a/src/NadekoBot/DataStructures/ModuleBehaviors/ILateBlockingExecutor.cs b/src/NadekoBot/Common/ModuleBehaviors/ILateBlockingExecutor.cs similarity index 55% rename from src/NadekoBot/DataStructures/ModuleBehaviors/ILateBlockingExecutor.cs rename to src/NadekoBot/Common/ModuleBehaviors/ILateBlockingExecutor.cs index b4ebe54d..d1062524 100644 --- a/src/NadekoBot/DataStructures/ModuleBehaviors/ILateBlockingExecutor.cs +++ b/src/NadekoBot/Common/ModuleBehaviors/ILateBlockingExecutor.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.DataStructures.ModuleBehaviors +namespace NadekoBot.Common.ModuleBehaviors { public interface ILateBlockingExecutor { diff --git a/src/NadekoBot/Common/ModuleBehaviors/ILateExecutor.cs b/src/NadekoBot/Common/ModuleBehaviors/ILateExecutor.cs new file mode 100644 index 00000000..53e878e4 --- /dev/null +++ b/src/NadekoBot/Common/ModuleBehaviors/ILateExecutor.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; + +namespace NadekoBot.Common.ModuleBehaviors +{ + /// + /// Last thing to be executed, won't stop further executions + /// + public interface ILateExecutor + { + Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg); + } +} diff --git a/src/NadekoBot/DataStructures/NadekoRandom.cs b/src/NadekoBot/Common/NadekoRandom.cs similarity index 77% rename from src/NadekoBot/DataStructures/NadekoRandom.cs rename to src/NadekoBot/Common/NadekoRandom.cs index f1af50c1..b6f68ac6 100644 --- a/src/NadekoBot/DataStructures/NadekoRandom.cs +++ b/src/NadekoBot/Common/NadekoRandom.cs @@ -1,26 +1,21 @@ using System; using System.Security.Cryptography; -namespace NadekoBot.Services +namespace NadekoBot.Common { public class NadekoRandom : Random { - RandomNumberGenerator rng; + readonly RandomNumberGenerator _rng; public NadekoRandom() : base() { - rng = RandomNumberGenerator.Create(); - } - - private NadekoRandom(int Seed) : base(Seed) - { - rng = RandomNumberGenerator.Create(); + _rng = RandomNumberGenerator.Create(); } public override int Next() { var bytes = new byte[sizeof(int)]; - rng.GetBytes(bytes); + _rng.GetBytes(bytes); return Math.Abs(BitConverter.ToInt32(bytes, 0)); } @@ -29,7 +24,7 @@ namespace NadekoBot.Services if (maxValue <= 0) throw new ArgumentOutOfRangeException(); var bytes = new byte[sizeof(int)]; - rng.GetBytes(bytes); + _rng.GetBytes(bytes); return Math.Abs(BitConverter.ToInt32(bytes, 0)) % maxValue; } @@ -40,27 +35,27 @@ namespace NadekoBot.Services if (minValue == maxValue) return minValue; var bytes = new byte[sizeof(int)]; - rng.GetBytes(bytes); + _rng.GetBytes(bytes); var sign = Math.Sign(BitConverter.ToInt32(bytes, 0)); return (sign * BitConverter.ToInt32(bytes, 0)) % (maxValue - minValue) + minValue; } public override void NextBytes(byte[] buffer) { - rng.GetBytes(buffer); + _rng.GetBytes(buffer); } protected override double Sample() { var bytes = new byte[sizeof(double)]; - rng.GetBytes(bytes); + _rng.GetBytes(bytes); return Math.Abs(BitConverter.ToDouble(bytes, 0) / double.MaxValue + 1); } public override double NextDouble() { var bytes = new byte[sizeof(double)]; - rng.GetBytes(bytes); + _rng.GetBytes(bytes); return BitConverter.ToDouble(bytes, 0); } } diff --git a/src/NadekoBot/DataStructures/NoPublicBotPrecondition.cs b/src/NadekoBot/Common/NoPublicBotPrecondition.cs similarity index 85% rename from src/NadekoBot/DataStructures/NoPublicBotPrecondition.cs rename to src/NadekoBot/Common/NoPublicBotPrecondition.cs index c5e06578..95f35566 100644 --- a/src/NadekoBot/DataStructures/NoPublicBotPrecondition.cs +++ b/src/NadekoBot/Common/NoPublicBotPrecondition.cs @@ -1,8 +1,8 @@ -using Discord.Commands; -using System; +using System; using System.Threading.Tasks; +using Discord.Commands; -namespace NadekoBot.DataStructures +namespace NadekoBot.Common { public class NoPublicBot : PreconditionAttribute { diff --git a/src/NadekoBot/DataStructures/PlatformHelper.cs b/src/NadekoBot/Common/PlatformHelper.cs similarity index 95% rename from src/NadekoBot/DataStructures/PlatformHelper.cs rename to src/NadekoBot/Common/PlatformHelper.cs index 8c523ec9..a8a53b0a 100644 --- a/src/NadekoBot/DataStructures/PlatformHelper.cs +++ b/src/NadekoBot/Common/PlatformHelper.cs @@ -1,6 +1,6 @@ using System; -namespace ConcurrentCollections +namespace NadekoBot.Common { public static class PlatformHelper { diff --git a/src/NadekoBot/Common/Replacements/ReplacementBuilder.cs b/src/NadekoBot/Common/Replacements/ReplacementBuilder.cs new file mode 100644 index 00000000..4df63980 --- /dev/null +++ b/src/NadekoBot/Common/Replacements/ReplacementBuilder.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Text.RegularExpressions; +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Extensions; +using NadekoBot.Modules.Administration.Services; +using NadekoBot.Modules.Music.Services; + +namespace NadekoBot.Common.Replacements +{ + public class ReplacementBuilder + { + private static readonly Regex rngRegex = new Regex("%rng(?:(?(?:-)?\\d+)-(?(?:-)?\\d+))?%", RegexOptions.Compiled); + private ConcurrentDictionary> _reps = new ConcurrentDictionary>(); + private ConcurrentDictionary> _regex = new ConcurrentDictionary>(); + + public ReplacementBuilder() + { + WithRngRegex(); + } + + public ReplacementBuilder WithDefault(IUser usr, IMessageChannel ch, IGuild g, DiscordSocketClient client) + { + return this.WithUser(usr) + .WithChannel(ch) + .WithServer(client, g) + .WithClient(client); + } + + public ReplacementBuilder WithDefault(ICommandContext ctx) => + WithDefault(ctx.User, ctx.Channel, ctx.Guild, (DiscordSocketClient)ctx.Client); + + public ReplacementBuilder WithClient(DiscordSocketClient client) + { + _reps.TryAdd("%mention%", () => $"<@{client.CurrentUser.Id}>"); + _reps.TryAdd("%shardid%", () => client.ShardId.ToString()); + _reps.TryAdd("%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials())); + return this; + } + + public ReplacementBuilder WithServer(DiscordSocketClient client, IGuild g) + { + + _reps.TryAdd("%sid%", () => g == null ? "DM" : g.Id.ToString()); + _reps.TryAdd("%server%", () => g == null ? "DM" : g.Name); + _reps.TryAdd("%server_time%", () => + { + TimeZoneInfo to = TimeZoneInfo.Local; + if (g != null) + { + if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz)) + to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local; + } + + return TimeZoneInfo.ConvertTime(DateTime.UtcNow, + TimeZoneInfo.Utc, + to).ToString("HH:mm ") + to.StandardName.GetInitials(); + }); + return this; + } + + public ReplacementBuilder WithChannel(IMessageChannel ch) + { + _reps.TryAdd("%channel%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name); + _reps.TryAdd("%chname%", () => ch.Name); + _reps.TryAdd("%cid%", () => ch?.Id.ToString()); + return this; + } + + public ReplacementBuilder WithUser(IUser user) + { + _reps.TryAdd("%user%", () => user.Mention); + _reps.TryAdd("%userfull%", () => user.ToString()); + _reps.TryAdd("%username%", () => user.Username); + _reps.TryAdd("%userdiscrim%", () => user.Discriminator); + _reps.TryAdd("%id%", () => user.Id.ToString()); + _reps.TryAdd("%uid%", () => user.Id.ToString()); + return this; + } + + public ReplacementBuilder WithStats(DiscordSocketClient c) + { + _reps.TryAdd("%servers%", () => c.Guilds.Count.ToString()); + _reps.TryAdd("%users%", () => c.Guilds.Sum(s => s.Users.Count).ToString()); + return this; + } + + public ReplacementBuilder WithMusic(MusicService ms) + { + _reps.TryAdd("%playing%", () => + { + var cnt = ms.MusicPlayers.Count(kvp => kvp.Value.Current.Current != null); + if (cnt != 1) return cnt.ToString(); + try + { + var mp = ms.MusicPlayers.FirstOrDefault(); + var title = mp.Value?.Current.Current?.Title; + return title ?? "No songs"; + } + catch + { + return "error"; + } + }); + _reps.TryAdd("%queued%", () => ms.MusicPlayers.Sum(kvp => kvp.Value.QueueArray().Songs.Length).ToString()); + return this; + } + + public ReplacementBuilder WithRngRegex() + { + var rng = new NadekoRandom(); + _regex.TryAdd(rngRegex, (match) => + { + int from = 0; + int.TryParse(match.Groups["from"].ToString(), out from); + + int to = 0; + int.TryParse(match.Groups["to"].ToString(), out to); + + if (from == 0 && to == 0) + { + return rng.Next(0, 11).ToString(); + } + + if (from >= to) + return string.Empty; + + return rng.Next(from, to + 1).ToString(); + }); + return this; + } + + public ReplacementBuilder WithOverride(string key, Func output) + { + _reps.AddOrUpdate(key, output, delegate { return output; }); + return this; + } + + public Replacer Build() + { + return new Replacer(_reps.Select(x => (x.Key, x.Value)).ToArray(), _regex.Select(x => (x.Key, x.Value)).ToArray()); + } + } +} diff --git a/src/NadekoBot/Common/Replacements/Replacer.cs b/src/NadekoBot/Common/Replacements/Replacer.cs new file mode 100644 index 00000000..410b6c9a --- /dev/null +++ b/src/NadekoBot/Common/Replacements/Replacer.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace NadekoBot.Common.Replacements +{ + public class Replacer + { + private readonly IEnumerable<(string Key, Func Text)> _replacements; + private readonly IEnumerable<(Regex Regex, Func Replacement)> _regex; + + public Replacer(IEnumerable<(string, Func)> replacements, IEnumerable<(Regex, Func)> regex) + { + _replacements = replacements; + _regex = regex; + } + + public string Replace(string input) + { + if (string.IsNullOrWhiteSpace(input)) + return input; + + foreach (var item in _replacements) + { + if (input.Contains(item.Key)) + input = input.Replace(item.Key, item.Text()); + } + + foreach (var item in _regex) + { + input = item.Regex.Replace(input, (m) => item.Replacement(m)); + } + + return input; + } + + public void Replace(CREmbed embedData) + { + embedData.PlainText = Replace(embedData.PlainText); + embedData.Description = Replace(embedData.Description); + embedData.Title = Replace(embedData.Title); + + if (embedData.Fields != null) + foreach (var f in embedData.Fields) + { + f.Name = Replace(f.Name); + f.Value = Replace(f.Value); + } + + if (embedData.Footer != null) + embedData.Footer.Text = Replace(embedData.Footer.Text); + } + } +} diff --git a/src/NadekoBot/Common/Shard0Precondition.cs b/src/NadekoBot/Common/Shard0Precondition.cs new file mode 100644 index 00000000..965101a3 --- /dev/null +++ b/src/NadekoBot/Common/Shard0Precondition.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using Discord.Commands; +using Discord.WebSocket; + +namespace NadekoBot.Common +{ + public class Shard0Precondition : PreconditionAttribute + { + public override Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + { + var c = (DiscordSocketClient)context.Client; + if (c.ShardId != 0) + return Task.FromResult(PreconditionResult.FromError("Must be ran from shard #0")); + + return Task.FromResult(PreconditionResult.FromSuccess()); + } + } +} diff --git a/src/NadekoBot/Common/ShardCom/IShardComMessage.cs b/src/NadekoBot/Common/ShardCom/IShardComMessage.cs new file mode 100644 index 00000000..1ea37c67 --- /dev/null +++ b/src/NadekoBot/Common/ShardCom/IShardComMessage.cs @@ -0,0 +1,13 @@ +using System; +using Discord; + +namespace NadekoBot.Common.ShardCom +{ + public class ShardComMessage + { + public int ShardId { get; set; } + public ConnectionState ConnectionState { get; set; } + public int Guilds { get; set; } + public DateTime Time { get; set; } + } +} diff --git a/src/NadekoBot/Common/ShardCom/ShardComClient.cs b/src/NadekoBot/Common/ShardCom/ShardComClient.cs new file mode 100644 index 00000000..9c10a11d --- /dev/null +++ b/src/NadekoBot/Common/ShardCom/ShardComClient.cs @@ -0,0 +1,28 @@ +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace NadekoBot.Common.ShardCom +{ + public class ShardComClient + { + private int port; + + public ShardComClient(int port) + { + this.port = port; + } + + public async Task Send(ShardComMessage data) + { + var msg = JsonConvert.SerializeObject(data); + using (var client = new UdpClient()) + { + var bytes = Encoding.UTF8.GetBytes(msg); + await client.SendAsync(bytes, bytes.Length, IPAddress.Loopback.ToString(), port).ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Common/ShardCom/ShardComServer.cs b/src/NadekoBot/Common/ShardCom/ShardComServer.cs new file mode 100644 index 00000000..b6b5a0ba --- /dev/null +++ b/src/NadekoBot/Common/ShardCom/ShardComServer.cs @@ -0,0 +1,40 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace NadekoBot.Common.ShardCom +{ + public class ShardComServer : IDisposable + { + private readonly UdpClient _client; + + public ShardComServer(int port) + { + _client = new UdpClient(port); + } + + public void Start() + { + Task.Run(async () => + { + var ip = new IPEndPoint(IPAddress.Any, 0); + while (true) + { + var recv = await _client.ReceiveAsync(); + var data = Encoding.UTF8.GetString(recv.Buffer); + var _ = OnDataReceived(JsonConvert.DeserializeObject(data)); + } + }); + } + + public void Dispose() + { + _client.Dispose(); + } + + public event Func OnDataReceived = delegate { return Task.CompletedTask; }; + } +} diff --git a/src/NadekoBot/Common/TypeReaders/AddRemove.cs b/src/NadekoBot/Common/TypeReaders/AddRemove.cs new file mode 100644 index 00000000..6bd3ea7c --- /dev/null +++ b/src/NadekoBot/Common/TypeReaders/AddRemove.cs @@ -0,0 +1,9 @@ +namespace NadekoBot.Common.TypeReaders +{ + public enum AddRemove + { + Add = 0, + Rem = 1, + Rm = 1, + } +} diff --git a/src/NadekoBot/DataStructures/TypeReaders/BotCommandTypeReader.cs b/src/NadekoBot/Common/TypeReaders/BotCommandTypeReader.cs similarity index 75% rename from src/NadekoBot/DataStructures/TypeReaders/BotCommandTypeReader.cs rename to src/NadekoBot/Common/TypeReaders/BotCommandTypeReader.cs index 6a292833..d80fbd52 100644 --- a/src/NadekoBot/DataStructures/TypeReaders/BotCommandTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/BotCommandTypeReader.cs @@ -1,24 +1,18 @@ -using Discord.Commands; -using NadekoBot.Services; -using NadekoBot.Services.CustomReactions; +using System; using System.Linq; using System.Threading.Tasks; +using Discord.Commands; +using NadekoBot.Modules.CustomReactions.Services; +using NadekoBot.Services; -namespace NadekoBot.TypeReaders +namespace NadekoBot.Common.TypeReaders { public class CommandTypeReader : TypeReader { - private readonly CommandService _cmds; - private readonly CommandHandler _cmdHandler; - - public CommandTypeReader(CommandService cmds, CommandHandler cmdHandler) - { - _cmds = cmds; - _cmdHandler = cmdHandler; - } - - public override Task Read(ICommandContext context, string input) + public override Task Read(ICommandContext context, string input, IServiceProvider services) { + var _cmds = ((INServiceProvider)services).GetService(); + var _cmdHandler = ((INServiceProvider)services).GetService(); input = input.ToUpperInvariant(); var prefix = _cmdHandler.GetPrefix(context.Guild); if (!input.StartsWith(prefix.ToUpperInvariant())) @@ -37,17 +31,12 @@ namespace NadekoBot.TypeReaders public class CommandOrCrTypeReader : CommandTypeReader { - private readonly CustomReactionsService _crs; - - public CommandOrCrTypeReader(CustomReactionsService crs, CommandService cmds, CommandHandler cmdHandler) : base(cmds, cmdHandler) - { - _crs = crs; - } - - public override async Task Read(ICommandContext context, string input) + public override async Task Read(ICommandContext context, string input, IServiceProvider services) { input = input.ToUpperInvariant(); + var _crs = ((INServiceProvider)services).GetService(); + if (_crs.GlobalReactions.Any(x => x.Trigger.ToUpperInvariant() == input)) { return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input)); @@ -64,7 +53,7 @@ namespace NadekoBot.TypeReaders } } - var cmd = await base.Read(context, input); + var cmd = await base.Read(context, input, services); if (cmd.IsSuccess) { return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name)); diff --git a/src/NadekoBot/DataStructures/TypeReaders/GuildDateTimeTypeReader.cs b/src/NadekoBot/Common/TypeReaders/GuildDateTimeTypeReader.cs similarity index 79% rename from src/NadekoBot/DataStructures/TypeReaders/GuildDateTimeTypeReader.cs rename to src/NadekoBot/Common/TypeReaders/GuildDateTimeTypeReader.cs index 798caf62..b1fa1f00 100644 --- a/src/NadekoBot/DataStructures/TypeReaders/GuildDateTimeTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/GuildDateTimeTypeReader.cs @@ -1,21 +1,15 @@ -using Discord.Commands; -using NadekoBot.Services.Administration; -using System; +using System; using System.Threading.Tasks; +using Discord.Commands; +using NadekoBot.Modules.Administration.Services; -namespace NadekoBot.TypeReaders +namespace NadekoBot.Common.TypeReaders { public class GuildDateTimeTypeReader : TypeReader { - private readonly GuildTimezoneService _gts; - - public GuildDateTimeTypeReader(GuildTimezoneService gts) - { - _gts = gts; - } - - public override Task Read(ICommandContext context, string input) + public override Task Read(ICommandContext context, string input, IServiceProvider services) { + var _gts = (GuildTimezoneService)services.GetService(typeof(GuildTimezoneService)); if (!DateTime.TryParse(input, out var dt)) return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input string is in an incorrect format.")); diff --git a/src/NadekoBot/DataStructures/TypeReaders/GuildTypeReader.cs b/src/NadekoBot/Common/TypeReaders/GuildTypeReader.cs similarity index 77% rename from src/NadekoBot/DataStructures/TypeReaders/GuildTypeReader.cs rename to src/NadekoBot/Common/TypeReaders/GuildTypeReader.cs index 63971f5a..132e1f0f 100644 --- a/src/NadekoBot/DataStructures/TypeReaders/GuildTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/GuildTypeReader.cs @@ -1,19 +1,20 @@ -using Discord.Commands; -using Discord.WebSocket; +using System; using System.Linq; using System.Threading.Tasks; +using Discord.Commands; +using Discord.WebSocket; -namespace NadekoBot.TypeReaders +namespace NadekoBot.Common.TypeReaders { public class GuildTypeReader : TypeReader { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public GuildTypeReader(DiscordShardedClient client) + public GuildTypeReader(DiscordSocketClient client) { _client = client; } - public override Task Read(ICommandContext context, string input) + public override Task Read(ICommandContext context, string input, IServiceProvider _) { input = input.Trim().ToLowerInvariant(); var guilds = _client.Guilds; diff --git a/src/NadekoBot/DataStructures/PermissionAction.cs b/src/NadekoBot/Common/TypeReaders/Models/PermissionAction.cs similarity index 93% rename from src/NadekoBot/DataStructures/PermissionAction.cs rename to src/NadekoBot/Common/TypeReaders/Models/PermissionAction.cs index 324bbf44..f9a80058 100644 --- a/src/NadekoBot/DataStructures/PermissionAction.cs +++ b/src/NadekoBot/Common/TypeReaders/Models/PermissionAction.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Modules.Permissions +namespace NadekoBot.Common.TypeReaders.Models { public class PermissionAction { diff --git a/src/NadekoBot/DataStructures/TypeReaders/ModuleTypeReader.cs b/src/NadekoBot/Common/TypeReaders/ModuleTypeReader.cs similarity index 90% rename from src/NadekoBot/DataStructures/TypeReaders/ModuleTypeReader.cs rename to src/NadekoBot/Common/TypeReaders/ModuleTypeReader.cs index 88645835..1978732d 100644 --- a/src/NadekoBot/DataStructures/TypeReaders/ModuleTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/ModuleTypeReader.cs @@ -1,9 +1,10 @@ -using Discord.Commands; -using NadekoBot.Extensions; +using System; using System.Linq; using System.Threading.Tasks; +using Discord.Commands; +using NadekoBot.Extensions; -namespace NadekoBot.TypeReaders +namespace NadekoBot.Common.TypeReaders { public class ModuleTypeReader : TypeReader { @@ -14,7 +15,7 @@ namespace NadekoBot.TypeReaders _cmds = cmds; } - public override Task Read(ICommandContext context, string input) + public override Task Read(ICommandContext context, string input, IServiceProvider _) { input = input.ToUpperInvariant(); var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key; @@ -34,7 +35,7 @@ namespace NadekoBot.TypeReaders _cmds = cmds; } - public override Task Read(ICommandContext context, string input) + public override Task Read(ICommandContext context, string input, IServiceProvider _) { input = input.ToLowerInvariant(); var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToLowerInvariant() == input)?.Key; diff --git a/src/NadekoBot/DataStructures/TypeReaders/PermissionActionTypeReader.cs b/src/NadekoBot/Common/TypeReaders/PermissionActionTypeReader.cs similarity index 88% rename from src/NadekoBot/DataStructures/TypeReaders/PermissionActionTypeReader.cs rename to src/NadekoBot/Common/TypeReaders/PermissionActionTypeReader.cs index aa3510a6..82e16e16 100644 --- a/src/NadekoBot/DataStructures/TypeReaders/PermissionActionTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/PermissionActionTypeReader.cs @@ -1,15 +1,16 @@ -using Discord.Commands; +using System; using System.Threading.Tasks; -using NadekoBot.Modules.Permissions; +using Discord.Commands; +using NadekoBot.Common.TypeReaders.Models; -namespace NadekoBot.TypeReaders +namespace NadekoBot.Common.TypeReaders { /// /// Used instead of bool for more flexible keywords for true/false only in the permission module /// public class PermissionActionTypeReader : TypeReader { - public override Task Read(ICommandContext context, string input) + public override Task Read(ICommandContext context, string input, IServiceProvider _) { input = input.ToUpperInvariant(); switch (input) diff --git a/src/NadekoBot/DataStructures/ModuleBehaviors/ILateBlocker.cs b/src/NadekoBot/DataStructures/ModuleBehaviors/ILateBlocker.cs deleted file mode 100644 index 3b3fc020..00000000 --- a/src/NadekoBot/DataStructures/ModuleBehaviors/ILateBlocker.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Discord; -using Discord.WebSocket; -using System.Threading.Tasks; - -namespace NadekoBot.DataStructures.ModuleBehaviors -{ - public interface ILateBlocker - { - Task TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild, - IMessageChannel channel, IUser user, string moduleName, string commandName); - } -} diff --git a/src/NadekoBot/DataStructures/ModuleBehaviors/ILateExecutor.cs b/src/NadekoBot/DataStructures/ModuleBehaviors/ILateExecutor.cs deleted file mode 100644 index 3cf11603..00000000 --- a/src/NadekoBot/DataStructures/ModuleBehaviors/ILateExecutor.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Discord; -using Discord.WebSocket; -using System.Threading.Tasks; - -namespace NadekoBot.DataStructures.ModuleBehaviors -{ - /// - /// Last thing to be executed, won't stop further executions - /// - public interface ILateExecutor - { - Task LateExecute(DiscordShardedClient client, IGuild guild, IUserMessage msg); - } -} diff --git a/src/NadekoBot/Migrations/20170612094138_verbose-errors.cs b/src/NadekoBot/Migrations/20170612094138_verbose-errors.cs index 3caf4aca..291e4e40 100644 --- a/src/NadekoBot/Migrations/20170612094138_verbose-errors.cs +++ b/src/NadekoBot/Migrations/20170612094138_verbose-errors.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; namespace NadekoBot.Migrations { diff --git a/src/NadekoBot/Migrations/20170612234751_repeat time of day.cs b/src/NadekoBot/Migrations/20170612234751_repeat time of day.cs index 076d69d3..a5a7bfac 100644 --- a/src/NadekoBot/Migrations/20170612234751_repeat time of day.cs +++ b/src/NadekoBot/Migrations/20170612234751_repeat time of day.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Migrations; namespace NadekoBot.Migrations diff --git a/src/NadekoBot/Migrations/20170613231358_maxdropamount.cs b/src/NadekoBot/Migrations/20170613231358_maxdropamount.cs index ea9d58c8..31495e9b 100644 --- a/src/NadekoBot/Migrations/20170613231358_maxdropamount.cs +++ b/src/NadekoBot/Migrations/20170613231358_maxdropamount.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; namespace NadekoBot.Migrations { diff --git a/src/NadekoBot/Migrations/20170616154106_crstartswith.cs b/src/NadekoBot/Migrations/20170616154106_crstartswith.cs index 53aa582f..0aeca5bb 100644 --- a/src/NadekoBot/Migrations/20170616154106_crstartswith.cs +++ b/src/NadekoBot/Migrations/20170616154106_crstartswith.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; namespace NadekoBot.Migrations { diff --git a/src/NadekoBot/Migrations/20170714021615_stream-role.Designer.cs b/src/NadekoBot/Migrations/20170714021615_stream-role.Designer.cs new file mode 100644 index 00000000..a1621e42 --- /dev/null +++ b/src/NadekoBot/Migrations/20170714021615_stream-role.Designer.cs @@ -0,0 +1,1597 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170714021615_stream-role")] + partial class streamrole + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170714021615_stream-role.cs b/src/NadekoBot/Migrations/20170714021615_stream-role.cs new file mode 100644 index 00000000..7cbce423 --- /dev/null +++ b/src/NadekoBot/Migrations/20170714021615_stream-role.cs @@ -0,0 +1,45 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class streamrole : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "StreamRoleSettings", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AddRoleId = table.Column(nullable: false), + DateAdded = table.Column(nullable: true), + FromRoleId = table.Column(nullable: false), + GuildConfigId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_StreamRoleSettings", x => x.Id); + table.ForeignKey( + name: "FK_StreamRoleSettings_GuildConfigs_GuildConfigId", + column: x => x.GuildConfigId, + principalTable: "GuildConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_StreamRoleSettings_GuildConfigId", + table: "StreamRoleSettings", + column: "GuildConfigId", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "StreamRoleSettings"); + } + } +} diff --git a/src/NadekoBot/Migrations/20170719023924_streamrole-kw-bl-wl.Designer.cs b/src/NadekoBot/Migrations/20170719023924_streamrole-kw-bl-wl.Designer.cs new file mode 100644 index 00000000..222865ec --- /dev/null +++ b/src/NadekoBot/Migrations/20170719023924_streamrole-kw-bl-wl.Designer.cs @@ -0,0 +1,1655 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170719023924_streamrole-kw-bl-wl")] + partial class streamrolekwblwl + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170719023924_streamrole-kw-bl-wl.cs b/src/NadekoBot/Migrations/20170719023924_streamrole-kw-bl-wl.cs new file mode 100644 index 00000000..7d18d3b3 --- /dev/null +++ b/src/NadekoBot/Migrations/20170719023924_streamrole-kw-bl-wl.cs @@ -0,0 +1,93 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class streamrolekwblwl : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Enabled", + table: "StreamRoleSettings", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Keyword", + table: "StreamRoleSettings", + nullable: true); + + migrationBuilder.CreateTable( + name: "StreamRoleBlacklistedUser", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(nullable: true), + StreamRoleSettingsId = table.Column(nullable: true), + UserId = table.Column(nullable: false), + Username = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_StreamRoleBlacklistedUser", x => x.Id); + table.ForeignKey( + name: "FK_StreamRoleBlacklistedUser_StreamRoleSettings_StreamRoleSettingsId", + column: x => x.StreamRoleSettingsId, + principalTable: "StreamRoleSettings", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "StreamRoleWhitelistedUser", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(nullable: true), + StreamRoleSettingsId = table.Column(nullable: true), + UserId = table.Column(nullable: false), + Username = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_StreamRoleWhitelistedUser", x => x.Id); + table.ForeignKey( + name: "FK_StreamRoleWhitelistedUser_StreamRoleSettings_StreamRoleSettingsId", + column: x => x.StreamRoleSettingsId, + principalTable: "StreamRoleSettings", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_StreamRoleBlacklistedUser_StreamRoleSettingsId", + table: "StreamRoleBlacklistedUser", + column: "StreamRoleSettingsId"); + + migrationBuilder.CreateIndex( + name: "IX_StreamRoleWhitelistedUser_StreamRoleSettingsId", + table: "StreamRoleWhitelistedUser", + column: "StreamRoleSettingsId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "StreamRoleBlacklistedUser"); + + migrationBuilder.DropTable( + name: "StreamRoleWhitelistedUser"); + + migrationBuilder.DropColumn( + name: "Enabled", + table: "StreamRoleSettings"); + + migrationBuilder.DropColumn( + name: "Keyword", + table: "StreamRoleSettings"); + } + } +} diff --git a/src/NadekoBot/Migrations/20170721004230_nsfw-blacklist.Designer.cs b/src/NadekoBot/Migrations/20170721004230_nsfw-blacklist.Designer.cs new file mode 100644 index 00000000..918bf926 --- /dev/null +++ b/src/NadekoBot/Migrations/20170721004230_nsfw-blacklist.Designer.cs @@ -0,0 +1,1680 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170721004230_nsfw-blacklist")] + partial class nsfwblacklist + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170721004230_nsfw-blacklist.cs b/src/NadekoBot/Migrations/20170721004230_nsfw-blacklist.cs new file mode 100644 index 00000000..f255fabc --- /dev/null +++ b/src/NadekoBot/Migrations/20170721004230_nsfw-blacklist.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class nsfwblacklist : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "NsfwBlacklitedTag", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(nullable: true), + GuildConfigId = table.Column(nullable: true), + Tag = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_NsfwBlacklitedTag", x => x.Id); + table.ForeignKey( + name: "FK_NsfwBlacklitedTag_GuildConfigs_GuildConfigId", + column: x => x.GuildConfigId, + principalTable: "GuildConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_NsfwBlacklitedTag_GuildConfigId", + table: "NsfwBlacklitedTag", + column: "GuildConfigId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "NsfwBlacklitedTag"); + } + } +} diff --git a/src/NadekoBot/Migrations/20170722074959_cr-ca.Designer.cs b/src/NadekoBot/Migrations/20170722074959_cr-ca.Designer.cs new file mode 100644 index 00000000..df888de6 --- /dev/null +++ b/src/NadekoBot/Migrations/20170722074959_cr-ca.Designer.cs @@ -0,0 +1,1682 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170722074959_cr-ca")] + partial class crca + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("ContainsAnywhere"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170722074959_cr-ca.cs b/src/NadekoBot/Migrations/20170722074959_cr-ca.cs new file mode 100644 index 00000000..2655c82d --- /dev/null +++ b/src/NadekoBot/Migrations/20170722074959_cr-ca.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class crca : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ContainsAnywhere", + table: "CustomReactions", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ContainsAnywhere", + table: "CustomReactions"); + } + } +} diff --git a/src/NadekoBot/Migrations/20170814044636_waifu-items.Designer.cs b/src/NadekoBot/Migrations/20170814044636_waifu-items.Designer.cs new file mode 100644 index 00000000..c76dcc33 --- /dev/null +++ b/src/NadekoBot/Migrations/20170814044636_waifu-items.Designer.cs @@ -0,0 +1,1711 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170814044636_waifu-items")] + partial class waifuitems + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("ContainsAnywhere"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Item"); + + b.Property("ItemEmoji"); + + b.Property("Price"); + + b.Property("WaifuInfoId"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170814044636_waifu-items.cs b/src/NadekoBot/Migrations/20170814044636_waifu-items.cs new file mode 100644 index 00000000..3054342f --- /dev/null +++ b/src/NadekoBot/Migrations/20170814044636_waifu-items.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class waifuitems : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "WaifuItem", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(nullable: true), + Item = table.Column(nullable: false), + ItemEmoji = table.Column(nullable: true), + Price = table.Column(nullable: false), + WaifuInfoId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_WaifuItem", x => x.Id); + table.ForeignKey( + name: "FK_WaifuItem_WaifuInfo_WaifuInfoId", + column: x => x.WaifuInfoId, + principalTable: "WaifuInfo", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_WaifuItem_WaifuInfoId", + table: "WaifuItem", + column: "WaifuInfoId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "WaifuItem"); + } + } +} diff --git a/src/NadekoBot/Migrations/20170815222316_mute-time-antispam.Designer.cs b/src/NadekoBot/Migrations/20170815222316_mute-time-antispam.Designer.cs new file mode 100644 index 00000000..fcab240e --- /dev/null +++ b/src/NadekoBot/Migrations/20170815222316_mute-time-antispam.Designer.cs @@ -0,0 +1,1713 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170815222316_mute-time-antispam")] + partial class mutetimeantispam + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.Property("MuteTime"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("ContainsAnywhere"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Item"); + + b.Property("ItemEmoji"); + + b.Property("Price"); + + b.Property("WaifuInfoId"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170815222316_mute-time-antispam.cs b/src/NadekoBot/Migrations/20170815222316_mute-time-antispam.cs new file mode 100644 index 00000000..63765c2a --- /dev/null +++ b/src/NadekoBot/Migrations/20170815222316_mute-time-antispam.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class mutetimeantispam : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MuteTime", + table: "AntiSpamSetting", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MuteTime", + table: "AntiSpamSetting"); + } + } +} diff --git a/src/NadekoBot/Migrations/20170908230730_xp-and-clubs.Designer.cs b/src/NadekoBot/Migrations/20170908230730_xp-and-clubs.Designer.cs new file mode 100644 index 00000000..6b324c08 --- /dev/null +++ b/src/NadekoBot/Migrations/20170908230730_xp-and-clubs.Designer.cs @@ -0,0 +1,1945 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170908230730_xp-and-clubs")] + partial class xpandclubs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.Property("MuteTime"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.Property("XpMinutesTimeout") + .ValueGeneratedOnAdd() + .HasDefaultValue(5); + + b.Property("XpPerMessage") + .ValueGeneratedOnAdd() + .HasDefaultValue(3); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubApplicants"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubBans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Discrim"); + + b.Property("ImageUrl"); + + b.Property("MinimumLevelReq"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20); + + b.Property("OwnerId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name", "Discrim"); + + b.HasIndex("OwnerId") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("ContainsAnywhere"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("ClubId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 9, 1, 7, 29, 857, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.HasIndex("ClubId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("ItemType"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("ExcludedItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AwardedXp"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 9, 1, 7, 29, 858, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GuildId") + .IsUnique(); + + b.ToTable("UserXpStats"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Item"); + + b.Property("ItemEmoji"); + + b.Property("Price"); + + b.Property("WaifuInfoId"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Level"); + + b.Property("RoleId"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasAlternateKey("Level"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("XpRoleReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("NotifyMessage"); + + b.Property("ServerExcluded"); + + b.Property("XpRoleRewardExclusive"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Applicants") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Bans") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Owner") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.ClubInfo", "OwnerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Users") + .HasForeignKey("ClubId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170908230730_xp-and-clubs.cs b/src/NadekoBot/Migrations/20170908230730_xp-and-clubs.cs new file mode 100644 index 00000000..aad21131 --- /dev/null +++ b/src/NadekoBot/Migrations/20170908230730_xp-and-clubs.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class xpandclubs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "XpMinutesTimeout", + table: "BotConfig", + nullable: false, + defaultValue: 5) + .Annotation("Sqlite:Autoincrement", true); + + migrationBuilder.AddColumn( + name: "XpPerMessage", + table: "BotConfig", + nullable: false, + defaultValue: 3) + .Annotation("Sqlite:Autoincrement", true); + + migrationBuilder.CreateTable( + name: "Clubs", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(nullable: true), + Discrim = table.Column(nullable: false), + ImageUrl = table.Column(nullable: true), + MinimumLevelReq = table.Column(nullable: false), + Name = table.Column(maxLength: 20, nullable: false), + OwnerId = table.Column(nullable: false), + Xp = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Clubs", x => x.Id); + table.UniqueConstraint("AK_Clubs_Name_Discrim", x => new { x.Name, x.Discrim }); + table.ForeignKey( + name: "FK_Clubs_DiscordUser_OwnerId", + column: x => x.OwnerId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.Sql(MigrationQueries.UserClub); + + migrationBuilder.AddColumn( + name: "LastLevelUp", + table: "DiscordUser", + nullable: false, + defaultValue: DateTime.UtcNow); + + migrationBuilder.AddColumn( + name: "NotifyOnLevelUp", + table: "DiscordUser", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateTable( + name: "UserXpStats", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AwardedXp = table.Column(nullable: false), + DateAdded = table.Column(nullable: true), + GuildId = table.Column(nullable: false), + LastLevelUp = table.Column(nullable: false, defaultValue: new DateTime(2017, 9, 9, 1, 7, 29, 858, DateTimeKind.Local)), + NotifyOnLevelUp = table.Column(nullable: false), + UserId = table.Column(nullable: false), + Xp = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserXpStats", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "XpSettings", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(nullable: true), + GuildConfigId = table.Column(nullable: false), + NotifyMessage = table.Column(nullable: true), + ServerExcluded = table.Column(nullable: false), + XpRoleRewardExclusive = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_XpSettings", x => x.Id); + table.ForeignKey( + name: "FK_XpSettings_GuildConfigs_GuildConfigId", + column: x => x.GuildConfigId, + principalTable: "GuildConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClubApplicants", + columns: table => new + { + ClubId = table.Column(nullable: false), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClubApplicants", x => new { x.ClubId, x.UserId }); + table.ForeignKey( + name: "FK_ClubApplicants_Clubs_ClubId", + column: x => x.ClubId, + principalTable: "Clubs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ClubApplicants_DiscordUser_UserId", + column: x => x.UserId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClubBans", + columns: table => new + { + ClubId = table.Column(nullable: false), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClubBans", x => new { x.ClubId, x.UserId }); + table.ForeignKey( + name: "FK_ClubBans_Clubs_ClubId", + column: x => x.ClubId, + principalTable: "Clubs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ClubBans_DiscordUser_UserId", + column: x => x.UserId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ExcludedItem", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(nullable: true), + ItemId = table.Column(nullable: false), + ItemType = table.Column(nullable: false), + XpSettingsId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ExcludedItem", x => x.Id); + table.ForeignKey( + name: "FK_ExcludedItem_XpSettings_XpSettingsId", + column: x => x.XpSettingsId, + principalTable: "XpSettings", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "XpRoleReward", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(nullable: true), + Level = table.Column(nullable: false), + RoleId = table.Column(nullable: false), + XpSettingsId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_XpRoleReward", x => x.Id); + table.UniqueConstraint("AK_XpRoleReward_Level", x => x.Level); + table.ForeignKey( + name: "FK_XpRoleReward_XpSettings_XpSettingsId", + column: x => x.XpSettingsId, + principalTable: "XpSettings", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_DiscordUser_ClubId", + table: "DiscordUser", + column: "ClubId"); + + migrationBuilder.CreateIndex( + name: "IX_ClubApplicants_UserId", + table: "ClubApplicants", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_ClubBans_UserId", + table: "ClubBans", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Clubs_OwnerId", + table: "Clubs", + column: "OwnerId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ExcludedItem_XpSettingsId", + table: "ExcludedItem", + column: "XpSettingsId"); + + migrationBuilder.CreateIndex( + name: "IX_UserXpStats_UserId_GuildId", + table: "UserXpStats", + columns: new[] { "UserId", "GuildId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_XpRoleReward_XpSettingsId", + table: "XpRoleReward", + column: "XpSettingsId"); + + migrationBuilder.CreateIndex( + name: "IX_XpSettings_GuildConfigId", + table: "XpSettings", + column: "GuildConfigId", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_DiscordUser_Clubs_ClubId", + table: "DiscordUser"); + + migrationBuilder.DropTable( + name: "ClubApplicants"); + + migrationBuilder.DropTable( + name: "ClubBans"); + + migrationBuilder.DropTable( + name: "ExcludedItem"); + + migrationBuilder.DropTable( + name: "UserXpStats"); + + migrationBuilder.DropTable( + name: "XpRoleReward"); + + migrationBuilder.DropTable( + name: "Clubs"); + + migrationBuilder.DropTable( + name: "XpSettings"); + + migrationBuilder.DropIndex( + name: "IX_DiscordUser_ClubId", + table: "DiscordUser"); + + migrationBuilder.DropColumn( + name: "ClubId", + table: "DiscordUser"); + + migrationBuilder.DropColumn( + name: "LastLevelUp", + table: "DiscordUser"); + + migrationBuilder.DropColumn( + name: "NotifyOnLevelUp", + table: "DiscordUser"); + + migrationBuilder.DropColumn( + name: "XpMinutesTimeout", + table: "BotConfig"); + + migrationBuilder.DropColumn( + name: "XpPerMessage", + table: "BotConfig"); + } + } +} diff --git a/src/NadekoBot/Migrations/20170911200031_lastXpGain.Designer.cs b/src/NadekoBot/Migrations/20170911200031_lastXpGain.Designer.cs new file mode 100644 index 00000000..e5c805df --- /dev/null +++ b/src/NadekoBot/Migrations/20170911200031_lastXpGain.Designer.cs @@ -0,0 +1,1947 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170911200031_lastXpGain")] + partial class lastXpGain + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.Property("MuteTime"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.Property("XpMinutesTimeout") + .ValueGeneratedOnAdd() + .HasDefaultValue(5); + + b.Property("XpPerMessage") + .ValueGeneratedOnAdd() + .HasDefaultValue(3); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubApplicants"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubBans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Discrim"); + + b.Property("ImageUrl"); + + b.Property("MinimumLevelReq"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20); + + b.Property("OwnerId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name", "Discrim"); + + b.HasIndex("OwnerId") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("ContainsAnywhere"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("ClubId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 11, 22, 0, 31, 236, DateTimeKind.Local)); + + b.Property("LastXpGain"); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.HasIndex("ClubId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("ItemType"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("ExcludedItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AwardedXp"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 11, 22, 0, 31, 238, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GuildId") + .IsUnique(); + + b.ToTable("UserXpStats"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Item"); + + b.Property("ItemEmoji"); + + b.Property("Price"); + + b.Property("WaifuInfoId"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Level"); + + b.Property("RoleId"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasAlternateKey("Level"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("XpRoleReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("NotifyMessage"); + + b.Property("ServerExcluded"); + + b.Property("XpRoleRewardExclusive"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Applicants") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Bans") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Owner") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.ClubInfo", "OwnerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Users") + .HasForeignKey("ClubId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170911200031_lastXpGain.cs b/src/NadekoBot/Migrations/20170911200031_lastXpGain.cs new file mode 100644 index 00000000..50bd6b9e --- /dev/null +++ b/src/NadekoBot/Migrations/20170911200031_lastXpGain.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class lastXpGain : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LastXpGain", + table: "DiscordUser", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.Sql("DELETE FROM XpRoleReward WHERE XpSettingsId is null"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LastXpGain", + table: "DiscordUser"); + } + } +} diff --git a/src/NadekoBot/Migrations/20170913022654_total-xp.Designer.cs b/src/NadekoBot/Migrations/20170913022654_total-xp.Designer.cs new file mode 100644 index 00000000..6764dd56 --- /dev/null +++ b/src/NadekoBot/Migrations/20170913022654_total-xp.Designer.cs @@ -0,0 +1,1949 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170913022654_total-xp")] + partial class totalxp + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.Property("MuteTime"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.Property("XpMinutesTimeout") + .ValueGeneratedOnAdd() + .HasDefaultValue(5); + + b.Property("XpPerMessage") + .ValueGeneratedOnAdd() + .HasDefaultValue(3); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubApplicants"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubBans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Discrim"); + + b.Property("ImageUrl"); + + b.Property("MinimumLevelReq"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20); + + b.Property("OwnerId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name", "Discrim"); + + b.HasIndex("OwnerId") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("ContainsAnywhere"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("ClubId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 13, 4, 26, 53, 906, DateTimeKind.Local)); + + b.Property("LastXpGain"); + + b.Property("NotifyOnLevelUp"); + + b.Property("TotalXp"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.HasIndex("ClubId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("ItemType"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("ExcludedItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AwardedXp"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 13, 4, 26, 53, 910, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GuildId") + .IsUnique(); + + b.ToTable("UserXpStats"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Item"); + + b.Property("ItemEmoji"); + + b.Property("Price"); + + b.Property("WaifuInfoId"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Level"); + + b.Property("RoleId"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasAlternateKey("Level"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("XpRoleReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("NotifyMessage"); + + b.Property("ServerExcluded"); + + b.Property("XpRoleRewardExclusive"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Applicants") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Bans") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Owner") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.ClubInfo", "OwnerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Users") + .HasForeignKey("ClubId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170913022654_total-xp.cs b/src/NadekoBot/Migrations/20170913022654_total-xp.cs new file mode 100644 index 00000000..f608e696 --- /dev/null +++ b/src/NadekoBot/Migrations/20170913022654_total-xp.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class totalxp : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TotalXp", + table: "DiscordUser", + nullable: false, + defaultValue: 0); + + migrationBuilder.Sql(MigrationQueries.TotalXp); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TotalXp", + table: "DiscordUser"); + } + } +} diff --git a/src/NadekoBot/Migrations/20170915034808_club-admins.Designer.cs b/src/NadekoBot/Migrations/20170915034808_club-admins.Designer.cs new file mode 100644 index 00000000..d7b4c4d9 --- /dev/null +++ b/src/NadekoBot/Migrations/20170915034808_club-admins.Designer.cs @@ -0,0 +1,1951 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170915034808_club-admins")] + partial class clubadmins + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.Property("MuteTime"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.Property("XpMinutesTimeout") + .ValueGeneratedOnAdd() + .HasDefaultValue(5); + + b.Property("XpPerMessage") + .ValueGeneratedOnAdd() + .HasDefaultValue(3); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubApplicants"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubBans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Discrim"); + + b.Property("ImageUrl"); + + b.Property("MinimumLevelReq"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20); + + b.Property("OwnerId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name", "Discrim"); + + b.HasIndex("OwnerId") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("ContainsAnywhere"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("ClubId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("IsClubAdmin"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 15, 5, 48, 8, 660, DateTimeKind.Local)); + + b.Property("LastXpGain"); + + b.Property("NotifyOnLevelUp"); + + b.Property("TotalXp"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.HasIndex("ClubId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("ItemType"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("ExcludedItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AwardedXp"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 15, 5, 48, 8, 665, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GuildId") + .IsUnique(); + + b.ToTable("UserXpStats"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Item"); + + b.Property("ItemEmoji"); + + b.Property("Price"); + + b.Property("WaifuInfoId"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Level"); + + b.Property("RoleId"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasAlternateKey("Level"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("XpRoleReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("NotifyMessage"); + + b.Property("ServerExcluded"); + + b.Property("XpRoleRewardExclusive"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Applicants") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Bans") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Owner") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.ClubInfo", "OwnerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Users") + .HasForeignKey("ClubId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170915034808_club-admins.cs b/src/NadekoBot/Migrations/20170915034808_club-admins.cs new file mode 100644 index 00000000..b1c0d3e6 --- /dev/null +++ b/src/NadekoBot/Migrations/20170915034808_club-admins.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class clubadmins : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsClubAdmin", + table: "DiscordUser", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsClubAdmin", + table: "DiscordUser"); + } + } +} diff --git a/src/NadekoBot/Migrations/20170921185313_feeds.Designer.cs b/src/NadekoBot/Migrations/20170921185313_feeds.Designer.cs new file mode 100644 index 00000000..86ee94cc --- /dev/null +++ b/src/NadekoBot/Migrations/20170921185313_feeds.Designer.cs @@ -0,0 +1,1985 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using System; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170921185313_feeds")] + partial class feeds + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.Property("MuteTime"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.Property("XpMinutesTimeout") + .ValueGeneratedOnAdd() + .HasDefaultValue(5); + + b.Property("XpPerMessage") + .ValueGeneratedOnAdd() + .HasDefaultValue(3); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubApplicants"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubBans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Discrim"); + + b.Property("ImageUrl"); + + b.Property("MinimumLevelReq"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20); + + b.Property("OwnerId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name", "Discrim"); + + b.HasIndex("OwnerId") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("ContainsAnywhere"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("ClubId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("IsClubAdmin"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 21, 20, 53, 13, 305, DateTimeKind.Local)); + + b.Property("LastXpGain"); + + b.Property("NotifyOnLevelUp"); + + b.Property("TotalXp"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.HasIndex("ClubId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("ItemType"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("ExcludedItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Url") + .IsRequired(); + + b.HasKey("Id"); + + b.HasAlternateKey("GuildConfigId", "Url"); + + b.ToTable("FeedSub"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AwardedXp"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 21, 20, 53, 13, 307, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GuildId") + .IsUnique(); + + b.ToTable("UserXpStats"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Item"); + + b.Property("ItemEmoji"); + + b.Property("Price"); + + b.Property("WaifuInfoId"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Level"); + + b.Property("RoleId"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasAlternateKey("Level"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("XpRoleReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("NotifyMessage"); + + b.Property("ServerExcluded"); + + b.Property("XpRoleRewardExclusive"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Applicants") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Bans") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Owner") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.ClubInfo", "OwnerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Users") + .HasForeignKey("ClubId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithMany("FeedSubs") + .HasForeignKey("GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/NadekoBot/Migrations/20170921185313_feeds.cs b/src/NadekoBot/Migrations/20170921185313_feeds.cs new file mode 100644 index 00000000..b4cbc41b --- /dev/null +++ b/src/NadekoBot/Migrations/20170921185313_feeds.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace NadekoBot.Migrations +{ + public partial class feeds : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "FeedSub", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ChannelId = table.Column(type: "INTEGER", nullable: false), + DateAdded = table.Column(type: "TEXT", nullable: true), + GuildConfigId = table.Column(type: "INTEGER", nullable: false), + Url = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FeedSub", x => x.Id); + table.UniqueConstraint("AK_FeedSub_GuildConfigId_Url", x => new { x.GuildConfigId, x.Url }); + table.ForeignKey( + name: "FK_FeedSub_GuildConfigs_GuildConfigId", + column: x => x.GuildConfigId, + principalTable: "GuildConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "FeedSub"); + + migrationBuilder.AlterColumn( + name: "LastLevelUp", + table: "UserXpStats", + nullable: false, + defaultValue: new DateTime(2017, 9, 15, 5, 48, 8, 665, DateTimeKind.Local), + oldClrType: typeof(DateTime), + oldType: "TEXT", + oldDefaultValue: new DateTime(2017, 9, 21, 20, 53, 13, 307, DateTimeKind.Local)); + + migrationBuilder.AlterColumn( + name: "LastLevelUp", + table: "DiscordUser", + nullable: false, + defaultValue: new DateTime(2017, 9, 15, 5, 48, 8, 660, DateTimeKind.Local), + oldClrType: typeof(DateTime), + oldType: "TEXT", + oldDefaultValue: new DateTime(2017, 9, 21, 20, 53, 13, 305, DateTimeKind.Local)); + } + } +} diff --git a/src/NadekoBot/Migrations/20170923002439_xprr-fix.Designer.cs b/src/NadekoBot/Migrations/20170923002439_xprr-fix.Designer.cs new file mode 100644 index 00000000..79e0e678 --- /dev/null +++ b/src/NadekoBot/Migrations/20170923002439_xprr-fix.Designer.cs @@ -0,0 +1,1985 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using System; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170923002439_xprr-fix")] + partial class xprrfix + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.Property("MuteTime"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.Property("XpMinutesTimeout") + .ValueGeneratedOnAdd() + .HasDefaultValue(5); + + b.Property("XpPerMessage") + .ValueGeneratedOnAdd() + .HasDefaultValue(3); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubApplicants"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubBans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Discrim"); + + b.Property("ImageUrl"); + + b.Property("MinimumLevelReq"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20); + + b.Property("OwnerId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name", "Discrim"); + + b.HasIndex("OwnerId") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("ContainsAnywhere"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("ClubId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("IsClubAdmin"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 21, 20, 53, 13, 305, DateTimeKind.Local)); + + b.Property("LastXpGain"); + + b.Property("NotifyOnLevelUp"); + + b.Property("TotalXp"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.HasIndex("ClubId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("ItemType"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("ExcludedItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Url") + .IsRequired(); + + b.HasKey("Id"); + + b.HasAlternateKey("GuildConfigId", "Url"); + + b.ToTable("FeedSub"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AwardedXp"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 21, 20, 53, 13, 307, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GuildId") + .IsUnique(); + + b.ToTable("UserXpStats"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Item"); + + b.Property("ItemEmoji"); + + b.Property("Price"); + + b.Property("WaifuInfoId"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Level"); + + b.Property("RoleId"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId", "Level") + .IsUnique(); + + b.ToTable("XpRoleReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("NotifyMessage"); + + b.Property("ServerExcluded"); + + b.Property("XpRoleRewardExclusive"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Applicants") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Bans") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Owner") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.ClubInfo", "OwnerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Users") + .HasForeignKey("ClubId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithMany("FeedSubs") + .HasForeignKey("GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings", "XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/NadekoBot/Migrations/20170923002439_xprr-fix.cs b/src/NadekoBot/Migrations/20170923002439_xprr-fix.cs new file mode 100644 index 00000000..e8160e83 --- /dev/null +++ b/src/NadekoBot/Migrations/20170923002439_xprr-fix.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace NadekoBot.Migrations +{ + public partial class xprrfix : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("XpRoleReward"); + + migrationBuilder.CreateTable( + name: "XpRoleReward", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(type: "TEXT", nullable: true), + Level = table.Column(type: "INTEGER", nullable: false), + RoleId = table.Column(type: "INTEGER", nullable: false), + XpSettingsId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_XpRoleReward", x => x.Id); + table.ForeignKey( + name: "FK_XpRoleReward_XpSettings_XpSettingsId", + column: x => x.XpSettingsId, + principalTable: "XpSettings", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_XpRoleReward_XpSettingsId_Level", + table: "XpRoleReward", + columns: new[] { "XpSettingsId", "Level" }, + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/NadekoBot/Migrations/MigrationQueries.cs b/src/NadekoBot/Migrations/MigrationQueries.cs new file mode 100644 index 00000000..b0d8413a --- /dev/null +++ b/src/NadekoBot/Migrations/MigrationQueries.cs @@ -0,0 +1,42 @@ +namespace NadekoBot.Migrations +{ + internal class MigrationQueries + { + public static string UserClub { get; } = @" +CREATE TABLE DiscordUser_tmp( + Id INTEGER PRIMARY KEY, + AvatarId TEXT, + Discriminator TEXT, + UserId INTEGER UNIQUE NOT NULL, + DateAdded TEXT, + Username TEXT +); + +INSERT INTO DiscordUser_tmp + SELECT Id, AvatarId, Discriminator, UserId, DateAdded, Username + FROM DiscordUser; + +DROP TABLE DiscordUser; + +CREATE TABLE DiscordUser( + Id INTEGER PRIMARY KEY, + AvatarId TEXT, + Discriminator TEXT, + UserId INTEGER UNIQUE NOT NULL, + DateAdded TEXT, + Username TEXT, + ClubId INTEGER, + CONSTRAINT FK_DiscordUser_Clubs_ClubId FOREIGN KEY(ClubId) REFERENCES Clubs(Id) ON DELETE RESTRICT +); + +INSERT INTO DiscordUser + SELECT Id, AvatarId, Discriminator, UserId, DateAdded, Username, NULL + FROM DiscordUser_tmp; + +DROP TABLE DiscordUser_tmp;"; + public static string TotalXp { get; } = +@"UPDATE DiscordUser +SET TotalXp = ifnull((SELECT SUM(Xp) FROM UserXpStats WHERE UserId = DiscordUser.UserId), 0)"; + + } +} \ No newline at end of file diff --git a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs index 44ce853f..dee868e0 100644 --- a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs @@ -1,10 +1,13 @@ -using System; +// using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; using NadekoBot.Services.Database; using NadekoBot.Services.Database.Models; +using System; namespace NadekoBot.Migrations { @@ -13,8 +16,9 @@ namespace NadekoBot.Migrations { protected override void BuildModel(ModelBuilder modelBuilder) { +#pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "1.1.1"); + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => { @@ -70,6 +74,8 @@ namespace NadekoBot.Migrations b.Property("MessageThreshold"); + b.Property("MuteTime"); + b.HasKey("Id"); b.HasIndex("GuildConfigId") @@ -181,6 +187,14 @@ namespace NadekoBot.Migrations b.Property("TriviaCurrencyReward"); + b.Property("XpMinutesTimeout") + .ValueGeneratedOnAdd() + .HasDefaultValue(5); + + b.Property("XpPerMessage") + .ValueGeneratedOnAdd() + .HasDefaultValue(3); + b.HasKey("Id"); b.ToTable("BotConfig"); @@ -236,6 +250,63 @@ namespace NadekoBot.Migrations b.ToTable("ClashOfClans"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubApplicants"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubBans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Discrim"); + + b.Property("ImageUrl"); + + b.Property("MinimumLevelReq"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20); + + b.Property("OwnerId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name", "Discrim"); + + b.HasIndex("OwnerId") + .IsUnique(); + + b.ToTable("Clubs"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => { b.Property("Id") @@ -361,6 +432,8 @@ namespace NadekoBot.Migrations b.Property("AutoDeleteTrigger"); + b.Property("ContainsAnywhere"); + b.Property("DateAdded"); b.Property("DmResponse"); @@ -387,10 +460,24 @@ namespace NadekoBot.Migrations b.Property("AvatarId"); + b.Property("ClubId"); + b.Property("DateAdded"); b.Property("Discriminator"); + b.Property("IsClubAdmin"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 21, 20, 53, 13, 305, DateTimeKind.Local)); + + b.Property("LastXpGain"); + + b.Property("NotifyOnLevelUp"); + + b.Property("TotalXp"); + b.Property("UserId"); b.Property("Username"); @@ -399,6 +486,8 @@ namespace NadekoBot.Migrations b.HasAlternateKey("UserId"); + b.HasIndex("ClubId"); + b.ToTable("DiscordUser"); }); @@ -441,6 +530,47 @@ namespace NadekoBot.Migrations b.ToTable("EightBallResponses"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("ItemType"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("ExcludedItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Url") + .IsRequired(); + + b.HasKey("Id"); + + b.HasAlternateKey("GuildConfigId", "Url"); + + b.ToTable("FeedSub"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => { b.Property("Id") @@ -800,6 +930,24 @@ namespace NadekoBot.Migrations b.ToTable("MutedUserId"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => { b.Property("Id") @@ -1126,6 +1274,71 @@ namespace NadekoBot.Migrations b.ToTable("StartupCommand"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => { b.Property("Id") @@ -1165,6 +1378,35 @@ namespace NadekoBot.Migrations b.ToTable("PokeGame"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AwardedXp"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 21, 20, 53, 13, 307, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GuildId") + .IsUnique(); + + b.ToTable("UserXpStats"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => { b.Property("Id") @@ -1212,6 +1454,28 @@ namespace NadekoBot.Migrations b.ToTable("WaifuInfo"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Item"); + + b.Property("ItemEmoji"); + + b.Property("Price"); + + b.Property("WaifuInfoId"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => { b.Property("Id") @@ -1284,6 +1548,50 @@ namespace NadekoBot.Migrations b.ToTable("WarningPunishment"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Level"); + + b.Property("RoleId"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId", "Level") + .IsUnique(); + + b.ToTable("XpRoleReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("NotifyMessage"); + + b.Property("ServerExcluded"); + + b.Property("XpRoleRewardExclusive"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("XpSettings"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => { b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") @@ -1333,6 +1641,40 @@ namespace NadekoBot.Migrations .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Applicants") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Bans") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Owner") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.ClubInfo", "OwnerId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => { b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") @@ -1354,6 +1696,13 @@ namespace NadekoBot.Migrations .HasForeignKey("BotConfigId"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Users") + .HasForeignKey("ClubId"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => { b.HasOne("NadekoBot.Services.Database.Models.BotConfig") @@ -1361,6 +1710,21 @@ namespace NadekoBot.Migrations .HasForeignKey("BotConfigId"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithMany("FeedSubs") + .HasForeignKey("GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => { b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") @@ -1439,6 +1803,13 @@ namespace NadekoBot.Migrations .HasForeignKey("GuildConfigId"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .HasForeignKey("GuildConfigId"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => { b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") @@ -1510,6 +1881,28 @@ namespace NadekoBot.Migrations .HasForeignKey("BotConfigId"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => { b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") @@ -1540,6 +1933,13 @@ namespace NadekoBot.Migrations .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => { b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") @@ -1562,6 +1962,23 @@ namespace NadekoBot.Migrations .WithMany("WarnPunishments") .HasForeignKey("GuildConfigId"); }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings", "XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 } } } diff --git a/src/NadekoBot/Modules/Administration/Administration.cs b/src/NadekoBot/Modules/Administration/Administration.cs index 15185ca3..5c943b27 100644 --- a/src/NadekoBot/Modules/Administration/Administration.cs +++ b/src/NadekoBot/Modules/Administration/Administration.cs @@ -5,23 +5,21 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; using NadekoBot.Services; -using NadekoBot.Attributes; +using NadekoBot.Modules.Administration.Services; using NadekoBot.Services.Database.Models; -using NadekoBot.Services.Administration; namespace NadekoBot.Modules.Administration { - public partial class Administration : NadekoTopLevelModule + public partial class Administration : NadekoTopLevelModule { private IGuild _nadekoSupportServer; private readonly DbService _db; - private readonly AdministrationService _admin; - public Administration(DbService db, AdministrationService admin) + public Administration(DbService db) { _db = db; - _admin = admin; } [NadekoCommand, Usage, Description, Aliases] @@ -40,12 +38,12 @@ namespace NadekoBot.Modules.Administration } if (enabled) { - _admin.DeleteMessagesOnCommand.Add(Context.Guild.Id); + _service.DeleteMessagesOnCommand.Add(Context.Guild.Id); await ReplyConfirmLocalized("delmsg_on").ConfigureAwait(false); } else { - _admin.DeleteMessagesOnCommand.TryRemove(Context.Guild.Id); + _service.DeleteMessagesOnCommand.TryRemove(Context.Guild.Id); await ReplyConfirmLocalized("delmsg_off").ConfigureAwait(false); } } @@ -58,7 +56,7 @@ namespace NadekoBot.Modules.Administration { var guser = (IGuildUser)Context.User; var maxRole = guser.GetRoles().Max(x => x.Position); - if ((Context.User.Id != Context.Guild.OwnerId) && (maxRole < role.Position || maxRole <= usr.GetRoles().Max(x => x.Position))) + if ((Context.User.Id != Context.Guild.OwnerId) && (maxRole <= role.Position || maxRole <= usr.GetRoles().Max(x => x.Position))) return; try { @@ -126,16 +124,15 @@ namespace NadekoBot.Modules.Administration { var guser = (IGuildUser)Context.User; - var userRoles = user.GetRoles(); - if (guser.Id != Context.Guild.OwnerId && - (user.Id == Context.Guild.OwnerId || guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position))) + var userRoles = user.GetRoles().Except(new[] { guser.Guild.EveryoneRole }); + if (user.Id == Context.Guild.OwnerId || (Context.User.Id != Context.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position))) return; try { await user.RemoveRolesAsync(userRoles).ConfigureAwait(false); await ReplyConfirmLocalized("rar", Format.Bold(user.ToString())).ConfigureAwait(false); } - catch + catch (Exception) { await ReplyErrorLocalized("rar_err").ConfigureAwait(false); } diff --git a/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs b/src/NadekoBot/Modules/Administration/AutoAssignRoleCommands.cs similarity index 83% rename from src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs rename to src/NadekoBot/Modules/Administration/AutoAssignRoleCommands.cs index b7930e66..54d2e244 100644 --- a/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs +++ b/src/NadekoBot/Modules/Administration/AutoAssignRoleCommands.cs @@ -1,26 +1,24 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; -using NadekoBot.Services.Administration; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Administration.Services; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class AutoAssignRoleCommands : NadekoSubmodule + public class AutoAssignRoleCommands : NadekoSubmodule { private readonly DbService _db; - private readonly AutoAssignRoleService _service; - public AutoAssignRoleCommands(AutoAssignRoleService service, DbService db) + public AutoAssignRoleCommands(DbService db) { _db = db; - _service = service; } [NadekoCommand, Usage, Description, Aliases] @@ -39,7 +37,7 @@ namespace NadekoBot.Modules.Administration if (role == null) { conf.AutoAssignRoleId = 0; - _service.AutoAssignedRoles.TryRemove(Context.Guild.Id, out ulong throwaway); + _service.AutoAssignedRoles.TryRemove(Context.Guild.Id, out _); } else { diff --git a/src/NadekoBot/Modules/Administration/Commands/Migration/0_9..cs b/src/NadekoBot/Modules/Administration/Common/Migration/0_9..cs similarity index 99% rename from src/NadekoBot/Modules/Administration/Commands/Migration/0_9..cs rename to src/NadekoBot/Modules/Administration/Common/Migration/0_9..cs index b7de1523..b7e2bc0a 100644 --- a/src/NadekoBot/Modules/Administration/Commands/Migration/0_9..cs +++ b/src/NadekoBot/Modules/Administration/Common/Migration/0_9..cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace NadekoBot.Modules.Administration.Commands.Migration +namespace NadekoBot.Modules.Administration.Common.Migration { public class CommandPrefixes0_9 { diff --git a/src/NadekoBot/Modules/Administration/Commands/Migration/MigrationException.cs b/src/NadekoBot/Modules/Administration/Common/Migration/MigrationException.cs similarity index 57% rename from src/NadekoBot/Modules/Administration/Commands/Migration/MigrationException.cs rename to src/NadekoBot/Modules/Administration/Common/Migration/MigrationException.cs index 307ed63d..639ff5bc 100644 --- a/src/NadekoBot/Modules/Administration/Commands/Migration/MigrationException.cs +++ b/src/NadekoBot/Modules/Administration/Common/Migration/MigrationException.cs @@ -1,6 +1,6 @@ using System; -namespace NadekoBot.Modules.Administration.Commands.Migration +namespace NadekoBot.Modules.Administration.Common.Migration { public class MigrationException : Exception { diff --git a/src/NadekoBot/Services/Administration/ProtectionStats.cs b/src/NadekoBot/Modules/Administration/Common/ProtectionStats.cs similarity index 81% rename from src/NadekoBot/Services/Administration/ProtectionStats.cs rename to src/NadekoBot/Modules/Administration/Common/ProtectionStats.cs index 01d9f5f5..6c1a5dea 100644 --- a/src/NadekoBot/Services/Administration/ProtectionStats.cs +++ b/src/NadekoBot/Modules/Administration/Common/ProtectionStats.cs @@ -1,8 +1,9 @@ -using Discord; +using System.Collections.Concurrent; +using Discord; +using NadekoBot.Common.Collections; using NadekoBot.Services.Database.Models; -using System.Collections.Concurrent; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Common { public enum ProtectionType { diff --git a/src/NadekoBot/Services/Administration/Ratelimiter.cs b/src/NadekoBot/Modules/Administration/Common/Ratelimiter.cs similarity index 92% rename from src/NadekoBot/Services/Administration/Ratelimiter.cs rename to src/NadekoBot/Modules/Administration/Common/Ratelimiter.cs index 1fd17a6a..12d115ae 100644 --- a/src/NadekoBot/Services/Administration/Ratelimiter.cs +++ b/src/NadekoBot/Modules/Administration/Common/Ratelimiter.cs @@ -1,12 +1,13 @@ -using Discord.WebSocket; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Discord.WebSocket; +using NadekoBot.Modules.Administration.Services; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Common { public class Ratelimiter { diff --git a/src/NadekoBot/Services/Administration/UserSpamStats.cs b/src/NadekoBot/Modules/Administration/Common/UserSpamStats.cs similarity index 94% rename from src/NadekoBot/Services/Administration/UserSpamStats.cs rename to src/NadekoBot/Modules/Administration/Common/UserSpamStats.cs index ad2efcbe..28f338ea 100644 --- a/src/NadekoBot/Services/Administration/UserSpamStats.cs +++ b/src/NadekoBot/Modules/Administration/Common/UserSpamStats.cs @@ -1,10 +1,10 @@ -using Discord; -using System; +using System; using System.Collections.Concurrent; using System.Linq; using System.Threading; +using Discord; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Common { public class UserSpamStats : IDisposable { diff --git a/src/NadekoBot/Modules/Administration/Commands/GameChannelCommands.cs b/src/NadekoBot/Modules/Administration/GameChannelCommands.cs similarity index 86% rename from src/NadekoBot/Modules/Administration/Commands/GameChannelCommands.cs rename to src/NadekoBot/Modules/Administration/GameChannelCommands.cs index 46a250e1..647874a6 100644 --- a/src/NadekoBot/Modules/Administration/Commands/GameChannelCommands.cs +++ b/src/NadekoBot/Modules/Administration/GameChannelCommands.cs @@ -1,24 +1,22 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Services; using System.Threading.Tasks; -using NadekoBot.Services.Administration; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Administration.Services; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class GameChannelCommands : NadekoSubmodule + public class GameChannelCommands : NadekoSubmodule { private readonly DbService _db; - private readonly GameVoiceChannelService _service; - public GameChannelCommands(GameVoiceChannelService service, DbService db) + public GameChannelCommands(DbService db) { _db = db; - _service = service; } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Modules/Administration/Commands/LocalizationCommands.cs b/src/NadekoBot/Modules/Administration/LocalizationCommands.cs similarity index 99% rename from src/NadekoBot/Modules/Administration/Commands/LocalizationCommands.cs rename to src/NadekoBot/Modules/Administration/LocalizationCommands.cs index eef4b14c..bdef0799 100644 --- a/src/NadekoBot/Modules/Administration/Commands/LocalizationCommands.cs +++ b/src/NadekoBot/Modules/Administration/LocalizationCommands.cs @@ -1,6 +1,5 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using System; using System.Collections.Generic; @@ -8,6 +7,7 @@ using System.Collections.Immutable; using System.Globalization; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; namespace NadekoBot.Modules.Administration { diff --git a/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/src/NadekoBot/Modules/Administration/LogCommands.cs similarity index 89% rename from src/NadekoBot/Modules/Administration/Commands/LogCommand.cs rename to src/NadekoBot/Modules/Administration/LogCommands.cs index 9823d735..daffd9f7 100644 --- a/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/src/NadekoBot/Modules/Administration/LogCommands.cs @@ -1,16 +1,16 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; -using NadekoBot.DataStructures; using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions; using NadekoBot.Services; -using NadekoBot.Services.Administration; using NadekoBot.Services.Database.Models; using System; using System.Linq; using System.Threading.Tasks; -using static NadekoBot.Services.Administration.LogCommandService; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.TypeReaders.Models; +using NadekoBot.Modules.Administration.Services; +using static NadekoBot.Modules.Administration.Services.LogCommandService; namespace NadekoBot.Modules.Administration { @@ -18,14 +18,12 @@ namespace NadekoBot.Modules.Administration { [Group] [NoPublicBot] - public class LogCommands : NadekoSubmodule + public class LogCommands : NadekoSubmodule { - private readonly LogCommandService _lc; private readonly DbService _db; - public LogCommands(LogCommandService lc, DbService db) + public LogCommands(DbService db) { - _lc = lc; _db = db; } @@ -46,7 +44,7 @@ namespace NadekoBot.Modules.Administration using (var uow = _db.UnitOfWork) { logSetting = uow.GuildConfigs.LogSettingsFor(channel.Guild.Id).LogSetting; - _lc.GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting); + _service.GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting); logSetting.LogOtherId = logSetting.MessageUpdatedId = logSetting.MessageDeletedId = @@ -82,7 +80,7 @@ namespace NadekoBot.Modules.Administration using (var uow = _db.UnitOfWork) { var config = uow.GuildConfigs.LogSettingsFor(channel.Guild.Id); - LogSetting logSetting = _lc.GuildLogSettings.GetOrAdd(channel.Guild.Id, (id) => config.LogSetting); + LogSetting logSetting = _service.GuildLogSettings.GetOrAdd(channel.Guild.Id, (id) => config.LogSetting); removed = logSetting.IgnoredChannels.RemoveWhere(ilc => ilc.ChannelId == channel.Id); config.LogSetting.IgnoredChannels.RemoveWhere(ilc => ilc.ChannelId == channel.Id); if (removed == 0) @@ -106,8 +104,8 @@ namespace NadekoBot.Modules.Administration [OwnerOnly] public async Task LogEvents() { - await Context.Channel.SendConfirmAsync(GetText("log_events") + "\n" + - string.Join(", ", Enum.GetNames(typeof(LogType)).Cast())) + await Context.Channel.SendConfirmAsync(Format.Bold(GetText("log_events")) + "\n" + + $"```fix\n{string.Join(", ", Enum.GetNames(typeof(LogType)).Cast())}```") .ConfigureAwait(false); } @@ -122,7 +120,7 @@ namespace NadekoBot.Modules.Administration using (var uow = _db.UnitOfWork) { var logSetting = uow.GuildConfigs.LogSettingsFor(channel.Guild.Id).LogSetting; - _lc.GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting); + _service.GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting); switch (type) { case LogType.Other: diff --git a/src/NadekoBot/Modules/Administration/Commands/Migration.cs b/src/NadekoBot/Modules/Administration/MigrationCommands.cs similarity index 87% rename from src/NadekoBot/Modules/Administration/Commands/Migration.cs rename to src/NadekoBot/Modules/Administration/MigrationCommands.cs index e1497999..5c9b4a70 100644 --- a/src/NadekoBot/Modules/Administration/Commands/Migration.cs +++ b/src/NadekoBot/Modules/Administration/MigrationCommands.cs @@ -4,27 +4,28 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Services; using NadekoBot.Services.Database.Models; using Newtonsoft.Json; -using NadekoBot.Modules.Administration.Commands.Migration; using System.Collections.Concurrent; using NadekoBot.Extensions; using NadekoBot.Services.Database; using Microsoft.Data.Sqlite; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.Collections; +using NadekoBot.Modules.Administration.Common.Migration; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class Migration : NadekoSubmodule + public class MigrationCommands : NadekoSubmodule { private const int CURRENT_VERSION = 1; private readonly DbService _db; - public Migration(DbService db) + public MigrationCommands(DbService db) { _db = db; } @@ -211,10 +212,10 @@ namespace NadekoBot.Modules.Administration type = FollowedStream.FollowedStreamType.Twitch; break; case StreamNotificationConfig0_9.StreamType.Beam: - type = FollowedStream.FollowedStreamType.Beam; + type = FollowedStream.FollowedStreamType.Mixer; break; case StreamNotificationConfig0_9.StreamType.Hitbox: - type = FollowedStream.FollowedStreamType.Hitbox; + type = FollowedStream.FollowedStreamType.Smashcast; break; default: break; @@ -351,54 +352,6 @@ namespace NadekoBot.Modules.Administration oldConfig.RotatingStatuses.ForEach(i => messages.Add(new PlayingStatus { Status = i })); botConfig.RotatingStatusMessages = messages; - //Prefix - botConfig.ModulePrefixes.Clear(); - botConfig.ModulePrefixes.AddRange(new HashSet - { - new ModulePrefix() - { - ModuleName = "Administration", - Prefix = oldConfig.CommandPrefixes.Administration - }, - new ModulePrefix() - { - ModuleName = "Searches", - Prefix = oldConfig.CommandPrefixes.Searches - }, - new ModulePrefix() {ModuleName = "NSFW", Prefix = oldConfig.CommandPrefixes.NSFW}, - new ModulePrefix() - { - ModuleName = "Conversations", - Prefix = oldConfig.CommandPrefixes.Conversations - }, - new ModulePrefix() - { - ModuleName = "ClashOfClans", - Prefix = oldConfig.CommandPrefixes.ClashOfClans - }, - new ModulePrefix() {ModuleName = "Help", Prefix = oldConfig.CommandPrefixes.Help}, - new ModulePrefix() {ModuleName = "Music", Prefix = oldConfig.CommandPrefixes.Music}, - new ModulePrefix() {ModuleName = "Trello", Prefix = oldConfig.CommandPrefixes.Trello}, - new ModulePrefix() {ModuleName = "Games", Prefix = oldConfig.CommandPrefixes.Games}, - new ModulePrefix() - { - ModuleName = "Gambling", - Prefix = oldConfig.CommandPrefixes.Gambling - }, - new ModulePrefix() - { - ModuleName = "Permissions", - Prefix = oldConfig.CommandPrefixes.Permissions - }, - new ModulePrefix() - { - ModuleName = "Programming", - Prefix = oldConfig.CommandPrefixes.Programming - }, - new ModulePrefix() {ModuleName = "Pokemon", Prefix = oldConfig.CommandPrefixes.Pokemon}, - new ModulePrefix() {ModuleName = "Utility", Prefix = oldConfig.CommandPrefixes.Utility} - }); - //Blacklist var blacklist = new HashSet(oldConfig.ServerBlacklist.Select(server => new BlacklistItem() { ItemId = server, Type = BlacklistType.Server })); blacklist.AddRange(oldConfig.ChannelBlacklist.Select(channel => new BlacklistItem() { ItemId = channel, Type = BlacklistType.Channel })); diff --git a/src/NadekoBot/Modules/Administration/Commands/MuteCommands.cs b/src/NadekoBot/Modules/Administration/MuteCommands.cs similarity index 95% rename from src/NadekoBot/Modules/Administration/Commands/MuteCommands.cs rename to src/NadekoBot/Modules/Administration/MuteCommands.cs index 85f075fa..a8322de4 100644 --- a/src/NadekoBot/Modules/Administration/Commands/MuteCommands.cs +++ b/src/NadekoBot/Modules/Administration/MuteCommands.cs @@ -1,31 +1,29 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Services; -using NadekoBot.Services.Administration; using System; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Administration.Services; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class MuteCommands : NadekoSubmodule + public class MuteCommands : NadekoSubmodule { - private readonly MuteService _service; private readonly DbService _db; - public MuteCommands(MuteService service, DbService db) + public MuteCommands(DbService db) { - _service = service; _db = db; } [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.ManageRoles)] - [Priority(1)] + [Priority(0)] public async Task SetMuteRole([Remainder] string name) { name = name.Trim(); @@ -45,7 +43,7 @@ namespace NadekoBot.Modules.Administration [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.ManageRoles)] - [Priority(0)] + [Priority(1)] public Task SetMuteRole([Remainder] IRole role) => SetMuteRole(role.Name); @@ -53,7 +51,7 @@ namespace NadekoBot.Modules.Administration [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.MuteMembers)] - [Priority(1)] + [Priority(0)] public async Task Mute(IGuildUser user) { try @@ -71,7 +69,7 @@ namespace NadekoBot.Modules.Administration [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.MuteMembers)] - [Priority(0)] + [Priority(1)] public async Task Mute(int minutes, IGuildUser user) { if (minutes < 1 || minutes > 1440) diff --git a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs b/src/NadekoBot/Modules/Administration/PlayingRotateCommands.cs similarity index 72% rename from src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs rename to src/NadekoBot/Modules/Administration/PlayingRotateCommands.cs index 47c39849..4cccf226 100644 --- a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs +++ b/src/NadekoBot/Modules/Administration/PlayingRotateCommands.cs @@ -1,43 +1,39 @@ using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Services; -using NadekoBot.Services.Administration; using NadekoBot.Services.Database.Models; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Administration.Services; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class PlayingRotateCommands : NadekoSubmodule + public class PlayingRotateCommands : NadekoSubmodule { private static readonly object _locker = new object(); private readonly DbService _db; - private readonly PlayingRotateService _service; - public PlayingRotateCommands(PlayingRotateService service, DbService db) + public PlayingRotateCommands(DbService db) { _db = db; - _service = service; } [NadekoCommand, Usage, Description, Aliases] [OwnerOnly] public async Task RotatePlaying() { - lock (_locker) + bool enabled; + using (var uow = _db.UnitOfWork) { - using (var uow = _db.UnitOfWork) - { - var config = uow.BotConfig.GetOrCreate(); + var config = uow.BotConfig.GetOrCreate(); - _service.RotatingStatuses = config.RotatingStatuses = !config.RotatingStatuses; - uow.Complete(); - } + enabled = config.RotatingStatuses = !config.RotatingStatuses; + uow.Complete(); } - if (_service.RotatingStatuses) + if (enabled) await ReplyConfirmLocalized("ropl_enabled").ConfigureAwait(false); else await ReplyConfirmLocalized("ropl_disabled").ConfigureAwait(false); @@ -52,7 +48,6 @@ namespace NadekoBot.Modules.Administration var config = uow.BotConfig.GetOrCreate(); var toAdd = new PlayingStatus { Status = status }; config.RotatingStatusMessages.Add(toAdd); - _service.RotatingStatusMessages.Add(toAdd); await uow.CompleteAsync(); } @@ -63,13 +58,13 @@ namespace NadekoBot.Modules.Administration [OwnerOnly] public async Task ListPlaying() { - if (!_service.RotatingStatusMessages.Any()) + if (!_service.BotConfig.RotatingStatusMessages.Any()) await ReplyErrorLocalized("ropl_not_set").ConfigureAwait(false); else { var i = 1; await ReplyConfirmLocalized("ropl_list", - string.Join("\n\t", _service.RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}"))) + string.Join("\n\t", _service.BotConfig.RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}"))) .ConfigureAwait(false); } @@ -90,7 +85,6 @@ namespace NadekoBot.Modules.Administration return; msg = config.RotatingStatusMessages[index].Status; config.RotatingStatusMessages.RemoveAt(index); - _service.RotatingStatusMessages.RemoveAt(index); await uow.CompleteAsync(); } await ReplyConfirmLocalized("reprm", msg).ConfigureAwait(false); diff --git a/src/NadekoBot/Modules/Administration/Commands/PrefixCommands.cs b/src/NadekoBot/Modules/Administration/PrefixCommands.cs similarity index 98% rename from src/NadekoBot/Modules/Administration/Commands/PrefixCommands.cs rename to src/NadekoBot/Modules/Administration/PrefixCommands.cs index 8f8d4cec..617ebc30 100644 --- a/src/NadekoBot/Modules/Administration/Commands/PrefixCommands.cs +++ b/src/NadekoBot/Modules/Administration/PrefixCommands.cs @@ -1,7 +1,7 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; namespace NadekoBot.Modules.Administration { @@ -11,7 +11,7 @@ namespace NadekoBot.Modules.Administration public class PrefixCommands : NadekoSubmodule { [NadekoCommand, Usage, Description, Aliases] - [Priority(0)] + [Priority(1)] public new async Task Prefix() { await ReplyConfirmLocalized("prefix_current", Format.Code(_cmdHandler.GetPrefix(Context.Guild))).ConfigureAwait(false); @@ -21,7 +21,7 @@ namespace NadekoBot.Modules.Administration [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.Administrator)] - [Priority(1)] + [Priority(0)] public new async Task Prefix([Remainder]string prefix) { if (string.IsNullOrWhiteSpace(prefix)) diff --git a/src/NadekoBot/Modules/Administration/Commands/ProtectionCommands.cs b/src/NadekoBot/Modules/Administration/ProtectionCommands.cs similarity index 83% rename from src/NadekoBot/Modules/Administration/Commands/ProtectionCommands.cs rename to src/NadekoBot/Modules/Administration/ProtectionCommands.cs index 73bd2de0..d9a2cd54 100644 --- a/src/NadekoBot/Modules/Administration/Commands/ProtectionCommands.cs +++ b/src/NadekoBot/Modules/Administration/ProtectionCommands.cs @@ -1,42 +1,50 @@ using Discord; using Discord.Commands; using Microsoft.EntityFrameworkCore; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using NadekoBot.Services.Database.Models; using System; using System.Linq; using System.Threading.Tasks; -using NadekoBot.Services.Administration; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Administration.Common; +using NadekoBot.Modules.Administration.Services; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class ProtectionCommands : NadekoSubmodule + public class ProtectionCommands : NadekoSubmodule { - private readonly ProtectionService _service; private readonly MuteService _mute; private readonly DbService _db; - public ProtectionCommands(ProtectionService service, MuteService mute, DbService db) + public ProtectionCommands(MuteService mute, DbService db) { - _service = service; _mute = mute; _db = db; } private string GetAntiSpamString(AntiSpamStats stats) { - var ignoredString = string.Join(", ", stats.AntiSpamSettings.IgnoredChannels.Select(c => $"<#{c.ChannelId}>")); + var settings = stats.AntiSpamSettings; + var ignoredString = string.Join(", ", settings.IgnoredChannels.Select(c => $"<#{c.ChannelId}>")); if (string.IsNullOrWhiteSpace(ignoredString)) ignoredString = "none"; + + string add = ""; + if (settings.Action == PunishmentAction.Mute + && settings.MuteTime > 0) + { + add = " (" + settings.MuteTime + "s)"; + } + return GetText("spam_stats", - Format.Bold(stats.AntiSpamSettings.MessageThreshold.ToString()), - Format.Bold(stats.AntiSpamSettings.Action.ToString()), + Format.Bold(settings.MessageThreshold.ToString()), + Format.Bold(settings.Action.ToString() + add), ignoredString); } @@ -62,8 +70,7 @@ namespace NadekoBot.Modules.Administration return; } - AntiRaidStats throwaway; - if (_service.AntiRaidGuilds.TryRemove(Context.Guild.Id, out throwaway)) + if (_service.AntiRaidGuilds.TryRemove(Context.Guild.Id, out _)) { using (var uow = _db.UnitOfWork) { @@ -114,15 +121,12 @@ namespace NadekoBot.Modules.Administration [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.Administrator)] - public async Task AntiSpam(int messageCount = 3, PunishmentAction action = PunishmentAction.Mute) + [Priority(1)] + public async Task AntiSpam() { - if (messageCount < 2 || messageCount > 10) - return; - - AntiSpamStats throwaway; - if (_service.AntiSpamGuilds.TryRemove(Context.Guild.Id, out throwaway)) + if (_service.AntiSpamGuilds.TryRemove(Context.Guild.Id, out var removed)) { - throwaway.UserStats.ForEach(x => x.Value.Dispose()); + removed.UserStats.ForEach(x => x.Value.Dispose()); using (var uow = _db.UnitOfWork) { var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiSpamSetting) @@ -135,6 +139,21 @@ namespace NadekoBot.Modules.Administration return; } + await AntiSpam(3).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + [Priority(0)] + public async Task AntiSpam(int messageCount, PunishmentAction action = PunishmentAction.Mute, int time = 0) + { + if (messageCount < 2 || messageCount > 10) + return; + + if (time < 0 || time > 60 * 12) + return; + try { await _mute.GetMuteRole(Context.Guild).ConfigureAwait(false); @@ -152,10 +171,15 @@ namespace NadekoBot.Modules.Administration { Action = action, MessageThreshold = messageCount, + MuteTime = time, } }; - _service.AntiSpamGuilds.AddOrUpdate(Context.Guild.Id, stats, (key, old) => stats); + stats = _service.AntiSpamGuilds.AddOrUpdate(Context.Guild.Id, stats, (key, old) => + { + stats.AntiSpamSettings.IgnoredChannels = old.AntiSpamSettings.IgnoredChannels; + return stats; + }); using (var uow = _db.UnitOfWork) { diff --git a/src/NadekoBot/Modules/Administration/Commands/PruneCommands.cs b/src/NadekoBot/Modules/Administration/PruneCommands.cs similarity index 70% rename from src/NadekoBot/Modules/Administration/Commands/PruneCommands.cs rename to src/NadekoBot/Modules/Administration/PruneCommands.cs index a4086f82..5fa42f28 100644 --- a/src/NadekoBot/Modules/Administration/Commands/PruneCommands.cs +++ b/src/NadekoBot/Modules/Administration/PruneCommands.cs @@ -1,28 +1,19 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; -using NadekoBot.Services.Administration; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Administration.Services; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class PruneCommands : ModuleBase + public class PruneCommands : NadekoSubmodule { private readonly TimeSpan twoWeeks = TimeSpan.FromDays(14); - private readonly PruneService _prune; - - public PruneCommands(PruneService prune) - { - _prune = prune; - } //delets her own messages, no perm required [NadekoCommand, Usage, Description, Aliases] @@ -31,7 +22,7 @@ namespace NadekoBot.Modules.Administration { var user = await Context.Guild.GetCurrentUserAsync().ConfigureAwait(false); - await _prune.PruneWhere((ITextChannel)Context.Channel, 100, (x) => x.Author.Id == user.Id).ConfigureAwait(false); + await _service.PruneWhere((ITextChannel)Context.Channel, 100, (x) => x.Author.Id == user.Id).ConfigureAwait(false); Context.Message.DeleteAfter(3); } // prune x @@ -39,7 +30,7 @@ namespace NadekoBot.Modules.Administration [RequireContext(ContextType.Guild)] [RequireUserPermission(ChannelPermission.ManageMessages)] [RequireBotPermission(GuildPermission.ManageMessages)] - [Priority(0)] + [Priority(1)] public async Task Prune(int count) { count++; @@ -47,7 +38,7 @@ namespace NadekoBot.Modules.Administration return; if (count > 1000) count = 1000; - await _prune.PruneWhere((ITextChannel)Context.Channel, count, x => true).ConfigureAwait(false); + await _service.PruneWhere((ITextChannel)Context.Channel, count, x => true).ConfigureAwait(false); } //prune @user [x] @@ -55,7 +46,7 @@ namespace NadekoBot.Modules.Administration [RequireContext(ContextType.Guild)] [RequireUserPermission(ChannelPermission.ManageMessages)] [RequireBotPermission(GuildPermission.ManageMessages)] - [Priority(1)] + [Priority(0)] public async Task Prune(IGuildUser user, int count = 100) { if (user.Id == Context.User.Id) @@ -66,7 +57,7 @@ namespace NadekoBot.Modules.Administration if (count > 1000) count = 1000; - await _prune.PruneWhere((ITextChannel)Context.Channel, count, m => m.Author.Id == user.Id && DateTime.UtcNow - m.CreatedAt < twoWeeks); + await _service.PruneWhere((ITextChannel)Context.Channel, count, m => m.Author.Id == user.Id && DateTime.UtcNow - m.CreatedAt < twoWeeks); } } } diff --git a/src/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs b/src/NadekoBot/Modules/Administration/RatelimitCommands.cs similarity index 92% rename from src/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs rename to src/NadekoBot/Modules/Administration/RatelimitCommands.cs index 878b4a77..40b37884 100644 --- a/src/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs +++ b/src/NadekoBot/Modules/Administration/RatelimitCommands.cs @@ -1,28 +1,27 @@ using Discord; using Discord.Commands; using Microsoft.EntityFrameworkCore; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; -using NadekoBot.Services.Administration; using NadekoBot.Services.Database.Models; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Administration.Common; +using NadekoBot.Modules.Administration.Services; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class SlowModeCommands : NadekoSubmodule + public class SlowModeCommands : NadekoSubmodule { - private readonly SlowmodeService _service; private readonly DbService _db; - public SlowModeCommands(SlowmodeService service, DbService db) + public SlowModeCommands(DbService db) { - _service = service; _db = db; } @@ -31,9 +30,9 @@ namespace NadekoBot.Modules.Administration [RequireUserPermission(GuildPermission.ManageMessages)] public async Task Slowmode() { - if (_service.RatelimitingChannels.TryRemove(Context.Channel.Id, out Ratelimiter throwaway)) + if (_service.RatelimitingChannels.TryRemove(Context.Channel.Id, out Ratelimiter removed)) { - throwaway.CancelSource.Cancel(); + removed.CancelSource.Cancel(); await ReplyConfirmLocalized("slowmode_disabled").ConfigureAwait(false); } } @@ -67,7 +66,7 @@ namespace NadekoBot.Modules.Administration [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.ManageMessages)] - [Priority(1)] + [Priority(0)] public async Task SlowmodeWhitelist(IGuildUser user) { var siu = new SlowmodeIgnoredUser @@ -99,7 +98,7 @@ namespace NadekoBot.Modules.Administration [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.ManageMessages)] - [Priority(0)] + [Priority(1)] public async Task SlowmodeWhitelist(IRole role) { var sir = new SlowmodeIgnoredRole diff --git a/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs b/src/NadekoBot/Modules/Administration/SelfAssignedRolesCommands.cs similarity index 93% rename from src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs rename to src/NadekoBot/Modules/Administration/SelfAssignedRolesCommands.cs index 9815fa1b..79e76a1d 100644 --- a/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs +++ b/src/NadekoBot/Modules/Administration/SelfAssignedRolesCommands.cs @@ -1,16 +1,16 @@ using Discord; using Discord.Commands; using Discord.WebSocket; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using NadekoBot.Services.Database.Models; using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.Collections; namespace NadekoBot.Modules.Administration { @@ -140,7 +140,7 @@ namespace NadekoBot.Modules.Administration await uow.CompleteAsync(); } - await Context.Channel.SendPaginatedConfirmAsync((DiscordShardedClient)Context.Client, page, (curPage) => + await Context.Channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client, page, (curPage) => { return new EmbedBuilder() .WithTitle(GetText("self_assign_list", roleCnt)) @@ -195,18 +195,23 @@ namespace NadekoBot.Modules.Administration var roleIds = roles.Select(x => x.RoleId).ToArray(); if (conf.ExclusiveSelfAssignedRoles) { - var sameRoleId = guildUser.RoleIds.FirstOrDefault(r => roleIds.Contains(r)); + var sameRoles = guildUser.RoleIds.Where(r => roleIds.Contains(r)); - if (sameRoleId != default(ulong)) + foreach (var roleId in sameRoles) { - var sameRole = Context.Guild.GetRole(sameRoleId); + var sameRole = Context.Guild.GetRole(roleId); if (sameRole != null) { - await guildUser.RemoveRoleAsync(sameRole).ConfigureAwait(false); - await Task.Delay(500).ConfigureAwait(false); + try + { + await guildUser.RemoveRoleAsync(sameRole).ConfigureAwait(false); + await Task.Delay(300).ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Warn(ex); + } } - //await ReplyErrorLocalized("self_assign_already_excl", Format.Bold(sameRole?.Name)).ConfigureAwait(false); - //return; } } try diff --git a/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs b/src/NadekoBot/Modules/Administration/SelfCommands.cs similarity index 85% rename from src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs rename to src/NadekoBot/Modules/Administration/SelfCommands.cs index aa99eeab..32d902df 100644 --- a/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs +++ b/src/NadekoBot/Modules/Administration/SelfCommands.cs @@ -1,6 +1,5 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using System; using System.Collections.Generic; @@ -12,29 +11,36 @@ using Discord.WebSocket; using NadekoBot.Services; using NadekoBot.Services.Database.Models; using Microsoft.EntityFrameworkCore; -using NadekoBot.Services.Administration; +using System.Diagnostics; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Administration.Services; +using NadekoBot.Modules.Music.Services; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class SelfCommands : NadekoSubmodule + public class SelfCommands : NadekoSubmodule { private readonly DbService _db; private static readonly object _locker = new object(); - private readonly SelfService _service; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IImagesService _images; + private readonly MusicService _music; + private readonly IBotConfigProvider _bc; + private readonly NadekoBot _bot; - public SelfCommands(DbService db, SelfService service, DiscordShardedClient client, - IImagesService images) + public SelfCommands(DbService db, NadekoBot bot, DiscordSocketClient client, + MusicService music, IImagesService images, IBotConfigProvider bc) { _db = db; - _service = service; _client = client; _images = images; + _music = music; + _bc = bc; + _bot = bot; } [NadekoCommand, Usage, Description, Aliases] @@ -95,13 +101,13 @@ namespace NadekoBot.Modules.Administration } else { - await Context.Channel.SendConfirmAsync("", string.Join("\n--\n", scmds.Select(x => + await Context.Channel.SendConfirmAsync("", string.Join("\n", scmds.Select(x => { - string str = Format.Code(GetText("server")) + ": " + (x.GuildId == null ? "-" : x.GuildName + "/" + x.GuildId); + string str = $"```css\n[{GetText("server") + "]: " + (x.GuildId == null ? "-" : x.GuildName + " #" + x.GuildId)}"; str += $@" -{Format.Code(GetText("channel"))}: {x.ChannelName}/{x.ChannelId} -{Format.Code(GetText("command_text"))}: {x.CommandText}"; +[{GetText("channel")}]: {x.ChannelName} #{x.ChannelId} +[{GetText("command_text")}]: {x.CommandText}```"; return str; })), footer: GetText("page", page + 1)) .ConfigureAwait(false); @@ -177,9 +183,11 @@ namespace NadekoBot.Modules.Administration using (var uow = _db.UnitOfWork) { var config = uow.BotConfig.GetOrCreate(); - _service.ForwardDMs = config.ForwardMessages = !config.ForwardMessages; + config.ForwardMessages = !config.ForwardMessages; uow.Complete(); } + _bc.Reload(); + if (_service.ForwardDMs) await ReplyConfirmLocalized("fwdm_start").ConfigureAwait(false); else @@ -194,9 +202,11 @@ namespace NadekoBot.Modules.Administration { var config = uow.BotConfig.GetOrCreate(); lock (_locker) - _service.ForwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners; + config.ForwardToAllOwners = !config.ForwardToAllOwners; uow.Complete(); } + _bc.Reload(); + if (_service.ForwardDMsToAllOwners) await ReplyConfirmLocalized("fwall_start").ConfigureAwait(false); else @@ -204,28 +214,28 @@ namespace NadekoBot.Modules.Administration } - [NadekoCommand, Usage, Description, Aliases] - [OwnerOnly] - public async Task ConnectShard(int shardid) - { - var shard = _client.GetShard(shardid); - - if (shard == null) - { - await ReplyErrorLocalized("no_shard_id").ConfigureAwait(false); - return; - } - try - { - await ReplyConfirmLocalized("shard_reconnecting", Format.Bold("#" + shardid)).ConfigureAwait(false); - await shard.StartAsync().ConfigureAwait(false); - await ReplyConfirmLocalized("shard_reconnected", Format.Bold("#" + shardid)).ConfigureAwait(false); - } - catch (Exception ex) - { - _log.Warn(ex); - } - } + //todo 2 shard commands + //[NadekoCommand, Usage, Description, Aliases] + //[Shard0Precondition] + //[OwnerOnly] + //public async Task RestartShard(int shardid) + //{ + // if (shardid == 0 || shardid > b) + // { + // await ReplyErrorLocalized("no_shard_id").ConfigureAwait(false); + // return; + // } + // try + // { + // await ReplyConfirmLocalized("shard_reconnecting", Format.Bold("#" + shardid)).ConfigureAwait(false); + // await shard.StartAsync().ConfigureAwait(false); + // await ReplyConfirmLocalized("shard_reconnected", Format.Bold("#" + shardid)).ConfigureAwait(false); + // } + // catch (Exception ex) + // { + // _log.Warn(ex); + // } + //} [NadekoCommand, Usage, Description, Aliases] [OwnerOnly] @@ -266,6 +276,7 @@ namespace NadekoBot.Modules.Administration // ignored } await Task.Delay(2000).ConfigureAwait(false); + try { await _music.DestroyAllPlayers().ConfigureAwait(false); } catch { } Environment.Exit(0); } @@ -283,7 +294,7 @@ namespace NadekoBot.Modules.Administration [NadekoCommand, Usage, Description, Aliases] [RequireUserPermission(GuildPermission.ManageNicknames)] - [Priority(1)] + [Priority(0)] public async Task SetNick([Remainder] string newNick = null) { if (string.IsNullOrWhiteSpace(newNick)) @@ -297,7 +308,7 @@ namespace NadekoBot.Modules.Administration [NadekoCommand, Usage, Description, Aliases] [RequireBotPermission(GuildPermission.ManageNicknames)] [RequireUserPermission(GuildPermission.ManageNicknames)] - [Priority(0)] + [Priority(1)] public async Task SetNick(IGuildUser gu, [Remainder] string newNick = null) { await gu.ModifyAsync(u => u.Nickname = newNick).ConfigureAwait(false); @@ -340,7 +351,7 @@ namespace NadekoBot.Modules.Administration [OwnerOnly] public async Task SetGame([Remainder] string game = null) { - await _client.SetGameAsync(game).ConfigureAwait(false); + await _bot.SetGameAsync(game).ConfigureAwait(false); await ReplyConfirmLocalized("set_game").ConfigureAwait(false); } @@ -400,25 +411,14 @@ namespace NadekoBot.Modules.Administration await ReplyConfirmLocalized("message_sent").ConfigureAwait(false); } - [NadekoCommand, Usage, Description, Aliases] - [OwnerOnly] - public async Task Announce([Remainder] string message) - { - var channels = _client.Guilds.Select(g => g.DefaultChannel).ToArray(); - if (channels == null) - return; - await Task.WhenAll(channels.Where(c => c != null).Select(c => c.SendConfirmAsync(GetText("message_from_bo", Context.User.ToString()), message))) - .ConfigureAwait(false); - - await ReplyConfirmLocalized("message_sent").ConfigureAwait(false); - } - [NadekoCommand, Usage, Description, Aliases] [OwnerOnly] public async Task ReloadImages() { - var time = _images.Reload(); - await ReplyConfirmLocalized("images_loaded", time.TotalSeconds.ToString("F3")).ConfigureAwait(false); + var sw = Stopwatch.StartNew(); + _images.Reload(); + sw.Stop(); + await ReplyConfirmLocalized("images_loaded", sw.Elapsed.TotalSeconds.ToString("F3")).ConfigureAwait(false); } private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus) diff --git a/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs b/src/NadekoBot/Modules/Administration/ServerGreetCommands.cs similarity index 83% rename from src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs rename to src/NadekoBot/Modules/Administration/ServerGreetCommands.cs index 5a2f9f98..0f94e002 100644 --- a/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs +++ b/src/NadekoBot/Modules/Administration/ServerGreetCommands.cs @@ -1,24 +1,22 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using NadekoBot.Services.Database.Models; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class ServerGreetCommands : NadekoSubmodule + public class ServerGreetCommands : NadekoSubmodule { - private readonly GreetSettingsService _greetService; private readonly DbService _db; - public ServerGreetCommands(GreetSettingsService greetService, DbService db) + public ServerGreetCommands(DbService db) { - _greetService = greetService; _db = db; } @@ -30,7 +28,7 @@ namespace NadekoBot.Modules.Administration if (timer < 0 || timer > 600) return; - await _greetService.SetGreetDel(Context.Guild.Id, timer).ConfigureAwait(false); + await _service.SetGreetDel(Context.Guild.Id, timer).ConfigureAwait(false); if (timer > 0) await ReplyConfirmLocalized("greetdel_on", timer).ConfigureAwait(false); @@ -43,7 +41,7 @@ namespace NadekoBot.Modules.Administration [RequireUserPermission(GuildPermission.ManageGuild)] public async Task Greet() { - var enabled = await _greetService.SetGreet(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false); + var enabled = await _service.SetGreet(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false); if (enabled) await ReplyConfirmLocalized("greet_on").ConfigureAwait(false); @@ -67,7 +65,7 @@ namespace NadekoBot.Modules.Administration return; } - var sendGreetEnabled = _greetService.SetGreetMessage(Context.Guild.Id, ref text); + var sendGreetEnabled = _service.SetGreetMessage(Context.Guild.Id, ref text); await ReplyConfirmLocalized("greetmsg_new").ConfigureAwait(false); if (!sendGreetEnabled) @@ -79,7 +77,7 @@ namespace NadekoBot.Modules.Administration [RequireUserPermission(GuildPermission.ManageGuild)] public async Task GreetDm() { - var enabled = await _greetService.SetGreetDm(Context.Guild.Id).ConfigureAwait(false); + var enabled = await _service.SetGreetDm(Context.Guild.Id).ConfigureAwait(false); if (enabled) await ReplyConfirmLocalized("greetdm_on").ConfigureAwait(false); @@ -103,7 +101,7 @@ namespace NadekoBot.Modules.Administration return; } - var sendGreetEnabled = _greetService.SetGreetDmMessage(Context.Guild.Id, ref text); + var sendGreetEnabled = _service.SetGreetDmMessage(Context.Guild.Id, ref text); await ReplyConfirmLocalized("greetdmmsg_new").ConfigureAwait(false); if (!sendGreetEnabled) @@ -115,7 +113,7 @@ namespace NadekoBot.Modules.Administration [RequireUserPermission(GuildPermission.ManageGuild)] public async Task Bye() { - var enabled = await _greetService.SetBye(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false); + var enabled = await _service.SetBye(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false); if (enabled) await ReplyConfirmLocalized("bye_on").ConfigureAwait(false); @@ -139,7 +137,7 @@ namespace NadekoBot.Modules.Administration return; } - var sendByeEnabled = _greetService.SetByeMessage(Context.Guild.Id, ref text); + var sendByeEnabled = _service.SetByeMessage(Context.Guild.Id, ref text); await ReplyConfirmLocalized("byemsg_new").ConfigureAwait(false); if (!sendByeEnabled) @@ -151,7 +149,7 @@ namespace NadekoBot.Modules.Administration [RequireUserPermission(GuildPermission.ManageGuild)] public async Task ByeDel(int timer = 30) { - await _greetService.SetByeDel(Context.Guild.Id, timer).ConfigureAwait(false); + await _service.SetByeDel(Context.Guild.Id, timer).ConfigureAwait(false); if (timer > 0) await ReplyConfirmLocalized("byedel_on", timer).ConfigureAwait(false); diff --git a/src/NadekoBot/Services/Administration/AdministrationService.cs b/src/NadekoBot/Modules/Administration/Services/AdministrationService.cs similarity index 87% rename from src/NadekoBot/Services/Administration/AdministrationService.cs rename to src/NadekoBot/Modules/Administration/Services/AdministrationService.cs index 3ead9cc3..57ec29a8 100644 --- a/src/NadekoBot/Services/Administration/AdministrationService.cs +++ b/src/NadekoBot/Modules/Administration/Services/AdministrationService.cs @@ -1,17 +1,18 @@ -using Discord; -using Discord.Commands; -using Discord.WebSocket; -using NadekoBot.Services.Database.Models; -using NLog; -using System; -using System.Collections.Concurrent; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Common.Collections; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Services { - public class AdministrationService + public class AdministrationService : INService { public readonly ConcurrentHashSet DeleteMessagesOnCommand; private readonly Logger _log; diff --git a/src/NadekoBot/Services/Administration/AutoAssignRoleService.cs b/src/NadekoBot/Modules/Administration/Services/AutoAssignRoleService.cs similarity index 80% rename from src/NadekoBot/Services/Administration/AutoAssignRoleService.cs rename to src/NadekoBot/Modules/Administration/Services/AutoAssignRoleService.cs index 14679b6d..040986cb 100644 --- a/src/NadekoBot/Services/Administration/AutoAssignRoleService.cs +++ b/src/NadekoBot/Modules/Administration/Services/AutoAssignRoleService.cs @@ -1,23 +1,24 @@ -using Discord.WebSocket; -using NadekoBot.Services.Database.Models; -using NLog; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Discord.WebSocket; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Services { - public class AutoAssignRoleService + public class AutoAssignRoleService : INService { private readonly Logger _log; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; //guildid/roleid public ConcurrentDictionary AutoAssignedRoles { get; } - public AutoAssignRoleService(DiscordShardedClient client, IEnumerable gcs) + public AutoAssignRoleService(DiscordSocketClient client, IEnumerable gcs) { _log = LogManager.GetCurrentClassLogger(); _client = client; diff --git a/src/NadekoBot/Services/Administration/GameVoiceChannelService.cs b/src/NadekoBot/Modules/Administration/Services/GameVoiceChannelService.cs similarity index 81% rename from src/NadekoBot/Services/Administration/GameVoiceChannelService.cs rename to src/NadekoBot/Modules/Administration/Services/GameVoiceChannelService.cs index 14f9c6c6..e97e0eb7 100644 --- a/src/NadekoBot/Services/Administration/GameVoiceChannelService.cs +++ b/src/NadekoBot/Modules/Administration/Services/GameVoiceChannelService.cs @@ -1,24 +1,25 @@ -using Discord.WebSocket; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using NLog; -using System; -using System.Collections.Concurrent; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Discord.WebSocket; +using NadekoBot.Common.Collections; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Services { - public class GameVoiceChannelService + public class GameVoiceChannelService : INService { public readonly ConcurrentHashSet GameVoiceChannels = new ConcurrentHashSet(); private readonly Logger _log; private readonly DbService _db; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public GameVoiceChannelService(DiscordShardedClient client, DbService db, IEnumerable gcs) + public GameVoiceChannelService(DiscordSocketClient client, DbService db, IEnumerable gcs) { _log = LogManager.GetCurrentClassLogger(); _db = db; @@ -42,7 +43,7 @@ namespace NadekoBot.Services.Administration if (gUser == null) return; - var game = gUser.Game?.Name.TrimTo(50).ToLowerInvariant(); + var game = gUser.Game?.Name?.TrimTo(50).ToLowerInvariant(); if (oldState.VoiceChannel == newState.VoiceChannel || newState.VoiceChannel == null) diff --git a/src/NadekoBot/Services/Administration/GuildTimezoneService.cs b/src/NadekoBot/Modules/Administration/Services/GuildTimezoneService.cs similarity index 66% rename from src/NadekoBot/Services/Administration/GuildTimezoneService.cs rename to src/NadekoBot/Modules/Administration/Services/GuildTimezoneService.cs index 1d469211..fdef20c5 100644 --- a/src/NadekoBot/Services/Administration/GuildTimezoneService.cs +++ b/src/NadekoBot/Modules/Administration/Services/GuildTimezoneService.cs @@ -1,19 +1,22 @@ -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using System; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Collections.Concurrent; +using Discord.WebSocket; +using NadekoBot.Extensions; using NadekoBot.Services; +using NadekoBot.Services.Database.Models; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Services { - public class GuildTimezoneService + public class GuildTimezoneService : INService { + // todo 70 this is a hack >.< + public static ConcurrentDictionary AllServices { get; } = new ConcurrentDictionary(); private ConcurrentDictionary _timezones; private readonly DbService _db; - public GuildTimezoneService(IEnumerable gcs, DbService db) + public GuildTimezoneService(DiscordSocketClient client, IEnumerable gcs, DbService db) { _timezones = gcs .Select(x => @@ -21,7 +24,10 @@ namespace NadekoBot.Services.Administration TimeZoneInfo tz; try { - tz = TimeZoneInfo.FindSystemTimeZoneById(x.TimeZoneId); + if (x.TimeZoneId == null) + tz = null; + else + tz = TimeZoneInfo.FindSystemTimeZoneById(x.TimeZoneId); } catch { @@ -33,6 +39,9 @@ namespace NadekoBot.Services.Administration .ToDictionary(x => x.Item1, x => x.Item2) .ToConcurrent(); + var curUser = client.CurrentUser; + if (curUser != null) + AllServices.TryAdd(curUser.Id, this); _db = db; } diff --git a/src/NadekoBot/Services/Administration/LogCommandService.cs b/src/NadekoBot/Modules/Administration/Services/LogCommandService.cs similarity index 80% rename from src/NadekoBot/Services/Administration/LogCommandService.cs rename to src/NadekoBot/Modules/Administration/Services/LogCommandService.cs index 84555345..37b2781e 100644 --- a/src/NadekoBot/Services/Administration/LogCommandService.cs +++ b/src/NadekoBot/Modules/Administration/Services/LogCommandService.cs @@ -1,26 +1,43 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using NLog; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using NadekoBot.Extensions; +using NadekoBot.Modules.Administration.Common; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using NLog; +using NadekoBot.Common; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Services { - public class LogCommandService + [NoPublicBot] + public class LogCommandService : INService { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly Logger _log; - private string PrettyCurrentTime => $"【{DateTime.UtcNow:HH:mm:ss}】"; - private string CurrentTime => $"{DateTime.UtcNow:HH:mm:ss}"; + private string PrettyCurrentTime(IGuild g) + { + var time = DateTime.UtcNow; + if(g != null) + time = TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(g.Id)); + return $"【{time:HH:mm:ss}】"; + } + private string CurrentTime(IGuild g) + { + DateTime time = DateTime.UtcNow; + if (g != null) + time = TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(g.Id)); + + return $"{time:HH:mm:ss}"; + } public ConcurrentDictionary GuildLogSettings { get; } @@ -30,9 +47,10 @@ namespace NadekoBot.Services.Administration private readonly DbService _db; private readonly MuteService _mute; private readonly ProtectionService _prot; + private readonly GuildTimezoneService _tz; - public LogCommandService(DiscordShardedClient client, NadekoStrings strings, - IEnumerable gcs, DbService db, MuteService mute, ProtectionService prot) + public LogCommandService(DiscordSocketClient client, NadekoStrings strings, + IEnumerable gcs, DbService db, MuteService mute, ProtectionService prot, GuildTimezoneService tz) { _client = client; _log = LogManager.GetCurrentClassLogger(); @@ -40,6 +58,7 @@ namespace NadekoBot.Services.Administration _db = db; _mute = mute; _prot = prot; + _tz = tz; GuildLogSettings = gcs .ToDictionary(g => g.GuildId, g => g.LogSetting) @@ -51,14 +70,15 @@ namespace NadekoBot.Services.Administration { var keys = PresenceUpdates.Keys.ToList(); - await Task.WhenAll(keys.Select(async key => + await Task.WhenAll(keys.Select(key => { - if (PresenceUpdates.TryRemove(key, out List messages)) - try { await key.SendConfirmAsync(GetText(key.Guild, "presence_updates"), string.Join(Environment.NewLine, messages)); } - catch - { - // ignored - } + if (PresenceUpdates.TryRemove(key, out var msgs)) + { + var title = GetText(key.Guild, "presence_updates"); + var desc = string.Join(Environment.NewLine, msgs); + return key.SendConfirmAsync(title, desc.TrimTo(2048)); + } + return Task.CompletedTask; })); } catch (Exception ex) @@ -74,7 +94,7 @@ namespace NadekoBot.Services.Administration _client.UserUnbanned += _client_UserUnbanned; _client.UserJoined += _client_UserJoined; _client.UserLeft += _client_UserLeft; - _client.UserPresenceUpdated += _client_UserPresenceUpdated; + //_client.UserPresenceUpdated += _client_UserPresenceUpdated; _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated; _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS; _client.GuildMemberUpdated += _client_GuildUserUpdated; @@ -124,17 +144,20 @@ namespace NadekoBot.Services.Administration .WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}") .AddField(fb => fb.WithName("Old Name").WithValue($"{before.Username}").WithIsInline(true)) .AddField(fb => fb.WithName("New Name").WithValue($"{after.Username}").WithIsInline(true)) - .WithFooter(fb => fb.WithText(CurrentTime)) + .WithFooter(fb => fb.WithText(CurrentTime(g))) .WithOkColor(); } else if (before.AvatarId != after.AvatarId) { embed.WithTitle("👥" + GetText(g, "avatar_changed")) .WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}") - .WithThumbnailUrl(before.GetAvatarUrl()) - .WithImageUrl(after.GetAvatarUrl()) - .WithFooter(fb => fb.WithText(CurrentTime)) + .WithFooter(fb => fb.WithText(CurrentTime(g))) .WithOkColor(); + + if (Uri.IsWellFormedUriString(before.GetAvatarUrl(), UriKind.Absolute)) + embed.WithThumbnailUrl(before.GetAvatarUrl()); + if (Uri.IsWellFormedUriString(after.GetAvatarUrl(), UriKind.Absolute)) + embed.WithImageUrl(after.GetAvatarUrl()); } else { @@ -244,7 +267,7 @@ namespace NadekoBot.Services.Administration var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName(mutes)) .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}") - .WithFooter(fb => fb.WithText(CurrentTime)) + .WithFooter(fb => fb.WithText(CurrentTime(usr.Guild))) .WithOkColor(); await logChannel.EmbedAsync(embed).ConfigureAwait(false); @@ -287,7 +310,7 @@ namespace NadekoBot.Services.Administration var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName(mutes)) .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}") - .WithFooter(fb => fb.WithText($"{CurrentTime}")) + .WithFooter(fb => fb.WithText($"{CurrentTime(usr.Guild)}")) .WithOkColor(); await logChannel.EmbedAsync(embed).ConfigureAwait(false); @@ -335,7 +358,7 @@ namespace NadekoBot.Services.Administration var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName($"🛡 Anti-{protection}")) .WithTitle(GetText(logChannel.Guild, "users") + " " + punishment) .WithDescription(string.Join("\n", users.Select(u => u.ToString()))) - .WithFooter(fb => fb.WithText($"{CurrentTime}")) + .WithFooter(fb => fb.WithText(CurrentTime(logChannel.Guild))) .WithOkColor(); await logChannel.EmbedAsync(embed).ConfigureAwait(false); @@ -354,40 +377,59 @@ namespace NadekoBot.Services.Administration { try { - if (!GuildLogSettings.TryGetValue(before.Guild.Id, out LogSetting logSetting) - || (logSetting.UserUpdatedId == null)) + if (!GuildLogSettings.TryGetValue(before.Guild.Id, out LogSetting logSetting)) return; ITextChannel logChannel; - if ((logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) == null) - return; - var embed = new EmbedBuilder().WithOkColor().WithFooter(efb => efb.WithText(CurrentTime)) - .WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}"); - if (before.Nickname != after.Nickname) + if (logSetting.UserUpdatedId != null && (logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) != null) { - embed.WithAuthor(eab => eab.WithName("👥 " + GetText(logChannel.Guild, "nick_change"))) + var embed = new EmbedBuilder().WithOkColor().WithFooter(efb => efb.WithText(CurrentTime(before.Guild))) + .WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}"); + if (before.Nickname != after.Nickname) + { + embed.WithAuthor(eab => eab.WithName("👥 " + GetText(logChannel.Guild, "nick_change"))) + .AddField(efb => efb.WithName(GetText(logChannel.Guild, "old_nick")).WithValue($"{before.Nickname}#{before.Discriminator}")) + .AddField(efb => efb.WithName(GetText(logChannel.Guild, "new_nick")).WithValue($"{after.Nickname}#{after.Discriminator}")); - .AddField(efb => efb.WithName(GetText(logChannel.Guild, "old_nick")).WithValue($"{before.Nickname}#{before.Discriminator}")) - .AddField(efb => efb.WithName(GetText(logChannel.Guild, "new_nick")).WithValue($"{after.Nickname}#{after.Discriminator}")); + await logChannel.EmbedAsync(embed).ConfigureAwait(false); + } + else if (!before.Roles.SequenceEqual(after.Roles)) + { + if (before.Roles.Count < after.Roles.Count) + { + var diffRoles = after.Roles.Where(r => !before.Roles.Contains(r)).Select(r => r.Name); + embed.WithAuthor(eab => eab.WithName("⚔ " + GetText(logChannel.Guild, "user_role_add"))) + .WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); + } + else if (before.Roles.Count > after.Roles.Count) + { + var diffRoles = before.Roles.Where(r => !after.Roles.Contains(r)).Select(r => r.Name); + embed.WithAuthor(eab => eab.WithName("⚔ " + GetText(logChannel.Guild, "user_role_rem"))) + .WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); + } + await logChannel.EmbedAsync(embed).ConfigureAwait(false); + } } - else if (!before.Roles.SequenceEqual(after.Roles)) + + logChannel = null; + if (logSetting.LogUserPresenceId != null && (logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserPresence)) != null) { - if (before.Roles.Count < after.Roles.Count) + if (before.Status != after.Status) { - var diffRoles = after.Roles.Where(r => !before.Roles.Contains(r)).Select(r => r.Name); - embed.WithAuthor(eab => eab.WithName("⚔ " + GetText(logChannel.Guild, "user_role_add"))) - .WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); + var str = "🎭" + Format.Code(PrettyCurrentTime(after.Guild)) + + GetText(logChannel.Guild, "user_status_change", + "👤" + Format.Bold(after.Username), + Format.Bold(after.Status.ToString())); + PresenceUpdates.AddOrUpdate(logChannel, + new List() { str }, (id, list) => { list.Add(str); return list; }); } - else if (before.Roles.Count > after.Roles.Count) + else if (before.Game?.Name != after.Game?.Name) { - var diffRoles = before.Roles.Where(r => !after.Roles.Contains(r)).Select(r => r.Name); - embed.WithAuthor(eab => eab.WithName("⚔ " + GetText(logChannel.Guild, "user_role_rem"))) - .WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); + var str = $"👾`{PrettyCurrentTime(after.Guild)}`👤__**{after.Username}**__ is now playing **{after.Game?.Name ?? "-"}**."; + PresenceUpdates.AddOrUpdate(logChannel, + new List() { str }, (id, list) => { list.Add(str); return list; }); } } - else - return; - await logChannel.EmbedAsync(embed).ConfigureAwait(false); } catch { @@ -417,7 +459,7 @@ namespace NadekoBot.Services.Administration if ((logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.ChannelUpdated)) == null) return; - var embed = new EmbedBuilder().WithOkColor().WithFooter(efb => efb.WithText(CurrentTime)); + var embed = new EmbedBuilder().WithOkColor().WithFooter(efb => efb.WithText(CurrentTime(before.Guild))); var beforeTextChannel = cbefore as ITextChannel; var afterTextChannel = cafter as ITextChannel; @@ -477,7 +519,7 @@ namespace NadekoBot.Services.Administration .WithOkColor() .WithTitle("🆕 " + title) .WithDescription($"{ch.Name} | {ch.Id}") - .WithFooter(efb => efb.WithText(CurrentTime))).ConfigureAwait(false); + .WithFooter(efb => efb.WithText(CurrentTime(ch.Guild)))).ConfigureAwait(false); } catch { @@ -515,7 +557,7 @@ namespace NadekoBot.Services.Administration .WithOkColor() .WithTitle("🆕 " + title) .WithDescription($"{ch.Name} | {ch.Id}") - .WithFooter(efb => efb.WithText(CurrentTime))).ConfigureAwait(false); + .WithFooter(efb => efb.WithText(CurrentTime(ch.Guild)))).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } }); @@ -549,23 +591,23 @@ namespace NadekoBot.Services.Administration string str = null; if (beforeVch?.Guild == afterVch?.Guild) { - str = "🎙" + Format.Code(PrettyCurrentTime) + GetText(logChannel.Guild, "user_vmoved", + str = "🎙" + Format.Code(PrettyCurrentTime(usr.Guild)) + GetText(logChannel.Guild, "user_vmoved", "👤" + Format.Bold(usr.Username + "#" + usr.Discriminator), Format.Bold(beforeVch?.Name ?? ""), Format.Bold(afterVch?.Name ?? "")); } else if (beforeVch == null) { - str = "🎙" + Format.Code(PrettyCurrentTime) + GetText(logChannel.Guild, "user_vjoined", + str = "🎙" + Format.Code(PrettyCurrentTime(usr.Guild)) + GetText(logChannel.Guild, "user_vjoined", "👤" + Format.Bold(usr.Username + "#" + usr.Discriminator), Format.Bold(afterVch.Name ?? "")); } else if (afterVch == null) { - str = "🎙" + Format.Code(PrettyCurrentTime) + GetText(logChannel.Guild, "user_vleft", + str = "🎙" + Format.Code(PrettyCurrentTime(usr.Guild)) + GetText(logChannel.Guild, "user_vleft", "👤" + Format.Bold(usr.Username + "#" + usr.Discriminator), Format.Bold(beforeVch.Name ?? "")); } - if (str != null) + if (!string.IsNullOrWhiteSpace(str)) PresenceUpdates.AddOrUpdate(logChannel, new List() { str }, (id, list) => { list.Add(str); return list; }); } catch @@ -576,48 +618,48 @@ namespace NadekoBot.Services.Administration return Task.CompletedTask; } - private Task _client_UserPresenceUpdated(Optional optGuild, SocketUser usr, SocketPresence before, SocketPresence after) - { - var _ = Task.Run(async () => - { - try - { - var guild = optGuild.GetValueOrDefault() ?? (usr as SocketGuildUser)?.Guild; + //private Task _client_UserPresenceUpdated(Optional optGuild, SocketUser usr, SocketPresence before, SocketPresence after) + //{ + // var _ = Task.Run(async () => + // { + // try + // { + // var guild = optGuild.GetValueOrDefault() ?? (usr as SocketGuildUser)?.Guild; - if (guild == null) - return; + // if (guild == null) + // return; - if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting) - || (logSetting.LogUserPresenceId == null) - || before.Status == after.Status) - return; + // if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting) + // || (logSetting.LogUserPresenceId == null) + // || before.Status == after.Status) + // return; - ITextChannel logChannel; - if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserPresence)) == null) - return; - string str = ""; - if (before.Status != after.Status) - str = "🎭" + Format.Code(PrettyCurrentTime) + - GetText(logChannel.Guild, "user_status_change", - "👤" + Format.Bold(usr.Username), - Format.Bold(after.Status.ToString())); + // ITextChannel logChannel; + // if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserPresence)) == null) + // return; + // string str = ""; + // if (before.Status != after.Status) + // str = "🎭" + Format.Code(PrettyCurrentTime(g)) + + // GetText(logChannel.Guild, "user_status_change", + // "👤" + Format.Bold(usr.Username), + // Format.Bold(after.Status.ToString())); - //if (before.Game?.Name != after.Game?.Name) - //{ - // if (str != "") - // str += "\n"; - // str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**."; - //} + // //if (before.Game?.Name != after.Game?.Name) + // //{ + // // if (str != "") + // // str += "\n"; + // // str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**."; + // //} - PresenceUpdates.AddOrUpdate(logChannel, new List() { str }, (id, list) => { list.Add(str); return list; }); - } - catch - { - // ignored - } - }); - return Task.CompletedTask; - } + // PresenceUpdates.AddOrUpdate(logChannel, new List() { str }, (id, list) => { list.Add(str); return list; }); + // } + // catch + // { + // // ignored + // } + // }); + // return Task.CompletedTask; + //} private Task _client_UserLeft(IGuildUser usr) { @@ -632,14 +674,17 @@ namespace NadekoBot.Services.Administration ITextChannel logChannel; if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserLeft)) == null) return; - - await logChannel.EmbedAsync(new EmbedBuilder() + var embed = new EmbedBuilder() .WithOkColor() .WithTitle("❌ " + GetText(logChannel.Guild, "user_left")) - .WithThumbnailUrl(usr.GetAvatarUrl()) .WithDescription(usr.ToString()) .AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString())) - .WithFooter(efb => efb.WithText(CurrentTime))).ConfigureAwait(false); + .WithFooter(efb => efb.WithText(CurrentTime(usr.Guild))); + + if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute)) + embed.WithThumbnailUrl(usr.GetAvatarUrl()); + + await logChannel.EmbedAsync(embed).ConfigureAwait(false); } catch { @@ -663,13 +708,17 @@ namespace NadekoBot.Services.Administration if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserJoined)) == null) return; - await logChannel.EmbedAsync(new EmbedBuilder() + var embed = new EmbedBuilder() .WithOkColor() .WithTitle("✅ " + GetText(logChannel.Guild, "user_joined")) - .WithThumbnailUrl(usr.GetAvatarUrl()) .WithDescription($"{usr}") .AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString())) - .WithFooter(efb => efb.WithText(CurrentTime))).ConfigureAwait(false); + .WithFooter(efb => efb.WithText(CurrentTime(usr.Guild))); + + if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute)) + embed.WithThumbnailUrl(usr.GetAvatarUrl()); + + await logChannel.EmbedAsync(embed).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } }); @@ -689,14 +738,17 @@ namespace NadekoBot.Services.Administration ITextChannel logChannel; if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserUnbanned)) == null) return; - - await logChannel.EmbedAsync(new EmbedBuilder() + var embed = new EmbedBuilder() .WithOkColor() .WithTitle("♻️ " + GetText(logChannel.Guild, "user_unbanned")) - .WithThumbnailUrl(usr.GetAvatarUrl()) .WithDescription(usr.ToString()) .AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString())) - .WithFooter(efb => efb.WithText(CurrentTime))).ConfigureAwait(false); + .WithFooter(efb => efb.WithText(CurrentTime(guild))); + + if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute)) + embed.WithThumbnailUrl(usr.GetAvatarUrl()); + + await logChannel.EmbedAsync(embed).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } }); @@ -716,13 +768,19 @@ namespace NadekoBot.Services.Administration ITextChannel logChannel; if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserBanned)) == null) return; - await logChannel.EmbedAsync(new EmbedBuilder() + var embed = new EmbedBuilder() .WithOkColor() .WithTitle("🚫 " + GetText(logChannel.Guild, "user_banned")) - .WithThumbnailUrl(usr.GetAvatarUrl()) .WithDescription(usr.ToString()) .AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString())) - .WithFooter(efb => efb.WithText(CurrentTime))).ConfigureAwait(false); + .WithFooter(efb => efb.WithText(CurrentTime(guild))); + + var avatarUrl = usr.GetAvatarUrl(); + + if (Uri.IsWellFormedUriString(avatarUrl, UriKind.Absolute)) + embed.WithThumbnailUrl(usr.GetAvatarUrl()); + + await logChannel.EmbedAsync(embed).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } }); @@ -757,7 +815,7 @@ namespace NadekoBot.Services.Administration .WithDescription(msg.Author.ToString()) .AddField(efb => efb.WithName(GetText(logChannel.Guild, "content")).WithValue(string.IsNullOrWhiteSpace(msg.Content) ? "-" : msg.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false)) .AddField(efb => efb.WithName("Id").WithValue(msg.Id.ToString()).WithIsInline(false)) - .WithFooter(efb => efb.WithText(CurrentTime)); + .WithFooter(efb => efb.WithText(CurrentTime(channel.Guild))); if (msg.Attachments.Any()) embed.AddField(efb => efb.WithName(GetText(logChannel.Guild, "attachments")).WithValue(string.Join(", ", msg.Attachments.Select(a => a.Url))).WithIsInline(false)); @@ -809,7 +867,7 @@ namespace NadekoBot.Services.Administration .AddField(efb => efb.WithName(GetText(logChannel.Guild, "old_msg")).WithValue(string.IsNullOrWhiteSpace(before.Content) ? "-" : before.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false)) .AddField(efb => efb.WithName(GetText(logChannel.Guild, "new_msg")).WithValue(string.IsNullOrWhiteSpace(after.Content) ? "-" : after.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false)) .AddField(efb => efb.WithName("Id").WithValue(after.Id.ToString()).WithIsInline(false)) - .WithFooter(efb => efb.WithText(CurrentTime)); + .WithFooter(efb => efb.WithText(CurrentTime(channel.Guild))); await logChannel.EmbedAsync(embed).ConfigureAwait(false); } diff --git a/src/NadekoBot/Services/Administration/MuteService.cs b/src/NadekoBot/Modules/Administration/Services/MuteService.cs similarity index 95% rename from src/NadekoBot/Services/Administration/MuteService.cs rename to src/NadekoBot/Modules/Administration/Services/MuteService.cs index 7ed9062a..77b4136c 100644 --- a/src/NadekoBot/Services/Administration/MuteService.cs +++ b/src/NadekoBot/Modules/Administration/Services/MuteService.cs @@ -1,17 +1,19 @@ -using Discord; -using Discord.WebSocket; -using Microsoft.EntityFrameworkCore; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using NLog; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using Microsoft.EntityFrameworkCore; +using NadekoBot.Common.Collections; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Services { public enum MuteType { @@ -20,7 +22,7 @@ namespace NadekoBot.Services.Administration All } - public class MuteService + public class MuteService : INService { public ConcurrentDictionary GuildMuteRoles { get; } public ConcurrentDictionary> MutedUsers { get; } @@ -30,13 +32,13 @@ namespace NadekoBot.Services.Administration public event Action UserMuted = delegate { }; public event Action UserUnmuted = delegate { }; - private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny); + private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(addReactions: PermValue.Deny, sendMessages: PermValue.Deny, attachFiles: PermValue.Deny); private readonly Logger _log = LogManager.GetCurrentClassLogger(); - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly DbService _db; - public MuteService(DiscordShardedClient client, IEnumerable gcs, DbService db) + public MuteService(DiscordSocketClient client, IEnumerable gcs, DbService db) { _client = client; _db = db; diff --git a/src/NadekoBot/Modules/Administration/Services/PlayingRotateService.cs b/src/NadekoBot/Modules/Administration/Services/PlayingRotateService.cs new file mode 100644 index 00000000..d0cd5eef --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Services/PlayingRotateService.cs @@ -0,0 +1,88 @@ +using System; +using System.Linq; +using System.Threading; +using Discord.WebSocket; +using NadekoBot.Common.Replacements; +using NadekoBot.Modules.Music.Services; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration.Services +{ + public class PlayingRotateService : INService + { + private readonly Timer _t; + private readonly DiscordSocketClient _client; + private readonly MusicService _music; + private readonly Logger _log; + private readonly IDataCache _cache; + private readonly Replacer _rep; + private readonly DbService _db; + private readonly IBotConfigProvider _bcp; + + public BotConfig BotConfig => _bcp.BotConfig; + + private class TimerState + { + public int Index { get; set; } + } + + public PlayingRotateService(DiscordSocketClient client, IBotConfigProvider bcp, + MusicService music, DbService db, IDataCache cache, NadekoBot bot) + { + _client = client; + _bcp = bcp; + _music = music; + _db = db; + _log = LogManager.GetCurrentClassLogger(); + _cache = cache; + + if (client.ShardId == 0) + { + + _rep = new ReplacementBuilder() + .WithClient(client) + .WithStats(client) + .WithMusic(music) + .Build(); + + _t = new Timer(async (objState) => + { + try + { + bcp.Reload(); + + var state = (TimerState)objState; + if (!BotConfig.RotatingStatuses) + return; + if (state.Index >= BotConfig.RotatingStatusMessages.Count) + state.Index = 0; + + if (!BotConfig.RotatingStatusMessages.Any()) + return; + var status = BotConfig.RotatingStatusMessages[state.Index++].Status; + if (string.IsNullOrWhiteSpace(status)) + return; + + status = _rep.Replace(status); + + try + { + await bot.SetGameAsync(status).ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Warn(ex); + } + } + catch (Exception ex) + { + _log.Warn("Rotating playing status errored.\n" + ex); + } + }, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); + } + } + } +} diff --git a/src/NadekoBot/Services/Administration/ProtectionService.cs b/src/NadekoBot/Modules/Administration/Services/ProtectionService.cs similarity index 88% rename from src/NadekoBot/Services/Administration/ProtectionService.cs rename to src/NadekoBot/Modules/Administration/Services/ProtectionService.cs index 23bca7ad..ea65fd3f 100644 --- a/src/NadekoBot/Services/Administration/ProtectionService.cs +++ b/src/NadekoBot/Modules/Administration/Services/ProtectionService.cs @@ -1,16 +1,18 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.Services.Database.Models; -using NLog; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using NadekoBot.Modules.Administration.Common; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Services { - public class ProtectionService + public class ProtectionService : INService { public readonly ConcurrentDictionary AntiRaidGuilds = new ConcurrentDictionary(); @@ -21,10 +23,10 @@ namespace NadekoBot.Services.Administration public event Func OnAntiProtectionTriggered = delegate { return Task.CompletedTask; }; private readonly Logger _log; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly MuteService _mute; - public ProtectionService(DiscordShardedClient client, IEnumerable gcs, MuteService mute) + public ProtectionService(DiscordSocketClient client, IEnumerable gcs, MuteService mute) { _log = LogManager.GetCurrentClassLogger(); _client = client; @@ -76,7 +78,7 @@ namespace NadekoBot.Services.Administration if (spamSettings.UserStats.TryRemove(msg.Author.Id, out stats)) { stats.Dispose(); - await PunishUsers(spamSettings.AntiSpamSettings.Action, ProtectionType.Spamming, (IGuildUser)msg.Author) + await PunishUsers(spamSettings.AntiSpamSettings.Action, ProtectionType.Spamming, spamSettings.AntiSpamSettings.MuteTime, (IGuildUser)msg.Author) .ConfigureAwait(false); } } @@ -109,7 +111,7 @@ namespace NadekoBot.Services.Administration var users = settings.RaidUsers.ToArray(); settings.RaidUsers.Clear(); - await PunishUsers(settings.AntiRaidSettings.Action, ProtectionType.Raiding, users).ConfigureAwait(false); + await PunishUsers(settings.AntiRaidSettings.Action, ProtectionType.Raiding, 0, users).ConfigureAwait(false); } await Task.Delay(1000 * settings.AntiRaidSettings.Seconds).ConfigureAwait(false); @@ -127,7 +129,7 @@ namespace NadekoBot.Services.Administration } - private async Task PunishUsers(PunishmentAction action, ProtectionType pt, params IGuildUser[] gus) + private async Task PunishUsers(PunishmentAction action, ProtectionType pt, int muteTime, params IGuildUser[] gus) { _log.Info($"[{pt}] - Punishing [{gus.Length}] users with [{action}] in {gus[0].Guild.Name} guild"); foreach (var gu in gus) @@ -137,7 +139,10 @@ namespace NadekoBot.Services.Administration case PunishmentAction.Mute: try { - await _mute.MuteUser(gu).ConfigureAwait(false); + if (muteTime <= 0) + await _mute.MuteUser(gu).ConfigureAwait(false); + else + await _mute.TimedMute(gu, TimeSpan.FromSeconds(muteTime)).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex, "I can't apply punishement"); } break; diff --git a/src/NadekoBot/Services/Administration/PruneService.cs b/src/NadekoBot/Modules/Administration/Services/PruneService.cs similarity index 77% rename from src/NadekoBot/Services/Administration/PruneService.cs rename to src/NadekoBot/Modules/Administration/Services/PruneService.cs index 0929c1a6..4f25e3c3 100644 --- a/src/NadekoBot/Services/Administration/PruneService.cs +++ b/src/NadekoBot/Modules/Administration/Services/PruneService.cs @@ -1,18 +1,18 @@ -using Discord; -using NadekoBot.Extensions; -using System; -using System.Collections.Concurrent; +using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; +using Discord; +using NadekoBot.Common.Collections; +using NadekoBot.Extensions; +using NadekoBot.Services; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Services { - public class PruneService + public class PruneService : INService { //channelids where prunes are currently occuring - private ConcurrentHashSet _pruningChannels = new ConcurrentHashSet(); + private ConcurrentHashSet _pruningGuilds = new ConcurrentHashSet(); private readonly TimeSpan twoWeeks = TimeSpan.FromDays(14); public async Task PruneWhere(ITextChannel channel, int amount, Func predicate) @@ -21,14 +21,14 @@ namespace NadekoBot.Services.Administration if (amount <= 0) throw new ArgumentOutOfRangeException(nameof(amount)); - if (!_pruningChannels.Add(channel.Id)) + if (!_pruningGuilds.Add(channel.GuildId)) return; try { IMessage[] msgs; IMessage lastMessage = null; - msgs = (await channel.GetMessagesAsync().Flatten()).Where(predicate).Take(amount).ToArray(); + msgs = (await channel.GetMessagesAsync(50).Flatten()).Where(predicate).Take(amount).ToArray(); while (amount > 0 && msgs.Any()) { lastMessage = msgs[msgs.Length - 1]; @@ -52,9 +52,9 @@ namespace NadekoBot.Services.Administration //this isn't good, because this still work as if i want to remove only specific user's messages from the last //100 messages, Maybe this needs to be reduced by msgs.Length instead of 100 - amount -= 100; + amount -= 50; if(amount > 0) - msgs = (await channel.GetMessagesAsync(lastMessage, Direction.Before).Flatten()).Where(predicate).Take(amount).ToArray(); + msgs = (await channel.GetMessagesAsync(lastMessage, Direction.Before, 50).Flatten()).Where(predicate).Take(amount).ToArray(); } } catch @@ -63,7 +63,7 @@ namespace NadekoBot.Services.Administration } finally { - _pruningChannels.TryRemove(channel.Id); + _pruningGuilds.TryRemove(channel.GuildId); } } } diff --git a/src/NadekoBot/Services/Administration/RatelimitService.cs b/src/NadekoBot/Modules/Administration/Services/RatelimitService.cs similarity index 83% rename from src/NadekoBot/Services/Administration/RatelimitService.cs rename to src/NadekoBot/Modules/Administration/Services/RatelimitService.cs index b344930f..9667d193 100644 --- a/src/NadekoBot/Services/Administration/RatelimitService.cs +++ b/src/NadekoBot/Modules/Administration/Services/RatelimitService.cs @@ -1,27 +1,29 @@ -using Discord.WebSocket; -using NadekoBot.DataStructures.ModuleBehaviors; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using NLog; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using Discord; using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Extensions; +using NadekoBot.Modules.Administration.Common; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Services { - public class SlowmodeService : IEarlyBlocker + public class SlowmodeService : IEarlyBlocker, INService { public ConcurrentDictionary RatelimitingChannels = new ConcurrentDictionary(); public ConcurrentDictionary> IgnoredRoles = new ConcurrentDictionary>(); public ConcurrentDictionary> IgnoredUsers = new ConcurrentDictionary>(); private readonly Logger _log; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public SlowmodeService(DiscordShardedClient client, IEnumerable gcs) + public SlowmodeService(DiscordSocketClient client, IEnumerable gcs) { _log = LogManager.GetCurrentClassLogger(); _client = client; diff --git a/src/NadekoBot/Services/Administration/SelfService.cs b/src/NadekoBot/Modules/Administration/Services/SelfService.cs similarity index 71% rename from src/NadekoBot/Services/Administration/SelfService.cs rename to src/NadekoBot/Modules/Administration/Services/SelfService.cs index f3148d5a..93699858 100644 --- a/src/NadekoBot/Services/Administration/SelfService.cs +++ b/src/NadekoBot/Modules/Administration/Services/SelfService.cs @@ -1,21 +1,22 @@ -using Discord; -using NadekoBot.DataStructures; -using NadekoBot.DataStructures.ModuleBehaviors; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using NLog; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; +using Discord; using Discord.WebSocket; -using System.Collections.Generic; +using NadekoBot.Common; +using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Impl; +using NLog; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Services { - public class SelfService : ILateExecutor + public class SelfService : ILateExecutor, INService { - public volatile bool ForwardDMs; - public volatile bool ForwardDMsToAllOwners; + public bool ForwardDMs => _bc.BotConfig.ForwardMessages; + public bool ForwardDMsToAllOwners => _bc.BotConfig.ForwardToAllOwners; private readonly NadekoBot _bot; private readonly CommandHandler _cmdHandler; @@ -23,12 +24,13 @@ namespace NadekoBot.Services.Administration private readonly Logger _log; private readonly ILocalization _localization; private readonly NadekoStrings _strings; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IBotCredentials _creds; private ImmutableArray> ownerChannels = new ImmutableArray>(); + private readonly IBotConfigProvider _bc; - public SelfService(DiscordShardedClient client, NadekoBot bot, CommandHandler cmdHandler, DbService db, - BotConfig bc, ILocalization localization, NadekoStrings strings, IBotCredentials creds) + public SelfService(DiscordSocketClient client, NadekoBot bot, CommandHandler cmdHandler, DbService db, + IBotConfigProvider bc, ILocalization localization, NadekoStrings strings, IBotCredentials creds) { _bot = bot; _cmdHandler = cmdHandler; @@ -38,20 +40,13 @@ namespace NadekoBot.Services.Administration _strings = strings; _client = client; _creds = creds; - - using (var uow = _db.UnitOfWork) - { - var config = uow.BotConfig.GetOrCreate(); - ForwardDMs = config.ForwardMessages; - ForwardDMsToAllOwners = config.ForwardToAllOwners; - } + _bc = bc; var _ = Task.Run(async () => { - while (!bot.Ready) - await Task.Delay(1000); + await bot.Ready.Task.ConfigureAwait(false); - foreach (var cmd in bc.StartupCommands) + foreach (var cmd in bc.BotConfig.StartupCommands) { await cmdHandler.ExecuteExternal(cmd.GuildId, cmd.ChannelId, cmd.CommandText); await Task.Delay(400).ConfigureAwait(false); @@ -60,19 +55,14 @@ namespace NadekoBot.Services.Administration var ___ = Task.Run(async () => { - while (!bot.Ready) - await Task.Delay(1000); + await bot.Ready.Task.ConfigureAwait(false); await Task.Delay(5000); _client.Guilds.SelectMany(g => g.Users); - LoadOwnerChannels(); - - if (!ownerChannels.Any()) - _log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file."); - else - _log.Info($"Created {ownerChannels.Length} out of {_creds.OwnerIds.Length} owner message channels."); + if(client.ShardId == 0) + LoadOwnerChannels(); }); } @@ -81,11 +71,9 @@ namespace NadekoBot.Services.Administration var hs = new HashSet(_creds.OwnerIds); var channels = new Dictionary>(); - foreach (var s in _client.Shards) + if (hs.Count > 0) { - if (hs.Count == 0) - break; - foreach (var g in s.Guilds) + foreach (var g in _client.Guilds) { if (hs.Count == 0) break; @@ -94,7 +82,7 @@ namespace NadekoBot.Services.Administration { if (hs.Remove(u.Id)) { - channels.Add(u.Id, new AsyncLazy(async () => await u.CreateDMChannelAsync())); + channels.Add(u.Id, new AsyncLazy(async () => await u.GetOrCreateDMChannelAsync())); if (hs.Count == 0) break; } @@ -105,10 +93,15 @@ namespace NadekoBot.Services.Administration ownerChannels = channels.OrderBy(x => _creds.OwnerIds.IndexOf(x.Key)) .Select(x => x.Value) .ToImmutableArray(); + + if (!ownerChannels.Any()) + _log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file."); + else + _log.Info($"Created {ownerChannels.Length} out of {_creds.OwnerIds.Length} owner message channels."); } // forwards dms - public async Task LateExecute(DiscordShardedClient client, IGuild guild, IUserMessage msg) + public async Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg) { if (msg.Channel is IDMChannel && ForwardDMs && ownerChannels.Length > 0) { diff --git a/src/NadekoBot/Modules/Administration/Services/UserPunishService.cs b/src/NadekoBot/Modules/Administration/Services/UserPunishService.cs new file mode 100644 index 00000000..55b886f3 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Services/UserPunishService.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using Microsoft.EntityFrameworkCore; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Modules.Administration.Services +{ + public class UserPunishService : INService + { + private readonly MuteService _mute; + private readonly DbService _db; + + public UserPunishService(MuteService mute, DbService db) + { + _mute = mute; + _db = db; + } + + public async Task Warn(IGuild guild, ulong userId, string modName, string reason) + { + if (string.IsNullOrWhiteSpace(reason)) + reason = "-"; + + var guildId = guild.Id; + + var warn = new Warning() + { + UserId = userId, + GuildId = guildId, + Forgiven = false, + Reason = reason, + Moderator = modName, + }; + + int warnings = 1; + List ps; + using (var uow = _db.UnitOfWork) + { + ps = uow.GuildConfigs.For(guildId, set => set.Include(x => x.WarnPunishments)) + .WarnPunishments; + + warnings += uow.Warnings + .For(guildId, userId) + .Where(w => !w.Forgiven && w.UserId == userId) + .Count(); + + uow.Warnings.Add(warn); + + uow.Complete(); + } + + var p = ps.FirstOrDefault(x => x.Count == warnings); + + if (p != null) + { + var user = await guild.GetUserAsync(userId); + if (user == null) + return null; + switch (p.Punishment) + { + case PunishmentAction.Mute: + if (p.Time == 0) + await _mute.MuteUser(user).ConfigureAwait(false); + else + await _mute.TimedMute(user, TimeSpan.FromMinutes(p.Time)).ConfigureAwait(false); + break; + case PunishmentAction.Kick: + await user.KickAsync().ConfigureAwait(false); + break; + case PunishmentAction.Ban: + await guild.AddBanAsync(user).ConfigureAwait(false); + break; + case PunishmentAction.Softban: + await guild.AddBanAsync(user, 7).ConfigureAwait(false); + try + { + await guild.RemoveBanAsync(user).ConfigureAwait(false); + } + catch + { + await guild.RemoveBanAsync(user).ConfigureAwait(false); + } + break; + default: + break; + } + return p.Punishment; + } + + return null; + } + } +} diff --git a/src/NadekoBot/Services/Administration/VcRoleService.cs b/src/NadekoBot/Modules/Administration/Services/VcRoleService.cs similarity index 93% rename from src/NadekoBot/Services/Administration/VcRoleService.cs rename to src/NadekoBot/Modules/Administration/Services/VcRoleService.cs index 49dbe9ff..9f91d114 100644 --- a/src/NadekoBot/Services/Administration/VcRoleService.cs +++ b/src/NadekoBot/Modules/Administration/Services/VcRoleService.cs @@ -1,24 +1,25 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.Services.Database.Models; -using NLog; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Services { - public class VcRoleService + public class VcRoleService : INService { private readonly Logger _log; private readonly DbService _db; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; public ConcurrentDictionary> VcRoles { get; } - public VcRoleService(DiscordShardedClient client, IEnumerable gcs, DbService db) + public VcRoleService(DiscordSocketClient client, IEnumerable gcs, DbService db) { _log = LogManager.GetCurrentClassLogger(); _db = db; diff --git a/src/NadekoBot/Services/Administration/VplusTService.cs b/src/NadekoBot/Modules/Administration/Services/VplusTService.cs similarity index 94% rename from src/NadekoBot/Services/Administration/VplusTService.cs rename to src/NadekoBot/Modules/Administration/Services/VplusTService.cs index 3d9e86cd..9ffd8dcb 100644 --- a/src/NadekoBot/Services/Administration/VplusTService.cs +++ b/src/NadekoBot/Modules/Administration/Services/VplusTService.cs @@ -1,31 +1,34 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using NLog; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using NadekoBot.Common.Collections; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using NLog; -namespace NadekoBot.Services.Administration +namespace NadekoBot.Modules.Administration.Services { - public class VplusTService + public class VplusTService : INService { private readonly Regex _channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled); public readonly ConcurrentHashSet VoicePlusTextCache; private readonly ConcurrentDictionary _guildLockObjects = new ConcurrentDictionary(); - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly NadekoStrings _strings; private readonly DbService _db; private readonly Logger _log; - public VplusTService(DiscordShardedClient client, IEnumerable gcs, NadekoStrings strings, + public VplusTService(DiscordSocketClient client, IEnumerable gcs, NadekoStrings strings, DbService db) { _client = client; diff --git a/src/NadekoBot/Modules/Administration/Commands/TimeZoneCommands.cs b/src/NadekoBot/Modules/Administration/TimeZoneCommands.cs similarity index 85% rename from src/NadekoBot/Modules/Administration/Commands/TimeZoneCommands.cs rename to src/NadekoBot/Modules/Administration/TimeZoneCommands.cs index e054288c..25d55dd2 100644 --- a/src/NadekoBot/Modules/Administration/Commands/TimeZoneCommands.cs +++ b/src/NadekoBot/Modules/Administration/TimeZoneCommands.cs @@ -1,27 +1,20 @@ using Discord; using Discord.Commands; using Discord.WebSocket; -using NadekoBot.Attributes; using NadekoBot.Extensions; -using NadekoBot.Services.Administration; using System; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Administration.Services; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class TimeZoneCommands : NadekoSubmodule + public class TimeZoneCommands : NadekoSubmodule { - private readonly GuildTimezoneService _service; - - public TimeZoneCommands(GuildTimezoneService service) - { - _service = service; - } - [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] public async Task Timezones(int page = 1) @@ -36,7 +29,7 @@ namespace NadekoBot.Modules.Administration .ToArray(); var timezonesPerPage = 20; - await Context.Channel.SendPaginatedConfirmAsync((DiscordShardedClient)Context.Client, page, + await Context.Channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client, page, (curPage) => new EmbedBuilder() .WithOkColor() .WithTitle(GetText("timezones_available")) diff --git a/src/NadekoBot/Modules/Administration/Commands/UserPunishCommands.cs b/src/NadekoBot/Modules/Administration/UserPunishCommands.cs similarity index 81% rename from src/NadekoBot/Modules/Administration/Commands/UserPunishCommands.cs rename to src/NadekoBot/Modules/Administration/UserPunishCommands.cs index 3a110f79..d4b29723 100644 --- a/src/NadekoBot/Modules/Administration/Commands/UserPunishCommands.cs +++ b/src/NadekoBot/Modules/Administration/UserPunishCommands.cs @@ -2,104 +2,26 @@ using Discord.Commands; using Discord.WebSocket; using Microsoft.EntityFrameworkCore; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; -using NadekoBot.Services.Administration; using NadekoBot.Services.Database.Models; -using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Administration.Services; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class UserPunishCommands : NadekoSubmodule + public class UserPunishCommands : NadekoSubmodule { private readonly DbService _db; - private readonly MuteService _muteService; public UserPunishCommands(DbService db, MuteService muteService) { _db = db; - _muteService = muteService; - } - - private async Task InternalWarn(IGuild guild, ulong userId, string modName, string reason) - { - if (string.IsNullOrWhiteSpace(reason)) - reason = "-"; - - var guildId = guild.Id; - - var warn = new Warning() - { - UserId = userId, - GuildId = guildId, - Forgiven = false, - Reason = reason, - Moderator = modName, - }; - - int warnings = 1; - List ps; - using (var uow = _db.UnitOfWork) - { - ps = uow.GuildConfigs.For(guildId, set => set.Include(x => x.WarnPunishments)) - .WarnPunishments; - - warnings += uow.Warnings - .For(guildId, userId) - .Where(w => !w.Forgiven && w.UserId == userId) - .Count(); - - uow.Warnings.Add(warn); - - uow.Complete(); - } - - var p = ps.FirstOrDefault(x => x.Count == warnings); - - if (p != null) - { - var user = await guild.GetUserAsync(userId); - if (user == null) - return null; - switch (p.Punishment) - { - case PunishmentAction.Mute: - if (p.Time == 0) - await _muteService.MuteUser(user).ConfigureAwait(false); - else - await _muteService.TimedMute(user, TimeSpan.FromMinutes(p.Time)).ConfigureAwait(false); - break; - case PunishmentAction.Kick: - await user.KickAsync().ConfigureAwait(false); - break; - case PunishmentAction.Ban: - await guild.AddBanAsync(user).ConfigureAwait(false); - break; - case PunishmentAction.Softban: - await guild.AddBanAsync(user, 7).ConfigureAwait(false); - try - { - await guild.RemoveBanAsync(user).ConfigureAwait(false); - } - catch - { - await guild.RemoveBanAsync(user).ConfigureAwait(false); - } - break; - default: - break; - } - return p.Punishment; - } - - return null; } [NadekoCommand, Usage, Description, Aliases] @@ -109,14 +31,14 @@ namespace NadekoBot.Modules.Administration { try { - await (await user.CreateDMChannelAsync()).EmbedAsync(new EmbedBuilder().WithErrorColor() + await (await user.GetOrCreateDMChannelAsync()).EmbedAsync(new EmbedBuilder().WithErrorColor() .WithDescription(GetText("warned_on", Context.Guild.ToString())) .AddField(efb => efb.WithName(GetText("moderator")).WithValue(Context.User.ToString())) .AddField(efb => efb.WithName(GetText("reason")).WithValue(reason ?? "-"))) .ConfigureAwait(false); } catch { } - var punishment = await InternalWarn(Context.Guild, user.Id, Context.User.ToString(), reason).ConfigureAwait(false); + var punishment = await _service.Warn(Context.Guild, user.Id, Context.User.ToString(), reason).ConfigureAwait(false); if (punishment == null) { @@ -131,24 +53,27 @@ namespace NadekoBot.Modules.Administration [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.BanMembers)] + [Priority(2)] public Task Warnlog(int page, IGuildUser user) => Warnlog(page, user.Id); [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - [RequireUserPermission(GuildPermission.BanMembers)] + [Priority(3)] public Task Warnlog(IGuildUser user) - => Warnlog(user.Id); + => Context.User.Id == user.Id || ((IGuildUser)Context.User).GuildPermissions.BanMembers ? Warnlog(user.Id) : Task.CompletedTask; [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.BanMembers)] + [Priority(0)] public Task Warnlog(int page, ulong userId) => InternalWarnlog(userId, page - 1); [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.BanMembers)] + [Priority(1)] public Task Warnlog(ulong userId) => InternalWarnlog(userId, 0); @@ -191,6 +116,39 @@ namespace NadekoBot.Modules.Administration await Context.Channel.EmbedAsync(embed); } + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.BanMembers)] + public async Task WarnlogAll(int page = 1) + { + if (--page < 0) + return; + IGrouping[] warnings; + using (var uow = _db.UnitOfWork) + { + warnings = uow.Warnings.GetForGuild(Context.Guild.Id).GroupBy(x => x.UserId).ToArray(); + } + + await Context.Channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client, page, async (curPage) => + { + var ws = await Task.WhenAll(warnings.Skip(curPage * 15) + .Take(15) + .ToArray() + .Select(async x => + { + var all = x.Count(); + var forgiven = x.Count(y => y.Forgiven); + var total = all - forgiven; + return ((await Context.Guild.GetUserAsync(x.Key))?.ToString() ?? x.Key.ToString()) + $" | {total} ({all} - {forgiven})"; + })); + + return new EmbedBuilder() + .WithTitle(GetText("warnings_list")) + .WithDescription(string.Join("\n", ws)); + + }, warnings.Length / 15); + } + [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.BanMembers)] diff --git a/src/NadekoBot/Modules/Administration/Commands/VcRoleCommands.cs b/src/NadekoBot/Modules/Administration/VcRoleCommands.cs similarity index 93% rename from src/NadekoBot/Modules/Administration/Commands/VcRoleCommands.cs rename to src/NadekoBot/Modules/Administration/VcRoleCommands.cs index 5fe31422..ad0c7feb 100644 --- a/src/NadekoBot/Modules/Administration/Commands/VcRoleCommands.cs +++ b/src/NadekoBot/Modules/Administration/VcRoleCommands.cs @@ -2,28 +2,26 @@ using System.Linq; using Discord; using Discord.Commands; -using NadekoBot.Attributes; using System.Threading.Tasks; using Discord.WebSocket; using Microsoft.EntityFrameworkCore; +using NadekoBot.Common.Attributes; using NadekoBot.Extensions; +using NadekoBot.Modules.Administration.Services; using NadekoBot.Services; using NadekoBot.Services.Database.Models; -using NadekoBot.Services.Administration; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class VcRoleCommands : NadekoSubmodule + public class VcRoleCommands : NadekoSubmodule { - private readonly VcRoleService _service; private readonly DbService _db; - public VcRoleCommands(VcRoleService service, DbService db) + public VcRoleCommands(DbService db) { - _service = service; _db = db; } diff --git a/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs b/src/NadekoBot/Modules/Administration/VoicePlusTextCommands.cs similarity index 94% rename from src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs rename to src/NadekoBot/Modules/Administration/VoicePlusTextCommands.cs index 53f85510..ee063f8b 100644 --- a/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs +++ b/src/NadekoBot/Modules/Administration/VoicePlusTextCommands.cs @@ -1,28 +1,26 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; -using NadekoBot.Services.Administration; using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Administration.Services; namespace NadekoBot.Modules.Administration { public partial class Administration { [Group] - public class VoicePlusTextCommands : NadekoSubmodule + public class VoicePlusTextCommands : NadekoSubmodule { - private readonly VplusTService _service; private readonly DbService _db; - public VoicePlusTextCommands(VplusTService service, DbService db) + public VoicePlusTextCommands(DbService db) { - _service = service; _db = db; } diff --git a/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs b/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs deleted file mode 100644 index cb11dd29..00000000 --- a/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs +++ /dev/null @@ -1,239 +0,0 @@ -using Discord.Commands; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using Discord; -using NadekoBot.Attributes; -using NadekoBot.Services.Database.Models; -using NadekoBot.Extensions; -using NadekoBot.Services.ClashOfClans; - -namespace NadekoBot.Modules.ClashOfClans -{ - public class ClashOfClans : NadekoTopLevelModule - { - private readonly ClashOfClansService _service; - - public ClashOfClans(ClashOfClansService service) - { - _service = service; - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [RequireUserPermission(GuildPermission.ManageMessages)] - public async Task CreateWar(int size, [Remainder] string enemyClan = null) - { - if (string.IsNullOrWhiteSpace(enemyClan)) - return; - - if (size < 10 || size > 50 || size % 5 != 0) - { - await ReplyErrorLocalized("invalid_size").ConfigureAwait(false); - return; - } - List wars; - if (!_service.ClashWars.TryGetValue(Context.Guild.Id, out wars)) - { - wars = new List(); - if (!_service.ClashWars.TryAdd(Context.Guild.Id, wars)) - return; - } - - - var cw = await _service.CreateWar(enemyClan, size, Context.Guild.Id, Context.Channel.Id); - - wars.Add(cw); - await ReplyErrorLocalized("war_created", _service.ShortPrint(cw)).ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task StartWar([Remainder] string number = null) - { - int num = 0; - int.TryParse(number, out num); - - var warsInfo = _service.GetWarInfo(Context.Guild, num); - if (warsInfo == null) - { - await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false); - return; - } - var war = warsInfo.Item1[warsInfo.Item2]; - try - { - war.Start(); - await ReplyConfirmLocalized("war_started", _service.ShortPrint(war)).ConfigureAwait(false); - } - catch - { - await ReplyErrorLocalized("war_already_started", _service.ShortPrint(war)).ConfigureAwait(false); - } - _service.SaveWar(war); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task ListWar([Remainder] string number = null) - { - - // if number is null, print all wars in a short way - if (string.IsNullOrWhiteSpace(number)) - { - //check if there are any wars - List wars = null; - _service.ClashWars.TryGetValue(Context.Guild.Id, out wars); - if (wars == null || wars.Count == 0) - { - await ReplyErrorLocalized("no_active_wars").ConfigureAwait(false); - return; - } - - var sb = new StringBuilder(); - sb.AppendLine("**-------------------------**"); - for (var i = 0; i < wars.Count; i++) - { - sb.AppendLine($"**#{i + 1}.** `{GetText("enemy")}:` **{wars[i].EnemyClan}**"); - sb.AppendLine($"\t\t`{GetText("size")}:` **{wars[i].Size} v {wars[i].Size}**"); - sb.AppendLine("**-------------------------**"); - } - await Context.Channel.SendConfirmAsync(GetText("list_active_wars"), sb.ToString()).ConfigureAwait(false); - return; - } - var num = 0; - int.TryParse(number, out num); - //if number is not null, print the war needed - var warsInfo = _service.GetWarInfo(Context.Guild, num); - if (warsInfo == null) - { - await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false); - return; - } - var war = warsInfo.Item1[warsInfo.Item2]; - await Context.Channel.SendConfirmAsync(_service.Localize(war, "info_about_war", $"`{war.EnemyClan}` ({war.Size} v {war.Size})"), _service.ToPrettyString(war)).ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task BaseCall(int number, int baseNumber, [Remainder] string other_name = null) - { - var warsInfo = _service.GetWarInfo(Context.Guild, number); - if (warsInfo == null || warsInfo.Item1.Count == 0) - { - await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false); - return; - } - var usr = - string.IsNullOrWhiteSpace(other_name) ? - Context.User.Username : - other_name; - try - { - var war = warsInfo.Item1[warsInfo.Item2]; - _service.Call(war, usr, baseNumber - 1); - _service.SaveWar(war); - await ConfirmLocalized("claimed_base", Format.Bold(usr.ToString()), baseNumber, _service.ShortPrint(war)).ConfigureAwait(false); - } - catch (Exception ex) - { - await Context.Channel.SendErrorAsync(ex.Message).ConfigureAwait(false); - } - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task CallFinish1(int number, int baseNumber = 0) - { - await FinishClaim(number, baseNumber - 1, 1); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task CallFinish2(int number, int baseNumber = 0) - { - await FinishClaim(number, baseNumber - 1, 2); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task CallFinish(int number, int baseNumber = 0) - { - await FinishClaim(number, baseNumber - 1); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task EndWar(int number) - { - var warsInfo = _service.GetWarInfo(Context.Guild, number); - if (warsInfo == null) - { - await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false); - return; - } - var war = warsInfo.Item1[warsInfo.Item2]; - war.End(); - _service.SaveWar(war); - await ReplyConfirmLocalized("war_ended", _service.ShortPrint(warsInfo.Item1[warsInfo.Item2])).ConfigureAwait(false); - - warsInfo.Item1.RemoveAt(warsInfo.Item2); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task Unclaim(int number, [Remainder] string otherName = null) - { - var warsInfo = _service.GetWarInfo(Context.Guild, number); - if (warsInfo == null || warsInfo.Item1.Count == 0) - { - await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false); - return; - } - var usr = - string.IsNullOrWhiteSpace(otherName) ? - Context.User.Username : - otherName; - try - { - var war = warsInfo.Item1[warsInfo.Item2]; - var baseNumber = _service.Uncall(war, usr); - _service.SaveWar(war); - await ReplyConfirmLocalized("base_unclaimed", usr, baseNumber + 1, _service.ShortPrint(war)).ConfigureAwait(false); - } - catch (Exception ex) - { - await Context.Channel.SendErrorAsync(ex.Message).ConfigureAwait(false); - } - } - - private async Task FinishClaim(int number, int baseNumber, int stars = 3) - { - var warInfo = _service.GetWarInfo(Context.Guild, number); - if (warInfo == null || warInfo.Item1.Count == 0) - { - await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false); - return; - } - var war = warInfo.Item1[warInfo.Item2]; - try - { - if (baseNumber == -1) - { - baseNumber = _service.FinishClaim(war, Context.User.Username, stars); - _service.SaveWar(war); - } - else - { - _service.FinishClaim(war, baseNumber, stars); - } - await ReplyConfirmLocalized("base_destroyed", baseNumber + 1, _service.ShortPrint(war)).ConfigureAwait(false); - } - catch (Exception ex) - { - await Context.Channel.SendErrorAsync($"🔰 {ex.Message}").ConfigureAwait(false); - } - } - } -} \ No newline at end of file diff --git a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs index 5dac3b9c..33af0699 100644 --- a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs +++ b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs @@ -2,29 +2,27 @@ using System.Threading.Tasks; using Discord.Commands; using NadekoBot.Services; -using NadekoBot.Attributes; using NadekoBot.Services.Database.Models; using Discord; using NadekoBot.Extensions; using Discord.WebSocket; using System; -using NadekoBot.Services.CustomReactions; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.CustomReactions.Services; namespace NadekoBot.Modules.CustomReactions { - public class CustomReactions : NadekoTopLevelModule + public class CustomReactions : NadekoTopLevelModule { private readonly IBotCredentials _creds; private readonly DbService _db; - private readonly CustomReactionsService _crs; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public CustomReactions(IBotCredentials creds, DbService db, CustomReactionsService crs, - DiscordShardedClient client) + public CustomReactions(IBotCredentials creds, DbService db, + DiscordSocketClient client) { _creds = creds; _db = db; - _crs = crs; _client = client; } @@ -60,12 +58,11 @@ namespace NadekoBot.Modules.CustomReactions if (channel == null) { - Array.Resize(ref _crs.GlobalReactions, _crs.GlobalReactions.Length + 1); - _crs.GlobalReactions[_crs.GlobalReactions.Length - 1] = cr; + await _service.AddGcr(cr).ConfigureAwait(false); } else { - _crs.GuildReactions.AddOrUpdate(Context.Guild.Id, + _service.GuildReactions.AddOrUpdate(Context.Guild.Id, new CustomReaction[] { cr }, (k, old) => { @@ -79,21 +76,21 @@ namespace NadekoBot.Modules.CustomReactions .WithTitle(GetText("new_cust_react")) .WithDescription($"#{cr.Id}") .AddField(efb => efb.WithName(GetText("trigger")).WithValue(key)) - .AddField(efb => efb.WithName(GetText("response")).WithValue(message)) + .AddField(efb => efb.WithName(GetText("response")).WithValue(message.Length > 1024 ? GetText("redacted_too_long") : message)) ).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] - [Priority(0)] + [Priority(1)] public async Task ListCustReact(int page = 1) { if (--page < 0 || page > 999) return; CustomReaction[] customReactions; if (Context.Guild == null) - customReactions = _crs.GlobalReactions.Where(cr => cr != null).ToArray(); + customReactions = _service.GlobalReactions.Where(cr => cr != null).ToArray(); else - customReactions = _crs.GuildReactions.GetOrAdd(Context.Guild.Id, Array.Empty()).Where(cr => cr != null).ToArray(); + customReactions = _service.GuildReactions.GetOrAdd(Context.Guild.Id, Array.Empty()).Where(cr => cr != null).ToArray(); if (customReactions == null || !customReactions.Any()) { @@ -130,14 +127,14 @@ namespace NadekoBot.Modules.CustomReactions } [NadekoCommand, Usage, Description, Aliases] - [Priority(1)] + [Priority(0)] public async Task ListCustReact(All x) { CustomReaction[] customReactions; if (Context.Guild == null) - customReactions = _crs.GlobalReactions.Where(cr => cr != null).ToArray(); + customReactions = _service.GlobalReactions.Where(cr => cr != null).ToArray(); else - customReactions = _crs.GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray(); + customReactions = _service.GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray(); if (customReactions == null || !customReactions.Any()) { @@ -155,7 +152,7 @@ namespace NadekoBot.Modules.CustomReactions if (Context.Guild == null) // its a private one, just send back await Context.Channel.SendFileAsync(txtStream, "customreactions.txt", GetText("list_all")).ConfigureAwait(false); else - await ((IGuildUser)Context.User).SendFileAsync(txtStream, "customreactions.txt", GetText("list_all")).ConfigureAwait(false); + await ((IGuildUser)Context.User).SendFileAsync(txtStream, "customreactions.txt", GetText("list_all"), false).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -165,9 +162,9 @@ namespace NadekoBot.Modules.CustomReactions return; CustomReaction[] customReactions; if (Context.Guild == null) - customReactions = _crs.GlobalReactions.Where(cr => cr != null).ToArray(); + customReactions = _service.GlobalReactions.Where(cr => cr != null).ToArray(); else - customReactions = _crs.GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray(); + customReactions = _service.GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray(); if (customReactions == null || !customReactions.Any()) { @@ -197,9 +194,9 @@ namespace NadekoBot.Modules.CustomReactions { CustomReaction[] customReactions; if (Context.Guild == null) - customReactions = _crs.GlobalReactions; + customReactions = _service.GlobalReactions; else - customReactions = _crs.GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }); + customReactions = _service.GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }); var found = customReactions.FirstOrDefault(cr => cr?.Id == id); @@ -239,14 +236,13 @@ namespace NadekoBot.Modules.CustomReactions if ((toDelete.GuildId == null || toDelete.GuildId == 0) && Context.Guild == null) { uow.CustomReactions.Remove(toDelete); - //todo 91 i can dramatically improve performance of this, if Ids are ordered. - _crs.GlobalReactions = _crs.GlobalReactions.Where(cr => cr?.Id != toDelete.Id).ToArray(); + await _service.DelGcr(toDelete.Id); success = true; } else if ((toDelete.GuildId != null && toDelete.GuildId != 0) && Context.Guild.Id == toDelete.GuildId) { uow.CustomReactions.Remove(toDelete); - _crs.GuildReactions.AddOrUpdate(Context.Guild.Id, new CustomReaction[] { }, (key, old) => + _service.GuildReactions.AddOrUpdate(Context.Guild.Id, new CustomReaction[] { }, (key, old) => { return old.Where(cr => cr?.Id != toDelete.Id).ToArray(); }); @@ -271,6 +267,57 @@ namespace NadekoBot.Modules.CustomReactions } } + [NadekoCommand, Usage, Description, Aliases] + public async Task CrCa(int id) + { + if ((Context.Guild == null && !_creds.IsOwner(Context.User)) || + (Context.Guild != null && !((IGuildUser)Context.User).GuildPermissions.Administrator)) + { + await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false); + return; + } + + CustomReaction[] reactions = new CustomReaction[0]; + + if (Context.Guild == null) + reactions = _service.GlobalReactions; + else + { + _service.GuildReactions.TryGetValue(Context.Guild.Id, out reactions); + } + if (reactions.Any()) + { + var reaction = reactions.FirstOrDefault(x => x.Id == id); + + if (reaction == null) + { + await ReplyErrorLocalized("no_found_id").ConfigureAwait(false); + return; + } + + var setValue = reaction.ContainsAnywhere = !reaction.ContainsAnywhere; + + using (var uow = _db.UnitOfWork) + { + uow.CustomReactions.Get(id).ContainsAnywhere = setValue; + uow.Complete(); + } + + if (setValue) + { + await ReplyConfirmLocalized("crca_enabled", Format.Code(reaction.Id.ToString())).ConfigureAwait(false); + } + else + { + await ReplyConfirmLocalized("crca_disabled", Format.Code(reaction.Id.ToString())).ConfigureAwait(false); + } + } + else + { + await ReplyErrorLocalized("no_found").ConfigureAwait(false); + } + } + [NadekoCommand, Usage, Description, Aliases] public async Task CrDm(int id) { @@ -284,10 +331,10 @@ namespace NadekoBot.Modules.CustomReactions CustomReaction[] reactions = new CustomReaction[0]; if (Context.Guild == null) - reactions = _crs.GlobalReactions; + reactions = _service.GlobalReactions; else { - _crs.GuildReactions.TryGetValue(Context.Guild.Id, out reactions); + _service.GuildReactions.TryGetValue(Context.Guild.Id, out reactions); } if (reactions.Any()) { @@ -335,10 +382,10 @@ namespace NadekoBot.Modules.CustomReactions CustomReaction[] reactions = new CustomReaction[0]; if (Context.Guild == null) - reactions = _crs.GlobalReactions; + reactions = _service.GlobalReactions; else { - _crs.GuildReactions.TryGetValue(Context.Guild.Id, out reactions); + _service.GuildReactions.TryGetValue(Context.Guild.Id, out reactions); } if (reactions.Any()) { @@ -379,13 +426,12 @@ namespace NadekoBot.Modules.CustomReactions { if (string.IsNullOrWhiteSpace(trigger)) { - _crs.ClearStats(); + _service.ClearStats(); await ReplyConfirmLocalized("all_stats_cleared").ConfigureAwait(false); } else { - uint throwaway; - if (_crs.ReactionStats.TryRemove(trigger, out throwaway)) + if (_service.ReactionStats.TryRemove(trigger, out _)) { await ReplyErrorLocalized("stats_cleared", Format.Bold(trigger)).ConfigureAwait(false); } @@ -401,7 +447,7 @@ namespace NadekoBot.Modules.CustomReactions { if (--page < 0) return; - var ordered = _crs.ReactionStats.OrderByDescending(x => x.Value).ToArray(); + var ordered = _service.ReactionStats.OrderByDescending(x => x.Value).ToArray(); if (!ordered.Any()) return; var lastPage = ordered.Length / 9; diff --git a/src/NadekoBot/Modules/CustomReactions/Extensions/Extensions.cs b/src/NadekoBot/Modules/CustomReactions/Extensions/Extensions.cs new file mode 100644 index 00000000..f24effb1 --- /dev/null +++ b/src/NadekoBot/Modules/CustomReactions/Extensions/Extensions.cs @@ -0,0 +1,148 @@ +using AngleSharp; +using AngleSharp.Dom.Html; +using Discord; +using Discord.WebSocket; +using NadekoBot.Extensions; +using NadekoBot.Modules.CustomReactions.Services; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using NadekoBot.Common; +using NadekoBot.Common.Replacements; + +namespace NadekoBot.Modules.CustomReactions.Extensions +{ + public static class Extensions + { + private static readonly Regex imgRegex = new Regex("%(img|image):(?.*?)%", RegexOptions.Compiled); + + private static readonly NadekoRandom rng = new NadekoRandom(); + + public static Dictionary>> regexPlaceholders = new Dictionary>>() + { + { imgRegex, async (match) => { + var tag = match.Groups["tag"].ToString(); + if(string.IsNullOrWhiteSpace(tag)) + return ""; + + var fullQueryLink = $"http://imgur.com/search?q={ tag }"; + var config = Configuration.Default.WithDefaultLoader(); + var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); + + var elems = document.QuerySelectorAll("a.image-list-link").ToArray(); + + if (!elems.Any()) + return ""; + + var img = (elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Length))?.Children?.FirstOrDefault() as IHtmlImageElement); + + if (img?.Source == null) + return ""; + + return " " + img.Source.Replace("b.", ".") + " "; + } } + }; + + private static string ResolveTriggerString(this string str, IUserMessage ctx, DiscordSocketClient client) + { + var rep = new ReplacementBuilder() + .WithUser(ctx.Author) + .WithClient(client) + .Build(); + + str = rep.Replace(str.ToLowerInvariant()); + + return str; + } + + private static async Task ResolveResponseStringAsync(this string str, IUserMessage ctx, DiscordSocketClient client, string resolvedTrigger, bool containsAnywhere) + { + var substringIndex = resolvedTrigger.Length; + if (containsAnywhere) + { + var pos = ctx.Content.GetWordPosition(resolvedTrigger); + if (pos == WordPosition.Start) + substringIndex += 1; + else if (pos == WordPosition.End) + substringIndex = ctx.Content.Length; + else if (pos == WordPosition.Middle) + substringIndex += ctx.Content.IndexOf(resolvedTrigger); + } + + var rep = new ReplacementBuilder() + .WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild, client) + .WithOverride("%target%", () => ctx.Content.Substring(substringIndex).Trim()) + .Build(); + + str = rep.Replace(str); + + foreach (var ph in regexPlaceholders) + { + str = await ph.Key.ReplaceAsync(str, ph.Value); + } + return str; + } + + public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client) + => cr.Trigger.ResolveTriggerString(ctx, client); + + public static Task ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client, bool containsAnywhere) + => cr.Response.ResolveResponseStringAsync(ctx, client, cr.Trigger.ResolveTriggerString(ctx, client), containsAnywhere); + + public static async Task Send(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client, CustomReactionsService crs) + { + var channel = cr.DmResponse ? await ctx.Author.GetOrCreateDMChannelAsync() : ctx.Channel; + + crs.ReactionStats.AddOrUpdate(cr.Trigger, 1, (k, old) => ++old); + + if (CREmbed.TryParse(cr.Response, out CREmbed crembed)) + { + var trigger = cr.Trigger.ResolveTriggerString(ctx, client); + var substringIndex = trigger.Length; + if (cr.ContainsAnywhere) + { + var pos = ctx.Content.GetWordPosition(trigger); + if (pos == WordPosition.Start) + substringIndex += 1; + else if (pos == WordPosition.End) + substringIndex = ctx.Content.Length; + else if (pos == WordPosition.Middle) + substringIndex += ctx.Content.IndexOf(trigger); + } + + var rep = new ReplacementBuilder() + .WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild, client) + .WithOverride("%target%", () => ctx.Content.Substring(substringIndex).Trim()) + .Build(); + + rep.Replace(crembed); + + return await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? ""); + } + return await channel.SendMessageAsync((await cr.ResponseWithContextAsync(ctx, client, cr.ContainsAnywhere)).SanitizeMentions()); + } + + public static WordPosition GetWordPosition(this string str, string word) + { + if (str.StartsWith(word + " ")) + return WordPosition.Start; + else if (str.EndsWith(" " + word)) + return WordPosition.End; + else if (str.Contains(" " + word + " ")) + return WordPosition.Middle; + else + return WordPosition.None; + } + } + + public enum WordPosition + { + None, + Start, + Middle, + End, + } +} diff --git a/src/NadekoBot/Services/CustomReactions/CustomReactionsService.cs b/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs similarity index 58% rename from src/NadekoBot/Services/CustomReactions/CustomReactionsService.cs rename to src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs index 206b0bd9..3f965dda 100644 --- a/src/NadekoBot/Services/CustomReactions/CustomReactionsService.cs +++ b/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs @@ -1,19 +1,25 @@ using Discord; using Discord.WebSocket; -using NadekoBot.DataStructures.ModuleBehaviors; using NadekoBot.Services.Database.Models; using NLog; using System.Collections.Concurrent; -using System.Diagnostics; using System.Linq; using System; using System.Threading.Tasks; -using NadekoBot.Services.Permissions; +using NadekoBot.Common; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Extensions; +using NadekoBot.Services.Database; +using NadekoBot.Services; +using NadekoBot.Modules.CustomReactions.Extensions; +using NadekoBot.Modules.Permissions.Common; +using NadekoBot.Modules.Permissions.Services; +using NadekoBot.Services.Impl; +using Newtonsoft.Json; -namespace NadekoBot.Services.CustomReactions +namespace NadekoBot.Modules.CustomReactions.Services { - public class CustomReactionsService : IEarlyBlockingExecutor + public class CustomReactionsService : IEarlyBlockingExecutor, INService { public CustomReaction[] GlobalReactions = new CustomReaction[] { }; public ConcurrentDictionary GuildReactions { get; } = new ConcurrentDictionary(); @@ -22,13 +28,16 @@ namespace NadekoBot.Services.CustomReactions private readonly Logger _log; private readonly DbService _db; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly PermissionService _perms; private readonly CommandHandler _cmd; - private readonly BotConfig _bc; + private readonly IBotConfigProvider _bc; + private readonly NadekoStrings _strings; + private readonly IDataCache _cache; - public CustomReactionsService(PermissionService perms, DbService db, - DiscordShardedClient client, CommandHandler cmd, BotConfig bc) + public CustomReactionsService(PermissionService perms, DbService db, NadekoStrings strings, + DiscordSocketClient client, CommandHandler cmd, IBotConfigProvider bc, IUnitOfWork uow, + IDataCache cache) { _log = LogManager.GetCurrentClassLogger(); _db = db; @@ -36,16 +45,37 @@ namespace NadekoBot.Services.CustomReactions _perms = perms; _cmd = cmd; _bc = bc; + _strings = strings; + _cache = cache; - var sw = Stopwatch.StartNew(); - using (var uow = _db.UnitOfWork) + var sub = _cache.Redis.GetSubscriber(); + sub.Subscribe(_client.CurrentUser.Id + "_gcr.added", (ch, msg) => { - var items = uow.CustomReactions.GetAll(); - GuildReactions = new ConcurrentDictionary(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray())); - GlobalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray(); - } - sw.Stop(); - _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); + Array.Resize(ref GlobalReactions, GlobalReactions.Length + 1); + GlobalReactions[GlobalReactions.Length - 1] = JsonConvert.DeserializeObject(msg); + }, StackExchange.Redis.CommandFlags.FireAndForget); + sub.Subscribe(_client.CurrentUser.Id + "_gcr.deleted", (ch, msg) => + { + var id = int.Parse(msg); + GlobalReactions = GlobalReactions.Where(cr => cr?.Id != id).ToArray(); + }, StackExchange.Redis.CommandFlags.FireAndForget); + + var items = uow.CustomReactions.GetAll(); + + GuildReactions = new ConcurrentDictionary(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray())); + GlobalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray(); + } + + public Task AddGcr(CustomReaction cr) + { + var sub = _cache.Redis.GetSubscriber(); + return sub.PublishAsync(_client.CurrentUser.Id + "_gcr.added", JsonConvert.SerializeObject(cr)); + } + + public Task DelGcr(int id) + { + var sub = _cache.Redis.GetSubscriber(); + return sub.PublishAsync(_client.CurrentUser.Id + "_gcr.deleted", id); } public void ClearStats() => ReactionStats.Clear(); @@ -68,7 +98,11 @@ namespace NadekoBot.Services.CustomReactions var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%"); var trigger = cr.TriggerWithContext(umsg, _client).Trim().ToLowerInvariant(); - return ((hasTarget && content.StartsWith(trigger + " ")) || (_bc.CustomReactionsStartWith && content.StartsWith(trigger + " ")) || content == trigger); + return ((cr.ContainsAnywhere && + (content.GetWordPosition(trigger) != WordPosition.None)) + || (hasTarget && content.StartsWith(trigger + " ")) + || (_bc.BotConfig.CustomReactionsStartWith && content.StartsWith(trigger + " ")) + || content == trigger); }).ToArray(); if (rs.Length != 0) @@ -89,7 +123,7 @@ namespace NadekoBot.Services.CustomReactions return false; var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%"); var trigger = cr.TriggerWithContext(umsg, _client).Trim().ToLowerInvariant(); - return ((hasTarget && content.StartsWith(trigger + " ")) || (_bc.CustomReactionsStartWith && content.StartsWith(trigger + " ")) || content == trigger); + return ((hasTarget && content.StartsWith(trigger + " ")) || (_bc.BotConfig.CustomReactionsStartWith && content.StartsWith(trigger + " ")) || content == trigger); }).ToArray(); if (grs.Length == 0) return null; @@ -98,7 +132,7 @@ namespace NadekoBot.Services.CustomReactions return greaction; } - public async Task TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage msg) + public async Task TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg) { // maybe this message is a custom reaction var cr = await Task.Run(() => TryGetCustomReaction(msg)).ConfigureAwait(false); @@ -114,7 +148,7 @@ namespace NadekoBot.Services.CustomReactions { if (pc.Verbose) { - var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg)}** is preventing this action."; + var returnMsg = _strings.GetText("trigger", guild.Id, "Permissions".ToLowerInvariant(), index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild))); try { await msg.Channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { } _log.Info(returnMsg); } diff --git a/src/NadekoBot/Modules/Gambling/AnimalRacingCommands.cs b/src/NadekoBot/Modules/Gambling/AnimalRacingCommands.cs new file mode 100644 index 00000000..2b9ee3ce --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/AnimalRacingCommands.cs @@ -0,0 +1,151 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Extensions; +using NadekoBot.Services; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions; +using NadekoBot.Modules.Gambling.Common.AnimalRacing; + +namespace NadekoBot.Modules.Gambling +{ + public partial class Gambling + { + [Group] + public class AnimalRacingCommands : NadekoSubmodule + { + private readonly IBotConfigProvider _bc; + private readonly CurrencyService _cs; + private readonly DiscordSocketClient _client; + + + public static ConcurrentDictionary AnimalRaces { get; } = new ConcurrentDictionary(); + + public AnimalRacingCommands(IBotConfigProvider bc, CurrencyService cs, DiscordSocketClient client) + { + _bc = bc; + _cs = cs; + _client = client; + } + + private IUserMessage raceMessage = null; + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public Task Race() + { + var ar = new AnimalRace(_cs, _bc.BotConfig.RaceAnimals.Shuffle().ToArray()); + if (!AnimalRaces.TryAdd(Context.Guild.Id, ar)) + return Context.Channel.SendErrorAsync(GetText("animal_race"), GetText("animal_race_already_started")); + ar.Initialize(); + + ar.OnStartingFailed += Ar_OnStartingFailed; + ar.OnStateUpdate += Ar_OnStateUpdate; + ar.OnEnded += Ar_OnEnded; + ar.OnStarted += Ar_OnStarted; + + return Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting"), + footer: GetText("animal_race_join_instr", Prefix)); + } + + private Task Ar_OnStarted(AnimalRace race) + { + if(race.Users.Length == race.MaxUsers) + return Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_full")); + else + return Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting_with_x", race.Users.Length)); + } + + private Task Ar_OnEnded(AnimalRace race) + { + AnimalRaces.TryRemove(Context.Guild.Id, out _); + var winner = race.FinishedUsers[0]; + if (race.FinishedUsers[0].Bet > 0) + { + return Context.Channel.SendConfirmAsync(GetText("animal_race"), + GetText("animal_race_won_money", Format.Bold(winner.Username), + winner.Animal.Icon, (race.FinishedUsers[0].Bet * (race.Users.Length - 1)) + _bc.BotConfig.CurrencySign)); + } + else + { + return Context.Channel.SendConfirmAsync(GetText("animal_race"), + GetText("animal_race_won", Format.Bold(winner.Username), winner.Animal.Icon)); + } + } + + private async Task Ar_OnStateUpdate(AnimalRace race) + { + var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚| +{String.Join("\n", race.Users.Select(p => + { + var index = race.FinishedUsers.IndexOf(p); + var extra = (index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}"); + return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}"; + }))} +|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|"; + + if (raceMessage == null) + raceMessage = await Context.Channel.SendConfirmAsync(text) + .ConfigureAwait(false); + else + await raceMessage.ModifyAsync(x => x.Embed = new EmbedBuilder() + .WithTitle(GetText("animal_race")) + .WithDescription(text) + .WithOkColor() + .Build()) + .ConfigureAwait(false); + } + + private Task Ar_OnStartingFailed(AnimalRace race) + { + AnimalRaces.TryRemove(Context.Guild.Id, out _); + return ReplyErrorLocalized("animal_race_failed"); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task JoinRace(int amount = 0) + { + if (!AnimalRaces.TryGetValue(Context.Guild.Id, out var ar)) + { + await ReplyErrorLocalized("race_not_exist").ConfigureAwait(false); + return; + } + try + { + var user = await ar.JoinRace(Context.User.Id, Context.User.ToString(), amount) + .ConfigureAwait(false); + if (amount > 0) + await Context.Channel.SendConfirmAsync(GetText("animal_race_join_bet", Context.User.Mention, user.Animal.Icon, amount + _bc.BotConfig.CurrencySign)).ConfigureAwait(false); + else + await Context.Channel.SendConfirmAsync(GetText("animal_race_join", Context.User.Mention, user.Animal.Icon)).ConfigureAwait(false); + } + catch (ArgumentOutOfRangeException) + { + //ignore if user inputed an invalid amount + } + catch (AlreadyJoinedException) + { + // just ignore this + } + catch (AlreadyStartedException) + { + //ignore + } + catch (AnimalRaceFullException) + { + await Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_full")) + .ConfigureAwait(false); + } + catch (NotEnoughFundsException) + { + await Context.Channel.SendErrorAsync(GetText("not_enough", _bc.BotConfig.CurrencySign)).ConfigureAwait(false); + } + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs b/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs deleted file mode 100644 index 4db985e5..00000000 --- a/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs +++ /dev/null @@ -1,362 +0,0 @@ -using Discord; -using Discord.Commands; -using Discord.WebSocket; -using NadekoBot.Attributes; -using NadekoBot.Extensions; -using NadekoBot.Services; -using NadekoBot.Services.Database.Models; -using NLog; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Gambling -{ - public partial class Gambling - { - [Group] - public class AnimalRacing : NadekoSubmodule - { - private readonly BotConfig _bc; - private readonly CurrencyService _cs; - private readonly DiscordShardedClient _client; - - - public static ConcurrentDictionary AnimalRaces { get; } = new ConcurrentDictionary(); - - public AnimalRacing(BotConfig bc, CurrencyService cs, DiscordShardedClient client) - { - _bc = bc; - _cs = cs; - _client = client; - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task Race() - { - var ar = new AnimalRace(Context.Guild.Id, (ITextChannel)Context.Channel, Prefix, - _bc, _cs, _client,_localization, _strings); - - if (ar.Fail) - await ReplyErrorLocalized("race_failed_starting").ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task JoinRace(int amount = 0) - { - - if (amount < 0) - amount = 0; - - - AnimalRace ar; - if (!AnimalRaces.TryGetValue(Context.Guild.Id, out ar)) - { - await ReplyErrorLocalized("race_not_exist").ConfigureAwait(false); - return; - } - await ar.JoinRace(Context.User as IGuildUser, amount); - } - - //todo 85 needs to be completely isolated, shouldn't use any services in the constructor, - //then move the rest either to the module itself, or the service - public class AnimalRace - { - - private ConcurrentQueue animals { get; } - - public bool Fail { get; set; } - - private readonly List _participants = new List(); - private readonly ulong _serverId; - private int _messagesSinceGameStarted; - private readonly string _prefix; - - private readonly Logger _log; - - private readonly ITextChannel _raceChannel; - private readonly BotConfig _bc; - private readonly CurrencyService _cs; - private readonly DiscordShardedClient _client; - private readonly ILocalization _localization; - private readonly NadekoStrings _strings; - - public bool Started { get; private set; } - - public AnimalRace(ulong serverId, ITextChannel channel, string prefix, BotConfig bc, - CurrencyService cs, DiscordShardedClient client, ILocalization localization, - NadekoStrings strings) - { - _prefix = prefix; - _bc = bc; - _cs = cs; - _log = LogManager.GetCurrentClassLogger(); - _serverId = serverId; - _raceChannel = channel; - _client = client; - _localization = localization; - _strings = strings; - - if (!AnimalRaces.TryAdd(serverId, this)) - { - Fail = true; - return; - } - - animals = new ConcurrentQueue(_bc.RaceAnimals.Select(ra => ra.Icon).Shuffle()); - - - var cancelSource = new CancellationTokenSource(); - var token = cancelSource.Token; - var fullgame = CheckForFullGameAsync(token); - Task.Run(async () => - { - try - { - try - { - await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting"), - footer: GetText("animal_race_join_instr", _prefix)); - } - catch (Exception ex) - { - _log.Warn(ex); - } - var t = await Task.WhenAny(Task.Delay(20000, token), fullgame); - Started = true; - cancelSource.Cancel(); - if (t == fullgame) - { - try { await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_full") ); } catch (Exception ex) { _log.Warn(ex); } - } - else if (_participants.Count > 1) - { - try { await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting_with_x", _participants.Count)); } catch (Exception ex) { _log.Warn(ex); } - } - else - { - try { await _raceChannel.SendErrorAsync(GetText("animal_race"), GetText("animal_race_failed")); } catch (Exception ex) { _log.Warn(ex); } - var p = _participants.FirstOrDefault(); - - if (p != null && p.AmountBet > 0) - await _cs.AddAsync(p.User, "BetRace", p.AmountBet, false).ConfigureAwait(false); - End(); - return; - } - await Task.Run(StartRace); - End(); - } - catch { try { End(); } catch { } } - }); - } - - private void End() - { - AnimalRace throwaway; - AnimalRaces.TryRemove(_serverId, out throwaway); - } - - private async Task StartRace() - { - var rng = new NadekoRandom(); - Participant winner = null; - IUserMessage msg = null; - var place = 1; - try - { - _client.MessageReceived += Client_MessageReceived; - - while (!_participants.All(p => p.Total >= 60)) - { - //update the state - _participants.ForEach(p => - { - p.Total += 1 + rng.Next(0, 10); - }); - - - _participants - .OrderByDescending(p => p.Total) - .ForEach(p => - { - if (p.Total > 60) - { - if (winner == null) - { - winner = p; - } - p.Total = 60; - if (p.Place == 0) - p.Place = place++; - } - }); - - - //draw the state - - var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚| -{String.Join("\n", _participants.Select(p => $"{(int)(p.Total / 60f * 100),-2}%|{p.ToString()}"))} -|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|"; - if (msg == null || _messagesSinceGameStarted >= 10) // also resend the message if channel was spammed - { - if (msg != null) - try { await msg.DeleteAsync(); } catch { } - _messagesSinceGameStarted = 0; - try { msg = await _raceChannel.SendMessageAsync(text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } - } - else - { - try { await msg.ModifyAsync(m => m.Content = text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } - } - - await Task.Delay(2500); - } - } - catch - { - // ignored - } - finally - { - _client.MessageReceived -= Client_MessageReceived; - } - - if (winner != null) - { - if (winner.AmountBet > 0) - { - var wonAmount = winner.AmountBet * (_participants.Count - 1); - - await _cs.AddAsync(winner.User, "Won a Race", wonAmount, true) - .ConfigureAwait(false); - await _raceChannel.SendConfirmAsync(GetText("animal_race"), - Format.Bold(GetText("animal_race_won_money", winner.User.Mention, - winner.Animal, wonAmount + _bc.CurrencySign))) - .ConfigureAwait(false); - } - else - { - await _raceChannel.SendConfirmAsync(GetText("animal_race"), - Format.Bold(GetText("animal_race_won", winner.User.Mention, winner.Animal))).ConfigureAwait(false); - } - } - - } - - private Task Client_MessageReceived(SocketMessage imsg) - { - var _ = Task.Run(() => - { - var msg = imsg as SocketUserMessage; - if (msg == null) - return Task.CompletedTask; - if ((msg.Author.Id == _client.CurrentUser.Id) || !(imsg.Channel is ITextChannel) || imsg.Channel != _raceChannel) - return Task.CompletedTask; - Interlocked.Increment(ref _messagesSinceGameStarted); - return Task.CompletedTask; - }); - return Task.CompletedTask; - } - - private async Task CheckForFullGameAsync(CancellationToken cancelToken) - { - while (animals.Count > 0) - { - await Task.Delay(100, cancelToken); - } - } - - public async Task JoinRace(IGuildUser u, int amount = 0) - { - string animal; - if (!animals.TryDequeue(out animal)) - { - await _raceChannel.SendErrorAsync(GetText("animal_race_no_race")).ConfigureAwait(false); - return; - } - var p = new Participant(u, animal, amount); - if (_participants.Contains(p)) - { - await _raceChannel.SendErrorAsync(GetText("animal_race_already_in")).ConfigureAwait(false); - return; - } - if (Started) - { - await _raceChannel.SendErrorAsync(GetText("animal_race_already_started")).ConfigureAwait(false); - return; - } - if (amount > 0) - if (!await _cs.RemoveAsync(u, "BetRace", amount, false).ConfigureAwait(false)) - { - await _raceChannel.SendErrorAsync(GetText("not_enough", _bc.CurrencySign)).ConfigureAwait(false); - return; - } - _participants.Add(p); - string confStr; - if (amount > 0) - confStr = GetText("animal_race_join_bet", u.Mention, p.Animal, amount + _bc.CurrencySign); - else - confStr = GetText("animal_race_join", u.Mention, p.Animal); - await _raceChannel.SendConfirmAsync(GetText("animal_race"), Format.Bold(confStr)).ConfigureAwait(false); - } - - private string GetText(string text) - => _strings.GetText(text, - _localization.GetCultureInfo(_raceChannel.Guild), - typeof(Gambling).Name.ToLowerInvariant()); - - private string GetText(string text, params object[] replacements) - => _strings.GetText(text, - _localization.GetCultureInfo(_raceChannel.Guild), - typeof(Gambling).Name.ToLowerInvariant(), - replacements); - } - - public class Participant - { - public IGuildUser User { get; } - public string Animal { get; } - public int AmountBet { get; } - - public float Coeff { get; set; } - public int Total { get; set; } - - public int Place { get; set; } - - public Participant(IGuildUser u, string a, int amount) - { - User = u; - Animal = a; - AmountBet = amount; - } - - public override int GetHashCode() => User.GetHashCode(); - - public override bool Equals(object obj) - { - var p = obj as Participant; - return p != null && p.User == User; - } - - public override string ToString() - { - var str = new string('‣', Total) + Animal; - if (Place == 0) - return str; - - str += $"`#{Place}`"; - - if (Place == 1) - str += "🏆"; - - return str; - } - } - } - } -} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Commands/Lucky7Commands.cs b/src/NadekoBot/Modules/Gambling/Commands/Lucky7Commands.cs deleted file mode 100644 index 725fe7c0..00000000 --- a/src/NadekoBot/Modules/Gambling/Commands/Lucky7Commands.cs +++ /dev/null @@ -1,185 +0,0 @@ -namespace NadekoBot.Modules.Gambling -{ - //public partial class Gambling - //{ - // [Group] - // public class Lucky7Commands : NadekoSubmodule - // { - // [NadekoCommand, Usage, Description, Aliases] - // [RequireContext(ContextType.Guild)] - // [OwnerOnly] - // public async Task Lucky7Test(uint tests) - // { - // if (tests <= 0) - // return; - - // var dict = new Dictionary(); - // var totalWon = 0; - // for (var i = 0; i < tests; i++) - // { - // var g = new Lucky7Game(10); - // while (!g.Ended) - // { - // if (g.CurrentPosition == 0) - // g.Stay(); - // else - // g.Move(); - // } - // totalWon += (int)(g.CurrentMultiplier * g.Bet); - // if (!dict.ContainsKey(g.CurrentMultiplier)) - // dict.Add(g.CurrentMultiplier, 0); - - // dict[g.CurrentMultiplier] ++; - - // } - - // await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() - // .WithTitle("Move Or Stay test") - // .WithDescription(string.Join("\n", - // dict.Select(x => $"x{x.Key} occured {x.Value} times {x.Value * 1.0f / tests * 100:F2}%"))) - // .WithFooter( - // efb => efb.WithText($"Total Bet: {tests * 10} | Payout: {totalWon} | {totalWon *1.0f / tests * 10}%"))); - // } - - // private static readonly ConcurrentDictionary _games = - // new ConcurrentDictionary(); - - // [NadekoCommand, Usage, Description, Aliases] - // [RequireContext(ContextType.Guild)] - // public async Task Lucky7(int bet) - // { - // if (bet < 4) - // return; - // var game = new Lucky7Game(bet); - // if (!_games.TryAdd(Context.User.Id, game)) - // { - // await ReplyAsync("You're already betting on move or stay.").ConfigureAwait(false); - // return; - // } - - // if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User, "MoveOrStay bet", bet, false)) - // { - // _games.TryRemove(Context.User.Id, out game); - // await ReplyConfirmLocalized("not_enough", CurrencySign).ConfigureAwait(false); - // return; - // } - // await Context.Channel.EmbedAsync(GetGameState(game), - // string.Format("{0} rolled {1}.", Context.User, game.Rolled)).ConfigureAwait(false); - // } - - // public enum MoveOrStay - // { - // Move = 1, - // M = 1, - // Stay = 2, - // S = 2 - // } - - // [NadekoCommand, Usage, Description, Aliases] - // [RequireContext(ContextType.Guild)] - // public async Task Lucky7(MoveOrStay action) - // { - // Lucky7Game game; - // if (!_games.TryGetValue(Context.User.Id, out game)) - // { - // await ReplyAsync("You're not betting on move or stay.").ConfigureAwait(false); - // return; - // } - - // if (action == MoveOrStay.Move) - // { - // game.Move(); - // await Context.Channel.EmbedAsync(GetGameState(game), - // string.Format("{0} rolled {1}.", Context.User, game.Rolled)).ConfigureAwait(false); - // if (game.Ended) - // _games.TryRemove(Context.User.Id, out game); - // } - // else if (action == MoveOrStay.Stay) - // { - // var won = game.Stay(); - // await CurrencyHandler.AddCurrencyAsync(Context.User, "MoveOrStay stay", won, false) - // .ConfigureAwait(false); - // _games.TryRemove(Context.User.Id, out game); - // await ReplyAsync(string.Format("You've finished with {0}", - // won + CurrencySign)) - // .ConfigureAwait(false); - // } - - - // } - - // private EmbedBuilder GetGameState(Lucky7Game game) - // { - // var arr = Lucky7Game.Winnings.ToArray(); - // var sb = new StringBuilder(); - // for (var i = 0; i < arr.Length; i++) - // { - // if (i == game.CurrentPosition) - // { - // sb.Append("[" + arr[i] + "]"); - // } - // else - // { - // sb.Append(arr[i].ToString()); - // } - - // if (i != arr.Length - 1) - // sb.Append(' '); - // } - - // return new EmbedBuilder().WithOkColor() - // .WithTitle("Lucky7") - // .WithDescription(sb.ToString()) - // .AddField(efb => efb.WithName("Bet") - // .WithValue(game.Bet.ToString()) - // .WithIsInline(true)) - // .AddField(efb => efb.WithName("Current Value") - // .WithValue((game.CurrentMultiplier * game.Bet).ToString(_cultureInfo)) - // .WithIsInline(true)); - // } - // } - - // public class Lucky7Game - // { - // public int Bet { get; } - // public bool Ended { get; private set; } - // public int PreviousPosition { get; private set; } - // public int CurrentPosition { get; private set; } = -1; - // public int Rolled { get; private set; } - // public float CurrentMultiplier => Winnings[CurrentPosition]; - // private readonly NadekoRandom _rng = new NadekoRandom(); - - // public static readonly ImmutableArray Winnings = new[] - // { - // 1.2f, 0.8f, 0.75f, 0.90f, 0.7f, 0.5f, 1.8f, 0f, 0f - // }.ToImmutableArray(); - - // public Lucky7Game(int bet) - // { - // Bet = bet; - // Move(); - // } - - // public void Move() - // { - // if (Ended) - // return; - // PreviousPosition = CurrentPosition; - // Rolled = _rng.Next(1, 4); - // CurrentPosition += Rolled; - - // if (CurrentPosition >= 6) - // Ended = true; - // } - - // public int Stay() - // { - // if (Ended) - // return 0; - - // Ended = true; - // return (int) (CurrentMultiplier * Bet); - // } - // } - //} -} diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs new file mode 100644 index 00000000..bf1c51ac --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs @@ -0,0 +1,161 @@ +using NadekoBot.Common; +using NadekoBot.Extensions; +using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Gambling.Common.AnimalRacing +{ + public class AnimalRace : IDisposable + { + public enum Phase + { + WaitingForPlayers, + Running, + Ended, + } + + private const int _startingDelayMiliseconds = 20_000; + + public Phase CurrentPhase = Phase.WaitingForPlayers; + + public event Func OnStarted = delegate { return Task.CompletedTask; }; + public event Func OnStartingFailed = delegate { return Task.CompletedTask; }; + public event Func OnStateUpdate = delegate { return Task.CompletedTask; }; + public event Func OnEnded = delegate { return Task.CompletedTask; }; + + public ImmutableArray Users => _users.ToImmutableArray(); + public List FinishedUsers { get; } = new List(); + + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1); + private readonly HashSet _users = new HashSet(); + private readonly CurrencyService _currency; + private readonly Queue _animalsQueue; + public int MaxUsers { get; } + + public AnimalRace(CurrencyService currency, RaceAnimal[] availableAnimals) + { + this._currency = currency; + this._animalsQueue = new Queue(availableAnimals); + this.MaxUsers = availableAnimals.Length; + + if (this._animalsQueue.Count == 0) + CurrentPhase = Phase.Ended; + } + + public void Initialize() //lame name + { + var _t = Task.Run(async () => + { + await Task.Delay(_startingDelayMiliseconds).ConfigureAwait(false); + + await _locker.WaitAsync().ConfigureAwait(false); + try + { + if (CurrentPhase != Phase.WaitingForPlayers) + return; + + await Start().ConfigureAwait(false); + } + finally { _locker.Release(); } + }); + } + + public async Task JoinRace(ulong userId, string userName, int bet = 0) + { + if (bet < 0) + throw new ArgumentOutOfRangeException(nameof(bet)); + + var user = new AnimalRacingUser(userName, userId, bet); + + await _locker.WaitAsync().ConfigureAwait(false); + try + { + if (_users.Count == MaxUsers) + throw new AnimalRaceFullException(); + + if (CurrentPhase != Phase.WaitingForPlayers) + throw new AlreadyStartedException(); + + if (!await _currency.RemoveAsync(userId, "BetRace", bet).ConfigureAwait(false)) + throw new NotEnoughFundsException(); + + if (_users.Contains(user)) + throw new AlreadyJoinedException(); + + var animal = _animalsQueue.Dequeue(); + user.Animal = animal; + _users.Add(user); + + if (_animalsQueue.Count == 0) //start if no more spots left + await Start().ConfigureAwait(false); + + return user; + } + finally { _locker.Release(); } + } + + private async Task Start() + { + CurrentPhase = Phase.Running; + if (_users.Count <= 1) + { + foreach (var user in _users) + { + if(user.Bet > 0) + await _currency.AddAsync(user.UserId, "Race refund", user.Bet).ConfigureAwait(false); + } + + var _sf = OnStartingFailed?.Invoke(this); + CurrentPhase = Phase.Ended; + return; + } + + var _ = OnStarted?.Invoke(this); + var _t = Task.Run(async () => + { + var rng = new NadekoRandom(); + while (!_users.All(x => x.Progress >= 60)) + { + foreach (var user in _users) + { + user.Progress += rng.Next(1, 11); + if (user.Progress >= 60) + user.Progress = 60; + } + + var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x)) + .Shuffle(); + + FinishedUsers.AddRange(finished); + + var _ignore = OnStateUpdate?.Invoke(this); + await Task.Delay(2500).ConfigureAwait(false); + } + + if (FinishedUsers[0].Bet > 0) + await _currency.AddAsync(FinishedUsers[0].UserId, "Won a Race", FinishedUsers[0].Bet * (_users.Count - 1)) + .ConfigureAwait(false); + + var _ended = OnEnded?.Invoke(this); + }); + } + + public void Dispose() + { + CurrentPhase = Phase.Ended; + OnStarted = null; + OnEnded = null; + OnStartingFailed = null; + OnStateUpdate = null; + _locker.Dispose(); + _users.Clear(); + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRacingUser.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRacingUser.cs new file mode 100644 index 00000000..ea9bc453 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRacingUser.cs @@ -0,0 +1,32 @@ +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Modules.Gambling.Common.AnimalRacing +{ + public class AnimalRacingUser + { + public int Bet { get; } + public string Username { get; } + public ulong UserId { get; } + public RaceAnimal Animal { get; set; } + public int Progress { get; set; } + + public AnimalRacingUser(string username, ulong userId, int bet) + { + this.Bet = bet; + this.Username = username; + this.UserId = userId; + } + + public override bool Equals(object obj) + { + return obj is AnimalRacingUser x + ? x.UserId == this.UserId + : false; + } + + public override int GetHashCode() + { + return this.UserId.GetHashCode(); + } + } +} diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyJoinedException.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyJoinedException.cs new file mode 100644 index 00000000..56469bf8 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyJoinedException.cs @@ -0,0 +1,9 @@ +using System; + +namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions +{ + public class AlreadyJoinedException : Exception + { + + } +} diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyStartedException.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyStartedException.cs new file mode 100644 index 00000000..ab54a87a --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyStartedException.cs @@ -0,0 +1,9 @@ +using System; + +namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions +{ + public class AlreadyStartedException : Exception + { + + } +} diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AnimalRaceFullException.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AnimalRaceFullException.cs new file mode 100644 index 00000000..9c3e8f69 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AnimalRaceFullException.cs @@ -0,0 +1,8 @@ +using System; + +namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions +{ + public class AnimalRaceFullException : Exception + { + } +} diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/NotEnoughFundsException.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/NotEnoughFundsException.cs new file mode 100644 index 00000000..1fb01093 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/NotEnoughFundsException.cs @@ -0,0 +1,9 @@ +using System; + +namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions +{ + public class NotEnoughFundsException : Exception + { + + } +} diff --git a/src/NadekoBot/Modules/Gambling/Commands/Models/Cards.cs b/src/NadekoBot/Modules/Gambling/Common/Cards.cs similarity index 98% rename from src/NadekoBot/Modules/Gambling/Commands/Models/Cards.cs rename to src/NadekoBot/Modules/Gambling/Common/Cards.cs index 2c2624c6..41c39834 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/Models/Cards.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Cards.cs @@ -1,10 +1,10 @@ -using NadekoBot.Extensions; -using NadekoBot.Services; -using System; +using System; using System.Collections.Generic; using System.Linq; +using NadekoBot.Common; +using NadekoBot.Extensions; -namespace NadekoBot.Modules.Gambling.Models +namespace NadekoBot.Modules.Gambling.Common { public class Cards { diff --git a/src/NadekoBot/Modules/Gambling/Common/WheelOfFortune/WheelOfFortune.cs b/src/NadekoBot/Modules/Gambling/Common/WheelOfFortune/WheelOfFortune.cs new file mode 100644 index 00000000..f99a969a --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/WheelOfFortune/WheelOfFortune.cs @@ -0,0 +1,45 @@ +using NadekoBot.Common; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Gambling.Common.WheelOfFortune +{ + public class WheelOfFortune + { + private static readonly NadekoRandom _rng = new NadekoRandom(); + + private static readonly ImmutableArray _emojis = new string[] { + "⬆", + "↖", + "⬅", + "↙", + "⬇", + "↘", + "➡", + "↗" }.ToImmutableArray(); + + public static readonly ImmutableArray Multipliers = new float[] { + 1.7f, + 1.5f, + 0.2f, + 0.1f, + 0.3f, + 0.5f, + 1.2f, + 2.4f, + }.ToImmutableArray(); + + public int Result { get; } + public string Emoji => _emojis[Result]; + public float Multiplier => Multipliers[Result]; + + public WheelOfFortune() + { + this.Result = _rng.Next(0, 8); + } + } +} diff --git a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs b/src/NadekoBot/Modules/Gambling/CurrencyEventsCommands.cs similarity index 67% rename from src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs rename to src/NadekoBot/Modules/Gambling/CurrencyEventsCommands.cs index 76369c15..0ad3ddf9 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs +++ b/src/NadekoBot/Modules/Gambling/CurrencyEventsCommands.cs @@ -1,15 +1,18 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using System; -using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; using Discord.WebSocket; using System.Threading; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.Collections; using NLog; +using System.Collections.Concurrent; +using System.Collections.Generic; using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Gambling @@ -17,11 +20,11 @@ namespace NadekoBot.Modules.Gambling public partial class Gambling { [Group] - public class CurrencyEvents : NadekoSubmodule + public class CurrencyEventsCommands : NadekoSubmodule { public enum CurrencyEvent { - FlowerReaction, + Reaction, SneakyGameStatus } //flower reaction event @@ -34,11 +37,11 @@ namespace NadekoBot.Modules.Gambling .ToArray(); private string _secretCode = string.Empty; - private readonly DiscordShardedClient _client; - private readonly BotConfig _bc; + private readonly DiscordSocketClient _client; + private readonly IBotConfigProvider _bc; private readonly CurrencyService _cs; - public CurrencyEvents(DiscordShardedClient client, BotConfig bc, CurrencyService cs) + public CurrencyEventsCommands(DiscordSocketClient client, IBotConfigProvider bc, CurrencyService cs) { _client = client; _bc = bc; @@ -52,8 +55,8 @@ namespace NadekoBot.Modules.Gambling { switch (e) { - case CurrencyEvent.FlowerReaction: - await FlowerReactionEvent(Context, arg).ConfigureAwait(false); + case CurrencyEvent.Reaction: + await ReactionEvent(Context, arg).ConfigureAwait(false); break; case CurrencyEvent.SneakyGameStatus: await SneakyGameStatusEvent(Context, arg).ConfigureAwait(false); @@ -78,12 +81,12 @@ namespace NadekoBot.Modules.Gambling _secretCode += _sneakyGameStatusChars[rng.Next(0, _sneakyGameStatusChars.Length)]; } - await _client.SetGameAsync($"type {_secretCode} for " + _bc.CurrencyPluralName) + await _client.SetGameAsync($"type {_secretCode} for " + _bc.BotConfig.CurrencyPluralName) .ConfigureAwait(false); try { var title = GetText("sneakygamestatus_title"); - var desc = GetText("sneakygamestatus_desc", Format.Bold(100.ToString()) + _bc.CurrencySign, Format.Bold(num.ToString())); + var desc = GetText("sneakygamestatus_desc", Format.Bold(100.ToString()) + _bc.BotConfig.CurrencySign, Format.Bold(num.ToString())); await context.Channel.SendConfirmAsync(title, desc).ConfigureAwait(false); } catch @@ -125,47 +128,76 @@ namespace NadekoBot.Modules.Gambling return Task.CompletedTask; } - public async Task FlowerReactionEvent(ICommandContext context, int amount) + public async Task ReactionEvent(ICommandContext context, int amount) { if (amount <= 0) amount = 100; - var title = GetText("flowerreaction_title"); - var desc = GetText("flowerreaction_desc", "🌸", Format.Bold(amount.ToString()) + _bc.CurrencySign); - var footer = GetText("flowerreaction_footer", 24); + var title = GetText("reaction_title"); + var desc = GetText("reaction_desc", _bc.BotConfig.CurrencySign, Format.Bold(amount.ToString()) + _bc.BotConfig.CurrencySign); + var footer = GetText("reaction_footer", 24); var msg = await context.Channel.SendConfirmAsync(title, desc, footer: footer) .ConfigureAwait(false); - await new FlowerReactionEvent(_client, _cs).Start(msg, context, amount); + await new ReactionEvent(_bc.BotConfig, _client, _cs, amount).Start(msg, context); } } } public abstract class CurrencyEvent { - public abstract Task Start(IUserMessage msg, ICommandContext channel, int amount); + public abstract Task Start(IUserMessage msg, ICommandContext channel); } - public class FlowerReactionEvent : CurrencyEvent + public class ReactionEvent : CurrencyEvent { - private readonly ConcurrentHashSet _flowerReactionAwardedUsers = new ConcurrentHashSet(); + private readonly ConcurrentHashSet _reactionAwardedUsers = new ConcurrentHashSet(); + private readonly BotConfig _bc; private readonly Logger _log; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly CurrencyService _cs; + private readonly SocketSelfUser _botUser; private IUserMessage StartingMessage { get; set; } private CancellationTokenSource Source { get; } private CancellationToken CancelToken { get; } - public FlowerReactionEvent(DiscordShardedClient client, CurrencyService cs) + private readonly ConcurrentQueue _toGiveTo = new ConcurrentQueue(); + private readonly int _amount; + + public ReactionEvent(BotConfig bc, DiscordSocketClient client, CurrencyService cs, int amount) { + _bc = bc; _log = LogManager.GetCurrentClassLogger(); _client = client; _cs = cs; + _botUser = client.CurrentUser; + _amount = amount; Source = new CancellationTokenSource(); CancelToken = Source.Token; + + var _ = Task.Run(async () => + { + + var users = new List(); + while (!CancelToken.IsCancellationRequested) + { + await Task.Delay(1000).ConfigureAwait(false); + while (_toGiveTo.TryDequeue(out var usrId)) + { + users.Add(usrId); + } + + if (users.Count > 0) + { + await _cs.AddToManyAsync("", _amount, users.ToArray()).ConfigureAwait(false); + } + + users.Clear(); + } + }, CancelToken); } private async Task End() @@ -189,29 +221,38 @@ namespace NadekoBot.Modules.Gambling return Task.CompletedTask; } - public override async Task Start(IUserMessage umsg, ICommandContext context, int amount) + public override async Task Start(IUserMessage umsg, ICommandContext context) { StartingMessage = umsg; _client.MessageDeleted += MessageDeletedEventHandler; - try { await StartingMessage.AddReactionAsync(new Emoji("🌸")).ConfigureAwait(false); } + IEmote iemote; + if (Emote.TryParse(_bc.CurrencySign, out var emote)) + { + iemote = emote; + } + else + iemote = new Emoji(_bc.CurrencySign); + try { await StartingMessage.AddReactionAsync(iemote).ConfigureAwait(false); } catch { - try { await StartingMessage.AddReactionAsync(new Emoji("🌸")).ConfigureAwait(false); } + try { await StartingMessage.AddReactionAsync(iemote).ConfigureAwait(false); } catch { try { await StartingMessage.DeleteAsync().ConfigureAwait(false); } catch { return; } } } - using (StartingMessage.OnReaction(_client, async (r) => + using (StartingMessage.OnReaction(_client, (r) => { try { - if (r.Emote.Name == "🌸" && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _flowerReactionAwardedUsers.Add(r.User.Value.Id)) + if (r.UserId == _botUser.Id) + return; + + if (r.Emote.Name == iemote.Name && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _reactionAwardedUsers.Add(r.User.Value.Id)) { - await _cs.AddAsync(r.User.Value, "Flower Reaction Event", amount, false) - .ConfigureAwait(false); + _toGiveTo.Enqueue(r.UserId); } } catch diff --git a/src/NadekoBot/Modules/Gambling/Commands/DiceRollCommand.cs b/src/NadekoBot/Modules/Gambling/DiceRollCommands.cs similarity index 92% rename from src/NadekoBot/Modules/Gambling/Commands/DiceRollCommand.cs rename to src/NadekoBot/Modules/Gambling/DiceRollCommands.cs index 2b15c79d..cb2c3b3c 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/DiceRollCommand.cs +++ b/src/NadekoBot/Modules/Gambling/DiceRollCommands.cs @@ -1,6 +1,5 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using System; @@ -9,7 +8,10 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; using Image = ImageSharp.Image; +using ImageSharp; namespace NadekoBot.Modules.Gambling { @@ -18,8 +20,8 @@ namespace NadekoBot.Modules.Gambling [Group] public class DriceRollCommands : NadekoSubmodule { - private Regex dndRegex { get; } = new Regex(@"^(?\d+)d(?\d+)(?:\+(?\d+))?(?:\-(?\d+))?$", RegexOptions.Compiled); - private Regex fudgeRegex { get; } = new Regex(@"^(?\d+)d(?:F|f)$", RegexOptions.Compiled); + private readonly Regex dndRegex = new Regex(@"^(?\d+)d(?\d+)(?:\+(?\d+))?(?:\-(?\d+))?$", RegexOptions.Compiled); + private readonly Regex fudgeRegex = new Regex(@"^(?\d+)d(?:F|f)$", RegexOptions.Compiled); private readonly char[] _fateRolls = { '-', ' ', '+' }; private readonly IImagesService _images; @@ -41,7 +43,7 @@ namespace NadekoBot.Modules.Gambling var imageStream = await Task.Run(() => { var ms = new MemoryStream(); - new[] { GetDice(num1), GetDice(num2) }.Merge().Save(ms); + new[] { GetDice(num1), GetDice(num2) }.Merge().SaveAsPng(ms); ms.Position = 0; return ms; }).ConfigureAwait(false); @@ -58,7 +60,7 @@ namespace NadekoBot.Modules.Gambling } [NadekoCommand, Usage, Description, Aliases] - [Priority(0)] + [Priority(1)] public async Task Roll(int num) { await InternalRoll(num, true).ConfigureAwait(false); @@ -66,21 +68,21 @@ namespace NadekoBot.Modules.Gambling [NadekoCommand, Usage, Description, Aliases] - [Priority(0)] + [Priority(1)] public async Task Rolluo(int num = 1) { await InternalRoll(num, false).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] - [Priority(1)] + [Priority(0)] public async Task Roll(string arg) { await InternallDndRoll(arg, true).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] - [Priority(1)] + [Priority(0)] public async Task Rolluo(string arg) { await InternallDndRoll(arg, false).ConfigureAwait(false); @@ -96,7 +98,7 @@ namespace NadekoBot.Modules.Gambling var rng = new NadekoRandom(); - var dice = new List(num); + var dice = new List>(num); var values = new List(num); for (var i = 0; i < num; i++) { @@ -126,7 +128,7 @@ namespace NadekoBot.Modules.Gambling var bitmap = dice.Merge(); var ms = new MemoryStream(); - bitmap.Save(ms); + bitmap.SaveAsPng(ms); ms.Position = 0; await Context.Channel.SendFileAsync(ms, "dice.png", Context.User.Mention + " " + @@ -212,7 +214,7 @@ namespace NadekoBot.Modules.Gambling await ReplyConfirmLocalized("dice_rolled", Format.Bold(rolled.ToString())).ConfigureAwait(false); } - private Image GetDice(int num) + private Image GetDice(int num) { if (num < 0 || num > 10) throw new ArgumentOutOfRangeException(nameof(num)); @@ -223,15 +225,15 @@ namespace NadekoBot.Modules.Gambling using (var imgOneStream = images[1].ToStream()) using (var imgZeroStream = images[0].ToStream()) { - Image imgOne = new Image(imgOneStream); - Image imgZero = new Image(imgZeroStream); + var imgOne = Image.Load(imgOneStream); + var imgZero = Image.Load(imgZeroStream); return new[] { imgOne, imgZero }.Merge(); } } using (var die = _images.Dice[num].ToStream()) { - return new Image(die); + return Image.Load(die); } } } diff --git a/src/NadekoBot/Modules/Gambling/Commands/DrawCommand.cs b/src/NadekoBot/Modules/Gambling/DrawCommands.cs similarity index 93% rename from src/NadekoBot/Modules/Gambling/Commands/DrawCommand.cs rename to src/NadekoBot/Modules/Gambling/DrawCommands.cs index f2fe5f4c..72bff0f2 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/DrawCommand.cs +++ b/src/NadekoBot/Modules/Gambling/DrawCommands.cs @@ -1,14 +1,15 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; -using NadekoBot.Modules.Gambling.Models; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Gambling.Common; using Image = ImageSharp.Image; +using ImageSharp; namespace NadekoBot.Modules.Gambling { @@ -27,7 +28,7 @@ namespace NadekoBot.Modules.Gambling throw new ArgumentOutOfRangeException(nameof(num)); Cards cards = guildId == null ? new Cards() : _allDecks.GetOrAdd(Context.Guild, (s) => new Cards()); - var images = new List(); + var images = new List>(); var cardObjects = new List(); for (var i = 0; i < num; i++) { @@ -46,10 +47,10 @@ namespace NadekoBot.Modules.Gambling var currentCard = cards.DrawACard(); cardObjects.Add(currentCard); using (var stream = File.OpenRead(Path.Combine(_cardsPath, currentCard.ToString().ToLowerInvariant() + ".jpg").Replace(' ', '_'))) - images.Add(new Image(stream)); + images.Add(Image.Load(stream)); } MemoryStream bitmapStream = new MemoryStream(); - images.Merge().Save(bitmapStream); + images.Merge().SaveAsPng(bitmapStream); bitmapStream.Position = 0; var toSend = $"{Context.User.Mention}"; diff --git a/src/NadekoBot/Modules/Gambling/Commands/FlipCoinCommand.cs b/src/NadekoBot/Modules/Gambling/FlipCoinCommands.cs similarity index 86% rename from src/NadekoBot/Modules/Gambling/Commands/FlipCoinCommand.cs rename to src/NadekoBot/Modules/Gambling/FlipCoinCommands.cs index 98c526f9..96e69acf 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/FlipCoinCommand.cs +++ b/src/NadekoBot/Modules/Gambling/FlipCoinCommands.cs @@ -1,13 +1,14 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; -using NadekoBot.Services.Database.Models; using System; using System.Collections.Generic; using System.Threading.Tasks; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; using Image = ImageSharp.Image; +using ImageSharp; namespace NadekoBot.Modules.Gambling { @@ -17,12 +18,12 @@ namespace NadekoBot.Modules.Gambling public class FlipCoinCommands : NadekoSubmodule { private readonly IImagesService _images; - private readonly BotConfig _bc; + private readonly IBotConfigProvider _bc; private readonly CurrencyService _cs; private readonly NadekoRandom rng = new NadekoRandom(); - public FlipCoinCommands(IImagesService images, CurrencyService cs, BotConfig bc) + public FlipCoinCommands(IImagesService images, CurrencyService cs, IBotConfigProvider bc) { _images = images; _bc = bc; @@ -55,7 +56,7 @@ namespace NadekoBot.Modules.Gambling await ReplyErrorLocalized("flip_invalid", 10).ConfigureAwait(false); return; } - var imgs = new Image[count]; + var imgs = new Image[count]; for (var i = 0; i < count; i++) { using (var heads = _images.Heads.ToStream()) @@ -63,11 +64,11 @@ namespace NadekoBot.Modules.Gambling { if (rng.Next(0, 10) < 5) { - imgs[i] = new Image(heads); + imgs[i] = Image.Load(heads); } else { - imgs[i] = new Image(tails); + imgs[i] = Image.Load(tails); } } } @@ -87,15 +88,15 @@ namespace NadekoBot.Modules.Gambling [NadekoCommand, Usage, Description, Aliases] public async Task Betflip(int amount, BetFlipGuess guess) { - if (amount < _bc.MinimumBetAmount) + if (amount < _bc.BotConfig.MinimumBetAmount) { - await ReplyErrorLocalized("min_bet_limit", _bc.MinimumBetAmount + _bc.CurrencySign).ConfigureAwait(false); + await ReplyErrorLocalized("min_bet_limit", _bc.BotConfig.MinimumBetAmount + _bc.BotConfig.CurrencySign).ConfigureAwait(false); return; } var removed = await _cs.RemoveAsync(Context.User, "Betflip Gamble", amount, false).ConfigureAwait(false); if (!removed) { - await ReplyErrorLocalized("not_enough", _bc.CurrencyPluralName).ConfigureAwait(false); + await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencyPluralName).ConfigureAwait(false); return; } BetFlipGuess result; @@ -114,8 +115,8 @@ namespace NadekoBot.Modules.Gambling string str; if (guess == result) { - var toWin = (int)Math.Round(amount * _bc.BetflipMultiplier); - str = Context.User.Mention + " " + GetText("flip_guess", toWin + _bc.CurrencySign); + var toWin = (int)Math.Round(amount * _bc.BotConfig.BetflipMultiplier); + str = Context.User.Mention + " " + GetText("flip_guess", toWin + _bc.BotConfig.CurrencySign); await _cs.AddAsync(Context.User, "Betflip Gamble", toWin, false).ConfigureAwait(false); } else diff --git a/src/NadekoBot/Modules/Gambling/Commands/FlowerShop.cs b/src/NadekoBot/Modules/Gambling/FlowerShopCommands.cs similarity index 92% rename from src/NadekoBot/Modules/Gambling/Commands/FlowerShop.cs rename to src/NadekoBot/Modules/Gambling/FlowerShopCommands.cs index dfc8d648..6f50b7c3 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/FlowerShop.cs +++ b/src/NadekoBot/Modules/Gambling/FlowerShopCommands.cs @@ -2,8 +2,6 @@ using Discord.Commands; using Discord.WebSocket; using Microsoft.EntityFrameworkCore; -using NadekoBot.Attributes; -using NadekoBot.DataStructures; using NadekoBot.Extensions; using NadekoBot.Services; using NadekoBot.Services.Database.Models; @@ -11,18 +9,21 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.Collections; namespace NadekoBot.Modules.Gambling { public partial class Gambling { [Group] - public class FlowerShop : NadekoSubmodule + public class FlowerShopCommands : NadekoSubmodule { - private readonly BotConfig _bc; + private readonly IBotConfigProvider _bc; private readonly DbService _db; private readonly CurrencyService _cs; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; public enum Role { @@ -34,7 +35,7 @@ namespace NadekoBot.Modules.Gambling List } - public FlowerShop(BotConfig bc, DbService db, CurrencyService cs, DiscordShardedClient client) + public FlowerShopCommands(IBotConfigProvider bc, DbService db, CurrencyService cs, DiscordSocketClient client) { _db = db; _bc = bc; @@ -58,18 +59,18 @@ namespace NadekoBot.Modules.Gambling await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) => { - var theseEntries = entries.Skip(curPage * 9).Take(9); + var theseEntries = entries.Skip(curPage * 9).Take(9).ToArray(); if (!theseEntries.Any()) return new EmbedBuilder().WithErrorColor() .WithDescription(GetText("shop_none")); var embed = new EmbedBuilder().WithOkColor() - .WithTitle(GetText("shop", _bc.CurrencySign)); + .WithTitle(GetText("shop", _bc.BotConfig.CurrencySign)); - for (int i = 0; i < entries.Count; i++) + for (int i = 0; i < theseEntries.Length; i++) { - var entry = entries[i]; - embed.AddField(efb => efb.WithName($"#{i + 1} - {entry.Price}{_bc.CurrencySign}").WithValue(EntryToString(entry)).WithIsInline(true)); + var entry = theseEntries[i]; + embed.AddField(efb => efb.WithName($"#{curPage * 9 + i + 1} - {entry.Price}{_bc.BotConfig.CurrencySign}").WithValue(EntryToString(entry)).WithIsInline(true)); } return embed; }, entries.Count / 9, true); @@ -129,7 +130,7 @@ namespace NadekoBot.Modules.Gambling } else { - await ReplyErrorLocalized("not_enough", _bc.CurrencySign).ConfigureAwait(false); + await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false); return; } } @@ -154,7 +155,7 @@ namespace NadekoBot.Modules.Gambling } try { - await (await Context.User.CreateDMChannelAsync()) + await (await Context.User.GetOrCreateDMChannelAsync()) .EmbedAsync(new EmbedBuilder().WithOkColor() .WithTitle(GetText("shop_purchase", Context.Guild.Name)) .AddField(efb => efb.WithName(GetText("item")).WithValue(item.Text).WithIsInline(false)) @@ -185,7 +186,7 @@ namespace NadekoBot.Modules.Gambling } else { - await ReplyErrorLocalized("not_enough", _bc.CurrencySign).ConfigureAwait(false); + await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false); return; } } @@ -330,7 +331,7 @@ namespace NadekoBot.Modules.Gambling var embed = new EmbedBuilder().WithOkColor(); if (entry.Type == ShopEntryType.Role) - return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(GetText("shop_role", Format.Bold(entry.RoleName))).WithIsInline(true)) + return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(GetText("shop_role", Format.Bold(Context.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE"))).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("type")).WithValue(entry.Type.ToString()).WithIsInline(true)); else if (entry.Type == ShopEntryType.List) @@ -348,7 +349,7 @@ namespace NadekoBot.Modules.Gambling { if (entry.Type == ShopEntryType.Role) { - return GetText("shop_role", Format.Bold(entry.RoleName)); + return GetText("shop_role", Format.Bold(Context.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE")); } else if (entry.Type == ShopEntryType.List) { diff --git a/src/NadekoBot/Modules/Gambling/Gambling.cs b/src/NadekoBot/Modules/Gambling/Gambling.cs index 500319ca..2ecda7d7 100644 --- a/src/NadekoBot/Modules/Gambling/Gambling.cs +++ b/src/NadekoBot/Modules/Gambling/Gambling.cs @@ -1,26 +1,27 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using System.Linq; using System.Threading.Tasks; using NadekoBot.Services; using NadekoBot.Services.Database.Models; using System.Collections.Generic; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; namespace NadekoBot.Modules.Gambling { public partial class Gambling : NadekoTopLevelModule { - private readonly BotConfig _bc; + private readonly IBotConfigProvider _bc; private readonly DbService _db; private readonly CurrencyService _currency; - private string CurrencyName => _bc.CurrencyName; - private string CurrencyPluralName => _bc.CurrencyPluralName; - private string CurrencySign => _bc.CurrencySign; + private string CurrencyName => _bc.BotConfig.CurrencyName; + private string CurrencyPluralName => _bc.BotConfig.CurrencyPluralName; + private string CurrencySign => _bc.BotConfig.CurrencySign; - public Gambling(BotConfig bc, DbService db, CurrencyService currency) + public Gambling(IBotConfigProvider bc, DbService db, CurrencyService currency) { _bc = bc; _db = db; @@ -41,7 +42,7 @@ namespace NadekoBot.Modules.Gambling { role = role ?? Context.Guild.EveryoneRole; - var members = role.Members().Where(u => u.Status != UserStatus.Offline); + var members = (await role.GetMembersAsync()).Where(u => u.Status != UserStatus.Offline); var membersArray = members as IUser[] ?? members.ToArray(); if (membersArray.Length == 0) { @@ -52,7 +53,7 @@ namespace NadekoBot.Modules.Gambling } [NadekoCommand, Usage, Description, Aliases] - [Priority(0)] + [Priority(1)] public async Task Cash([Remainder] IUser user = null) { if(user == null) @@ -62,7 +63,7 @@ namespace NadekoBot.Modules.Gambling } [NadekoCommand, Usage, Description, Aliases] - [Priority(1)] + [Priority(0)] public async Task Cash(ulong userId) { await ReplyConfirmLocalized("has", Format.Code(userId.ToString()), $"{GetCurrency(userId)} {CurrencySign}").ConfigureAwait(false); @@ -88,7 +89,7 @@ namespace NadekoBot.Modules.Gambling [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] - [Priority(2)] + [Priority(0)] public Task Award(int amount, [Remainder] IGuildUser usr) => Award(amount, usr.Id); @@ -107,7 +108,7 @@ namespace NadekoBot.Modules.Gambling [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] - [Priority(0)] + [Priority(2)] public async Task Award(int amount, [Remainder] IRole role) { var users = (await Context.Guild.GetUsersAsync()) @@ -229,21 +230,21 @@ namespace NadekoBot.Modules.Gambling { if (rnd < 91) { - str += GetText("br_win", (amount * _bc.Betroll67Multiplier) + CurrencySign, 66); + str += GetText("br_win", (amount * _bc.BotConfig.Betroll67Multiplier) + CurrencySign, 66); await _currency.AddAsync(Context.User, "Betroll Gamble", - (int) (amount * _bc.Betroll67Multiplier), false).ConfigureAwait(false); + (int) (amount * _bc.BotConfig.Betroll67Multiplier), false).ConfigureAwait(false); } else if (rnd < 100) { - str += GetText("br_win", (amount * _bc.Betroll91Multiplier) + CurrencySign, 90); + str += GetText("br_win", (amount * _bc.BotConfig.Betroll91Multiplier) + CurrencySign, 90); await _currency.AddAsync(Context.User, "Betroll Gamble", - (int) (amount * _bc.Betroll91Multiplier), false).ConfigureAwait(false); + (int) (amount * _bc.BotConfig.Betroll91Multiplier), false).ConfigureAwait(false); } else { - str += GetText("br_win", (amount * _bc.Betroll100Multiplier) + CurrencySign, 100) + " 👑"; + str += GetText("br_win", (amount * _bc.BotConfig.Betroll100Multiplier) + CurrencySign, 100) + " 👑"; await _currency.AddAsync(Context.User, "Betroll Gamble", - (int) (amount * _bc.Betroll100Multiplier), false).ConfigureAwait(false); + (int) (amount * _bc.BotConfig.Betroll100Multiplier), false).ConfigureAwait(false); } } await Context.Channel.SendConfirmAsync(str).ConfigureAwait(false); diff --git a/src/NadekoBot/Modules/Gambling/Commands/Slots.cs b/src/NadekoBot/Modules/Gambling/SlotCommands.cs similarity index 89% rename from src/NadekoBot/Modules/Gambling/Commands/Slots.cs rename to src/NadekoBot/Modules/Gambling/SlotCommands.cs index 99e1229a..43cfb8d4 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/Slots.cs +++ b/src/NadekoBot/Modules/Gambling/SlotCommands.cs @@ -1,29 +1,30 @@ using Discord; using Discord.Commands; using ImageSharp; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; -using NadekoBot.Services.Database.Models; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; +using SixLabors.Primitives; namespace NadekoBot.Modules.Gambling { public partial class Gambling { [Group] - public class Slots : NadekoSubmodule + public class SlotCommands : NadekoSubmodule { private static int _totalBet; private static int _totalPaidOut; private static readonly HashSet _runningUsers = new HashSet(); - private readonly BotConfig _bc; + private readonly IBotConfigProvider _bc; private const int _alphaCutOut = byte.MaxValue / 3; @@ -34,7 +35,7 @@ namespace NadekoBot.Modules.Gambling private readonly IImagesService _images; private readonly CurrencyService _cs; - public Slots(IImagesService images, BotConfig bc, CurrencyService cs) + public SlotCommands(IImagesService images, IBotConfigProvider bc, CurrencyService cs) { _images = images; _bc = bc; @@ -146,26 +147,26 @@ namespace NadekoBot.Modules.Gambling { if (amount < 1) { - await ReplyErrorLocalized("min_bet_limit", 1 + _bc.CurrencySign).ConfigureAwait(false); + await ReplyErrorLocalized("min_bet_limit", 1 + _bc.BotConfig.CurrencySign).ConfigureAwait(false); return; } const int maxAmount = 9999; if (amount > maxAmount) { - GetText("slot_maxbet", maxAmount + _bc.CurrencySign); - await ReplyErrorLocalized("max_bet_limit", maxAmount + _bc.CurrencySign).ConfigureAwait(false); + GetText("slot_maxbet", maxAmount + _bc.BotConfig.CurrencySign); + await ReplyErrorLocalized("max_bet_limit", maxAmount + _bc.BotConfig.CurrencySign).ConfigureAwait(false); return; } if (!await _cs.RemoveAsync(Context.User, "Slot Machine", amount, false)) { - await ReplyErrorLocalized("not_enough", _bc.CurrencySign).ConfigureAwait(false); + await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false); return; } Interlocked.Add(ref _totalBet, amount); using (var bgFileStream = _images.SlotBackground.ToStream()) { - var bgImage = new ImageSharp.Image(bgFileStream); + var bgImage = ImageSharp.Image.Load(bgFileStream); var result = SlotMachine.Pull(); int[] numbers = result.Numbers; @@ -173,7 +174,7 @@ namespace NadekoBot.Modules.Gambling for (int i = 0; i < 3; i++) { using (var file = _images.SlotEmojis[numbers[i]].ToStream()) - using (var randomImage = new ImageSharp.Image(file)) + using (var randomImage = ImageSharp.Image.Load(file)) { bgImage.DrawImage(randomImage, 100, default(Size), new Point(95 + 142 * i, 330)); } @@ -186,7 +187,7 @@ namespace NadekoBot.Modules.Gambling { var digit = printWon % 10; using (var fs = _images.SlotNumbers[digit].ToStream()) - using (var img = new ImageSharp.Image(fs)) + using (var img = ImageSharp.Image.Load(fs)) { bgImage.DrawImage(img, 100, default(Size), new Point(230 - n * 16, 462)); } @@ -199,8 +200,8 @@ namespace NadekoBot.Modules.Gambling { var digit = printAmount % 10; using (var fs = _images.SlotNumbers[digit].ToStream()) - using (var img = new ImageSharp.Image(fs)) - { + using (var img = ImageSharp.Image.Load(fs)) + { bgImage.DrawImage(img, 100, default(Size), new Point(395 - n * 16, 462)); } n++; @@ -212,16 +213,16 @@ namespace NadekoBot.Modules.Gambling await _cs.AddAsync(Context.User, $"Slot Machine x{result.Multiplier}", amount * result.Multiplier, false); Interlocked.Add(ref _totalPaidOut, amount * result.Multiplier); if (result.Multiplier == 1) - msg = GetText("slot_single", _bc.CurrencySign, 1); + msg = GetText("slot_single", _bc.BotConfig.CurrencySign, 1); else if (result.Multiplier == 4) - msg = GetText("slot_two", _bc.CurrencySign, 4); + msg = GetText("slot_two", _bc.BotConfig.CurrencySign, 4); else if (result.Multiplier == 10) msg = GetText("slot_three", 10); else if (result.Multiplier == 30) msg = GetText("slot_jackpot", 30); } - await Context.Channel.SendFileAsync(bgImage.ToStream(), "result.png", Context.User.Mention + " " + msg + $"\n`{GetText("slot_bet")}:`{amount} `{GetText("slot_won")}:` {amount * result.Multiplier}{_bc.CurrencySign}").ConfigureAwait(false); + await Context.Channel.SendFileAsync(bgImage.ToStream(), "result.png", Context.User.Mention + " " + msg + $"\n`{GetText("slot_bet")}:`{amount} `{GetText("slot_won")}:` {amount * result.Multiplier}{_bc.BotConfig.CurrencySign}").ConfigureAwait(false); } } finally diff --git a/src/NadekoBot/Modules/Gambling/Commands/WaifuClaimCommands.cs b/src/NadekoBot/Modules/Gambling/WaifuClaimCommands.cs similarity index 84% rename from src/NadekoBot/Modules/Gambling/Commands/WaifuClaimCommands.cs rename to src/NadekoBot/Modules/Gambling/WaifuClaimCommands.cs index 02025444..5d46f976 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/WaifuClaimCommands.cs +++ b/src/NadekoBot/Modules/Gambling/WaifuClaimCommands.cs @@ -1,6 +1,5 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using NadekoBot.Services.Database.Models; @@ -9,6 +8,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; namespace NadekoBot.Modules.Gambling { @@ -47,8 +48,8 @@ namespace NadekoBot.Modules.Gambling [Group] public class WaifuClaimCommands : NadekoSubmodule { - private static ConcurrentDictionary divorceCooldowns { get; } = new ConcurrentDictionary(); - private static ConcurrentDictionary affinityCooldowns { get; } = new ConcurrentDictionary(); + private static ConcurrentDictionary _divorceCooldowns { get; } = new ConcurrentDictionary(); + private static ConcurrentDictionary _affinityCooldowns { get; } = new ConcurrentDictionary(); enum WaifuClaimResult { @@ -57,7 +58,7 @@ namespace NadekoBot.Modules.Gambling InsufficientAmount } - public WaifuClaimCommands(BotConfig bc, CurrencyService cs, DbService db) + public WaifuClaimCommands(IBotConfigProvider bc, CurrencyService cs, DbService db) { _bc = bc; _cs = cs; @@ -70,7 +71,7 @@ namespace NadekoBot.Modules.Gambling { if (amount < 50) { - await ReplyErrorLocalized("waifu_isnt_cheap", 50 + _bc.CurrencySign).ConfigureAwait(false); + await ReplyErrorLocalized("waifu_isnt_cheap", 50 + _bc.BotConfig.CurrencySign).ConfigureAwait(false); return; } @@ -172,14 +173,14 @@ namespace NadekoBot.Modules.Gambling } if (result == WaifuClaimResult.NotEnoughFunds) { - await ReplyErrorLocalized("not_enough", _bc.CurrencySign).ConfigureAwait(false); + await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false); return; } var msg = GetText("waifu_claimed", Format.Bold(target.ToString()), - amount + _bc.CurrencySign); + amount + _bc.BotConfig.CurrencySign); if (w.Affinity?.UserId == Context.User.Id) - msg += "\n" + GetText("waifu_fulfilled", target, w.Price + _bc.CurrencySign); + msg += "\n" + GetText("waifu_fulfilled", target, w.Price + _bc.BotConfig.CurrencySign); else msg = " " + msg; await Context.Channel.SendConfirmAsync(Context.User.Mention + msg).ConfigureAwait(false); @@ -197,12 +198,12 @@ namespace NadekoBot.Modules.Gambling private static readonly TimeSpan _divorceLimit = TimeSpan.FromHours(6); [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - [Priority(1)] + [Priority(0)] public Task Divorce([Remainder]IGuildUser target) => Divorce(target.Id); [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - [Priority(0)] + [Priority(1)] public async Task Divorce([Remainder]ulong targetId) { if (targetId == Context.User.Id) @@ -218,7 +219,7 @@ namespace NadekoBot.Modules.Gambling var now = DateTime.UtcNow; if (w?.Claimer == null || w.Claimer.UserId != Context.User.Id) result = DivorceResult.NotYourWife; - else if (divorceCooldowns.AddOrUpdate(Context.User.Id, + else if (_divorceCooldowns.AddOrUpdate(Context.User.Id, now, (key, old) => ((difference = now.Subtract(old)) > _divorceLimit) ? now : old) != now) { @@ -257,11 +258,11 @@ namespace NadekoBot.Modules.Gambling if (result == DivorceResult.SucessWithPenalty) { - await ReplyConfirmLocalized("waifu_divorced_like", Format.Bold(w.Waifu.ToString()), amount + _bc.CurrencySign).ConfigureAwait(false); + await ReplyConfirmLocalized("waifu_divorced_like", Format.Bold(w.Waifu.ToString()), amount + _bc.BotConfig.CurrencySign).ConfigureAwait(false); } else if (result == DivorceResult.Success) { - await ReplyConfirmLocalized("waifu_divorced_notlike", amount + _bc.CurrencySign).ConfigureAwait(false); + await ReplyConfirmLocalized("waifu_divorced_notlike", amount + _bc.BotConfig.CurrencySign).ConfigureAwait(false); } else if (result == DivorceResult.NotYourWife) { @@ -277,7 +278,7 @@ namespace NadekoBot.Modules.Gambling } private static readonly TimeSpan _affinityLimit = TimeSpan.FromMinutes(30); - private readonly BotConfig _bc; + private readonly IBotConfigProvider _bc; private readonly CurrencyService _cs; private readonly DbService _db; @@ -302,7 +303,7 @@ namespace NadekoBot.Modules.Gambling if (w?.Affinity?.UserId == u?.Id) { } - else if (affinityCooldowns.AddOrUpdate(Context.User.Id, + else if (_affinityCooldowns.AddOrUpdate(Context.User.Id, now, (key, old) => ((difference = now.Subtract(old)) > _affinityLimit) ? now : old) != now) { @@ -405,7 +406,7 @@ namespace NadekoBot.Modules.Gambling var w = waifus[i]; var j = i; - embed.AddField(efb => efb.WithName("#" + ((page * 9) + j + 1) + " - " + w.Price + _bc.CurrencySign).WithValue(w.ToString()).WithIsInline(false)); + embed.AddField(efb => efb.WithName("#" + ((page * 9) + j + 1) + " - " + w.Price + _bc.BotConfig.CurrencySign).WithValue(w.ToString()).WithIsInline(false)); } await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); @@ -458,11 +459,77 @@ namespace NadekoBot.Modules.Gambling .AddField(efb => efb.WithName(GetText("likes")).WithValue(w.Affinity?.ToString() ?? nobody).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("changes_of_heart")).WithValue($"{affInfo.Count} - \"the {affInfo.Title}\"").WithIsInline(true)) .AddField(efb => efb.WithName(GetText("divorces")).WithValue(divorces.ToString()).WithIsInline(true)) - .AddField(efb => efb.WithName($"Waifus ({claims.Count})").WithValue(claims.Count == 0 ? nobody : string.Join("\n", claims.OrderBy(x => rng.Next()).Take(30).Select(x => x.Waifu))).WithIsInline(true)); + .AddField(efb => efb.WithName(GetText("gifts")).WithValue(!w.Items.Any() ? "-" : string.Join("\n", w.Items.OrderBy(x => x.Price).GroupBy(x => x.ItemEmoji).Select(x => $"{x.Key} x{x.Count()}"))).WithIsInline(false)) + .AddField(efb => efb.WithName($"Waifus ({claims.Count})").WithValue(claims.Count == 0 ? nobody : string.Join("\n", claims.OrderBy(x => rng.Next()).Take(30).Select(x => x.Waifu))).WithIsInline(false)); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [Priority(1)] + public async Task WaifuGift() + { + var embed = new EmbedBuilder() + .WithTitle(GetText("waifu_gift_shop")) + .WithOkColor(); + + Enum.GetValues(typeof(WaifuItem.ItemName)) + .Cast() + .Select(x => WaifuItem.GetItem(x)) + .ForEach(x => embed.AddField(f => f.WithName(x.ItemEmoji + " " + x.Item).WithValue(x.Price).WithIsInline(true))); + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [Priority(0)] + public async Task WaifuGift(WaifuItem.ItemName item, [Remainder] IUser waifu) + { + if (waifu.Id == Context.User.Id) + return; + + var itemObj = WaifuItem.GetItem(item); + + using (var uow = _db.UnitOfWork) + { + var w = uow.Waifus.ByWaifuUserId(waifu.Id); + + //try to buy the item first + + if (!await _cs.RemoveAsync(Context.User.Id, "Bought waifu item", itemObj.Price, uow)) + { + await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false); + return; + } + if (w == null) + { + uow.Waifus.Add(w = new WaifuInfo() + { + Affinity = null, + Claimer = null, + Price = 1, + Waifu = uow.DiscordUsers.GetOrCreate(waifu), + }); + + w.Waifu.Username = waifu.Username; + w.Waifu.Discriminator = waifu.Discriminator; + } + w.Items.Add(itemObj); + if (w.Claimer?.UserId == Context.User.Id) + { + w.Price += itemObj.Price; + } + else + w.Price += itemObj.Price / 2; + + await uow.CompleteAsync().ConfigureAwait(false); + } + + await ReplyConfirmLocalized("waifu_gift", Format.Bold(item.ToString() + " " +itemObj.ItemEmoji), Format.Bold(waifu.ToString())).ConfigureAwait(false); + } + public struct WaifuProfileTitle { diff --git a/src/NadekoBot/Modules/Gambling/WheelOfFortuneCommands.cs b/src/NadekoBot/Modules/Gambling/WheelOfFortuneCommands.cs new file mode 100644 index 00000000..cd3de369 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/WheelOfFortuneCommands.cs @@ -0,0 +1,81 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Common.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Modules.Gambling.Common.WheelOfFortune; +using NadekoBot.Services; +using System.Threading.Tasks; +using Wof = NadekoBot.Modules.Gambling.Common.WheelOfFortune.WheelOfFortune; + +namespace NadekoBot.Modules.Gambling +{ + public partial class Gambling + { + public class WheelOfFortuneCommands : NadekoSubmodule + { + private readonly CurrencyService _cs; + private readonly IBotConfigProvider _bc; + + public WheelOfFortuneCommands(CurrencyService cs, IBotConfigProvider bc) + { + _cs = cs; + _bc = bc; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task WheelOfFortune(int bet) + { + const int minBet = 10; + if (bet < minBet) + { + await ReplyErrorLocalized("min_bet_limit", minBet + _bc.BotConfig.CurrencySign).ConfigureAwait(false); + return; + } + + if (!await _cs.RemoveAsync(Context.User.Id, "Wheel Of Fortune - bet", bet).ConfigureAwait(false)) + { + await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false); + return; + } + + var wof = new WheelOfFortune(); + + var amount = (int)(bet * wof.Multiplier); + + if (amount > 0) + await _cs.AddAsync(Context.User.Id, "Wheel Of Fortune - won", amount).ConfigureAwait(false); + + await Context.Channel.SendConfirmAsync( +Format.Bold($@"{Context.User.ToString()} won: {amount + _bc.BotConfig.CurrencySign} + + 『{Wof.Multipliers[1]}』 『{Wof.Multipliers[0]}』 『{Wof.Multipliers[7]}』 + +『{Wof.Multipliers[2]}』 {wof.Emoji} 『{Wof.Multipliers[6]}』 + + 『{Wof.Multipliers[3]}』 『{Wof.Multipliers[4]}』 『{Wof.Multipliers[5]}』")).ConfigureAwait(false); + } + + //[NadekoCommand, Usage, Description, Aliases] + //[RequireContext(ContextType.Guild)] + //public async Task WofTest(int length = 1000) + //{ + // var mults = new Dictionary(); + // for (int i = 0; i < length; i++) + // { + // var x = new Wof(); + // if (mults.ContainsKey(x.Multiplier)) + // ++mults[x.Multiplier]; + // else + // mults.Add(x.Multiplier, 1); + // } + + // var payout = mults.Sum(x => x.Key * x.Value); + // await Context.Channel.SendMessageAsync($"Total bet: {length}\n" + + // $"Paid out: {payout}\n" + + // $"Total Payout: {payout / length:F3}x") + // .ConfigureAwait(false); + //} + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/AcropobiaCommands.cs b/src/NadekoBot/Modules/Games/AcropobiaCommands.cs new file mode 100644 index 00000000..f933d3bf --- /dev/null +++ b/src/NadekoBot/Modules/Games/AcropobiaCommands.cs @@ -0,0 +1,151 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Extensions; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Games.Common.Acrophobia; + +namespace NadekoBot.Modules.Games +{ + public partial class Games + { + [Group] + public class AcropobiaCommands : NadekoSubmodule + { + private readonly DiscordSocketClient _client; + + //channelId, game + public static ConcurrentDictionary AcrophobiaGames { get; } = new ConcurrentDictionary(); + + public AcropobiaCommands(DiscordSocketClient client) + { + _client = client; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Acro(int submissionTime = 30) + { + if (submissionTime < 10 || submissionTime > 120) + return; + var channel = (ITextChannel)Context.Channel; + + var game = new Acrophobia(submissionTime); + if (AcrophobiaGames.TryAdd(channel.Id, game)) + { + try + { + game.OnStarted += Game_OnStarted; + game.OnEnded += Game_OnEnded; + game.OnVotingStarted += Game_OnVotingStarted; + game.OnUserVoted += Game_OnUserVoted; + _client.MessageReceived += _client_MessageReceived; + await game.Run().ConfigureAwait(false); + } + finally + { + _client.MessageReceived -= _client_MessageReceived; + AcrophobiaGames.TryRemove(channel.Id, out game); + game.Dispose(); + } + } + else + { + await ReplyErrorLocalized("acro_running").ConfigureAwait(false); + } + + Task _client_MessageReceived(SocketMessage msg) + { + if (msg.Channel.Id != Context.Channel.Id) + return Task.CompletedTask; + + var _ = Task.Run(async () => + { + try + { + var success = await game.UserInput(msg.Author.Id, msg.Author.ToString(), msg.Content) + .ConfigureAwait(false); + if (success) + await msg.DeleteAsync().ConfigureAwait(false); + } + catch { } + }); + + return Task.CompletedTask; + } + } + + private Task Game_OnStarted(Acrophobia game) + { + var embed = new EmbedBuilder().WithOkColor() + .WithTitle(GetText("acrophobia")) + .WithDescription(GetText("acro_started", Format.Bold(string.Join(".", game.StartingLetters)))) + .WithFooter(efb => efb.WithText(GetText("acro_started_footer", game.SubmissionPhaseLength))); + + return Context.Channel.EmbedAsync(embed); + } + + private Task Game_OnUserVoted(string user) + { + return Context.Channel.SendConfirmAsync( + GetText("acrophobia"), + GetText("acro_vote_cast", Format.Bold(user))); + } + + private async Task Game_OnVotingStarted(Acrophobia game, ImmutableArray> submissions) + { + if (submissions.Length == 0) + { + await Context.Channel.SendErrorAsync(GetText("acrophobia"), GetText("acro_ended_no_sub")); + return; + } + if (submissions.Length == 1) + { + await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() + .WithDescription( + GetText("acro_winner_only", + Format.Bold(submissions.First().Key.UserName))) + .WithFooter(efb => efb.WithText(submissions.First().Key.Input))) + .ConfigureAwait(false); + return; + } + + + var i = 0; + var embed = new EmbedBuilder() + .WithOkColor() + .WithTitle(GetText("acrophobia") + " - " + GetText("submissions_closed")) + .WithDescription(GetText("acro_nym_was", Format.Bold(string.Join(".", game.StartingLetters)) + "\n" + +$@"-- +{submissions.Aggregate("", (agg, cur) => agg + $"`{++i}.` **{cur.Key.Input}**\n")} +--")) + .WithFooter(efb => efb.WithText(GetText("acro_vote"))); + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + + private async Task Game_OnEnded(Acrophobia game, ImmutableArray> votes) + { + if (!votes.Any() || votes.All(x => x.Value == 0)) + { + await Context.Channel.SendErrorAsync(GetText("acrophobia"), GetText("acro_no_votes_cast")).ConfigureAwait(false); + return; + } + var table = votes.OrderByDescending(v => v.Value); + var winner = table.First(); + var embed = new EmbedBuilder().WithOkColor() + .WithTitle(GetText("acrophobia")) + .WithDescription(GetText("acro_winner", Format.Bold(winner.Key.UserName), + Format.Bold(winner.Value.ToString()))) + .WithFooter(efb => efb.WithText(winner.Key.Input)); + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Commands/CleverBotCommands.cs b/src/NadekoBot/Modules/Games/CleverBotCommands.cs similarity index 71% rename from src/NadekoBot/Modules/Games/Commands/CleverBotCommands.cs rename to src/NadekoBot/Modules/Games/CleverBotCommands.cs index ddf0e78d..3373a98a 100644 --- a/src/NadekoBot/Modules/Games/Commands/CleverBotCommands.cs +++ b/src/NadekoBot/Modules/Games/CleverBotCommands.cs @@ -1,25 +1,24 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Services; using System; using System.Threading.Tasks; -using NadekoBot.Services.Games; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Games.Services; +using NadekoBot.Modules.Games.Common.ChatterBot; namespace NadekoBot.Modules.Games { public partial class Games { [Group] - public class CleverBotCommands : NadekoSubmodule + public class ChatterBotCommands : NadekoSubmodule { private readonly DbService _db; - private readonly ChatterBotService _games; - public CleverBotCommands(DbService db, ChatterBotService games) + public ChatterBotCommands(DbService db) { _db = db; - _games = games; } [NadekoCommand, Usage, Description, Aliases] @@ -29,7 +28,7 @@ namespace NadekoBot.Modules.Games { var channel = (ITextChannel)Context.Channel; - if (_games.ChatterBotGuilds.TryRemove(channel.Guild.Id, out Lazy throwaway)) + if (_service.ChatterBotGuilds.TryRemove(channel.Guild.Id, out _)) { using (var uow = _db.UnitOfWork) { @@ -40,7 +39,7 @@ namespace NadekoBot.Modules.Games return; } - _games.ChatterBotGuilds.TryAdd(channel.Guild.Id, new Lazy(() => new ChatterBotSession(Context.Guild.Id), true)); + _service.ChatterBotGuilds.TryAdd(channel.Guild.Id, new Lazy(() => _service.CreateSession(), true)); using (var uow = _db.UnitOfWork) { diff --git a/src/NadekoBot/Modules/Games/Commands/Acropobia.cs b/src/NadekoBot/Modules/Games/Commands/Acropobia.cs deleted file mode 100644 index 0d8e337f..00000000 --- a/src/NadekoBot/Modules/Games/Commands/Acropobia.cs +++ /dev/null @@ -1,322 +0,0 @@ -using Discord; -using Discord.Commands; -using Discord.WebSocket; -using NadekoBot.Attributes; -using NadekoBot.Extensions; -using NadekoBot.Services; -using NLog; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Games -{ - public partial class Games - { - [Group] - public class Acropobia : NadekoSubmodule - { - private readonly DiscordShardedClient _client; - - //channelId, game - public static ConcurrentDictionary AcrophobiaGames { get; } = new ConcurrentDictionary(); - - public Acropobia(DiscordShardedClient client) - { - _client = client; - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task Acro(int time = 60) - { - if (time < 10 || time > 120) - return; - var channel = (ITextChannel)Context.Channel; - - var game = new AcrophobiaGame(_client, _strings, channel, time); - if (AcrophobiaGames.TryAdd(channel.Id, game)) - { - try - { - await game.Run(); - } - finally - { - game.EnsureStopped(); - AcrophobiaGames.TryRemove(channel.Id, out game); - } - } - else - { - await ReplyErrorLocalized("acro_running").ConfigureAwait(false); - } - } - } - - public enum AcroPhase - { - Submitting, - Idle, // used to wait for some other actions while transitioning through phases - Voting - } - - //todo 85 Isolate, this shouldn't print or anything like that. - public class AcrophobiaGame - { - private readonly ITextChannel _channel; - private readonly int _time; - private readonly NadekoRandom _rng; - private readonly ImmutableArray _startingLetters; - private readonly CancellationTokenSource _source; - private AcroPhase phase { get; set; } = AcroPhase.Submitting; - - private readonly ConcurrentDictionary _submissions = new ConcurrentDictionary(); - public IReadOnlyDictionary Submissions => _submissions; - - private readonly ConcurrentHashSet _usersWhoSubmitted = new ConcurrentHashSet(); - private readonly ConcurrentHashSet _usersWhoVoted = new ConcurrentHashSet(); - - private int _spamCount; - - //text, votes - private readonly ConcurrentDictionary _votes = new ConcurrentDictionary(); - private readonly Logger _log; - private readonly DiscordShardedClient _client; - private readonly NadekoStrings _strings; - - public AcrophobiaGame(DiscordShardedClient client, NadekoStrings strings, ITextChannel channel, int time) - { - _log = LogManager.GetCurrentClassLogger(); - _client = client; - _strings = strings; - - _channel = channel; - _time = time; - _source = new CancellationTokenSource(); - - _rng = new NadekoRandom(); - var wordCount = _rng.Next(3, 6); - - var lettersArr = new char[wordCount]; - - for (int i = 0; i < wordCount; i++) - { - var randChar = (char)_rng.Next(65, 91); - lettersArr[i] = randChar == 'X' ? (char)_rng.Next(65, 88) : randChar; - } - _startingLetters = lettersArr.ToImmutableArray(); - } - - private EmbedBuilder GetEmbed() - { - var i = 0; - return phase == AcroPhase.Submitting - - ? new EmbedBuilder().WithOkColor() - .WithTitle(GetText("acrophobia")) - .WithDescription(GetText("acro_started", Format.Bold(string.Join(".", _startingLetters)))) - .WithFooter(efb => efb.WithText(GetText("acro_started_footer", _time))) - - : new EmbedBuilder() - .WithOkColor() - .WithTitle(GetText("acrophobia") + " - " + GetText("submissions_closed")) - .WithDescription(GetText("acro_nym_was", Format.Bold(string.Join(".", _startingLetters)) + "\n" + -$@"-- -{_submissions.Aggregate("",(agg, cur) => agg + $"`{++i}.` **{cur.Key.ToLowerInvariant().ToTitleCase()}**\n")} ---")) - .WithFooter(efb => efb.WithText(GetText("acro_vote"))); - } - - public async Task Run() - { - _client.MessageReceived += PotentialAcro; - var embed = GetEmbed(); - - //SUBMISSIONS PHASE - await _channel.EmbedAsync(embed).ConfigureAwait(false); - try - { - await Task.Delay(_time * 1000, _source.Token).ConfigureAwait(false); - phase = AcroPhase.Idle; - } - catch (OperationCanceledException) - { - return; - } - - //var i = 0; - if (_submissions.Count == 0) - { - await _channel.SendErrorAsync(GetText("acrophobia"), GetText("acro_ended_no_sub")); - return; - } - if (_submissions.Count == 1) - { - await _channel.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithDescription( - GetText("acro_winner_only", - Format.Bold(_submissions.First().Value.ToString()))) - .WithFooter(efb => efb.WithText(_submissions.First().Key.ToLowerInvariant().ToTitleCase()))) - .ConfigureAwait(false); - return; - } - var submissionClosedEmbed = GetEmbed(); - - await _channel.EmbedAsync(submissionClosedEmbed).ConfigureAwait(false); - - //VOTING PHASE - phase = AcroPhase.Voting; - try - { - //30 secondds for voting - await Task.Delay(30000, _source.Token).ConfigureAwait(false); - phase = AcroPhase.Idle; - } - catch (OperationCanceledException) - { - return; - } - await End().ConfigureAwait(false); - } - - private Task PotentialAcro(SocketMessage arg) - { - var _ = Task.Run(async () => - { - try - { - var msg = arg as SocketUserMessage; - if (msg == null || msg.Author.IsBot || msg.Channel.Id != _channel.Id) - return; - - ++_spamCount; - - var guildUser = (IGuildUser)msg.Author; - - var input = msg.Content.ToUpperInvariant().Trim(); - - if (phase == AcroPhase.Submitting) - { - if (_spamCount > 10) - { - _spamCount = 0; - try { await _channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); } - catch { } - } - var inputWords = input.Split(' '); //get all words - - if (inputWords.Length != _startingLetters.Length) // number of words must be the same as the number of the starting letters - return; - - for (int i = 0; i < _startingLetters.Length; i++) - { - var letter = _startingLetters[i]; - - if (!inputWords[i].StartsWith(letter.ToString())) // all first letters must match - return; - } - - - if (!_usersWhoSubmitted.Add(guildUser.Id)) - return; - //try adding it to the list of answers - if (!_submissions.TryAdd(input, guildUser)) - { - _usersWhoSubmitted.TryRemove(guildUser.Id); - return; - } - - // all good. valid input. answer recorded - await _channel.SendConfirmAsync(GetText("acrophobia"), - GetText("acro_submit", guildUser.Mention, - _submissions.Count)); - try - { - await msg.DeleteAsync(); - } - catch - { - await msg.DeleteAsync(); //try twice - } - } - else if (phase == AcroPhase.Voting) - { - if (_spamCount > 10) - { - _spamCount = 0; - try { await _channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); } - catch { } - } - - //if (submissions.TryGetValue(input, out usr) && usr.Id != guildUser.Id) - //{ - // if (!usersWhoVoted.Add(guildUser.Id)) - // return; - // votes.AddOrUpdate(input, 1, (key, old) => ++old); - // await channel.SendConfirmAsync("Acrophobia", $"{guildUser.Mention} cast their vote!").ConfigureAwait(false); - // await msg.DeleteAsync().ConfigureAwait(false); - // return; - //} - - int num; - if (int.TryParse(input, out num) && num > 0 && num <= _submissions.Count) - { - var kvp = _submissions.Skip(num - 1).First(); - var usr = kvp.Value; - //can't vote for yourself, can't vote multiple times - if (usr.Id == guildUser.Id || !_usersWhoVoted.Add(guildUser.Id)) - return; - _votes.AddOrUpdate(kvp.Key, 1, (key, old) => ++old); - await _channel.SendConfirmAsync(GetText("acrophobia"), - GetText("acro_vote_cast", Format.Bold(guildUser.ToString()))).ConfigureAwait(false); - await msg.DeleteAsync().ConfigureAwait(false); - } - - } - } - catch (Exception ex) - { - _log.Warn(ex); - } - }); - return Task.CompletedTask; - } - - public async Task End() - { - if (!_votes.Any()) - { - await _channel.SendErrorAsync(GetText("acrophobia"), GetText("acro_no_votes_cast")).ConfigureAwait(false); - return; - } - var table = _votes.OrderByDescending(v => v.Value); - var winner = table.First(); - var embed = new EmbedBuilder().WithOkColor() - .WithTitle(GetText("acrophobia")) - .WithDescription(GetText("acro_winner", Format.Bold(_submissions[winner.Key].ToString()), - Format.Bold(winner.Value.ToString()))) - .WithFooter(efb => efb.WithText(winner.Key.ToLowerInvariant().ToTitleCase())); - - await _channel.EmbedAsync(embed).ConfigureAwait(false); - } - - public void EnsureStopped() - { - _client.MessageReceived -= PotentialAcro; - if (!_source.IsCancellationRequested) - _source.Cancel(); - } - - private string GetText(string key, params object[] replacements) - => _strings.GetText(key, - _channel.Guild.Id, - typeof(Games).Name.ToLowerInvariant(), - replacements); - } - } -} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs b/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs deleted file mode 100644 index 24ca87c9..00000000 --- a/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs +++ /dev/null @@ -1,213 +0,0 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.Extensions; -using NadekoBot.Services; -using Newtonsoft.Json; -using NLog; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using NadekoBot.Modules.Games.Commands.Hangman; - -namespace NadekoBot.Modules.Games.Hangman -{ - public class HangmanTermPool - { - const string termsPath = "data/hangman3.json"; - public static IReadOnlyDictionary data { get; } - static HangmanTermPool() - { - try - { - data = JsonConvert.DeserializeObject>(File.ReadAllText(termsPath)); - } - catch (Exception) - { - //ignored - } - } - - public static HangmanObject GetTerm(string type) - { - if (string.IsNullOrWhiteSpace(type)) - throw new ArgumentNullException(nameof(type)); - - type = type.Trim(); - - var rng = new NadekoRandom(); - - if (type == "All") { - var keys = data.Keys.ToArray(); - type = keys[rng.Next(0, keys.Length)]; - } - - HangmanObject[] termTypes; - data.TryGetValue(type, out termTypes); - - if (termTypes == null || termTypes.Length == 0) - return null; - - return termTypes[rng.Next(0, termTypes.Length)]; - } - } - - public class HangmanGame: IDisposable - { - private readonly Logger _log; - private readonly DiscordShardedClient _client; - - public IMessageChannel GameChannel { get; } - public HashSet Guesses { get; } = new HashSet(); - public HangmanObject Term { get; private set; } - public uint Errors { get; private set; } = 0; - public uint MaxErrors { get; } = 6; - public uint MessagesSinceLastPost { get; private set; } = 0; - public string ScrambledWord => "`" + String.Concat(Term.Word.Select(c => - { - if (c == ' ') - return " \u2000"; - if (!(char.IsLetter(c) || char.IsDigit(c))) - return $" {c}"; - - c = char.ToUpperInvariant(c); - return Guesses.Contains(c) ? $" {c}" : " ◯"; - })) + "`"; - - public bool GuessedAll => Guesses.IsSupersetOf(Term.Word.ToUpperInvariant() - .Where(c => char.IsLetter(c) || char.IsDigit(c))); - - public string TermType { get; } - - public event Action OnEnded; - - public HangmanGame(DiscordShardedClient client, IMessageChannel channel, string type) - { - _log = LogManager.GetCurrentClassLogger(); - _client = client; - - this.GameChannel = channel; - this.TermType = type.ToTitleCase(); - } - - public void Start() - { - this.Term = HangmanTermPool.GetTerm(TermType); - - if (this.Term == null) - throw new KeyNotFoundException("Can't find a term with that type. Use hangmanlist command."); - // start listening for answers when game starts - _client.MessageReceived += PotentialGuess; - } - - public async Task End() - { - _client.MessageReceived -= PotentialGuess; - OnEnded(this); - var toSend = "Game ended. You **" + (Errors >= MaxErrors ? "LOSE" : "WIN") + "**!\n" + GetHangman(); - var embed = new EmbedBuilder().WithTitle("Hangman Game") - .WithDescription(toSend) - .AddField(efb => efb.WithName("It was").WithValue(Term.Word)) - .WithImageUrl(Term.ImageUrl) - .WithFooter(efb => efb.WithText(string.Join(" ", Guesses))); - if (Errors >= MaxErrors) - await GameChannel.EmbedAsync(embed.WithErrorColor()).ConfigureAwait(false); - else - await GameChannel.EmbedAsync(embed.WithOkColor()).ConfigureAwait(false); - } - - private Task PotentialGuess(SocketMessage msg) - { - var _ = Task.Run(async () => - { - try - { - if (!(msg is SocketUserMessage)) - return; - - if (msg.Channel != GameChannel) - return; // message's channel has to be the same as game's - if (msg.Content.Length == 1) // message must be 1 char long - { - if (++MessagesSinceLastPost > 10) - { - MessagesSinceLastPost = 0; - try - { - await GameChannel.SendConfirmAsync("Hangman Game", - ScrambledWord + "\n" + GetHangman(), - footer: string.Join(" ", Guesses)).ConfigureAwait(false); - } - catch { } - } - - if (!(char.IsLetter(msg.Content[0]) || char.IsDigit(msg.Content[0])))// and a letter or a digit - return; - - var guess = char.ToUpperInvariant(msg.Content[0]); - if (Guesses.Contains(guess)) - { - MessagesSinceLastPost = 0; - ++Errors; - if (Errors < MaxErrors) - await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author} Letter `{guess}` has already been used.\n" + ScrambledWord + "\n" + GetHangman(), - footer: string.Join(" ", Guesses)).ConfigureAwait(false); - else - await End().ConfigureAwait(false); - return; - } - - Guesses.Add(guess); - - if (Term.Word.ToUpperInvariant().Contains(guess)) - { - if (GuessedAll) - { - try { await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author} guessed a letter `{guess}`!").ConfigureAwait(false); } catch { } - - await End().ConfigureAwait(false); - return; - } - MessagesSinceLastPost = 0; - try - { - await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author} guessed a letter `{guess}`!\n" + ScrambledWord + "\n" + GetHangman(), - footer: string.Join(" ", Guesses)).ConfigureAwait(false); - } - catch { } - - } - else - { - MessagesSinceLastPost = 0; - ++Errors; - if (Errors < MaxErrors) - await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author} Letter `{guess}` does not exist.\n" + ScrambledWord + "\n" + GetHangman(), - footer: string.Join(" ", Guesses)).ConfigureAwait(false); - else - await End().ConfigureAwait(false); - } - - } - } - catch (Exception ex) { _log.Warn(ex); } - }); - return Task.CompletedTask; - } - - public string GetHangman() => $@". ┌─────┐ -.┃...............┋ -.┃...............┋ -.┃{(Errors > 0 ? ".............😲" : "")} -.┃{(Errors > 1 ? "............./" : "")} {(Errors > 2 ? "|" : "")} {(Errors > 3 ? "\\" : "")} -.┃{(Errors > 4 ? "............../" : "")} {(Errors > 5 ? "\\" : "")} -/-\"; - - public void Dispose() - { - _client.MessageReceived -= PotentialGuess; - OnEnded = null; - } - } -} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Commands/HangmanCommands.cs b/src/NadekoBot/Modules/Games/Commands/HangmanCommands.cs deleted file mode 100644 index 633be81f..00000000 --- a/src/NadekoBot/Modules/Games/Commands/HangmanCommands.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Discord.Commands; -using NadekoBot.Attributes; -using NadekoBot.Extensions; -using System; -using System.Collections.Concurrent; -using System.Threading.Tasks; -using NadekoBot.Modules.Games.Hangman; -using Discord; -using Discord.WebSocket; - -namespace NadekoBot.Modules.Games -{ - public partial class Games - { - [Group] - public class HangmanCommands : NadekoSubmodule - { - private readonly DiscordShardedClient _client; - - public HangmanCommands(DiscordShardedClient client) - { - _client = client; - } - - //channelId, game - public static ConcurrentDictionary HangmanGames { get; } = new ConcurrentDictionary(); - [NadekoCommand, Usage, Description, Aliases] - public async Task Hangmanlist() - { - await Context.Channel.SendConfirmAsync(Format.Code(GetText("hangman_types", Prefix)) + "\n" + string.Join(", ", HangmanTermPool.data.Keys)); - } - - [NadekoCommand, Usage, Description, Aliases] - public async Task Hangman([Remainder]string type = "All") - { - var hm = new HangmanGame(_client, Context.Channel, type); - - if (!HangmanGames.TryAdd(Context.Channel.Id, hm)) - { - await ReplyErrorLocalized("hangman_running").ConfigureAwait(false); - return; - } - - hm.OnEnded += g => - { - HangmanGames.TryRemove(g.GameChannel.Id, out HangmanGame throwaway); - }; - try - { - hm.Start(); - } - catch (Exception ex) - { - try { await Context.Channel.SendErrorAsync(GetText("hangman_start_errored") + " " + ex.Message).ConfigureAwait(false); } catch { } - HangmanGames.TryRemove(Context.Channel.Id, out HangmanGame throwaway); - throwaway.Dispose(); - return; - } - - await Context.Channel.SendConfirmAsync(GetText("hangman_game_started"), hm.ScrambledWord + "\n" + hm.GetHangman()); - } - } - } -} diff --git a/src/NadekoBot/Modules/Games/Common/Acrophobia/Acrophobia.cs b/src/NadekoBot/Modules/Games/Common/Acrophobia/Acrophobia.cs new file mode 100644 index 00000000..daeaf8a6 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Common/Acrophobia/Acrophobia.cs @@ -0,0 +1,177 @@ +using NadekoBot.Common; +using NadekoBot.Extensions; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games.Common.Acrophobia +{ + /// + /// Platform-agnostic acrophobia game + /// + public class Acrophobia : IDisposable + { + private const int VotingPhaseLength = 30; + + public enum Phase + { + Submission, + Voting, + Ended + } + + public enum UserInputResult + { + Submitted, + SubmissionFailed, + Voted, + VotingFailed, + Failed + } + + public int SubmissionPhaseLength { get; } + + public Phase CurrentPhase { get; private set; } = Phase.Submission; + public ImmutableArray StartingLetters { get; private set; } + + private readonly Dictionary submissions = new Dictionary(); + private readonly SemaphoreSlim locker = new SemaphoreSlim(1, 1); + private readonly NadekoRandom _rng; + + public event Func OnStarted = delegate { return Task.CompletedTask; }; + public event Func>, Task> OnVotingStarted = delegate { return Task.CompletedTask; }; + public event Func OnUserVoted = delegate { return Task.CompletedTask; }; + public event Func>, Task> OnEnded = delegate { return Task.CompletedTask; }; + + private readonly HashSet _usersWhoVoted = new HashSet(); + + public Acrophobia(int submissionPhaseLength = 30) + { + _rng = new NadekoRandom(); + SubmissionPhaseLength = submissionPhaseLength; + InitializeStartingLetters(); + } + + public async Task Run() + { + await OnStarted(this).ConfigureAwait(false); + await Task.Delay(SubmissionPhaseLength * 1000); + await locker.WaitAsync().ConfigureAwait(false); + try + { + if (submissions.Count == 0) + { + CurrentPhase = Phase.Ended; + await OnVotingStarted(this, ImmutableArray.Create>()).ConfigureAwait(false); + return; + } + if (submissions.Count == 1) + { + CurrentPhase = Phase.Ended; + await OnVotingStarted(this, submissions.ToArray().ToImmutableArray()).ConfigureAwait(false); + return; + } + + CurrentPhase = Phase.Voting; + + await OnVotingStarted(this, submissions.ToArray().ToImmutableArray()).ConfigureAwait(false); + } + finally { locker.Release(); } + + await Task.Delay(VotingPhaseLength * 1000); + await locker.WaitAsync().ConfigureAwait(false); + try + { + CurrentPhase = Phase.Ended; + await OnEnded(this, submissions.ToArray().ToImmutableArray()).ConfigureAwait(false) ; + } + finally { locker.Release(); } + } + + private void InitializeStartingLetters() + { + var wordCount = _rng.Next(3, 6); + + var lettersArr = new char[wordCount]; + + for (int i = 0; i < wordCount; i++) + { + var randChar = (char)_rng.Next(65, 91); + lettersArr[i] = randChar == 'X' ? (char)_rng.Next(65, 88) : randChar; + } + StartingLetters = lettersArr.ToImmutableArray(); + } + + public async Task UserInput(ulong userId, string userName, string input) + { + var user = new AcrophobiaUser(userId, userName, input.ToLowerInvariant().ToTitleCase()); + + await locker.WaitAsync(); + try + { + switch (CurrentPhase) + { + case Phase.Submission: + if (submissions.ContainsKey(user) || !IsValidAnswer(input)) + break; + + submissions.Add(user, 0); + return true; + case Phase.Voting: + AcrophobiaUser toVoteFor; + if (!int.TryParse(input, out var index) + || --index < 0 + || index >= submissions.Count + || (toVoteFor = submissions.ToArray()[index].Key).UserId == user.UserId + || !_usersWhoVoted.Add(userId)) + break; + ++submissions[toVoteFor]; + var _ = Task.Run(() => OnUserVoted(userName)); + return true; + default: + break; + } + return false; + } + finally + { + locker.Release(); + } + } + + private bool IsValidAnswer(string input) + { + input = input.ToUpperInvariant(); + + var inputWords = input.Split(' '); + + if (inputWords.Length != StartingLetters.Length) // number of words must be the same as the number of the starting letters + return false; + + for (int i = 0; i < StartingLetters.Length; i++) + { + var letter = StartingLetters[i]; + + if (!inputWords[i].StartsWith(letter.ToString())) // all first letters must match + return false; + } + + return true; + } + + public void Dispose() + { + this.CurrentPhase = Phase.Ended; + OnStarted = null; + OnEnded = null; + OnUserVoted = null; + OnVotingStarted = null; + _usersWhoVoted.Clear(); + submissions.Clear(); + locker.Dispose(); + } + } +} diff --git a/src/NadekoBot/Modules/Games/Common/Acrophobia/AcrophobiaUser.cs b/src/NadekoBot/Modules/Games/Common/Acrophobia/AcrophobiaUser.cs new file mode 100644 index 00000000..8801e700 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Common/Acrophobia/AcrophobiaUser.cs @@ -0,0 +1,28 @@ +namespace NadekoBot.Modules.Games.Common.Acrophobia +{ + public class AcrophobiaUser + { + public string UserName { get; } + public ulong UserId { get; } + public string Input { get; } + + public AcrophobiaUser(ulong userId, string userName, string input) + { + this.UserName = userName; + this.UserId = userId; + this.Input = input; + } + + public override int GetHashCode() + { + return UserId.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is AcrophobiaUser x + ? x.UserId == this.UserId + : false; + } + } +} diff --git a/src/NadekoBot/Services/Games/ChatterBotResponse.cs b/src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotResponse.cs similarity index 71% rename from src/NadekoBot/Services/Games/ChatterBotResponse.cs rename to src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotResponse.cs index b064af4c..acf5c3de 100644 --- a/src/NadekoBot/Services/Games/ChatterBotResponse.cs +++ b/src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotResponse.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Services.Games +namespace NadekoBot.Modules.Games.Common.ChatterBot { public class ChatterBotResponse { diff --git a/src/NadekoBot/Services/Games/ChatterBotSession.cs b/src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotSession.cs similarity index 59% rename from src/NadekoBot/Services/Games/ChatterBotSession.cs rename to src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotSession.cs index 920ea709..347a705a 100644 --- a/src/NadekoBot/Services/Games/ChatterBotSession.cs +++ b/src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotSession.cs @@ -1,27 +1,27 @@ -using NadekoBot.Extensions; -using Newtonsoft.Json; -using System.Net.Http; +using System.Net.Http; using System.Threading.Tasks; +using NadekoBot.Common; +using NadekoBot.Extensions; +using Newtonsoft.Json; -namespace NadekoBot.Services.Games +namespace NadekoBot.Modules.Games.Common.ChatterBot { - public class ChatterBotSession + public class ChatterBotSession : IChatterBotSession { - private static NadekoRandom rng { get; } = new NadekoRandom(); - public string ChatterbotId { get; } - public string ChannelId { get; } + private static NadekoRandom Rng { get; } = new NadekoRandom(); + + private readonly string _chatterBotId; private int _botId = 6; - public ChatterBotSession(ulong channelId) + public ChatterBotSession() { - ChannelId = channelId.ToString().ToBase64(); - ChatterbotId = rng.Next(0, 1000000).ToString().ToBase64(); + _chatterBotId = Rng.Next(0, 1000000).ToString().ToBase64(); } private string apiEndpoint => "http://api.program-o.com/v2/chatbot/" + $"?bot_id={_botId}&" + "say={0}&" + - $"convo_id=nadekobot_{ChatterbotId}_{ChannelId}&" + + $"convo_id=nadekobot_{_chatterBotId}&" + "format=json"; public async Task Think(string message) diff --git a/src/NadekoBot/Modules/Games/Common/ChatterBot/CleverbotResponse.cs b/src/NadekoBot/Modules/Games/Common/ChatterBot/CleverbotResponse.cs new file mode 100644 index 00000000..23e2b9e8 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Common/ChatterBot/CleverbotResponse.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games.Common.ChatterBot +{ + public class CleverbotResponse + { + public string Cs { get; set; } + public string Output { get; set; } + } +} diff --git a/src/NadekoBot/Modules/Games/Common/ChatterBot/IChatterBotSession.cs b/src/NadekoBot/Modules/Games/Common/ChatterBot/IChatterBotSession.cs new file mode 100644 index 00000000..14b749dc --- /dev/null +++ b/src/NadekoBot/Modules/Games/Common/ChatterBot/IChatterBotSession.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games.Common.ChatterBot +{ + public interface IChatterBotSession + { + Task Think(string input); + } +} diff --git a/src/NadekoBot/Modules/Games/Common/ChatterBot/OfficialCleverbotSession.cs b/src/NadekoBot/Modules/Games/Common/ChatterBot/OfficialCleverbotSession.cs new file mode 100644 index 00000000..a970f135 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Common/ChatterBot/OfficialCleverbotSession.cs @@ -0,0 +1,33 @@ +using Newtonsoft.Json; +using System.Net.Http; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games.Common.ChatterBot +{ + public class OfficialCleverbotSession : IChatterBotSession + { + private readonly string _apiKey; + private string _cs = null; + + private string queryString => $"https://www.cleverbot.com/getreply?key={_apiKey}" + + "&wrapper=nadekobot" + + "&input={0}" + + "&cs={1}"; + + public OfficialCleverbotSession(string apiKey) + { + this._apiKey = apiKey; + } + + public async Task Think(string input) + { + using (var http = new HttpClient()) + { + var dataString = await http.GetStringAsync(string.Format(queryString, input, _cs ?? "")).ConfigureAwait(false); + var data = JsonConvert.DeserializeObject(dataString); + _cs = data?.Cs; + return data?.Output; + } + } + } +} diff --git a/src/NadekoBot/Modules/Games/Common/Connect4/Connect4.cs b/src/NadekoBot/Modules/Games/Common/Connect4/Connect4.cs new file mode 100644 index 00000000..ae475e5e --- /dev/null +++ b/src/NadekoBot/Modules/Games/Common/Connect4/Connect4.cs @@ -0,0 +1,364 @@ +using NadekoBot.Common; +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games.Common.Connect4 +{ + public class Connect4Game : IDisposable + { + public enum Phase + { + Joining, // waiting for second player to join + P1Move, + P2Move, + Ended, + } + + public enum Field //temporary most likely + { + Empty, + P1, + P2, + } + + public enum Result + { + Draw, + CurrentPlayerWon, + OtherPlayerWon, + } + + public const int NumberOfColumns = 6; + public const int NumberOfRows = 7; + + public Phase CurrentPhase { get; private set; } = Phase.Joining; + + //state is bottom to top, left to right + private readonly Field[] _gameState = new Field[NumberOfRows * NumberOfColumns]; + private readonly (ulong UserId, string Username)?[] _players = new(ulong, string)?[2]; + + public ImmutableArray GameState => _gameState.ToImmutableArray(); + public ImmutableArray<(ulong UserId, string Username)?> Players => _players.ToImmutableArray(); + + public string CurrentPlayer => CurrentPhase == Phase.P1Move + ? _players[0].Value.Username + : _players[1].Value.Username; + + public string OtherPlayer => CurrentPhase == Phase.P2Move + ? _players[0].Value.Username + : _players[1].Value.Username; + + //public event Func OnGameStarted; + public event Func OnGameStateUpdated; + public event Func OnGameFailedToStart; + public event Func OnGameEnded; + + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1); + private readonly NadekoRandom _rng; + + private Timer _playerTimeoutTimer; + + /* [ ][ ][ ][ ][ ][ ] + * [ ][ ][ ][ ][ ][ ] + * [ ][ ][ ][ ][ ][ ] + * [ ][ ][ ][ ][ ][ ] + * [ ][ ][ ][ ][ ][ ] + * [ ][ ][ ][ ][ ][ ] + * [ ][ ][ ][ ][ ][ ] + */ + + public Connect4Game(ulong userId, string userName) + { + _players[0] = (userId, userName); + + _rng = new NadekoRandom(); + for (int i = 0; i < NumberOfColumns * NumberOfRows; i++) + { + _gameState[i] = Field.Empty; + } + } + + public void Initialize() + { + if (CurrentPhase != Phase.Joining) + return; + var _ = Task.Run(async () => + { + await Task.Delay(15000).ConfigureAwait(false); + await _locker.WaitAsync().ConfigureAwait(false); + try + { + if (_players[1] == null) + { + var __ = OnGameFailedToStart?.Invoke(this); + CurrentPhase = Phase.Ended; + return; + } + } + finally { _locker.Release(); } + }); + } + + public async Task Join(ulong userId, string userName) + { + await _locker.WaitAsync().ConfigureAwait(false); + try + { + if (CurrentPhase != Phase.Joining) //can't join if its not a joining phase + return false; + + if (_players[0].Value.UserId == userId) // same user can't join own game + return false; + + if (_rng.Next(0, 2) == 0) //rolling from 0-1, if number is 0, join as first player + { + _players[1] = _players[0]; + _players[0] = (userId, userName); + } + else //else join as a second player + _players[1] = (userId, userName); + + CurrentPhase = Phase.P1Move; //start the game + _playerTimeoutTimer = new Timer(async state => + { + await _locker.WaitAsync().ConfigureAwait(false); + try + { + EndGame(Result.OtherPlayerWon); + } + finally { _locker.Release(); } + }, null, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30)); + var __ = OnGameStateUpdated?.Invoke(this); + + return true; + } + finally { _locker.Release(); } + } + + public async Task Input(ulong userId, string userName, int inputCol) + { + await _locker.WaitAsync().ConfigureAwait(false); + try + { + inputCol -= 1; + if (CurrentPhase == Phase.Ended || CurrentPhase == Phase.Joining) + return false; + + if (!((_players[0].Value.UserId == userId && CurrentPhase == Phase.P1Move) + || (_players[1].Value.UserId == userId && CurrentPhase == Phase.P2Move))) + return false; + + if (inputCol < 0 || inputCol > NumberOfColumns) //invalid input + return false; + + if (IsColumnFull(inputCol)) //can't play there event? + return false; + + var start = NumberOfRows * inputCol; + for (int i = start; i < start + NumberOfRows; i++) + { + if (_gameState[i] == Field.Empty) + { + _gameState[i] = GetPlayerPiece(userId); + break; + } + } + + //check winnning condition + // ok, i'll go from [0-2] in rows (and through all columns) and check upward if 4 are connected + + for (int i = 0; i < NumberOfRows - 3; i++) + { + if (CurrentPhase == Phase.Ended) + break; + + for (int j = 0; j < NumberOfColumns; j++) + { + if (CurrentPhase == Phase.Ended) + break; + + var first = _gameState[i + j * NumberOfRows]; + if (first != Field.Empty) + { + //Console.WriteLine(i + j * NumberOfRows); + for (int k = 1; k < 4; k++) + { + var next = _gameState[i + k + j * NumberOfRows]; + if (next == first) + { + //Console.WriteLine(i + k + j * NumberOfRows); + if (k == 3) + EndGame(Result.CurrentPlayerWon); + else + continue; + } + else break; + } + } + } + } + + // i'll go [0-1] in columns (and through all rows) and check to the right if 4 are connected + for (int i = 0; i < NumberOfColumns - 3; i++) + { + if (CurrentPhase == Phase.Ended) + break; + + for (int j = 0; j < NumberOfRows; j++) + { + if (CurrentPhase == Phase.Ended) + break; + + var first = _gameState[j + i * NumberOfRows]; + if (first != Field.Empty) + { + for (int k = 1; k < 4; k++) + { + var next = _gameState[j + (i + k) * NumberOfRows]; + if (next == first) + if (k == 3) + EndGame(Result.CurrentPlayerWon); + else + continue; + else break; + } + } + } + } + + //need to check diagonal now + for (int col = 0; col < NumberOfColumns; col++) + { + if (CurrentPhase == Phase.Ended) + break; + + for (int row = 0; row < NumberOfRows; row++) + { + if (CurrentPhase == Phase.Ended) + break; + + var first = _gameState[row + col * NumberOfRows]; + + if (first != Field.Empty) + { + var same = 1; + + //top left + for (int i = 1; i < 4; i++) + { + //while going top left, rows are increasing, columns are decreasing + var curRow = row + i; + var curCol = col - i; + + //check if current values are in range + if (curRow >= NumberOfRows || curRow < 0) + break; + if (curCol < 0 || curCol >= NumberOfColumns) + break; + + var cur = _gameState[curRow + curCol * NumberOfRows]; + if (cur == first) + same++; + else break; + } + + if (same == 4) + { + Console.WriteLine($"Won top left diagonal starting from {row + col * NumberOfRows}"); + EndGame(Result.CurrentPlayerWon); + break; + } + + same = 1; + + //top right + for (int i = 1; i < 4; i++) + { + //while going top right, rows are increasing, columns are increasing + var curRow = row + i; + var curCol = col + i; + + //check if current values are in range + if (curRow >= NumberOfRows || curRow < 0) + break; + if (curCol < 0 || curCol >= NumberOfColumns) + break; + + var cur = _gameState[curRow + curCol * NumberOfRows]; + if (cur == first) + same++; + else break; + } + + if (same == 4) + { + Console.WriteLine($"Won top right diagonal starting from {row + col * NumberOfRows}"); + EndGame(Result.CurrentPlayerWon); + break; + } + } + } + } + + //check draw? if it's even possible + if (_gameState.All(x => x != Field.Empty)) + { + EndGame(Result.Draw); + } + + if (CurrentPhase != Phase.Ended) + { + if (CurrentPhase == Phase.P1Move) + CurrentPhase = Phase.P2Move; + else + CurrentPhase = Phase.P1Move; + + ResetTimer(); + } + var _ = OnGameStateUpdated?.Invoke(this); + return true; + } + finally { _locker.Release(); } + } + + private void ResetTimer() + { + _playerTimeoutTimer.Change(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30)); + } + + private void EndGame(Result result) + { + if (CurrentPhase == Phase.Ended) + return; + var _ = OnGameEnded?.Invoke(this, result); + CurrentPhase = Phase.Ended; + } + + private Field GetPlayerPiece(ulong userId) => _players[0].Value.UserId == userId + ? Field.P1 + : Field.P2; + + //column is full if there are no empty fields + private bool IsColumnFull(int column) + { + var start = NumberOfRows * column; + for (int i = start; i < start + NumberOfRows; i++) + { + if (_gameState[i] == Field.Empty) + return false; + } + return true; + } + + public void Dispose() + { + OnGameFailedToStart = null; + OnGameStateUpdated = null; + OnGameEnded = null; + _playerTimeoutTimer?.Change(Timeout.Infinite, Timeout.Infinite); + } + } +} diff --git a/src/NadekoBot/Services/Games/GirlRating.cs b/src/NadekoBot/Modules/Games/Common/GirlRating.cs similarity index 87% rename from src/NadekoBot/Services/Games/GirlRating.cs rename to src/NadekoBot/Modules/Games/Common/GirlRating.cs index 9cf72945..d605be0d 100644 --- a/src/NadekoBot/Services/Games/GirlRating.cs +++ b/src/NadekoBot/Modules/Games/Common/GirlRating.cs @@ -1,13 +1,15 @@ -using ImageSharp; -using NadekoBot.DataStructures; -using NadekoBot.Extensions; -using NLog; -using System; +using System; using System.IO; using System.Linq; using System.Net.Http; +using ImageSharp; +using NadekoBot.Common; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NLog; +using SixLabors.Primitives; -namespace NadekoBot.Services.Games +namespace NadekoBot.Modules.Games.Common { public class GirlRating { @@ -31,7 +33,7 @@ namespace NadekoBot.Services.Games try { using (var ms = new MemoryStream(_images.WifeMatrix.ToArray(), false)) - using (var img = new ImageSharp.Image(ms)) + using (var img = Image.Load(ms)) { const int minx = 35; const int miny = 385; @@ -41,7 +43,7 @@ namespace NadekoBot.Services.Games var pointy = (int)(miny - length * ((Crazy - 4) / 6)); using (var pointMs = new MemoryStream(_images.RategirlDot.ToArray(), false)) - using (var pointImg = new ImageSharp.Image(pointMs)) + using (var pointImg = Image.Load(pointMs)) { img.DrawImage(pointImg, 100, default(Size), new Point(pointx - 10, pointy - 10)); } @@ -50,7 +52,7 @@ namespace NadekoBot.Services.Games using (var http = new HttpClient()) using (var imgStream = new MemoryStream()) { - img.Save(imgStream); + img.SaveAsPng(imgStream); var byteContent = new ByteArrayContent(imgStream.ToArray()); http.AddFakeHeaders(); diff --git a/src/NadekoBot/Modules/Games/Common/Hangman/Exceptions/TermNotFoundException.cs b/src/NadekoBot/Modules/Games/Common/Hangman/Exceptions/TermNotFoundException.cs new file mode 100644 index 00000000..01573bf1 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Common/Hangman/Exceptions/TermNotFoundException.cs @@ -0,0 +1,11 @@ +using System; + +namespace NadekoBot.Modules.Games.Common.Hangman.Exceptions +{ + public class TermNotFoundException : Exception + { + public TermNotFoundException() : base("Term of that type couldn't be found") + { + } + } +} diff --git a/src/NadekoBot/Modules/Games/Common/Hangman/Hangman.cs b/src/NadekoBot/Modules/Games/Common/Hangman/Hangman.cs new file mode 100644 index 00000000..6c91edc1 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Common/Hangman/Hangman.cs @@ -0,0 +1,170 @@ +using NadekoBot.Extensions; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games.Common.Hangman +{ + public class Hangman : IDisposable + { + public string TermType { get; } + public HangmanObject Term { get; } + + public string ScrambledWord => "`" + String.Concat(Term.Word.Select(c => + { + if (c == ' ') + return " \u2000"; + if (!(char.IsLetter(c) || char.IsDigit(c))) + return $" {c}"; + + c = char.ToLowerInvariant(c); + return _previousGuesses.Contains(c) ? $" {c}" : " ◯"; + })) + "`"; + + private Phase _currentPhase = Phase.Active; + public Phase CurrentPhase + { + get => _currentPhase; + set + { + if (value == Phase.Ended) + _endingCompletionSource.TrySetResult(true); + + _currentPhase = value; + } + } + + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1); + + private readonly HashSet _recentUsers = new HashSet(); + + public uint Errors { get; private set; } = 0; + public uint MaxErrors { get; } = 6; + + public event Func OnGameEnded = delegate { return Task.CompletedTask; }; + public event Func OnLetterAlreadyUsed = delegate { return Task.CompletedTask; }; + public event Func OnGuessFailed = delegate { return Task.CompletedTask; }; + public event Func OnGuessSucceeded = delegate { return Task.CompletedTask; }; + + private readonly HashSet _previousGuesses = new HashSet(); + public ImmutableArray PreviousGuesses => _previousGuesses.ToImmutableArray(); + + private readonly TaskCompletionSource _endingCompletionSource = new TaskCompletionSource(); + + public Task EndedTask => _endingCompletionSource.Task; + + public Hangman(TermType type) + { + this.TermType = type.ToString().Replace('_', ' ').ToTitleCase(); + this.Term = TermPool.GetTerm(type); + } + + private void AddError() + { + Errors++; + if (Errors > MaxErrors) + { + var _ = OnGameEnded(this, null); + CurrentPhase = Phase.Ended; + } + } + + public string GetHangman() => $@". ┌─────┐ +.┃...............┋ +.┃...............┋ +.┃{(Errors > 0 ? ".............😲" : "")} +.┃{(Errors > 1 ? "............./" : "")} {(Errors > 2 ? "|" : "")} {(Errors > 3 ? "\\" : "")} +.┃{(Errors > 4 ? "............../" : "")} {(Errors > 5 ? "\\" : "")} +/-\"; + + public async Task Input(ulong userId, string userName, string input) + { + if (CurrentPhase == Phase.Ended) + return; + + if (string.IsNullOrWhiteSpace(input)) + return; + + input = input.Trim().ToLowerInvariant(); + + await _locker.WaitAsync().ConfigureAwait(false); + try + { + if (CurrentPhase == Phase.Ended) + return; + + if (input.Length > 1) // tried to guess the whole word + { + if (input != Term.Word) // failed + return; + + var _ = OnGameEnded?.Invoke(this, userName); + CurrentPhase = Phase.Ended; + return; + } + + var ch = input[0]; + + if (!(char.IsLetterOrDigit(ch))) + return; + + if (!_recentUsers.Add(userId)) // don't let a single user spam guesses + return; + + if (!_previousGuesses.Add(ch)) // that latter was already guessed + { + var _ = OnLetterAlreadyUsed?.Invoke(this, userName, ch); + AddError(); + } + else if (!Term.Word.Contains(ch)) // guessed letter doesn't exist + { + var _ = OnGuessFailed?.Invoke(this, userName, ch); + AddError(); + } + else if (Term.Word.All(x => _previousGuesses.IsSupersetOf(Term.Word.ToLowerInvariant() + .Where(c => char.IsLetterOrDigit(c))))) + { + var _ = OnGameEnded.Invoke(this, userName); //if all letters are guessed + CurrentPhase = Phase.Ended; + } + else //guessed but not last letter + { + var _ = OnGuessSucceeded?.Invoke(this, userName, ch); + _recentUsers.Remove(userId); // he can guess again right away + return; + } + + var clearSpam = Task.Run(async () => + { + await Task.Delay(3000).ConfigureAwait(false); // remove the user from the spamlist after 5 seconds + _recentUsers.Remove(userId); + }); + } + finally { _locker.Release(); } + } + + public async Task Stop() + { + await _locker.WaitAsync().ConfigureAwait(false); + try + { + CurrentPhase = Phase.Ended; + } + finally { _locker.Release(); } + } + + public void Dispose() + { + OnGameEnded = null; + OnGuessFailed = null; + OnGuessSucceeded = null; + OnLetterAlreadyUsed = null; + _previousGuesses.Clear(); + _recentUsers.Clear(); + _locker.Dispose(); + } + } +} diff --git a/src/NadekoBot/Modules/Games/Commands/Hangman/IHangmanObject.cs b/src/NadekoBot/Modules/Games/Common/Hangman/HangmanObject.cs similarity index 71% rename from src/NadekoBot/Modules/Games/Commands/Hangman/IHangmanObject.cs rename to src/NadekoBot/Modules/Games/Common/Hangman/HangmanObject.cs index c2069477..4179e7bc 100644 --- a/src/NadekoBot/Modules/Games/Commands/Hangman/IHangmanObject.cs +++ b/src/NadekoBot/Modules/Games/Common/Hangman/HangmanObject.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Modules.Games.Commands.Hangman +namespace NadekoBot.Modules.Games.Common.Hangman { public class HangmanObject { diff --git a/src/NadekoBot/Modules/Games/Common/Hangman/Phase.cs b/src/NadekoBot/Modules/Games/Common/Hangman/Phase.cs new file mode 100644 index 00000000..b23b856b --- /dev/null +++ b/src/NadekoBot/Modules/Games/Common/Hangman/Phase.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games.Common.Hangman +{ + public enum Phase + { + Active, + Ended, + } +} diff --git a/src/NadekoBot/Modules/Games/Common/Hangman/TermPool.cs b/src/NadekoBot/Modules/Games/Common/Hangman/TermPool.cs new file mode 100644 index 00000000..94423b54 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Common/Hangman/TermPool.cs @@ -0,0 +1,51 @@ +using NadekoBot.Common; +using NadekoBot.Modules.Games.Common.Hangman.Exceptions; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; + +namespace NadekoBot.Modules.Games.Common.Hangman +{ + public class TermPool + { + const string termsPath = "data/hangman3.json"; + public static IReadOnlyDictionary Data { get; } = new Dictionary(); + static TermPool() + { + try + { + Data = JsonConvert.DeserializeObject>(File.ReadAllText(termsPath)); + } + catch (Exception) + { + //ignored + } + } + + private static readonly ImmutableArray _termTypes = Enum.GetValues(typeof(TermType)) + .Cast() + .ToImmutableArray(); + + public static HangmanObject GetTerm(TermType type) + { + var rng = new NadekoRandom(); + + if (type == TermType.Random) + { + var keys = Data.Keys.ToArray(); + + type = _termTypes[rng.Next(0, _termTypes.Length - 1)]; // - 1 because last one is 'all' + } + if (!Data.TryGetValue(type.ToString(), out var termTypes) || termTypes.Length == 0) + throw new TermNotFoundException(); + + var obj = termTypes[rng.Next(0, termTypes.Length)]; + + obj.Word = obj.Word.Trim().ToLowerInvariant(); + return obj; + } + } +} diff --git a/src/NadekoBot/Modules/Games/Common/Hangman/TermType.cs b/src/NadekoBot/Modules/Games/Common/Hangman/TermType.cs new file mode 100644 index 00000000..f3c09af6 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Common/Hangman/TermType.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games.Common.Hangman +{ + [Flags] + public enum TermType + { + Countries = 0, + Movies = 1, + Animals = 2, + Things = 4, + Random = 8, + } +} diff --git a/src/NadekoBot/Modules/Games/Common/Nunchi/Nunchi.cs b/src/NadekoBot/Modules/Games/Common/Nunchi/Nunchi.cs new file mode 100644 index 00000000..1fd1cbcc --- /dev/null +++ b/src/NadekoBot/Modules/Games/Common/Nunchi/Nunchi.cs @@ -0,0 +1,181 @@ +using NadekoBot.Common; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games.Common.Nunchi +{ + public class Nunchi : IDisposable + { + public enum Phase + { + Joining, + Playing, + WaitingForNextRound, + Ended, + } + + public int CurrentNumber { get; private set; } = new NadekoRandom().Next(0, 100); + public Phase CurrentPhase { get; private set; } = Phase.Joining; + + public event Func OnGameStarted; + public event Func OnRoundStarted; + public event Func OnUserGuessed; + public event Func OnRoundEnded; // tuple of the user who failed + public event Func OnGameEnded; // name of the user who won + + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1); + + private HashSet<(ulong Id, string Name)> _participants = new HashSet<(ulong Id, string Name)>(); + private HashSet<(ulong Id, string Name)> _passed = new HashSet<(ulong Id, string Name)>(); + + public ImmutableArray<(ulong Id, string Name)> Participants => _participants.ToImmutableArray(); + public int ParticipantCount => _participants.Count; + + private const int _killTimeout = 20 * 1000; + private const int _nextRoundTimeout = 5 * 1000; + private Timer _killTimer; + + public Nunchi(ulong creatorId, string creatorName) + { + _participants.Add((creatorId, creatorName)); + } + + public async Task Join(ulong userId, string userName) + { + await _locker.WaitAsync().ConfigureAwait(false); + try + { + if (CurrentPhase != Phase.Joining) + return false; + + return _participants.Add((userId, userName)); + } + finally { _locker.Release(); } + } + + public async Task Initialize() + { + CurrentPhase = Phase.Joining; + await Task.Delay(30000).ConfigureAwait(false); + await _locker.WaitAsync().ConfigureAwait(false); + try + { + if (_participants.Count < 3) + { + CurrentPhase = Phase.Ended; + return false; + } + + _killTimer = new Timer(async state => + { + await _locker.WaitAsync().ConfigureAwait(false); + try + { + if (CurrentPhase != Phase.Playing) + return; + + //if some players took too long to type a number, boot them all out and start a new round + _participants = new HashSet<(ulong, string)>(_passed); + EndRound(); + } + finally { _locker.Release(); } + }, null, _killTimeout, _killTimeout); + + CurrentPhase = Phase.Playing; + var _ = OnGameStarted?.Invoke(this); + var __ = OnRoundStarted?.Invoke(this, CurrentNumber); + return true; + } + finally { _locker.Release(); } + } + + public async Task Input(ulong userId, string userName, int input) + { + await _locker.WaitAsync().ConfigureAwait(false); + try + { + if (CurrentPhase != Phase.Playing) + return; + + var userTuple = (Id: userId, Name: userName); + + // if the user is not a member of the race, + // or he already successfully typed the number + // ignore the input + if (!_participants.Contains(userTuple) || !_passed.Add(userTuple)) + return; + + //if the number is correct + if (CurrentNumber == input - 1) + { + //increment current number + ++CurrentNumber; + if (_passed.Count == _participants.Count - 1) + { + // if only n players are left, and n - 1 type the correct number, round is over + + // if only 2 players are left, game is over + if (_participants.Count == 2) + { + _killTimer.Change(Timeout.Infinite, Timeout.Infinite); + CurrentPhase = Phase.Ended; + var _ = OnGameEnded?.Invoke(this, userTuple.Name); + } + else // else just start the new round without the user who was the last + { + var failure = _participants.Except(_passed).First(); + EndRound(failure); + } + } + var __ = OnUserGuessed?.Invoke(this); + } + else + { + //if the user failed + + EndRound(userTuple); + } + } + finally { _locker.Release(); } + } + + private void EndRound((ulong, string)? failure = null) + { + _killTimer.Change(_killTimeout, _killTimeout); + CurrentNumber = new NadekoRandom().Next(0, 100); // reset the counter + _passed.Clear(); // reset all users who passed (new round starts) + if(failure != null) + _participants.Remove(failure.Value); // remove the dude who failed from the list of players + + var __ = OnRoundEnded?.Invoke(this, failure); + if (_participants.Count <= 1) // means we have a winner or everyone was booted out + { + _killTimer.Change(Timeout.Infinite, Timeout.Infinite); + CurrentPhase = Phase.Ended; + var _ = OnGameEnded?.Invoke(this, _participants.Count > 0 ? _participants.First().Name : null); + return; + } + CurrentPhase = Phase.WaitingForNextRound; + var throwawayDelay = Task.Run(async () => + { + await Task.Delay(_nextRoundTimeout).ConfigureAwait(false); + CurrentPhase = Phase.Playing; + var ___ = OnRoundStarted?.Invoke(this, CurrentNumber); + }); + + } + + public void Dispose() + { + OnGameEnded = null; + OnGameStarted = null; + OnRoundEnded = null; + OnRoundStarted = null; + OnUserGuessed = null; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Games/Poll.cs b/src/NadekoBot/Modules/Games/Common/Poll.cs similarity index 66% rename from src/NadekoBot/Services/Games/Poll.cs rename to src/NadekoBot/Modules/Games/Common/Poll.cs index 8feb4cb5..cf051a07 100644 --- a/src/NadekoBot/Services/Games/Poll.cs +++ b/src/NadekoBot/Modules/Games/Common/Poll.cs @@ -1,33 +1,30 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.Extensions; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using NadekoBot.Extensions; +using NadekoBot.Services.Impl; -namespace NadekoBot.Services.Games +namespace NadekoBot.Modules.Games.Common { - //todo 75 rewrite public class Poll { private readonly IUserMessage _originalMessage; private readonly IGuild _guild; - private string[] answers { get; } + private readonly string[] answers; private readonly ConcurrentDictionary _participants = new ConcurrentDictionary(); private readonly string _question; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly NadekoStrings _strings; private bool running = false; - private HashSet _guildUsers; public event Action OnEnded = delegate { }; - public bool IsPublic { get; } - - public Poll(DiscordShardedClient client, NadekoStrings strings, IUserMessage umsg, string question, IEnumerable enumerable, bool isPublic = false) + public Poll(DiscordSocketClient client, NadekoStrings strings, IUserMessage umsg, string question, IEnumerable enumerable) { _client = client; _strings = strings; @@ -36,7 +33,6 @@ namespace NadekoBot.Services.Games _guild = ((ITextChannel)umsg.Channel).Guild; _question = question; answers = enumerable as string[] ?? enumerable.ToArray(); - IsPublic = isPublic; } public EmbedBuilder GetStats(string title) @@ -82,13 +78,7 @@ namespace NadekoBot.Services.Games var msgToSend = GetText("poll_created", Format.Bold(_originalMessage.Author.Username)) + "\n\n" + Format.Bold(_question) + "\n"; var num = 1; msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n"); - if (!IsPublic) - msgToSend += "\n" + Format.Bold(GetText("poll_vote_private")); - else - msgToSend += "\n" + Format.Bold(GetText("poll_vote_public")); - - if (!IsPublic) - _guildUsers = new HashSet((await _guild.GetUsersAsync().ConfigureAwait(false)).Select(x => x.Id)); + msgToSend += "\n" + Format.Bold(GetText("poll_vote_public")); await _originalMessage.Channel.SendConfirmAsync(msgToSend).ConfigureAwait(false); running = true; @@ -114,36 +104,17 @@ namespace NadekoBot.Services.Games return false; IMessageChannel ch; - if (IsPublic) - { - //if public, channel must be the same the poll started in - if (_originalMessage.Channel.Id != msg.Channel.Id) - return false; - ch = msg.Channel; - } - else - { - //if private, channel must be dm channel - if ((ch = msg.Channel as IDMChannel) == null) - return false; - - // user must be a member of the guild this poll is in - if (!_guildUsers.Contains(msg.Author.Id)) - return false; - } + //if public, channel must be the same the poll started in + if (_originalMessage.Channel.Id != msg.Channel.Id) + return false; + ch = msg.Channel; //user can vote only once if (_participants.TryAdd(msg.Author.Id, vote)) { - if (!IsPublic) - { - await ch.SendConfirmAsync(GetText("thanks_for_voting", Format.Bold(msg.Author.Username))).ConfigureAwait(false); - } - else - { - var toDelete = await ch.SendConfirmAsync(GetText("poll_voted", Format.Bold(msg.Author.ToString()))).ConfigureAwait(false); - toDelete.DeleteAfter(5); - } + var toDelete = await ch.SendConfirmAsync(GetText("poll_voted", Format.Bold(msg.Author.ToString()))).ConfigureAwait(false); + toDelete.DeleteAfter(5); + try { await msg.DeleteAsync().ConfigureAwait(false); } catch { } return true; } return false; diff --git a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaGame.cs similarity index 77% rename from src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs rename to src/NadekoBot/Modules/Games/Common/Trivia/TriviaGame.cs index ea6e1e2c..abafdb67 100644 --- a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs +++ b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaGame.cs @@ -1,37 +1,37 @@ -using Discord; -using Discord.Net; -using Discord.WebSocket; -using NadekoBot.Extensions; -using NadekoBot.Services; -using NadekoBot.Services.Database.Models; -using NLog; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Discord; +using Discord.Net; +using Discord.WebSocket; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Impl; +using NLog; -namespace NadekoBot.Modules.Games.Trivia +namespace NadekoBot.Modules.Games.Common.Trivia { public class TriviaGame { private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1); private readonly Logger _log; private readonly NadekoStrings _strings; - private readonly DiscordShardedClient _client; - private readonly BotConfig _bc; + private readonly DiscordSocketClient _client; + private readonly IBotConfigProvider _bc; private readonly CurrencyService _cs; public IGuild Guild { get; } public ITextChannel Channel { get; } - private int questionDurationMiliseconds { get; } = 30000; - private int hintTimeoutMiliseconds { get; } = 6000; + private readonly int _questionDurationMiliseconds = 30000; + private readonly int _hintTimeoutMiliseconds = 6000; public bool ShowHints { get; } public bool IsPokemon { get; } - private CancellationTokenSource triviaCancelSource { get; set; } + private CancellationTokenSource _triviaCancelSource; public TriviaQuestion CurrentQuestion { get; private set; } public HashSet OldQuestions { get; } = new HashSet(); @@ -43,7 +43,7 @@ namespace NadekoBot.Modules.Games.Trivia public int WinRequirement { get; } - public TriviaGame(NadekoStrings strings, DiscordShardedClient client, BotConfig bc, + public TriviaGame(NadekoStrings strings, DiscordSocketClient client, IBotConfigProvider bc, CurrencyService cs, IGuild guild, ITextChannel channel, bool showHints, int winReq, bool isPokemon) { @@ -71,7 +71,7 @@ namespace NadekoBot.Modules.Games.Trivia while (!ShouldStopGame) { // reset the cancellation source - triviaCancelSource = new CancellationTokenSource(); + _triviaCancelSource = new CancellationTokenSource(); // load question CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(OldQuestions, IsPokemon); @@ -89,12 +89,13 @@ namespace NadekoBot.Modules.Games.Trivia questionEmbed = new EmbedBuilder().WithOkColor() .WithTitle(GetText("trivia_game")) .AddField(eab => eab.WithName(GetText("category")).WithValue(CurrentQuestion.Category)) - .AddField(eab => eab.WithName(GetText("question")).WithValue(CurrentQuestion.Question)) - .WithImageUrl(CurrentQuestion.ImageUrl); + .AddField(eab => eab.WithName(GetText("question")).WithValue(CurrentQuestion.Question)); + if (Uri.IsWellFormedUriString(CurrentQuestion.ImageUrl, UriKind.Absolute)) + questionEmbed.WithImageUrl(CurrentQuestion.ImageUrl); questionMessage = await Channel.EmbedAsync(questionEmbed).ConfigureAwait(false); } - catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound || + catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound || ex.HttpCode == System.Net.HttpStatusCode.Forbidden || ex.HttpCode == System.Net.HttpStatusCode.BadRequest) { @@ -117,7 +118,7 @@ namespace NadekoBot.Modules.Games.Trivia try { //hint - await Task.Delay(hintTimeoutMiliseconds, triviaCancelSource.Token).ConfigureAwait(false); + await Task.Delay(_hintTimeoutMiliseconds, _triviaCancelSource.Token).ConfigureAwait(false); if (ShowHints) try { @@ -131,7 +132,7 @@ namespace NadekoBot.Modules.Games.Trivia catch (Exception ex) { _log.Warn(ex); } //timeout - await Task.Delay(questionDurationMiliseconds - hintTimeoutMiliseconds, triviaCancelSource.Token).ConfigureAwait(false); + await Task.Delay(_questionDurationMiliseconds - _hintTimeoutMiliseconds, _triviaCancelSource.Token).ConfigureAwait(false); } catch (TaskCanceledException) { } //means someone guessed the answer @@ -141,15 +142,17 @@ namespace NadekoBot.Modules.Games.Trivia GameActive = false; _client.MessageReceived -= PotentialGuess; } - if (!triviaCancelSource.IsCancellationRequested) + if (!_triviaCancelSource.IsCancellationRequested) { try { - await Channel.EmbedAsync(new EmbedBuilder().WithErrorColor() + var embed = new EmbedBuilder().WithErrorColor() .WithTitle(GetText("trivia_game")) - .WithDescription(GetText("trivia_times_up", Format.Bold(CurrentQuestion.Answer))) - .WithImageUrl(CurrentQuestion.AnswerImageUrl)) - .ConfigureAwait(false); + .WithDescription(GetText("trivia_times_up", Format.Bold(CurrentQuestion.Answer))); + if (Uri.IsWellFormedUriString(CurrentQuestion.AnswerImageUrl, UriKind.Absolute)) + embed.WithImageUrl(CurrentQuestion.AnswerImageUrl); + + await Channel.EmbedAsync(embed).ConfigureAwait(false); } catch (Exception ex) { @@ -199,7 +202,7 @@ namespace NadekoBot.Modules.Games.Trivia await _guessLock.WaitAsync().ConfigureAwait(false); try { - if (GameActive && CurrentQuestion.IsAnswerCorrect(umsg.Content) && !triviaCancelSource.IsCancellationRequested) + if (GameActive && CurrentQuestion.IsAnswerCorrect(umsg.Content) && !_triviaCancelSource.IsCancellationRequested) { Users.AddOrUpdate(guildUser, 1, (gu, old) => ++old); guess = true; @@ -207,7 +210,7 @@ namespace NadekoBot.Modules.Games.Trivia } finally { _guessLock.Release(); } if (!guess) return; - triviaCancelSource.Cancel(); + _triviaCancelSource.Cancel(); if (Users[guildUser] == WinRequirement) @@ -215,29 +218,30 @@ namespace NadekoBot.Modules.Games.Trivia ShouldStopGame = true; try { - await Channel.EmbedAsync(new EmbedBuilder().WithOkColor() + var embedS = new EmbedBuilder().WithOkColor() .WithTitle(GetText("trivia_game")) .WithDescription(GetText("trivia_win", guildUser.Mention, - Format.Bold(CurrentQuestion.Answer))) - .WithImageUrl(CurrentQuestion.AnswerImageUrl)) - .ConfigureAwait(false); + Format.Bold(CurrentQuestion.Answer))); + if (Uri.IsWellFormedUriString(CurrentQuestion.AnswerImageUrl, UriKind.Absolute)) + embedS.WithImageUrl(CurrentQuestion.AnswerImageUrl); + await Channel.EmbedAsync(embedS).ConfigureAwait(false); } catch { // ignored } - var reward = _bc.TriviaCurrencyReward; + var reward = _bc.BotConfig.TriviaCurrencyReward; if (reward > 0) await _cs.AddAsync(guildUser, "Won trivia", reward, true).ConfigureAwait(false); return; } - - await Channel.EmbedAsync(new EmbedBuilder().WithOkColor() + var embed = new EmbedBuilder().WithOkColor() .WithTitle(GetText("trivia_game")) - .WithDescription(GetText("trivia_guess", guildUser.Mention, Format.Bold(CurrentQuestion.Answer))) - .WithImageUrl(CurrentQuestion.AnswerImageUrl)) - .ConfigureAwait(false); + .WithDescription(GetText("trivia_guess", guildUser.Mention, Format.Bold(CurrentQuestion.Answer))); + if (Uri.IsWellFormedUriString(CurrentQuestion.AnswerImageUrl, UriKind.Absolute)) + embed.WithImageUrl(CurrentQuestion.AnswerImageUrl); + await Channel.EmbedAsync(embed).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } }); diff --git a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestion.cs similarity index 82% rename from src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs rename to src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestion.cs index 01e676fd..3533aa9e 100644 --- a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs +++ b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestion.cs @@ -1,11 +1,11 @@ -using NadekoBot.Extensions; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using NadekoBot.Extensions; // THANKS @ShoMinamimoto for suggestions and coding help -namespace NadekoBot.Modules.Games.Trivia +namespace NadekoBot.Modules.Games.Common.Trivia { public class TriviaQuestion { @@ -23,6 +23,8 @@ namespace NadekoBot.Modules.Games.Trivia public string ImageUrl { get; set; } public string AnswerImageUrl { get; set; } public string Answer { get; set; } + private string _cleanAnswer; + public string CleanAnswer => _cleanAnswer ?? (_cleanAnswer = Clean(Answer)); public TriviaQuestion(string q, string a, string c, string img = null, string answerImage = null) { @@ -37,20 +39,20 @@ namespace NadekoBot.Modules.Games.Trivia public bool IsAnswerCorrect(string guess) { - guess = CleanGuess(guess); if (Answer.Equals(guess)) { return true; } - Answer = CleanGuess(Answer); - guess = CleanGuess(guess); - if (Answer.Equals(guess)) + var cleanGuess = Clean(guess); + if (CleanAnswer.Equals(cleanGuess)) { return true; } - int levDistance = Answer.LevenshteinDistance(guess); - return JudgeGuess(Answer.Length, guess.Length, levDistance); + int levDistanceClean = CleanAnswer.LevenshteinDistance(cleanGuess); + int levDistanceNormal = Answer.LevenshteinDistance(guess); + return JudgeGuess(CleanAnswer.Length, cleanGuess.Length, levDistanceClean) + || JudgeGuess(Answer.Length, guess.Length, levDistanceNormal); } private bool JudgeGuess(int guessLength, int answerLength, int levDistance) @@ -68,7 +70,7 @@ namespace NadekoBot.Modules.Games.Trivia return false; } - private string CleanGuess(string str) + private string Clean(string str) { str = " " + str.ToLower() + " "; str = Regex.Replace(str, "\\s+", " "); diff --git a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestionPool.cs similarity index 94% rename from src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs rename to src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestionPool.cs index 694112dc..2307c209 100644 --- a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs +++ b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestionPool.cs @@ -1,13 +1,13 @@ -using NadekoBot.Extensions; -using NadekoBot.Services; -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; +using NadekoBot.Common; +using NadekoBot.Extensions; +using Newtonsoft.Json; -namespace NadekoBot.Modules.Games.Trivia +namespace NadekoBot.Modules.Games.Common.Trivia { public class TriviaQuestionPool { diff --git a/src/NadekoBot/Services/Games/TypingArticle.cs b/src/NadekoBot/Modules/Games/Common/TypingArticle.cs similarity index 74% rename from src/NadekoBot/Services/Games/TypingArticle.cs rename to src/NadekoBot/Modules/Games/Common/TypingArticle.cs index 2024704f..2b69b577 100644 --- a/src/NadekoBot/Services/Games/TypingArticle.cs +++ b/src/NadekoBot/Modules/Games/Common/TypingArticle.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Services.Games +namespace NadekoBot.Modules.Games.Common { public class TypingArticle { diff --git a/src/NadekoBot/Modules/Games/Commands/Models/TypingGame.cs b/src/NadekoBot/Modules/Games/Common/TypingGame.cs similarity index 94% rename from src/NadekoBot/Modules/Games/Commands/Models/TypingGame.cs rename to src/NadekoBot/Modules/Games/Common/TypingGame.cs index db670fd2..f2d2980a 100644 --- a/src/NadekoBot/Modules/Games/Commands/Models/TypingGame.cs +++ b/src/NadekoBot/Modules/Games/Common/TypingGame.cs @@ -1,16 +1,16 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.Extensions; -using NadekoBot.Services; -using NadekoBot.Services.Games; -using NLog; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using NadekoBot.Common; +using NadekoBot.Extensions; +using NadekoBot.Modules.Games.Services; +using NLog; -namespace NadekoBot.Modules.Games.Models +namespace NadekoBot.Modules.Games.Common { public class TypingGame { @@ -20,13 +20,13 @@ namespace NadekoBot.Modules.Games.Models public bool IsActive { get; private set; } private readonly Stopwatch sw; private readonly List finishedUserIds; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly GamesService _games; private readonly string _prefix; private Logger _log { get; } - public TypingGame(GamesService games, DiscordShardedClient client, ITextChannel channel, string prefix) //kek@prefix + public TypingGame(GamesService games, DiscordSocketClient client, ITextChannel channel, string prefix) //kek@prefix { _log = LogManager.GetCurrentClassLogger(); _games = games; diff --git a/src/NadekoBot/Modules/Games/Connect4Commands.cs b/src/NadekoBot/Modules/Games/Connect4Commands.cs new file mode 100644 index 00000000..5a1183c2 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Connect4Commands.cs @@ -0,0 +1,184 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Common.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Modules.Games.Common.Connect4; +using System.Collections.Concurrent; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games +{ + public partial class Games + { + public class Connect4Commands : NadekoSubmodule + { + public static ConcurrentDictionary Games = new ConcurrentDictionary(); + private readonly DiscordSocketClient _client; + + //private readonly string[] numbers = new string[] { "⓪", " ①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨" }; + private readonly string[] numbers = new string[] { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:"}; + + public Connect4Commands(DiscordSocketClient client) + { + _client = client; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Connect4() + { + var newGame = new Connect4Game(Context.User.Id, Context.User.ToString()); + Connect4Game game; + if ((game = Games.GetOrAdd(Context.Channel.Id, newGame)) != newGame) + { + if (game.CurrentPhase != Connect4Game.Phase.Joining) + return; + + newGame.Dispose(); + //means game already exists, try to join + var joined = await game.Join(Context.User.Id, Context.User.ToString()).ConfigureAwait(false); + return; + } + + game.OnGameStateUpdated += Game_OnGameStateUpdated; + game.OnGameFailedToStart += Game_OnGameFailedToStart; + game.OnGameEnded += Game_OnGameEnded; + _client.MessageReceived += _client_MessageReceived; + + game.Initialize(); + + await ReplyConfirmLocalized("connect4_created").ConfigureAwait(false); + + Task _client_MessageReceived(SocketMessage arg) + { + if (Context.Channel.Id != arg.Channel.Id) + return Task.CompletedTask; + + var _ = Task.Run(async () => + { + bool success = false; + if (int.TryParse(arg.Content, out var col)) + { + success = await game.Input(arg.Author.Id, arg.Author.ToString(), col).ConfigureAwait(false); + } + + if (success) + try { await arg.DeleteAsync().ConfigureAwait(false); } catch { } + else + { + if (game.CurrentPhase == Connect4Game.Phase.Joining + || game.CurrentPhase == Connect4Game.Phase.Ended) + { + return; + } + RepostCounter++; + if (RepostCounter == 0) + try { msg = await Context.Channel.SendMessageAsync("", embed: (Embed)msg.Embeds.First()); } catch { } + } + }); + return Task.CompletedTask; + } + + Task Game_OnGameFailedToStart(Connect4Game arg) + { + if (Games.TryRemove(Context.Channel.Id, out var toDispose)) + { + _client.MessageReceived -= _client_MessageReceived; + toDispose.Dispose(); + } + return ErrorLocalized("connect4_failed_to_start"); + } + + Task Game_OnGameEnded(Connect4Game arg, Connect4Game.Result result) + { + if (Games.TryRemove(Context.Channel.Id, out var toDispose)) + { + _client.MessageReceived -= _client_MessageReceived; + toDispose.Dispose(); + } + + string title; + if (result == Connect4Game.Result.CurrentPlayerWon) + { + title = GetText("connect4_won", Format.Bold(arg.CurrentPlayer), Format.Bold(arg.OtherPlayer)); + } + else if (result == Connect4Game.Result.OtherPlayerWon) + { + title = GetText("connect4_won", Format.Bold(arg.OtherPlayer), Format.Bold(arg.CurrentPlayer)); + } + else + title = GetText("connect4_draw"); + + return msg.ModifyAsync(x => x.Embed = new EmbedBuilder() + .WithTitle(title) + .WithDescription(GetGameStateText(game)) + .WithOkColor() + .Build()); + } + } + + private IUserMessage msg; + + private int _repostCounter = 0; + private int RepostCounter + { + get => _repostCounter; + set + { + if (value < 0 || value > 7) + _repostCounter = 0; + else _repostCounter = value; + } + } + + private async Task Game_OnGameStateUpdated(Connect4Game game) + { + var embed = new EmbedBuilder() + .WithTitle($"{game.CurrentPlayer} vs {game.OtherPlayer}") + .WithDescription(GetGameStateText(game)) + .WithOkColor(); + + + if (msg == null) + msg = await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + else + await msg.ModifyAsync(x => x.Embed = embed.Build()).ConfigureAwait(false); + } + + private string GetGameStateText(Connect4Game game) + { + var sb = new StringBuilder(); + + if (game.CurrentPhase == Connect4Game.Phase.P1Move || + game.CurrentPhase == Connect4Game.Phase.P2Move) + sb.AppendLine(GetText("connect4_player_to_move", Format.Bold(game.CurrentPlayer))); + + for (int i = Connect4Game.NumberOfRows; i > 0; i--) + { + for (int j = 0; j < Connect4Game.NumberOfColumns; j++) + { + //Console.WriteLine(i + (j * Connect4Game.NumberOfRows) - 1); + var cur = game.GameState[i + (j * Connect4Game.NumberOfRows) - 1]; + + if (cur == Connect4Game.Field.Empty) + sb.Append("⚫"); //black circle + else if (cur == Connect4Game.Field.P1) + sb.Append("🔴"); //red circle + else + sb.Append("🔵"); //blue circle + } + sb.AppendLine(); + } + + for (int i = 0; i < Connect4Game.NumberOfColumns; i++) + { + sb.Append(/*new string(' ', 1 + ((i + 1) / 2)) + */numbers[i]); + } + return sb.ToString(); + } + } + } +} diff --git a/src/NadekoBot/Modules/Games/Games.cs b/src/NadekoBot/Modules/Games/Games.cs index 5c4e0f61..07fd450e 100644 --- a/src/NadekoBot/Modules/Games/Games.cs +++ b/src/NadekoBot/Modules/Games/Games.cs @@ -2,21 +2,26 @@ using Discord; using NadekoBot.Services; using System.Threading.Tasks; -using NadekoBot.Attributes; using System; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; using NadekoBot.Extensions; -using NadekoBot.Services.Games; +using NadekoBot.Modules.Games.Common; +using NadekoBot.Modules.Games.Services; namespace NadekoBot.Modules.Games { - public partial class Games : NadekoTopLevelModule + /* more games + - Blackjack + - Shiritori + - Simple RPG adventure + */ + public partial class Games : NadekoTopLevelModule { - private readonly GamesService _games; private readonly IImagesService _images; - public Games(GamesService games, IImagesService images) + public Games(IImagesService images) { - _games = games; _images = images; } @@ -33,14 +38,14 @@ namespace NadekoBot.Modules.Games } [NadekoCommand, Usage, Description, Aliases] - public async Task _8Ball([Remainder] string question = null) + public async Task EightBall([Remainder] string question = null) { if (string.IsNullOrWhiteSpace(question)) return; await Context.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) .AddField(efb => efb.WithName("❓ " + GetText("question") ).WithValue(question).WithIsInline(false)) - .AddField(efb => efb.WithName("🎱 " + GetText("8ball")).WithValue(_games.EightBallResponses[new NadekoRandom().Next(0, _games.EightBallResponses.Length)]).WithIsInline(false))); + .AddField(efb => efb.WithName("🎱 " + GetText("8ball")).WithValue(_service.EightBallResponses[new NadekoRandom().Next(0, _service.EightBallResponses.Length)]).WithIsInline(false))); } [NadekoCommand, Usage, Description, Aliases] @@ -100,7 +105,7 @@ namespace NadekoBot.Modules.Games [RequireContext(ContextType.Guild)] public async Task RateGirl(IGuildUser usr) { - var gr = _games.GirlRatings.GetOrAdd(usr.Id, GetGirl); + var gr = _service.GirlRatings.GetOrAdd(usr.Id, GetGirl); var img = await gr.Url; await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithTitle("Girl Rating For " + usr) @@ -135,7 +140,7 @@ namespace NadekoBot.Modules.Games hot = NextDouble(0, 5); crazy = NextDouble(4, 10); advice = - "This is your NO-GO ZONE. We do not hang around, and date, and marry women who are atleast, in our mind, a 5. " + + "This is your NO-GO ZONE. We do not hang around, and date, and marry women who are at least, in our mind, a 5. " + "So, this is your no-go zone. You don't go here. You just rule this out. Life is better this way, that's the way it is."; } else if (roll < 750) diff --git a/src/NadekoBot/Modules/Games/HangmanCommands.cs b/src/NadekoBot/Modules/Games/HangmanCommands.cs new file mode 100644 index 00000000..4070bee1 --- /dev/null +++ b/src/NadekoBot/Modules/Games/HangmanCommands.cs @@ -0,0 +1,138 @@ +using Discord.Commands; +using NadekoBot.Extensions; +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Games.Common.Hangman; + +namespace NadekoBot.Modules.Games +{ + public partial class Games + { + [Group] + public class HangmanCommands : NadekoSubmodule + { + private readonly DiscordSocketClient _client; + + public HangmanCommands(DiscordSocketClient client) + { + _client = client; + } + + //channelId, game + public static ConcurrentDictionary HangmanGames { get; } = new ConcurrentDictionary(); + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Hangmanlist() + { + await Context.Channel.SendConfirmAsync(Format.Code(GetText("hangman_types", Prefix)) + "\n" + string.Join(", ", TermPool.Data.Keys)); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Hangman([Remainder]TermType type = TermType.Random) + { + var hm = new Hangman(type); + + if (!HangmanGames.TryAdd(Context.Channel.Id, hm)) + { + hm.Dispose(); + await ReplyErrorLocalized("hangman_running").ConfigureAwait(false); + return; + } + hm.OnGameEnded += Hm_OnGameEnded; + hm.OnGuessFailed += Hm_OnGuessFailed; + hm.OnGuessSucceeded += Hm_OnGuessSucceeded; + hm.OnLetterAlreadyUsed += Hm_OnLetterAlreadyUsed; + _client.MessageReceived += _client_MessageReceived; + + try + { + await Context.Channel.SendConfirmAsync(GetText("hangman_game_started") + $" ({hm.TermType})", + hm.ScrambledWord + "\n" + hm.GetHangman()) + .ConfigureAwait(false); + } + catch { } + + await hm.EndedTask.ConfigureAwait(false); + + _client.MessageReceived -= _client_MessageReceived; + HangmanGames.TryRemove(Context.Channel.Id, out _); + hm.Dispose(); + + Task _client_MessageReceived(SocketMessage msg) + { + var _ = Task.Run(() => + { + if (Context.Channel.Id == msg.Channel.Id) + return hm.Input(msg.Author.Id, msg.Author.ToString(), msg.Content); + else + return Task.CompletedTask; + }); + return Task.CompletedTask; + } + } + + Task Hm_OnGameEnded(Hangman game, string winner) + { + if (winner == null) + { + var loseEmbed = new EmbedBuilder().WithTitle($"Hangman Game ({game.TermType}) - Ended") + .WithDescription(Format.Bold("You lose.")) + .AddField(efb => efb.WithName("It was").WithValue(game.Term.Word.ToTitleCase())) + .WithFooter(efb => efb.WithText(string.Join(" ", game.PreviousGuesses))) + .WithErrorColor(); + + if (Uri.IsWellFormedUriString(game.Term.ImageUrl, UriKind.Absolute)) + loseEmbed.WithImageUrl(game.Term.ImageUrl); + + return Context.Channel.EmbedAsync(loseEmbed); + } + + var winEmbed = new EmbedBuilder().WithTitle($"Hangman Game ({game.TermType}) - Ended") + .WithDescription(Format.Bold($"{winner} Won.")) + .AddField(efb => efb.WithName("It was").WithValue(game.Term.Word.ToTitleCase())) + .WithFooter(efb => efb.WithText(string.Join(" ", game.PreviousGuesses))) + .WithOkColor(); + + if (Uri.IsWellFormedUriString(game.Term.ImageUrl, UriKind.Absolute)) + winEmbed.WithImageUrl(game.Term.ImageUrl); + + return Context.Channel.EmbedAsync(winEmbed); + } + + private Task Hm_OnLetterAlreadyUsed(Hangman game, string user, char guess) + { + return Context.Channel.SendErrorAsync($"Hangman Game ({game.TermType})", $"{user} Letter `{guess}` has already been used. You can guess again in 3 seconds.\n" + game.ScrambledWord + "\n" + game.GetHangman(), + footer: string.Join(" ", game.PreviousGuesses)); + } + + private Task Hm_OnGuessSucceeded(Hangman game, string user, char guess) + { + return Context.Channel.SendConfirmAsync($"Hangman Game ({game.TermType})", $"{user} guessed a letter `{guess}`!\n" + game.ScrambledWord + "\n" + game.GetHangman(), + footer: string.Join(" ", game.PreviousGuesses)); + } + + private Task Hm_OnGuessFailed(Hangman game, string user, char guess) + { + return Context.Channel.SendErrorAsync($"Hangman Game ({game.TermType})", $"{user} Letter `{guess}` does not exist. You can guess again in 3 seconds.\n" + game.ScrambledWord + "\n" + game.GetHangman(), + footer: string.Join(" ", game.PreviousGuesses)); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task HangmanStop() + { + if (HangmanGames.TryRemove(Context.Channel.Id, out var removed)) + { + await removed.Stop().ConfigureAwait(false); + await ReplyConfirmLocalized("hangman_stopped").ConfigureAwait(false); + } + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Commands/LeetCommands.cs b/src/NadekoBot/Modules/Games/LeetCommands.cs similarity index 99% rename from src/NadekoBot/Modules/Games/Commands/LeetCommands.cs rename to src/NadekoBot/Modules/Games/LeetCommands.cs index e1f79c56..4b0088eb 100644 --- a/src/NadekoBot/Modules/Games/Commands/LeetCommands.cs +++ b/src/NadekoBot/Modules/Games/LeetCommands.cs @@ -1,8 +1,8 @@ using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using System.Text; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; // taken from // http://www.codeproject.com/Tips/207582/L-t-Tr-nsl-t-r-Leet-Translator (thanks) diff --git a/src/NadekoBot/Modules/Games/NunchiCommands.cs b/src/NadekoBot/Modules/Games/NunchiCommands.cs new file mode 100644 index 00000000..834e2c02 --- /dev/null +++ b/src/NadekoBot/Modules/Games/NunchiCommands.cs @@ -0,0 +1,128 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Games.Common.Nunchi; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games +{ + public partial class Games + { + public class NunchiCommands : NadekoSubmodule + { + public static readonly ConcurrentDictionary Games = new ConcurrentDictionary(); + private readonly DiscordSocketClient _client; + + public NunchiCommands(DiscordSocketClient client) + { + _client = client; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Nunchi() + { + var newNunchi = new Nunchi(Context.User.Id, Context.User.ToString()); + Nunchi nunchi; + + //if a game was already active + if ((nunchi = Games.GetOrAdd(Context.Guild.Id, newNunchi)) != newNunchi) + { + // join it + if (!await nunchi.Join(Context.User.Id, Context.User.ToString())) + { + // if you failed joining, that means game is running or just ended + // await ReplyErrorLocalized("nunchi_already_started").ConfigureAwait(false); + return; + } + + await ReplyConfirmLocalized("nunchi_joined", nunchi.ParticipantCount).ConfigureAwait(false); + return; + } + + + try { await ConfirmLocalized("nunchi_created").ConfigureAwait(false); } catch { } + + nunchi.OnGameEnded += Nunchi_OnGameEnded; + //nunchi.OnGameStarted += Nunchi_OnGameStarted; + nunchi.OnRoundEnded += Nunchi_OnRoundEnded; + nunchi.OnUserGuessed += Nunchi_OnUserGuessed; + nunchi.OnRoundStarted += Nunchi_OnRoundStarted; + _client.MessageReceived += _client_MessageReceived; + + var success = await nunchi.Initialize().ConfigureAwait(false); + if (!success) + { + if (Games.TryRemove(Context.Guild.Id, out var game)) + game.Dispose(); + await ConfirmLocalized("nunchi_failed_to_start").ConfigureAwait(false); + } + + Task _client_MessageReceived(SocketMessage arg) + { + var _ = Task.Run(async () => + { + if (arg.Channel.Id != Context.Channel.Id) + return; + + if (!int.TryParse(arg.Content, out var number)) + return; + try + { + await nunchi.Input(arg.Author.Id, arg.Author.ToString(), number).ConfigureAwait(false); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + }); + return Task.CompletedTask; + } + + Task Nunchi_OnGameEnded(Nunchi arg1, string arg2) + { + if (Games.TryRemove(Context.Guild.Id, out var game)) + { + _client.MessageReceived -= _client_MessageReceived; + game.Dispose(); + } + + if (arg2 == null) + return ConfirmLocalized("nunchi_ended_no_winner", Format.Bold(arg2)); + else + return ConfirmLocalized("nunchi_ended", Format.Bold(arg2)); + } + } + + private Task Nunchi_OnRoundStarted(Nunchi arg, int cur) + { + return ConfirmLocalized("nunchi_round_started", + Format.Bold(arg.ParticipantCount.ToString()), + Format.Bold(cur.ToString())); + } + + private Task Nunchi_OnUserGuessed(Nunchi arg) + { + return ConfirmLocalized("nunchi_next_number", Format.Bold(arg.CurrentNumber.ToString())); + } + + private Task Nunchi_OnRoundEnded(Nunchi arg1, (ulong Id, string Name)? arg2) + { + if(arg2.HasValue) + return ConfirmLocalized("nunchi_round_ended", Format.Bold(arg2.Value.Name)); + else + return ConfirmLocalized("nunchi_round_ended_boot", + Format.Bold("\n" + string.Join("\n, ", arg1.Participants.Select(x => x.Name)))); // this won't work if there are too many users + } + + private Task Nunchi_OnGameStarted(Nunchi arg) + { + return ConfirmLocalized("nunchi_started", Format.Bold(arg.ParticipantCount.ToString())); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs b/src/NadekoBot/Modules/Games/PlantAndPickCommands.cs similarity index 88% rename from src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs rename to src/NadekoBot/Modules/Games/PlantAndPickCommands.cs index 191fd30d..3bbe2c79 100644 --- a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs +++ b/src/NadekoBot/Modules/Games/PlantAndPickCommands.cs @@ -1,14 +1,14 @@ using Discord; using Discord.Commands; using Microsoft.EntityFrameworkCore; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using NadekoBot.Services.Database.Models; -using NadekoBot.Services.Games; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Games.Services; namespace NadekoBot.Modules.Games { @@ -25,11 +25,11 @@ namespace NadekoBot.Modules.Games public class PlantPickCommands : NadekoSubmodule { private readonly CurrencyService _cs; - private readonly BotConfig _bc; + private readonly IBotConfigProvider _bc; private readonly GamesService _games; private readonly DbService _db; - public PlantPickCommands(BotConfig bc, CurrencyService cs, GamesService games, + public PlantPickCommands(IBotConfigProvider bc, CurrencyService cs, GamesService games, DbService db) { _bc = bc; @@ -54,8 +54,8 @@ namespace NadekoBot.Modules.Games await Task.WhenAll(msgs.Where(m => m != null).Select(toDelete => toDelete.DeleteAsync())).ConfigureAwait(false); - await _cs.AddAsync((IGuildUser)Context.User, $"Picked {_bc.CurrencyPluralName}", msgs.Count, false).ConfigureAwait(false); - var msg = await ReplyConfirmLocalized("picked", msgs.Count + _bc.CurrencySign) + await _cs.AddAsync((IGuildUser)Context.User, $"Picked {_bc.BotConfig.CurrencyPluralName}", msgs.Count, false).ConfigureAwait(false); + var msg = await ReplyConfirmLocalized("picked", msgs.Count + _bc.BotConfig.CurrencySign) .ConfigureAwait(false); msg.DeleteAfter(10); } @@ -67,19 +67,18 @@ namespace NadekoBot.Modules.Games if (amount < 1) return; - var removed = await _cs.RemoveAsync((IGuildUser)Context.User, $"Planted a {_bc.CurrencyName}", amount, false).ConfigureAwait(false); + var removed = await _cs.RemoveAsync((IGuildUser)Context.User, $"Planted a {_bc.BotConfig.CurrencyName}", amount, false).ConfigureAwait(false); if (!removed) { - await ReplyErrorLocalized("not_enough", _bc.CurrencySign).ConfigureAwait(false); + await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false); return; } var imgData = _games.GetRandomCurrencyImage(); - - //todo 81 upload all currency images to transfer.sh and use that one as cdn + var msgToSend = GetText("planted", Format.Bold(Context.User.ToString()), - amount + _bc.CurrencySign, + amount + _bc.BotConfig.CurrencySign, Prefix); if (amount > 1) @@ -116,7 +115,7 @@ namespace NadekoBot.Modules.Games bool enabled; using (var uow = _db.UnitOfWork) { - var guildConfig = uow.GuildConfigs.For(channel.Id, set => set.Include(gc => gc.GenerateCurrencyChannelIds)); + var guildConfig = uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.GenerateCurrencyChannelIds)); var toAdd = new GCChannelId() { ChannelId = channel.Id }; if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd)) diff --git a/src/NadekoBot/Modules/Games/Commands/PollCommands.cs b/src/NadekoBot/Modules/Games/PollCommands.cs similarity index 57% rename from src/NadekoBot/Modules/Games/Commands/PollCommands.cs rename to src/NadekoBot/Modules/Games/PollCommands.cs index 487473f0..bd136e7f 100644 --- a/src/NadekoBot/Modules/Games/Commands/PollCommands.cs +++ b/src/NadekoBot/Modules/Games/PollCommands.cs @@ -1,53 +1,45 @@ using Discord; using Discord.Commands; using Discord.WebSocket; -using NadekoBot.Attributes; using NadekoBot.Extensions; using System.Threading.Tasks; -using NadekoBot.Services.Games; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Games.Services; namespace NadekoBot.Modules.Games { public partial class Games { [Group] - public class PollCommands : NadekoSubmodule + public class PollCommands : NadekoSubmodule { - private readonly DiscordShardedClient _client; - private readonly PollService _polls; + private readonly DiscordSocketClient _client; - public PollCommands(DiscordShardedClient client, PollService polls) + public PollCommands(DiscordSocketClient client) { _client = client; - _polls = polls; } [NadekoCommand, Usage, Description, Aliases] [RequireUserPermission(GuildPermission.ManageMessages)] [RequireContext(ContextType.Guild)] public Task Poll([Remainder] string arg = null) - => InternalStartPoll(arg, false); - - [NadekoCommand, Usage, Description, Aliases] - [RequireUserPermission(GuildPermission.ManageMessages)] - [RequireContext(ContextType.Guild)] - public Task PublicPoll([Remainder] string arg = null) - => InternalStartPoll(arg, true); + => InternalStartPoll(arg); [NadekoCommand, Usage, Description, Aliases] [RequireUserPermission(GuildPermission.ManageMessages)] [RequireContext(ContextType.Guild)] public async Task PollStats() { - if (!_polls.ActivePolls.TryGetValue(Context.Guild.Id, out var poll)) + if (!_service.ActivePolls.TryGetValue(Context.Guild.Id, out var poll)) return; await Context.Channel.EmbedAsync(poll.GetStats(GetText("current_poll_results"))); } - private async Task InternalStartPoll(string arg, bool isPublic = false) + private async Task InternalStartPoll(string arg) { - if(await _polls.StartPoll((ITextChannel)Context.Channel, Context.Message, arg, isPublic) == false) + if(await _service.StartPoll((ITextChannel)Context.Channel, Context.Message, arg) == false) await ReplyErrorLocalized("poll_already_running").ConfigureAwait(false); } @@ -58,7 +50,7 @@ namespace NadekoBot.Modules.Games { var channel = (ITextChannel)Context.Channel; - _polls.ActivePolls.TryRemove(channel.Guild.Id, out var poll); + _service.ActivePolls.TryRemove(channel.Guild.Id, out var poll); await poll.StopPoll().ConfigureAwait(false); } } diff --git a/src/NadekoBot/Services/Games/ChatterbotService.cs b/src/NadekoBot/Modules/Games/Services/ChatterbotService.cs similarity index 64% rename from src/NadekoBot/Services/Games/ChatterbotService.cs rename to src/NadekoBot/Modules/Games/Services/ChatterbotService.cs index 8039b79d..a137b953 100644 --- a/src/NadekoBot/Services/Games/ChatterbotService.cs +++ b/src/NadekoBot/Modules/Games/Services/ChatterbotService.cs @@ -1,40 +1,57 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.DataStructures.ModuleBehaviors; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using NadekoBot.Services.Permissions; -using NLog; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Extensions; +using NadekoBot.Modules.Permissions.Common; +using NadekoBot.Modules.Permissions.Services; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using NLog; +using NadekoBot.Modules.Games.Common.ChatterBot; -namespace NadekoBot.Services.Games +namespace NadekoBot.Modules.Games.Services { - public class ChatterBotService : IEarlyBlockingExecutor + public class ChatterBotService : IEarlyBlockingExecutor, INService { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly Logger _log; private readonly PermissionService _perms; private readonly CommandHandler _cmd; + private readonly NadekoStrings _strings; + private readonly IBotCredentials _creds; - public ConcurrentDictionary> ChatterBotGuilds { get; } + public ConcurrentDictionary> ChatterBotGuilds { get; } - public ChatterBotService(DiscordShardedClient client, PermissionService perms, IEnumerable gcs, CommandHandler cmd) + public ChatterBotService(DiscordSocketClient client, PermissionService perms, IEnumerable gcs, + CommandHandler cmd, NadekoStrings strings, IBotCredentials creds) { _client = client; _log = LogManager.GetCurrentClassLogger(); _perms = perms; _cmd = cmd; + _strings = strings; + _creds = creds; - ChatterBotGuilds = new ConcurrentDictionary>( + ChatterBotGuilds = new ConcurrentDictionary>( gcs.Where(gc => gc.CleverbotEnabled) - .ToDictionary(gc => gc.GuildId, gc => new Lazy(() => new ChatterBotSession(gc.GuildId), true))); + .ToDictionary(gc => gc.GuildId, gc => new Lazy(() => CreateSession(), true))); } - public string PrepareMessage(IUserMessage msg, out ChatterBotSession cleverbot) + public IChatterBotSession CreateSession() + { + if (string.IsNullOrWhiteSpace(_creds.CleverbotApiKey)) + return new ChatterBotSession(); + else + return new OfficialCleverbotSession(_creds.CleverbotApiKey); + } + + public string PrepareMessage(IUserMessage msg, out IChatterBotSession cleverbot) { var channel = msg.Channel as ITextChannel; cleverbot = null; @@ -42,7 +59,7 @@ namespace NadekoBot.Services.Games if (channel == null) return null; - if (!ChatterBotGuilds.TryGetValue(channel.Guild.Id, out Lazy lazyCleverbot)) + if (!ChatterBotGuilds.TryGetValue(channel.Guild.Id, out Lazy lazyCleverbot)) return null; cleverbot = lazyCleverbot.Value; @@ -67,7 +84,7 @@ namespace NadekoBot.Services.Games return message; } - public async Task TryAsk(ChatterBotSession cleverbot, ITextChannel channel, string message) + public async Task TryAsk(IChatterBotSession cleverbot, ITextChannel channel, string message) { await channel.TriggerTypingAsync().ConfigureAwait(false); @@ -83,13 +100,13 @@ namespace NadekoBot.Services.Games return true; } - public async Task TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage usrMsg) + public async Task TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage usrMsg) { if (!(guild is SocketGuild sg)) return false; try { - var message = PrepareMessage(usrMsg, out ChatterBotSession cbs); + var message = PrepareMessage(usrMsg, out IChatterBotSession cbs); if (message == null || cbs == null) return false; @@ -101,8 +118,7 @@ namespace NadekoBot.Services.Games { if (pc.Verbose) { - //todo move this to permissions - var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg)}** is preventing this action."; + var returnMsg = _strings.GetText("trigger", guild.Id, "Permissions".ToLowerInvariant(), index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild))); try { await usrMsg.Channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { } _log.Info(returnMsg); } diff --git a/src/NadekoBot/Services/Games/GamesService.cs b/src/NadekoBot/Modules/Games/Services/GamesService.cs similarity index 83% rename from src/NadekoBot/Services/Games/GamesService.cs rename to src/NadekoBot/Modules/Games/Services/GamesService.cs index 24139906..39164486 100644 --- a/src/NadekoBot/Services/Games/GamesService.cs +++ b/src/NadekoBot/Modules/Games/Services/GamesService.cs @@ -1,10 +1,4 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using Newtonsoft.Json; -using NLog; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -12,18 +6,29 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using NadekoBot.Common; +using NadekoBot.Common.Collections; +using NadekoBot.Extensions; +using NadekoBot.Modules.Games.Common; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using Newtonsoft.Json; +using NLog; -namespace NadekoBot.Services.Games +namespace NadekoBot.Modules.Games.Services { - public class GamesService + public class GamesService : INService { - private readonly BotConfig _bc; + private readonly IBotConfigProvider _bc; public readonly ConcurrentDictionary GirlRatings = new ConcurrentDictionary(); public readonly ImmutableArray EightBallResponses; private readonly Timer _t; - private readonly DiscordShardedClient _client; + private readonly CommandHandler _cmd; private readonly NadekoStrings _strings; private readonly IImagesService _images; private readonly Logger _log; @@ -33,18 +38,18 @@ namespace NadekoBot.Services.Games public List TypingArticles { get; } = new List(); - public GamesService(DiscordShardedClient client, BotConfig bc, IEnumerable gcs, + public GamesService(CommandHandler cmd, IBotConfigProvider bc, IEnumerable gcs, NadekoStrings strings, IImagesService images, CommandHandler cmdHandler) { _bc = bc; - _client = client; + _cmd = cmd; _strings = strings; _images = images; _cmdHandler = cmdHandler; _log = LogManager.GetCurrentClassLogger(); //8ball - EightBallResponses = _bc.EightBallResponses.Select(ebr => ebr.Text).ToImmutableArray(); + EightBallResponses = _bc.BotConfig.EightBallResponses.Select(ebr => ebr.Text).ToImmutableArray(); //girl ratings _t = new Timer((_) => @@ -54,7 +59,7 @@ namespace NadekoBot.Services.Games }, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1)); //plantpick - client.MessageReceived += PotentialFlowerGeneration; + _cmd.OnMessageNoTrigger += PotentialFlowerGeneration; GenerationChannels = new ConcurrentHashSet(gcs .SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId))); @@ -97,7 +102,7 @@ namespace NadekoBot.Services.Games private string GetText(ITextChannel ch, string key, params object[] rep) => _strings.GetText(key, ch.GuildId, "Games".ToLowerInvariant(), rep); - private Task PotentialFlowerGeneration(SocketMessage imsg) + private Task PotentialFlowerGeneration(IUserMessage imsg) { var msg = imsg as SocketUserMessage; if (msg == null || msg.Author.IsBot) @@ -117,14 +122,14 @@ namespace NadekoBot.Services.Games var lastGeneration = LastGenerations.GetOrAdd(channel.Id, DateTime.MinValue); var rng = new NadekoRandom(); - if (DateTime.UtcNow - TimeSpan.FromSeconds(_bc.CurrencyGenerationCooldown) < lastGeneration) //recently generated in this channel, don't generate again + if (DateTime.UtcNow - TimeSpan.FromSeconds(_bc.BotConfig.CurrencyGenerationCooldown) < lastGeneration) //recently generated in this channel, don't generate again return; - var num = rng.Next(1, 101) + _bc.CurrencyGenerationChance * 100; + var num = rng.Next(1, 101) + _bc.BotConfig.CurrencyGenerationChance * 100; if (num > 100 && LastGenerations.TryUpdate(channel.Id, DateTime.UtcNow, lastGeneration)) { - var dropAmount = _bc.CurrencyDropAmount; - var dropAmountMax = _bc.CurrencyDropAmountMax; + var dropAmount = _bc.BotConfig.CurrencyDropAmount; + var dropAmountMax = _bc.BotConfig.CurrencyDropAmountMax; if (dropAmountMax != null && dropAmountMax > dropAmount) dropAmount = new NadekoRandom().Next(dropAmount, dropAmountMax.Value + 1); @@ -134,9 +139,9 @@ namespace NadekoBot.Services.Games var msgs = new IUserMessage[dropAmount]; var prefix = _cmdHandler.GetPrefix(channel.Guild.Id); var toSend = dropAmount == 1 - ? GetText(channel, "curgen_sn", _bc.CurrencySign) + ? GetText(channel, "curgen_sn", _bc.BotConfig.CurrencySign) + " " + GetText(channel, "pick_sn", prefix) - : GetText(channel, "curgen_pl", dropAmount, _bc.CurrencySign) + : GetText(channel, "curgen_pl", dropAmount, _bc.BotConfig.CurrencySign) + " " + GetText(channel, "pick_pl", prefix); var file = GetRandomCurrencyImage(); using (var fileStream = file.Data.ToStream()) diff --git a/src/NadekoBot/Services/Games/PollService.cs b/src/NadekoBot/Modules/Games/Services/PollService.cs similarity index 65% rename from src/NadekoBot/Services/Games/PollService.cs rename to src/NadekoBot/Modules/Games/Services/PollService.cs index a421bd77..5c14afbc 100644 --- a/src/NadekoBot/Services/Games/PollService.cs +++ b/src/NadekoBot/Modules/Games/Services/PollService.cs @@ -1,29 +1,32 @@ -using NadekoBot.DataStructures.ModuleBehaviors; -using System; +using System; using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; using Discord; using Discord.WebSocket; +using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Modules.Games.Common; +using NadekoBot.Services; +using NadekoBot.Services.Impl; using NLog; -namespace NadekoBot.Services.Games +namespace NadekoBot.Modules.Games.Services { - public class PollService : IEarlyBlockingExecutor + public class PollService : IEarlyBlockingExecutor, INService { public ConcurrentDictionary ActivePolls = new ConcurrentDictionary(); private readonly Logger _log; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly NadekoStrings _strings; - public PollService(DiscordShardedClient client, NadekoStrings strings) + public PollService(DiscordSocketClient client, NadekoStrings strings) { _log = LogManager.GetCurrentClassLogger(); _client = client; _strings = strings; } - public async Task StartPoll(ITextChannel channel, IUserMessage msg, string arg, bool isPublic = false) + public async Task StartPoll(ITextChannel channel, IUserMessage msg, string arg) { if (string.IsNullOrWhiteSpace(arg) || !arg.Contains(";")) return null; @@ -31,7 +34,7 @@ namespace NadekoBot.Services.Games if (data.Length < 3) return null; - var poll = new Poll(_client, _strings, msg, data[0], data.Skip(1), isPublic: isPublic); + var poll = new Poll(_client, _strings, msg, data[0], data.Skip(1)); if (ActivePolls.TryAdd(channel.Guild.Id, poll)) { poll.OnEnded += (gid) => @@ -45,20 +48,10 @@ namespace NadekoBot.Services.Games return false; } - public async Task TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage msg) + public async Task TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg) { if (guild == null) - { - foreach (var kvp in ActivePolls) - { - if (!kvp.Value.IsPublic) - { - if (await kvp.Value.TryVote(msg).ConfigureAwait(false)) - return true; - } - } return false; - } if (!ActivePolls.TryGetValue(guild.Id, out var poll)) return false; diff --git a/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs b/src/NadekoBot/Modules/Games/SpeedTypingCommands.cs similarity index 94% rename from src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs rename to src/NadekoBot/Modules/Games/SpeedTypingCommands.cs index 72663094..315080ed 100644 --- a/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs +++ b/src/NadekoBot/Modules/Games/SpeedTypingCommands.cs @@ -1,15 +1,15 @@ using Discord; using Discord.Commands; using Discord.WebSocket; -using NadekoBot.Attributes; using NadekoBot.Extensions; -using NadekoBot.Modules.Games.Models; -using NadekoBot.Services.Games; using Newtonsoft.Json; using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Games.Common; +using NadekoBot.Modules.Games.Services; namespace NadekoBot.Modules.Games { @@ -20,9 +20,9 @@ namespace NadekoBot.Modules.Games { public static ConcurrentDictionary RunningContests = new ConcurrentDictionary(); private readonly GamesService _games; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public SpeedTypingCommands(DiscordShardedClient client, GamesService games) + public SpeedTypingCommands(DiscordSocketClient client, GamesService games) { _games = games; _client = client; diff --git a/src/NadekoBot/Modules/Games/Commands/TicTacToe.cs b/src/NadekoBot/Modules/Games/TicTacToeCommands.cs similarity index 94% rename from src/NadekoBot/Modules/Games/Commands/TicTacToe.cs rename to src/NadekoBot/Modules/Games/TicTacToeCommands.cs index 7de40565..539d9cdd 100644 --- a/src/NadekoBot/Modules/Games/Commands/TicTacToe.cs +++ b/src/NadekoBot/Modules/Games/TicTacToeCommands.cs @@ -1,14 +1,14 @@ using Discord; using Discord.Commands; using Discord.WebSocket; -using NadekoBot.Attributes; using NadekoBot.Extensions; -using NadekoBot.Services; using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Services.Impl; namespace NadekoBot.Modules.Games { @@ -21,9 +21,9 @@ namespace NadekoBot.Modules.Games private static readonly Dictionary _games = new Dictionary(); private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1); - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public TicTacToeCommands(DiscordShardedClient client) + public TicTacToeCommands(DiscordSocketClient client) { _client = client; } @@ -37,8 +37,7 @@ namespace NadekoBot.Modules.Games await _sem.WaitAsync(1000); try { - TicTacToe game; - if (_games.TryGetValue(channel.Id, out game)) + if (_games.TryGetValue(channel.Id, out TicTacToe game)) { var _ = Task.Run(async () => { @@ -46,7 +45,7 @@ namespace NadekoBot.Modules.Games }); return; } - game = new TicTacToe(_strings, _client, channel, (IGuildUser)Context.User); + game = new TicTacToe(base._strings, (DiscordSocketClient)this._client, channel, (IGuildUser)Context.User); _games.Add(channel.Id, game); await ReplyConfirmLocalized("ttt_created").ConfigureAwait(false); @@ -87,9 +86,9 @@ namespace NadekoBot.Modules.Games private IUserMessage _previousMessage; private Timer _timeoutTimer; private readonly NadekoStrings _strings; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public TicTacToe(NadekoStrings strings, DiscordShardedClient client, ITextChannel channel, IGuildUser firstUser) + public TicTacToe(NadekoStrings strings, DiscordSocketClient client, ITextChannel channel, IGuildUser firstUser) { _channel = channel; _strings = strings; @@ -247,9 +246,8 @@ namespace NadekoBot.Modules.Games var curUser = _users[_curUserIndex]; if (_phase == Phase.Ended || msg.Author?.Id != curUser.Id) return; - - int index; - if (int.TryParse(msg.Content, out index) && + + if (int.TryParse(msg.Content, out var index) && --index >= 0 && index <= 9 && _state[index / 3, index % 3] == null) diff --git a/src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs b/src/NadekoBot/Modules/Games/TriviaCommands.cs similarity index 91% rename from src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs rename to src/NadekoBot/Modules/Games/TriviaCommands.cs index d38e04dd..3360c87f 100644 --- a/src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs +++ b/src/NadekoBot/Modules/Games/TriviaCommands.cs @@ -1,14 +1,12 @@ using Discord; using Discord.Commands; using Discord.WebSocket; -using NadekoBot.Attributes; using NadekoBot.Extensions; -using NadekoBot.Modules.Games.Trivia; using NadekoBot.Services; -using NadekoBot.Services.Database.Models; using System.Collections.Concurrent; using System.Threading.Tasks; - +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Games.Common.Trivia; namespace NadekoBot.Modules.Games { @@ -18,12 +16,12 @@ namespace NadekoBot.Modules.Games public class TriviaCommands : NadekoSubmodule { private readonly CurrencyService _cs; - private readonly DiscordShardedClient _client; - private readonly BotConfig _bc; + private readonly DiscordSocketClient _client; + private readonly IBotConfigProvider _bc; public static ConcurrentDictionary RunningTrivias { get; } = new ConcurrentDictionary(); - public TriviaCommands(DiscordShardedClient client, BotConfig bc, CurrencyService cs) + public TriviaCommands(DiscordSocketClient client, IBotConfigProvider bc, CurrencyService cs) { _cs = cs; _client = client; diff --git a/src/NadekoBot/Modules/Help/Help.cs b/src/NadekoBot/Modules/Help/Help.cs index 18ddf039..0f1376e0 100644 --- a/src/NadekoBot/Modules/Help/Help.cs +++ b/src/NadekoBot/Modules/Help/Help.cs @@ -4,37 +4,34 @@ using System.Linq; using Discord; using NadekoBot.Services; using System.Threading.Tasks; -using NadekoBot.Attributes; using System; using System.IO; using System.Text; using System.Collections.Generic; -using NadekoBot.Services.Database.Models; -using NadekoBot.Services.Permissions; -using NadekoBot.Services.Help; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Help.Services; +using NadekoBot.Modules.Permissions.Services; namespace NadekoBot.Modules.Help { - public class Help : NadekoTopLevelModule + public class Help : NadekoTopLevelModule { public const string PatreonUrl = "https://patreon.com/nadekobot"; public const string PaypalUrl = "https://paypal.me/Kwoth"; private readonly IBotCredentials _creds; - private readonly BotConfig _config; + private readonly IBotConfigProvider _config; private readonly CommandService _cmds; private readonly GlobalPermissionService _perms; - private readonly HelpService _h; - public string HelpString => String.Format(_config.HelpString, _creds.ClientId, Prefix); - public string DMHelpString => _config.DMHelpString; + public string HelpString => String.Format(_config.BotConfig.HelpString, _creds.ClientId, Prefix); + public string DMHelpString => _config.BotConfig.DMHelpString; - public Help(IBotCredentials creds, GlobalPermissionService perms, BotConfig config, CommandService cmds, HelpService h) + public Help(IBotCredentials creds, GlobalPermissionService perms, IBotConfigProvider config, CommandService cmds) { _creds = creds; _config = config; _cmds = cmds; _perms = perms; - _h = h; } [NadekoCommand, Usage, Description, Aliases] @@ -82,21 +79,21 @@ namespace NadekoBot.Modules.Help await ConfirmLocalized("commands_instr", Prefix).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] - [Priority(1)] + [Priority(0)] public async Task H([Remainder] string fail) { await ReplyErrorLocalized("command_not_found").ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] - [Priority(0)] + [Priority(1)] public async Task H([Remainder] CommandInfo com = null) { var channel = Context.Channel; if (com == null) { - IMessageChannel ch = channel is ITextChannel ? await ((IGuildUser)Context.User).CreateDMChannelAsync() : channel; + IMessageChannel ch = channel is ITextChannel ? await ((IGuildUser)Context.User).GetOrCreateDMChannelAsync() : channel; await ch.SendMessageAsync(HelpString).ConfigureAwait(false); return; } @@ -107,7 +104,7 @@ namespace NadekoBot.Modules.Help // return; //} - var embed = _h.GetCommandHelp(com, Context.Guild); + var embed = _service.GetCommandHelp(com, Context.Guild); await channel.EmbedAsync(embed).ConfigureAwait(false); } @@ -144,7 +141,7 @@ namespace NadekoBot.Modules.Help lastModule = module.Name; } helpstr.AppendLine($"{string.Join(" ", com.Aliases.Select(a => "`" + Prefix + a + "`"))} |" + - $" {string.Format(com.Summary, Prefix)} {_h.GetCommandRequirements(com, Context.Guild)} |" + + $" {string.Format(com.Summary, Prefix)} {_service.GetCommandRequirements(com, Context.Guild)} |" + $" {string.Format(com.Remarks, Prefix)}"); } File.WriteAllText("../../docs/Commands List.md", helpstr.ToString()); diff --git a/src/NadekoBot/Services/Help/HelpService.cs b/src/NadekoBot/Modules/Help/Services/HelpService.cs similarity index 76% rename from src/NadekoBot/Services/Help/HelpService.cs rename to src/NadekoBot/Modules/Help/Services/HelpService.cs index 25a91f16..88220154 100644 --- a/src/NadekoBot/Services/Help/HelpService.cs +++ b/src/NadekoBot/Modules/Help/Services/HelpService.cs @@ -1,35 +1,36 @@ -using NadekoBot.DataStructures.ModuleBehaviors; -using NadekoBot.Services.Database.Models; -using System.Threading.Tasks; +using System.Threading.Tasks; using Discord; using Discord.WebSocket; using System; using Discord.Commands; using NadekoBot.Extensions; using System.Linq; -using NadekoBot.Attributes; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Services; +using NadekoBot.Services.Impl; -namespace NadekoBot.Services.Help +namespace NadekoBot.Modules.Help.Services { - public class HelpService : ILateExecutor + public class HelpService : ILateExecutor, INService { - private readonly BotConfig _bc; + private readonly IBotConfigProvider _bc; private readonly CommandHandler _ch; private readonly NadekoStrings _strings; - public HelpService(BotConfig bc, CommandHandler ch, NadekoStrings strings) + public HelpService(IBotConfigProvider bc, CommandHandler ch, NadekoStrings strings) { _bc = bc; _ch = ch; _strings = strings; } - public async Task LateExecute(DiscordShardedClient client, IGuild guild, IUserMessage msg) + public async Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg) { try { if(guild == null) - await msg.Channel.SendMessageAsync(_bc.DMHelpString).ConfigureAwait(false); + await msg.Channel.SendMessageAsync(_bc.BotConfig.DMHelpString).ConfigureAwait(false); } catch (Exception) { @@ -48,6 +49,7 @@ namespace NadekoBot.Services.Help return new EmbedBuilder() .AddField(fb => fb.WithName(str).WithValue($"{com.RealSummary(prefix)} {GetCommandRequirements(com, guild)}").WithIsInline(true)) .AddField(fb => fb.WithName(GetText("usage", guild)).WithValue(com.RealRemarks(prefix)).WithIsInline(false)) + .WithFooter(efb => efb.WithText(GetText("module", guild, com.Module.GetTopLevelModule().Name))) .WithColor(NadekoBot.OkColor); } diff --git a/src/NadekoBot/Modules/Music/Common/Exceptions/NotInVoiceChannelException.cs b/src/NadekoBot/Modules/Music/Common/Exceptions/NotInVoiceChannelException.cs new file mode 100644 index 00000000..7e2a7706 --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/Exceptions/NotInVoiceChannelException.cs @@ -0,0 +1,9 @@ +using System; + +namespace NadekoBot.Modules.Music.Common.Exceptions +{ + public class NotInVoiceChannelException : Exception + { + public NotInVoiceChannelException() : base("You're not in the voice channel on this server.") { } + } +} diff --git a/src/NadekoBot/Modules/Music/Common/Exceptions/QueueFullException.cs b/src/NadekoBot/Modules/Music/Common/Exceptions/QueueFullException.cs new file mode 100644 index 00000000..be92f09d --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/Exceptions/QueueFullException.cs @@ -0,0 +1,12 @@ +using System; + +namespace NadekoBot.Modules.Music.Common.Exceptions +{ + public class QueueFullException : Exception + { + public QueueFullException(string message) : base(message) + { + } + public QueueFullException() : base("Queue is full.") { } + } +} diff --git a/src/NadekoBot/Modules/Music/Common/Exceptions/SongNotFoundException.cs b/src/NadekoBot/Modules/Music/Common/Exceptions/SongNotFoundException.cs new file mode 100644 index 00000000..823d2776 --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/Exceptions/SongNotFoundException.cs @@ -0,0 +1,12 @@ +using System; + +namespace NadekoBot.Modules.Music.Common.Exceptions +{ + public class SongNotFoundException : Exception + { + public SongNotFoundException(string message) : base(message) + { + } + public SongNotFoundException() : base("Song is not found.") { } + } +} diff --git a/src/NadekoBot/Modules/Music/Common/MusicPlayer.cs b/src/NadekoBot/Modules/Music/Common/MusicPlayer.cs new file mode 100644 index 00000000..aa61a9ce --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/MusicPlayer.cs @@ -0,0 +1,670 @@ +using Discord; +using Discord.Audio; +using System; +using System.Threading; +using System.Threading.Tasks; +using NLog; +using System.Linq; +using NadekoBot.Extensions; +using System.Diagnostics; +using NadekoBot.Common.Collections; +using NadekoBot.Modules.Music.Services; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Modules.Music.Common +{ + public enum StreamState + { + Resolving, + Queued, + Playing, + Completed + } + public class MusicPlayer + { + private readonly Thread _player; + public IVoiceChannel VoiceChannel { get; private set; } + private readonly Logger _log; + + private MusicQueue Queue { get; } = new MusicQueue(); + + public bool Exited { get; set; } = false; + public bool Stopped { get; private set; } = false; + public float Volume { get; private set; } = 1.0f; + public bool Paused => pauseTaskSource != null; + private TaskCompletionSource pauseTaskSource { get; set; } = null; + + public string PrettyVolume => $"🔉 {(int)(Volume * 100)}%"; + public string PrettyCurrentTime + { + get + { + var time = CurrentTime.ToString(@"mm\:ss"); + var hrs = (int)CurrentTime.TotalHours; + + if (hrs > 0) + return hrs + ":" + time; + else + return time; + } + } + public string PrettyFullTime => PrettyCurrentTime + " / " + (Queue.Current.Song?.PrettyTotalTime ?? "?"); + private CancellationTokenSource SongCancelSource { get; set; } + public ITextChannel OutputTextChannel { get; set; } + public (int Index, SongInfo Current) Current + { + get + { + if (Stopped) + return (0, null); + return Queue.Current; + } + } + + public bool RepeatCurrentSong { get; private set; } + public bool Shuffle { get; private set; } + public bool Autoplay { get; private set; } + public bool RepeatPlaylist { get; private set; } = false; + public uint MaxQueueSize + { + get => Queue.MaxQueueSize; + set { lock (locker) Queue.MaxQueueSize = value; } + } + private bool _fairPlay; + public bool FairPlay + { + get => _fairPlay; + set + { + if (value) + { + var cur = Queue.Current; + if (cur.Song != null) + RecentlyPlayedUsers.Add(cur.Song.QueuerName); + } + else + { + RecentlyPlayedUsers.Clear(); + } + + _fairPlay = value; + } + } + public bool AutoDelete { get; set; } + public uint MaxPlaytimeSeconds { get; set; } + + + const int _frameBytes = 3840; + const float _miliseconds = 20.0f; + public TimeSpan CurrentTime => TimeSpan.FromSeconds(_bytesSent / (float)_frameBytes / (1000 / _miliseconds)); + + private int _bytesSent = 0; + + private IAudioClient _audioClient; + private readonly object locker = new object(); + private MusicService _musicService; + + #region events + public event Action OnStarted; + public event Action OnCompleted; + public event Action OnPauseChanged; + #endregion + + + private bool manualSkip = false; + private bool manualIndex = false; + private bool newVoiceChannel = false; + private readonly IGoogleApiService _google; + + private bool cancel = false; + + private ConcurrentHashSet RecentlyPlayedUsers { get; } = new ConcurrentHashSet(); + public TimeSpan TotalPlaytime + { + get + { + var songs = Queue.ToArray().Songs; + return songs.Any(s => s.TotalTime == TimeSpan.MaxValue) + ? TimeSpan.MaxValue + : new TimeSpan(songs.Sum(s => s.TotalTime.Ticks)); + } + } + + + public MusicPlayer(MusicService musicService, IGoogleApiService google, IVoiceChannel vch, ITextChannel output, float volume) + { + _log = LogManager.GetCurrentClassLogger(); + this.Volume = volume; + this.VoiceChannel = vch; + this.SongCancelSource = new CancellationTokenSource(); + this.OutputTextChannel = output; + this._musicService = musicService; + this._google = google; + + _log.Info("Initialized"); + + _player = new Thread(new ThreadStart(PlayerLoop)); + _player.Start(); + _log.Info("Loop started"); + } + + private async void PlayerLoop() + { + while (!Exited) + { + _bytesSent = 0; + cancel = false; + CancellationToken cancelToken; + (int Index, SongInfo Song) data; + lock (locker) + { + data = Queue.Current; + cancelToken = SongCancelSource.Token; + manualSkip = false; + manualIndex = false; + } + if (data.Song != null) + { + _log.Info("Starting"); + AudioOutStream pcm = null; + SongBuffer b = null; + try + { + b = new SongBuffer(await data.Song.Uri(), "", data.Song.ProviderType == MusicType.Local); + //_log.Info("Created buffer, buffering..."); + + //var bufferTask = b.StartBuffering(cancelToken); + //var timeout = Task.Delay(10000); + //if (Task.WhenAny(bufferTask, timeout) == timeout) + //{ + // _log.Info("Buffering failed due to a timeout."); + // continue; + //} + //else if (!bufferTask.Result) + //{ + // _log.Info("Buffering failed due to a cancel or error."); + // continue; + //} + //_log.Info("Buffered. Getting audio client..."); + var ac = await GetAudioClient(); + _log.Info("Got Audio client"); + if (ac == null) + { + _log.Info("Can't join"); + await Task.Delay(900, cancelToken); + // just wait some time, maybe bot doesn't even have perms to join that voice channel, + // i don't want to spam connection attempts + continue; + } + pcm = ac.CreatePCMStream(AudioApplication.Music, bufferMillis: 500); + _log.Info("Created pcm stream"); + OnStarted?.Invoke(this, data); + + byte[] buffer = new byte[3840]; + int bytesRead = 0; + + while ((bytesRead = b.Read(buffer, 0, buffer.Length)) > 0 + && (MaxPlaytimeSeconds <= 0 || MaxPlaytimeSeconds >= CurrentTime.TotalSeconds)) + { + AdjustVolume(buffer, Volume); + await pcm.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); + unchecked { _bytesSent += bytesRead; } + + await (pauseTaskSource?.Task ?? Task.CompletedTask); + } + } + catch (OperationCanceledException) + { + _log.Info("Song Canceled"); + cancel = true; + } + catch (Exception ex) + { + _log.Warn(ex); + } + finally + { + if (pcm != null) + { + // flush is known to get stuck from time to time, + // just skip flushing if it takes more than 1 second + var flushCancel = new CancellationTokenSource(); + var flushToken = flushCancel.Token; + var flushDelay = Task.Delay(1000, flushToken); + await Task.WhenAny(flushDelay, pcm.FlushAsync(flushToken)); + flushCancel.Cancel(); + pcm.Dispose(); + } + + if (b != null) + b.Dispose(); + + OnCompleted?.Invoke(this, data.Song); + + if (_bytesSent == 0 && !cancel) + { + lock (locker) + Queue.RemoveSong(data.Song); + _log.Info("Song removed because it can't play"); + } + } + try + { + //if repeating current song, just ignore other settings, + // and play this song again (don't change the index) + // ignore rcs if song is manually skipped + + int queueCount; + bool stopped; + int currentIndex; + lock (locker) + { + queueCount = Queue.Count; + stopped = Stopped; + currentIndex = Queue.CurrentIndex; + } + + if (AutoDelete && !RepeatCurrentSong && !RepeatPlaylist && data.Song != null) + { + Queue.RemoveSong(data.Song); + } + + if (!manualIndex && (!RepeatCurrentSong || manualSkip)) + { + if (Shuffle) + { + _log.Info("Random song"); + Queue.Random(); //if shuffle is set, set current song index to a random number + } + else + { + //if last song, and autoplay is enabled, and if it's a youtube song + // do autplay magix + if (queueCount - 1 == data.Index && Autoplay && data.Song?.ProviderType == MusicType.YouTube) + { + try + { + _log.Info("Loading related song"); + await _musicService.TryQueueRelatedSongAsync(data.Song, OutputTextChannel, VoiceChannel); + if(!AutoDelete) + Queue.Next(); + } + catch + { + _log.Info("Loading related song failed."); + } + } + else if (FairPlay) + { + lock (locker) + { + _log.Info("Next fair song"); + var q = Queue.ToArray().Songs.Shuffle().ToArray(); + + bool found = false; + for (var i = 0; i < q.Length; i++) //first try to find a queuer who didn't have their song played recently + { + var item = q[i]; + if (RecentlyPlayedUsers.Add(item.QueuerName)) // if it's found, set current song to that index + { + Queue.CurrentIndex = i; + found = true; + break; + } + } + if (!found) //if it's not + { + RecentlyPlayedUsers.Clear(); //clear all recently played users (that means everyone from the playlist has had their song played) + Queue.Random(); //go to a random song (to prevent looping on the first few songs) + var cur = Current; + if (cur.Current != null) // add newely scheduled song's queuer to the recently played list + RecentlyPlayedUsers.Add(cur.Current.QueuerName); + } + } + } + else if (queueCount - 1 == data.Index && !RepeatPlaylist && !manualSkip) + { + _log.Info("Stopping because repeatplaylist is disabled"); + lock (locker) + { + Stop(); + } + } + else + { + _log.Info("Next song"); + lock (locker) + { + if (!Stopped) + if(!AutoDelete) + Queue.Next(); + } + } + } + } + } + catch (Exception ex) + { + _log.Error(ex); + } + } + do + { + await Task.Delay(500); + } + while ((Queue.Count == 0 || Stopped) && !Exited); + } + } + + private async Task GetAudioClient(bool reconnect = false) + { + if (_audioClient == null || + _audioClient.ConnectionState != ConnectionState.Connected || + reconnect || + newVoiceChannel) + try + { + try + { + var t = _audioClient?.StopAsync(); + if (t != null) + { + + _log.Info("Stopping audio client"); + await t; + + _log.Info("Disposing audio client"); + _audioClient.Dispose(); + } + } + catch + { + } + newVoiceChannel = false; + + _log.Info("Get current user"); + var curUser = await VoiceChannel.Guild.GetCurrentUserAsync(); + if (curUser.VoiceChannel != null) + { + _log.Info("Connecting"); + var ac = await VoiceChannel.ConnectAsync(); + _log.Info("Connected, stopping"); + await ac.StopAsync(); + _log.Info("Disconnected"); + await Task.Delay(1000); + } + _log.Info("Connecting"); + _audioClient = await VoiceChannel.ConnectAsync(); + } + catch + { + return null; + } + return _audioClient; + } + + public int Enqueue(SongInfo song) + { + lock (locker) + { + if (Exited) + return -1; + Queue.Add(song); + return Queue.Count - 1; + } + } + + public int EnqueueNext(SongInfo song) + { + lock (locker) + { + if (Exited) + return -1; + return Queue.AddNext(song); + } + } + + public void SetIndex(int index) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + lock (locker) + { + if (Exited) + return; + if (AutoDelete && index >= Queue.CurrentIndex && index > 0) + index--; + Queue.CurrentIndex = index; + manualIndex = true; + Stopped = false; + CancelCurrentSong(); + } + } + + public void Next(int skipCount = 1) + { + lock (locker) + { + if (Exited) + return; + manualSkip = true; + // if player is stopped, and user uses .n, it should play current song. + // It's a bit weird, but that's the least annoying solution + if (!Stopped) + if (!RepeatPlaylist && Queue.IsLast()) // if it's the last song in the queue, and repeat playlist is disabled + { //stop the queue + Stop(); + return; + } + else + Queue.Next(skipCount - 1); + else + Queue.CurrentIndex = 0; + Stopped = false; + CancelCurrentSong(); + Unpause(); + } + } + + public void Stop(bool clearQueue = false) + { + lock (locker) + { + Stopped = true; + //Queue.ResetCurrent(); + if (clearQueue) + Queue.Clear(); + Unpause(); + CancelCurrentSong(); + } + } + + private void Unpause() + { + lock (locker) + { + if (pauseTaskSource != null) + { + pauseTaskSource.TrySetResult(true); + pauseTaskSource = null; + } + } + } + + public void TogglePause() + { + lock (locker) + { + if (pauseTaskSource == null) + pauseTaskSource = new TaskCompletionSource(); + else + { + Unpause(); + } + } + OnPauseChanged?.Invoke(this, pauseTaskSource != null); + } + + public void SetVolume(int volume) + { + if (volume < 0 || volume > 100) + throw new ArgumentOutOfRangeException(nameof(volume)); + lock (locker) + { + Volume = ((float)volume) / 100; + } + } + + public SongInfo RemoveAt(int index) + { + lock (locker) + { + var cur = Queue.Current; + var toReturn = Queue.RemoveAt(index); + if (cur.Index == index) + Next(); + return toReturn; + } + } + + private void CancelCurrentSong() + { + lock (locker) + { + var cs = SongCancelSource; + SongCancelSource = new CancellationTokenSource(); + cs.Cancel(); + } + } + + public void ClearQueue() + { + lock (locker) + { + Queue.Clear(); + } + } + + public (int CurrentIndex, SongInfo[] Songs) QueueArray() + { + lock (locker) + return Queue.ToArray(); + } + + //aidiakapi ftw + public static unsafe byte[] AdjustVolume(byte[] audioSamples, float volume) + { + if (Math.Abs(volume - 1f) < 0.0001f) return audioSamples; + + // 16-bit precision for the multiplication + var volumeFixed = (int)Math.Round(volume * 65536d); + + var count = audioSamples.Length / 2; + + fixed (byte* srcBytes = audioSamples) + { + var src = (short*)srcBytes; + + for (var i = count; i != 0; i--, src++) + *src = (short)(((*src) * volumeFixed) >> 16); + } + + return audioSamples; + } + + public bool ToggleRepeatSong() + { + lock (locker) + { + return RepeatCurrentSong = !RepeatCurrentSong; + } + } + + public async Task Destroy() + { + _log.Info("Destroying"); + lock (locker) + { + Stop(); + Exited = true; + Unpause(); + + OnCompleted = null; + OnPauseChanged = null; + OnStarted = null; + } + var ac = _audioClient; + if (ac != null) + await ac.StopAsync(); + } + + public bool ToggleShuffle() + { + lock (locker) + { + return Shuffle = !Shuffle; + } + } + + public bool ToggleAutoplay() + { + lock (locker) + { + return Autoplay = !Autoplay; + } + } + + public bool ToggleRepeatPlaylist() + { + lock (locker) + { + return RepeatPlaylist = !RepeatPlaylist; + } + } + + public async Task SetVoiceChannel(IVoiceChannel vch) + { + lock (locker) + { + if (Exited) + return; + VoiceChannel = vch; + } + _audioClient = await vch.ConnectAsync(); + } + + public async Task UpdateSongDurationsAsync() + { + var sw = Stopwatch.StartNew(); + var (_, songs) = Queue.ToArray(); + var toUpdate = songs + .Where(x => x.ProviderType == MusicType.YouTube + && x.TotalTime == TimeSpan.Zero); + + var vIds = toUpdate.Select(x => x.VideoId); + + sw.Stop(); + _log.Info(sw.Elapsed.TotalSeconds); + if (!vIds.Any()) + return; + + var durations = await _google.GetVideoDurationsAsync(vIds); + + foreach (var x in toUpdate) + { + if (durations.TryGetValue(x.VideoId, out var dur)) + x.TotalTime = dur; + } + } + + public SongInfo MoveSong(int n1, int n2) + => Queue.MoveSong(n1, n2); + + //// this should be written better + //public TimeSpan TotalPlaytime => + // _playlist.Any(s => s.TotalTime == TimeSpan.MaxValue) ? + // TimeSpan.MaxValue : + // new TimeSpan(_playlist.Sum(s => s.TotalTime.Ticks)); + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/MusicQueue.cs b/src/NadekoBot/Modules/Music/Common/MusicQueue.cs new file mode 100644 index 00000000..81241adf --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/MusicQueue.cs @@ -0,0 +1,215 @@ +using NadekoBot.Extensions; +using NadekoBot.Modules.Music.Common.Exceptions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NadekoBot.Common; + +namespace NadekoBot.Modules.Music.Common +{ + public class MusicQueue : IDisposable + { + private LinkedList Songs { get; set; } = new LinkedList(); + private int _currentIndex = 0; + public int CurrentIndex + { + get + { + return _currentIndex; + } + set + { + lock (locker) + { + if (Songs.Count == 0) + _currentIndex = 0; + else + _currentIndex = value %= Songs.Count; + } + } + } + public (int Index, SongInfo Song) Current + { + get + { + var cur = CurrentIndex; + return (cur, Songs.ElementAtOrDefault(cur)); + } + } + + private readonly object locker = new object(); + private TaskCompletionSource nextSource { get; } = new TaskCompletionSource(); + public int Count + { + get + { + lock (locker) + { + return Songs.Count; + } + } + } + + private uint _maxQueueSize; + public uint MaxQueueSize + { + get => _maxQueueSize; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value)); + + lock (locker) + { + _maxQueueSize = value; + } + } + } + + public void Add(SongInfo song) + { + song.ThrowIfNull(nameof(song)); + lock (locker) + { + if(MaxQueueSize != 0 && Songs.Count >= MaxQueueSize) + throw new QueueFullException(); + Songs.AddLast(song); + } + } + + public int AddNext(SongInfo song) + { + song.ThrowIfNull(nameof(song)); + lock (locker) + { + if (MaxQueueSize != 0 && Songs.Count >= MaxQueueSize) + throw new QueueFullException(); + var curSong = Current.Song; + if (curSong == null) + { + Songs.AddLast(song); + return Songs.Count; + } + + var songlist = Songs.ToList(); + songlist.Insert(CurrentIndex + 1, song); + Songs = new LinkedList(songlist); + return CurrentIndex + 1; + } + } + + public void Next(int skipCount = 1) + { + lock(locker) + CurrentIndex += skipCount; + } + + public void Dispose() + { + Clear(); + } + + public SongInfo RemoveAt(int index) + { + lock (locker) + { + if (index < 0 || index >= Songs.Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + var current = Songs.First.Value; + for (int i = 0; i < Songs.Count; i++) + { + if (i == index) + { + current = Songs.ElementAt(index); + Songs.Remove(current); + if (CurrentIndex != 0) + { + if (CurrentIndex >= index) + { + --CurrentIndex; + } + } + break; + } + } + return current; + } + } + + public void Clear() + { + lock (locker) + { + Songs.Clear(); + CurrentIndex = 0; + } + } + + public (int CurrentIndex, SongInfo[] Songs) ToArray() + { + lock (locker) + { + return (CurrentIndex, Songs.ToArray()); + } + } + + public void ResetCurrent() + { + lock (locker) + { + CurrentIndex = 0; + } + } + + public void Random() + { + lock (locker) + { + CurrentIndex = new NadekoRandom().Next(Songs.Count); + } + } + + public SongInfo MoveSong(int n1, int n2) + { + lock (locker) + { + var currentSong = Current.Song; + var playlist = Songs.ToList(); + if (n1 >= playlist.Count || n2 >= playlist.Count || n1 == n2) + return null; + + var s = playlist[n1]; + + playlist.RemoveAt(n1); + playlist.Insert(n2, s); + + Songs = new LinkedList(playlist); + + + if (currentSong != null) + CurrentIndex = playlist.IndexOf(currentSong); + + return s; + } + } + + public void RemoveSong(SongInfo song) + { + lock (locker) + { + Songs.Remove(song); + } + } + + public bool IsLast() + { + lock (locker) + return CurrentIndex == Songs.Count - 1; + } + } +} +//O O [O] O O O O +// +// 3 \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/SongBuffer.cs b/src/NadekoBot/Modules/Music/Common/SongBuffer.cs new file mode 100644 index 00000000..2193877f --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/SongBuffer.cs @@ -0,0 +1,95 @@ +using NLog; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; + +namespace NadekoBot.Modules.Music.Common +{ + public class SongBuffer : IDisposable + { + const int readSize = 81920; + private Process p; + private Stream _outStream; + + private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1); + private readonly Logger _log; + + public string SongUri { get; private set; } + + public SongBuffer(string songUri, string skipTo, bool isLocal) + { + _log = LogManager.GetCurrentClassLogger(); + this.SongUri = songUri; + this._isLocal = isLocal; + + try + { + this.p = StartFFmpegProcess(SongUri, 0); + this._outStream = this.p.StandardOutput.BaseStream; + } + catch (System.ComponentModel.Win32Exception) + { + _log.Error(@"You have not properly installed or configured FFMPEG. +Please install and configure FFMPEG to play music. +Check the guides for your platform on how to setup ffmpeg correctly: + Windows Guide: https://goo.gl/OjKk8F + Linux Guide: https://goo.gl/ShjCUo"); + } + catch (OperationCanceledException) { } + catch (InvalidOperationException) { } // when ffmpeg is disposed + catch (Exception ex) + { + _log.Info(ex); + } + } + + private Process StartFFmpegProcess(string songUri, float skipTo = 0) + { + var args = $"-err_detect ignore_err -i {songUri} -f s16le -ar 48000 -vn -ac 2 pipe:1 -loglevel error"; + if (!_isLocal) + args = "-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5 " + args; + + return Process.Start(new ProcessStartInfo + { + FileName = "ffmpeg", + Arguments = args, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = false, + CreateNoWindow = true, + }); + } + + private readonly object locker = new object(); + private readonly bool _isLocal; + + public int Read(byte[] b, int offset, int toRead) + { + lock (locker) + return _outStream.Read(b, offset, toRead); + } + + public void Dispose() + { + try + { + this.p.StandardOutput.Dispose(); + } + catch (Exception ex) + { + _log.Error(ex); + } + try + { + if(!this.p.HasExited) + this.p.Kill(); + } + catch + { + } + _outStream.Dispose(); + this.p.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Music/SongHandler.cs b/src/NadekoBot/Modules/Music/Common/SongHandler.cs similarity index 78% rename from src/NadekoBot/Services/Music/SongHandler.cs rename to src/NadekoBot/Modules/Music/Common/SongHandler.cs index f716196b..660ec8f8 100644 --- a/src/NadekoBot/Services/Music/SongHandler.cs +++ b/src/NadekoBot/Modules/Music/Common/SongHandler.cs @@ -1,6 +1,6 @@ using NLog; -namespace NadekoBot.Services.Music +namespace NadekoBot.Modules.Music.Common { public static class SongHandler { diff --git a/src/NadekoBot/Modules/Music/Common/SongInfo.cs b/src/NadekoBot/Modules/Music/Common/SongInfo.cs new file mode 100644 index 00000000..c19f8057 --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/SongInfo.cs @@ -0,0 +1,79 @@ +using Discord; +using NadekoBot.Extensions; +using NadekoBot.Services.Database.Models; +using System; +using System.Net; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Music.Common +{ + public class SongInfo + { + public string Provider { get; set; } + public MusicType ProviderType { get; set; } + public string Query { get; set; } + public string Title { get; set; } + public Func> Uri { get; set; } + public string Thumbnail { get; set; } + public string QueuerName { get; set; } + public TimeSpan TotalTime { get; set; } = TimeSpan.Zero; + + public string PrettyProvider => (Provider ?? "???"); + //public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime; + public string PrettyName => $"**[{Title.TrimTo(65)}]({SongUrl})**"; + public string PrettyInfo => $"{PrettyTotalTime} | {PrettyProvider} | {QueuerName}"; + public string PrettyFullName => $"{PrettyName}\n\t\t`{PrettyTotalTime} | {PrettyProvider} | {Format.Sanitize(QueuerName.TrimTo(15))}`"; + public string PrettyTotalTime + { + get + { + if (TotalTime == TimeSpan.Zero) + return "(?)"; + if (TotalTime == TimeSpan.MaxValue) + return "∞"; + var time = TotalTime.ToString(@"mm\:ss"); + var hrs = (int)TotalTime.TotalHours; + + if (hrs > 0) + return hrs + ":" + time; + return time; + } + } + + public string SongUrl + { + get + { + switch (ProviderType) + { + case MusicType.YouTube: + return Query; + case MusicType.Soundcloud: + return Query; + case MusicType.Local: + return $"https://google.com/search?q={ WebUtility.UrlEncode(Title).Replace(' ', '+') }"; + case MusicType.Radio: + return $"https://google.com/search?q={Title}"; + default: + return ""; + } + } + } + private string _videoId = null; + public string VideoId + { + get + { + if (ProviderType == MusicType.YouTube) + return _videoId = _videoId ?? videoIdRegex.Match(Query)?.ToString(); + + return _videoId ?? ""; + } + + set => _videoId = value; + } + + private readonly Regex videoIdRegex = new Regex("<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+", RegexOptions.Compiled); + } +} diff --git a/src/NadekoBot/Modules/Music/Common/SongResolver/ISongResolverFactory.cs b/src/NadekoBot/Modules/Music/Common/SongResolver/ISongResolverFactory.cs new file mode 100644 index 00000000..c7d79e73 --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/SongResolver/ISongResolverFactory.cs @@ -0,0 +1,11 @@ +using NadekoBot.Modules.Music.Common.SongResolver.Strategies; +using NadekoBot.Services.Database.Models; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Music.Common.SongResolver +{ + public interface ISongResolverFactory + { + Task GetResolveStrategy(string query, MusicType? musicType); + } +} diff --git a/src/NadekoBot/Modules/Music/Common/SongResolver/SongResolverFactory.cs b/src/NadekoBot/Modules/Music/Common/SongResolver/SongResolverFactory.cs new file mode 100644 index 00000000..7e65701c --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/SongResolver/SongResolverFactory.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using NadekoBot.Modules.Music.Common.SongResolver.Strategies; + +namespace NadekoBot.Modules.Music.Common.SongResolver +{ + public class SongResolverFactory : ISongResolverFactory + { + private readonly SoundCloudApiService _sc; + + public SongResolverFactory(SoundCloudApiService sc) + { + _sc = sc; + } + + public async Task GetResolveStrategy(string query, MusicType? musicType) + { + await Task.Yield(); //for async warning + switch (musicType) + { + case MusicType.YouTube: + return new YoutubeResolveStrategy(); + case MusicType.Radio: + return new RadioResolveStrategy(); + case MusicType.Local: + return new LocalSongResolveStrategy(); + case MusicType.Soundcloud: + return new SoundcloudResolveStrategy(_sc); + default: + if (_sc.IsSoundCloudLink(query)) + return new SoundcloudResolveStrategy(_sc); + else if (RadioResolveStrategy.IsRadioLink(query)) + return new RadioResolveStrategy(); + // maybe add a check for local files in the future + else + return new YoutubeResolveStrategy(); + } + } + } +} diff --git a/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/IResolverStrategy.cs b/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/IResolverStrategy.cs new file mode 100644 index 00000000..ec709dab --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/IResolverStrategy.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Music.Common.SongResolver.Strategies +{ + public interface IResolveStrategy + { + Task ResolveSong(string query); + } +} diff --git a/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/LocalSongResolveStrategy.cs b/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/LocalSongResolveStrategy.cs new file mode 100644 index 00000000..413dfa9d --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/LocalSongResolveStrategy.cs @@ -0,0 +1,22 @@ +using NadekoBot.Services.Database.Models; +using System.IO; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Music.Common.SongResolver.Strategies +{ + public class LocalSongResolveStrategy : IResolveStrategy + { + public Task ResolveSong(string query) + { + return Task.FromResult(new SongInfo + { + Uri = () => Task.FromResult("\"" + Path.GetFullPath(query) + "\""), + Title = Path.GetFileNameWithoutExtension(query), + Provider = "Local File", + ProviderType = MusicType.Local, + Query = query, + Thumbnail = "https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png", + }); + } + } +} diff --git a/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/RadioResolveStrategy.cs b/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/RadioResolveStrategy.cs new file mode 100644 index 00000000..5e11ae4e --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/RadioResolveStrategy.cs @@ -0,0 +1,138 @@ +using NadekoBot.Services.Database.Models; +using NLog; +using System; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Music.Common.SongResolver.Strategies +{ + public class RadioResolveStrategy : IResolveStrategy + { + private readonly Regex plsRegex = new Regex("File1=(?.*?)\\n", RegexOptions.Compiled); + private readonly Regex m3uRegex = new Regex("(?^[^#].*)", RegexOptions.Compiled | RegexOptions.Multiline); + private readonly Regex asxRegex = new Regex(".*?)\"", RegexOptions.Compiled); + private readonly Regex xspfRegex = new Regex("(?.*?)", RegexOptions.Compiled); + private readonly Logger _log; + + public RadioResolveStrategy() + { + _log = LogManager.GetCurrentClassLogger(); + } + + public async Task ResolveSong(string query) + { + if (IsRadioLink(query)) + query = await HandleStreamContainers(query); + + return new SongInfo + { + Uri = () => Task.FromResult(query), + Title = query, + Provider = "Radio Stream", + ProviderType = MusicType.Radio, + Query = query, + TotalTime = TimeSpan.MaxValue, + Thumbnail = "https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png", + }; + } + + public static bool IsRadioLink(string query) => + (query.StartsWith("http") || + query.StartsWith("ww")) + && + (query.Contains(".pls") || + query.Contains(".m3u") || + query.Contains(".asx") || + query.Contains(".xspf")); + + private async Task HandleStreamContainers(string query) + { + string file = null; + try + { + using (var http = new HttpClient()) + { + file = await http.GetStringAsync(query).ConfigureAwait(false); + } + } + catch + { + return query; + } + if (query.Contains(".pls")) + { + //File1=http://armitunes.com:8000/ + //Regex.Match(query) + try + { + var m = plsRegex.Match(file); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + _log.Warn($"Failed reading .pls:\n{file}"); + return null; + } + } + if (query.Contains(".m3u")) + { + /* +# This is a comment + C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3 + C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3 + */ + try + { + var m = m3uRegex.Match(file); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + _log.Warn($"Failed reading .m3u:\n{file}"); + return null; + } + + } + if (query.Contains(".asx")) + { + // + try + { + var m = asxRegex.Match(file); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + _log.Warn($"Failed reading .asx:\n{file}"); + return null; + } + } + if (query.Contains(".xspf")) + { + /* + + + + file:///mp3s/song_1.mp3 + */ + try + { + var m = xspfRegex.Match(file); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + _log.Warn($"Failed reading .xspf:\n{file}"); + return null; + } + } + + return query; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/SoundCloudResolveStrategy.cs b/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/SoundCloudResolveStrategy.cs new file mode 100644 index 00000000..66e42075 --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/SoundCloudResolveStrategy.cs @@ -0,0 +1,27 @@ +using NadekoBot.Modules.Music.Extensions; +using NadekoBot.Services.Impl; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Music.Common.SongResolver.Strategies +{ + public class SoundcloudResolveStrategy : IResolveStrategy + { + private readonly SoundCloudApiService _sc; + + public SoundcloudResolveStrategy(SoundCloudApiService sc) + { + _sc = sc; + } + + public async Task ResolveSong(string query) + { + var svideo = !_sc.IsSoundCloudLink(query) ? + await _sc.GetVideoByQueryAsync(query).ConfigureAwait(false) : + await _sc.ResolveVideoAsync(query).ConfigureAwait(false); + + if (svideo == null) + return null; + return await svideo.GetSongInfo(); + } + } +} diff --git a/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/YoutubeResolveStrategy.cs b/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/YoutubeResolveStrategy.cs new file mode 100644 index 00000000..e4a0058e --- /dev/null +++ b/src/NadekoBot/Modules/Music/Common/SongResolver/Strategies/YoutubeResolveStrategy.cs @@ -0,0 +1,69 @@ +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using NLog; +using System; +using System.Globalization; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Music.Common.SongResolver.Strategies +{ + public class YoutubeResolveStrategy : IResolveStrategy + { + private readonly Logger _log; + + public YoutubeResolveStrategy() + { + _log = LogManager.GetCurrentClassLogger(); + } + + public async Task ResolveSong(string query) + { + _log.Info("Getting link"); + string[] data; + try + { + using (var ytdl = new YtdlOperation()) + { + data = (await ytdl.GetDataAsync(query)).Split('\n'); + } + if (data.Length < 6) + { + _log.Info("No song found. Data less than 6"); + return null; + } + TimeSpan time; + if (!TimeSpan.TryParseExact(data[4], new[] { "ss", "m\\:ss", "mm\\:ss", "h\\:mm\\:ss", "hh\\:mm\\:ss", "hhh\\:mm\\:ss" }, CultureInfo.InvariantCulture, out time)) + time = TimeSpan.FromHours(24); + + return new SongInfo() + { + Title = data[0], + VideoId = data[1], + Uri = async () => + { + using (var ytdl = new YtdlOperation()) + { + data = (await ytdl.GetDataAsync(query)).Split('\n'); + } + if (data.Length < 6) + { + _log.Info("No song found. Data less than 6"); + return null; + } + return data[2]; + }, + Thumbnail = data[3], + TotalTime = time, + Provider = "YouTube", + ProviderType = MusicType.YouTube, + Query = "https://youtube.com/watch?v=" + data[1], + }; + } + catch (Exception ex) + { + _log.Warn(ex); + return null; + } + } + } +} diff --git a/src/NadekoBot/Modules/Music/Extensions/Extensions.cs b/src/NadekoBot/Modules/Music/Extensions/Extensions.cs new file mode 100644 index 00000000..b3eb1d38 --- /dev/null +++ b/src/NadekoBot/Modules/Music/Extensions/Extensions.cs @@ -0,0 +1,23 @@ +using NadekoBot.Modules.Music.Common; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using System; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Music.Extensions +{ + public static class Extensions + { + public static Task GetSongInfo(this SoundCloudVideo svideo) => + Task.FromResult(new SongInfo + { + Title = svideo.FullName, + Provider = "SoundCloud", + Uri = () => svideo.StreamLink(), + ProviderType = MusicType.Soundcloud, + Query = svideo.TrackLink, + Thumbnail = svideo.artwork_url, + TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) + }); + } +} diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index afd60bb2..a5b0295e 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -1,173 +1,174 @@ using Discord.Commands; using Discord.WebSocket; using NadekoBot.Services; -using System.IO; using Discord; using System.Threading.Tasks; -using NadekoBot.Attributes; using System; using System.Linq; using NadekoBot.Extensions; -using System.Net.Http; -using Newtonsoft.Json.Linq; using System.Collections.Generic; using NadekoBot.Services.Database.Models; -using System.Threading; -using NadekoBot.Services.Music; -using NadekoBot.DataStructures; +using System.IO; +using System.Net.Http; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.Collections; +using Newtonsoft.Json.Linq; +using NadekoBot.Services.Impl; +using NadekoBot.Modules.Music.Services; +using NadekoBot.Modules.Music.Common.Exceptions; +using NadekoBot.Modules.Music.Common; +using NadekoBot.Modules.Music.Extensions; namespace NadekoBot.Modules.Music { [NoPublicBot] - public class Music : NadekoTopLevelModule + public class Music : NadekoTopLevelModule { - private static MusicService _music; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IBotCredentials _creds; private readonly IGoogleApiService _google; private readonly DbService _db; - public Music(DiscordShardedClient client, IBotCredentials creds, IGoogleApiService google, - DbService db, MusicService music) + public Music(DiscordSocketClient client, IBotCredentials creds, IGoogleApiService google, + DbService db) { _client = client; _creds = creds; _google = google; _db = db; - _music = music; - //it can fail if its currenctly opened or doesn't exist. Either way i don't care - _client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated; + //_client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated; + _client.LeftGuild += _client_LeftGuild; } - private Task Client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState oldState, SocketVoiceState newState) + private Task _client_LeftGuild(SocketGuild arg) { - var usr = iusr as SocketGuildUser; - if (usr == null || - oldState.VoiceChannel == newState.VoiceChannel) - return Task.CompletedTask; + var t = _service.DestroyPlayer(arg.Id); + return Task.CompletedTask; + } - MusicPlayer player; - if ((player = _music.GetPlayer(usr.Guild.Id)) == null) - return Task.CompletedTask; + //todo changing server region is bugged again + //private Task Client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState oldState, SocketVoiceState newState) + //{ + // var t = Task.Run(() => + // { + // var usr = iusr as SocketGuildUser; + // if (usr == null || + // oldState.VoiceChannel == newState.VoiceChannel) + // return; + // var player = _music.GetPlayerOrDefault(usr.Guild.Id); + + // if (player == null) + // return; + + // try + // { + // //if bot moved + // if ((player.VoiceChannel == oldState.VoiceChannel) && + // usr.Id == _client.CurrentUser.Id) + // { + // //if (player.Paused && newState.VoiceChannel.Users.Count > 1) //unpause if there are people in the new channel + // // player.TogglePause(); + // //else if (!player.Paused && newState.VoiceChannel.Users.Count <= 1) // pause if there are no users in the new channel + // // player.TogglePause(); + + // // player.SetVoiceChannel(newState.VoiceChannel); + // return; + // } + + // ////if some other user moved + // //if ((player.VoiceChannel == newState.VoiceChannel && //if joined first, and player paused, unpause + // // player.Paused && + // // newState.VoiceChannel.Users.Count >= 2) || // keep in mind bot is in the channel (+1) + // // (player.VoiceChannel == oldState.VoiceChannel && // if left last, and player unpaused, pause + // // !player.Paused && + // // oldState.VoiceChannel.Users.Count == 1)) + // //{ + // // player.TogglePause(); + // // return; + // //} + // } + // catch + // { + // // ignored + // } + // }); + // return Task.CompletedTask; + //} + + private async Task InternalQueue(MusicPlayer mp, SongInfo songInfo, bool silent, bool queueFirst = false) + { + if (songInfo == null) + { + if(!silent) + await ReplyErrorLocalized("song_not_found").ConfigureAwait(false); + return; + } + + int index; try { - //if bot moved - if ((player.PlaybackVoiceChannel == oldState.VoiceChannel) && - usr.Id == _client.CurrentUser.Id) + index = queueFirst + ? mp.EnqueueNext(songInfo) + : mp.Enqueue(songInfo); + } + catch (QueueFullException) + { + await ReplyErrorLocalized("queue_full", mp.MaxQueueSize).ConfigureAwait(false); + throw; + } + if (index != -1) + { + if (!silent) { - if (player.Paused && newState.VoiceChannel.Users.Count > 1) //unpause if there are people in the new channel - player.TogglePause(); - else if (!player.Paused && newState.VoiceChannel.Users.Count <= 1) // pause if there are no users in the new channel - player.TogglePause(); + try + { + var embed = new EmbedBuilder().WithOkColor() + .WithAuthor(eab => eab.WithName(GetText("queued_song") + " #" + (index + 1)).WithMusicIcon()) + .WithDescription($"{songInfo.PrettyName}\n{GetText("queue")} ") + .WithFooter(ef => ef.WithText(songInfo.PrettyProvider)); - return Task.CompletedTask; + if (Uri.IsWellFormedUriString(songInfo.Thumbnail, UriKind.Absolute)) + embed.WithThumbnailUrl(songInfo.Thumbnail); + + var queuedMessage = await mp.OutputTextChannel.EmbedAsync(embed).ConfigureAwait(false); + if (mp.Stopped) + { + (await ReplyErrorLocalized("queue_stopped", Format.Code(Prefix + "play")).ConfigureAwait(false)).DeleteAfter(10); + } + queuedMessage?.DeleteAfter(10); + } + catch + { + // ignored + } } - - - //if some other user moved - if ((player.PlaybackVoiceChannel == newState.VoiceChannel && //if joined first, and player paused, unpause - player.Paused && - newState.VoiceChannel.Users.Count == 2) || // keep in mind bot is in the channel (+1) - (player.PlaybackVoiceChannel == oldState.VoiceChannel && // if left last, and player unpaused, pause - !player.Paused && - oldState.VoiceChannel.Users.Count == 1)) - { - player.TogglePause(); - return Task.CompletedTask; - } - } - catch + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Play([Remainder] string query = null) + { + var mp = await _service.GetOrCreatePlayer(Context); + if (string.IsNullOrWhiteSpace(query)) { - // ignored - } - return Task.CompletedTask; - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public Task Next(int skipCount = 1) - { - if (skipCount < 1) - return Task.CompletedTask; - - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return Task.CompletedTask; - if (musicPlayer.PlaybackVoiceChannel == ((IGuildUser)Context.User).VoiceChannel) - { - while (--skipCount > 0) - { - musicPlayer.RemoveSongAt(0); - } - musicPlayer.Next(); - } - return Task.CompletedTask; - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public Task Stop() - { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return Task.CompletedTask; - if (((IGuildUser)Context.User).VoiceChannel == musicPlayer.PlaybackVoiceChannel) - { - musicPlayer.Autoplay = false; - musicPlayer.Stop(); - } - return Task.CompletedTask; - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public Task Destroy() - { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return Task.CompletedTask; - if (((IGuildUser)Context.User).VoiceChannel == musicPlayer.PlaybackVoiceChannel) - _music.DestroyPlayer(Context.Guild.Id); - - return Task.CompletedTask; - - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public Task Pause() - { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return Task.CompletedTask; - if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return Task.CompletedTask; - musicPlayer.TogglePause(); - return Task.CompletedTask; - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task Fairplay() - { - var channel = (ITextChannel)Context.Channel; - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; - if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return; - var val = musicPlayer.FairPlay = !musicPlayer.FairPlay; - - if (val) - { - await ReplyConfirmLocalized("fp_enabled").ConfigureAwait(false); + await Next(); } + else if (int.TryParse(query, out var index)) + if (index >= 1) + mp.SetIndex(index - 1); + else + return; else { - await ReplyConfirmLocalized("fp_disabled").ConfigureAwait(false); + try + { + await Queue(query); + } + catch { } } } @@ -175,7 +176,22 @@ namespace NadekoBot.Modules.Music [RequireContext(ContextType.Guild)] public async Task Queue([Remainder] string query) { - await _music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, query).ConfigureAwait(false); + var mp = await _service.GetOrCreatePlayer(Context); + var songInfo = await _service.ResolveSong(query, Context.User.ToString()); + try { await InternalQueue(mp, songInfo, false); } catch (QueueFullException) { return; } + if ((await Context.Guild.GetCurrentUserAsync()).GetPermissions((IGuildChannel)Context.Channel).ManageMessages) + { + Context.Message.DeleteAfter(10); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task QueueNext([Remainder] string query) + { + var mp = await _service.GetOrCreatePlayer(Context); + var songInfo = await _service.ResolveSong(query, Context.User.ToString()); + try { await InternalQueue(mp, songInfo, false, true); } catch (QueueFullException) { return; } if ((await Context.Guild.GetCurrentUserAsync()).GetPermissions((IGuildChannel)Context.Channel).ManageMessages) { Context.Message.DeleteAfter(10); @@ -217,76 +233,85 @@ namespace NadekoBot.Modules.Music { try { await msg.DeleteAsync().ConfigureAwait(false); } catch { } } - } - + [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - public async Task SoundCloudQueue([Remainder] string query) + public async Task ListQueue(int page = 0) { - await _music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, query, musicType: MusicType.Soundcloud).ConfigureAwait(false); - if ((await Context.Guild.GetCurrentUserAsync()).GetPermissions((IGuildChannel)Context.Channel).ManageMessages) - { - Context.Message.DeleteAfter(10); - } - } + var mp = await _service.GetOrCreatePlayer(Context); + var (current, songs) = mp.QueueArray(); - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task ListQueue(int page = 1) - { - Song currentSong; - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; - if ((currentSong = musicPlayer?.CurrentSong) == null) + if (!songs.Any()) { await ReplyErrorLocalized("no_player").ConfigureAwait(false); return; } - - if (--page < 0) + + if (--page < -1) return; - - try { await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { } + + try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { } const int itemsPerPage = 10; - var total = musicPlayer.TotalPlaytime; - var totalStr = total == TimeSpan.MaxValue ? "∞" : GetText("time_format", - (int) total.TotalHours, - total.Minutes, + if (page == -1) + page = current / itemsPerPage; + + //if page is 0 (-1 after this decrement) that means default to the page current song is playing from + var total = mp.TotalPlaytime; + var totalStr = total == TimeSpan.MaxValue ? "∞" : GetText("time_format", + (int)total.TotalHours, + total.Minutes, total.Seconds); - var maxPlaytime = musicPlayer.MaxPlaytimeSeconds; - var lastPage = musicPlayer.Playlist.Count / itemsPerPage; + var maxPlaytime = mp.MaxPlaytimeSeconds; + var lastPage = songs.Length / itemsPerPage; Func printAction = curPage => { var startAt = itemsPerPage * curPage; var number = 0 + startAt; - var desc = string.Join("\n", musicPlayer.Playlist + var desc = string.Join("\n", songs .Skip(startAt) .Take(itemsPerPage) - .Select(v => $"`{++number}.` {v.PrettyFullName}")); + .Select(v => + { + if(number++ == current) + return $"**⇒**`{number}.` {v.PrettyFullName}"; + else + return $"`{number}.` {v.PrettyFullName}"; + })); + + desc = $"`🔊` {songs[current].PrettyFullName}\n\n" + desc; + + var add = ""; + if (mp.Stopped) + add += Format.Bold(GetText("queue_stopped", Format.Code(Prefix + "play"))) + "\n"; + var mps = mp.MaxPlaytimeSeconds; + if (mps > 0) + add += Format.Bold(GetText("song_skips_after", TimeSpan.FromSeconds(mps).ToString("HH\\:mm\\:ss"))) + "\n"; + if (mp.RepeatCurrentSong) + add += "🔂 " + GetText("repeating_cur_song") + "\n"; + else if (mp.Shuffle) + add += "🔀 " + GetText("shuffling_playlist") + "\n"; + else + { + if (mp.Autoplay) + add += "↪ " + GetText("autoplaying") + "\n"; + if (mp.FairPlay && !mp.Autoplay) + add += " " + GetText("fairplay") + "\n"; + else if (mp.RepeatPlaylist) + add += "🔁 " + GetText("repeating_playlist") + "\n"; + } + + if (!string.IsNullOrWhiteSpace(add)) + desc = add + "\n" + desc; - desc = $"`🔊` {currentSong.PrettyFullName}\n\n" + desc; - - if (musicPlayer.RepeatSong) - desc = "🔂 " + GetText("repeating_cur_song") +"\n\n" + desc; - else if (musicPlayer.RepeatPlaylist) - desc = "🔁 " + GetText("repeating_playlist")+"\n\n" + desc; - - - var embed = new EmbedBuilder() .WithAuthor(eab => eab.WithName(GetText("player_queue", curPage + 1, lastPage + 1)) .WithMusicIcon()) .WithDescription(desc) - .WithFooter(ef => ef.WithText($"{musicPlayer.PrettyVolume} | {musicPlayer.Playlist.Count} " + - $"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {totalStr} | " + - (musicPlayer.FairPlay - ? "✔️" + GetText("fairplay") - : "✖️" + GetText("fairplay")) + " | " + - (maxPlaytime == 0 ? "unlimited" : GetText("play_limit", maxPlaytime)))) + .WithFooter(ef => ef.WithText($"{mp.PrettyVolume} | {songs.Length} " + + $"{("tracks".SnPl(songs.Length))} | {totalStr}")) .WithOkColor(); return embed; @@ -296,41 +321,51 @@ namespace NadekoBot.Modules.Music [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - public async Task NowPlaying() + public async Task Next(int skipCount = 1) { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) + if (skipCount < 1) return; - var currentSong = musicPlayer.CurrentSong; - if (currentSong == null) - return; - try { await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { } + + var mp = await _service.GetOrCreatePlayer(Context); - var embed = new EmbedBuilder().WithOkColor() - .WithAuthor(eab => eab.WithName(GetText("now_playing")).WithMusicIcon()) - .WithDescription(currentSong.PrettyName) - .WithThumbnailUrl(currentSong.Thumbnail) - .WithFooter(ef => ef.WithText(musicPlayer.PrettyVolume + " | " + currentSong.PrettyFullTime + $" | {currentSong.PrettyProvider} | {currentSong.QueuerName}")); + mp.Next(skipCount); + } - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Stop() + { + var mp = await _service.GetOrCreatePlayer(Context); + mp.Stop(); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Destroy() + { + await _service.DestroyPlayer(Context.Guild.Id); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Pause() + { + var mp = await _service.GetOrCreatePlayer(Context); + mp.TogglePause(); } [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] public async Task Volume(int val) { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; - if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return; + var mp = await _service.GetOrCreatePlayer(Context); if (val < 0 || val > 100) { await ReplyErrorLocalized("volume_input_invalid").ConfigureAwait(false); return; } - var volume = musicPlayer.SetVolume(val); - await ReplyConfirmLocalized("volume_set", volume).ConfigureAwait(false); + mp.SetVolume(val); + await ReplyConfirmLocalized("volume_set", val).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -352,399 +387,45 @@ namespace NadekoBot.Modules.Music [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - public async Task ShufflePlaylist() + [Priority(1)] + public async Task SongRemove(int index) { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; - if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return; - if (musicPlayer.Playlist.Count < 2) - return; - - musicPlayer.Shuffle(); - await ReplyConfirmLocalized("songs_shuffled").ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task Playlist([Remainder] string playlist) - { - - var arg = playlist; - if (string.IsNullOrWhiteSpace(arg)) - return; - if (((IGuildUser)Context.User).VoiceChannel?.Guild != Context.Guild) + if (index < 1) { - await ReplyErrorLocalized("must_be_in_voice").ConfigureAwait(false); + await ReplyErrorLocalized("removed_song_error").ConfigureAwait(false); return; } - var plId = (await _google.GetPlaylistIdsByKeywordsAsync(arg).ConfigureAwait(false)).FirstOrDefault(); - if (plId == null) + var mp = await _service.GetOrCreatePlayer(Context); + try { - await ReplyErrorLocalized("no_search_results").ConfigureAwait(false); - return; + var song = mp.RemoveAt(index - 1); + var embed = new EmbedBuilder() + .WithAuthor(eab => eab.WithName(GetText("removed_song") + " #" + (index)).WithMusicIcon()) + .WithDescription(song.PrettyName) + .WithFooter(ef => ef.WithText(song.PrettyInfo)) + .WithErrorColor(); + + await mp.OutputTextChannel.EmbedAsync(embed).ConfigureAwait(false); } - var ids = await _google.GetPlaylistTracksAsync(plId, 500).ConfigureAwait(false); - if (!ids.Any()) + catch (ArgumentOutOfRangeException) { - await ReplyErrorLocalized("no_search_results").ConfigureAwait(false); - return; - } - var count = ids.Count(); - var msg = await Context.Channel.SendMessageAsync("🎵 " + GetText("attempting_to_queue", - Format.Bold(count.ToString()))).ConfigureAwait(false); - - var cancelSource = new CancellationTokenSource(); - - var gusr = (IGuildUser)Context.User; - while (ids.Any() && !cancelSource.IsCancellationRequested) - { - var tasks = Task.WhenAll(ids.Take(5).Select(async id => - { - if (cancelSource.Token.IsCancellationRequested) - return; - try - { - await _music.QueueSong(gusr, (ITextChannel)Context.Channel, gusr.VoiceChannel, id, true).ConfigureAwait(false); - } - catch (SongNotFoundException) { } - catch { try { cancelSource.Cancel(); } catch { } } - })); - - await Task.WhenAny(tasks, Task.Delay(Timeout.Infinite, cancelSource.Token)); - ids = ids.Skip(5); - } - - await msg.ModifyAsync(m => m.Content = "✅ " + Format.Bold(GetText("playlist_queue_complete"))).ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task SoundCloudPl([Remainder] string pl) - { - - pl = pl?.Trim(); - - if (string.IsNullOrWhiteSpace(pl)) - return; - - using (var http = new HttpClient()) - { - var scvids = JObject.Parse(await http.GetStringAsync($"https://scapi.nadekobot.me/resolve?url={pl}").ConfigureAwait(false))["tracks"].ToObject(); - await _music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, scvids[0].TrackLink).ConfigureAwait(false); - - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; - - foreach (var svideo in scvids.Skip(1)) - { - try - { - musicPlayer.AddSong(new Song(new SongInfo - { - Title = svideo.FullName, - Provider = "SoundCloud", - Uri = await svideo.StreamLink(), - ProviderType = MusicType.Normal, - Query = svideo.TrackLink, - }), ((IGuildUser)Context.User).Username); - } - catch (PlaylistFullException) { break; } - } + await ReplyErrorLocalized("removed_song_error").ConfigureAwait(false); } } - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [OwnerOnly] - public async Task LocalPl([Remainder] string directory) - { - - var arg = directory; - if (string.IsNullOrWhiteSpace(arg)) - return; - var dir = new DirectoryInfo(arg); - var fileEnum = dir.GetFiles("*", SearchOption.AllDirectories) - .Where(x => !x.Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); - var gusr = (IGuildUser)Context.User; - foreach (var file in fileEnum) - { - try - { - await _music.QueueSong(gusr, (ITextChannel)Context.Channel, gusr.VoiceChannel, file.FullName, true, MusicType.Local).ConfigureAwait(false); - } - catch (PlaylistFullException) - { - break; - } - catch - { - // ignored - } - } - await ReplyConfirmLocalized("dir_queue_complete").ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task Radio(string radioLink) - { - - if (((IGuildUser)Context.User).VoiceChannel?.Guild != Context.Guild) - { - await ReplyErrorLocalized("must_be_in_voice").ConfigureAwait(false); - return; - } - await _music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, radioLink, musicType: MusicType.Radio).ConfigureAwait(false); - if ((await Context.Guild.GetCurrentUserAsync()).GetPermissions((IGuildChannel)Context.Channel).ManageMessages) - { - Context.Message.DeleteAfter(10); - } - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [OwnerOnly] - public async Task Local([Remainder] string path) - { - - var arg = path; - if (string.IsNullOrWhiteSpace(arg)) - return; - await _music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, path, musicType: MusicType.Local).ConfigureAwait(false); - - } - - //[NadekoCommand, Usage, Description, Aliases] - //[RequireContext(ContextType.Guild)] - //public async Task Move() - //{ - - // MusicPlayer musicPlayer; - // var voiceChannel = ((IGuildUser)Context.User).VoiceChannel; - // if (voiceChannel == null || voiceChannel.Guild != Context.Guild || !MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) - // return; - // await musicPlayer.MoveToVoiceChannel(voiceChannel); - //} - + public enum All { All } [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] - public Task SongRemove(int num) + public async Task SongRemove(All all) { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return Task.CompletedTask; - if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return Task.CompletedTask; - - musicPlayer.RemoveSongAt(num - 1); - return Task.CompletedTask; - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [Priority(1)] - public async Task SongRemove(string all) - { - if (all.Trim().ToUpperInvariant() != "ALL") + var mp = _service.GetPlayerOrDefault(Context.Guild.Id); + if (mp == null) return; - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; - musicPlayer.ClearQueue(); + mp.Stop(true); await ReplyConfirmLocalized("queue_cleared").ConfigureAwait(false); } - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task MoveSong([Remainder] string fromto) - { - if (string.IsNullOrWhiteSpace(fromto)) - return; - - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; - - fromto = fromto?.Trim(); - var fromtoArr = fromto.Split('>'); - - int n1; - int n2; - - var playlist = musicPlayer.Playlist as List ?? musicPlayer.Playlist.ToList(); - - if (fromtoArr.Length != 2 || !int.TryParse(fromtoArr[0], out n1) || - !int.TryParse(fromtoArr[1], out n2) || n1 < 1 || n2 < 1 || n1 == n2 || - n1 > playlist.Count || n2 > playlist.Count) - { - await ReplyConfirmLocalized("invalid_input").ConfigureAwait(false); - return; - } - - var s = playlist[n1 - 1]; - playlist.Insert(n2 - 1, s); - var nn1 = n2 < n1 ? n1 : n1 - 1; - playlist.RemoveAt(nn1); - - var embed = new EmbedBuilder() - .WithTitle($"{s.SongInfo.Title.TrimTo(70)}") - .WithUrl(s.SongUrl) - .WithAuthor(eab => eab.WithName(GetText("song_moved")).WithIconUrl("https://cdn.discordapp.com/attachments/155726317222887425/258605269972549642/music1.png")) - .AddField(fb => fb.WithName(GetText("from_position")).WithValue($"#{n1}").WithIsInline(true)) - .AddField(fb => fb.WithName(GetText("to_position")).WithValue($"#{n2}").WithIsInline(true)) - .WithColor(NadekoBot.OkColor); - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); - - //await channel.SendConfirmAsync($"🎵Moved {s.PrettyName} `from #{n1} to #{n2}`").ConfigureAwait(false); - - - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task SetMaxQueue(uint size = 0) - { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; - - musicPlayer.MaxQueueSize = size; - - if(size == 0) - await ReplyConfirmLocalized("max_queue_unlimited").ConfigureAwait(false); - else - await ReplyConfirmLocalized("max_queue_x", size).ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task SetMaxPlaytime(uint seconds) - { - if (seconds < 15 && seconds != 0) - return; - - var channel = (ITextChannel)Context.Channel; - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; - musicPlayer.MaxPlaytimeSeconds = seconds; - if (seconds == 0) - await ReplyConfirmLocalized("max_playtime_none").ConfigureAwait(false); - else - await ReplyConfirmLocalized("max_playtime_set", seconds).ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task ReptCurSong() - { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; - var currentSong = musicPlayer.CurrentSong; - if (currentSong == null) - return; - var currentValue = musicPlayer.ToggleRepeatSong(); - - if (currentValue) - await Context.Channel.EmbedAsync(new EmbedBuilder() - .WithOkColor() - .WithAuthor(eab => eab.WithMusicIcon().WithName("🔂 " + GetText("repeating_track"))) - .WithDescription(currentSong.PrettyName) - .WithFooter(ef => ef.WithText(currentSong.PrettyInfo))).ConfigureAwait(false); - else - await Context.Channel.SendConfirmAsync("🔂 " + GetText("repeating_track_stopped")) - .ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task RepeatPl() - { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; - var currentValue = musicPlayer.ToggleRepeatPlaylist(); - if(currentValue) - await ReplyConfirmLocalized("rpl_enabled").ConfigureAwait(false); - else - await ReplyConfirmLocalized("rpl_disabled").ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task Save([Remainder] string name) - { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; - - var curSong = musicPlayer.CurrentSong; - var songs = musicPlayer.Playlist.Append(curSong) - .Select(s => new PlaylistSong() - { - Provider = s.SongInfo.Provider, - ProviderType = s.SongInfo.ProviderType, - Title = s.SongInfo.Title, - Uri = s.SongInfo.Uri, - Query = s.SongInfo.Query, - }).ToList(); - - MusicPlaylist playlist; - using (var uow = _db.UnitOfWork) - { - playlist = new MusicPlaylist - { - Name = name, - Author = Context.User.Username, - AuthorId = Context.User.Id, - Songs = songs, - }; - uow.MusicPlaylists.Add(playlist); - await uow.CompleteAsync().ConfigureAwait(false); - } - - await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithTitle(GetText("playlist_saved")) - .AddField(efb => efb.WithName(GetText("name")).WithValue(name)) - .AddField(efb => efb.WithName(GetText("id")).WithValue(playlist.Id.ToString()))); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task Load([Remainder] int id) - { - MusicPlaylist mpl; - using (var uow = _db.UnitOfWork) - { - mpl = uow.MusicPlaylists.GetWithSongs(id); - } - - if (mpl == null) - { - await ReplyErrorLocalized("playlist_id_not_found").ConfigureAwait(false); - return; - } - IUserMessage msg = null; - try { msg = await Context.Channel.SendMessageAsync(GetText("attempting_to_queue", Format.Bold(mpl.Songs.Count.ToString()))).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } - foreach (var item in mpl.Songs) - { - var usr = (IGuildUser)Context.User; - try - { - await _music.QueueSong(usr, (ITextChannel)Context.Channel, usr.VoiceChannel, item.Query, true, item.ProviderType).ConfigureAwait(false); - } - catch (SongNotFoundException) { } - catch { break; } - } - if (msg != null) - await msg.ModifyAsync(m => m.Content = GetText("playlist_queue_complete")).ConfigureAwait(false); - } - [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] public async Task Playlists([Remainder] int num = 1) @@ -767,7 +448,7 @@ namespace NadekoBot.Modules.Music await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } - + [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] public async Task DeletePlaylist([Remainder] int id) @@ -803,48 +484,410 @@ namespace NadekoBot.Modules.Music [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - public async Task Goto(int time) + public async Task Save([Remainder] string name) { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) + var mp = await _service.GetOrCreatePlayer(Context); + + var songs = mp.QueueArray().Songs + .Select(s => new PlaylistSong() + { + Provider = s.Provider, + ProviderType = s.ProviderType, + Title = s.Title, + Query = s.Query, + }).ToList(); + + MusicPlaylist playlist; + using (var uow = _db.UnitOfWork) + { + playlist = new MusicPlaylist + { + Name = name, + Author = Context.User.Username, + AuthorId = Context.User.Id, + Songs = songs.ToList(), + }; + uow.MusicPlaylists.Add(playlist); + await uow.CompleteAsync().ConfigureAwait(false); + } + + await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() + .WithTitle(GetText("playlist_saved")) + .AddField(efb => efb.WithName(GetText("name")).WithValue(name)) + .AddField(efb => efb.WithName(GetText("id")).WithValue(playlist.Id.ToString()))); + } + + private static readonly ConcurrentHashSet PlaylistLoadBlacklist = new ConcurrentHashSet(); + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Load([Remainder] int id) + { + if (!PlaylistLoadBlacklist.Add(Context.Guild.Id)) return; - if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel) + try + { + var mp = await _service.GetOrCreatePlayer(Context); + MusicPlaylist mpl; + using (var uow = _db.UnitOfWork) + { + mpl = uow.MusicPlaylists.GetWithSongs(id); + } + + if (mpl == null) + { + await ReplyErrorLocalized("playlist_id_not_found").ConfigureAwait(false); + return; + } + IUserMessage msg = null; + try { msg = await Context.Channel.SendMessageAsync(GetText("attempting_to_queue", Format.Bold(mpl.Songs.Count.ToString()))).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + foreach (var item in mpl.Songs) + { + try + { + await Task.Yield(); + + await Task.WhenAll(Task.Delay(1000), InternalQueue(mp, await _service.ResolveSong(item.Query, Context.User.ToString(), item.ProviderType), true)).ConfigureAwait(false); + } + catch (SongNotFoundException) { } + catch { break; } + } + if (msg != null) + await msg.ModifyAsync(m => m.Content = GetText("playlist_queue_complete")).ConfigureAwait(false); + } + finally + { + PlaylistLoadBlacklist.TryRemove(Context.Guild.Id); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Fairplay() + { + var mp = await _service.GetOrCreatePlayer(Context); + var val = mp.FairPlay = !mp.FairPlay; + + if (val) + { + await ReplyConfirmLocalized("fp_enabled").ConfigureAwait(false); + } + else + { + await ReplyConfirmLocalized("fp_disabled").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task SongAutoDelete() + { + var mp = await _service.GetOrCreatePlayer(Context); + var val = mp.AutoDelete = !mp.AutoDelete; + + if (val) + { + await ReplyConfirmLocalized("sad_enabled").ConfigureAwait(false); + } + else + { + await ReplyConfirmLocalized("sad_disabled").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task SoundCloudQueue([Remainder] string query) + { + var mp = await _service.GetOrCreatePlayer(Context); + var song = await _service.ResolveSong(query, Context.User.ToString(), MusicType.Soundcloud); + await InternalQueue(mp, song, false).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task SoundCloudPl([Remainder] string pl) + { + pl = pl?.Trim(); + + if (string.IsNullOrWhiteSpace(pl)) return; - if (time < 0) - return; - - var currentSong = musicPlayer.CurrentSong; + var mp = await _service.GetOrCreatePlayer(Context); + using (var http = new HttpClient()) + { + var scvids = JObject.Parse(await http.GetStringAsync($"https://scapi.nadekobot.me/resolve?url={pl}").ConfigureAwait(false))["tracks"].ToObject(); + IUserMessage msg = null; + try { msg = await Context.Channel.SendMessageAsync(GetText("attempting_to_queue", Format.Bold(scvids.Length.ToString()))).ConfigureAwait(false); } catch { } + foreach (var svideo in scvids) + { + try + { + await Task.Yield(); + var sinfo = await svideo.GetSongInfo(); + sinfo.QueuerName = Context.User.ToString(); + await InternalQueue(mp, sinfo, true); + } + catch (Exception ex) + { + _log.Warn(ex); + break; + } + } + if (msg != null) + await msg.ModifyAsync(m => m.Content = GetText("playlist_queue_complete")).ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task NowPlaying() + { + var mp = await _service.GetOrCreatePlayer(Context); + var (_, currentSong) = mp.Current; if (currentSong == null) return; + try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { } - //currentSong.PrintStatusMessage = false; - var gotoSong = currentSong.Clone(); - gotoSong.SkipTo = time; - musicPlayer.AddSong(gotoSong, 0); - musicPlayer.Next(); + var embed = new EmbedBuilder().WithOkColor() + .WithAuthor(eab => eab.WithName(GetText("now_playing")).WithMusicIcon()) + .WithDescription(currentSong.PrettyName) + .WithThumbnailUrl(currentSong.Thumbnail) + .WithFooter(ef => ef.WithText(mp.PrettyVolume + " | " + mp.PrettyFullTime + $" | {currentSong.PrettyProvider} | {currentSong.QueuerName}")); - var minutes = (time / 60).ToString(); - var seconds = (time % 60).ToString(); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } - if (minutes.Length == 1) - minutes = "0" + minutes; - if (seconds.Length == 1) - seconds = "0" + seconds; + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ShufflePlaylist() + { + var mp = await _service.GetOrCreatePlayer(Context); + var val = mp.ToggleShuffle(); + if(val) + await ReplyConfirmLocalized("songs_shuffle_enable").ConfigureAwait(false); + else + await ReplyConfirmLocalized("songs_shuffle_disable").ConfigureAwait(false); + } - await ReplyConfirmLocalized("skipped_to", minutes, seconds).ConfigureAwait(false); + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Playlist([Remainder] string playlist) + { + if (string.IsNullOrWhiteSpace(playlist)) + return; + + var mp = await _service.GetOrCreatePlayer(Context); + + var plId = (await _google.GetPlaylistIdsByKeywordsAsync(playlist).ConfigureAwait(false)).FirstOrDefault(); + if (plId == null) + { + await ReplyErrorLocalized("no_search_results").ConfigureAwait(false); + return; + } + var ids = await _google.GetPlaylistTracksAsync(plId, 500).ConfigureAwait(false); + if (!ids.Any()) + { + await ReplyErrorLocalized("no_search_results").ConfigureAwait(false); + return; + } + var count = ids.Count(); + var msg = await Context.Channel.SendMessageAsync("🎵 " + GetText("attempting_to_queue", + Format.Bold(count.ToString()))).ConfigureAwait(false); + + foreach (var song in ids) + { + try + { + if (mp.Exited) + return; + + await Task.WhenAll(Task.Delay(150), InternalQueue(mp, await _service.ResolveSong(song, Context.User.ToString(), MusicType.YouTube), true)); + } + catch (SongNotFoundException) { } + catch { break; } + } + + await msg.ModifyAsync(m => m.Content = "✅ " + Format.Bold(GetText("playlist_queue_complete"))).ConfigureAwait(false); + } + + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Radio(string radioLink) + { + var mp = await _service.GetOrCreatePlayer(Context); + var song = await _service.ResolveSong(radioLink, Context.User.ToString(), MusicType.Radio); + await InternalQueue(mp, song, false).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Local([Remainder] string path) + { + var mp = await _service.GetOrCreatePlayer(Context); + var song = await _service.ResolveSong(path, Context.User.ToString(), MusicType.Local); + await InternalQueue(mp, song, false).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task LocalPl([Remainder] string dirPath) + { + if (string.IsNullOrWhiteSpace(dirPath)) + return; + + var mp = await _service.GetOrCreatePlayer(Context); + + DirectoryInfo dir; + try { dir = new DirectoryInfo(dirPath); } catch { return; } + var fileEnum = dir.GetFiles("*", SearchOption.AllDirectories) + .Where(x => !x.Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System) && x.Extension != ".jpg" && x.Extension != ".png"); + foreach (var file in fileEnum) + { + try + { + await Task.Yield(); + var song = await _service.ResolveSong(file.FullName, Context.User.ToString(), MusicType.Local); + await InternalQueue(mp, song, true).ConfigureAwait(false); + } + catch (QueueFullException) + { + break; + } + catch (Exception ex) + { + _log.Warn(ex); + break; + } + } + await ReplyConfirmLocalized("dir_queue_complete").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Move() + { + var vch = ((IGuildUser)Context.User).VoiceChannel; + + if (vch == null) + return; + + var mp = _service.GetPlayerOrDefault(Context.Guild.Id); + + if (mp == null) + return; + + await mp.SetVoiceChannel(vch); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task MoveSong([Remainder] string fromto) + { + if (string.IsNullOrWhiteSpace(fromto)) + return; + + MusicPlayer mp = _service.GetPlayerOrDefault(Context.Guild.Id); + if (mp == null) + return; + + fromto = fromto?.Trim(); + var fromtoArr = fromto.Split('>'); + + SongInfo s; + if (fromtoArr.Length != 2 || !int.TryParse(fromtoArr[0], out var n1) || + !int.TryParse(fromtoArr[1], out var n2) || n1 < 1 || n2 < 1 || n1 == n2 + || (s = mp.MoveSong(--n1, --n2)) == null) + { + await ReplyConfirmLocalized("invalid_input").ConfigureAwait(false); + return; + } + + var embed = new EmbedBuilder() + .WithTitle(s.Title.TrimTo(65)) + .WithUrl(s.SongUrl) + .WithAuthor(eab => eab.WithName(GetText("song_moved")).WithIconUrl("https://cdn.discordapp.com/attachments/155726317222887425/258605269972549642/music1.png")) + .AddField(fb => fb.WithName(GetText("from_position")).WithValue($"#{n1 + 1}").WithIsInline(true)) + .AddField(fb => fb.WithName(GetText("to_position")).WithValue($"#{n2 + 1}").WithIsInline(true)) + .WithColor(NadekoBot.OkColor); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task SetMaxQueue(uint size = 0) + { + if (size < 0) + return; + var mp = await _service.GetOrCreatePlayer(Context); + + mp.MaxQueueSize = size; + + if (size == 0) + await ReplyConfirmLocalized("max_queue_unlimited").ConfigureAwait(false); + else + await ReplyConfirmLocalized("max_queue_x", size).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task SetMaxPlaytime(uint seconds) + { + if (seconds < 15 && seconds != 0) + return; + + var mp = await _service.GetOrCreatePlayer(Context); + mp.MaxPlaytimeSeconds = seconds; + if (seconds == 0) + await ReplyConfirmLocalized("max_playtime_none").ConfigureAwait(false); + else + await ReplyConfirmLocalized("max_playtime_set", seconds).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ReptCurSong() + { + var mp = await _service.GetOrCreatePlayer(Context); + var (_, currentSong) = mp.Current; + if (currentSong == null) + return; + var currentValue = mp.ToggleRepeatSong(); + + if (currentValue) + await Context.Channel.EmbedAsync(new EmbedBuilder() + .WithOkColor() + .WithAuthor(eab => eab.WithMusicIcon().WithName("🔂 " + GetText("repeating_track"))) + .WithDescription(currentSong.PrettyName) + .WithFooter(ef => ef.WithText(currentSong.PrettyInfo))).ConfigureAwait(false); + else + await Context.Channel.SendConfirmAsync("🔂 " + GetText("repeating_track_stopped")) + .ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task RepeatPl() + { + var mp = await _service.GetOrCreatePlayer(Context); + var currentValue = mp.ToggleRepeatPlaylist(); + if (currentValue) + await ReplyConfirmLocalized("rpl_enabled").ConfigureAwait(false); + else + await ReplyConfirmLocalized("rpl_disabled").ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] public async Task Autoplay() { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - return; + var mp = await _service.GetOrCreatePlayer(Context); - if (!musicPlayer.ToggleAutoplay()) + if (!mp.ToggleAutoplay()) await ReplyConfirmLocalized("autoplay_disabled").ConfigureAwait(false); else await ReplyConfirmLocalized("autoplay_enabled").ConfigureAwait(false); @@ -855,17 +898,11 @@ namespace NadekoBot.Modules.Music [RequireUserPermission(GuildPermission.ManageMessages)] public async Task SetMusicChannel() { - MusicPlayer musicPlayer; - if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - { - await ReplyErrorLocalized("no_player").ConfigureAwait(false); - return; - } + var mp = await _service.GetOrCreatePlayer(Context); - musicPlayer.OutputTextChannel = (ITextChannel)Context.Channel; + mp.OutputTextChannel = (ITextChannel)Context.Channel; await ReplyConfirmLocalized("set_music_channel").ConfigureAwait(false); } - } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Services/MusicService.cs b/src/NadekoBot/Modules/Music/Services/MusicService.cs new file mode 100644 index 00000000..d0b00657 --- /dev/null +++ b/src/NadekoBot/Modules/Music/Services/MusicService.cs @@ -0,0 +1,226 @@ +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using NadekoBot.Extensions; +using NadekoBot.Services.Database.Models; +using NLog; +using System.IO; +using System.Collections.Generic; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Common; +using NadekoBot.Services.Impl; +using NadekoBot.Services; +using NadekoBot.Modules.Music.Common; +using NadekoBot.Modules.Music.Common.Exceptions; +using NadekoBot.Modules.Music.Common.SongResolver; + +namespace NadekoBot.Modules.Music.Services +{ + public class MusicService : INService + { + public const string MusicDataPath = "data/musicdata"; + + private readonly IGoogleApiService _google; + private readonly NadekoStrings _strings; + private readonly ILocalization _localization; + private readonly DbService _db; + private readonly Logger _log; + private readonly SoundCloudApiService _sc; + private readonly IBotCredentials _creds; + private readonly ConcurrentDictionary _defaultVolumes; + private readonly DiscordSocketClient _client; + + public ConcurrentDictionary MusicPlayers { get; } = new ConcurrentDictionary(); + + public MusicService(DiscordSocketClient client, IGoogleApiService google, + NadekoStrings strings, ILocalization localization, DbService db, + SoundCloudApiService sc, IBotCredentials creds, IEnumerable gcs) + { + _client = client; + _google = google; + _strings = strings; + _localization = localization; + _db = db; + _sc = sc; + _creds = creds; + _log = LogManager.GetCurrentClassLogger(); + + try { Directory.Delete(MusicDataPath, true); } catch { } + + _defaultVolumes = new ConcurrentDictionary(gcs.ToDictionary(x => x.GuildId, x => x.DefaultMusicVolume)); + + Directory.CreateDirectory(MusicDataPath); + + //_t = new Timer(_ => _log.Info(MusicPlayers.Count(x => x.Value.Current.Current != null)), null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5)); + } + + public float GetDefaultVolume(ulong guildId) + { + return _defaultVolumes.GetOrAdd(guildId, (id) => + { + using (var uow = _db.UnitOfWork) + { + return uow.GuildConfigs.For(guildId, set => set).DefaultMusicVolume; + } + }); + } + + public Task GetOrCreatePlayer(ICommandContext context) + { + var gUsr = (IGuildUser)context.User; + var txtCh = (ITextChannel)context.Channel; + var vCh = gUsr.VoiceChannel; + return GetOrCreatePlayer(context.Guild.Id, vCh, txtCh); + } + + public async Task GetOrCreatePlayer(ulong guildId, IVoiceChannel voiceCh, ITextChannel textCh) + { + string GetText(string text, params object[] replacements) => + _strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements); + + _log.Info("Checks"); + if (voiceCh == null || voiceCh.Guild != textCh.Guild) + { + if (textCh != null) + { + await textCh.SendErrorAsync(GetText("must_be_in_voice")).ConfigureAwait(false); + } + throw new NotInVoiceChannelException(); + } + _log.Info("Get or add"); + return MusicPlayers.GetOrAdd(guildId, _ => + { + _log.Info("Getting default volume"); + var vol = GetDefaultVolume(guildId); + _log.Info("Creating musicplayer instance"); + var mp = new MusicPlayer(this, _google, voiceCh, textCh, vol); + + IUserMessage playingMessage = null; + IUserMessage lastFinishedMessage = null; + + _log.Info("Subscribing"); + mp.OnCompleted += async (s, song) => + { + try + { + lastFinishedMessage?.DeleteAfter(0); + + try + { + lastFinishedMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor() + .WithAuthor(eab => eab.WithName(GetText("finished_song")).WithMusicIcon()) + .WithDescription(song.PrettyName) + .WithFooter(ef => ef.WithText(song.PrettyInfo))) + .ConfigureAwait(false); + } + catch + { + // ignored + } + } + catch + { + // ignored + } + }; + mp.OnStarted += async (player, song) => + { + //try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); } + //catch + //{ + // // ignored + //} + var sender = player; + if (sender == null) + return; + try + { + playingMessage?.DeleteAfter(0); + + playingMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor() + .WithAuthor(eab => eab.WithName(GetText("playing_song", song.Index + 1)).WithMusicIcon()) + .WithDescription(song.Song.PrettyName) + .WithFooter(ef => ef.WithText(mp.PrettyVolume + " | " + song.Song.PrettyInfo))) + .ConfigureAwait(false); + } + catch + { + // ignored + } + }; + mp.OnPauseChanged += async (player, paused) => + { + try + { + IUserMessage msg; + if (paused) + msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("paused")).ConfigureAwait(false); + else + msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("resumed")).ConfigureAwait(false); + + msg?.DeleteAfter(10); + } + catch + { + // ignored + } + }; + _log.Info("Done creating"); + return mp; + }); + } + + public MusicPlayer GetPlayerOrDefault(ulong guildId) + { + if (MusicPlayers.TryGetValue(guildId, out var mp)) + return mp; + else + return null; + } + + public async Task TryQueueRelatedSongAsync(SongInfo song, ITextChannel txtCh, IVoiceChannel vch) + { + var related = (await _google.GetRelatedVideosAsync(song.VideoId, 4)).ToArray(); + if (!related.Any()) + return; + + var si = await ResolveSong(related[new NadekoRandom().Next(related.Length)], _client.CurrentUser.ToString(), MusicType.YouTube); + if (si == null) + throw new SongNotFoundException(); + var mp = await GetOrCreatePlayer(txtCh.GuildId, vch, txtCh); + mp.Enqueue(si); + } + + public async Task ResolveSong(string query, string queuerName, MusicType? musicType = null) + { + query.ThrowIfNull(nameof(query)); + + ISongResolverFactory resolverFactory = new SongResolverFactory(_sc); + var strategy = await resolverFactory.GetResolveStrategy(query, musicType); + var sinfo = await strategy.ResolveSong(query); + + if (sinfo == null) + return null; + + sinfo.QueuerName = queuerName; + + return sinfo; + } + + public async Task DestroyAllPlayers() + { + foreach (var key in MusicPlayers.Keys) + { + await DestroyPlayer(key); + } + } + + public async Task DestroyPlayer(ulong id) + { + if (MusicPlayers.TryRemove(id, out var mp)) + await mp.Destroy(); + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/NSFW/Exceptions/TagBlacklistedException.cs b/src/NadekoBot/Modules/NSFW/Exceptions/TagBlacklistedException.cs new file mode 100644 index 00000000..c0f881f3 --- /dev/null +++ b/src/NadekoBot/Modules/NSFW/Exceptions/TagBlacklistedException.cs @@ -0,0 +1,12 @@ +using System; + +namespace NadekoBot.Modules.NSFW.Exceptions +{ + public class TagBlacklistedException : Exception + { + public TagBlacklistedException() : base("Tag you used is blacklisted.") + { + + } + } +} diff --git a/src/NadekoBot/Modules/NSFW/NSFW.cs b/src/NadekoBot/Modules/NSFW/NSFW.cs index d09467dd..1ca27691 100644 --- a/src/NadekoBot/Modules/NSFW/NSFW.cs +++ b/src/NadekoBot/Modules/NSFW/NSFW.cs @@ -1,56 +1,47 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using Newtonsoft.Json.Linq; using System; using System.Linq; using System.Threading.Tasks; -using NadekoBot.Services; -using System.Net.Http; using NadekoBot.Extensions; -using System.Xml; using System.Threading; using System.Collections.Concurrent; -using NadekoBot.Services.Searches; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.Collections; +using NadekoBot.Modules.Searches.Common; +using NadekoBot.Modules.Searches.Services; +using NadekoBot.Modules.NSFW.Exceptions; namespace NadekoBot.Modules.NSFW { - public class NSFW : NadekoTopLevelModule + // thanks to halitalf for adding autoboob and autobutt features :D + public class NSFW : NadekoTopLevelModule { private static readonly ConcurrentDictionary _autoHentaiTimers = new ConcurrentDictionary(); - private static readonly ConcurrentHashSet _hentaiBombBlacklist = new ConcurrentHashSet(); - private readonly SearchesService _service; + private static readonly ConcurrentDictionary _autoBoobTimers = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary _autoButtTimers = new ConcurrentDictionary(); - public NSFW(SearchesService service) - { - _service = service; - } + private static readonly ConcurrentHashSet _hentaiBombBlacklist = new ConcurrentHashSet(); private async Task InternalHentai(IMessageChannel channel, string tag, bool noError) { - tag = tag?.Trim() ?? ""; - - tag = "rating%3Aexplicit+" + tag; - var rng = new NadekoRandom(); - var provider = Task.FromResult(""); - switch (rng.Next(0, 4)) + var arr = Enum.GetValues(typeof(DapiSearchType)); + var type = (DapiSearchType)arr.GetValue(new NadekoRandom().Next(2, arr.Length)); + ImageCacherObject img; + try { - case 0: - provider = GetDanbooruImageLink(tag); - break; - case 1: - provider = GetGelbooruImageLink(tag); - break; - case 2: - provider = GetKonachanImageLink(tag); - break; - case 3: - provider = GetYandereImageLink(tag); - break; + img = await _service.DapiSearch(tag, type, Context.Guild?.Id, true).ConfigureAwait(false); } - var link = await provider.ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(link)) + catch (TagBlacklistedException) + { + await ReplyErrorLocalized("blacklisted_tag").ConfigureAwait(false); + return; + } + + if (img == null) { if (!noError) await ReplyErrorLocalized("not_found").ConfigureAwait(false); @@ -58,14 +49,38 @@ namespace NadekoBot.Modules.NSFW } await channel.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithImageUrl(link) - .WithDescription($"[{GetText("tag")}: {tag}]({link})")) + .WithImageUrl(img.FileUrl) + .WithDescription($"[{GetText("tag")}: {tag}]({img})")) .ConfigureAwait(false); } + private async Task InternalBoobs(IMessageChannel Channel) + { + try + { + JToken obj; + obj = JArray.Parse(await _service.Http.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 10330)}").ConfigureAwait(false))[0]; + await Channel.SendMessageAsync($"http://media.oboobs.ru/{obj["preview"]}").ConfigureAwait(false); + } + catch (Exception ex) + { + await Channel.SendErrorAsync(ex.Message).ConfigureAwait(false); + } + } + + private async Task InternalButts(IMessageChannel Channel) + { + try + { + JToken obj; + obj = JArray.Parse(await _service.Http.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 4335)}").ConfigureAwait(false))[0]; + await Channel.SendMessageAsync($"http://media.obutts.ru/{obj["preview"]}").ConfigureAwait(false); + } + catch (Exception ex) + { + await Channel.SendErrorAsync(ex.Message).ConfigureAwait(false); + } + } - [NadekoCommand, Usage, Description, Aliases] - public Task Hentai([Remainder] string tag = null) => - InternalHentai(Context.Channel, tag, false); #if !GLOBAL_NADEKO [NadekoCommand, Usage, Description, Aliases] [RequireUserPermission(ChannelPermission.ManageMessages)] @@ -78,7 +93,7 @@ namespace NadekoBot.Modules.NSFW if (!_autoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return; t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer - await ReplyConfirmLocalized("autohentai_stopped").ConfigureAwait(false); + await ReplyConfirmLocalized("stopped").ConfigureAwait(false); return; } @@ -108,114 +123,144 @@ namespace NadekoBot.Modules.NSFW return t; }); - await ReplyConfirmLocalized("autohentai_started", - interval, + await ReplyConfirmLocalized("autohentai_started", + interval, string.Join(", ", tagsArr)).ConfigureAwait(false); } + [NadekoCommand, Usage, Description, Aliases] + [RequireUserPermission(ChannelPermission.ManageMessages)] + public async Task AutoBoobs(int interval = 0) + { + Timer t; + + if (interval == 0) + { + if (!_autoBoobTimers.TryRemove(Context.Channel.Id, out t)) return; + + t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer + await ReplyConfirmLocalized("stopped").ConfigureAwait(false); + return; + } + + if (interval < 20) + return; + + t = new Timer(async (state) => + { + try + { + await InternalBoobs(Context.Channel).ConfigureAwait(false); + } + catch + { + // ignored + } + }, null, interval * 1000, interval * 1000); + + _autoBoobTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) => + { + old.Change(Timeout.Infinite, Timeout.Infinite); + return t; + }); + + await ReplyConfirmLocalized("started", interval).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireUserPermission(ChannelPermission.ManageMessages)] + public async Task AutoButts(int interval = 0) + { + Timer t; + + if (interval == 0) + { + if (!_autoButtTimers.TryRemove(Context.Channel.Id, out t)) return; + + t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer + await ReplyConfirmLocalized("stopped").ConfigureAwait(false); + return; + } + + if (interval < 20) + return; + + t = new Timer(async (state) => + { + try + { + await InternalButts(Context.Channel).ConfigureAwait(false); + } + catch + { + // ignored + } + }, null, interval * 1000, interval * 1000); + + _autoButtTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) => + { + old.Change(Timeout.Infinite, Timeout.Infinite); + return t; + }); + + await ReplyConfirmLocalized("started", interval).ConfigureAwait(false); + } +#endif + + [NadekoCommand, Usage, Description, Aliases] + public Task Hentai([Remainder] string tag = null) => + InternalHentai(Context.Channel, tag, false); [NadekoCommand, Usage, Description, Aliases] public async Task HentaiBomb([Remainder] string tag = null) { - if (!_hentaiBombBlacklist.Add(Context.User.Id)) + if (!_hentaiBombBlacklist.Add(Context.Guild?.Id ?? Context.User.Id)) return; try { - tag = tag?.Trim() ?? ""; - tag = "rating%3Aexplicit+" + tag; + var images = await Task.WhenAll(_service.DapiSearch(tag, DapiSearchType.Gelbooru, Context.Guild?.Id, true), + _service.DapiSearch(tag, DapiSearchType.Danbooru, Context.Guild?.Id, true), + _service.DapiSearch(tag, DapiSearchType.Konachan, Context.Guild?.Id, true), + _service.DapiSearch(tag, DapiSearchType.Yandere, Context.Guild?.Id, true)).ConfigureAwait(false); - var links = await Task.WhenAll(GetGelbooruImageLink(tag), - GetDanbooruImageLink(tag), - GetKonachanImageLink(tag), - GetYandereImageLink(tag)).ConfigureAwait(false); - - var linksEnum = links?.Where(l => l != null).ToArray(); - if (links == null || !linksEnum.Any()) + var linksEnum = images?.Where(l => l != null).ToArray(); + if (images == null || !linksEnum.Any()) { await ReplyErrorLocalized("not_found").ConfigureAwait(false); return; } - await Context.Channel.SendMessageAsync(string.Join("\n\n", linksEnum)).ConfigureAwait(false); + await Context.Channel.SendMessageAsync(string.Join("\n\n", linksEnum.Select(x => x.FileUrl))).ConfigureAwait(false); } finally { - await Task.Delay(5000).ConfigureAwait(false); - _hentaiBombBlacklist.TryRemove(Context.User.Id); + _hentaiBombBlacklist.TryRemove(Context.Guild?.Id ?? Context.User.Id); } } -#endif + [NadekoCommand, Usage, Description, Aliases] public Task Yandere([Remainder] string tag = null) - => InternalDapiCommand(tag, DapiSearchType.Yandere); + => InternalDapiCommand(tag, DapiSearchType.Yandere, false); [NadekoCommand, Usage, Description, Aliases] public Task Konachan([Remainder] string tag = null) - => InternalDapiCommand(tag, DapiSearchType.Konachan); + => InternalDapiCommand(tag, DapiSearchType.Konachan, false); [NadekoCommand, Usage, Description, Aliases] - public async Task E621([Remainder] string tag = null) - { - tag = tag?.Trim() ?? ""; - - var url = await GetE621ImageLink(tag).ConfigureAwait(false); - - if (url == null) - await ReplyErrorLocalized("not_found").ConfigureAwait(false); - else - await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithDescription(Context.User.Mention + " " + tag) - .WithImageUrl(url) - .WithFooter(efb => efb.WithText("e621"))) - .ConfigureAwait(false); - } + public Task E621([Remainder] string tag = null) + => InternalDapiCommand(tag, DapiSearchType.E621, false); [NadekoCommand, Usage, Description, Aliases] public Task Rule34([Remainder] string tag = null) - => InternalDapiCommand(tag, DapiSearchType.Rule34); + => InternalDapiCommand(tag, DapiSearchType.Rule34, false); [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 ReplyErrorLocalized("not_found").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 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 Task Danbooru([Remainder] string tag = null) + => InternalDapiCommand(tag, DapiSearchType.Danbooru, false); [NadekoCommand, Usage, Description, Aliases] public Task Gelbooru([Remainder] string tag = null) - => InternalDapiCommand(tag, DapiSearchType.Gelbooru); + => InternalDapiCommand(tag, DapiSearchType.Gelbooru, false); [NadekoCommand, Usage, Description, Aliases] public async Task Boobs() @@ -223,10 +268,7 @@ namespace NadekoBot.Modules.NSFW try { JToken obj; - using (var http = new HttpClient()) - { - obj = JArray.Parse(await http.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 10330)}").ConfigureAwait(false))[0]; - } + obj = JArray.Parse(await _service.Http.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 10330)}").ConfigureAwait(false))[0]; await Context.Channel.SendMessageAsync($"http://media.oboobs.ru/{obj["preview"]}").ConfigureAwait(false); } catch (Exception ex) @@ -241,10 +283,7 @@ namespace NadekoBot.Modules.NSFW try { JToken obj; - using (var http = new HttpClient()) - { - obj = JArray.Parse(await http.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 4335)}").ConfigureAwait(false))[0]; - } + obj = JArray.Parse(await _service.Http.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 4335)}").ConfigureAwait(false))[0]; await Context.Channel.SendMessageAsync($"http://media.obutts.ru/{obj["preview"]}").ConfigureAwait(false); } catch (Exception ex) @@ -253,53 +292,67 @@ namespace NadekoBot.Modules.NSFW } } - public static Task GetE621ImageLink(string tag) => Task.Run(async () => + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task NsfwTagBlacklist([Remainder] string tag = null) { + if (string.IsNullOrWhiteSpace(tag)) + { + var blTags = _service.GetBlacklistedTags(Context.Guild.Id); + await Context.Channel.SendConfirmAsync(GetText("blacklisted_tag_list"), + blTags.Any() + ? string.Join(", ", blTags) + : "-").ConfigureAwait(false); + } + else + { + tag = tag.Trim().ToLowerInvariant(); + var added = _service.ToggleBlacklistedTag(Context.Guild.Id, tag); + + if (added) + await ReplyConfirmLocalized("blacklisted_tag_add", tag).ConfigureAwait(false); + else + await ReplyConfirmLocalized("blacklisted_tag_remove", tag).ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public Task NsfwClearCache() + { + _service.ClearCache(); + return Context.Channel.SendConfirmAsync("👌"); + } + + public async Task InternalDapiCommand(string tag, DapiSearchType type, bool forceExplicit) + { + ImageCacherObject imgObj; try { - using (var http = new HttpClient()) - { - http.AddFakeHeaders(); - var data = await http.GetStreamAsync("http://e621.net/post/index.xml?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 node.InnerText; - } + imgObj = await _service.DapiSearch(tag, type, Context.Guild?.Id, forceExplicit).ConfigureAwait(false); } - catch + catch (TagBlacklistedException) { - return null; + await ReplyErrorLocalized("blacklisted_tag").ConfigureAwait(false); + return; } - }); - public Task GetRule34ImageLink(string tag) => - _service.DapiSearch(tag, DapiSearchType.Rule34); - - public Task GetYandereImageLink(string tag) => - _service.DapiSearch(tag, DapiSearchType.Yandere); - - public Task GetKonachanImageLink(string tag) => - _service.DapiSearch(tag, DapiSearchType.Konachan); - - public Task GetGelbooruImageLink(string tag) => - _service.DapiSearch(tag, DapiSearchType.Gelbooru); - - public async Task InternalDapiCommand(string tag, DapiSearchType type) - { - tag = tag?.Trim() ?? ""; - - var url = await _service.DapiSearch(tag, type).ConfigureAwait(false); - - if (url == null) + if (imgObj == null) await ReplyErrorLocalized("not_found").ConfigureAwait(false); else - await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithDescription($"{Context.User} [{tag}]({url}) ") - .WithImageUrl(url) - .WithFooter(efb => efb.WithText(type.ToString()))).ConfigureAwait(false); + { + var embed = new EmbedBuilder().WithOkColor() + .WithDescription($"{Context.User} [{tag ?? "url"}]({imgObj}) ") + .WithFooter(efb => efb.WithText(type.ToString())); + + if (Uri.IsWellFormedUriString(imgObj.FileUrl, UriKind.Absolute)) + embed.WithImageUrl(imgObj.FileUrl); + else + _log.Error($"Image link from {type} is not a proper Url: {imgObj.FileUrl}"); + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } } } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/NadekoModule.cs b/src/NadekoBot/Modules/NadekoModule.cs index dae471d4..f6f06afd 100644 --- a/src/NadekoBot/Modules/NadekoModule.cs +++ b/src/NadekoBot/Modules/NadekoModule.cs @@ -5,8 +5,8 @@ using NadekoBot.Services; using NLog; using System.Globalization; using System.Threading.Tasks; -using System; using Discord.WebSocket; +using NadekoBot.Services.Impl; namespace NadekoBot.Modules { @@ -32,7 +32,7 @@ namespace NadekoBot.Modules _log = LogManager.GetCurrentClassLogger(); } - protected override void BeforeExecute() + protected override void BeforeExecute(CommandInfo cmd) { _cultureInfo = _localization.GetCultureInfo(Context.Guild?.Id); } @@ -86,13 +86,12 @@ namespace NadekoBot.Modules var text = GetText(textKey, replacements); return Context.Channel.SendConfirmAsync(Context.User.Mention + " " + text); } - - // todo maybe make this generic and use - // TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); + + // TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); ? public async Task GetUserInputAsync(ulong userId, ulong channelId) { var userInputTask = new TaskCompletionSource(); - var dsc = (DiscordShardedClient)Context.Client; + var dsc = (DiscordSocketClient)Context.Client; try { dsc.MessageReceived += MessageReceived; @@ -131,8 +130,22 @@ namespace NadekoBot.Modules } } } + + public abstract class NadekoTopLevelModule : NadekoTopLevelModule where TService : INService + { + public TService _service { get; set; } + + public NadekoTopLevelModule(bool isTopLevel = true) : base(isTopLevel) + { + } + } public abstract class NadekoSubmodule : NadekoTopLevelModule + { + protected NadekoSubmodule() : base(false) { } + } + + public abstract class NadekoSubmodule : NadekoTopLevelModule where TService : INService { protected NadekoSubmodule() : base(false) { diff --git a/src/NadekoBot/Modules/Permissions/Commands/BlacklistCommands.cs b/src/NadekoBot/Modules/Permissions/BlacklistCommands.cs similarity index 96% rename from src/NadekoBot/Modules/Permissions/Commands/BlacklistCommands.cs rename to src/NadekoBot/Modules/Permissions/BlacklistCommands.cs index 4b63c967..377631e0 100644 --- a/src/NadekoBot/Modules/Permissions/Commands/BlacklistCommands.cs +++ b/src/NadekoBot/Modules/Permissions/BlacklistCommands.cs @@ -1,24 +1,19 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; -using NadekoBot.Modules.Games.Trivia; using NadekoBot.Services; using NadekoBot.Services.Database.Models; -using NadekoBot.Services.Permissions; -using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.Collections; +using NadekoBot.Modules.Games.Common.Trivia; +using NadekoBot.Modules.Permissions.Services; +using NadekoBot.Common.TypeReaders; namespace NadekoBot.Modules.Permissions { public partial class Permissions { - public enum AddRemove - { - Add, - Rem - } - [Group] public class BlacklistCommands : NadekoSubmodule { diff --git a/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs b/src/NadekoBot/Modules/Permissions/CmdCdsCommands.cs similarity index 97% rename from src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs rename to src/NadekoBot/Modules/Permissions/CmdCdsCommands.cs index 817d7c54..5c3fed31 100644 --- a/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs +++ b/src/NadekoBot/Modules/Permissions/CmdCdsCommands.cs @@ -1,14 +1,15 @@ using Discord; using Discord.Commands; using Microsoft.EntityFrameworkCore; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using NadekoBot.Services.Database.Models; -using NadekoBot.Services.Permissions; using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.Collections; +using NadekoBot.Modules.Permissions.Services; namespace NadekoBot.Modules.Permissions { diff --git a/src/NadekoBot/Modules/Permissions/Commands/CommandCostCommands.cs b/src/NadekoBot/Modules/Permissions/CommandCostCommands.cs similarity index 96% rename from src/NadekoBot/Modules/Permissions/Commands/CommandCostCommands.cs rename to src/NadekoBot/Modules/Permissions/CommandCostCommands.cs index 13651ca3..ed31eb9e 100644 --- a/src/NadekoBot/Modules/Permissions/Commands/CommandCostCommands.cs +++ b/src/NadekoBot/Modules/Permissions/CommandCostCommands.cs @@ -74,8 +74,7 @@ namespace NadekoBot.Modules.Permissions // else // { // bc.CommandCosts.RemoveAt(bc.CommandCosts.IndexOf(cmdPrice)); - // int throwaway; - // _commandCosts.TryRemove(cmdName, out throwaway); + // _commandCosts.TryRemove(cmdName, out _); // } // await uow.CompleteAsync().ConfigureAwait(false); diff --git a/src/NadekoBot/Modules/Permissions/Commands/ResetPermissionsCommands.cs b/src/NadekoBot/Modules/Permissions/Commands/ResetPermissionsCommands.cs deleted file mode 100644 index 049b4782..00000000 --- a/src/NadekoBot/Modules/Permissions/Commands/ResetPermissionsCommands.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Attributes; -using NadekoBot.Services; -using NadekoBot.Services.Database.Models; -using NadekoBot.Services.Permissions; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Permissions -{ - public partial class Permissions - { - [Group] - public class ResetPermissionsCommands : NadekoSubmodule - { - private readonly PermissionService _service; - private readonly DbService _db; - private readonly GlobalPermissionService _globalPerms; - - public ResetPermissionsCommands(PermissionService service, GlobalPermissionService globalPerms, DbService db) - { - _service = service; - _db = db; - _globalPerms = globalPerms; - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [RequireUserPermission(GuildPermission.Administrator)] - public async Task ResetPermissions() - { - //todo 50 move to service - using (var uow = _db.UnitOfWork) - { - var config = uow.GuildConfigs.GcWithPermissionsv2For(Context.Guild.Id); - config.Permissions = Permissionv2.GetDefaultPermlist; - await uow.CompleteAsync(); - _service.UpdateCache(config); - } - await ReplyConfirmLocalized("perms_reset").ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [OwnerOnly] - public async Task ResetGlobalPermissions() - { - //todo 50 move to service - using (var uow = _db.UnitOfWork) - { - var gc = uow.BotConfig.GetOrCreate(); - gc.BlockedCommands.Clear(); - gc.BlockedModules.Clear(); - - _globalPerms.BlockedCommands.Clear(); - _globalPerms.BlockedModules.Clear(); - await uow.CompleteAsync(); - } - await ReplyConfirmLocalized("global_perms_reset").ConfigureAwait(false); - } - } - } -} diff --git a/src/NadekoBot/Services/Permissions/PermissionCache.cs b/src/NadekoBot/Modules/Permissions/Common/PermissionCache.cs similarity index 90% rename from src/NadekoBot/Services/Permissions/PermissionCache.cs rename to src/NadekoBot/Modules/Permissions/Common/PermissionCache.cs index 5b4dc4b7..fd02a0b3 100644 --- a/src/NadekoBot/Services/Permissions/PermissionCache.cs +++ b/src/NadekoBot/Modules/Permissions/Common/PermissionCache.cs @@ -1,6 +1,6 @@ using NadekoBot.Services.Database.Models; -namespace NadekoBot.Services.Permissions +namespace NadekoBot.Modules.Permissions.Common { public class OldPermissionCache { diff --git a/src/NadekoBot/Services/Permissions/PermissionExtensions.cs b/src/NadekoBot/Modules/Permissions/Common/PermissionExtensions.cs similarity index 97% rename from src/NadekoBot/Services/Permissions/PermissionExtensions.cs rename to src/NadekoBot/Modules/Permissions/Common/PermissionExtensions.cs index 960e7c32..977a319a 100644 --- a/src/NadekoBot/Services/Permissions/PermissionExtensions.cs +++ b/src/NadekoBot/Modules/Permissions/Common/PermissionExtensions.cs @@ -1,10 +1,10 @@ -using Discord; +using System.Collections.Generic; +using System.Linq; +using Discord; using Discord.WebSocket; using NadekoBot.Services.Database.Models; -using System.Collections.Generic; -using System.Linq; -namespace NadekoBot.Services.Permissions +namespace NadekoBot.Modules.Permissions.Common { public static class PermissionExtensions { diff --git a/src/NadekoBot/Services/Permissions/PermissionsCollection.cs b/src/NadekoBot/Modules/Permissions/Common/PermissionsCollection.cs similarity index 93% rename from src/NadekoBot/Services/Permissions/PermissionsCollection.cs rename to src/NadekoBot/Modules/Permissions/Common/PermissionsCollection.cs index 5d37155d..0f6ab6b9 100644 --- a/src/NadekoBot/Services/Permissions/PermissionsCollection.cs +++ b/src/NadekoBot/Modules/Permissions/Common/PermissionsCollection.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; +using NadekoBot.Common.Collections; using NadekoBot.Services.Database.Models; -using NadekoBot.DataStructures; -namespace NadekoBot.Services.Permissions +namespace NadekoBot.Modules.Permissions.Common { - public class PermissionsCollection : IndexedCollection where T : IIndexed + public class PermissionsCollection : IndexedCollection where T : class, IIndexed { private readonly object _localLocker = new object(); public PermissionsCollection(IEnumerable source) : base(source) @@ -59,7 +59,7 @@ namespace NadekoBot.Services.Permissions } public override T this[int index] { - get { return Source[index]; } + get => Source[index]; set { lock (_localLocker) { diff --git a/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs b/src/NadekoBot/Modules/Permissions/FilterCommands.cs similarity index 94% rename from src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs rename to src/NadekoBot/Modules/Permissions/FilterCommands.cs index 371678ff..22e22c82 100644 --- a/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs +++ b/src/NadekoBot/Modules/Permissions/FilterCommands.cs @@ -2,13 +2,14 @@ using Discord.Commands; using Discord.WebSocket; using Microsoft.EntityFrameworkCore; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; -using NadekoBot.Services.Permissions; -using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.Collections; +using NadekoBot.Modules.Permissions.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Permissions { @@ -65,7 +66,7 @@ namespace NadekoBot.Modules.Permissions removed = config.FilterInvitesChannelIds.RemoveWhere(fc => fc.ChannelId == channel.Id); if (removed == 0) { - config.FilterInvitesChannelIds.Add(new Services.Database.Models.FilterChannelId() + config.FilterInvitesChannelIds.Add(new FilterChannelId() { ChannelId = channel.Id }); @@ -123,7 +124,7 @@ namespace NadekoBot.Modules.Permissions removed = config.FilterWordsChannelIds.RemoveWhere(fc => fc.ChannelId == channel.Id); if (removed == 0) { - config.FilterWordsChannelIds.Add(new Services.Database.Models.FilterChannelId() + config.FilterWordsChannelIds.Add(new FilterChannelId() { ChannelId = channel.Id }); @@ -162,7 +163,7 @@ namespace NadekoBot.Modules.Permissions removed = config.FilteredWords.RemoveWhere(fw => fw.Word.Trim().ToLowerInvariant() == word); if (removed == 0) - config.FilteredWords.Add(new Services.Database.Models.FilteredWord() { Word = word }); + config.FilteredWords.Add(new FilteredWord() { Word = word }); await uow.CompleteAsync().ConfigureAwait(false); } @@ -195,7 +196,7 @@ namespace NadekoBot.Modules.Permissions var fws = fwHash.ToArray(); - await channel.SendPaginatedConfirmAsync((DiscordShardedClient)Context.Client, + await channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client, page, (curPage) => new EmbedBuilder() diff --git a/src/NadekoBot/Modules/Permissions/Commands/GlobalPermissionCommands.cs b/src/NadekoBot/Modules/Permissions/GlobalPermissionCommands.cs similarity index 93% rename from src/NadekoBot/Modules/Permissions/Commands/GlobalPermissionCommands.cs rename to src/NadekoBot/Modules/Permissions/GlobalPermissionCommands.cs index eb60f78f..5c49adf7 100644 --- a/src/NadekoBot/Modules/Permissions/Commands/GlobalPermissionCommands.cs +++ b/src/NadekoBot/Modules/Permissions/GlobalPermissionCommands.cs @@ -1,12 +1,13 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; -using NadekoBot.Services.Permissions; -using NadekoBot.TypeReaders; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.TypeReaders; +using NadekoBot.Modules.Permissions.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Permissions { @@ -55,7 +56,7 @@ namespace NadekoBot.Modules.Permissions using (var uow = _db.UnitOfWork) { var bc = uow.BotConfig.GetOrCreate(); - bc.BlockedModules.Add(new Services.Database.Models.BlockedCmdOrMdl + bc.BlockedModules.Add(new BlockedCmdOrMdl { Name = moduleName, }); @@ -87,7 +88,7 @@ namespace NadekoBot.Modules.Permissions using (var uow = _db.UnitOfWork) { var bc = uow.BotConfig.GetOrCreate(); - bc.BlockedCommands.Add(new Services.Database.Models.BlockedCmdOrMdl + bc.BlockedCommands.Add(new BlockedCmdOrMdl { Name = commandName, }); diff --git a/src/NadekoBot/Modules/Permissions/Permissions.cs b/src/NadekoBot/Modules/Permissions/Permissions.cs index 2fd612a3..ce7f6fa9 100644 --- a/src/NadekoBot/Modules/Permissions/Permissions.cs +++ b/src/NadekoBot/Modules/Permissions/Permissions.cs @@ -1,5 +1,4 @@ -using NadekoBot.Attributes; -using System; +using System; using System.Linq; using System.Threading.Tasks; using Discord.Commands; @@ -8,20 +7,21 @@ using Discord; using NadekoBot.Services.Database.Models; using System.Collections.Generic; using Discord.WebSocket; -using NadekoBot.TypeReaders; -using NadekoBot.Services.Permissions; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.TypeReaders; +using NadekoBot.Common.TypeReaders.Models; +using NadekoBot.Modules.Permissions.Common; +using NadekoBot.Modules.Permissions.Services; namespace NadekoBot.Modules.Permissions { - public partial class Permissions : NadekoTopLevelModule + public partial class Permissions : NadekoTopLevelModule { private readonly DbService _db; - private readonly PermissionService _service; - public Permissions(PermissionService service, DbService db) + public Permissions(DbService db) { _db = db; - _service = service; } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Modules/Permissions/ResetPermissionsCommands.cs b/src/NadekoBot/Modules/Permissions/ResetPermissionsCommands.cs new file mode 100644 index 00000000..91e4a9c2 --- /dev/null +++ b/src/NadekoBot/Modules/Permissions/ResetPermissionsCommands.cs @@ -0,0 +1,39 @@ +using Discord; +using Discord.Commands; +using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Permissions.Services; + +namespace NadekoBot.Modules.Permissions +{ + public partial class Permissions + { + [Group] + public class ResetPermissionsCommands : NadekoSubmodule + { + private readonly ResetPermissionsService _service; + + public ResetPermissionsCommands(ResetPermissionsService service) + { + _service = service; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + public async Task ResetPermissions() + { + await _service.ResetPermissions(Context.Guild.Id).ConfigureAwait(false); + await ReplyConfirmLocalized("perms_reset").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public async Task ResetGlobalPermissions() + { + await _service.ResetGlobalPermissions().ConfigureAwait(false); + await ReplyConfirmLocalized("global_perms_reset").ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Services/Permissions/BlacklistService.cs b/src/NadekoBot/Modules/Permissions/Services/BlacklistService.cs similarity index 75% rename from src/NadekoBot/Services/Permissions/BlacklistService.cs rename to src/NadekoBot/Modules/Permissions/Services/BlacklistService.cs index 4f7a691d..3c2128dc 100644 --- a/src/NadekoBot/Services/Permissions/BlacklistService.cs +++ b/src/NadekoBot/Modules/Permissions/Services/BlacklistService.cs @@ -1,21 +1,22 @@ -using NadekoBot.DataStructures.ModuleBehaviors; -using NadekoBot.Services.Database.Models; -using System.Collections.Concurrent; -using System.Linq; -using Discord; +using System.Linq; using System.Threading.Tasks; +using Discord; +using NadekoBot.Common.Collections; +using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; -namespace NadekoBot.Services.Permissions +namespace NadekoBot.Modules.Permissions.Services { - public class BlacklistService : IEarlyBlocker + public class BlacklistService : IEarlyBlocker, INService { public ConcurrentHashSet BlacklistedUsers { get; } public ConcurrentHashSet BlacklistedGuilds { get; } public ConcurrentHashSet BlacklistedChannels { get; } - public BlacklistService(BotConfig bc) + public BlacklistService(IBotConfigProvider bc) { - var blacklist = bc.Blacklist; + var blacklist = bc.BotConfig.Blacklist; BlacklistedUsers = new ConcurrentHashSet(blacklist.Where(bi => bi.Type == BlacklistType.User).Select(c => c.ItemId)); BlacklistedGuilds = new ConcurrentHashSet(blacklist.Where(bi => bi.Type == BlacklistType.Server).Select(c => c.ItemId)); BlacklistedChannels = new ConcurrentHashSet(blacklist.Where(bi => bi.Type == BlacklistType.Channel).Select(c => c.ItemId)); diff --git a/src/NadekoBot/Services/Permissions/CmdCdService.cs b/src/NadekoBot/Modules/Permissions/Services/CmdCdService.cs similarity index 87% rename from src/NadekoBot/Services/Permissions/CmdCdService.cs rename to src/NadekoBot/Modules/Permissions/Services/CmdCdService.cs index 0a1cbf47..bead8238 100644 --- a/src/NadekoBot/Services/Permissions/CmdCdService.cs +++ b/src/NadekoBot/Modules/Permissions/Services/CmdCdService.cs @@ -1,15 +1,17 @@ -using NadekoBot.DataStructures.ModuleBehaviors; -using NadekoBot.Services.Database.Models; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Discord; using Discord.WebSocket; -using System.Threading.Tasks; +using NadekoBot.Common.Collections; +using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; -namespace NadekoBot.Services.Permissions +namespace NadekoBot.Modules.Permissions.Services { - public class CmdCdService : ILateBlocker + public class CmdCdService : ILateBlocker, INService { public ConcurrentDictionary> CommandCooldowns { get; } public ConcurrentDictionary> ActiveCooldowns { get; } = new ConcurrentDictionary>(); @@ -21,7 +23,7 @@ namespace NadekoBot.Services.Permissions v => new ConcurrentHashSet(v.CommandCooldowns))); } - public Task TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild, + public Task TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName) { if (guild == null) diff --git a/src/NadekoBot/Services/Permissions/FilterService.cs b/src/NadekoBot/Modules/Permissions/Services/FilterService.cs similarity index 83% rename from src/NadekoBot/Services/Permissions/FilterService.cs rename to src/NadekoBot/Modules/Permissions/Services/FilterService.cs index 2dbd375d..e15926a9 100644 --- a/src/NadekoBot/Services/Permissions/FilterService.cs +++ b/src/NadekoBot/Modules/Permissions/Services/FilterService.cs @@ -1,18 +1,20 @@ -using NadekoBot.DataStructures.ModuleBehaviors; -using NadekoBot.Services.Database.Models; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using Discord; -using Discord.WebSocket; using System.Threading.Tasks; -using NadekoBot.Extensions; +using Discord; using Discord.Net; +using Discord.WebSocket; +using NadekoBot.Common.Collections; +using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; using NLog; -namespace NadekoBot.Services.Permissions +namespace NadekoBot.Modules.Permissions.Services { - public class FilterService : IEarlyBlocker + public class FilterService : IEarlyBlocker, INService { private readonly Logger _log; @@ -41,7 +43,7 @@ namespace NadekoBot.Services.Permissions return words; } - public FilterService(DiscordShardedClient _client, IEnumerable gcs) + public FilterService(DiscordSocketClient _client, IEnumerable gcs) { _log = LogManager.GetCurrentClassLogger(); @@ -59,7 +61,16 @@ namespace NadekoBot.Services.Permissions _client.MessageUpdated += (oldData, newMsg, channel) => { - var _ = Task.Run(() => FilterInvites((channel as ITextChannel)?.Guild, newMsg as IUserMessage)); + var _ = Task.Run(() => + { + var guild = (channel as ITextChannel)?.Guild; + var usrMsg = newMsg as IUserMessage; + + if (guild == null || usrMsg == null) + return Task.CompletedTask; + + return TryBlockEarly(guild, usrMsg); + }); return Task.CompletedTask; }; } @@ -67,7 +78,7 @@ namespace NadekoBot.Services.Permissions public async Task TryBlockEarly(IGuild guild, IUserMessage msg) => !(msg.Author is IGuildUser gu) //it's never filtered outside of guilds, and never block administrators ? false - : gu.GuildPermissions.Administrator && (await FilterInvites(guild, msg) || await FilterWords(guild, msg)); + : !gu.GuildPermissions.Administrator && (await FilterInvites(guild, msg) || await FilterWords(guild, msg)); public async Task FilterWords(IGuild guild, IUserMessage usrMsg) { @@ -108,9 +119,9 @@ namespace NadekoBot.Services.Permissions if (usrMsg is null) return false; - if ((InviteFilteringChannels.Contains(usrMsg.Channel.Id) || - InviteFilteringServers.Contains(guild.Id)) && - usrMsg.Content.IsDiscordInvite()) + if ((InviteFilteringChannels.Contains(usrMsg.Channel.Id) + || InviteFilteringServers.Contains(guild.Id)) + && usrMsg.Content.IsDiscordInvite()) { try { diff --git a/src/NadekoBot/Services/Permissions/GlobalPermissionService.cs b/src/NadekoBot/Modules/Permissions/Services/GlobalPermissionService.cs similarity index 57% rename from src/NadekoBot/Services/Permissions/GlobalPermissionService.cs rename to src/NadekoBot/Modules/Permissions/Services/GlobalPermissionService.cs index 633a81a3..18d5337f 100644 --- a/src/NadekoBot/Services/Permissions/GlobalPermissionService.cs +++ b/src/NadekoBot/Modules/Permissions/Services/GlobalPermissionService.cs @@ -1,25 +1,25 @@ -using NadekoBot.DataStructures.ModuleBehaviors; -using NadekoBot.Services.Database.Models; -using System.Collections.Concurrent; -using System.Linq; +using System.Linq; +using System.Threading.Tasks; using Discord; using Discord.WebSocket; -using System.Threading.Tasks; +using NadekoBot.Common.Collections; +using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Services; -namespace NadekoBot.Services.Permissions +namespace NadekoBot.Modules.Permissions.Services { - public class GlobalPermissionService : ILateBlocker + public class GlobalPermissionService : ILateBlocker, INService { public readonly ConcurrentHashSet BlockedModules; public readonly ConcurrentHashSet BlockedCommands; - public GlobalPermissionService(BotConfig bc) + public GlobalPermissionService(IBotConfigProvider bc) { - BlockedModules = new ConcurrentHashSet(bc.BlockedModules.Select(x => x.Name)); - BlockedCommands = new ConcurrentHashSet(bc.BlockedCommands.Select(x => x.Name)); + BlockedModules = new ConcurrentHashSet(bc.BotConfig.BlockedModules.Select(x => x.Name)); + BlockedCommands = new ConcurrentHashSet(bc.BotConfig.BlockedCommands.Select(x => x.Name)); } - public async Task TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName) + public async Task TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName) { await Task.Yield(); commandName = commandName.ToLowerInvariant(); diff --git a/src/NadekoBot/Services/Permissions/PermissionsService.cs b/src/NadekoBot/Modules/Permissions/Services/PermissionsService.cs similarity index 80% rename from src/NadekoBot/Services/Permissions/PermissionsService.cs rename to src/NadekoBot/Modules/Permissions/Services/PermissionsService.cs index 765cb131..9b218758 100644 --- a/src/NadekoBot/Services/Permissions/PermissionsService.cs +++ b/src/NadekoBot/Modules/Permissions/Services/PermissionsService.cs @@ -1,39 +1,44 @@ -using Microsoft.EntityFrameworkCore; -using NadekoBot.DataStructures.ModuleBehaviors; -using NadekoBot.Services.Database.Models; -using NLog; -using System; +using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Discord; using Discord.WebSocket; +using Microsoft.EntityFrameworkCore; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Extensions; +using NadekoBot.Modules.Permissions.Common; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using NLog; -namespace NadekoBot.Services.Permissions +namespace NadekoBot.Modules.Permissions.Services { - public class PermissionService : ILateBlocker + public class PermissionService : ILateBlocker, INService { private readonly DbService _db; - private readonly Logger _log; private readonly CommandHandler _cmd; + private readonly NadekoStrings _strings; //guildid, root permission public ConcurrentDictionary Cache { get; } = new ConcurrentDictionary(); - public PermissionService(DbService db, BotConfig bc, CommandHandler cmd) + public PermissionService(DiscordSocketClient client, DbService db, CommandHandler cmd, NadekoStrings strings) { - _log = LogManager.GetCurrentClassLogger(); _db = db; _cmd = cmd; + _strings = strings; var sw = Stopwatch.StartNew(); - TryMigratePermissions(bc); + if (client.ShardId == 0) + TryMigratePermissions(); + using (var uow = _db.UnitOfWork) { - foreach (var x in uow.GuildConfigs.Permissionsv2ForAll()) + foreach (var x in uow.GuildConfigs.Permissionsv2ForAll(client.Guilds.ToArray().Select(x => (long)x.Id).ToList())) { Cache.TryAdd(x.GuildId, new PermissionCache() { @@ -43,9 +48,6 @@ namespace NadekoBot.Services.Permissions }); } } - - sw.Stop(); - _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); } public PermissionCache GetCache(ulong guildId) @@ -65,13 +67,13 @@ namespace NadekoBot.Services.Permissions return pc; } - private void TryMigratePermissions(BotConfig bc) + private void TryMigratePermissions() { - var log = LogManager.GetCurrentClassLogger(); using (var uow = _db.UnitOfWork) { - var _bc = uow.BotConfig.GetOrCreate(); - if (_bc.PermissionVersion <= 1) + var bc = uow.BotConfig.GetOrCreate(); + var log = LogManager.GetCurrentClassLogger(); + if (bc.PermissionVersion <= 1) { log.Info("Permission version is 1, upgrading to 2."); var oldCache = new ConcurrentDictionary(uow.GuildConfigs @@ -126,27 +128,28 @@ namespace NadekoBot.Services.Permissions log.Info("Permission migration to v2 is done."); } - _bc.PermissionVersion = 2; + bc.PermissionVersion = 2; + uow.Complete(); } - if (_bc.PermissionVersion <= 2) + if (bc.PermissionVersion <= 2) { var oldPrefixes = new[] { ".", ";", "!!", "!m", "!", "+", "-", "$", ">" }; uow._context.Database.ExecuteSqlCommand( -$@"UPDATE {nameof(Permissionv2)} + @"UPDATE Permissionv2 SET secondaryTargetName=trim(substr(secondaryTargetName, 3)) WHERE secondaryTargetName LIKE '!!%' OR secondaryTargetName LIKE '!m%'; -UPDATE {nameof(Permissionv2)} +UPDATE Permissionv2 SET secondaryTargetName=substr(secondaryTargetName, 2) WHERE secondaryTargetName LIKE '.%' OR - secondaryTargetName LIKE '~%' OR - secondaryTargetName LIKE ';%' OR - secondaryTargetName LIKE '>%' OR - secondaryTargetName LIKE '-%' OR - secondaryTargetName LIKE '!%';"); - _bc.PermissionVersion = 3; +secondaryTargetName LIKE '~%' OR +secondaryTargetName LIKE ';%' OR +secondaryTargetName LIKE '>%' OR +secondaryTargetName LIKE '-%' OR +secondaryTargetName LIKE '!%';"); + bc.PermissionVersion = 3; + uow.Complete(); } - uow.Complete(); } } @@ -183,7 +186,7 @@ WHERE secondaryTargetName LIKE '.%' OR }); } - public async Task TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName) + public async Task TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName) { await Task.Yield(); if (guild == null) @@ -198,11 +201,9 @@ WHERE secondaryTargetName LIKE '.%' OR PermissionCache pc = GetCache(guild.Id); if (!resetCommand && !pc.Permissions.CheckPermissions(msg, commandName, moduleName, out int index)) { - var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild)}** is preventing this action."; if (pc.Verbose) - try { await channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { } + try { await channel.SendErrorAsync(_strings.GetText("trigger", guild.Id, "Permissions".ToLowerInvariant(), index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild)))).ConfigureAwait(false); } catch { } return true; - //return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, returnMsg)); } @@ -215,7 +216,6 @@ WHERE secondaryTargetName LIKE '.%' OR if (pc.Verbose) try { await channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { } return true; - //return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"You need the **{pc.PermRole}** role in order to use permission commands.")); } } } @@ -223,4 +223,4 @@ WHERE secondaryTargetName LIKE '.%' OR return false; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/Services/ResetPermissionsService.cs b/src/NadekoBot/Modules/Permissions/Services/ResetPermissionsService.cs new file mode 100644 index 00000000..08834b36 --- /dev/null +++ b/src/NadekoBot/Modules/Permissions/Services/ResetPermissionsService.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Modules.Permissions.Services +{ + public class ResetPermissionsService : INService + { + private readonly PermissionService _perms; + private readonly GlobalPermissionService _globalPerms; + private readonly DbService _db; + + public ResetPermissionsService(PermissionService perms, GlobalPermissionService globalPerms, DbService db) + { + _perms = perms; + _globalPerms = globalPerms; + _db = db; + } + + public async Task ResetPermissions(ulong guildId) + { + using (var uow = _db.UnitOfWork) + { + var config = uow.GuildConfigs.GcWithPermissionsv2For(guildId); + config.Permissions = Permissionv2.GetDefaultPermlist; + await uow.CompleteAsync().ConfigureAwait(false); + _perms.UpdateCache(config); + } + } + + public async Task ResetGlobalPermissions() + { + using (var uow = _db.UnitOfWork) + { + var gc = uow.BotConfig.GetOrCreate(); + gc.BlockedCommands.Clear(); + gc.BlockedModules.Clear(); + + _globalPerms.BlockedCommands.Clear(); + _globalPerms.BlockedModules.Clear(); + await uow.CompleteAsync().ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Services/Pokemon/PokeStats.cs b/src/NadekoBot/Modules/Pokemon/Common/PokeStats.cs similarity index 90% rename from src/NadekoBot/Services/Pokemon/PokeStats.cs rename to src/NadekoBot/Modules/Pokemon/Common/PokeStats.cs index 069cfd72..eb06a1a6 100644 --- a/src/NadekoBot/Services/Pokemon/PokeStats.cs +++ b/src/NadekoBot/Modules/Pokemon/Common/PokeStats.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace NadekoBot.Services.Pokemon +namespace NadekoBot.Modules.Pokemon.Common { public class PokeStats { diff --git a/src/NadekoBot/Services/Pokemon/PokemonType.cs b/src/NadekoBot/Modules/Pokemon/Common/PokemonType.cs similarity index 95% rename from src/NadekoBot/Services/Pokemon/PokemonType.cs rename to src/NadekoBot/Modules/Pokemon/Common/PokemonType.cs index 1b2bae80..ac83c314 100644 --- a/src/NadekoBot/Services/Pokemon/PokemonType.cs +++ b/src/NadekoBot/Modules/Pokemon/Common/PokemonType.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace NadekoBot.Services.Pokemon +namespace NadekoBot.Modules.Pokemon.Common { public class PokemonType { diff --git a/src/NadekoBot/Modules/Pokemon/Pokemon.cs b/src/NadekoBot/Modules/Pokemon/Pokemon.cs index 3b8d47a4..0389e1b0 100644 --- a/src/NadekoBot/Modules/Pokemon/Pokemon.cs +++ b/src/NadekoBot/Modules/Pokemon/Pokemon.cs @@ -1,5 +1,4 @@ using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using System.Linq; using NadekoBot.Services; @@ -8,20 +7,20 @@ using System.Collections.Generic; using System.Threading.Tasks; using Discord; using System; -using NadekoBot.Services.Pokemon; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Pokemon.Common; +using NadekoBot.Modules.Pokemon.Services; namespace NadekoBot.Modules.Pokemon { - public class Pokemon : NadekoTopLevelModule + public class Pokemon : NadekoTopLevelModule { - private readonly PokemonService _service; private readonly DbService _db; - private readonly BotConfig _bc; + private readonly IBotConfigProvider _bc; private readonly CurrencyService _cs; - public Pokemon(PokemonService pokemonService, DbService db, BotConfig bc, CurrencyService cs) + public Pokemon(DbService db, IBotConfigProvider bc, CurrencyService cs) { - _service = pokemonService; _db = db; _bc = bc; _cs = cs; @@ -231,7 +230,7 @@ namespace NadekoBot.Modules.Pokemon { if (!await _cs.RemoveAsync(user, $"Poke-Heal {target}", amount, true).ConfigureAwait(false)) { - await ReplyErrorLocalized("no_currency", _bc.CurrencySign).ConfigureAwait(false); + await ReplyErrorLocalized("no_currency", _bc.BotConfig.CurrencySign).ConfigureAwait(false); return; } } @@ -244,13 +243,13 @@ namespace NadekoBot.Modules.Pokemon _service.Stats[targetUser.Id].Hp = (targetStats.MaxHp / 2); if (target == "yourself") { - await ReplyConfirmLocalized("revive_yourself", _bc.CurrencySign).ConfigureAwait(false); + await ReplyConfirmLocalized("revive_yourself", _bc.BotConfig.CurrencySign).ConfigureAwait(false); return; } - await ReplyConfirmLocalized("revive_other", Format.Bold(targetUser.ToString()), _bc.CurrencySign).ConfigureAwait(false); + await ReplyConfirmLocalized("revive_other", Format.Bold(targetUser.ToString()), _bc.BotConfig.CurrencySign).ConfigureAwait(false); } - await ReplyConfirmLocalized("healed", Format.Bold(targetUser.ToString()), _bc.CurrencySign).ConfigureAwait(false); + await ReplyConfirmLocalized("healed", Format.Bold(targetUser.ToString()), _bc.BotConfig.CurrencySign).ConfigureAwait(false); } else { @@ -297,7 +296,7 @@ namespace NadekoBot.Modules.Pokemon { if (!await _cs.RemoveAsync(user, $"{user} change type to {typeTargeted}", amount, true).ConfigureAwait(false)) { - await ReplyErrorLocalized("no_currency", _bc.CurrencySign).ConfigureAwait(false); + await ReplyErrorLocalized("no_currency", _bc.BotConfig.CurrencySign).ConfigureAwait(false); return; } } @@ -331,7 +330,7 @@ namespace NadekoBot.Modules.Pokemon //Now for the response await ReplyConfirmLocalized("settype_success", targetType, - _bc.CurrencySign).ConfigureAwait(false); + _bc.BotConfig.CurrencySign).ConfigureAwait(false); } } } diff --git a/src/NadekoBot/Services/Pokemon/PokemonService.cs b/src/NadekoBot/Modules/Pokemon/Services/PokemonService.cs similarity index 80% rename from src/NadekoBot/Services/Pokemon/PokemonService.cs rename to src/NadekoBot/Modules/Pokemon/Services/PokemonService.cs index d2160865..43edc5cd 100644 --- a/src/NadekoBot/Services/Pokemon/PokemonService.cs +++ b/src/NadekoBot/Modules/Pokemon/Services/PokemonService.cs @@ -1,12 +1,14 @@ -using Newtonsoft.Json; -using NLog; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using NadekoBot.Modules.Pokemon.Common; +using NadekoBot.Services; +using Newtonsoft.Json; +using NLog; -namespace NadekoBot.Services.Pokemon +namespace NadekoBot.Modules.Pokemon.Services { - public class PokemonService + public class PokemonService : INService { public readonly List PokemonTypes = new List(); public readonly ConcurrentDictionary Stats = new ConcurrentDictionary(); diff --git a/src/NadekoBot/Modules/Searches/Commands/AnimeSearchCommands.cs b/src/NadekoBot/Modules/Searches/AnimeSearchCommands.cs similarity index 89% rename from src/NadekoBot/Modules/Searches/Commands/AnimeSearchCommands.cs rename to src/NadekoBot/Modules/Searches/AnimeSearchCommands.cs index c80bd2c1..f6825b7d 100644 --- a/src/NadekoBot/Modules/Searches/Commands/AnimeSearchCommands.cs +++ b/src/NadekoBot/Modules/Searches/AnimeSearchCommands.cs @@ -2,30 +2,22 @@ using AngleSharp.Dom.Html; using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; -using NadekoBot.Services.Searches; +using NadekoBot.Modules.Searches.Services; using System; using System.Linq; -using System.Threading; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; namespace NadekoBot.Modules.Searches { public partial class Searches { [Group] - public class AnimeSearchCommands : NadekoSubmodule + public class AnimeSearchCommands : NadekoSubmodule { - private readonly AnimeSearchService _service; - - public AnimeSearchCommands(AnimeSearchService service) - { - _service = service; - } - [NadekoCommand, Usage, Description, Aliases] - [Priority(1)] + [Priority(0)] public async Task Mal([Remainder] string name) { if (string.IsNullOrWhiteSpace(name)) @@ -54,16 +46,6 @@ namespace NadekoBot.Modules.Searches return $"[{elem.InnerHtml}]({elem.Href})"; })); - //var favManga = "No favorite manga yet."; - //if (favorites[1].QuerySelector("p") == null) - // favManga = string.Join("\n", favorites[1].QuerySelectorAll("ul > li > div.di-tc.va-t > a") - // .Take(3) - // .Select(x => - // { - // var elem = (IHtmlAnchorElement)x; - // return $"[{elem.InnerHtml}]({elem.Href})"; - // })); - var info = document.QuerySelectorAll("ul.user-status:nth-child(3) > li.clearfix") .Select(x => Tuple.Create(x.Children[0].InnerHtml, x.Children[1].InnerHtml)) .ToList(); @@ -113,7 +95,8 @@ namespace NadekoBot.Modules.Searches await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } - private static string MalInfoToEmoji(string info) { + private static string MalInfoToEmoji(string info) + { info = info.Trim().ToLowerInvariant(); switch (info) { @@ -132,7 +115,7 @@ namespace NadekoBot.Modules.Searches [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - [Priority(0)] + [Priority(1)] public Task Mal(IGuildUser usr) => Mal(usr.Username); [NadekoCommand, Usage, Description, Aliases] @@ -156,7 +139,7 @@ namespace NadekoBot.Modules.Searches .WithImageUrl(animeData.image_url_lge) .AddField(efb => efb.WithName(GetText("episodes")).WithValue(animeData.total_episodes.ToString()).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("status")).WithValue(animeData.AiringStatus.ToString()).WithIsInline(true)) - .AddField(efb => efb.WithName(GetText("genres")).WithValue(String.Join(",\n", animeData.Genres)).WithIsInline(true)) + .AddField(efb => efb.WithName(GetText("genres")).WithValue(string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : new[] { "none" })).WithIsInline(true)) .WithFooter(efb => efb.WithText(GetText("score") + " " + animeData.average_score + " / 100")); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } @@ -183,7 +166,7 @@ namespace NadekoBot.Modules.Searches .WithImageUrl(mangaData.image_url_lge) .AddField(efb => efb.WithName(GetText("chapters")).WithValue(mangaData.total_chapters.ToString()).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("status")).WithValue(mangaData.publishing_status.ToString()).WithIsInline(true)) - .AddField(efb => efb.WithName(GetText("genres")).WithValue(String.Join(",\n", mangaData.Genres)).WithIsInline(true)) + .AddField(efb => efb.WithName(GetText("genres")).WithValue(string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : new[] { "none" })).WithIsInline(true)) .WithFooter(efb => efb.WithText(GetText("score") + " " + mangaData.average_score + " / 100")); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); diff --git a/src/NadekoBot/Modules/Searches/Commands/JokeCommands.cs b/src/NadekoBot/Modules/Searches/Commands/JokeCommands.cs deleted file mode 100644 index ec653b87..00000000 --- a/src/NadekoBot/Modules/Searches/Commands/JokeCommands.cs +++ /dev/null @@ -1,91 +0,0 @@ -using AngleSharp; -using Discord.Commands; -using NadekoBot.Attributes; -using NadekoBot.Extensions; -using NadekoBot.Services; -using NadekoBot.Services.Searches; -using Newtonsoft.Json.Linq; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Searches -{ - public partial class Searches - { - [Group] - public class JokeCommands : NadekoSubmodule - { - private readonly SearchesService _searches; - - public JokeCommands(SearchesService searches) - { - _searches = searches; - } - - [NadekoCommand, Usage, Description, Aliases] - public async Task Yomama() - { - using (var http = new HttpClient()) - { - var response = await http.GetStringAsync("http://api.yomomma.info/").ConfigureAwait(false); - await Context.Channel.SendConfirmAsync(JObject.Parse(response)["joke"].ToString() + " 😆").ConfigureAwait(false); - } - } - - [NadekoCommand, Usage, Description, Aliases] - public async Task Randjoke() - { - using (var http = new HttpClient()) - { - http.AddFakeHeaders(); - - var config = Configuration.Default.WithDefaultLoader(); - var document = await BrowsingContext.New(config).OpenAsync("http://www.goodbadjokes.com/random"); - - var html = document.QuerySelector(".post > .joke-content"); - - var part1 = html.QuerySelector("dt").TextContent; - var part2 = html.QuerySelector("dd").TextContent; - - await Context.Channel.SendConfirmAsync("", part1 + "\n\n" + part2, footer: document.BaseUri).ConfigureAwait(false); - } - } - - [NadekoCommand, Usage, Description, Aliases] - public async Task ChuckNorris() - { - using (var http = new HttpClient()) - { - var response = await http.GetStringAsync("http://api.icndb.com/jokes/random/").ConfigureAwait(false); - await Context.Channel.SendConfirmAsync(JObject.Parse(response)["value"]["joke"].ToString() + " 😆").ConfigureAwait(false); - } - } - - [NadekoCommand, Usage, Description, Aliases] - public async Task WowJoke() - { - if (!_searches.WowJokes.Any()) - { - await ReplyErrorLocalized("jokes_not_loaded").ConfigureAwait(false); - return; - } - var joke = _searches.WowJokes[new NadekoRandom().Next(0, _searches.WowJokes.Count)]; - await Context.Channel.SendConfirmAsync(joke.Question, joke.Answer).ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - public async Task MagicItem() - { - if (!_searches.WowJokes.Any()) - { - await ReplyErrorLocalized("magicitems_not_loaded").ConfigureAwait(false); - return; - } - var item = _searches.MagicItems[new NadekoRandom().Next(0, _searches.MagicItems.Count)]; - - await Context.Channel.SendConfirmAsync("✨" + item.Name, item.Description).ConfigureAwait(false); - } - } - } -} diff --git a/src/NadekoBot/Modules/Searches/Commands/LoLCommands.cs b/src/NadekoBot/Modules/Searches/Commands/LoLCommands.cs deleted file mode 100644 index d7697a5c..00000000 --- a/src/NadekoBot/Modules/Searches/Commands/LoLCommands.cs +++ /dev/null @@ -1,382 +0,0 @@ -using Discord; -using NadekoBot.Attributes; -using NadekoBot.Extensions; -using NadekoBot.Services; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -//todo 50 drawing -namespace NadekoBot.Modules.Searches -{ - public partial class Searches - { - private class ChampionNameComparer : IEqualityComparer - { - public bool Equals(JToken a, JToken b) => a["name"].ToString() == b["name"].ToString(); - - public int GetHashCode(JToken obj) => - obj["name"].GetHashCode(); - } - - private static readonly string[] trashTalk = { "Better ban your counters. You are going to carry the game anyway.", - "Go with the flow. Don't think. Just ban one of these.", - "DONT READ BELOW! Ban Urgot mid OP 100%. Im smurf Diamond 1.", - "Ask your teammates what would they like to play, and ban that.", - "If you consider playing teemo, do it. If you consider teemo, you deserve him.", - "Doesn't matter what you ban really. Enemy will ban your main and you will lose." }; - - - [NadekoCommand, Usage, Description, Aliases] - public async Task Lolban() - { - const int showCount = 8; - //http://api.champion.gg/stats/champs/mostBanned?api_key=YOUR_API_TOKEN&page=1&limit=2 - try - { - using (var http = new HttpClient()) - { - var data = JObject.Parse(await http.GetStringAsync($"http://api.champion.gg/stats/champs/mostBanned?" + - $"api_key={_creds.LoLApiKey}&page=1&" + - $"limit={showCount}") - .ConfigureAwait(false))["data"] as JArray; - var dataList = data.Distinct(new ChampionNameComparer()).Take(showCount).ToList(); - var eb = new EmbedBuilder().WithOkColor().WithTitle(Format.Underline(GetText("x_most_banned_champs",dataList.Count))); - foreach (var champ in dataList) - { - var champ1 = champ; - eb.AddField(efb => efb.WithName(champ1["name"].ToString()).WithValue(champ1["general"]["banRate"] + "%").WithIsInline(true)); - } - - await Context.Channel.EmbedAsync(eb, Format.Italics(trashTalk[new NadekoRandom().Next(0, trashTalk.Length)])).ConfigureAwait(false); - } - } - catch (Exception ex) - { - _log.Warn(ex); - await ReplyErrorLocalized("something_went_wrong").ConfigureAwait(false); - } - } - } -} - -// private class CachedChampion -// { -// public System.IO.Stream ImageStream { get; set; } -// public DateTime AddedAt { get; set; } -// public string Name { get; set; } -// } - -// -// private static Dictionary CachedChampionImages = new Dictionary(); - -// private System.Timers.Timer clearTimer { get; } = new System.Timers.Timer(); -// public LoLCommands(DiscordModule module) : base(module) -// { -// clearTimer.Interval = new TimeSpan(0, 10, 0).TotalMilliseconds; -// clearTimer.Start(); -// clearTimer.Elapsed += (s, e) => -// { -// try -// { -// CachedChampionImages = CachedChampionImages -// .Where(kvp => DateTime.UtcNow - kvp.Value.AddedAt > new TimeSpan(1, 0, 0)) -// .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); -// } -// catch { } -// }; -// } - -// public Func DoFunc() -// { -// throw new NotImplementedException(); -// } - -// private class MatchupModel -// { -// public int Games { get; set; } -// public float WinRate { get; set; } -// [Newtonsoft.Json.JsonProperty("key")] -// public string Name { get; set; } -// public float StatScore { get; set; } -// } - -// public override void Init(CommandGroupBuilder cgb) -// { -// cgb.CreateCommand(Module.Name + "lolchamp") -// .Description($"Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role. |`{Prefix}lolchamp Riven` or `{Prefix}lolchamp Annie sup`") -// .Parameter("champ", ParameterType.Required) -// .Parameter("position", ParameterType.Unparsed) -// .Do(async e => -// { -// try -// { -// //get role -// var role = ResolvePos(position); -// var resolvedRole = role; -// var name = champ.Replace(" ", "").ToLower(); -// CachedChampion champ = null; - -// if (CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ)) -// if (champ != null) -// { -// champ.ImageStream.Position = 0; -// await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false); -// return; -// } -// var allData = JArray.Parse(await Classes.http.GetStringAsync($"http://api.champion.gg/champion/{name}?api_key={_creds.LOLAPIKey}").ConfigureAwait(false)); -// JToken data = null; -// if (role != null) -// { -// for (var i = 0; i < allData.Count; i++) -// { -// if (allData[i]["role"].ToString().Equals(role)) -// { -// data = allData[i]; -// break; -// } -// } -// if (data == null) -// { -// await channel.SendMessageAsync("💢 Data for that role does not exist.").ConfigureAwait(false); -// return; -// } -// } -// else -// { -// data = allData[0]; -// role = allData[0]["role"].ToString(); -// resolvedRole = ResolvePos(role); -// } -// if (CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ)) -// if (champ != null) -// { -// champ.ImageStream.Position = 0; -// await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false); -// return; -// } -// //name = data["title"].ToString(); -// // get all possible roles, and "select" the shown one -// var roles = new string[allData.Count]; -// for (var i = 0; i < allData.Count; i++) -// { -// roles[i] = allData[i]["role"].ToString(); -// if (roles[i] == role) -// roles[i] = ">" + roles[i] + "<"; -// } -// var general = JArray.Parse(await http.GetStringAsync($"http://api.champion.gg/stats/" + -// $"champs/{name}?api_key={_creds.LOLAPIKey}") -// .ConfigureAwait(false)) -// .FirstOrDefault(jt => jt["role"].ToString() == role)?["general"]; -// if (general == null) -// { -// //Console.WriteLine("General is null."); -// return; -// } -// //get build data for this role -// var buildData = data["items"]["mostGames"]["items"]; -// var items = new string[6]; -// for (var i = 0; i < 6; i++) -// { -// items[i] = buildData[i]["id"].ToString(); -// } - -// //get matchup data to show counters and countered champions -// var matchupDataIE = data["matchups"].ToObject>(); - -// var matchupData = matchupDataIE.OrderBy(m => m.StatScore).ToArray(); - -// var countered = new[] { matchupData[0].Name, matchupData[1].Name, matchupData[2].Name }; -// var counters = new[] { matchupData[matchupData.Length - 1].Name, matchupData[matchupData.Length - 2].Name, matchupData[matchupData.Length - 3].Name }; - -// //get runes data -// var runesJArray = data["runes"]["mostGames"]["runes"] as JArray; -// var runes = string.Join("\n", runesJArray.OrderBy(jt => int.Parse(jt["number"].ToString())).Select(jt => jt["number"].ToString() + "x" + jt["name"])); - -// // get masteries data - -// var masteries = (data["masteries"]["mostGames"]["masteries"] as JArray); - -// //get skill order data - -// var orderArr = (data["skills"]["mostGames"]["order"] as JArray); - -// var img = Image.FromFile("data/lol/bg.png"); -// using (var g = Graphics.FromImage(img)) -// { -// g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; -// //g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy; -// const int margin = 5; -// const int imageSize = 75; -// var normalFont = new Font("Monaco", 8, FontStyle.Regular); -// var smallFont = new Font("Monaco", 7, FontStyle.Regular); -// //draw champ image -// var champName = data["key"].ToString().Replace(" ", ""); - -// g.DrawImage(GetImage(champName), new Rectangle(margin, margin, imageSize, imageSize)); -// //draw champ name -// if (champName == "MonkeyKing") -// champName = "Wukong"; -// g.DrawString($"{champName}", new Font("Times New Roman", 24, FontStyle.Regular), Brushes.WhiteSmoke, margin + imageSize + margin, margin); -// //draw champ surname - -// //draw skill order -// if (orderArr.Count != 0) -// { -// float orderFormula = 120 / orderArr.Count; -// const float orderVerticalSpacing = 10; -// for (var i = 0; i < orderArr.Count; i++) -// { -// var orderX = margin + margin + imageSize + orderFormula * i + i; -// float orderY = margin + 35; -// var spellName = orderArr[i].ToString().ToLowerInvariant(); - -// switch (spellName) -// { -// case "w": -// orderY += orderVerticalSpacing; -// break; -// case "e": -// orderY += orderVerticalSpacing * 2; -// break; -// case "r": -// orderY += orderVerticalSpacing * 3; -// break; -// default: -// break; -// } - -// g.DrawString(spellName.ToUpperInvariant(), new Font("Monaco", 7), Brushes.LimeGreen, orderX, orderY); -// } -// } -// //draw roles -// g.DrawString("Roles: " + string.Join(", ", roles), normalFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin); - -// //draw average stats -// g.DrawString( -//$@" Average Stats - -//Kills: {general["kills"]} CS: {general["minionsKilled"]} -//Deaths: {general["deaths"]} Win: {general["winPercent"]}% -//Assists: {general["assists"]} Ban: {general["banRate"]}% -//", normalFont, Brushes.WhiteSmoke, img.Width - 150, margin); -// //draw masteries -// g.DrawString($"Masteries: {string.Join(" / ", masteries?.Select(jt => jt["total"]))}", normalFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin + 20); -// //draw runes -// g.DrawString($"{runes}", smallFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin + 40); -// //draw counters -// g.DrawString($"Best against", smallFont, Brushes.WhiteSmoke, margin, img.Height - imageSize + margin); -// var smallImgSize = 50; - -// for (var i = 0; i < counters.Length; i++) -// { -// g.DrawImage(GetImage(counters[i]), -// new Rectangle(i * (smallImgSize + margin) + margin, img.Height - smallImgSize - margin, -// smallImgSize, -// smallImgSize)); -// } -// //draw countered by -// g.DrawString($"Worst against", smallFont, Brushes.WhiteSmoke, img.Width - 3 * (smallImgSize + margin), img.Height - imageSize + margin); - -// for (var i = 0; i < countered.Length; i++) -// { -// var j = countered.Length - i; -// g.DrawImage(GetImage(countered[i]), -// new Rectangle(img.Width - (j * (smallImgSize + margin) + margin), img.Height - smallImgSize - margin, -// smallImgSize, -// smallImgSize)); -// } -// //draw item build -// g.DrawString("Popular build", normalFont, Brushes.WhiteSmoke, img.Width - (3 * (smallImgSize + margin) + margin), 77); - -// for (var i = 0; i < 6; i++) -// { -// var inverseI = 5 - i; -// var j = inverseI % 3 + 1; -// var k = inverseI / 3; -// g.DrawImage(GetImage(items[i], GetImageType.Item), -// new Rectangle(img.Width - (j * (smallImgSize + margin) + margin), 92 + k * (smallImgSize + margin), -// smallImgSize, -// smallImgSize)); -// } -// } -// var cachedChamp = new CachedChampion { AddedAt = DateTime.UtcNow, ImageStream = img.ToStream(System.Drawing.Imaging.ImageFormat.Png), Name = name.ToLower() + "_" + resolvedRole }; -// CachedChampionImages.Add(cachedChamp.Name, cachedChamp); -// await e.Channel.SendFile(data["title"] + "_stats.png", cachedChamp.ImageStream).ConfigureAwait(false); -// } -// catch (Exception ex) -// { -// //Console.WriteLine(ex); -// await channel.SendMessageAsync("💢 Failed retreiving data for that champion.").ConfigureAwait(false); -// } -// }); -// } - -// private enum GetImageType -// { -// Champion, -// Item -// } -// private static Image GetImage(string id, GetImageType imageType = GetImageType.Champion) -// { -// try -// { -// switch (imageType) -// { -// case GetImageType.Champion: -// return Image.FromFile($"data/lol/champions/{id}.png"); -// case GetImageType.Item: -// default: -// return Image.FromFile($"data/lol/items/{id}.png"); -// } -// } -// catch (Exception) -// { -// return Image.FromFile("data/lol/_ERROR.png"); -// } -// } - -// private static string ResolvePos(string pos) -// { -// if (string.IsNullOrWhiteSpace(pos)) -// return null; -// switch (pos.ToLowerInvariant()) -// { -// case "m": -// case "mid": -// case "midorfeed": -// case "midd": -// case "middle": -// return "Middle"; -// case "top": -// case "topp": -// case "t": -// case "toporfeed": -// return "Top"; -// case "j": -// case "jun": -// case "jungl": -// case "jungle": -// return "Jungle"; -// case "a": -// case "ad": -// case "adc": -// case "carry": -// case "ad carry": -// case "adcarry": -// case "c": -// return "ADC"; -// case "s": -// case "sup": -// case "supp": -// case "support": -// return "Support"; -// default: -// return pos; -// } -// } -// } -//} diff --git a/src/NadekoBot/Modules/Searches/Commands/Models/WeatherModels.cs b/src/NadekoBot/Modules/Searches/Commands/Models/WeatherModels.cs deleted file mode 100644 index d9495625..00000000 --- a/src/NadekoBot/Modules/Searches/Commands/Models/WeatherModels.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; - -namespace NadekoBot.Modules.Searches.Commands.Models -{ - public class Coord - { - public double lon { get; set; } - public double lat { get; set; } - } - - public class Weather - { - public int id { get; set; } - public string main { get; set; } - public string description { get; set; } - public string icon { get; set; } - } - - public class Main - { - public double temp { get; set; } - public float pressure { get; set; } - public float humidity { get; set; } - public double temp_min { get; set; } - public double temp_max { get; set; } - } - - public class Wind - { - public double speed { get; set; } - public double deg { get; set; } - } - - public class Clouds - { - public int all { get; set; } - } - - public class Sys - { - public int type { get; set; } - public int id { get; set; } - public double message { get; set; } - public string country { get; set; } - public double sunrise { get; set; } - public double sunset { get; set; } - } - - public class WeatherData - { - public Coord coord { get; set; } - public List weather { get; set; } - public Main main { get; set; } - public int visibility { get; set; } - public Wind wind { get; set; } - public Clouds clouds { get; set; } - public int dt { get; set; } - public Sys sys { get; set; } - public int id { get; set; } - public string name { get; set; } - public int cod { get; set; } - } -} diff --git a/src/NadekoBot/Services/Searches/Models/AnimeResult.cs b/src/NadekoBot/Modules/Searches/Common/AnimeResult.cs similarity index 93% rename from src/NadekoBot/Services/Searches/Models/AnimeResult.cs rename to src/NadekoBot/Modules/Searches/Common/AnimeResult.cs index 626421d2..f2d3e8f0 100644 --- a/src/NadekoBot/Services/Searches/Models/AnimeResult.cs +++ b/src/NadekoBot/Modules/Searches/Common/AnimeResult.cs @@ -1,6 +1,6 @@ using NadekoBot.Extensions; -namespace NadekoBot.Services.Searches +namespace NadekoBot.Modules.Searches.Common { public class AnimeResult { diff --git a/src/NadekoBot/Modules/Searches/Commands/Models/DefineModel.cs b/src/NadekoBot/Modules/Searches/Common/DefineModel.cs similarity index 93% rename from src/NadekoBot/Modules/Searches/Commands/Models/DefineModel.cs rename to src/NadekoBot/Modules/Searches/Common/DefineModel.cs index f88fd9d3..5a3b4603 100644 --- a/src/NadekoBot/Modules/Searches/Commands/Models/DefineModel.cs +++ b/src/NadekoBot/Modules/Searches/Common/DefineModel.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace NadekoBot.Modules.Searches.Commands.Models +namespace NadekoBot.Modules.Searches.Common { public class Audio { diff --git a/src/NadekoBot/Services/Searches/StreamNotFoundException.cs b/src/NadekoBot/Modules/Searches/Common/Exceptions/StreamNotFoundException.cs similarity index 78% rename from src/NadekoBot/Services/Searches/StreamNotFoundException.cs rename to src/NadekoBot/Modules/Searches/Common/Exceptions/StreamNotFoundException.cs index f05d250f..3e43016d 100644 --- a/src/NadekoBot/Services/Searches/StreamNotFoundException.cs +++ b/src/NadekoBot/Modules/Searches/Common/Exceptions/StreamNotFoundException.cs @@ -1,6 +1,6 @@ using System; -namespace NadekoBot.Services.Searches +namespace NadekoBot.Modules.Searches.Common.Exceptions { public class StreamNotFoundException : Exception { diff --git a/src/NadekoBot/Modules/Searches/Commands/Models/GoogleSearchResult.cs b/src/NadekoBot/Modules/Searches/Common/GoogleSearchResult.cs similarity index 86% rename from src/NadekoBot/Modules/Searches/Commands/Models/GoogleSearchResult.cs rename to src/NadekoBot/Modules/Searches/Common/GoogleSearchResult.cs index 72fe4483..843db40e 100644 --- a/src/NadekoBot/Modules/Searches/Commands/Models/GoogleSearchResult.cs +++ b/src/NadekoBot/Modules/Searches/Common/GoogleSearchResult.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Modules.Searches.Commands.Models +namespace NadekoBot.Modules.Searches.Common { public struct GoogleSearchResult { diff --git a/src/NadekoBot/Services/Searches/Models/MagicItem.cs b/src/NadekoBot/Modules/Searches/Common/MagicItem.cs similarity index 73% rename from src/NadekoBot/Services/Searches/Models/MagicItem.cs rename to src/NadekoBot/Modules/Searches/Common/MagicItem.cs index 1d555e7e..a69629bc 100644 --- a/src/NadekoBot/Services/Searches/Models/MagicItem.cs +++ b/src/NadekoBot/Modules/Searches/Common/MagicItem.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Services.Searches +namespace NadekoBot.Modules.Searches.Common { public class MagicItem { diff --git a/src/NadekoBot/Services/Searches/Models/MangaResult.cs b/src/NadekoBot/Modules/Searches/Common/MangaResult.cs similarity index 92% rename from src/NadekoBot/Services/Searches/Models/MangaResult.cs rename to src/NadekoBot/Modules/Searches/Common/MangaResult.cs index 3109b940..0ea0b961 100644 --- a/src/NadekoBot/Services/Searches/Models/MangaResult.cs +++ b/src/NadekoBot/Modules/Searches/Common/MangaResult.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Services.Searches +namespace NadekoBot.Modules.Searches.Common { public class MangaResult { diff --git a/src/NadekoBot/Modules/Searches/Commands/OMDB/OmdbProvider.cs b/src/NadekoBot/Modules/Searches/Common/OmdbProvider.cs similarity index 96% rename from src/NadekoBot/Modules/Searches/Commands/OMDB/OmdbProvider.cs rename to src/NadekoBot/Modules/Searches/Common/OmdbProvider.cs index c5a478ea..16ea322a 100644 --- a/src/NadekoBot/Modules/Searches/Commands/OMDB/OmdbProvider.cs +++ b/src/NadekoBot/Modules/Searches/Common/OmdbProvider.cs @@ -1,12 +1,12 @@ -using Discord; +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Discord; using NadekoBot.Extensions; using NadekoBot.Services; using Newtonsoft.Json; -using System; -using System.Net.Http; -using System.Threading.Tasks; -namespace NadekoBot.Modules.Searches.Commands.OMDB +namespace NadekoBot.Modules.Searches.Common { public static class OmdbProvider { diff --git a/src/NadekoBot/Modules/Searches/Commands/Models/OverwatchApiModel.cs b/src/NadekoBot/Modules/Searches/Common/OverwatchApiModel.cs similarity index 96% rename from src/NadekoBot/Modules/Searches/Commands/Models/OverwatchApiModel.cs rename to src/NadekoBot/Modules/Searches/Common/OverwatchApiModel.cs index a373e062..c7f73c1b 100644 --- a/src/NadekoBot/Modules/Searches/Commands/Models/OverwatchApiModel.cs +++ b/src/NadekoBot/Modules/Searches/Common/OverwatchApiModel.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace NadekoBot.Modules.Searches.Models +namespace NadekoBot.Modules.Searches.Common { public class OverwatchApiModel { diff --git a/src/NadekoBot/Modules/Searches/Common/SearchImageCacher.cs b/src/NadekoBot/Modules/Searches/Common/SearchImageCacher.cs new file mode 100644 index 00000000..20b06014 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Common/SearchImageCacher.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using NadekoBot.Common; +using NadekoBot.Extensions; +using Newtonsoft.Json; +using NLog; + +namespace NadekoBot.Modules.Searches.Common +{ + public class SearchImageCacher + { + private readonly NadekoRandom _rng; + private readonly ConcurrentDictionary _locks = new ConcurrentDictionary(); + + private readonly SortedSet _cache; + private readonly Logger _log; + private readonly HttpClient _http; + + public SearchImageCacher() + { + _http = new HttpClient(); + _http.AddFakeHeaders(); + + _log = LogManager.GetCurrentClassLogger(); + _rng = new NadekoRandom(); + _cache = new SortedSet(); + } + + public async Task GetImage(string tag, bool forceExplicit, DapiSearchType type, + HashSet blacklistedTags = null) + { + tag = tag?.ToLowerInvariant(); + + blacklistedTags = blacklistedTags ?? new HashSet(); + + if (type == DapiSearchType.E621) + tag = tag?.Replace("yuri", "female/female"); + + var _lock = GetLock(type); + await _lock.WaitAsync(); + try + { + ImageCacherObject[] imgs; + if (!string.IsNullOrWhiteSpace(tag)) + { + imgs = _cache.Where(x => x.Tags.IsSupersetOf(tag.Split('+')) && x.SearchType == type && (!forceExplicit || x.Rating == "e")).ToArray(); + } + else + { + tag = null; + imgs = _cache.Where(x => x.SearchType == type).ToArray(); + } + ImageCacherObject img; + if (imgs.Length == 0) + img = null; + else + img = imgs[_rng.Next(imgs.Length)]; + + if (img != null) + { + _cache.Remove(img); + return img; + } + else + { + var images = await DownloadImages(tag, forceExplicit, type).ConfigureAwait(false); + images = images + .Where(x => x.Tags.All(t => !blacklistedTags.Contains(t))) + .ToArray(); + if (images.Length == 0) + return null; + var toReturn = images[_rng.Next(images.Length)]; +#if !GLOBAL_NADEKO + foreach (var dledImg in images) + { + if(dledImg != toReturn) + _cache.Add(dledImg); + } +#endif + return toReturn; + } + } + finally + { + _lock.Release(); + } + } + + private SemaphoreSlim GetLock(DapiSearchType type) + { + return _locks.GetOrAdd(type, _ => new SemaphoreSlim(1, 1)); + } + + public async Task DownloadImages(string tag, bool isExplicit, DapiSearchType type) + { + tag = tag?.Replace(" ", "_").ToLowerInvariant(); + if (isExplicit) + tag = "rating%3Aexplicit+" + tag; + var website = ""; + switch (type) + { + case DapiSearchType.Safebooru: + website = $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=1000&tags={tag}"; + break; + case DapiSearchType.E621: + website = $"https://e621.net/post/index.json?limit=1000&tags={tag}"; + break; + case DapiSearchType.Danbooru: + website = $"http://danbooru.donmai.us/posts.json?limit=100&tags={tag}"; + break; + case DapiSearchType.Gelbooru: + website = $"http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}"; + break; + case DapiSearchType.Rule34: + website = $"https://rule34.xxx/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}"; + break; + case DapiSearchType.Konachan: + website = $"https://konachan.com/post.json?s=post&q=index&limit=100&tags={tag}"; + break; + case DapiSearchType.Yandere: + website = $"https://yande.re/post.json?limit=100&tags={tag}"; + break; + } + + if (type == DapiSearchType.Konachan || type == DapiSearchType.Yandere || + type == DapiSearchType.E621 || type == DapiSearchType.Danbooru) + { + var data = await _http.GetStringAsync(website).ConfigureAwait(false); + return JsonConvert.DeserializeObject(data) + .Where(x => x.File_Url != null) + .Select(x => new ImageCacherObject(x, type)) + .ToArray(); + } + + return (await LoadXmlAsync(website, type)).ToArray(); + } + + private async Task LoadXmlAsync(string website, DapiSearchType type) + { + var list = new List(); + using (var reader = XmlReader.Create(await _http.GetStreamAsync(website), new XmlReaderSettings() + { + Async = true, + })) + { + while (await reader.ReadAsync()) + { + if (reader.NodeType == XmlNodeType.Element && + reader.Name == "post") + { + list.Add(new ImageCacherObject(new DapiImageObject() + { + File_Url = reader["file_url"], + Tags = reader["tags"], + Rating = reader["rating"] ?? "e" + + }, type)); + } + } + } + return list.ToArray(); + } + + public void Clear() + { + _cache.Clear(); + } + } + + public class ImageCacherObject : IComparable + { + public DapiSearchType SearchType { get; } + public string FileUrl { get; } + public HashSet Tags { get; } + public string Rating { get; } + + public ImageCacherObject(DapiImageObject obj, DapiSearchType type) + { + if (type == DapiSearchType.Danbooru) + this.FileUrl = "https://danbooru.donmai.us" + obj.File_Url; + else + this.FileUrl = obj.File_Url.StartsWith("http") ? obj.File_Url : "https:" + obj.File_Url; + this.SearchType = type; + this.Rating = obj.Rating; + this.Tags = new HashSet((obj.Tags ?? obj.Tag_String).Split(' ')); + } + + public override string ToString() + { + return FileUrl; + } + + public int CompareTo(ImageCacherObject other) + { + return FileUrl.CompareTo(other.FileUrl); + } + } + + public class DapiImageObject + { + public string File_Url { get; set; } + public string Tags { get; set; } + public string Tag_String { get; set; } + public string Rating { get; set; } + } + + public enum DapiSearchType + { + Safebooru, + E621, + Gelbooru, + Konachan, + Rule34, + Yandere, + Danbooru + } +} diff --git a/src/NadekoBot/Services/Searches/Models/SearchPokemon.cs b/src/NadekoBot/Modules/Searches/Common/SearchPokemon.cs similarity index 97% rename from src/NadekoBot/Services/Searches/Models/SearchPokemon.cs rename to src/NadekoBot/Modules/Searches/Common/SearchPokemon.cs index 4fd0674d..29de4e00 100644 --- a/src/NadekoBot/Services/Searches/Models/SearchPokemon.cs +++ b/src/NadekoBot/Modules/Searches/Common/SearchPokemon.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace NadekoBot.Services.Searches +namespace NadekoBot.Modules.Searches.Common { public class SearchPokemon { diff --git a/src/NadekoBot/Services/Searches/Models/StreamResponses.cs b/src/NadekoBot/Modules/Searches/Common/StreamResponses.cs similarity index 95% rename from src/NadekoBot/Services/Searches/Models/StreamResponses.cs rename to src/NadekoBot/Modules/Searches/Common/StreamResponses.cs index 484a4962..95168773 100644 --- a/src/NadekoBot/Services/Searches/Models/StreamResponses.cs +++ b/src/NadekoBot/Modules/Searches/Common/StreamResponses.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace NadekoBot.Services.Searches +namespace NadekoBot.Modules.Searches.Common { public class HitboxResponse { diff --git a/src/NadekoBot/Modules/Searches/Commands/Models/TimeModels.cs b/src/NadekoBot/Modules/Searches/Common/TimeModels.cs similarity index 94% rename from src/NadekoBot/Modules/Searches/Commands/Models/TimeModels.cs rename to src/NadekoBot/Modules/Searches/Common/TimeModels.cs index f30571fc..83eb4437 100644 --- a/src/NadekoBot/Modules/Searches/Commands/Models/TimeModels.cs +++ b/src/NadekoBot/Modules/Searches/Common/TimeModels.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace NadekoBot.Modules.Searches.Commands.Models +namespace NadekoBot.Modules.Searches.Common { public class TimeZoneResult { diff --git a/src/NadekoBot/Modules/Searches/Common/WeatherModels.cs b/src/NadekoBot/Modules/Searches/Common/WeatherModels.cs new file mode 100644 index 00000000..c7552a90 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Common/WeatherModels.cs @@ -0,0 +1,66 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace NadekoBot.Modules.Searches.Common +{ + public class Coord + { + public double Lon { get; set; } + public double Lat { get; set; } + } + + public class Weather + { + public int Id { get; set; } + public string Main { get; set; } + public string Description { get; set; } + public string Icon { get; set; } + } + + public class Main + { + public double Temp { get; set; } + public float Pressure { get; set; } + public float Humidity { get; set; } + [JsonProperty("temp_min")] + public double TempMin { get; set; } + [JsonProperty("temp_max")] + public double TempMax { get; set; } + } + + public class Wind + { + public double Speed { get; set; } + public double Deg { get; set; } + } + + public class Clouds + { + public int All { get; set; } + } + + public class Sys + { + public int Type { get; set; } + public int Id { get; set; } + public double Message { get; set; } + public string Country { get; set; } + public double Sunrise { get; set; } + public double Sunset { get; set; } + } + + public class WeatherData + { + public Coord Coord { get; set; } + public List Weather { get; set; } + public Main Main { get; set; } + public int Visibility { get; set; } + public Wind Wind { get; set; } + public Clouds Clouds { get; set; } + public int Dt { get; set; } + public Sys Sys { get; set; } + public int Id { get; set; } + public string Name { get; set; } + public int Cod { get; set; } + } +} diff --git a/src/NadekoBot/Modules/Searches/Commands/Models/WikipediaApiModel.cs b/src/NadekoBot/Modules/Searches/Common/WikipediaApiModel.cs similarity index 89% rename from src/NadekoBot/Modules/Searches/Commands/Models/WikipediaApiModel.cs rename to src/NadekoBot/Modules/Searches/Common/WikipediaApiModel.cs index ed961a2a..fd77e482 100644 --- a/src/NadekoBot/Modules/Searches/Commands/Models/WikipediaApiModel.cs +++ b/src/NadekoBot/Modules/Searches/Common/WikipediaApiModel.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Modules.Searches.Models +namespace NadekoBot.Modules.Searches.Common { public class WikipediaApiModel { diff --git a/src/NadekoBot/Services/Searches/Models/WoWJoke.cs b/src/NadekoBot/Modules/Searches/Common/WoWJoke.cs similarity index 81% rename from src/NadekoBot/Services/Searches/Models/WoWJoke.cs rename to src/NadekoBot/Modules/Searches/Common/WoWJoke.cs index d363b8b2..7e414505 100644 --- a/src/NadekoBot/Services/Searches/Models/WoWJoke.cs +++ b/src/NadekoBot/Modules/Searches/Common/WoWJoke.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Services.Searches +namespace NadekoBot.Modules.Searches.Common { public class WoWJoke { diff --git a/src/NadekoBot/Modules/Searches/FeedCommands.cs b/src/NadekoBot/Modules/Searches/FeedCommands.cs new file mode 100644 index 00000000..38fe6be6 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/FeedCommands.cs @@ -0,0 +1,106 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using Microsoft.SyndicationFeed.Rss; +using NadekoBot.Common.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Modules.Searches.Services; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Xml; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + [Group] + public class FeedCommands : NadekoSubmodule + { + private readonly DiscordSocketClient _client; + + public FeedCommands(DiscordSocketClient client) + { + _client = client; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.ManageMessages)] + public async Task Feed(string url, [Remainder] ITextChannel channel = null) + { + var success = Uri.TryCreate(url, UriKind.Absolute, out var uri) && + (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps); + if (success) + { + channel = channel ?? (ITextChannel)Context.Channel; + using (var xmlReader = XmlReader.Create(url, new XmlReaderSettings() { Async = true })) + { + var reader = new RssFeedReader(xmlReader); + try + { + await reader.Read(); + } + catch { success = false; } + } + + if (success) + { + success = _service.AddFeed(Context.Guild.Id, channel.Id, url); + if (success) + { + await ReplyConfirmLocalized("feed_added").ConfigureAwait(false); + return; + } + } + } + + await ReplyErrorLocalized("feed_not_valid").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.ManageMessages)] + public async Task FeedRemove(int index) + { + if (_service.RemoveFeed(Context.Guild.Id, --index)) + { + await ReplyConfirmLocalized("feed_removed").ConfigureAwait(false); + } + else + await ReplyErrorLocalized("feed_out_of_range").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.ManageMessages)] + public async Task FeedList() + { + var feeds = _service.GetFeeds(Context.Guild.Id); + + if (!feeds.Any()) + { + await Context.Channel.EmbedAsync(new EmbedBuilder() + .WithOkColor() + .WithDescription(GetText("feed_no_feed"))) + .ConfigureAwait(false); + return; + } + + await Context.Channel.SendPaginatedConfirmAsync(_client, 0, (cur) => + { + var embed = new EmbedBuilder() + .WithOkColor(); + var i = 0; + var fs = string.Join("\n", feeds.Skip(cur * 10) + .Take(10) + .Select(x => $"`{(cur * 10) + (++i)}.` <#{x.ChannelId}> {x.Url}")); + + return embed.WithDescription(fs); + + }, feeds.Count / 10); + } + } + } +} diff --git a/src/NadekoBot/Modules/Searches/JokeCommands.cs b/src/NadekoBot/Modules/Searches/JokeCommands.cs new file mode 100644 index 00000000..c7db2dbd --- /dev/null +++ b/src/NadekoBot/Modules/Searches/JokeCommands.cs @@ -0,0 +1,62 @@ +using Discord.Commands; +using NadekoBot.Extensions; +using NadekoBot.Modules.Searches.Services; +using System.Linq; +using System.Threading.Tasks; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + [Group] + public class JokeCommands : NadekoSubmodule + { + + [NadekoCommand, Usage, Description, Aliases] + public async Task Yomama() + { + await Context.Channel.SendConfirmAsync(await _service.GetYomamaJoke()).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task Randjoke() + { + var jokeInfo = await _service.GetRandomJoke(); + await Context.Channel.SendConfirmAsync("", jokeInfo.Text, footer: jokeInfo.BaseUri).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task ChuckNorris() + { + await Context.Channel.SendConfirmAsync(await _service.GetChuckNorrisJoke()).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task WowJoke() + { + if (!_service.WowJokes.Any()) + { + await ReplyErrorLocalized("jokes_not_loaded").ConfigureAwait(false); + return; + } + var joke = _service.WowJokes[new NadekoRandom().Next(0, _service.WowJokes.Count)]; + await Context.Channel.SendConfirmAsync(joke.Question, joke.Answer).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task MagicItem() + { + if (!_service.WowJokes.Any()) + { + await ReplyErrorLocalized("magicitems_not_loaded").ConfigureAwait(false); + return; + } + var item = _service.MagicItems[new NadekoRandom().Next(0, _service.MagicItems.Count)]; + + await Context.Channel.SendConfirmAsync("✨" + item.Name, item.Description).ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Modules/Searches/LoLCommands.cs b/src/NadekoBot/Modules/Searches/LoLCommands.cs new file mode 100644 index 00000000..39c86636 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/LoLCommands.cs @@ -0,0 +1,68 @@ +using Discord; +using NadekoBot.Extensions; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + private class ChampionNameComparer : IEqualityComparer + { + public bool Equals(JToken a, JToken b) => a["name"].ToString() == b["name"].ToString(); + + public int GetHashCode(JToken obj) => + obj["name"].GetHashCode(); + } + + private static readonly string[] trashTalk = { "Better ban your counters. You are going to carry the game anyway.", + "Go with the flow. Don't think. Just ban one of these.", + "DONT READ BELOW! Ban Urgot mid OP 100%. Im smurf Diamond 1.", + "Ask your teammates what would they like to play, and ban that.", + "If you consider playing teemo, do it. If you consider teemo, you deserve him.", + "Doesn't matter what you ban really. Enemy will ban your main and you will lose." }; + + private static readonly Lazy> champData = new Lazy>(() => + ((IDictionary)JObject.Parse(File.ReadAllText("data/lolchamps.json"))) + .ToDictionary(x => (int)x.Value["id"], x => x.Value["name"].ToString()), true); + + [NadekoCommand, Usage, Description, Aliases] + public async Task Lolban() + { + //const int showCount = 8; + try + { + using (var http = new HttpClient()) + { + var data = JArray.Parse(await http.GetStringAsync($"http://api.champion.gg/v2/champions?champData=general&limit=200&api_key={_creds.LoLApiKey}")); + + var champs = data.OrderByDescending(x => (double)x["banRate"]).Distinct(x => x["championId"]).Take(6); + + //_log.Info(string.Join("\n", champs.Select(x => x["championId"] + " | " + x["banRate"] + " | " + x["normalized"]["banRate"]))); + var eb = new EmbedBuilder().WithOkColor().WithTitle(Format.Underline(GetText("x_most_banned_champs", champs.Count()))); + foreach (var champ in champs) + { + var lChamp = champ; + if (!champData.Value.TryGetValue((int)champ["championId"], out var champName)) + champName = "UNKNOWN"; + eb.AddField(efb => efb.WithName(champName).WithValue(((double)lChamp["banRate"] * 100).ToString("F2") + "%").WithIsInline(true)); + } + + await Context.Channel.EmbedAsync(eb, Format.Italics(trashTalk[new NadekoRandom().Next(0, trashTalk.Length)])).ConfigureAwait(false); + } + } + catch (Exception ex) + { + _log.Warn(ex); + await ReplyErrorLocalized("something_went_wrong").ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs b/src/NadekoBot/Modules/Searches/MemegenCommands.cs similarity index 98% rename from src/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs rename to src/NadekoBot/Modules/Searches/MemegenCommands.cs index 1425436e..c74fe419 100644 --- a/src/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs +++ b/src/NadekoBot/Modules/Searches/MemegenCommands.cs @@ -4,10 +4,10 @@ using System.Collections.Immutable; using System.IO; using System.Linq; using System.Threading.Tasks; -using NadekoBot.Attributes; using System.Net.Http; using System.Text; using Discord.Commands; +using NadekoBot.Common.Attributes; using NadekoBot.Extensions; namespace NadekoBot.Modules.Searches diff --git a/src/NadekoBot/Modules/Searches/Commands/OsuCommands.cs b/src/NadekoBot/Modules/Searches/OsuCommands.cs similarity index 99% rename from src/NadekoBot/Modules/Searches/Commands/OsuCommands.cs rename to src/NadekoBot/Modules/Searches/OsuCommands.cs index 1fcc789e..b8f05df0 100644 --- a/src/NadekoBot/Modules/Searches/Commands/OsuCommands.cs +++ b/src/NadekoBot/Modules/Searches/OsuCommands.cs @@ -1,6 +1,5 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using Newtonsoft.Json.Linq; @@ -10,6 +9,7 @@ using System.IO; using System.Net.Http; using System.Text.RegularExpressions; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; namespace NadekoBot.Modules.Searches { diff --git a/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs b/src/NadekoBot/Modules/Searches/OverwatchCommands.cs similarity index 95% rename from src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs rename to src/NadekoBot/Modules/Searches/OverwatchCommands.cs index 98b7b583..210aca73 100644 --- a/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs +++ b/src/NadekoBot/Modules/Searches/OverwatchCommands.cs @@ -1,111 +1,111 @@ -using System; -using Discord; -using Discord.Commands; -using NadekoBot.Attributes; -using NadekoBot.Extensions; -using NadekoBot.Modules.Searches.Models; -using Newtonsoft.Json; -using System.Net.Http; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Searches -{ - public partial class Searches - { - [Group] - public class OverwatchCommands : NadekoSubmodule - { - public enum Region - { - Eu, - Us, - Kr - } - - [NadekoCommand, Usage, Description, Aliases] - public async Task Overwatch(Region region, [Remainder] string query = null) - { - if (string.IsNullOrWhiteSpace(query)) - return; - var battletag = query.Replace("#", "-"); - - await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); - var model = (await GetProfile(region, battletag))?.Stats; - - if (model != null) - { - if (model.Competitive == null) - { - var qp = model.Quickplay; - var embed = new EmbedBuilder() - .WithAuthor(eau => eau.WithName(query) - .WithUrl($"https://www.overbuff.com/players/pc/{battletag}") - .WithIconUrl("https://cdn.discordapp.com/attachments/155726317222887425/255653487512256512/YZ4w2ey.png")) - .WithThumbnailUrl(qp.OverallStats.avatar) - .AddField(fb => fb.WithName(GetText("level")).WithValue((qp.OverallStats.level + (qp.OverallStats.prestige * 100)).ToString()).WithIsInline(true)) - .AddField(fb => fb.WithName(GetText("quick_wins")).WithValue(qp.OverallStats.wins.ToString()).WithIsInline(true)) - .AddField(fb => fb.WithName(GetText("compet_rank")).WithValue("0").WithIsInline(true)) - .AddField(fb => fb.WithName(GetText("quick_playtime")).WithValue($"{qp.GameStats.timePlayed}hrs").WithIsInline(true)) - .WithOkColor(); - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); - } - else - { - var qp = model.Quickplay; - var compet = model.Competitive; - var embed = new EmbedBuilder() - .WithAuthor(eau => eau.WithName(query) - .WithUrl($"https://www.overbuff.com/players/pc/{battletag}") - .WithIconUrl(compet.OverallStats.rank_image)) - .WithThumbnailUrl(compet.OverallStats.avatar) - .AddField(fb => fb.WithName(GetText("level")).WithValue((qp.OverallStats.level + (qp.OverallStats.prestige * 100)).ToString()).WithIsInline(true)) - .AddField(fb => fb.WithName(GetText("quick_wins")).WithValue(qp.OverallStats.wins.ToString()).WithIsInline(true)) - .AddField(fb => fb.WithName(GetText("compet_wins")).WithValue(compet.OverallStats.wins.ToString()).WithIsInline(true)) - .AddField(fb => fb.WithName(GetText("compet_loses")).WithValue(compet.OverallStats.losses.ToString()).WithIsInline(true)) - .AddField(fb => fb.WithName(GetText("compet_played")).WithValue(compet.OverallStats.games.ToString()).WithIsInline(true)) - .AddField(fb => fb.WithName(GetText("compet_rank")).WithValue(compet.OverallStats.comprank.ToString()).WithIsInline(true)) - .AddField(fb => fb.WithName(GetText("compet_playtime")).WithValue(compet.GameStats.timePlayed + "hrs").WithIsInline(true)) - .AddField(fb => fb.WithName(GetText("quick_playtime")).WithValue(qp.GameStats.timePlayed.ToString("F1") + "hrs").WithIsInline(true)) - .WithColor(NadekoBot.OkColor); - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); - } - } - else - { - await ReplyErrorLocalized("ow_user_not_found").ConfigureAwait(false); - } - } - public async Task GetProfile(Region region, string battletag) - { - try - { - using (var handler = new HttpClientHandler()) - { - handler.ServerCertificateCustomValidationCallback = (x, y, z, e) => true; - using (var http = new HttpClient(handler)) - { - http.AddFakeHeaders(); - var url = $"https://owapi.nadekobot.me/api/v3/u/{battletag}/stats"; - var res = await http.GetStringAsync($"https://owapi.nadekobot.me/api/v3/u/{battletag}/stats"); - var model = JsonConvert.DeserializeObject(res); - switch (region) - { - case Region.Eu: - return model.Eu; - case Region.Kr: - return model.Kr; - default: - return model.Us; - } - } - } - } - catch (Exception ex) - { - _log.Warn(ex); - return null; - } - } - } - } +using System; +using Discord; +using Discord.Commands; +using NadekoBot.Extensions; +using Newtonsoft.Json; +using System.Net.Http; +using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Searches.Common; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + [Group] + public class OverwatchCommands : NadekoSubmodule + { + public enum Region + { + Eu, + Us, + Kr + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task Overwatch(Region region, [Remainder] string query = null) + { + if (string.IsNullOrWhiteSpace(query)) + return; + var battletag = query.Replace("#", "-"); + + await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); + var model = (await GetProfile(region, battletag))?.Stats; + + if (model != null) + { + if (model.Competitive == null) + { + var qp = model.Quickplay; + var embed = new EmbedBuilder() + .WithAuthor(eau => eau.WithName(query) + .WithUrl($"https://www.overbuff.com/players/pc/{battletag}") + .WithIconUrl("https://cdn.discordapp.com/attachments/155726317222887425/255653487512256512/YZ4w2ey.png")) + .WithThumbnailUrl(qp.OverallStats.avatar) + .AddField(fb => fb.WithName(GetText("level")).WithValue((qp.OverallStats.level + (qp.OverallStats.prestige * 100)).ToString()).WithIsInline(true)) + .AddField(fb => fb.WithName(GetText("quick_wins")).WithValue(qp.OverallStats.wins.ToString()).WithIsInline(true)) + .AddField(fb => fb.WithName(GetText("compet_rank")).WithValue("0").WithIsInline(true)) + .AddField(fb => fb.WithName(GetText("quick_playtime")).WithValue($"{qp.GameStats.timePlayed}hrs").WithIsInline(true)) + .WithOkColor(); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + else + { + var qp = model.Quickplay; + var compet = model.Competitive; + var embed = new EmbedBuilder() + .WithAuthor(eau => eau.WithName(query) + .WithUrl($"https://www.overbuff.com/players/pc/{battletag}") + .WithIconUrl(compet.OverallStats.rank_image)) + .WithThumbnailUrl(compet.OverallStats.avatar) + .AddField(fb => fb.WithName(GetText("level")).WithValue((qp.OverallStats.level + (qp.OverallStats.prestige * 100)).ToString()).WithIsInline(true)) + .AddField(fb => fb.WithName(GetText("quick_wins")).WithValue(qp.OverallStats.wins.ToString()).WithIsInline(true)) + .AddField(fb => fb.WithName(GetText("compet_wins")).WithValue(compet.OverallStats.wins.ToString()).WithIsInline(true)) + .AddField(fb => fb.WithName(GetText("compet_loses")).WithValue(compet.OverallStats.losses.ToString()).WithIsInline(true)) + .AddField(fb => fb.WithName(GetText("compet_played")).WithValue(compet.OverallStats.games.ToString() ?? "-").WithIsInline(true)) + .AddField(fb => fb.WithName(GetText("compet_rank")).WithValue(compet.OverallStats.comprank?.ToString() ?? "-").WithIsInline(true)) + .AddField(fb => fb.WithName(GetText("compet_playtime")).WithValue(compet.GameStats.timePlayed + "hrs").WithIsInline(true)) + .AddField(fb => fb.WithName(GetText("quick_playtime")).WithValue(qp.GameStats.timePlayed.ToString("F1") + "hrs").WithIsInline(true)) + .WithColor(NadekoBot.OkColor); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + } + else + { + await ReplyErrorLocalized("ow_user_not_found").ConfigureAwait(false); + } + } + public async Task GetProfile(Region region, string battletag) + { + try + { + using (var handler = new HttpClientHandler()) + { + handler.ServerCertificateCustomValidationCallback = (x, y, z, e) => true; + using (var http = new HttpClient(handler)) + { + http.AddFakeHeaders(); + var url = $"https://owapi.nadekobot.me/api/v3/u/{battletag}/stats"; + var res = await http.GetStringAsync($"https://owapi.nadekobot.me/api/v3/u/{battletag}/stats"); + var model = JsonConvert.DeserializeObject(res); + switch (region) + { + case Region.Eu: + return model.Eu; + case Region.Kr: + return model.Kr; + default: + return model.Us; + } + } + } + } + catch (Exception ex) + { + _log.Warn(ex); + return null; + } + } + } + } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Commands/PlaceCommands.cs b/src/NadekoBot/Modules/Searches/PlaceCommands.cs similarity index 97% rename from src/NadekoBot/Modules/Searches/Commands/PlaceCommands.cs rename to src/NadekoBot/Modules/Searches/PlaceCommands.cs index 61f5aec9..92260965 100644 --- a/src/NadekoBot/Modules/Searches/Commands/PlaceCommands.cs +++ b/src/NadekoBot/Modules/Searches/PlaceCommands.cs @@ -1,9 +1,9 @@ using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; -using NadekoBot.Services; using System; using System.Threading.Tasks; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; namespace NadekoBot.Modules.Searches { diff --git a/src/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs b/src/NadekoBot/Modules/Searches/PokemonSearchCommands.cs similarity index 88% rename from src/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs rename to src/NadekoBot/Modules/Searches/PokemonSearchCommands.cs index b457b02b..2ea5c498 100644 --- a/src/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs +++ b/src/NadekoBot/Modules/Searches/PokemonSearchCommands.cs @@ -1,28 +1,22 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; -using NadekoBot.Services.Searches; +using NadekoBot.Modules.Searches.Services; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Searches.Common; namespace NadekoBot.Modules.Searches { public partial class Searches { [Group] - public class PokemonSearchCommands : NadekoSubmodule + public class PokemonSearchCommands : NadekoSubmodule { - private readonly SearchesService _searches; - - public Dictionary Pokemons => _searches.Pokemons; - public Dictionary PokemonAbilities => _searches.PokemonAbilities; - - public PokemonSearchCommands(SearchesService searches) - { - _searches = searches; - } + public Dictionary Pokemons => _service.Pokemons; + public Dictionary PokemonAbilities => _service.PokemonAbilities; [NadekoCommand, Usage, Description, Aliases] public async Task Pokemon([Remainder] string pokemon = null) diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index b7886f44..0738414a 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -7,34 +7,31 @@ using System.Net.Http; using NadekoBot.Services; using System.Threading.Tasks; using System.Net; -using NadekoBot.Modules.Searches.Models; using System.Collections.Generic; using NadekoBot.Extensions; using System.IO; -using NadekoBot.Modules.Searches.Commands.OMDB; -using NadekoBot.Modules.Searches.Commands.Models; using AngleSharp; using AngleSharp.Dom.Html; using AngleSharp.Dom; using Configuration = AngleSharp.Configuration; -using NadekoBot.Attributes; using Discord.Commands; using ImageSharp; -using NadekoBot.Services.Searches; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Searches.Common; +using NadekoBot.Modules.Searches.Services; namespace NadekoBot.Modules.Searches { - public partial class Searches : NadekoTopLevelModule + public partial class Searches : NadekoTopLevelModule { private readonly IBotCredentials _creds; private readonly IGoogleApiService _google; - private readonly SearchesService _searches; - public Searches(IBotCredentials creds, IGoogleApiService google, SearchesService searches) + public Searches(IBotCredentials creds, IGoogleApiService google) { _creds = creds; _google = google; - _searches = searches; } [NadekoCommand, Usage, Description, Aliases] @@ -44,23 +41,22 @@ namespace NadekoBot.Modules.Searches return; string response; - using (var http = new HttpClient()) - response = await http.GetStringAsync($"http://api.openweathermap.org/data/2.5/weather?q={query}&appid=42cd627dd60debf25a5739e50a217d74&units=metric").ConfigureAwait(false); + response = await _service.Http.GetStringAsync($"http://api.openweathermap.org/data/2.5/weather?q={query}&appid=42cd627dd60debf25a5739e50a217d74&units=metric").ConfigureAwait(false); var data = JsonConvert.DeserializeObject(response); var embed = new EmbedBuilder() - .AddField(fb => fb.WithName("🌍 " + Format.Bold(GetText("location"))).WithValue($"[{data.name + ", " + data.sys.country}](https://openweathermap.org/city/{data.id})").WithIsInline(true)) - .AddField(fb => fb.WithName("📏 " + Format.Bold(GetText("latlong"))).WithValue($"{data.coord.lat}, {data.coord.lon}").WithIsInline(true)) - .AddField(fb => fb.WithName("☁ " + Format.Bold(GetText("condition"))).WithValue(string.Join(", ", data.weather.Select(w => w.main))).WithIsInline(true)) - .AddField(fb => fb.WithName("😓 " + Format.Bold(GetText("humidity"))).WithValue($"{data.main.humidity}%").WithIsInline(true)) - .AddField(fb => fb.WithName("💨 " + Format.Bold(GetText("wind_speed"))).WithValue(data.wind.speed + " m/s").WithIsInline(true)) - .AddField(fb => fb.WithName("🌡 " + Format.Bold(GetText("temperature"))).WithValue(data.main.temp + "°C").WithIsInline(true)) - .AddField(fb => fb.WithName("🔆 " + Format.Bold(GetText("min_max"))).WithValue($"{data.main.temp_min}°C - {data.main.temp_max}°C").WithIsInline(true)) - .AddField(fb => fb.WithName("🌄 " + Format.Bold(GetText("sunrise"))).WithValue($"{data.sys.sunrise.ToUnixTimestamp():HH:mm} UTC").WithIsInline(true)) - .AddField(fb => fb.WithName("🌇 " + Format.Bold(GetText("sunset"))).WithValue($"{data.sys.sunset.ToUnixTimestamp():HH:mm} UTC").WithIsInline(true)) + .AddField(fb => fb.WithName("🌍 " + Format.Bold(GetText("location"))).WithValue($"[{data.Name + ", " + data.Sys.Country}](https://openweathermap.org/city/{data.Id})").WithIsInline(true)) + .AddField(fb => fb.WithName("📏 " + Format.Bold(GetText("latlong"))).WithValue($"{data.Coord.Lat}, {data.Coord.Lon}").WithIsInline(true)) + .AddField(fb => fb.WithName("☁ " + Format.Bold(GetText("condition"))).WithValue(string.Join(", ", data.Weather.Select(w => w.Main))).WithIsInline(true)) + .AddField(fb => fb.WithName("😓 " + Format.Bold(GetText("humidity"))).WithValue($"{data.Main.Humidity}%").WithIsInline(true)) + .AddField(fb => fb.WithName("💨 " + Format.Bold(GetText("wind_speed"))).WithValue(data.Wind.Speed + " m/s").WithIsInline(true)) + .AddField(fb => fb.WithName("🌡 " + Format.Bold(GetText("temperature"))).WithValue(data.Main.Temp + "°C").WithIsInline(true)) + .AddField(fb => fb.WithName("🔆 " + Format.Bold(GetText("min_max"))).WithValue($"{data.Main.TempMin}°C - {data.Main.TempMax}°C").WithIsInline(true)) + .AddField(fb => fb.WithName("🌄 " + Format.Bold(GetText("sunrise"))).WithValue($"{data.Sys.Sunrise.ToUnixTimestamp():HH:mm} UTC").WithIsInline(true)) + .AddField(fb => fb.WithName("🌇 " + Format.Bold(GetText("sunset"))).WithValue($"{data.Sys.Sunset.ToUnixTimestamp():HH:mm} UTC").WithIsInline(true)) .WithOkColor() - .WithFooter(efb => efb.WithText("Powered by openweathermap.org").WithIconUrl($"http://openweathermap.org/img/w/{data.weather[0].icon}.png")); + .WithFooter(efb => efb.WithText("Powered by openweathermap.org").WithIconUrl($"http://openweathermap.org/img/w/{data.Weather[0].Icon}.png")); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } @@ -70,19 +66,17 @@ namespace NadekoBot.Modules.Searches if (string.IsNullOrWhiteSpace(arg) || string.IsNullOrWhiteSpace(_creds.GoogleApiKey)) return; - using (var http = new HttpClient()) - { - var res = await http.GetStringAsync($"https://maps.googleapis.com/maps/api/geocode/json?address={arg}&key={_creds.GoogleApiKey}").ConfigureAwait(false); - var obj = JsonConvert.DeserializeObject(res); + var res = await _service.Http.GetStringAsync($"https://maps.googleapis.com/maps/api/geocode/json?address={arg}&key={_creds.GoogleApiKey}").ConfigureAwait(false); + var obj = JsonConvert.DeserializeObject(res); - var currentSeconds = DateTime.UtcNow.UnixTimestamp(); - var timeRes = await http.GetStringAsync($"https://maps.googleapis.com/maps/api/timezone/json?location={obj.results[0].Geometry.Location.Lat},{obj.results[0].Geometry.Location.Lng}×tamp={currentSeconds}&key={_creds.GoogleApiKey}").ConfigureAwait(false); - var timeObj = JsonConvert.DeserializeObject(timeRes); + var currentSeconds = DateTime.UtcNow.UnixTimestamp(); + var timeRes = await _service.Http.GetStringAsync($"https://maps.googleapis.com/maps/api/timezone/json?location={obj.results[0].Geometry.Location.Lat},{obj.results[0].Geometry.Location.Lng}×tamp={currentSeconds}&key={_creds.GoogleApiKey}").ConfigureAwait(false); + var timeObj = JsonConvert.DeserializeObject(timeRes); - var time = DateTime.UtcNow.AddSeconds(timeObj.DstOffset + timeObj.RawOffset); + var time = DateTime.UtcNow.AddSeconds(timeObj.DstOffset + timeObj.RawOffset); + + await ReplyConfirmLocalized("time", Format.Bold(obj.results[0].FormattedAddress), Format.Code(time.ToString("HH:mm")), timeObj.TimeZoneName).ConfigureAwait(false); - await ReplyConfirmLocalized("time", Format.Bold(obj.results[0].FormattedAddress), Format.Code(time.ToString("HH:mm")), timeObj.TimeZoneName).ConfigureAwait(false); - } } [NadekoCommand, Usage, Description, Aliases] @@ -117,21 +111,15 @@ namespace NadekoBot.Modules.Searches [NadekoCommand, Usage, Description, Aliases] public async Task RandomCat() { - using (var http = new HttpClient()) - { - var res = JObject.Parse(await http.GetStringAsync("http://www.random.cat/meow").ConfigureAwait(false)); - await Context.Channel.SendMessageAsync(Uri.EscapeUriString(res["file"].ToString())).ConfigureAwait(false); - } + var res = JObject.Parse(await _service.Http.GetStringAsync("http://www.random.cat/meow").ConfigureAwait(false)); + await Context.Channel.SendMessageAsync(Uri.EscapeUriString(res["file"].ToString())).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] public async Task RandomDog() { - using (var http = new HttpClient()) - { - await Context.Channel.SendMessageAsync("http://random.dog/" + await http.GetStringAsync("http://random.dog/woof") - .ConfigureAwait(false)).ConfigureAwait(false); - } + await Context.Channel.SendMessageAsync("http://random.dog/" + await _service.Http.GetStringAsync("http://random.dog/woof") + .ConfigureAwait(false)).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -393,7 +381,7 @@ namespace NadekoBot.Modules.Searches try { var items = JArray.Parse(response).Shuffle().ToList(); - var images = new List(); + var images = new List>(); if (items == null) throw new KeyNotFoundException("Cannot find a card by that name"); foreach (var item in items.Where(item => item.HasValues && item["img"] != null).Take(4)) @@ -405,7 +393,7 @@ namespace NadekoBot.Modules.Searches var imgStream = new MemoryStream(); await sr.CopyToAsync(imgStream); imgStream.Position = 0; - images.Add(new ImageSharp.Image(imgStream)); + images.Add(ImageSharp.Image.Load(imgStream)); } }).ConfigureAwait(false); } @@ -415,7 +403,7 @@ namespace NadekoBot.Modules.Searches msg = GetText("hs_over_x", 4); } var ms = new MemoryStream(); - await Task.Run(() => images.AsEnumerable().Merge().Save(ms)); + await Task.Run(() => images.AsEnumerable().Merge().SaveAsPng(ms)); ms.Position = 0; await Context.Channel.SendFileAsync(ms, arg + ".png", msg).ConfigureAwait(false); } @@ -506,34 +494,32 @@ namespace NadekoBot.Modules.Searches if (string.IsNullOrWhiteSpace(word)) return; - using (var http = new HttpClient()) + var res = await _service.Http.GetStringAsync("http://api.pearson.com/v2/dictionaries/entries?headword=" + WebUtility.UrlEncode(word.Trim())).ConfigureAwait(false); + + var data = JsonConvert.DeserializeObject(res); + + var sense = data.Results.FirstOrDefault(x => x.Senses?[0].Definition != null)?.Senses[0]; + + if (sense?.Definition == null) { - var res = await http.GetStringAsync("http://api.pearson.com/v2/dictionaries/entries?headword=" + WebUtility.UrlEncode(word.Trim())).ConfigureAwait(false); - - var data = JsonConvert.DeserializeObject(res); - - var sense = data.Results.FirstOrDefault(x => x.Senses?[0].Definition != null)?.Senses[0]; - - if (sense?.Definition == null) - { - await ReplyErrorLocalized("define_unknown").ConfigureAwait(false); - return; - } - - var definition = sense.Definition.ToString(); - if (!(sense.Definition is string)) - definition = ((JArray)JToken.Parse(sense.Definition.ToString())).First.ToString(); - - var embed = new EmbedBuilder().WithOkColor() - .WithTitle(GetText("define") + " " + word) - .WithDescription(definition) - .WithFooter(efb => efb.WithText(sense.Gramatical_info?.type)); - - if (sense.Examples != null) - embed.AddField(efb => efb.WithName(GetText("example")).WithValue(sense.Examples.First().text)); - - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + await ReplyErrorLocalized("define_unknown").ConfigureAwait(false); + return; } + + var definition = sense.Definition.ToString(); + if (!(sense.Definition is string)) + definition = ((JArray)JToken.Parse(sense.Definition.ToString())).First.ToString(); + + var embed = new EmbedBuilder().WithOkColor() + .WithTitle(GetText("define") + " " + word) + .WithDescription(definition) + .WithFooter(efb => efb.WithText(sense.Gramatical_info?.type)); + + if (sense.Examples != null) + embed.AddField(efb => efb.WithName(GetText("example")).WithValue(sense.Examples.First().text)); + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } [NadekoCommand, Usage, Description, Aliases] @@ -580,15 +566,12 @@ namespace NadekoBot.Modules.Searches [NadekoCommand, Usage, Description, Aliases] public async Task Catfact() { - using (var http = new HttpClient()) - { - var response = await http.GetStringAsync("http://catfacts-api.appspot.com/api/facts").ConfigureAwait(false); - if (response == null) - return; + var response = await _service.Http.GetStringAsync("https://catfact.ninja/fact").ConfigureAwait(false); + if (response == null) + return; - var fact = JObject.Parse(response)["facts"][0].ToString(); - await Context.Channel.SendConfirmAsync("🐈" + GetText("catfact"), fact).ConfigureAwait(false); - } + var fact = JObject.Parse(response)["fact"].ToString(); + await Context.Channel.SendConfirmAsync("🐈" + GetText("catfact"), fact).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -637,10 +620,10 @@ namespace NadekoBot.Modules.Searches color = color?.Trim().Replace("#", ""); if (string.IsNullOrWhiteSpace(color)) return; - ImageSharp.Color clr; + Rgba32 clr; try { - clr = ImageSharp.Color.FromHex(color); + clr = Rgba32.FromHex(color); } catch { @@ -649,7 +632,7 @@ namespace NadekoBot.Modules.Searches } - var img = new ImageSharp.Image(50, 50); + var img = new ImageSharp.Image(50, 50); img.BackgroundColor(clr); @@ -666,7 +649,7 @@ namespace NadekoBot.Modules.Searches str += new NadekoRandom().Next(); foreach (var usr in allUsrsArray) { - await (await usr.CreateDMChannelAsync()).SendConfirmAsync(str).ConfigureAwait(false); + await (await usr.GetOrCreateDMChannelAsync()).SendConfirmAsync(str).ConfigureAwait(false); } } @@ -791,14 +774,14 @@ namespace NadekoBot.Modules.Searches tag = tag?.Trim() ?? ""; - var url = await _searches.DapiSearch(tag, type).ConfigureAwait(false); + var imgObj = await _service.DapiSearch(tag, type, Context.Guild?.Id).ConfigureAwait(false); - if (url == null) + if (imgObj == null) await channel.SendErrorAsync(umsg.Author.Mention + " " + GetText("no_results")); else await channel.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithDescription($"{umsg.Author.Mention} [{tag}]({url})") - .WithImageUrl(url) + .WithDescription($"{umsg.Author.Mention} [{tag ?? "url"}]({imgObj.FileUrl})") + .WithImageUrl(imgObj.FileUrl) .WithFooter(efb => efb.WithText(type.ToString()))).ConfigureAwait(false); } diff --git a/src/NadekoBot/Modules/Searches/Services/AnimeSearchService.cs b/src/NadekoBot/Modules/Searches/Services/AnimeSearchService.cs new file mode 100644 index 00000000..1a372d8c --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Services/AnimeSearchService.cs @@ -0,0 +1,74 @@ +using NadekoBot.Services; +using Newtonsoft.Json; +using NLog; +using System; +using System.Net.Http; +using System.Threading.Tasks; +using NadekoBot.Modules.Searches.Common; + +namespace NadekoBot.Modules.Searches.Services +{ + public class AnimeSearchService : INService + { + private readonly Logger _log; + private readonly IDataCache _cache; + private readonly HttpClient _http; + + public AnimeSearchService(IDataCache cache) + { + _log = LogManager.GetCurrentClassLogger(); + _cache = cache; + _http = new HttpClient(); + } + + public async Task GetAnimeData(string query) + { + if (string.IsNullOrWhiteSpace(query)) + throw new ArgumentNullException(nameof(query)); + try + { + + var link = "https://aniapi.nadekobot.me/anime/" + Uri.EscapeDataString(query.Replace("/", " ")); + link = link.ToLowerInvariant(); + var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false); + if (!ok) + { + data = await _http.GetStringAsync(link).ConfigureAwait(false); + await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false); + } + + + return JsonConvert.DeserializeObject(data); + } + catch + { + return null; + } + } + + public async Task GetMangaData(string query) + { + if (string.IsNullOrWhiteSpace(query)) + throw new ArgumentNullException(nameof(query)); + try + { + + var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " ")); + link = link.ToLowerInvariant(); + var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false); + if (!ok) + { + data = await _http.GetStringAsync(link).ConfigureAwait(false); + await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false); + } + + + return JsonConvert.DeserializeObject(data); + } + catch + { + return null; + } + } + } +} diff --git a/src/NadekoBot/Modules/Searches/Services/FeedsService.cs b/src/NadekoBot/Modules/Searches/Services/FeedsService.cs new file mode 100644 index 00000000..33ac0ff9 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Services/FeedsService.cs @@ -0,0 +1,213 @@ +using Discord; +using Microsoft.SyndicationFeed; +using Microsoft.SyndicationFeed.Rss; +using NadekoBot.Extensions; +using NadekoBot.Services; +using System; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml; +using System.Collections.Generic; +using NadekoBot.Services.Database.Models; +using Microsoft.EntityFrameworkCore; +using System.Collections.Concurrent; +using Discord.WebSocket; + +namespace NadekoBot.Modules.Searches.Services +{ + public class FeedsService : INService + { + private readonly DbService _db; + private readonly ConcurrentDictionary> _subs; + private readonly DiscordSocketClient _client; + private readonly ConcurrentDictionary _lastPosts = + new ConcurrentDictionary(); + + public FeedsService(IEnumerable gcs, DbService db, DiscordSocketClient client) + { + _db = db; + + _subs = gcs.SelectMany(x => x.FeedSubs) + .GroupBy(x => x.Url) + .ToDictionary(x => x.Key, x => x.ToHashSet()) + .ToConcurrent(); + + _client = client; + + foreach (var kvp in _subs) + { + // to make sure rss feeds don't post right away, but + // only the updates from after the bot has started + _lastPosts.AddOrUpdate(kvp.Key, DateTime.UtcNow, (k, old) => DateTime.UtcNow); + } + + var _ = Task.Run(TrackFeeds); + } + + public async Task TrackFeeds() + { + while (true) + { + foreach (var kvp in _subs) + { + if (kvp.Value.Count == 0) + continue; + + DateTime lastTime; + if (!_lastPosts.TryGetValue(kvp.Key, out lastTime)) + lastTime = _lastPosts.AddOrUpdate(kvp.Key, DateTime.UtcNow, (k, old) => DateTime.UtcNow); + + var rssUrl = kvp.Key; + try + { + using (var xmlReader = XmlReader.Create(rssUrl, new XmlReaderSettings() { Async = true })) + { + var feedReader = new RssFeedReader(xmlReader); + + var embed = new EmbedBuilder() + .WithAuthor(kvp.Key) + .WithOkColor(); + + while (await feedReader.Read() && feedReader.ElementType != SyndicationElementType.Item) + { + switch (feedReader.ElementType) + { + case SyndicationElementType.Link: + var uri = await feedReader.ReadLink(); + embed.WithAuthor(kvp.Key, url: uri.Uri.AbsoluteUri); + break; + case SyndicationElementType.Content: + var content = await feedReader.ReadContent(); + break; + case SyndicationElementType.Category: + break; + case SyndicationElementType.Image: + ISyndicationImage image = await feedReader.ReadImage(); + embed.WithThumbnailUrl(image.Url.AbsoluteUri); + break; + default: + break; + } + } + + ISyndicationItem item = await feedReader.ReadItem(); + if (item.Published.UtcDateTime <= lastTime) + continue; + + var desc = item.Description.StripHTML(); + + lastTime = item.Published.UtcDateTime; + var title = string.IsNullOrWhiteSpace(item.Title) ? "-" : item.Title; + desc = Format.Code(item.Published.ToString()) + Environment.NewLine + desc; + var link = item.Links.FirstOrDefault(); + if (link != null) + desc = $"[link]({link.Uri}) " + desc; + + var img = item.Links.FirstOrDefault(x => x.RelationshipType == "enclosure")?.Uri.AbsoluteUri + ?? Regex.Match(item.Description, @"src=""(?.*?)""").Groups["src"].ToString(); + + if (!string.IsNullOrWhiteSpace(img) && Uri.IsWellFormedUriString(img, UriKind.Absolute)) + embed.WithImageUrl(img); + + embed.AddField(title, desc); + + //send the created embed to all subscribed channels + var sendTasks = kvp.Value + .Where(x => x.GuildConfig != null) + .Select(x => _client.GetGuild(x.GuildConfig.GuildId) + ?.GetTextChannel(x.ChannelId)) + .Where(x => x != null) + .Select(x => x.EmbedAsync(embed)); + + _lastPosts.AddOrUpdate(kvp.Key, item.Published.UtcDateTime, (k, old) => item.Published.UtcDateTime); + + await Task.WhenAll(sendTasks).ConfigureAwait(false); + } + } + catch { } + } + + await Task.Delay(10000); + } + } + + public List GetFeeds(ulong guildId) + { + using (var uow = _db.UnitOfWork) + { + return uow.GuildConfigs.For(guildId, set => set.Include(x => x.FeedSubs)) + .FeedSubs + .OrderBy(x => x.Id) + .ToList(); + } + } + + public bool AddFeed(ulong guildId, ulong channelId, string rssFeed) + { + rssFeed.ThrowIfNull(nameof(rssFeed)); + + var fs = new FeedSub() + { + ChannelId = channelId, + Url = rssFeed.Trim().ToLowerInvariant(), + }; + + using (var uow = _db.UnitOfWork) + { + var gc = uow.GuildConfigs.For(guildId, set => set.Include(x => x.FeedSubs)); + + if (gc.FeedSubs.Contains(fs)) + { + return false; + } + else if (gc.FeedSubs.Count >= 10) + { + return false; + } + + gc.FeedSubs.Add(fs); + + //adding all, in case bot wasn't on this guild when it started + foreach (var f in gc.FeedSubs) + { + _subs.AddOrUpdate(f.Url, new HashSet(), (k, old) => + { + old.Add(f); + return old; + }); + } + + uow.Complete(); + } + + return true; + } + + public bool RemoveFeed(ulong guildId, int index) + { + if (index < 0) + return false; + + using (var uow = _db.UnitOfWork) + { + var items = uow.GuildConfigs.For(guildId, set => set.Include(x => x.FeedSubs)) + .FeedSubs + .OrderBy(x => x.Id) + .ToList(); + + if (items.Count <= index) + return false; + var toRemove = items[index]; + _subs.AddOrUpdate(toRemove.Url, new HashSet(), (key, old) => + { + old.Remove(toRemove); + return old; + }); + uow._context.Remove(toRemove); + uow.Complete(); + } + return true; + } + } +} diff --git a/src/NadekoBot/Services/Searches/SearchesService.cs b/src/NadekoBot/Modules/Searches/Services/SearchesService.cs similarity index 54% rename from src/NadekoBot/Services/Searches/SearchesService.cs rename to src/NadekoBot/Modules/Searches/Services/SearchesService.cs index 864c9522..be785bf3 100644 --- a/src/NadekoBot/Services/Searches/SearchesService.cs +++ b/src/NadekoBot/Modules/Searches/Services/SearchesService.cs @@ -1,21 +1,30 @@ using Discord; using Discord.WebSocket; using NadekoBot.Extensions; +using NadekoBot.Services; using Newtonsoft.Json; using NLog; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; -using System.Net.Http; using System.Threading.Tasks; -using System.Xml; +using NadekoBot.Modules.Searches.Common; +using NadekoBot.Services.Database.Models; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using NadekoBot.Modules.NSFW.Exceptions; +using System.Net.Http; +using Newtonsoft.Json.Linq; +using AngleSharp; -namespace NadekoBot.Services.Searches +namespace NadekoBot.Modules.Searches.Services { - public class SearchesService + public class SearchesService : INService { - private readonly DiscordShardedClient _client; + public HttpClient Http { get; } + + private readonly DiscordSocketClient _client; private readonly IGoogleApiService _google; private readonly DbService _db; private readonly Logger _log; @@ -31,13 +40,24 @@ namespace NadekoBot.Services.Searches public List WowJokes { get; } = new List(); public List MagicItems { get; } = new List(); - public SearchesService(DiscordShardedClient client, IGoogleApiService google, DbService db) + private readonly ConcurrentDictionary _imageCacher = new ConcurrentDictionary(); + + private readonly ConcurrentDictionary> _blacklistedTags = new ConcurrentDictionary>(); + + public SearchesService(DiscordSocketClient client, IGoogleApiService google, DbService db, IEnumerable gcs) { + Http = new HttpClient(); + Http.AddFakeHeaders(); _client = client; _google = google; _db = db; _log = LogManager.GetCurrentClassLogger(); + _blacklistedTags = new ConcurrentDictionary>( + gcs.ToDictionary( + x => x.GuildId, + x => new HashSet(x.NsfwBlacklistedTags.Select(y => y.Tag)))); + //translate commands _client.MessageReceived += (msg) => { @@ -113,63 +133,95 @@ namespace NadekoBot.Services.Searches return (await _google.Translate(text, from, to).ConfigureAwait(false)).SanitizeMentions(); } - public async Task DapiSearch(string tag, DapiSearchType type) + public Task DapiSearch(string tag, DapiSearchType type, ulong? guild, bool isExplicit = false) { - tag = tag?.Replace(" ", "_"); - var website = ""; - switch (type) + if (guild.HasValue) { - case DapiSearchType.Safebooru: - website = $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}"; - break; - case DapiSearchType.Gelbooru: - website = $"http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}"; - break; - case DapiSearchType.Rule34: - website = $"https://rule34.xxx/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}"; - break; - case DapiSearchType.Konachan: - website = $"https://konachan.com/post.xml?s=post&q=index&limit=100&tags={tag}"; - break; - case DapiSearchType.Yandere: - website = $"https://yande.re/post.xml?limit=100&tags={tag}"; - break; - } - try - { - var toReturn = await Task.Run(async () => + var blacklistedTags = GetBlacklistedTags(guild.Value); + + if (blacklistedTags + .Any(x => tag.ToLowerInvariant().Contains(x))) { - using (var http = new HttpClient()) - { - http.AddFakeHeaders(); - var data = await http.GetStreamAsync(website).ConfigureAwait(false); - var doc = new XmlDocument(); - doc.Load(data); + throw new TagBlacklistedException(); + } - var node = doc.LastChild.ChildNodes[new NadekoRandom().Next(0, doc.LastChild.ChildNodes.Count)]; + var cacher = _imageCacher.GetOrAdd(guild.Value, (key) => new SearchImageCacher()); - var url = node.Attributes["file_url"].Value; - if (!url.StartsWith("http")) - url = "https:" + url; - return url; - } - }).ConfigureAwait(false); - return toReturn; + return cacher.GetImage(tag, isExplicit, type, blacklistedTags); } - catch + else { - return null; + var cacher = _imageCacher.GetOrAdd(guild ?? 0, (key) => new SearchImageCacher()); + + return cacher.GetImage(tag, isExplicit, type); } } - } - public enum DapiSearchType - { - Safebooru, - Gelbooru, - Konachan, - Rule34, - Yandere + public HashSet GetBlacklistedTags(ulong guildId) + { + if (_blacklistedTags.TryGetValue(guildId, out var tags)) + return tags; + return new HashSet(); + } + + public bool ToggleBlacklistedTag(ulong guildId, string tag) + { + var tagObj = new NsfwBlacklitedTag + { + Tag = tag + }; + + bool added; + using (var uow = _db.UnitOfWork) + { + var gc = uow.GuildConfigs.For(guildId, set => set.Include(y => y.NsfwBlacklistedTags)); + if (gc.NsfwBlacklistedTags.Add(tagObj)) + added = true; + else + { + gc.NsfwBlacklistedTags.Remove(tagObj); + added = false; + } + var newTags = new HashSet(gc.NsfwBlacklistedTags.Select(x => x.Tag)); + _blacklistedTags.AddOrUpdate(guildId, newTags, delegate { return newTags; }); + + uow.Complete(); + } + return added; + } + + public void ClearCache() + { + foreach (var c in _imageCacher) + { + c.Value?.Clear(); + } + } + + public async Task GetYomamaJoke() + { + var response = await Http.GetStringAsync("http://api.yomomma.info/").ConfigureAwait(false); + return JObject.Parse(response)["joke"].ToString() + " 😆"; + } + + public async Task<(string Text, string BaseUri)> GetRandomJoke() + { + var config = Configuration.Default.WithDefaultLoader(); + var document = await BrowsingContext.New(config).OpenAsync("http://www.goodbadjokes.com/random"); + + var html = document.QuerySelector(".post > .joke-content"); + + var part1 = html.QuerySelector("dt").TextContent; + var part2 = html.QuerySelector("dd").TextContent; + + return (part1 + "\n\n" + part2, document.BaseUri); + } + + public async Task GetChuckNorrisJoke() + { + var response = await Http.GetStringAsync("http://api.icndb.com/jokes/random/").ConfigureAwait(false); + return JObject.Parse(response)["value"]["joke"].ToString() + " 😆"; + } } public struct UserChannelPair @@ -178,7 +230,6 @@ namespace NadekoBot.Services.Searches public ulong ChannelId { get; set; } } - public class StreamStatus { public bool IsLive { get; set; } diff --git a/src/NadekoBot/Services/Searches/StreamNotificationService.cs b/src/NadekoBot/Modules/Searches/Services/StreamNotificationService.cs similarity index 87% rename from src/NadekoBot/Services/Searches/StreamNotificationService.cs rename to src/NadekoBot/Modules/Searches/Services/StreamNotificationService.cs index 3fdbb9ac..679e59a5 100644 --- a/src/NadekoBot/Services/Searches/StreamNotificationService.cs +++ b/src/NadekoBot/Modules/Searches/Services/StreamNotificationService.cs @@ -1,6 +1,7 @@ using Discord; using Discord.WebSocket; using NadekoBot.Extensions; +using NadekoBot.Services; using NadekoBot.Services.Database.Models; using Newtonsoft.Json; using System; @@ -10,20 +11,23 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using NadekoBot.Modules.Searches.Common; +using NadekoBot.Modules.Searches.Common.Exceptions; +using NadekoBot.Services.Impl; -namespace NadekoBot.Services.Searches +namespace NadekoBot.Modules.Searches.Services { - public class StreamNotificationService + public class StreamNotificationService : INService { private readonly Timer _streamCheckTimer; private bool firstStreamNotifPass { get; set; } = true; private readonly ConcurrentDictionary _cachedStatuses = new ConcurrentDictionary(); private readonly DbService _db; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly NadekoStrings _strings; - public StreamNotificationService(DbService db, DiscordShardedClient client, NadekoStrings strings) + public StreamNotificationService(DbService db, DiscordSocketClient client, NadekoStrings strings) { _db = db; _client = client; @@ -35,7 +39,7 @@ namespace NadekoBot.Services.Searches IEnumerable streams; using (var uow = _db.UnitOfWork) { - streams = uow.GuildConfigs.GetAllFollowedStreams(); + streams = uow.GuildConfigs.GetAllFollowedStreams(client.Guilds.Select(x => (long)x.Id).ToList()); } await Task.WhenAll(streams.Select(async fs => @@ -73,7 +77,7 @@ namespace NadekoBot.Services.Searches })); firstStreamNotifPass = false; - }, null, TimeSpan.Zero, TimeSpan.FromSeconds(60)); + }, null, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60)); } public async Task GetStreamStatus(FollowedStream stream, bool checkCache = true) @@ -82,7 +86,7 @@ namespace NadekoBot.Services.Searches StreamStatus result; switch (stream.Type) { - case FollowedStream.FollowedStreamType.Hitbox: + case FollowedStream.FollowedStreamType.Smashcast: var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username.ToLowerInvariant()}"; if (checkCache && _cachedStatuses.TryGetValue(hitboxUrl, out result)) return result; @@ -122,8 +126,8 @@ namespace NadekoBot.Services.Searches }; _cachedStatuses.AddOrUpdate(twitchUrl, result, (key, old) => result); return result; - case FollowedStream.FollowedStreamType.Beam: - var beamUrl = $"https://beam.pro/api/v1/channels/{stream.Username.ToLowerInvariant()}"; + case FollowedStream.FollowedStreamType.Mixer: + var beamUrl = $"https://mixer.com/api/v1/channels/{stream.Username.ToLowerInvariant()}"; if (checkCache && _cachedStatuses.TryGetValue(beamUrl, out result)) return result; using (var http = new HttpClient()) @@ -174,12 +178,12 @@ namespace NadekoBot.Services.Searches public string GetLink(FollowedStream fs) { - if (fs.Type == FollowedStream.FollowedStreamType.Hitbox) - return $"http://www.hitbox.tv/{fs.Username}/"; + if (fs.Type == FollowedStream.FollowedStreamType.Smashcast) + return $"https://www.hitbox.tv/{fs.Username}/"; if (fs.Type == FollowedStream.FollowedStreamType.Twitch) - return $"http://www.twitch.tv/{fs.Username}/"; - if (fs.Type == FollowedStream.FollowedStreamType.Beam) - return $"https://beam.pro/{fs.Username}/"; + return $"https://www.twitch.tv/{fs.Username}/"; + if (fs.Type == FollowedStream.FollowedStreamType.Mixer) + return $"https://www.mixer.com/{fs.Username}/"; return "??"; } } diff --git a/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs b/src/NadekoBot/Modules/Searches/StreamNotificationCommands.cs similarity index 93% rename from src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs rename to src/NadekoBot/Modules/Searches/StreamNotificationCommands.cs index 30e18523..0dab151a 100644 --- a/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs +++ b/src/NadekoBot/Modules/Searches/StreamNotificationCommands.cs @@ -5,32 +5,30 @@ using System.Threading.Tasks; using NadekoBot.Services; using System.Collections.Generic; using NadekoBot.Services.Database.Models; -using NadekoBot.Attributes; using Microsoft.EntityFrameworkCore; +using NadekoBot.Common.Attributes; using NadekoBot.Extensions; -using NadekoBot.Services.Searches; +using NadekoBot.Modules.Searches.Services; namespace NadekoBot.Modules.Searches { public partial class Searches { [Group] - public class StreamNotificationCommands : NadekoSubmodule + public class StreamNotificationCommands : NadekoSubmodule { private readonly DbService _db; - private readonly StreamNotificationService _service; - public StreamNotificationCommands(DbService db, StreamNotificationService service) + public StreamNotificationCommands(DbService db) { _db = db; - _service = service; } [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.ManageMessages)] - public async Task Hitbox([Remainder] string username) => - await TrackStream((ITextChannel)Context.Channel, username, FollowedStream.FollowedStreamType.Hitbox) + public async Task Smashcast([Remainder] string username) => + await TrackStream((ITextChannel)Context.Channel, username, FollowedStream.FollowedStreamType.Smashcast) .ConfigureAwait(false); [NadekoCommand, Usage, Description, Aliases] @@ -43,8 +41,8 @@ namespace NadekoBot.Modules.Searches [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.ManageMessages)] - public async Task Beam([Remainder] string username) => - await TrackStream((ITextChannel)Context.Channel, username, FollowedStream.FollowedStreamType.Beam) + public async Task Mixer([Remainder] string username) => + await TrackStream((ITextChannel)Context.Channel, username, FollowedStream.FollowedStreamType.Mixer) .ConfigureAwait(false); [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Modules/Searches/Commands/Translator.cs b/src/NadekoBot/Modules/Searches/TranslatorCommands.cs similarity index 79% rename from src/NadekoBot/Modules/Searches/Commands/Translator.cs rename to src/NadekoBot/Modules/Searches/TranslatorCommands.cs index 1dfca6d7..9f4960fe 100644 --- a/src/NadekoBot/Modules/Searches/Commands/Translator.cs +++ b/src/NadekoBot/Modules/Searches/TranslatorCommands.cs @@ -1,11 +1,11 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using System.Threading.Tasks; using System.Linq; -using NadekoBot.Services.Searches; +using NadekoBot.Common.Attributes; using NadekoBot.Services; +using NadekoBot.Modules.Searches.Services; namespace NadekoBot.Modules.Searches { @@ -38,6 +38,27 @@ namespace NadekoBot.Modules.Searches } } + //[NadekoCommand, Usage, Description, Aliases] + //[OwnerOnly] + //public async Task Obfuscate([Remainder] string txt) + //{ + // var lastItem = "en"; + // foreach (var item in _google.Languages.Except(new[] { "en" }).Where(x => x.Length < 4)) + // { + // var txt2 = await _searches.Translate(lastItem + ">" + item, txt); + // await Context.Channel.EmbedAsync(new EmbedBuilder() + // .WithOkColor() + // .WithTitle(lastItem + ">" + item) + // .AddField("Input", txt) + // .AddField("Output", txt2)); + // txt = txt2; + // await Task.Delay(500); + // lastItem = item; + // } + // txt = await _searches.Translate(lastItem + ">en", txt); + // await Context.Channel.SendConfirmAsync("Final output:\n\n" + txt); + //} + public enum AutoDeleteAutoTranslate { Del, diff --git a/src/NadekoBot/Modules/Searches/Commands/XkcdCommands.cs b/src/NadekoBot/Modules/Searches/XkcdCommands.cs similarity index 98% rename from src/NadekoBot/Modules/Searches/Commands/XkcdCommands.cs rename to src/NadekoBot/Modules/Searches/XkcdCommands.cs index a4b031a3..d5027656 100644 --- a/src/NadekoBot/Modules/Searches/Commands/XkcdCommands.cs +++ b/src/NadekoBot/Modules/Searches/XkcdCommands.cs @@ -1,11 +1,11 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; -using NadekoBot.Services; using Newtonsoft.Json; using System.Net.Http; using System.Threading.Tasks; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; namespace NadekoBot.Modules.Searches { @@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Searches private const string _xkcdUrl = "https://xkcd.com"; [NadekoCommand, Usage, Description, Aliases] - [Priority(1)] + [Priority(0)] public async Task Xkcd(string arg = null) { if (arg?.ToLowerInvariant().Trim() == "latest") @@ -44,7 +44,7 @@ namespace NadekoBot.Modules.Searches } [NadekoCommand, Usage, Description, Aliases] - [Priority(0)] + [Priority(1)] public async Task Xkcd(int num) { if (num < 1) diff --git a/src/NadekoBot/Modules/Utility/BotConfigCommands.cs b/src/NadekoBot/Modules/Utility/BotConfigCommands.cs new file mode 100644 index 00000000..cc91dd9f --- /dev/null +++ b/src/NadekoBot/Modules/Utility/BotConfigCommands.cs @@ -0,0 +1,46 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; +using NadekoBot.Services; +using System; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Utility +{ + public partial class Utility + { + public class BotConfigCommands : NadekoSubmodule + { + private readonly IBotConfigProvider _service; + + public BotConfigCommands(IBotConfigProvider service) + { + _service = service; + } + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public async Task BotConfigEdit() + { + var names = Enum.GetNames(typeof(BotConfigEditType)); + await ReplyAsync(string.Join(", ", names)).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public async Task BotConfigEdit(BotConfigEditType type, [Remainder]string newValue = null) + { + if (string.IsNullOrWhiteSpace(newValue)) + newValue = null; + + var success = _service.Edit(type, newValue); + + if (!success) + await ReplyErrorLocalized("bot_config_edit_fail", Format.Bold(type.ToString()), Format.Bold(newValue ?? "NULL")).ConfigureAwait(false); + else + await ReplyConfirmLocalized("bot_config_edit_success", Format.Bold(type.ToString()), Format.Bold(newValue ?? "NULL")).ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Modules/Utility/Commands/CalcCommand.cs b/src/NadekoBot/Modules/Utility/CalcCommands.cs similarity index 98% rename from src/NadekoBot/Modules/Utility/Commands/CalcCommand.cs rename to src/NadekoBot/Modules/Utility/CalcCommands.cs index 7617cdc9..a1944972 100644 --- a/src/NadekoBot/Modules/Utility/Commands/CalcCommand.cs +++ b/src/NadekoBot/Modules/Utility/CalcCommands.cs @@ -1,11 +1,11 @@ using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; namespace NadekoBot.Modules.Utility { diff --git a/src/NadekoBot/Modules/Utility/Commands/CommandMapCommands.cs b/src/NadekoBot/Modules/Utility/CommandMapCommands.cs similarity index 87% rename from src/NadekoBot/Modules/Utility/Commands/CommandMapCommands.cs rename to src/NadekoBot/Modules/Utility/CommandMapCommands.cs index d38cfb90..9903dedf 100644 --- a/src/NadekoBot/Modules/Utility/Commands/CommandMapCommands.cs +++ b/src/NadekoBot/Modules/Utility/CommandMapCommands.cs @@ -2,30 +2,28 @@ using Discord.Commands; using Discord.WebSocket; using Microsoft.EntityFrameworkCore; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using NadekoBot.Services.Database.Models; -using NadekoBot.Services.Utility; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Utility.Services; namespace NadekoBot.Modules.Utility { public partial class Utility { [Group] - public class CommandMapCommands : NadekoSubmodule + public class CommandMapCommands : NadekoSubmodule { - private readonly CommandMapService _service; private readonly DbService _db; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public CommandMapCommands(CommandMapService service, DbService db, DiscordShardedClient client) + public CommandMapCommands(DbService db, DiscordSocketClient client) { - _service = service; _db = db; _client = client; } @@ -44,10 +42,8 @@ namespace NadekoBot.Modules.Utility if (string.IsNullOrWhiteSpace(mapping)) { - ConcurrentDictionary maps; - string throwaway; - if (!_service.AliasMaps.TryGetValue(Context.Guild.Id, out maps) || - !maps.TryRemove(trigger, out throwaway)) + if (!_service.AliasMaps.TryGetValue(Context.Guild.Id, out var maps) || + !maps.TryRemove(trigger, out _)) { await ReplyErrorLocalized("alias_remove_fail", Format.Code(trigger)).ConfigureAwait(false); return; @@ -114,9 +110,8 @@ namespace NadekoBot.Modules.Utility if (page < 0) return; - - ConcurrentDictionary maps; - if (!_service.AliasMaps.TryGetValue(Context.Guild.Id, out maps) || !maps.Any()) + + if (!_service.AliasMaps.TryGetValue(Context.Guild.Id, out var maps) || !maps.Any()) { await ReplyErrorLocalized("aliases_none").ConfigureAwait(false); return; diff --git a/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs b/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs deleted file mode 100644 index de917054..00000000 --- a/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Attributes; -using NadekoBot.Extensions; -using NadekoBot.Services; -using NadekoBot.Services.Utility; -using System.Collections.Concurrent; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Utility -{ - public partial class Utility - { - [Group] - public class CrossServerTextChannel : NadekoSubmodule - { - private readonly CrossServerTextService _service; - - public CrossServerTextChannel(CrossServerTextService service) - { - _service = service; - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [OwnerOnly] - public async Task Scsc() - { - var token = new NadekoRandom().Next(); - var set = new ConcurrentHashSet(); - if (_service.Subscribers.TryAdd(token, set)) - { - set.Add((ITextChannel) Context.Channel); - await ((IGuildUser) Context.User).SendConfirmAsync(GetText("csc_token"), token.ToString()) - .ConfigureAwait(false); - } - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [RequireUserPermission(GuildPermission.ManageGuild)] - public async Task Jcsc(int token) - { - ConcurrentHashSet set; - if (!_service.Subscribers.TryGetValue(token, out set)) - return; - set.Add((ITextChannel) Context.Channel); - await ReplyConfirmLocalized("csc_join").ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [RequireUserPermission(GuildPermission.ManageGuild)] - public async Task Lcsc() - { - foreach (var subscriber in _service.Subscribers) - { - subscriber.Value.TryRemove((ITextChannel) Context.Channel); - } - await ReplyConfirmLocalized("csc_leave").ConfigureAwait(false); - } - } - } -} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRoleNotFoundException.cs b/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRoleNotFoundException.cs new file mode 100644 index 00000000..d6ac31cf --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRoleNotFoundException.cs @@ -0,0 +1,11 @@ +using System; + +namespace NadekoBot.Modules.Utility.Common.Exceptions +{ + public class StreamRoleNotFoundException : Exception + { + public StreamRoleNotFoundException() : base("Stream role wasn't found.") + { + } + } +} diff --git a/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRolePermissionException.cs b/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRolePermissionException.cs new file mode 100644 index 00000000..b0fd0dbe --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRolePermissionException.cs @@ -0,0 +1,11 @@ +using System; + +namespace NadekoBot.Modules.Utility.Common.Exceptions +{ + public class StreamRolePermissionException : Exception + { + public StreamRolePermissionException() : base("Stream role was unable to be applied.") + { + } + } +} diff --git a/src/NadekoBot/Services/Utility/Patreon/PatreonData.cs b/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonData.cs similarity index 90% rename from src/NadekoBot/Services/Utility/Patreon/PatreonData.cs rename to src/NadekoBot/Modules/Utility/Common/Patreon/PatreonData.cs index c57ea817..748c7170 100644 --- a/src/NadekoBot/Services/Utility/Patreon/PatreonData.cs +++ b/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonData.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json.Linq; -namespace NadekoBot.Services.Utility.Patreon +namespace NadekoBot.Modules.Utility.Common.Patreon { public class PatreonData { diff --git a/src/NadekoBot/Services/Utility/Patreon/PatreonPledge.cs b/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonPledge.cs similarity index 96% rename from src/NadekoBot/Services/Utility/Patreon/PatreonPledge.cs rename to src/NadekoBot/Modules/Utility/Common/Patreon/PatreonPledge.cs index 7193718c..9960039f 100644 --- a/src/NadekoBot/Services/Utility/Patreon/PatreonPledge.cs +++ b/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonPledge.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Services.Utility.Patreon +namespace NadekoBot.Modules.Utility.Common.Patreon { public class Attributes { diff --git a/src/NadekoBot/Services/Utility/Patreon/PatreonUser.cs b/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonUser.cs similarity index 97% rename from src/NadekoBot/Services/Utility/Patreon/PatreonUser.cs rename to src/NadekoBot/Modules/Utility/Common/Patreon/PatreonUser.cs index f054544b..e4d16869 100644 --- a/src/NadekoBot/Services/Utility/Patreon/PatreonUser.cs +++ b/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonUser.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Services.Utility.Patreon +namespace NadekoBot.Modules.Utility.Common.Patreon { public class DiscordConnection { diff --git a/src/NadekoBot/Services/Utility/RepeatRunner.cs b/src/NadekoBot/Modules/Utility/Common/RepeatRunner.cs similarity index 91% rename from src/NadekoBot/Services/Utility/RepeatRunner.cs rename to src/NadekoBot/Modules/Utility/Common/RepeatRunner.cs index 8c96dc8b..bf21d317 100644 --- a/src/NadekoBot/Services/Utility/RepeatRunner.cs +++ b/src/NadekoBot/Modules/Utility/Common/RepeatRunner.cs @@ -1,14 +1,14 @@ -using Discord; +using System; +using System.Threading; +using System.Threading.Tasks; +using Discord; using Discord.Net; using Discord.WebSocket; using NadekoBot.Extensions; using NadekoBot.Services.Database.Models; using NLog; -using System; -using System.Threading; -using System.Threading.Tasks; -namespace NadekoBot.Services.Utility +namespace NadekoBot.Modules.Utility.Common { public class RepeatRunner { @@ -22,18 +22,15 @@ namespace NadekoBot.Services.Utility private IUserMessage oldMsg = null; private Timer _t; - public RepeatRunner(DiscordShardedClient client, Repeater repeater) + public RepeatRunner(DiscordSocketClient client, SocketGuild guild, Repeater repeater) { _log = LogManager.GetCurrentClassLogger(); Repeater = repeater; - - //todo 40 @.@ fix all of this - Guild = client.GetGuild(repeater.GuildId); + Guild = guild; InitialInterval = Repeater.Interval; - if (Guild != null) - Run(); + Run(); } private void Run() diff --git a/src/NadekoBot/Modules/Utility/Common/StreamRoleListType.cs b/src/NadekoBot/Modules/Utility/Common/StreamRoleListType.cs new file mode 100644 index 00000000..4281744b --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Common/StreamRoleListType.cs @@ -0,0 +1,8 @@ +namespace NadekoBot.Modules.Utility.Common +{ + public enum StreamRoleListType + { + Whitelist, + Blacklist, + } +} diff --git a/src/NadekoBot/Modules/Utility/Extensions/StreamRoleExtensions.cs b/src/NadekoBot/Modules/Utility/Extensions/StreamRoleExtensions.cs new file mode 100644 index 00000000..eaf16e84 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Extensions/StreamRoleExtensions.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Database.Repositories; + +namespace NadekoBot.Modules.Utility.Extensions +{ + public static class StreamRoleExtensions + { + /// + /// Gets full stream role settings for the guild with the specified id. + /// + /// + /// Id of the guild to get stream role settings for. + /// + public static StreamRoleSettings GetStreamRoleSettings(this IGuildConfigRepository gc, ulong guildId) + { + var conf = gc.For(guildId, x => x.Include(y => y.StreamRole) + .Include(y => y.StreamRole.Whitelist) + .Include(y => y.StreamRole.Blacklist)); + + if (conf.StreamRole == null) + conf.StreamRole = new StreamRoleSettings(); + + return conf.StreamRole; + } + } +} diff --git a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/src/NadekoBot/Modules/Utility/InfoCommands.cs similarity index 91% rename from src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs rename to src/NadekoBot/Modules/Utility/InfoCommands.cs index 3a5b8fe8..95a3f6b1 100644 --- a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ b/src/NadekoBot/Modules/Utility/InfoCommands.cs @@ -1,13 +1,13 @@ using Discord; using Discord.Commands; using Discord.WebSocket; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using System; using System.Linq; using System.Text; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; namespace NadekoBot.Modules.Utility { @@ -16,10 +16,10 @@ namespace NadekoBot.Modules.Utility [Group] public class InfoCommands : NadekoSubmodule { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IStatsService _stats; - public InfoCommands(DiscordShardedClient client, IStatsService stats, CommandHandler ch) + public InfoCommands(DiscordSocketClient client, IStatsService stats, CommandHandler ch) { _client = client; _stats = stats; @@ -31,19 +31,18 @@ namespace NadekoBot.Modules.Utility { var channel = (ITextChannel)Context.Channel; guildName = guildName?.ToUpperInvariant(); - IGuild guild; + SocketGuild guild; if (string.IsNullOrWhiteSpace(guildName)) - guild = channel.Guild; + guild = (SocketGuild)channel.Guild; else guild = _client.Guilds.FirstOrDefault(g => g.Name.ToUpperInvariant() == guildName.ToUpperInvariant()); if (guild == null) return; - var ownername = await guild.GetUserAsync(guild.OwnerId); - var textchn = (await guild.GetTextChannelsAsync()).Count(); - var voicechn = (await guild.GetVoiceChannelsAsync()).Count(); + var ownername = guild.GetUser(guild.OwnerId); + var textchn = guild.TextChannels.Count(); + var voicechn = guild.VoiceChannels.Count(); var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(guild.Id >> 22); - var users = await guild.GetUsersAsync().ConfigureAwait(false); var features = string.Join("\n", guild.Features); if (string.IsNullOrWhiteSpace(features)) features = "-"; @@ -52,15 +51,16 @@ namespace NadekoBot.Modules.Utility .WithTitle(guild.Name) .AddField(fb => fb.WithName(GetText("id")).WithValue(guild.Id.ToString()).WithIsInline(true)) .AddField(fb => fb.WithName(GetText("owner")).WithValue(ownername.ToString()).WithIsInline(true)) - .AddField(fb => fb.WithName(GetText("members")).WithValue(users.Count.ToString()).WithIsInline(true)) + .AddField(fb => fb.WithName(GetText("members")).WithValue(guild.MemberCount.ToString()).WithIsInline(true)) .AddField(fb => fb.WithName(GetText("text_channels")).WithValue(textchn.ToString()).WithIsInline(true)) .AddField(fb => fb.WithName(GetText("voice_channels")).WithValue(voicechn.ToString()).WithIsInline(true)) .AddField(fb => fb.WithName(GetText("created_at")).WithValue($"{createdAt:dd.MM.yyyy HH:mm}").WithIsInline(true)) .AddField(fb => fb.WithName(GetText("region")).WithValue(guild.VoiceRegionId.ToString()).WithIsInline(true)) .AddField(fb => fb.WithName(GetText("roles")).WithValue((guild.Roles.Count - 1).ToString()).WithIsInline(true)) .AddField(fb => fb.WithName(GetText("features")).WithValue(features).WithIsInline(true)) - .WithImageUrl(guild.IconUrl) .WithColor(NadekoBot.OkColor); + if (Uri.IsWellFormedUriString(guild.IconUrl, UriKind.Absolute)) + embed.WithImageUrl(guild.IconUrl); if (guild.Emotes.Any()) { embed.AddField(fb => fb.WithName(GetText("custom_emojis") + $"({guild.Emotes.Count})").WithValue(string.Join(" ", guild.Emotes.Shuffle().Take(20).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>")))); diff --git a/src/NadekoBot/Modules/Utility/Commands/PatreonCommands.cs b/src/NadekoBot/Modules/Utility/PatreonCommands.cs similarity index 75% rename from src/NadekoBot/Modules/Utility/Commands/PatreonCommands.cs rename to src/NadekoBot/Modules/Utility/PatreonCommands.cs index 509419fd..96ac04c4 100644 --- a/src/NadekoBot/Modules/Utility/Commands/PatreonCommands.cs +++ b/src/NadekoBot/Modules/Utility/PatreonCommands.cs @@ -1,49 +1,51 @@ using System.Threading.Tasks; using Discord.Commands; -using NadekoBot.Attributes; using System; using NadekoBot.Services; -using NadekoBot.Services.Database.Models; using NadekoBot.Extensions; using Discord; -using NadekoBot.Services.Utility; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Utility.Services; namespace NadekoBot.Modules.Utility { public partial class Utility { [Group] - public class PatreonCommands : NadekoSubmodule + public class PatreonCommands : NadekoSubmodule { - private readonly PatreonRewardsService _patreon; private readonly IBotCredentials _creds; - private readonly BotConfig _config; + private readonly IBotConfigProvider _config; private readonly DbService _db; private readonly CurrencyService _currency; - public PatreonCommands(PatreonRewardsService p, IBotCredentials creds, BotConfig config, DbService db, CurrencyService currency) + public PatreonCommands(IBotCredentials creds, IBotConfigProvider config, DbService db, CurrencyService currency) { _creds = creds; _config = config; _db = db; _currency = currency; - _patreon = p; } [NadekoCommand, Usage, Description, Aliases] [OwnerOnly] + [RequireContext(ContextType.DM)] public async Task PatreonRewardsReload() { - await _patreon.LoadPledges().ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken)) + return; + await _service.RefreshPledges(true).ConfigureAwait(false); await Context.Channel.SendConfirmAsync("👌").ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.DM)] public async Task ClaimPatreonRewards() { if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken)) return; + if (DateTime.UtcNow.Day < 5) { await ReplyErrorLocalized("clpa_too_early").ConfigureAwait(false); @@ -52,7 +54,7 @@ namespace NadekoBot.Modules.Utility int amount = 0; try { - amount = await _patreon.ClaimReward(Context.User.Id).ConfigureAwait(false); + amount = await _service.ClaimReward(Context.User.Id).ConfigureAwait(false); } catch (Exception ex) { @@ -61,10 +63,10 @@ namespace NadekoBot.Modules.Utility if (amount > 0) { - await ReplyConfirmLocalized("clpa_success", amount + _config.CurrencySign).ConfigureAwait(false); + await ReplyConfirmLocalized("clpa_success", amount + _config.BotConfig.CurrencySign).ConfigureAwait(false); return; } - var rem = (_patreon.Interval - (DateTime.UtcNow - _patreon.LastUpdate)); + var rem = (_service.Interval - (DateTime.UtcNow - _service.LastUpdate)); var helpcmd = Format.Code(Prefix + "donate"); await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithDescription(GetText("clpa_fail")) diff --git a/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs b/src/NadekoBot/Modules/Utility/QuoteCommands.cs similarity index 84% rename from src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs rename to src/NadekoBot/Modules/Utility/QuoteCommands.cs index 5ac2e69a..8b4a6752 100644 --- a/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs +++ b/src/NadekoBot/Modules/Utility/QuoteCommands.cs @@ -1,14 +1,14 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using NadekoBot.Services.Database.Models; -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using NadekoBot.DataStructures; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.Replacements; namespace NadekoBot.Modules.Utility { @@ -66,22 +66,18 @@ namespace NadekoBot.Modules.Utility if (quote == null) return; - CREmbed crembed; - if (CREmbed.TryParse(quote.Text, out crembed)) + var rep = new ReplacementBuilder() + .WithDefault(Context) + .Build(); + + if (CREmbed.TryParse(quote.Text, out var crembed)) { - try - { - await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "") - .ConfigureAwait(false); - } - catch (Exception ex) - { - _log.Warn("Sending CREmbed failed"); - _log.Warn(ex); - } + rep.Replace(crembed); + await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? "") + .ConfigureAwait(false); return; } - await Context.Channel.SendMessageAsync($"`#{quote.Id}` 📣 " + quote.Text.SanitizeMentions()); + await Context.Channel.SendMessageAsync($"`#{quote.Id}` 📣 " + rep.Replace(quote.Text)?.SanitizeMentions()); } [NadekoCommand, Usage, Description, Aliases] @@ -118,29 +114,28 @@ namespace NadekoBot.Modules.Utility using (var uow = _db.UnitOfWork) { var qfromid = uow.Quotes.Get(id); - CREmbed crembed; - + + var rep = new ReplacementBuilder() + .WithDefault(Context) + .Build(); + if (qfromid == null) { await Context.Channel.SendErrorAsync(GetText("quotes_notfound")); } - else if (CREmbed.TryParse(qfromid.Text, out crembed)) + else if (CREmbed.TryParse(qfromid.Text, out var crembed)) { - try - { - await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "") - .ConfigureAwait(false); - } - catch (Exception ex) - { - _log.Warn("Sending CREmbed failed"); - _log.Warn(ex); - } - return; + rep.Replace(crembed); + + await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? "") + .ConfigureAwait(false); } - - else { await Context.Channel.SendMessageAsync($"`#{qfromid.Id}` 🗯️ " + qfromid.Keyword.ToLowerInvariant().SanitizeMentions() + ": " + - qfromid.Text.SanitizeMentions()); } + else + { + await Context.Channel.SendMessageAsync($"`#{qfromid.Id}` 🗯️ " + qfromid.Keyword.ToLowerInvariant().SanitizeMentions() + ": " + + rep.Replace(qfromid.Text)?.SanitizeMentions()); + } + } } diff --git a/src/NadekoBot/Modules/Utility/Commands/Remind.cs b/src/NadekoBot/Modules/Utility/RemindCommands.cs similarity index 91% rename from src/NadekoBot/Modules/Utility/Commands/Remind.cs rename to src/NadekoBot/Modules/Utility/RemindCommands.cs index 448ec424..caa13aed 100644 --- a/src/NadekoBot/Modules/Utility/Commands/Remind.cs +++ b/src/NadekoBot/Modules/Utility/RemindCommands.cs @@ -1,29 +1,29 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using NadekoBot.Services.Database.Models; -using NadekoBot.Services.Utility; using System; using System.Collections.Generic; -using System.Text.RegularExpressions; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Administration.Services; +using NadekoBot.Modules.Utility.Services; namespace NadekoBot.Modules.Utility { public partial class Utility { [Group] - public class RemindCommands : NadekoSubmodule + public class RemindCommands : NadekoSubmodule { - private readonly RemindService _service; private readonly DbService _db; + private readonly GuildTimezoneService _tz; - public RemindCommands(RemindService service, DbService db) + public RemindCommands(DbService db, GuildTimezoneService tz) { - _service = service; _db = db; + _tz = tz; } public enum MeOrHere @@ -119,6 +119,7 @@ namespace NadekoBot.Modules.Utility await uow.CompleteAsync(); } + var gTime = TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(Context.Guild.Id)); try { await Context.Channel.SendConfirmAsync( @@ -126,7 +127,7 @@ namespace NadekoBot.Modules.Utility Format.Bold(!isPrivate ? $"<#{targetId}>" : Context.User.Username), Format.Bold(message.SanitizeMentions()), Format.Bold(output), - time, time)).ConfigureAwait(false); + gTime, gTime)).ConfigureAwait(false); } catch { diff --git a/src/NadekoBot/Modules/Utility/Commands/RepeatCommands.cs b/src/NadekoBot/Modules/Utility/RepeatCommands.cs similarity index 93% rename from src/NadekoBot/Modules/Utility/Commands/RepeatCommands.cs rename to src/NadekoBot/Modules/Utility/RepeatCommands.cs index 6561712d..921c0746 100644 --- a/src/NadekoBot/Modules/Utility/Commands/RepeatCommands.cs +++ b/src/NadekoBot/Modules/Utility/RepeatCommands.cs @@ -1,7 +1,6 @@ using Discord; using Discord.Commands; using Microsoft.EntityFrameworkCore; -using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; using NadekoBot.Services.Database.Models; @@ -11,23 +10,23 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Discord.WebSocket; -using NadekoBot.Services.Utility; -using NadekoBot.TypeReaders; +using NadekoBot.Common.Attributes; +using NadekoBot.Common.TypeReaders; +using NadekoBot.Modules.Utility.Common; +using NadekoBot.Modules.Utility.Services; namespace NadekoBot.Modules.Utility { public partial class Utility { [Group] - public class RepeatCommands : NadekoSubmodule + public class RepeatCommands : NadekoSubmodule { - private readonly MessageRepeaterService _service; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly DbService _db; - public RepeatCommands(MessageRepeaterService service, DiscordShardedClient client, DbService db) + public RepeatCommands(DiscordSocketClient client, DbService db) { - _service = service; _client = client; _db = db; } @@ -63,7 +62,6 @@ namespace NadekoBot.Modules.Utility [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.ManageMessages)] - [Priority(0)] public async Task RepeatRemove(int index) { if (!_service.RepeaterReady) @@ -103,7 +101,7 @@ namespace NadekoBot.Modules.Utility [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.ManageMessages)] - [Priority(1)] + [Priority(0)] public async Task Repeat(int minutes, [Remainder] string message) { if (!_service.RepeaterReady) @@ -133,7 +131,7 @@ namespace NadekoBot.Modules.Utility await uow.CompleteAsync().ConfigureAwait(false); } - var rep = new RepeatRunner(_client, toAdd); + var rep = new RepeatRunner(_client, (SocketGuild)Context.Guild, toAdd); _service.Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue(new[] {rep}), (key, old) => { @@ -152,7 +150,7 @@ namespace NadekoBot.Modules.Utility [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.ManageMessages)] - [Priority(0)] + [Priority(1)] public async Task Repeat(GuildDateTime gt, [Remainder] string message) { if (!_service.RepeaterReady) @@ -181,7 +179,7 @@ namespace NadekoBot.Modules.Utility await uow.CompleteAsync().ConfigureAwait(false); } - var rep = new RepeatRunner(_client, toAdd); + var rep = new RepeatRunner(_client, (SocketGuild)Context.Guild, toAdd); _service.Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue(new[] { rep }), (key, old) => { diff --git a/src/NadekoBot/Services/Utility/CommandMapService.cs b/src/NadekoBot/Modules/Utility/Services/CommandMapService.cs similarity index 92% rename from src/NadekoBot/Services/Utility/CommandMapService.cs rename to src/NadekoBot/Modules/Utility/Services/CommandMapService.cs index 9fe6e546..2aa1c419 100644 --- a/src/NadekoBot/Services/Utility/CommandMapService.cs +++ b/src/NadekoBot/Modules/Utility/Services/CommandMapService.cs @@ -1,16 +1,17 @@ -using NadekoBot.DataStructures.ModuleBehaviors; -using NadekoBot.Services.Database.Models; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Discord; -using NLog; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; -namespace NadekoBot.Services.Utility +namespace NadekoBot.Modules.Utility.Services { - public class CommandMapService : IInputTransformer + public class CommandMapService : IInputTransformer, INService { private readonly Logger _log; diff --git a/src/NadekoBot/Modules/Utility/Services/ConverterService.cs b/src/NadekoBot/Modules/Utility/Services/ConverterService.cs new file mode 100644 index 00000000..49bfd43e --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Services/ConverterService.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Discord.WebSocket; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using Newtonsoft.Json; +using NLog; + +namespace NadekoBot.Modules.Utility.Services +{ + public class ConverterService : INService + { + public List Units { get; } = new List(); + private readonly Logger _log; + private readonly Timer _currencyUpdater; + private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0); + private readonly DbService _db; + private readonly ConvertUnit[] fileData; + + public ConverterService(DiscordSocketClient client, DbService db) + { + _log = LogManager.GetCurrentClassLogger(); + _db = db; + + if (client.ShardId == 0) + { + try + { + fileData = JsonConvert.DeserializeObject>( + File.ReadAllText("data/units.json")) + .Select(u => new ConvertUnit() + { + Modifier = u.Modifier, + UnitType = u.UnitType, + InternalTrigger = string.Join("|", u.Triggers) + }).ToArray(); + + using (var uow = _db.UnitOfWork) + { + if (uow.ConverterUnits.Empty()) + { + uow.ConverterUnits.AddRange(fileData); + + Units = uow.ConverterUnits.GetAll().ToList(); + uow.Complete(); + } + } + } + catch (Exception ex) + { + _log.Warn("Could not load units: " + ex.Message); + } + } + + _currencyUpdater = new Timer(async (shouldLoad) => await UpdateCurrency((bool)shouldLoad), + client.ShardId == 0, + TimeSpan.FromSeconds(1), + _updateInterval); + } + + private async Task GetCurrencyRates() + { + using (var http = new HttpClient()) + { + var res = await http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false); + return JsonConvert.DeserializeObject(res); + } + } + + private async Task UpdateCurrency(bool shouldLoad) + { + try + { + var unitTypeString = "currency"; + if (shouldLoad) + { + var currencyRates = await GetCurrencyRates(); + var baseType = new ConvertUnit() + { + Triggers = new[] { currencyRates.Base }, + Modifier = decimal.One, + UnitType = unitTypeString + }; + var range = currencyRates.ConversionRates.Select(u => new ConvertUnit() + { + InternalTrigger = u.Key, + Modifier = u.Value, + UnitType = unitTypeString + }).ToArray(); + var toRemove = Units.Where(u => u.UnitType == unitTypeString); + + using (var uow = _db.UnitOfWork) + { + if(toRemove.Any()) + uow.ConverterUnits.RemoveRange(toRemove.ToArray()); + uow.ConverterUnits.Add(baseType); + uow.ConverterUnits.AddRange(range); + + await uow.CompleteAsync().ConfigureAwait(false); + } + Units.RemoveAll(u => u.UnitType == unitTypeString); + Units.Add(baseType); + Units.AddRange(range); + Units.AddRange(fileData); + } + else + { + using (var uow = _db.UnitOfWork) + { + Units.RemoveAll(u => u.UnitType == unitTypeString); + Units.AddRange(uow.ConverterUnits.GetAll().ToArray()); + } + } + } + catch + { + _log.Warn("Failed updating currency. Ignore this."); + } + } + } + + public class MeasurementUnit + { + public List Triggers { get; set; } + public string UnitType { get; set; } + public decimal Modifier { get; set; } + } + + public class Rates + { + public string Base { get; set; } + public DateTime Date { get; set; } + [JsonProperty("rates")] + public Dictionary ConversionRates { get; set; } + } +} diff --git a/src/NadekoBot/Modules/Utility/Services/MessageRepeaterService.cs b/src/NadekoBot/Modules/Utility/Services/MessageRepeaterService.cs new file mode 100644 index 00000000..4e937eb4 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Services/MessageRepeaterService.cs @@ -0,0 +1,41 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Discord.WebSocket; +using NadekoBot.Modules.Utility.Common; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Modules.Utility.Services +{ + public class MessageRepeaterService : INService + { + //messagerepeater + //guildid/RepeatRunners + public ConcurrentDictionary> Repeaters { get; set; } + public bool RepeaterReady { get; private set; } + + public MessageRepeaterService(NadekoBot bot, DiscordSocketClient client, IEnumerable gcs) + { + var _ = Task.Run(async () => + { + await bot.Ready.Task.ConfigureAwait(false); + + Repeaters = new ConcurrentDictionary>(gcs + .Select(gc => + { + var guild = client.GetGuild(gc.GuildId); + if (guild == null) + return (0, null); + return (gc.GuildId, new ConcurrentQueue(gc.GuildRepeaters + .Select(gr => new RepeatRunner(client, guild, gr)) + .Where(x => x.Guild != null))); + }) + .Where(x => x.Item2 != null) + .ToDictionary(x => x.Item1, x => x.Item2)); + RepeaterReady = true; + }); + } + } +} diff --git a/src/NadekoBot/Services/Utility/PatreonRewardsService.cs b/src/NadekoBot/Modules/Utility/Services/PatreonRewardsService.cs similarity index 55% rename from src/NadekoBot/Services/Utility/PatreonRewardsService.cs rename to src/NadekoBot/Modules/Utility/Services/PatreonRewardsService.cs index e8202d84..c0dbc96e 100644 --- a/src/NadekoBot/Services/Utility/PatreonRewardsService.cs +++ b/src/NadekoBot/Modules/Utility/Services/PatreonRewardsService.cs @@ -1,18 +1,21 @@ -using NadekoBot.Services.Database.Models; -using NadekoBot.Services.Utility.Patreon; -using Newtonsoft.Json; -using NLog; -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Discord.WebSocket; +using NadekoBot.Modules.Utility.Common.Patreon; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using Newtonsoft.Json; +using NLog; -namespace NadekoBot.Services.Utility +namespace NadekoBot.Modules.Utility.Services { - public class PatreonRewardsService + public class PatreonRewardsService : INService { private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1); @@ -23,12 +26,15 @@ namespace NadekoBot.Services.Utility private readonly SemaphoreSlim claimLockJustInCase = new SemaphoreSlim(1, 1); private readonly Logger _log; - public readonly TimeSpan Interval = TimeSpan.FromMinutes(15); - private IBotCredentials _creds; + public readonly TimeSpan Interval = TimeSpan.FromMinutes(3); + private readonly IBotCredentials _creds; private readonly DbService _db; private readonly CurrencyService _currency; - public PatreonRewardsService(IBotCredentials creds, DbService db, CurrencyService currency) + private readonly string cacheFileName = "./patreon-rewards.json"; + + public PatreonRewardsService(IBotCredentials creds, DbService db, CurrencyService currency, + DiscordSocketClient client) { _creds = creds; _db = db; @@ -36,58 +42,65 @@ namespace NadekoBot.Services.Utility if (string.IsNullOrWhiteSpace(creds.PatreonAccessToken)) return; _log = LogManager.GetCurrentClassLogger(); - Updater = new Timer(async (_) => await LoadPledges(), null, TimeSpan.Zero, Interval); + Updater = new Timer(async (load) => await RefreshPledges((bool)load), + client.ShardId == 0, client.ShardId == 0 ? TimeSpan.Zero : TimeSpan.FromMinutes(2), Interval); } - public async Task LoadPledges() + public async Task RefreshPledges(bool shouldLoad) { - LastUpdate = DateTime.UtcNow; - await getPledgesLocker.WaitAsync(1000).ConfigureAwait(false); - try + if (shouldLoad) { - var rewards = new List(); - var users = new List(); - using (var http = new HttpClient()) + LastUpdate = DateTime.UtcNow; + await getPledgesLocker.WaitAsync().ConfigureAwait(false); + try { - http.DefaultRequestHeaders.Clear(); - http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken); - var data = new PatreonData() + var rewards = new List(); + var users = new List(); + using (var http = new HttpClient()) { - Links = new PatreonDataLinks() + http.DefaultRequestHeaders.Clear(); + http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken); + var data = new PatreonData() { - next = "https://api.patreon.com/oauth2/api/campaigns/334038/pledges" - } - }; - do + Links = new PatreonDataLinks() + { + next = $"https://api.patreon.com/oauth2/api/campaigns/{_creds.PatreonCampaignId}/pledges" + } + }; + do + { + var res = await http.GetStringAsync(data.Links.next) + .ConfigureAwait(false); + data = JsonConvert.DeserializeObject(res); + var pledgers = data.Data.Where(x => x["type"].ToString() == "pledge"); + rewards.AddRange(pledgers.Select(x => JsonConvert.DeserializeObject(x.ToString())) + .Where(x => x.attributes.declined_since == null)); + users.AddRange(data.Included + .Where(x => x["type"].ToString() == "user") + .Select(x => JsonConvert.DeserializeObject(x.ToString()))); + } while (!string.IsNullOrWhiteSpace(data.Links.next)); + } + Pledges = rewards.Join(users, (r) => r.relationships?.patron?.data?.id, (u) => u.id, (x, y) => new PatreonUserAndReward() { - var res = await http.GetStringAsync(data.Links.next) - .ConfigureAwait(false); - data = JsonConvert.DeserializeObject(res); - var pledgers = data.Data.Where(x => x["type"].ToString() == "pledge"); - rewards.AddRange(pledgers.Select(x => JsonConvert.DeserializeObject(x.ToString())) - .Where(x => x.attributes.declined_since == null)); - users.AddRange(data.Included - .Where(x => x["type"].ToString() == "user") - .Select(x => JsonConvert.DeserializeObject(x.ToString()))); - } while (!string.IsNullOrWhiteSpace(data.Links.next)); + User = y, + Reward = x, + }).ToImmutableArray(); + File.WriteAllText("./patreon_rewards.json", JsonConvert.SerializeObject(Pledges)); } - Pledges = rewards.Join(users, (r) => r.relationships?.patron?.data?.id, (u) => u.id, (x, y) => new PatreonUserAndReward() + catch (Exception ex) { - User = y, - Reward = x, - }).ToImmutableArray(); - } - catch (Exception ex) - { - _log.Warn(ex); - } - finally - { - var _ = Task.Run(async () => + _log.Warn(ex); + } + finally { - await Task.Delay(TimeSpan.FromMinutes(5)).ConfigureAwait(false); getPledgesLocker.Release(); - }); + } + } + else + { + if(File.Exists(cacheFileName)) + Pledges = JsonConvert.DeserializeObject(File.ReadAllText("./patreon_rewards.json")) + .ToImmutableArray(); } } diff --git a/src/NadekoBot/Services/Utility/RemindService.cs b/src/NadekoBot/Modules/Utility/Services/RemindService.cs similarity index 62% rename from src/NadekoBot/Services/Utility/RemindService.cs rename to src/NadekoBot/Modules/Utility/Services/RemindService.cs index a6dd276b..0e3b2206 100644 --- a/src/NadekoBot/Services/Utility/RemindService.cs +++ b/src/NadekoBot/Modules/Utility/Services/RemindService.cs @@ -1,39 +1,36 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using NLog; -using System; -using System.Collections.Generic; +using System; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using NadekoBot.Common.Replacements; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using NLog; -namespace NadekoBot.Services.Utility +namespace NadekoBot.Modules.Utility.Services { - public class RemindService + public class RemindService : INService { public readonly Regex Regex = new Regex(@"^(?:(?\d)mo)?(?:(?\d)w)?(?:(?\d{1,2})d)?(?:(?\d{1,2})h)?(?:(?\d{1,2})m)?$", RegexOptions.Compiled | RegexOptions.Multiline); public string RemindMessageFormat { get; } - public readonly IDictionary> _replacements = new Dictionary> - { - { "%message%" , (r) => r.Message }, - { "%user%", (r) => $"<@!{r.UserId}>" }, - { "%target%", (r) => r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>"} - }; - private readonly Logger _log; private readonly CancellationTokenSource cancelSource; private readonly CancellationToken cancelAllToken; - private readonly BotConfig _config; - private readonly DiscordShardedClient _client; + private readonly IBotConfigProvider _config; + private readonly DiscordSocketClient _client; private readonly DbService _db; - public RemindService(DiscordShardedClient client, BotConfig config, DbService db) + public RemindService(DiscordSocketClient client, IBotConfigProvider config, DbService db, + StartingGuildsService guilds, IUnitOfWork uow) { _config = config; _client = client; @@ -42,12 +39,9 @@ namespace NadekoBot.Services.Utility cancelSource = new CancellationTokenSource(); cancelAllToken = cancelSource.Token; - List reminders; - using (var uow = _db.UnitOfWork) - { - reminders = uow.Reminders.GetAll().ToList(); - } - RemindMessageFormat = _config.RemindMessageFormat; + + var reminders = uow.Reminders.GetIncludedReminders(guilds).ToList(); + RemindMessageFormat = _config.BotConfig.RemindMessageFormat; foreach (var r in reminders) { @@ -74,7 +68,7 @@ namespace NadekoBot.Services.Utility var user = _client.GetGuild(r.ServerId).GetUser(r.ChannelId); if (user == null) return; - ch = await user.CreateDMChannelAsync().ConfigureAwait(false); + ch = await user.GetOrCreateDMChannelAsync().ConfigureAwait(false); } else { @@ -83,11 +77,13 @@ namespace NadekoBot.Services.Utility if (ch == null) return; - await ch.SendMessageAsync( - _replacements.Aggregate(RemindMessageFormat, - (cur, replace) => cur.Replace(replace.Key, replace.Value(r))) - .SanitizeMentions() - ).ConfigureAwait(false); //it works trust me + var rep = new ReplacementBuilder() + .WithOverride("%user%", () => $"<@!{r.UserId}>") + .WithOverride("%message%", () => r.Message) + .WithOverride("%target%", () => r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>") + .Build(); + + await ch.SendMessageAsync(rep.Replace(RemindMessageFormat).SanitizeMentions()).ConfigureAwait(false); //it works trust me } catch (Exception ex) { _log.Warn(ex); } finally diff --git a/src/NadekoBot/Modules/Utility/Services/StreamRoleService.cs b/src/NadekoBot/Modules/Utility/Services/StreamRoleService.cs new file mode 100644 index 00000000..98f34eaf --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Services/StreamRoleService.cs @@ -0,0 +1,297 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; +using NadekoBot.Modules.Utility.Extensions; +using NadekoBot.Common.TypeReaders; +using NadekoBot.Modules.Utility.Common; +using NadekoBot.Modules.Utility.Common.Exceptions; +using Discord.Net; + +namespace NadekoBot.Modules.Utility.Services +{ + public class StreamRoleService : INService + { + private readonly DbService _db; + private readonly ConcurrentDictionary guildSettings; + private readonly Logger _log; + + public StreamRoleService(DiscordSocketClient client, DbService db, IEnumerable gcs) + { + this._db = db; + this._log = LogManager.GetCurrentClassLogger(); + + guildSettings = gcs.ToDictionary(x => x.GuildId, x => x.StreamRole) + .Where(x => x.Value != null && x.Value.Enabled) + .ToConcurrent(); + + client.GuildMemberUpdated += Client_GuildMemberUpdated; + + var _ = Task.Run(async () => + { + try + { + await Task.WhenAll(client.Guilds.Select(g => RescanUsers(g))).ConfigureAwait(false); + } + catch + { + // ignored + } + }); + } + + private Task Client_GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after) + { + var _ = Task.Run(async () => + { + //if user wasn't streaming or didn't have a game status at all + if (guildSettings.TryGetValue(after.Guild.Id, out var setting)) + { + await RescanUser(after, setting).ConfigureAwait(false); + } + }); + + return Task.CompletedTask; + } + /// + /// Adds or removes a user from a blacklist or a whitelist in the specified guild. + /// + /// Guild + /// Add or rem action + /// User's Id + /// User's name#discrim + /// Whether the operation was successful + public async Task ApplyListAction(StreamRoleListType listType, IGuild guild, AddRemove action, ulong userId, string userName) + { + userName.ThrowIfNull(nameof(userName)); + + bool success; + using (var uow = _db.UnitOfWork) + { + var streamRoleSettings = uow.GuildConfigs.GetStreamRoleSettings(guild.Id); + + if (listType == StreamRoleListType.Whitelist) + { + var userObj = new StreamRoleWhitelistedUser() + { + UserId = userId, + Username = userName, + }; + + if (action == AddRemove.Rem) + success = streamRoleSettings.Whitelist.Remove(userObj); + else + success = streamRoleSettings.Whitelist.Add(userObj); + } + else + { + var userObj = new StreamRoleBlacklistedUser() + { + UserId = userId, + Username = userName, + }; + + if (action == AddRemove.Rem) + success = streamRoleSettings.Blacklist.Remove(userObj); + else + success = streamRoleSettings.Blacklist.Add(userObj); + } + + await uow.CompleteAsync().ConfigureAwait(false); + UpdateCache(guild.Id, streamRoleSettings); + } + if (success) + { + await RescanUsers(guild).ConfigureAwait(false); + } + return success; + } + + /// + /// Sets keyword on a guild and updates the cache. + /// + /// Guild Id + /// Keyword to set + /// The keyword set + public async Task SetKeyword(IGuild guild, string keyword) + { + keyword = keyword?.Trim()?.ToLowerInvariant(); + + using (var uow = _db.UnitOfWork) + { + var streamRoleSettings = uow.GuildConfigs.GetStreamRoleSettings(guild.Id); + + streamRoleSettings.Keyword = keyword; + UpdateCache(guild.Id, streamRoleSettings); + uow.Complete(); + } + + await RescanUsers(guild).ConfigureAwait(false); + return keyword; + } + + /// + /// Gets the currently set keyword on a guild. + /// + /// Guild Id + /// The keyword set + public string GetKeyword(ulong guildId) + { + if (guildSettings.TryGetValue(guildId, out var outSetting)) + return outSetting.Keyword; + + StreamRoleSettings setting; + using (var uow = _db.UnitOfWork) + { + setting = uow.GuildConfigs.GetStreamRoleSettings(guildId); + } + + UpdateCache(guildId, setting); + + return setting.Keyword; + } + + /// + /// Sets the role to monitor, and a role to which to add to + /// the user who starts streaming in the monitored role. + /// + /// Role to monitor + /// Role to add to the user + public async Task SetStreamRole(IRole fromRole, IRole addRole) + { + fromRole.ThrowIfNull(nameof(fromRole)); + addRole.ThrowIfNull(nameof(addRole)); + + StreamRoleSettings setting; + using (var uow = _db.UnitOfWork) + { + var streamRoleSettings = uow.GuildConfigs.GetStreamRoleSettings(fromRole.Guild.Id); + + streamRoleSettings.Enabled = true; + streamRoleSettings.AddRoleId = addRole.Id; + streamRoleSettings.FromRoleId = fromRole.Id; + + setting = streamRoleSettings; + await uow.CompleteAsync().ConfigureAwait(false); + } + + UpdateCache(fromRole.Guild.Id, setting); + + foreach (var usr in await fromRole.GetMembersAsync()) + { + if (usr is IGuildUser x) + await RescanUser(x, setting, addRole).ConfigureAwait(false); + } + } + + /// + /// Stops the stream role feature on the specified guild. + /// + /// Guild's Id + public async Task StopStreamRole(IGuild guild, bool cleanup = false) + { + using (var uow = _db.UnitOfWork) + { + var streamRoleSettings = uow.GuildConfigs.GetStreamRoleSettings(guild.Id); + streamRoleSettings.Enabled = false; + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (guildSettings.TryRemove(guild.Id, out var setting) && cleanup) + await RescanUsers(guild).ConfigureAwait(false); + } + + private async Task RescanUser(IGuildUser user, StreamRoleSettings setting, IRole addRole = null) + { + if (user.Game.HasValue && + user.Game.Value.StreamType != StreamType.NotStreaming + && setting.Enabled + && !setting.Blacklist.Any(x => x.UserId == user.Id) + && user.RoleIds.Contains(setting.FromRoleId) + && (string.IsNullOrWhiteSpace(setting.Keyword) + || user.Game.Value.Name.ToLowerInvariant().Contains(setting.Keyword.ToLowerInvariant()) + || setting.Whitelist.Any(x => x.UserId == user.Id))) + { + try + { + addRole = addRole ?? user.Guild.GetRole(setting.AddRoleId); + if (addRole == null) + throw new StreamRoleNotFoundException(); + + //check if he doesn't have addrole already, to avoid errors + if (!user.RoleIds.Contains(setting.AddRoleId)) + await user.AddRoleAsync(addRole).ConfigureAwait(false); + _log.Info("Added stream role to user {0} in {1} server", user.ToString(), user.Guild.ToString()); + } + catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden) + { + await StopStreamRole(user.Guild).ConfigureAwait(false); + _log.Warn("Error adding stream role(s). Forcibly disabling stream role feature."); + _log.Error(ex); + throw new StreamRolePermissionException(); + } + catch (Exception ex) + { + _log.Warn("Failed adding stream role."); + _log.Error(ex); + } + } + else + { + //check if user is in the addrole + if (user.RoleIds.Contains(setting.AddRoleId)) + { + try + { + addRole = addRole ?? user.Guild.GetRole(setting.AddRoleId); + if (addRole == null) + throw new StreamRoleNotFoundException(); + + await user.RemoveRoleAsync(addRole).ConfigureAwait(false); + _log.Info("Removed stream role from a user {0} in {1} server", user.ToString(), user.Guild.ToString()); + } + catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden) + { + await StopStreamRole(user.Guild).ConfigureAwait(false); + _log.Warn("Error removing stream role(s). Forcibly disabling stream role feature."); + _log.Error(ex); + throw new StreamRolePermissionException(); + } + _log.Info("Removed stream role from the user {0} in {1} server", user.ToString(), user.Guild.ToString()); + } + } + } + + private async Task RescanUsers(IGuild guild) + { + if (!guildSettings.TryGetValue(guild.Id, out var setting)) + return; + + var addRole = guild.GetRole(setting.AddRoleId); + if (addRole == null) + return; + + if (setting.Enabled) + { + var users = await guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false); + foreach (var usr in users.Where(x => x.RoleIds.Contains(setting.FromRoleId) || x.RoleIds.Contains(addRole.Id))) + { + if(usr is IGuildUser x) + await RescanUser(x, setting, addRole).ConfigureAwait(false); + } + } + } + + private void UpdateCache(ulong guildId, StreamRoleSettings setting) + { + guildSettings.AddOrUpdate(guildId, (key) => setting, (key, old) => setting); + } + } +} diff --git a/src/NadekoBot/Services/Utility/VerboseErrorsService.cs b/src/NadekoBot/Modules/Utility/Services/VerboseErrorsService.cs similarity index 78% rename from src/NadekoBot/Services/Utility/VerboseErrorsService.cs rename to src/NadekoBot/Modules/Utility/Services/VerboseErrorsService.cs index 009de8ee..9f438128 100644 --- a/src/NadekoBot/Services/Utility/VerboseErrorsService.cs +++ b/src/NadekoBot/Modules/Utility/Services/VerboseErrorsService.cs @@ -1,16 +1,17 @@ -using NadekoBot.Services.Database.Models; -using System.Collections.Concurrent; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Discord; -using NadekoBot.Extensions; -using NadekoBot.Services.Help; using Discord.Commands; -using System.Linq; +using NadekoBot.Common.Collections; +using NadekoBot.Extensions; +using NadekoBot.Modules.Help.Services; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; -namespace NadekoBot.Services.Utility +namespace NadekoBot.Modules.Utility.Services { - public class VerboseErrorsService + public class VerboseErrorsService : INService { private readonly ConcurrentHashSet guildsEnabled; private readonly DbService _db; @@ -50,12 +51,12 @@ namespace NadekoBot.Services.Utility public bool ToggleVerboseErrors(ulong guildId) { - + bool enabled; using (var uow = _db.UnitOfWork) { var gc = uow.GuildConfigs.For(guildId, set => set); - gc.VerboseErrors = !gc.VerboseErrors; + enabled = gc.VerboseErrors = !gc.VerboseErrors; uow.Complete(); @@ -65,15 +66,12 @@ namespace NadekoBot.Services.Utility guildsEnabled.TryRemove(guildId); } - if (guildsEnabled.Add(guildId)) - { - return true; - } + if (enabled) + guildsEnabled.Add(guildId); else - { guildsEnabled.TryRemove(guildId); - return false; - } + + return enabled; } } diff --git a/src/NadekoBot/Modules/Utility/StreamRoleCommands.cs b/src/NadekoBot/Modules/Utility/StreamRoleCommands.cs new file mode 100644 index 00000000..e6f38947 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/StreamRoleCommands.cs @@ -0,0 +1,94 @@ +using Discord; +using Discord.Commands; +using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Utility.Services; +using NadekoBot.Common.TypeReaders; +using NadekoBot.Modules.Utility.Common; +using NadekoBot.Common; + +namespace NadekoBot.Modules.Utility +{ + public partial class Utility + { + public class StreamRoleCommands : NadekoSubmodule + { + [NadekoCommand, Usage, Description, Aliases] + [RequireBotPermission(GuildPermission.ManageRoles)] + [RequireUserPermission(GuildPermission.ManageRoles)] + [RequireContext(ContextType.Guild)] + public async Task StreamRole(IRole fromRole, IRole addRole) + { + await this._service.SetStreamRole(fromRole, addRole).ConfigureAwait(false); + + await ReplyConfirmLocalized("stream_role_enabled", Format.Bold(fromRole.ToString()), Format.Bold(addRole.ToString())).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireBotPermission(GuildPermission.ManageRoles)] + [RequireUserPermission(GuildPermission.ManageRoles)] + [RequireContext(ContextType.Guild)] + public async Task StreamRole() + { + await this._service.StopStreamRole(Context.Guild).ConfigureAwait(false); + await ReplyConfirmLocalized("stream_role_disabled").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireBotPermission(GuildPermission.ManageRoles)] + [RequireUserPermission(GuildPermission.ManageRoles)] + [RequireContext(ContextType.Guild)] + public async Task StreamRoleKeyword([Remainder]string keyword = null) + { + string kw = await this._service.SetKeyword(Context.Guild, keyword).ConfigureAwait(false); + + if(string.IsNullOrWhiteSpace(keyword)) + await ReplyConfirmLocalized("stream_role_kw_reset").ConfigureAwait(false); + else + await ReplyConfirmLocalized("stream_role_kw_set", Format.Bold(kw)).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireBotPermission(GuildPermission.ManageRoles)] + [RequireUserPermission(GuildPermission.ManageRoles)] + [RequireContext(ContextType.Guild)] + public async Task StreamRoleBlacklist(AddRemove action, [Remainder] IGuildUser user) + { + var success = await this._service.ApplyListAction(StreamRoleListType.Blacklist, Context.Guild, action, user.Id, user.ToString()) + .ConfigureAwait(false); + + if(action == AddRemove.Add) + if(success) + await ReplyConfirmLocalized("stream_role_bl_add", Format.Bold(user.ToString())).ConfigureAwait(false); + else + await ReplyConfirmLocalized("stream_role_bl_add_fail", Format.Bold(user.ToString())).ConfigureAwait(false); + else + if (success) + await ReplyConfirmLocalized("stream_role_bl_rem", Format.Bold(user.ToString())).ConfigureAwait(false); + else + await ReplyErrorLocalized("stream_role_bl_rem_fail", Format.Bold(user.ToString())).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireBotPermission(GuildPermission.ManageRoles)] + [RequireUserPermission(GuildPermission.ManageRoles)] + [RequireContext(ContextType.Guild)] + public async Task StreamRoleWhitelist(AddRemove action, [Remainder] IGuildUser user) + { + var success = await this._service.ApplyListAction(StreamRoleListType.Whitelist, Context.Guild, action, user.Id, user.ToString()) + .ConfigureAwait(false); + + if (action == AddRemove.Add) + if(success) + await ReplyConfirmLocalized("stream_role_wl_add", Format.Bold(user.ToString())).ConfigureAwait(false); + else + await ReplyConfirmLocalized("stream_role_wl_add_fail", Format.Bold(user.ToString())).ConfigureAwait(false); + else + if (success) + await ReplyConfirmLocalized("stream_role_wl_rem", Format.Bold(user.ToString())).ConfigureAwait(false); + else + await ReplyErrorLocalized("stream_role_wl_rem_fail", Format.Bold(user.ToString())).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs b/src/NadekoBot/Modules/Utility/UnitConversionCommands.cs similarity index 92% rename from src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs rename to src/NadekoBot/Modules/Utility/UnitConversionCommands.cs index 9b9c121c..4f9ca0e6 100644 --- a/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs +++ b/src/NadekoBot/Modules/Utility/UnitConversionCommands.cs @@ -1,26 +1,19 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using NadekoBot.Extensions; -using NadekoBot.Services.Utility; using System; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Utility.Services; namespace NadekoBot.Modules.Utility { public partial class Utility { [Group] - public class UnitConverterCommands : NadekoSubmodule + public class UnitConverterCommands : NadekoSubmodule { - private readonly ConverterService _service; - - public UnitConverterCommands(ConverterService service) - { - _service = service; - } - [NadekoCommand, Usage, Description, Aliases] public async Task ConvertList() { diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index 4774a266..82c8551e 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -1,6 +1,5 @@ using Discord; using Discord.Commands; -using NadekoBot.Attributes; using System; using System.Linq; using System.Threading.Tasks; @@ -16,6 +15,8 @@ using System.Collections.Generic; using Newtonsoft.Json; using Discord.WebSocket; using System.Diagnostics; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; using Color = Discord.Color; using NadekoBot.Services; @@ -23,158 +24,17 @@ namespace NadekoBot.Modules.Utility { public partial class Utility : NadekoTopLevelModule { - private static ConcurrentDictionary _rotatingRoleColors = new ConcurrentDictionary(); - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IStatsService _stats; private readonly IBotCredentials _creds; + private readonly ShardsCoordinator _shardCoord; - public Utility(DiscordShardedClient client, IStatsService stats, IBotCredentials creds) + public Utility(NadekoBot nadeko, DiscordSocketClient client, IStatsService stats, IBotCredentials creds) { _client = client; _stats = stats; _creds = creds; - } - - //[NadekoCommand, Usage, Description, Aliases] - //[RequireContext(ContextType.Guild)] - //public async Task Midorina([Remainder] string arg) - //{ - // var channel = (ITextChannel)Context.Channel; - - // var roleNames = arg?.Split(';'); - - // if (roleNames == null || roleNames.Length == 0) - // return; - - // var j = 0; - // var roles = roleNames.Select(x => Context.Guild.Roles.FirstOrDefault(r => String.Compare(r.Name, x, StringComparison.OrdinalIgnoreCase) == 0)) - // .Where(x => x != null) - // .Take(10) - // .ToArray(); - - // var rnd = new NadekoRandom(); - // var reactions = new[] { "🎬", "🐧", "🌍", "🌺", "🚀", "☀", "🌲", "🍒", "🐾", "🏀" } - // .OrderBy(x => rnd.Next()) - // .ToArray(); - - // var roleStrings = roles - // .Select(x => $"{reactions[j++]} -> {x.Name}"); - - // var msg = await Context.Channel.SendConfirmAsync("Pick a Role", - // string.Join("\n", roleStrings)).ConfigureAwait(false); - - // for (int i = 0; i < roles.Length; i++) - // { - // try { await msg.AddReactionAsync(reactions[i]).ConfigureAwait(false); } - // catch (Exception ex) { _log.Warn(ex); } - // await Task.Delay(1000).ConfigureAwait(false); - // } - - // msg.OnReaction((r) => Task.Run(async () => - // { - // try - // { - // var usr = r.User.GetValueOrDefault() as IGuildUser; - - // if (usr == null) - // return; - - // var index = Array.IndexOf(reactions, r.Emoji.Name); - // if (index == -1) - // return; - - // await usr.RemoveRolesAsync(roles[index]); - // } - // catch (Exception ex) - // { - // _log.Warn(ex); - // } - // }), (r) => Task.Run(async () => - // { - // try - // { - // var usr = r.User.GetValueOrDefault() as IGuildUser; - - // if (usr == null) - // return; - - // var index = Array.IndexOf(reactions, r.Emoji.Name); - // if (index == -1) - // return; - - // await usr.RemoveRolesAsync(roles[index]); - // } - // catch (Exception ex) - // { - // _log.Warn(ex); - // } - // })); - //} - - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [RequireUserPermission(GuildPermission.ManageRoles)] - [OwnerOnly] - public async Task RotateRoleColor(int timeout, IRole role, params string[] hexes) - { - var channel = (ITextChannel)Context.Channel; - - if ((timeout < 60 && timeout != 0) || timeout > 3600) - return; - - Timer t; - if (timeout == 0 || hexes.Length == 0) - { - if (_rotatingRoleColors.TryRemove(role.Id, out t)) - { - t.Change(Timeout.Infinite, Timeout.Infinite); - await ReplyConfirmLocalized("rrc_stop", Format.Bold(role.Name)).ConfigureAwait(false); - } - return; - } - - var hexColors = hexes.Select(hex => - { - try { return (ImageSharp.Color?)ImageSharp.Color.FromHex(hex.Replace("#", "")); } catch { return null; } - }) - .Where(c => c != null) - .Select(c => c.Value) - .ToArray(); - - if (!hexColors.Any()) - { - await ReplyErrorLocalized("rrc_no_colors").ConfigureAwait(false); - return; - } - - var images = hexColors.Select(color => - { - var img = new ImageSharp.Image(50, 50); - img.BackgroundColor(color); - return img; - }).Merge().ToStream(); - - var i = 0; - t = new Timer(async (_) => - { - try - { - var color = hexColors[i]; - await role.ModifyAsync(r => r.Color = new Color(color.R, color.G, color.B)).ConfigureAwait(false); - ++i; - if (i >= hexColors.Length) - i = 0; - } - catch { } - }, null, 0, timeout * 1000); - - _rotatingRoleColors.AddOrUpdate(role.Id, t, (key, old) => - { - old.Change(Timeout.Infinite, Timeout.Infinite); - return t; - }); - await channel.SendFileAsync(images, "magicalgirl.jpg", GetText("rrc_start", Format.Bold(role.Name))).ConfigureAwait(false); + _shardCoord = nadeko.ShardCoord; } [NadekoCommand, Usage, Description, Aliases] @@ -235,11 +95,12 @@ namespace NadekoBot.Modules.Utility var usrs = (await Context.Guild.GetUsersAsync()).ToArray(); var roleUsers = usrs.Where(u => u.RoleIds.Contains(role.Id)).Select(u => u.ToString()) .ToArray(); + var inroleusers = string.Join(", ", roleUsers + .OrderBy(x => rng.Next()) + .Take(50)); var embed = new EmbedBuilder().WithOkColor() .WithTitle("ℹ️ " + Format.Bold(GetText("inrole_list", Format.Bold(role.Name))) + $" - {roleUsers.Length}") - .WithDescription(string.Join(", ", roleUsers - .OrderBy(x => rng.Next()) - .Take(50))); + .WithDescription($"```css\n[{role.Name}]\n{inroleusers}```"); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } @@ -354,23 +215,30 @@ namespace NadekoBot.Modules.Utility } [NadekoCommand, Usage, Description, Aliases] + [Shard0Precondition] public async Task ShardStats(int page = 1) { if (--page < 0) return; + var statuses = _shardCoord.Statuses.ToArray() + .Where(x => x != null); - var status = string.Join(", ", _client.Shards.GroupBy(x => x.ConnectionState) + var status = string.Join(", ", statuses + .GroupBy(x => x.ConnectionState) .Select(x => $"{x.Count()} {x.Key}") .ToArray()); - var allShardStrings = _client.Shards + var allShardStrings = statuses .Select(x => - GetText("shard_stats_txt", x.ShardId.ToString(), - Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.Count.ToString()))) + { + var timeDiff = DateTime.UtcNow - x.Time; + if (timeDiff > TimeSpan.FromSeconds(20)) + return $"Shard #{Format.Bold(x.ShardId.ToString())} **UNRESPONSIVE** for {timeDiff.ToString(@"hh\:mm\:ss")}"; + return GetText("shard_stats_txt", x.ShardId.ToString(), + Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.ToString()), timeDiff.ToString(@"hh\:mm\:ss")); + }) .ToArray(); - - await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) => { @@ -387,21 +255,9 @@ namespace NadekoBot.Modules.Utility }, allShardStrings.Length / 25); } - [NadekoCommand, Usage, Description, Aliases] - public async Task ShardId(IGuild guild) - { - var shardId = _client.GetShardIdFor(guild); - - await Context.Channel.SendConfirmAsync(shardId.ToString()).ConfigureAwait(false); - } - [NadekoCommand, Usage, Description, Aliases] public async Task Stats() - { - var shardId = Context.Guild != null - ? _client.GetShardIdFor(Context.Guild) - : 0; - + { await Context.Channel.EmbedAsync( new EmbedBuilder().WithOkColor() .WithAuthor(eab => eab.WithName($"NadekoBot v{StatsService.BotVersion}") @@ -409,7 +265,7 @@ namespace NadekoBot.Modules.Utility .WithIconUrl("https://cdn.discordapp.com/avatars/116275390695079945/b21045e778ef21c96d175400e779f0fb.jpg")) .AddField(efb => efb.WithName(GetText("author")).WithValue(_stats.Author).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("botid")).WithValue(_client.CurrentUser.Id.ToString()).WithIsInline(true)) - .AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{shardId} / {_client.Shards.Count}").WithIsInline(true)) + .AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{_client.ShardId} / {_creds.TotalShards}").WithIsInline(true)) .AddField(efb => efb.WithName(GetText("commands_ran")).WithValue(_stats.CommandsRan.ToString()).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("messages")).WithValue($"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)").WithIsInline(true)) .AddField(efb => efb.WithName(GetText("memory")).WithValue($"{_stats.Heap} MB").WithIsInline(true)) @@ -417,13 +273,7 @@ namespace NadekoBot.Modules.Utility .AddField(efb => efb.WithName(GetText("uptime")).WithValue(_stats.GetUptimeString("\n")).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("presence")).WithValue( GetText("presence_txt", - _client.Guilds.Count, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true)) -#if !GLOBAL_NADEKO - //.WithFooter(efb => efb.WithText(GetText("stats_songs", - // _music.MusicPlayers.Count(mp => mp.Value.CurrentSong != null), - // _music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count)))) -#endif - ); + _stats.GuildCount, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true))); } [NadekoCommand, Usage, Description, Aliases] @@ -501,7 +351,7 @@ namespace NadekoBot.Modules.Utility }) }); await Context.User.SendFileAsync( - await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream().ConfigureAwait(false), title, title).ConfigureAwait(false); + await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream().ConfigureAwait(false), title, title, false).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] public async Task Ping() @@ -514,4 +364,4 @@ namespace NadekoBot.Modules.Utility await Context.Channel.SendConfirmAsync($"{Format.Bold(Context.User.ToString())} 🏓 {(int)sw.Elapsed.TotalMilliseconds}ms").ConfigureAwait(false); } } -} \ No newline at end of file +} diff --git a/src/NadekoBot/Modules/Utility/Commands/VerboseCommandErrors.cs b/src/NadekoBot/Modules/Utility/VerboseErrorCommands.cs similarity index 64% rename from src/NadekoBot/Modules/Utility/Commands/VerboseCommandErrors.cs rename to src/NadekoBot/Modules/Utility/VerboseErrorCommands.cs index 8ec131d0..a092dab7 100644 --- a/src/NadekoBot/Modules/Utility/Commands/VerboseCommandErrors.cs +++ b/src/NadekoBot/Modules/Utility/VerboseErrorCommands.cs @@ -1,28 +1,21 @@ using Discord.Commands; -using NadekoBot.Attributes; -using NadekoBot.Services.Utility; using System.Threading.Tasks; +using NadekoBot.Common.Attributes; +using NadekoBot.Modules.Utility.Services; namespace NadekoBot.Modules.Utility { public partial class Utility { [Group] - public class VerboseCommandErrors : NadekoSubmodule + public class VerboseErrorCommands : NadekoSubmodule { - private readonly VerboseErrorsService _ves; - - public VerboseCommandErrors(VerboseErrorsService ves) - { - _ves = ves; - } - [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(Discord.GuildPermission.ManageMessages)] public async Task VerboseError() { - var state = _ves.ToggleVerboseErrors(Context.Guild.Id); + var state = _service.ToggleVerboseErrors(Context.Guild.Id); if (state) await ReplyConfirmLocalized("verbose_errors_enabled").ConfigureAwait(false); diff --git a/src/NadekoBot/Modules/Xp/Club.cs b/src/NadekoBot/Modules/Xp/Club.cs new file mode 100644 index 00000000..20a6e97a --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Club.cs @@ -0,0 +1,350 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Common.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Modules.Xp.Common; +using NadekoBot.Modules.Xp.Services; +using NadekoBot.Services.Database.Models; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Xp +{ + public partial class Xp + { + [Group] + public class Club : NadekoSubmodule + { + private readonly XpService _xps; + private readonly DiscordSocketClient _client; + + public Club(XpService xps, DiscordSocketClient client) + { + _xps = xps; + _client = client; + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task ClubAdmin([Remainder]IUser toAdmin) + { + bool admin; + try + { + admin = _service.ToggleAdmin(Context.User, toAdmin); + } + catch (InvalidOperationException) + { + await ReplyErrorLocalized("club_admin_error").ConfigureAwait(false); + return; + } + + if(admin) + await ReplyConfirmLocalized("club_admin_add", Format.Bold(toAdmin.ToString())).ConfigureAwait(false); + else + await ReplyConfirmLocalized("club_admin_remove", Format.Bold(toAdmin.ToString())).ConfigureAwait(false); + + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task ClubCreate([Remainder]string clubName) + { + if (string.IsNullOrWhiteSpace(clubName) || clubName.Length > 20) + return; + + if (!_service.CreateClub(Context.User, clubName, out ClubInfo club)) + { + await ReplyErrorLocalized("club_create_error").ConfigureAwait(false); + return; + } + + await ReplyConfirmLocalized("club_created", Format.Bold(club.ToString())).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + public Task ClubIcon([Remainder]string url = null) + { + if ((!Uri.IsWellFormedUriString(url, UriKind.Absolute) && url != null) + || !_service.SetClubIcon(Context.User.Id, url)) + { + return ReplyErrorLocalized("club_icon_error"); + } + + return ReplyConfirmLocalized("club_icon_set"); + } + + [NadekoCommand, Usage, Description, Aliases] + [Priority(1)] + public async Task ClubInformation(IUser user = null) + { + user = user ?? Context.User; + var club = _service.GetClubByMember(user); + if (club == null) + { + await ReplyErrorLocalized("club_not_exists").ConfigureAwait(false); + return; + } + + await ClubInformation(club.ToString()).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [Priority(0)] + public async Task ClubInformation([Remainder]string clubName = null) + { + if (string.IsNullOrWhiteSpace(clubName)) + { + await ClubInformation(Context.User).ConfigureAwait(false); + return; + } + + ClubInfo club; + if (!_service.GetClubByName(clubName, out club)) + { + await ReplyErrorLocalized("club_not_exists").ConfigureAwait(false); + return; + } + + var lvl = new LevelStats(club.Xp); + + await Context.Channel.SendPaginatedConfirmAsync(_client, 0, (page) => + { + var embed = new EmbedBuilder() + .WithOkColor() + .WithTitle($"{club.ToString()}") + .WithDescription(GetText("level_x", lvl.Level) + $" ({club.Xp} xp)") + .AddField("Owner", club.Owner.ToString(), true) + .AddField("Level Req.", club.MinimumLevelReq.ToString(), true) + .AddField("Members", string.Join("\n", club.Users + .OrderByDescending(x => { + if (club.OwnerId == x.Id) + return 2; + else if (x.IsClubAdmin) + return 1; + else + return 0; + }) + .Skip(page * 10) + .Take(10) + .Select(x => + { + if (club.OwnerId == x.Id) + return x.ToString() + "🌟"; + else if (x.IsClubAdmin) + return x.ToString() + "⭐"; + return x.ToString(); + })), false); + + if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute)) + return embed.WithThumbnailUrl(club.ImageUrl); + + return embed; + }, club.Users.Count / 10); + } + + [NadekoCommand, Usage, Description, Aliases] + public Task ClubBans(int page = 1) + { + if (--page < 0) + return Task.CompletedTask; + + var club = _service.GetClubWithBansAndApplications(Context.User.Id); + if (club == null) + return ReplyErrorLocalized("club_not_exists_owner"); + + var bans = club + .Bans + .Select(x => x.User) + .ToArray(); + + return Context.Channel.SendPaginatedConfirmAsync(_client, page, + curPage => + { + var toShow = string.Join("\n", bans + .Skip(page * 10) + .Take(10) + .Select(x => x.ToString())); + + return new EmbedBuilder() + .WithTitle(GetText("club_bans_for", club.ToString())) + .WithDescription(toShow) + .WithOkColor(); + + }, bans.Length / 10); + } + + + [NadekoCommand, Usage, Description, Aliases] + public Task ClubApps(int page = 1) + { + if (--page < 0) + return Task.CompletedTask; + + var club = _service.GetClubWithBansAndApplications(Context.User.Id); + if (club == null) + return ReplyErrorLocalized("club_not_exists_owner"); + + var apps = club + .Applicants + .Select(x => x.User) + .ToArray(); + + return Context.Channel.SendPaginatedConfirmAsync(_client, page, + curPage => + { + var toShow = string.Join("\n", apps + .Skip(page * 10) + .Take(10) + .Select(x => x.ToString())); + + return new EmbedBuilder() + .WithTitle(GetText("club_apps_for", club.ToString())) + .WithDescription(toShow) + .WithOkColor(); + + }, apps.Length / 10); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task ClubApply([Remainder]string clubName) + { + if (string.IsNullOrWhiteSpace(clubName)) + return; + + if (!_service.GetClubByName(clubName, out ClubInfo club)) + { + await ReplyErrorLocalized("club_not_exists").ConfigureAwait(false); + return; + } + + if (_service.ApplyToClub(Context.User, club)) + { + await ReplyConfirmLocalized("club_applied", Format.Bold(club.ToString())).ConfigureAwait(false); + } + else + { + await ReplyErrorLocalized("club_apply_error").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [Priority(1)] + public Task ClubAccept(IUser user) + => ClubAccept(user.ToString()); + + [NadekoCommand, Usage, Description, Aliases] + [Priority(0)] + public async Task ClubAccept([Remainder]string userName) + { + if (_service.AcceptApplication(Context.User.Id, userName, out var discordUser)) + { + await ReplyConfirmLocalized("club_accepted", Format.Bold(discordUser.ToString())).ConfigureAwait(false); + } + else + await ReplyErrorLocalized("club_accept_error").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task Clubleave() + { + if (_service.LeaveClub(Context.User)) + await ReplyConfirmLocalized("club_left").ConfigureAwait(false); + else + await ReplyErrorLocalized("club_not_in_club").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [Priority(1)] + public Task ClubKick([Remainder]IUser user) + => ClubKick(user.ToString()); + + [NadekoCommand, Usage, Description, Aliases] + [Priority(0)] + public Task ClubKick([Remainder]string userName) + { + if (_service.Kick(Context.User.Id, userName, out var club)) + return ReplyConfirmLocalized("club_user_kick", Format.Bold(userName), Format.Bold(club.ToString())); + else + return ReplyErrorLocalized("club_user_kick_fail"); + } + + [NadekoCommand, Usage, Description, Aliases] + [Priority(1)] + public Task ClubBan([Remainder]IUser user) + => ClubBan(user.ToString()); + + [NadekoCommand, Usage, Description, Aliases] + [Priority(0)] + public Task ClubBan([Remainder]string userName) + { + if (_service.Ban(Context.User.Id, userName, out var club)) + return ReplyConfirmLocalized("club_user_banned", Format.Bold(userName), Format.Bold(club.ToString())); + else + return ReplyErrorLocalized("club_user_ban_fail"); + } + + [NadekoCommand, Usage, Description, Aliases] + [Priority(1)] + public Task ClubUnBan([Remainder]IUser user) + => ClubUnBan(user.ToString()); + + [NadekoCommand, Usage, Description, Aliases] + [Priority(0)] + public Task ClubUnBan([Remainder]string userName) + { + if (_service.UnBan(Context.User.Id, userName, out var club)) + return ReplyConfirmLocalized("club_user_unbanned", Format.Bold(userName), Format.Bold(club.ToString())); + else + return ReplyErrorLocalized("club_user_unban_fail"); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task ClubLevelReq(int level) + { + if (_service.ChangeClubLevelReq(Context.User.Id, level)) + { + await ReplyConfirmLocalized("club_level_req_changed", Format.Bold(level.ToString())).ConfigureAwait(false); + } + else + { + await ReplyErrorLocalized("club_level_req_change_error").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task ClubDisband() + { + if (_service.Disband(Context.User.Id, out ClubInfo club)) + { + await ReplyConfirmLocalized("club_disbanded", Format.Bold(club.ToString())).ConfigureAwait(false); + } + else + { + await ReplyErrorLocalized("club_disband_error").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + public Task ClubLeaderboard(int page = 1) + { + if (--page < 0) + return Task.CompletedTask; + + var clubs = _service.GetClubLeaderboardPage(page); + + var embed = new EmbedBuilder() + .WithTitle(GetText("club_leaderboard", page + 1)) + .WithOkColor(); + + var i = page * 9; + foreach (var club in clubs) + { + embed.AddField($"#{++i} " + club.ToString(), club.Xp.ToString() + " xp", false); + } + + return Context.Channel.EmbedAsync(embed); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Common/FullUserStats.cs b/src/NadekoBot/Modules/Xp/Common/FullUserStats.cs new file mode 100644 index 00000000..11f38648 --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Common/FullUserStats.cs @@ -0,0 +1,26 @@ +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Modules.Xp.Common +{ + public class FullUserStats + { + public DiscordUser User { get; } + public UserXpStats FullGuildStats { get; } + public LevelStats Global { get; } + public LevelStats Guild { get; } + public int GlobalRanking { get; } + public int GuildRanking { get; } + + public FullUserStats(DiscordUser usr, + UserXpStats fullGuildStats, LevelStats global, + LevelStats guild, int globalRanking, int guildRanking) + { + this.User = usr; + this.Global = global; + this.Guild = guild; + this.GlobalRanking = globalRanking; + this.GuildRanking = guildRanking; + this.FullGuildStats = fullGuildStats; + } + } +} diff --git a/src/NadekoBot/Modules/Xp/Common/LevelStats.cs b/src/NadekoBot/Modules/Xp/Common/LevelStats.cs new file mode 100644 index 00000000..11dfc8c7 --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Common/LevelStats.cs @@ -0,0 +1,43 @@ +using NadekoBot.Modules.Xp.Services; +using System; + +namespace NadekoBot.Modules.Xp.Common +{ + public class LevelStats + { + public int Level { get; } + public int LevelXp { get; } + public int RequiredXp { get; } + public int TotalXp { get; } + + public LevelStats(int xp) + { + if (xp < 0) + xp = 0; + + TotalXp = xp; + + const int baseXp = XpService.XP_REQUIRED_LVL_1; + + var required = baseXp; + var totalXp = 0; + var lvl = 1; + while (true) + { + required = (int)(baseXp + baseXp / 4.0 * (lvl - 1)); + + if (required + totalXp > xp) + break; + + totalXp += required; + lvl++; + } + + Level = lvl - 1; + LevelXp = xp - totalXp; + RequiredXp = required; + } + + public static LevelStats FromXp(int xp) => new LevelStats(xp); + } +} diff --git a/src/NadekoBot/Modules/Xp/Extensions/Extensions.cs b/src/NadekoBot/Modules/Xp/Extensions/Extensions.cs new file mode 100644 index 00000000..c5d6605b --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Extensions/Extensions.cs @@ -0,0 +1,34 @@ +using NadekoBot.Modules.Xp.Services; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Xp.Extensions +{ + public static class Extensions + { + public static (int Level, int LevelXp, int LevelRequiredXp) GetLevelData(this UserXpStats stats) + { + var baseXp = XpService.XP_REQUIRED_LVL_1; + + var required = baseXp; + var totalXp = 0; + var lvl = 1; + while (true) + { + required = (int)(baseXp + baseXp / 4.0 * (lvl - 1)); + + if (required + totalXp > stats.Xp) + break; + + totalXp += required; + lvl++; + } + + return (lvl - 1, stats.Xp - totalXp, required); + } + } +} diff --git a/src/NadekoBot/Modules/Xp/Services/ClubService.cs b/src/NadekoBot/Modules/Xp/Services/ClubService.cs new file mode 100644 index 00000000..4c61d7ab --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Services/ClubService.cs @@ -0,0 +1,324 @@ +using NadekoBot.Services; +using System; +using NadekoBot.Services.Database.Models; +using Discord; +using NadekoBot.Modules.Xp.Common; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; + +namespace NadekoBot.Modules.Xp.Services +{ + public class ClubService : INService + { + private readonly DbService _db; + + public ClubService(DbService db) + { + _db = db; + } + + public bool CreateClub(IUser user, string clubName, out ClubInfo club) + { + //must be lvl 5 and must not be in a club already + + club = null; + using (var uow = _db.UnitOfWork) + { + var du = uow.DiscordUsers.GetOrCreate(user); + uow._context.SaveChanges(); + var xp = new LevelStats(du.TotalXp); + + if (xp.Level >= 5 && du.Club == null) + { + du.IsClubAdmin = true; + du.Club = new ClubInfo() + { + Name = clubName, + Discrim = uow.Clubs.GetNextDiscrim(clubName), + Owner = du, + }; + uow.Clubs.Add(du.Club); + uow._context.SaveChanges(); + } + else + return false; + + uow._context.Set() + .RemoveRange(uow._context.Set().Where(x => x.UserId == du.Id)); + club = du.Club; + uow.Complete(); + } + + return true; + } + + public bool ToggleAdmin(IUser owner, IUser toAdmin) + { + bool newState; + using (var uow = _db.UnitOfWork) + { + var club = uow.Clubs.GetByOwner(owner.Id); + var adminUser = uow.DiscordUsers.GetOrCreate(toAdmin); + + if (club.OwnerId == adminUser.Id) + return true; + + if (club == null || club.Owner.UserId != owner.Id || + !club.Users.Contains(adminUser)) + throw new InvalidOperationException(); + + newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin; + uow.Complete(); + } + return newState; + } + + public ClubInfo GetClubByMember(IUser user) + { + using (var uow = _db.UnitOfWork) + { + return uow.Clubs.GetByMember(user.Id); + } + } + + public bool SetClubIcon(ulong ownerUserId, string url) + { + using (var uow = _db.UnitOfWork) + { + var club = uow.Clubs.GetByOwner(ownerUserId, set => set); + + if (club == null) + return false; + + club.ImageUrl = url; + uow.Complete(); + } + + return true; + } + + public bool GetClubByName(string clubName, out ClubInfo club) + { + club = null; + var arr = clubName.Split('#'); + if (arr.Length < 2 || !int.TryParse(arr[arr.Length - 1], out var discrim)) + return false; + + //incase club has # in it + var name = string.Concat(arr.Except(new[] { arr[arr.Length - 1] })); + + if (string.IsNullOrWhiteSpace(name)) + return false; + + using (var uow = _db.UnitOfWork) + { + club = uow.Clubs.GetByName(name, discrim); + if (club == null) + return false; + else + return true; + } + } + + public bool ApplyToClub(IUser user, ClubInfo club) + { + using (var uow = _db.UnitOfWork) + { + var du = uow.DiscordUsers.GetOrCreate(user); + uow._context.SaveChanges(); + + if (du.Club != null + || new LevelStats(du.TotalXp).Level < club.MinimumLevelReq + || club.Bans.Any(x => x.UserId == du.Id) + || club.Applicants.Any(x => x.UserId == du.Id)) + { + //user banned or a member of a club, or already applied, + // or doesn't min minumum level requirement, can't apply + return false; + } + + var app = new ClubApplicants + { + ClubId = club.Id, + UserId = du.Id, + }; + + uow._context.Set().Add(app); + + uow.Complete(); + } + return true; + } + + public bool AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser) + { + discordUser = null; + using (var uow = _db.UnitOfWork) + { + var club = uow.Clubs.GetByOwnerOrAdmin(clubOwnerUserId); + if (club == null) + return false; + + var applicant = club.Applicants.FirstOrDefault(x => x.User.ToString().ToLowerInvariant() == userName.ToLowerInvariant()); + if (applicant == null) + return false; + + applicant.User.Club = club; + applicant.User.IsClubAdmin = false; + club.Applicants.Remove(applicant); + + //remove that user's all other applications + uow._context.Set() + .RemoveRange(uow._context.Set().Where(x => x.UserId == applicant.User.Id)); + + discordUser = applicant.User; + uow.Complete(); + } + return true; + } + + public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId) + { + using (var uow = _db.UnitOfWork) + { + return uow.Clubs.GetByOwnerOrAdmin(ownerUserId); + } + } + + public bool LeaveClub(IUser user) + { + using (var uow = _db.UnitOfWork) + { + var du = uow.DiscordUsers.GetOrCreate(user); + if (du.Club == null || du.Club.OwnerId == du.Id) + return false; + + du.Club = null; + du.IsClubAdmin = false; + uow.Complete(); + } + return true; + } + + public bool ChangeClubLevelReq(ulong userId, int level) + { + if (level < 5) + return false; + + using (var uow = _db.UnitOfWork) + { + var club = uow.Clubs.GetByOwner(userId); + if (club == null) + return false; + + club.MinimumLevelReq = level; + uow.Complete(); + } + + return true; + } + + public bool Disband(ulong userId, out ClubInfo club) + { + using (var uow = _db.UnitOfWork) + { + club = uow.Clubs.GetByOwner(userId); + if (club == null) + return false; + + uow.Clubs.Remove(club); + uow.Complete(); + } + return true; + } + + public bool Ban(ulong ownerUserId, string userName, out ClubInfo club) + { + using (var uow = _db.UnitOfWork) + { + club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); + if (club == null) + return false; + + var usr = club.Users.FirstOrDefault(x => x.ToString().ToLowerInvariant() == userName.ToLowerInvariant()) + ?? club.Applicants.FirstOrDefault(x => x.User.ToString().ToLowerInvariant() == userName.ToLowerInvariant())?.User; + if (usr == null) + return false; + + if (club.OwnerId == usr.Id) // can't ban the owner kek, whew + return false; + + club.Bans.Add(new ClubBans + { + Club = club, + User = usr, + }); + club.Users.Remove(usr); + + var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id); + if (app != null) + club.Applicants.Remove(app); + + uow.Complete(); + } + + return true; + } + + public bool UnBan(ulong ownerUserId, string userName, out ClubInfo club) + { + using (var uow = _db.UnitOfWork) + { + club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); + if (club == null) + return false; + + var ban = club.Bans.FirstOrDefault(x => x.User.ToString().ToLowerInvariant() == userName.ToLowerInvariant()); + if (ban == null) + return false; + + club.Bans.Remove(ban); + uow.Complete(); + } + + return true; + } + + public bool Kick(ulong ownerUserId, string userName, out ClubInfo club) + { + using (var uow = _db.UnitOfWork) + { + club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); + if (club == null) + return false; + + var usr = club.Users.FirstOrDefault(x => x.ToString().ToLowerInvariant() == userName.ToLowerInvariant()); + if (usr == null) + return false; + + if (club.OwnerId == usr.Id) + return false; + + club.Users.Remove(usr); + var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id); + if (app != null) + club.Applicants.Remove(app); + uow.Complete(); + } + + return true; + } + + public ClubInfo[] GetClubLeaderboardPage(int page) + { + if (page < 0) + throw new ArgumentOutOfRangeException(nameof(page)); + + using (var uow = _db.UnitOfWork) + { + return uow.Clubs.GetClubLeaderboardPage(page); + } + } + } +} diff --git a/src/NadekoBot/Modules/Xp/Services/UserCacheItem.cs b/src/NadekoBot/Modules/Xp/Services/UserCacheItem.cs new file mode 100644 index 00000000..d1a07c11 --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Services/UserCacheItem.cs @@ -0,0 +1,21 @@ +using Discord; + +namespace NadekoBot.Modules.Xp.Services +{ + public class UserCacheItem + { + public IGuildUser User { get; set; } + public IGuild Guild { get; set; } + public IMessageChannel Channel { get; set; } + + public override int GetHashCode() + { + return User.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is UserCacheItem uci && uci.User == User; + } + } +} diff --git a/src/NadekoBot/Modules/Xp/Services/XpService.cs b/src/NadekoBot/Modules/Xp/Services/XpService.cs new file mode 100644 index 00000000..09fa7ec9 --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Services/XpService.cs @@ -0,0 +1,755 @@ +using Discord; +using Discord.WebSocket; +using NadekoBot.Common.Collections; +using NadekoBot.Extensions; +using NadekoBot.Modules.Xp.Common; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using NLog; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ImageSharp; +using Image = ImageSharp.Image; +using SixLabors.Fonts; +using System.IO; +using SixLabors.Primitives; +using System.Net.Http; +using SixLabors.Shapes; +using System.Numerics; +using ImageSharp.Drawing.Pens; +using ImageSharp.Drawing.Brushes; + +namespace NadekoBot.Modules.Xp.Services +{ + public class XpService : INService + { + private enum NotifOf { Server, Global } // is it a server level-up or global level-up notification + + private readonly DbService _db; + private readonly CommandHandler _cmd; + private readonly IBotConfigProvider _bc; + private readonly IImagesService _images; + private readonly Logger _log; + private readonly NadekoStrings _strings; + private readonly IDataCache _cache; + private readonly FontCollection _fonts = new FontCollection(); + public const int XP_REQUIRED_LVL_1 = 36; + + private readonly ConcurrentDictionary> _excludedRoles + = new ConcurrentDictionary>(); + + private readonly ConcurrentDictionary> _excludedChannels + = new ConcurrentDictionary>(); + + private readonly ConcurrentHashSet _excludedServers + = new ConcurrentHashSet(); + + private readonly ConcurrentHashSet _rewardedUsers + = new ConcurrentHashSet(); + + private readonly ConcurrentQueue _addMessageXp + = new ConcurrentQueue(); + + private readonly Timer updateXpTimer; + private readonly HttpClient http = new HttpClient(); + private FontFamily _usernameFontFamily; + private FontFamily _clubFontFamily; + private Font _levelFont; + private Font _xpFont; + private Font _awardedFont; + private Font _rankFont; + private Font _timeFont; + + public XpService(CommandHandler cmd, IBotConfigProvider bc, + IEnumerable allGuildConfigs, IImagesService images, + DbService db, NadekoStrings strings, IDataCache cache) + { + _db = db; + _cmd = cmd; + _bc = bc; + _images = images; + _log = LogManager.GetCurrentClassLogger(); + _strings = strings; + _cache = cache; + + //load settings + allGuildConfigs = allGuildConfigs.Where(x => x.XpSettings != null); + _excludedChannels = allGuildConfigs + .ToDictionary( + x => x.GuildId, + x => new ConcurrentHashSet(x.XpSettings + .ExclusionList + .Where(ex => ex.ItemType == ExcludedItemType.Channel) + .Select(ex => ex.ItemId) + .Distinct())) + .ToConcurrent(); + + _excludedRoles = allGuildConfigs + .ToDictionary( + x => x.GuildId, + x => new ConcurrentHashSet(x.XpSettings + .ExclusionList + .Where(ex => ex.ItemType == ExcludedItemType.Role) + .Select(ex => ex.ItemId) + .Distinct())) + .ToConcurrent(); + + _excludedServers = new ConcurrentHashSet( + allGuildConfigs.Where(x => x.XpSettings.ServerExcluded) + .Select(x => x.GuildId)); + + //todo 60 move to font provider or somethign + _fonts = new FontCollection(); + if (Directory.Exists("data/fonts")) + foreach (var file in Directory.GetFiles("data/fonts")) + { + _fonts.Install(file); + } + + InitializeFonts(); + + _cmd.OnMessageNoTrigger += _cmd_OnMessageNoTrigger; + + updateXpTimer = new Timer(async _ => + { + try + { + var toNotify = new List<(IMessageChannel MessageChannel, IUser User, int Level, XpNotificationType NotifyType, NotifOf NotifOf)>(); + var roleRewards = new Dictionary>(); + + var toAddTo = new List(); + while (_addMessageXp.TryDequeue(out var usr)) + toAddTo.Add(usr); + + var group = toAddTo.GroupBy(x => (GuildId: x.Guild.Id, User: x.User)); + if (toAddTo.Count == 0) + return; + + using (var uow = _db.UnitOfWork) + { + foreach (var item in group) + { + var xp = item.Select(x => bc.BotConfig.XpPerMessage).Sum(); + + var usr = uow.Xp.GetOrCreateUser(item.Key.GuildId, item.Key.User.Id); + var du = uow.DiscordUsers.GetOrCreate(item.Key.User); + + if (du.LastXpGain + TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout) > DateTime.UtcNow) + continue; + + du.LastXpGain = DateTime.UtcNow; + + var globalXp = du.TotalXp; + var oldGlobalLevelData = new LevelStats(globalXp); + var newGlobalLevelData = new LevelStats(globalXp + xp); + + var oldGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp); + usr.Xp += xp; + du.TotalXp += xp; + if (du.Club != null) + du.Club.Xp += xp; + var newGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp); + + if (oldGlobalLevelData.Level < newGlobalLevelData.Level) + { + du.LastLevelUp = DateTime.UtcNow; + var first = item.First(); + if (du.NotifyOnLevelUp != XpNotificationType.None) + toNotify.Add((first.Channel, first.User, newGlobalLevelData.Level, du.NotifyOnLevelUp, NotifOf.Global)); + } + + if (oldGuildLevelData.Level < newGuildLevelData.Level) + { + usr.LastLevelUp = DateTime.UtcNow; + //send level up notification + var first = item.First(); + if (usr.NotifyOnLevelUp != XpNotificationType.None) + toNotify.Add((first.Channel, first.User, newGuildLevelData.Level, usr.NotifyOnLevelUp, NotifOf.Server)); + + //give role + if (!roleRewards.TryGetValue(usr.GuildId, out var rewards)) + { + rewards = uow.GuildConfigs.XpSettingsFor(usr.GuildId).RoleRewards.ToList(); + roleRewards.Add(usr.GuildId, rewards); + } + + var rew = rewards.FirstOrDefault(x => x.Level == newGuildLevelData.Level); + if (rew != null) + { + var role = first.User.Guild.GetRole(rew.RoleId); + if (role != null) + { + var __ = first.User.AddRoleAsync(role); + } + } + } + } + + uow.Complete(); + } + + await Task.WhenAll(toNotify.Select(async x => + { + if (x.NotifOf == NotifOf.Server) + { + if (x.NotifyType == XpNotificationType.Dm) + { + var chan = await x.User.GetOrCreateDMChannelAsync().ConfigureAwait(false); + if (chan != null) + await chan.SendConfirmAsync(_strings.GetText("level_up_dm", + (x.MessageChannel as ITextChannel)?.GuildId, + "xp", + x.User.Mention, Format.Bold(x.Level.ToString()), + Format.Bold((x.MessageChannel as ITextChannel)?.Guild.ToString() ?? "-"))) + .ConfigureAwait(false); + } + else // channel + { + await x.MessageChannel.SendConfirmAsync(_strings.GetText("level_up_channel", + (x.MessageChannel as ITextChannel)?.GuildId, + "xp", + x.User.Mention, Format.Bold(x.Level.ToString()))) + .ConfigureAwait(false); + } + } + else + { + IMessageChannel chan; + if (x.NotifyType == XpNotificationType.Dm) + { + chan = await x.User.GetOrCreateDMChannelAsync().ConfigureAwait(false); + } + else // channel + { + chan = x.MessageChannel; + } + await chan.SendConfirmAsync(_strings.GetText("level_up_global", + (x.MessageChannel as ITextChannel)?.GuildId, + "xp", + x.User.Mention, Format.Bold(x.Level.ToString()))) + .ConfigureAwait(false); + } + })); + } + catch (Exception ex) + { + _log.Warn(ex); + } + }, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5)); + + + //just a first line, in order to prevent queries. But since other shards can try to do this too, + //i'll check in the db too. + var clearRewardTimer = Task.Run(async () => + { + while (true) + { + _rewardedUsers.Clear(); + + await Task.Delay(TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout)); + } + }); + } + + public IEnumerable GetRoleRewards(ulong id) + { + using (var uow = _db.UnitOfWork) + { + return uow.GuildConfigs.XpSettingsFor(id) + .RoleRewards + .ToArray(); + } + } + + public void SetRoleReward(ulong guildId, int level, ulong? roleId) + { + using (var uow = _db.UnitOfWork) + { + var settings = uow.GuildConfigs.XpSettingsFor(guildId); + + if (roleId == null) + { + var toRemove = settings.RoleRewards.FirstOrDefault(x => x.Level == level); + if (toRemove != null) + { + uow._context.Remove(toRemove); + settings.RoleRewards.Remove(toRemove); + } + } + else + { + + var rew = settings.RoleRewards.FirstOrDefault(x => x.Level == level); + + if (rew != null) + rew.RoleId = roleId.Value; + else + settings.RoleRewards.Add(new XpRoleReward() + { + Level = level, + RoleId = roleId.Value, + }); + } + + uow.Complete(); + } + } + + public UserXpStats[] GetUserXps(ulong guildId, int page) + { + using (var uow = _db.UnitOfWork) + { + return uow.Xp.GetUsersFor(guildId, page); + } + } + + public DiscordUser[] GetUserXps(int page) + { + using (var uow = _db.UnitOfWork) + { + return uow.DiscordUsers.GetUsersXpLeaderboardFor(page); + } + } + + public async Task ChangeNotificationType(ulong userId, ulong guildId, XpNotificationType type) + { + using (var uow = _db.UnitOfWork) + { + var user = uow.Xp.GetOrCreateUser(guildId, userId); + user.NotifyOnLevelUp = type; + await uow.CompleteAsync().ConfigureAwait(false); + } + } + + public async Task ChangeNotificationType(IUser user, XpNotificationType type) + { + using (var uow = _db.UnitOfWork) + { + var du = uow.DiscordUsers.GetOrCreate(user); + du.NotifyOnLevelUp = type; + await uow.CompleteAsync().ConfigureAwait(false); + } + } + + private Task _cmd_OnMessageNoTrigger(IUserMessage arg) + { + if (!(arg.Author is SocketGuildUser user) || user.IsBot) + return Task.CompletedTask; + + var _ = Task.Run(() => + { + if (_excludedChannels.TryGetValue(user.Guild.Id, out var chans) && + chans.Contains(arg.Channel.Id)) + return; + + if (_excludedServers.Contains(user.Guild.Id)) + return; + + if (_excludedRoles.TryGetValue(user.Guild.Id, out var roles) && + user.Roles.Any(x => roles.Contains(x.Id))) + return; + + if (!arg.Content.Contains(' ') && arg.Content.Length < 5) + return; + + if (!SetUserRewarded(user.Id)) + return; + + _addMessageXp.Enqueue(new UserCacheItem { Guild = user.Guild, Channel = arg.Channel, User = user }); + }); + return Task.CompletedTask; + } + + public void AddXp(ulong userId, ulong guildId, int amount) + { + using (var uow = _db.UnitOfWork) + { + var usr = uow.Xp.GetOrCreateUser(guildId, userId); + + usr.AwardedXp += amount; + + uow.Complete(); + } + } + + public bool IsServerExcluded(ulong id) + { + return _excludedServers.Contains(id); + } + + public IEnumerable GetExcludedRoles(ulong id) + { + if (_excludedRoles.TryGetValue(id, out var val)) + return val.ToArray(); + + return Enumerable.Empty(); + } + + public IEnumerable GetExcludedChannels(ulong id) + { + if (_excludedChannels.TryGetValue(id, out var val)) + return val.ToArray(); + + return Enumerable.Empty(); + } + + private bool SetUserRewarded(ulong userId) + { + return _rewardedUsers.Add(userId); + } + + public FullUserStats GetUserStats(IGuildUser user) + { + DiscordUser du; + UserXpStats stats; + int totalXp; + int globalRank; + int guildRank; + using (var uow = _db.UnitOfWork) + { + du = uow.DiscordUsers.GetOrCreate(user); + stats = uow.Xp.GetOrCreateUser(user.GuildId, user.Id); + totalXp = du.TotalXp; + globalRank = uow.DiscordUsers.GetUserGlobalRanking(user.Id); + guildRank = uow.Xp.GetUserGuildRanking(user.Id, user.GuildId); + } + + return new FullUserStats(du, + stats, + new LevelStats(totalXp), + new LevelStats(stats.Xp + stats.AwardedXp), + globalRank, + guildRank); + } + + public static (int Level, int LevelXp, int LevelRequiredXp) GetLevelData(UserXpStats stats) + { + var baseXp = XpService.XP_REQUIRED_LVL_1; + + var required = baseXp; + var totalXp = 0; + var lvl = 1; + while (true) + { + required = (int)(baseXp + baseXp / 4.0 * (lvl - 1)); + + if (required + totalXp > stats.Xp) + break; + + totalXp += required; + lvl++; + } + + return (lvl - 1, stats.Xp - totalXp, required); + } + + public bool ToggleExcludeServer(ulong id) + { + using (var uow = _db.UnitOfWork) + { + var xpSetting = uow.GuildConfigs.XpSettingsFor(id); + if (_excludedServers.Add(id)) + { + xpSetting.ServerExcluded = true; + uow.Complete(); + return true; + } + + _excludedServers.TryRemove(id); + xpSetting.ServerExcluded = false; + uow.Complete(); + return false; + } + } + + public bool ToggleExcludeRole(ulong guildId, ulong rId) + { + var roles = _excludedRoles.GetOrAdd(guildId, _ => new ConcurrentHashSet()); + using (var uow = _db.UnitOfWork) + { + var xpSetting = uow.GuildConfigs.XpSettingsFor(guildId); + var excludeObj = new ExcludedItem + { + ItemId = rId, + ItemType = ExcludedItemType.Role, + }; + + if (roles.Add(rId)) + { + + if (xpSetting.ExclusionList.Add(excludeObj)) + { + uow.Complete(); + } + + return true; + } + else + { + roles.TryRemove(rId); + + if (xpSetting.ExclusionList.Remove(excludeObj)) + { + uow.Complete(); + } + + return false; + } + } + } + + public bool ToggleExcludeChannel(ulong guildId, ulong chId) + { + var channels = _excludedChannels.GetOrAdd(guildId, _ => new ConcurrentHashSet()); + using (var uow = _db.UnitOfWork) + { + var xpSetting = uow.GuildConfigs.XpSettingsFor(guildId); + var excludeObj = new ExcludedItem + { + ItemId = chId, + ItemType = ExcludedItemType.Channel, + }; + + if (channels.Add(chId)) + { + + if (xpSetting.ExclusionList.Add(excludeObj)) + { + uow.Complete(); + } + + return true; + } + else + { + channels.TryRemove(chId); + + if (xpSetting.ExclusionList.Remove(excludeObj)) + { + uow.Complete(); + } + + return false; + } + } + } + + public Task GenerateImageAsync(IGuildUser user) + { + return GenerateImageAsync(GetUserStats(user)); + } + + private void InitializeFonts() + { + _usernameFontFamily = _fonts.Find("Whitney-Bold"); + _clubFontFamily = _fonts.Find("Whitney-Bold"); + _levelFont = _fonts.Find("Whitney-Bold").CreateFont(45); + _xpFont = _fonts.Find("Whitney-Bold").CreateFont(50); + _awardedFont = _fonts.Find("Whitney-Bold").CreateFont(25); + _rankFont = _fonts.Find("Uni Sans Thin CAPS").CreateFont(30); + _timeFont = _fonts.Find("Whitney-Bold").CreateFont(20); + } + + public Task GenerateImageAsync(FullUserStats stats) => Task.Run(async () => + { + using (var img = Image.Load(_images.XpCard.ToArray())) + { + + var username = stats.User.ToString(); + var usernameFont = _usernameFontFamily + .CreateFont(username.Length <= 6 + ? 50 + : 50 - username.Length); + + img.DrawText("@" + username, usernameFont, Rgba32.White, + new PointF(130, 5)); + + // level + + img.DrawText(stats.Global.Level.ToString(), _levelFont, Rgba32.White, + new PointF(47, 137)); + + img.DrawText(stats.Guild.Level.ToString(), _levelFont, Rgba32.White, + new PointF(47, 285)); + + //club name + + var clubName = stats.User.Club?.ToString() ?? "-"; + + var clubFont = _clubFontFamily + .CreateFont(clubName.Length <= 8 + ? 35 + : 35 - (clubName.Length / 2)); + + img.DrawText(clubName, clubFont, Rgba32.White, + new PointF(650 - clubName.Length * 10, 40)); + + var pen = new Pen(Rgba32.Black, 1); + var brush = Brushes.Solid(Rgba32.White); + var xpBgBrush = Brushes.Solid(new Rgba32(0, 0, 0, 0.4f)); + + var global = stats.Global; + var guild = stats.Guild; + + //xp bar + + img.FillPolygon(xpBgBrush, new[] { + new PointF(321, 104), + new PointF(321 + (450 * (global.LevelXp / (float)global.RequiredXp)), 104), + new PointF(286 + (450 * (global.LevelXp / (float)global.RequiredXp)), 235), + new PointF(286, 235), + }); + img.DrawText($"{global.LevelXp}/{global.RequiredXp}", _xpFont, brush, pen, + new PointF(430, 130)); + + img.FillPolygon(xpBgBrush, new[] { + new PointF(282, 248), + new PointF(282 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 248), + new PointF(247 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 379), + new PointF(247, 379), + }); + img.DrawText($"{guild.LevelXp}/{guild.RequiredXp}", _xpFont, brush, pen, + new PointF(400, 270)); + + if (stats.FullGuildStats.AwardedXp != 0) + { + var sign = stats.FullGuildStats.AwardedXp > 0 + ? "+ " + : ""; + img.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})", _awardedFont, brush, pen, + new PointF(445 - (Math.Max(0, (stats.FullGuildStats.AwardedXp.ToString().Length - 2)) * 5), 335)); + } + + //ranking + + img.DrawText(stats.GlobalRanking.ToString(), _rankFont, Rgba32.White, + new PointF(148, 170)); + + img.DrawText(stats.GuildRanking.ToString(), _rankFont, Rgba32.White, + new PointF(148, 317)); + + //time on this level + + string GetTimeSpent(DateTime time) + { + var offset = DateTime.UtcNow - time; + return $"{offset.Days}d{offset.Hours}h{offset.Minutes}m"; + } + + img.DrawText(GetTimeSpent(stats.User.LastLevelUp), _timeFont, Rgba32.White, + new PointF(50, 197)); + + img.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp), _timeFont, Rgba32.White, + new PointF(50, 344)); + + //avatar + + if (stats.User.AvatarId != null) + { + try + { + var avatarUrl = stats.User.RealAvatarUrl(); + + var (succ, data) = await _cache.TryGetImageDataAsync(avatarUrl); + if (!succ) + { + using (var temp = await http.GetStreamAsync(avatarUrl)) + using (var tempDraw = Image.Load(temp).Resize(69, 70)) + { + ApplyRoundedCorners(tempDraw, 35); + data = tempDraw.ToStream().ToArray(); + } + + await _cache.SetImageDataAsync(avatarUrl, data); + } + var toDraw = Image.Load(data); + + + img.DrawImage(toDraw, + 1, + new Size(69, 70), + new Point(32, 10)); + } + catch (Exception ex) + { + _log.Warn(ex); + } + } + + //club image + + if (!string.IsNullOrWhiteSpace(stats.User.Club?.ImageUrl)) + { + var imgUrl = stats.User.Club.ImageUrl; + try + { + var (succ, data) = await _cache.TryGetImageDataAsync(imgUrl); + if (!succ) + { + using (var temp = await http.GetStreamAsync(imgUrl)) + using (var tempDraw = Image.Load(temp).Resize(45, 45)) + { + ApplyRoundedCorners(tempDraw, 22.5f); + data = tempDraw.ToStream().ToArray(); + } + + await _cache.SetImageDataAsync(imgUrl, data); + } + var toDraw = Image.Load(data); + + img.DrawImage(toDraw, + 1, + new Size(45, 45), + new Point(722, 25)); + } + catch (Exception ex) + { + _log.Warn(ex); + } + } + + return img.Resize(432, 211).ToStream(); + } + }); + + + // https://github.com/SixLabors/ImageSharp/tree/master/samples/AvatarWithRoundedCorner + public static void ApplyRoundedCorners(Image img, float cornerRadius) + { + var corners = BuildCorners(img.Width, img.Height, cornerRadius); + // now we have our corners time to draw them + img.Fill(Rgba32.Transparent, corners, new GraphicsOptions(true) + { + BlenderMode = ImageSharp.PixelFormats.PixelBlenderMode.Src // enforces that any part of this shape that has color is punched out of the background + }); + } + + public static IPathCollection BuildCorners(int imageWidth, int imageHeight, float cornerRadius) + { + // first create a square + var rect = new RectangularePolygon(-0.5f, -0.5f, cornerRadius, cornerRadius); + + // then cut out of the square a circle so we are left with a corner + var cornerToptLeft = rect.Clip(new EllipsePolygon(cornerRadius - 0.5f, cornerRadius - 0.5f, cornerRadius)); + + // corner is now a corner shape positions top left + //lets make 3 more positioned correctly, we can do that by translating the orgional around the center of the image + var center = new Vector2(imageWidth / 2, imageHeight / 2); + + float rightPos = imageWidth - cornerToptLeft.Bounds.Width + 1; + float bottomPos = imageHeight - cornerToptLeft.Bounds.Height + 1; + + // move it across the width of the image - the width of the shape + var cornerTopRight = cornerToptLeft.RotateDegree(90).Translate(rightPos, 0); + var cornerBottomLeft = cornerToptLeft.RotateDegree(-90).Translate(0, bottomPos); + var cornerBottomRight = cornerToptLeft.RotateDegree(180).Translate(rightPos, bottomPos); + + return new PathCollection(cornerToptLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight); + } + } +} diff --git a/src/NadekoBot/Modules/Xp/Xp.cs b/src/NadekoBot/Modules/Xp/Xp.cs new file mode 100644 index 00000000..a3356372 --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Xp.cs @@ -0,0 +1,284 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Common; +using NadekoBot.Common.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Modules.Xp.Common; +using NadekoBot.Modules.Xp.Services; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Xp +{ + public partial class Xp : NadekoTopLevelModule + { + private readonly DiscordSocketClient _client; + private readonly DbService _db; + + public Xp(DiscordSocketClient client,DbService db) + { + _client = client; + _db = db; + } + + //[NadekoCommand, Usage, Description, Aliases] + //[RequireContext(ContextType.Guild)] + //[OwnerOnly] + //public async Task Populate() + //{ + // var rng = new NadekoRandom(); + // using (var uow = _db.UnitOfWork) + // { + // for (var i = 0ul; i < 1000000; i++) + // { + // uow.DiscordUsers.Add(new DiscordUser() + // { + // AvatarId = i.ToString(), + // Discriminator = "1234", + // UserId = i, + // Username = i.ToString(), + // Club = null, + // }); + // var xp = uow.Xp.GetOrCreateUser(Context.Guild.Id, i); + // xp.Xp = rng.Next(100, 100000); + // } + // uow.Complete(); + // } + //} + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + //[Ratelimit(30)] + public async Task Experience([Remainder]IUser user = null) + { + user = user ?? Context.User; + var sw = Stopwatch.StartNew(); + await Context.Channel.TriggerTypingAsync(); + var img = await _service.GenerateImageAsync((IGuildUser)user); + sw.Stop(); + _log.Info("Generating finished in {0:F2}s", sw.Elapsed.TotalSeconds); + sw.Restart(); + await Context.Channel.SendFileAsync(img, $"{user.Id}_xp.png") + .ConfigureAwait(false); + sw.Stop(); + _log.Info("Sending finished in {0:F2}s", sw.Elapsed.TotalSeconds); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public Task XpRoleRewards(int page = 1) + { + page--; + + if (page < 0 || page > 100) + return Task.CompletedTask; + + var roles = _service.GetRoleRewards(Context.Guild.Id) + .OrderBy(x => x.Level) + .Skip(page * 9) + .Take(9); + + var embed = new EmbedBuilder() + .WithTitle(GetText("role_rewards")) + .WithOkColor(); + + if (!roles.Any()) + return Context.Channel.EmbedAsync(embed.WithDescription(GetText("no_role_rewards"))); + + foreach (var rolerew in roles) + { + var role = Context.Guild.GetRole(rolerew.RoleId); + + if (role == null) + continue; + + embed.AddField(GetText("level_x", Format.Bold(rolerew.Level.ToString())), role.ToString()); + } + return Context.Channel.EmbedAsync(embed); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireUserPermission(GuildPermission.ManageRoles)] + [RequireContext(ContextType.Guild)] + public async Task XpRoleReward(int level, [Remainder] IRole role = null) + { + if (level < 1) + return; + + _service.SetRoleReward(Context.Guild.Id, level, role?.Id); + + if(role == null) + await ReplyConfirmLocalized("role_reward_cleared", level).ConfigureAwait(false); + else + await ReplyConfirmLocalized("role_reward_added", level, Format.Bold(role.ToString())).ConfigureAwait(false); + } + + public enum NotifyPlace + { + Server = 0, + Guild = 0, + Global = 1, + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task XpNotify(NotifyPlace place = NotifyPlace.Guild, XpNotificationType type = XpNotificationType.Channel) + { + if (place == NotifyPlace.Guild) + await _service.ChangeNotificationType(Context.User.Id, Context.Guild.Id, type); + else + await _service.ChangeNotificationType(Context.User, type); + await Context.Channel.SendConfirmAsync("👌").ConfigureAwait(false); + } + + public enum Server { Server }; + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + public async Task XpExclude(Server _) + { + var ex = _service.ToggleExcludeServer(Context.Guild.Id); + + await ReplyConfirmLocalized((ex ? "excluded" : "not_excluded"), Format.Bold(Context.Guild.ToString())).ConfigureAwait(false); + } + + public enum Role { Role }; + + [NadekoCommand, Usage, Description, Aliases] + [RequireUserPermission(GuildPermission.ManageRoles)] + [RequireContext(ContextType.Guild)] + public async Task XpExclude(Role _, [Remainder] IRole role) + { + var ex = _service.ToggleExcludeRole(Context.Guild.Id, role.Id); + + await ReplyConfirmLocalized((ex ? "excluded" : "not_excluded"), Format.Bold(role.ToString())).ConfigureAwait(false); + } + + public enum Channel { Channel }; + + [NadekoCommand, Usage, Description, Aliases] + [RequireUserPermission(GuildPermission.ManageChannels)] + [RequireContext(ContextType.Guild)] + public async Task XpExclude(Channel _, [Remainder] ITextChannel channel = null) + { + if (channel == null) + channel = (ITextChannel)Context.Channel; + + var ex = _service.ToggleExcludeChannel(Context.Guild.Id, channel.Id); + + await ReplyConfirmLocalized((ex ? "excluded" : "not_excluded"), Format.Bold(channel.ToString())).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task XpExclusionList() + { + var serverExcluded = _service.IsServerExcluded(Context.Guild.Id); + var roles = _service.GetExcludedRoles(Context.Guild.Id) + .Select(x => Context.Guild.GetRole(x)?.Name) + .Where(x => x != null); + + var chans = (await Task.WhenAll(_service.GetExcludedChannels(Context.Guild.Id) + .Select(x => Context.Guild.GetChannelAsync(x))) + .ConfigureAwait(false)) + .Where(x => x != null) + .Select(x => x.Name); + + var embed = new EmbedBuilder() + .WithTitle(GetText("exclusion_list")) + .WithDescription((serverExcluded ? GetText("server_is_excluded") : GetText("server_is_not_excluded"))) + .AddField(GetText("excluded_roles"), roles.Any() ? string.Join("\n", roles) : "-", false) + .AddField(GetText("excluded_channels"), chans.Any() ? string.Join("\n", chans) : "-", false) + .WithOkColor(); + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public Task XpLeaderboard(int page = 1) + { + if (--page < 0 || page > 100) + return Task.CompletedTask; + + return Context.Channel.SendPaginatedConfirmAsync(_client, page, async (curPage) => + { + var users = _service.GetUserXps(Context.Guild.Id, curPage); + + var embed = new EmbedBuilder() + .WithTitle(GetText("server_leaderboard")) + .WithOkColor(); + + if (!users.Any()) + return embed.WithDescription("-"); + else + { + for (int i = 0; i < users.Length; i++) + { + var levelStats = LevelStats.FromXp(users[i].Xp + users[i].AwardedXp); + var user = await Context.Guild.GetUserAsync(users[i].UserId).ConfigureAwait(false); + + var userXpData = users[i]; + + var awardStr = ""; + if (userXpData.AwardedXp > 0) + awardStr = $"(+{userXpData.AwardedXp})"; + else if (userXpData.AwardedXp < 0) + awardStr = $"({userXpData.AwardedXp.ToString()})"; + + embed.AddField( + $"#{(i + 1 + curPage * 9)} {(user?.ToString() ?? users[i].UserId.ToString())}", + $"{GetText("level_x", levelStats.Level)} - {levelStats.TotalXp}xp {awardStr}"); + } + return embed; + } + }, addPaginatedFooter: false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task XpGlobalLeaderboard(int page = 1) + { + if (--page < 0 || page > 100) + return; + var users = _service.GetUserXps(page); + + var embed = new EmbedBuilder() + .WithTitle(GetText("global_leaderboard")) + .WithOkColor(); + + if (!users.Any()) + embed.WithDescription("-"); + else + { + for (int i = 0; i < users.Length; i++) + { + var user = users[i]; + embed.AddField( + $"#{(i + 1 + page * 9)} {(user.ToString())}", + $"{GetText("level_x", LevelStats.FromXp(users[i].TotalXp).Level)} - {users[i].TotalXp}xp"); + } + } + + await Context.Channel.EmbedAsync(embed); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + public async Task XpAdd(int amount, [Remainder] IGuildUser user) + { + if (amount == 0) + return; + + _service.AddXp(user.Id, Context.Guild.Id, amount); + + await ReplyConfirmLocalized("modified", Format.Bold(user.ToString()), Format.Bold(amount.ToString())).ConfigureAwait(false); + } + } +} diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index 44802af6..946ba5c3 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -4,31 +4,24 @@ using Discord.WebSocket; using NadekoBot.Services; using NadekoBot.Services.Impl; using NLog; -using NLog.Config; -using NLog.Targets; using System; using System.Linq; using System.Reflection; using System.Threading.Tasks; -using NadekoBot.Modules.Permissions; -using NadekoBot.TypeReaders; using System.Collections.Immutable; using System.Diagnostics; using NadekoBot.Services.Database.Models; using System.Threading; -using NadekoBot.Services.Searches; -using NadekoBot.Services.ClashOfClans; -using NadekoBot.Services.Music; -using NadekoBot.Services.CustomReactions; -using NadekoBot.Services.Games; -using NadekoBot.Services.Administration; -using NadekoBot.Services.Permissions; -using NadekoBot.Services.Utility; -using NadekoBot.Services.Help; using System.IO; -using NadekoBot.Services.Pokemon; -using NadekoBot.DataStructures; using NadekoBot.Extensions; +using System.Collections.Generic; +using NadekoBot.Common; +using NadekoBot.Common.ShardCom; +using NadekoBot.Common.TypeReaders; +using NadekoBot.Common.TypeReaders.Models; +using NadekoBot.Services.Database; +using StackExchange.Redis; +using Newtonsoft.Json; namespace NadekoBot { @@ -36,6 +29,14 @@ namespace NadekoBot { private Logger _log; + public BotCredentials Credentials { get; } + + public DiscordSocketClient Client { get; } + public CommandService CommandService { get; } + + private readonly DbService _db; + public ImmutableArray AllGuildConfigs { get; private set; } + /* I don't know how to make this not be static * and keep the convenience of .WithOkColor * and .WithErrorColor extensions methods. @@ -45,231 +46,196 @@ namespace NadekoBot public static Color OkColor { get; private set; } public static Color ErrorColor { get; private set; } - public ImmutableArray AllGuildConfigs { get; } - public BotConfig BotConfig { get; } - public DbService Db { get; } - public CommandService CommandService { get; } - public CommandHandler CommandHandler { get; private set; } - public Localization Localization { get; } - public NadekoStrings Strings { get; } - public StatsService Stats { get; } - public ImagesService Images { get; } - public CurrencyService Currency { get; } - public GoogleApiService GoogleApi { get; } - - public DiscordShardedClient Client { get; } - public bool Ready { get; private set; } + public TaskCompletionSource Ready { get; private set; } = new TaskCompletionSource(); public INServiceProvider Services { get; private set; } - public BotCredentials Credentials { get; } - public NadekoBot() + public ShardsCoordinator ShardCoord { get; private set; } + + private readonly ShardComClient _comClient; + + private readonly BotConfig _botConfig; + + public NadekoBot(int shardId, int parentProcessId, int? port = null) { - SetupLogger(); + if (shardId < 0) + throw new ArgumentOutOfRangeException(nameof(shardId)); + + //var obj = JsonConvert.DeserializeObject>(File.ReadAllText("./data/command_strings.json")) + // .ToDictionary(x => x.Key, x => new CommandData2 + // { + // Cmd = x.Value.Cmd, + // Desc = x.Value.Desc, + // Usage = x.Value.Usage.Select(y => y.Substring(1, y.Length - 2)).ToArray(), + // }); + + //File.WriteAllText("./data/command_strings.json", JsonConvert.SerializeObject(obj, Formatting.Indented)); + + + LogSetup.SetupLogger(); _log = LogManager.GetCurrentClassLogger(); TerribleElevatedPermissionCheck(); - - Credentials = new BotCredentials(); - Db = new DbService(Credentials); - using (var uow = Db.UnitOfWork) - { - AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs().ToImmutableArray(); - BotConfig = uow.BotConfig.GetOrCreate(); - OkColor = new Color(Convert.ToUInt32(BotConfig.OkColor, 16)); - ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 16)); - } - - Client = new DiscordShardedClient(new DiscordSocketConfig + Credentials = new BotCredentials(); + _db = new DbService(Credentials); + Client = new DiscordSocketClient(new DiscordSocketConfig { MessageCacheSize = 10, LogLevel = LogSeverity.Warning, - TotalShards = Credentials.TotalShards, ConnectionTimeout = int.MaxValue, + TotalShards = Credentials.TotalShards, + ShardId = shardId, AlwaysDownloadUsers = false, }); - CommandService = new CommandService(new CommandServiceConfig() { CaseSensitiveCommands = false, - DefaultRunMode = RunMode.Async, + DefaultRunMode = RunMode.Sync, }); - //foundation services - Localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), Db); - Strings = new NadekoStrings(Localization); - CommandHandler = new CommandHandler(Client, Db, BotConfig, AllGuildConfigs, CommandService, Credentials, this); - Stats = new StatsService(Client, CommandHandler, Credentials); - Images = new ImagesService(); - Currency = new CurrencyService(BotConfig, Db); - GoogleApi = new GoogleApiService(Credentials); + port = port ?? Credentials.ShardRunPort; + _comClient = new ShardComClient(port.Value); + + using (var uow = _db.UnitOfWork) + { + _botConfig = uow.BotConfig.GetOrCreate(); + OkColor = new Color(Convert.ToUInt32(_botConfig.OkColor, 16)); + ErrorColor = new Color(Convert.ToUInt32(_botConfig.ErrorColor, 16)); + } + + SetupShard(parentProcessId, port.Value); #if GLOBAL_NADEKO Client.Log += Client_Log; #endif } + private void StartSendingData() + { + Task.Run(async () => + { + while (true) + { + await _comClient.Send(new ShardComMessage() + { + ConnectionState = Client.ConnectionState, + Guilds = Client.ConnectionState == ConnectionState.Connected ? Client.Guilds.Count : 0, + ShardId = Client.ShardId, + Time = DateTime.UtcNow, + }); + await Task.Delay(5000); + } + }); + } + private void AddServices() { - var soundcloudApiService = new SoundCloudApiService(Credentials); + var startingGuildIdList = Client.Guilds.Select(x => (long)x.Id).ToList(); - #region help - var helpService = new HelpService(BotConfig, CommandHandler, Strings); - #endregion + //this unit of work will be used for initialization of all modules too, to prevent multiple queries from running + using (var uow = _db.UnitOfWork) + { + AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray(); - //module services - //todo 90 - autodiscover, DI, and add instead of manual like this - #region utility - var crossServerTextService = new CrossServerTextService(AllGuildConfigs, Client); - var remindService = new RemindService(Client, BotConfig, Db); - var repeaterService = new MessageRepeaterService(this, Client, AllGuildConfigs); - var converterService = new ConverterService(Db); - var commandMapService = new CommandMapService(AllGuildConfigs); - var patreonRewardsService = new PatreonRewardsService(Credentials, Db, Currency); - var verboseErrorsService = new VerboseErrorsService(AllGuildConfigs, Db, CommandHandler, helpService); - var pruneService = new PruneService(); - #endregion + IBotConfigProvider botConfigProvider = new BotConfigProvider(_db, _botConfig); - #region permissions - var permissionsService = new PermissionService(Db, BotConfig, CommandHandler); - var blacklistService = new BlacklistService(BotConfig); - var cmdcdsService = new CmdCdService(AllGuildConfigs); - var filterService = new FilterService(Client, AllGuildConfigs); - var globalPermsService = new GlobalPermissionService(BotConfig); - #endregion + //var localization = new Localization(_botConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), Db); - #region Searches - var searchesService = new SearchesService(Client, GoogleApi, Db); - var streamNotificationService = new StreamNotificationService(Db, Client, Strings); - var animeSearchService = new AnimeSearchService(); - #endregion + //initialize Services + Services = new NServiceProvider.ServiceProviderBuilder() + .AddManual(Credentials) + .AddManual(_db) + .AddManual(Client) + .AddManual(CommandService) + .AddManual(botConfigProvider) + //.AddManual(localization) + .AddManual>(AllGuildConfigs) //todo wrap this + .AddManual(this) + .AddManual(uow) + .AddManual(new RedisCache(Client.CurrentUser.Id)) + .LoadFrom(Assembly.GetEntryAssembly()) + .Build(); - var clashService = new ClashOfClansService(Client, Db, Localization, Strings); - var musicService = new MusicService(GoogleApi, Strings, Localization, Db, soundcloudApiService, Credentials, AllGuildConfigs); - var crService = new CustomReactionsService(permissionsService, Db, Client, CommandHandler, BotConfig); + var commandHandler = Services.GetService(); + commandHandler.AddServices(Services); - #region Games - var gamesService = new GamesService(Client, BotConfig, AllGuildConfigs, Strings, Images, CommandHandler); - var chatterBotService = new ChatterBotService(Client, permissionsService, AllGuildConfigs, CommandHandler); - var pollService = new PollService(Client, Strings); - #endregion - - #region administration - var administrationService = new AdministrationService(AllGuildConfigs, CommandHandler); - var greetSettingsService = new GreetSettingsService(Client, AllGuildConfigs, Db); - var selfService = new SelfService(Client, this, CommandHandler, Db, BotConfig, Localization, Strings, Credentials); - var vcRoleService = new VcRoleService(Client, AllGuildConfigs, Db); - var vPlusTService = new VplusTService(Client, AllGuildConfigs, Strings, Db); - var muteService = new MuteService(Client, AllGuildConfigs, Db); - var ratelimitService = new SlowmodeService(Client, AllGuildConfigs); - var protectionService = new ProtectionService(Client, AllGuildConfigs, muteService); - var playingRotateService = new PlayingRotateService(Client, BotConfig, musicService); - var gameVcService = new GameVoiceChannelService(Client, Db, AllGuildConfigs); - var autoAssignRoleService = new AutoAssignRoleService(Client, AllGuildConfigs); - var logCommandService = new LogCommandService(Client, Strings, AllGuildConfigs, Db, muteService, protectionService); - var guildTimezoneService = new GuildTimezoneService(AllGuildConfigs, Db); - #endregion - - #region pokemon - var pokemonService = new PokemonService(); - #endregion - - - //initialize Services - Services = new NServiceProvider.ServiceProviderBuilder() - .Add(Localization) - .Add(Stats) - .Add(Images) - .Add(GoogleApi) - .Add(Stats) - .Add(Credentials) - .Add(CommandService) - .Add(Strings) - .Add(Client) - .Add(BotConfig) - .Add(Currency) - .Add(CommandHandler) - .Add(Db) - //modules - .Add(crossServerTextService) - .Add(commandMapService) - .Add(remindService) - .Add(repeaterService) - .Add(converterService) - .Add(verboseErrorsService) - .Add(patreonRewardsService) - .Add(pruneService) - .Add(searchesService) - .Add(streamNotificationService) - .Add(animeSearchService) - .Add(clashService) - .Add(musicService) - .Add(greetSettingsService) - .Add(crService) - .Add(helpService) - .Add(gamesService) - .Add(chatterBotService) - .Add(pollService) - .Add(administrationService) - .Add(selfService) - .Add(vcRoleService) - .Add(vPlusTService) - .Add(muteService) - .Add(ratelimitService) - .Add(playingRotateService) - .Add(gameVcService) - .Add(autoAssignRoleService) - .Add(protectionService) - .Add(logCommandService) - .Add(guildTimezoneService) - .Add(permissionsService) - .Add(blacklistService) - .Add(cmdcdsService) - .Add(filterService) - .Add(globalPermsService) - .Add(pokemonService) - .Build(); - - CommandHandler.AddServices(Services); - - //setup typereaders - CommandService.AddTypeReader(new PermissionActionTypeReader()); - CommandService.AddTypeReader(new CommandTypeReader(CommandService, CommandHandler)); - CommandService.AddTypeReader(new CommandOrCrTypeReader(crService, CommandService, CommandHandler)); - CommandService.AddTypeReader(new ModuleTypeReader(CommandService)); - CommandService.AddTypeReader(new ModuleOrCrTypeReader(CommandService)); - CommandService.AddTypeReader(new GuildTypeReader(Client)); - CommandService.AddTypeReader(new GuildDateTimeTypeReader(guildTimezoneService)); + //setup typereaders + CommandService.AddTypeReader(new PermissionActionTypeReader()); + CommandService.AddTypeReader(new CommandTypeReader()); + CommandService.AddTypeReader(new CommandOrCrTypeReader()); + CommandService.AddTypeReader(new ModuleTypeReader(CommandService)); + CommandService.AddTypeReader(new ModuleOrCrTypeReader(CommandService)); + CommandService.AddTypeReader(new GuildTypeReader(Client)); + CommandService.AddTypeReader(new GuildDateTimeTypeReader()); + } } private async Task LoginAsync(string token) { - _log.Info("Logging in..."); + var clientReady = new TaskCompletionSource(); + + Task SetClientReady() + { + var _ = Task.Run(async () => + { + clientReady.TrySetResult(true); + try + { + foreach (var chan in (await Client.GetDMChannelsAsync())) + { + await chan.CloseAsync().ConfigureAwait(false); + } + } + catch + { + // ignored + } + finally + { + + } + }); + return Task.CompletedTask; + } + //connect + _log.Info("Shard {0} logging in ...", Client.ShardId); await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false); await Client.StartAsync().ConfigureAwait(false); + Client.Ready += SetClientReady; + await clientReady.Task.ConfigureAwait(false); + Client.Ready -= SetClientReady; + Client.JoinedGuild += Client_JoinedGuild; + Client.LeftGuild += Client_LeftGuild; + _log.Info("Shard {0} logged in.", Client.ShardId); + } - _log.Info("Waiting for all shards to connect..."); - while (!Client.Shards.All(x => x.ConnectionState == ConnectionState.Connected)) - { - _log.Info("Connecting... {0}/{1}", Client.Shards.Count(x => x.ConnectionState == ConnectionState.Connected), Client.Shards.Count); - await Task.Delay(1000).ConfigureAwait(false); - } + private Task Client_LeftGuild(SocketGuild arg) + { + _log.Info("Left server: {0} [{1}]", arg?.Name, arg?.Id); + return Task.CompletedTask; + } + + private Task Client_JoinedGuild(SocketGuild arg) + { + _log.Info("Joined server: {0} [{1}]", arg?.Name, arg?.Id); + return Task.CompletedTask; } public async Task RunAsync(params string[] args) { + if(Client.ShardId == 0) _log.Info("Starting NadekoBot v" + StatsService.BotVersion); var sw = Stopwatch.StartNew(); await LoginAsync(Credentials.Token).ConfigureAwait(false); - _log.Info("Loading services..."); + _log.Info($"Shard {Client.ShardId} loading services..."); AddServices(); sw.Stop(); - _log.Info($"Connected in {sw.Elapsed.TotalSeconds:F2} s"); + _log.Info($"Shard {Client.ShardId} connected in {sw.Elapsed.TotalSeconds:F2}s"); var stats = Services.GetService(); stats.Initialize(); @@ -282,23 +248,23 @@ namespace NadekoBot var _ = await CommandService.AddModulesAsync(this.GetType().GetTypeInfo().Assembly); - //Console.WriteLine(string.Join(", ", CommandService.Commands - // .Distinct(x => x.Name + x.Module.Name) - // .SelectMany(x => x.Aliases) - // .GroupBy(x => x) - // .Where(x => x.Count() > 1) - // .Select(x => x.Key + $"({x.Count()})"))); - -//unload modules which are not available on the public bot -#if GLOBAL_NADEKO - CommandService - .Modules - .ToArray() - .Where(x => x.Preconditions.Any(y => y.GetType() == typeof(NoPublicBot))) - .ForEach(x => CommandService.RemoveModuleAsync(x)); + bool isPublicNadeko = false; +#if GLOBAL_NADEKO + isPublicNadeko = true; #endif - Ready = true; - _log.Info(await stats.Print().ConfigureAwait(false)); + //unload modules which are not available on the public bot + + if(isPublicNadeko) + CommandService + .Modules + .ToArray() + .Where(x => x.Preconditions.Any(y => y.GetType() == typeof(NoPublicBot))) + .ForEach(x => CommandService.RemoveModuleAsync(x)); + + Ready.TrySetResult(true); + HandleStatusChanges(); + _log.Info($"Shard {Client.ShardId} ready."); + //_log.Info(await stats.Print().ConfigureAwait(false)); } private Task Client_Log(LogMessage arg) @@ -313,7 +279,13 @@ namespace NadekoBot public async Task RunAndBlockAsync(params string[] args) { await RunAsync(args).ConfigureAwait(false); - await Task.Delay(-1).ConfigureAwait(false); + StartSendingData(); + if (ShardCoord != null) + await ShardCoord.RunAndBlockAsync(); + else + { + await Task.Delay(-1).ConfigureAwait(false); + } } private void TerribleElevatedPermissionCheck() @@ -331,18 +303,73 @@ namespace NadekoBot } } - private static void SetupLogger() + private void SetupShard(int parentProcessId, int port) { - var logConfig = new LoggingConfiguration(); - var consoleTarget = new ColoredConsoleTarget() + if (Client.ShardId == 0) { - Layout = @"${date:format=HH\:mm\:ss} ${logger} | ${message}" - }; - logConfig.AddTarget("Console", consoleTarget); + ShardCoord = new ShardsCoordinator(port); + return; + } + new Thread(new ThreadStart(() => + { + try + { + var p = Process.GetProcessById(parentProcessId); + if (p == null) + return; + p.WaitForExit(); + } + finally + { + Environment.Exit(10); + } + })).Start(); + } - logConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget)); + private void HandleStatusChanges() + { + var sub = Services.GetService().Redis.GetSubscriber(); + sub.Subscribe(Client.CurrentUser.Id + "_status.game_set", async (ch, game) => + { + try + { + var obj = new { Name = default(string) }; + obj = JsonConvert.DeserializeAnonymousType(game, obj); + await Client.SetGameAsync(obj.Name).ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Warn(ex); + } + }, CommandFlags.FireAndForget); - LogManager.Configuration = logConfig; + sub.Subscribe(Client.CurrentUser.Id + "_status.stream_set", async (ch, streamData) => + { + try + { + var obj = new { Name = "", Url = "" }; + obj = JsonConvert.DeserializeAnonymousType(streamData, obj); + await Client.SetGameAsync(obj.Name, obj.Url, StreamType.Twitch).ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Warn(ex); + } + }, CommandFlags.FireAndForget); + } + + public Task SetGameAsync(string game) + { + var obj = new { Name = game }; + var sub = Services.GetService().Redis.GetSubscriber(); + return sub.PublishAsync(Client.CurrentUser.Id + "_status.game_set", JsonConvert.SerializeObject(obj)); + } + + public Task SetStreamAsync(string name, string url) + { + var obj = new { Name = name, Url = url }; + var sub = Services.GetService().Redis.GetSubscriber(); + return sub.PublishAsync(Client.CurrentUser.Id + "_status.game_set", JsonConvert.SerializeObject(obj)); } } } diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index 0cc722bc..816e2916 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -1,38 +1,30 @@  - General purpose Discord bot written in C#. - Kwoth - Kwoth - Kwoth - netcoreapp1.1 - true - NadekoBot - Exe - NadekoBot - 1.1.1 - $(PackageTargetFallback);dnxcore50;portable-net45+win8+wpa81 - false - false - false - false - False - 1.0.0.0 - 1.0.0.0 + netcoreapp2.0 + 2.0.0 + exe + $(AssetTargetFallback);dnxcore50;portable-net45+win8+wpa81 nadeko_icon.ico win7-x64 + Debug;Release;global_nadeko + latest + true - 1.4.1 + 1.9.1 $(VersionPrefix).$(VersionSuffix) $(VersionPrefix) + + + Always @@ -55,27 +47,26 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -84,11 +75,7 @@ - - - - - - + + diff --git a/src/NadekoBot/NadekoBot.nuspec b/src/NadekoBot/NadekoBot.nuspec deleted file mode 100644 index 109f83d5..00000000 --- a/src/NadekoBot/NadekoBot.nuspec +++ /dev/null @@ -1,15 +0,0 @@ - - - - NadekoBot - 1.4.0-2$suffix$ - NadekoBot - Kwoth - Kwoth - General purpose discord chat bot written in C#. - nadeko;bot;nadekobot;discord bot - https://github.com/Kwoth/NadekoBot - https://choosealicense.com/licenses/unlicense/ - false - - \ No newline at end of file diff --git a/src/NadekoBot/NadekoBot.xproj.DotSettings b/src/NadekoBot/NadekoBot.xproj.DotSettings deleted file mode 100644 index c5dd7773..00000000 --- a/src/NadekoBot/NadekoBot.xproj.DotSettings +++ /dev/null @@ -1,7 +0,0 @@ - - True - True - True - True - True - True \ No newline at end of file diff --git a/src/NadekoBot/Program.cs b/src/NadekoBot/Program.cs index 0c8832e2..09e53142 100644 --- a/src/NadekoBot/Program.cs +++ b/src/NadekoBot/Program.cs @@ -2,7 +2,17 @@ { public class Program { - public static void Main(string[] args) => - new NadekoBot().RunAndBlockAsync(args).GetAwaiter().GetResult(); + public static void Main(string[] args) + { + if (args.Length == 3 && int.TryParse(args[0], out int shardId) && int.TryParse(args[1], out int parentProcessId)) + { + int? port = null; + if (int.TryParse(args[2], out var outPort)) + port = outPort; + new NadekoBot(shardId, parentProcessId, outPort).RunAndBlockAsync(args).GetAwaiter().GetResult(); + } + else + new NadekoBot(0, 0).RunAndBlockAsync(args).GetAwaiter().GetResult(); + } } } diff --git a/src/NadekoBot/Properties/AssemblyInfo.cs b/src/NadekoBot/Properties/AssemblyInfo.cs deleted file mode 100644 index ca3bd293..00000000 --- a/src/NadekoBot/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NadekoBot")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyInformationalVersion("1.0")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f8225ac4-3cbc-40b4-bcf3-1cacf276bf29")] diff --git a/src/NadekoBot/Properties/launchSettings.json b/src/NadekoBot/Properties/launchSettings.json new file mode 100644 index 00000000..ac657813 --- /dev/null +++ b/src/NadekoBot/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "NadekoBot": { + "commandName": "Project" + }, + "Watch": { + "executablePath": "C:\\Program Files\\dotnet\\dotnet.exe", + "commandLineArgs": "watch run" + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index 708abfe8..957e8059 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -774,15 +774,6 @@ `{0}donadd Donate Amount` - - announce - - - Sends a message to all servers' default channel that bot is connected to. - - - `{0}announce Useless spam` - savechat @@ -814,7 +805,7 @@ serverinfo sinfo - Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. + Shows info about the server the bot is on. If no server is supplied, it defaults to current one. `{0}sinfo Some Server` @@ -1198,7 +1189,7 @@ `{0}drawnew` or `{0}drawnew 5` - playlistshuffle plsh + shuffle sh plsh Shuffles the current playlist. @@ -1269,6 +1260,24 @@ `{0}jr` or `{0}jr 5` + + nunchi + + + Creates or joins an existing nunchi game. Users have to count up by 1 from the starting number shown by the bot. If someone makes a mistake (types an incorrent number, or repeats the same number) they are out of the game and a new round starts without them. Minimum 3 users required. + + + `{0}nunchi` + + + connect4 con4 + + + Creates or joins an existing connect4 game. 2 players are required for the game. Objective of the game is to get 4 of your pieces next to each other in a vertical, horizontal or diagonal line. + + + `{0}connect4` + raffle @@ -1314,6 +1323,15 @@ `{0}br 5` + + wheeloffortune wheel + + + Bets a certain amount of currency on the wheel of fortune. Wheel can stop on one of many different multipliers. Won amount is rounded down to the nearest whole number. + + + `{0}wheel 10` + leaderboard lb @@ -1377,15 +1395,6 @@ `{0}typeadd wordswords` - - poll - - - Creates a poll which requires users to send the number of the voting option to the bot. - - - `{0}poll Question?;Answer1;Answ 2;A_3` - pollend @@ -1476,11 +1485,20 @@ `{0}n` or `{0}n 5` + + play start + + + If no arguments are specified, acts as `{0}next 1` command. If you specify a song number, it will jump to that song. If you specify a search query, acts as a `{0}q` command + + + `{0}play` or `{0}play 5` or `{0}play Dream Of Venice` + stop s - Stops the music and clears the playlist. Stays in the channel. + Stops the music and preserves the current song index. Stays in the channel. `{0}s` @@ -1512,6 +1530,15 @@ `{0}q Dream Of Venice` + + queuenext qn + + + Works the same as `{0}queue` command, except it enqueues the new song after the current one. **You must be in a voice channel**. + + + `{0}qn Dream Of Venice` + queuesearch qs yqs @@ -1534,7 +1561,7 @@ listqueue lq - Lists 15 currently queued songs per page. Default page is 1. + Lists 10 currently queued songs per page. Default page is 1. `{0}lq` or `{0}lq 2` @@ -1642,7 +1669,7 @@ songremove srm - Remove a song by its # in the queue, or 'all' to remove all songs from the queue. + Remove a song by its # in the queue, or 'all' to remove all songs from the queue and reset the song index. `{0}srm 5` @@ -1700,6 +1727,15 @@ `{0}save classical1` + + + streamrole + + + Sets a role which is monitored for streamers (FromRole), and a role to add if a user from 'FromRole' is streaming (AddRole). When a user from 'FromRole' starts streaming, they will receive an 'AddRole'. Provide no arguments to disable + + + `{0}streamrole "Eligible Streamers" "Featured Streams"` load @@ -1764,14 +1800,14 @@ `{0}lolban` - - hitbox hb + + smashcast hb - + Notifies this channel when a certain user starts streaming. - - `{0}hitbox SomeStreamer` + + `{0}smashcast SomeStreamer` twitch tw @@ -1782,14 +1818,14 @@ `{0}twitch SomeStreamer` - - beam bm + + mixer bm - + Notifies this channel when a certain user starts streaming. - - `{0}beam SomeStreamer` + + `{0}mixer SomeStreamer` removestream rms @@ -1798,7 +1834,7 @@ Removes notifications of a certain streamer from a certain platform on this channel. - `{0}rms Twitch SomeGuy` or `{0}rms Beam SomeOtherGuy` + `{0}rms Twitch SomeGuy` or `{0}rms mixer SomeOtherGuy` liststreams ls @@ -2214,87 +2250,6 @@ `{0}butts` or `{0}ass` - - createwar cw - - - Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. - - - `{0}cw 15 The Enemy Clan` - - - startwar sw - - - Starts a war with a given number. - - - `{0}sw 15` - - - listwar lw - - - Shows the active war claims by a number. Shows all wars in a short way if no number is specified. - - - `{0}lw [war_number]` or `{0}lw` - - - basecall - - - Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. - - - `{0}basecall [war_number] [base_number] [optional_other_name]` - - - callfinish cf - - - Finish your claim with 3 stars if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. - - - `{0}cf 1` or `{0}cf 1 5` - - - callfinish2 cf2 - - - Finish your claim with 2 stars if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. - - - `{0}cf2 1` or `{0}cf2 1 5` - - - callfinish1 cf1 - - - Finish your claim with 1 star if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. - - - `{0}cf1 1` or `{0}cf1 1 5` - - - uncall - - - Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim - - - `{0}uc [war_number] [optional_other_name]` - - - endwar ew - - - Ends the war with a given index. - - - `{0}ew [war_number]` - translate trans @@ -2583,13 +2538,13 @@ `{0}totube` - - publicpoll ppoll + + poll ppoll - + Creates a public poll which requires users to type a number of the voting option in the channel command is ran in. - + `{0}ppoll Question?;Answer1;Answ 2;A_3` @@ -2790,6 +2745,15 @@ `{0}hangman` or `{0}hangman movies` + + hangmanstop + + + Stops the active hangman game on this channel if it exists. + + + `{0}hangmanstop` + crstatsclear @@ -2853,6 +2817,15 @@ `{0}fp` + + songautodelete sad + + + Toggles whether the song should be automatically removed from the music queue when it finishes playing. + + + `{0}sad` + define def @@ -2977,7 +2950,7 @@ Sets a price for a command. Running that command will take currency from users. Set 0 to remove the price. - `{0}cmdcost 0 !!q` or `{0}cmdcost 1 >8ball` + `{0}cmdcost 0 !!q` or `{0}cmdcost 1 {0}8ball` startevent @@ -3033,6 +3006,15 @@ `{0}claim 50 @Himesama` + + waifugift gift gifts + + + Gift an item to someone. This will increase their waifu value by 50% of the gifted item's value if they don't have affinity set towards you, or 100% if they do. Provide no arguments to see a list of items that you can gift. + + + `{0}gifts` or `{0}gift Rose @Himesama` + waifus waifulb @@ -3096,14 +3078,14 @@ `{0}shardstats` or `{0}shardstats 2` - - connectshard + + restartshard - + Try (re)connecting a shard with a certain shardid when it dies. No one knows will it work. Keep an eye on the console for errors. - - `{0}connectshard 2` + + `{0}restartshard 2` shardid @@ -3121,7 +3103,7 @@ Starts a game of tic tac toe. Another user must run the command in the same channel in order to accept the challenge. Use numbers 1-9 to play. 15 seconds per move. - >ttt + {0}ttt timezones @@ -3231,6 +3213,15 @@ `{0}crdm 44` + + crca + + + Toggles whether the custom reaction will trigger if the triggering message contains the keyword (instead of only starting with it). + + + `{0}crca 44` + aliaslist cmdmaplist aliases @@ -3258,6 +3249,15 @@ `{0}warnlog @b1nzy` + + warnlogall + + + See a list of all warnings on the server. 15 users per page. + + + `{0}warnlogall` or `{0}warnlogall 2` + warn @@ -3519,4 +3519,292 @@ Toggles whether the bot should print command errors when a command is incorrectly used. + + streamrolekw srkw + + + `{0}srkw` or `{0}srkw PUBG` + + + Sets keyword which is required in the stream's title in order for the streamrole to apply. Provide no keyword in order to reset. + + + streamrolebl srbl + + + `{0}srbl add @b1nzy#1234` or `{0}srbl rem @b1nzy#1234` + + + Adds or removes a blacklisted user. Blacklisted users will never receive the stream role. + + + streamrolewl srwl + + + `{0}srwl add @b1nzy#1234` or `{0}srwl rem @b1nzy#1234` + + + Adds or removes a whitelisted user. Whitelisted users will receive the stream role even if they don't have the specified keyword in their stream title. + + + botconfigedit bce + + + `{0}bce CurrencyName b1nzy` or `{0}bce` + + + Sets one of available bot config settings to a specified value. Use the command without any parameters to get a list of available settings. + + + nsfwtagbl nsfwtbl + + + `{0}nsfwtbl poop` + + + Toggles whether the tag is blacklisted or not in nsfw searches. Provide no parameters to see the list of blacklisted tags. + + + experience xp + + + `{0}xp` + + + Shows your xp stats. Specify the user to show that user's stats instead. + + + xpexclusionlist xpexl + + + `{0}xpexl` + + + Shows the roles and channels excluded from the XP system on this server, as well as whether the whole server is excluded. + + + xpexclude xpex + + + `{0}xpex Role Excluded-Role` `{0}xpex Server` + + + Exclude a channel, role or current server from the xp system. + + + xpnotify xpn + + + `{0}xpn global dm` `{0}xpn server channel` + + + Sets how the bot should notify you when you get a `server` or `global` level. You can set `dm` (for the bot to send a direct message), `channel` (to get notified in the channel you sent the last message in) or `none` to disable. + + + xprolerewards xprrs + + + `{0}xprrs` + + + Shows currently set role rewards. + + + xprolereward xprr + + + `{0}xprr 3 Social` + + + Sets a role reward on a specified level. Provide no role name in order to remove the role reward. + + + xpleaderboard xplb + + + `{0}xplb` + + + Shows current server's xp leaderboard. + + + xpgleaderboard xpglb + + + `{0}xpglb` + + + Shows the global xp leaderboard. + + + xpadd + + + `{0}xpadd 100 @b1nzy` + + + Adds xp to a user on the server. This does not affect their global ranking. You can use negative values. + + + clubcreate + + + `{0}clubcreate b1nzy's friends` + + + Creates a club. You must be atleast level 5 and not be in the club already. + + + clubinfo + + + `{0}clubinfo b1nzy's friends#123` + + + Shows information about the club. + + + clubapply + + + `{0}clubapply b1nzy's friends#123` + + + Apply to join a club. You must meet that club's minimum level requirement, and not be on its ban list. + + + clubaccept + + + `{0}clubaccept b1nzy#1337` + + + Accept a user who applied to your club. + + + clubleave + + + `{0}clubleave` + + + Leaves the club you're currently in. + + + clubdisband + + + `{0}clubdisband` + + + Disbands the club you're the owner of. This action is irreversible. + + + clubkick + + + `{0}clubkick b1nzy#1337` + + + Kicks the user from the club. You must be the club owner. They will be able to apply again. + + + clubban + + + `{0}clubban b1nzy#1337` + + + Bans the user from the club. You must be the club owner. They will not be able to apply again. + + + clubunban + + + `{0}clubunban b1nzy#1337` + + + Unbans the previously banned user from the club. You must be the club owner. + + + clublevelreq + + + `{0}clublevelreq 7` + + + Sets the club required level to apply to join the club. You must be club owner. You can't set this number below 5. + + + clubicon + + + `{0}clubicon https://i.imgur.com/htfDMfU.png` + + + Sets the club icon. + + + clubapps + + + `{0}clubapps 2` + + + Shows the list of users who have applied to your club. Paginated. You must be club owner to use this command. + + + clubbans + + + `{0}clubbans 2` + + + Shows the list of users who have banned from your club. Paginated. You must be club owner to use this command. + + + clublb + + + `{0}clublb 2` + + + Shows club rankings on the specified page. + + + nsfwcc + + + `{0}nsfwcc` + + + Clears nsfw cache. + + + clubadmin + + + `{0}clubadmin` + + + Assigns (or unassigns) staff role to the member of the club. Admins can ban, kick and accept applications. + + + autoboobs + + + Posts a boobs every X seconds. 20 seconds minimum. Provide no arguments to disable. + + + `{0}autoboobs 30` or `{0}autoboobs` + + + autobutts + + + Posts a butts every X seconds. 20 seconds minimum. Provide no arguments to disable. + + + `{0}autobutts 30` or `{0}autobutts` + diff --git a/src/NadekoBot/Services/Administration/PlayingRotateService.cs b/src/NadekoBot/Services/Administration/PlayingRotateService.cs deleted file mode 100644 index 7662b7ca..00000000 --- a/src/NadekoBot/Services/Administration/PlayingRotateService.cs +++ /dev/null @@ -1,102 +0,0 @@ -using Discord.WebSocket; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using NadekoBot.Services.Music; -using NLog; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace NadekoBot.Services.Administration -{ - //todo 99 - Could make a placeholder service, which can work for any module - //and have replacements which are dependent on the types provided in the constructor - public class PlayingRotateService - { - public List RotatingStatusMessages { get; } - public volatile bool RotatingStatuses; - private readonly Timer _t; - private readonly DiscordShardedClient _client; - private readonly BotConfig _bc; - private readonly MusicService _music; - private readonly Logger _log; - - private class TimerState - { - public int Index { get; set; } - } - - public PlayingRotateService(DiscordShardedClient client, BotConfig bc, MusicService music) - { - _client = client; - _bc = bc; - _music = music; - _log = LogManager.GetCurrentClassLogger(); - - RotatingStatusMessages = _bc.RotatingStatusMessages; - RotatingStatuses = _bc.RotatingStatuses; - - _t = new Timer(async (objState) => - { - try - { - var state = (TimerState)objState; - if (!RotatingStatuses) - return; - if (state.Index >= RotatingStatusMessages.Count) - state.Index = 0; - - if (!RotatingStatusMessages.Any()) - return; - var status = RotatingStatusMessages[state.Index++].Status; - if (string.IsNullOrWhiteSpace(status)) - return; - PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(_client,_music))); - var shards = _client.Shards; - for (int i = 0; i < shards.Count; i++) - { - var curShard = shards.ElementAt(i); - ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(curShard))); - try { await shards.ElementAt(i).SetGameAsync(status).ConfigureAwait(false); } - catch (Exception ex) - { - _log.Warn(ex); - } - } - } - catch (Exception ex) - { - _log.Warn("Rotating playing status errored.\n" + ex); - } - }, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); - } - - public Dictionary> PlayingPlaceholders { get; } = - new Dictionary> { - { "%servers%", (c, ms) => c.Guilds.Count.ToString()}, - { "%users%", (c, ms) => c.Guilds.Sum(s => s.Users.Count).ToString()}, - { "%playing%", (c, ms) => { - var cnt = ms.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null); - if (cnt != 1) return cnt.ToString(); - try { - var mp = ms.MusicPlayers.FirstOrDefault(); - return mp.Value.CurrentSong.SongInfo.Title; - } - catch { - return "No songs"; - } - } - }, - { "%queued%", (c, ms) => ms.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()}, - { "%time%", (c, ms) => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) }, - { "%shardcount%", (c, ms) => c.Shards.Count.ToString() }, - }; - - public Dictionary> ShardSpecificPlaceholders { get; } = - new Dictionary> { - { "%shardid%", (client) => client.ShardId.ToString()}, - { "%shardguilds%", (client) => client.Guilds.Count.ToString()}, - }; - } -} diff --git a/src/NadekoBot/Services/ClashOfClans/ClashOfClansService.cs b/src/NadekoBot/Services/ClashOfClans/ClashOfClansService.cs deleted file mode 100644 index edb78512..00000000 --- a/src/NadekoBot/Services/ClashOfClans/ClashOfClansService.cs +++ /dev/null @@ -1,262 +0,0 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Services.ClashOfClans -{ - // todo 99 rewrite, just made this compile, it's a complete mess. A lot of the things here should actually be in the actual module. - // service should just handle the state, module should print out what happened, so anything that has to do with strings - // shouldn't be here - public class ClashOfClansService - { - private readonly DiscordShardedClient _client; - private readonly DbService _db; - private readonly ILocalization _localization; - private readonly NadekoStrings _strings; - private readonly Timer checkWarTimer; - - public ConcurrentDictionary> ClashWars { get; set; } - - public ClashOfClansService(DiscordShardedClient client, DbService db, ILocalization localization, NadekoStrings strings) - { - _client = client; - _db = db; - _localization = localization; - _strings = strings; - - using (var uow = _db.UnitOfWork) - { - ClashWars = new ConcurrentDictionary>( - uow.ClashOfClans - .GetAllWars() - .Select(cw => - { - cw.Channel = _client.GetGuild(cw.GuildId)? - .GetTextChannel(cw.ChannelId); - return cw; - }) - .Where(cw => cw.Channel != null) - .GroupBy(cw => cw.GuildId) - .ToDictionary(g => g.Key, g => g.ToList())); - } - - checkWarTimer = new Timer(async _ => - { - foreach (var kvp in ClashWars) - { - foreach (var war in kvp.Value) - { - try { await CheckWar(TimeSpan.FromHours(2), war).ConfigureAwait(false); } catch { } - } - } - }, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); - } - - private async Task CheckWar(TimeSpan callExpire, ClashWar war) - { - var Bases = war.Bases; - for (var i = 0; i < Bases.Count; i++) - { - var callUser = Bases[i].CallUser; - if (callUser == null) continue; - if ((!Bases[i].BaseDestroyed) && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire) - { - if (Bases[i].Stars != 3) - Bases[i].BaseDestroyed = true; - else - Bases[i] = null; - try - { - SaveWar(war); - await war.Channel.SendErrorAsync(_strings.GetText("claim_expired", - _localization.GetCultureInfo(war.Channel.GuildId), - typeof(ClashOfClansService).Name.ToLowerInvariant(), - Format.Bold(Bases[i].CallUser), - ShortPrint(war))); - } - catch { } - } - } - } - - public Tuple, int> GetWarInfo(IGuild guild, int num) - { - List wars = null; - ClashWars.TryGetValue(guild.Id, out wars); - if (wars == null || wars.Count == 0) - { - return null; - } - // get the number of the war - else if (num < 1 || num > wars.Count) - { - return null; - } - num -= 1; - //get the actual war - return new Tuple, int>(wars, num); - } - - public async Task CreateWar(string enemyClan, int size, ulong serverId, ulong channelId) - { - var channel = _client.GetGuild(serverId)?.GetTextChannel(channelId); - using (var uow = _db.UnitOfWork) - { - var cw = new ClashWar - { - EnemyClan = enemyClan, - Size = size, - Bases = new List(size), - GuildId = serverId, - ChannelId = channelId, - Channel = channel, - }; - cw.Bases.Capacity = size; - for (int i = 0; i < size; i++) - { - cw.Bases.Add(new ClashCaller() - { - CallUser = null, - SequenceNumber = i, - }); - } - uow.ClashOfClans.Add(cw); - await uow.CompleteAsync(); - return cw; - } - } - - public void SaveWar(ClashWar cw) - { - if (cw.WarState == StateOfWar.Ended) - { - using (var uow = _db.UnitOfWork) - { - uow.ClashOfClans.Remove(cw); - uow.CompleteAsync(); - } - return; - } - - using (var uow = _db.UnitOfWork) - { - uow.ClashOfClans.Update(cw); - uow.CompleteAsync(); - } - } - - public void Call(ClashWar cw, string u, int baseNumber) - { - if (baseNumber < 0 || baseNumber >= cw.Bases.Count) - throw new ArgumentException(Localize(cw, "invalid_base_number")); - if (cw.Bases[baseNumber].CallUser != null && cw.Bases[baseNumber].Stars == 3) - throw new ArgumentException(Localize(cw, "base_already_claimed")); - for (var i = 0; i < cw.Bases.Count; i++) - { - if (cw.Bases[i]?.BaseDestroyed == false && cw.Bases[i]?.CallUser == u) - throw new ArgumentException(Localize(cw, "claimed_other", u, i + 1)); - } - - var cc = cw.Bases[baseNumber]; - cc.CallUser = u.Trim(); - cc.TimeAdded = DateTime.UtcNow; - cc.BaseDestroyed = false; - } - - public int FinishClaim(ClashWar cw, string user, int stars = 3) - { - user = user.Trim(); - for (var i = 0; i < cw.Bases.Count; i++) - { - if (cw.Bases[i]?.BaseDestroyed != false || cw.Bases[i]?.CallUser != user) continue; - cw.Bases[i].BaseDestroyed = true; - cw.Bases[i].Stars = stars; - return i; - } - throw new InvalidOperationException(Localize(cw, "not_partic_or_destroyed", user)); - } - - public void FinishClaim(ClashWar cw, int index, int stars = 3) - { - if (index < 0 || index > cw.Bases.Count) - throw new ArgumentOutOfRangeException(nameof(index)); - var toFinish = cw.Bases[index]; - if (toFinish.BaseDestroyed != false) throw new InvalidOperationException(Localize(cw, "base_already_destroyed")); - if (toFinish.CallUser == null) throw new InvalidOperationException(Localize(cw, "base_already_unclaimed")); - toFinish.BaseDestroyed = true; - toFinish.Stars = stars; - } - - public int Uncall(ClashWar cw, string user) - { - user = user.Trim(); - for (var i = 0; i < cw.Bases.Count; i++) - { - if (cw.Bases[i]?.CallUser != user) continue; - cw.Bases[i].CallUser = null; - return i; - } - throw new InvalidOperationException(Localize(cw, "not_partic")); - } - - public string ShortPrint(ClashWar cw) => - $"`{cw.EnemyClan}` ({cw.Size} v {cw.Size})"; - - public string ToPrettyString(ClashWar cw) - { - var sb = new StringBuilder(); - - if (cw.WarState == StateOfWar.Created) - sb.AppendLine("`not started`"); - var twoHours = new TimeSpan(2, 0, 0); - for (var i = 0; i < cw.Bases.Count; i++) - { - if (cw.Bases[i].CallUser == null) - { - sb.AppendLine($"`{i + 1}.` ❌*{Localize(cw, "not_claimed")}*"); - } - else - { - if (cw.Bases[i].BaseDestroyed) - { - sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {new string('⭐', cw.Bases[i].Stars)}"); - } - else - { - var left = (cw.WarState == StateOfWar.Started) ? twoHours - (DateTime.UtcNow - cw.Bases[i].TimeAdded) : twoHours; - if (cw.Bases[i].Stars == 3) - { - sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left"); - } - else - { - sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left {new string('⭐', cw.Bases[i].Stars)} {string.Concat(Enumerable.Repeat("🔸", 3 - cw.Bases[i].Stars))}"); - } - } - } - - } - return sb.ToString(); - } - - public string Localize(ClashWar cw, string key, params object[] replacements) - { - return string.Format(Localize(cw, key), replacements); - } - - public string Localize(ClashWar cw, string key) - { - return _strings.GetText(key, - _localization.GetCultureInfo(cw.Channel?.GuildId), - "ClashOfClans".ToLowerInvariant()); - } - } -} diff --git a/src/NadekoBot/Services/ClashOfClans/Extensions.cs b/src/NadekoBot/Services/ClashOfClans/Extensions.cs deleted file mode 100644 index 635557cf..00000000 --- a/src/NadekoBot/Services/ClashOfClans/Extensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using NadekoBot.Services.Database.Models; -using System; -using System.Linq; - -namespace NadekoBot.Services.ClashOfClans -{ - public static class Extensions - { - public static void ResetTime(this ClashCaller c) - { - c.TimeAdded = DateTime.UtcNow; - } - - public static void Destroy(this ClashCaller c) - { - c.BaseDestroyed = true; - } - - public static void End(this ClashWar cw) - { - //Ended = true; - cw.WarState = StateOfWar.Ended; - } - - public static void Start(this ClashWar cw) - { - if (cw.WarState == StateOfWar.Started) - throw new InvalidOperationException("war_already_started"); - //if (Started) - // throw new InvalidOperationException(); - //Started = true; - cw.WarState = StateOfWar.Started; - cw.StartedAt = DateTime.UtcNow; - foreach (var b in cw.Bases.Where(b => b.CallUser != null)) - { - b.ResetTime(); - } - } - } -} \ No newline at end of file diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index 2177ce01..c06d14ab 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -9,12 +9,13 @@ using Discord.Commands; using NadekoBot.Extensions; using System.Collections.Concurrent; using System.Threading; -using NadekoBot.DataStructures; using System.Collections.Immutable; -using NadekoBot.DataStructures.ModuleBehaviors; using NadekoBot.Services.Database.Models; using System.IO; using Discord.Net; +using NadekoBot.Common; +using NadekoBot.Common.Collections; +using NadekoBot.Common.ModuleBehaviors; namespace NadekoBot.Services { @@ -24,11 +25,12 @@ namespace NadekoBot.Services public int GetHashCode(IGuildUser obj) => obj.Id.GetHashCode(); } - public class CommandHandler + + public class CommandHandler : INService { public const int GlobalCommandsCooldown = 750; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly CommandService _commandService; private readonly Logger _log; private readonly IBotCredentials _creds; @@ -37,10 +39,11 @@ namespace NadekoBot.Services public string DefaultPrefix { get; private set; } private ConcurrentDictionary _prefixes { get; } = new ConcurrentDictionary(); - private ImmutableArray> ownerChannels { get; set; } = new ImmutableArray>(); + private ImmutableArray> OwnerChannels { get; set; } = new ImmutableArray>(); public event Func CommandExecuted = delegate { return Task.CompletedTask; }; public event Func CommandErrored = delegate { return Task.CompletedTask; }; + public event Func OnMessageNoTrigger = delegate { return Task.CompletedTask; }; //userid/msg count public ConcurrentDictionary UserMessagesSent { get; } = new ConcurrentDictionary(); @@ -48,7 +51,7 @@ namespace NadekoBot.Services public ConcurrentHashSet UsersOnShortCooldown { get; } = new ConcurrentHashSet(); private readonly Timer _clearUsersOnShortCooldown; - public CommandHandler(DiscordShardedClient client, DbService db, BotConfig bc, IEnumerable gcs, CommandService commandService, IBotCredentials credentials, NadekoBot bot) + public CommandHandler(DiscordSocketClient client, DbService db, IBotConfigProvider bc, IEnumerable gcs, CommandService commandService, IBotCredentials credentials, NadekoBot bot) { _client = client; _commandService = commandService; @@ -63,7 +66,7 @@ namespace NadekoBot.Services UsersOnShortCooldown.Clear(); }, null, GlobalCommandsCooldown, GlobalCommandsCooldown); - DefaultPrefix = bc.DefaultPrefix; + DefaultPrefix = bc.BotConfig.DefaultPrefix; _prefixes = gcs .Where(x => x.Prefix != null) .ToDictionary(x => x.GuildId, x => x.Prefix) @@ -189,7 +192,7 @@ namespace NadekoBot.Services { try { - if (msg.Author.IsBot || !_bot.Ready) //no bots, wait until bot connected and initialized + if (msg.Author.IsBot || !_bot.Ready.Task.IsCompleted) //no bots, wait until bot connected and initialized return; if (!(msg is SocketUserMessage usrMsg)) @@ -259,12 +262,13 @@ namespace NadekoBot.Services } } var prefix = GetPrefix(guild?.Id); + var isPrefixCommand = messageContent.StartsWith(".prefix"); // execute the command and measure the time it took - if (messageContent.StartsWith(prefix)) + if (messageContent.StartsWith(prefix) || isPrefixCommand) { - var result = await ExecuteCommandAsync(new CommandContext(_client, usrMsg), messageContent, prefix.Length, _services, MultiMatchHandling.Best); + var result = await ExecuteCommandAsync(new CommandContext(_client, usrMsg), messageContent, isPrefixCommand ? 1 : prefix.Length, _services, MultiMatchHandling.Best); execTime = Environment.TickCount - execTime; - + if (result.Success) { await LogSuccessfulExecution(usrMsg, channel as ITextChannel, exec2, exec3, execTime).ConfigureAwait(false); @@ -273,13 +277,14 @@ namespace NadekoBot.Services } else if (result.Error != null) { - //todo 80 should have log levels and it should return some kind of result, - // instead of tuple with the type of thing that went wrong, like before - LogErroredExecution(result.Error, usrMsg, channel as ITextChannel, exec2, exec3, execTime); + LogErroredExecution(result.Error, usrMsg, channel as ITextChannel, exec2, exec3, execTime); if (guild != null) await CommandErrored(result.Info, channel as ITextChannel, result.Error); } - + } + else + { + await OnMessageNoTrigger(usrMsg).ConfigureAwait(false); } foreach (var svc in _services) @@ -296,83 +301,124 @@ namespace NadekoBot.Services => ExecuteCommand(context, input.Substring(argPos), serviceProvider, multiMatchHandling); - public async Task<(bool Success, string Error, CommandInfo Info)> ExecuteCommand(CommandContext context, string input, IServiceProvider serviceProvider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public async Task<(bool Success, string Error, CommandInfo Info)> ExecuteCommand(CommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { var searchResult = _commandService.Search(context, input); if (!searchResult.IsSuccess) return (false, null, null); var commands = searchResult.Commands; - for (int i = commands.Count - 1; i >= 0; i--) + var preconditionResults = new Dictionary(); + + foreach (var match in commands) { - var preconditionResult = await commands[i].CheckPreconditionsAsync(context, serviceProvider).ConfigureAwait(false); - if (!preconditionResult.IsSuccess) - { - return (false, preconditionResult.ErrorReason, commands[i].Command); - } - - var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false); - if (!parseResult.IsSuccess) - { - if (parseResult.Error == CommandError.MultipleMatches) - { - TypeReaderValue[] argList, paramList; - switch (multiMatchHandling) - { - case MultiMatchHandling.Best: - argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray(); - paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray(); - parseResult = ParseResult.FromSuccess(argList, paramList); - break; - } - } - - if (!parseResult.IsSuccess) - { - if (commands.Count == 1) - return (false, parseResult.ErrorReason, commands[i].Command); - else - continue; - } - } - - var cmd = commands[i].Command; - - // Bot will ignore commands which are ran more often than what specified by - // GlobalCommandsCooldown constant (miliseconds) - if (!UsersOnShortCooldown.Add(context.Message.Author.Id)) - return (false, null, commands[i].Command); - //return SearchResult.FromError(CommandError.Exception, "You are on a global cooldown."); - - var commandName = cmd.Aliases.First(); - foreach (var svc in _services) - { - if (svc is ILateBlocker exec && - await exec.TryBlockLate(_client, context.Message, context.Guild, context.Channel, context.User, cmd.Module.GetTopLevelModule().Name, commandName).ConfigureAwait(false)) - { - _log.Info("Late blocking User [{0}] Command: [{1}] in [{2}]", context.User, commandName, svc.GetType().Name); - return (false, null, commands[i].Command); - } - } - - var execResult = await commands[i].ExecuteAsync(context, parseResult, serviceProvider); - if (execResult.Exception != null && (!(execResult.Exception is HttpException he) || he.DiscordCode != 50013)) - { - lock (errorLogLock) - { - var now = DateTime.Now; - File.AppendAllText($"./command_errors_{now:yyyy-MM-dd}.txt", - $"[{now:HH:mm-yyyy-MM-dd}]" + Environment.NewLine - + execResult.Exception.ToString() + Environment.NewLine - + "------" + Environment.NewLine); - _log.Warn(execResult.Exception); - } - } - return (true, null, commands[i].Command); + preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false); } - return (false, null, null); - //return new ExecuteCommandResult(null, null, SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload.")); + var successfulPreconditions = preconditionResults + .Where(x => x.Value.IsSuccess) + .ToArray(); + + if (successfulPreconditions.Length == 0) + { + //All preconditions failed, return the one from the highest priority command + var bestCandidate = preconditionResults + .OrderByDescending(x => x.Key.Command.Priority) + .FirstOrDefault(x => !x.Value.IsSuccess); + return (false, bestCandidate.Value.ErrorReason, commands[0].Command); + } + + var parseResultsDict = new Dictionary(); + foreach (var pair in successfulPreconditions) + { + var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false); + + if (parseResult.Error == CommandError.MultipleMatches) + { + IReadOnlyList argList, paramList; + switch (multiMatchHandling) + { + case MultiMatchHandling.Best: + argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + parseResult = ParseResult.FromSuccess(argList, paramList); + break; + } + } + + parseResultsDict[pair.Key] = parseResult; + } + // Calculates the 'score' of a command given a parse result + float CalculateScore(CommandMatch match, ParseResult parseResult) + { + float argValuesScore = 0, paramValuesScore = 0; + + if (match.Command.Parameters.Count > 0) + { + var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; + var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; + + argValuesScore = argValuesSum / match.Command.Parameters.Count; + paramValuesScore = paramValuesSum / match.Command.Parameters.Count; + } + + var totalArgsScore = (argValuesScore + paramValuesScore) / 2; + return match.Command.Priority + totalArgsScore * 0.99f; + } + + //Order the parse results by their score so that we choose the most likely result to execute + var parseResults = parseResultsDict + .OrderByDescending(x => CalculateScore(x.Key, x.Value)); + + var successfulParses = parseResults + .Where(x => x.Value.IsSuccess) + .ToArray(); + + if (successfulParses.Length == 0) + { + //All parses failed, return the one from the highest priority command, using score as a tie breaker + var bestMatch = parseResults + .FirstOrDefault(x => !x.Value.IsSuccess); + return (false, bestMatch.Value.ErrorReason, commands[0].Command); + } + + var cmd = successfulParses[0].Key.Command; + + // Bot will ignore commands which are ran more often than what specified by + // GlobalCommandsCooldown constant (miliseconds) + if (!UsersOnShortCooldown.Add(context.Message.Author.Id)) + return (false, null, cmd); + //return SearchResult.FromError(CommandError.Exception, "You are on a global cooldown."); + + var commandName = cmd.Aliases.First(); + foreach (var svc in _services) + { + if (svc is ILateBlocker exec && + await exec.TryBlockLate(_client, context.Message, context.Guild, context.Channel, context.User, cmd.Module.GetTopLevelModule().Name, commandName).ConfigureAwait(false)) + { + _log.Info("Late blocking User [{0}] Command: [{1}] in [{2}]", context.User, commandName, svc.GetType().Name); + return (false, null, cmd); + } + } + + //If we get this far, at least one parse was successful. Execute the most likely overload. + var chosenOverload = successfulParses[0]; + var execResult = (ExecuteResult)await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false); + + if (execResult.Exception != null && (!(execResult.Exception is HttpException he) || he.DiscordCode != 50013)) + { + lock (errorLogLock) + { + var now = DateTime.Now; + File.AppendAllText($"./command_errors_{now:yyyy-MM-dd}.txt", + $"[{now:HH:mm-yyyy-MM-dd}]" + Environment.NewLine + + execResult.Exception.ToString() + Environment.NewLine + + "------" + Environment.NewLine); + _log.Warn(execResult.Exception); + } + } + + return (true, null, cmd); } private readonly object errorLogLock = new object(); diff --git a/src/NadekoBot/Services/CurrencyService.cs b/src/NadekoBot/Services/CurrencyService.cs index 99f1644a..5b8fb44f 100644 --- a/src/NadekoBot/Services/CurrencyService.cs +++ b/src/NadekoBot/Services/CurrencyService.cs @@ -4,15 +4,16 @@ using Discord; using NadekoBot.Extensions; using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database; +using NadekoBot.Services; namespace NadekoBot.Services { - public class CurrencyService + public class CurrencyService : INService { - private readonly BotConfig _config; + private readonly IBotConfigProvider _config; private readonly DbService _db; - public CurrencyService(BotConfig config, DbService db) + public CurrencyService(IBotConfigProvider config, DbService db) { _config = config; _db = db; @@ -23,7 +24,7 @@ namespace NadekoBot.Services var success = await RemoveAsync(author.Id, reason, amount); if (success && sendMessage) - try { await author.SendErrorAsync($"`You lost:` {amount} {_config.CurrencySign}\n`Reason:` {reason}").ConfigureAwait(false); } catch { } + try { await author.SendErrorAsync($"`You lost:` {amount} {_config.BotConfig.CurrencySign}\n`Reason:` {reason}").ConfigureAwait(false); } catch { } return success; } @@ -60,12 +61,32 @@ namespace NadekoBot.Services return true; } + public async Task AddToManyAsync(string reason, long amount, params ulong[] userIds) + { + using (var uow = _db.UnitOfWork) + { + foreach (var userId in userIds) + { + var transaction = new CurrencyTransaction() + { + UserId = userId, + Reason = reason, + Amount = amount, + }; + uow.Currency.TryUpdateState(userId, amount); + uow.CurrencyTransactions.Add(transaction); + } + + await uow.CompleteAsync(); + } + } + public async Task AddAsync(IUser author, string reason, long amount, bool sendMessage) { await AddAsync(author.Id, reason, amount); if (sendMessage) - try { await author.SendConfirmAsync($"`You received:` {amount} {_config.CurrencySign}\n`Reason:` {reason}").ConfigureAwait(false); } catch { } + try { await author.SendConfirmAsync($"`You received:` {amount} {_config.BotConfig.CurrencySign}\n`Reason:` {reason}").ConfigureAwait(false); } catch { } } public async Task AddAsync(ulong receiverId, string reason, long amount, IUnitOfWork uow = null) diff --git a/src/NadekoBot/Services/CustomReactions/Extensions.cs b/src/NadekoBot/Services/CustomReactions/Extensions.cs deleted file mode 100644 index e2c5481f..00000000 --- a/src/NadekoBot/Services/CustomReactions/Extensions.cs +++ /dev/null @@ -1,149 +0,0 @@ -using AngleSharp; -using AngleSharp.Dom.Html; -using Discord; -using Discord.WebSocket; -using NadekoBot.DataStructures; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace NadekoBot.Services.CustomReactions -{ - public static class Extensions - { - public static Dictionary> responsePlaceholders = new Dictionary>() - { - {"%target%", (ctx, trigger) => { return ctx.Content.Substring(trigger.Length).Trim().SanitizeMentions(); } }, - {"%rnduser%", (ctx, client) => { - //var ch = ctx.Channel as ITextChannel; - //if(ch == null) - // return ""; - - //var g = ch.Guild as SocketGuild; - //if(g == null) - // return ""; - //try { - // var usr = g.Users.Skip(new NadekoRandom().Next(0, g.Users.Count)).FirstOrDefault(); - // return usr.Mention; - //} - //catch { - return "[%rnduser% is temp. disabled]"; - //} - - //var users = g.Users.ToArray(); - - //return users[new NadekoRandom().Next(0, users.Length-1)].Mention; - } }, - }; - - public static Dictionary> placeholders = new Dictionary>() - { - {"%mention%", (ctx, client) => { return $"<@{client.CurrentUser.Id}>"; } }, - {"%user%", (ctx, client) => { return ctx.Author.Mention; } }, - //{"%rng%", (ctx) => { return new NadekoRandom().Next(0,10).ToString(); } } - }; - - private static readonly Regex rngRegex = new Regex("%rng(?:(?(?:-)?\\d+)-(?(?:-)?\\d+))?%", RegexOptions.Compiled); - private static readonly Regex imgRegex = new Regex("%(img|image):(?.*?)%", RegexOptions.Compiled); - - private static readonly NadekoRandom rng = new NadekoRandom(); - - public static Dictionary>> regexPlaceholders = new Dictionary>>() - { - { rngRegex, (match) => { - int from = 0; - int.TryParse(match.Groups["from"].ToString(), out from); - - int to = 0; - int.TryParse(match.Groups["to"].ToString(), out to); - - if(from == 0 && to == 0) - { - return Task.FromResult(rng.Next(0, 11).ToString()); - } - - if(from >= to) - return Task.FromResult(string.Empty); - - return Task.FromResult(rng.Next(from,to+1).ToString()); - } }, - { imgRegex, async (match) => { - var tag = match.Groups["tag"].ToString(); - if(string.IsNullOrWhiteSpace(tag)) - return ""; - - var fullQueryLink = $"http://imgur.com/search?q={ tag }"; - var config = Configuration.Default.WithDefaultLoader(); - var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); - - var elems = document.QuerySelectorAll("a.image-list-link").ToArray(); - - if (!elems.Any()) - return ""; - - var img = (elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Length))?.Children?.FirstOrDefault() as IHtmlImageElement); - - if (img?.Source == null) - return ""; - - return " "+img.Source.Replace("b.", ".") + " "; - } } - }; - - private static string ResolveTriggerString(this string str, IUserMessage ctx, DiscordShardedClient client) - { - foreach (var ph in placeholders) - { - if (str.Contains(ph.Key)) - str = str.ToLowerInvariant().Replace(ph.Key, ph.Value(ctx, client)); - } - return str; - } - - private static async Task ResolveResponseStringAsync(this string str, IUserMessage ctx, DiscordShardedClient client, string resolvedTrigger) - { - foreach (var ph in placeholders) - { - var lowerKey = ph.Key.ToLowerInvariant(); - if (str.Contains(lowerKey)) - str = str.Replace(lowerKey, ph.Value(ctx, client)); - } - - foreach (var ph in responsePlaceholders) - { - var lowerKey = ph.Key.ToLowerInvariant(); - if (str.Contains(lowerKey)) - str = str.Replace(lowerKey, ph.Value(ctx, resolvedTrigger)); - } - - foreach (var ph in regexPlaceholders) - { - str = await ph.Key.ReplaceAsync(str, ph.Value); - } - return str; - } - - public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx, DiscordShardedClient client) - => cr.Trigger.ResolveTriggerString(ctx, client); - - public static Task ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordShardedClient client) - => cr.Response.ResolveResponseStringAsync(ctx, client, cr.Trigger.ResolveTriggerString(ctx, client)); - - public static async Task Send(this CustomReaction cr, IUserMessage context, DiscordShardedClient client, CustomReactionsService crs) - { - var channel = cr.DmResponse ? await context.Author.CreateDMChannelAsync() : context.Channel; - - crs.ReactionStats.AddOrUpdate(cr.Trigger, 1, (k, old) => ++old); - - if (CREmbed.TryParse(cr.Response, out CREmbed crembed)) - { - return await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? ""); - } - return await channel.SendMessageAsync((await cr.ResponseWithContextAsync(context, client)).SanitizeMentions()); - } - } -} diff --git a/src/NadekoBot/Services/Database/IUnitOfWork.cs b/src/NadekoBot/Services/Database/IUnitOfWork.cs index 52cb01cf..6b97a085 100644 --- a/src/NadekoBot/Services/Database/IUnitOfWork.cs +++ b/src/NadekoBot/Services/Database/IUnitOfWork.cs @@ -24,6 +24,8 @@ namespace NadekoBot.Services.Database IWaifuRepository Waifus { get; } IDiscordUserRepository DiscordUsers { get; } IWarningsRepository Warnings { get; } + IXpRepository Xp { get; } + IClubRepository Clubs { get; } int Complete(); Task CompleteAsync(); diff --git a/src/NadekoBot/Services/Database/Models/AntiProtection.cs b/src/NadekoBot/Services/Database/Models/AntiProtection.cs index 160425ea..fbafc9e3 100644 --- a/src/NadekoBot/Services/Database/Models/AntiProtection.cs +++ b/src/NadekoBot/Services/Database/Models/AntiProtection.cs @@ -18,6 +18,7 @@ namespace NadekoBot.Services.Database.Models public PunishmentAction Action { get; set; } public int MessageThreshold { get; set; } = 3; + public int MuteTime { get; set; } = 0; public HashSet IgnoredChannels { get; set; } = new HashSet(); } diff --git a/src/NadekoBot/Services/Database/Models/BotConfig.cs b/src/NadekoBot/Services/Database/Models/BotConfig.cs index 0fa46b84..98b84594 100644 --- a/src/NadekoBot/Services/Database/Models/BotConfig.cs +++ b/src/NadekoBot/Services/Database/Models/BotConfig.cs @@ -65,9 +65,11 @@ Nadeko Support Server: https://discord.gg/nadekobot"; public List StartupCommands { get; set; } public HashSet BlockedCommands { get; set; } public HashSet BlockedModules { get; set; } - public int PermissionVersion { get; set; } = 1; + public int PermissionVersion { get; set; } public string DefaultPrefix { get; set; } = "."; public bool CustomReactionsStartWith { get; set; } = false; + public int XpPerMessage { get; set; } = 3; + public int XpMinutesTimeout { get; set; } = 5; } public class BlockedCmdOrMdl : DbEntity diff --git a/src/NadekoBot/Services/Database/Models/ClubInfo.cs b/src/NadekoBot/Services/Database/Models/ClubInfo.cs new file mode 100644 index 00000000..68f68bc3 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/ClubInfo.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace NadekoBot.Services.Database.Models +{ + public class ClubInfo : DbEntity + { + [MaxLength(20)] + public string Name { get; set; } + public int Discrim { get; set; } + + public string ImageUrl { get; set; } = ""; + public int MinimumLevelReq { get; set; } = 5; + public int Xp { get; set; } = 0; + + public int OwnerId { get; set; } + public DiscordUser Owner { get; set; } + + public List Users { get; set; } = new List(); + + public List Applicants { get; set; } = new List(); + public List Bans { get; set; } = new List(); + + public override string ToString() + { + return Name + "#" + Discrim; + } + } + + public class ClubApplicants + { + public int ClubId { get; set; } + public ClubInfo Club { get; set; } + + public int UserId { get; set; } + public DiscordUser User { get; set; } + } + + public class ClubBans + { + public int ClubId { get; set; } + public ClubInfo Club { get; set; } + + public int UserId { get; set; } + public DiscordUser User { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/CustomReaction.cs b/src/NadekoBot/Services/Database/Models/CustomReaction.cs index 25bb34fb..cd57bc35 100644 --- a/src/NadekoBot/Services/Database/Models/CustomReaction.cs +++ b/src/NadekoBot/Services/Database/Models/CustomReaction.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations.Schema; using System.Text.RegularExpressions; namespace NadekoBot.Services.Database.Models @@ -6,7 +7,9 @@ namespace NadekoBot.Services.Database.Models public class CustomReaction : DbEntity { public ulong? GuildId { get; set; } + [NotMapped] + [JsonIgnore] public Regex Regex { get; set; } public string Response { get; set; } public string Trigger { get; set; } @@ -16,7 +19,10 @@ namespace NadekoBot.Services.Database.Models public bool AutoDeleteTrigger { get; set; } public bool DmResponse { get; set; } + [JsonIgnore] public bool IsGlobal => !GuildId.HasValue; + + public bool ContainsAnywhere { get; set; } } public class ReactionResponse : DbEntity diff --git a/src/NadekoBot/Services/Database/Models/DiscordUser.cs b/src/NadekoBot/Services/Database/Models/DiscordUser.cs index 86b84d5b..654408e2 100644 --- a/src/NadekoBot/Services/Database/Models/DiscordUser.cs +++ b/src/NadekoBot/Services/Database/Models/DiscordUser.cs @@ -1,4 +1,6 @@ -namespace NadekoBot.Services.Database.Models +using System; + +namespace NadekoBot.Services.Database.Models { public class DiscordUser : DbEntity { @@ -6,6 +8,26 @@ public string Username { get; set; } public string Discriminator { get; set; } public string AvatarId { get; set; } + + public ClubInfo Club { get; set; } + public bool IsClubAdmin { get; set; } + + public int TotalXp { get; set; } + public DateTime LastLevelUp { get; set; } = DateTime.UtcNow; + public DateTime LastXpGain { get; set; } = DateTime.MinValue; + public XpNotificationType NotifyOnLevelUp { get; set; } + + public override bool Equals(object obj) + { + return obj is DiscordUser du + ? du.UserId == UserId + : false; + } + + public override int GetHashCode() + { + return UserId.GetHashCode(); + } public override string ToString() => Username + "#" + Discriminator; diff --git a/src/NadekoBot/Services/Database/Models/FeedSub.cs b/src/NadekoBot/Services/Database/Models/FeedSub.cs new file mode 100644 index 00000000..aabf455e --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/FeedSub.cs @@ -0,0 +1,23 @@ +namespace NadekoBot.Services.Database.Models +{ + public class FeedSub : DbEntity + { + public int GuildConfigId { get; set; } + public GuildConfig GuildConfig { get; set; } + + public ulong ChannelId { get; set; } + public string Url { get; set; } + + public override int GetHashCode() + { + return Url.GetHashCode() ^ GuildConfigId.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is FeedSub s + ? s.Url == Url && s.GuildConfigId == GuildConfigId + : false; + } + } +} diff --git a/src/NadekoBot/Services/Database/Models/FollowedStream.cs b/src/NadekoBot/Services/Database/Models/FollowedStream.cs index 59c4ad21..b49bc430 100644 --- a/src/NadekoBot/Services/Database/Models/FollowedStream.cs +++ b/src/NadekoBot/Services/Database/Models/FollowedStream.cs @@ -9,7 +9,7 @@ public enum FollowedStreamType { - Twitch, Hitbox, Beam + Twitch, Smashcast, Mixer } public override int GetHashCode() => diff --git a/src/NadekoBot/Services/Database/Models/GuildConfig.cs b/src/NadekoBot/Services/Database/Models/GuildConfig.cs index 9b13294e..501b9a64 100644 --- a/src/NadekoBot/Services/Database/Models/GuildConfig.cs +++ b/src/NadekoBot/Services/Database/Models/GuildConfig.cs @@ -78,14 +78,37 @@ namespace NadekoBot.Services.Database.Models public bool WarningsInitialized { get; set; } public HashSet SlowmodeIgnoredUsers { get; set; } public HashSet SlowmodeIgnoredRoles { get; set; } + public HashSet NsfwBlacklistedTags { get; set; } = new HashSet(); public List ShopEntries { get; set; } public ulong? GameVoiceChannel { get; set; } = null; public bool VerboseErrors { get; set; } = false; + public StreamRoleSettings StreamRole { get; set; } + + public XpSettings XpSettings { get; set; } + public List FeedSubs { get; set; } = new List(); + //public List ProtectionIgnoredChannels { get; set; } = new List(); } + public class NsfwBlacklitedTag : DbEntity + { + public string Tag { get; set; } + + public override int GetHashCode() + { + return Tag.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is NsfwBlacklitedTag x + ? x.Tag == Tag + : false; + } + } + public class SlowmodeIgnoredUser : DbEntity { public ulong UserId { get; set; } diff --git a/src/NadekoBot/Services/Database/Models/PlaylistSong.cs b/src/NadekoBot/Services/Database/Models/PlaylistSong.cs index d1b09f9d..f938d242 100644 --- a/src/NadekoBot/Services/Database/Models/PlaylistSong.cs +++ b/src/NadekoBot/Services/Database/Models/PlaylistSong.cs @@ -12,7 +12,7 @@ public enum MusicType { Radio, - Normal, + YouTube, Local, Soundcloud } diff --git a/src/NadekoBot/Services/Database/Models/StreamRoleSettings.cs b/src/NadekoBot/Services/Database/Models/StreamRoleSettings.cs new file mode 100644 index 00000000..8ef5b690 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/StreamRoleSettings.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Models +{ + public class StreamRoleSettings : DbEntity + { + public int GuildConfigId { get; set; } + public GuildConfig GuildConfig { get; set; } + + /// + /// Whether the feature is enabled in the guild. + /// + public bool Enabled { get; set; } + + /// + /// Id of the role to give to the users in the role 'FromRole' when they start streaming + /// + public ulong AddRoleId { get; set; } + + /// + /// Id of the role whose users are eligible to get the 'AddRole' + /// + public ulong FromRoleId { get; set; } + + /// + /// If set, feature will only apply to users who have this keyword in their streaming status. + /// + public string Keyword { get; set; } + + /// + /// A collection of whitelisted users' IDs. Whitelisted users don't require 'keyword' in + /// order to get the stream role. + /// + public HashSet Whitelist { get; set; } = new HashSet(); + + /// + /// A collection of blacklisted users' IDs. Blacklisted useres will never get the stream role. + /// + public HashSet Blacklist { get; set; } = new HashSet(); + } + + public class StreamRoleBlacklistedUser : DbEntity + { + public ulong UserId { get; set; } + public string Username { get; set; } + + public override bool Equals(object obj) + { + var x = obj as StreamRoleBlacklistedUser; + + if (x == null) + return false; + + return x.UserId == UserId; + } + + public override int GetHashCode() + { + return UserId.GetHashCode(); + } + } + + public class StreamRoleWhitelistedUser : DbEntity + { + public ulong UserId { get; set; } + public string Username { get; set; } + + public override bool Equals(object obj) + { + var x = obj as StreamRoleWhitelistedUser; + + if (x == null) + return false; + + return x.UserId == UserId; + } + + public override int GetHashCode() + { + return UserId.GetHashCode(); + } + } +} diff --git a/src/NadekoBot/Services/Database/Models/UserXpStats.cs b/src/NadekoBot/Services/Database/Models/UserXpStats.cs new file mode 100644 index 00000000..8695298e --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/UserXpStats.cs @@ -0,0 +1,16 @@ +using System; + +namespace NadekoBot.Services.Database.Models +{ + public class UserXpStats : DbEntity + { + public ulong UserId { get; set; } + public ulong GuildId { get; set; } + public int Xp { get; set; } + public int AwardedXp { get; set; } + public XpNotificationType NotifyOnLevelUp { get; set; } + public DateTime LastLevelUp { get; set; } = DateTime.UtcNow; + } + + public enum XpNotificationType { None, Dm, Channel } +} diff --git a/src/NadekoBot/Services/Database/Models/Waifu.cs b/src/NadekoBot/Services/Database/Models/Waifu.cs index 88c2778a..5be73b46 100644 --- a/src/NadekoBot/Services/Database/Models/Waifu.cs +++ b/src/NadekoBot/Services/Database/Models/Waifu.cs @@ -1,4 +1,5 @@ using NadekoBot.Extensions; +using System.Collections.Generic; namespace NadekoBot.Services.Database.Models { @@ -14,6 +15,7 @@ namespace NadekoBot.Services.Database.Models public DiscordUser Affinity { get; set; } public int Price { get; set; } + public List Items { get; set; } = new List(); public override string ToString() { diff --git a/src/NadekoBot/Services/Database/Models/WaifuItem.cs b/src/NadekoBot/Services/Database/Models/WaifuItem.cs new file mode 100644 index 00000000..2184391f --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/WaifuItem.cs @@ -0,0 +1,87 @@ +using System; + +namespace NadekoBot.Services.Database.Models +{ + public class WaifuItem : DbEntity + { + public string ItemEmoji { get; set; } + public int Price { get; set; } + public ItemName Item { get; set; } + + public enum ItemName + { + Cookie, + Rose, + LoveLetter, + Chocolate, + Rice, + MovieTicket, + Book, + Lipstick, + Laptop, + Violin, + Ring, + Helicopter, + } + + public WaifuItem() + { + + } + + public WaifuItem(string itemEmoji, int price, ItemName item) + { + ItemEmoji = itemEmoji; + Price = price; + Item = item; + } + + public static WaifuItem GetItem(ItemName itemName) + { + switch (itemName) + { + case ItemName.Cookie: + return new WaifuItem("🍪", 10, itemName); + case ItemName.Rose: + return new WaifuItem("🌹", 50, itemName); + case ItemName.LoveLetter: + return new WaifuItem("💌", 100, itemName); + case ItemName.Chocolate: + return new WaifuItem("🍫", 200, itemName); + case ItemName.Rice: + return new WaifuItem("🍚", 400, itemName); + case ItemName.MovieTicket: + return new WaifuItem("🎟", 800, itemName); + case ItemName.Book: + return new WaifuItem("📔", 1500, itemName); + case ItemName.Lipstick: + return new WaifuItem("💄", 3000, itemName); + case ItemName.Laptop: + return new WaifuItem("💻", 5000, itemName); + case ItemName.Violin: + return new WaifuItem("🎻", 7500, itemName); + case ItemName.Ring: + return new WaifuItem("💍", 10000, itemName); + case ItemName.Helicopter: + return new WaifuItem("🚁", 20000, itemName); + default: + throw new ArgumentException(nameof(itemName)); + } + } + } +} + + +/* +🍪 Cookie 10 +🌹 Rose 50 +💌 Love Letter 100 +🍫 Chocolate 200 +🍚 Rice 400 +🎟 Movie Ticket 800 +📔 Book 1.5k +💄 Lipstick 3k +💻 Laptop 5k +🎻 Violin 7.5k +💍 Ring 10k +*/ diff --git a/src/NadekoBot/Services/Database/Models/XpSettings.cs b/src/NadekoBot/Services/Database/Models/XpSettings.cs new file mode 100644 index 00000000..fcc67fff --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/XpSettings.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Models +{ + public class XpSettings : DbEntity + { + public int GuildConfigId { get; set; } + public GuildConfig GuildConfig { get; set; } + + public HashSet RoleRewards { get; set; } = new HashSet(); + public bool XpRoleRewardExclusive { get; set; } + public string NotifyMessage { get; set; } = "Congratulations {0}! You have reached level {1}!"; + public HashSet ExclusionList { get; set; } = new HashSet(); + public bool ServerExcluded { get; set; } + } + + public enum ExcludedItemType { Channel, Role } + + public class XpRoleReward : DbEntity + { + public int XpSettingsId { get; set; } + public XpSettings XpSettings { get; set; } + + public int Level { get; set; } + public ulong RoleId { get; set; } + + public override int GetHashCode() + { + return Level.GetHashCode() ^ XpSettingsId.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is XpRoleReward xrr && xrr.Level == Level && xrr.XpSettingsId == XpSettingsId; + } + } + + public class ExcludedItem : DbEntity + { + public ulong ItemId { get; set; } + public ExcludedItemType ItemType { get; set; } + + public override int GetHashCode() + { + return ItemId.GetHashCode() ^ ItemType.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is ExcludedItem ei && ei.ItemId == ItemId && ei.ItemType == ItemType; + } + } +} diff --git a/src/NadekoBot/Services/Database/NadekoContext.cs b/src/NadekoBot/Services/Database/NadekoContext.cs index 9c084867..9b4c1e46 100644 --- a/src/NadekoBot/Services/Database/NadekoContext.cs +++ b/src/NadekoBot/Services/Database/NadekoContext.cs @@ -3,23 +3,24 @@ using System.Collections.Generic; using System.Linq; using NadekoBot.Services.Database.Models; using NadekoBot.Extensions; -using Microsoft.EntityFrameworkCore.Infrastructure; +using System; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Data.Sqlite; +using System.IO; namespace NadekoBot.Services.Database { - - public class NadekoContextFactory : IDbContextFactory - { - /// - /// :\ Used for migrations - /// - /// - /// - public NadekoContext Create(DbContextFactoryOptions options) + public class NadekoContextFactory : IDesignTimeDbContextFactory + { + public NadekoContext CreateDbContext(string[] args) { - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db"); - return new NadekoContext(optionsBuilder.Options); + var optionsBuilder = new DbContextOptionsBuilder(); + var builder = new SqliteConnectionStringBuilder("Data Source=data/NadekoBot.db"); + builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource); + optionsBuilder.UseSqlite(builder.ToString()); + var ctx = new NadekoContext(optionsBuilder.Options); + ctx.Database.SetCommandTimeout(60); + return ctx; } } @@ -41,6 +42,8 @@ namespace NadekoBot.Services.Database public DbSet PokeGame { get; set; } public DbSet WaifuUpdates { get; set; } public DbSet Warnings { get; set; } + public DbSet UserXpStats { get; set; } + public DbSet Clubs { get; set; } //logging public DbSet LogSettings { get; set; } @@ -53,12 +56,7 @@ namespace NadekoBot.Services.Database public DbSet ModulePrefixes { get; set; } public DbSet RewardedUsers { get; set; } - public NadekoContext() : base() - { - - } - - public NadekoContext(DbContextOptions options) : base(options) + public NadekoContext(DbContextOptions options) : base(options) { } @@ -68,24 +66,6 @@ namespace NadekoBot.Services.Database { var bc = new BotConfig(); - bc.ModulePrefixes.AddRange(new HashSet() - { - new ModulePrefix() { ModuleName = "Administration", Prefix = "." }, - new ModulePrefix() { ModuleName = "Searches", Prefix = "~" }, - new ModulePrefix() { ModuleName = "Translator", Prefix = "~" }, - new ModulePrefix() { ModuleName = "NSFW", Prefix = "~" }, - new ModulePrefix() { ModuleName = "ClashOfClans", Prefix = "," }, - new ModulePrefix() { ModuleName = "Help", Prefix = "-" }, - new ModulePrefix() { ModuleName = "Music", Prefix = "!!" }, - new ModulePrefix() { ModuleName = "Trello", Prefix = "trello" }, - new ModulePrefix() { ModuleName = "Games", Prefix = ">" }, - new ModulePrefix() { ModuleName = "Gambling", Prefix = "$" }, - new ModulePrefix() { ModuleName = "Permissions", Prefix = ";" }, - new ModulePrefix() { ModuleName = "Pokemon", Prefix = ">" }, - new ModulePrefix() { ModuleName = "Utility", Prefix = "." }, - new ModulePrefix() { ModuleName = "CustomReactions", Prefix = "." }, - new ModulePrefix() { ModuleName = "PokeGame", Prefix = ">" } - }); bc.RaceAnimals.AddRange(new HashSet { new RaceAnimal { Icon = "🐼", Name = "Panda" }, @@ -162,18 +142,34 @@ namespace NadekoBot.Services.Database .HasOne(x => x.GuildConfig) .WithOne(x => x.AntiRaidSetting); + modelBuilder.Entity() + .HasAlternateKey(x => new { x.GuildConfigId, x.Url }); + //modelBuilder.Entity() // .HasAlternateKey(c => new { c.ChannelId, c.ProtectionType }); #endregion + #region streamrole + modelBuilder.Entity() + .HasOne(x => x.GuildConfig) + .WithOne(x => x.StreamRole); + #endregion + #region BotConfig - //var botConfigEntity = modelBuilder.Entity(); + var botConfigEntity = modelBuilder.Entity(); + + botConfigEntity.Property(x => x.XpMinutesTimeout) + .HasDefaultValue(5); + + botConfigEntity.Property(x => x.XpPerMessage) + .HasDefaultValue(3); + //botConfigEntity // .HasMany(c => c.ModulePrefixes) // .WithOne(mp => mp.BotConfig) // .HasForeignKey(mp => mp.BotConfigId); - + #endregion #region ClashOfClans @@ -231,7 +227,7 @@ namespace NadekoBot.Services.Database musicPlaylistEntity .HasMany(p => p.Songs) .WithOne() - .OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade); #endregion @@ -269,9 +265,19 @@ namespace NadekoBot.Services.Database // .WithOne(); // //.HasForeignKey(w => w.ClaimerId) // //.IsRequired(false); + #endregion + #region DiscordUser + var du = modelBuilder.Entity(); du.HasAlternateKey(w => w.UserId); + du.HasOne(x => x.Club) + .WithMany(x => x.Users) + .IsRequired(false); + + modelBuilder.Entity() + .Property(x => x.LastLevelUp) + .HasDefaultValue(new DateTime(2017, 9, 21, 20, 53, 13, 305, DateTimeKind.Local)); #endregion @@ -284,6 +290,66 @@ namespace NadekoBot.Services.Database pr.HasIndex(x => x.UserId) .IsUnique(); #endregion + + #region XpStats + modelBuilder.Entity() + .HasIndex(x => new { x.UserId, x.GuildId }) + .IsUnique(); + + modelBuilder.Entity() + .Property(x => x.LastLevelUp) + .HasDefaultValue(new DateTime(2017, 9, 21, 20, 53, 13, 307, DateTimeKind.Local)); + + #endregion + + #region XpSettings + modelBuilder.Entity() + .HasOne(x => x.GuildConfig) + .WithOne(x => x.XpSettings); + #endregion + + //todo major bug + #region XpRoleReward + modelBuilder.Entity() + .HasIndex(x => new { x.XpSettingsId, x.Level }) + .IsUnique(); + #endregion + + #region Club + var ci = modelBuilder.Entity(); + ci.HasOne(x => x.Owner) + .WithOne() + .HasForeignKey(x => x.OwnerId); + + + ci.HasAlternateKey(x => new { x.Name, x.Discrim }); + #endregion + + #region ClubManytoMany + + modelBuilder.Entity() + .HasKey(t => new { t.ClubId, t.UserId }); + + modelBuilder.Entity() + .HasOne(pt => pt.User) + .WithMany(); + + modelBuilder.Entity() + .HasOne(pt => pt.Club) + .WithMany(x => x.Applicants); + + modelBuilder.Entity() + .HasKey(t => new { t.ClubId, t.UserId }); + + modelBuilder.Entity() + .HasOne(pt => pt.User) + .WithMany(); + + modelBuilder.Entity() + .HasOne(pt => pt.Club) + .WithMany(x => x.Bans); + + #endregion } } } diff --git a/src/NadekoBot/Services/Database/Repositories/IClashOfClansRepository.cs b/src/NadekoBot/Services/Database/Repositories/IClashOfClansRepository.cs index 756e9789..14edcea8 100644 --- a/src/NadekoBot/Services/Database/Repositories/IClashOfClansRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IClashOfClansRepository.cs @@ -5,6 +5,6 @@ namespace NadekoBot.Services.Database.Repositories { public interface IClashOfClansRepository : IRepository { - IEnumerable GetAllWars(); + IEnumerable GetAllWars(List guilds); } } diff --git a/src/NadekoBot/Services/Database/Repositories/IClubRepository.cs b/src/NadekoBot/Services/Database/Repositories/IClubRepository.cs new file mode 100644 index 00000000..66ad3d92 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IClubRepository.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database.Models; +using System; +using System.Linq; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IClubRepository : IRepository + { + int GetNextDiscrim(string clubName); + ClubInfo GetByName(string v, int discrim, Func, IQueryable> func = null); + ClubInfo GetByOwner(ulong userId, Func, IQueryable> func = null); + ClubInfo GetByOwnerOrAdmin(ulong userId); + ClubInfo GetByMember(ulong userId, Func, IQueryable> func = null); + ClubInfo[] GetClubLeaderboardPage(int page); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs b/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs index 480d0723..fb9360b7 100644 --- a/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs @@ -6,5 +6,7 @@ namespace NadekoBot.Services.Database.Repositories public interface IDiscordUserRepository : IRepository { DiscordUser GetOrCreate(IUser original); + int GetUserGlobalRanking(ulong id); + DiscordUser[] GetUsersXpLeaderboardFor(int page); } } diff --git a/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs index cca54609..498b72ed 100644 --- a/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs @@ -11,10 +11,11 @@ namespace NadekoBot.Services.Database.Repositories GuildConfig For(ulong guildId, Func, IQueryable> includes = null); GuildConfig LogSettingsFor(ulong guildId); IEnumerable OldPermissionsForAll(); - IEnumerable GetAllGuildConfigs(); - IEnumerable GetAllFollowedStreams(); + IEnumerable GetAllGuildConfigs(List availableGuilds); + IEnumerable GetAllFollowedStreams(List included); void SetCleverbotEnabled(ulong id, bool cleverbotEnabled); - IEnumerable Permissionsv2ForAll(); + IEnumerable Permissionsv2ForAll(List include); GuildConfig GcWithPermissionsv2For(ulong guildId); + XpSettings XpSettingsFor(ulong guildId); } } diff --git a/src/NadekoBot/Services/Database/Repositories/IReminderRepository.cs b/src/NadekoBot/Services/Database/Repositories/IReminderRepository.cs index 7c643ec7..dc757dbc 100644 --- a/src/NadekoBot/Services/Database/Repositories/IReminderRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IReminderRepository.cs @@ -1,9 +1,10 @@ using NadekoBot.Services.Database.Models; +using System.Collections.Generic; namespace NadekoBot.Services.Database.Repositories { public interface IReminderRepository : IRepository { - + IEnumerable GetIncludedReminders(IEnumerable guildIds); } } diff --git a/src/NadekoBot/Services/Database/Repositories/IWarningsRepository.cs b/src/NadekoBot/Services/Database/Repositories/IWarningsRepository.cs index 2b2e5ac4..f8c8296e 100644 --- a/src/NadekoBot/Services/Database/Repositories/IWarningsRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IWarningsRepository.cs @@ -7,5 +7,6 @@ namespace NadekoBot.Services.Database.Repositories { Warning[] For(ulong guildId, ulong userId); Task ForgiveAll(ulong guildId, ulong userId, string mod); + Warning[] GetForGuild(ulong id); } } diff --git a/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs b/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs new file mode 100644 index 00000000..f62394d7 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs @@ -0,0 +1,11 @@ +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IXpRepository : IRepository + { + UserXpStats GetOrCreateUser(ulong guildId, ulong userId); + int GetUserGuildRanking(ulong userId, ulong guildId); + UserXpStats[] GetUsersFor(ulong guildId, int page); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs index dd329796..c21b1ff2 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs @@ -20,7 +20,6 @@ namespace NadekoBot.Services.Database.Repositories.Impl .Include(bc => bc.RaceAnimals) .Include(bc => bc.Blacklist) .Include(bc => bc.EightBallResponses) - .Include(bc => bc.ModulePrefixes) .Include(bc => bc.StartupCommands) .Include(bc => bc.BlockedCommands) .Include(bc => bc.BlockedModules) diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/ClashOfClansRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/ClashOfClansRepository.cs index 54a391fa..828c4bce 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/ClashOfClansRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/ClashOfClansRepository.cs @@ -11,9 +11,11 @@ namespace NadekoBot.Services.Database.Repositories.Impl { } - public IEnumerable GetAllWars() + public IEnumerable GetAllWars(List guilds) { - var toReturn = _set.Include(cw => cw.Bases) + var toReturn = _set + .Where(cw => guilds.Contains((long)cw.GuildId)) + .Include(cw => cw.Bases) .ToList(); toReturn.ForEach(cw => cw.Bases = cw.Bases.Where(w => w.SequenceNumber != null).OrderBy(w => w.SequenceNumber).ToList()); return toReturn; diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/ClubRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/ClubRepository.cs new file mode 100644 index 00000000..8caa1838 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/ClubRepository.cs @@ -0,0 +1,95 @@ +using NadekoBot.Services.Database.Models; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using System; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class ClubRepository : Repository, IClubRepository + { + public ClubRepository(DbContext context) : base(context) + { + } + + public ClubInfo GetByOwner(ulong userId, Func, IQueryable> func = null) + { + if (func == null) + return _set + .Include(x => x.Bans) + .Include(x => x.Applicants) + .Include(x => x.Users) + .Include(x => x.Owner) + .FirstOrDefault(x => x.Owner.UserId == userId); + + return func(_set).FirstOrDefault(x => x.Owner.UserId == userId); + } + + public ClubInfo GetByOwnerOrAdmin(ulong userId) + { + return _set + .Include(x => x.Bans) + .ThenInclude(x => x.User) + .Include(x => x.Applicants) + .ThenInclude(x => x.User) + .Include(x => x.Owner) + .Include(x => x.Users) + .FirstOrDefault(x => x.Owner.UserId == userId) ?? + _context.Set() + .Include(x => x.Club) + .ThenInclude(x => x.Users) + .Include(x => x.Club) + .ThenInclude(x => x.Bans) + .ThenInclude(x => x.User) + .Include(x => x.Club) + .ThenInclude(x => x.Applicants) + .ThenInclude(x => x.User) + .Include(x => x.Club) + .ThenInclude(x => x.Owner) + .FirstOrDefault(x => x.UserId == userId && x.IsClubAdmin) + ?.Club; + } + + public ClubInfo GetByName(string name, int discrim, Func, IQueryable> func = null) + { + if (func == null) + return _set + .Where(x => x.Name == name && x.Discrim == discrim) + .Include(x => x.Users) + .Include(x => x.Bans) + .Include(x => x.Applicants) + .FirstOrDefault(); + + return func(_set).FirstOrDefault(x => x.Name == name && x.Discrim == discrim); + } + + public int GetNextDiscrim(string clubName) + { + return _set + .Where(x => x.Name.ToLowerInvariant() == clubName.ToLowerInvariant()) + .Select(x => x.Discrim) + .DefaultIfEmpty() + .Max() + 1; + } + + public ClubInfo GetByMember(ulong userId, Func, IQueryable> func = null) + { + if (func == null) + return _set + .Include(x => x.Users) + .Include(x => x.Bans) + .Include(x => x.Applicants) + .FirstOrDefault(x => x.Users.Any(y => y.UserId == userId)); + + return func(_set).FirstOrDefault(x => x.Users.Any(y => y.UserId == userId)); + } + + public ClubInfo[] GetClubLeaderboardPage(int page) + { + return _set + .OrderByDescending(x => x.Xp) + .Skip(page * 9) + .Take(9) + .ToArray(); + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs index e61c2d0e..933d583b 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs @@ -15,7 +15,15 @@ namespace NadekoBot.Services.Database.Repositories.Impl { DiscordUser toReturn; - toReturn = _set.FirstOrDefault(u => u.UserId == original.Id); + toReturn = _set.Include(x => x.Club) + .FirstOrDefault(u => u.UserId == original.Id); + + if (toReturn != null) + { + toReturn.AvatarId = original.AvatarId; + toReturn.Username = original.Username; + toReturn.Discriminator = original.Discriminator; + } if (toReturn == null) _set.Add(toReturn = new DiscordUser() @@ -24,9 +32,32 @@ namespace NadekoBot.Services.Database.Repositories.Impl Discriminator = original.Discriminator, UserId = original.Id, Username = original.Username, + Club = null, }); return toReturn; } + + public int GetUserGlobalRanking(ulong id) + { + if (!_set.Where(y => y.UserId == id).Any()) + { + return _set.Count() + 1; + } + return _set.Count(x => x.TotalXp >= + _set.Where(y => y.UserId == id) + .DefaultIfEmpty() + .Sum(y => y.TotalXp)); + } + + public DiscordUser[] GetUsersXpLeaderboardFor(int page) + { + return _set + .OrderByDescending(x => x.TotalXp) + .Skip(page * 9) + .Take(9) + .AsEnumerable() + .ToArray(); + } } } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs index f3ed3566..eac42399 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs @@ -24,8 +24,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl } }; - public IEnumerable GetAllGuildConfigs() => - _set.Include(gc => gc.LogSetting) + public IEnumerable GetAllGuildConfigs(List availableGuilds) => + _set + .Where(gc => availableGuilds.Contains((long)gc.GuildId)) + .Include(gc => gc.LogSetting) .ThenInclude(ls => ls.IgnoredChannels) .Include(gc => gc.MutedUsers) .Include(gc => gc.CommandAliases) @@ -42,6 +44,13 @@ namespace NadekoBot.Services.Database.Repositories.Impl .Include(gc => gc.SlowmodeIgnoredUsers) .Include(gc => gc.AntiSpamSetting) .ThenInclude(x => x.IgnoredChannels) + .Include(gc => gc.FeedSubs) + .ThenInclude(x => x.GuildConfig) + .Include(gc => gc.FollowedStreams) + .Include(gc => gc.StreamRole) + .Include(gc => gc.NsfwBlacklistedTags) + .Include(gc => gc.XpSettings) + .ThenInclude(x => x.ExclusionList) .ToList(); /// @@ -134,9 +143,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl return query.ToList(); } - public IEnumerable Permissionsv2ForAll() + public IEnumerable Permissionsv2ForAll(List include) { var query = _set + .Where(x => include.Contains((long)x.GuildId)) .Include(gc => gc.Permissions); return query.ToList(); @@ -167,8 +177,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl return config; } - public IEnumerable GetAllFollowedStreams() => - _set.Include(gc => gc.FollowedStreams) + public IEnumerable GetAllFollowedStreams(List included) => + _set + .Where(gc => included.Contains((long)gc.GuildId)) + .Include(gc => gc.FollowedStreams) .SelectMany(gc => gc.FollowedStreams) .ToList(); @@ -181,5 +193,19 @@ namespace NadekoBot.Services.Database.Repositories.Impl conf.CleverbotEnabled = cleverbotEnabled; } + + public XpSettings XpSettingsFor(ulong guildId) + { + var gc = For(guildId, + set => set.Include(x => x.XpSettings) + .ThenInclude(x => x.RoleRewards) + .Include(x => x.XpSettings) + .ThenInclude(x => x.ExclusionList)); + + if (gc.XpSettings == null) + gc.XpSettings = new XpSettings(); + + return gc.XpSettings; + } } } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/QuoteRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/QuoteRepository.cs index 76002d1c..00db27b0 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/QuoteRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/QuoteRepository.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using NadekoBot.Common; namespace NadekoBot.Services.Database.Repositories.Impl { diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/ReminderRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/ReminderRepository.cs index fc7c28ff..e29cc3f2 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/ReminderRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/ReminderRepository.cs @@ -1,5 +1,7 @@ using NadekoBot.Services.Database.Models; using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; namespace NadekoBot.Services.Database.Repositories.Impl { @@ -8,5 +10,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl public ReminderRepository(DbContext context) : base(context) { } + + public IEnumerable GetIncludedReminders(IEnumerable guildIds) + { + return _set.Where(x => guildIds.Contains((long)x.ServerId)).ToList(); + } } } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/WaifuRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/WaifuRepository.cs index 3b9a4dc6..f08473b6 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/WaifuRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/WaifuRepository.cs @@ -17,6 +17,7 @@ namespace NadekoBot.Services.Database.Repositories.Impl return _set.Include(wi => wi.Waifu) .Include(wi => wi.Affinity) .Include(wi => wi.Claimer) + .Include(wi => wi.Items) .FirstOrDefault(wi => wi.Waifu.UserId == userId); } @@ -25,6 +26,7 @@ namespace NadekoBot.Services.Database.Repositories.Impl return _set.Include(wi => wi.Waifu) .Include(wi => wi.Affinity) .Include(wi => wi.Claimer) + .Include(wi => wi.Items) .Where(wi => wi.Claimer != null && wi.Claimer.UserId == userId) .ToList(); } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/WarningsRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/WarningsRepository.cs index aa5a6d7e..cb2cc089 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/WarningsRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/WarningsRepository.cs @@ -32,5 +32,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl }) .ConfigureAwait(false); } + + public Warning[] GetForGuild(ulong id) + { + return _set.Where(x => x.GuildId == id).ToArray(); + } } } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs new file mode 100644 index 00000000..64c78413 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs @@ -0,0 +1,54 @@ +using NadekoBot.Services.Database.Models; +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class XpRepository : Repository, IXpRepository + { + public XpRepository(DbContext context) : base(context) + { + } + + public UserXpStats GetOrCreateUser(ulong guildId, ulong userId) + { + var usr = _set.FirstOrDefault(x => x.UserId == userId && x.GuildId == guildId); + + if (usr == null) + { + _context.Add(usr = new UserXpStats() + { + Xp = 0, + UserId = userId, + NotifyOnLevelUp = XpNotificationType.None, + GuildId = guildId, + }); + } + + return usr; + } + + public UserXpStats[] GetUsersFor(ulong guildId, int page) + { + return _set.Where(x => x.GuildId == guildId) + .OrderByDescending(x => x.Xp + x.AwardedXp) + .Skip(page * 9) + .Take(9) + .ToArray(); + } + + public int GetUserGuildRanking(ulong userId, ulong guildId) + { + if (!_set.Where(x => x.GuildId == guildId && x.UserId == userId).Any()) + return _set.Count(); + + return _set + .Where(x => x.GuildId == guildId) + .Count(x => x.Xp > (_set + .Where(y => y.UserId == userId && y.GuildId == guildId) + .Select(y => y.Xp) + .DefaultIfEmpty() + .Sum())) + 1; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Database/UnitOfWork.cs b/src/NadekoBot/Services/Database/UnitOfWork.cs index 02e78a97..27a43b13 100644 --- a/src/NadekoBot/Services/Database/UnitOfWork.cs +++ b/src/NadekoBot/Services/Database/UnitOfWork.cs @@ -57,6 +57,12 @@ namespace NadekoBot.Services.Database private IWarningsRepository _warnings; public IWarningsRepository Warnings => _warnings ?? (_warnings = new WarningsRepository(_context)); + private IXpRepository _xp; + public IXpRepository Xp => _xp ?? (_xp = new XpRepository(_context)); + + private IClubRepository _clubs; + public IClubRepository Clubs => _clubs ?? (_clubs = new ClubRepository(_context)); + public UnitOfWork(NadekoContext context) { _context = context; diff --git a/src/NadekoBot/Services/DbService.cs b/src/NadekoBot/Services/DbService.cs index 81e56d1d..56c6940a 100644 --- a/src/NadekoBot/Services/DbService.cs +++ b/src/NadekoBot/Services/DbService.cs @@ -1,40 +1,55 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Database; +using System; +using System.IO; +using System.Linq; namespace NadekoBot.Services { public class DbService { - private readonly DbContextOptions options; - - private readonly string _connectionString; + private readonly DbContextOptions options; + private readonly DbContextOptions migrateOptions; public DbService(IBotCredentials creds) { - _connectionString = creds.Db.ConnectionString; - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSqlite(creds.Db.ConnectionString); + var builder = new SqliteConnectionStringBuilder(creds.Db.ConnectionString); + builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource); + + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlite(builder.ToString()); options = optionsBuilder.Options; - //switch (_creds.Db.Type.ToUpperInvariant()) - //{ - // case "SQLITE": - // dbType = typeof(NadekoSqliteContext); - // break; - // //case "SQLSERVER": - // // dbType = typeof(NadekoSqlServerContext); - // // break; - // default: - // break; - //} + optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlite(builder.ToString(), x => x.SuppressForeignKeyEnforcement()); + migrateOptions = optionsBuilder.Options; } public NadekoContext GetDbContext() { var context = new NadekoContext(options); - context.Database.Migrate(); + if (context.Database.GetPendingMigrations().Any()) + { + var mContext = new NadekoContext(migrateOptions); + mContext.Database.Migrate(); + mContext.SaveChanges(); + mContext.Dispose(); + } + context.Database.SetCommandTimeout(60); context.EnsureSeedData(); + //set important sqlite stuffs + var conn = context.Database.GetDbConnection(); + conn.Open(); + + context.Database.ExecuteSqlCommand("PRAGMA journal_mode=WAL"); + using (var com = conn.CreateCommand()) + { + com.CommandText = "PRAGMA journal_mode=WAL; PRAGMA synchronous=OFF"; + com.ExecuteNonQuery(); + } + return context; } diff --git a/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs b/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs index 58b3a1e5..3c3aabf9 100644 --- a/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs +++ b/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs @@ -12,7 +12,7 @@ namespace NadekoBot.Services.Discord public event Action OnReactionRemoved = delegate { }; public event Action OnReactionsCleared = delegate { }; - public ReactionEventWrapper(DiscordShardedClient client, IUserMessage msg) + public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg) { Message = msg ?? throw new ArgumentNullException(nameof(msg)); _client = client; @@ -24,36 +24,45 @@ namespace NadekoBot.Services.Discord private Task Discord_ReactionsCleared(Cacheable msg, ISocketMessageChannel channel) { - try + Task.Run(() => { - if (msg.Id == Message.Id) - OnReactionsCleared?.Invoke(); - } - catch { } + try + { + if (msg.Id == Message.Id) + OnReactionsCleared?.Invoke(); + } + catch { } + }); return Task.CompletedTask; } private Task Discord_ReactionRemoved(Cacheable msg, ISocketMessageChannel channel, SocketReaction reaction) { - try + Task.Run(() => { - if (msg.Id == Message.Id) - OnReactionRemoved?.Invoke(reaction); - } - catch { } + try + { + if (msg.Id == Message.Id) + OnReactionRemoved?.Invoke(reaction); + } + catch { } + }); return Task.CompletedTask; } private Task Discord_ReactionAdded(Cacheable msg, ISocketMessageChannel channel, SocketReaction reaction) { - try + Task.Run(() => { - if (msg.Id == Message.Id) - OnReactionAdded?.Invoke(reaction); - } - catch { } + try + { + if (msg.Id == Message.Id) + OnReactionAdded?.Invoke(reaction); + } + catch { } + }); return Task.CompletedTask; } @@ -69,7 +78,7 @@ namespace NadekoBot.Services.Discord } private bool disposing = false; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; public void Dispose() { diff --git a/src/NadekoBot/Services/GreetSettingsService.cs b/src/NadekoBot/Services/GreetSettingsService.cs index 6e2e630d..2d8b5e9b 100644 --- a/src/NadekoBot/Services/GreetSettingsService.cs +++ b/src/NadekoBot/Services/GreetSettingsService.cs @@ -1,6 +1,5 @@ using Discord; using Discord.WebSocket; -using NadekoBot.DataStructures; using NadekoBot.Extensions; using NadekoBot.Services.Database.Models; using NLog; @@ -9,18 +8,20 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NadekoBot.Common; +using NadekoBot.Common.Replacements; namespace NadekoBot.Services { - public class GreetSettingsService + public class GreetSettingsService : INService { private readonly DbService _db; public readonly ConcurrentDictionary GuildConfigsCache; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly Logger _log; - public GreetSettingsService(DiscordShardedClient client, IEnumerable guildConfigs, DbService db) + public GreetSettingsService(DiscordSocketClient client, IEnumerable guildConfigs, DbService db) { _db = db; _client = client; @@ -45,15 +46,17 @@ namespace NadekoBot.Services if (channel == null) //maybe warn the server owner that the channel is missing return; - CREmbed embedData; - if (CREmbed.TryParse(conf.ChannelByeMessageText, out embedData)) + + var rep = new ReplacementBuilder() + .WithDefault(user, channel, user.Guild, _client) + .Build(); + + if (CREmbed.TryParse(conf.ChannelByeMessageText, out var embedData)) { - embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); - embedData.Description = embedData.Description?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); - embedData.Title = embedData.Title?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); + rep.Replace(embedData); try { - var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false); + var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText?.SanitizeMentions() ?? "").ConfigureAwait(false); if (conf.AutoDeleteByeMessagesTimer > 0) { toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer); @@ -63,7 +66,7 @@ namespace NadekoBot.Services } else { - var msg = conf.ChannelByeMessageText.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); + var msg = rep.Replace(conf.ChannelByeMessageText); if (string.IsNullOrWhiteSpace(msg)) return; try @@ -98,16 +101,16 @@ namespace NadekoBot.Services var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.GreetMessageChannelId); if (channel != null) //maybe warn the server owner that the channel is missing { + var rep = new ReplacementBuilder() + .WithDefault(user, channel, user.Guild, _client) + .Build(); - CREmbed embedData; - if (CREmbed.TryParse(conf.ChannelGreetMessageText, out embedData)) + if (CREmbed.TryParse(conf.ChannelGreetMessageText, out var embedData)) { - embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); - embedData.Description = embedData.Description?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); - embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); + rep.Replace(embedData); try { - var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false); + var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText?.SanitizeMentions() ?? "").ConfigureAwait(false); if (conf.AutoDeleteGreetMessagesTimer > 0) { toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer); @@ -117,7 +120,7 @@ namespace NadekoBot.Services } else { - var msg = conf.ChannelGreetMessageText.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); + var msg = rep.Replace(conf.ChannelGreetMessageText); if (!string.IsNullOrWhiteSpace(msg)) { try @@ -136,25 +139,29 @@ namespace NadekoBot.Services if (conf.SendDmGreetMessage) { - var channel = await user.CreateDMChannelAsync(); + var channel = await user.GetOrCreateDMChannelAsync(); if (channel != null) { - CREmbed embedData; - if (CREmbed.TryParse(conf.DmGreetMessageText, out embedData)) + var rep = new ReplacementBuilder() + .WithDefault(user, channel, user.Guild, _client) + .Build(); + if (CREmbed.TryParse(conf.DmGreetMessageText, out var embedData)) { - embedData.PlainText = embedData.PlainText?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); - embedData.Description = embedData.Description?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); - embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); + + rep.Replace(embedData); try { - await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false); + await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText?.SanitizeMentions() ?? "").ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Warn(ex); } - catch (Exception ex) { _log.Warn(ex); } } else { - var msg = conf.DmGreetMessageText.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); + var msg = rep.Replace(conf.DmGreetMessageText); if (!string.IsNullOrWhiteSpace(msg)) { await channel.SendConfirmAsync(msg).ConfigureAwait(false); @@ -409,4 +416,4 @@ namespace NadekoBot.Services ChannelByeMessageText = g.ChannelByeMessageText, }; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/IBotConfigProvider.cs b/src/NadekoBot/Services/IBotConfigProvider.cs new file mode 100644 index 00000000..6ef54970 --- /dev/null +++ b/src/NadekoBot/Services/IBotConfigProvider.cs @@ -0,0 +1,12 @@ +using NadekoBot.Common; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Services +{ + public interface IBotConfigProvider + { + BotConfig BotConfig { get; } + void Reload(); + bool Edit(BotConfigEditType type, string newValue); + } +} diff --git a/src/NadekoBot/Services/IBotCredentials.cs b/src/NadekoBot/Services/IBotCredentials.cs index c6c42f87..f5ed37cc 100644 --- a/src/NadekoBot/Services/IBotCredentials.cs +++ b/src/NadekoBot/Services/IBotCredentials.cs @@ -16,10 +16,14 @@ namespace NadekoBot.Services string CarbonKey { get; } DBConfig Db { get; } - string SoundCloudClientId { get; } string OsuApiKey { get; } bool IsOwner(IUser u); + int TotalShards { get; } + string ShardRunCommand { get; } + string ShardRunArguments { get; } + string PatreonCampaignId { get; } + string CleverbotApiKey { get; } } public class DBConfig diff --git a/src/NadekoBot/Services/IDataCache.cs b/src/NadekoBot/Services/IDataCache.cs new file mode 100644 index 00000000..3be7ad35 --- /dev/null +++ b/src/NadekoBot/Services/IDataCache.cs @@ -0,0 +1,18 @@ +using StackExchange.Redis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services +{ + public interface IDataCache + { + ConnectionMultiplexer Redis { get; } + Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key); + Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key); + Task SetImageDataAsync(string key, byte[] data); + Task SetAnimeDataAsync(string link, string data); + } +} diff --git a/src/NadekoBot/Services/IGoogleApiService.cs b/src/NadekoBot/Services/IGoogleApiService.cs index f2e26f11..758a7d0f 100644 --- a/src/NadekoBot/Services/IGoogleApiService.cs +++ b/src/NadekoBot/Services/IGoogleApiService.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace NadekoBot.Services { - public interface IGoogleApiService + public interface IGoogleApiService : INService { IEnumerable Languages { get; } diff --git a/src/NadekoBot/Services/IImagesService.cs b/src/NadekoBot/Services/IImagesService.cs index 0a76175d..0b2b94f2 100644 --- a/src/NadekoBot/Services/IImagesService.cs +++ b/src/NadekoBot/Services/IImagesService.cs @@ -1,9 +1,8 @@ -using System; -using System.Collections.Immutable; +using System.Collections.Immutable; namespace NadekoBot.Services { - public interface IImagesService + public interface IImagesService : INService { ImmutableArray Heads { get; } ImmutableArray Tails { get; } @@ -18,6 +17,8 @@ namespace NadekoBot.Services ImmutableArray WifeMatrix { get; } ImmutableArray RategirlDot { get; } - TimeSpan Reload(); + ImmutableArray XpCard { get; } + + void Reload(); } } diff --git a/src/NadekoBot/Services/ILocalization.cs b/src/NadekoBot/Services/ILocalization.cs index b9fa898d..c3cfbe94 100644 --- a/src/NadekoBot/Services/ILocalization.cs +++ b/src/NadekoBot/Services/ILocalization.cs @@ -4,7 +4,7 @@ using Discord; namespace NadekoBot.Services { - public interface ILocalization + public interface ILocalization : INService { CultureInfo DefaultCultureInfo { get; } ConcurrentDictionary GuildCultureInfos { get; } diff --git a/src/NadekoBot/Services/INService.cs b/src/NadekoBot/Services/INService.cs new file mode 100644 index 00000000..13bbbeea --- /dev/null +++ b/src/NadekoBot/Services/INService.cs @@ -0,0 +1,10 @@ +namespace NadekoBot.Services +{ + /// + /// All services must implement this interface in order to be auto-discovered by the DI system + /// + public interface INService + { + + } +} diff --git a/src/NadekoBot/Services/IStatsService.cs b/src/NadekoBot/Services/IStatsService.cs index a7735fc8..800b8fba 100644 --- a/src/NadekoBot/Services/IStatsService.cs +++ b/src/NadekoBot/Services/IStatsService.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; namespace NadekoBot.Services { - public interface IStatsService + public interface IStatsService : INService { string Author { get; } long CommandsRan { get; } @@ -13,6 +13,7 @@ namespace NadekoBot.Services double MessagesPerSecond { get; } long TextChannels { get; } long VoiceChannels { get; } + int GuildCount { get; } TimeSpan GetUptime(); string GetUptimeString(string separator = ", "); diff --git a/src/NadekoBot/Services/Impl/BotConfigProvider.cs b/src/NadekoBot/Services/Impl/BotConfigProvider.cs new file mode 100644 index 00000000..2d293401 --- /dev/null +++ b/src/NadekoBot/Services/Impl/BotConfigProvider.cs @@ -0,0 +1,147 @@ +using System; +using NadekoBot.Common; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services; + +namespace NadekoBot.Services.Impl +{ + public class BotConfigProvider : IBotConfigProvider + { + private readonly DbService _db; + public BotConfig BotConfig { get; private set; } + + public BotConfigProvider(DbService db, BotConfig bc) + { + _db = db; + BotConfig = bc; + } + + public void Reload() + { + using (var uow = _db.UnitOfWork) + { + BotConfig = uow.BotConfig.GetOrCreate(); + } + } + + public bool Edit(BotConfigEditType type, string newValue) + { + using (var uow = _db.UnitOfWork) + { + var bc = uow.BotConfig.GetOrCreate(); + switch (type) + { + case BotConfigEditType.CurrencyGenerationChance: + if (float.TryParse(newValue, out var chance) + && chance >= 0 + && chance <= 1) + { + bc.CurrencyGenerationChance = chance; + } + else + { + return false; + } + break; + case BotConfigEditType.CurrencyGenerationCooldown: + if (int.TryParse(newValue, out var cd) && cd >= 1) + { + bc.CurrencyGenerationCooldown = cd; + } + else + { + return false; + } + break; + case BotConfigEditType.CurrencyName: + bc.CurrencyName = newValue ?? "-"; + break; + case BotConfigEditType.CurrencyPluralName: + bc.CurrencyPluralName = newValue ?? bc.CurrencyName + "s"; + break; + case BotConfigEditType.CurrencySign: + bc.CurrencySign = newValue ?? "-"; + break; + case BotConfigEditType.DmHelpString: + bc.DMHelpString = string.IsNullOrWhiteSpace(newValue) + ? "-" + : newValue; + break; + case BotConfigEditType.HelpString: + bc.HelpString = string.IsNullOrWhiteSpace(newValue) + ? "-" + : newValue; + break; + case BotConfigEditType.CurrencyDropAmount: + if (int.TryParse(newValue, out var amount) && amount > 0) + bc.CurrencyDropAmount = amount; + else + return false; + break; + case BotConfigEditType.CurrencyDropAmountMax: + if (newValue == null) + bc.CurrencyDropAmountMax = null; + else if (int.TryParse(newValue, out var maxAmount) && maxAmount > 0) + bc.CurrencyDropAmountMax = maxAmount; + else + return false; + break; + case BotConfigEditType.MinimumBetAmount: + if (int.TryParse(newValue, out var minBetAmount) && minBetAmount > 0) + bc.MinimumBetAmount = minBetAmount; + else + return false; + break; + case BotConfigEditType.TriviaCurrencyReward: + if (int.TryParse(newValue, out var triviaReward) && triviaReward > 0) + bc.TriviaCurrencyReward = triviaReward; + else + return false; + break; + case BotConfigEditType.Betroll100Multiplier: + if (float.TryParse(newValue, out var br100) && br100 > 0) + bc.Betroll100Multiplier = br100; + else + return false; + break; + case BotConfigEditType.Betroll91Multiplier: + if (int.TryParse(newValue, out var br91) && br91 > 0) + bc.Betroll91Multiplier = br91; + else + return false; + break; + case BotConfigEditType.Betroll67Multiplier: + if (int.TryParse(newValue, out var br67) && br67 > 0) + bc.Betroll67Multiplier = br67; + else + return false; + break; + case BotConfigEditType.BetflipMultiplier: + if (int.TryParse(newValue, out var bf) && bf > 0) + bc.BetflipMultiplier = bf; + else + return false; + break; + case BotConfigEditType.XpPerMessage: + if (int.TryParse(newValue, out var xp) && xp > 0) + bc.XpPerMessage = xp; + else + return false; + break; + case BotConfigEditType.XpMinutesTimeout: + if (int.TryParse(newValue, out var min) && min > 0) + bc.XpMinutesTimeout = min; + else + return false; + break; + default: + return false; + } + + BotConfig = bc; + uow.Complete(); + } + return true; + } + } +} diff --git a/src/NadekoBot/Services/Impl/BotCredentials.cs b/src/NadekoBot/Services/Impl/BotCredentials.cs index 828ed573..0ee0dea9 100644 --- a/src/NadekoBot/Services/Impl/BotCredentials.cs +++ b/src/NadekoBot/Services/Impl/BotCredentials.cs @@ -6,6 +6,7 @@ using System.Linq; using NLog; using Microsoft.Extensions.Configuration; using System.Collections.Immutable; +using NadekoBot.Common; namespace NadekoBot.Services.Impl { @@ -25,36 +26,31 @@ namespace NadekoBot.Services.Impl public string LoLApiKey { get; } public string OsuApiKey { get; } - private string _soundcloudClientId; - public string SoundCloudClientId { - get { - return string.IsNullOrWhiteSpace(_soundcloudClientId) - ? "d0bd7768e3a1a2d15430f0dccb871117" - : _soundcloudClientId; - } - private set { - _soundcloudClientId = value; - } - } + public string CleverbotApiKey { get; } public DBConfig Db { get; } public int TotalShards { get; } public string CarbonKey { get; } - public string credsFileName { get; } = Path.Combine(Directory.GetCurrentDirectory(), "credentials.json"); + private readonly string _credsFileName = Path.Combine(Directory.GetCurrentDirectory(), "credentials.json"); public string PatreonAccessToken { get; } + public string ShardRunCommand { get; } + public string ShardRunArguments { get; } + public int ShardRunPort { get; } + + public string PatreonCampaignId { get; } public BotCredentials() { _log = LogManager.GetCurrentClassLogger(); try { File.WriteAllText("./credentials_example.json", JsonConvert.SerializeObject(new CredentialsModel(), Formatting.Indented)); } catch { } - if(!File.Exists(credsFileName)) + if(!File.Exists(_credsFileName)) _log.Warn($"credentials.json is missing. Attempting to load creds from environment variables prefixed with 'NadekoBot_'. Example is in {Path.GetFullPath("./credentials_example.json")}"); try { var configBuilder = new ConfigurationBuilder(); - configBuilder.AddJsonFile(credsFileName, true) + configBuilder.AddJsonFile(_credsFileName, true) .AddEnvironmentVariables("NadekoBot_"); var data = configBuilder.Build(); @@ -72,27 +68,35 @@ namespace NadekoBot.Services.Impl MashapeKey = data[nameof(MashapeKey)]; OsuApiKey = data[nameof(OsuApiKey)]; PatreonAccessToken = data[nameof(PatreonAccessToken)]; + PatreonCampaignId = data[nameof(PatreonCampaignId)] ?? "334038"; + ShardRunCommand = data[nameof(ShardRunCommand)]; + ShardRunArguments = data[nameof(ShardRunArguments)]; + CleverbotApiKey = data[nameof(CleverbotApiKey)]; + if (string.IsNullOrWhiteSpace(ShardRunCommand)) + ShardRunCommand = "dotnet"; + if (string.IsNullOrWhiteSpace(ShardRunArguments)) + ShardRunArguments = "run -c Release -- {0} {1} {2}"; + + var portStr = data[nameof(ShardRunPort)]; + if (string.IsNullOrWhiteSpace(portStr)) + ShardRunPort = new NadekoRandom().Next(5000, 6000); + else + ShardRunPort = int.Parse(portStr); int ts = 1; int.TryParse(data[nameof(TotalShards)], out ts); TotalShards = ts < 1 ? 1 : ts; - ulong clId = 0; - ulong.TryParse(data[nameof(ClientId)], out clId); + ulong.TryParse(data[nameof(ClientId)], out ulong clId); ClientId = clId; - var scId = data[nameof(SoundCloudClientId)]; - SoundCloudClientId = scId; - //SoundCloudClientId = string.IsNullOrWhiteSpace(scId) - // ? - // : scId; CarbonKey = data[nameof(CarbonKey)]; var dbSection = data.GetSection("db"); Db = new DBConfig(string.IsNullOrWhiteSpace(dbSection["Type"]) ? "sqlite" : dbSection["Type"], string.IsNullOrWhiteSpace(dbSection["ConnectionString"]) - ? "Filename=./data/NadekoBot.db" + ? "Data Source=data/NadekoBot.db" : dbSection["ConnectionString"]); } catch (Exception ex) @@ -114,10 +118,16 @@ namespace NadekoBot.Services.Impl public string MashapeKey { get; set; } = ""; public string OsuApiKey { get; set; } = ""; public string SoundCloudClientId { get; set; } = ""; + public string CleverbotApiKey { get; } = ""; public string CarbonKey { get; set; } = ""; - public DBConfig Db { get; set; } = new DBConfig("sqlite", "Filename=./data/NadekoBot.db"); + public DBConfig Db { get; set; } = new DBConfig("sqlite", "Data Source=data/NadekoBot.db"); public int TotalShards { get; set; } = 1; public string PatreonAccessToken { get; set; } = ""; + public string PatreonCampaignId { get; set; } = "334038"; + + public string ShardRunCommand { get; set; } = ""; + public string ShardRunArguments { get; set; } = ""; + public int? ShardRunPort { get; set; } = null; } private class DbModel diff --git a/src/NadekoBot/Services/Impl/GoogleApiService.cs b/src/NadekoBot/Services/Impl/GoogleApiService.cs index d7d93f4c..45727fba 100644 --- a/src/NadekoBot/Services/Impl/GoogleApiService.cs +++ b/src/NadekoBot/Services/Impl/GoogleApiService.cs @@ -42,16 +42,17 @@ namespace NadekoBot.Services.Impl sh = new UrlshortenerService(bcs); cs = new CustomsearchService(bcs); } - + private static readonly Regex plRegex = new Regex("(?:youtu\\.be\\/|list=)(?[\\da-zA-Z\\-_]*)", RegexOptions.Compiled); public async Task> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1) { + await Task.Yield(); if (string.IsNullOrWhiteSpace(keywords)) throw new ArgumentNullException(nameof(keywords)); if (count <= 0) throw new ArgumentOutOfRangeException(nameof(count)); - var match = new Regex("(?:youtu\\.be\\/|list=)(?[\\da-zA-Z\\-_]*)").Match(keywords); + var match = plRegex.Match(keywords); if (match.Length > 1) { return new[] { match.Groups["id"].Value.ToString() }; @@ -64,22 +65,17 @@ namespace NadekoBot.Services.Impl return (await query.ExecuteAsync()).Items.Select(i => i.Id.PlaylistId); } - private readonly Regex YtVideoIdRegex = new Regex(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)(?[a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled); + //private readonly Regex YtVideoIdRegex = new Regex(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)(?[a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled); private readonly IBotCredentials _creds; public async Task> GetRelatedVideosAsync(string id, int count = 1) { + await Task.Yield(); if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id)); if (count <= 0) throw new ArgumentOutOfRangeException(nameof(count)); - - var match = YtVideoIdRegex.Match(id); - if (match.Length > 1) - { - id = match.Groups["id"].Value; - } var query = yt.Search.List("snippet"); query.MaxResults = count; query.RelatedToVideoId = id; @@ -89,22 +85,13 @@ namespace NadekoBot.Services.Impl public async Task> GetVideoLinksByKeywordAsync(string keywords, int count = 1) { + await Task.Yield(); if (string.IsNullOrWhiteSpace(keywords)) throw new ArgumentNullException(nameof(keywords)); if (count <= 0) throw new ArgumentOutOfRangeException(nameof(count)); - - string id = ""; - var match = YtVideoIdRegex.Match(keywords); - if (match.Length > 1) - { - id = match.Groups["id"].Value; - } - if (!string.IsNullOrWhiteSpace(id)) - { - return new[] { "http://www.youtube.com/watch?v=" + id }; - } + var query = yt.Search.List("snippet"); query.MaxResults = count; query.Q = keywords; @@ -114,6 +101,7 @@ namespace NadekoBot.Services.Impl public async Task> GetVideoInfosByKeywordAsync(string keywords, int count = 1) { + await Task.Yield(); if (string.IsNullOrWhiteSpace(keywords)) throw new ArgumentNullException(nameof(keywords)); @@ -129,6 +117,7 @@ namespace NadekoBot.Services.Impl public async Task ShortenUrl(string url) { + await Task.Yield(); if (string.IsNullOrWhiteSpace(url)) throw new ArgumentNullException(nameof(url)); @@ -149,6 +138,7 @@ namespace NadekoBot.Services.Impl public async Task> GetPlaylistTracksAsync(string playlistId, int count = 50) { + await Task.Yield(); if (string.IsNullOrWhiteSpace(playlistId)) throw new ArgumentNullException(nameof(playlistId)); @@ -181,6 +171,7 @@ namespace NadekoBot.Services.Impl public async Task> GetVideoDurationsAsync(IEnumerable videoIds) { + await Task.Yield(); var videoIdsList = videoIds as List ?? videoIds.ToList(); Dictionary toReturn = new Dictionary(); @@ -211,6 +202,7 @@ namespace NadekoBot.Services.Impl public async Task GetImageAsync(string query, int start = 1) { + await Task.Yield(); if (string.IsNullOrWhiteSpace(query)) throw new ArgumentNullException(nameof(query)); @@ -362,6 +354,7 @@ namespace NadekoBot.Services.Impl public async Task Translate(string sourceText, string sourceLanguage, string targetLanguage) { + await Task.Yield(); string text; if (!_languageDictionary.ContainsKey(sourceLanguage) || @@ -384,8 +377,7 @@ namespace NadekoBot.Services.Impl private string ConvertToLanguageCode(string language) { - string mode; - _languageDictionary.TryGetValue(language, out mode); + _languageDictionary.TryGetValue(language, out var mode); return mode; } } diff --git a/src/NadekoBot/Services/Impl/ImagesService.cs b/src/NadekoBot/Services/Impl/ImagesService.cs index 4bce6148..3bb0bd2e 100644 --- a/src/NadekoBot/Services/Impl/ImagesService.cs +++ b/src/NadekoBot/Services/Impl/ImagesService.cs @@ -1,7 +1,6 @@ using NLog; using System; using System.Collections.Immutable; -using System.Diagnostics; using System.IO; using System.Linq; @@ -26,6 +25,8 @@ namespace NadekoBot.Services.Impl private const string _wifeMatrixPath = _basePath + "rategirl/wifematrix.png"; private const string _rategirlDot = _basePath + "rategirl/dot.png"; + private const string _xpCardPath = _basePath + "xp/xp.png"; + public ImmutableArray Heads { get; private set; } public ImmutableArray Tails { get; private set; } @@ -41,18 +42,18 @@ namespace NadekoBot.Services.Impl public ImmutableArray WifeMatrix { get; private set; } public ImmutableArray RategirlDot { get; private set; } + public ImmutableArray XpCard { get; private set; } + public ImagesService() { _log = LogManager.GetCurrentClassLogger(); this.Reload(); } - public TimeSpan Reload() + public void Reload() { try { - _log.Info("Loading images..."); - var sw = Stopwatch.StartNew(); Heads = File.ReadAllBytes(_headsPath).ToImmutableArray(); Tails = File.ReadAllBytes(_tailsPath).ToImmutableArray(); @@ -80,9 +81,7 @@ namespace NadekoBot.Services.Impl WifeMatrix = File.ReadAllBytes(_wifeMatrixPath).ToImmutableArray(); RategirlDot = File.ReadAllBytes(_rategirlDot).ToImmutableArray(); - sw.Stop(); - _log.Info($"Images loaded after {sw.Elapsed.TotalSeconds:F2}s!"); - return sw.Elapsed; + XpCard = File.ReadAllBytes(_xpCardPath).ToImmutableArray(); } catch (Exception ex) { diff --git a/src/NadekoBot/Services/Impl/Localization.cs b/src/NadekoBot/Services/Impl/Localization.cs index d4befd71..8bc0d257 100644 --- a/src/NadekoBot/Services/Impl/Localization.cs +++ b/src/NadekoBot/Services/Impl/Localization.cs @@ -1,11 +1,15 @@ -using Discord; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Discord; using NLog; +using NadekoBot.Services.Database.Models; +using NadekoBot.Common; +using Newtonsoft.Json; +using System.IO; -namespace NadekoBot.Services +namespace NadekoBot.Services.Impl { public class Localization : ILocalization { @@ -15,11 +19,24 @@ namespace NadekoBot.Services public ConcurrentDictionary GuildCultureInfos { get; } public CultureInfo DefaultCultureInfo { get; private set; } = CultureInfo.CurrentCulture; + private static readonly Dictionary _commandData; + + static Localization() + { + _commandData = JsonConvert.DeserializeObject>( + File.ReadAllText("./data/command_strings.json")); + } + private Localization() { } - public Localization(string defaultCulture, IDictionary cultureInfoNames, DbService db) + public Localization(IBotConfigProvider bcp, IEnumerable gcs, DbService db) { _log = LogManager.GetCurrentClassLogger(); + + var cultureInfoNames = gcs.ToDictionary(x => x.GuildId, x => x.Locale); + var defaultCulture = bcp.BotConfig.Locale; + _db = db; + if (string.IsNullOrWhiteSpace(defaultCulture)) DefaultCultureInfo = new CultureInfo("en-US"); else @@ -74,8 +91,7 @@ namespace NadekoBot.Services public void RemoveGuildCulture(ulong guildId) { - CultureInfo throwaway; - if (GuildCultureInfos.TryRemove(guildId, out throwaway)) + if (GuildCultureInfos.TryRemove(guildId, out var _)) { using (var uow = _db.UnitOfWork) { @@ -112,10 +128,19 @@ namespace NadekoBot.Services return info ?? DefaultCultureInfo; } - public static string LoadCommandString(string key) + public static CommandData LoadCommand(string key) { - string toReturn = Resources.CommandStrings.ResourceManager.GetString(key); - return string.IsNullOrWhiteSpace(toReturn) ? key : toReturn; + _commandData.TryGetValue(key, out var toReturn); + + if (toReturn == null) + return new CommandData + { + Cmd = key, + Desc = key, + Usage = new[] { key }, + }; + + return toReturn; } } } diff --git a/src/NadekoBot/Services/Impl/NadekoStrings.cs b/src/NadekoBot/Services/Impl/NadekoStrings.cs index fb0d6f17..b50f553e 100644 --- a/src/NadekoBot/Services/Impl/NadekoStrings.cs +++ b/src/NadekoBot/Services/Impl/NadekoStrings.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Globalization; using System.IO; -using NLog; -using System.Diagnostics; -using Newtonsoft.Json; -using System; -using System.Linq; using System.Text.RegularExpressions; +using Newtonsoft.Json; +using NLog; -namespace NadekoBot.Services +namespace NadekoBot.Services.Impl { - public class NadekoStrings + public class NadekoStrings : INService { public const string stringsPath = @"_strings/"; @@ -42,9 +41,9 @@ namespace NadekoBot.Services responseStrings = allLangsDict.ToImmutableDictionary(); sw.Stop(); - _log.Info("Loaded {0} languages ({1}) in {2:F2}s", + _log.Info("Loaded {0} languages in {1:F2}s", responseStrings.Count, - string.Join(",", responseStrings.Keys), + //string.Join(",", responseStrings.Keys), sw.Elapsed.TotalSeconds); ////improper string format checks diff --git a/src/NadekoBot/Services/Impl/RedisCache.cs b/src/NadekoBot/Services/Impl/RedisCache.cs new file mode 100644 index 00000000..e0951120 --- /dev/null +++ b/src/NadekoBot/Services/Impl/RedisCache.cs @@ -0,0 +1,43 @@ +using StackExchange.Redis; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Impl +{ + public class RedisCache : IDataCache + { + private ulong _botid; + + public ConnectionMultiplexer Redis { get; } + private readonly IDatabase _db; + + public RedisCache(ulong botId) + { + _botid = botId; + Redis = ConnectionMultiplexer.Connect("127.0.0.1"); + Redis.PreserveAsyncOrder = false; + _db = Redis.GetDatabase(); + } + + public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key) + { + byte[] x = await _db.StringGetAsync("image_" + key); + return (x != null, x); + } + + public Task SetImageDataAsync(string key, byte[] data) + { + return _db.StringSetAsync("image_" + key, data); + } + + public async Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key) + { + string x = await _db.StringGetAsync("anime_" + key); + return (x != null, x); + } + + public Task SetAnimeDataAsync(string key, string data) + { + return _db.StringSetAsync("anime_" + key, data); + } + } +} diff --git a/src/NadekoBot/Services/Music/SoundCloudApiService.cs b/src/NadekoBot/Services/Impl/SoundCloudApiService.cs similarity index 92% rename from src/NadekoBot/Services/Music/SoundCloudApiService.cs rename to src/NadekoBot/Services/Impl/SoundCloudApiService.cs index 55a343f8..acdba5b7 100644 --- a/src/NadekoBot/Services/Music/SoundCloudApiService.cs +++ b/src/NadekoBot/Services/Impl/SoundCloudApiService.cs @@ -4,9 +4,9 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; -namespace NadekoBot.Services.Music +namespace NadekoBot.Services.Impl { - public class SoundCloudApiService + public class SoundCloudApiService : INService { private readonly IBotCredentials _creds; @@ -19,8 +19,6 @@ namespace NadekoBot.Services.Music { if (string.IsNullOrWhiteSpace(url)) throw new ArgumentNullException(nameof(url)); - if (string.IsNullOrWhiteSpace(_creds.SoundCloudClientId)) - throw new ArgumentNullException(nameof(_creds.SoundCloudClientId)); string response = ""; @@ -44,8 +42,6 @@ namespace NadekoBot.Services.Music { if (string.IsNullOrWhiteSpace(query)) throw new ArgumentNullException(nameof(query)); - if (string.IsNullOrWhiteSpace(_creds.SoundCloudClientId)) - throw new ArgumentNullException(nameof(_creds.SoundCloudClientId)); var response = ""; using (var http = new HttpClient()) diff --git a/src/NadekoBot/Services/Impl/StartingGuildsListService.cs b/src/NadekoBot/Services/Impl/StartingGuildsListService.cs new file mode 100644 index 00000000..e7f413cc --- /dev/null +++ b/src/NadekoBot/Services/Impl/StartingGuildsListService.cs @@ -0,0 +1,24 @@ +using Discord.WebSocket; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Collections; + +namespace NadekoBot.Services.Impl +{ + public class StartingGuildsService : IEnumerable, INService + { + private readonly ImmutableList _guilds; + + public StartingGuildsService(DiscordSocketClient client) + { + this._guilds = client.Guilds.Select(x => (long)x.Id).ToImmutableList(); + } + + public IEnumerator GetEnumerator() => + _guilds.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => + _guilds.GetEnumerator(); + } +} diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index bcd4a092..e484bfb4 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -6,6 +6,9 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net.Http; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -13,11 +16,11 @@ namespace NadekoBot.Services.Impl { public class StatsService : IStatsService { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IBotCredentials _creds; private readonly DateTime _started; - public const string BotVersion = "1.43"; + public const string BotVersion = "1.9.1"; public string Author => "Kwoth#2560"; public string Library => "Discord.Net"; @@ -35,11 +38,17 @@ namespace NadekoBot.Services.Impl public long CommandsRan => Interlocked.Read(ref _commandsRan); private readonly Timer _carbonitexTimer; + private readonly Timer _dataTimer; + private readonly ShardsCoordinator _sc; - public StatsService(DiscordShardedClient client, CommandHandler cmdHandler, IBotCredentials creds) + public int GuildCount => + _sc?.GuildCount ?? _client.Guilds.Count(); + + public StatsService(DiscordSocketClient client, CommandHandler cmdHandler, IBotCredentials creds, NadekoBot nadeko) { _client = client; _creds = creds; + _sc = nadeko.ShardCoord; _started = DateTime.UtcNow; _client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter)); @@ -121,31 +130,68 @@ namespace NadekoBot.Services.Impl return Task.CompletedTask; }; - _carbonitexTimer = new Timer(async (state) => + if (_sc != null) { - if (string.IsNullOrWhiteSpace(_creds.CarbonKey)) - return; - try + _carbonitexTimer = new Timer(async (state) => { - using (var http = new HttpClient()) + if (string.IsNullOrWhiteSpace(_creds.CarbonKey)) + return; + try { - using (var content = new FormUrlEncodedContent( - new Dictionary { - { "servercount", _client.Guilds.Count.ToString() }, - { "key", _creds.CarbonKey }})) + using (var http = new HttpClient()) { - content.Headers.Clear(); - content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); + using (var content = new FormUrlEncodedContent( + new Dictionary { + { "servercount", _sc.GuildCount.ToString() }, + { "key", _creds.CarbonKey }})) + { + content.Headers.Clear(); + content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); - await http.PostAsync("https://www.carbonitex.net/discord/data/botdata.php", content).ConfigureAwait(false); + await http.PostAsync("https://www.carbonitex.net/discord/data/botdata.php", content).ConfigureAwait(false); + } } } - } - catch + catch + { + // ignored + } + }, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); + + var platform = "other"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + platform = "linux"; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + platform = "osx"; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + platform = "windows"; + + _dataTimer = new Timer(async (state) => { - // ignored - } - }, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); + try + { + using (var http = new HttpClient()) + { + using (var content = new FormUrlEncodedContent( + new Dictionary { + { "id", string.Concat(MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(_creds.ClientId.ToString())).Select(x => x.ToString("X2"))) }, + { "guildCount", _sc.GuildCount.ToString() }, + { "version", BotVersion }, + { "platform", platform }})) + { + content.Headers.Clear(); + content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); + + await http.PostAsync("https://selfstats.nadekobot.me/", content).ConfigureAwait(false); + } + } + } + catch + { + // ignored + } + }, null, TimeSpan.FromSeconds(1), TimeSpan.FromHours(1)); + } } public void Initialize() diff --git a/src/NadekoBot/Services/Impl/SyncPreconditionService.cs b/src/NadekoBot/Services/Impl/SyncPreconditionService.cs new file mode 100644 index 00000000..b6a47133 --- /dev/null +++ b/src/NadekoBot/Services/Impl/SyncPreconditionService.cs @@ -0,0 +1,7 @@ +namespace NadekoBot.Services.Impl +{ + public class SyncPreconditionService + { + + } +} diff --git a/src/NadekoBot/Services/Impl/Ytdl.cs b/src/NadekoBot/Services/Impl/Ytdl.cs new file mode 100644 index 00000000..0ddebe6b --- /dev/null +++ b/src/NadekoBot/Services/Impl/Ytdl.cs @@ -0,0 +1,46 @@ +using NLog; +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Impl +{ + public class YtdlOperation : IDisposable + { + private readonly Logger _log; + + public YtdlOperation() + { + _log = LogManager.GetCurrentClassLogger(); + } + + public async Task GetDataAsync(string url) + { + using (Process process = new Process() + { + StartInfo = new ProcessStartInfo() + { + FileName = "youtube-dl", + Arguments = $"-f bestaudio -e --get-url --get-id --get-thumbnail --get-duration --no-check-certificate --default-search \"ytsearch:\" \"{url}\"", + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true, + CreateNoWindow = true, + }, + }) + { + process.Start(); + var str = await process.StandardOutput.ReadToEndAsync(); + var err = await process.StandardError.ReadToEndAsync(); + if(!string.IsNullOrEmpty(err)) + _log.Warn(err); + return str; + } + } + + public void Dispose() + { + + } + } +} diff --git a/src/NadekoBot/Services/LogSetup.cs b/src/NadekoBot/Services/LogSetup.cs new file mode 100644 index 00000000..9cf85799 --- /dev/null +++ b/src/NadekoBot/Services/LogSetup.cs @@ -0,0 +1,23 @@ +using NLog; +using NLog.Config; +using NLog.Targets; + +namespace NadekoBot.Services +{ + public class LogSetup + { + public static void SetupLogger() + { + var logConfig = new LoggingConfiguration(); + var consoleTarget = new ColoredConsoleTarget() + { + Layout = @"${date:format=HH\:mm\:ss} ${logger:shortName=True} | ${message}" + }; + logConfig.AddTarget("Console", consoleTarget); + + logConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget)); + + LogManager.Configuration = logConfig; + } + } +} diff --git a/src/NadekoBot/Services/Music/Exceptions.cs b/src/NadekoBot/Services/Music/Exceptions.cs deleted file mode 100644 index 1dbe8ad7..00000000 --- a/src/NadekoBot/Services/Music/Exceptions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace NadekoBot.Services.Music -{ - class PlaylistFullException : Exception - { - public PlaylistFullException(string message) : base(message) - { - } - public PlaylistFullException() : base("Queue is full.") { } - } - - class SongNotFoundException : Exception - { - public SongNotFoundException(string message) : base(message) - { - } - public SongNotFoundException() : base("Song is not found.") { } - } -} diff --git a/src/NadekoBot/Services/Music/MusicControls.cs b/src/NadekoBot/Services/Music/MusicControls.cs deleted file mode 100644 index 4e688351..00000000 --- a/src/NadekoBot/Services/Music/MusicControls.cs +++ /dev/null @@ -1,381 +0,0 @@ -using Discord; -using Discord.Audio; -using NadekoBot.Extensions; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using NLog; -using NadekoBot.Services.Database.Models; - -namespace NadekoBot.Services.Music -{ - public enum StreamState - { - Resolving, - Queued, - Playing, - Completed - } - - public class MusicPlayer - { - private IAudioClient AudioClient { get; set; } - - /// - /// Player will prioritize different queuer name - /// over the song position in the playlist - /// - public bool FairPlay { get; set; } = false; - - /// - /// Song will stop playing after this amount of time. - /// To prevent people queueing radio or looped songs - /// while other people want to listen to other songs too. - /// - public uint MaxPlaytimeSeconds { get; set; } = 0; - - - // this should be written better - public TimeSpan TotalPlaytime => - _playlist.Any(s => s.TotalTime == TimeSpan.MaxValue) ? - TimeSpan.MaxValue : - new TimeSpan(_playlist.Sum(s => s.TotalTime.Ticks)); - - /// - /// Users who recently got their music wish - /// - private ConcurrentHashSet RecentlyPlayedUsers { get; } = new ConcurrentHashSet(); - - private readonly List _playlist = new List(); - private readonly Logger _log; - private readonly IGoogleApiService _google; - - public IReadOnlyCollection Playlist => _playlist; - - public Song CurrentSong { get; private set; } - public CancellationTokenSource SongCancelSource { get; private set; } - private CancellationToken CancelToken { get; set; } - - public bool Paused { get; set; } - - public float Volume { get; private set; } - - public event Action OnCompleted = delegate { }; - public event Action OnStarted = delegate { }; - public event Action OnPauseChanged = delegate { }; - - public IVoiceChannel PlaybackVoiceChannel { get; private set; } - public ITextChannel OutputTextChannel { get; set; } - - private bool Destroyed { get; set; } - public bool RepeatSong { get; private set; } - public bool RepeatPlaylist { get; private set; } - public bool Autoplay { get; set; } - public uint MaxQueueSize { get; set; } = 0; - - private ConcurrentQueue ActionQueue { get; } = new ConcurrentQueue(); - - public string PrettyVolume => $"🔉 {(int)(Volume * 100)}%"; - - public event Action SongRemoved = delegate { }; - - public MusicPlayer(IVoiceChannel startingVoiceChannel, ITextChannel outputChannel, float? defaultVolume, IGoogleApiService google) - { - _log = LogManager.GetCurrentClassLogger(); - _google = google; - - OutputTextChannel = outputChannel; - Volume = defaultVolume ?? 1.0f; - - PlaybackVoiceChannel = startingVoiceChannel ?? throw new ArgumentNullException(nameof(startingVoiceChannel)); - SongCancelSource = new CancellationTokenSource(); - CancelToken = SongCancelSource.Token; - - Task.Run(async () => - { - try - { - while (!Destroyed) - { - try - { - if (ActionQueue.TryDequeue(out Action action)) - { - action(); - } - } - finally - { - await Task.Delay(100).ConfigureAwait(false); - } - } - } - catch (Exception ex) - { - _log.Warn("Action queue crashed"); - _log.Warn(ex); - } - }).ConfigureAwait(false); - - var t = new Thread(async () => - { - while (!Destroyed) - { - try - { - CurrentSong = GetNextSong(); - - if (CurrentSong == null) - continue; - - while (AudioClient?.ConnectionState == ConnectionState.Disconnecting || - AudioClient?.ConnectionState == ConnectionState.Connecting) - { - _log.Info("Waiting for Audio client"); - await Task.Delay(200).ConfigureAwait(false); - } - - if (AudioClient == null || AudioClient.ConnectionState == ConnectionState.Disconnected) - AudioClient = await PlaybackVoiceChannel.ConnectAsync().ConfigureAwait(false); - - var index = _playlist.IndexOf(CurrentSong); - if (index != -1) - RemoveSongAt(index, true); - - OnStarted(this, CurrentSong); - try - { - await CurrentSong.Play(AudioClient, CancelToken); - } - catch (OperationCanceledException) - { - } - finally - { - OnCompleted(this, CurrentSong); - } - - - if (RepeatPlaylist & !RepeatSong) - AddSong(CurrentSong, CurrentSong.QueuerName); - - if (RepeatSong) - AddSong(CurrentSong, 0); - - } - catch (Exception ex) - { - _log.Warn("Music thread almost crashed."); - _log.Warn(ex); - await Task.Delay(3000).ConfigureAwait(false); - } - finally - { - if (!CancelToken.IsCancellationRequested) - { - SongCancelSource.Cancel(); - } - SongCancelSource = new CancellationTokenSource(); - CancelToken = SongCancelSource.Token; - CurrentSong = null; - await Task.Delay(300).ConfigureAwait(false); - } - } - }); - - t.Start(); - } - - public void Next() - { - ActionQueue.Enqueue(() => - { - Paused = false; - SongCancelSource.Cancel(); - }); - } - - public void Stop() - { - ActionQueue.Enqueue(() => - { - RepeatPlaylist = false; - RepeatSong = false; - Autoplay = false; - _playlist.Clear(); - if (!SongCancelSource.IsCancellationRequested) - SongCancelSource.Cancel(); - }); - } - - public void TogglePause() => OnPauseChanged(Paused = !Paused); - - public int SetVolume(int volume) - { - if (volume < 0) - volume = 0; - if (volume > 100) - volume = 100; - - Volume = volume / 100.0f; - return volume; - } - - private Song GetNextSong() - { - if (!FairPlay) - { - return _playlist.FirstOrDefault(); - } - var song = _playlist.FirstOrDefault(c => !RecentlyPlayedUsers.Contains(c.QueuerName)) - ?? _playlist.FirstOrDefault(); - - if (song == null) - return null; - - if (RecentlyPlayedUsers.Contains(song.QueuerName)) - { - RecentlyPlayedUsers.Clear(); - } - - RecentlyPlayedUsers.Add(song.QueuerName); - return song; - } - - public void Shuffle() - { - ActionQueue.Enqueue(() => - { - var oldPlaylist = _playlist.ToArray(); - _playlist.Clear(); - _playlist.AddRange(oldPlaylist.Shuffle()); - }); - } - - public void AddSong(Song s, string username) - { - if (s == null) - throw new ArgumentNullException(nameof(s)); - ThrowIfQueueFull(); - ActionQueue.Enqueue(() => - { - s.MusicPlayer = this; - s.QueuerName = username.TrimTo(10); - _playlist.Add(s); - }); - } - - public void AddSong(Song s, int index) - { - if (s == null) - throw new ArgumentNullException(nameof(s)); - ActionQueue.Enqueue(() => - { - _playlist.Insert(index, s); - }); - } - - public void RemoveSong(Song s) - { - if (s == null) - throw new ArgumentNullException(nameof(s)); - ActionQueue.Enqueue(() => - { - _playlist.Remove(s); - }); - } - - public void RemoveSongAt(int index, bool silent = false) - { - ActionQueue.Enqueue(() => - { - if (index < 0 || index >= _playlist.Count) - return; - var song = _playlist.ElementAtOrDefault(index); - if (_playlist.Remove(song) && !silent) - { - SongRemoved(song, index); - } - - }); - } - - public void ClearQueue() - { - ActionQueue.Enqueue(() => - { - _playlist.Clear(); - }); - } - - public async Task UpdateSongDurationsAsync() - { - var curSong = CurrentSong; - var toUpdate = _playlist.Where(s => s.SongInfo.ProviderType == MusicType.Normal && - s.TotalTime == TimeSpan.Zero) - .ToArray(); - if (curSong != null) - { - Array.Resize(ref toUpdate, toUpdate.Length + 1); - toUpdate[toUpdate.Length - 1] = curSong; - } - var ids = toUpdate.Select(s => s.SongInfo.Query.Substring(s.SongInfo.Query.LastIndexOf("?v=") + 3)) - .Distinct(); - - var durations = await _google.GetVideoDurationsAsync(ids); - - toUpdate.ForEach(s => - { - foreach (var kvp in durations) - { - if (s.SongInfo.Query.EndsWith(kvp.Key)) - { - s.TotalTime = kvp.Value; - return; - } - } - }); - } - - public void Destroy() - { - ActionQueue.Enqueue(async () => - { - RepeatPlaylist = false; - RepeatSong = false; - Autoplay = false; - Destroyed = true; - _playlist.Clear(); - - try { await AudioClient.StopAsync(); } catch { } - if (!SongCancelSource.IsCancellationRequested) - SongCancelSource.Cancel(); - }); - } - - //public async Task MoveToVoiceChannel(IVoiceChannel voiceChannel) - //{ - // if (audioClient?.ConnectionState != ConnectionState.Connected) - // throw new InvalidOperationException("Can't move while bot is not connected to voice channel."); - // PlaybackVoiceChannel = voiceChannel; - // audioClient = await voiceChannel.ConnectAsync().ConfigureAwait(false); - //} - - public bool ToggleRepeatSong() => RepeatSong = !RepeatSong; - - public bool ToggleRepeatPlaylist() => RepeatPlaylist = !RepeatPlaylist; - - public bool ToggleAutoplay() => Autoplay = !Autoplay; - - public void ThrowIfQueueFull() - { - if (MaxQueueSize == 0) - return; - if (_playlist.Count >= MaxQueueSize) - throw new PlaylistFullException(); - } - } -} diff --git a/src/NadekoBot/Services/Music/MusicService.cs b/src/NadekoBot/Services/Music/MusicService.cs deleted file mode 100644 index 9ea7a958..00000000 --- a/src/NadekoBot/Services/Music/MusicService.cs +++ /dev/null @@ -1,440 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; -using Discord; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using System.Text.RegularExpressions; -using NLog; -using System.IO; -using VideoLibrary; -using System.Net.Http; -using System.Collections.Generic; - -namespace NadekoBot.Services.Music -{ - public class MusicService - { - public const string MusicDataPath = "data/musicdata"; - - private readonly IGoogleApiService _google; - private readonly NadekoStrings _strings; - private readonly ILocalization _localization; - private readonly DbService _db; - private readonly Logger _log; - private readonly SoundCloudApiService _sc; - private readonly IBotCredentials _creds; - private readonly ConcurrentDictionary _defaultVolumes; - - public ConcurrentDictionary MusicPlayers { get; } = new ConcurrentDictionary(); - - public MusicService(IGoogleApiService google, - NadekoStrings strings, ILocalization localization, DbService db, - SoundCloudApiService sc, IBotCredentials creds, IEnumerable gcs) - { - _google = google; - _strings = strings; - _localization = localization; - _db = db; - _sc = sc; - _creds = creds; - _log = LogManager.GetCurrentClassLogger(); - - try { Directory.Delete(MusicDataPath, true); } catch { } - - _defaultVolumes = new ConcurrentDictionary(gcs.ToDictionary(x => x.GuildId, x => x.DefaultMusicVolume)); - - Directory.CreateDirectory(MusicDataPath); - } - - public MusicPlayer GetPlayer(ulong guildId) - { - MusicPlayers.TryGetValue(guildId, out var player); - return player; - } - - public MusicPlayer GetOrCreatePlayer(ulong guildId, IVoiceChannel voiceCh, ITextChannel textCh) - { - string GetText(string text, params object[] replacements) => - _strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements); - - return MusicPlayers.GetOrAdd(guildId, server => - { - var vol = _defaultVolumes.GetOrAdd(guildId, (id) => - { - using (var uow = _db.UnitOfWork) - { - return uow.GuildConfigs.For(guildId, set => set).DefaultMusicVolume; - } - }); - - var mp = new MusicPlayer(voiceCh, textCh, vol, _google); - IUserMessage playingMessage = null; - IUserMessage lastFinishedMessage = null; - mp.OnCompleted += async (s, song) => - { - try - { - lastFinishedMessage?.DeleteAfter(0); - - try - { - lastFinishedMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithAuthor(eab => eab.WithName(GetText("finished_song")).WithMusicIcon()) - .WithDescription(song.PrettyName) - .WithFooter(ef => ef.WithText(song.PrettyInfo))) - .ConfigureAwait(false); - } - catch - { - // ignored - } - - if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.ProviderType == MusicType.Normal) - { - var relatedVideos = (await _google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList(); - if (relatedVideos.Count > 0) - await QueueSong(await textCh.Guild.GetCurrentUserAsync(), - textCh, - voiceCh, - relatedVideos[new NadekoRandom().Next(0, relatedVideos.Count)], - true).ConfigureAwait(false); - } - } - catch - { - // ignored - } - }; - - mp.OnStarted += async (player, song) => - { - try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); } - catch - { - // ignored - } - var sender = player; - if (sender == null) - return; - try - { - playingMessage?.DeleteAfter(0); - - playingMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithAuthor(eab => eab.WithName(GetText("playing_song")).WithMusicIcon()) - .WithDescription(song.PrettyName) - .WithFooter(ef => ef.WithText(song.PrettyInfo))) - .ConfigureAwait(false); - } - catch - { - // ignored - } - }; - mp.OnPauseChanged += async (paused) => - { - try - { - IUserMessage msg; - if (paused) - msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("paused")).ConfigureAwait(false); - else - msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("resumed")).ConfigureAwait(false); - - msg?.DeleteAfter(10); - } - catch - { - // ignored - } - }; - - mp.SongRemoved += async (song, index) => - { - try - { - var embed = new EmbedBuilder() - .WithAuthor(eab => eab.WithName(GetText("removed_song") + " #" + (index + 1)).WithMusicIcon()) - .WithDescription(song.PrettyName) - .WithFooter(ef => ef.WithText(song.PrettyInfo)) - .WithErrorColor(); - - await mp.OutputTextChannel.EmbedAsync(embed).ConfigureAwait(false); - - } - catch - { - // ignored - } - }; - return mp; - }); - } - - - public async Task QueueSong(IGuildUser queuer, ITextChannel textCh, IVoiceChannel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal) - { - string GetText(string text, params object[] replacements) => - _strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements); - - if (voiceCh == null || voiceCh.Guild != textCh.Guild) - { - if (!silent) - await textCh.SendErrorAsync(GetText("must_be_in_voice")).ConfigureAwait(false); - throw new ArgumentNullException(nameof(voiceCh)); - } - if (string.IsNullOrWhiteSpace(query) || query.Length < 3) - throw new ArgumentException("Invalid song query.", nameof(query)); - - var musicPlayer = GetOrCreatePlayer(textCh.Guild.Id, voiceCh, textCh); - Song resolvedSong; - try - { - musicPlayer.ThrowIfQueueFull(); - resolvedSong = await ResolveSong(query, musicType).ConfigureAwait(false); - - if (resolvedSong == null) - throw new SongNotFoundException(); - - musicPlayer.AddSong(resolvedSong, queuer.Username); - } - catch (PlaylistFullException) - { - try - { - await textCh.SendConfirmAsync(GetText("queue_full", musicPlayer.MaxQueueSize)); - } - catch - { - // ignored - } - throw; - } - if (!silent) - { - try - { - //var queuedMessage = await textCh.SendConfirmAsync($"🎵 Queued **{resolvedSong.SongInfo.Title}** at `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false); - var queuedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithAuthor(eab => eab.WithName(GetText("queued_song") + " #" + (musicPlayer.Playlist.Count + 1)).WithMusicIcon()) - .WithDescription($"{resolvedSong.PrettyName}\n{GetText("queue")} ") - .WithThumbnailUrl(resolvedSong.Thumbnail) - .WithFooter(ef => ef.WithText(resolvedSong.PrettyProvider))) - .ConfigureAwait(false); - queuedMessage?.DeleteAfter(10); - } - catch - { - // ignored - } // if queued message sending fails, don't attempt to delete it - } - } - - public void DestroyPlayer(ulong id) - { - if (MusicPlayers.TryRemove(id, out var mp)) - mp.Destroy(); - } - - - public async Task ResolveSong(string query, MusicType musicType = MusicType.Normal) - { - if (string.IsNullOrWhiteSpace(query)) - throw new ArgumentNullException(nameof(query)); - - if (musicType != MusicType.Local && IsRadioLink(query)) - { - musicType = MusicType.Radio; - query = await HandleStreamContainers(query).ConfigureAwait(false) ?? query; - } - - try - { - switch (musicType) - { - case MusicType.Local: - return new Song(new SongInfo - { - Uri = "\"" + Path.GetFullPath(query) + "\"", - Title = Path.GetFileNameWithoutExtension(query), - Provider = "Local File", - ProviderType = musicType, - Query = query, - }); - case MusicType.Radio: - return new Song(new SongInfo - { - Uri = query, - Title = $"{query}", - Provider = "Radio Stream", - ProviderType = musicType, - Query = query - }) - { TotalTime = TimeSpan.MaxValue }; - } - if (_sc.IsSoundCloudLink(query)) - { - var svideo = await _sc.ResolveVideoAsync(query).ConfigureAwait(false); - return new Song(new SongInfo - { - Title = svideo.FullName, - Provider = "SoundCloud", - Uri = await svideo.StreamLink(), - ProviderType = musicType, - Query = svideo.TrackLink, - AlbumArt = svideo.artwork_url, - }) - { TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) }; - } - - if (musicType == MusicType.Soundcloud) - { - var svideo = await _sc.GetVideoByQueryAsync(query).ConfigureAwait(false); - return new Song(new SongInfo - { - Title = svideo.FullName, - Provider = "SoundCloud", - Uri = await svideo.StreamLink(), - ProviderType = MusicType.Soundcloud, - Query = svideo.TrackLink, - AlbumArt = svideo.artwork_url, - }) - { TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) }; - } - - var link = (await _google.GetVideoLinksByKeywordAsync(query).ConfigureAwait(false)).FirstOrDefault(); - if (string.IsNullOrWhiteSpace(link)) - throw new OperationCanceledException("Not a valid youtube query."); - var allVideos = await Task.Run(async () => { try { return await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false); } catch { return Enumerable.Empty(); } }).ConfigureAwait(false); - var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio); - var video = videos - .Where(v => v.AudioBitrate < 256) - .OrderByDescending(v => v.AudioBitrate) - .FirstOrDefault(); - - if (video == null) // do something with this error - throw new Exception("Could not load any video elements based on the query."); - var m = Regex.Match(query, @"\?t=(?\d*)"); - int gotoTime = 0; - if (m.Captures.Count > 0) - int.TryParse(m.Groups["t"].ToString(), out gotoTime); - var song = new Song(new SongInfo - { - Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube" - Provider = "YouTube", - Uri = await video.GetUriAsync().ConfigureAwait(false), - Query = link, - ProviderType = musicType, - }); - song.SkipTo = gotoTime; - return song; - } - catch (Exception ex) - { - _log.Warn($"Failed resolving the link.{ex.Message}"); - _log.Warn(ex); - return null; - } - } - - private async Task HandleStreamContainers(string query) - { - string file = null; - try - { - using (var http = new HttpClient()) - { - file = await http.GetStringAsync(query).ConfigureAwait(false); - } - } - catch - { - return query; - } - if (query.Contains(".pls")) - { - //File1=http://armitunes.com:8000/ - //Regex.Match(query) - try - { - var m = Regex.Match(file, "File1=(?.*?)\\n"); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - _log.Warn($"Failed reading .pls:\n{file}"); - return null; - } - } - if (query.Contains(".m3u")) - { - /* -# This is a comment - C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3 - C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3 - */ - try - { - var m = Regex.Match(file, "(?^[^#].*)", RegexOptions.Multiline); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - _log.Warn($"Failed reading .m3u:\n{file}"); - return null; - } - - } - if (query.Contains(".asx")) - { - // - try - { - var m = Regex.Match(file, ".*?)\""); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - _log.Warn($"Failed reading .asx:\n{file}"); - return null; - } - } - if (query.Contains(".xspf")) - { - /* - - - - file:///mp3s/song_1.mp3 - */ - try - { - var m = Regex.Match(file, "(?.*?)"); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - _log.Warn($"Failed reading .xspf:\n{file}"); - return null; - } - } - - return query; - } - - private bool IsRadioLink(string query) => - (query.StartsWith("http") || - query.StartsWith("ww")) - && - (query.Contains(".pls") || - query.Contains(".m3u") || - query.Contains(".asx") || - query.Contains(".xspf")); - } -} diff --git a/src/NadekoBot/Services/Music/Song.cs b/src/NadekoBot/Services/Music/Song.cs deleted file mode 100644 index 187b3b6e..00000000 --- a/src/NadekoBot/Services/Music/Song.cs +++ /dev/null @@ -1,296 +0,0 @@ -using Discord.Audio; -using NadekoBot.Extensions; -using NLog; -using System; -using System.Diagnostics; -using System.IO; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Net; -using Discord; -using NadekoBot.Services.Database.Models; - -namespace NadekoBot.Services.Music -{ - public class SongInfo - { - public string Provider { get; set; } - public MusicType ProviderType { get; set; } - public string Query { get; set; } - public string Title { get; set; } - public string Uri { get; set; } - public string AlbumArt { get; set; } - } - - public class Song - { - public SongInfo SongInfo { get; } - public MusicPlayer MusicPlayer { get; set; } - - private string _queuerName; - public string QueuerName { get{ - return Format.Sanitize(_queuerName); - } set { _queuerName = value; } } - - public TimeSpan TotalTime { get; set; } = TimeSpan.Zero; - public TimeSpan CurrentTime => TimeSpan.FromSeconds(BytesSent / (float)_frameBytes / (1000 / (float)_milliseconds)); - - private const int _milliseconds = 20; - private const int _samplesPerFrame = (48000 / 1000) * _milliseconds; - private const int _frameBytes = 3840; //16-bit, 2 channels - - private ulong BytesSent { get; set; } - - //pwetty - - public string PrettyProvider => - $"{(SongInfo.Provider ?? "???")}"; - - public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime; - - public string PrettyName => $"**[{SongInfo.Title.TrimTo(65)}]({SongUrl})**"; - - public string PrettyInfo => $"{MusicPlayer.PrettyVolume} | {PrettyTotalTime} | {PrettyProvider} | {QueuerName}"; - - public string PrettyFullName => $"{PrettyName}\n\t\t`{PrettyTotalTime} | {PrettyProvider} | {QueuerName}`"; - - public string PrettyCurrentTime { - get { - var time = CurrentTime.ToString(@"mm\:ss"); - var hrs = (int)CurrentTime.TotalHours; - - if (hrs > 0) - return hrs + ":" + time; - else - return time; - } - } - - public string PrettyTotalTime { - get - { - if (TotalTime == TimeSpan.Zero) - return "(?)"; - if (TotalTime == TimeSpan.MaxValue) - return "∞"; - var time = TotalTime.ToString(@"mm\:ss"); - var hrs = (int)TotalTime.TotalHours; - - if (hrs > 0) - return hrs + ":" + time; - return time; - } - } - - public string Thumbnail { - get { - switch (SongInfo.ProviderType) - { - case MusicType.Radio: - return "https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links - case MusicType.Normal: - //todo 50 have videoid in songinfo from the start - var videoId = Regex.Match(SongInfo.Query, "<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+"); - return $"https://img.youtube.com/vi/{ videoId }/0.jpg"; - case MusicType.Local: - return "https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png"; //test links - case MusicType.Soundcloud: - return SongInfo.AlbumArt; - default: - return ""; - } - } - } - - public string SongUrl { - get { - switch (SongInfo.ProviderType) - { - case MusicType.Normal: - return SongInfo.Query; - case MusicType.Soundcloud: - return SongInfo.Query; - case MusicType.Local: - return $"https://google.com/search?q={ WebUtility.UrlEncode(SongInfo.Title).Replace(' ', '+') }"; - case MusicType.Radio: - return $"https://google.com/search?q={SongInfo.Title}"; - default: - return ""; - } - } - } - - public int SkipTo { get; set; } - - private readonly Logger _log; - - public Song(SongInfo songInfo) - { - SongInfo = songInfo; - _log = LogManager.GetCurrentClassLogger(); - } - - public Song Clone() - { - var s = new Song(SongInfo) - { - MusicPlayer = MusicPlayer, - QueuerName = QueuerName - }; - return s; - } - - public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) - { - BytesSent = (ulong) SkipTo * 3840 * 50; - var filename = Path.Combine(MusicService.MusicDataPath, DateTime.UtcNow.UnixTimestamp().ToString()); - - var inStream = new SongBuffer(MusicPlayer, filename, SongInfo, SkipTo, _frameBytes * 100); - var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false); - - try - { - var attempt = 0; - - var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 1.MiB()); //Fast connection can do this easy - var finished = false; - var count = 0; - var sw = new Stopwatch(); - var slowconnection = false; - sw.Start(); - while (!finished) - { - var t = await Task.WhenAny(prebufferingTask, Task.Delay(2000, cancelToken)); - if (t != prebufferingTask) - { - count++; - if (count == 10) - { - slowconnection = true; - prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 20.MiB()); - _log.Warn("Slow connection buffering more to ensure no disruption, consider hosting in cloud"); - continue; - } - - if (inStream.BufferingCompleted && count == 1) - { - _log.Debug("Prebuffering canceled. Cannot get any data from the stream."); - return; - } - else - { - continue; - } - } - else if (prebufferingTask.IsCanceled) - { - _log.Debug("Prebuffering canceled. Cannot get any data from the stream."); - return; - } - finished = true; - } - sw.Stop(); - _log.Debug("Prebuffering successfully completed in " + sw.Elapsed); - - var outStream = voiceClient.CreatePCMStream(AudioApplication.Music); - - int nextTime = Environment.TickCount + _milliseconds; - - byte[] buffer = new byte[_frameBytes]; - while (!cancelToken.IsCancellationRequested && //song canceled for whatever reason - !(MusicPlayer.MaxPlaytimeSeconds != 0 && CurrentTime.TotalSeconds >= MusicPlayer.MaxPlaytimeSeconds)) // or exceedded max playtime - { - //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------"); - var read = await inStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); - //await inStream.CopyToAsync(voiceClient.OutputStream); - if (read < _frameBytes) - _log.Debug("read {0}", read); - unchecked - { - BytesSent += (ulong)read; - } - if (read < _frameBytes) - { - if (read == 0) - { - if (inStream.BufferingCompleted) - break; - if (attempt++ == 20) - { - MusicPlayer.SongCancelSource.Cancel(); - break; - } - if (slowconnection) - { - _log.Warn("Slow connection has disrupted music, waiting a bit for buffer"); - - await Task.Delay(1000, cancelToken).ConfigureAwait(false); - nextTime = Environment.TickCount + _milliseconds; - } - else - { - await Task.Delay(100, cancelToken).ConfigureAwait(false); - nextTime = Environment.TickCount + _milliseconds; - } - } - else - attempt = 0; - } - else - attempt = 0; - - while (MusicPlayer.Paused) - { - await Task.Delay(200, cancelToken).ConfigureAwait(false); - nextTime = Environment.TickCount + _milliseconds; - } - - - buffer = AdjustVolume(buffer, MusicPlayer.Volume); - if (read != _frameBytes) continue; - nextTime = unchecked(nextTime + _milliseconds); - int delayMillis = unchecked(nextTime - Environment.TickCount); - if (delayMillis > 0) - await Task.Delay(delayMillis, cancelToken).ConfigureAwait(false); - await outStream.WriteAsync(buffer, 0, read).ConfigureAwait(false); - } - } - finally - { - await bufferTask; - inStream.Dispose(); - } - } - - private async Task CheckPrebufferingAsync(SongBuffer inStream, CancellationToken cancelToken, long size) - { - while (!inStream.BufferingCompleted && inStream.Length < size) - { - await Task.Delay(100, cancelToken); - } - _log.Debug("Buffering successfull"); - } - - //aidiakapi ftw - public static unsafe byte[] AdjustVolume(byte[] audioSamples, float volume) - { - if (Math.Abs(volume - 1f) < 0.0001f) return audioSamples; - - // 16-bit precision for the multiplication - var volumeFixed = (int)Math.Round(volume * 65536d); - - var count = audioSamples.Length / 2; - - fixed (byte* srcBytes = audioSamples) - { - var src = (short*)srcBytes; - - for (var i = count; i != 0; i--, src++) - *src = (short)(((*src) * volumeFixed) >> 16); - } - - return audioSamples; - } - } -} \ No newline at end of file diff --git a/src/NadekoBot/Services/Music/SongBuffer.cs b/src/NadekoBot/Services/Music/SongBuffer.cs deleted file mode 100644 index ff3a0ed2..00000000 --- a/src/NadekoBot/Services/Music/SongBuffer.cs +++ /dev/null @@ -1,219 +0,0 @@ -using NadekoBot.Extensions; -using NLog; -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Services.Music -{ - /// - /// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space. - /// It also help for large music by deleting files that are already seen. - /// - class SongBuffer : Stream - { - public SongBuffer(MusicPlayer musicPlayer, string basename, SongInfo songInfo, int skipTo, int maxFileSize) - { - MusicPlayer = musicPlayer; - Basename = basename; - SongInfo = songInfo; - SkipTo = skipTo; - MaxFileSize = maxFileSize; - CurrentFileStream = new FileStream(this.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); - _log = LogManager.GetCurrentClassLogger(); - } - - MusicPlayer MusicPlayer { get; } - - private string Basename { get; } - - private SongInfo SongInfo { get; } - - private int SkipTo { get; } - - private int MaxFileSize { get; } = 2.MiB(); - - private long FileNumber = -1; - - private long NextFileToRead = 0; - - public bool BufferingCompleted { get; private set; } = false; - - private ulong CurrentBufferSize = 0; - - private FileStream CurrentFileStream; - private Logger _log; - - public Task BufferSong(CancellationToken cancelToken) => - Task.Run(async () => - { - Process p = null; - FileStream outStream = null; - try - { - p = Process.Start(new ProcessStartInfo - { - FileName = "ffmpeg", - Arguments = $"-ss {SkipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -vn -ac 2 pipe:1 -loglevel quiet", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = false, - CreateNoWindow = true, - }); - - byte[] buffer = new byte[81920]; - int currentFileSize = 0; - ulong prebufferSize = 100ul.MiB(); - - outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read); - while (!p.HasExited) //Also fix low bandwidth - { - int bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false); - if (currentFileSize >= MaxFileSize) - { - try - { - outStream.Dispose(); - } - catch { } - outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read); - currentFileSize = bytesRead; - } - else - { - currentFileSize += bytesRead; - } - CurrentBufferSize += Convert.ToUInt64(bytesRead); - await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); - while (CurrentBufferSize > prebufferSize) - await Task.Delay(100, cancelToken); - } - BufferingCompleted = true; - } - catch (System.ComponentModel.Win32Exception) - { - var oldclr = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(@"You have not properly installed or configured FFMPEG. -Please install and configure FFMPEG to play music. -Check the guides for your platform on how to setup ffmpeg correctly: - Windows Guide: https://goo.gl/OjKk8F - Linux Guide: https://goo.gl/ShjCUo"); - Console.ForegroundColor = oldclr; - } - catch (Exception ex) - { - Console.WriteLine($"Buffering stopped: {ex.Message}"); - } - finally - { - if (outStream != null) - outStream.Dispose(); - Console.WriteLine($"Buffering done."); - if (p != null) - { - try - { - p.Kill(); - } - catch { } - p.Dispose(); - } - } - }); - - /// - /// Return the next file to read, and delete the old one - /// - /// Name of the file to read - private string GetNextFile() - { - string filename = Basename + "-" + NextFileToRead; - - if (NextFileToRead != 0) - { - try - { - CurrentBufferSize -= Convert.ToUInt64(new FileInfo(Basename + "-" + (NextFileToRead - 1)).Length); - File.Delete(Basename + "-" + (NextFileToRead - 1)); - } - catch { } - } - NextFileToRead++; - return filename; - } - - private bool IsNextFileReady() - { - return NextFileToRead <= FileNumber; - } - - private void CleanFiles() - { - for (long i = NextFileToRead - 1; i <= FileNumber; i++) - { - try - { - File.Delete(Basename + "-" + i); - } - catch { } - } - } - - //Stream part - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override long Length => (long)CurrentBufferSize; - - public override long Position { get; set; } = 0; - - public override void Flush() { } - - public override int Read(byte[] buffer, int offset, int count) - { - int read = CurrentFileStream.Read(buffer, offset, count); - if (read < count) - { - if (!BufferingCompleted || IsNextFileReady()) - { - CurrentFileStream.Dispose(); - CurrentFileStream = new FileStream(GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); - read += CurrentFileStream.Read(buffer, read + offset, count - read); - } - if (read < count) - Array.Clear(buffer, read, count - read); - } - return read; - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - - public new void Dispose() - { - CurrentFileStream.Dispose(); - MusicPlayer.SongCancelSource.Cancel(); - CleanFiles(); - base.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/NadekoBot/Services/Searches/AnimeSearchService.cs b/src/NadekoBot/Services/Searches/AnimeSearchService.cs deleted file mode 100644 index 71820506..00000000 --- a/src/NadekoBot/Services/Searches/AnimeSearchService.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using NLog; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Services.Searches -{ - public class AnimeSearchService - { - private readonly Timer _anilistTokenRefresher; - private readonly Logger _log; - - private static string anilistToken { get; set; } - - public AnimeSearchService() - { - _log = LogManager.GetCurrentClassLogger(); - _anilistTokenRefresher = new Timer(async (state) => - { - try - { - var headers = new Dictionary - { - {"grant_type", "client_credentials"}, - {"client_id", "kwoth-w0ki9"}, - {"client_secret", "Qd6j4FIAi1ZK6Pc7N7V4Z"}, - }; - - using (var http = new HttpClient()) - { - //http.AddFakeHeaders(); - http.DefaultRequestHeaders.Clear(); - var formContent = new FormUrlEncodedContent(headers); - var response = await http.PostAsync("https://anilist.co/api/auth/access_token", formContent).ConfigureAwait(false); - var stringContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - anilistToken = JObject.Parse(stringContent)["access_token"].ToString(); - } - } - catch - { - // ignored - } - }, null, TimeSpan.FromSeconds(0), TimeSpan.FromMinutes(29)); - } - - public async Task GetAnimeData(string query) - { - if (string.IsNullOrWhiteSpace(query)) - throw new ArgumentNullException(nameof(query)); - try - { - - var link = "http://anilist.co/api/anime/search/" + Uri.EscapeUriString(query); - using (var http = new HttpClient()) - { - var res = await http.GetStringAsync(link + $"?access_token={anilistToken}").ConfigureAwait(false); - var smallObj = JArray.Parse(res)[0]; - var aniData = await http.GetStringAsync("http://anilist.co/api/anime/" + smallObj["id"] + $"?access_token={anilistToken}").ConfigureAwait(false); - - return await Task.Run(() => { try { return JsonConvert.DeserializeObject(aniData); } catch { return null; } }).ConfigureAwait(false); - } - } - catch (Exception ex) - { - _log.Warn(ex, "Failed anime search for {0}", query); - return null; - } - } - - public async Task GetMangaData(string query) - { - if (string.IsNullOrWhiteSpace(query)) - throw new ArgumentNullException(nameof(query)); - try - { - using (var http = new HttpClient()) - { - var res = await http.GetStringAsync("http://anilist.co/api/manga/search/" + Uri.EscapeUriString(query) + $"?access_token={anilistToken}").ConfigureAwait(false); - var smallObj = JArray.Parse(res)[0]; - var aniData = await http.GetStringAsync("http://anilist.co/api/manga/" + smallObj["id"] + $"?access_token={anilistToken}").ConfigureAwait(false); - - return await Task.Run(() => { try { return JsonConvert.DeserializeObject(aniData); } catch { return null; } }).ConfigureAwait(false); - } - } - catch (Exception ex) - { - _log.Warn(ex, "Failed anime search for {0}", query); - return null; - } - } - } -} diff --git a/src/NadekoBot/Services/ServiceProvider.cs b/src/NadekoBot/Services/ServiceProvider.cs index c3b46ba3..6efe0a2b 100644 --- a/src/NadekoBot/Services/ServiceProvider.cs +++ b/src/NadekoBot/Services/ServiceProvider.cs @@ -3,6 +3,14 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.Reflection; +using System.Linq; +using System.Diagnostics; +using NLog; +#if GLOBAL_NADEKO +using NadekoBot.Common; +#endif + namespace NadekoBot.Services { @@ -16,8 +24,14 @@ namespace NadekoBot.Services public class ServiceProviderBuilder { private ConcurrentDictionary _dict = new ConcurrentDictionary(); + private readonly Logger _log; - public ServiceProviderBuilder Add(T obj) + public ServiceProviderBuilder() + { + _log = LogManager.GetCurrentClassLogger(); + } + + public ServiceProviderBuilder AddManual(T obj) { _dict.TryAdd(typeof(T), obj); return this; @@ -27,6 +41,79 @@ namespace NadekoBot.Services { return new NServiceProvider(_dict); } + + public ServiceProviderBuilder LoadFrom(Assembly assembly) + { + var allTypes = assembly.GetTypes(); + var services = new Queue(allTypes + .Where(x => x.GetInterfaces().Contains(typeof(INService)) + && !x.GetTypeInfo().IsInterface && !x.GetTypeInfo().IsAbstract + +#if GLOBAL_NADEKO + && x.GetTypeInfo().GetCustomAttribute() == null +#endif + ) + .ToArray()); + + var interfaces = new HashSet(allTypes + .Where(x => x.GetInterfaces().Contains(typeof(INService)) + && x.GetTypeInfo().IsInterface)); + + var alreadyFailed = new Dictionary(); + + var sw = Stopwatch.StartNew(); + var swInstance = new Stopwatch(); + while (services.Count > 0) + { + var type = services.Dequeue(); //get a type i need to make an instance of + + if (_dict.TryGetValue(type, out _)) // if that type is already instantiated, skip + continue; + + var ctor = type.GetConstructors()[0]; + var argTypes = ctor + .GetParameters() + .Select(x => x.ParameterType) + .ToArray(); // get constructor argument types i need to pass in + + var args = new List(argTypes.Length); + foreach (var arg in argTypes) //get constructor arguments from the dictionary of already instantiated types + { + if (_dict.TryGetValue(arg, out var argObj)) //if i got current one, add it to the list of instances and move on + args.Add(argObj); + else //if i failed getting it, add it to the end, and break + { + services.Enqueue(type); + if (alreadyFailed.ContainsKey(type)) + { + alreadyFailed[type]++; + if (alreadyFailed[type] > 3) + _log.Warn(type.Name + " wasn't instantiated in the first 3 attempts. Missing " + arg.Name + " type"); + } + else + alreadyFailed.Add(type, 1); + break; + } + } + if (args.Count != argTypes.Length) + continue; + // _log.Info("Loading " + type.Name); + swInstance.Restart(); + var instance = ctor.Invoke(args.ToArray()); + swInstance.Stop(); + if (swInstance.Elapsed.TotalSeconds > 5) + _log.Info($"{type.Name} took {swInstance.Elapsed.TotalSeconds:F2}s to load."); + var interfaceType = interfaces.FirstOrDefault(x => instance.GetType().GetInterfaces().Contains(x)); + if (interfaceType != null) + _dict.TryAdd(interfaceType, instance); + + _dict.TryAdd(type, instance); + } + sw.Stop(); + _log.Info($"All services loaded in {sw.Elapsed.TotalSeconds:F2}s"); + + return this; + } } private readonly ImmutableDictionary _services; diff --git a/src/NadekoBot/Services/Utility/ConverterService.cs b/src/NadekoBot/Services/Utility/ConverterService.cs deleted file mode 100644 index 38fc7f9a..00000000 --- a/src/NadekoBot/Services/Utility/ConverterService.cs +++ /dev/null @@ -1,116 +0,0 @@ -using NadekoBot.Services.Database.Models; -using Newtonsoft.Json; -using NLog; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Services.Utility -{ - public class ConverterService - { - public List Units { get; set; } = new List(); - private readonly Logger _log; - private Timer _timer; - private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0); - private readonly DbService _db; - - public ConverterService(DbService db) - { - _log = LogManager.GetCurrentClassLogger(); - _db = db; - try - { - var data = JsonConvert.DeserializeObject>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit() - { - Modifier = u.Modifier, - UnitType = u.UnitType, - InternalTrigger = string.Join("|", u.Triggers) - }).ToArray(); - - using (var uow = _db.UnitOfWork) - { - if (uow.ConverterUnits.Empty()) - { - uow.ConverterUnits.AddRange(data); - uow.Complete(); - } - } - Units = data.ToList(); - } - catch (Exception ex) - { - _log.Warn("Could not load units: " + ex.Message); - } - - _timer = new Timer(async (obj) => await UpdateCurrency(), null, _updateInterval, _updateInterval); - } - - public static async Task UpdateCurrencyRates() - { - using (var http = new HttpClient()) - { - var res = await http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false); - return JsonConvert.DeserializeObject(res); - } - } - - public async Task UpdateCurrency() - { - try - { - var currencyRates = await UpdateCurrencyRates(); - var unitTypeString = "currency"; - var range = currencyRates.ConversionRates.Select(u => new ConvertUnit() - { - InternalTrigger = u.Key, - Modifier = u.Value, - UnitType = unitTypeString - }).ToArray(); - var baseType = new ConvertUnit() - { - Triggers = new[] { currencyRates.Base }, - Modifier = decimal.One, - UnitType = unitTypeString - }; - var toRemove = Units.Where(u => u.UnitType == unitTypeString); - - using (var uow = _db.UnitOfWork) - { - uow.ConverterUnits.RemoveRange(toRemove.ToArray()); - uow.ConverterUnits.Add(baseType); - uow.ConverterUnits.AddRange(range); - - await uow.CompleteAsync().ConfigureAwait(false); - } - Units.RemoveAll(u => u.UnitType == unitTypeString); - Units.Add(baseType); - Units.AddRange(range); - _log.Info("Updated Currency"); - } - catch - { - _log.Warn("Failed updating currency. Ignore this."); - } - } - } - - public class MeasurementUnit - { - public List Triggers { get; set; } - public string UnitType { get; set; } - public decimal Modifier { get; set; } - } - - public class Rates - { - public string Base { get; set; } - public DateTime Date { get; set; } - [JsonProperty("rates")] - public Dictionary ConversionRates { get; set; } - } -} diff --git a/src/NadekoBot/Services/Utility/MessageRepeaterService.cs b/src/NadekoBot/Services/Utility/MessageRepeaterService.cs deleted file mode 100644 index 57873ef1..00000000 --- a/src/NadekoBot/Services/Utility/MessageRepeaterService.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Discord.WebSocket; -using NadekoBot.Services.Database.Models; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Services.Utility -{ - //todo 50 rewrite - public class MessageRepeaterService - { - //messagerepeater - //guildid/RepeatRunners - public ConcurrentDictionary> Repeaters { get; set; } - public bool RepeaterReady { get; private set; } - - public MessageRepeaterService(NadekoBot bot, DiscordShardedClient client, IEnumerable gcs) - { - var _ = Task.Run(async () => - { - while (!bot.Ready) - await Task.Delay(1000); - - Repeaters = new ConcurrentDictionary>(gcs - .ToDictionary(gc => gc.GuildId, - gc => new ConcurrentQueue(gc.GuildRepeaters - .Select(gr => new RepeatRunner(client, gr)) - .Where(x => x.Guild != null)))); - RepeaterReady = true; - }); - } - } -} diff --git a/src/NadekoBot/Services/Utility/UtilityService.cs b/src/NadekoBot/Services/Utility/UtilityService.cs deleted file mode 100644 index 660984b2..00000000 --- a/src/NadekoBot/Services/Utility/UtilityService.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Services.Utility -{ - public class CrossServerTextService - { - public readonly ConcurrentDictionary> Subscribers = - new ConcurrentDictionary>(); - private DiscordShardedClient _client; - - public CrossServerTextService(IEnumerable guildConfigs, DiscordShardedClient client) - { - _client = client; - _client.MessageReceived += Client_MessageReceived; - } - - private Task Client_MessageReceived(SocketMessage imsg) - { - var _ = Task.Run(async () => { - try - { - if (imsg.Author.IsBot) - return; - var msg = imsg as IUserMessage; - if (msg == null) - return; - var channel = imsg.Channel as ITextChannel; - if (channel == null) - return; - if (msg.Author.Id == _client.CurrentUser.Id) return; - foreach (var subscriber in Subscribers) - { - var set = subscriber.Value; - if (!set.Contains(channel)) - continue; - foreach (var chan in set.Except(new[] { channel })) - { - try - { - await chan.SendMessageAsync(GetMessage(channel, (IGuildUser)msg.Author, - msg)).ConfigureAwait(false); - } - catch - { - // ignored - } - } - } - } - catch - { - // ignored - } - }); - - return Task.CompletedTask; - } - - private string GetMessage(ITextChannel channel, IGuildUser user, IUserMessage message) => - $"**{channel.Guild.Name} | {channel.Name}** `{user.Username}`: " + message.Content.SanitizeMentions(); - } -} diff --git a/src/NadekoBot/ShardsCoordinator.cs b/src/NadekoBot/ShardsCoordinator.cs new file mode 100644 index 00000000..72ed349c --- /dev/null +++ b/src/NadekoBot/ShardsCoordinator.cs @@ -0,0 +1,84 @@ +using NadekoBot.Services; +using NadekoBot.Services.Impl; +using NLog; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using NadekoBot.Common.ShardCom; + +namespace NadekoBot +{ + public class ShardsCoordinator + { + private readonly BotCredentials _creds; + private readonly Process[] _shardProcesses; + public ShardComMessage[] Statuses { get; } + public int GuildCount => Statuses.ToArray() + .Where(x => x != null) + .Sum(x => x.Guilds); + + private readonly Logger _log; + private readonly ShardComServer _comServer; + private readonly int _port; + private readonly int _curProcessId; + + public ShardsCoordinator(int port) + { + LogSetup.SetupLogger(); + _creds = new BotCredentials(); + _shardProcesses = new Process[_creds.TotalShards]; + Statuses = new ShardComMessage[_creds.TotalShards]; + _log = LogManager.GetCurrentClassLogger(); + _port = port; + + _comServer = new ShardComServer(port); + _comServer.Start(); + + _comServer.OnDataReceived += _comServer_OnDataReceived; + + _curProcessId = Process.GetCurrentProcess().Id; + } + + private Task _comServer_OnDataReceived(ShardComMessage msg) + { + Statuses[msg.ShardId] = msg; + + if (msg.ConnectionState == Discord.ConnectionState.Disconnected || msg.ConnectionState == Discord.ConnectionState.Disconnecting) + _log.Error("!!! SHARD {0} IS IN {1} STATE", msg.ShardId, msg.ConnectionState.ToString()); + return Task.CompletedTask; + } + + public async Task RunAsync() + { + for (int i = 1; i < _creds.TotalShards; i++) + { + var p = Process.Start(new ProcessStartInfo() + { + FileName = _creds.ShardRunCommand, + Arguments = string.Format(_creds.ShardRunArguments, i, _curProcessId, _port) + }); + await Task.Delay(5000); + } + } + + public async Task RunAndBlockAsync() + { + try + { + await RunAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Error(ex); + } + + await Task.Delay(-1); + foreach (var p in _shardProcesses) + { + try { p.Kill(); } catch { } + try { p.Dispose(); } catch { } + } + } + } +} diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index cafb5d32..d1bd796b 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -14,6 +14,8 @@ using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using NadekoBot.Common.Collections; +using SixLabors.Primitives; namespace NadekoBot.Extensions { @@ -48,85 +50,10 @@ namespace NadekoBot.Extensions public static bool IsAuthor(this IMessage msg, IDiscordClient client) => msg.Author?.Id == client.CurrentUser.Id; - private static readonly IEmote arrow_left = new Emoji("⬅"); - private static readonly IEmote arrow_right = new Emoji("➡"); - - public static string ToBase64(this string plainText) - { - var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); - return Convert.ToBase64String(plainTextBytes); - } - public static string RealSummary(this CommandInfo cmd, string prefix) => string.Format(cmd.Summary, prefix); public static string RealRemarks(this CommandInfo cmd, string prefix) => string.Format(cmd.Remarks, prefix); - public static Stream ToStream(this IEnumerable bytes, bool canWrite = false) - { - var ms = new MemoryStream(bytes as byte[] ?? bytes.ToArray(), canWrite); - ms.Seek(0, SeekOrigin.Begin); - return ms; - } - - /// - /// danny kamisama - /// - public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, DiscordShardedClient client, int currentPage, Func pageFunc, int? lastPage = null, bool addPaginatedFooter = true) - { - var embed = pageFunc(currentPage); - - if(addPaginatedFooter) - embed.AddPaginatedFooter(currentPage, lastPage); - - var msg = await channel.EmbedAsync(embed) as IUserMessage; - - if (lastPage == 0) - return; - - - await msg.AddReactionAsync( arrow_left).ConfigureAwait(false); - await msg.AddReactionAsync(arrow_right).ConfigureAwait(false); - - await Task.Delay(2000).ConfigureAwait(false); - - Action changePage = async r => - { - try - { - if (r.Emote.Name == arrow_left.Name) - { - if (currentPage == 0) - return; - var toSend = pageFunc(--currentPage); - if (addPaginatedFooter) - toSend.AddPaginatedFooter(currentPage, lastPage); - await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false); - } - else if (r.Emote.Name == arrow_right.Name) - { - if (lastPage == null || lastPage > currentPage) - { - var toSend = pageFunc(++currentPage); - if (addPaginatedFooter) - toSend.AddPaginatedFooter(currentPage, lastPage); - await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false); - } - } - } - catch (Exception) - { - //ignored - } - }; - - using (msg.OnReaction(client, changePage, changePage)) - { - await Task.Delay(30000).ConfigureAwait(false); - } - - await msg.RemoveAllReactionsAsync().ConfigureAwait(false); - } - - private static EmbedBuilder AddPaginatedFooter(this EmbedBuilder embed, int curPage, int? lastPage) + public static EmbedBuilder AddPaginatedFooter(this EmbedBuilder embed, int curPage, int? lastPage) { if (lastPage != null) return embed.WithFooter(efb => efb.WithText($"{curPage + 1} / {lastPage + 1}")); @@ -134,7 +61,13 @@ namespace NadekoBot.Extensions return embed.WithFooter(efb => efb.WithText(curPage.ToString())); } - public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordShardedClient client, Action reactionAdded, Action reactionRemoved = null) + public static EmbedBuilder WithOkColor(this EmbedBuilder eb) => + eb.WithColor(NadekoBot.OkColor); + + public static EmbedBuilder WithErrorColor(this EmbedBuilder eb) => + eb.WithColor(NadekoBot.ErrorColor); + + public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordSocketClient client, Action reactionAdded, Action reactionRemoved = null) { if (reactionRemoved == null) reactionRemoved = delegate { }; @@ -152,17 +85,6 @@ namespace NadekoBot.Extensions http.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); } - public static string GetInitials(this string txt, string glue = "") => - string.Join(glue, txt.Split(' ').Select(x => x.FirstOrDefault())); - - public static DateTime ToUnixTimestamp(this double number) => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(number); - - public static EmbedBuilder WithOkColor(this EmbedBuilder eb) => - eb.WithColor(NadekoBot.OkColor); - - public static EmbedBuilder WithErrorColor(this EmbedBuilder eb) => - eb.WithColor(NadekoBot.ErrorColor); - public static IMessage DeleteAfter(this IUserMessage msg, int seconds) { Task.Run(async () => @@ -183,29 +105,9 @@ namespace NadekoBot.Extensions return module; } - public static async Task SendMessageToOwnerAsync(this IGuild guild, string message) - { - var ownerPrivate = await (await guild.GetOwnerAsync().ConfigureAwait(false)).CreateDMChannelAsync() - .ConfigureAwait(false); - - return await ownerPrivate.SendMessageAsync(message).ConfigureAwait(false); - } - //public static async Task> MentionedUsers(this IUserMessage msg) => - public static IEnumerable GetRoles(this IGuildUser user) => - user.RoleIds.Select(r => user.Guild.GetRole(r)).Where(r => r != null); - - public static IEnumerable ForEach(this IEnumerable elems, Action exec) - { - foreach (var elem in elems) - { - exec(elem); - } - return elems; - } - public static void AddRange(this HashSet target, IEnumerable elements) where T : class { foreach (var item in elements) @@ -222,68 +124,22 @@ namespace NadekoBot.Extensions } } - public static bool IsInteger(this decimal number) => number == Math.Truncate(number); - - public static string SanitizeMentions(this string str) => - str.Replace("@everyone", "@everyοne").Replace("@here", "@һere"); - public static double UnixTimestamp(this DateTime dt) => dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds; - public static async Task SendMessageAsync(this IUser user, string message, bool isTTS = false) => - await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(message, isTTS).ConfigureAwait(false); + public static async Task> GetMembersAsync(this IRole role) => + (await role.Guild.GetUsersAsync(CacheMode.CacheOnly)).Where(u => u.RoleIds.Contains(role.Id)) ?? Enumerable.Empty(); + + public static string ToJson(this T any, Formatting formatting = Formatting.Indented) => + JsonConvert.SerializeObject(any, formatting); - public static async Task SendConfirmAsync(this IUser user, string text) - => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text)); - - public static async Task SendConfirmAsync(this IUser user, string title, string text, string url = null) - => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text) - .WithTitle(title).WithUrl(url)); - - public static async Task SendErrorAsync(this IUser user, string title, string error, string url = null) - => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error) - .WithTitle(title).WithUrl(url)); - - public static async Task SendErrorAsync(this IUser user, string error) - => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error)); - - public static async Task SendFileAsync(this IUser user, string filePath, string caption = null, string text = null, bool isTTS = false) => - await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(File.Open(filePath, FileMode.Open), caption ?? "x", text, isTTS).ConfigureAwait(false); - - public static async Task SendFileAsync(this IUser user, Stream fileStream, string fileName, string caption = null, bool isTTS = false) => - await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(fileStream, fileName, caption, isTTS).ConfigureAwait(false); - - public static IEnumerable Members(this IRole role) => - role.Guild.GetUsersAsync().GetAwaiter().GetResult().Where(u => u.RoleIds.Contains(role.Id)) ?? Enumerable.Empty(); - - public static Task EmbedAsync(this IMessageChannel ch, EmbedBuilder embed, string msg = "") - => ch.SendMessageAsync(msg, embed: embed); - - public static Task SendErrorAsync(this IMessageChannel ch, string title, string error, string url = null, string footer = null) - => ch.SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error) - .WithTitle(title).WithUrl(url).WithFooter(efb => efb.WithText(footer))); - - public static Task SendErrorAsync(this IMessageChannel ch, string error) - => ch.SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error)); - - public static Task SendConfirmAsync(this IMessageChannel ch, string title, string text, string url = null, string footer = null) - => ch.SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text) - .WithTitle(title).WithUrl(url).WithFooter(efb => efb.WithText(footer))); - - public static Task SendConfirmAsync(this IMessageChannel ch, string text) - => ch.SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text)); - - public static Task SendTableAsync(this IMessageChannel ch, string seed, IEnumerable items, Func howToPrint, int columns = 3) + public static MemoryStream ToStream(this ImageSharp.Image img) { - var i = 0; - return ch.SendMessageAsync($@"{seed}```css -{string.Join("\n", items.GroupBy(item => (i++) / columns) - .Select(ig => string.Concat(ig.Select(el => howToPrint(el)))))} -```"); + var imageStream = new MemoryStream(); + img.SaveAsPng(imageStream, new ImageSharp.Formats.PngEncoder() { CompressionLevel = 9}); + imageStream.Position = 0; + return imageStream; } - public static Task SendTableAsync(this IMessageChannel ch, IEnumerable items, Func howToPrint, int columns = 3) => - ch.SendTableAsync("", items, howToPrint, columns); - /// /// returns an IEnumerable with randomized element order /// @@ -315,142 +171,39 @@ namespace NadekoBot.Extensions return list; } } - - /// - /// Easy use of fast, efficient case-insensitive Contains check with StringComparison Member Types - /// CurrentCulture, CurrentCultureIgnoreCase, InvariantCulture, InvariantCultureIgnoreCase, Ordinal, OrdinalIgnoreCase - /// - public static bool ContainsNoCase(this string str, string contains, StringComparison compare) + + public static IEnumerable ForEach(this IEnumerable elems, Action exec) { - return str.IndexOf(contains, compare) >= 0; + foreach (var elem in elems) + { + exec(elem); + } + return elems; } - public static string TrimTo(this string str, int maxLength, bool hideDots = false) + public static Stream ToStream(this IEnumerable bytes, bool canWrite = false) { - if (maxLength < 0) - throw new ArgumentOutOfRangeException(nameof(maxLength), $"Argument {nameof(maxLength)} can't be negative."); - if (maxLength == 0) - return string.Empty; - if (maxLength <= 3) - return string.Concat(str.Select(c => '.')); - if (str.Length < maxLength) - return str; - return string.Concat(str.Take(maxLength - 3)) + (hideDots ? "" : "..."); - } - - public static string ToTitleCase(this string str) - { - var tokens = str.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries); - for (var i = 0; i < tokens.Length; i++) - { - var token = tokens[i]; - tokens[i] = token.Substring(0, 1).ToUpper() + token.Substring(1); - } - - return string.Join(" ", tokens); - } - - /// - /// Removes trailing S or ES (if specified) on the given string if the num is 1 - /// - /// - /// - /// - /// String with the correct singular/plural form - public static string SnPl(this string str, int? num, bool es = false) - { - if (str == null) - throw new ArgumentNullException(nameof(str)); - if (num == null) - throw new ArgumentNullException(nameof(num)); - return num == 1 ? str.Remove(str.Length - 1, es ? 2 : 1) : str; - } - - //http://www.dotnetperls.com/levenshtein - public static int LevenshteinDistance(this string s, string t) - { - var n = s.Length; - var m = t.Length; - var d = new int[n + 1, m + 1]; - - // Step 1 - if (n == 0) - { - return m; - } - - if (m == 0) - { - return n; - } - - // Step 2 - for (var i = 0; i <= n; d[i, 0] = i++) - { - } - - for (var j = 0; j <= m; d[0, j] = j++) - { - } - - // Step 3 - for (var i = 1; i <= n; i++) - { - //Step 4 - for (var j = 1; j <= m; j++) - { - // Step 5 - var cost = (t[j - 1] == s[i - 1]) ? 0 : 1; - - // Step 6 - d[i, j] = Math.Min( - Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), - d[i - 1, j - 1] + cost); - } - } - // Step 7 - return d[n, m]; - } - - public static async Task ToStream(this string str) - { - var ms = new MemoryStream(); - var sw = new StreamWriter(ms); - await sw.WriteAsync(str); - await sw.FlushAsync(); - ms.Position = 0; + var ms = new MemoryStream(bytes as byte[] ?? bytes.ToArray(), canWrite); + ms.Seek(0, SeekOrigin.Begin); return ms; - } - public static string ToJson(this T any, Formatting formatting = Formatting.Indented) => - JsonConvert.SerializeObject(any, formatting); + public static IEnumerable GetRoles(this IGuildUser user) => + user.RoleIds.Select(r => user.Guild.GetRole(r)).Where(r => r != null); - public static int KiB(this int value) => value * 1024; - public static int KB(this int value) => value * 1000; + public static async Task SendMessageToOwnerAsync(this IGuild guild, string message) + { + var ownerPrivate = await (await guild.GetOwnerAsync().ConfigureAwait(false)).GetOrCreateDMChannelAsync() + .ConfigureAwait(false); - public static int MiB(this int value) => value.KiB() * 1024; - public static int MB(this int value) => value.KB() * 1000; + return await ownerPrivate.SendMessageAsync(message).ConfigureAwait(false); + } - public static int GiB(this int value) => value.MiB() * 1024; - public static int GB(this int value) => value.MB() * 1000; - - public static ulong KiB(this ulong value) => value * 1024; - public static ulong KB(this ulong value) => value * 1000; - - public static ulong MiB(this ulong value) => value.KiB() * 1024; - public static ulong MB(this ulong value) => value.KB() * 1000; - - public static ulong GiB(this ulong value) => value.MiB() * 1024; - public static ulong GB(this ulong value) => value.MB() * 1000; - - public static string Unmention(this string str) => str.Replace("@", "ම"); - - public static ImageSharp.Image Merge(this IEnumerable images) + public static Image Merge(this IEnumerable> images) { var imgs = images.ToArray(); - var canvas = new ImageSharp.Image(imgs.Sum(img => img.Width), imgs.Max(img => img.Height)); + var canvas = new Image(imgs.Sum(img => img.Width), imgs.Max(img => img.Height)); var xOffset = 0; for (int i = 0; i < imgs.Length; i++) @@ -461,25 +214,5 @@ namespace NadekoBot.Extensions return canvas; } - - public static Stream ToStream(this ImageSharp.Image img) - { - var imageStream = new MemoryStream(); - img.Save(imageStream); - imageStream.Position = 0; - return imageStream; - } - - private static readonly Regex filterRegex = new Regex(@"(?:discord(?:\.gg|.me|app\.com\/invite)\/(?([\w]{16}|(?:[\w]+-?){3})))", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static bool IsDiscordInvite(this string str) - => filterRegex.IsMatch(str); - - public static string RealAvatarUrl(this IUser usr) - { - return usr.AvatarId.StartsWith("a_") - ? $"{DiscordConfig.CDNUrl}avatars/{usr.Id}/{usr.AvatarId}.gif" - : usr.GetAvatarUrl(ImageFormat.Auto); - } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/IMessageChannelExtensions.cs b/src/NadekoBot/_Extensions/IMessageChannelExtensions.cs new file mode 100644 index 00000000..992d7453 --- /dev/null +++ b/src/NadekoBot/_Extensions/IMessageChannelExtensions.cs @@ -0,0 +1,119 @@ +using Discord; +using Discord.WebSocket; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Extensions +{ + public static class IMessageChannelExtensions + { + public static Task EmbedAsync(this IMessageChannel ch, EmbedBuilder embed, string msg = "") + => ch.SendMessageAsync(msg, embed: embed.Build()); + + public static Task SendErrorAsync(this IMessageChannel ch, string title, string error, string url = null, string footer = null) + { + var eb = new EmbedBuilder().WithErrorColor().WithDescription(error) + .WithTitle(title); + if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute)) + eb.WithUrl(url); + if (!string.IsNullOrWhiteSpace(footer)) + eb.WithFooter(efb => efb.WithText(footer)); + return ch.SendMessageAsync("", embed: eb.Build()); + } + + public static Task SendErrorAsync(this IMessageChannel ch, string error) + => ch.SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error).Build()); + + public static Task SendConfirmAsync(this IMessageChannel ch, string title, string text, string url = null, string footer = null) + { + var eb = new EmbedBuilder().WithOkColor().WithDescription(text) + .WithTitle(title); + if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute)) + eb.WithUrl(url); + if (!string.IsNullOrWhiteSpace(footer)) + eb.WithFooter(efb => efb.WithText(footer)); + return ch.SendMessageAsync("", embed: eb.Build()); + } + + public static Task SendConfirmAsync(this IMessageChannel ch, string text) + => ch.SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text).Build()); + + public static Task SendTableAsync(this IMessageChannel ch, string seed, IEnumerable items, Func howToPrint, int columns = 3) + { + var i = 0; + return ch.SendMessageAsync($@"{seed}```css +{string.Join("\n", items.GroupBy(item => (i++) / columns) + .Select(ig => string.Concat(ig.Select(el => howToPrint(el)))))} +```"); + } + + public static Task SendTableAsync(this IMessageChannel ch, IEnumerable items, Func howToPrint, int columns = 3) => + ch.SendTableAsync("", items, howToPrint, columns); + + private static readonly IEmote arrow_left = new Emoji("⬅"); + private static readonly IEmote arrow_right = new Emoji("➡"); + + public static Task SendPaginatedConfirmAsync(this IMessageChannel channel, DiscordSocketClient client, int currentPage, Func pageFunc, int? lastPage = null, bool addPaginatedFooter = true) => + channel.SendPaginatedConfirmAsync(client, currentPage, (x) => Task.FromResult(pageFunc(x)), lastPage, addPaginatedFooter); + /// + /// danny kamisama + /// + public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, DiscordSocketClient client, int currentPage, Func> pageFunc, int? lastPage = null, bool addPaginatedFooter = true) + { + var embed = await pageFunc(currentPage).ConfigureAwait(false); + + if (addPaginatedFooter) + embed.AddPaginatedFooter(currentPage, lastPage); + + var msg = await channel.EmbedAsync(embed) as IUserMessage; + + if (lastPage == 0) + return; + + + await msg.AddReactionAsync(arrow_left).ConfigureAwait(false); + await msg.AddReactionAsync(arrow_right).ConfigureAwait(false); + + await Task.Delay(2000).ConfigureAwait(false); + + Action changePage = async r => + { + try + { + if (r.Emote.Name == arrow_left.Name) + { + if (currentPage == 0) + return; + var toSend = await pageFunc(--currentPage).ConfigureAwait(false); + if (addPaginatedFooter) + toSend.AddPaginatedFooter(currentPage, lastPage); + await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false); + } + else if (r.Emote.Name == arrow_right.Name) + { + if (lastPage == null || lastPage > currentPage) + { + var toSend = await pageFunc(++currentPage).ConfigureAwait(false); + if (addPaginatedFooter) + toSend.AddPaginatedFooter(currentPage, lastPage); + await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false); + } + } + } + catch (Exception) + { + //ignored + } + }; + + using (msg.OnReaction(client, changePage, changePage)) + { + await Task.Delay(30000).ConfigureAwait(false); + } + + await msg.RemoveAllReactionsAsync().ConfigureAwait(false); + } + } +} diff --git a/src/NadekoBot/_Extensions/IUserExtensions.cs b/src/NadekoBot/_Extensions/IUserExtensions.cs new file mode 100644 index 00000000..89650f21 --- /dev/null +++ b/src/NadekoBot/_Extensions/IUserExtensions.cs @@ -0,0 +1,49 @@ +using Discord; +using NadekoBot.Services.Database.Models; +using System; +using System.IO; +using System.Threading.Tasks; + +namespace NadekoBot.Extensions +{ + public static class IUserExtensions + { + public static async Task SendConfirmAsync(this IUser user, string text) + => await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text).Build()); + + public static async Task SendConfirmAsync(this IUser user, string title, string text, string url = null) + { + var eb = new EmbedBuilder().WithOkColor().WithDescription(text); + if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute)) + eb.WithUrl(url); + return await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: eb.Build()); + } + + public static async Task SendErrorAsync(this IUser user, string title, string error, string url = null) + { + var eb = new EmbedBuilder().WithErrorColor().WithDescription(error); + if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute)) + eb.WithUrl(url); + return await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: eb.Build()); + } + + public static async Task SendErrorAsync(this IUser user, string error) + => await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error).Build()); + + public static async Task SendFileAsync(this IUser user, string filePath, string caption = null, string text = null, bool isTTS = false) => + await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(File.Open(filePath, FileMode.Open), caption ?? "x", text, isTTS).ConfigureAwait(false); + + public static async Task SendFileAsync(this IUser user, Stream fileStream, string fileName, string caption = null, bool isTTS = false) => + await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(fileStream, fileName, caption, isTTS).ConfigureAwait(false); + + public static string RealAvatarUrl(this IUser usr) => + usr.AvatarId.StartsWith("a_") + ? $"{DiscordConfig.CDNUrl}avatars/{usr.Id}/{usr.AvatarId}.gif" + : usr.GetAvatarUrl(ImageFormat.Auto); + + public static string RealAvatarUrl(this DiscordUser usr) => + usr.AvatarId.StartsWith("a_") + ? $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.gif" + : $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.png"; + } +} diff --git a/src/NadekoBot/_Extensions/NumberExtensions.cs b/src/NadekoBot/_Extensions/NumberExtensions.cs new file mode 100644 index 00000000..b708841f --- /dev/null +++ b/src/NadekoBot/_Extensions/NumberExtensions.cs @@ -0,0 +1,29 @@ +using System; + +namespace NadekoBot.Extensions +{ + public static class NumberExtensions + { + public static int KiB(this int value) => value * 1024; + public static int KB(this int value) => value * 1000; + + public static int MiB(this int value) => value.KiB() * 1024; + public static int MB(this int value) => value.KB() * 1000; + + public static int GiB(this int value) => value.MiB() * 1024; + public static int GB(this int value) => value.MB() * 1000; + + public static ulong KiB(this ulong value) => value * 1024; + public static ulong KB(this ulong value) => value * 1000; + + public static ulong MiB(this ulong value) => value.KiB() * 1024; + public static ulong MB(this ulong value) => value.KB() * 1000; + + public static ulong GiB(this ulong value) => value.MiB() * 1024; + public static ulong GB(this ulong value) => value.MB() * 1000; + + public static bool IsInteger(this decimal number) => number == Math.Truncate(number); + + public static DateTime ToUnixTimestamp(this double number) => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(number); + } +} diff --git a/src/NadekoBot/_Extensions/StringExtensions.cs b/src/NadekoBot/_Extensions/StringExtensions.cs new file mode 100644 index 00000000..0b7adf65 --- /dev/null +++ b/src/NadekoBot/_Extensions/StringExtensions.cs @@ -0,0 +1,141 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NadekoBot.Extensions +{ + public static class StringExtensions + { + public static string StripHTML(this string input) + { + return Regex.Replace(input, "<.*?>", String.Empty); + } + + /// + /// Easy use of fast, efficient case-insensitive Contains check with StringComparison Member Types + /// CurrentCulture, CurrentCultureIgnoreCase, InvariantCulture, InvariantCultureIgnoreCase, Ordinal, OrdinalIgnoreCase + /// + public static bool ContainsNoCase(this string str, string contains, StringComparison compare) + { + return str.IndexOf(contains, compare) >= 0; + } + + public static string TrimTo(this string str, int maxLength, bool hideDots = false) + { + if (maxLength < 0) + throw new ArgumentOutOfRangeException(nameof(maxLength), $"Argument {nameof(maxLength)} can't be negative."); + if (maxLength == 0) + return string.Empty; + if (maxLength <= 3) + return string.Concat(str.Select(c => '.')); + if (str.Length < maxLength) + return str; + return string.Concat(str.Take(maxLength - 3)) + (hideDots ? "" : "..."); + } + + public static string ToTitleCase(this string str) + { + var tokens = str.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < tokens.Length; i++) + { + var token = tokens[i]; + tokens[i] = token.Substring(0, 1).ToUpper() + token.Substring(1); + } + + return string.Join(" ", tokens); + } + + /// + /// Removes trailing S or ES (if specified) on the given string if the num is 1 + /// + /// + /// + /// + /// String with the correct singular/plural form + public static string SnPl(this string str, int? num, bool es = false) + { + if (str == null) + throw new ArgumentNullException(nameof(str)); + if (num == null) + throw new ArgumentNullException(nameof(num)); + return num == 1 ? str.Remove(str.Length - 1, es ? 2 : 1) : str; + } + + //http://www.dotnetperls.com/levenshtein + public static int LevenshteinDistance(this string s, string t) + { + var n = s.Length; + var m = t.Length; + var d = new int[n + 1, m + 1]; + + // Step 1 + if (n == 0) + { + return m; + } + + if (m == 0) + { + return n; + } + + // Step 2 + for (var i = 0; i <= n; d[i, 0] = i++) + { + } + + for (var j = 0; j <= m; d[0, j] = j++) + { + } + + // Step 3 + for (var i = 1; i <= n; i++) + { + //Step 4 + for (var j = 1; j <= m; j++) + { + // Step 5 + var cost = (t[j - 1] == s[i - 1]) ? 0 : 1; + + // Step 6 + d[i, j] = Math.Min( + Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), + d[i - 1, j - 1] + cost); + } + } + // Step 7 + return d[n, m]; + } + + public static async Task ToStream(this string str) + { + var ms = new MemoryStream(); + var sw = new StreamWriter(ms); + await sw.WriteAsync(str); + await sw.FlushAsync(); + ms.Position = 0; + return ms; + } + + private static readonly Regex filterRegex = new Regex(@"(?:discord(?:\.gg|.me|app\.com\/invite)\/(?([\w]{16}|(?:[\w]+-?){3})))", RegexOptions.Compiled | RegexOptions.IgnoreCase); + public static bool IsDiscordInvite(this string str) + => filterRegex.IsMatch(str); + + public static string Unmention(this string str) => str.Replace("@", "ම"); + + public static string SanitizeMentions(this string str) => + str.Replace("@everyone", "@everyοne").Replace("@here", "@һere"); + + public static string ToBase64(this string plainText) + { + var plainTextBytes = Encoding.UTF8.GetBytes(plainText); + return Convert.ToBase64String(plainTextBytes); + } + + public static string GetInitials(this string txt, string glue = "") => + string.Join(glue, txt.Split(' ').Select(x => x.FirstOrDefault())); + } +} diff --git a/src/NadekoBot/_strings/ResponseStrings.ar.json b/src/NadekoBot/_strings/ResponseStrings.ar.json index d214ef53..9e316646 100644 --- a/src/NadekoBot/_strings/ResponseStrings.ar.json +++ b/src/NadekoBot/_strings/ResponseStrings.ar.json @@ -151,7 +151,6 @@ "administration_old_nick": "لقب قديم .", "administration_old_topic": "موضوع قديم .", "administration_perms": "خطأ . على الاغلب لا امتلك الصلاحيات الكافية .", - "administration_perms_reset": "جميع الأذون في السيرفر أعاد تعيينهم.", "administration_prot_active": "الحماية فعالة .", "administration_prot_disable": "تم تعطيل {0} على هذا السيرفر.", "administration_prot_enable": "{0} مفعل", @@ -243,7 +242,6 @@ "administration_sbdm": "لقد تم حظرك من {0} مزود.\nالسبب: {1}", "administration_user_unbanned": "ازالة الطرد عن المستخدم", "administration_migration_done": "تمت الهجرة !", - "adminsitration_migration_error": "حدث خطأ أثناء الترحيل، راجع وحدة تحكم بوت للحصول على مزيد من المعلومات.", "administration_presence_updates": "التحديث الحالي", "administration_sb_user": "المستخدم طرد-مخفض", "gambling_awarded": "تم منح {0} الى {1}", @@ -439,7 +437,6 @@ "music_rpl_enabled": "تم تمكين قائمة التشغيل المتكرر.", "music_set_music_channel": "وسوف أخرج الآن اللعب، والانتهاء، وأوقفت مؤقتا وإزالة الأغاني في هذه القناة.", "music_skipped_to": "تخطي الى `{0}:{1}`", - "music_songs_shuffled": "خلط الاغاني", "music_song_moved": "تحريك الاغنية", "music_time_format": "{0}س {1}د {2}ث", "music_to_position": "الى مكان", @@ -605,9 +602,6 @@ "utility_convert_not_found": "لا يمكنني تحويل {0} الى {1}: الوحة غير موجودة", "utility_convert_type_error": "لا يمكن تحويل {0} إلى {1}: أنواع الوحدات غير متساوية", "utility_created_at": "أنشئت في", - "utility_csc_join": "انضممت الى قناة تقاطع السيرفارات", - "utility_csc_leave": "مغادرة قناة تقاطع السيرفارات ", - "utility_csc_token": "هذا csc قطعة", "utility_custom_emojis": "الرمز التعبيري المخصص", "utility_error": "خطأ", "utility_features": "الميزات\n", @@ -754,7 +748,6 @@ "gambling_shop_role": "ستحصل على دور {0}.", "gambling_type": "نوع", "utility_clpa_next_update": "التحديث التالي في {0}", - "administration_global_perms_reset": "تمت إعادة تعيين الأذونات العامة.", "administration_gvc_disabled": "تم تعطيل ميزة قناة صوت اللعبة على هذا السيرفر.", "administration_gvc_enabled": "{0} هي قناة صوت اللعبة الآن.", "administration_not_in_voice": "أنت لست في قناة صوتية على هذا السيرفر.", @@ -786,5 +779,30 @@ "administration_prefix_current": "اعدادات التحديد على هذا السيرفر {0}", "administration_prefix_new": "تم تغيير اعدادات التحديد على هذا السيرفر من {0} إلى {1}", "administration_defprefix_current": "اعدادات التحديد الافتراضي للبوت هو {0}", - "administration_defprefix_new": "تم تغيير اعدادات التحديد الافتراضية للبوت من {0} إلى {1}" + "administration_defprefix_new": "تم تغيير اعدادات التحديد الافتراضية للبوت من {0} إلى {1}", + "administration_bot_nick": "اسم البوت المستعار تغير الى {0}", + "administration_user_nick": "الاسم المستعار للمستخدم {0} تغير الى {1}", + "administration_timezone_guild": "المنطقة الزمنية لهذا التحالف هي `{0}`", + "administration_timezone_not_found": "لم يتم العثور على المنطقة الزمنية. استخدام امر \"timezones\" لرؤية قائمة من المناطق الزمنية المتاحة.", + "administration_timezones_available": "المناطق الزمنية المتاحة", + "music_song_not_found": "لم يتم العثور على أي أغنية.", + "searches_define_unknown": "لا يمكن العثور على تعريف لهذا المصطلح.", + "utility_repeater_initial": "سيتم إرسال الرسالة المكررة الأولية في {0} h و {1} دقيقة.", + "utility_verbose_errors_enabled": "ستعرض الأوامر المستخدمة بشكل غير صحيح الآن أخطاء.", + "utility_verbose_errors_disabled": "لن تظهر الأوامر المستخدمة بشكل غير صحيح أية أخطاء.", + "permissions_perms_reset": " إعادة تعيين أذونات هذا السيرفر.", + "permissions_trigger": "رقم الإذن # {0} {1} يمنع هذا الإجراء.", + "administration_migration_error": "حدث خطأ أثناء الترحيل، راجع وحدة تحكم بوت للحصول على مزيد من المعلومات.", + "searches_hex_invalid": "اللون المحدد غير صالح.", + "permissions_global_perms_reset": "تمت إعادة تعيين الأذونات كاملة.", + "help_module": "", + "games_hangman_stopped": "", + "music_autoplaying": "", + "music_queue_stopped": "", + "music_removed_song_error": "", + "music_shuffling_playlist": "", + "music_songs_shuffle_enable": "", + "music_songs_shuffle_disable": "", + "music_song_skips_after": "", + "administration_warnings_list": "" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.cs-CZ.json b/src/NadekoBot/_strings/ResponseStrings.cs-CZ.json index 594c4728..ea900b0f 100644 --- a/src/NadekoBot/_strings/ResponseStrings.cs-CZ.json +++ b/src/NadekoBot/_strings/ResponseStrings.cs-CZ.json @@ -1,27 +1,4 @@ { - "clashofclans_base_already_claimed": "Tato základna je již obsazena nebo zničena.", - "clashofclans_base_already_destroyed": "Tato základna je již zničena.", - "clashofclans_base_already_unclaimed": "Tato základna není obsazena.", - "clashofclans_base_destroyed": "**ZNIČENA** základna #{0} ve válce proti {1}", - "clashofclans_base_unclaimed": "{0} má **NEOBSAZENOU** základnu #{1} ve válce proti {2}", - "clashofclans_claimed_base": "{0} obsadil základnu #{1} ve válce proti {2}", - "clashofclans_claimed_other": "@{0} Už jsi obsadil základnu #{1}. Nemůžeš obsadit další.", - "clashofclans_claim_expired": "Nárok na @{0} ve válce proti {1} vypršel.", - "clashofclans_enemy": "Nepřítel", - "clashofclans_info_about_war": "Info o válce proti {0}", - "clashofclans_invalid_base_number": "Neplatné číslo základny.", - "clashofclans_invalid_size": "Neplatná velikost války.", - "clashofclans_list_active_wars": "Seznam aktivních válek.", - "clashofclans_not_claimed": "neobsazeno", - "clashofclans_not_partic": "Této války se neúčastníš.", - "clashofclans_not_partic_or_destroyed": "@{0} Buď se této války neúčastníš, nebo je základna již zničena.", - "clashofclans_no_active_wars": "Žádná aktivní válka.", - "clashofclans_size": "Velikost", - "clashofclans_war_already_started": "Válka proti {0} už začala.", - "clashofclans_war_created": "Válka proti {0} vytvořena.", - "clashofclans_war_ended": "Válka proti {0} skončila.", - "clashofclans_war_not_exist": "Tato válka neexistuje.", - "clashofclans_war_started": "Válka proti {0} začala!", "customreactions_all_stats_cleared": "Statistika všech vlastních reakcí byla vyčištěna.", "customreactions_deleted": "Vlastní reakce smazána", "customreactions_insuff_perms": "Nedostatečná oprávnění. Potřebuješ vlastnictví bota pro globální vlastní reakce a oprávnění Administrátor pro vlastní reakce na svém serveru.", @@ -151,7 +128,6 @@ "administration_old_nick": "Stará přezdívka", "administration_old_topic": "Staré téma", "administration_perms": "Chyba. Pravděpodobně nemám potřebná oprávnění.", - "administration_perms_reset": "Opatření jsou pro tento server resetována.", "administration_prot_active": "Aktivní ochrany", "administration_prot_disable": "{0} byl **vypnut** na tomto serveru.", "administration_prot_enable": "{0} Zapnut", @@ -243,7 +219,6 @@ "administration_sbdm": "Byl jsi omezen na serveru {0}.\nDůvod: {1}", "administration_user_unbanned": "Uživatel odblokován", "administration_migration_done": "Migrace hotová!", - "adminsitration_migration_error": "Chyba při migrování, zkontroluj konzoli bota pro více informací.", "administration_presence_updates": "Současné aktualizace", "administration_sb_user": "Uživatel omezen", "gambling_awarded": "odměnil uživatele {0} {1}", @@ -439,7 +414,6 @@ "music_rpl_enabled": "Opakování seznamu písní bylo zapnuto.", "music_set_music_channel": "Skončím nyní s přehráváním, dokončeno, pozastaveno a písně odstraněny z tohoto kanálu.", "music_skipped_to": "Přeskočeno na `{0}:{1}`", - "music_songs_shuffled": "Písně náhodně zamíchány", "music_song_moved": "Píseň přesunuta", "music_time_format": "{0}h {1}m {2}s", "music_to_position": "Na pozici", @@ -605,9 +579,6 @@ "utility_convert_not_found": "Nepodařilo se převést {0} na {1}: jednotky nenalezeny", "utility_convert_type_error": "Nepodařilo se převést {0} na {1}: rozdílné typy jednotek", "utility_created_at": "Vytvořena v", - "utility_csc_join": "Připojil ses na meziserverový kanál (MSK).", - "utility_csc_leave": "Odešel jsi z meziserverového kanálu (MSK).", - "utility_csc_token": "Tohle je tvůj MSK žeton", "utility_custom_emojis": "Vlastní emoji", "utility_error": "Chyba", "utility_features": "Vlastnosti", @@ -663,7 +634,7 @@ "utility_server_info": "Informace o serveru", "utility_shard": "Shard", "utility_shard_stats": "Shard statistika", - "utility_shard_stats_txt": "Shard **#{0}** je ve stavu {1} na serverech {2}", + "utility_shard_stats_txt": "Shard **#{0}** je ve stavu {1} na serverech {2}- před {3}", "utility_showemojis": "**Jméno:** {0} **Odkaz:** {1}", "utility_showemojis_none": "Nepodařilo se najít žádné speciální emoji.", "utility_stats_songs": "Přehrávám {0} písní, {1} zařazena/o", @@ -754,7 +725,6 @@ "gambling_shop_role": "Dostaneš roli {0}.", "gambling_type": "Typ", "utility_clpa_next_update": "Další update v {0}", - "administration_global_perms_reset": "Globální povolení byla resetována.", "administration_gvc_disabled": "Herní hlasový kanál byl zakázán na tomto serveru.", "administration_gvc_enabled": "{0} je nyní Herní hlasový kanál.", "administration_not_in_voice": "Nejseš v hlasovém kanále na tomto serveru.", @@ -786,5 +756,117 @@ "administration_prefix_current": "Prefix na tomto serveru je {0}", "administration_prefix_new": "Na tomto serveru byl změněn prefix z {0} na {1}", "administration_defprefix_current": "Výchozí prefix bota je {0}", - "administration_defprefix_new": "Výchozí prefix bota byl změněn z {0} na {1}" + "administration_defprefix_new": "Výchozí prefix bota byl změněn z {0} na {1}", + "administration_bot_nick": "Přezdívka bota změněna na {0}", + "administration_user_nick": "Přezdívka uživatele {0} byla změněna na {1}", + "administration_timezone_guild": "Časové pásmo pro tento server je `{0}`", + "administration_timezone_not_found": "Časové pásmo nebylo nalezeno. Použij příkaz \"timezones\" pro seznam všech dostupných časových pásem.", + "administration_timezones_available": "Dostupná časová pásma", + "music_song_not_found": "Žádná píseň nebyla nalezena.", + "searches_define_unknown": "Nepodařilo se najít definici daného výrazu.", + "utility_repeater_initial": "První opakující se zpráva se pošle za {0}h a {1}min.", + "utility_verbose_errors_enabled": "Nesprávně použité příkazy se nyní budou zobrazovat jako chyba.", + "utility_verbose_errors_disabled": "Nesprávně použité příkazy se nyní nebudou zobrazovat jako chyba.", + "permissions_perms_reset": "Oprávnění tohoto serveru byla resetována.", + "permissions_trigger": "Oprávnění číslo #{0} {1} brání této akci.", + "administration_migration_error": "Chyba při migrování, v konzoli bota je více informací.", + "searches_hex_invalid": "Nesprávně specifikovaná barva.", + "permissions_global_perms_reset": "Globální oprávnění byla resetována.", + "help_module": "Modul: {0}", + "games_hangman_stopped": "Hra Hangman skončila.", + "music_autoplaying": "Automatické přehrávání.", + "music_queue_stopped": "Přehrávač je zastaven. Použij příkaz {0} pro spuštění.", + "music_removed_song_error": "Píseň na tomto indexu neexistuje", + "music_shuffling_playlist": "Míchání písní", + "music_songs_shuffle_enable": "Písně se budou odteď přehrávat náhodně.", + "music_songs_shuffle_disable": "Písně se odteď nebudou přehrávat náhodně.", + "music_song_skips_after": "Písně se přeskočí po {0}", + "administration_warnings_list": "Seznam všech varovaných uživatelů na tomto serveru", + "customreactions_redacted_too_long": "Redigováno, protože to trvalo dlouho.", + "nsfw_blacklisted_tag_list": "Černá listina tagů:", + "nsfw_blacklisted_tag": "Jeden nebo více tagů, které jsi použil jsou na černé listině.", + "nsfw_blacklisted_tag_add": "Nsfw tag {0} je nyní na černé listině.", + "nsfw_blacklisted_tag_remove": "Nsfw tag {0} již není na černé listině.", + "gambling_waifu_gift": "Darováno {0} {1}", + "gambling_waifu_gift_shop": "Obchod dárků pro waifu", + "gambling_gifts": "Dárky", + "games_connect4_created": "Hra Connect4 byla vytvořena. Čekám na připojení hráče.", + "games_connect4_player_to_move": "Hráč k posunutí: {0}", + "games_connect4_failed_to_start": "Connect4 hru se nepodařilo spustit, protože se nikdo nepřipojil.", + "games_connect4_draw": "Hra Connect4 skončila remízou.", + "games_connect4_won": "{0} vyhrál hru Connect4 proti {1}", + "games_nunchi_joined": "Připojen k nunchi hře.{0} uživatelů se již připojilo.", + "games_nunchi_ended": "Nunchi hra skončila. {0} vyhrál", + "games_nunchi_ended_no_winner": "Nunchi hra skončila bez vítěze.", + "games_nunchi_started": "Nunchi hra začala s {0} účastníky", + "games_nunchi_round_ended": "Kolo hry Nunchi skončilo. {0} je mimo hru.", + "games_nunchi_round_ended_boot": "Kolo hry Nunchi skončilo kvůli vypršení času pro některé uživatele. Tito uživatelé stále ještě hrají: {0}", + "games_nunchi_round_started": "Kolo hry Nunchi začalo s {0} uživateli. Začni počítat od čísla {1}.", + "games_nunchi_next_number": "Číslo registrováno. Poslední číslo bylo {0}.", + "games_nunchi_failed_to_start": "Hru Nunchi se nepodařilo spustit z důvodu nedostatku účastníků.", + "games_nunchi_created": "Hra Nunchi byla vytvořena. Čekám na připojení uživatelů.", + "music_sad_enabled": "Písně budou mazány ze seznamu přehrávání, když skončí.", + "music_sad_disabled": "Písně nebudou mazány ze seznamu přehrávání, když skončí.", + "utility_stream_role_enabled": "Když uživatel z role {0} začne streamovat, dám mu roli {1}.", + "utility_stream_role_disabled": "Funkce Role při streamování byla vypnuta.", + "utility_stream_role_kw_set": "Streameři nyní potřebují klíčové slovo {0}, aby dostali roli.", + "utility_stream_role_kw_reset": "Klíčové slovo pro streamování bylo resetováno.", + "utility_stream_role_bl_add": "Uživatel {0} nikdy nedostane streamovací roli.", + "utility_stream_role_bl_add_fail": "Uživatel {0} již je na černé listině.", + "utility_stream_role_bl_rem": "Uživatel {0} již není na černé listině.", + "utility_stream_role_bl_rem_fail": "Uživatel {0} není na černé listině.", + "utility_stream_role_wl_add": "Uživatel {0} získá streamovací roli, i když nemá klíčové slovo v názvu streamu.", + "utility_stream_role_wl_add_fail": "Uživatel {0} již je na bílé listině.", + "utility_stream_role_wl_rem": "Uživatel {0} již není na bílé listině.", + "utility_stream_role_wl_rem_fail": "Uživatel {0} není na bílé listině.", + "utility_bot_config_edit_fail": "Nepodařilo se nastavit {0} na hodnotu {1}", + "utility_bot_config_edit_success": "Hodnota {0} byla nastavena na {1}", + "customreactions_crca_disabled": "Vlastní realce s id{0} se nyní spustí, kdykoli se objeví na začátku věty.", + "customreactions_crca_enabled": "Vlastní realce s id{0} se nyní spustí, kdykoli se objeví kdekoli ve větě.", + "xp_server_level": "Serverová úroveň", + "xp_level": "Úroveň", + "xp_club": "Klub", + "xp_xp": "Zkušenost", + "xp_excluded": "{0} byl vyloučen z XP systému na tomto serveru.", + "xp_not_excluded": "{0} již není vyloučen z XP systému na tomto serveru.", + "xp_exclusion_list": "Seznam vyloučených", + "xp_server_is_excluded": "Tento server je vyloučen.", + "xp_server_is_not_excluded": "Tento server není vyloučen.", + "xp_excluded_roles": "Vyloučené role", + "xp_excluded_channels": "Vyloučené kanály", + "xp_level_up_channel": "Gratulace {0}, dosáhl jsi úrovně {1}!", + "xp_level_up_dm": "Gratulace {0}, dosáhl jsi úrovně {1} na serveru {2}!", + "xp_level_up_global": "Gratulace {0}, dosáhl jsi globální úrovně {1}!", + "xp_role_reward_cleared": "Za úroveň {0} již nikdo nedostane roli .", + "xp_role_reward_added": "Uživatelé, kteří dosáhnou úrovně {0} dostanou roli {1}.", + "xp_role_rewards": "Role za odměnu", + "xp_level_x": "Úroveň {0}", + "xp_no_role_rewards": "Na této straně není žádná role za odměnu.", + "xp_server_leaderboard": "Serverový XP žebříček", + "xp_global_leaderboard": "Globální XP žebříček", + "xp_modified": "Upraveno serverové XP uživatele {0} o {1}", + "xp_club_create_error": "Nepodařilo se založit klub. Ujisti se, že tvoje úroveň je alespoň 5, a že již nejsi členem jiného klubu.", + "xp_club_created": "Klub {0} byl úspěšně vytvořen.", + "xp_club_not_exists": "Tento klub neexistuje.", + "xp_club_applied": "Ucházíš se o místo v klubu {0}.", + "xp_club_apply_error": "Nepodařilo se ucházení o klub. Buď již jsi členem klubu nebo nesplňuješ minimální úroveň k přijetí nebo jsi z tohoto klubu byl zabanován.", + "xp_club_accepted": "Uživatel {0} byl přijat do klubu.", + "xp_club_accept_error": "Uživatel nenalezen.", + "xp_club_left": "Opustil jsi klub.", + "xp_club_not_in_club": "Nejsi v klubu nebo se snažíš opustit klub, který vlastníš.", + "xp_club_user_kick": "Uživatel {0} byl vyhozen z klubu {1}.", + "xp_club_user_kick_fail": "Nepodařilo se vyhodit. Buď nejsi majitelem klubu nebo tento uživatel není ve tvém klubu.", + "xp_club_user_banned": "Uživatel {0} byl zabanován v klubu {1}.", + "xp_club_user_ban_fail": "Nepodařilo se zabanovat. Buď nejsi majitelem klubu nebo tento uživatel není ve tvém klubu, ani se o něj neuchází.", + "xp_club_user_unbanned": "Uživatel {0} byl odbanován v klubu {1}.", + "xp_club_user_unban_fail": "Nepodařilo se odbanovat. Buď nejsi majitelem klubu nebo tento uživatel není ve tvém klubu, ani se o něj neuchází.", + "xp_club_level_req_changed": "Požadovaná úroveň pro vstup do klubu byla změněna na {0}", + "xp_club_level_req_change_error": "Nepodařilo se změnit požadovanou úroveň pro vstup.", + "xp_club_disbanded": "Klub {0} byl rozpuštěn.", + "xp_club_disband_error": "Chyba. Buď nejsi v klubu nebo nejsi jeho majitelem.", + "xp_club_icon_error": "Neplatné url obrázku nebo nejsi majitel klubu.", + "xp_club_icon_set": "Nová klubová ikona byla nastavena.", + "xp_club_bans_for": "Bany pro klub {0}", + "xp_club_apps_for": "Uchazeči o klub {0}", + "xp_club_leaderboard": "Klubový žebříček - strana {0}" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.da-DK.json b/src/NadekoBot/_strings/ResponseStrings.da-DK.json index 18afea3e..0f556069 100644 --- a/src/NadekoBot/_strings/ResponseStrings.da-DK.json +++ b/src/NadekoBot/_strings/ResponseStrings.da-DK.json @@ -151,7 +151,6 @@ "administration_old_nick": "Gammelt kaldenavn", "administration_old_topic": "Gammelt emne", "administration_perms": "Fejl. Jeg har formentlig ikke de rigtige tilladelser", - "administration_perms_reset": "Denne servers tilladelser er blevet nulstillet.", "administration_prot_active": "Aktive beskyttelser", "administration_prot_disable": "{0} er blevet **slået fra** på denne server.", "administration_prot_enable": "{0} slået til", @@ -243,7 +242,6 @@ "administration_sbdm": "Du er blevet soft-bannet fra {0} serveren.\nBegrundelse: {1}", "administration_user_unbanned": "Brugers udelukkelse ophævet", "administration_migration_done": "Migrasjon gjort!", - "adminsitration_migration_error": "", "administration_presence_updates": "Tilstedeværelses opdateringer", "administration_sb_user": "Brugeren blev soft-bannet", "gambling_awarded": "har givet {0} til {1}", @@ -439,7 +437,6 @@ "music_rpl_enabled": "Gentagelse af afspilningslisten slået til.", "music_set_music_channel": "Jeg vil nu sende afspillende, færdiggjorte, pausede og fjernede sand i denne kanal.", "music_skipped_to": "Sprang hen til `{0}:{1}`", - "music_songs_shuffled": "Sangene blandet", "music_song_moved": "Sang flyttet", "music_time_format": "{0}t {1}m {2}s", "music_to_position": "Til position", @@ -605,9 +602,6 @@ "utility_convert_not_found": "Kan ikke konvertere {0} til {1}: enhederne kunne ikke findes", "utility_convert_type_error": "Kan ikke konvertere {0} til {1}: Enhederne er ikke samme type", "utility_created_at": "Lavet den", - "utility_csc_join": "", - "utility_csc_leave": "", - "utility_csc_token": "Dette er din CSC token.", "utility_custom_emojis": "Brugerdefinerede emojis", "utility_error": "Fejl", "utility_features": "Funktioner", @@ -754,7 +748,6 @@ "gambling_shop_role": "Du får {0} rollen.", "gambling_type": "", "utility_clpa_next_update": "Næste opdatering om {0}", - "administration_global_perms_reset": "", "administration_gvc_disabled": "", "administration_gvc_enabled": "", "administration_not_in_voice": "", @@ -786,5 +779,30 @@ "administration_prefix_current": "", "administration_prefix_new": "", "administration_defprefix_current": "", - "administration_defprefix_new": "" + "administration_defprefix_new": "", + "administration_bot_nick": "", + "administration_user_nick": "", + "administration_timezone_guild": "", + "administration_timezone_not_found": "", + "administration_timezones_available": "", + "music_song_not_found": "", + "searches_define_unknown": "", + "utility_repeater_initial": "", + "utility_verbose_errors_enabled": "", + "utility_verbose_errors_disabled": "", + "permissions_perms_reset": "", + "permissions_trigger": "", + "administration_migration_error": "", + "searches_hex_invalid": "", + "permissions_global_perms_reset": "", + "help_module": "", + "games_hangman_stopped": "", + "music_autoplaying": "", + "music_queue_stopped": "", + "music_removed_song_error": "", + "music_shuffling_playlist": "", + "music_songs_shuffle_enable": "", + "music_songs_shuffle_disable": "", + "music_song_skips_after": "", + "administration_warnings_list": "" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.de-DE.json b/src/NadekoBot/_strings/ResponseStrings.de-DE.json index 6ad509b1..455ca29d 100644 --- a/src/NadekoBot/_strings/ResponseStrings.de-DE.json +++ b/src/NadekoBot/_strings/ResponseStrings.de-DE.json @@ -1,27 +1,4 @@ { - "clashofclans_base_already_claimed": "Diese Basis wurde bereits beansprucht oder zerstört.", - "clashofclans_base_already_destroyed": "Diese Basis ist bereits zerstört.", - "clashofclans_base_already_unclaimed": "Diese Basis ist nicht beansprucht.", - "clashofclans_base_destroyed": "Basis #{0} im Krieg gegen {1} **ZERSTÖRT** ", - "clashofclans_base_unclaimed": "{0} hat die Basis #{1} **UNBEANSPRUCHT** im Krieg gegen {2}", - "clashofclans_claimed_base": "{0} hat die Basis #{1} beansprucht im Krieg gegen {2}", - "clashofclans_claimed_other": "@{0} sie haben die Basis #{1} bereits beansprucht. Sie können keine weitere beanspruchen.", - "clashofclans_claim_expired": "Beanspruchung von @{0} für den Krieg gegen {1} ist abgelaufen.", - "clashofclans_enemy": "Gegner", - "clashofclans_info_about_war": "informationen über den Krieg mit {0}", - "clashofclans_invalid_base_number": "Ungültige Basisnummer.", - "clashofclans_invalid_size": "Keine gültige Kriegsgröße.", - "clashofclans_list_active_wars": "Liste der aktiven Kriege", - "clashofclans_not_claimed": "nicht beansprucht", - "clashofclans_not_partic": "Sie nehmen nicht an diesem Krieg teil.", - "clashofclans_not_partic_or_destroyed": "@{0} Sie nehmen nicht an diesem Krieg teil, oder diese Basis ist bereits zerstört.", - "clashofclans_no_active_wars": "Keine aktiven Kriege.", - "clashofclans_size": "Größe", - "clashofclans_war_already_started": "Krieg gegen {0} wurde schon gestartet.", - "clashofclans_war_created": "Krieg gegen {0} erstellt.", - "clashofclans_war_ended": "Krieg gegen {0} ist beendet.", - "clashofclans_war_not_exist": "Dieser Krieg existiert nicht.", - "clashofclans_war_started": "Krieg gegen {0} gestartet!", "customreactions_all_stats_cleared": "Reaktionsstatistiken gelöscht.", "customreactions_deleted": "Benutzerdefinierte Reaktion gelöscht.", "customreactions_insuff_perms": "Unzureichende Rechte. Dies erfordert das Sie den Bot besitzen für globale Reaktionen, und Administratorrechte für serverseitige Reaktionen.", @@ -97,7 +74,7 @@ "administration_fwdm_start": "Ich werde nun DNs weiterleiten.", "administration_fwdm_stop": "Ich werde aufhören DNs weiterzuleiten.", "administration_greetdel_off": "Automatisches löschen der Begrüßungsnachrichten wurde deaktiviert.", - "administration_greetdel_on": "Begrüßungsnachrichten werden gelöscht nach {0} sekunden.", + "administration_greetdel_on": "Begrüßungsnachrichten werden gelöscht nach {0} Sekunden.", "administration_greetdmmsg_cur": "Aktuelle DN Begrüßungsnachricht: {0}", "administration_greetdmmsg_enable": "Aktiviere DN Begrüßungsnachrichten durch schreiben von: {0}", "administration_greetdmmsg_new": "Neue DN Begrüßungsnachricht wurde gesetzt.", @@ -109,7 +86,7 @@ "administration_greet_off": "Begrüßungsankündigungen wurden deaktiviert.", "administration_greet_on": "Begrüßungsankündigungen wurden für diesen Kanal aktiviert.", "administration_hierarchy": "Sie können diesen befehl nicht an Benutzern mit einer Rolle über oder gleich zu Ihrer in der Rangordnung benutzen.", - "administration_images_loaded": "Bilder wurden geladen nach {0} sekunden!", + "administration_images_loaded": "Bilder wurden geladen nach {0} Sekunden!", "administration_invalid_format": "Ungültiges Eingabeformat.", "administration_invalid_params": "Ungültige Übergabevariable.", "administration_joined": "{0} ist {1} beigetreten", @@ -151,7 +128,6 @@ "administration_old_nick": "Alter Nickname", "administration_old_topic": "Altes Thema", "administration_perms": "Fehler. Ich habe wahrscheinlich nicht ausreichend Rechte.", - "administration_perms_reset": "Rechte für diesen Server zurückgesetzt.", "administration_prot_active": "Aktive Schutzmechanismen", "administration_prot_disable": "{0} wurde auf diesem Server **deaktiviert**.", "administration_prot_enable": "{0} aktiviert", @@ -243,7 +219,6 @@ "administration_sbdm": "Sie wurden vom Server {0} gekickt.\nGrund: {1}", "administration_user_unbanned": "Benutzer entbannt", "administration_migration_done": "Migration fertig!", - "adminsitration_migration_error": "Fehler beim migrieren von Daten. Prüfe die Konsole des Bots für mehr Informationen.", "administration_presence_updates": "Anwesenheits Änderungen", "administration_sb_user": "Nutzer wurde gekickt", "gambling_awarded": "gab {0} an {1}", @@ -285,7 +260,7 @@ "help_cmdlist_donate": "Sie können das Projekt auf Patreon: <{0}> oder Paypal: <{1}> unterstützen.", "help_cmd_and_alias": "Befehle und Alias", "help_commandlist_regen": "Befehlsliste neu generiert.", - "help_commands_instr": "Gebe `{0}h NameDesBefehls` ein, um die Hilfestellung für diesen Befehl zu sehen. Z.B. `{0}h >8ball`", + "help_commands_instr": "Gebe `{0}h NameDesBefehls` ein, um die Hilfestellung für diesen Befehl zu sehen. Z.B. `{0}h {0}8ball`", "help_command_not_found": "Ich konnte diesen Befehl nicht finden. Bitte stelle sicher das dieser Befehl existiert bevor Sie es erneut versuchen.", "help_desc": "Beschreibung", "help_donate": "Sie können das NadekoBot-Projekt über \nPatreon <{0}> oder \nPayPal <{1}> unterstützen.\nVergessen Sie bitte nicht, Ihren Discord-Namen oder Ihre ID in der Nachricht zu hinterlassen.\n\n**Vielen Dank**♥️", @@ -439,7 +414,6 @@ "music_rpl_enabled": "Playlist-Wiederholung aktiviert.", "music_set_music_channel": "Ich werde nun spielende, beendete, pausierte und entfernte Lieder in diesem Channel ausgeben.", "music_skipped_to": "Gesprungen zu `{0}:{1}`", - "music_songs_shuffled": "Lieder gemischt.", "music_song_moved": "Lied bewegt", "music_time_format": "{0}h {1}m {2}s", "music_to_position": "Zu Position", @@ -605,9 +579,6 @@ "utility_convert_not_found": "Kann {0} nicht zu {1} konvertieren: Einheiten nicht gefunden", "utility_convert_type_error": "Kann {0} nicht zu {1} konvertieren: Einheiten sind nicht gleich", "utility_created_at": "Erstellt am", - "utility_csc_join": "Betritt Multi-Server-Kanal.", - "utility_csc_leave": "Verließ Multi-Server-Kanal.", - "utility_csc_token": "Dies ist Ihr MSK token", "utility_custom_emojis": "Benutzerdefinierte Emojis", "utility_error": "Fehler", "utility_features": "Funktionalitäten", @@ -663,7 +634,7 @@ "utility_server_info": "Server Info", "utility_shard": "Shard", "utility_shard_stats": "Shard Statistiken", - "utility_shard_stats_txt": "Shard **#{0}** ist im {1} status mit {2} Servern", + "utility_shard_stats_txt": "Shard **#{0}** ist im {1} Status mit {2} Servern - vor {3}", "utility_showemojis": "**Name:** {0} **Link:** {1}", "utility_showemojis_none": "Keine speziellen emoji gefunden.", "utility_stats_songs": "Wiedergabe von {0} Liedern, {1} in der Musikliste.", @@ -685,19 +656,19 @@ "games_thanks_for_voting": "Danke für das Abstimmen. {0}", "games_x_votes_cast": "{0} totale Abstimmungen eingereicht.", "games_pick_pl": "Sammel sie durch das Schreiben von `{0}pick`", - "games_pick_sn": "Sammel sie durch das Schreiben von `{0}pick", + "games_pick_sn": "Sammel sie durch das Schreiben von `{0}pick`", "gambling_no_users_found": "Kein Benutzer gefunden.", "gambling_page": "Seite {0}", "administration_must_be_in_voice": "Sie müssen in einem Sprachkanal auf diesem Server sein.", "administration_no_vcroles": "Keine Sprachkanal Rollen gefunden.", - "administration_user_muted_time": "{0} wurde **stummgeschaltet** im Text- und Sprachchat für {1} minuten.", + "administration_user_muted_time": "{0} wurde **stummgeschaltet** im Text- und Sprachchat für {1} Minuten.", "administration_vcrole_added": "Benutzer die dem Sprachkanal {0} beitreten werden der Rolle {1} zugeweist.", "administration_vcrole_removed": "Benutzer die den Sprachkanal {0} beitreten werden nicht länger einer Rolle zugeweist.", "administration_vc_role_list": "Rollen für Sprachkanäle", "customreactions_crad_disabled": "Auslösende Nachricht der benutzerdefinierten Reaktion mit der ID {0} wird nicht automatisch gelöscht.", "customreactions_crad_enabled": "Auslösende Nachricht der benutzerdefinierten Reaktion mit der ID {0} wird automatisch gelöscht.", "customreactions_crdm_disabled": "Reaktionsnachricht für die benutzerdefinierte Reaktion mit der ID {0} wird nicht als DN gesendet.", - "customreactions_crdm_enabled": "Reaktionsnachricht für die benutzerdefinierte Reaktion mit der ID {0} wird nicht als DN gesendet.", + "customreactions_crdm_enabled": "Reaktionsnachricht für die benutzerdefinierte Reaktion mit der ID {0} wird als DN gesendet.", "utility_aliases_none": "Keinen Alias gefunden", "utility_alias_added": "{0} ist nun ein Alias für {1}", "utility_alias_list": "Liste der Aliasse", @@ -726,7 +697,7 @@ "administration_warnings_none": "Keine Warnungen auf dieser Seite.", "administration_warnlog_for": "Warnlog für {0}", "administration_warnpl_none": "Keine Bestrafungen gesetzt.", - "administration_warn_cleared_by": "Bereinigt bei {0}", + "administration_warn_cleared_by": "Gelöscht von {0}", "administration_warn_punish_list": "Warnungs Straf Liste", "administration_warn_punish_rem": "{0} Warnungen werden nicht mehr eine Bestrafung auslösen.", "administration_warn_punish_set": "Ich werde die Bestrafung {0} an Benutzern mit {1} Warnungen ausführen.", @@ -754,8 +725,7 @@ "gambling_shop_role": "Du wirst die Rolle {0} erhalten.", "gambling_type": "Art", "utility_clpa_next_update": "Nächste Aktualisierung in {0}", - "administration_global_perms_reset": "Globale Rechte zurückgesetzt.", - "administration_gvc_disabled": "Spiel-Sprachkanal-Feature ist nicht eingeschaltet auf diesem Server.", + "administration_gvc_disabled": "Die Spiel-Sprachkanal-Funktion ist nicht eingeschaltet auf diesem Server.", "administration_gvc_enabled": "{0} ist nun ein Spiel-Sprachkanal", "administration_not_in_voice": "Du bist in keinem Sprachkanal auf diesem Server.", "gambling_item": "Gegenstand", @@ -786,5 +756,117 @@ "administration_prefix_current": "Das Präfix des Servers ist {0}", "administration_prefix_new": "Das Präfix auf diesem Server wurde von {0} zu {1} geändert", "administration_defprefix_current": "Standard bot Präfix ist {0}", - "administration_defprefix_new": "Standard bot Präfix wurde von {0} zu {1} geändert" + "administration_defprefix_new": "Standard bot Präfix wurde von {0} zu {1} geändert", + "administration_bot_nick": "Bots Nickname wurde zu {0} geändert", + "administration_user_nick": "Nickname des Benutzer {0} wurde zu {1} geändert", + "administration_timezone_guild": "Die Zeitzone für die Gilde ist `{0}`", + "administration_timezone_not_found": "Zeitzone nicht gefunden. Benutze \"timezones\" Befehl um eine liste der Verfügbaren Zeitzonen zu sehen.", + "administration_timezones_available": "Verfügbare Zeitzonen", + "music_song_not_found": "Kein Lied gefunden", + "searches_define_unknown": "Konnte keine Definition für diesen Term finden.", + "utility_repeater_initial": "Ursprüngliche Nachricht wird nochmal gesendet in {0}Stunden und {1}Minuten.", + "utility_verbose_errors_enabled": "Falsch genutzte Befehle werden jetzt Fehlermeldungen zeigen.", + "utility_verbose_errors_disabled": "Falsch genutzte Befehle werden keine Fehlermeldungen mehr zeigen.", + "permissions_perms_reset": "Rechte für diesen Server wurden zurückgesetzt.", + "permissions_trigger": "Berechtigung Nummer #{0} {1} verhindert diese Aktion.", + "administration_migration_error": "Fehler während der Übernahme, überprüfe die Bot Console für mehr Informationen.", + "searches_hex_invalid": "Ungültige Farbe angegeben", + "permissions_global_perms_reset": "Globale Rechte wurden zurückgesetzt.", + "help_module": "Modul: {0}", + "games_hangman_stopped": "Galgenmännchenspiel wurde gestoppt.", + "music_autoplaying": "Auto-Musik wird gespielt.", + "music_queue_stopped": "Wiedergabe gestoppt. Benutze {0} Befehl um die Wiedergabe fortzusetzen.", + "music_removed_song_error": "Lied an diesem Index existiert nicht", + "music_shuffling_playlist": "Mische Lieder", + "music_songs_shuffle_enable": "Lieder werden jetzt zufällig wiedergegeben.", + "music_songs_shuffle_disable": "Lieder werden nicht mehr gemischt.", + "music_song_skips_after": "Lieder werden übersprungen nach {0}", + "administration_warnings_list": "Liste aller gewarnten Benutzer auf diesem Server", + "customreactions_redacted_too_long": "Bearbeitet da es zu lang ist.", + "nsfw_blacklisted_tag_list": "Liste von Stichwörtern auf der Sperrliste:", + "nsfw_blacklisted_tag": "Ein oder mehrere Stichwörter die du benutzt hast sind auf der Sperrliste", + "nsfw_blacklisted_tag_add": "Nsfw Stichwort {0} ist jetzt auf der Sperrliste.", + "nsfw_blacklisted_tag_remove": "Nsfw Stichwort {0} ist nicht mehr auf der Sperrliste.", + "gambling_waifu_gift": "Schenkte {0} an {1}", + "gambling_waifu_gift_shop": "Waifu Geschenke Laden", + "gambling_gifts": "Geschenke", + "games_connect4_created": "Connect4 Spiel wurde erstellt. Warte auf Spieler.", + "games_connect4_player_to_move": "Nächster Spieler an der Reihe: {0}", + "games_connect4_failed_to_start": "Connect4 Spiel konnte nicht gestartet werden weil niemand beigetreten ist.", + "games_connect4_draw": "Connect4 Spiel endete mit Gleichstand.", + "games_connect4_won": "{0} hat das Connect4 Spiel gewonnen gegen {1}.", + "games_nunchi_joined": "Nunchi Spiel beigetreten. {0} Benutzer sind bis jetzt eingetreten.", + "games_nunchi_ended": "Nunchi Spiel beendet. {0} hat gewonnen", + "games_nunchi_ended_no_winner": "Nunchi Spiel hat ohne Gewinner geendet.", + "games_nunchi_started": "Nunchi Spiel startet mit {0} Teilnehmern.", + "games_nunchi_round_ended": "Nunchi Runde ist beendet. {0} ist aus dem Spiel.", + "games_nunchi_round_ended_boot": "Nunchi Runde endet aufgrund von Zeitüberschreitung einiger Nutzer. Diese Nutzer sind noch im Spiel: {0}", + "games_nunchi_round_started": "Nunchi Runde startete mit {0} Nutzern. Beginnt ab Nummer {1} zu zählen.", + "games_nunchi_next_number": "Nummer registriert. Die letzte Nummer war {0}.", + "games_nunchi_failed_to_start": "Nunchi Spiel konnte nicht starten, da es nicht genügend Teilnehmer gibt.", + "games_nunchi_created": "Nunchi Spiel erstellt. Warte auf das Beitreten von Nutzern.", + "music_sad_enabled": "Lieder werden von der Musik-Warteschlange gelöscht sobald die Wiedergabe beendet wurde.", + "music_sad_disabled": "Lieder werden nun nicht mehr von der Musik-Warteschlange gelöscht sobald die Wiedergabe beendet wurde.", + "utility_stream_role_enabled": "Wenn ein Benutzer mit der {0} Rolle anfängt zu streamen, werde ich ihm die {1} Rolle geben.", + "utility_stream_role_disabled": "Das Stream Rollen Funtion wurde deaktiviert.", + "utility_stream_role_kw_set": "Streamer benötigen nun {0} Stichwort um die Rolle zu erhalten", + "utility_stream_role_kw_reset": "Stream Rollen Stichwort wurde zurückgesetzt.", + "utility_stream_role_bl_add": "Benutzer {0} wird nie die Stream Rolle erhalten.", + "utility_stream_role_bl_add_fail": "Nutzer {0} ist bereits auf der Sperrliste.", + "utility_stream_role_bl_rem": "Nutzer {0} ist nicht mehr auf der Sperrliste.", + "utility_stream_role_bl_rem_fail": "Nutzer {0} ist nicht auf der Sperrliste.", + "utility_stream_role_wl_add": "Benutzer {0} wird nun die Stream Rolle erhalten, auch wenn das Stichwort nicht im Stream Titel ist.", + "utility_stream_role_wl_add_fail": "Nutzer {0} ist bereits auf der weißen Liste.", + "utility_stream_role_wl_rem": "Nutzer {0} ist nicht mehr auf der weißen Liste.", + "utility_stream_role_wl_rem_fail": "Nutzer {0} ist nicht auf der weißen Liste.", + "utility_bot_config_edit_fail": "Fehlschlag der Änderung {0} zu dem Wert {1}", + "utility_bot_config_edit_success": "Der Wert von {0} wurde auf {1} gestellt", + "customreactions_crca_disabled": "Benutzerdefinierte Reaktion mit der ID {0} wird nicht mehr auslösen, außer der Auslöser ist am Anfang des Satzes.", + "customreactions_crca_enabled": "Benutzerdefinierte Reaktion mit der ID {0} wird nun auslösen wenn der Auslöser ein Teil des Satzes ist.", + "xp_server_level": "Server Level", + "xp_level": "Level", + "xp_club": "Club", + "xp_xp": "Erfahrungspunkte", + "xp_excluded": "{0} wurde vom XP System in diesem Server ausgeschlossen.", + "xp_not_excluded": "{0} ist nun nicht mehr vom XP System in diesem Server ausgeschlossen.", + "xp_exclusion_list": "Ausschluß Liste", + "xp_server_is_excluded": "Dieser Server ist von xp ausgeschlossen", + "xp_server_is_not_excluded": "Dieser Server ist nicht von xp ausgeschlossen", + "xp_excluded_roles": "Ausgeschlossene Rollen", + "xp_excluded_channels": "Ausgeschlossene Kanäle.", + "xp_level_up_channel": "Glückwunsch {0}, du hast Level {1} erreicht!", + "xp_level_up_dm": "Glückwunsch {0}, du hast Level {1} auf {2} Servern erreicht!", + "xp_level_up_global": "Glückwunsch {0}, du hast das globale Level {1} erreicht!", + "xp_role_reward_cleared": "Level {0} wird keine Rollen-Belohnung mehr erhalten.", + "xp_role_reward_added": "benutzer die Level {0} werden die {1} Rolle erhalten.", + "xp_role_rewards": "Rollen Belohnungen", + "xp_level_x": "Level {0}", + "xp_no_role_rewards": "Keine Rollen Belohnungen auf dieser Seite.", + "xp_server_leaderboard": "Server XP Bestenliste", + "xp_global_leaderboard": "Global XP Bestenliste", + "xp_modified": "Die Server XP dieses Nutzers wurde von {0} auf {1} eingestellt", + "xp_club_create_error": "Gründung des Clubs fehlgeschlagen. Stelle sicher, dass du mindestens Level 5 und nicht bereits Mitglied eines Clubs bist.", + "xp_club_created": "Club {0} wurde erfolgreich erstellt!", + "xp_club_not_exists": "Diesen Club gibt es nicht.", + "xp_club_applied": "Du hast dich für eine Mitgliedschaft im {0} Club beworben.", + "xp_club_apply_error": "Fehler beim Bewerben. Du bist entweder bereits ein Mitglied eines Clubs, oder du erfüllst nicht die Level-Anforderung, oder du wurdest vom Club gebannt.", + "xp_club_accepted": "Nutzer {0} wurde in den Club aufgenommen.", + "xp_club_accept_error": "Nutzer nicht gefunden", + "xp_club_left": "Du hast den Club verlassen.", + "xp_club_not_in_club": "Du bist nicht in einem Club oder du versuchst den Club zu verlassen den du besitzt.", + "xp_club_user_kick": "Nutzer {0} von {1} Club gekickt.", + "xp_club_user_kick_fail": "Fehler bei Kicken. Du bist entweder nicht Club-Besitzer oder dieser Nutzer ist nicht in deinem Club.", + "xp_club_user_banned": "Nutzer {0} wurde von {1} Club gebannt.", + "xp_club_user_ban_fail": "Bann fehlgeschlagen. Entweder bist du nicht Club-Besitzer, oder der Nutzer ist nicht im Club oder hat sich beworben.", + "xp_club_user_unbanned": "Der Bann des Nutzers {0} im {1} Club wurde aufgehoben.", + "xp_club_user_unban_fail": "Entbannung fehlgeschlagen. Du bist entweder nicht der Besitzer des Clubs, oder der Nutzer ist nicht in deinem Club oder hat sich nicht beworben.", + "xp_club_level_req_changed": "Änderte Level Anforderung des Clubs auf {0}", + "xp_club_level_req_change_error": "Änderung der Level Voraussetzung fehlgeschlagen.", + "xp_club_disbanded": "Club {0} wurde aufgelöst", + "xp_club_disband_error": "Fehler. Du bist entweder nicht in einem Club, oder du bist nicht der Besitzer des Clubs.", + "xp_club_icon_error": "Keine gültige Bild url oder du bist nicht der Besitzer des Clubs.", + "xp_club_icon_set": "Neues Club Bild eingestellt.", + "xp_club_bans_for": "Banne des {0} Clubs", + "xp_club_apps_for": "Bewerber für {0} Club", + "xp_club_leaderboard": "Club Bestenliste - Seite {0}" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.en-US.json b/src/NadekoBot/_strings/ResponseStrings.en-US.json index 98df2300..081dc5c9 100644 --- a/src/NadekoBot/_strings/ResponseStrings.en-US.json +++ b/src/NadekoBot/_strings/ResponseStrings.en-US.json @@ -1,27 +1,4 @@ { - "clashofclans_base_already_claimed": "That base is already claimed or destroyed.", - "clashofclans_base_already_destroyed": "That base is already destroyed.", - "clashofclans_base_already_unclaimed": "That base is not claimed.", - "clashofclans_base_destroyed": "**DESTROYED** base #{0} in a war against {1}", - "clashofclans_base_unclaimed": "{0} has **UNCLAIMED** base #{1} in a war against {2}", - "clashofclans_claimed_base": "{0} claimed a base #{1} in a war against {2}", - "clashofclans_claimed_other": "@{0} You already claimed base #{1}. You can't claim a new one.", - "clashofclans_claim_expired": "Claim from @{0} in a war against {1} has expired.", - "clashofclans_enemy": "Enemy", - "clashofclans_info_about_war": "Info about war against {0}", - "clashofclans_invalid_base_number": "Invalid base number.", - "clashofclans_invalid_size": "Not a valid war size.", - "clashofclans_list_active_wars": "List of active wars", - "clashofclans_not_claimed": "not claimed", - "clashofclans_not_partic": "You are not participating in that war.", - "clashofclans_not_partic_or_destroyed": "@{0} You are either not participating in that war, or that base is already destroyed.", - "clashofclans_no_active_wars": "No active war.", - "clashofclans_size": "Size", - "clashofclans_war_already_started": "War against {0} has already started.", - "clashofclans_war_created": "War against {0} created.", - "clashofclans_war_ended": "War against {0} ended.", - "clashofclans_war_not_exist": "That war does not exist.", - "clashofclans_war_started": "War against {0} started!", "customreactions_all_stats_cleared": "All custom reaction stats cleared.", "customreactions_deleted": "Custom Reaction deleted", "customreactions_insuff_perms": "Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for server custom reactions.", @@ -35,8 +12,12 @@ "customreactions_stats_cleared": "Stats cleared for {0} custom reaction.", "customreactions_stats_not_found": "No stats for that trigger found, no action taken.", "customreactions_trigger": "Trigger", - "nsfw_autohentai_stopped": "Autohentai stopped.", + "customreactions_redacted_too_long": "Redecated because it's too long.", "nsfw_not_found": "No results found.", + "nsfw_blacklisted_tag_list": "List of blacklisted tags:", + "nsfw_blacklisted_tag": "One or more tags you've used are blacklisted", + "nsfw_blacklisted_tag_add": "Nsfw tag {0} is now blacklisted.", + "nsfw_blacklisted_tag_remove": "Nsfw tag {0} is no longer blacklisted.", "pokemon_already_fainted": "{0} has already fainted.", "pokemon_already_full": "{0} already has full HP.", "pokemon_already_that_type": "Your type is already {0}", @@ -154,6 +135,7 @@ "administration_old_topic": "Old topic", "administration_perms": "Error. Most likely I don't have sufficient permissions.", "permissions_perms_reset": "Permissions for this server are reset.", + "permissions_trigger": "Permission number #{0} {1} is preventing this action.", "administration_prot_active": "Active protections", "administration_prot_disable": "{0} has been **disabled** on this server.", "administration_prot_enable": "{0} Enabled", @@ -248,7 +230,7 @@ "administration_sbdm": "You have been soft-banned from {0} server.\nReason: {1}", "administration_user_unbanned": "User unbanned", "administration_migration_done": "Migration done!", - "adminsitration_migration_error": "Error while migrating, check bot's console for more information.", + "administration_migration_error": "Error while migrating, check bot's console for more information.", "administration_presence_updates": "Presence updates", "administration_sb_user": "User soft-banned", "gambling_awarded": "has awarded {0} to {1}", @@ -258,9 +240,9 @@ "gambling_flipped": "flipped {0}.", "gambling_flip_guess": "You guessed it! You won {0}", "gambling_flip_invalid": "Invalid number specified. You can flip 1 to {0} coins.", - "gambling_flowerreaction_desc": "Add {0} reaction to this message to get {1} ", - "gambling_flowerreaction_footer": "This event is active for up to {0} hours.", - "gambling_flowerreaction_title": "Flower reaction event started!", + "gambling_reaction_desc": "Add {0} reaction to this message to get {1} ", + "gambling_reaction_footer": "This event is active for up to {0} hours.", + "gambling_reaction_title": "Reaction event started!", "gambling_gifted": "has gifted {0} to {1}", "gambling_has": "{0} has {1}", "gambling_heads": "Head", @@ -302,6 +284,7 @@ "help_server_permission": "Requires {0} server permission.", "help_table_of_contents": "Table of contents", "help_usage": "Usage", + "help_module": "Module: {0}", "nsfw_autohentai_started": "Autohentai started. Reposting every {0}s with one of the following tags:\n{1}", "nsfw_tag": "Tag", "gambling_animal_race": "Animal race", @@ -343,6 +326,9 @@ "gambling_waifu_recent_divorce": "You divorced recently. You must wait {0} hours and {1} minutes to divorce again.", "gambling_nobody": "Nobody", "gambling_waifu_divorced_notlike": "You have divorced a waifu who doesn't like you. You received {0} back.", + "gambling_waifu_gift": "Gifted {0} to {1}", + "gambling_waifu_gift_shop": "Waifu gift shop", + "gambling_gifts": "Gifts", "games_8ball": "8ball", "games_acrophobia": "Acrophobia", "games_acro_ended_no_sub": "Game ended with no submissions.", @@ -365,6 +351,11 @@ "games_category": "Category", "games_cleverbot_disabled": "Disabled cleverbot on this server.", "games_cleverbot_enabled": "Enabled cleverbot on this server.", + "games_connect4_created": "Created a Connect4 game. Waiting for a player to join.", + "games_connect4_player_to_move": "Player to move: {0}", + "games_connect4_failed_to_start": "Connect4 game failed to start because nobody joined.", + "games_connect4_draw": "Connect4 game ended in a draw.", + "games_connect4_won": "{0} won the game of Connect4 against {1}.", "games_curgen_disabled": "Currency generation has been disabled on this channel.", "games_curgen_enabled": "Currency generation has been enabled on this channel.", "games_curgen_pl": "{0} random {1} appeared!", @@ -374,10 +365,21 @@ "games_hangman_game_started": "Hangman game started", "games_hangman_running": "Hangman game already running on this channel.", "games_hangman_start_errored": "Starting hangman errored.", + "games_hangman_stopped": "Hangman game stopped.", "games_hangman_types": "List of \"{0}hangman\" term types:", "games_leaderboard": "Leaderboard", "games_not_enough": "You don't have enough {0}", "games_no_results": "No results", + "games_nunchi_joined": "Joined nunchi game. {0} users joined so far.", + "games_nunchi_ended": "Nunchi game ended. {0} won", + "games_nunchi_ended_no_winner": "Nunchi game ended with no winner.", + "games_nunchi_started": "Nunchi game started with {0} participants.", + "games_nunchi_round_ended": "Nunchi round ended. {0} is out of the game.", + "games_nunchi_round_ended_boot": "Nunchi round ended due to timeout of some users. These users are still in the game: {0}", + "games_nunchi_round_started": "Nunchi round started with {0} users. Start counting from the number {1}.", + "games_nunchi_next_number": "Number registered. Last number was {0}.", + "games_nunchi_failed_to_start": "Nunchi failed to start because there were not enough participants.", + "games_nunchi_created": "Nunchi game created. Waiting for users to join.", "games_picked": "picked {0}", "games_planted": "{0} planted {1}", "games_trivia_already_running": "Trivia game is already running on this server.", @@ -401,12 +403,15 @@ "music_attempting_to_queue": "Attempting to queue {0} songs...", "music_autoplay_disabled": "Autoplay disabled.", "music_autoplay_enabled": "Autoplay enabled.", + "music_autoplaying": "Auto-playing.", "music_defvol_set": "Default volume set to {0}%", "music_dir_queue_complete": "Directory queue complete.", - "music_fairplay": "fairplay", + "music_fairplay": "Fairplay", "music_finished_song": "Finished song", "music_fp_disabled": "Fair play disabled.", "music_fp_enabled": "Fair play enabled.", + "music_sad_enabled": "Songs will be deleted from the music queue when they finish playing.", + "music_sad_disabled": "Songs will no longer be deleted from the music queue when they finish playing.", "music_from_position": "From position", "music_id": "Id", "music_invalid_input": "Invalid input.", @@ -421,7 +426,7 @@ "music_no_search_results": "No search results.", "music_paused": "Music playback paused.", "music_player_queue": "Player queue - Page {0}/{1}", - "music_playing_song": "Playing song", + "music_playing_song": "Playing song #{0}", "music_playlists": "`#{0}` - **{1}** by *{2}* ({3} songs)", "music_playlists_page": "Page {0} of saved playlists", "music_playlist_deleted": "Playlist deleted.", @@ -434,19 +439,24 @@ "music_queued_song": "Queued song", "music_queue_cleared": "Music queue cleared.", "music_queue_full": "Queue is full at {0}/{0}.", + "music_queue_stopped": "Player is stopped. Use {0} command to start playing.", "music_removed_song": "Removed song", + "music_removed_song_error": "Song on that index doesn't exist", "music_repeating_cur_song": "Repeating current song", "music_repeating_playlist": "Repeating playlist", "music_repeating_track": "Repeating track", "music_repeating_track_stopped": "Current track repeat stopped.", + "music_shuffling_playlist": "Shuffling songs", "music_resumed": "Music playback resumed.", "music_rpl_disabled": "Repeat playlist disabled.", "music_rpl_enabled": "Repeat playlist enabled.", "music_set_music_channel": "I will now output playing, finished, paused and removed songs in this channel.", "music_skipped_to": "Skipped to `{0}:{1}`", - "music_songs_shuffled": "Songs shuffled", + "music_songs_shuffle_enable": "Songs will shuffle from now on.", + "music_songs_shuffle_disable": "Songs will no longer shuffle.", "music_song_moved": "Song moved", - "music_song_not_found": "No song found.", + "music_song_not_found": "No song found.", + "music_song_skips_after": "Songs will skip after {0}", "music_time_format": "{0}h {1}m {2}s", "music_to_position": "To position", "music_unlimited": "unlimited", @@ -523,7 +533,7 @@ "searches_cost": "Cost", "searches_date": "Date", "searches_define": "Define:", - "searches_define_unknown": "Can't find the definition for that term.", + "searches_define_unknown": "Can't find the definition for that term.", "searches_dropped": "Dropped", "searches_episodes": "Episodes", "searches_error_occured": "Error occurred.", @@ -534,7 +544,7 @@ "searches_hashtag_error": "Failed finding a definition for that tag.", "searches_height_weight": "Height/Weight", "searches_height_weight_val": "{0}m/{1}kg", - "searches_hex_invalid": "Invalid color specified.", + "searches_hex_invalid": "Invalid color specified.", "searches_humidity": "Humidity", "searches_image_search_for": "Image search for:", "searches_imdb_fail": "Failed to find that movie.", @@ -598,12 +608,26 @@ "searches_wind_speed": "Wind speed", "searches_x_most_banned_champs": "The {0} most banned champions", "searches_yodify_error": "Failed to yodify your sentence.", + "utility_stream_role_enabled": "When a user from {0} role starts streaming, I will give them {1} role.", + "utility_stream_role_disabled": "Stream role feature has been disabled.", + "utility_stream_role_kw_set": "Streamers now require {0} keyword in order to receive the role.", + "utility_stream_role_kw_reset": "Stream role keyword reset.", + "utility_stream_role_bl_add": "User {0} will never receive the stream role.", + "utility_stream_role_bl_add_fail": "User {0} is already blacklisted.", + "utility_stream_role_bl_rem": "User {0} is no longer blacklisted.", + "utility_stream_role_bl_rem_fail": "User {0} is not blacklisted.", + "utility_stream_role_wl_add": "User {0} will receive the stream role even if they don't have the keyword in the stream title.", + "utility_stream_role_wl_add_fail": "User {0} is already whitelisted.", + "utility_stream_role_wl_rem": "User {0} is no longer whitelisted.", + "utility_stream_role_wl_rem_fail": "User {0} is not whitelisted.", "utiliity_joined": "Joined", "utility_activity_line": "`{0}.` {1} [{2:F2}/s] - {3} total", "utility_activity_page": "Activity page #{0}", "utility_activity_users_total": "{0} users total.", "utility_author": "Author", "utility_botid": "Bot ID", + "utility_bot_config_edit_fail": "Failed setting {0} to the value {1}", + "utility_bot_config_edit_success": "The value of {0} is set to {1}", "utility_calcops": "List of functions in {0}calc command", "utility_channelid": "{0} of this channel is {1}", "utility_channel_topic": "Channel topic", @@ -613,9 +637,6 @@ "utility_convert_not_found": "Cannot convert {0} to {1}: units not found", "utility_convert_type_error": "Cannot convert {0} to {1}: types of unit are not equal", "utility_created_at": "Created at", - "utility_csc_join": "Joined cross server channel.", - "utility_csc_leave": "Left cross server channel.", - "utility_csc_token": "This is your CSC token", "utility_custom_emojis": "Custom emojis", "utility_error": "Error", "utility_features": "Features", @@ -672,7 +693,7 @@ "utility_server_info": "Server info", "utility_shard": "Shard", "utility_shard_stats": "Shard stats", - "utility_shard_stats_txt": "Shard **#{0}** is in {1} state with {2} servers", + "utility_shard_stats_txt": "Shard **#{0}** is in {1} state with {2} servers - {3} ago", "utility_showemojis": "**Name:** {0} **Link:** {1}", "utility_showemojis_none": "No special emojis found.", "utility_stats_songs": "Playing {0} songs, {1} queued.", @@ -709,6 +730,8 @@ "customreactions_crad_enabled": "Message triggering the custom reaction with id {0} will get automatically deleted.", "customreactions_crdm_disabled": "Response message for the custom reaction with id {0} won't be sent as a DM.", "customreactions_crdm_enabled": "Response message for the custom reaction with id {0} will be sent as a DM.", + "customreactions_crca_disabled": "Custom reaction with id {0} will no longer trigger unless it's trigger word is at the beggining of the sentence.", + "customreactions_crca_enabled": "Custom reaction with id {0} will now trigger if it's contained anywhere in the sentence.", "utility_aliases_none": "No alias found", "utility_alias_added": "Typing {0} will now be an alias of {1}.", "utility_alias_list": "List of aliases", @@ -735,6 +758,7 @@ "administration_warned_on_by": "On {0} at {1} by {2}", "administration_warnings_cleared": "All warnings have been cleared for {0}.", "administration_warnings_none": "No warning on this page.", + "administration_warnings_list": "List of all warned users on the server", "administration_warnlog_for": "Warnlog for {0}", "administration_warnpl_none": "No punishments set.", "administration_warn_cleared_by": "cleared by {0}", @@ -797,5 +821,62 @@ "administration_prefix_current": "Prefix on this server is {0}", "administration_prefix_new": "Changed prefix on this server from {0} to {1}", "administration_defprefix_current": "Default bot prefix is {0}", - "administration_defprefix_new": "Changed Default bot prefix from {0} to {1}" + "administration_defprefix_new": "Changed Default bot prefix from {0} to {1}", + "xp_server_level": "Server Level", + "xp_level": "Level", + "xp_club": "Club", + "xp_xp": "Experience", + "xp_excluded": "{0} has been excluded from the XP system on this server.", + "xp_not_excluded": "{0} is no longer excluded from the XP system on this server.", + "xp_exclusion_list": "Exclusion List", + "xp_server_is_excluded": "This server is excluded.", + "xp_server_is_not_excluded": "This server is not excluded.", + "xp_excluded_roles": "Excluded Roles", + "xp_excluded_channels": "Excluded Channels", + "xp_level_up_channel": "Congratulations {0}, You've reached level {1}!", + "xp_level_up_dm": "Congratulations {0}, You've reached level {1} on {2} server!", + "xp_level_up_global": "Congratulations {0}, You've reached global level {1}!", + "xp_role_reward_cleared": "Level {0} will no longer reward a role.", + "xp_role_reward_added": "Users who reach level {0} will receive {1} role.", + "xp_role_rewards": "Role Rewards", + "xp_level_x": "Level {0}", + "xp_no_role_rewards": "No role reward on this page.", + "xp_server_leaderboard": "Server XP Leaderboard", + "xp_global_leaderboard": "Global XP Leaderboard", + "xp_modified": "Modified server XP of the user {0} by {1}", + "xp_club_create_error": "Failed creating the club. Make sure you're above level 5 and not a member of a club already.", + "xp_club_created": "Club {0} successfully created!", + "xp_club_not_exists": "That club doesn't exist.", + "xp_club_not_exists_owner": "You are not the owner or the admin of the club.", + "xp_club_applied": "You've applied for membership in {0} club.", + "xp_club_apply_error": "Error applying. You are either already a member of the club, or you don't meet the minimum level requirement, or you've been banned from this one.", + "xp_club_accepted": "Accepted user {0} to the club.", + "xp_club_accept_error": "User not found", + "xp_club_left": "You've left the club.", + "xp_club_not_in_club": "You are not in a club, or you're trying to leave the club you're the owner of.", + "xp_club_user_kick": "User {0} kicked from {1} club.", + "xp_club_user_kick_fail": "Error kicking. You're either not the club owner, or that user is not in your club.", + "xp_club_user_banned": "Banned user {0} from {1} club.", + "xp_club_user_ban_fail": "Failed to ban. You're either not the club owner, or that user is not in your club or applied to it.", + "xp_club_user_unbanned": "Unbanned user {0} in {1} club.", + "xp_club_user_unban_fail": "Failed to unban. You're either not the club owner, or that user is not in your club or applied to it.", + "xp_club_level_req_changed": "Changed club's level requirement to {0}", + "xp_club_level_req_change_error": "Failed changing level requirement.", + "xp_club_disbanded": "Club {0} has been disbanded", + "xp_club_disband_error": "Error. You are either not in a club, or you are not the owner of your club.", + "xp_club_icon_error": "Not a valid image url or you're not the club owner.", + "xp_club_icon_set": "New club icon set.", + "xp_club_bans_for": "Bans for {0} club", + "xp_club_apps_for": "Applicants for {0} club", + "xp_club_leaderboard": "Club leaderboard - page {0}", + "xp_club_admin_add": "{0} is now a club admin.", + "xp_club_admin_remove": "{0} is no longer club admin.", + "xp_club_admin_error": "Error. You are either not the owner of the club, or that user is not in your club.", + "nsfw_started": "Started. Reposting every {0}s.", + "nsfw_stopped": "Stopped reposting.", + "searches_feed_added": "Feed added.", + "searches_feed_not_valid": "Invalid link, or you're already following that feed on this server, or you've reached maximum number of feeds allowed.", + "searches_feed_out_of_range": "Index out of range.", + "searches_feed_removed": "Feed removed.", + "searches_feed_no_feed": "You haven't subscribed to any feeds on this server." } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.es-ES.json b/src/NadekoBot/_strings/ResponseStrings.es-ES.json index 448a164a..6853bb38 100644 --- a/src/NadekoBot/_strings/ResponseStrings.es-ES.json +++ b/src/NadekoBot/_strings/ResponseStrings.es-ES.json @@ -1,27 +1,4 @@ { - "clashofclans_base_already_claimed": "Esa base ya fue reclamada o destruida.", - "clashofclans_base_already_destroyed": "Esa base ya fue destruida.", - "clashofclans_base_already_unclaimed": "Esa base no ha sido reclamada.", - "clashofclans_base_destroyed": "**Destruida** la base #{0} en la guerrra contra {1}", - "clashofclans_base_unclaimed": "{0} ha **liberado** la base #{1} en la guerra contra {2}", - "clashofclans_claimed_base": "{0} reclamó la base #{1} en la guerra contra {2}", - "clashofclans_claimed_other": "@{0} Ya has reclamado la base #{1}. No puedes reclamar otra.", - "clashofclans_claim_expired": "La reclamación de @{0} en la guerra contra {1} ha expirado.", - "clashofclans_enemy": "Enemigo", - "clashofclans_info_about_war": "Información sobre la guerra contra {0}", - "clashofclans_invalid_base_number": "Número de base inválido.", - "clashofclans_invalid_size": "No es un tamaño de guerra válido.", - "clashofclans_list_active_wars": "Lista de guerras activas", - "clashofclans_not_claimed": "no reclamada", - "clashofclans_not_partic": "No estás participando en esa guerra.", - "clashofclans_not_partic_or_destroyed": "@{0} No estás participando en esa guerra o esa base ya fue destruida.", - "clashofclans_no_active_wars": "No hay guerra activa.", - "clashofclans_size": "Tamaño", - "clashofclans_war_already_started": "La guerra contra {0} ya ha iniciado.", - "clashofclans_war_created": "La guerra contra {0} ha sido creada.", - "clashofclans_war_ended": "La guerra contra {0} ha terminado.", - "clashofclans_war_not_exist": "Esa guerra no existe.", - "clashofclans_war_started": "¡La guerra contra {0} ha iniciado!", "customreactions_all_stats_cleared": "Las estadísticas de los comandos personalizados han sido borradas.", "customreactions_deleted": "Comando personalizado eliminado", "customreactions_insuff_perms": "Insuficientes permisos. Necesitas administrar propiamente el Bot para comandos globales y ser administrador del servidor para locales.", @@ -151,7 +128,6 @@ "administration_old_nick": "Apodo anterior", "administration_old_topic": "Tema anterior", "administration_perms": "Error. Creo que no tengo suficientes permisos.", - "administration_perms_reset": "Los permisos de este servidor han sido reiniciados.", "administration_prot_active": "Protecciones activas", "administration_prot_disable": "{0} ha sido **desactivado** en este servidor.", "administration_prot_enable": "{0} activado", @@ -243,7 +219,6 @@ "administration_sbdm": "Has sido advertido en el servidor {0}. \nRazón: {1}", "administration_user_unbanned": "Usuario desbloqueado:", "administration_migration_done": "¡Migración terminada!", - "adminsitration_migration_error": "Error al migrar, revisa la consola para más información.", "administration_presence_updates": "Actualizaciones de presencia", "administration_sb_user": "Usuario advertido", "gambling_awarded": "le ha regalado {0} a {1}", @@ -439,7 +414,6 @@ "music_rpl_enabled": "Repetición de listas de reproducción activada.", "music_set_music_channel": "Desde ahora publicaré la reproducción, finalización, pausa y eliminación de canciones en este canal.", "music_skipped_to": "Saltado a `{0}:{1}`", - "music_songs_shuffled": "Canciones reorganizadas.", "music_song_moved": "Canción movida", "music_time_format": "{0}h {1}m {2}s", "music_to_position": "A la posición", @@ -605,9 +579,6 @@ "utility_convert_not_found": "No pude convertir {0} a {1}: Unidades no encontradas", "utility_convert_type_error": "No pude convertir {0} a {1}: el tipo de unidad no es igual", "utility_created_at": "Creada el", - "utility_csc_join": "Entró al canal del cruce de servidor.", - "utility_csc_leave": "Salió del canal del cruce de servidor.", - "utility_csc_token": "Este es tu token CSC", "utility_custom_emojis": "Emoticones personales", "utility_error": "Error", "utility_features": "Características", @@ -754,7 +725,6 @@ "gambling_shop_role": "Obtendrás el rol {0}.", "gambling_type": "Tipo", "utility_clpa_next_update": "Próxima actualización en {0}", - "administration_global_perms_reset": "Los permisos globales han sido reiniciados.", "administration_gvc_disabled": "El canal de juego por voz ha sido desactivado.", "administration_gvc_enabled": "{0} ahora es un canal de juego por voz.", "administration_not_in_voice": "No estás en un canal de voz en este servidor.", @@ -782,5 +752,121 @@ "permissions_lgp_none": "No hay comandos o módulos bloqueados.", "gambling_animal_race_no_race": "¡Esta carrera de animales está llena!", "utility_cant_read_or_send": "No puedes leer ni enviar mensajes a ese canal.", - "utility_quotes_notfound": "No se han encontrado citas con la ID especificada." + "utility_quotes_notfound": "No se han encontrado citas con la ID especificada.", + "administration_prefix_current": "El prefijo de este servidor es {0}", + "administration_prefix_new": "Cambiado el prefijo en este servidor de {0} a {1}", + "administration_defprefix_current": "El prefijo por defecto es {0}", + "administration_defprefix_new": "Cambiado el prefijo por defecto de {0} a {1}", + "administration_bot_nick": "Apodo del bot cambiado a {0}", + "administration_user_nick": "Apodo del usuario {0} cambiado a {1}", + "administration_timezone_guild": "La zona horaria de este servidor es `{0}`", + "administration_timezone_not_found": "Zona horaria no encontrada. Usa \".timezones\" para ver la lista de husos horarios disponibles.", + "administration_timezones_available": "Zonas horarias disponibles", + "music_song_not_found": "No encontré esa canción.", + "searches_define_unknown": "No pude encontrar una definición para ese término.", + "utility_repeater_initial": "El mensaje de repetición inicial será enviado en {0}h y {1}m.", + "utility_verbose_errors_enabled": "Desde ahora el uso incorrecto de comandos mostrará errores.", + "utility_verbose_errors_disabled": "El uso incorrecto de comandos ya no mostrará errores.", + "permissions_perms_reset": "Los permisos de este servidor se han reiniciado.", + "permissions_trigger": "El permiso número #{0} {1} está impidiendo esta acción.", + "administration_migration_error": "Error al migrar, revisa la consola para más información.", + "searches_hex_invalid": "Color especificado no válido.", + "permissions_global_perms_reset": "Los permisos globales se han reiniciado.", + "help_module": "Módulo: {0}", + "games_hangman_stopped": "Juego del ahorcado detenido.", + "music_autoplaying": "Reproducción automática.", + "music_queue_stopped": "Reproducción detenida. Usa el comando {0} para resumir.", + "music_removed_song_error": "No existe una canción relacionada.", + "music_shuffling_playlist": "Mezclando canciones", + "music_songs_shuffle_enable": "Las canciones se mezclarán desde ahora.", + "music_songs_shuffle_disable": "Las canciones ya no se mezclarán.", + "music_song_skips_after": "Las canciones se omitirán después de {0}", + "administration_warnings_list": "Lista de usuarios advertidos en el servidor", + "customreactions_redacted_too_long": "Editado porque es muy largo.", + "nsfw_blacklisted_tag_list": "Lista de etiquetas bloqueadas:", + "nsfw_blacklisted_tag": "Una o más etiquetas usadas están en la lista negra", + "nsfw_blacklisted_tag_add": "La etiqueta NSFW {0} ahora está bloqueada.", + "nsfw_blacklisted_tag_remove": "La etiqueta NSFW {0} ya no está bloqueada.", + "gambling_waifu_gift": "Le regaló {0} a {1}", + "gambling_waifu_gift_shop": "Tienda de regalos para waifus", + "gambling_gifts": "Regalos", + "games_connect4_created": "Juego Conecta 4 creado. Esperando usuarios", + "games_connect4_player_to_move": "El movimiento es de: {0}", + "games_connect4_failed_to_start": "El juego no pudo iniciarse porque nadie ingresó.", + "games_connect4_draw": "Es un empate.", + "games_connect4_won": "{0} ganó el juego de Conecta 4 contra {1}.", + "games_nunchi_joined": "Inicia el juego de Nunchi. {0} usuarios en cola.", + "games_nunchi_ended": "Ha finalizado el juego de Nunchi. {0} gana.", + "games_nunchi_ended_no_winner": "El juego terminó sin ganadores.", + "games_nunchi_started": "Empieza el juego con {0} participantes.", + "games_nunchi_round_ended": "Ronda terminada. {0} queda fuera.", + "games_nunchi_round_ended_boot": "Terminó la ronda debido a que varios usuarios tardaron. Estos usuarios siguen en juego: {0}", + "games_nunchi_round_started": "La ronda de Nunchi empezó con {0} usuarios.Empiecen contando desde el {1}.", + "games_nunchi_next_number": "Número registrado. El último número fue {0}.", + "games_nunchi_failed_to_start": "No se pudo iniciar el juego debido a que no hay suficientes participantes.", + "games_nunchi_created": "Juego de Nunchi creado. Esperando usuarios.", + "music_sad_enabled": "Las canciones serán eliminadas de la cola cuando la reproducción termine.", + "music_sad_disabled": "Las canciones ya no serán eliminadas de la cola cuando la reproducción termine.", + "utility_stream_role_enabled": "Cuando un usuario del rol {0} empiece a transmitir, les daré el rol {1}", + "utility_stream_role_disabled": "Rol de transmisión desactivado.", + "utility_stream_role_kw_set": "Los que transmitan ahora requerirán la clave {0} para recibir el rol.", + "utility_stream_role_kw_reset": "Clave de rol de transmisión reiniciada.", + "utility_stream_role_bl_add": "El usuario {0} no recibirá jamás el rol de transmisor.", + "utility_stream_role_bl_add_fail": "El usuario {0} ya está en la lista negra.", + "utility_stream_role_bl_rem": "El usuario {0} ya no está en la lista negra.", + "utility_stream_role_bl_rem_fail": "El usuario {0} no está en la lista negra.", + "utility_stream_role_wl_add": "El usuario {0} recibirá el rol de transmisor aunque no tengan la clave en el título de la transmisión.", + "utility_stream_role_wl_add_fail": "El usuario {0} ya está en la lista blanca.", + "utility_stream_role_wl_rem": "El usuario {0} ya no está en la lista blanca.", + "utility_stream_role_wl_rem_fail": "El usuario {0} no está en la lista blanca.", + "utility_bot_config_edit_fail": "Configuración {0} fallida en el valor {1}.", + "utility_bot_config_edit_success": "El valor de {0} ha sido configurado a {1}.", + "customreactions_crca_disabled": "El comando personalizado con la ID {0} no se ejecutará a menos que la palabra esté al principio de la oración.", + "customreactions_crca_enabled": "El comando personalizado con la ID {0} se ejecutará si está en cualquier parte de la oración.\n", + "xp_server_level": "Nivel en el servidor", + "xp_level": "Nivel\n", + "xp_club": "Club\n", + "xp_xp": "Experiencia", + "xp_excluded": "{0} ha sido excluido del sistema de XP en este servidor.", + "xp_not_excluded": "{0} ya no será excluido del sistema de XP en este servidor.", + "xp_exclusion_list": "Lista de excluidos", + "xp_server_is_excluded": "Este servidor está excluido.", + "xp_server_is_not_excluded": "Este servidor no está excluido.", + "xp_excluded_roles": "Roles excluidos", + "xp_excluded_channels": "Canales excluidos", + "xp_level_up_channel": "¡Felicitaciones {0}, has alcanzado el nivel {1}!", + "xp_level_up_dm": "¡Felicitaciones {0}, has alcanzado el nivel {1} en el servidor {2}!", + "xp_level_up_global": "¡Felicitaciones {0}, has alcanzado el nivel global {1}!", + "xp_role_reward_cleared": "El nivel {0} ya no recompensará un rol.", + "xp_role_reward_added": "Los usuarios que alcancen el nivel {0} recibirán el rol {1}.", + "xp_role_rewards": "Roles de recompensa", + "xp_level_x": "Nivel {0}", + "xp_no_role_rewards": "No hay roles en esta página.", + "xp_server_leaderboard": "Marcador de XP del servidor", + "xp_global_leaderboard": "Marcador de XP global", + "xp_modified": "Modificada la XP del servidor del usuario {0} por {1}", + "xp_club_create_error": "No pude crear el club. Asegúrate de que eres al menos nivel 5 y que no estés ya en un club.", + "xp_club_created": "¡Club {0} creado!", + "xp_club_not_exists": "Ese club no existe.", + "xp_club_applied": "Has aplicado al club {0}.", + "xp_club_apply_error": "Error aplicando. O ya eres un miembro del club, o no cumples con el nivel mínimo, o has sido bloqueado de este club.", + "xp_club_accepted": "Se ha aceptado al usuario {0} en el club.", + "xp_club_accept_error": "No encuentro ese usuario.", + "xp_club_left": "Has salido del club.", + "xp_club_not_in_club": "No estás en ningún club o estás tratando de dejar un club que administras.", + "xp_club_user_kick": "El usuario {0} ha sido expulsado del club {1}.", + "xp_club_user_kick_fail": "Error expulsando. No eres el administrador del club o ese usuario no está en tu club.", + "xp_club_user_banned": "El usuario {0} ha sido bloqueado del club {1}.", + "xp_club_user_ban_fail": "No pude bloquearlo. No eres el administrador del club, o ese usuario no está en tu club.", + "xp_club_user_unbanned": "El usuario {0} ha sido desbloqueado en el club {1}.", + "xp_club_user_unban_fail": "No pude desbloquearlo. No eres el administrador del club, o ese usuario no está en tu club.", + "xp_club_level_req_changed": "Modificado el nivel requerido para ingresar al club a {0}", + "xp_club_level_req_change_error": "No pude cambiar el nivel requerido.", + "xp_club_disbanded": "El club Club {0} ha sido disuelto", + "xp_club_disband_error": "Error. No estás en un club o no eres el administrador de ese club.", + "xp_club_icon_error": "Esa imagen no es válida o no eres el administrador del club.", + "xp_club_icon_set": "Nuevo avatar de club configurado.", + "xp_club_bans_for": "Bloqueos en el club {0}", + "xp_club_apps_for": "Aplicantes al club {0}", + "xp_club_leaderboard": "Marcador de clubes - página {0}" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.fr-FR.json b/src/NadekoBot/_strings/ResponseStrings.fr-FR.json index bf1e517c..ef17903d 100644 --- a/src/NadekoBot/_strings/ResponseStrings.fr-FR.json +++ b/src/NadekoBot/_strings/ResponseStrings.fr-FR.json @@ -1,27 +1,4 @@ { - "clashofclans_base_already_claimed": "Cette base a déjà été revendiquée ou détruite.", - "clashofclans_base_already_destroyed": "Cette base est déjà détruite.", - "clashofclans_base_already_unclaimed": "Cette base n'est pas revendiquée.", - "clashofclans_base_destroyed": "Base #{0} **DETRUITE** dans une guerre contre {1}", - "clashofclans_base_unclaimed": "{0} a **ABANDONNÉ** la base #{1} dans une guerre contre {2}", - "clashofclans_claimed_base": "{0} a revendiqué une base #{1} dans une guerre contre {2}", - "clashofclans_claimed_other": "@{0} Vous avez déjà revendiqué la base #{1}. Vous ne pouvez pas en revendiquer une nouvelle.", - "clashofclans_claim_expired": "La demande de la part de @{0} pour une guerre contre {1} a expiré.", - "clashofclans_enemy": "Ennemi", - "clashofclans_info_about_war": "Informations concernant la guerre contre {0}", - "clashofclans_invalid_base_number": "Numéro de base invalide.", - "clashofclans_invalid_size": "La taille de la guerre n'est pas valide.", - "clashofclans_list_active_wars": "Liste des guerres en cours", - "clashofclans_not_claimed": "non réclamé", - "clashofclans_not_partic": "Vous ne participez pas a cette guerre.", - "clashofclans_not_partic_or_destroyed": "@{0} Vous ne participez pas à cette guerre ou la base a déjà été détruite.", - "clashofclans_no_active_wars": "Aucune guerre en cours.", - "clashofclans_size": "Taille", - "clashofclans_war_already_started": "La guerre contre {0} a déjà commencé!", - "clashofclans_war_created": "La guerre contre {0} commence!", - "clashofclans_war_ended": "La guerre contre {0} est terminée.", - "clashofclans_war_not_exist": "Cette guerre n'existe pas.", - "clashofclans_war_started": "La guerre contre {0} a éclaté !", "customreactions_all_stats_cleared": "Statistiques de réactions personnalisées effacées.", "customreactions_deleted": "Réaction personnalisée supprimée", "customreactions_insuff_perms": "Permissions insuffisantes. Nécessite d'être le propriétaire du Bot pour avoir les réactions personnalisées globales, et Administrateur pour les réactions personnalisées du serveur.", @@ -32,16 +9,16 @@ "customreactions_no_found_id": "Aucune réaction personnalisée ne correspond à cet ID.", "customreactions_response": "Réponse", "customreactions_stats": "Statistiques des Réactions Personnalisées", - "customreactions_stats_cleared": "Statistiques effacées pour {0} réaction personnalisée.", + "customreactions_stats_cleared": "Statistiques effacées pour {0} réactions personnalisées.", "customreactions_stats_not_found": "Pas de statistiques pour ce déclencheur trouvées, aucune action effectuée.", "customreactions_trigger": "Déclencheur", "nsfw_autohentai_stopped": "Autohentai arrêté.", - "nsfw_not_found": "Aucun résultat trouvé.", + "nsfw_not_found": "Aucuns résultats trouvés.", "pokemon_already_fainted": "{0} est déjà inconscient.", "pokemon_already_full": "{0} a tous ses PV.", "pokemon_already_that_type": "Votre type est déjà {0}", "pokemon_attack": "Vous avez utilisé {0}{1} sur {2}{3} pour {4} dégâts.", - "pokemon_cant_attack_again": "Vous ne pouvez pas attaquer de nouveau sans représailles !", + "pokemon_cant_attack_again": "Vous ne pouvez pas attaquer de nouveau avant les représailles !", "pokemon_cant_attack_yourself": "Vous ne pouvez pas vous attaquer vous-même.", "pokemon_fainted": "{0} s'est évanoui!", "pokemon_healed": "Vous avez soigné {0} avec un {1}", @@ -151,7 +128,6 @@ "administration_old_nick": "Ancien pseudonyme", "administration_old_topic": "Ancien sujet", "administration_perms": "Erreur. Je ne dois sûrement pas posséder les permissions suffisantes.", - "administration_perms_reset": "Les permissions pour ce serveur ont été réinitialisées.", "administration_prot_active": "Protections actives", "administration_prot_disable": "{0} a été **désactivé** sur ce serveur.", "administration_prot_enable": "{0} Activé", @@ -243,7 +219,6 @@ "administration_sbdm": "Vous avez été expulsé du serveur {0}.\nRaison: {1}", "administration_user_unbanned": "Utilisateur débanni", "administration_migration_done": "Migration effectuée!", - "adminsitration_migration_error": "Erreur lors de la migration, veuillez consulter la console pour plus d'informations.", "administration_presence_updates": "Présences mises à jour.", "administration_sb_user": "Utilisateur expulsé.", "gambling_awarded": "a récompensé {0} à {1}", @@ -285,7 +260,7 @@ "help_cmdlist_donate": "Vous pouvez supporter ce projet sur Patreon <{0}> ou via Paypal <{1}>", "help_cmd_and_alias": "Commandes et alias", "help_commandlist_regen": "Liste des commandes rafraîchie.", - "help_commands_instr": "Écrivez `{0}h NomDeLaCommande` pour voir l'aide spécifique à cette commande. Ex: `{0}h >8ball`", + "help_commands_instr": "Écrivez `{0}h NomDeLaCommande` pour voir l'aide spécifique à cette commande. Ex: `{0}h {0}8ball`", "help_command_not_found": "Impossible de trouver cette commande. Veuillez vérifier qu'elle existe avant de réessayer.", "help_desc": "Description", "help_donate": "Vous pouvez supporter le projet NadekoBot\nsur Patreon <{0}>\npar Paypal <{1}>\nN'oubliez pas de mettre votre nom discord ou ID dans le message.\n\n**Merci** ♥️", @@ -439,7 +414,6 @@ "music_rpl_enabled": "Lecture en boucle activée.", "music_set_music_channel": "Je vais désormais afficher les pistes en cours, en pause, terminées et supprimées dans ce salon.", "music_skipped_to": "Saut à `{0}:{1}`", - "music_songs_shuffled": "Pistes mélangées.", "music_song_moved": "Piste déplacée", "music_time_format": "{0}h {1}m {2}s", "music_to_position": "À la position", @@ -605,9 +579,6 @@ "utility_convert_not_found": "Impossible de convertir {0} en {1}: unités non trouvées", "utility_convert_type_error": "Impossible de convertir {0} en {1} : les types des unités ne sont pas compatibles.", "utility_created_at": "Créé le", - "utility_csc_join": "Salon inter-serveur rejoint.", - "utility_csc_leave": "Salon inter-serveur quitté.", - "utility_csc_token": "Voici votre jeton CSC", "utility_custom_emojis": "Emojis personnalisées", "utility_error": "Erreur", "utility_features": "Fonctionnalités", @@ -754,7 +725,6 @@ "gambling_shop_role": "Vous recevrez le rôle {0}.", "gambling_type": "Type", "utility_clpa_next_update": "Prochaine mise à jour dans {0}", - "administration_global_perms_reset": "Permissions globales réinitialisées.", "administration_gvc_disabled": "Les salons vocaux de jeux ont été désactivés sur ce serveur.", "administration_gvc_enabled": "{0} est maintenant un salon vocal de jeu.", "administration_not_in_voice": "Vous n'êtes pas connectés à un salon vocal sur ce serveur.", @@ -786,5 +756,117 @@ "administration_prefix_current": "Le préfixe sur ce serveur est {0}", "administration_prefix_new": "Le préfixe sur ce serveur a changé de {0} à {1}", "administration_defprefix_current": "Le préfixe par défaut du bot est {0}", - "administration_defprefix_new": "Le préfixe par défaut du bot a changé de {0} à {1}" + "administration_defprefix_new": "Le préfixe par défaut du bot a changé de {0} à {1}", + "administration_bot_nick": "Le surnom du bot est maintenant {1}.", + "administration_user_nick": "Le surnom de l’utilisateur {0} est maintenant {1}.", + "administration_timezone_guild": "Le fuseau horaire de cette guilde est '{0}'", + "administration_timezone_not_found": "Fuseau horaire introuvable. Utilisez la commande \"timezones\" pour voir la liste des fuseaux disponibles.", + "administration_timezones_available": "Fuseaux horaires disponibles", + "music_song_not_found": "Aucune chanson trouvée.", + "searches_define_unknown": "La définition pour se terme est introuvable.", + "utility_repeater_initial": "Le message répétitif initial sera envoyé dans {0}h et {1}min.", + "utility_verbose_errors_enabled": "Les commandes utilisées incorrectement afficheront les erreurs.", + "utility_verbose_errors_disabled": "Les commandes utilisées incorrectement n'afficheront plus d'erreurs.", + "permissions_perms_reset": "Les permissions ont été réinitialisées sur ce serveur.", + "permissions_trigger": "Action impossible en raison de la permission #{0} {1}.", + "administration_migration_error": "Erreur lors de la migration, veuillez vérifier la console pour plus d'informations.", + "searches_hex_invalid": "Couleur spécifiée invalide.", + "permissions_global_perms_reset": "Les permissions globales ont été réinitialisées.", + "help_module": "Module : {0}", + "games_hangman_stopped": "Partie de pendu suspendue.", + "music_autoplaying": "Écoute automtique.", + "music_queue_stopped": "Le lecteur est arrêté. Utilisez la commande {0} pour relancer le lecteur.", + "music_removed_song_error": "La chanson avec cet index n'existe pas", + "music_shuffling_playlist": "Lecture aléatoire des chansons", + "music_songs_shuffle_enable": "Les chansons seront désormais lues de façon aléatoire.", + "music_songs_shuffle_disable": "Les chansons ne seront plus désormais lues de façon aléatoire.", + "music_song_skips_after": "La chanson passera à la suivante après {0}", + "administration_warnings_list": "Liste de tous les utilisateurs ayant un avertissement sur le serveur.", + "customreactions_redacted_too_long": "Édité car c'est trop long.", + "nsfw_blacklisted_tag_list": "Liste des tags en liste noire:", + "nsfw_blacklisted_tag": "Un ou plusieurs tags que vous avez utilisés sont en liste noire", + "nsfw_blacklisted_tag_add": "Le tag nsfw {0} est maintenant en liste noire.", + "nsfw_blacklisted_tag_remove": "Le tag nsfw {0} n'est plus en liste noire.", + "gambling_waifu_gift": "Donné {0} à {1}", + "gambling_waifu_gift_shop": "Boutique cadeau pour Waifu", + "gambling_gifts": "Cadeaux", + "games_connect4_created": "Partie de Connect4 créée. En attente de joueurs.", + "games_connect4_player_to_move": "Joueur en cours: {0}", + "games_connect4_failed_to_start": "La partie de Connect4 est annulée car personne ne l'a rejoint.", + "games_connect4_draw": "La partie de Connect4 s'est terminée en égalitée.", + "games_connect4_won": "{0} a gagné la partie de Connect4 contre {1}.", + "games_nunchi_joined": "Partie de Nunchi rejoint. {0} joueurs l'ont rejoint jusqu'à présent.", + "games_nunchi_ended": "Partie de Nunchi terminée. {0} a gagné", + "games_nunchi_ended_no_winner": "La partie de Nunchi s'est terminée sans gagnant.", + "games_nunchi_started": "Partie de Nunchi démarrée avec {0} participants", + "games_nunchi_round_ended": "Tour de Nunchi est terminé. {0} est hors de la partie.", + "games_nunchi_round_ended_boot": "Le tour de Nunchi est terminé dut à l'inactivité de quelques joueurs. Ces joueurs sont toujours dans la partie: {0}", + "games_nunchi_round_started": "Tour de Nunchi démarré avec {0} joueurs. Commencez à compter a partir du nombre {1}", + "games_nunchi_next_number": "Nombre enregistré. Le dernier nombre était {0}.", + "games_nunchi_failed_to_start": "La partie de Nunchi n'a pas pu démarré car il n'y a pas assez de participants.", + "games_nunchi_created": "Partie de Nunchi créée. En attente de joueursé", + "music_sad_enabled": "Les pistes seront effacés de la liste de lecture lorsqu'elles seront terminés.", + "music_sad_disabled": "Les pistes ne seront plus effacés de la liste de lecture lorsqu'elles seront terminés.", + "utility_stream_role_enabled": "Lorsqu'un utilisateur du rôle {0} commencera à streamer, je lui donnerai le rôle {1}.", + "utility_stream_role_disabled": "La fonctionnalité du rôle streamer a été désactivée", + "utility_stream_role_kw_set": "Les streamers requièrent le mot clé {0} afin de recevoir le rôle.", + "utility_stream_role_kw_reset": "Mot clé de stream réinitialisé.", + "utility_stream_role_bl_add": "L'utilisateur {0} ne recevra jamais le rôle streameur.", + "utility_stream_role_bl_add_fail": "L'utilisateur {0} est déjà sur liste noire.", + "utility_stream_role_bl_rem": "L'utilisateur {0} n'est désormais plus sur liste noire.", + "utility_stream_role_bl_rem_fail": "L'utilisateur {0} n'est pas sur liste noire.", + "utility_stream_role_wl_add": "L'utilisateur {0} recevra le rôle streameur même s'ils n'ont pas le mot-clé dans le titre sur stream.", + "utility_stream_role_wl_add_fail": "L'utilisateur {0} est déjà sur liste blanche.", + "utility_stream_role_wl_rem": "L'utilisateur {0} n'est désormais plus sur liste blanche.", + "utility_stream_role_wl_rem_fail": "L'utilisateur {0} n'est pas sur liste blanche.", + "utility_bot_config_edit_fail": "Le réglage {0} à échoué à la valeur {1}", + "utility_bot_config_edit_success": "La valeur de {0} a été mise à {1}", + "customreactions_crca_disabled": "La réaction personnalisée avec l'id {0} ne se déclenchera plus sauf si le mot déclencheur est au début de la phrase.", + "customreactions_crca_enabled": "La réaction personnalisée avec l'id {0} se déclenchera si elle est à n'importe quel endroit dans la phrase.", + "xp_server_level": "Niveau sur le serveur", + "xp_level": "Niveau", + "xp_club": "Club", + "xp_xp": "Expérience", + "xp_excluded": "{0} a été exclu du système XP sur ce serveur.", + "xp_not_excluded": "{0} n'est plus exclu du système XP sur ce serveur.", + "xp_exclusion_list": "Liste des exclus", + "xp_server_is_excluded": "Ce serveur est exclu.", + "xp_server_is_not_excluded": "Ce serveur n'est pas exclu.", + "xp_excluded_roles": "Rôles exclus.", + "xp_excluded_channels": "Salons exclus.", + "xp_level_up_channel": "Félicitations {0}, vous avez atteint le niveau {1}!", + "xp_level_up_dm": "Félicitations {0}, vous avez atteint le niveau {1} sur le serveur {2}!", + "xp_level_up_global": "Félicitations {0}, vous avez atteint le niveau global {1}!", + "xp_role_reward_cleared": "Le niveau {0} ne récompensera plus d'un rôle.", + "xp_role_reward_added": "Les utilisateurs qui atteignent le niveau {0} recevront le rôle {1}.", + "xp_role_rewards": "Rôles en récompenses", + "xp_level_x": "Niveau {0}", + "xp_no_role_rewards": "Aucun rôle en récompense sur cette page.", + "xp_server_leaderboard": "Tableau de classement de l'XP serveur", + "xp_global_leaderboard": "Tableau de classement de l'XP global", + "xp_modified": "L'XP serveur a été modifiée pour l'utilisateur {0} par {1}", + "xp_club_create_error": "Échec de la création du club. Vérifiez que vous êtes supérieur au niveau 5 et n'êtes pas déjà membre d'un club.", + "xp_club_created": "Le club {0} a été créé avec succès!", + "xp_club_not_exists": "Ce club n'existe pas.", + "xp_club_applied": "Vous avez postulé pour être membre dans le club {0}.", + "xp_club_apply_error": "Erreur lors de la postulation. Vous êtes soit déjà membre du club, soit vous n'avez pas le niveau minimum requis pour postuler, soit vous avez été bannis de celui-ci.", + "xp_club_accepted": "L'utilisateur {0} a été accepté dans le club.", + "xp_club_accept_error": "Utilisateur non trouvé", + "xp_club_left": "Vous avez quitté le club.", + "xp_club_not_in_club": "Vous n'êtes pas dans un club, ou vous essayez de quitter le club dont vous êtes le propriétaire.", + "xp_club_user_kick": "L'utilisateur {0} a été expulsé du club {1}", + "xp_club_user_kick_fail": "Erreur lors de l'expulsion. Vous n'êtes soit pas le propriétaire du club, soit l'utilisateur n'est pas dans votre club.", + "xp_club_user_banned": "L'utilisateur {0} a été banni du club {1}", + "xp_club_user_ban_fail": "Échec du bannissement. Vous n'êtes soit pas le propriétaire du club, soit l'utilisateur n'est pas dans votre club ou n'y a pas postulé. ", + "xp_club_user_unbanned": "L'utilisateur {0} a été gracié du club {0}", + "xp_club_user_unban_fail": "Échec de la grâce. Vous n'êtes soit pas le propriétaire du club, soit l'utilisateur n'est pas dans votre club ou n'y a pas postulé.", + "xp_club_level_req_changed": "Changement du niveau requis pour le club à {0}", + "xp_club_level_req_change_error": "Échec dans le changement du niveau requis.", + "xp_club_disbanded": "Le club {0} a été dissous", + "xp_club_disband_error": "Erreur. Vous n'êtes soit pas dans un club, soit pas le propriétaire de votre club.", + "xp_club_icon_error": "L'url de l'image n'est pas valide ou vous n'êtes pas le propriétaire du club.", + "xp_club_icon_set": "Nouvelle icône du club définie.", + "xp_club_bans_for": "Bannis du club {0}", + "xp_club_apps_for": "Postulants pour le club {0}", + "xp_club_leaderboard": "Tableau de classement du club - page {0}" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.he-IL.json b/src/NadekoBot/_strings/ResponseStrings.he-IL.json index 39fe3af7..33e31ee5 100644 --- a/src/NadekoBot/_strings/ResponseStrings.he-IL.json +++ b/src/NadekoBot/_strings/ResponseStrings.he-IL.json @@ -23,7 +23,7 @@ "clashofclans_war_not_exist": "המלחמה הזאת לא קיימת.", "clashofclans_war_started": "מלחמה נגד {0} התחילה!", "customreactions_all_stats_cleared": "כל הסטטיסטיקות של התגובות המותאמות נמחקו.", - "customreactions_deleted": "תגובות מתאם נמחקו", + "customreactions_deleted": "תגובות מותאמות אישית נמחקו", "customreactions_insuff_perms": "אין מספיק רשות, מתחייבת בעלות של הבוט בשביל תגובות מתאם גלובליות, ומנהג בשביל תגובות מתאם של הרשת.", "customreactions_list_all": "רשימה של כל התגובות מתאם", "customreactions_name": "תגובות מתאם", @@ -151,7 +151,6 @@ "administration_old_nick": "כינוי ישן", "administration_old_topic": "נושא ישן", "administration_perms": "שגיאה. כנראה אין לי את האישורים המתאימים.", - "administration_perms_reset": "ההרשאות לשרת זה התחדשו.", "administration_prot_active": "הגנות פועלות", "administration_prot_disable": "{0} **הופסק** בשרת זה.", "administration_prot_enable": "{0} אופשר.", @@ -185,18 +184,18 @@ "administration_self_assign_excl": "תפקידי הקצאה עצמית הם עכשיו בלעדי!", "administration_self_assign_list": "יש {0} תפקידים הניתנים להקצאה עצמית", "administration_self_assign_not": "תפקיד זה אינו ניתן לשינוי עצמי.", - "administration_self_assign_not_have": "אין לך {0} תפקיד.", + "administration_self_assign_not_have": "אין לך את תפקיד {0}.", "administration_self_assign_no_excl": "תפקידי הקצאה עצמית אינם עכשיו בלעדיים!", "administration_self_assign_perms": "אני לא יכול להוסיף לך את התפקיד הזה. `אני לא יכול להוסיף תפקידים לבעלים או לתפקידים אחרים מעבר לתפקיד שלי בהיררכיה של התפקיד`", "administration_self_assign_rem": "{0} הוסר מרשימת תפקידי ההקצאה העצמית.", - "administration_self_assign_remove": "אין לך את התפקיד {0}.", + "administration_self_assign_remove": "כבר אין לך את התפקיד {0}.", "administration_self_assign_success": "יש לך את התפקיד {0}.", "administration_setrole": "נוסף בהצלחה תפקיד {0} למשתמש {1}", "administration_setrole_err": "הוספת התפקיד נכשלה. אין לי הרשאות מספיקות.", - "administration_set_avatar": "תמונת הפרופיל הוספה.", + "administration_set_avatar": "תמונת הפרופיל שונתה בהצלחה!", "administration_set_channel_name": "שם הערוץ החדש נקבע.", "administration_set_game": "משחק חדש נוצר.", - "administration_set_stream": "סטרים חדש מוגדר!", + "administration_set_stream": "שידור חדש מוגדר!", "administration_set_topic": "נושא חדש לערוץ מוגדר.", "administration_shard_reconnected": "חרס {0} התחבר מחדש", "administration_shard_reconnecting": "חרד {0} מתחבר מחדש.", @@ -243,7 +242,6 @@ "administration_sbdm": "אתה קיבלת איסור רך מהסרבר {0}.\nסיבה: {1}", "administration_user_unbanned": "משתמש ונאסר", "administration_migration_done": "נדידה גמורה!", - "adminsitration_migration_error": "שגיעה בזמן הנדידה, תבדוק את ה מסוף בקרה של הבוט בשביל יותר מידע.\n\n ", "administration_presence_updates": "עדכון נוכחות", "administration_sb_user": "משתמש קיבל איסור רך.", "gambling_awarded": "העניק {0} ל{1}", @@ -279,7 +277,7 @@ "gambling_tails": "זנב", "gambling_take": "בהצלחה נלקח {0} מ{1}", "gambling_take_fail": "לא הצליח לקחת {0} מ{1} משום שלמשתמש אין כל כך הרבה {2}!", - "help_back_to_toc": "חזר לToC", + "help_back_to_toc": "חזרה לToC", "help_bot_owner_only": "מנהל של הבוט בלבד", "help_channel_permission": "מתחייבת {0} רשות ערוץ.", "help_cmdlist_donate": "אתה יכול לתמוח בפרוייקט על Patreon: <{0}> או Paypal: <{1}>", @@ -439,7 +437,6 @@ "music_rpl_enabled": "הפעלת הפלייליסט חזרה.", "music_set_music_channel": "כעת אשמע את הפלט, השלים, השהה והסרתי שירים בערוץ זה.", "music_skipped_to": "דלג ל `{0}:{1}`", - "music_songs_shuffled": "שירים דשדשו", "music_song_moved": "השיר זז", "music_time_format": "{0} שעות {1} דקות {2} שניות", "music_to_position": "למקום", @@ -605,9 +602,6 @@ "utility_convert_not_found": "לא ניתן להמיר את {0} ל- {1}: יחידות לא נמצאות", "utility_convert_type_error": "לא ניתן להמיר {0} ל- {1}: סוגי יחידות אינם שווים", "utility_created_at": "נוצר ב", - "utility_csc_join": "הצטרף לערוץ השרתים הצולבים.", - "utility_csc_leave": "שמאל לחצות ערוץ השרת.", - "utility_csc_token": "זה אסימון CSC שלך", "utility_custom_emojis": "אימו'זי מותאם אישית", "utility_error": "שגיאה", "utility_features": "מאפיינים", @@ -744,7 +738,7 @@ "utility_clpa_fail_wait": "אתה צריך לחכות כמה שעות לאחר ביצוע התחייבות שלך, אם לא, נסה שוב מאוחר יותר.", "utility_clpa_fail_wait_title": "חכה קצת", "utility_clpa_success": "קיבלת {0} תודה על תמיכתך בפרויקט!", - "utility_clpa_too_early": "תגמולים יכולים להיות טענה על או לאחר 5 של כל חודש.", + "utility_clpa_too_early": "ניתן לקבל את הפרסים ב5 לכל חודש או אחרי ה5 לחודש.", "searches_time": "הזמן ב {0} הוא {1} - {2}", "administration_rh": "הגדר את התצוגה של תפקיד הגילדה {0} ל- {1}.", "gambling_name": "שם", @@ -754,7 +748,6 @@ "gambling_shop_role": "אתה תקבל {0} תפקיד.", "gambling_type": "סוג", "utility_clpa_next_update": "העדכון הבא ב- {0}", - "administration_global_perms_reset": "הרשאות גלובליות אופסו.", "administration_gvc_disabled": "התכונה 'ערוץ קול' של המשחק הושבתה בשרת זה.", "administration_gvc_enabled": "{0} הוא ערוץ קול של משחקים כעת.", "administration_not_in_voice": "אינך נמצא בערוץ קול בשרת זה.", @@ -768,7 +761,7 @@ "gambling_shop_item_wrong_type": "ערך חנות זה אינו תומך בהוספת פריטים.", "gambling_shop_list_item_added": "הפריט נוסף בהצלחה.", "gambling_shop_list_item_not_unique": "פריט זה כבר נוסף.", - "gambling_shop_purchase": "רכישה בסרבר {0}", + "gambling_shop_purchase": "רכישה בשרת {0}", "gambling_shop_role_not_found": "התפקיד שנמכר כבר לא קיים.", "gambling_shop_role_purchase": "רכשת בהצלחה את תפקיד {0}.", "gambling_shop_role_purchase_error": "שגיאה בהקצאת תפקיד. הרכישה שלך הוחזרה.", @@ -780,7 +773,36 @@ "permissions_gmod_add": "מודול {0} הושבת בכל השרתים.", "permissions_gmod_remove": "מודול {0} הופעל בכל השרתים.", "permissions_lgp_none": "אין פקודות או מודולים חסומים.", - "gambling_animal_race_no_race": "מרוץ החיות מלאה!", + "gambling_animal_race_no_race": "מרוץ החיות מלא!", "utility_cant_read_or_send": "את\\ה לא יכול לשלוח הודאות מ\\לערוץ זה.", - "utility_quotes_notfound": "אין ציטוטים המתאימים למס' זהות זה." + "utility_quotes_notfound": "אין ציטוטים המתאימים למס' זהות זה.", + "administration_prefix_current": "", + "administration_prefix_new": "", + "administration_defprefix_current": "", + "administration_defprefix_new": "", + "administration_bot_nick": "כינוי הבוט שונה ל {0}", + "administration_user_nick": "כינוי של המשתמש {0} שונה ל{1}", + "administration_timezone_guild": "אזור הזמן של אגודה זו הוא '{0}'", + "administration_timezone_not_found": "אזור זמן לא נמצא. השתמש בפקודה \"timezones\" כדי לראות רשימה של אזורי הזמן.", + "administration_timezones_available": "אזורי זמן זמינים", + "music_song_not_found": "שיר לא נמצא.", + "searches_define_unknown": "לא ניתן למצוא את ההגדרה של מושג זה.", + "utility_repeater_initial": "", + "utility_verbose_errors_enabled": "פקודות לא נכונות יציגו הערת שגיאה שוב.", + "utility_verbose_errors_disabled": "פקודות לא נכונות לא יציגו הערת שגיאה שוב.", + "permissions_perms_reset": "הרשאות בשביל שרת זה אופסו.", + "permissions_trigger": "", + "administration_migration_error": "שגיאה בזמן מעבר. למידע נוסף, ראה את הקונסולה של הבוט.", + "searches_hex_invalid": "צבע לא קיים", + "permissions_global_perms_reset": "הרשאות כלליות התאפסו", + "help_module": "", + "games_hangman_stopped": "", + "music_autoplaying": "", + "music_queue_stopped": "", + "music_removed_song_error": "", + "music_shuffling_playlist": "", + "music_songs_shuffle_enable": "", + "music_songs_shuffle_disable": "", + "music_song_skips_after": "", + "administration_warnings_list": "" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.id-ID.json b/src/NadekoBot/_strings/ResponseStrings.id-ID.json index e8a5ee2c..af888820 100644 --- a/src/NadekoBot/_strings/ResponseStrings.id-ID.json +++ b/src/NadekoBot/_strings/ResponseStrings.id-ID.json @@ -151,7 +151,6 @@ "administration_old_nick": "Nama panggilan lama", "administration_old_topic": "Topik lama", "administration_perms": "Error. Kemungkinan besar saya tidak punya izin yang cukup.", - "administration_perms_reset": "Izin untuk server ini telah diatur ulang.", "administration_prot_active": "Perlindungan aktif", "administration_prot_disable": "{0} telah **dinonaktifkan** pada server ini.", "administration_prot_enable": "{0} diaktifkan", @@ -243,7 +242,6 @@ "administration_sbdm": "Anda telah diban halus dari server {0}.\nAlasan: {1}", "administration_user_unbanned": "Pengguna telah di unban", "administration_migration_done": "Migrasi selesai!", - "adminsitration_migration_error": "Error ketika migrasi, cek konsol bot untuk informasi lebih lanjut.", "administration_presence_updates": "Pembaharuan kehadiran", "administration_sb_user": "Pengguna diban halus", "gambling_awarded": "Telah menyerahkan {0} ke {1}", @@ -439,7 +437,6 @@ "music_rpl_enabled": "Pengulangan playlist diaktifkan", "music_set_music_channel": "Sekarang saya akan mengeluarkan lagu yang sedang diputar, yang telah diputar, yang dihentikan dan menghapus lagu-lagu di channel ini.", "music_skipped_to": "Lewat ke `{0}:{1}`", - "music_songs_shuffled": "Lagu diacak", "music_song_moved": "Lagu dipindahkan", "music_time_format": "{0}jam {1}menit {2}detik", "music_to_position": "Pada posisi", @@ -605,9 +602,6 @@ "utility_convert_not_found": "Tidak bisa mengubah {0} menjadi {1}: satuan tidak ditemukan", "utility_convert_type_error": "Tidak dapat mengubah {0} ke {1}: tipe untuk tidak sama", "utility_created_at": "Dibuat di", - "utility_csc_join": "Mengikuti saluran silang server.", - "utility_csc_leave": "Saluran silang server ditinggalkan.", - "utility_csc_token": "Ini adalah token CSC anda", "utility_custom_emojis": "emoji kustom", "utility_error": "Kesalahan", "utility_features": "Ciri - ciri", @@ -754,7 +748,6 @@ "gambling_shop_role": "Anda akan mendapat peran {0}", "gambling_type": "Tipe", "utility_clpa_next_update": "Update selanjutnya dalam {0}", - "administration_global_perms_reset": "Ijin global telah diulang.", "administration_gvc_disabled": "Fitur channel suara permainan telah dinonaktifkan di server ini. ", "administration_gvc_enabled": "{0} sekarang adalah saluran suara permainan.", "administration_not_in_voice": "Anda tidak ada di saluran suara server ini.", @@ -782,5 +775,34 @@ "permissions_lgp_none": "Tidak ada modul atau perintah yang di larang.", "gambling_animal_race_no_race": "Balapan hewan ini penuh!", "utility_cant_read_or_send": "Anda tidak bisa membaca atau mengirim ke channel itu.", - "utility_quotes_notfound": "Tidak ada kutipan cocok dengan ID kutipan yang ditentukan." + "utility_quotes_notfound": "Tidak ada kutipan cocok dengan ID kutipan yang ditentukan.", + "administration_prefix_current": "Awalan di server in adalah {0}", + "administration_prefix_new": "Awalan di server ini diubah dari {0} ke {1}", + "administration_defprefix_current": "Awalan default adalah {0}", + "administration_defprefix_new": "Awalan default bot diubah dari {0} ke {1}", + "administration_bot_nick": "Nama panggilan bot diubah menjadi {0}", + "administration_user_nick": "Nama panggilan pengguna diubah dari {0} menjadi {1}.", + "administration_timezone_guild": "Timezone untuk kelompok ini adalah '{0}'", + "administration_timezone_not_found": "Timezone tidak ditemukan. Gunakan perintah \"timezones\" untuk melihat daftar timezone yang tersedia.", + "administration_timezones_available": "Timezone yang tersedia", + "music_song_not_found": "Lagu tidak ditemukan.", + "searches_define_unknown": "Tidak dapat menemukan definisi dari istilah itu.", + "utility_repeater_initial": "Awal pesan mengulang akan dikirim dalam {0}h dan {1} menit lagi.", + "utility_verbose_errors_enabled": "Perintah salah akan sekarang menunjukkan error.", + "utility_verbose_errors_disabled": "Perintah salah tidak akan menunukkan error.", + "permissions_perms_reset": "Ijin untuk server telah direset.", + "permissions_trigger": "Ijin nomor #{0} {1} sedang memberhentikan aksi ini.", + "administration_migration_error": "Error saat memindah, cek konsol bot untuk informasi selanjutnya.", + "searches_hex_invalid": "Warna yang dipilih tidak sah.", + "permissions_global_perms_reset": "Ijin global telah direset.", + "help_module": "Modul : {0}", + "games_hangman_stopped": "Permainan Hangman diberhentikan.", + "music_autoplaying": "Bermain Otomatis.", + "music_queue_stopped": "Pemain diberhentikan. Gunakan perintah{0} untuk menyalakan.", + "music_removed_song_error": "Lagu di indeks itu tidak ada.", + "music_shuffling_playlist": "Mengacak lagu - lagu.", + "music_songs_shuffle_enable": "Lagu - lagu akan teracak mulai sekarang.", + "music_songs_shuffle_disable": "Lagu - lagu tidak akan teracak.", + "music_song_skips_after": "Lagu - lagu akan melompat setelah {0}", + "administration_warnings_list": "Daftar pengguna yang diperingatkan." } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.it-IT.json b/src/NadekoBot/_strings/ResponseStrings.it-IT.json index f77d54f0..ac53c9b2 100644 --- a/src/NadekoBot/_strings/ResponseStrings.it-IT.json +++ b/src/NadekoBot/_strings/ResponseStrings.it-IT.json @@ -151,7 +151,6 @@ "administration_old_nick": "Nickname precedente", "administration_old_topic": "Discussione precedente", "administration_perms": "Errore. Probabilmente non dispongo dei permessi necessari.", - "administration_perms_reset": "I permessi di questo server sono stati reimpostati.", "administration_prot_active": "Protezioni attive", "administration_prot_disable": "{0} è stato **disattivato** in questo server.", "administration_prot_enable": "{0} Attivato", @@ -243,7 +242,6 @@ "administration_sbdm": "Sei statto cacciato da {0} server.\nMotivo : {1}", "administration_user_unbanned": "Utente sbannato", "administration_migration_done": "Trasferimento completato!", - "adminsitration_migration_error": "Errore durante il trasferimento, guarda la console del bot per più informazioni.", "administration_presence_updates": "Aggiornamenti sulla presenza", "administration_sb_user": "Utente ammonito", "gambling_awarded": "ha regalato {0} to {1}", @@ -439,7 +437,6 @@ "music_rpl_enabled": "Ripeti playlist abilitato.", "music_set_music_channel": "Ora mostrerò i brani in ascolto, finiti, messi in pausa e rimossi in questo canale.", "music_skipped_to": "Saltato a '{0}:{1}'", - "music_songs_shuffled": "Canzoni mischiate", "music_song_moved": "Canzone spostata", "music_time_format": "{0}o {1}m {2}s", "music_to_position": "Alla posizione", @@ -605,9 +602,6 @@ "utility_convert_not_found": "Non posso convertire {0} a {1}: unità non trovate.", "utility_convert_type_error": "Impossibile convertire {0} in {1}: i tipi di unitá sono diversi", "utility_created_at": "Creato a", - "utility_csc_join": "Entrato nel canale tra server.", - "utility_csc_leave": "Uscito dal canale tra server.", - "utility_csc_token": "Questo é il tuo token CSC", "utility_custom_emojis": "Emoji personalizzati", "utility_error": "Errore", "utility_features": "Funzioni", @@ -754,7 +748,6 @@ "gambling_shop_role": "Otterrai il ruolo {0}.", "gambling_type": "Tipo", "utility_clpa_next_update": "Prossimo update alle {0}", - "administration_global_perms_reset": "I permessi globali sono stati resettati.", "administration_gvc_disabled": "La funzione di canale vocale di gioco é stata disabilitata in questo server.", "administration_gvc_enabled": "{0} é un canale vocale di gioco ora.", "administration_not_in_voice": "Non sei in un canale vocale in questo server.", @@ -782,5 +775,34 @@ "permissions_lgp_none": "Nessun comando o modulo bloccato", "gambling_animal_race_no_race": "Questa Gara degli Animali è al completo!", "utility_cant_read_or_send": "Non puoi leggere o mandare messaggi al quel canale.", - "utility_quotes_notfound": "Non è stata trovata nessuna citazione corrispondente all'ID specificato." + "utility_quotes_notfound": "Non è stata trovata nessuna citazione corrispondente all'ID specificato.", + "administration_prefix_current": "Il prefisso in questo server é {0}", + "administration_prefix_new": "Prefisso in questo server cambiato da {0} a {1}", + "administration_defprefix_current": "Il prefisso base del bot é {0}", + "administration_defprefix_new": "Cambiato il prefisso base del bot da {0} a {1}", + "administration_bot_nick": "Il soprannome del Bot é cambiato in {0}", + "administration_user_nick": "Il soprannome dell'utente {0} é cambiato in {1}", + "administration_timezone_guild": "Il fuso orario di questa gilda é '{0}'", + "administration_timezone_not_found": "", + "administration_timezones_available": "Fuso orari disponibili", + "music_song_not_found": "Nessuna canzone trovata.", + "searches_define_unknown": "Impossibile trovare la definizione per quel termine.", + "utility_repeater_initial": "", + "utility_verbose_errors_enabled": "I comandi usati incorrettamente faranno ora vedere gli errori", + "utility_verbose_errors_disabled": "", + "permissions_perms_reset": "I permessi per questo server sono stati resettati.", + "permissions_trigger": "", + "administration_migration_error": "Errore nella migrazione, controlla la console del bot per maggiori informazioni.", + "searches_hex_invalid": "", + "permissions_global_perms_reset": "I permessi globali sono stati resettati.", + "help_module": "", + "games_hangman_stopped": "", + "music_autoplaying": "", + "music_queue_stopped": "", + "music_removed_song_error": "", + "music_shuffling_playlist": "", + "music_songs_shuffle_enable": "", + "music_songs_shuffle_disable": "", + "music_song_skips_after": "", + "administration_warnings_list": "Lista di tutti gli utenti avvisati in questo server" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.ja-JP.json b/src/NadekoBot/_strings/ResponseStrings.ja-JP.json index 78e7d425..934754ad 100644 --- a/src/NadekoBot/_strings/ResponseStrings.ja-JP.json +++ b/src/NadekoBot/_strings/ResponseStrings.ja-JP.json @@ -151,7 +151,6 @@ "administration_old_nick": "古いニックネーム", "administration_old_topic": "古いトピック", "administration_perms": "エラー。ほとんどの場合、十分な権限がありません。\n", - "administration_perms_reset": "このサーバーのアクセス許可はリセットされます。\n", "administration_prot_active": "アクティブな保護\n", "administration_prot_disable": "このサーバーで{0}は**無効**になっています。\n", "administration_prot_enable": "{0}を有効にしました\n", @@ -243,7 +242,6 @@ "administration_sbdm": "あなたは{0}サーバーからソフト禁止されました。\n理由:{1}", "administration_user_unbanned": "Since I'm using \"exiled\" for banned. need to rethink a new word.. so just leave this one alone\n", "administration_migration_done": "移行が完了しました!\n", - "adminsitration_migration_error": "移行中にエラーが発生しました。詳細については、ボットのコンソールを確認してください。\n", "administration_presence_updates": "存在の更新", "administration_sb_user": "ユーザはソフトバン", "gambling_awarded": "{0}から{1}に授与されました\n", @@ -439,7 +437,6 @@ "music_rpl_enabled": "プレイリストの繰り返しは有効です。", "music_set_music_channel": "このチャンネルでは、再生、終了、一時停止、曲の削除ができます。", "music_skipped_to": "スキップしました。 {0}:{1} ", - "music_songs_shuffled": "シャッフルした曲", "music_song_moved": "削除した曲", "music_time_format": "{0}時 {1}分 {2}秒 ", "music_to_position": "位置に", @@ -605,9 +602,6 @@ "utility_convert_not_found": "{0}から{1}に変換できません:ユニットが見つかりません\n", "utility_convert_type_error": "{0}から{1}に変換できません:ユニットのタイプが等しくない\n", "utility_created_at": "作成日\n", - "utility_csc_join": "結合されたクロスサーバチャネル。\n", - "utility_csc_leave": "左クロスサーバチャネル。\n", - "utility_csc_token": "これはあなたのCSCトークンです\n", "utility_custom_emojis": "カスタム絵文字\n", "utility_error": "エラー", "utility_features": "特徴", @@ -754,7 +748,6 @@ "gambling_shop_role": "あなたは{0}ロールを取得します。\n", "gambling_type": "タイプ\n", "utility_clpa_next_update": "{0}の次の更新\n", - "administration_global_perms_reset": "グローバル権限がリセットされました。\n", "administration_gvc_disabled": "このサーバーでGame Voice Channel機能が無効になっています。\n", "administration_gvc_enabled": "{0}はゲーム音声チャンネルです。\n", "administration_not_in_voice": "あなたはこのサーバー上の音声チャネルにいません。\n", @@ -782,5 +775,34 @@ "permissions_lgp_none": "ブロックされたコマンドやモジュールはありません。\n", "gambling_animal_race_no_race": "この動物レースはいっぱいです!\n", "utility_cant_read_or_send": "そのチャンネルへのメッセージの読み込みや送信はできません。\n", - "utility_quotes_notfound": "指定された見積もりIDに一致する見積もりは見つかりませんでした。\n" + "utility_quotes_notfound": "指定された見積もりIDに一致する見積もりは見つかりませんでした。\n", + "administration_prefix_current": "", + "administration_prefix_new": "", + "administration_defprefix_current": "", + "administration_defprefix_new": "", + "administration_bot_nick": "", + "administration_user_nick": "", + "administration_timezone_guild": "", + "administration_timezone_not_found": "", + "administration_timezones_available": "", + "music_song_not_found": "", + "searches_define_unknown": "", + "utility_repeater_initial": "", + "utility_verbose_errors_enabled": "", + "utility_verbose_errors_disabled": "", + "permissions_perms_reset": "", + "permissions_trigger": "", + "administration_migration_error": "", + "searches_hex_invalid": "", + "permissions_global_perms_reset": "", + "help_module": "", + "games_hangman_stopped": "", + "music_autoplaying": "", + "music_queue_stopped": "", + "music_removed_song_error": "", + "music_shuffling_playlist": "", + "music_songs_shuffle_enable": "", + "music_songs_shuffle_disable": "", + "music_song_skips_after": "", + "administration_warnings_list": "" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.ko-KR.json b/src/NadekoBot/_strings/ResponseStrings.ko-KR.json index e0444f6c..60c4cac6 100644 --- a/src/NadekoBot/_strings/ResponseStrings.ko-KR.json +++ b/src/NadekoBot/_strings/ResponseStrings.ko-KR.json @@ -1,27 +1,4 @@ { - "clashofclans_base_already_claimed": "기지가 이미 요청되었거나 파괴되었습니다.", - "clashofclans_base_already_destroyed": "기지가 이미 파괴되었습니다.", - "clashofclans_base_already_unclaimed": "기지가 요청당하지 않았습니다.", - "clashofclans_base_destroyed": "{1}와의 전쟁에서 #{0}번 기지가 **파괴**되었습니다.", - "clashofclans_base_unclaimed": "{2}와의 전쟁에서 {0}가 #{1}번 기지를 **요청**하지 못했습니다.", - "clashofclans_claimed_base": "{2}와의 전쟁에서 #{1}번 기지를 {0}가 요청하였습니다.", - "clashofclans_claimed_other": "@{0} 이미 #{1}번 기지를 요청했습니다. 새로운 기지를 요청 할 수 없습니다.", - "clashofclans_claim_expired": "{1}와의 전쟁에서 @{0}의 요청이 만료되었습니다.", - "clashofclans_enemy": "적", - "clashofclans_info_about_war": "{0}와의 전쟁에 대한 정보", - "clashofclans_invalid_base_number": "유효하지 않은 기지 숫자입니다.", - "clashofclans_invalid_size": "유효하지 않은 전쟁 크기입니다.", - "clashofclans_list_active_wars": "활성화된 전쟁 목록", - "clashofclans_not_claimed": "요청되지 않았습니다.", - "clashofclans_not_partic": "당신은 이 전쟁에 참여하고 있지 않습니다.", - "clashofclans_not_partic_or_destroyed": "@{0} 당신은 그 전쟁에 참여하지 않거나 그 기지가 이미 파괴되었습니다.", - "clashofclans_no_active_wars": "활성화된 전쟁이 없습니다.", - "clashofclans_size": "크기", - "clashofclans_war_already_started": "{0}에 대한 전쟁이 이미 시작되었습니다.", - "clashofclans_war_created": "{0}에 대한 전쟁이 생성되었습니다.", - "clashofclans_war_ended": "{0}와의 전쟁이 종료되었습니다.", - "clashofclans_war_not_exist": "전쟁이 존재하지않습니다.", - "clashofclans_war_started": "{0} 와의 전쟁이 시작되었습니다.", "customreactions_all_stats_cleared": "모든 커스텀 리액션에 대한 통계가 삭제되었습니다.", "customreactions_deleted": "커스텀 리액션이 삭제되었습니다.", "customreactions_insuff_perms": "권한이 부족합니다. 전체 커스텀 리액션을 관리하기 위해서는 봇의 소유권과 서버의 관리자 권한이 필요합니다.", @@ -151,7 +128,6 @@ "administration_old_nick": "이전 닉네임", "administration_old_topic": "이전 주제", "administration_perms": "오류. 봇에게 충분한 권한이 없습니다.", - "administration_perms_reset": "이 서버의 모든 권한이 초기화되었습니다.", "administration_prot_active": "보호기능 활성화", "administration_prot_disable": "{0}은(는) 이 서버에서 **비활성화**되었습니다.", "administration_prot_enable": "{0}이(가) 활성화되었습니다.", @@ -243,7 +219,6 @@ "administration_sbdm": "당신은 {0} 서버에서 소프트밴을 당했습니다.\n사유: {1}", "administration_user_unbanned": "사용자 밴 해제", "administration_migration_done": "이전 완료!", - "adminsitration_migration_error": "이전 과정에서 오류가 발생했습니다. 자세한 정보는 봇의 콘솔을 통해서 확인하세요.", "administration_presence_updates": "현재 상태 업데이트", "administration_sb_user": "사용자 소프트 밴", "gambling_awarded": "님이 {1}에게 {0}개를 지급했습니다.", @@ -370,7 +345,7 @@ "games_hangman_running": "행맨 게임이 이미 시작되었습니다", "games_hangman_start_errored": "행맨 게임 시작에 오류가 발생하였습니다.", "games_hangman_types": "\"{0}hangman\" 용어 종류 목록", - "games_leaderboard": "리더 보드", + "games_leaderboard": "리더보드", "games_not_enough": "당신은 충분한 {0}이(가) 없습니다.", "games_no_results": "결과가 없습니다.", "games_picked": "이(가) {0}을(를) 뽑았습니다.", @@ -439,7 +414,6 @@ "music_rpl_enabled": "재생목록 반복이 활성화되었습니다.", "music_set_music_channel": "앞으로 재생 중, 완료, 일시정지, 삭제된 곡들을 이 채널에 출력합니다.", "music_skipped_to": "`{0}:{1}`로 이동하였습니다.", - "music_songs_shuffled": "곡을 섞었습니다.", "music_song_moved": "곡이 이동되었습니다.", "music_time_format": "{0}시간 {1}분 {2}초", "music_to_position": "바뀐 위치", @@ -605,9 +579,6 @@ "utility_convert_not_found": "단위를 찾지 못해서 {0}을(를) {1}(으)로 변환 할 수 없습니다.", "utility_convert_type_error": "단위의 종류가 같지 않기때문에 {0}을(를) {1}(으)로 변환 할 수 없습니다.", "utility_created_at": "생성 시간", - "utility_csc_join": "크로스 서버 채널에 입장했습니다.", - "utility_csc_leave": "크로스 서버 채널에서 퇴장했습니다.", - "utility_csc_token": "이것이 당신의 CSC 토큰입니다.", "utility_custom_emojis": "커스텀 이모지", "utility_error": "오류", "utility_features": "기능", @@ -644,7 +615,7 @@ "utility_quote_deleted": "인용구 #{0}이(가) 삭제되었습니다.", "utility_region": "지역", "utility_registered_on": "가입 날짜", - "utility_remind": "{2}마다 {0}에게 {1}을(를) 상기시킵니다. `({3:d.M.yyyy.} {4:HH:mm})`", + "utility_remind": "{2}후에 {0}에게 {1}을(를) 상기시킵니다. `({3:d.M.yyyy.} {4:HH:mm})`", "utility_remind_invalid_format": "유효하지 않은 시간 포맷입니다. 명령어 목록을 확인하세요.", "utility_remind_template": "새로운 상기 템플릿이 설정되었습니다.", "utility_repeater": "{0}을(를) {1}일 {2}시간 {3}분마다 반복합니다.", @@ -678,7 +649,7 @@ "games_no_votes_cast": "진행중인 투표가 없습니다.", "games_poll_already_running": "이 서버에서 이미 투표가 진행되고있습니다.", "games_poll_created": "📃 {0}님이 투표를 생성하였습니다.", - "games_poll_result": "`{0}.` {2}표를 획득한 {1}이(가) 되었습니다.", + "games_poll_result": "`{0}.` {1}이(가) {2}표를 획득하였습니다.", "games_poll_voted": "{0}님이 투표하였습니다.", "games_poll_vote_private": "해당 답변 번호와 함께 개인 메시지를 보내세요.", "games_poll_vote_public": "해당 답변 번호와 함께 여기에 메시지를 보내세요.", @@ -754,7 +725,6 @@ "gambling_shop_role": "당신은 {0} 역할을 얻게 될 것 입니다.", "gambling_type": "종류", "utility_clpa_next_update": "다음 업데이트는 {0}에 됩니다.", - "administration_global_perms_reset": "글로벌 권한이 리셋되었습니다.", "administration_gvc_disabled": "이 서버에서 게임 음성 채널 기능이 비활성화되었습니다.", "administration_gvc_enabled": "이제 {0}이(가) 게임 음성 채널입니다.", "administration_not_in_voice": "당신은 이 서버의 음성 채널에 있지 않습니다.", @@ -780,11 +750,123 @@ "permissions_gmod_add": "모든 서버에서 {0} 모듈이 비활성화되었습니다.", "permissions_gmod_remove": "모든 서버에서 {0} 모듈이 활성화되었습니다.", "permissions_lgp_none": "차단된 명령어나 모듈이 없습니다.", - "gambling_animal_race_no_race": "이 동물 레이스는 찼습니다!", + "gambling_animal_race_no_race": "이 동물 레이스는 꽉 찼습니다!", "utility_cant_read_or_send": "당신은 그 채널에서 메시지를 보내거나 볼 수 없습니다.", "utility_quotes_notfound": "해당 ID에 일치하는 인용구를 찾을 수 없습니다.", "administration_prefix_current": "이 서버의 봇 명령어 접두사는 {0} 입니다.", "administration_prefix_new": "이 서버의 봇 명령어 접두사가 {0}에서 {1}(으)로 변경되었습니다.", - "administration_defprefix_current": "표준의 봇 명령어 접두사는 {0} 입니다.", - "administration_defprefix_new": "표준의 봇 명령어 접두사가 {0}에서 {1}(으)로 변경되었습니다." + "administration_defprefix_current": "기본 봇 명령어 접두사는 {0} 입니다.", + "administration_defprefix_new": "기본 봇 명령어 접두사가 {0}에서 {1}(으)로 변경되었습니다.", + "administration_bot_nick": "봇의 닉네임이 {0}(으)로 변경되었습니다.", + "administration_user_nick": "사용자 {0}의 닉네임이 {1}(으)로 변경되었습니다.", + "administration_timezone_guild": "이 길드의 시간대는 `{0}`입니다.", + "administration_timezone_not_found": "시간대를 찾지 못했습니다. \"timezones\" 명령어를 통해서 가능한 시간대 목록을 볼 수 있습니다.", + "administration_timezones_available": "가능한 시간대", + "music_song_not_found": "음악을 찾지 못했습니다.", + "searches_define_unknown": "해당 용어에 대한 정의를 찾을 수 없습니다.", + "utility_repeater_initial": "이제 초기 반복 메시지는 {0}시간 {1}분 마다 전송됩니다.", + "utility_verbose_errors_enabled": "잘못 사용된 명령어는 이제 오류를 표시합니다.", + "utility_verbose_errors_disabled": "잘못 사용된 명령어는 이제 오류를 표시하지 않습니다.", + "permissions_perms_reset": "이 서버에서 권한을 초기화했습니다.", + "permissions_trigger": "권한 #{0} {1}이(가) 이 행동을 금지하고 있습니다.", + "administration_migration_error": "이주하는 동안 오류가 발생했습니다. 자세한 정보는 봇의 콘솔 확인하십시오.", + "searches_hex_invalid": "유효하지 않은 색입니다.", + "permissions_global_perms_reset": "모든 서버의 권한을 초기화했습니다.", + "help_module": "모듈: {0}", + "games_hangman_stopped": "행맨 게임이 정지되었습니다.", + "music_autoplaying": "자동재생", + "music_queue_stopped": "플레이어가 멈췄습니다. {0} 명령어를 사용하여 다시 시작하세요.", + "music_removed_song_error": "해당 색인에 노래가 없습니다.", + "music_shuffling_playlist": "노래들을 섞는 중...", + "music_songs_shuffle_enable": "지금부터 노래들을 섞습니다.", + "music_songs_shuffle_disable": "노래들을 더 이상 섞지 않습니다.", + "music_song_skips_after": "노래들을 {0}초 후에 스킵합니다.", + "administration_warnings_list": "이 서버의 경고받은 사용자 목록", + "customreactions_redacted_too_long": "너무 길기 때문에 편집했습니다.", + "nsfw_blacklisted_tag_list": "블랙리스트 된 태그 목록", + "nsfw_blacklisted_tag": "당신이 입력한 태그 중 하나 이상의 태그가 블랙리스트에 있습니다.", + "nsfw_blacklisted_tag_add": "NSFW 태그 {0}이(가) 블랙리스트에 추가 됬습니다.", + "nsfw_blacklisted_tag_remove": "NSFW 태그 {0}은(는) 더 이상 블랙리스트가 아닙니다.", + "gambling_waifu_gift": "{1}에게 {0}개를 선물하셨습니다", + "gambling_waifu_gift_shop": "와이프 선물 가게", + "gambling_gifts": "선물", + "games_connect4_created": "Connect4 게임을 생성했습니다. 플레이어의 참여를 기다리는 중입니다.", + "games_connect4_player_to_move": "", + "games_connect4_failed_to_start": "아무도 게임에 참여하지 않아서 Connect4 게임 시작에 실패했습니다.", + "games_connect4_draw": "Connect4 게임이 무승부로 끝났습니다.", + "games_connect4_won": "{0}님이 {1}을 상대로 Connect4 게임을 이겼습니다.", + "games_nunchi_joined": "눈치 게임에 참가했습니다. {0} 명의 사용자가 지금까지 참가했습니다.", + "games_nunchi_ended": "눈치 게임이 종료되었습니다. {0}님이 이겼습니다", + "games_nunchi_ended_no_winner": "눈치 게임이 승자없이 종료되었습니다.", + "games_nunchi_started": "{0}명의 참가자와 함께 눈치 게임을 시작했습니다.", + "games_nunchi_round_ended": "눈치 게임 라운드가 종료되었습니다. {0}님이 탈락했습니다.", + "games_nunchi_round_ended_boot": "눈치 게임 라운드는 일부 사용자의 시간 초과로 종료되었습니다. 이 사용자는 여전히 게임 중입니다: {0}", + "games_nunchi_round_started": "{0}명의 참가자와 함께 눈치 게임 라운드가 시작되었습니다. 숫자 {1}부터 시작합니다.", + "games_nunchi_next_number": "숫자가 등록되었습니다. 마지막 숫자는 {0} 입니다.", + "games_nunchi_failed_to_start": "충분한 플레이어가 없어서 눈치게임 시작에 실패했습니다.", + "games_nunchi_created": "눈치게임이 생성되었습니다. 플레이어의 참여를 기다리는 중입니다.", + "music_sad_enabled": "음악 재생이 완료되면 대기열에서 삭제됩니다.", + "music_sad_disabled": "", + "utility_stream_role_enabled": "", + "utility_stream_role_disabled": "스트림 역할 기능이 비활성화되었습니다.", + "utility_stream_role_kw_set": "이제 스트리머는 역할을 얻기위해 {0} 키워드가 필요합니다.", + "utility_stream_role_kw_reset": "스트림 역할 키워드가 초기화되었습니다.", + "utility_stream_role_bl_add": "{0}은(는) 절대 스트림 역할을 얻을 수 없습니다.", + "utility_stream_role_bl_add_fail": "{0}은(는) 이미 블랙리스트에 추가됬습니다.", + "utility_stream_role_bl_rem": "{0}은(는) 더 이상 블랙리스트에 속해있지 않습니다.", + "utility_stream_role_bl_rem_fail": "{0}은(는) 블랙리스트에 속해있지 않습니다.", + "utility_stream_role_wl_add": "", + "utility_stream_role_wl_add_fail": "{0}은(는) 이미 화이트리스트에 추가되었습니다.", + "utility_stream_role_wl_rem": "", + "utility_stream_role_wl_rem_fail": "", + "utility_bot_config_edit_fail": "", + "utility_bot_config_edit_success": "", + "customreactions_crca_disabled": "", + "customreactions_crca_enabled": "", + "xp_server_level": "서버 레벨", + "xp_level": "레벨", + "xp_club": "클럽", + "xp_xp": "경험치", + "xp_excluded": "", + "xp_not_excluded": "", + "xp_exclusion_list": "제외 목록", + "xp_server_is_excluded": "이 서버가 제외 됐습니다", + "xp_server_is_not_excluded": "이 서버가 제외되지 않습니다", + "xp_excluded_roles": "", + "xp_excluded_channels": "", + "xp_level_up_channel": "", + "xp_level_up_dm": "", + "xp_level_up_global": "{0}님 축하드립니다, 글로벌 레벨 {1}을(를) 달성했습니다.", + "xp_role_reward_cleared": "{0} 레벨에 도달해도 역할을 얻지않습니다.", + "xp_role_reward_added": "{0} 레벨에 도달한 유저는 {1} 역할을 얻게됩니다.", + "xp_role_rewards": "역할 보상", + "xp_level_x": "레벨 {0}", + "xp_no_role_rewards": "", + "xp_server_leaderboard": "서버 XP 리더보드", + "xp_global_leaderboard": "글로벌 XP 리더보드", + "xp_modified": "", + "xp_club_create_error": "", + "xp_club_created": "", + "xp_club_not_exists": "", + "xp_club_applied": "", + "xp_club_apply_error": "", + "xp_club_accepted": "", + "xp_club_accept_error": "사용자를 찾을 수 없습니다", + "xp_club_left": "", + "xp_club_not_in_club": "", + "xp_club_user_kick": "", + "xp_club_user_kick_fail": "", + "xp_club_user_banned": "", + "xp_club_user_ban_fail": "", + "xp_club_user_unbanned": "", + "xp_club_user_unban_fail": "", + "xp_club_level_req_changed": "", + "xp_club_level_req_change_error": "", + "xp_club_disbanded": "", + "xp_club_disband_error": "", + "xp_club_icon_error": "", + "xp_club_icon_set": "", + "xp_club_bans_for": "{0} 클럽 밴 목록", + "xp_club_apps_for": "", + "xp_club_leaderboard": "클럽 리더보드 - 페이지 {0}" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.nb-NO.json b/src/NadekoBot/_strings/ResponseStrings.nb-NO.json index 0f98d7d6..e79389e9 100644 --- a/src/NadekoBot/_strings/ResponseStrings.nb-NO.json +++ b/src/NadekoBot/_strings/ResponseStrings.nb-NO.json @@ -1,27 +1,4 @@ { - "clashofclans_base_already_claimed": "Den basen har allerede blitt tatt over eller ødelagt.", - "clashofclans_base_already_destroyed": "Den basen er allerede ødelagt.", - "clashofclans_base_already_unclaimed": "Ingen har hevdet denne basen.", - "clashofclans_base_destroyed": "**ØDELAGT** base #{0} i en krig mot {1}", - "clashofclans_base_unclaimed": "{0} har **hevdet** bare #{1} i en krig mot {2}", - "clashofclans_claimed_base": "{0} har hevdet en base #{1} i en krig mot {2}", - "clashofclans_claimed_other": "@{0} Du har allerede hevdet basen #{1}. Du kan ikke kreve en ny.", - "clashofclans_claim_expired": "Krav fra @{0} for en krig mot {1} har utløpt.", - "clashofclans_enemy": "Fiende", - "clashofclans_info_about_war": "Info om krigen mot {0}", - "clashofclans_invalid_base_number": "Ugyldig basenummer", - "clashofclans_invalid_size": "Ikke en gyldig krigstørrelse", - "clashofclans_list_active_wars": "Liste over aktive kriger", - "clashofclans_not_claimed": "ikke hevdet", - "clashofclans_not_partic": "Du er ikke en deltager i den krigen.", - "clashofclans_not_partic_or_destroyed": "@{0} Du deltar enten ikke i denne krigen, eller basen er allerede ødelagt.", - "clashofclans_no_active_wars": "Ingen aktive kriger.", - "clashofclans_size": "Størrelse", - "clashofclans_war_already_started": "Krig mot {0} har allerede begynt.", - "clashofclans_war_created": "Krig mot {0} opprettet.", - "clashofclans_war_ended": "Krig mot {0} avsluttet.", - "clashofclans_war_not_exist": "Den krigen eksisterer ikke.", - "clashofclans_war_started": "Krig mot {0} startet!", "customreactions_all_stats_cleared": "Alle tilpassede reaksjons-statistikker fjernet.", "customreactions_deleted": "Tilpasset reaksjon fjernet", "customreactions_insuff_perms": "Utilstrekkelig tilgang. Krever Bot-eierskap for globale tilpassede reaksjoner, og Administrator for server reaksjoner.", @@ -66,7 +43,7 @@ "administration_bandm": "Du har blitt utestengt fra {0}\nGrunn: {1}", "administration_banned_pl": "utestengt", "administration_banned_user": "Bruker ble utestengt", - "administration_bot_name": "Bot navn endret til {0}", + "administration_bot_name": "Bot'ens navn er endret til {0}", "administration_bot_status": "Bot status endret til {0}", "administration_byedel_off": "Automatisk sletting av 'farvel'-meldinger er aktivert", "administration_byedel_on": "Farvel-meldinger vil bli slettet etter {0} sekunder.", @@ -151,7 +128,6 @@ "administration_old_nick": "Gammelt kallenavn", "administration_old_topic": "Gammelt emne", "administration_perms": "Feil. Mest sansynlig har jeg ikke tilstrekkelige tillatelser.", - "administration_perms_reset": "Tillatelsene til denne serveren er nullstilt.", "administration_prot_active": "Aktive Beskyttelser", "administration_prot_disable": "{0} har blitt **deaktivert** på denne serveren.", "administration_prot_enable": "{0} aktivert", @@ -243,7 +219,6 @@ "administration_sbdm": "Du har blitt 'soft-banned' fra {0} server.\nGrunn: {1}", "administration_user_unbanned": "Fjernet bruker fra utestenging", "administration_migration_done": "Migrering fullført!", - "adminsitration_migration_error": "Feil under migrering, sjekk botens konsoll for mer informasjon.", "administration_presence_updates": "Oppdatering av tilstedeværelse", "administration_sb_user": "Bruker soft-banned", "gambling_awarded": "har tildelt {0} til {1}", @@ -285,7 +260,7 @@ "help_cmdlist_donate": "Du kan støtte prosjektet på Patreon: <{0}> eller paypal: <{1}>", "help_cmd_and_alias": "Kommandoer og aliaser", "help_commandlist_regen": "Kommandoliste regenerert.", - "help_commands_instr": "Skriv `{0}h Kommandonavn` å se hjelpen for den oppgitte kommandoen. f.eks `{0}h >8ball`", + "help_commands_instr": "Skriv `{0}h Kommandonavn` å se hjelpen for den oppgitte kommandoen. f.eks `{0}h {0}8ball`", "help_command_not_found": "Jeg kan ikke finne den kommandoen. Kontroller at kommandoen finnes før du prøver igjen.", "help_desc": "Beskrivelse", "help_donate": "Du kan støtte NadekoBot prosjektet på\nPatreon <{0}> eller\nPaypal <{1}>\nIkke glem å skrive Discord navnet eller ID i meldingen.\n\n** Takk ** ♥ ️", @@ -398,7 +373,7 @@ "music_autoplay_enabled": "Automatisk avspilling aktivert.", "music_defvol_set": "Standard volum satt til {0}%", "music_dir_queue_complete": "Mappelisting ferdig.", - "music_fairplay": "fairplay", + "music_fairplay": "Fairplay", "music_finished_song": "Sang ferdig", "music_fp_disabled": "Rettferdig avspilling deaktivert", "music_fp_enabled": "Rettferdig avspilling aktivert", @@ -416,7 +391,7 @@ "music_no_search_results": "Ingen søkeresultater.", "music_paused": "Musikkavspilling pauset", "music_player_queue": "Spilleliste - Side {0}/{1}", - "music_playing_song": "Spiller sang", + "music_playing_song": "Spiller sang #{0}", "music_playlists": "#{0}` - **{1}** av *{2}* ({3} sanger)", "music_playlists_page": "Side {0} av lagrede spillelister", "music_playlist_deleted": "Spilleliste slettet.", @@ -439,7 +414,6 @@ "music_rpl_enabled": "Repetisjon av spilleliste startet.", "music_set_music_channel": "Meldinger om sanger som spilles av, er ferdige, pauset og fjernet vil bli vist i denne kanalen.", "music_skipped_to": "Skippet til '{0}:{1}'", - "music_songs_shuffled": "Sanger stokket", "music_song_moved": "Sang flyttet", "music_time_format": "{0}t {1}m {2}s", "music_to_position": "Til posisjon", @@ -605,9 +579,6 @@ "utility_convert_not_found": "Kan ikke konvertere {0} til {1}: enheter ikke funnet", "utility_convert_type_error": "Kan ikke konvertere {0} til {1}: typer enheter er ikke lik", "utility_created_at": "Laget på", - "utility_csc_join": "Ble med i kanalen for snakk på tvers av servere", - "utility_csc_leave": "Forlot kanalen for snakk på tvers av servere", - "utility_csc_token": "Dette er din CSC token", "utility_custom_emojis": "Spesiallagde emojier", "utility_error": "Feil", "utility_features": "Egenskaper", @@ -663,7 +634,7 @@ "utility_server_info": "Server info", "utility_shard": "Shard", "utility_shard_stats": "Shard status", - "utility_shard_stats_txt": "Shard **#{0}** sin status: {1} med {2} servere", + "utility_shard_stats_txt": "Shard **#{0}** sin status: {1} med {2} servere - for {3} siden", "utility_showemojis": "**Navn:** {0} **Link:** {1}", "utility_showemojis_none": "Ingen spesielle emojier funnet.", "utility_stats_songs": "Spiller {0} sanger, {1} i kø.", @@ -754,7 +725,6 @@ "gambling_shop_role": "Du får rollen {0}", "gambling_type": "Type", "utility_clpa_next_update": "Neste oppdatering om {0}", - "administration_global_perms_reset": "Globale tillatelser er nullstilt", "administration_gvc_disabled": "Funksjonen 'Talekanal for spill' er slått av.", "administration_gvc_enabled": "{0} er nå 'talekanal for spill'.", "administration_not_in_voice": "Du er ikke i en talekanal på denne serveren.", @@ -785,6 +755,118 @@ "utility_quotes_notfound": "Ingen sitater funnet med det ID nummeret.", "administration_prefix_current": "Denne serverens prefiks er {0}", "administration_prefix_new": "Endret prefiks fra {0} til {1}", - "administration_defprefix_current": "Standard prefiks er {1}", - "administration_defprefix_new": "Endret standard prefiks fra {0} til {1}" + "administration_defprefix_current": "Standard prefiks er {0}", + "administration_defprefix_new": "Endret standard prefiks fra {0} til {1}", + "administration_bot_nick": "Bot'ens kallenavn er nå {0}", + "administration_user_nick": "{0} sitt kallenavn er nå {1}", + "administration_timezone_guild": "Denne guilden sin tidssone er {0}", + "administration_timezone_not_found": "Kunne ikke finne tidssone. Bruk \"timezone\"-kommandoen for å se en liste over tilgjengelige tidssoner.", + "administration_timezones_available": "Tilgjengelige tidssoner", + "music_song_not_found": "Kunne ikke finne sang", + "searches_define_unknown": "Kunne ikke finne definisjonen for det ordet", + "utility_repeater_initial": "Første repeterende melding vil bli sendt om {0}t og {1}m", + "utility_verbose_errors_enabled": "Ukorrekt bruk av kommandoer vil nå vise feilmeldinger.", + "utility_verbose_errors_disabled": "Ukorrekt bruk av kommandoer vil ikke lenger vise feilmeldinger.", + "permissions_perms_reset": "Tillatelsene er tilbakestilt for denne serveren.", + "permissions_trigger": "Tillatelse #{0} {1} forhindrer kommandoen i å bli utført.", + "administration_migration_error": "En feil oppstod under migreringen. Sjekk konsollen for mer informasjon.", + "searches_hex_invalid": "Ugyldig fargekode.", + "permissions_global_perms_reset": "Globale tillatelser er tilbakestilt.", + "help_module": "Modul: {0}", + "games_hangman_stopped": "Hangman stoppet.", + "music_autoplaying": "Spiller:", + "music_queue_stopped": "Avspiller stoppet. Bruk kommandoen {0} for å starte avspilling.", + "music_removed_song_error": "Ingen sang med den indexen.", + "music_shuffling_playlist": "Stokker sanger", + "music_songs_shuffle_enable": "Sanger vil spilles av i tilfeldig rekkefølge fra nå.", + "music_songs_shuffle_disable": "Sanger vil ikke lenger spilles av i tilfeldig rekkefølge.", + "music_song_skips_after": "Sanger vil hoppe etter {0}", + "administration_warnings_list": "Liste over advarte brukere på denne serveren.", + "customreactions_redacted_too_long": "Fjernet fordi den var for lang.", + "nsfw_blacklisted_tag_list": "Svartelistede knagger.", + "nsfw_blacklisted_tag": "Én eller flere av knaggene du brukte er svartelistet.", + "nsfw_blacklisted_tag_add": "NSFW knagg {0} er nå svartelistet.", + "nsfw_blacklisted_tag_remove": "NSFW knagg {0} er ikke lenger svartelistet.", + "gambling_waifu_gift": "Ga {0} til {1].", + "gambling_waifu_gift_shop": "Waifu gavebutikk.", + "gambling_gifts": "Gaver.", + "games_connect4_created": "Startet et 4-på-rad spill. Venter på at spiller skal bli med.", + "games_connect4_player_to_move": "Spiller {0} sin tur.", + "games_connect4_failed_to_start": "4-på-rad kunne ikke starte fordi ingen ble med.", + "games_connect4_draw": "4-på-rad endte uavgjort.", + "games_connect4_won": "{0} vant 4-på-rad mot {1}", + "games_nunchi_joined": "Startet 'nunchi'. {0} spillere har blitt med.", + "games_nunchi_ended": "Nunchi avsluttet. {0} vant.", + "games_nunchi_ended_no_winner": "Nunchi avsluttet uten en vinner.", + "games_nunchi_started": "Nunchi startet med {0} deltakere.", + "games_nunchi_round_ended": "Nunchi runde avsluttet. {0} er ute av spillet.", + "games_nunchi_round_ended_boot": "Nunchi runded avsluttet fordi én eller flere spillere ikke reagerte i tide. Disse er fremdeles med i spillet: {0}", + "games_nunchi_round_started": "Nunchi startet med {0} spillere. Start med å telle fra {1}", + "games_nunchi_next_number": "Tall registrert. Siste nummer var {0}.", + "games_nunchi_failed_to_start": "Nunchi kunne ikke starte fordi det ikke var nok deltakere.", + "games_nunchi_created": "Nunchi startet. Venter på spillere.", + "music_sad_enabled": "Sanger vil bli fjernet fra lista når de er spilt av.", + "music_sad_disabled": "Sanger blir ikke lenger fjernet fra lista når de er spilt av.", + "utility_stream_role_enabled": "Når en bruker med rollen {0} strømmer, får de rollen {1}.", + "utility_stream_role_disabled": "Strømmerolle skrudd på.", + "utility_stream_role_kw_set": "Strømmere trenger nå nøkkelordet {0} for å få den rollen.", + "utility_stream_role_kw_reset": "Nøkkelord for strømmerolle er tilbakestilt.", + "utility_stream_role_bl_add": "Bruker {0} vil aldri få strømmerollen.", + "utility_stream_role_bl_add_fail": "Bruker {0} er allerede svartelistet.", + "utility_stream_role_bl_rem": "Bruker {0} er ikke lenger svartelistet.", + "utility_stream_role_bl_rem_fail": "Bruker {0} er ikke svartelistet.", + "utility_stream_role_wl_add": "Bruker {0} får strømmerollen selv om de ikke har nøkkelordet i strømmens tittel.", + "utility_stream_role_wl_add_fail": "Bruker {0} er allerede hvitelistet.", + "utility_stream_role_wl_rem": "Bruker {0} er ikke lenger hvitelistet.", + "utility_stream_role_wl_rem_fail": "Bruker {0} er ikke hvitelistet.", + "utility_bot_config_edit_fail": "Kunne ikke sette verdien {0} til {1}", + "utility_bot_config_edit_success": "Verdien {0} er satt til {1}", + "customreactions_crca_disabled": "Tilpasset reaksjon med ID {0} vil ikke lenger trigges med mindre triggerordet er i begynnelsen av en setning.", + "customreactions_crca_enabled": "Tilpasset reaksjon med ID {0} vil nå trigges uansett hvor i setningen triggerordet er.", + "xp_server_level": "Nivå i serveren.", + "xp_level": "Nivå", + "xp_club": "Klubb", + "xp_xp": "Erfaring", + "xp_excluded": "{0} er nå ekskludert fra nivå-systemet på denne serveren.", + "xp_not_excluded": "{0} er ikke lenger ekskludert fra nivå-systemet på denne serveren.", + "xp_exclusion_list": "Eksklusjonsliste", + "xp_server_is_excluded": "Denne serveren er ekskludert", + "xp_server_is_not_excluded": "Denne serveren er ikke ekskludert.", + "xp_excluded_roles": "Ekskluderte roller.", + "xp_excluded_channels": "Ekskluderte kanaler.", + "xp_level_up_channel": "Gratulerer {0}, du er nå nivå {1}!", + "xp_level_up_dm": "Gratulerer {0}, du er nå nivå {1} på serveren {2}.", + "xp_level_up_global": "Gratulerer {0}, ditt globale nivå er nå {1}!", + "xp_role_reward_cleared": "Nivå {0} vil ikke lenger gi noen belønning.", + "xp_role_reward_added": "Brukere som når nivå {0} vil få rollen {1}.", + "xp_role_rewards": "Rolle belønninger.", + "xp_level_x": "Nivå {0}", + "xp_no_role_rewards": "Ingen rollebelønninger på denne siden.", + "xp_server_leaderboard": "Server XP ledetavle.", + "xp_global_leaderboard": "Global XP ledetavle", + "xp_modified": "Redigerte server XP-nivået til {0} med {1}", + "xp_club_create_error": "Kunne ikke opprette klubb. Du må være minst nivå 5 og ikke være i en klubb allerede.", + "xp_club_created": "Klubben {0} ble suksessfult opprettet.", + "xp_club_not_exists": "Den klubben eksisterer ikke.", + "xp_club_applied": "Du har søkt om medlemskap i klubben {0}.", + "xp_club_apply_error": "Kunne ikke søke. Du er enten medlem i en annen klubb, møter ikke minimum nivå-krav, eller du er utestengt fra denne klubben.", + "xp_club_accepted": "Aksepterte {0} inn i klubben.", + "xp_club_accept_error": "Fant ikke bruker.", + "xp_club_left": "Du forlot klubben.", + "xp_club_not_in_club": "Du er ikke i en klubb", + "xp_club_user_kick": "{0} sparket deg fra klubben {1}", + "xp_club_user_kick_fail": "Kunne ikke sparke brukeren. Enten er du ikke klubbeieren, eller brukeren er ikke med i klubben.", + "xp_club_user_banned": "Utestengte {0} fra {1}", + "xp_club_user_ban_fail": "Kunne ikke utestenge. Enten er du ikke eier av klubben, eller den brukeren er ikke medlem av klubben.", + "xp_club_user_unbanned": "{0} er ikke lenger utestengt fra {1}", + "xp_club_user_unban_fail": "Kunne ikke fjerne utestenging. Enten er du ikke klubbeieren, eller brukeren har ikke søkt om medlemskap.", + "xp_club_level_req_changed": "Endret klubbens nivå-krav til {0}", + "xp_club_level_req_change_error": "Kunne ikke endre nivå-krav\n", + "xp_club_disbanded": "Klubben {0} er nå oppløst.\n", + "xp_club_disband_error": "Feil. Enten er du ikke i en klubb eller du er ikke eieren av klubben.", + "xp_club_icon_error": "Ikke en gyldig bilde-URL eller du er ikke eieren av klubben.", + "xp_club_icon_set": "Nytt klubb-bilde satt.", + "xp_club_bans_for": "Utestengelser for klubben {0}", + "xp_club_apps_for": "Søkere for klubben {0}", + "xp_club_leaderboard": "Ledetavle - side {0}" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.nl-NL.json b/src/NadekoBot/_strings/ResponseStrings.nl-NL.json index d223eb8d..36900859 100644 --- a/src/NadekoBot/_strings/ResponseStrings.nl-NL.json +++ b/src/NadekoBot/_strings/ResponseStrings.nl-NL.json @@ -1,28 +1,5 @@ { - "clashofclans_base_already_claimed": "Die basis is al veroverd of vernietigd.", - "clashofclans_base_already_destroyed": "Die basis is al vernietigd.", - "clashofclans_base_already_unclaimed": "Die basis is nog niet veroverd.", - "clashofclans_base_destroyed": "**VERNIETIGD** basis #{0} in een oorlog tegen {1}", - "clashofclans_base_unclaimed": "{0} heeft **ONOVERWONNEN** basis #{1} in een oorlog tegen {2}", - "clashofclans_claimed_base": "{0} heeft basis #{1} overwonnen in een oorlog tegen {2}", - "clashofclans_claimed_other": "@{0} Je hebt al basis #{1} overwonnen. Je kunt er niet nog een nemen.", - "clashofclans_claim_expired": "De aanvraag van @{0} voor een oorlog tegen {1} is niet meer geldig.", - "clashofclans_enemy": "Vijand", - "clashofclans_info_about_war": "Informatie over de oorlog tegen {0}", - "clashofclans_invalid_base_number": "Ongeldige basis nummer", - "clashofclans_invalid_size": "Ongeldig oorlogsformaat.", - "clashofclans_list_active_wars": "Lijst van actieve oorlogen", - "clashofclans_not_claimed": "Niet veroverd.", - "clashofclans_not_partic": "Jij doet niet mee aan die oorlog.", - "clashofclans_not_partic_or_destroyed": "@{0} Je doet niet mee aan die oorlog, of die basis is al vernietigd.", - "clashofclans_no_active_wars": "Geen actieve oorlogen", - "clashofclans_size": "Grootte.", - "clashofclans_war_already_started": "Oorlog tegen {0} is al begonnen.", - "clashofclans_war_created": "Oorlog tegen {0} gecreëerd", - "clashofclans_war_ended": "De oorlog tegen {0} is beëindigd.", - "clashofclans_war_not_exist": "Die oorlog bestaat niet.", - "clashofclans_war_started": "De oorlog tegen {0} is begonnen!", - "customreactions_all_stats_cleared": "Alle speciale reactie statistieken zijn verwijderd.", + "customreactions_all_stats_cleared": "Alle speciale reactiestatistieken zijn verwijderd.", "customreactions_deleted": "Speciale Reactie verwijderd.", "customreactions_insuff_perms": "Onvoldoende rechten. Bot Eigendom is nodig voor globale speciale reacties, en Administrator voor speciale server-reacties.", "customreactions_list_all": "Lijst van alle zelf gemaakte reacties", @@ -66,7 +43,7 @@ "administration_bandm": "Je bent verbannen van {0} server. Reden: {1}", "administration_banned_pl": "verbannen", "administration_banned_user": "Gebruiker verbannen", - "administration_bot_name": "Botnaam is veranderd naar {0}", + "administration_bot_name": "Bot naam is veranderd naar {0}", "administration_bot_status": "Botstatus is veranderd naar {0}", "administration_byedel_off": "Automatische verwijdering van de afscheidsberichten is uitgeschakeld.", "administration_byedel_on": "Afscheidsberichten zullen worden verwijderd na {0} seconden.", @@ -151,7 +128,6 @@ "administration_old_nick": "Oude bijnaam", "administration_old_topic": "Oud onderwerp", "administration_perms": "Error. Hoogst waarschijnlijk heb ik geen voldoende rechten.", - "administration_perms_reset": "De rechten op deze server zijn gereset.", "administration_prot_active": "Actieve beschermingen.", "administration_prot_disable": "{0} is nu ** uitgeschakeld** op deze server.", "administration_prot_enable": "{0} Ingeschakeld", @@ -243,7 +219,6 @@ "administration_sbdm": "Je bent soft-gebant van {0} server.\nReden: {1}", "administration_user_unbanned": "Gebruiker niet meer verbannen", "administration_migration_done": "Migratie klaar!", - "adminsitration_migration_error": "Fout tijdens het migreren, kijk in de bot zijn console voor meer informatie.", "administration_presence_updates": "Aanwezigheid updates", "administration_sb_user": "Gebruiker soft-gebant", "gambling_awarded": "heeft {0} aan {1} gegeven", @@ -285,7 +260,7 @@ "help_cmdlist_donate": "Je kan het project steunen op Patreon: <{0}> of PayPal: <{1}>", "help_cmd_and_alias": "Commando's en aliassen", "help_commandlist_regen": "Commandolijst gegenereerd.", - "help_commands_instr": "Typ `{0}h CommandoNaam` om de uitleg te zien voor dat specifieke commando. b.v `{0}h >8bal`", + "help_commands_instr": "Typ `{0}h CommandoNaam` om de uitleg te zien voor dat specifieke commando. b.v `{0}h {0}8bal`", "help_command_not_found": "Ik kan het commando niet vinden. Verifieer of dit commando echt bestaat voordat je het opnieuw probeert.", "help_desc": "Beschrijving", "help_donate": "Je kan het NadekoBot project steunen op\nPatreon <{0}> of\nPayPal <{1}>\nVergeet niet je discord naam en id in het bericht te zetten.\n\n**Hartelijk bedankt** ♥️", @@ -398,7 +373,7 @@ "music_autoplay_enabled": "Autoplay aangezet.", "music_defvol_set": "Standaardvolume op {0}% gezet", "music_dir_queue_complete": "Map afspeellijst geladen/compleet.", - "music_fairplay": "fairplay", + "music_fairplay": "Fairplay", "music_finished_song": "Track afgelopen:", "music_fp_disabled": "Fairplay uitgeschakeld.", "music_fp_enabled": "Fairplay ingeschakeld.", @@ -416,7 +391,7 @@ "music_no_search_results": "Geen zoekresultaten.", "music_paused": "Muziek gepauzeerd.", "music_player_queue": "Speler afspeellijst - Pagina {0}/{1}", - "music_playing_song": "Track speelt af", + "music_playing_song": "Track #{0} wordt afgespeeld", "music_playlists": "`#{0}` - **{1}** van *{2}* ({3} tracks) ", "music_playlists_page": "Pagina {0} van opgeslagen afspeellijsten\n", "music_playlist_deleted": "Afspeellijst verwijderd.", @@ -439,7 +414,6 @@ "music_rpl_enabled": "Herhalende afspeellijst ingeschakeld.", "music_set_music_channel": "Tracks die worden afgespeeld, aflopen, gepauzeerd worden en worden verwijderd laat ik in dit kanaal zien.", "music_skipped_to": "Overslaan naar `{0}:{1}`", - "music_songs_shuffled": "Tracks geschud", "music_song_moved": "Track verzet", "music_time_format": "{0}u {1}m {2}s", "music_to_position": "Naar positie", @@ -605,9 +579,6 @@ "utility_convert_not_found": "Kan {0} niet naar {1} omvormen: Eenheden niet gevonden", "utility_convert_type_error": "Kan {0} niet naar {1} omvormen: Type eenheden zijn niet gelijk", "utility_created_at": "Gemaakt op", - "utility_csc_join": "Aangesloten cross server kanaal.", - "utility_csc_leave": "Verbroken cross server kanaal.", - "utility_csc_token": "Dit is je CSC token", "utility_custom_emojis": "Speciale emojis", "utility_error": "Fout", "utility_features": "Kenmerken", @@ -635,7 +606,7 @@ "utility_owner": "Eigenaar", "utility_owner_ids": "Eigenaar IDs", "utility_presence": "Aanwezigheid", - "utility_presence_txt": "{0} Servers\n{1} Tekst Kanalen\n{2} Spraak Kanalen", + "utility_presence_txt": "{0} Server(s)\n{1} Tekstkanalen\n{2} Spraakkanalen", "utility_quotes_deleted": "Verwijder alle citaten met het trefwoord {0}", "utility_quotes_page": "Pagina {0} met citaten", "utility_quotes_page_none": "Geen citaten op die pagina.", @@ -663,7 +634,7 @@ "utility_server_info": "Server Informatie", "utility_shard": "Shard", "utility_shard_stats": "Shard Statestieken", - "utility_shard_stats_txt": "Shard **#{0}** is in {1} status met {2} servers", + "utility_shard_stats_txt": "Shard **#{0}** is in {1} status met {2} servers - {3} geleden", "utility_showemojis": "*Naam:** {0} **Link:** {1}", "utility_showemojis_none": "Geen speciale emojis gevonden.", "utility_stats_songs": "{0} tracks aan het spelen, {1} in de wachtrij", @@ -754,7 +725,6 @@ "gambling_shop_role": "Je krijgt {0} rol.", "gambling_type": "Type", "utility_clpa_next_update": "Volgende update in {0}", - "administration_global_perms_reset": "Globale machtigingen zijn teruggezet.", "administration_gvc_disabled": "Game Voice kanaal-functie is uitgeschakeld op deze server.", "administration_gvc_enabled": "{0} is een Game Voice Kanaal nu.", "administration_not_in_voice": "Je zit niet in een spraak kanaal in deze server.", @@ -786,5 +756,117 @@ "administration_prefix_current": "De Prefix op deze server is {0}", "administration_prefix_new": "De prefix op deze server is aangepast van {0} naar {1}", "administration_defprefix_current": "De default bot prefix is {0}", - "administration_defprefix_new": "Default bot prefix is aangepast van {0} naar {1}" + "administration_defprefix_new": "Default bot prefix is aangepast van {0} naar {1}", + "administration_bot_nick": "De bijnaam van de bot is veranderd naar {0}", + "administration_user_nick": "Bijnaam van de gebruiker {0} is veranderd naar {1}", + "administration_timezone_guild": "Tijdzone van deze guild is `{0}`", + "administration_timezone_not_found": "Tijdzone niet gevonden. Gebruik \"tijdzones\"commando om de lijst te zien van alle tijdzones.", + "administration_timezones_available": "Beschikbare Tijdzones", + "music_song_not_found": "Liedje niet gevonden.", + "searches_define_unknown": "Kan de definitie van die term niet vinden.", + "utility_repeater_initial": "Initiële herhalend bericht wordt verstuurd in {0}uur en {1}min.", + "utility_verbose_errors_enabled": "Incorrecte gebruikte commando's laten nu errors zien.", + "utility_verbose_errors_disabled": "Incorrecte gebruikte commando's laten nu geen errors meer zien.", + "permissions_perms_reset": "Permissies voor deze server zijn gereset.", + "permissions_trigger": "Permissie nummer #{0} {1} voorkomt deze actie.", + "administration_migration_error": "Error tijdens migreren, controleer de bot console voor meer informatie.", + "searches_hex_invalid": "Invalide kleur gespecificeerd.", + "permissions_global_perms_reset": "Globale permissies zijn gerest.", + "help_module": "Module: {0}", + "games_hangman_stopped": "Galgje game gestopt.", + "music_autoplaying": "Automatisch-afspelen.", + "music_queue_stopped": "Afspeler is gestopt. Hervat the afspeler door {0} commando.", + "music_removed_song_error": "Liedje op die index bestaat niet", + "music_shuffling_playlist": "Shuffling liedjes", + "music_songs_shuffle_enable": "Liedjes worden vanaf nu geshuffeld.", + "music_songs_shuffle_disable": "Liedjes worden niet langer meer geshuffeld.", + "music_song_skips_after": "Liedjes worden overgeslagen na {0}", + "administration_warnings_list": "Lijst van alle gewaarschuwde gebruikers op deze server", + "customreactions_redacted_too_long": "Redacted omdat het te lang is.", + "nsfw_blacklisted_tag_list": "De zwarte lijst van tags:", + "nsfw_blacklisted_tag": "Een of meerder tags die je hebt gebruikt staan op de zwarte lijst.", + "nsfw_blacklisted_tag_add": "Nsfw tag {0} is nu op de zwarte lijst.", + "nsfw_blacklisted_tag_remove": "Nsfw tag {0} is niet langer meer op de zwarte lijst.", + "gambling_waifu_gift": "Cadeau {0} gegeven aan {1}", + "gambling_waifu_gift_shop": "Waifu cadeauwinkel", + "gambling_gifts": "Cadeaus", + "games_connect4_created": "Connect4-spel gecreëerd. Wachten op een speler om mee te doen.", + "games_connect4_player_to_move": "Speler om te verplaatsen: {0}", + "games_connect4_failed_to_start": "Connect4-spel kan niet starten omdat niemand anders deelnam.", + "games_connect4_draw": "Connect4-spel is geëindigd in gelijkspel.", + "games_connect4_won": "{0} won het spel Connect4 tegen {1}.", + "games_nunchi_joined": "Aangesloten bij het nunchi spel. {0} gebruikers zijn tot nu toe aangesloten.", + "games_nunchi_ended": "Nunchi spel geëindigd. {0} heeft gewonnen", + "games_nunchi_ended_no_winner": "Nunchi spel geëindigd zonder winnaar.", + "games_nunchi_started": "Nunchi spel begonnen met {0} deelnemers.", + "games_nunchi_round_ended": "Nunchi ronde is geëindigd. {0} is uit het spel.", + "games_nunchi_round_ended_boot": "Nunchi ronde is afgelopen wegens een time-out van sommige gebruikers. Deze gebruikers zijn nog steeds in het spel: {0}", + "games_nunchi_round_started": "Nunchi ronde begonnen met {0} gebruikers. Begint met het nummer {1} te tellen.", + "games_nunchi_next_number": "Nummer geregistreerd. Laatste nummer was {0}.", + "games_nunchi_failed_to_start": "Nunchi kon niet starten omdat er niet genoeg deelnemers waren.", + "games_nunchi_created": "Nunchi spel is aangemaakt. Wachten op gebruikers om mee te spelen.", + "music_sad_enabled": "Liedjes worden verwijderd uit de muziekwachtrij wanneer ze afgelopen zijn.", + "music_sad_disabled": "Liedjes worden niet langer meer uit de muziekwachtrij verwijderd wanneer ze klaar zijn met afspelen.", + "utility_stream_role_enabled": "Wanneer een gebruiker van {0} rol begint te streaming, zal ik ze {1} rol geven.", + "utility_stream_role_disabled": "Stream rol functie is nu uitgeschakeld.", + "utility_stream_role_kw_set": "Streamers moeten nu {0} zoekwoord gebruiken om de rol te kunnen ontvangen.", + "utility_stream_role_kw_reset": "Stream rol zoekwoord gereset.", + "utility_stream_role_bl_add": "Gebruiker {0} ontvangt nooit de stream rol.", + "utility_stream_role_bl_add_fail": "Gebruiker {0} staat al op de zwarte lijst.", + "utility_stream_role_bl_rem": "Gebruiker {0} staat niet langer meer op de zwarte lijst.", + "utility_stream_role_bl_rem_fail": "Gebruiker {0} staat niet op de zwarte lijst.", + "utility_stream_role_wl_add": "Gebruiker {0} ontvangt de streamingrol, zelfs als ze het zoekwoord niet hebben in de streamtitel.", + "utility_stream_role_wl_add_fail": "Gebruiker {0} staat al op de witte lijst.", + "utility_stream_role_wl_rem": "Gebruiker {0} staat niet langer meer op de witte lijst.", + "utility_stream_role_wl_rem_fail": "Gebruiker {0} staat niet op de witte lijst.", + "utility_bot_config_edit_fail": "Mislukt bij het instellen van {0} naar de waarde {1}", + "utility_bot_config_edit_success": "De waarde van {0} is ingesteld op {1}", + "customreactions_crca_disabled": "Aangepaste reactie met id {0} zal niet langer worden geactiveerd, tenzij het triggerwoord in het begin van een zin staat.", + "customreactions_crca_enabled": "Aangepaste reactie met id {0} wordt nu geactiveerd als het ergens in de zin voorkomt.", + "xp_server_level": "Server niveau", + "xp_level": "Niveau", + "xp_club": "Vereniging", + "xp_xp": "Ervaring", + "xp_excluded": "{0} is uitgesloten van het XP-systeem op deze server.", + "xp_not_excluded": "{0} is niet meer uitgesloten van het XP-systeem op deze server.", + "xp_exclusion_list": "Uitsluitingslijst", + "xp_server_is_excluded": "Deze server is uitgesloten.", + "xp_server_is_not_excluded": "Deze server is niet uitgesloten.", + "xp_excluded_roles": "Uitgesloten Rollen", + "xp_excluded_channels": "Uitgesloten Kanalen", + "xp_level_up_channel": "Gefeliciteerd {0}, Je hebt niveau {1} bereikt!", + "xp_level_up_dm": "Gefeliciteerd {0}, je hebt niveau {1} bereikt op {2} server!", + "xp_level_up_global": "Gefeliciteerd {0}, je hebt globale niveau {1} bereikt!", + "xp_role_reward_cleared": "Niveau {0} wordt niet langer meer beloond met een rol.", + "xp_role_reward_added": "Gebruikers die niveau {0} bereiken, krijgen de rol {1}.", + "xp_role_rewards": "Rolbeloningen", + "xp_level_x": "Niveau {0}", + "xp_no_role_rewards": "Geen rolbeloning op deze pagina.", + "xp_server_leaderboard": "Server XP Scorebord", + "xp_global_leaderboard": "Global XP Scorebord", + "xp_modified": "Aangepaste server XP van de gebruiker {0} met {1}", + "xp_club_create_error": "Mislukt bij het maken van een club. Zorg ervoor dat je boven level 5 bent en niet al lid bent van een club.", + "xp_club_created": "Club {0} succesvol gemaakt!", + "xp_club_not_exists": "Die club bestaat niet.", + "xp_club_applied": "Je hebt je aangemeld om lid te worden bij {0} club.", + "xp_club_apply_error": "Fout bij aanmelden. Je bent al lid van de club of je voldoet niet aan het minimumniveau, of je bent bij deze club verbannen.", + "xp_club_accepted": "Gebruiker geaccepteerde bij {0} club.", + "xp_club_accept_error": "Gebruiker niet gevonden", + "xp_club_left": "Je hebt de club verlaten.", + "xp_club_not_in_club": "Je bent niet in een club, of je probeert de club te verlaten waar je de eigenaar van bent.", + "xp_club_user_kick": "Gebruiker {0} gekicked van {1} club.", + "xp_club_user_kick_fail": "Fout bij kicken. Je bent ook niet de club eigenaar, of die gebruiker zit niet in je club.", + "xp_club_user_banned": "Gebruiker {0} van {1} club verbannen.", + "xp_club_user_ban_fail": "Kan niet verbannen. Je bent ook niet de eigenaar van de club, of die gebruiker zit niet in jouw club of heeft nog niet gesolliciteerd.", + "xp_club_user_unbanned": "Gebruiker {0} van {1} club verbanning opgeheven.", + "xp_club_user_unban_fail": "Mislukt om verbanning op te heffen. Je bent niet de eigenaar van de club, of die gebruiker zit niet in jouw club, of heeft nog niet gesolliciteerd.", + "xp_club_level_req_changed": "Veranderd clubvereisten naar {0}", + "xp_club_level_req_change_error": "Vereiste niveau veranderen mislukt", + "xp_club_disbanded": "Club {0} is ontbonden", + "xp_club_disband_error": "Fout. Je bent niet in een club, of je bent niet de eigenaar van jouw club.", + "xp_club_icon_error": "Niet een geldige image url of je bent niet de eigenaar van de club.", + "xp_club_icon_set": "Nieuwe club icon set.", + "xp_club_bans_for": "Verbanne van {0} club", + "xp_club_apps_for": "Aanvragers voor {0} club", + "xp_club_leaderboard": "Club scorebord - pagina {0}" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.pl-PL.json b/src/NadekoBot/_strings/ResponseStrings.pl-PL.json index abfb1764..d1c79767 100644 --- a/src/NadekoBot/_strings/ResponseStrings.pl-PL.json +++ b/src/NadekoBot/_strings/ResponseStrings.pl-PL.json @@ -151,7 +151,6 @@ "administration_old_nick": "Stary pseudonim", "administration_old_topic": "Stary temat", "administration_perms": "Wystąpił błąd. Najprawdopodobniej nie mam wystarczających uprawnień.", - "administration_perms_reset": "Uprawnienia tego serwera zostały zresetowane.", "administration_prot_active": "Aktywne zabezpieczenia", "administration_prot_disable": "{0} został **wyłączony** na tym serwerze.", "administration_prot_enable": "{0} włączony", @@ -243,7 +242,6 @@ "administration_sbdm": "Zostałeś tymczasowo zablokowany na serwerze {0}\nPowód: {1}", "administration_user_unbanned": "Użytkownik został odbanowany", "administration_migration_done": "Migracja zakończona!", - "adminsitration_migration_error": "Błąd podczas migracji, sprawdz konsole bota po więcej informacji.", "administration_presence_updates": "Aktualizacje obecności", "administration_sb_user": "Użytkownik tymczasowo zablokowany", "gambling_awarded": "nagrodził {0} {1}", @@ -439,7 +437,6 @@ "music_rpl_enabled": "Powtarzanie playlisty włączone.", "music_set_music_channel": "Teraz wypiszę odtwarzane, skończone, wstrzymane i usunięte piosenki na tym kanale", "music_skipped_to": "Przewinięto do `{0}:{1}`", - "music_songs_shuffled": "Utwory pomieszane", "music_song_moved": "Utwór przeniesiony", "music_time_format": "{0}g {1}m {2}s", "music_to_position": "Do pozycji", @@ -605,9 +602,6 @@ "utility_convert_not_found": "Nie można przekonwertować {0} na {1}: nie znaleziono jednostki", "utility_convert_type_error": "Nie mogę przeliczyć {0} na {1}: rodzaje jednostek są różne", "utility_created_at": "Stworzono w", - "utility_csc_join": "Dołączyłeś do międzyserwerowego kanału.", - "utility_csc_leave": "Opuściłeś międzyserwerowy kanał.", - "utility_csc_token": "To jest twój token CSC", "utility_custom_emojis": "Niestandardowe emotikony", "utility_error": "Błąd", "utility_features": "Funkcje", @@ -754,7 +748,6 @@ "gambling_shop_role": "Dostaniesz rolę {0}.", "gambling_type": "Typ", "utility_clpa_next_update": "Następna aktualizacja za {0}", - "administration_global_perms_reset": "Uprawnienia globalne zostały zresetowane", "administration_gvc_disabled": "Opcja Czatu Głosowego gry została wyłączona na tym serwerze.", "administration_gvc_enabled": "{0} jest teraz Grą w Czacie głosowym.", "administration_not_in_voice": "Nie jesteś w kanale głosowym na tym serwerze", @@ -782,5 +775,34 @@ "permissions_lgp_none": "Brak zablokowanych komend i modułów", "gambling_animal_race_no_race": "Ten wyścig zwierzaków jest pełny!", "utility_cant_read_or_send": "Nie możesz czytać ani wysyłać wiadomości na tym kanale.", - "utility_quotes_notfound": "Nie znaleziono cytatów pasujących do podanego ID." + "utility_quotes_notfound": "Nie znaleziono cytatów pasujących do podanego ID.", + "administration_prefix_current": "Prefix na tym serwerze to {0}", + "administration_prefix_new": "Zmieniono prefix na tym serwerze z {0} na {1}", + "administration_defprefix_current": "Domyślny prefix to {0}", + "administration_defprefix_new": "Zmieniono domyślny prefix z {0} na {1}", + "administration_bot_nick": "Nickname bota został zmieniony na {0}", + "administration_user_nick": "Nickname użytkowanika {0} został zmieniony na {1}", + "administration_timezone_guild": "Strefa czasowa tej gildii to {0}", + "administration_timezone_not_found": "Strefa czasowa nie znaleziona. Użyj komendy \"timezones\" aby zobaczyć listę dostepnych stref czasowych.", + "administration_timezones_available": "Dostępne strefy czasowe.", + "music_song_not_found": "Nie znaleziono piosenki.", + "searches_define_unknown": "Nie można znaleźć definicji tego terminu.", + "utility_repeater_initial": "", + "utility_verbose_errors_enabled": "Niewłaściwie użyte komendy będą teraz wyświetlać błędy.", + "utility_verbose_errors_disabled": "Niewłaściwie użyte komendy nie będą już pokazywać błędów.", + "permissions_perms_reset": "", + "permissions_trigger": "", + "administration_migration_error": "", + "searches_hex_invalid": "", + "permissions_global_perms_reset": "", + "help_module": "", + "games_hangman_stopped": "", + "music_autoplaying": "", + "music_queue_stopped": "", + "music_removed_song_error": "", + "music_shuffling_playlist": "", + "music_songs_shuffle_enable": "", + "music_songs_shuffle_disable": "", + "music_song_skips_after": "", + "administration_warnings_list": "" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.pt-BR.json b/src/NadekoBot/_strings/ResponseStrings.pt-BR.json index 894f5604..fb5ee165 100644 --- a/src/NadekoBot/_strings/ResponseStrings.pt-BR.json +++ b/src/NadekoBot/_strings/ResponseStrings.pt-BR.json @@ -151,7 +151,6 @@ "administration_old_nick": "Apelido Antigo", "administration_old_topic": "Tópico Antigo", "administration_perms": "Erro. Não tenho permissões suficientes.", - "administration_perms_reset": "As permissões para este servidor foram resetadas.", "administration_prot_active": "Proteções ativadas", "administration_prot_disable": "{0} foi **desativado** neste servidor.", "administration_prot_enable": "{0} Ativado", @@ -243,7 +242,6 @@ "administration_sbdm": "Você foi banido temporariamente do servidor {0}.\nMotivo: {1}", "administration_user_unbanned": "Usuário desbanido", "administration_migration_done": "Migração concluída!", - "adminsitration_migration_error": "Erro enquanto migrava, verifique o console do bot para mais informações.", "administration_presence_updates": "Atualizações de Presença", "administration_sb_user": "Usuário Banido Temporariamente", "gambling_awarded": "concedeu {0} para {1}", @@ -439,7 +437,6 @@ "music_rpl_enabled": "Repetição de playlist habilitada.", "music_set_music_channel": "Eu irei mostrar as músicas em andamento, concluídas, pausadas e removidas neste canal.", "music_skipped_to": "Pulando para `{0}:{1}`", - "music_songs_shuffled": "Músicas embaralhadas.", "music_song_moved": "Música movida", "music_time_format": "{0}h {1}m {2}s", "music_to_position": "para a posição", @@ -605,9 +602,6 @@ "utility_convert_not_found": "Não foi possível converter {0} para {1}: unidades não encontradas", "utility_convert_type_error": "Não foi possível converter {0} para {1}: as unidades não são do mesmo tipo", "utility_created_at": "Criado em", - "utility_csc_join": "Juntou-se ao canal de servidor cruzado.", - "utility_csc_leave": "Deixou o canal de servidor cruzado.", - "utility_csc_token": "Este é seu token de Canal de Servidor Cruzado", "utility_custom_emojis": "Emojis Personalizados", "utility_error": "Erro", "utility_features": "Atributos", @@ -754,7 +748,6 @@ "gambling_shop_role": "Você receberá a role {0}.", "gambling_type": "Tipo", "utility_clpa_next_update": "Próximo update em {0}", - "administration_global_perms_reset": "Permissões globais foram resetadas.", "administration_gvc_disabled": "Canais de Voz de Jogos foram desabilitados neste server.", "administration_gvc_enabled": "{0} é um Canal de Voz de Jogos agora.", "administration_not_in_voice": "Você não está em um canal de voz neste server.", @@ -786,5 +779,30 @@ "administration_prefix_current": "Prefixo neste server é {0}", "administration_prefix_new": "Prefixo neste server modificado de {0} para {1}", "administration_defprefix_current": "Prefixo de bot padrão é {0}", - "administration_defprefix_new": "Prefixo de bot padrão modificado de {0} para {1}" + "administration_defprefix_new": "Prefixo de bot padrão modificado de {0} para {1}", + "administration_bot_nick": "Apelido do Bot mudou para {0}", + "administration_user_nick": "Apelido do usuário {0} mudou para {1}", + "administration_timezone_guild": "Fuso horário para esta guild é `{0}`", + "administration_timezone_not_found": "Fuso horário não encontrado. Use o comando \"timezones\" para ver a lista de fusos horários disponíveis.", + "administration_timezones_available": "Fusos Horários Disponíveis", + "music_song_not_found": "Nenhuma música encontrada.", + "searches_define_unknown": "Não foi possível encontrar a definição para este termo.", + "utility_repeater_initial": "Repetição de mensagem inicial será enviada em {0}h e {1}min.", + "utility_verbose_errors_enabled": "Comandos usados incorretamente agora irão exibir erros.", + "utility_verbose_errors_disabled": "Comandos usados incorretamente não irão mais exibir erros.", + "permissions_perms_reset": "Permissões para este server foram resetadas.", + "permissions_trigger": "Permissão número #{0} {1} está prevenindo esta ação.", + "administration_migration_error": "Erro ao migrar, cheque o console do bot para mais informações.", + "searches_hex_invalid": "Cor inválida especificada.", + "permissions_global_perms_reset": "Permissões globais foram resetadas.", + "help_module": "Módulo: {0}", + "games_hangman_stopped": "Jogo da forca foi terminado.", + "music_autoplaying": "Reproduzindo automaticamente.", + "music_queue_stopped": "Player foi pausado. Use o comando {0} para começar a tocar.", + "music_removed_song_error": "Não existe uma música neste índice", + "music_shuffling_playlist": "Embaralhando músicas", + "music_songs_shuffle_enable": "Músicas serão embaralhadas a partir de agora.", + "music_songs_shuffle_disable": "Músicas não serão mais embaralhadas.", + "music_song_skips_after": "Músicas serão puladas depois de {0}", + "administration_warnings_list": "Lista de todos os usuários advertidos neste server" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.ro-RO.json b/src/NadekoBot/_strings/ResponseStrings.ro-RO.json index 27eaa985..3c4720c8 100644 --- a/src/NadekoBot/_strings/ResponseStrings.ro-RO.json +++ b/src/NadekoBot/_strings/ResponseStrings.ro-RO.json @@ -151,7 +151,6 @@ "administration_old_nick": "Poreclă veche", "administration_old_topic": "Topic vechi", "administration_perms": "Eroare. Mai mult ca sigur că nu am destule permisiuni.", - "administration_perms_reset": "Permisiunile pentru acest server au fost resetate.", "administration_prot_active": "Protecții active.", "administration_prot_disable": "{0} a fost **dezactivat** pe acest server.", "administration_prot_enable": "{0} Activat", @@ -243,7 +242,6 @@ "administration_sbdm": "Ai fost banat-ușor din serverul {0}.\nMotivul: {1}", "administration_user_unbanned": "Utilizator bannat.", "administration_migration_done": "Migrare terminata!", - "adminsitration_migration_error": "Eroare in timpul migrarii, verifica consola bot-ului pentru mai multe informatii.", "administration_presence_updates": "Actualizări de prezență.", "administration_sb_user": "Utilizator banat-ușor.", "gambling_awarded": "A acordat {0} la {1}.", @@ -288,7 +286,7 @@ "help_commands_instr": "Tasteaza `{0}h NumeleComenzii` pentru a vedea informatii legate de acea comanda. ex. `{0}h >8ball`", "help_command_not_found": "Nu gasesc acea comanda. Te rog asigura-te ca exista comanda inainte de a incerca din nou.", "help_desc": "Descriere", - "help_donate": "Poți susține proiectul NadekoBot pe\nPatreon <{0}> sau\nPaypal <{1}>\nNu uita să iți lași numele de Discord sau ID-ul în mesaj.\n\n**Mulțumesc** <3", + "help_donate": "Poți susține proiectul NadekoBot pe\nPatreon <{0}> sau\nPaypal <{1}>\nNu uita să iți lași numele de Discord sau ID-ul în mesaj.\n\n**Mulțumesc** ♥️", "help_guide": "** Listă de comenzi **: <{0}>\n** Ghiduri și documente de găzduire pot fi găsite aici **: <{1}>", "help_list_of_commands": "Lista comenzilor", "help_list_of_modules": "Lista modulelor", @@ -439,7 +437,6 @@ "music_rpl_enabled": "Repetarea listei de redare activată.", "music_set_music_channel": "Acum voi scoate melodiile cântate, terminate, pauzate și eliminate în acest canal.", "music_skipped_to": "S-a sărit la `{0}:{1}`", - "music_songs_shuffled": "Cântece amestecate", "music_song_moved": "Cântec mutat", "music_time_format": "{0}o {1}m {2}s", "music_to_position": "Către poziția", @@ -537,7 +534,7 @@ "searches_list_of_place_tags": "Lista {0}etichete plasate", "searches_location": "Locație", "searches_magicitems_not_loaded": "Articolele magice nu au fost încărcate", - "searches_mal_profile": "{0} profilul lui MAL", + "searches_mal_profile": "profilul MAL al lui {0}", "searches_mashape_api_missing": "Proprietarul botului nu a specificat MashapeApiKey. Nu puteți utiliza această funcție.", "searches_min_max": "Min/Max", "searches_no_channel_found": "Nici-un canal găsit.", @@ -605,9 +602,6 @@ "utility_convert_not_found": "Nu se poate converti {0} la {1}: unitățile nu pot fii găsite", "utility_convert_type_error": "Nu se poate converti {0} la {1}: tipurile de unități nu sunt egale.", "utility_created_at": "Creat la", - "utility_csc_join": "S-a alăturat canalului încrucișat al serverului.", - "utility_csc_leave": "A părăsit canalul încrucișat al serverului.", - "utility_csc_token": "Acesta este tokenul tău CSC", "utility_custom_emojis": "Emojis personalizate", "utility_error": "Eroare", "utility_features": "Caracteristici", @@ -706,7 +700,7 @@ "searches_compet_playtime": "Timp jucat competitiv", "administration_channel": "Canal", "administration_command_text": "Comandă de text", - "administration_kicked_pl": "Dat afară", + "administration_kicked_pl": "Dați afară", "administration_moderator": "Moderator", "administration_page": "pagina {0}", "administration_reason": "Motiv", @@ -754,7 +748,6 @@ "gambling_shop_role": "O sa primești rolul {0}.", "gambling_type": "Tip", "utility_clpa_next_update": "Urmatorul update în {0}", - "administration_global_perms_reset": "Permisiile globale au fost resetate.", "administration_gvc_disabled": "Caracteristica Canalul Vocal de Joc a fost dezactivat pe acest server.", "administration_gvc_enabled": "{0} este un Canal Vocal de Joc acum.", "administration_not_in_voice": "Nu ești intr-un canal vocal pe acest server.", @@ -782,5 +775,34 @@ "permissions_lgp_none": "Nu sunt comenzi blocate sau module.", "gambling_animal_race_no_race": "Cursa animalelor este plină!", "utility_cant_read_or_send": "Nu poți să citești sau să trimiți mesaje din acel canal.", - "utility_quotes_notfound": "Nici un citat găsit care se potrivește cu ID citatului specificat." + "utility_quotes_notfound": "Nici un citat găsit care se potrivește cu ID citatului specificat.", + "administration_prefix_current": "Prefixul pe acest server este {0}", + "administration_prefix_new": "Am schimbat prefixul pe acest server din {0} în {1}", + "administration_defprefix_current": "Prefixul implicit al bot-ului este {0}", + "administration_defprefix_new": "Am schimbat prefixul implicit at bot-ului din {0} în {1}", + "administration_bot_nick": "", + "administration_user_nick": "", + "administration_timezone_guild": "", + "administration_timezone_not_found": "", + "administration_timezones_available": "", + "music_song_not_found": "", + "searches_define_unknown": "", + "utility_repeater_initial": "", + "utility_verbose_errors_enabled": "", + "utility_verbose_errors_disabled": "", + "permissions_perms_reset": "", + "permissions_trigger": "", + "administration_migration_error": "", + "searches_hex_invalid": "", + "permissions_global_perms_reset": "", + "help_module": "", + "games_hangman_stopped": "", + "music_autoplaying": "", + "music_queue_stopped": "", + "music_removed_song_error": "", + "music_shuffling_playlist": "", + "music_songs_shuffle_enable": "", + "music_songs_shuffle_disable": "", + "music_song_skips_after": "", + "administration_warnings_list": "" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.ru-RU.json b/src/NadekoBot/_strings/ResponseStrings.ru-RU.json index 1955c4fe..99a3f46f 100644 --- a/src/NadekoBot/_strings/ResponseStrings.ru-RU.json +++ b/src/NadekoBot/_strings/ResponseStrings.ru-RU.json @@ -1,27 +1,4 @@ { - "clashofclans_base_already_claimed": "Эта база уже захвачена или разрушена.", - "clashofclans_base_already_destroyed": "Эта база уже разрушена", - "clashofclans_base_already_unclaimed": "Эта база не захвачена.", - "clashofclans_base_destroyed": "**РАЗРУШЕННАЯ** база #{0} ведёт войну против {1}.", - "clashofclans_base_unclaimed": "У {0} есть **НЕ ЗАХВАЧЕННАЯ** база #{1}, ведущая войну против {2}", - "clashofclans_claimed_base": "{0} захватил базу #{1}, ведя войну против {2}", - "clashofclans_claimed_other": "@{0} Вы уже захватили базу #{1}. Вы не можете захватить новую базу.", - "clashofclans_claim_expired": "Время действия запроса от @{0} на войну против {1} истекло.", - "clashofclans_enemy": "Враг", - "clashofclans_info_about_war": "Информация о войне против {0}", - "clashofclans_invalid_base_number": "Неправильный номер базы.", - "clashofclans_invalid_size": "Неправильный размер войны.", - "clashofclans_list_active_wars": "Список активных войн", - "clashofclans_not_claimed": "не захвачена", - "clashofclans_not_partic": "Вы не участвуете в этой войне.", - "clashofclans_not_partic_or_destroyed": "@{0} Вы либо не участвуете в этой войне, либо эта база уже разрушена.", - "clashofclans_no_active_wars": "Нет активных войн.", - "clashofclans_size": "Размер", - "clashofclans_war_already_started": "Война против {0} уже началась.", - "clashofclans_war_created": "Война против {0} начата.", - "clashofclans_war_ended": "Война против {0} закончилась.", - "clashofclans_war_not_exist": "Этой войны не существует.", - "clashofclans_war_started": "Война против {0} началась!", "customreactions_all_stats_cleared": "Вся статистика пользовательских реакций стёрта.", "customreactions_deleted": "Пользовательская реакция удалена", "customreactions_insuff_perms": "Недостаточно разрешений. Необходимо иметь разрешение владельца бота для глобальных пользовательских реакций, и разрешение Администратора для реакций на сервере.", @@ -151,7 +128,6 @@ "administration_old_nick": "Старое имя", "administration_old_topic": "Старый заголовок", "administration_perms": "Ошибка. Скорее всего мне не хватает прав.", - "administration_perms_reset": "Разрешения для этого сервера были сброшены.", "administration_prot_active": "Активные защиты от рейдов", "administration_prot_disable": "{0} был **отключён** на этом сервере.", "administration_prot_enable": "{0} включен", @@ -243,7 +219,6 @@ "administration_sbdm": "Вас забанили на сервере {0}. Причина: {1}", "administration_user_unbanned": "Пользователь разбанен.", "administration_migration_done": "Перемещение закончено!", - "adminsitration_migration_error": "Ошибка при переносе файлов, проверьте консоль бота для получения дальнейшей информации.", "administration_presence_updates": "История присутвия пользователей", "administration_sb_user": "Пользователя выгнали", "gambling_awarded": "наградил {0} пользователю {1}", @@ -285,7 +260,7 @@ "help_cmdlist_donate": "Вы можете поддержать проект в patreon: <{0}> или через paypal: <{1}>", "help_cmd_and_alias": "Команды и альтернативные имена команд", "help_commandlist_regen": "Список команд создан.", - "help_commands_instr": "Напишите '{0}h ИмяКоманды', чтобы получить справку для этой команды. Например, '{0}h >8ball'", + "help_commands_instr": "Напишите `{0}h ИмяКоманды`, чтобы получить справку для этой команды. Например, `{0}h {0}8ball`", "help_command_not_found": "Эта команда не найдена. Пожалуйста, убедитесь, что команда существует.", "help_desc": "Описание", "help_donate": "Вы можете поддержать проект NadekoBot в \nPatreon <{0}> или\nPaypal <{1}>\nНе забудьте оставить ваше имя в Discord или id в Вашем сообщении.\n\n**Спасибо** ♥️", @@ -398,7 +373,7 @@ "music_autoplay_enabled": "Автовоспроизведение включено.", "music_defvol_set": "Громкость по умолчанию выставлена на {0}%", "music_dir_queue_complete": "Папка успешно добавлена в очередь воспроизведения.", - "music_fairplay": "справедливое воспроизведение", + "music_fairplay": "Честное воспроизведение", "music_finished_song": "Песня завершилась.", "music_fp_disabled": "Отключено честное воспроизведение.", "music_fp_enabled": "Включено честное воспроизведение.", @@ -416,7 +391,7 @@ "music_no_search_results": "Нет результатов поиска.", "music_paused": "Проигрывание музыки приостановлено.", "music_player_queue": "Очередь воспроизведения - Страница {0}/{1}", - "music_playing_song": "Проигрывается песня", + "music_playing_song": "Воспроизводится песня #{0}", "music_playlists": "'#{0}' - **{1}** *{2}* ({3} песен)", "music_playlists_page": "Страница {0} сохранённых плейлистов.", "music_playlist_deleted": "Плейлист удалён.", @@ -439,7 +414,6 @@ "music_rpl_enabled": "Включено повторение плейлиста.", "music_set_music_channel": "Проигрываемые, завершённые, приостановленные и удалённые песни будут выводится в этом канале.", "music_skipped_to": "Пропускаю до '{0}:{1}'", - "music_songs_shuffled": "Песни перемешаны", "music_song_moved": "Песня перемещена", "music_time_format": "{0}ч {1}м {2}с", "music_to_position": "К моменту", @@ -605,9 +579,6 @@ "utility_convert_not_found": "Нельзя перевести {0} в {1}: единицы измерения не найдены", "utility_convert_type_error": "Нельзя перевести {0} в {1}: единицы измерения не эквивалентны.", "utility_created_at": "Создано", - "utility_csc_join": "Присоедился к межсерверному каналу.", - "utility_csc_leave": "Покинул межсерверный канал.", - "utility_csc_token": "Ваш CSC токен:", "utility_custom_emojis": "Серверные emoji", "utility_error": "Ошибка", "utility_features": "Признаки", @@ -626,7 +597,7 @@ "utility_messages": "Сообщения", "utility_message_repeater": "Повторяемые сообщения", "utility_name": "Имя", - "utility_nickname": "Кличка", + "utility_nickname": "Имя", "utility_nobody_playing_game": "Никто не играет в эту игру", "utility_no_active_repeaters": "Нет активных повторяемых сообщений", "utility_no_roles_on_page": "На этой странице нет ролей.", @@ -663,7 +634,7 @@ "utility_server_info": "Информация о сервере", "utility_shard": "Shard", "utility_shard_stats": "Статистика Shard-а", - "utility_shard_stats_txt": "Shard **#{0}** находится в состоянии {1} с {2} серверами.", + "utility_shard_stats_txt": "Shard **#{0}** в состоянии {1} на {2} серверах — {3} назад", "utility_showemojis": "**Имя:** {0} **Link:** {1}", "utility_showemojis_none": "Серверные emoji не найдены.", "utility_stats_songs": "Проигрывается {0} песен, {1} в очереди", @@ -754,7 +725,6 @@ "gambling_shop_role": "Вы получите роль {0}", "gambling_type": "Вид", "utility_clpa_next_update": "Следующее обновление в {0}", - "administration_global_perms_reset": "Глобальные разрешения были сброшены.", "administration_gvc_disabled": "Функция Игрового Голосового Канала отключена на этом сервере.", "administration_gvc_enabled": "{0} теперь является Игровым Голосовом Каналом.", "administration_not_in_voice": "Вы не находитесь в голосовом канале на этом сервере.", @@ -786,5 +756,117 @@ "administration_prefix_current": "Префикс для этого сервера — {0}", "administration_prefix_new": "Префикс для этого сервера изменен с {0} на {1}", "administration_defprefix_current": "Стандартный префикс для бота — {0}", - "administration_defprefix_new": "Стандартный префикс изменен с {0} на {1}" + "administration_defprefix_new": "Стандартный префикс изменен с {0} на {1}", + "administration_bot_nick": "Имя бота было изменено на {0}", + "administration_user_nick": "Имя пользователя {0} изменено на {1}", + "administration_timezone_guild": "Часовой пояс для этого сервера — '{0}'", + "administration_timezone_not_found": "Часовой пояс не найден. Используйте команду \"timezones\", чтобы просмотреть список доступных часовых поясов.", + "administration_timezones_available": "Доступные часовые пояса", + "music_song_not_found": "Песен не найдено.", + "searches_define_unknown": "Не могу найти определение для этого термина.", + "utility_repeater_initial": "Первоначальное повторяющееся сообщение будет отправлено через {0} часов {1} минут.", + "utility_verbose_errors_enabled": "Неверно введенные команды теперь будут выводить ошибки.", + "utility_verbose_errors_disabled": "Неверно введенные команды больше не будут выводить ошибки.", + "permissions_perms_reset": "Разрешения для этого сервера были сброшены.", + "permissions_trigger": "Разрешение #{0} {1} запрещает это действие.", + "administration_migration_error": "Ошибка при переносе данных, проверьте консоль для получения дополнительной информации.", + "searches_hex_invalid": "Задан неверный цвет.", + "permissions_global_perms_reset": "Глобальные разрешения были сброшены.", + "help_module": "Модуль: {0}", + "games_hangman_stopped": "Игра в Виселицу остановлена.", + "music_autoplaying": "Автовоспроизведение.", + "music_queue_stopped": "Воспроизведение приостановлено. Используйте команду {0}, чтобы начать воспроизведение.", + "music_removed_song_error": "Песни по этому индексу не существует", + "music_shuffling_playlist": "Воспроизвожу песни в случайном порядке", + "music_songs_shuffle_enable": "Песни будут воспроизводиться в случайном порядке.", + "music_songs_shuffle_disable": "Песни больше не будут воспроизводиться в случайном порядке.", + "music_song_skips_after": "Песни будут пропущены после {0}", + "administration_warnings_list": "Список всех предупреждённых пользователей на сервере", + "customreactions_redacted_too_long": "Текст пользовательской реакции изменён, поскольку он был слишком длинным.", + "nsfw_blacklisted_tag_list": "Чёрный список тэгов:", + "nsfw_blacklisted_tag": "Один или несколько использованных тэгов в чёрном списке:", + "nsfw_blacklisted_tag_add": "Небезопасный для работы тэг {0} добавлен в чёрный список.", + "nsfw_blacklisted_tag_remove": "Небезопасный для работы тэг {0} исключён из чёрного списка.", + "gambling_waifu_gift": "Подарили {0} пользователю {1}", + "gambling_waifu_gift_shop": "Список подарков для вайфу", + "gambling_gifts": "Подарки", + "games_connect4_created": "Создана игра \"Четыре в ряд\". Ожидание игроков.", + "games_connect4_player_to_move": "Игроки, которым осталось сделать ход: {0}", + "games_connect4_failed_to_start": "Игра \"Четыре в ряд\" не началась, поскольку не присоединился ни один игрок.", + "games_connect4_draw": "Игра \"Четыре в ряд\" завершилась ничьёй.", + "games_connect4_won": "{0} выиграл игру \"Четыре в ряд\" против {1}.", + "games_nunchi_joined": "Началась игра в нунчи. {0} пользователей присоединились к игре.", + "games_nunchi_ended": "Игра в нунчи завершилась. {0} выиграл", + "games_nunchi_ended_no_winner": "Игра в нунчи закончилась, никто не выиграл.", + "games_nunchi_started": "Игра в нунчи началась с {0} участниками.", + "games_nunchi_round_ended": "Раунд нунчи закончился. {0} выбывает из игры.", + "games_nunchi_round_ended_boot": "Раунд нунчи закончился, поскольку у игроков закончилось время. Игроки, оставшиеся в игре: {0}", + "games_nunchi_round_started": "Раунд нунчи начался с {0} пользователями. Отсчёт начинается с номера {1}.", + "games_nunchi_next_number": "Число записано. Предыдущее число —{0}.", + "games_nunchi_failed_to_start": "Игра в нунчи не началась, поскольку было недостаточно участников.", + "games_nunchi_created": "Игра в нунчи создана. Ожидание игроков.", + "music_sad_enabled": "Песни будут удаляться из очереди после завершения воспроизведения.", + "music_sad_disabled": "Песни больше не будут удаляться из очереди после завершения воспроизведения.", + "utility_stream_role_enabled": "Когда пользователь с ролью {0} начинает трансляцию, им будет добавлена роль {1}.", + "utility_stream_role_disabled": "Функция добавления специальной роли стримерам отключена.", + "utility_stream_role_kw_set": "Стримерам теперь будет требоваться ключевое слово {0} для получения роли.", + "utility_stream_role_kw_reset": "Ключевое слово для роли стримеров сброшено.", + "utility_stream_role_bl_add": "Пользователь {0} не будет получать роль стримера.", + "utility_stream_role_bl_add_fail": "Пользователь {0} уже в чёрном списке.", + "utility_stream_role_bl_rem": "Пользователь {0} больше не в чёрном списке.", + "utility_stream_role_bl_rem_fail": "Пользователь {0} не в чёрном списке.", + "utility_stream_role_wl_add": "Пользователь {0} будет получать роль стримера, даже если в названии их трансляции нет ключевого слова.", + "utility_stream_role_wl_add_fail": "Пользователь {0} уже в белом списке.", + "utility_stream_role_wl_rem": "Пользователь {0} больше не в белом списке.", + "utility_stream_role_wl_rem_fail": "Пользователь {0} не в белом списке.", + "utility_bot_config_edit_fail": "Не удалось выставить величину {0} равной {1}", + "utility_bot_config_edit_success": "Величина {0} была выставлена равной {1}", + "customreactions_crca_disabled": "Пользовательская реакция с id {0} будет вызываться, только если сообщение начинается со слова-триггера.", + "customreactions_crca_enabled": "Пользовательская реакция с id {0} будет теперь вызываться, если слово-триггер содержится в любой части сообщения.", + "xp_server_level": "Серверный уровень", + "xp_level": "Уровень", + "xp_club": "Клуб", + "xp_xp": "Опыт", + "xp_excluded": "{0} исключён из системы опыта на данном сервере.", + "xp_not_excluded": "{0} больше не исключён из системы опыта на данном сервере.", + "xp_exclusion_list": "Список исключённых серверов", + "xp_server_is_excluded": "Данный сервер исключён.", + "xp_server_is_not_excluded": "Данный сервер не исключён.", + "xp_excluded_roles": "Исключённые роли", + "xp_excluded_channels": "Исключённые текстовые каналы", + "xp_level_up_channel": "Поздравляем, {0}, Вы достигли уровень {1}!", + "xp_level_up_dm": "Поздравляем, {0}, Вы достигли уровень {1} на сервере {2}!", + "xp_level_up_global": "Поздравляем, {0}, Вы достигли глобальный уровень {1}!", + "xp_role_reward_cleared": "Пользователи больше не будут получать роль за уровень {0} .", + "xp_role_reward_added": "Пользователи, достигающие уровня {0}, будут получать роль {1}.", + "xp_role_rewards": "Награды для ролей", + "xp_level_x": "Уровень {0}", + "xp_no_role_rewards": "На этой странице нет наград для ролей.", + "xp_server_leaderboard": "Серверный рейтинг опыта", + "xp_global_leaderboard": "Глобальный рейтинг опыта", + "xp_modified": "Изменено количество серверного опыта на {1} для пользователя {0}", + "xp_club_create_error": "Не удалось создать клуб. Убедитесь, что Вы 5-го уровня или выше, и что Вы не состоите в клубе на настоящий момент.", + "xp_club_created": "Клуб {0} создан!", + "xp_club_not_exists": "Данный клуб не существует.", + "xp_club_applied": "Вы подали заявку на вступление в клуб {0}.", + "xp_club_apply_error": "Не удалось подать заявку. Либо Вы уже состоите в клубе, либо у Вас недостаточный уровень, либо Вас забанили в этом клубе.", + "xp_club_accepted": "Пользователь {0} принят в клуб.", + "xp_club_accept_error": "Пользователь не найден", + "xp_club_left": "Вы покинули клуб.", + "xp_club_not_in_club": "Вы либо не состоите в клубе, либо пытаетесь покинуть клуб, владельцем которого Вы являетесь.", + "xp_club_user_kick": "Пользователь {0} выгнан из клуба {1}.", + "xp_club_user_kick_fail": "Не удалось выгнать пользователя. Либо вы не являетесь владельцем клуба, либо данный пользователь не состоит в вашем клубе.", + "xp_club_user_banned": "Пользователь {0} забанен в клубе {1}.", + "xp_club_user_ban_fail": "Не удалось забанить. Вы либо не являетесь владельцем клуба, либо этот пользователь не состоит в клубе и не подавал заявку на вступление.", + "xp_club_user_unbanned": "Пользователь {0} разбанен в клубе {1}.", + "xp_club_user_unban_fail": "Не удалось разбанить. Вы либо не являетесь владельцем клуба, либо этот пользователь не состоит в клубе и не подавал заявку на вступление.", + "xp_club_level_req_changed": "Минимальный уровень для клуба изменён на {0}.", + "xp_club_level_req_change_error": "Не удалось изменить минимальный уровень для клуба.", + "xp_club_disbanded": "Клуб {0} распущен.", + "xp_club_disband_error": "Ошибка. Вы либо не состоите в клубе, либо не являетесь владельцем клуба.", + "xp_club_icon_error": "Неправильный URL для изображения, или Вы не являетесь владельцем клуба.", + "xp_club_icon_set": "Выставлен новый аватар клуба.", + "xp_club_bans_for": "Баны клуба {0}", + "xp_club_apps_for": "Заявки на вступление в клуб {0}", + "xp_club_leaderboard": "Рейтинг клуба - страница {0}" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.sr-cyrl-rs.json b/src/NadekoBot/_strings/ResponseStrings.sr-cyrl-rs.json index 703735f1..58f91079 100644 --- a/src/NadekoBot/_strings/ResponseStrings.sr-cyrl-rs.json +++ b/src/NadekoBot/_strings/ResponseStrings.sr-cyrl-rs.json @@ -243,7 +243,7 @@ "administration_sbdm": "You have been soft-banned from {0} server.\nReason: {1}", "administration_user_unbanned": "User Unbanned", "administration_migration_done": "Migration done!", - "adminsitration_migration_error": "Error while migrating, check bot's console for more information.", + "administration_migration_error": "Error while migrating, check bot's console for more information.", "administration_presence_updates": "Presence Updates", "administration_sb_user": "User Soft-Banned", "gambling_awarded": "has awarded {0} to {1}", diff --git a/src/NadekoBot/_strings/ResponseStrings.sv-SE.json b/src/NadekoBot/_strings/ResponseStrings.sv-SE.json index 692215a4..4e170a60 100644 --- a/src/NadekoBot/_strings/ResponseStrings.sv-SE.json +++ b/src/NadekoBot/_strings/ResponseStrings.sv-SE.json @@ -243,7 +243,7 @@ "administration_sbdm": "Du har blivit mjuk-bannad från {0} server.\nAnledning: {1}", "administration_user_unbanned": "Användare inte längre bannad.", "administration_migration_done": "Migration gjort!", - "adminsitration_migration_error": "Fel vid migrering, kontrollera botens konsol för mer information.", + "administration_migration_error": "Fel vid migrering, kontrollera botens konsol för mer information.", "administration_presence_updates": "Närvaro Uppdateringar.", "administration_sb_user": "Användare mjuk-bannad.", "gambling_awarded": "har tilldelat {0} till {1}", diff --git a/src/NadekoBot/_strings/ResponseStrings.tr-TR.json b/src/NadekoBot/_strings/ResponseStrings.tr-TR.json index 245f9b0e..e2254327 100644 --- a/src/NadekoBot/_strings/ResponseStrings.tr-TR.json +++ b/src/NadekoBot/_strings/ResponseStrings.tr-TR.json @@ -1,27 +1,4 @@ { - "clashofclans_base_already_claimed": "Bu köy daha önceden alınmış veya yok edilmiş.", - "clashofclans_base_already_destroyed": "Bu köy önceden yok edilmiş.", - "clashofclans_base_already_unclaimed": "Bu köy alınmamış.", - "clashofclans_base_destroyed": " {1}'ye karşı savaşta köy #{0} **BERTARAF EDİLDİ**", - "clashofclans_base_unclaimed": "{0},{2} karşı savaşta #{1} üssünü alamadı.", - "clashofclans_claimed_base": "{0}, {2} ile savaşan bir #{1} temel hak iddia etti.", - "clashofclans_claimed_other": "@{0} zaten #{1} aldın. Yeni bir tane daha alamazsın.", - "clashofclans_claim_expired": "{1}'a karşı savaşta @{0} tarafından yapılan hak talebinin süresi dolmuştur.", - "clashofclans_enemy": "Düşman", - "clashofclans_info_about_war": "{0} ile savaşa ilişkin bilgi", - "clashofclans_invalid_base_number": "Hatalı köy numarası.", - "clashofclans_invalid_size": "Savaş büyüklüğü geçerli değil.", - "clashofclans_list_active_wars": "Aktif olan savaşlar", - "clashofclans_not_claimed": "Alınmamış", - "clashofclans_not_partic": "Bu savaşa katılmadınız.", - "clashofclans_not_partic_or_destroyed": "@{0} Bu savaşa katılmadınız veya köyünüz yok edildi.", - "clashofclans_no_active_wars": "Aktif olan savaş yok.", - "clashofclans_size": "Büyüklük", - "clashofclans_war_already_started": "{0} ile olan savaş başladı.", - "clashofclans_war_created": "{0} ile şavaş çıkardın.", - "clashofclans_war_ended": "{0} ile olan savaş sona erdi.", - "clashofclans_war_not_exist": "Böyle bir savaş bulunamadı.", - "clashofclans_war_started": "{0} ile olan savaş başladı.", "customreactions_all_stats_cleared": "Tüm özel reaksiyon istatistikleri temizlendi.", "customreactions_deleted": "Özel reaksiyon silindi", "customreactions_insuff_perms": "Yetersiz yetki.Global özel reaksiyonlar için bot sahibi yetkisi gerkirken, server tabanlı özel reaksiyonlar için Administrator yetkisi gerekir.", @@ -151,7 +128,6 @@ "administration_old_nick": "Eski takma ad", "administration_old_topic": "Eski konu", "administration_perms": "Hata.Yüksek ihtimalle yeterli yetki'ye sahip değilim.", - "administration_perms_reset": "Bu server için yetkiler sıfırlandı.", "administration_prot_active": "Koruma Aktif", "administration_prot_disable": "{0} Bu sunucudaki {0} **Devre Dışı**.", "administration_prot_enable": "{0} Etkin", @@ -243,7 +219,6 @@ "administration_sbdm": "{0} sunucusundan hafif bir şekilde yasaklandınız.\nSebep: {1}", "administration_user_unbanned": "Kullanıcı yasağı kaldırıldı", "administration_migration_done": "Eski kayıtlar taşındı!", - "adminsitration_migration_error": "Taşıma işlemi sırasında hata oluştu, daha fazla bilgi için bot konsolunu kontrol edin.", "administration_presence_updates": "Durum güncellemeleri", "administration_sb_user": "Kullanıcı hafif bir şekilde yasaklandı.", "gambling_awarded": "{1} 'e {0} ödül vermiş", @@ -439,7 +414,6 @@ "music_rpl_enabled": "Çalma listesi tekrarı etkin.", "music_set_music_channel": "Şimdi bu kanaldaki şarkıların çalınması, bitmesi, duraklatılması ve çıkarılmasını yapacağım.", "music_skipped_to": "`{0}:{1}` atlandı", - "music_songs_shuffled": "Şarkılar karıştırıldı", "music_song_moved": "Şarkı taşındı", "music_time_format": "{0} saat {1} dakika {2} saniye", "music_to_position": "Konumlandır", @@ -605,9 +579,6 @@ "utility_convert_not_found": "{0} 'ı {1}' e dönüştüremem: birimleri bulunamadım", "utility_convert_type_error": "{0} 'ı {1}' e dönüştüremiyorum: birim türleri eşit değil.", "utility_created_at": "Tarihinde oluşturuldu", - "utility_csc_join": "Çapraz sunucu kanalına katıldı.", - "utility_csc_leave": "Çapraz sunucu kanalından ayrıldı.", - "utility_csc_token": "Bu,senin CSC anahtarın", "utility_custom_emojis": "Özel emojiler", "utility_error": "Hata", "utility_features": "Özellikler", @@ -754,7 +725,6 @@ "gambling_shop_role": "{0} rol alacaksınız.", "gambling_type": "Tip", "utility_clpa_next_update": "{0} 'da sonraki güncelleme", - "administration_global_perms_reset": "Genel izinler sıfırlandı.", "administration_gvc_disabled": "Oyun Ses Kanalı özelliği bu sunucuda devre dışı bırakıldı.", "administration_gvc_enabled": "{0} şimdi bir Oyun Sesli Kanal.", "administration_not_in_voice": "Bu sunucuda sesli kanalda değilsiniz.", @@ -782,5 +752,121 @@ "permissions_lgp_none": "Bloke edilmiş komut veya modül yok.", "gambling_animal_race_no_race": "Bu Hayvan Yarışı dolu!", "utility_cant_read_or_send": "Bu kanalda mesaj okuyamaz veya bu kanala mesaj gönderemezsiniz.", - "utility_quotes_notfound": "Belirtilen teklif kimliğine uyan teklif bulunamadı." + "utility_quotes_notfound": "Belirtilen teklif kimliğine uyan teklif bulunamadı.", + "administration_prefix_current": "Bu sunucudaki önek {0}", + "administration_prefix_new": "Bu sunucudaki bot önekini {0} 'ten {1} 'e değiştirildi", + "administration_defprefix_current": "Varsayılan bot öneki {0}", + "administration_defprefix_new": "Varsayılan bot önekini {0} 'ten {1} 'e değiştirildi", + "administration_bot_nick": "Bot'un takma adı {0} olarak değiştirildi", + "administration_user_nick": "{0} kullanıcısının kullanıcı adı {1} olarak değiştirildi", + "administration_timezone_guild": "Bu lonca için saat dilimi `{0}`", + "administration_timezone_not_found": "Saat dilimi bulunamadı. Kullanılabilen saat dilimlerinin listesini görmek için \"timezones\" komutunu kullanın", + "administration_timezones_available": "Kullanılabilir Saat Dilimleri", + "music_song_not_found": "Hiç şarkı bulunamadı.", + "searches_define_unknown": "Bu terim için tanım bulamıyor.", + "utility_repeater_initial": "İlk tekrarlanan mesaj {0} saat ve {1} dakika sonra gönderilecektir.", + "utility_verbose_errors_enabled": "Yanlış kullanılan komutlar artık hataları gösterecektir.", + "utility_verbose_errors_disabled": "Yanlış kullanılan komutlar artık hataları göstermeyecek.", + "permissions_perms_reset": "Bu sunucu için izinler sıfırlandı.", + "permissions_trigger": "{0} {1} izin numarası, bu işlemi önlüyor.", + "administration_migration_error": "Taşıma işlemi sırasında hata oluştu, daha fazla bilgi için bot konsolunu kontrol edin.", + "searches_hex_invalid": "Geçersiz renk belirtildi.", + "permissions_global_perms_reset": "Genel izinler sıfırlandı.", + "help_module": "Modül: {0}", + "games_hangman_stopped": "Hangman oyunu durdu.", + "music_autoplaying": "Otomatik-oynatılıyor.", + "music_queue_stopped": "Oynatıcı durduruldu. Oynatmaya başlamak için {0} komutunu kullanın.", + "music_removed_song_error": "Bu dizinde şarkı mevcut değil.", + "music_shuffling_playlist": "Şarkılar karıştırılıyor.", + "music_songs_shuffle_enable": "Şarkılar bundan sonra karıştırılacak.", + "music_songs_shuffle_disable": "Şarkılar artık karıştırılmayacak.", + "music_song_skips_after": "Şarkılar {0} sonrasında atlanacak", + "administration_warnings_list": "Sunucuda uyarılan tüm kullanıcıların listesi", + "customreactions_redacted_too_long": "Kırılmış çünkü çok uzun.", + "nsfw_blacklisted_tag_list": "Kara listeye alınmış etiketler listesi:", + "nsfw_blacklisted_tag": "Kullandığınız bir veya daha fazla etiket kara listeye alındı", + "nsfw_blacklisted_tag_add": "Nsfw etiketi {0} şimdi kara listeye alındı.", + "nsfw_blacklisted_tag_remove": "Nsfw etiketi {0} artık kara listeye alınmayacak.", + "gambling_waifu_gift": "{0} {1}'den daha yetenekli", + "gambling_waifu_gift_shop": "Waifu hediye dükkanı", + "gambling_gifts": "Hediyeler", + "games_connect4_created": "Connect4 oyunu kuruldu. Bir oyuncunun katılmasını bekliyorum.", + "games_connect4_player_to_move": "Taşınacak oyuncu: {0}", + "games_connect4_failed_to_start": "Connect4 oyunu başlayamadı, çünkü kimse katılmadı.", + "games_connect4_draw": "Connect4 oyunu berabere bitti.", + "games_connect4_won": "{0}, {1} a karşı Connect4 oyununu kazandı.", + "games_nunchi_joined": "Nunchi oyununa katıldın. şimdiye kadar {0} kullanıcı katıldı.", + "games_nunchi_ended": "Nunchi oyunu sona erdi. {0} kazandı", + "games_nunchi_ended_no_winner": "Nunchi oyunu kazanan olmaksızın bitti.", + "games_nunchi_started": "Nunchi oyunu {0} katılımcılarla başladı.", + "games_nunchi_round_ended": "Nunchi turu sona erdi. {0} oyun dışı.", + "games_nunchi_round_ended_boot": "Bazı kullanıcıların zaman aşımı nedeniyle Nunchi turu sona erdi. Bu kadar kullanıcı hala oyunda: {0}", + "games_nunchi_round_started": "Nunchi turu {0} kullanıcılarıyla başladı. {1} numarasından saymaya başlayın.", + "games_nunchi_next_number": "Numara kaydedildi. Son sayı {0} idi.", + "games_nunchi_failed_to_start": "Nunchi başlayamadı, çünkü yeterli katılımcı yoktu.", + "games_nunchi_created": "Nunchi oyunu kuruldu. Kullanıcıların katılmasını bekliyorum.", + "music_sad_enabled": "Şarkılar, çalmayı bitirince müzik kuyruğundan silinir.", + "music_sad_disabled": "Şarkılar, çalmayı bitirdiğinde artık müzik kuyruğundan silinmeyecek.", + "utility_stream_role_enabled": "{0} rolündeki bir kullanıcı yayın başlattığında, onlara {1} rol vereceğim.", + "utility_stream_role_disabled": "Yayın rolü özelliği devre dışı bırakıldı.", + "utility_stream_role_kw_set": "Yayıncılar artık rol alabilmek için {0} anahtar kelimesine ihtiyaç duyuyorlar.", + "utility_stream_role_kw_reset": "Yayın rolü anahtar kelimesini sıfırla.", + "utility_stream_role_bl_add": "{0} kullanıcısı asla yayın rolünü almaz.", + "utility_stream_role_bl_add_fail": "{0} kullanıcısı zaten kara listeye alındı.", + "utility_stream_role_bl_rem": "{0} kullanıcısı artık kara listede değil.", + "utility_stream_role_bl_rem_fail": "{0} kullanıcısı kara listeye alınmadı.", + "utility_stream_role_wl_add": "Kullanıcı {0}, yayın başlığında anahtar kelime olmasa da yayın rolünü alacak.", + "utility_stream_role_wl_add_fail": "{0} kullanıcısı zaten beyaz listeye alındı.", + "utility_stream_role_wl_rem": "{0} kullanıcısı artık beyaz listede değil.", + "utility_stream_role_wl_rem_fail": "{0} kullanıcısı beyaz listeye alınmadı.", + "utility_bot_config_edit_fail": "{0} değerini {1} değerine ayarlama başarısız oldu", + "utility_bot_config_edit_success": "{0} değeri {1} olarak ayarlandı.", + "customreactions_crca_disabled": "{0} kimlikli özel reaksiyon, tetikleyici kelime cümlenin başında olmadığı sürece tetiklenmeyecektir.", + "customreactions_crca_enabled": "{0} kimlikli özel reaksiyon, cümledeki herhangi bir yerde bulunursa tetiklenecektir.", + "xp_server_level": "Sunucu Seviyesi", + "xp_level": "Seviye", + "xp_club": "Kulüp", + "xp_xp": "Tecrübe", + "xp_excluded": "{0}, bu sunucu XP sisteminden çıkarıldı.", + "xp_not_excluded": "{0} artık bu sunucu XP sisteminden çıkartılmıyor.", + "xp_exclusion_list": "Dışlanmış listesi", + "xp_server_is_excluded": "Bu sunucu dışlanmıştır.", + "xp_server_is_not_excluded": "Bu sunucu dışlanmamıştır.", + "xp_excluded_roles": "Dışlanmış Roller", + "xp_excluded_channels": "Dışlanmış Kanallar", + "xp_level_up_channel": "Tebrikler {0}, {1} seviyesine ulaştınız!", + "xp_level_up_dm": "Tebrikler {0}, {2} sunucusunda {1} seviyesine ulaştınız!", + "xp_level_up_global": "Tebrikler {0}, {1} genel seviyesine ulaştınız!", + "xp_role_reward_cleared": "{0} seviyesi artık bir rolü ödüllendirmeyecek.", + "xp_role_reward_added": "{0} düzeyine ulaşan kullanıcılar {1} rol alacaktır.", + "xp_role_rewards": "Rol Ödülleri", + "xp_level_x": "Seviye {0}", + "xp_no_role_rewards": "Bu sayfada rol ödülü yok.", + "xp_server_leaderboard": "Sunucu XP Lider Tablosu", + "xp_global_leaderboard": "Genel XP Lider Tablosu", + "xp_modified": "{0} kullanıcısının {1} tarafından değiştirilen sunucu XP'si", + "xp_club_create_error": "Kulüp oluşturulamadı. Halihazırda bir kulübün üyesi olmadığınızdan ve 5. seviyenin üstünde olduğunuzdan emin olun.", + "xp_club_created": "Kulüp {0} başarıyla oluşturuldu!", + "xp_club_not_exists": "Bu kulüp yok.", + "xp_club_applied": "{0} kulübüne üyelik başvurusunda bulundunuz.", + "xp_club_apply_error": "Başvurularda hata oluştu. Siz zaten ya bir kulübün üyesiydiniz ya da minimum seviye gereksinimini karşılamıyorsunuz ya da bu kulüpten yasaklandınız.", + "xp_club_accepted": "{0} kullanıcısı kulübe kabul edildi.", + "xp_club_accept_error": "Kullanıcı bulunamadı.", + "xp_club_left": "Kulübü terk ettin.", + "xp_club_not_in_club": "Bir kulüpte değilsin veya kulübün sahibi olduğun kulüpten ayrılmaya çalışıyorsun.", + "xp_club_user_kick": "{0} kullanıcısı {1} kulübünden atıldı.", + "xp_club_user_kick_fail": "Atma hatası. Siz ya kulüp sahibi değilsiniz ya da o kullanıcı kulübünüzde değil.", + "xp_club_user_banned": "{0} kullanıcısı {1} kulübünden yasaklandı.", + "xp_club_user_ban_fail": "Yasaklama hatası. Siz ya kulüp sahibi değilsiniz ya da o kullanıcı kulübünüzde değil ya da kulübe başvurmuş.", + "xp_club_user_unbanned": "{0} kullanıcısı {1} kulübündeki yasak kaldırıldı.", + "xp_club_user_unban_fail": "Yasak kaldırmada hata. Siz ya kulüp sahibi değilsiniz ya da o kullanıcı kulübünüzde değil ya da kulübe başvurmuş.", + "xp_club_level_req_changed": "Kulübün seviye gereksinimini {0} olarak değiştirildi", + "xp_club_level_req_change_error": "Gerekli seviye değişimi başarısız.", + "xp_club_disbanded": "{0} kulübü dağıtıldı", + "xp_club_disband_error": "Hata. Ya bir kulüpte değilsin ya da kulübün sahibi değilsin.", + "xp_club_icon_error": "Geçersiz bir resim URL'si veya kulüp sahibi değilsiniz.", + "xp_club_icon_set": "Yeni kulüp simgesi ayarla.", + "xp_club_bans_for": "{0} kulübü için yasaklar", + "xp_club_apps_for": "{0} kulübü için başvuranlar", + "xp_club_leaderboard": "Kulüp lider tablosu - sayfa {0}" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.zh-CN.json b/src/NadekoBot/_strings/ResponseStrings.zh-CN.json index 7c9e5934..24303dc0 100644 --- a/src/NadekoBot/_strings/ResponseStrings.zh-CN.json +++ b/src/NadekoBot/_strings/ResponseStrings.zh-CN.json @@ -151,7 +151,6 @@ "administration_old_nick": "旧昵称", "administration_old_topic": "旧题目", "administration_perms": "失败。很可能我没有足够的权限。", - "administration_perms_reset": "重置此服务器的权限。", "administration_prot_active": "主动保护", "administration_prot_disable": "{0}已在此服务器禁用。", "administration_prot_enable": "{0}已启用。", @@ -243,7 +242,6 @@ "administration_sbdm": "您已从{0}服务器软禁止。\n原因:{1}", "administration_user_unbanned": "用户已取消禁止", "administration_migration_done": "迁移完成!", - "adminsitration_migration_error": "在迁移时出错,请检查机器人的控制台以获取更多信息。", "administration_presence_updates": "在线状态更新", "administration_sb_user": "用户被软禁用", "gambling_awarded": "已将{0}奖励给{1}", @@ -439,7 +437,6 @@ "music_rpl_enabled": "重复播放列表已启用。", "music_set_music_channel": "我现在将在此频道里输出播放,完成,暂停和删除歌曲。", "music_skipped_to": "跳到‘{0}:{1}’", - "music_songs_shuffled": "歌播放列表曲被随机。", "music_song_moved": "歌曲被移动", "music_time_format": "{0}小时 {1}分钟 {2}秒", "music_to_position": "到位置", @@ -605,9 +602,6 @@ "utility_convert_not_found": "无法将{0}转换为{1}:找不到单位", "utility_convert_type_error": "无法将{0}转换为{1}:单位类型不属于一类", "utility_created_at": "创建于", - "utility_csc_join": "加入跨服务器频道。", - "utility_csc_leave": "离开跨服务器频道。", - "utility_csc_token": "这是您的CSC令牌", "utility_custom_emojis": "自定义Emojis", "utility_error": "错误", "utility_features": "功能", @@ -754,7 +748,6 @@ "gambling_shop_role": "你会得到 {0} 身份", "gambling_type": "类型", "utility_clpa_next_update": "{0} 內进行下个更新", - "administration_global_perms_reset": "铜盘权限被重置", "administration_gvc_disabled": "这服务器的游戏语音频道功能已被禁用。", "administration_gvc_enabled": "{0} 现在是个游戏语音频道。", "administration_not_in_voice": "你不在这服务器的语音频道。", @@ -782,5 +775,34 @@ "permissions_lgp_none": "没有被禁的命令或模块", "gambling_animal_race_no_race": "这动物竞赛已充满了!", "utility_cant_read_or_send": "您不可以读或送短信在那通路", - "utility_quotes_notfound": "没找到引用像给的引用身份" + "utility_quotes_notfound": "没找到引用像给的引用身份", + "administration_prefix_current": "", + "administration_prefix_new": "", + "administration_defprefix_current": "", + "administration_defprefix_new": "", + "administration_bot_nick": "", + "administration_user_nick": "", + "administration_timezone_guild": "", + "administration_timezone_not_found": "", + "administration_timezones_available": "", + "music_song_not_found": "", + "searches_define_unknown": "", + "utility_repeater_initial": "", + "utility_verbose_errors_enabled": "", + "utility_verbose_errors_disabled": "", + "permissions_perms_reset": "", + "permissions_trigger": "", + "administration_migration_error": "", + "searches_hex_invalid": "", + "permissions_global_perms_reset": "", + "help_module": "", + "games_hangman_stopped": "", + "music_autoplaying": "", + "music_queue_stopped": "", + "music_removed_song_error": "", + "music_shuffling_playlist": "", + "music_songs_shuffle_enable": "", + "music_songs_shuffle_disable": "", + "music_song_skips_after": "", + "administration_warnings_list": "" } \ No newline at end of file diff --git a/src/NadekoBot/_strings/ResponseStrings.zh-TW.json b/src/NadekoBot/_strings/ResponseStrings.zh-TW.json index e2618efd..3cb5c124 100644 --- a/src/NadekoBot/_strings/ResponseStrings.zh-TW.json +++ b/src/NadekoBot/_strings/ResponseStrings.zh-TW.json @@ -1,27 +1,4 @@ { - "clashofclans_base_already_claimed": "該基地已被佔領或摧毀。", - "clashofclans_base_already_destroyed": "該基地已摧毀。", - "clashofclans_base_already_unclaimed": "該基地尚未佔領", - "clashofclans_base_destroyed": "在與 {1} 對戰時 **摧毀** 了基地 #{0}", - "clashofclans_base_unclaimed": "{0} 在與 {2} 對戰時 **解放** 基地 #{1}", - "clashofclans_claimed_base": "{0} 在與 {2} 對戰時 **佔領** 基地 #{1}", - "clashofclans_claimed_other": "@{0} 您已佔領基地 #{1}。您不能再佔領一個新的。", - "clashofclans_claim_expired": "在 @{0} 與 {1} 對戰後佔領的基地已失效。", - "clashofclans_enemy": "敵人", - "clashofclans_info_about_war": "與 {0} 對戰的信息", - "clashofclans_invalid_base_number": "無效的基地編號。", - "clashofclans_invalid_size": "無效的戰爭大小。", - "clashofclans_list_active_wars": "目前正在進行的戰爭", - "clashofclans_not_claimed": "未佔領", - "clashofclans_not_partic": "您並未参與此戰爭。", - "clashofclans_not_partic_or_destroyed": "@{0} 您並未参與此戰爭或此基地已被毁壞。", - "clashofclans_no_active_wars": "目前尚無戰爭進行中。", - "clashofclans_size": "大小", - "clashofclans_war_already_started": "與 {0} 的戰爭已經開始了。", - "clashofclans_war_created": "與 {0} 的戰爭已創建。", - "clashofclans_war_ended": "與 {0} 的戰爭已結束。", - "clashofclans_war_not_exist": "此戰爭不存在。", - "clashofclans_war_started": "與 {0} 的戰爭已開始!", "customreactions_all_stats_cleared": "所有自訂回應統計已刪除。", "customreactions_deleted": "自訂回應已刪除", "customreactions_insuff_perms": "權限不足。伺服器自訂回應需要管理員權限,而只有 Bot 擁有者才能設定全域自訂回應。", @@ -66,7 +43,7 @@ "administration_bandm": "你已被 {0} 伺服器封鎖。\n原因:{1}", "administration_banned_pl": "成員已封鎖", "administration_banned_user": "成員已封鎖", - "administration_bot_name": "Bot 暱稱已改為 {0}", + "administration_bot_name": "Bot 的暱稱已變更為 {0}", "administration_bot_status": "Bot 狀態已改為 {0}", "administration_byedel_off": "自動刪除告別訊息功能已停用。", "administration_byedel_on": "告別訊息將於 {0} 秒後刪除。", @@ -151,7 +128,6 @@ "administration_old_nick": "原始暱稱", "administration_old_topic": "原始主題", "administration_perms": "錯誤: 很可能我並沒有足夠的權限。", - "administration_perms_reset": "此伺服器的權限已重設。", "administration_prot_active": "生效中的保護機制", "administration_prot_disable": "{0} 已在此伺服器 **停用**。", "administration_prot_enable": "{0} 已啟用。", @@ -243,7 +219,6 @@ "administration_sbdm": "您已被伺服器 {0} 軟禁。\n理由: {1}", "administration_user_unbanned": "成員已解禁", "administration_migration_done": "合併完成!", - "adminsitration_migration_error": "在合併時發生問題,請查閱命令視窗來取得更多資訊。", "administration_presence_updates": "在線狀態更新", "administration_sb_user": "成員被軟禁", "gambling_awarded": "賜予了 {1} 給 {0}", @@ -285,7 +260,7 @@ "help_cmdlist_donate": "您可以透過 Patreon: <{0}> 或 Paypal: <{1}> 來贊助此專案", "help_cmd_and_alias": "指令和快捷", "help_commandlist_regen": "已重建指令清單。", - "help_commands_instr": "輸入`{0}h 指令名稱`來查閱該指令的詳細說明。 例如 `{0}h >8ball`", + "help_commands_instr": "輸入`{0}h 指令名稱`來查閱該指令的詳細說明。 例如 `{0}h {0}8ball`", "help_command_not_found": "我找不到该指令。請再次查詢前確定該指令是否存在。", "help_desc": "說明", "help_donate": "您可以透過:\nPatreon <{0}> 或\nPaypal <{1}>\n來贊助NadekoBot專案\n請不要忘記在訊息中留下您的ID。\n\n**感謝您**♥️", @@ -360,16 +335,16 @@ "games_category": "分類", "games_cleverbot_disabled": "已在此伺服器上停用Cleverbot。", "games_cleverbot_enabled": "已在此伺服器上啟用Cleverbot。", - "games_curgen_disabled": "停止在此頻道上生產貨幣。", - "games_curgen_enabled": "開始在此頻道上生產貨幣。", + "games_curgen_disabled": "停止在此頻道上生產代幣。", + "games_curgen_enabled": "開始在此頻道上生產代幣。", "games_curgen_pl": "發現了 {0} 個野生的 {1}!", "games_curgen_sn": "發現了一個野生的 {0}!", "games_failed_loading_question": "載入問題失敗。", "games_game_started": "遊戲開始", - "games_hangman_game_started": "Hangman遊戲開始了", - "games_hangman_running": "Hangman遊戲已經在此頻道上執行中。", - "games_hangman_start_errored": "Hangman啟動失敗。", - "games_hangman_types": "“{0}hangman” 單詞類型:", + "games_hangman_game_started": "猜單詞遊戲開始了", + "games_hangman_running": "猜單詞遊戲已經在此頻道上執行中。", + "games_hangman_start_errored": "猜單詞遊戲啟動失敗。", + "games_hangman_types": "“{0}猜單詞” 單詞類型列表:", "games_leaderboard": "排行榜", "games_not_enough": "您沒有足夠的 {0}", "games_no_results": "沒有結果", @@ -416,7 +391,7 @@ "music_no_search_results": "沒有搜尋結果。", "music_paused": "暫停播放音樂。", "music_player_queue": "播放清單 - 第 {0} / {1} 頁", - "music_playing_song": "正在播放", + "music_playing_song": "正在播放 #{0}", "music_playlists": "`#{0}` - **{1}** by *{2}* ({3}首歌)", "music_playlists_page": "已儲存的播放清單第 {0} 頁", "music_playlist_deleted": "播放清單已刪除。", @@ -439,7 +414,6 @@ "music_rpl_enabled": "啟用重複播放清單。", "music_set_music_channel": "我將在此頻道輸出播放、暫停、結束和移除歌曲的訊息。", "music_skipped_to": "跳至 `{0}:{1}`", - "music_songs_shuffled": "隨機播放", "music_song_moved": "歌曲移至", "music_time_format": "{0}小時{1}分鐘{2}秒", "music_to_position": "到位置", @@ -552,7 +526,7 @@ "searches_platform": "平台", "searches_pokemon_ability_none": "找不到技能", "searches_pokemon_none": "找不到寶可夢", - "searches_profile_link": "個人首頁:", + "searches_profile_link": "個人檔案:", "searches_quality": "品質:", "searches_quick_playtime": "快速對戰時間", "searches_quick_wins": "快速對戰勝場數", @@ -593,7 +567,7 @@ "utiliity_joined": "已加入", "utility_activity_line": "`{0}.` {1} [{2:F2}/秒] - 共 {3}", "utility_activity_page": "活動頁面 #{0}", - "utility_activity_users_total": "共 {0} 個使用者。", + "utility_activity_users_total": "共 {0} 個成員。", "utility_author": "作者", "utility_botid": "Bot ID", "utility_calcops": "可在 {0}calc 使用的函式", @@ -605,9 +579,6 @@ "utility_convert_not_found": "無法將 {0} 轉換成 {1}: 找不到單位", "utility_convert_type_error": "無法將 {0} 轉換成 {1}: 單位類型並不相同", "utility_created_at": "建立於", - "utility_csc_join": "加入了跨服頻道", - "utility_csc_leave": "離開了跨服頻道", - "utility_csc_token": "這個是你的跨服頻道代碼", "utility_custom_emojis": "自訂表情符號", "utility_error": "錯誤", "utility_features": "特色", @@ -663,7 +634,7 @@ "utility_server_info": "伺服器資訊", "utility_shard": "Shard", "utility_shard_stats": "Shard狀態", - "utility_shard_stats_txt": "Shard **#{0}** 的狀態與 {2} 伺服器為 {1} ", + "utility_shard_stats_txt": "{2} 伺服器 Shard **#{0}** 的狀態為 {1} - {3} 之前", "utility_showemojis": "**名稱:** {0} **連結:** {1}", "utility_showemojis_none": "找不到特殊的表情符號。", "utility_stats_songs": "正在播放 {0} 首歌,{1} 首已點播。", @@ -720,7 +691,7 @@ "administration_user_not_found": "找不到成員。", "administration_user_warned": "已警告成員 {0} 。", "administration_user_warned_and_punished": "成員 {0} 因警告多次故以 {1} 作為懲罰。", - "administration_warned_on": "在 {0} 伺服器上警告", + "administration_warned_on": "您在 {0} 伺服器上被警告", "administration_warned_on_by": "於 {0} {1},由 {2}", "administration_warnings_cleared": "已清除成員 {0} 上的所有警告。", "administration_warnings_none": "此頁沒有警告。", @@ -754,7 +725,6 @@ "gambling_shop_role": "您將會獲得{0}身分組。", "gambling_type": "類型", "utility_clpa_next_update": "下次更新於{0}", - "administration_global_perms_reset": "全域權限已重置。", "administration_gvc_disabled": "遊戲語音頻道功能在此伺服器上停用。", "administration_gvc_enabled": "{0}現在是一個遊戲語音頻道了。", "administration_not_in_voice": "你並沒有在此伺服器上的語音頻道。", @@ -786,5 +756,117 @@ "administration_prefix_current": "指令開頭於此伺服器為 {0}", "administration_prefix_new": "變更此伺服器指令開頭從 {0} 至 {1}", "administration_defprefix_current": "預設機器人指令開頭為 {0}", - "administration_defprefix_new": "變更預設機器人指令開頭從 {0} 至 {1}" + "administration_defprefix_new": "變更預設機器人指令開頭從 {0} 至 {1}", + "administration_bot_nick": "Bot 的暱稱已改為 {0}", + "administration_user_nick": "成員 {0} 的暱稱已改為 {1}", + "administration_timezone_guild": "這個公會的時區是 `{0}`", + "administration_timezone_not_found": "找不到該時區。使用\"timezones\"命令查看可用時區的列表。", + "administration_timezones_available": "可用的時區", + "music_song_not_found": "找不到歌曲。", + "searches_define_unknown": "找不到該詞語的定義。", + "utility_repeater_initial": "最初的重複訊息將發送於 {0}小時 {1}分鐘。", + "utility_verbose_errors_enabled": "使用錯誤的命令現在將顯示錯誤訊息。", + "utility_verbose_errors_disabled": "使用錯誤的命令現在將不會顯示錯誤訊息。", + "permissions_perms_reset": "此伺服器的權限已重設。", + "permissions_trigger": "權限設定#{0} {1}正在預防此行為。", + "administration_migration_error": "升級失敗,請參閱Bot的主畫面來獲得更多資訊。", + "searches_hex_invalid": "無效的顏色碼。", + "permissions_global_perms_reset": "全域權限已重設。", + "help_module": "模組: {0}", + "games_hangman_stopped": "猜單詞遊戲已結束。", + "music_autoplaying": "自動播放中。", + "music_queue_stopped": "播放器已停止。使用 {0} 指令來開始播放。", + "music_removed_song_error": "該索引上的歌曲不存在", + "music_shuffling_playlist": "播放清單洗牌中", + "music_songs_shuffle_enable": "開始隨機播放清單。", + "music_songs_shuffle_disable": "停止隨機播放清單。", + "music_song_skips_after": "音樂將在 {0} 後跳過", + "administration_warnings_list": "伺服器上所有被警告成員的列表", + "customreactions_redacted_too_long": "字串因太長而裁短。", + "nsfw_blacklisted_tag_list": "黑名單關鍵字:", + "nsfw_blacklisted_tag": "你所使用的部分關鍵字已被黑名單。", + "nsfw_blacklisted_tag_add": "{0}標籤已被新增至黑名單。", + "nsfw_blacklisted_tag_remove": "{0}標籤已被移除黑名單。", + "gambling_waifu_gift": "贈與了{0}給{1}", + "gambling_waifu_gift_shop": "後宮精品店", + "gambling_gifts": "獲得禮物", + "games_connect4_created": "建立了一個四子棋遊戲。等待另一位玩家的加入中。", + "games_connect4_player_to_move": "輪到玩家: {0}", + "games_connect4_failed_to_start": "因為沒有對手而無法開始四子棋遊戲。", + "games_connect4_draw": "平局。", + "games_connect4_won": "{0} 從和 {1} 的四子棋比賽中勝出。", + "games_nunchi_joined": "加入了心臟病遊戲。目前共有{0}位玩家。", + "games_nunchi_ended": "心臟病遊戲結束。{0}獲勝", + "games_nunchi_ended_no_winner": "心臟病遊戲在沒有勝利者的狀況下結束。", + "games_nunchi_started": "{0}位玩家開始了心臟病遊戲。", + "games_nunchi_round_ended": "心臟病本回合結束。{0}已遭淘汰。", + "games_nunchi_round_ended_boot": "心臟病回合已將部分沒有回應玩家剔除。目前還在遊戲內的玩家為: {0}", + "games_nunchi_round_started": "{0}位玩家的心臟病回合開始。請往上+1數右邊的數字: {1}。", + "games_nunchi_next_number": "號碼以註冊。目前號碼為{0}。", + "games_nunchi_failed_to_start": "因人數不足而無法開始心臟病遊戲 (最少要三位)。", + "games_nunchi_created": "心臟病遊戲開始了。等待其他玩家的加入。", + "music_sad_enabled": "歌曲會在撥放完畢後從清單中移除。", + "music_sad_disabled": "歌曲會在撥放完畢後保留在清單中。", + "utility_stream_role_enabled": "當擁有{0}身分組的使用者開始實況時,我將會給予他們{0}身分組。", + "utility_stream_role_disabled": "實況身分組已被停用。", + "utility_stream_role_kw_set": "實況主現在必須要包含 {0} 關鍵字才能獲得身分組。", + "utility_stream_role_kw_reset": "實況身分組關鍵字以重設。", + "utility_stream_role_bl_add": "{0} 使用者將永不獲得實況者群組", + "utility_stream_role_bl_add_fail": "使用者{0}已經在黑名單上了。", + "utility_stream_role_bl_rem": "使用者{0}不再會被黑名單。", + "utility_stream_role_bl_rem_fail": "使用者{0}並不在黑名單上。", + "utility_stream_role_wl_add": "使用者{0}將會獲得實況身放組,即便實況標題內並沒有任何關鍵字。", + "utility_stream_role_wl_add_fail": "使用者{0}已經在白名單上了。", + "utility_stream_role_wl_rem": "使用者{0}不再會被白名單了。", + "utility_stream_role_wl_rem_fail": "使用者{0}並不在白名單上。", + "utility_bot_config_edit_fail": "{0}的值在設定成{1}時失敗", + "utility_bot_config_edit_success": "{0}的值已被設定為{1}", + "customreactions_crca_disabled": "自訂回應編號{0}現在只會在訊息開頭包含關鍵字時觸發。", + "customreactions_crca_enabled": "自訂回應編號{0}現在會在訊息中有包含關鍵字時觸發。", + "xp_server_level": "伺服器等級", + "xp_level": "等級", + "xp_club": "公會", + "xp_xp": "經驗", + "xp_excluded": "{0}已被排除XP系統", + "xp_not_excluded": "{0} 不再從伺服器上的經驗值系統中排除。", + "xp_exclusion_list": "排除名單", + "xp_server_is_excluded": "這個伺服器已被排除在外。", + "xp_server_is_not_excluded": "這個伺服器不被排除在外。", + "xp_excluded_roles": "排除身分組", + "xp_excluded_channels": "排除頻道", + "xp_level_up_channel": "恭喜{0},您的等級已提升為{1}!", + "xp_level_up_dm": "恭喜{0},您的等級已於{2}伺服器上提升為{1}!", + "xp_level_up_global": "恭喜{0},您的全域等級提升為{1}!", + "xp_role_reward_cleared": "等級{0}不再會給予身分組。", + "xp_role_reward_added": "使用者達到等級{0}時將會獲得{1}身分組。", + "xp_role_rewards": "身分組獎勵", + "xp_level_x": "等級 {0}", + "xp_no_role_rewards": "這一頁上沒有身分組獎勵。", + "xp_server_leaderboard": "伺服器經驗值排名", + "xp_global_leaderboard": "全局經驗值排名", + "xp_modified": "修改成員 {0} 的經驗值為 {1}", + "xp_club_create_error": "公會創建失敗。請確保你已經超過等級5,而且不是其他公會的成員。", + "xp_club_created": "{0}已被成功建立!", + "xp_club_not_exists": "找不到此公會。", + "xp_club_applied": "你已經向{0}公會申請了會員。", + "xp_club_apply_error": "無法申請。你要不是已經是此公會的成員,要不就是你還沒達到最低入會等級需求。或你已經被此公會黑單了。", + "xp_club_accepted": "已批准成員 {0} 加入公會。.", + "xp_club_accept_error": "找不到使用者", + "xp_club_left": "你已離開公會。", + "xp_club_not_in_club": "你要不是目前沒有工會,或者就是你嘗試想要離開一個由你創立的工會。", + "xp_club_user_kick": "成員 {0} 已從 {1} 公會中被踢出。.", + "xp_club_user_kick_fail": "在踢除時發生錯誤。您不是公會的擁有者,或該用戶不在您的公會。", + "xp_club_user_banned": "已將{0}逐出{1}公會並設定黑名單。", + "xp_club_user_ban_fail": "無法設定黑名單。您要不是並非貴會會長,就是您想要設定黑名單的對象並不在您的公會內。", + "xp_club_user_unbanned": "將成員 {0} 從 {1} 俱樂部解除封鎖。", + "xp_club_user_unban_fail": "無法解除黑名單。您要不是並非貴會會長,就是您想要解除黑名單的對象並不在您的公會內。", + "xp_club_level_req_changed": "已設定本公會的最低需求等級為 {0}", + "xp_club_level_req_change_error": "變更等級需求失敗。", + "xp_club_disbanded": "{0} 已遭解散。", + "xp_club_disband_error": "錯誤: 你要不是沒有在公會,就是你並不是你所在公會的會長。", + "xp_club_icon_error": "圖示網址不正確或者你不是公會會長。", + "xp_club_icon_set": "已設定新的公會圖示。", + "xp_club_bans_for": "{0} 公會黑名單", + "xp_club_apps_for": "{0} 公會會員申請名單", + "xp_club_leaderboard": "公會排行榜 - 第 {0} 頁" } \ No newline at end of file diff --git a/src/NadekoBot/credentials_example.json b/src/NadekoBot/credentials_example.json index e9ec0b12..5074f8e0 100644 --- a/src/NadekoBot/credentials_example.json +++ b/src/NadekoBot/credentials_example.json @@ -1,19 +1,24 @@ { "ClientId": 123123123, - "BotId": null, "Token": "", "OwnerIds": [ - 105635576866156544 + 0 ], "LoLApiKey": "", "GoogleApiKey": "", "MashapeKey": "", "OsuApiKey": "", - "PatreonAccessToken": "", + "SoundCloudClientId": "", + "CleverbotApiKey": "", "CarbonKey": "", "Db": { "Type": "sqlite", - "ConnectionString": "Filename=./data/NadekoBot.db" + "ConnectionString": "Data Source=data/NadekoBot.db" }, - "TotalShards": 1 + "TotalShards": 1, + "PatreonAccessToken": "", + "PatreonCampaignId": "334038", + "ShardRunCommand": "", + "ShardRunArguments": "", + "ShardRunPort": null } \ No newline at end of file diff --git a/src/NadekoBot/data/command_strings.json b/src/NadekoBot/data/command_strings.json new file mode 100644 index 00000000..1640acb0 --- /dev/null +++ b/src/NadekoBot/data/command_strings.json @@ -0,0 +1,2990 @@ +{ + "h": { + "Cmd": "help h", + "Desc": "Either shows a help for a single command, or DMs you help link if no arguments are specified.", + "Usage": [ + "{0}h {0}cmds", + "{0}h" + ] + }, + "hgit": { + "Cmd": "hgit", + "Desc": "Generates the commandlist.md file.", + "Usage": [ + "{0}hgit" + ] + }, + "donate": { + "Cmd": "donate", + "Desc": "Instructions for helping the project financially.", + "Usage": [ + "{0}donate" + ] + }, + "modules": { + "Cmd": "modules mdls", + "Desc": "Lists all bot modules.", + "Usage": [ + "{0}modules" + ] + }, + "commands": { + "Cmd": "commands cmds", + "Desc": "List all of the bot's commands from a certain module. You can either specify the full name or only the first few letters of the module name.", + "Usage": [ + "{0}commands Administration", + "{0}cmds Admin" + ] + }, + "greetdel": { + "Cmd": "greetdel grdel", + "Desc": "Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set it to 0 to disable automatic deletion.", + "Usage": [ + "{0}greetdel 0", + "{0}greetdel 30" + ] + }, + "greet": { + "Cmd": "greet", + "Desc": "Toggles anouncements on the current channel when someone joins the server.", + "Usage": [ + "{0}greet" + ] + }, + "greetmsg": { + "Cmd": "greetmsg", + "Desc": "Sets a new join announcement message which will be shown in the server's channel. Type `%user%` if you want to mention the new member. Using it with no message will show the current greet message. You can use embed json from instead of a regular text, if you want the message to be embedded.", + "Usage": [ + "{0}greetmsg Welcome, %user%." + ] + }, + "bye": { + "Cmd": "bye", + "Desc": "Toggles anouncements on the current channel when someone leaves the server.", + "Usage": [ + "{0}bye" + ] + }, + "byemsg": { + "Cmd": "byemsg", + "Desc": "Sets a new leave announcement message. Type `%user%` if you want to show the name the user who left. Type `%id%` to show id. Using this command with no message will show the current bye message. You can use embed json from instead of a regular text, if you want the message to be embedded.", + "Usage": [ + "{0}byemsg %user% has left." + ] + }, + "byedel": { + "Cmd": "byedel", + "Desc": "Sets the time it takes (in seconds) for bye messages to be auto-deleted. Set it to `0` to disable automatic deletion.", + "Usage": [ + "{0}byedel 0", + "{0}byedel 30" + ] + }, + "greetdm": { + "Cmd": "greetdm", + "Desc": "Toggles whether the greet messages will be sent in a DM (This is separate from greet - you can have both, any or neither enabled).", + "Usage": [ + "{0}greetdm" + ] + }, + "logserver": { + "Cmd": "logserver", + "Desc": "Enables or Disables ALL log events. If enabled, all log events will log to this channel.", + "Usage": [ + "{0}logserver enable", + "{0}logserver disable" + ] + }, + "logignore": { + "Cmd": "logignore", + "Desc": "Toggles whether the `.logserver` command ignores this channel. Useful if you have hidden admin channel and public log channel.", + "Usage": [ + "{0}logignore" + ] + }, + "userpresence": { + "Cmd": "userpresence", + "Desc": "Starts logging to this channel when someone from the server goes online/offline/idle.", + "Usage": [ + "{0}userpresence" + ] + }, + "voicepresence": { + "Cmd": "voicepresence", + "Desc": "Toggles logging to this channel whenever someone joins or leaves a voice channel you are currently in.", + "Usage": [ + "{0}voicepresence" + ] + }, + "repeatinvoke": { + "Cmd": "repeatinvoke repinv", + "Desc": "Immediately shows the repeat message on a certain index and restarts its timer.", + "Usage": [ + "{0}repinv 1" + ] + }, + "repeat": { + "Cmd": "repeat", + "Desc": "Repeat a message every `X` minutes in the current channel. You can instead specify time of day for the message to be repeated at daily (make sure you've set your server's timezone). You can have up to 5 repeating messages on the server in total.", + "Usage": [ + "{0}repeat 5 Hello there", + "{0}repeat 17:30 tea time" + ] + }, + "rotateplaying": { + "Cmd": "rotateplaying ropl", + "Desc": "Toggles rotation of playing status of the dynamic strings you previously specified.", + "Usage": [ + "{0}ropl" + ] + }, + "addplaying": { + "Cmd": "addplaying adpl", + "Desc": "Adds a specified string to the list of playing strings to rotate. Supported placeholders: `%servers%`, `%users%`, `%playing%`, `%queued%`, `%time%`, `%shardid%`, `%shardcount%`, `%shardguilds%`.", + "Usage": [ + "{0}adpl" + ] + }, + "listplaying": { + "Cmd": "listplaying lipl", + "Desc": "Lists all playing statuses with their corresponding number.", + "Usage": [ + "{0}lipl" + ] + }, + "removeplaying": { + "Cmd": "removeplaying rmpl repl", + "Desc": "Removes a playing string on a given number.", + "Usage": [ + "{0}rmpl" + ] + }, + "slowmode": { + "Cmd": "slowmode", + "Desc": "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.", + "Usage": [ + "{0}slowmode 1 5", + "{0}slowmode" + ] + }, + "cleanvplust": { + "Cmd": "cleanvplust cv+t", + "Desc": "Deletes all text channels ending in `-voice` for which voicechannels are not found. Use at your own risk.", + "Usage": [ + "{0}cleanv+t" + ] + }, + "voiceplustext": { + "Cmd": "voice+text v+t", + "Desc": "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.", + "Usage": [ + "{0}v+t" + ] + }, + "scsc": { + "Cmd": "scsc", + "Desc": "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.", + "Usage": [ + "{0}scsc" + ] + }, + "jcsc": { + "Cmd": "jcsc", + "Desc": "Joins current channel to an instance of cross server channel using the token.", + "Usage": [ + "{0}jcsc TokenHere" + ] + }, + "lcsc": { + "Cmd": "lcsc", + "Desc": "Leaves a cross server channel instance from this channel.", + "Usage": [ + "{0}lcsc" + ] + }, + "asar": { + "Cmd": "asar", + "Desc": "Adds a role to the list of self-assignable roles.", + "Usage": [ + "{0}asar Gamer" + ] + }, + "rsar": { + "Cmd": "rsar", + "Desc": "Removes a specified role from the list of self-assignable roles.", + "Usage": [ + "{0}rsar" + ] + }, + "lsar": { + "Cmd": "lsar", + "Desc": "Lists all self-assignable roles.", + "Usage": [ + "{0}lsar" + ] + }, + "tesar": { + "Cmd": "togglexclsar tesar", + "Desc": "Toggles whether the self-assigned roles are exclusive. (So that any person can have only one of the self assignable roles)", + "Usage": [ + "{0}tesar" + ] + }, + "iam": { + "Cmd": "iam", + "Desc": "Adds a role to you that you choose. Role must be on a list of self-assignable roles.", + "Usage": [ + "{0}iam Gamer" + ] + }, + "iamnot": { + "Cmd": "iamnot iamn", + "Desc": "Removes a specified role from you. Role must be on a list of self-assignable roles.", + "Usage": [ + "{0}iamn Gamer" + ] + }, + "addcustreact": { + "Cmd": "addcustreact acr", + "Desc": "Add a custom reaction with a trigger and a response. Running this command in server requires the Administration permission. Running this command in DM is Bot Owner only and adds a new global custom reaction. Guide here: ", + "Usage": [ + "{0}acr \"hello\" Hi there %user%" + ] + }, + "listcustreact": { + "Cmd": "listcustreact lcr", + "Desc": "Lists global or server custom reactions (20 commands per page). Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. Specifying `all` argument instead of the number will DM you a text file with a list of all custom reactions.", + "Usage": [ + "{0}lcr 1", + "{0}lcr all" + ] + }, + "listcustreactg": { + "Cmd": "listcustreactg lcrg", + "Desc": "Lists global or server custom reactions (20 commands per page) grouped by trigger, and show a number of responses for each. Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions.", + "Usage": [ + "{0}lcrg 1" + ] + }, + "showcustreact": { + "Cmd": "showcustreact scr", + "Desc": "Shows a custom reaction's response on a given ID.", + "Usage": [ + "{0}scr 1" + ] + }, + "delcustreact": { + "Cmd": "delcustreact dcr", + "Desc": "Deletes a custom reaction on a specific index. If ran in DM, it is bot owner only and deletes a global custom reaction. If ran in a server, it requires Administration privileges and removes server custom reaction.", + "Usage": [ + "{0}dcr 5" + ] + }, + "autoassignrole": { + "Cmd": "autoassignrole aar", + "Desc": "Automaticaly assigns a specified role to every user who joins the server.", + "Usage": [ + "{0}aar` to disable, `{0}aar Role Name` to enabl" + ] + }, + "leave": { + "Cmd": "leave", + "Desc": "Makes Nadeko leave the server. Either server name or server ID is required.", + "Usage": [ + "{0}leave 123123123331" + ] + }, + "delmsgoncmd": { + "Cmd": "delmsgoncmd", + "Desc": "Toggles the automatic deletion of the user's successful command message to prevent chat flood.", + "Usage": [ + "{0}delmsgoncmd" + ] + }, + "restart": { + "Cmd": "restart", + "Desc": "Restarts the bot. Might not work.", + "Usage": [ + "{0}restart" + ] + }, + "setrole": { + "Cmd": "setrole sr", + "Desc": "Sets a role for a given user.", + "Usage": [ + "{0}sr @User Guest" + ] + }, + "removerole": { + "Cmd": "removerole rr", + "Desc": "Removes a role from a given user.", + "Usage": [ + "{0}rr @User Admin" + ] + }, + "renamerole": { + "Cmd": "renamerole renr", + "Desc": "Renames a role. The role you are renaming must be lower than bot's highest role.", + "Usage": [ + "{0}renr \"First role\" SecondRole" + ] + }, + "removeallroles": { + "Cmd": "removeallroles rar", + "Desc": "Removes all roles from a mentioned user.", + "Usage": [ + "{0}rar @User" + ] + }, + "createrole": { + "Cmd": "createrole cr", + "Desc": "Creates a role with a given name.", + "Usage": [ + "{0}cr Awesome Role" + ] + }, + "rolecolor": { + "Cmd": "rolecolor roleclr", + "Desc": "Set a role's color to the hex or 0-255 rgb color value provided.", + "Usage": [ + "{0}roleclr Admin 255 200 100", + "{0}roleclr Admin ffba55" + ] + }, + "ban": { + "Cmd": "ban b", + "Desc": "Bans a user by ID or name with an optional message.", + "Usage": [ + "{0}b \"@some Guy\" Your behaviour is toxic." + ] + }, + "softban": { + "Cmd": "softban sb", + "Desc": "Bans and then unbans a user by ID or name with an optional message.", + "Usage": [ + "{0}sb \"@some Guy\" Your behaviour is toxic." + ] + }, + "kick": { + "Cmd": "kick k", + "Desc": "Kicks a mentioned user.", + "Usage": [ + "{0}k \"@some Guy\" Your behaviour is toxic." + ] + }, + "mute": { + "Cmd": "mute", + "Desc": "Mutes a mentioned user both from speaking and chatting. You can also specify time in minutes (up to 1440) for how long the user should be muted.", + "Usage": [ + "{0}mute @Someone", + "{0}mute 30 @Someone" + ] + }, + "voiceunmute": { + "Cmd": "voiceunmute", + "Desc": "Gives a previously voice-muted user a permission to speak.", + "Usage": [ + "{0}voiceunmute @Someguy" + ] + }, + "deafen": { + "Cmd": "deafen deaf", + "Desc": "Deafens mentioned user or users.", + "Usage": [ + "{0}deaf \"@Someguy\"", + "{0}deaf \"@Someguy\" \"@Someguy\"" + ] + }, + "undeafen": { + "Cmd": "undeafen undef", + "Desc": "Undeafens mentioned user or users.", + "Usage": [ + "{0}undef \"@Someguy\"", + "{0}undef \"@Someguy\" \"@Someguy\"" + ] + }, + "delvoichanl": { + "Cmd": "delvoichanl dvch", + "Desc": "Deletes a voice channel with a given name.", + "Usage": [ + "{0}dvch VoiceChannelName" + ] + }, + "creatvoichanl": { + "Cmd": "creatvoichanl cvch", + "Desc": "Creates a new voice channel with a given name.", + "Usage": [ + "{0}cvch VoiceChannelName" + ] + }, + "deltxtchanl": { + "Cmd": "deltxtchanl dtch", + "Desc": "Deletes a text channel with a given name.", + "Usage": [ + "{0}dtch TextChannelName" + ] + }, + "creatxtchanl": { + "Cmd": "creatxtchanl ctch", + "Desc": "Creates a new text channel with a given name.", + "Usage": [ + "{0}ctch TextChannelName" + ] + }, + "settopic": { + "Cmd": "settopic st", + "Desc": "Sets a topic on the current channel.", + "Usage": [ + "{0}st My new topic" + ] + }, + "setchanlname": { + "Cmd": "setchanlname schn", + "Desc": "Changes the name of the current channel.", + "Usage": [ + "{0}schn NewName" + ] + }, + "prune": { + "Cmd": "prune clear", + "Desc": "`{0}prune` removes all Nadeko's messages in the last 100 messages. `{0}prune X` removes last `X` number of messages from the channel (up to 100). `{0}prune @Someone` removes all Someone's messages in the last 100 messages. `{0}prune @Someone X` removes last `X` number of 'Someone's' messages in the channel.", + "Usage": [ + "{0}prune", + "{0}prune 5", + "{0}prune @Someone", + "{0}prune @Someone X" + ] + }, + "die": { + "Cmd": "die", + "Desc": "Shuts the bot down.", + "Usage": [ + "{0}die" + ] + }, + "setname": { + "Cmd": "setname newnm", + "Desc": "Gives the bot a new name.", + "Usage": [ + "{0}newnm BotName" + ] + }, + "setnick": { + "Cmd": "setnick", + "Desc": "Changes the nickname of the bot on this server. You can also target other users to change their nickname.", + "Usage": [ + "{0}setnick BotNickname", + "{0}setnick @SomeUser New Nickname" + ] + }, + "setavatar": { + "Cmd": "setavatar setav", + "Desc": "Sets a new avatar image for the NadekoBot. Argument is a direct link to an image.", + "Usage": [ + "{0}setav http://i.imgur.com/xTG3a1I.jpg" + ] + }, + "setgame": { + "Cmd": "setgame", + "Desc": "Sets the bots game.", + "Usage": [ + "{0}setgame with snakes" + ] + }, + "send": { + "Cmd": "send", + "Desc": "Sends a message to someone on a different server through the bot. Separate server and channel/user ids with `|` and prefix the channel id with `c:` and the user id with `u:`.", + "Usage": [ + "{0}send serverid|c:channelid message", + "{0}send serverid|u:userid message" + ] + }, + "mentionrole": { + "Cmd": "mentionrole menro", + "Desc": "Mentions every person from the provided role or roles (separated by a ',') on this server.", + "Usage": [ + "{0}menro RoleName" + ] + }, + "unstuck": { + "Cmd": "unstuck", + "Desc": "Clears the message queue.", + "Usage": [ + "{0}unstuck" + ] + }, + "donators": { + "Cmd": "donators", + "Desc": "List of the lovely people who donated to keep this project alive.", + "Usage": [ + "{0}donators" + ] + }, + "donadd": { + "Cmd": "donadd", + "Desc": "Add a donator to the database.", + "Usage": [ + "{0}donadd Donate Amount" + ] + }, + "savechat": { + "Cmd": "savechat", + "Desc": "Saves a number of messages to a text file and sends it to you.", + "Usage": [ + "{0}savechat 150" + ] + }, + "remind": { + "Cmd": "remind", + "Desc": "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.", + "Usage": [ + "{0}remind me 1d5h Do something", + "{0}remind #general 1m Start now!" + ] + }, + "remindtemplate": { + "Cmd": "remindtemplate", + "Desc": "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.", + "Usage": [ + "{0}remindtemplate %user%, do %message%!" + ] + }, + "serverinfo": { + "Cmd": "serverinfo sinfo", + "Desc": "Shows info about the server the bot is on. If no server is supplied, it defaults to current one.", + "Usage": [ + "{0}sinfo Some Server" + ] + }, + "channelinfo": { + "Cmd": "channelinfo cinfo", + "Desc": "Shows info about the channel. If no channel is supplied, it defaults to current one.", + "Usage": [ + "{0}cinfo #some-channel" + ] + }, + "userinfo": { + "Cmd": "userinfo uinfo", + "Desc": "Shows info about the user. If no user is supplied, it defaults a user running the command.", + "Usage": [ + "{0}uinfo @SomeUser" + ] + }, + "whosplaying": { + "Cmd": "whosplaying whpl", + "Desc": "Shows a list of users who are playing the specified game.", + "Usage": [ + "{0}whpl Overwatch" + ] + }, + "inrole": { + "Cmd": "inrole", + "Desc": "Lists every person from the specified role on this server. You can use role ID, role name.", + "Usage": [ + "{0}inrole Some Role" + ] + }, + "checkmyperms": { + "Cmd": "checkmyperms", + "Desc": "Checks your user-specific permissions on this channel.", + "Usage": [ + "{0}checkmyperms" + ] + }, + "stats": { + "Cmd": "stats", + "Desc": "Shows some basic stats for Nadeko.", + "Usage": [ + "{0}stats" + ] + }, + "userid": { + "Cmd": "userid uid", + "Desc": "Shows user ID.", + "Usage": [ + "{0}uid", + "{0}uid @SomeGuy" + ] + }, + "channelid": { + "Cmd": "channelid cid", + "Desc": "Shows current channel ID.", + "Usage": [ + "{0}cid" + ] + }, + "serverid": { + "Cmd": "serverid sid", + "Desc": "Shows current server ID.", + "Usage": [ + "{0}sid" + ] + }, + "roles": { + "Cmd": "roles", + "Desc": "List roles on this server or a roles of a specific user if specified. Paginated, 20 roles per page.", + "Usage": [ + "{0}roles 2", + "{0}roles @Someone" + ] + }, + "channeltopic": { + "Cmd": "channeltopic ct", + "Desc": "Sends current channel's topic as a message.", + "Usage": [ + "{0}ct" + ] + }, + "chnlfilterinv": { + "Cmd": "chnlfilterinv cfi", + "Desc": "Toggles automatic deletion of invites posted in the channel. Does not negate the `{0}srvrfilterinv` enabled setting. Does not affect the Bot Owner.", + "Usage": [ + "{0}cfi" + ] + }, + "srvrfilterinv": { + "Cmd": "srvrfilterinv sfi", + "Desc": "Toggles automatic deletion of invites posted in the server. Does not affect the Bot Owner.", + "Usage": [ + "{0}sfi" + ] + }, + "chnlfilterwords": { + "Cmd": "chnlfilterwords cfw", + "Desc": "Toggles automatic deletion of messages containing filtered words on the channel. Does not negate the `{0}srvrfilterwords` enabled setting. Does not affect the Bot Owner.", + "Usage": [ + "{0}cfw" + ] + }, + "filterword": { + "Cmd": "fw", + "Desc": "Adds or removes (if it exists) a word from the list of filtered words. Use`{0}sfw` or `{0}cfw` to toggle filtering.", + "Usage": [ + "{0}fw poop" + ] + }, + "lstfilterwords": { + "Cmd": "lstfilterwords lfw", + "Desc": "Shows a list of filtered words.", + "Usage": [ + "{0}lfw" + ] + }, + "srvrfilterwords": { + "Cmd": "srvrfilterwords sfw", + "Desc": "Toggles automatic deletion of messages containing filtered words on the server. Does not affect the Bot Owner.", + "Usage": [ + "{0}sfw" + ] + }, + "permrole": { + "Cmd": "permrole pr", + "Desc": "Sets a role which can change permissions. Supply no parameters to see the current one. Default is 'Nadeko'.", + "Usage": [ + "{0}pr role" + ] + }, + "verbose": { + "Cmd": "verbose v", + "Desc": "Sets whether to show when a command/module is blocked.", + "Usage": [ + "{0}verbose true" + ] + }, + "srvrmdl": { + "Cmd": "srvrmdl sm", + "Desc": "Sets a module's permission at the server level.", + "Usage": [ + "{0}sm ModuleName enable" + ] + }, + "srvrcmd": { + "Cmd": "srvrcmd sc", + "Desc": "Sets a command's permission at the server level.", + "Usage": [ + "{0}sc \"command name\" disable" + ] + }, + "rolemdl": { + "Cmd": "rolemdl rm", + "Desc": "Sets a module's permission at the role level.", + "Usage": [ + "{0}rm ModuleName enable MyRole" + ] + }, + "rolecmd": { + "Cmd": "rolecmd rc", + "Desc": "Sets a command's permission at the role level.", + "Usage": [ + "{0}rc \"command name\" disable MyRole" + ] + }, + "chnlmdl": { + "Cmd": "chnlmdl cm", + "Desc": "Sets a module's permission at the channel level.", + "Usage": [ + "{0}cm ModuleName enable SomeChannel" + ] + }, + "chnlcmd": { + "Cmd": "chnlcmd cc", + "Desc": "Sets a command's permission at the channel level.", + "Usage": [ + "{0}cc \"command name\" enable SomeChannel" + ] + }, + "usrmdl": { + "Cmd": "usrmdl um", + "Desc": "Sets a module's permission at the user level.", + "Usage": [ + "{0}um ModuleName enable SomeUsername" + ] + }, + "usrcmd": { + "Cmd": "usrcmd uc", + "Desc": "Sets a command's permission at the user level.", + "Usage": [ + "{0}uc \"command name\" enable SomeUsername" + ] + }, + "allsrvrmdls": { + "Cmd": "allsrvrmdls asm", + "Desc": "Enable or disable all modules for your server.", + "Usage": [ + "{0}asm [enable/disable]" + ] + }, + "allchnlmdls": { + "Cmd": "allchnlmdls acm", + "Desc": "Enable or disable all modules in a specified channel.", + "Usage": [ + "{0}acm enable #SomeChannel" + ] + }, + "allrolemdls": { + "Cmd": "allrolemdls arm", + "Desc": "Enable or disable all modules for a specific role.", + "Usage": [ + "{0}arm [enable/disable] MyRole" + ] + }, + "userblacklist": { + "Cmd": "ubl", + "Desc": "Either [add]s or [rem]oves a user specified by a Mention or an ID from a blacklist.", + "Usage": [ + "{0}ubl add @SomeUser", + "{0}ubl rem 12312312313" + ] + }, + "channelblacklist": { + "Cmd": "cbl", + "Desc": "Either [add]s or [rem]oves a channel specified by an ID from a blacklist.", + "Usage": [ + "{0}cbl rem 12312312312" + ] + }, + "serverblacklist": { + "Cmd": "sbl", + "Desc": "Either [add]s or [rem]oves a server specified by a Name or an ID from a blacklist.", + "Usage": [ + "{0}sbl add 12312321312", + "{0}sbl rem SomeTrashServer" + ] + }, + "cmdcooldown": { + "Cmd": "cmdcooldown cmdcd", + "Desc": "Sets a cooldown per user for a command. Set it to 0 to remove the cooldown.", + "Usage": [ + "{0}cmdcd \"some cmd\" 5" + ] + }, + "allcmdcooldowns": { + "Cmd": "allcmdcooldowns acmdcds", + "Desc": "Shows a list of all commands and their respective cooldowns.", + "Usage": [ + "{0}acmdcds" + ] + }, + "addquote": { + "Cmd": ".", + "Desc": "Adds a new quote with the specified name and message.", + "Usage": [ + "{0}. sayhi Hi" + ] + }, + "showquote": { + "Cmd": "..", + "Desc": "Shows a random quote with a specified name.", + "Usage": [ + "{0}.. abc" + ] + }, + "quotesearch": { + "Cmd": "qsearch", + "Desc": "Shows a random quote for a keyword that contains any text specified in the search.", + "Usage": [ + "{0}qsearch keyword text" + ] + }, + "quoteid": { + "Cmd": "quoteid qid", + "Desc": "Displays the quote with the specified ID number. Quote ID numbers can be found by typing `.liqu [num]` where `[num]` is a number of a page which contains 15 quotes.", + "Usage": [ + "{0}qid 123456" + ] + }, + "quotedelete": { + "Cmd": "quotedel qdel", + "Desc": "Deletes a quote with the specified ID. You have to be either server Administrator or the creator of the quote to delete it.", + "Usage": [ + "{0}qdel 123456" + ] + }, + "draw": { + "Cmd": "draw", + "Desc": "Draws a card from this server's deck. You can draw up to 10 cards by supplying a number of cards to draw.", + "Usage": [ + "{0}draw", + "{0}draw 5" + ] + }, + "drawnew": { + "Cmd": "drawnew", + "Desc": "Draws a card from the NEW deck of cards. You can draw up to 10 cards by supplying a number of cards to draw.", + "Usage": [ + "{0}drawnew", + "{0}drawnew 5" + ] + }, + "shuffleplaylist": { + "Cmd": "shuffle sh plsh", + "Desc": "Shuffles the current playlist.", + "Usage": [ + "{0}plsh" + ] + }, + "flip": { + "Cmd": "flip", + "Desc": "Flips coin(s) - heads or tails, and shows an image.", + "Usage": [ + "{0}flip", + "{0}flip 3" + ] + }, + "betflip": { + "Cmd": "betflip bf", + "Desc": "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.", + "Usage": [ + "{0}bf 5 heads", + "{0}bf 3 t" + ] + }, + "roll": { + "Cmd": "roll", + "Desc": "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.", + "Usage": [ + "{0}roll", + "{0}roll 7", + "{0}roll 3d5", + "{0}roll 5dF" + ] + }, + "rolluo": { + "Cmd": "rolluo", + "Desc": "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`.", + "Usage": [ + "{0}rolluo", + "{0}rolluo 7", + "{0}rolluo 3d5" + ] + }, + "nroll": { + "Cmd": "nroll", + "Desc": "Rolls in a given range.", + "Usage": [ + "{0}nroll 5` (rolls 0-5", + "{0}nroll 5-15" + ] + }, + "race": { + "Cmd": "race", + "Desc": "Starts a new animal race.", + "Usage": [ + "{0}race" + ] + }, + "joinrace": { + "Cmd": "joinrace jr", + "Desc": "Joins a new race. You can specify an amount of currency for betting (optional). You will get YourBet*(participants-1) back if you win.", + "Usage": [ + "{0}jr", + "{0}jr 5" + ] + }, + "nunchi": { + "Cmd": "nunchi", + "Desc": "Creates or joins an existing nunchi game. Users have to count up by 1 from the starting number shown by the bot. If someone makes a mistake (types an incorrent number, or repeats the same number) they are out of the game and a new round starts without them. Minimum 3 users required.", + "Usage": [ + "{0}nunchi" + ] + }, + "connect4": { + "Cmd": "connect4 con4", + "Desc": "Creates or joins an existing connect4 game. 2 players are required for the game. Objective of the game is to get 4 of your pieces next to each other in a vertical, horizontal or diagonal line.", + "Usage": [ + "{0}connect4" + ] + }, + "raffle": { + "Cmd": "raffle", + "Desc": "Prints a name and ID of a random user from the online list from the (optional) role.", + "Usage": [ + "{0}raffle", + "{0}raffle RoleName" + ] + }, + "give": { + "Cmd": "give", + "Desc": "Give someone a certain amount of currency.", + "Usage": [ + "{0}give 1 @SomeGuy" + ] + }, + "award": { + "Cmd": "award", + "Desc": "Awards someone a certain amount of currency. You can also specify a role name to award currency to all users in a role.", + "Usage": [ + "{0}award 100 @person", + "{0}award 5 Role Of Gamblers" + ] + }, + "take": { + "Cmd": "take", + "Desc": "Takes a certain amount of currency from someone.", + "Usage": [ + "{0}take 1 @SomeGuy" + ] + }, + "betroll": { + "Cmd": "betroll br", + "Desc": "Bets a certain amount of currency and rolls a dice. Rolling over 66 yields x2 of your currency, over 90 - x4 and 100 x10.", + "Usage": [ + "{0}br 5" + ] + }, + "wheeloffortune": { + "Cmd": "wheeloffortune wheel", + "Desc": "Bets a certain amount of currency on the wheel of fortune. Wheel can stop on one of many different multipliers. Won amount is rounded down to the nearest whole number.", + "Usage": [ + "{0}wheel 10" + ] + }, + "leaderboard": { + "Cmd": "leaderboard lb", + "Desc": "Displays the bot's currency leaderboard.", + "Usage": [ + "{0}lb" + ] + }, + "trivia": { + "Cmd": "trivia t", + "Desc": "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.", + "Usage": [ + "{0}t", + "{0}t 5 nohint" + ] + }, + "tl": { + "Cmd": "tl", + "Desc": "Shows a current trivia leaderboard.", + "Usage": [ + "{0}tl" + ] + }, + "tq": { + "Cmd": "tq", + "Desc": "Quits current trivia after current question.", + "Usage": [ + "{0}tq" + ] + }, + "typestart": { + "Cmd": "typestart", + "Desc": "Starts a typing contest.", + "Usage": [ + "{0}typestart" + ] + }, + "typestop": { + "Cmd": "typestop", + "Desc": "Stops a typing contest on the current channel.", + "Usage": [ + "{0}typestop" + ] + }, + "typeadd": { + "Cmd": "typeadd", + "Desc": "Adds a new article to the typing contest.", + "Usage": [ + "{0}typeadd wordswords" + ] + }, + "pollend": { + "Cmd": "pollend", + "Desc": "Stops active poll on this server and prints the results in this channel.", + "Usage": [ + "{0}pollend" + ] + }, + "pick": { + "Cmd": "pick", + "Desc": "Picks the currency planted in this channel. 60 seconds cooldown.", + "Usage": [ + "{0}pick" + ] + }, + "plant": { + "Cmd": "plant", + "Desc": "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)", + "Usage": [ + "{0}plant", + "{0}plant 5" + ] + }, + "gencurrency": { + "Cmd": "gencurrency gc", + "Desc": "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%)", + "Usage": [ + "{0}gc" + ] + }, + "leet": { + "Cmd": "leet", + "Desc": "Converts a text to leetspeak with 6 (1-6) severity levels", + "Usage": [ + "{0}leet 3 Hello" + ] + }, + "choose": { + "Cmd": "choose", + "Desc": "Chooses a thing from a list of things", + "Usage": [ + "{0}choose Get up;Sleep;Sleep more" + ] + }, + "rps": { + "Cmd": "rps", + "Desc": "Play a game of Rocket-Paperclip-Scissors with Nadeko.", + "Usage": [ + "{0}rps scissors" + ] + }, + "linux": { + "Cmd": "linux", + "Desc": "Prints a customizable Linux interjection", + "Usage": [ + "{0}linux Spyware Windows" + ] + }, + "next": { + "Cmd": "next n", + "Desc": "Goes to the next song in the queue. You have to be in the same voice channel as the bot. You can skip multiple songs, but in that case songs will not be requeued if {0}rcs or {0}rpl is enabled.", + "Usage": [ + "{0}n", + "{0}n 5" + ] + }, + "play": { + "Cmd": "play start", + "Desc": "If no arguments are specified, acts as `{0}next 1` command. If you specify a song number, it will jump to that song. If you specify a search query, acts as a `{0}q` command", + "Usage": [ + "{0}play", + "{0}play 5", + "{0}play Dream Of Venice" + ] + }, + "stop": { + "Cmd": "stop s", + "Desc": "Stops the music and preserves the current song index. Stays in the channel.", + "Usage": [ + "{0}s" + ] + }, + "destroy": { + "Cmd": "destroy d", + "Desc": "Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour)", + "Usage": [ + "{0}d" + ] + }, + "pause": { + "Cmd": "pause p", + "Desc": "Pauses or Unpauses the song.", + "Usage": [ + "{0}p" + ] + }, + "queue": { + "Cmd": "queue q yq", + "Desc": "Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**.", + "Usage": [ + "{0}q Dream Of Venice" + ] + }, + "queuenext": { + "Cmd": "queuenext qn", + "Desc": "Works the same as `{0}queue` command, except it enqueues the new song after the current one. **You must be in a voice channel**.", + "Usage": [ + "{0}qn Dream Of Venice" + ] + }, + "queuesearch": { + "Cmd": "queuesearch qs yqs", + "Desc": "Search for top 5 youtube song result using keywords, and type the index of the song to play that song. Bot will join your voice channel. **You must be in a voice channel**.", + "Usage": [ + "{0}qs Dream Of Venice" + ] + }, + "soundcloudqueue": { + "Cmd": "soundcloudqueue sq", + "Desc": "Queue a soundcloud song using keywords. Bot will join your voice channel. **You must be in a voice channel**.", + "Usage": [ + "{0}sq Dream Of Venice" + ] + }, + "listqueue": { + "Cmd": "listqueue lq", + "Desc": "Lists 10 currently queued songs per page. Default page is 1.", + "Usage": [ + "{0}lq", + "{0}lq 2" + ] + }, + "nowplaying": { + "Cmd": "nowplaying np", + "Desc": "Shows the song that the bot is currently playing.", + "Usage": [ + "{0}np" + ] + }, + "volume": { + "Cmd": "volume vol", + "Desc": "Sets the music playback volume (0-100%)", + "Usage": [ + "{0}vol 50" + ] + }, + "defvol": { + "Cmd": "defvol dv", + "Desc": "Sets the default music volume when music playback is started (0-100). Persists through restarts.", + "Usage": [ + "{0}dv 80" + ] + }, + "max": { + "Cmd": "max", + "Desc": "Sets the music playback volume to 100%.", + "Usage": [ + "{0}max" + ] + }, + "half": { + "Cmd": "half", + "Desc": "Sets the music playback volume to 50%.", + "Usage": [ + "{0}half" + ] + }, + "playlist": { + "Cmd": "playlist pl", + "Desc": "Queues up to 500 songs from a youtube playlist specified by a link, or keywords.", + "Usage": [ + "{0}pl playlist lin", + "ame" + ] + }, + "soundcloudpl": { + "Cmd": "soundcloudpl scpl", + "Desc": "Queue a Soundcloud playlist using a link.", + "Usage": [ + "{0}scpl soundcloudseturl" + ] + }, + "localpl": { + "Cmd": "localplaylst lopl", + "Desc": "Queues all songs from a directory.", + "Usage": [ + "{0}lopl C:/music/classical" + ] + }, + "radio": { + "Cmd": "radio ra", + "Desc": "Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: )", + "Usage": [ + "{0}ra radio link here" + ] + }, + "local": { + "Cmd": "local lo", + "Desc": "Queues a local file by specifying a full path.", + "Usage": [ + "{0}lo C:/music/mysong.mp3" + ] + }, + "move": { + "Cmd": "move mv", + "Desc": "Moves the bot to your voice channel. (works only if music is already playing)", + "Usage": [ + "{0}mv" + ] + }, + "songremove": { + "Cmd": "songremove srm", + "Desc": "Remove a song by its # in the queue, or 'all' to remove all songs from the queue and reset the song index.", + "Usage": [ + "{0}srm 5" + ] + }, + "movesong": { + "Cmd": "movesong ms", + "Desc": "Moves a song from one position to another.", + "Usage": [ + "{0}ms 5>3" + ] + }, + "setmaxqueue": { + "Cmd": "setmaxqueue smq", + "Desc": "Sets a maximum queue size. Supply 0 or no argument to have no limit.", + "Usage": [ + "{0}smq 50", + "{0}smq" + ] + }, + "cleanup": { + "Cmd": "cleanup", + "Desc": "Cleans up hanging voice connections.", + "Usage": [ + "{0}cleanup" + ] + }, + "reptcursong": { + "Cmd": "reptcursong rcs", + "Desc": "Toggles repeat of current song.", + "Usage": [ + "{0}rcs" + ] + }, + "repeatpl": { + "Cmd": "rpeatplaylst rpl", + "Desc": "Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue).", + "Usage": [ + "{0}rpl" + ] + }, + "save": { + "Cmd": "save", + "Desc": "Saves a playlist under a certain name. Playlist name must be no longer than 20 characters and must not contain dashes.", + "Usage": [ + "{0}save classical1" + ] + }, + "streamrole": { + "Cmd": "streamrole", + "Desc": "Sets a role which is monitored for streamers (FromRole), and a role to add if a user from 'FromRole' is streaming (AddRole). When a user from 'FromRole' starts streaming, they will receive an 'AddRole'. Provide no arguments to disable", + "Usage": [ + "{0}streamrole \"Eligible Streamers\" \"Featured Streams\"" + ] + }, + "load": { + "Cmd": "load", + "Desc": "Loads a saved playlist using its ID. Use `{0}pls` to list all saved playlists and `{0}save` to save new ones.", + "Usage": [ + "{0}load 5" + ] + }, + "playlists": { + "Cmd": "playlists pls", + "Desc": "Lists all playlists. Paginated, 20 per page. Default page is 0.", + "Usage": [ + "{0}pls 1" + ] + }, + "deleteplaylist": { + "Cmd": "deleteplaylist delpls", + "Desc": "Deletes a saved playlist. Works only if you made it or if you are the bot owner.", + "Usage": [ + "{0}delpls animu-5" + ] + }, + "goto": { + "Cmd": "goto", + "Desc": "Goes to a specific time in seconds in a song.", + "Usage": [ + "{0}goto 30" + ] + }, + "autoplay": { + "Cmd": "autoplay ap", + "Desc": "Toggles autoplay - When the song is finished, automatically queue a related Youtube song. (Works only for Youtube songs and when queue is empty)", + "Usage": [ + "{0}ap" + ] + }, + "lolchamp": { + "Cmd": "lolchamp", + "Desc": "Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role.", + "Usage": [ + "{0}lolchamp Riven", + "{0}lolchamp Annie sup" + ] + }, + "lolban": { + "Cmd": "lolban", + "Desc": "Shows top banned champions ordered by ban rate.", + "Usage": [ + "{0}lolban" + ] + }, + "smashcast": { + "Cmd": "smashcast hb", + "Desc": "Notifies this channel when a certain user starts streaming.", + "Usage": [ + "{0}smashcast SomeStreamer" + ] + }, + "twitch": { + "Cmd": "twitch tw", + "Desc": "Notifies this channel when a certain user starts streaming.", + "Usage": [ + "{0}twitch SomeStreamer" + ] + }, + "mixer": { + "Cmd": "mixer bm", + "Desc": "Notifies this channel when a certain user starts streaming.", + "Usage": [ + "{0}mixer SomeStreamer" + ] + }, + "removestream": { + "Cmd": "removestream rms", + "Desc": "Removes notifications of a certain streamer from a certain platform on this channel.", + "Usage": [ + "{0}rms Twitch SomeGuy", + "{0}rms mixer SomeOtherGuy" + ] + }, + "liststreams": { + "Cmd": "liststreams ls", + "Desc": "Lists all streams you are following on this server.", + "Usage": [ + "{0}ls" + ] + }, + "convert": { + "Cmd": "convert", + "Desc": "Convert quantities. Use `{0}convertlist` to see supported dimensions and currencies.", + "Usage": [ + "{0}convert m km 1000" + ] + }, + "convertlist": { + "Cmd": "convertlist", + "Desc": "List of the convertible dimensions and currencies.", + "Usage": [ + "{0}convertlist" + ] + }, + "wowjoke": { + "Cmd": "wowjoke", + "Desc": "Get one of Kwoth's penultimate WoW jokes.", + "Usage": [ + "{0}wowjoke" + ] + }, + "calculate": { + "Cmd": "calculate calc", + "Desc": "Evaluate a mathematical expression.", + "Usage": [ + "{0}calc 1+1" + ] + }, + "osu": { + "Cmd": "osu", + "Desc": "Shows osu stats for a player.", + "Usage": [ + "{0}osu Name", + "{0}osu Name taiko" + ] + }, + "osub": { + "Cmd": "osub", + "Desc": "Shows information about an osu beatmap.", + "Usage": [ + "{0}osub https://osu.ppy.sh/s/127712" + ] + }, + "osu5": { + "Cmd": "osu5", + "Desc": "Displays a user's top 5 plays.", + "Usage": [ + "{0}osu5 Name" + ] + }, + "pokemon": { + "Cmd": "pokemon poke", + "Desc": "Searches for a pokemon.", + "Usage": [ + "{0}poke Sylveon" + ] + }, + "pokemonability": { + "Cmd": "pokemonability pokeab", + "Desc": "Searches for a pokemon ability.", + "Usage": [ + "{0}pokeab overgrow" + ] + }, + "memelist": { + "Cmd": "memelist", + "Desc": "Pulls a list of memes you can use with `{0}memegen` from http://memegen.link/templates/", + "Usage": [ + "{0}memelist" + ] + }, + "memegen": { + "Cmd": "memegen", + "Desc": "Generates a meme from memelist with top and bottom text.", + "Usage": [ + "{0}memegen biw \"gets iced coffee\" \"in the winter\"" + ] + }, + "weather": { + "Cmd": "weather we", + "Desc": "Shows weather data for a specified city. You can also specify a country after a comma.", + "Usage": [ + "{0}we Moscow, RU" + ] + }, + "youtube": { + "Cmd": "youtube yt", + "Desc": "Searches youtubes and shows the first result", + "Usage": [ + "{0}yt query" + ] + }, + "anime": { + "Cmd": "anime ani aq", + "Desc": "Queries anilist for an anime and shows the first result.", + "Usage": [ + "{0}ani aquarion evol" + ] + }, + "imdb": { + "Cmd": "imdb omdb", + "Desc": "Queries omdb for movies or series, show first result.", + "Usage": [ + "{0}imdb Batman vs Superman" + ] + }, + "manga": { + "Cmd": "manga mang mq", + "Desc": "Queries anilist for a manga and shows the first result.", + "Usage": [ + "{0}mq Shingeki no kyojin" + ] + }, + "randomcat": { + "Cmd": "randomcat meow", + "Desc": "Shows a random cat image.", + "Usage": [ + "{0}meow" + ] + }, + "randomdog": { + "Cmd": "randomdog woof", + "Desc": "Shows a random dog image.", + "Usage": [ + "{0}woof" + ] + }, + "image": { + "Cmd": "image img", + "Desc": "Pulls the first image found using a search parameter. Use `{0}rimg` for different results.", + "Usage": [ + "{0}img cute kitten" + ] + }, + "randomimage": { + "Cmd": "randomimage rimg", + "Desc": "Pulls a random image using a search parameter.", + "Usage": [ + "{0}rimg cute kitten" + ] + }, + "lmgtfy": { + "Cmd": "lmgtfy", + "Desc": "Google something for an idiot.", + "Usage": [ + "{0}lmgtfy query" + ] + }, + "google": { + "Cmd": "google g", + "Desc": "Get a Google search link for some terms.", + "Usage": [ + "{0}google query" + ] + }, + "hearthstone": { + "Cmd": "hearthstone hs", + "Desc": "Searches for a Hearthstone card and shows its image. Takes a while to complete.", + "Usage": [ + "{0}hs Ysera" + ] + }, + "urbandict": { + "Cmd": "urbandict ud", + "Desc": "Searches Urban Dictionary for a word.", + "Usage": [ + "{0}ud Pineapple" + ] + }, + "hashtag": { + "Cmd": "#", + "Desc": "Searches Tagdef.com for a hashtag.", + "Usage": [ + "{0}# ff" + ] + }, + "catfact": { + "Cmd": "catfact", + "Desc": "Shows a random catfact from ", + "Usage": [ + "{0}catfact" + ] + }, + "yomama": { + "Cmd": "yomama ym", + "Desc": "Shows a random joke from ", + "Usage": [ + "{0}ym" + ] + }, + "randjoke": { + "Cmd": "randjoke rj", + "Desc": "Shows a random joke from ", + "Usage": [ + "{0}rj" + ] + }, + "chucknorris": { + "Cmd": "chucknorris cn", + "Desc": "Shows a random Chuck Norris joke from ", + "Usage": [ + "{0}cn" + ] + }, + "magicitem": { + "Cmd": "magicitem mi", + "Desc": "Shows a random magic item from ", + "Usage": [ + "{0}mi" + ] + }, + "revav": { + "Cmd": "revav", + "Desc": "Returns a Google reverse image search for someone's avatar.", + "Usage": [ + "{0}revav @SomeGuy" + ] + }, + "revimg": { + "Cmd": "revimg", + "Desc": "Returns a Google reverse image search for an image from a link.", + "Usage": [ + "{0}revimg Image link" + ] + }, + "safebooru": { + "Cmd": "safebooru", + "Desc": "Shows a random image from safebooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +)", + "Usage": [ + "{0}safebooru yuri+kissing" + ] + }, + "wiki": { + "Cmd": "wikipedia wiki", + "Desc": "Gives you back a wikipedia link", + "Usage": [ + "{0}wiki query" + ] + }, + "color": { + "Cmd": "color", + "Desc": "Shows you what color corresponds to that hex.", + "Usage": [ + "{0}color 00ff00" + ] + }, + "videocall": { + "Cmd": "videocall", + "Desc": "Creates a private video call link for you and other mentioned people. The link is sent to mentioned people via a private message.", + "Usage": [ + "{0}videocall \"@the First\" \"@Xyz\"" + ] + }, + "avatar": { + "Cmd": "avatar av", + "Desc": "Shows a mentioned person's avatar.", + "Usage": [ + "{0}av @SomeGuy" + ] + }, + "hentai": { + "Cmd": "hentai", + "Desc": "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.", + "Usage": [ + "{0}hentai yuri" + ] + }, + "danbooru": { + "Cmd": "danbooru", + "Desc": "Shows a random hentai image from danbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +)", + "Usage": [ + "{0}danbooru yuri+kissing" + ] + }, + "atfbooru": { + "Cmd": "atfbooru atf", + "Desc": "Shows a random hentai image from atfbooru with a given tag. Tag is optional but preferred.", + "Usage": [ + "{0}atfbooru yuri+kissing" + ] + }, + "gelbooru": { + "Cmd": "gelbooru", + "Desc": "Shows a random hentai image from gelbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +)", + "Usage": [ + "{0}gelbooru yuri+kissing" + ] + }, + "rule34": { + "Cmd": "rule34", + "Desc": "Shows a random image from rule34.xx with a given tag. Tag is optional but preferred. (multiple tags are appended with +)", + "Usage": [ + "{0}rule34 yuri+kissing" + ] + }, + "e621": { + "Cmd": "e621", + "Desc": "Shows a random hentai image from e621.net with a given tag. Tag is optional but preferred. Use spaces for multiple tags.", + "Usage": [ + "{0}e621 yuri kissing" + ] + }, + "boobs": { + "Cmd": "boobs", + "Desc": "Real adult content.", + "Usage": [ + "{0}boobs" + ] + }, + "butts": { + "Cmd": "butts ass butt", + "Desc": "Real adult content.", + "Usage": [ + "{0}butts", + "{0}ass" + ] + }, + "translate": { + "Cmd": "translate trans", + "Desc": "Translates from>to text. From the given language to the destination language.", + "Usage": [ + "{0}trans en>fr Hello" + ] + }, + "translangs": { + "Cmd": "translangs", + "Desc": "Lists the valid languages for translation.", + "Usage": [ + "{0}translangs" + ] + }, + "guide": { + "Cmd": "readme guide", + "Desc": "Sends a readme and a guide links to the channel.", + "Usage": [ + "{0}readme", + "{0}guide" + ] + }, + "calcops": { + "Cmd": "calcops", + "Desc": "Shows all available operations in the `{0}calc` command", + "Usage": [ + "{0}calcops" + ] + }, + "delallquotes": { + "Cmd": "delallq daq", + "Desc": "Deletes all quotes on a specified keyword.", + "Usage": [ + "{0}delallq kek" + ] + }, + "greetdmmsg": { + "Cmd": "greetdmmsg", + "Desc": "Sets a new join announcement message which will be sent to the user who joined. Type `%user%` if you want to mention the new member. Using it with no message will show the current DM greet message. You can use embed json from instead of a regular text, if you want the message to be embedded.", + "Usage": [ + "{0}greetdmmsg Welcome to the server, %user%`" + ] + }, + "cash": { + "Cmd": "$ currency $$ $$$ cash cur", + "Desc": "Check how much currency a person has. (Defaults to yourself)", + "Usage": [ + "{0}$", + "{0}$ @SomeGuy" + ] + }, + "listperms": { + "Cmd": "listperms lp", + "Desc": "Lists whole permission chain with their indexes. You can specify an optional page number if there are a lot of permissions.", + "Usage": [ + "{0}lp", + "{0}lp 3" + ] + }, + "allusrmdls": { + "Cmd": "allusrmdls aum", + "Desc": "Enable or disable all modules for a specific user.", + "Usage": [ + "{0}aum enable @someone" + ] + }, + "moveperm": { + "Cmd": "moveperm mp", + "Desc": "Moves permission from one position to another in the Permissions list.", + "Usage": [ + "{0}mp 2 4" + ] + }, + "removeperm": { + "Cmd": "removeperm rp", + "Desc": "Removes a permission from a given position in the Permissions list.", + "Usage": [ + "{0}rp 1" + ] + }, + "migratedata": { + "Cmd": "migratedata", + "Desc": "Migrate data from old bot configuration", + "Usage": [ + "{0}migratedata" + ] + }, + "checkstream": { + "Cmd": "checkstream cs", + "Desc": "Checks if a user is online on a certain streaming platform.", + "Usage": [ + "{0}cs twitch MyFavStreamer" + ] + }, + "showemojis": { + "Cmd": "showemojis se", + "Desc": "Shows a name and a link to every SPECIAL emoji in the message.", + "Usage": [ + "{0}se A message full of SPECIAL emojis" + ] + }, + "deckshuffle": { + "Cmd": "deckshuffle dsh", + "Desc": "Reshuffles all cards back into the deck.", + "Usage": [ + "{0}dsh" + ] + }, + "forwardmessages": { + "Cmd": "fwmsgs", + "Desc": "Toggles forwarding of non-command messages sent to bot's DM to the bot owners", + "Usage": [ + "{0}fwmsgs" + ] + }, + "forwardtoall": { + "Cmd": "fwtoall", + "Desc": "Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json file", + "Usage": [ + "{0}fwtoall" + ] + }, + "resetpermissions": { + "Cmd": "resetperms", + "Desc": "Resets the bot's permissions module on this server to the default value.", + "Usage": [ + "{0}resetperms" + ] + }, + "antiraid": { + "Cmd": "antiraid", + "Desc": "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)", + "Usage": [ + "{0}antiraid 5 20 Kick" + ] + }, + "antispam": { + "Cmd": "antispam", + "Desc": "Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders. Max message count is 10.", + "Usage": [ + "{0}antispam 3 Mute", + "{0}antispam 4 Kick", + "{0}antispam 6 Ban" + ] + }, + "chatmute": { + "Cmd": "chatmute", + "Desc": "Prevents a mentioned user from chatting in text channels.", + "Usage": [ + "{0}chatmute @Someone" + ] + }, + "voicemute": { + "Cmd": "voicemute", + "Desc": "Prevents a mentioned user from speaking in voice channels.", + "Usage": [ + "{0}voicemute @Someone" + ] + }, + "konachan": { + "Cmd": "konachan", + "Desc": "Shows a random hentai image from konachan with a given tag. Tag is optional but preferred.", + "Usage": [ + "{0}konachan yuri" + ] + }, + "setmuterole": { + "Cmd": "setmuterole", + "Desc": "Sets a name of the role which will be assigned to people who should be muted. Default is nadeko-mute.", + "Usage": [ + "{0}setmuterole Silenced" + ] + }, + "adsarm": { + "Cmd": "adsarm", + "Desc": "Toggles the automatic deletion of confirmations for `{0}iam` and `{0}iamn` commands.", + "Usage": [ + "{0}adsarm" + ] + }, + "setstream": { + "Cmd": "setstream", + "Desc": "Sets the bots stream. First argument is the twitch link, second argument is stream name.", + "Usage": [ + "{0}setstream TWITCHLINK Hello" + ] + }, + "chatunmute": { + "Cmd": "chatunmute", + "Desc": "Removes a mute role previously set on a mentioned user with `{0}chatmute` which prevented him from chatting in text channels.", + "Usage": [ + "{0}chatunmute @Someone" + ] + }, + "unmute": { + "Cmd": "unmute", + "Desc": "Unmutes a mentioned user previously muted with `{0}mute` command.", + "Usage": [ + "{0}unmute @Someone" + ] + }, + "xkcd": { + "Cmd": "xkcd", + "Desc": "Shows a XKCD comic. No arguments will retrieve random one. Number argument will retrieve a specific comic, and \"latest\" will get the latest one.", + "Usage": [ + "{0}xkcd", + "{0}xkcd 1400", + "{0}xkcd latest" + ] + }, + "placelist": { + "Cmd": "placelist", + "Desc": "Shows the list of available tags for the `{0}place` command.", + "Usage": [ + "{0}placelist" + ] + }, + "place": { + "Cmd": "place", + "Desc": "Shows a placeholder image of a given tag. Use `{0}placelist` to see all available tags. You can specify the width and height of the image as the last two optional arguments.", + "Usage": [ + "{0}place Cage", + "{0}place steven 500 400" + ] + }, + "togethertube": { + "Cmd": "togethertube totube", + "Desc": "Creates a new room on and shows the link in the chat.", + "Usage": [ + "{0}totube" + ] + }, + "poll": { + "Cmd": "poll ppoll", + "Desc": "Creates a public poll which requires users to type a number of the voting option in the channel command is ran in.", + "Usage": [ + "{0}ppoll Question?;Answer1;Answ 2;A_3" + ] + }, + "autotranslang": { + "Cmd": "autotranslang atl", + "Desc": "Sets your source and target language to be used with `{0}at`. Specify no arguments to remove previously set value.", + "Usage": [ + "{0}atl en>fr" + ] + }, + "autotranslate": { + "Cmd": "autotrans at", + "Desc": "Starts automatic translation of all messages by users who set their `{0}atl` in this channel. You can set \"del\" argument to automatically delete all translated user messages.", + "Usage": [ + "{0}at", + "{0}at del" + ] + }, + "listquotes": { + "Cmd": "listquotes liqu", + "Desc": "Lists all quotes on the server ordered alphabetically. 15 Per page.", + "Usage": [ + "{0}liqu", + "{0}liqu 3" + ] + }, + "typedel": { + "Cmd": "typedel", + "Desc": "Deletes a typing article given the ID.", + "Usage": [ + "{0}typedel 3" + ] + }, + "typelist": { + "Cmd": "typelist", + "Desc": "Lists added typing articles with their IDs. 15 per page.", + "Usage": [ + "{0}typelist", + "{0}typelist 3" + ] + }, + "listservers": { + "Cmd": "listservers", + "Desc": "Lists servers the bot is on with some basic info. 15 per page.", + "Usage": [ + "{0}listservers 3" + ] + }, + "hentaibomb": { + "Cmd": "hentaibomb", + "Desc": "Shows a total 5 images (from gelbooru, danbooru, konachan, yandere and atfbooru). Tag is optional but preferred.", + "Usage": [ + "{0}hentaibomb yuri" + ] + }, + "cleverbot": { + "Cmd": "cleverbot", + "Desc": "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.", + "Usage": [ + "{0}cleverbot" + ] + }, + "shorten": { + "Cmd": "shorten", + "Desc": "Attempts to shorten an URL, if it fails, returns the input URL.", + "Usage": [ + "{0}shorten https://google.com" + ] + }, + "mcping": { + "Cmd": "minecraftping mcping", + "Desc": "Pings a minecraft server.", + "Usage": [ + "{0}mcping 127.0.0.1:25565" + ] + }, + "mcq": { + "Cmd": "minecraftquery mcq", + "Desc": "Finds information about a minecraft server.", + "Usage": [ + "{0}mcq server:ip" + ] + }, + "wikia": { + "Cmd": "wikia", + "Desc": "Gives you back a wikia link", + "Usage": [ + "{0}wikia mtg Vigilance", + "{0}wikia mlp Dashy" + ] + }, + "yandere": { + "Cmd": "yandere", + "Desc": "Shows a random image from yandere with a given tag. Tag is optional but preferred. (multiple tags are appended with +)", + "Usage": [ + "{0}yandere tag1+tag2" + ] + }, + "magicthegathering": { + "Cmd": "magicthegathering mtg", + "Desc": "Searches for a Magic The Gathering card.", + "Usage": [ + "{0}magicthegathering about face", + "{0}mtg about face" + ] + }, + "yodify": { + "Cmd": "yodify yoda", + "Desc": "Translates your normal sentences into Yoda styled sentences!", + "Usage": [ + "{0}yoda my feelings hurt" + ] + }, + "attack": { + "Cmd": "attack", + "Desc": "Attacks a target with the given move. Use `{0}movelist` to see a list of moves your type can use.", + "Usage": [ + "{0}attack \"vine whip\" @someguy" + ] + }, + "heal": { + "Cmd": "heal", + "Desc": "Heals someone. Revives those who fainted. Costs one Currency. ", + "Usage": [ + "{0}heal @someone" + ] + }, + "movelist": { + "Cmd": "movelist ml", + "Desc": "Lists the moves you are able to use", + "Usage": [ + "{0}ml" + ] + }, + "settype": { + "Cmd": "settype", + "Desc": "Set your poketype. Costs one Currency. Provide no arguments to see a list of available types.", + "Usage": [ + "{0}settype fire", + "{0}settype" + ] + }, + "type": { + "Cmd": "type", + "Desc": "Get the poketype of the target.", + "Usage": [ + "{0}type @someone" + ] + }, + "hangmanlist": { + "Cmd": "hangmanlist", + "Desc": "Shows a list of hangman term types.", + "Usage": [ + "{0}hangmanlist" + ] + }, + "hangman": { + "Cmd": "hangman", + "Desc": "Starts a game of hangman in the channel. Use `{0}hangmanlist` to see a list of available term types. Defaults to 'all'.", + "Usage": [ + "{0}hangman", + "{0}hangman movies" + ] + }, + "hangmanstop": { + "Cmd": "hangmanstop", + "Desc": "Stops the active hangman game on this channel if it exists.", + "Usage": [ + "{0}hangmanstop" + ] + }, + "crstatsclear": { + "Cmd": "crstatsclear", + "Desc": "Resets the counters on `{0}crstats`. You can specify a trigger to clear stats only for that trigger.", + "Usage": [ + "{0}crstatsclear", + "{0}crstatsclear rng" + ] + }, + "crstats": { + "Cmd": "crstats", + "Desc": "Shows a list of custom reactions and the number of times they have been executed. Paginated with 10 per page. Use `{0}crstatsclear` to reset the counters.", + "Usage": [ + "{0}crstats", + "{0}crstats 3" + ] + }, + "overwatch": { + "Cmd": "overwatch ow", + "Desc": "Show's basic stats on a player (competitive rank, playtime, level etc) Region codes are: `eu` `us` `cn` `kr`", + "Usage": [ + "{0}ow us Battletag#1337", + "`{0}overwatch eu Battletag#2016" + ] + }, + "acro": { + "Cmd": "acrophobia acro", + "Desc": "Starts an Acrophobia game. Second argument is optional round length in seconds. (default is 60)", + "Usage": [ + "{0}acro", + "{0}acro 30" + ] + }, + "logevents": { + "Cmd": "logevents", + "Desc": "Shows a list of all events you can subscribe to with `{0}log`", + "Usage": [ + "{0}logevents" + ] + }, + "log": { + "Cmd": "log", + "Desc": "Toggles logging event. Disables it if it is active anywhere on the server. Enables if it isn't active. Use `{0}logevents` to see a list of all events you can subscribe to.", + "Usage": [ + "{0}log userpresence", + "{0}log userbanned" + ] + }, + "fairplay": { + "Cmd": "fairplay fp", + "Desc": "Toggles fairplay. While enabled, the bot will prioritize songs from users who didn't have their song recently played instead of the song's position in the queue.", + "Usage": [ + "{0}fp" + ] + }, + "songautodelete": { + "Cmd": "songautodelete sad", + "Desc": "Toggles whether the song should be automatically removed from the music queue when it finishes playing.", + "Usage": [ + "{0}sad" + ] + }, + "define": { + "Cmd": "define def", + "Desc": "Finds a definition of a word.", + "Usage": [ + "{0}def heresy" + ] + }, + "setmaxplaytime": { + "Cmd": "setmaxplaytime smp", + "Desc": "Sets a maximum number of seconds (>14) a song can run before being skipped automatically. Set 0 to have no limit.", + "Usage": [ + "{0}smp 0", + "{0}smp 270" + ] + }, + "activity": { + "Cmd": "activity", + "Desc": "Checks for spammers.", + "Usage": [ + "{0}activity" + ] + }, + "autohentai": { + "Cmd": "autohentai", + "Desc": "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.", + "Usage": [ + "{0}autohentai 30 yuri|tail|long_hair", + "{0}autohentai" + ] + }, + "setstatus": { + "Cmd": "setstatus", + "Desc": "Sets the bot's status. (Online/Idle/Dnd/Invisible)", + "Usage": [ + "{0}setstatus Idle" + ] + }, + "rotaterolecolor": { + "Cmd": "rotaterolecolor rrc", + "Desc": "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.", + "Usage": [ + "{0}rrc 60 MyLsdRole #ff0000 #00ff00 #0000ff", + "{0}rrc 0 MyLsdRole" + ] + }, + "createinvite": { + "Cmd": "createinvite crinv", + "Desc": "Creates a new invite which has infinite max uses and never expires.", + "Usage": [ + "{0}crinv" + ] + }, + "pollstats": { + "Cmd": "pollstats", + "Desc": "Shows the poll results without stopping the poll on this server.", + "Usage": [ + "{0}pollstats" + ] + }, + "repeatlist": { + "Cmd": "repeatlist replst", + "Desc": "Shows currently repeating messages and their indexes.", + "Usage": [ + "{0}repeatlist" + ] + }, + "repeatremove": { + "Cmd": "repeatremove reprm", + "Desc": "Removes a repeating message on a specified index. Use `{0}repeatlist` to see indexes.", + "Usage": [ + "{0}reprm 2" + ] + }, + "antilist": { + "Cmd": "antilist antilst", + "Desc": "Shows currently enabled protection features.", + "Usage": [ + "{0}antilist" + ] + }, + "antispamignore": { + "Cmd": "antispamignore", + "Desc": "Toggles whether antispam ignores current channel. Antispam must be enabled.", + "Usage": [ + "{0}antispamignore" + ] + }, + "cmdcosts": { + "Cmd": "cmdcosts", + "Desc": "Shows a list of command costs. Paginated with 9 commands per page.", + "Usage": [ + "{0}cmdcosts", + "{0}cmdcosts 2" + ] + }, + "commandcost": { + "Cmd": "commandcost cmdcost", + "Desc": "Sets a price for a command. Running that command will take currency from users. Set 0 to remove the price.", + "Usage": [ + "{0}cmdcost 0 !!q", + "{0}cmdcost 1 {0}8ball" + ] + }, + "startevent": { + "Cmd": "startevent", + "Desc": "Starts one of the events seen on public nadeko. `reaction` and `sneakygamestatus` are the only 2 available now.", + "Usage": [ + "{0}startevent reaction" + ] + }, + "slotstats": { + "Cmd": "slotstats", + "Desc": "Shows the total stats of the slot command for this bot's session.", + "Usage": [ + "{0}slotstats" + ] + }, + "slottest": { + "Cmd": "slottest", + "Desc": "Tests to see how much slots payout for X number of plays.", + "Usage": [ + "{0}slottest 1000" + ] + }, + "slot": { + "Cmd": "slot", + "Desc": "Play Nadeko slots. Max bet is 9999. 1.5 second cooldown per user.", + "Usage": [ + "{0}slot 5" + ] + }, + "waifuclaimeraffinity": { + "Cmd": "affinity", + "Desc": "Sets your affinity towards someone you want to be claimed by. Setting affinity will reduce their `{0}claim` on you by 20%. You can leave second argument empty to clear your affinity. 30 minutes cooldown.", + "Usage": [ + "{0}affinity @MyHusband", + "{0}affinity" + ] + }, + "waifuclaim": { + "Cmd": "claimwaifu claim", + "Desc": "Claim a waifu for yourself by spending currency. You must spend at least 10% more than her current value unless she set `{0}affinity` towards you.", + "Usage": [ + "{0}claim 50 @Himesama" + ] + }, + "waifugift": { + "Cmd": "waifugift gift gifts", + "Desc": "Gift an item to someone. This will increase their waifu value by 50% of the gifted item's value if they don't have affinity set towards you, or 100% if they do. Provide no arguments to see a list of items that you can gift.", + "Usage": [ + "{0}gifts", + "{0}gift Rose @Himesama" + ] + }, + "waifuleaderboard": { + "Cmd": "waifus waifulb", + "Desc": "Shows top 9 waifus. You can specify another page to show other waifus.", + "Usage": [ + "{0}waifus", + "{0}waifulb 3" + ] + }, + "divorce": { + "Cmd": "divorce", + "Desc": "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.", + "Usage": [ + "{0}divorce @CheatingSloot" + ] + }, + "waifuinfo": { + "Cmd": "waifuinfo waifustats", + "Desc": "Shows waifu stats for a target person. Defaults to you if no user is provided.", + "Usage": [ + "{0}waifuinfo @MyCrush", + "{0}waifuinfo" + ] + }, + "mal": { + "Cmd": "mal", + "Desc": "Shows basic info from a MyAnimeList profile.", + "Usage": [ + "{0}mal straysocks" + ] + }, + "setmusicchannel": { + "Cmd": "setmusicchannel smch", + "Desc": "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.", + "Usage": [ + "{0}smch" + ] + }, + "reloadimages": { + "Cmd": "reloadimages", + "Desc": "Reloads images bot is using. Safe to use even when bot is being used heavily.", + "Usage": [ + "{0}reloadimages" + ] + }, + "shardstats": { + "Cmd": "shardstats", + "Desc": "Stats for shards. Paginated with 25 shards per page.", + "Usage": [ + "{0}shardstats", + "{0}shardstats 2" + ] + }, + "restartshard": { + "Cmd": "restartshard", + "Desc": "Try (re)connecting a shard with a certain shardid when it dies. No one knows will it work. Keep an eye on the console for errors.", + "Usage": [ + "{0}restartshard 2" + ] + }, + "shardid": { + "Cmd": "shardid", + "Desc": "Shows which shard is a certain guild on, by guildid.", + "Usage": [ + "{0}shardid 117523346618318850" + ] + }, + "tictactoe": { + "Cmd": "tictactoe ttt", + "Desc": "Starts a game of tic tac toe. Another user must run the command in the same channel in order to accept the challenge. Use numbers 1-9 to play. 15 seconds per move.", + "Usage": [ + "0}tt" + ] + }, + "timezones": { + "Cmd": "timezones", + "Desc": "Lists all timezones available on the system to be used with `{0}timezone`.", + "Usage": [ + "{0}timezones" + ] + }, + "timezone": { + "Cmd": "timezone", + "Desc": "Sets this guilds timezone. This affects bot's time output in this server (logs, etc..)", + "Usage": [ + "{0}timezone", + "{0}timezone GMT Standard Time" + ] + }, + "languagesetdefault": { + "Cmd": "langsetdefault langsetd", + "Desc": "Sets the bot's default response language. All servers which use a default locale will use this one. Setting to `default` will use the host's current culture. Provide no arguments to see currently set language.", + "Usage": [ + "{0}langsetd en-US", + "{0}langsetd default" + ] + }, + "languageset": { + "Cmd": "languageset langset", + "Desc": "Sets this server's response language. If bot's response strings have been translated to that language, bot will use that language in this server. Reset by using `default` as the locale name. Provide no arguments to see currently set language.", + "Usage": [ + "{0}langset de-DE ", + "{0}langset default" + ] + }, + "languageslist": { + "Cmd": "languageslist langli", + "Desc": "List of languages for which translation (or part of it) exist atm.", + "Usage": [ + "{0}langli" + ] + }, + "rategirl": { + "Cmd": "rategirl", + "Desc": "Use the universal hot-crazy wife zone matrix to determine the girl's worth. It is everything young men need to know about women. At any moment in time, any woman you have previously located on this chart can vanish from that location and appear anywhere else on the chart.", + "Usage": [ + "{0}rategirl @SomeGurl" + ] + }, + "lucky7test": { + "Cmd": "lucky7test l7t", + "Desc": "Tests the l7 command.", + "Usage": [ + "{0}l7t 10000" + ] + }, + "lucky7": { + "Cmd": "lucky7 l7", + "Desc": "Bet currency on the game and start rolling 3 sided dice. At any point you can choose to [m]ove (roll again) or [s]tay (get the amount bet times the current multiplier).", + "Usage": [ + "{0}l7 10", + "{0}l7 move", + "{0}l7 s" + ] + }, + "vcrolelist": { + "Cmd": "vcrolelist", + "Desc": "Shows a list of currently set voice channel roles.", + "Usage": [ + "{0}vcrolelist" + ] + }, + "vcrole": { + "Cmd": "vcrole", + "Desc": "Sets or resets a role which will be given to users who join the voice channel you're in when you run this command. Provide no role name to disable. You must be in a voice channel to run this command.", + "Usage": [ + "{0}vcrole SomeRole", + "{0}vcrole" + ] + }, + "crad": { + "Cmd": "crad", + "Desc": "Toggles whether the message triggering the custom reaction will be automatically deleted.", + "Usage": [ + "{0}crad 59" + ] + }, + "crdm": { + "Cmd": "crdm", + "Desc": "Toggles whether the response message of the custom reaction will be sent as a direct message.", + "Usage": [ + "{0}crdm 44" + ] + }, + "crca": { + "Cmd": "crca", + "Desc": "Toggles whether the custom reaction will trigger if the triggering message contains the keyword (instead of only starting with it).", + "Usage": [ + "{0}crca 44" + ] + }, + "aliaslist": { + "Cmd": "aliaslist cmdmaplist aliases", + "Desc": "Shows the list of currently set aliases. Paginated.", + "Usage": [ + "{0}aliaslist", + "{0}aliaslist 3" + ] + }, + "alias": { + "Cmd": "alias cmdmap", + "Desc": "Create a custom alias for a certain Nadeko command. Provide no alias to remove the existing one.", + "Usage": [ + "{0}alias allin $bf 100 h", + "{0}alias \"linux thingy\" >loonix Spyware Windows" + ] + }, + "warnlog": { + "Cmd": "warnlog", + "Desc": "See a list of warnings of a certain user.", + "Usage": [ + "{0}warnlog @b1nzy" + ] + }, + "warnlogall": { + "Cmd": "warnlogall", + "Desc": "See a list of all warnings on the server. 15 users per page.", + "Usage": [ + "{0}warnlogall", + "{0}warnlogall 2" + ] + }, + "warn": { + "Cmd": "warn", + "Desc": "Warns a user.", + "Usage": [ + "{0}warn @b1nzy Very rude person" + ] + }, + "startupcommandadd": { + "Cmd": "scadd", + "Desc": "Adds a command to the list of commands which will be executed automatically in the current channel, in the order they were added in, by the bot when it startups up.", + "Usage": [ + "{0}scadd .stats" + ] + }, + "startupcommandremove": { + "Cmd": "scrm", + "Desc": "Removes a startup command with the provided command text.", + "Usage": [ + "{0}scrm .stats" + ] + }, + "startupcommandsclear": { + "Cmd": "scclr", + "Desc": "Removes all startup commands.", + "Usage": [ + "{0}scclr" + ] + }, + "startupcommands": { + "Cmd": "sclist", + "Desc": "Lists all startup commands in the order they will be executed in.", + "Usage": [ + "{0}sclist" + ] + }, + "unban": { + "Cmd": "unban", + "Desc": "Unbans a user with the provided user#discrim or id.", + "Usage": [ + "{0}unban kwoth#1234", + "{0}unban 123123123" + ] + }, + "wait": { + "Cmd": "wait", + "Desc": "Used only as a startup command. Waits a certain number of miliseconds before continuing the execution of the following startup commands.", + "Usage": [ + "{0}wait 3000" + ] + }, + "warnclear": { + "Cmd": "warnclear warnc", + "Desc": "Clears all warnings from a certain user.", + "Usage": [ + "{0}warnclear @PoorDude" + ] + }, + "warnpunishlist": { + "Cmd": "warnpunishlist warnpl", + "Desc": "Lists punishments for warnings.", + "Usage": [ + "{0}warnpunishlist" + ] + }, + "warnpunish": { + "Cmd": "warnpunish warnp", + "Desc": "Sets a punishment for a certain number of warnings. Provide no punishment to remove.", + "Usage": [ + "{0}warnpunish 5 Ban", + "{0}warnpunish 3" + ] + }, + "claimpatreonrewards": { + "Cmd": "clparew", + "Desc": "Claim patreon rewards. If you're subscribed to bot owner's patreon you can use this command to claim your rewards - assuming bot owner did setup has their patreon key.", + "Usage": [ + "{0}clparew" + ] + }, + "ping": { + "Cmd": "ping", + "Desc": "Ping the bot to see if there are latency issues.", + "Usage": [ + "{0}ping" + ] + }, + "slowmodewhitelist": { + "Cmd": "slowmodewl", + "Desc": "Ignores a role or a user from the slowmode feature.", + "Usage": [ + "{0}slowmodewl SomeRole", + "{0}slowmodewl AdminDude" + ] + }, + "time": { + "Cmd": "time", + "Desc": "Shows the current time and timezone in the specified location.", + "Usage": [ + "{0}time London, UK" + ] + }, + "patreonrewardsreload": { + "Cmd": "parewrel", + "Desc": "Forces the update of the list of patrons who are eligible for the reward.", + "Usage": [ + "{0}parewrel" + ] + }, + "shopadd": { + "Cmd": "shopadd", + "Desc": "Adds an item to the shop by specifying type price and name. Available types are role and list.", + "Usage": [ + "{0}shopadd role 1000 Rich" + ] + }, + "shopremove": { + "Cmd": "shoprem shoprm", + "Desc": "Removes an item from the shop by its ID.", + "Usage": [ + "{0}shoprm 1" + ] + }, + "shop": { + "Cmd": "shop", + "Desc": "Lists this server's administrators' shop. Paginated.", + "Usage": [ + "{0}shop", + "{0}shop 2" + ] + }, + "rolehoist": { + "Cmd": "rolehoist rh", + "Desc": "Toggles whether this role is displayed in the sidebar or not.", + "Usage": [ + "{0}rh Guests", + "{0}rh \"Space Wizards\"" + ] + }, + "buy": { + "Cmd": "buy", + "Desc": "Buys an item from the shop on a given index. If buying items, make sure that the bot can DM you.", + "Usage": [ + "{0}buy 2" + ] + }, + "gamevoicechannel": { + "Cmd": "gvc", + "Desc": "Toggles game voice channel feature in the voice channel you're currently in. Users who join the game voice channel will get automatically redirected to the voice channel with the name of their current game, if it exists. Can't move users to channels that the bot has no connect permission for. One per server.", + "Usage": [ + "{0}gvc" + ] + }, + "shoplistadd": { + "Cmd": "shoplistadd", + "Desc": "Adds an item to the list of items for sale in the shop entry given the index. You usually want to run this command in the secret channel, so that the unique items are not leaked.", + "Usage": [ + "{0}shoplistadd 1 Uni-que-Steam-Key" + ] + }, + "gcmd": { + "Cmd": "globalcommand gcmd", + "Desc": "Toggles whether a command can be used on any server.", + "Usage": [ + "{0}gcmd .stats" + ] + }, + "gmod": { + "Cmd": "globalmodule gmod", + "Desc": "Toggles whether a module can be used on any server.", + "Usage": [ + "{0}gmod nsfw" + ] + }, + "lgp": { + "Cmd": "listglobalperms lgp", + "Desc": "Lists global permissions set by the bot owner.", + "Usage": [ + "{0}lgp" + ] + }, + "resetglobalpermissions": { + "Cmd": "resetglobalperms", + "Desc": "Resets global permissions set by bot owner.", + "Usage": [ + "{0}resetglobalperms" + ] + }, + "prefix": { + "Cmd": "prefix", + "Desc": "Sets this server's prefix for all bot commands. Provide no arguments to see the current server prefix.", + "Usage": [ + "{0}prefix +" + ] + }, + "defprefix": { + "Cmd": "defprefix", + "Desc": "Sets bot's default prefix for all bot commands. Provide no arguments to see the current default prefix. This will not change this server's current prefix.", + "Usage": [ + "{0}defprefix +" + ] + }, + "verboseerror": { + "Cmd": "verboseerror ve", + "Desc": "Toggles whether the bot should print command errors when a command is incorrectly used.", + "Usage": [ + "{0}ve" + ] + }, + "streamrolekeyword": { + "Cmd": "streamrolekw srkw", + "Desc": "Sets keyword which is required in the stream's title in order for the streamrole to apply. Provide no keyword in order to reset.", + "Usage": [ + "{0}srkw", + "{0}srkw PUBG" + ] + }, + "streamroleblacklist": { + "Cmd": "streamrolebl srbl", + "Desc": "Adds or removes a blacklisted user. Blacklisted users will never receive the stream role.", + "Usage": [ + "{0}srbl add @b1nzy#1234", + "{0}srbl rem @b1nzy#1234" + ] + }, + "streamrolewhitelist": { + "Cmd": "streamrolewl srwl", + "Desc": "Adds or removes a whitelisted user. Whitelisted users will receive the stream role even if they don't have the specified keyword in their stream title.", + "Usage": [ + "{0}srwl add @b1nzy#1234", + "{0}srwl rem @b1nzy#1234" + ] + }, + "botconfigedit": { + "Cmd": "botconfigedit bce", + "Desc": "Sets one of available bot config settings to a specified value. Use the command without any parameters to get a list of available settings.", + "Usage": [ + "{0}bce CurrencyName b1nzy", + "{0}bce" + ] + }, + "nsfwtagblacklist": { + "Cmd": "nsfwtagbl nsfwtbl", + "Desc": "Toggles whether the tag is blacklisted or not in nsfw searches. Provide no parameters to see the list of blacklisted tags.", + "Usage": [ + "{0}nsfwtbl poop" + ] + }, + "experience": { + "Cmd": "experience xp", + "Desc": "Shows your xp stats. Specify the user to show that user's stats instead.", + "Usage": [ + "{0}xp" + ] + }, + "xpexclusionlist": { + "Cmd": "xpexclusionlist xpexl", + "Desc": "Shows the roles and channels excluded from the XP system on this server, as well as whether the whole server is excluded.", + "Usage": [ + "{0}xpexl" + ] + }, + "xpexclude": { + "Cmd": "xpexclude xpex", + "Desc": "Exclude a channel, role or current server from the xp system.", + "Usage": [ + "{0}xpex Role Excluded-Role` `{0}xpex Server" + ] + }, + "xpnotify": { + "Cmd": "xpnotify xpn", + "Desc": "Sets how the bot should notify you when you get a `server` or `global` level. You can set `dm` (for the bot to send a direct message), `channel` (to get notified in the channel you sent the last message in) or `none` to disable.", + "Usage": [ + "{0}xpn global dm` `{0}xpn server channel" + ] + }, + "xprolerewards": { + "Cmd": "xprolerewards xprrs", + "Desc": "Shows currently set role rewards.", + "Usage": [ + "{0}xprrs" + ] + }, + "xprolereward": { + "Cmd": "xprolereward xprr", + "Desc": "Sets a role reward on a specified level. Provide no role name in order to remove the role reward.", + "Usage": [ + "{0}xprr 3 Social" + ] + }, + "xpleaderboard": { + "Cmd": "xpleaderboard xplb", + "Desc": "Shows current server's xp leaderboard.", + "Usage": [ + "{0}xplb" + ] + }, + "xpgloballeaderboard": { + "Cmd": "xpgleaderboard xpglb", + "Desc": "Shows the global xp leaderboard.", + "Usage": [ + "{0}xpglb" + ] + }, + "xpadd": { + "Cmd": "xpadd", + "Desc": "Adds xp to a user on the server. This does not affect their global ranking. You can use negative values.", + "Usage": [ + "{0}xpadd 100 @b1nzy" + ] + }, + "clubcreate": { + "Cmd": "clubcreate", + "Desc": "Creates a club. You must be atleast level 5 and not be in the club already.", + "Usage": [ + "{0}clubcreate b1nzy's friends" + ] + }, + "clubinformation": { + "Cmd": "clubinfo", + "Desc": "Shows information about the club.", + "Usage": [ + "{0}clubinfo b1nzy's friends#123" + ] + }, + "clubapply": { + "Cmd": "clubapply", + "Desc": "Apply to join a club. You must meet that club's minimum level requirement, and not be on its ban list.", + "Usage": [ + "{0}clubapply b1nzy's friends#123" + ] + }, + "clubaccept": { + "Cmd": "clubaccept", + "Desc": "Accept a user who applied to your club.", + "Usage": [ + "{0}clubaccept b1nzy#1337" + ] + }, + "clubleave": { + "Cmd": "clubleave", + "Desc": "Leaves the club you're currently in.", + "Usage": [ + "{0}clubleave" + ] + }, + "clubdisband": { + "Cmd": "clubdisband", + "Desc": "Disbands the club you're the owner of. This action is irreversible.", + "Usage": [ + "{0}clubdisband" + ] + }, + "clubkick": { + "Cmd": "clubkick", + "Desc": "Kicks the user from the club. You must be the club owner. They will be able to apply again.", + "Usage": [ + "{0}clubkick b1nzy#1337" + ] + }, + "clubban": { + "Cmd": "clubban", + "Desc": "Bans the user from the club. You must be the club owner. They will not be able to apply again.", + "Usage": [ + "{0}clubban b1nzy#1337" + ] + }, + "clubunban": { + "Cmd": "clubunban", + "Desc": "Unbans the previously banned user from the club. You must be the club owner.", + "Usage": [ + "{0}clubunban b1nzy#1337" + ] + }, + "clublevelreq": { + "Cmd": "clublevelreq", + "Desc": "Sets the club required level to apply to join the club. You must be club owner. You can't set this number below 5.", + "Usage": [ + "{0}clublevelreq 7" + ] + }, + "clubicon": { + "Cmd": "clubicon", + "Desc": "Sets the club icon.", + "Usage": [ + "{0}clubicon https://i.imgur.com/htfDMfU.png" + ] + }, + "clubapps": { + "Cmd": "clubapps", + "Desc": "Shows the list of users who have applied to your club. Paginated. You must be club owner to use this command.", + "Usage": [ + "{0}clubapps 2" + ] + }, + "clubbans": { + "Cmd": "clubbans", + "Desc": "Shows the list of users who have banned from your club. Paginated. You must be club owner to use this command.", + "Usage": [ + "{0}clubbans 2" + ] + }, + "clubleaderboard": { + "Cmd": "clublb", + "Desc": "Shows club rankings on the specified page.", + "Usage": [ + "{0}clublb 2" + ] + }, + "nsfwclearcache": { + "Cmd": "nsfwcc", + "Desc": "Clears nsfw cache.", + "Usage": [ + "{0}nsfwcc" + ] + }, + "clubadmin": { + "Cmd": "clubadmin", + "Desc": "Assigns (or unassigns) staff role to the member of the club. Admins can ban, kick and accept applications.", + "Usage": [ + "{0}clubadmin" + ] + }, + "autoboobs": { + "Cmd": "autoboobs", + "Desc": "Posts a boobs every X seconds. 20 seconds minimum. Provide no arguments to disable.", + "Usage": [ + "{0}autoboobs 30", + "{0}autoboobs" + ] + }, + "autobutts": { + "Cmd": "autobutts", + "Desc": "Posts a butts every X seconds. 20 seconds minimum. Provide no arguments to disable.", + "Usage": [ + "{0}autobutts 30", + "{0}autobutts" + ] + }, + "eightball": { + "Cmd": "8ball", + "Desc": "Ask the 8ball a yes/no question.", + "Usage": [ + "{0}8ball" + ] + }, + "feed": { + "cmd": "feed feedadd", + "desc": "Subscribes to a feed. Bot will post an update up to once every 10 seconds. You can have up to 10 feeds on one server. All feeds must have unique URLs.", + "usage": [ + "{0}feed https://www.rt.com/rss/" + ] + }, + "feedremove": { + "cmd": "feedremove feedrm feeddel", + "desc": "Stops tracking a feed on the given index. Use `{0}feeds` command to see a list of feeds and their indexes.", + "usage": [ + "{0}feedremove 3" + ] + }, + "feedlist": { + "cmd": "feeds feedlist", + "desc": "Shows the list of feeds you've subscribed to on this server.", + "usage": [ + "{0}feeds" + ] + } +} \ No newline at end of file diff --git a/src/NadekoBot/data/fonts/Uni Sans.ttf b/src/NadekoBot/data/fonts/Uni Sans.ttf new file mode 100644 index 00000000..a7b39d02 Binary files /dev/null and b/src/NadekoBot/data/fonts/Uni Sans.ttf differ diff --git a/src/NadekoBot/data/fonts/WhitneyBold.ttf b/src/NadekoBot/data/fonts/WhitneyBold.ttf new file mode 100644 index 00000000..301bc88d Binary files /dev/null and b/src/NadekoBot/data/fonts/WhitneyBold.ttf differ diff --git a/src/NadekoBot/data/fonts/WhitneyBoldItalic.ttf b/src/NadekoBot/data/fonts/WhitneyBoldItalic.ttf new file mode 100644 index 00000000..7e65c9f4 Binary files /dev/null and b/src/NadekoBot/data/fonts/WhitneyBoldItalic.ttf differ diff --git a/src/NadekoBot/data/images/slots/numbers/1.png b/src/NadekoBot/data/images/slots/numbers/1.png index 4766b854..eeec3949 100644 Binary files a/src/NadekoBot/data/images/slots/numbers/1.png and b/src/NadekoBot/data/images/slots/numbers/1.png differ diff --git a/src/NadekoBot/data/images/slots/numbers/3.png b/src/NadekoBot/data/images/slots/numbers/3.png index 04d6e982..481e7edf 100644 Binary files a/src/NadekoBot/data/images/slots/numbers/3.png and b/src/NadekoBot/data/images/slots/numbers/3.png differ diff --git a/src/NadekoBot/data/images/slots/numbers/7.png b/src/NadekoBot/data/images/slots/numbers/7.png index 0ffc89b9..c668c23f 100644 Binary files a/src/NadekoBot/data/images/slots/numbers/7.png and b/src/NadekoBot/data/images/slots/numbers/7.png differ diff --git a/src/NadekoBot/data/images/xp/UNUSED_old_xp_high_res.png b/src/NadekoBot/data/images/xp/UNUSED_old_xp_high_res.png new file mode 100644 index 00000000..28025180 Binary files /dev/null and b/src/NadekoBot/data/images/xp/UNUSED_old_xp_high_res.png differ diff --git a/src/NadekoBot/data/images/xp/xp.png b/src/NadekoBot/data/images/xp/xp.png new file mode 100644 index 00000000..d5af22b4 Binary files /dev/null and b/src/NadekoBot/data/images/xp/xp.png differ diff --git a/src/NadekoBot/data/lolchamps.json b/src/NadekoBot/data/lolchamps.json new file mode 100644 index 00000000..df7ab4d2 --- /dev/null +++ b/src/NadekoBot/data/lolchamps.json @@ -0,0 +1,818 @@ +{ + "Aatrox": { + "id": 266, + "key": "Aatrox", + "name": "Aatrox", + "title": "the Darkin Blade" + }, + "Ahri": { + "id": 103, + "key": "Ahri", + "name": "Ahri", + "title": "the Nine-Tailed Fox" + }, + "Akali": { + "id": 84, + "key": "Akali", + "name": "Akali", + "title": "the Fist of Shadow" + }, + "Alistar": { + "id": 12, + "key": "Alistar", + "name": "Alistar", + "title": "the Minotaur" + }, + "Amumu": { + "id": 32, + "key": "Amumu", + "name": "Amumu", + "title": "the Sad Mummy" + }, + "Anivia": { + "id": 34, + "key": "Anivia", + "name": "Anivia", + "title": "the Cryophoenix" + }, + "Annie": { + "id": 1, + "key": "Annie", + "name": "Annie", + "title": "the Dark Child" + }, + "Ashe": { + "id": 22, + "key": "Ashe", + "name": "Ashe", + "title": "the Frost Archer" + }, + "AurelionSol": { + "id": 136, + "key": "AurelionSol", + "name": "Aurelion Sol", + "title": "The Star Forger" + }, + "Azir": { + "id": 268, + "key": "Azir", + "name": "Azir", + "title": "the Emperor of the Sands" + }, + "Bard": { + "id": 432, + "key": "Bard", + "name": "Bard", + "title": "the Wandering Caretaker" + }, + "Blitzcrank": { + "id": 53, + "key": "Blitzcrank", + "name": "Blitzcrank", + "title": "the Great Steam Golem" + }, + "Brand": { + "id": 63, + "key": "Brand", + "name": "Brand", + "title": "the Burning Vengeance" + }, + "Braum": { + "id": 201, + "key": "Braum", + "name": "Braum", + "title": "the Heart of the Freljord" + }, + "Caitlyn": { + "id": 51, + "key": "Caitlyn", + "name": "Caitlyn", + "title": "the Sheriff of Piltover" + }, + "Camille": { + "id": 164, + "key": "Camille", + "name": "Camille", + "title": "the Steel Shadow" + }, + "Cassiopeia": { + "id": 69, + "key": "Cassiopeia", + "name": "Cassiopeia", + "title": "the Serpent's Embrace" + }, + "Chogath": { + "id": 31, + "key": "Chogath", + "name": "Cho'Gath", + "title": "the Terror of the Void" + }, + "Corki": { + "id": 42, + "key": "Corki", + "name": "Corki", + "title": "the Daring Bombardier" + }, + "Darius": { + "id": 122, + "key": "Darius", + "name": "Darius", + "title": "the Hand of Noxus" + }, + "Diana": { + "id": 131, + "key": "Diana", + "name": "Diana", + "title": "Scorn of the Moon" + }, + "Draven": { + "id": 119, + "key": "Draven", + "name": "Draven", + "title": "the Glorious Executioner" + }, + "DrMundo": { + "id": 36, + "key": "DrMundo", + "name": "Dr. Mundo", + "title": "the Madman of Zaun" + }, + "Ekko": { + "id": 245, + "key": "Ekko", + "name": "Ekko", + "title": "the Boy Who Shattered Time" + }, + "Elise": { + "id": 60, + "key": "Elise", + "name": "Elise", + "title": "the Spider Queen" + }, + "Evelynn": { + "id": 28, + "key": "Evelynn", + "name": "Evelynn", + "title": "the Widowmaker" + }, + "Ezreal": { + "id": 81, + "key": "Ezreal", + "name": "Ezreal", + "title": "the Prodigal Explorer" + }, + "Fiddlesticks": { + "id": 9, + "key": "Fiddlesticks", + "name": "Fiddlesticks", + "title": "the Harbinger of Doom" + }, + "Fiora": { + "id": 114, + "key": "Fiora", + "name": "Fiora", + "title": "the Grand Duelist" + }, + "Fizz": { + "id": 105, + "key": "Fizz", + "name": "Fizz", + "title": "the Tidal Trickster" + }, + "Galio": { + "id": 3, + "key": "Galio", + "name": "Galio", + "title": "the Colossus" + }, + "Gangplank": { + "id": 41, + "key": "Gangplank", + "name": "Gangplank", + "title": "the Saltwater Scourge" + }, + "Garen": { + "id": 86, + "key": "Garen", + "name": "Garen", + "title": "The Might of Demacia" + }, + "Gnar": { + "id": 150, + "key": "Gnar", + "name": "Gnar", + "title": "the Missing Link" + }, + "Gragas": { + "id": 79, + "key": "Gragas", + "name": "Gragas", + "title": "the Rabble Rouser" + }, + "Graves": { + "id": 104, + "key": "Graves", + "name": "Graves", + "title": "the Outlaw" + }, + "Hecarim": { + "id": 120, + "key": "Hecarim", + "name": "Hecarim", + "title": "the Shadow of War" + }, + "Heimerdinger": { + "id": 74, + "key": "Heimerdinger", + "name": "Heimerdinger", + "title": "the Revered Inventor" + }, + "Illaoi": { + "id": 420, + "key": "Illaoi", + "name": "Illaoi", + "title": "the Kraken Priestess" + }, + "Irelia": { + "id": 39, + "key": "Irelia", + "name": "Irelia", + "title": "the Will of the Blades" + }, + "Ivern": { + "id": 427, + "key": "Ivern", + "name": "Ivern", + "title": "the Green Father" + }, + "Janna": { + "id": 40, + "key": "Janna", + "name": "Janna", + "title": "the Storm's Fury" + }, + "JarvanIV": { + "id": 59, + "key": "JarvanIV", + "name": "Jarvan IV", + "title": "the Exemplar of Demacia" + }, + "Jax": { + "id": 24, + "key": "Jax", + "name": "Jax", + "title": "Grandmaster at Arms" + }, + "Jayce": { + "id": 126, + "key": "Jayce", + "name": "Jayce", + "title": "the Defender of Tomorrow" + }, + "Jhin": { + "id": 202, + "key": "Jhin", + "name": "Jhin", + "title": "the Virtuoso" + }, + "Jinx": { + "id": 222, + "key": "Jinx", + "name": "Jinx", + "title": "the Loose Cannon" + }, + "Kalista": { + "id": 429, + "key": "Kalista", + "name": "Kalista", + "title": "the Spear of Vengeance" + }, + "Karma": { + "id": 43, + "key": "Karma", + "name": "Karma", + "title": "the Enlightened One" + }, + "Karthus": { + "id": 30, + "key": "Karthus", + "name": "Karthus", + "title": "the Deathsinger" + }, + "Kassadin": { + "id": 38, + "key": "Kassadin", + "name": "Kassadin", + "title": "the Void Walker" + }, + "Katarina": { + "id": 55, + "key": "Katarina", + "name": "Katarina", + "title": "the Sinister Blade" + }, + "Kayle": { + "id": 10, + "key": "Kayle", + "name": "Kayle", + "title": "The Judicator" + }, + "Kennen": { + "id": 85, + "key": "Kennen", + "name": "Kennen", + "title": "the Heart of the Tempest" + }, + "Khazix": { + "id": 121, + "key": "Khazix", + "name": "Kha'Zix", + "title": "the Voidreaver" + }, + "Kindred": { + "id": 203, + "key": "Kindred", + "name": "Kindred", + "title": "The Eternal Hunters" + }, + "Kled": { + "id": 240, + "key": "Kled", + "name": "Kled", + "title": "the Cantankerous Cavalier" + }, + "KogMaw": { + "id": 96, + "key": "KogMaw", + "name": "Kog'Maw", + "title": "the Mouth of the Abyss" + }, + "Leblanc": { + "id": 7, + "key": "Leblanc", + "name": "LeBlanc", + "title": "the Deceiver" + }, + "LeeSin": { + "id": 64, + "key": "LeeSin", + "name": "Lee Sin", + "title": "the Blind Monk" + }, + "Leona": { + "id": 89, + "key": "Leona", + "name": "Leona", + "title": "the Radiant Dawn" + }, + "Lissandra": { + "id": 127, + "key": "Lissandra", + "name": "Lissandra", + "title": "the Ice Witch" + }, + "Lucian": { + "id": 236, + "key": "Lucian", + "name": "Lucian", + "title": "the Purifier" + }, + "Lulu": { + "id": 117, + "key": "Lulu", + "name": "Lulu", + "title": "the Fae Sorceress" + }, + "Lux": { + "id": 99, + "key": "Lux", + "name": "Lux", + "title": "the Lady of Luminosity" + }, + "Malphite": { + "id": 54, + "key": "Malphite", + "name": "Malphite", + "title": "Shard of the Monolith" + }, + "Malzahar": { + "id": 90, + "key": "Malzahar", + "name": "Malzahar", + "title": "the Prophet of the Void" + }, + "Maokai": { + "id": 57, + "key": "Maokai", + "name": "Maokai", + "title": "the Twisted Treant" + }, + "MasterYi": { + "id": 11, + "key": "MasterYi", + "name": "Master Yi", + "title": "the Wuju Bladesman" + }, + "MissFortune": { + "id": 21, + "key": "MissFortune", + "name": "Miss Fortune", + "title": "the Bounty Hunter" + }, + "MonkeyKing": { + "id": 62, + "key": "MonkeyKing", + "name": "Wukong", + "title": "the Monkey King" + }, + "Mordekaiser": { + "id": 82, + "key": "Mordekaiser", + "name": "Mordekaiser", + "title": "the Iron Revenant" + }, + "Morgana": { + "id": 25, + "key": "Morgana", + "name": "Morgana", + "title": "Fallen Angel" + }, + "Nami": { + "id": 267, + "key": "Nami", + "name": "Nami", + "title": "the Tidecaller" + }, + "Nasus": { + "id": 75, + "key": "Nasus", + "name": "Nasus", + "title": "the Curator of the Sands" + }, + "Nautilus": { + "id": 111, + "key": "Nautilus", + "name": "Nautilus", + "title": "the Titan of the Depths" + }, + "Nidalee": { + "id": 76, + "key": "Nidalee", + "name": "Nidalee", + "title": "the Bestial Huntress" + }, + "Nocturne": { + "id": 56, + "key": "Nocturne", + "name": "Nocturne", + "title": "the Eternal Nightmare" + }, + "Nunu": { + "id": 20, + "key": "Nunu", + "name": "Nunu", + "title": "the Yeti Rider" + }, + "Olaf": { + "id": 2, + "key": "Olaf", + "name": "Olaf", + "title": "the Berserker" + }, + "Orianna": { + "id": 61, + "key": "Orianna", + "name": "Orianna", + "title": "the Lady of Clockwork" + }, + "Pantheon": { + "id": 80, + "key": "Pantheon", + "name": "Pantheon", + "title": "the Artisan of War" + }, + "Poppy": { + "id": 78, + "key": "Poppy", + "name": "Poppy", + "title": "Keeper of the Hammer" + }, + "Quinn": { + "id": 133, + "key": "Quinn", + "name": "Quinn", + "title": "Demacia's Wings" + }, + "Rakan": { + "id": 497, + "key": "Rakan", + "name": "Rakan", + "title": "The Charmer" + }, + "Rammus": { + "id": 33, + "key": "Rammus", + "name": "Rammus", + "title": "the Armordillo" + }, + "RekSai": { + "id": 421, + "key": "RekSai", + "name": "Rek'Sai", + "title": "the Void Burrower" + }, + "Renekton": { + "id": 58, + "key": "Renekton", + "name": "Renekton", + "title": "the Butcher of the Sands" + }, + "Rengar": { + "id": 107, + "key": "Rengar", + "name": "Rengar", + "title": "the Pridestalker" + }, + "Riven": { + "id": 92, + "key": "Riven", + "name": "Riven", + "title": "the Exile" + }, + "Rumble": { + "id": 68, + "key": "Rumble", + "name": "Rumble", + "title": "the Mechanized Menace" + }, + "Ryze": { + "id": 13, + "key": "Ryze", + "name": "Ryze", + "title": "the Rune Mage" + }, + "Sejuani": { + "id": 113, + "key": "Sejuani", + "name": "Sejuani", + "title": "Fury of the North" + }, + "Shaco": { + "id": 35, + "key": "Shaco", + "name": "Shaco", + "title": "the Demon Jester" + }, + "Shen": { + "id": 98, + "key": "Shen", + "name": "Shen", + "title": "the Eye of Twilight" + }, + "Shyvana": { + "id": 102, + "key": "Shyvana", + "name": "Shyvana", + "title": "the Half-Dragon" + }, + "Singed": { + "id": 27, + "key": "Singed", + "name": "Singed", + "title": "the Mad Chemist" + }, + "Sion": { + "id": 14, + "key": "Sion", + "name": "Sion", + "title": "The Undead Juggernaut" + }, + "Sivir": { + "id": 15, + "key": "Sivir", + "name": "Sivir", + "title": "the Battle Mistress" + }, + "Skarner": { + "id": 72, + "key": "Skarner", + "name": "Skarner", + "title": "the Crystal Vanguard" + }, + "Sona": { + "id": 37, + "key": "Sona", + "name": "Sona", + "title": "Maven of the Strings" + }, + "Soraka": { + "id": 16, + "key": "Soraka", + "name": "Soraka", + "title": "the Starchild" + }, + "Swain": { + "id": 50, + "key": "Swain", + "name": "Swain", + "title": "the Master Tactician" + }, + "Syndra": { + "id": 134, + "key": "Syndra", + "name": "Syndra", + "title": "the Dark Sovereign" + }, + "TahmKench": { + "id": 223, + "key": "TahmKench", + "name": "Tahm Kench", + "title": "the River King" + }, + "Taliyah": { + "id": 163, + "key": "Taliyah", + "name": "Taliyah", + "title": "the Stoneweaver" + }, + "Talon": { + "id": 91, + "key": "Talon", + "name": "Talon", + "title": "the Blade's Shadow" + }, + "Taric": { + "id": 44, + "key": "Taric", + "name": "Taric", + "title": "the Shield of Valoran" + }, + "Teemo": { + "id": 17, + "key": "Teemo", + "name": "Teemo", + "title": "the Swift Scout" + }, + "Thresh": { + "id": 412, + "key": "Thresh", + "name": "Thresh", + "title": "the Chain Warden" + }, + "Tristana": { + "id": 18, + "key": "Tristana", + "name": "Tristana", + "title": "the Yordle Gunner" + }, + "Trundle": { + "id": 48, + "key": "Trundle", + "name": "Trundle", + "title": "the Troll King" + }, + "Tryndamere": { + "id": 23, + "key": "Tryndamere", + "name": "Tryndamere", + "title": "the Barbarian King" + }, + "TwistedFate": { + "id": 4, + "key": "TwistedFate", + "name": "Twisted Fate", + "title": "the Card Master" + }, + "Twitch": { + "id": 29, + "key": "Twitch", + "name": "Twitch", + "title": "the Plague Rat" + }, + "Udyr": { + "id": 77, + "key": "Udyr", + "name": "Udyr", + "title": "the Spirit Walker" + }, + "Urgot": { + "id": 6, + "key": "Urgot", + "name": "Urgot", + "title": "the Headsman's Pride" + }, + "Varus": { + "id": 110, + "key": "Varus", + "name": "Varus", + "title": "the Arrow of Retribution" + }, + "Vayne": { + "id": 67, + "key": "Vayne", + "name": "Vayne", + "title": "the Night Hunter" + }, + "Veigar": { + "id": 45, + "key": "Veigar", + "name": "Veigar", + "title": "the Tiny Master of Evil" + }, + "Velkoz": { + "id": 161, + "key": "Velkoz", + "name": "Vel'Koz", + "title": "the Eye of the Void" + }, + "Vi": { + "id": 254, + "key": "Vi", + "name": "Vi", + "title": "the Piltover Enforcer" + }, + "Viktor": { + "id": 112, + "key": "Viktor", + "name": "Viktor", + "title": "the Machine Herald" + }, + "Vladimir": { + "id": 8, + "key": "Vladimir", + "name": "Vladimir", + "title": "the Crimson Reaper" + }, + "Volibear": { + "id": 106, + "key": "Volibear", + "name": "Volibear", + "title": "the Thunder's Roar" + }, + "Warwick": { + "id": 19, + "key": "Warwick", + "name": "Warwick", + "title": "the Uncaged Wrath of Zaun" + }, + "Xayah": { + "id": 498, + "key": "Xayah", + "name": "Xayah", + "title": "the Rebel" + }, + "Xerath": { + "id": 101, + "key": "Xerath", + "name": "Xerath", + "title": "the Magus Ascendant" + }, + "XinZhao": { + "id": 5, + "key": "XinZhao", + "name": "Xin Zhao", + "title": "the Seneschal of Demacia" + }, + "Yasuo": { + "id": 157, + "key": "Yasuo", + "name": "Yasuo", + "title": "the Unforgiven" + }, + "Yorick": { + "id": 83, + "key": "Yorick", + "name": "Yorick", + "title": "Shepherd of Souls" + }, + "Zac": { + "id": 154, + "key": "Zac", + "name": "Zac", + "title": "the Secret Weapon" + }, + "Zed": { + "id": 238, + "key": "Zed", + "name": "Zed", + "title": "the Master of Shadows" + }, + "Ziggs": { + "id": 115, + "key": "Ziggs", + "name": "Ziggs", + "title": "the Hexplosives Expert" + }, + "Zilean": { + "id": 26, + "key": "Zilean", + "name": "Zilean", + "title": "the Chronokeeper" + }, + "Zyra": { + "id": 143, + "key": "Zyra", + "name": "Zyra", + "title": "Rise of the Thorns" + } +} \ No newline at end of file