diff --git a/Discord.Net b/Discord.Net index 58766448..80384323 160000 --- a/Discord.Net +++ b/Discord.Net @@ -1 +1 @@ -Subproject commit 58766448d79ac9adec228f341f258aa262a3f278 +Subproject commit 80384323790471d254c7db5c237a49dc62624378 diff --git a/NuGet.Config b/NuGet.Config index d40268d3..e482807c 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -2,5 +2,6 @@ + diff --git a/docs/Commands List.md b/docs/Commands List.md index 1d354c4a..ed71757c 100644 --- a/docs/Commands List.md +++ b/docs/Commands List.md @@ -125,6 +125,14 @@ Command and aliases | Description | Usage ### Gambling Command and aliases | Description | Usage ----------------|--------------|------- +`$claimwaifu` `$claim` | Claim a waifu for yourself by spending currency. You must spend atleast 10% more than her current value unless she set `$affinity` towards you. | `$claim 50 @Himesama` +`$divorce` | Releases your claim on a specific waifu. You will get some of the money you've spent back unless that waifu has an affinity towards you. 6 hours cooldown. | `$divorce @CheatingSloot` +`$affinity` | Sets your affinity towards someone you want to be claimed by. Setting affinity will reduce their `$claim` on you by 20%. You can leave second argument empty to clear your affinity. 30 minutes cooldown. | `$affinity @MyHusband` or `$affinity` +`$waifus` `$waifulb` | Shows top 9 waifus. | `$waifus` +`$waifuinfo` `$waifustats` | Shows waifu stats for a target person. Defaults to you if no user is provided. | `$waifuinfo @MyCrush` or `$waifuinfo` +`$slotstats` | Shows the total stats of the slot command for this bot's session. **Bot Owner only.** | `$slotstats` +`$slottest` | Tests to see how much slots payout for X number of plays. **Bot Owner only.** | `$slottest 1000` +`$slot` | Play Nadeko slots. Max bet is 999. 3 seconds cooldown per user. | `$slot 5` `$flip` | Flips coin(s) - heads or tails, and shows an image. | `$flip` or `$flip 3` `$betflip` `$bf` | Bet to guess will the result be heads or tails. Guessing awards you 1.8x the currency you've bet. | `$bf 5 heads` or `$bf 3 t` `$draw` | Draws a card from the deck.If you supply number X, she draws up to 5 cards from the deck. | `$draw` or `$draw 5` @@ -132,6 +140,7 @@ Command and aliases | Description | Usage `$roll` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. Y can be a letter 'F' if you want to roll fate dice instead of dnd. | `$roll` or `$roll 7` or `$roll 3d5` or `$roll 5dF` `$rolluo` | Rolls X normal dice (up to 30) unordered. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | `$rolluo` or `$rolluo 7` or `$rolluo 3d5` `$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15` +`$startevent` | Starts one of the events seen on public nadeko. **Bot Owner only.** | `$startevent flowerreaction` `$race` | Starts a new animal race. | `$race` `$joinrace` `$jr` | Joins a new race. You can specify an amount of currency for betting (optional). You will get YourBet*(participants-1) back if you win. | `$jr` or `$jr 5` `$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. | `$raffle` or `$raffle RoleName` @@ -206,7 +215,6 @@ Command and aliases | Description | Usage `!!localplaylst` `!!lopl` | Queues all songs from a directory. **Bot Owner only.** | `!!lopl C:/music/classical` `!!radio` `!!ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: ) | `!!ra radio link here` `!!local` `!!lo` | Queues a local file by specifying a full path. **Bot Owner only.** | `!!lo C:/music/mysong.mp3` -`!!move` `!!mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!!mv` `!!remove` `!!rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!!rm 5` `!!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` @@ -226,7 +234,7 @@ Command and aliases | Description | Usage Command and aliases | Description | Usage ----------------|--------------|------- `~hentai` | Shows a hentai image from a random website (gelbooru or danbooru or konachan or atfbooru or yandere) with a given tag. Tag is optional but preferred. Only 1 tag allowed. | `~hentai yuri` -`~autohentai` | Posts a hentai every X seconds with a random tag from the provided tags. Use `|` to separate tags. 20 seconds minimum. Provide no arguments to disable. | `~autohentai 30 yuri|tail|long_hair` or `~autohentai` +`~autohentai` | Posts a hentai every X seconds with a random tag from the provided tags. Use `|` to separate tags. 20 seconds minimum. Provide no arguments to disable. **Requires ManageMessages channel permission.** | `~autohentai 30 yuri|tail|long_hair` or `~autohentai` `~hentaibomb` | Shows a total 5 images (from gelbooru, danbooru, konachan, yandere and atfbooru). Tag is optional but preferred. | `~hentaibomb yuri` `~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~danbooru yuri+kissing` `~yandere` | Shows a random image from yandere with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~yandere tag1+tag2` @@ -249,6 +257,7 @@ Command and aliases | Description | Usage `;chnlfilterwords` `;cfw` | Toggles automatic deleting of messages containing banned words on the channel. Does not negate the ;srvrfilterwords enabled setting. Does not affect bot owner. | `;cfw` `;fw` | Adds or removes (if it exists) a word from the list of filtered words. Use`;sfw` or `;cfw` to toggle filtering. | `;fw poop` `;lstfilterwords` `;lfw` | Shows a list of filtered words. | `;lfw` +`;cmdcosts` | Shows a list of command costs. Paginated with 9 command per page. | `;cmdcosts` or `;cmdcosts 2` `;cmdcooldown` `;cmdcd` | Sets a cooldown per user for a command. Set to 0 to remove the cooldown. | `;cmdcd "some cmd" 5` `;allcmdcooldowns` `;acmdcds` | Shows a list of all commands and their respective cooldowns. | `;acmdcds` `;ubl` | Either [add]s or [rem]oves a user specified by a mention or ID from a blacklist. **Bot Owner only.** | `;ubl add @SomeUser` or `;ubl rem 12312312313` diff --git a/scripts/Latest.bat b/scripts/Latest.bat index 8d59ec86..a4f9e15b 100644 --- a/scripts/Latest.bat +++ b/scripts/Latest.bat @@ -1,5 +1,5 @@ @ECHO off -TITLE Downloading NadekoBot, please wait +TITLE Downloading Latest Build of NadekoBot... ::Setting convenient to read variables which don't delete the windows temp folder SET root=%~dp0 CD /D %root% @@ -24,30 +24,43 @@ ECHO Downloading Nadeko... ECHO. git clone -b dev --recursive --depth 1 --progress https://github.com/Kwoth/NadekoBot.git >nul IF %ERRORLEVEL% EQU 128 (GOTO :giterror) -TITLE Installing NadekoBot, please wait +TITLE Installing NadekoBot, please wait... ECHO. -ECHO Installing... +ECHO Installing Discord.Net(1/4)... ::Building Nadeko CD /D %build1% dotnet restore >nul 2>&1 +ECHO Installing Discord.Net(2/4)... CD /D %build2% dotnet restore >nul 2>&1 +ECHO Installing Discord.Net(3/4)... CD /D %build3% dotnet restore >nul 2>&1 +ECHO Installing Discord.Net(4/4)... CD /D %build4% dotnet restore >nul 2>&1 +ECHO. +ECHO Discord.Net installation completed successfully... +ECHO. +ECHO Installing NadekoBot... CD /D %build5% dotnet restore >nul 2>&1 dotnet build --configuration Release >nul 2>&1 +ECHO. +ECHO NadekoBot installation completed successfully... ::Attempts to backup old files if they currently exist in the same folder as the batch file IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) :freshinstall ::Moves the NadekoBot folder to keep things tidy + ECHO. + ECHO Moving files, Please wait... ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1 IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) - GOTO :end + IF EXIST "%PROGRAMFILES(X86)%" (GOTO 64BIT) ELSE (GOTO 32BIT) :backupinstall - TITLE Backing up old files + TITLE Backing up old files... + ECHO. + ECHO Moving and Backing up old files... ::Recursively copies all files and folders from NadekoBot to NadekoBot_Old ROBOCOPY "%root%NadekoBot" "%root%NadekoBot_Old" /MIR >nul 2>&1 IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) @@ -70,7 +83,7 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) RMDIR "%root%NadekoBot\" /S /Q >nul 2>&1 ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1 IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) - GOTO :end + IF EXIST "%PROGRAMFILES(X86)%" (GOTO 64BIT) ELSE (GOTO 32BIT) :dotnet ::Terminates the batch script if it can't run dotnet --version TITLE Error! @@ -102,6 +115,25 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) PAUSE >nul 2>&1 CD /D "%root%" GOTO :EOF +:64BIT +ECHO. +ECHO Your System Architecture is 64bit... +GOTO end +:32BIT +ECHO. +ECHO Your System Architecture is 32bit... +timeout /t 5 +ECHO. +ECHO Downloading libsodium.dll and opus.dll... +SET "FILENAME=%~dp0\NadekoBot\src\NadekoBot\libsodium.dll" +bitsadmin.exe /transfer "Downloading libsodium.dll" /priority high https://github.com/Kwoth/NadekoBot/raw/dev/src/NadekoBot/_libs/32/libsodium.dll "%FILENAME%" +ECHO libsodium.dll downloaded. +ECHO. +timeout /t 5 +SET "FILENAME=%~dp0\NadekoBot\src\NadekoBot\opus.dll" +bitsadmin.exe /transfer "Downloading opus.dll" /priority high https://github.com/Kwoth/NadekoBot/raw/dev/src/NadekoBot/_libs/32/opus.dll "%FILENAME%" +ECHO opus.dll downloaded. +GOTO end :end ::Normal execution of end of script TITLE Installation complete! diff --git a/scripts/Stable.bat b/scripts/Stable.bat index a2016889..f55fc958 100644 --- a/scripts/Stable.bat +++ b/scripts/Stable.bat @@ -1,5 +1,5 @@ @ECHO off -TITLE Downloading NadekoBot, please wait +TITLE Downloading Stable Build of NadekoBot... ::Setting convenient to read variables which don't delete the windows temp folder SET root=%~dp0 CD /D %root% @@ -24,30 +24,43 @@ ECHO Downloading Nadeko... ECHO. git clone -b master --recursive --depth 1 --progress https://github.com/Kwoth/NadekoBot.git >nul IF %ERRORLEVEL% EQU 128 (GOTO :giterror) -TITLE Installing NadekoBot, please wait +TITLE Installing NadekoBot, please wait... ECHO. -ECHO Installing... +ECHO Installing Discord.Net(1/4)... ::Building Nadeko CD /D %build1% dotnet restore >nul 2>&1 +ECHO Installing Discord.Net(2/4)... CD /D %build2% dotnet restore >nul 2>&1 +ECHO Installing Discord.Net(3/4)... CD /D %build3% dotnet restore >nul 2>&1 +ECHO Installing Discord.Net(4/4)... CD /D %build4% dotnet restore >nul 2>&1 +ECHO. +ECHO Discord.Net installation completed successfully... +ECHO. +ECHO Installing NadekoBot... CD /D %build5% dotnet restore >nul 2>&1 dotnet build --configuration Release >nul 2>&1 +ECHO. +ECHO NadekoBot installation completed successfully... ::Attempts to backup old files if they currently exist in the same folder as the batch file IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) :freshinstall ::Moves the NadekoBot folder to keep things tidy + ECHO. + ECHO Moving files, Please wait... ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1 IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) - GOTO :end + IF EXIST "%PROGRAMFILES(X86)%" (GOTO 64BIT) ELSE (GOTO 32BIT) :backupinstall - TITLE Backing up old files + TITLE Backing up old files... + ECHO. + ECHO Moving and Backing up old files... ::Recursively copies all files and folders from NadekoBot to NadekoBot_Old ROBOCOPY "%root%NadekoBot" "%root%NadekoBot_Old" /MIR >nul 2>&1 IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) @@ -70,7 +83,7 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) RMDIR "%root%NadekoBot\" /S /Q >nul 2>&1 ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1 IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) - GOTO :end + IF EXIST "%PROGRAMFILES(X86)%" (GOTO 64BIT) ELSE (GOTO 32BIT) :dotnet ::Terminates the batch script if it can't run dotnet --version TITLE Error! @@ -102,6 +115,25 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) PAUSE >nul 2>&1 CD /D "%root%" GOTO :EOF +:64BIT +ECHO. +ECHO Your System Architecture is 64bit... +GOTO end +:32BIT +ECHO. +ECHO Your System Architecture is 32bit... +timeout /t 5 +ECHO. +ECHO Downloading libsodium.dll and opus.dll... +SET "FILENAME=%~dp0\NadekoBot\src\NadekoBot\libsodium.dll" +bitsadmin.exe /transfer "Downloading libsodium.dll" /priority high https://github.com/Kwoth/NadekoBot/raw/dev/src/NadekoBot/_libs/32/libsodium.dll "%FILENAME%" +ECHO libsodium.dll downloaded. +ECHO. +timeout /t 5 +SET "FILENAME=%~dp0\NadekoBot\src\NadekoBot\opus.dll" +bitsadmin.exe /transfer "Downloading opus.dll" /priority high https://github.com/Kwoth/NadekoBot/raw/dev/src/NadekoBot/_libs/32/opus.dll "%FILENAME%" +ECHO opus.dll downloaded. +GOTO end :end ::Normal execution of end of script TITLE Installation complete! diff --git a/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs b/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs index 3f2f8c22..e29e5c8c 100644 --- a/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs +++ b/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs @@ -30,7 +30,7 @@ namespace NadekoBot.Migrations name: "Betroll91Multiplier", table: "BotConfig", nullable: false, - defaultValue: 3f); + defaultValue: 4f); migrationBuilder.AddColumn( name: "CurrencyDropAmount", diff --git a/src/NadekoBot/Migrations/20170122044958_waifus.Designer.cs b/src/NadekoBot/Migrations/20170122044958_waifus.Designer.cs new file mode 100644 index 00000000..46eebfab --- /dev/null +++ b/src/NadekoBot/Migrations/20170122044958_waifus.Designer.cs @@ -0,0 +1,1089 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using NadekoBot.Modules.Music.Classes; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170122044958_waifus")] + partial class waifus + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.0-rtm-22752"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + 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.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("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("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + 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("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("DMHelpString"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + 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("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("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.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + 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("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("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("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("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + 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("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("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("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("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("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("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("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("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + 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("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("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("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("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("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + 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("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.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + 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("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("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("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("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.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + 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("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.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + 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); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170122044958_waifus.cs b/src/NadekoBot/Migrations/20170122044958_waifus.cs new file mode 100644 index 00000000..4396ae77 --- /dev/null +++ b/src/NadekoBot/Migrations/20170122044958_waifus.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class waifus : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DiscordUser", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AvatarId = table.Column(nullable: true), + Discriminator = table.Column(nullable: true), + UserId = table.Column(nullable: false), + Username = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DiscordUser", x => x.Id); + table.UniqueConstraint("AK_DiscordUser_UserId", x => x.UserId); + }); + + migrationBuilder.CreateTable( + name: "WaifuInfo", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AffinityId = table.Column(nullable: true), + ClaimerId = table.Column(nullable: true), + Price = table.Column(nullable: false), + WaifuId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_WaifuInfo", x => x.Id); + table.ForeignKey( + name: "FK_WaifuInfo_DiscordUser_AffinityId", + column: x => x.AffinityId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_WaifuInfo_DiscordUser_ClaimerId", + column: x => x.ClaimerId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_WaifuInfo_DiscordUser_WaifuId", + column: x => x.WaifuId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "WaifuUpdates", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + NewId = table.Column(nullable: true), + OldId = table.Column(nullable: true), + UpdateType = table.Column(nullable: false), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_WaifuUpdates", x => x.Id); + table.ForeignKey( + name: "FK_WaifuUpdates_DiscordUser_NewId", + column: x => x.NewId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_WaifuUpdates_DiscordUser_OldId", + column: x => x.OldId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_WaifuUpdates_DiscordUser_UserId", + column: x => x.UserId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_WaifuInfo_AffinityId", + table: "WaifuInfo", + column: "AffinityId"); + + migrationBuilder.CreateIndex( + name: "IX_WaifuInfo_ClaimerId", + table: "WaifuInfo", + column: "ClaimerId"); + + migrationBuilder.CreateIndex( + name: "IX_WaifuInfo_WaifuId", + table: "WaifuInfo", + column: "WaifuId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_WaifuUpdates_NewId", + table: "WaifuUpdates", + column: "NewId"); + + migrationBuilder.CreateIndex( + name: "IX_WaifuUpdates_OldId", + table: "WaifuUpdates", + column: "OldId"); + + migrationBuilder.CreateIndex( + name: "IX_WaifuUpdates_UserId", + table: "WaifuUpdates", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "WaifuInfo"); + + migrationBuilder.DropTable( + name: "WaifuUpdates"); + + migrationBuilder.DropTable( + name: "DiscordUser"); + } + } +} diff --git a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs index 454c641f..dd9bb84d 100644 --- a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs @@ -299,6 +299,26 @@ namespace NadekoBot.Migrations b.ToTable("CustomReactions"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + 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") @@ -817,6 +837,55 @@ namespace NadekoBot.Migrations b.ToTable("PokeGame"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + 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("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.AntiRaidSetting", b => { b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") @@ -982,6 +1051,38 @@ namespace NadekoBot.Migrations .WithMany("RaceAnimals") .HasForeignKey("BotConfigId"); }); + + 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); + }); } } } diff --git a/src/NadekoBot/Modules/Administration/Administration.cs b/src/NadekoBot/Modules/Administration/Administration.cs index cf431be1..45e99b8e 100644 --- a/src/NadekoBot/Modules/Administration/Administration.cs +++ b/src/NadekoBot/Modules/Administration/Administration.cs @@ -45,7 +45,7 @@ namespace NadekoBot.Modules.Administration var channel = msg.Channel as SocketTextChannel; if (channel == null) return; - if (DeleteMessagesOnCommand.Contains(channel.Guild.Id)) + if (DeleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune") await msg.DeleteAsync().ConfigureAwait(false); } catch (Exception ex) diff --git a/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs b/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs index e3cb8380..01c19fa8 100644 --- a/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs @@ -25,7 +25,6 @@ namespace NadekoBot.Modules.Administration static AutoAssignRoleCommands() { _log = LogManager.GetCurrentClassLogger(); - var sw = Stopwatch.StartNew(); AutoAssignedRoles = new ConcurrentDictionary(NadekoBot.AllGuildConfigs.Where(x => x.AutoAssignRoleId != 0) .ToDictionary(k => k.GuildId, v => v.AutoAssignRoleId)); @@ -46,9 +45,6 @@ namespace NadekoBot.Modules.Administration } catch (Exception ex) { _log.Warn(ex); } }; - - sw.Stop(); - _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs index 89eeb2c5..7fff2dc6 100644 --- a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs @@ -1,5 +1,6 @@ using Discord; using Discord.Commands; +using Discord.WebSocket; using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; @@ -9,6 +10,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace NadekoBot.Modules.Administration @@ -21,8 +23,8 @@ namespace NadekoBot.Modules.Administration private static Logger _log { get; } public static List RotatingStatusMessages { get; } public static bool RotatingStatuses { get; private set; } = false; - - //todo wtf is with this while(true) in constructor + private static Timer _t { get; } + static PlayingRotateCommands() { _log = LogManager.GetCurrentClassLogger(); @@ -30,46 +32,50 @@ namespace NadekoBot.Modules.Administration RotatingStatusMessages = NadekoBot.BotConfig.RotatingStatusMessages; RotatingStatuses = NadekoBot.BotConfig.RotatingStatuses; - var t = Task.Run(async () => + + + _t = new Timer(async (_) => { var index = 0; - do + try { - try + if (!RotatingStatuses) + return; + else { - if (!RotatingStatuses) - continue; - else - { - if (index >= RotatingStatusMessages.Count) - index = 0; + if (index >= RotatingStatusMessages.Count) + index = 0; - if (!RotatingStatusMessages.Any()) - continue; - var status = RotatingStatusMessages[index++].Status; - if (string.IsNullOrWhiteSpace(status)) - continue; - PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value())); - await NadekoBot.Client.SetGameAsync(status).ConfigureAwait(false); + if (!RotatingStatusMessages.Any()) + return; + var status = RotatingStatusMessages[index++].Status; + if (string.IsNullOrWhiteSpace(status)) + return; + PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value())); + var shards = NadekoBot.Client.Shards; + for (int i = 0; i < shards.Count; i++) + { + ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(shards.ElementAt(i)))); + 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); - } - finally - { - await Task.Delay(TimeSpan.FromMinutes(1)); - } - } while (true); - }); + } + catch (Exception ex) + { + _log.Warn("Rotating playing status errored.\n" + ex); + } + }, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); } public static Dictionary> PlayingPlaceholders { get; } = new Dictionary> { - {"%servers%", () => NadekoBot.Client.GetGuildCount().ToString()}, - {"%users%", () => NadekoBot.Client.GetGuilds().Sum(s => s.Users.Count).ToString()}, - {"%playing%", () => { + { "%servers%", () => NadekoBot.Client.GetGuildCount().ToString()}, + { "%users%", () => NadekoBot.Client.GetGuilds().Sum(s => s.Users.Count).ToString()}, + { "%playing%", () => { var cnt = Music.Music.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null); if (cnt != 1) return cnt.ToString(); try { @@ -81,7 +87,15 @@ namespace NadekoBot.Modules.Administration } } }, - {"%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()} + { "%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()}, + { "%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) }, + { "%shardcount%", () => NadekoBot.Client.Shards.Count.ToString() }, + }; + + public static Dictionary> ShardSpecificPlaceholders { get; } = + new Dictionary> { + { "%shardid%", (client) => client.ShardId.ToString()}, + { "%shardguilds%", (client) => client.Guilds.Count.ToString()}, }; [NadekoCommand, Usage, Description, Aliases] @@ -153,4 +167,4 @@ namespace NadekoBot.Modules.Administration } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs b/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs index a087e6f2..13817a65 100644 --- a/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs @@ -132,7 +132,7 @@ namespace NadekoBot.Modules.Administration if (ids[1].ToUpperInvariant().StartsWith("C:")) { var cid = ulong.Parse(ids[1].Substring(2)); - var ch = (await server.GetTextChannelsAsync()).Where(c => c.Id == cid).FirstOrDefault(); + var ch = server.TextChannels.Where(c => c.Id == cid).FirstOrDefault(); if (ch == null) { return; @@ -159,9 +159,7 @@ namespace NadekoBot.Modules.Administration [OwnerOnly] public async Task Announce([Remainder] string message) { - var channels = await Task.WhenAll(NadekoBot.Client.GetGuilds().Select(g => - g.GetDefaultChannelAsync() - )).ConfigureAwait(false); + var channels = NadekoBot.Client.GetGuilds().Select(g => g.DefaultChannel).ToArray(); if (channels == null) return; await Task.WhenAll(channels.Where(c => c != null).Select(c => c.SendConfirmAsync($"🆕 Message from {Context.User} `[Bot Owner]`:", message))) diff --git a/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs b/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs index 9fa0971c..9183b814 100644 --- a/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs @@ -4,9 +4,11 @@ using Discord.WebSocket; using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; +using NadekoBot.Services.Database; using NadekoBot.Services.Database.Models; using NLog; using System; +using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; @@ -17,24 +19,76 @@ namespace NadekoBot.Modules.Administration [Group] public class ServerGreetCommands : ModuleBase { + //make this to a field in the guildconfig table + class GreetSettings + { + public int AutoDeleteGreetMessagesTimer { get; set; } + public int AutoDeleteByeMessagesTimer { get; set; } + + public ulong GreetMessageChannelId { get; set; } + public ulong ByeMessageChannelId { get; set; } + + public bool SendDmGreetMessage { get; set; } + public string DmGreetMessageText { get; set; } + + public bool SendChannelGreetMessage { get; set; } + public string ChannelGreetMessageText { get; set; } + + public bool SendChannelByeMessage { get; set; } + public string ChannelByeMessageText { get; set; } + + public static GreetSettings Create(GuildConfig g) => new GreetSettings() + { + AutoDeleteByeMessagesTimer = g.AutoDeleteByeMessagesTimer, + AutoDeleteGreetMessagesTimer = g.AutoDeleteGreetMessagesTimer, + GreetMessageChannelId = g.GreetMessageChannelId, + ByeMessageChannelId = g.ByeMessageChannelId, + SendDmGreetMessage = g.SendDmGreetMessage, + DmGreetMessageText = g.DmGreetMessageText, + SendChannelGreetMessage = g.SendChannelGreetMessage, + ChannelGreetMessageText = g.ChannelGreetMessageText, + SendChannelByeMessage = g.SendChannelByeMessage, + ChannelByeMessageText = g.ChannelByeMessageText, + }; + } + private static Logger _log { get; } + private static ConcurrentDictionary GuildConfigsCache { get; } = new ConcurrentDictionary(); + static ServerGreetCommands() { NadekoBot.Client.UserJoined += UserJoined; NadekoBot.Client.UserLeft += UserLeft; _log = LogManager.GetCurrentClassLogger(); + + GuildConfigsCache = new ConcurrentDictionary(NadekoBot.AllGuildConfigs.ToDictionary(g => g.GuildId, (g) => GreetSettings.Create(g))); } + + private static GreetSettings GetOrAddSettingsForGuild(ulong guildId) + { + GreetSettings settings; + GuildConfigsCache.TryGetValue(guildId, out settings); + + if (settings != null) + return settings; + + using (var uow = DbHandler.UnitOfWork()) + { + var gc = uow.GuildConfigs.For(guildId, set => set); + settings = GreetSettings.Create(gc); + } + + GuildConfigsCache.TryAdd(guildId, settings); + return settings; + } + //todo optimize ASAP private static async Task UserLeft(IGuildUser user) { try { - GuildConfig conf; - using (var uow = DbHandler.UnitOfWork()) - { - conf = uow.GuildConfigs.For(user.Guild.Id, set => set); - } + var conf = GetOrAddSettingsForGuild(user.GuildId); if (!conf.SendChannelByeMessage) return; var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.ByeMessageChannelId); @@ -62,11 +116,7 @@ namespace NadekoBot.Modules.Administration { try { - GuildConfig conf; - using (var uow = DbHandler.UnitOfWork()) - { - conf = uow.GuildConfigs.For(user.Guild.Id, set => set); - } + var conf = GetOrAddSettingsForGuild(user.GuildId); if (conf.SendChannelGreetMessage) { @@ -133,6 +183,9 @@ namespace NadekoBot.Modules.Administration var conf = uow.GuildConfigs.For(id, set => set); conf.AutoDeleteGreetMessagesTimer = timer; + var toAdd = GreetSettings.Create(conf); + GuildConfigsCache.AddOrUpdate(id, toAdd, (key, old) => toAdd); + await uow.CompleteAsync().ConfigureAwait(false); } } @@ -159,6 +212,9 @@ namespace NadekoBot.Modules.Administration enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage; conf.GreetMessageChannelId = channelId; + var toAdd = GreetSettings.Create(conf); + GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd); + await uow.CompleteAsync().ConfigureAwait(false); } return enabled; @@ -201,6 +257,9 @@ namespace NadekoBot.Modules.Administration conf.ChannelGreetMessageText = message; greetMsgEnabled = conf.SendChannelGreetMessage; + var toAdd = GreetSettings.Create(conf); + GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd); + uow.Complete(); } return greetMsgEnabled; @@ -227,6 +286,9 @@ namespace NadekoBot.Modules.Administration var conf = uow.GuildConfigs.For(guildId, set => set); enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage; + var toAdd = GreetSettings.Create(conf); + GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd); + await uow.CompleteAsync().ConfigureAwait(false); } return enabled; @@ -269,6 +331,9 @@ namespace NadekoBot.Modules.Administration conf.DmGreetMessageText = message; greetMsgEnabled = conf.SendDmGreetMessage; + var toAdd = GreetSettings.Create(conf); + GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd); + uow.Complete(); } return greetMsgEnabled; @@ -296,6 +361,9 @@ namespace NadekoBot.Modules.Administration enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage; conf.ByeMessageChannelId = channelId; + var toAdd = GreetSettings.Create(conf); + GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd); + await uow.CompleteAsync(); } return enabled; @@ -338,6 +406,9 @@ namespace NadekoBot.Modules.Administration conf.ChannelByeMessageText = message; byeMsgEnabled = conf.SendChannelByeMessage; + var toAdd = GreetSettings.Create(conf); + GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd); + uow.Complete(); } return byeMsgEnabled; @@ -356,16 +427,19 @@ namespace NadekoBot.Modules.Administration await Context.Channel.SendConfirmAsync("ℹ️ Automatic deletion of bye messages has been **disabled**.").ConfigureAwait(false); } - private static async Task SetByeDel(ulong id, int timer) + private static async Task SetByeDel(ulong guildId, int timer) { if (timer < 0 || timer > 600) return; using (var uow = DbHandler.UnitOfWork()) { - var conf = uow.GuildConfigs.For(id, set => set); + var conf = uow.GuildConfigs.For(guildId, set => set); conf.AutoDeleteByeMessagesTimer = timer; + var toAdd = GreetSettings.Create(conf); + GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd); + await uow.CompleteAsync().ConfigureAwait(false); } } diff --git a/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs b/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs index a52a2c60..9d92ff98 100644 --- a/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs @@ -57,7 +57,7 @@ namespace NadekoBot.Modules.Administration { try { - await (await guild.GetOwnerAsync()).SendErrorAsync( + await guild.Owner.SendErrorAsync( "⚠️ I don't have **manage server** and/or **manage channels** permission," + $" so I cannot run `voice+text` on **{guild.Name}** server.").ConfigureAwait(false); } @@ -75,16 +75,16 @@ namespace NadekoBot.Modules.Administration var beforeVch = before.VoiceChannel; if (beforeVch != null) { - var textChannel = (await guild.GetTextChannelsAsync()).Where(t => t.Name == GetChannelName(beforeVch.Name).ToLowerInvariant()).FirstOrDefault(); + var textChannel = guild.TextChannels.Where(t => t.Name == GetChannelName(beforeVch.Name).ToLowerInvariant()).FirstOrDefault(); if (textChannel != null) await textChannel.AddPermissionOverwriteAsync(user, new OverwritePermissions(readMessages: PermValue.Deny, sendMessages: PermValue.Deny)).ConfigureAwait(false); } var afterVch = after.VoiceChannel; - if (afterVch != null && guild.AFKChannelId != afterVch.Id) + if (afterVch != null && guild.AFKChannel?.Id != afterVch.Id) { - var textChannel = (await guild.GetTextChannelsAsync()) + ITextChannel textChannel = guild.TextChannels .Where(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant()) .FirstOrDefault(); if (textChannel == null) diff --git a/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs b/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs index 26b18995..67510d56 100644 --- a/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs +++ b/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs @@ -36,10 +36,8 @@ namespace NadekoBot.Modules.ClashOfClans .GetAllWars() .Select(cw => { - cw.Channel = NadekoBot.Client.GetGuild(cw.GuildId) - ?.GetTextChannelAsync(cw.ChannelId) - .GetAwaiter() - .GetResult(); + cw.Channel = NadekoBot.Client.GetGuild(cw.GuildId)? + .GetTextChannel(cw.ChannelId); return cw; }) .Where(cw => cw.Channel != null) @@ -322,7 +320,7 @@ namespace NadekoBot.Modules.ClashOfClans public static async Task CreateWar(string enemyClan, int size, ulong serverId, ulong channelId) { - var channel = await NadekoBot.Client.GetGuild(serverId)?.GetTextChannelAsync(channelId); + var channel = NadekoBot.Client.GetGuild(serverId)?.GetTextChannel(channelId); using (var uow = DbHandler.UnitOfWork()) { var cw = new ClashWar diff --git a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs index 8d95e5d4..1f1cca1d 100644 --- a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs +++ b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs @@ -10,14 +10,16 @@ using NadekoBot.Extensions; using NLog; using System.Diagnostics; using Discord.WebSocket; +using System; namespace NadekoBot.Modules.CustomReactions { [NadekoModule("CustomReactions", ".")] public class CustomReactions : DiscordModule { - public static ConcurrentHashSet GlobalReactions { get; } = new ConcurrentHashSet(); - public static ConcurrentDictionary> GuildReactions { get; } = new ConcurrentDictionary>(); + private static CustomReaction[] _globalReactions = new CustomReaction[] { }; + public static CustomReaction[] GlobalReactions => _globalReactions; + public static ConcurrentDictionary GuildReactions { get; } = new ConcurrentDictionary(); public static ConcurrentDictionary ReactionStats { get; } = new ConcurrentDictionary(); @@ -30,8 +32,8 @@ namespace NadekoBot.Modules.CustomReactions using (var uow = DbHandler.UnitOfWork()) { 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 => new ConcurrentHashSet(g))); - GlobalReactions = new ConcurrentHashSet(items.Where(g => g.GuildId == null || g.GuildId == 0)); + 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"); @@ -46,32 +48,46 @@ namespace NadekoBot.Modules.CustomReactions return false; var content = umsg.Content.Trim().ToLowerInvariant(); - ConcurrentHashSet reactions; + CustomReaction[] reactions; GuildReactions.TryGetValue(channel.Guild.Id, out reactions); if (reactions != null && reactions.Any()) { - var reaction = reactions.Where(cr => + var rs = reactions.Where(cr => { + if (cr == null) + return false; + var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%"); var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant(); return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger); - }).Shuffle().FirstOrDefault(); - if (reaction != null) - { - if (reaction.Response != "-") - try { await channel.SendMessageAsync(reaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { } + }).ToArray(); - ReactionStats.AddOrUpdate(reaction.Trigger, 1, (k, old) => ++old); - return true; + if (rs.Length != 0) + { + var reaction = rs[new NadekoRandom().Next(0, rs.Length)]; + if (reaction != null) + { + if (reaction.Response != "-") + try { await channel.SendMessageAsync(reaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { } + + ReactionStats.AddOrUpdate(reaction.Trigger, 1, (k, old) => ++old); + return true; + } } } - var greaction = GlobalReactions.Where(cr => + + var grs = GlobalReactions.Where(cr => { + if (cr == null) + return false; var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%"); var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant(); return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger); - }).Shuffle().FirstOrDefault(); + }).ToArray(); + if (grs.Length == 0) + return false; + var greaction = grs[new NadekoRandom().Next(0, grs.Length)]; if (greaction != null) { @@ -114,12 +130,19 @@ namespace NadekoBot.Modules.CustomReactions if (channel == null) { - GlobalReactions.Add(cr); + Array.Resize(ref _globalReactions, _globalReactions.Length + 1); + _globalReactions[_globalReactions.Length - 1] = cr; } else { - var reactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet()); - reactions.Add(cr); + var reactions = GuildReactions.AddOrUpdate(Context.Guild.Id, + Array.Empty(), + (k, old) => + { + Array.Resize(ref old, old.Length + 1); + old[old.Length - 1] = cr; + return old; + }); } await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() @@ -136,17 +159,17 @@ namespace NadekoBot.Modules.CustomReactions { if (page < 1 || page > 1000) return; - ConcurrentHashSet customReactions; + CustomReaction[] customReactions; if (Context.Guild == null) - customReactions = GlobalReactions; + customReactions = GlobalReactions.Where(cr => cr != null).ToArray(); else - customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet()); + customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, Array.Empty()).Where(cr => cr != null).ToArray(); if (customReactions == null || !customReactions.Any()) await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false); else { - var lastPage = customReactions.Count / 20; + var lastPage = customReactions.Length / 20; await Context.Channel.SendPaginatedConfirmAsync(page, curPage => new EmbedBuilder().WithOkColor() .WithTitle("Custom reactions") @@ -167,11 +190,11 @@ namespace NadekoBot.Modules.CustomReactions [Priority(1)] public async Task ListCustReact(All x) { - ConcurrentHashSet customReactions; + CustomReaction[] customReactions; if (Context.Guild == null) - customReactions = GlobalReactions; + customReactions = GlobalReactions.Where(cr => cr != null).ToArray(); else - customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet()); + customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray(); if (customReactions == null || !customReactions.Any()) await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false); @@ -195,11 +218,11 @@ namespace NadekoBot.Modules.CustomReactions { if (page < 1 || page > 10000) return; - ConcurrentHashSet customReactions; + CustomReaction[] customReactions; if (Context.Guild == null) - customReactions = GlobalReactions; + customReactions = GlobalReactions.Where(cr => cr != null).ToArray(); else - customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet()); + customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray(); if (customReactions == null || !customReactions.Any()) await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false); @@ -225,13 +248,13 @@ namespace NadekoBot.Modules.CustomReactions [NadekoCommand, Usage, Description, Aliases] public async Task ShowCustReact(int id) { - ConcurrentHashSet customReactions; + CustomReaction[] customReactions; if (Context.Guild == null) customReactions = GlobalReactions; else - customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet()); + customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }); - var found = customReactions.FirstOrDefault(cr => cr.Id == id); + var found = customReactions.FirstOrDefault(cr => cr?.Id == id); if (found == null) await Context.Channel.SendErrorAsync("No custom reaction found with that id.").ConfigureAwait(false); @@ -265,13 +288,17 @@ namespace NadekoBot.Modules.CustomReactions if ((toDelete.GuildId == null || toDelete.GuildId == 0) && Context.Guild == null) { uow.CustomReactions.Remove(toDelete); - GlobalReactions.RemoveWhere(cr => cr.Id == toDelete.Id); + //todo i can dramatically improve performance of this, if Ids are ordered. + _globalReactions = GlobalReactions.Where(cr => cr?.Id != toDelete.Id).ToArray(); success = true; } else if ((toDelete.GuildId != null && toDelete.GuildId != 0) && Context.Guild.Id == toDelete.GuildId) { uow.CustomReactions.Remove(toDelete); - GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet()).RemoveWhere(cr => cr.Id == toDelete.Id); + GuildReactions.AddOrUpdate(Context.Guild.Id, new CustomReaction[] { }, (key, old) => + { + return old.Where(cr => cr?.Id != toDelete.Id).ToArray(); + }); success = true; } if (success) @@ -312,8 +339,10 @@ namespace NadekoBot.Modules.CustomReactions { if (page < 1) return; - var ordered = ReactionStats.OrderByDescending(x => x.Value).ToList(); - var lastPage = ordered.Count / 9; + var ordered = ReactionStats.OrderByDescending(x => x.Value).ToArray(); + if (!ordered.Any()) + return; + var lastPage = ordered.Length / 9; await Context.Channel.SendPaginatedConfirmAsync(page, (curPage) => ordered.Skip((curPage - 1) * 9) .Take(9) diff --git a/src/NadekoBot/Modules/CustomReactions/Extensions.cs b/src/NadekoBot/Modules/CustomReactions/Extensions.cs index a894ad2e..ff20fa89 100644 --- a/src/NadekoBot/Modules/CustomReactions/Extensions.cs +++ b/src/NadekoBot/Modules/CustomReactions/Extensions.cs @@ -1,9 +1,11 @@ using Discord; +using Discord.WebSocket; using NadekoBot.Extensions; using NadekoBot.Services; using NadekoBot.Services.Database.Models; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; @@ -25,9 +27,13 @@ namespace NadekoBot.Modules.CustomReactions if(ch == null) return ""; - var usrs = (ch.Guild.GetUsersAsync().GetAwaiter().GetResult()); + var g = ch.Guild as SocketGuild; + if(g == null) + return ""; - return usrs.Skip(new NadekoRandom().Next(0,usrs.Count-1)).Shuffle().FirstOrDefault()?.Mention ?? ""; + var users = g.Users.ToArray(); + + return users[new NadekoRandom().Next(0, users.Length-1)].Mention; } } //{"%rng%", (ctx) => { return new NadekoRandom().Next(0,10).ToString(); } } }; diff --git a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs b/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs index 4dfa935f..927f8a72 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs @@ -9,6 +9,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Discord.WebSocket; +using NadekoBot.Services.Database; namespace NadekoBot.Modules.Gambling { @@ -19,15 +21,26 @@ namespace NadekoBot.Modules.Gambling { public enum CurrencyEvent { - FlowerReaction + FlowerReaction, + SneakyGameStatus } //flower reaction event public static readonly ConcurrentHashSet _flowerReactionAwardedUsers = new ConcurrentHashSet(); + public static readonly ConcurrentHashSet _sneakyGameAwardedUsers = new ConcurrentHashSet(); + + + private static readonly char[] _sneakyGameStatusChars = Enumerable.Range(48, 10) + .Concat(Enumerable.Range(65, 26)) + .Concat(Enumerable.Range(97, 26)) + .Select(x => (char)x) + .ToArray(); + + private static string _secretCode = String.Empty; [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] - public async Task StartEvent(CurrencyEvent e) + public async Task StartEvent(CurrencyEvent e, int arg = -1) { var channel = (ITextChannel)Context.Channel; try @@ -38,6 +51,9 @@ namespace NadekoBot.Modules.Gambling case CurrencyEvent.FlowerReaction: await FlowerReactionEvent(Context).ConfigureAwait(false); break; + case CurrencyEvent.SneakyGameStatus: + await SneakyGameStatusEvent(Context, arg).ConfigureAwait(false); + break; default: break; } @@ -45,6 +61,63 @@ namespace NadekoBot.Modules.Gambling catch { } } + public static async Task SneakyGameStatusEvent(CommandContext Context, int? arg) + { + int num; + if (arg == null || arg < 5) + num = 60; + else + num = arg.Value; + + if (_secretCode != String.Empty) + return; + var rng = new NadekoRandom(); + + for (int i = 0; i < 5; i++) + { + _secretCode += _sneakyGameStatusChars[rng.Next(0, _sneakyGameStatusChars.Length)]; + } + + await NadekoBot.Client.SetGameAsync($"type {_secretCode} for " + NadekoBot.BotConfig.CurrencyPluralName) + .ConfigureAwait(false); + try + { + await Context.Channel.SendConfirmAsync($"SneakyGameStatus event started", + $"Users must type a secret code to get 100 currency.\n" + + $"Lasts {num} seconds. Don't tell anyone. Shhh.") + .ConfigureAwait(false); + } + catch { } + + + NadekoBot.Client.MessageReceived += SneakyGameMessageReceivedEventHandler; + await Task.Delay(num * 1000); + NadekoBot.Client.MessageReceived -= SneakyGameMessageReceivedEventHandler; + + _sneakyGameAwardedUsers.Clear(); + _secretCode = String.Empty; + + await NadekoBot.Client.SetGameAsync($"SneakyGame event ended.") + .ConfigureAwait(false); + } + + private static Task SneakyGameMessageReceivedEventHandler(SocketMessage arg) + { + if (arg.Content == _secretCode && + _sneakyGameAwardedUsers.Add(arg.Author.Id)) + { + var _ = Task.Run(async () => + { + await CurrencyHandler.AddCurrencyAsync(arg.Author, "Sneaky Game Event", 100, false) + .ConfigureAwait(false); + + try { await arg.DeleteAsync(new RequestOptions() { RetryMode = RetryMode.AlwaysFail }).ConfigureAwait(false); } + catch { } + }); + } + + return Task.Delay(0); + } public static async Task FlowerReactionEvent(CommandContext Context) { @@ -59,14 +132,14 @@ namespace NadekoBot.Modules.Gambling catch { try { await msg.DeleteAsync().ConfigureAwait(false); } - catch { } + catch { return; } } } using (msg.OnReaction(async (r) => { try { - if (r.Emoji.Name == "🌸" && r.User.IsSpecified && _flowerReactionAwardedUsers.Add(r.User.Value.Id)) + if (r.Emoji.Name == "🌸" && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _flowerReactionAwardedUsers.Add(r.User.Value.Id)) { try { await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", 100, false).ConfigureAwait(false); } catch { } } diff --git a/src/NadekoBot/Modules/Gambling/Commands/Slots.cs b/src/NadekoBot/Modules/Gambling/Commands/Slots.cs index 7ed1f9ee..e1671a6d 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/Slots.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/Slots.cs @@ -184,7 +184,10 @@ namespace NadekoBot.Modules.Gambling } if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User, "Slot Machine", amount, false)) + { + await Context.Channel.SendErrorAsync($"You don't have enough {NadekoBot.BotConfig.CurrencySign}.").ConfigureAwait(false); return; + } Interlocked.Add(ref totalBet, amount); using (var bgFileStream = new MemoryStream(backgroundBuffer)) { diff --git a/src/NadekoBot/Modules/Gambling/Commands/WaifuClaimCommands.cs b/src/NadekoBot/Modules/Gambling/Commands/WaifuClaimCommands.cs new file mode 100644 index 00000000..f5a2a454 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Commands/WaifuClaimCommands.cs @@ -0,0 +1,516 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Gambling +{ + public partial class Gambling + { + public enum ClaimTitles + { + Lonely, + Devoted, + Rookie, + Schemer, + Dilettante, + Intermediate, + Seducer, + Expert, + Veteran, + Incubis, + Harem_King, + Harem_God, + } + + public enum AffinityTitles + { + Pure, + Faithful, + Defiled, + Cheater, + Tainted, + Corrupted, + Lewd, + Sloot, + Depraved, + Harlot + } + + [Group] + public class WaifuClaimCommands : ModuleBase + { + private static ConcurrentDictionary _divorceCooldowns { get; } = new ConcurrentDictionary(); + private static ConcurrentDictionary _affinityCooldowns { get; } = new ConcurrentDictionary(); + + enum WaifuClaimResult + { + Success, + NotEnoughFunds, + InsufficientAmount + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task WaifuClaim(int amount, [Remainder]IUser target) + { + if (amount < 50) + { + await Context.Channel.SendErrorAsync($"{Context.User.Mention} No waifu is that cheap. You must pay at least 50{NadekoBot.BotConfig.CurrencySign} to get a waifu, even if their actual value is lower.").ConfigureAwait(false); + return; + } + + if (target.Id == Context.User.Id) + { + await Context.Channel.SendErrorAsync(Context.User.Mention + " You can't claim yourself.").ConfigureAwait(false); + return; + } + + WaifuClaimResult result = WaifuClaimResult.NotEnoughFunds; + int? oldPrice = null; + WaifuInfo w; + var isAffinity = false; + using (var uow = DbHandler.UnitOfWork()) + { + w = uow.Waifus.ByWaifuUserId(target.Id); + isAffinity = (w?.Affinity?.UserId == Context.User.Id); + if (w == null) + { + var claimer = uow.DiscordUsers.GetOrCreate(Context.User); + var waifu = uow.DiscordUsers.GetOrCreate(target); + if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User.Id, "Claimed Waifu", amount, uow).ConfigureAwait(false)) + { + result = WaifuClaimResult.NotEnoughFunds; + } + else + { + uow.Waifus.Add(w = new WaifuInfo() + { + Waifu = waifu, + Claimer = claimer, + Affinity = null, + Price = amount + }); + uow._context.WaifuUpdates.Add(new WaifuUpdate() + { + User = waifu, + Old = null, + New = claimer, + UpdateType = WaifuUpdateType.Claimed + }); + result = WaifuClaimResult.Success; + } + } + else if (isAffinity && amount >= w.Price * 0.88f) + { + if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User.Id, "Claimed Waifu", amount, uow).ConfigureAwait(false)) + { + result = WaifuClaimResult.NotEnoughFunds; + } + else + { + var oldClaimer = w.Claimer; + w.Claimer = uow.DiscordUsers.GetOrCreate(Context.User); + oldPrice = w.Price; + w.Price = amount + (amount / 4); + result = WaifuClaimResult.Success; + + uow._context.WaifuUpdates.Add(new WaifuUpdate() + { + User = w.Waifu, + Old = oldClaimer, + New = w.Claimer, + UpdateType = WaifuUpdateType.Claimed + }); + } + } + else if (amount >= w.Price * 1.1f) // if no affinity + { + if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User.Id, "Claimed Waifu", amount, uow).ConfigureAwait(false)) + { + result = WaifuClaimResult.NotEnoughFunds; + } + else + { + var oldClaimer = w.Claimer; + w.Claimer = uow.DiscordUsers.GetOrCreate(Context.User); + oldPrice = w.Price; + w.Price = amount; + result = WaifuClaimResult.Success; + + uow._context.WaifuUpdates.Add(new WaifuUpdate() + { + User = w.Waifu, + Old = oldClaimer, + New = w.Claimer, + UpdateType = WaifuUpdateType.Claimed + }); + } + } + else + result = WaifuClaimResult.InsufficientAmount; + + + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (result == WaifuClaimResult.InsufficientAmount) + { + await Context.Channel.SendErrorAsync($"{Context.User.Mention} You must pay {Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f))} or more to claim that waifu!").ConfigureAwait(false); + return; + } + else if (result == WaifuClaimResult.NotEnoughFunds) + { + await Context.Channel.SendConfirmAsync($"{Context.User.Mention} you don't have {amount}{NadekoBot.BotConfig.CurrencySign}!") + .ConfigureAwait(false); + } + else + { + var msg = $"{Context.User.Mention} claimed {target.Mention} as their waifu for {amount}{NadekoBot.BotConfig.CurrencySign}!"; + if (w.Affinity?.UserId == Context.User.Id) + msg += $"\n🎉 Their love is fulfilled! 🎉\n**{target}'s** new value is {w.Price}{NadekoBot.BotConfig.CurrencySign}!"; + await Context.Channel.SendConfirmAsync(msg) + .ConfigureAwait(false); + } + } + + public enum DivorceResult + { + Success, + SucessWithPenalty, + NotYourWife, + Cooldown + } + + + private static readonly TimeSpan DivorceLimit = TimeSpan.FromHours(6); + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Divorce([Remainder]IUser target) + { + var channel = (ITextChannel)Context.Channel; + + if (target.Id == Context.User.Id) + return; + + var result = DivorceResult.NotYourWife; + TimeSpan difference = TimeSpan.Zero; + var amount = 0; + WaifuInfo w = null; + using (var uow = DbHandler.UnitOfWork()) + { + w = uow.Waifus.ByWaifuUserId(target.Id); + var now = DateTime.UtcNow; + if (w == null || w.Claimer == null || w.Claimer.UserId != Context.User.Id) + result = DivorceResult.NotYourWife; + else if (_divorceCooldowns.AddOrUpdate(Context.User.Id, + now, + (key, old) => ((difference = now.Subtract(old)) > DivorceLimit) ? now : old) != now) + { + result = DivorceResult.Cooldown; + } + else + { + amount = w.Price / 2; + + if (w.Affinity?.UserId == Context.User.Id) + { + await CurrencyHandler.AddCurrencyAsync(w.Waifu.UserId, "Waifu Compensation", amount, uow).ConfigureAwait(false); + w.Price = (int)Math.Floor(w.Price * 0.75f); + result = DivorceResult.SucessWithPenalty; + } + else + { + await CurrencyHandler.AddCurrencyAsync(Context.User.Id, "Waifu Refund", amount, uow).ConfigureAwait(false); + + result = DivorceResult.Success; + } + var oldClaimer = w.Claimer; + w.Claimer = null; + + uow._context.WaifuUpdates.Add(new WaifuUpdate() + { + User = w.Waifu, + Old = oldClaimer, + New = null, + UpdateType = WaifuUpdateType.Claimed + }); + } + + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (result == DivorceResult.SucessWithPenalty) + { + await Context.Channel.SendConfirmAsync($"{Context.User.Mention} You have divorced a waifu who likes you. You heartless monster.\n{w.Waifu} received {amount}{NadekoBot.BotConfig.CurrencySign} as a compensation.").ConfigureAwait(false); + } + else if (result == DivorceResult.Success) + { + await Context.Channel.SendConfirmAsync($"{Context.User.Mention} You have divorced a waifu who doesn't like you. You received {amount}{NadekoBot.BotConfig.CurrencySign} back.").ConfigureAwait(false); + } + else if (result == DivorceResult.NotYourWife) + { + await Context.Channel.SendErrorAsync($"{Context.User.Mention} That waifu is not yours.").ConfigureAwait(false); + } + else + { + var remaining = DivorceLimit.Subtract(difference); + await Context.Channel.SendErrorAsync($"{Context.User.Mention} You divorced recently. You must wait **{remaining.Hours} hours and {remaining.Minutes} minutes** to divorce again.").ConfigureAwait(false); + } + } + + private static readonly TimeSpan AffinityLimit = TimeSpan.FromMinutes(30); + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task WaifuClaimerAffinity([Remainder]IUser u = null) + { + if (u?.Id == Context.User.Id) + { + await Context.Channel.SendErrorAsync($"{Context.User.Mention} you can't set affinity to yourself, you egomaniac.").ConfigureAwait(false); + return; + } + DiscordUser oldAff = null; + var sucess = false; + var cooldown = false; + TimeSpan difference = TimeSpan.Zero; + using (var uow = DbHandler.UnitOfWork()) + { + var w = uow.Waifus.ByWaifuUserId(Context.User.Id); + var newAff = u == null ? null : uow.DiscordUsers.GetOrCreate(u); + var now = DateTime.UtcNow; + if (w?.Affinity?.UserId == u?.Id) + { + sucess = false; + } + else if (_affinityCooldowns.AddOrUpdate(Context.User.Id, + now, + (key, old) => ((difference = now.Subtract(old)) > AffinityLimit) ? now : old) != now) + { + sucess = false; + cooldown = true; + } + else if (w == null) + { + var thisUser = uow.DiscordUsers.GetOrCreate(Context.User); + uow.Waifus.Add(new WaifuInfo() + { + Affinity = newAff, + Waifu = thisUser, + Price = 1, + Claimer = null + }); + sucess = true; + + uow._context.WaifuUpdates.Add(new WaifuUpdate() + { + User = thisUser, + Old = null, + New = newAff, + UpdateType = WaifuUpdateType.AffinityChanged + }); + } + else + { + if (w.Affinity != null) + oldAff = w.Affinity; + w.Affinity = newAff; + sucess = true; + + uow._context.WaifuUpdates.Add(new WaifuUpdate() + { + User = w.Waifu, + Old = oldAff, + New = newAff, + UpdateType = WaifuUpdateType.AffinityChanged + }); + } + + await uow.CompleteAsync().ConfigureAwait(false); + } + if (!sucess) + { + if (cooldown) + { + var remaining = AffinityLimit.Subtract(difference); + await Context.Channel.SendErrorAsync($"{Context.User.Mention} You must wait **{remaining.Hours} hours and {remaining.Minutes} minutes** in order to change your affinity again.").ConfigureAwait(false); + } + else + await Context.Channel.SendErrorAsync($"{Context.User.Mention} your affinity is already set to that waifu or you're trying to remove your affinity while not having one.").ConfigureAwait(false); + return; + } + if (u == null) + await Context.Channel.SendConfirmAsync("Affinity Reset", $"{Context.User.Mention} Your affinity is reset. You no longer have a person you like.").ConfigureAwait(false); + else if (oldAff == null) + await Context.Channel.SendConfirmAsync("Affinity Set", $"{Context.User.Mention} wants to be {u.Mention}'s waifu. Aww <3").ConfigureAwait(false); + else + await Context.Channel.SendConfirmAsync("Affinity Changed", $"{Context.User.Mention} changed their affinity from {oldAff} to {u.Mention}.\n\n*This is morally questionable.*🤔").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task WaifuLeaderboard() + { + IList waifus; + using (var uow = DbHandler.UnitOfWork()) + { + waifus = uow.Waifus.GetTop(9); + } + + if (waifus.Count == 0) + { + await Context.Channel.SendConfirmAsync("No waifus have been claimed yet.").ConfigureAwait(false); + return; + } + + var embed = new EmbedBuilder() + .WithTitle("Top Waifus") + .WithOkColor(); + + for (int i = 0; i < waifus.Count; i++) + { + var w = waifus[i]; + + embed.AddField(efb => efb.WithName("#" + (i + 1) + " - " + w.Price + NadekoBot.BotConfig.CurrencySign).WithValue(w.ToString()).WithIsInline(false)); + } + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task WaifuInfo([Remainder]IUser target = null) + { + if (target == null) + target = Context.User; + WaifuInfo w; + IList claims; + int divorces = 0; + using (var uow = DbHandler.UnitOfWork()) + { + w = uow.Waifus.ByWaifuUserId(target.Id); + claims = uow.Waifus.ByClaimerUserId(target.Id); + divorces = uow._context.WaifuUpdates.Count(x => x.Old != null && + x.Old.UserId == target.Id && + x.UpdateType == WaifuUpdateType.Claimed && + x.New == null); + if (w == null) + uow.Waifus.Add(w = new WaifuInfo() + { + Affinity = null, + Claimer = null, + Price = 1, + Waifu = uow.DiscordUsers.GetOrCreate(target), + }); + await uow.CompleteAsync().ConfigureAwait(false); + } + + var claimInfo = GetClaimTitle(target.Id); + var affInfo = GetAffinityTitle(target.Id); + + var embed = new EmbedBuilder() + .WithOkColor() + .WithTitle("Waifu " + w.Waifu.ToString() + " - \"the " + claimInfo.Title + "\"") + .AddField(efb => efb.WithName("Price").WithValue(w.Price.ToString()).WithIsInline(true)) + .AddField(efb => efb.WithName("Claimed by").WithValue(w.Claimer?.ToString() ?? "No one").WithIsInline(true)) + .AddField(efb => efb.WithName("Likes").WithValue(w.Affinity?.ToString() ?? "Nobody").WithIsInline(true)) + .AddField(efb => efb.WithName("Changes Of Heart").WithValue($"{affInfo.Count} - \"the {affInfo.Title}\"").WithIsInline(true)) + .AddField(efb => efb.WithName("Divorces").WithValue(divorces.ToString()).WithIsInline(true)) + .AddField(efb => efb.WithName($"Waifus ({claims.Count})").WithValue(claims.Count == 0 ? "Nobody" : string.Join("\n", claims.Select(x => x.Waifu))).WithIsInline(true)); + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + + + public struct WaifuProfileTitle + { + public int Count { get; } + public string Title { get; } + + public WaifuProfileTitle(int count, string title) + { + Count = count; + Title = title; + } + } + + private static WaifuProfileTitle GetClaimTitle(ulong userId) + { + int count = 0; + using (var uow = DbHandler.UnitOfWork()) + { + count = uow.Waifus.ByClaimerUserId(userId).Count; + } + + ClaimTitles title = ClaimTitles.Lonely; + if (count == 0) + title = ClaimTitles.Lonely; + else if (count == 1) + title = ClaimTitles.Devoted; + else if (count < 4) + title = ClaimTitles.Rookie; + else if (count < 6) + title = ClaimTitles.Schemer; + else if (count < 8) + title = ClaimTitles.Dilettante; + else if (count < 10) + title = ClaimTitles.Intermediate; + else if (count < 12) + title = ClaimTitles.Seducer; + else if (count < 15) + title = ClaimTitles.Expert; + else if (count < 17) + title = ClaimTitles.Veteran; + else if (count < 25) + title = ClaimTitles.Incubis; + else if (count < 50) + title = ClaimTitles.Harem_King; + else + title = ClaimTitles.Harem_God; + + return new WaifuProfileTitle(count, title.ToString().Replace('_', ' ')); + } + + private static WaifuProfileTitle GetAffinityTitle(ulong userId) + { + int count = 0; + using (var uow = DbHandler.UnitOfWork()) + { + count = uow._context.WaifuUpdates.Count(w => w.User.UserId == userId && w.UpdateType == WaifuUpdateType.AffinityChanged); + } + + AffinityTitles title = AffinityTitles.Pure; + if (count < 1) + title = AffinityTitles.Pure; + else if (count < 2) + title = AffinityTitles.Faithful; + else if (count < 4) + title = AffinityTitles.Defiled; + else if (count < 7) + title = AffinityTitles.Cheater; + else if (count < 9) + title = AffinityTitles.Tainted; + else if (count < 11) + title = AffinityTitles.Corrupted; + else if (count < 13) + title = AffinityTitles.Lewd; + else if (count < 15) + title = AffinityTitles.Sloot; + else if (count < 17) + title = AffinityTitles.Depraved; + else + title = AffinityTitles.Harlot; + + return new WaifuProfileTitle(count, title.ToString().Replace('_', ' ')); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Gambling.cs b/src/NadekoBot/Modules/Gambling/Gambling.cs index a52ea433..ceaa4300 100644 --- a/src/NadekoBot/Modules/Gambling/Gambling.cs +++ b/src/NadekoBot/Modules/Gambling/Gambling.cs @@ -42,7 +42,7 @@ namespace NadekoBot.Modules.Gambling var members = role.Members().Where(u => u.Status != UserStatus.Offline && u.Status != UserStatus.Unknown); var membersArray = members as IUser[] ?? members.ToArray(); var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)]; - await Context.Channel.SendConfirmAsync("🎟 Raffled user", $"**{usr.Username}#{usr.Discriminator}** ID: `{usr.Id}`").ConfigureAwait(false); + await Context.Channel.SendConfirmAsync("🎟 Raffled user", $"**{usr.Username}#{usr.Discriminator}**", footer: $"ID: {usr.Id}").ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -67,14 +67,14 @@ namespace NadekoBot.Modules.Gambling { if (amount <= 0 || Context.User.Id == receiver.Id) return; - var success = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount, true).ConfigureAwait(false); + var success = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount, false).ConfigureAwait(false); if (!success) { await Context.Channel.SendErrorAsync($"{Context.User.Mention} You don't have enough {CurrencyPluralName}.").ConfigureAwait(false); return; } await CurrencyHandler.AddCurrencyAsync(receiver, $"Gift from {Context.User.Username} ({Context.User.Id}).", amount, true).ConfigureAwait(false); - await Context.Channel.SendConfirmAsync($"{Context.User.Mention} successfully sent {amount} {(amount == 1 ? CurrencyName : CurrencyPluralName)} to {receiver.Mention}!").ConfigureAwait(false); + await Context.Channel.SendConfirmAsync($"{Context.User.Mention} gifted {amount}{CurrencySign} to {Format.Bold(receiver.ToString())}!").ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -94,7 +94,7 @@ namespace NadekoBot.Modules.Gambling await CurrencyHandler.AddCurrencyAsync(usrId, $"Awarded by bot owner. ({Context.User.Username}/{Context.User.Id})", amount).ConfigureAwait(false); - await Context.Channel.SendConfirmAsync($"{Context.User.Mention} awarded {amount} {(amount == 1 ? CurrencyName : CurrencyPluralName)} to <@{usrId}>!").ConfigureAwait(false); + await Context.Channel.SendConfirmAsync($"{Context.User.Mention} awarded {amount}{CurrencySign} to <@{usrId}>!").ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -145,6 +145,61 @@ namespace NadekoBot.Modules.Gambling await Context.Channel.SendErrorAsync($"{Context.User.Mention} was unable to take {amount} {(amount == 1 ? CurrencyName : CurrencyPluralName)} from `{usrId}` because the user doesn't have that much {CurrencyPluralName}!").ConfigureAwait(false); } + //[NadekoCommand, Usage, Description, Aliases] + //[OwnerOnly] + //public Task BrTest(int tests = 1000) + //{ + // var t = Task.Run(async () => + // { + // if (tests <= 0) + // return; + // //multi vs how many times it occured + // var dict = new Dictionary(); + // var generator = new NadekoRandom(); + // for (int i = 0; i < tests; i++) + // { + // var rng = generator.Next(0, 101); + // var mult = 0; + // if (rng < 67) + // { + // mult = 0; + // } + // else if (rng < 91) + // { + // mult = 2; + // } + // else if (rng < 100) + // { + // mult = 4; + // } + // else + // mult = 10; + + // if (dict.ContainsKey(mult)) + // dict[mult] += 1; + // else + // dict.Add(mult, 1); + // } + + // var sb = new StringBuilder(); + // const int bet = 1; + // int payout = 0; + // foreach (var key in dict.Keys.OrderByDescending(x => x)) + // { + // sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%"); + // payout += key * dict[key]; + // } + // try + // { + // await Context.Channel.SendConfirmAsync("BetRoll Test Results", sb.ToString(), + // footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%"); + // } + // catch { } + + // }); + // return Task.CompletedTask; + //} + [NadekoCommand, Usage, Description, Aliases] public async Task BetRoll(long amount) { @@ -193,22 +248,33 @@ namespace NadekoBot.Modules.Gambling [NadekoCommand, Usage, Description, Aliases] public async Task Leaderboard() { - IEnumerable richest = new List(); + var richest = new List(); using (var uow = DbHandler.UnitOfWork()) { - richest = uow.Currency.GetTopRichest(10); + richest = uow.Currency.GetTopRichest(9).ToList(); } if (!richest.Any()) return; - await Context.Channel.SendMessageAsync( - richest.Aggregate(new StringBuilder( -$@"```xl -┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓ -┃ Id ┃ $$$ ┃ -"), - (cur, cs) => cur.AppendLine($@"┣━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━┫ -┃{(Context.Guild.GetUserAsync(cs.UserId).GetAwaiter().GetResult()?.Username?.TrimTo(18, true) ?? cs.UserId.ToString()),-20} ┃ {cs.Amount,6} ┃") - ).ToString() + "┗━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━┛```").ConfigureAwait(false); + + + var embed = new EmbedBuilder() + .WithOkColor() + .WithTitle(NadekoBot.BotConfig.CurrencySign + " Leaderboard"); + + for (var i = 0; i < richest.Count; i++) + { + var x = richest[i]; + var usr = await Context.Guild.GetUserAsync(x.UserId).ConfigureAwait(false); + var usrStr = ""; + if (usr == null) + usrStr = x.UserId.ToString(); + else + usrStr = usr.Username?.TrimTo(20, true); + + embed.AddField(efb => efb.WithName("#" + (i + 1) + " " + usrStr).WithValue(x.Amount.ToString() + " " + NadekoBot.BotConfig.CurrencySign).WithIsInline(true)); + } + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } } } diff --git a/src/NadekoBot/Modules/Games/Commands/Acropobia.cs b/src/NadekoBot/Modules/Games/Commands/Acropobia.cs index 2a7307bb..ff0cc725 100644 --- a/src/NadekoBot/Modules/Games/Commands/Acropobia.cs +++ b/src/NadekoBot/Modules/Games/Commands/Acropobia.cs @@ -69,6 +69,7 @@ namespace NadekoBot.Modules.Games 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 = 0; @@ -190,10 +191,6 @@ namespace NadekoBot.Modules.Games try { await channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); } catch { } } - //user didn't input something already - IGuildUser throwaway; - if (submissions.TryGetValue(input, out throwaway)) - return; 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 @@ -207,9 +204,15 @@ namespace NadekoBot.Modules.Games 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("Acrophobia", $"{guildUser.Mention} submitted their sentence. ({submissions.Count} total)"); diff --git a/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs b/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs index 4dcb54e9..3d5f7ec6 100644 --- a/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs +++ b/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs @@ -189,13 +189,13 @@ namespace NadekoBot.Modules.Games.Commands.Hangman catch (Exception ex) { _log.Warn(ex); } } - public string GetHangman() => $@"\_\_\_\_\_\_\_\_\_ - | | - | | - {(Errors > 0 ? "😲" : " ")} | - {(Errors > 1 ? "/" : " ")} {(Errors > 2 ? "|" : " ")} {(Errors > 3 ? "\\" : " ")} | - {(Errors > 4 ? "/" : " ")} {(Errors > 5 ? "\\" : " ")} | - /-\"; + public string GetHangman() => $@". ┌─────┐ +.┃...............┋ +.┃...............┋ +.┃{(Errors > 0 ? ".............😲" : "")} +.┃{(Errors > 1 ? "............./" : "")} {(Errors > 2 ? "|" : "")} {(Errors > 3 ? "\\" : "")} +.┃{(Errors > 4 ? "............../" : "")} {(Errors > 5 ? "\\" : "")} +/-\"; public void Dispose() { diff --git a/src/NadekoBot/Modules/Games/Commands/HangmanCommands.cs b/src/NadekoBot/Modules/Games/Commands/HangmanCommands.cs index 50596e4c..4d499301 100644 --- a/src/NadekoBot/Modules/Games/Commands/HangmanCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/HangmanCommands.cs @@ -61,7 +61,7 @@ namespace NadekoBot.Modules.Games return; } - await Context.Channel.SendConfirmAsync("Hangman game started", hm.ScrambledWord + "\n" + hm.GetHangman() + "\n" + hm.ScrambledWord); + await Context.Channel.SendConfirmAsync("Hangman game started", hm.ScrambledWord + "\n" + hm.GetHangman()); } } } diff --git a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs index 272ced2a..6b5da1b1 100644 --- a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs @@ -147,9 +147,12 @@ namespace NadekoBot.Modules.Games [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - public async Task Plant() + public async Task Plant(int amount = 1) { - var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Planted a {NadekoBot.BotConfig.CurrencyName}", 1, false).ConfigureAwait(false); + if (amount < 1) + return; + + var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Planted a {NadekoBot.BotConfig.CurrencyName}", amount, false).ConfigureAwait(false); if (!removed) { await Context.Channel.SendErrorAsync($"You don't have any {NadekoBot.BotConfig.CurrencyPluralName}.").ConfigureAwait(false); @@ -160,7 +163,7 @@ namespace NadekoBot.Modules.Games IUserMessage msg; var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.BotConfig.CurrencyName[0]); - var msgToSend = $"Oh how Nice! **{Context.User.Username}** planted {(vowelFirst ? "an" : "a")} {NadekoBot.BotConfig.CurrencyName}. Pick it using {NadekoBot.ModulePrefixes[typeof(Games).Name]}pick"; + var msgToSend = $"Oh how Nice! **{Context.User.Username}** planted {(amount == 1 ? (vowelFirst ? "an" : "a") : amount.ToString())} {(amount > 1 ? NadekoBot.BotConfig.CurrencyPluralName : NadekoBot.BotConfig.CurrencyName)}. Pick it using {NadekoBot.ModulePrefixes[typeof(Games).Name]}pick"; if (file == null) { msg = await Context.Channel.SendConfirmAsync(NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false); @@ -169,7 +172,15 @@ namespace NadekoBot.Modules.Games { msg = await Context.Channel.SendFileAsync(File.Open(file, FileMode.OpenOrCreate), new FileInfo(file).Name, msgToSend).ConfigureAwait(false); } - plantedFlowers.AddOrUpdate(Context.Channel.Id, new List() { msg }, (id, old) => { old.Add(msg); return old; }); + + var msgs = new IUserMessage[amount]; + msgs[0] = msg; + + plantedFlowers.AddOrUpdate(Context.Channel.Id, msgs.ToList(), (id, old) => + { + old.AddRange(msgs); + return old; + }); } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs index 08e9a9c8..2646ebd5 100644 --- a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs +++ b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs @@ -76,9 +76,9 @@ namespace NadekoBot.Modules.Games.Trivia questionMessage = await channel.EmbedAsync(questionEmbed).ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound || - ex.StatusCode == System.Net.HttpStatusCode.Forbidden || - ex.StatusCode == System.Net.HttpStatusCode.BadRequest) + catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound || + ex.HttpCode == System.Net.HttpStatusCode.Forbidden || + ex.HttpCode == System.Net.HttpStatusCode.BadRequest) { return; } @@ -106,7 +106,7 @@ namespace NadekoBot.Modules.Games.Trivia await questionMessage.ModifyAsync(m => m.Embed = questionEmbed.WithFooter(efb => efb.WithText(CurrentQuestion.GetHint())).Build()) .ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound || ex.StatusCode == System.Net.HttpStatusCode.Forbidden) + catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound || ex.HttpCode == System.Net.HttpStatusCode.Forbidden) { break; } diff --git a/src/NadekoBot/Modules/Games/Games.cs b/src/NadekoBot/Modules/Games/Games.cs index 8dfd7d5a..6faf288c 100644 --- a/src/NadekoBot/Modules/Games/Games.cs +++ b/src/NadekoBot/Modules/Games/Games.cs @@ -13,7 +13,7 @@ namespace NadekoBot.Modules.Games [NadekoModule("Games", ">")] public partial class Games : DiscordModule { - private static IEnumerable _8BallResponses { get; } = NadekoBot.BotConfig.EightBallResponses.Select(ebr => ebr.Text); + private static string[] _8BallResponses { get; } = NadekoBot.BotConfig.EightBallResponses.Select(ebr => ebr.Text).ToArray(); [NadekoCommand, Usage, Description, Aliases] @@ -37,7 +37,7 @@ namespace NadekoBot.Modules.Games await Context.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) .AddField(efb => efb.WithName("❓ Question").WithValue(question).WithIsInline(false)) - .AddField(efb => efb.WithName("🎱 8Ball").WithValue(_8BallResponses.Shuffle().FirstOrDefault()).WithIsInline(false))); + .AddField(efb => efb.WithName("🎱 8Ball").WithValue(_8BallResponses[new NadekoRandom().Next(0, _8BallResponses.Length)]).WithIsInline(false))); } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Modules/Music/Classes/MusicControls.cs b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs index 6d101975..e11b680e 100644 --- a/src/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -71,6 +71,7 @@ namespace NadekoBot.Modules.Music.Classes public event Action OnPauseChanged = delegate { }; public IVoiceChannel PlaybackVoiceChannel { get; private set; } + public ITextChannel OutputTextChannel { get; set; } private bool Destroyed { get; set; } = false; public bool RepeatSong { get; private set; } = false; @@ -84,10 +85,12 @@ namespace NadekoBot.Modules.Music.Classes public event Action SongRemoved = delegate { }; - public MusicPlayer(IVoiceChannel startingVoiceChannel, float? defaultVolume) + public MusicPlayer(IVoiceChannel startingVoiceChannel, ITextChannel outputChannel, float? defaultVolume) { if (startingVoiceChannel == null) throw new ArgumentNullException(nameof(startingVoiceChannel)); + + OutputTextChannel = outputChannel; Volume = defaultVolume ?? 1.0f; PlaybackVoiceChannel = startingVoiceChannel; diff --git a/src/NadekoBot/Modules/Music/Classes/SongHandler.cs b/src/NadekoBot/Modules/Music/Classes/SongHandler.cs index 7892e278..f296d2cb 100644 --- a/src/NadekoBot/Modules/Music/Classes/SongHandler.cs +++ b/src/NadekoBot/Modules/Music/Classes/SongHandler.cs @@ -97,7 +97,7 @@ namespace NadekoBot.Modules.Music.Classes { Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube" Provider = "YouTube", - Uri = video.Uri, + Uri = await video.GetUriAsync().ConfigureAwait(false), Query = link, ProviderType = musicType, }); diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index 868a0885..5ad9918c 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -211,30 +211,30 @@ namespace NadekoBot.Modules.Music { int startAt = itemsPerPage * (curPage - 1); var number = 0 + startAt; + var desc = string.Join("\n", musicPlayer.Playlist + .Skip(startAt) + .Take(itemsPerPage) + .Select(v => $"`{++number}.` {v.PrettyFullName}")); + + if (currentSong != null) + desc = $"`🔊` {currentSong.PrettyFullName}\n\n" + desc; + + if (musicPlayer.RepeatSong) + desc = "🔂 Repeating Current Song\n\n" + desc; + else if (musicPlayer.RepeatPlaylist) + desc = "🔁 Repeating Playlist\n\n" + desc; + + + var embed = new EmbedBuilder() .WithAuthor(eab => eab.WithName($"Player Queue - Page {curPage}/{lastPage + 1}") .WithMusicIcon()) - .WithDescription(string.Join("\n", musicPlayer.Playlist - .Skip(startAt) - .Take(itemsPerPage) - .Select(v => $"`{++number}.` {v.PrettyFullName}"))) + .WithDescription(desc) .WithFooter(ef => ef.WithText($"{musicPlayer.PrettyVolume} | {musicPlayer.Playlist.Count} " + $"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {totalStr} | " + (musicPlayer.FairPlay ? "✔️fairplay" : "✖️fairplay") + $" | " + (maxPlaytime == 0 ? "unlimited" : $"{maxPlaytime}s limit"))) .WithOkColor(); - if (musicPlayer.RepeatSong) - { - embed.WithTitle($"🔂 Repeating Song: {currentSong.SongInfo.Title} | {currentSong.PrettyFullTime}"); - } - else if (musicPlayer.RepeatPlaylist) - { - embed.WithTitle("🔁 Repeating Playlist"); - } - if (musicPlayer.MaxQueueSize != 0 && musicPlayer.Playlist.Count >= musicPlayer.MaxQueueSize) - { - embed.WithTitle("🎵 Song queue is full!"); - } return embed; }; await Context.Channel.SendPaginatedConfirmAsync(page, printAction, lastPage, false).ConfigureAwait(false); @@ -711,14 +711,11 @@ namespace NadekoBot.Modules.Music await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } - - //todo only author or owner + [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] public async Task DeletePlaylist([Remainder] int id) { - - bool success = false; MusicPlaylist pl = null; try @@ -747,7 +744,7 @@ namespace NadekoBot.Modules.Music } catch (Exception ex) { - Console.WriteLine(ex); + _log.Warn(ex); } } @@ -800,6 +797,23 @@ namespace NadekoBot.Modules.Music await Context.Channel.SendConfirmAsync("✅ Autoplay enabled.").ConfigureAwait(false); } + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.ManageMessages)] + public async Task SetMusicChannel() + { + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) + { + await Context.Channel.SendErrorAsync("Music must be playing before you set an ouput channel.").ConfigureAwait(false); + return; + } + + musicPlayer.OutputTextChannel = (ITextChannel)Context.Channel; + + await Context.Channel.SendConfirmAsync("I will now output playing, finished, paused and removed songs in this channel.").ConfigureAwait(false); + } + public static async Task QueueSong(IGuildUser queuer, ITextChannel textCh, IVoiceChannel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal) { if (voiceCh == null || voiceCh.Guild != textCh.Guild) @@ -818,7 +832,7 @@ namespace NadekoBot.Modules.Music { vol = uow.GuildConfigs.For(textCh.Guild.Id, set => set).DefaultMusicVolume; } - var mp = new MusicPlayer(voiceCh, vol); + var mp = new MusicPlayer(voiceCh, textCh, vol); IUserMessage playingMessage = null; IUserMessage lastFinishedMessage = null; mp.OnCompleted += async (s, song) => @@ -828,7 +842,7 @@ namespace NadekoBot.Modules.Music if (lastFinishedMessage != null) lastFinishedMessage.DeleteAfter(0); - lastFinishedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor() + lastFinishedMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithAuthor(eab => eab.WithName("Finished Song").WithMusicIcon()) .WithDescription(song.PrettyName) .WithFooter(ef => ef.WithText(song.PrettyInfo))) @@ -836,7 +850,14 @@ namespace NadekoBot.Modules.Music if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.ProviderType == MusicType.Normal) { - await QueueSong(await queuer.Guild.GetCurrentUserAsync(), textCh, voiceCh, (await NadekoBot.Google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList().Shuffle().FirstOrDefault(), silent, musicType).ConfigureAwait(false); + var relatedVideos = (await NadekoBot.Google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList(); + if(relatedVideos.Count > 0) + await QueueSong(await queuer.Guild.GetCurrentUserAsync(), + textCh, + voiceCh, + relatedVideos[new NadekoRandom().Next(0, relatedVideos.Count)], + silent, + musicType).ConfigureAwait(false); } } catch { } @@ -853,7 +874,7 @@ namespace NadekoBot.Modules.Music if (playingMessage != null) playingMessage.DeleteAfter(0); - playingMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor() + playingMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithAuthor(eab => eab.WithName("Playing Song").WithMusicIcon()) .WithDescription(song.PrettyName) .WithFooter(ef => ef.WithText(song.PrettyInfo))) @@ -862,22 +883,21 @@ namespace NadekoBot.Modules.Music catch { } }; mp.OnPauseChanged += async (paused) => - { - try - { - IUserMessage msg; - if (paused) - msg = await textCh.SendConfirmAsync("🎵 Music playback **paused**.").ConfigureAwait(false); - else - msg = await textCh.SendConfirmAsync("🎵 Music playback **resumed**.").ConfigureAwait(false); - - if (msg != null) - msg.DeleteAfter(10); - } - catch { } - }; - + { + try + { + IUserMessage msg; + if (paused) + msg = await mp.OutputTextChannel.SendConfirmAsync("🎵 Music playback **paused**.").ConfigureAwait(false); + else + msg = await mp.OutputTextChannel.SendConfirmAsync("🎵 Music playback **resumed**.").ConfigureAwait(false); + if (msg != null) + msg.DeleteAfter(10); + } + catch { } + }; + mp.SongRemoved += async (song, index) => { try @@ -888,7 +908,7 @@ namespace NadekoBot.Modules.Music .WithFooter(ef => ef.WithText(song.PrettyInfo)) .WithErrorColor(); - await textCh.EmbedAsync(embed).ConfigureAwait(false); + await mp.OutputTextChannel.EmbedAsync(embed).ConfigureAwait(false); } catch { } diff --git a/src/NadekoBot/Modules/NSFW/NSFW.cs b/src/NadekoBot/Modules/NSFW/NSFW.cs index 589d421a..266ffa68 100644 --- a/src/NadekoBot/Modules/NSFW/NSFW.cs +++ b/src/NadekoBot/Modules/NSFW/NSFW.cs @@ -289,6 +289,6 @@ namespace NadekoBot.Modules.NSFW public static Task GetRule34ImageLink(string tag) => Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Rule34); - } #endif + } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs b/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs index d00972eb..fbb41acd 100644 --- a/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs +++ b/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs @@ -65,12 +65,12 @@ namespace NadekoBot.Modules.Permissions { var activeCds = activeCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); activeCds.RemoveWhere(ac => ac.Command == command.Aliases.First().ToLowerInvariant()); - await channel.SendConfirmAsync($"🚮 Command **{command}** has no coooldown now and all existing cooldowns have been cleared.") + await channel.SendConfirmAsync($"🚮 Command **{command.Aliases.First()}** has no coooldown now and all existing cooldowns have been cleared.") .ConfigureAwait(false); } else { - await channel.SendConfirmAsync($"✅ Command **{command}** now has a **{secs} {"seconds".SnPl(secs)}** cooldown.") + await channel.SendConfirmAsync($"✅ Command **{command.Aliases.First()}** now has a **{secs} {"seconds".SnPl(secs)}** cooldown.") .ConfigureAwait(false); } } diff --git a/src/NadekoBot/Modules/Searches/Commands/AnimeSearchCommands.cs b/src/NadekoBot/Modules/Searches/Commands/AnimeSearchCommands.cs index ed6c058b..2aeb3c5b 100644 --- a/src/NadekoBot/Modules/Searches/Commands/AnimeSearchCommands.cs +++ b/src/NadekoBot/Modules/Searches/Commands/AnimeSearchCommands.cs @@ -1,4 +1,7 @@ -using Discord; +using AngleSharp; +using AngleSharp.Dom.Html; +using AngleSharp.Extensions; +using Discord; using Discord.Commands; using NadekoBot.Attributes; using NadekoBot.Extensions; @@ -8,6 +11,7 @@ using Newtonsoft.Json.Linq; using NLog; using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -52,6 +56,116 @@ namespace NadekoBot.Modules.Searches }, null, TimeSpan.FromSeconds(0), TimeSpan.FromMinutes(29)); } + [NadekoCommand, Usage, Description, Aliases] + [Priority(1)] + public async Task Mal([Remainder] string name) + { + if (string.IsNullOrWhiteSpace(name)) + return; + + var fullQueryLink = "https://myanimelist.net/profile/" + name; + + var config = Configuration.Default.WithDefaultLoader(); + var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); + + var imageElem = document.QuerySelector("body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-left > div.user-profile > div.user-image > img"); + var imageUrl = ((IHtmlImageElement)imageElem)?.Source ?? "http://icecream.me/uploads/870b03f36b59cc16ebfe314ef2dde781.png"; + + var stats = document.QuerySelectorAll("body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-right > div#statistics > div.user-statistics-stats > div.stats > div.clearfix > ul.stats-status > li > span").Select(x => x.InnerHtml).ToList(); + + var favorites = document.QuerySelectorAll("div.user-favorites > div.di-tc"); + + var favAnime = "No favorite anime yet"; + if (favorites[0].QuerySelector("p") == null) + favAnime = string.Join("\n", favorites[0].QuerySelectorAll("ul > li > div.di-tc.va-t > a") + .Shuffle() + .Take(3) + .Select(x => + { + var elem = (IHtmlAnchorElement)x; + 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") + .Select(x => Tuple.Create(x.Children[0].InnerHtml, x.Children[1].InnerHtml)) + .ToList(); + + var daysAndMean = document.QuerySelectorAll("div.anime:nth-child(1) > div:nth-child(2) > div") + .Select(x => x.TextContent.Split(':').Select(y => y.Trim()).ToArray()) + .ToArray(); + + var embed = new EmbedBuilder() + .WithOkColor() + .WithTitle($"{name}'s MAL profile") + .AddField(efb => efb.WithName("💚 Watching").WithValue(stats[0]).WithIsInline(true)) + .AddField(efb => efb.WithName("💙 Completed").WithValue(stats[1]).WithIsInline(true)); + if (info.Count < 3) + embed.AddField(efb => efb.WithName("💛 On-Hold").WithValue(stats[2]).WithIsInline(true)); + embed + .AddField(efb => efb.WithName("💔 Dropped").WithValue(stats[3]).WithIsInline(true)) + .AddField(efb => efb.WithName("⚪ Plan to watch").WithValue(stats[4]).WithIsInline(true)) + .AddField(efb => efb.WithName("🕐 " + daysAndMean[0][0]).WithValue(daysAndMean[0][1]).WithIsInline(true)) + .AddField(efb => efb.WithName("📊 " + daysAndMean[1][0]).WithValue(daysAndMean[1][1]).WithIsInline(true)) + .AddField(efb => efb.WithName(MalInfoToEmoji(info[0].Item1) + " " + info[0].Item1).WithValue(info[0].Item2.TrimTo(20)).WithIsInline(true)) + .AddField(efb => efb.WithName(MalInfoToEmoji(info[1].Item1) + " " + info[1].Item1).WithValue(info[1].Item2.TrimTo(20)).WithIsInline(true)); + if (info.Count > 2) + embed.AddField(efb => efb.WithName(MalInfoToEmoji(info[2].Item1) + " " + info[2].Item1).WithValue(info[2].Item2.TrimTo(20)).WithIsInline(true)); + //if(info.Count > 3) + // embed.AddField(efb => efb.WithName(MalInfoToEmoji(info[3].Item1) + " " + info[3].Item1).WithValue(info[3].Item2).WithIsInline(true)) + embed + .WithDescription($@" +** https://myanimelist.net/animelist/{ name } ** + +**Top 3 Favorite Anime:** +{favAnime}" + +//**[Manga List](https://myanimelist.net/mangalist/{name})** +//💚`Reading:` {stats[5]} +//💙`Completed:` {stats[6]} +//💔`Dropped:` {stats[8]} +//⚪`Plan to read:` {stats[9]} + +//**Top 3 Favorite Manga:** +//{favManga}" + +) + .WithUrl(fullQueryLink) + .WithImageUrl(imageUrl); + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + + private static string MalInfoToEmoji(string info) { + info = info.Trim().ToLowerInvariant(); + switch (info) + { + case "gender": + return "🚁"; + case "location": + return "🗺"; + case "last online": + return "👥"; + case "birthday": + return "📆"; + default: + return "❔"; + } + } + + [NadekoCommand, Usage, Description, Aliases] + [Priority(0)] + public Task Mal(IUser usr) => Mal(usr.Username); + [NadekoCommand, Usage, Description, Aliases] public async Task Anime([Remainder] string query) { diff --git a/src/NadekoBot/Modules/Searches/Commands/OsuCommands.cs b/src/NadekoBot/Modules/Searches/Commands/OsuCommands.cs index f7f83361..a2760a1a 100644 --- a/src/NadekoBot/Modules/Searches/Commands/OsuCommands.cs +++ b/src/NadekoBot/Modules/Searches/Commands/OsuCommands.cs @@ -45,7 +45,7 @@ namespace NadekoBot.Modules.Searches MemoryStream ms = new MemoryStream(); res.CopyTo(ms); ms.Position = 0; - await Context.Channel.SendFileAsync(ms, $"{usr}.png", $"🎧 **Profile Link: **https://osu.ppy.sh/u/{Uri.EscapeDataString(usr)}\n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false); + await Context.Channel.SendFileAsync(ms, $"{usr}.png", $"🎧 **Profile Link:** \n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false); } catch (Exception ex) { diff --git a/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs b/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs index 019f7f20..e70381b2 100644 --- a/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs +++ b/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs @@ -1,102 +1,97 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Attributes; -using NadekoBot.Extensions; -using NadekoBot.Modules.Searches.Models; -using Newtonsoft.Json; -using NLog; -using System.Net.Http; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Searches -{ - public partial class Searches - { - [Group] - public class OverwatchCommands : ModuleBase - { - private readonly Logger _log; - public OverwatchCommands() - { - _log = LogManager.GetCurrentClassLogger(); - } - [NadekoCommand, Usage, Description, Aliases] - public async Task Overwatch(string region, [Remainder] string query = null) - { - if (string.IsNullOrWhiteSpace(query)) - return; - var battletag = Regex.Replace(query, "#", "-", RegexOptions.IgnoreCase); - - await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); - try - { - await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); - var model = await GetProfile(region, battletag); - - var rankimg = $"{model.Competitive.rank_img}"; - var rank = $"{model.Competitive.rank}"; - var competitiveplay = $"{model.Games.Competitive.played}"; - if (string.IsNullOrWhiteSpace(rank)) - { - var embed = new EmbedBuilder() - .WithAuthor(eau => eau.WithName($"{model.username}") - .WithUrl($"https://www.overbuff.com/players/pc/{battletag}") - .WithIconUrl($"{model.avatar}")) - .WithThumbnailUrl("https://cdn.discordapp.com/attachments/155726317222887425/255653487512256512/YZ4w2ey.png") - .AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Current Competitive Wins**").WithValue($"{model.Games.Competitive.wins}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Current Competitive Loses**").WithValue($"{model.Games.Competitive.lost}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Current Competitive Played**").WithValue($"{model.Games.Competitive.played}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Competitive Rank**").WithValue("0").WithIsInline(true)) - .AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true)) - .WithOkColor(); - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); - } - else - { - var embed = new EmbedBuilder() - .WithAuthor(eau => eau.WithName($"{model.username}") - .WithUrl($"https://www.overbuff.com/players/pc/{battletag}") - .WithIconUrl($"{model.avatar}")) - .WithThumbnailUrl(rankimg) - .AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Current Competitive Wins**").WithValue($"{model.Games.Competitive.wins}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Current Competitive Loses**").WithValue($"{model.Games.Competitive.lost}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Current Competitive Played**").WithValue($"{model.Games.Competitive.played}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Competitive Rank**").WithValue(rank).WithIsInline(true)) - .AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true)) - .WithColor(NadekoBot.OkColor); - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); - return; - } - } - catch - { - await Context.Channel.SendErrorAsync("Found no user! Please check the **Region** and **BattleTag** before trying again."); - } - } - public async Task GetProfile(string region, string battletag) - { - try - { - using (var http = new HttpClient()) - { - var Url = await http.GetStringAsync($"https://api.lootbox.eu/pc/{region.ToLower()}/{battletag}/profile"); - var model = JsonConvert.DeserializeObject(Url); - return model.data; - } - } - catch - { - return null; - } - } - - } - } -} +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Modules.Searches.Models; +using Newtonsoft.Json; +using NLog; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + [Group] + public class OverwatchCommands : ModuleBase + { + private readonly Logger _log; + public OverwatchCommands() + { + _log = LogManager.GetCurrentClassLogger(); + } + [NadekoCommand, Usage, Description, Aliases] + public async Task Overwatch(string region, [Remainder] string query = null) + { + if (string.IsNullOrWhiteSpace(query)) + return; + var battletag = Regex.Replace(query, "#", "-", RegexOptions.IgnoreCase); + + await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); + try + { + await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); + var model = await GetProfile(region, battletag); + + var rankimg = $"{model.Competitive.rank_img}"; + var rank = $"{model.Competitive.rank}"; + var competitiveplay = $"{model.Games.Competitive.played}"; + if (string.IsNullOrWhiteSpace(rank)) + { + var embed = new EmbedBuilder() + .WithAuthor(eau => eau.WithName($"{model.username}") + .WithUrl($"https://www.overbuff.com/players/pc/{battletag}") + .WithIconUrl($"{model.avatar}")) + .WithThumbnailUrl("https://cdn.discordapp.com/attachments/155726317222887425/255653487512256512/YZ4w2ey.png") + .AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Competitive Rank**").WithValue("0").WithIsInline(true)) + .AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true)) + .WithOkColor(); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + else + { + var embed = new EmbedBuilder() + .WithAuthor(eau => eau.WithName($"{model.username}") + .WithUrl($"https://www.overbuff.com/players/pc/{battletag}") + .WithIconUrl($"{model.avatar}")) + .WithThumbnailUrl(rankimg) + .AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Competitive Wins**").WithValue($"{model.Games.Competitive.wins}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Competitive Loses**").WithValue($"{model.Games.Competitive.lost}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Competitive Played**").WithValue($"{model.Games.Competitive.played}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Competitive Rank**").WithValue(rank).WithIsInline(true)) + .AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true)) + .WithColor(NadekoBot.OkColor); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + return; + } + } + catch + { + await Context.Channel.SendErrorAsync("Found no user! Please check the **Region** and **BattleTag** before trying again."); + } + } + public async Task GetProfile(string region, string battletag) + { + try + { + using (var http = new HttpClient()) + { + var Url = await http.GetStringAsync($"https://api.lootbox.eu/pc/{region.ToLower()}/{battletag}/profile"); + var model = JsonConvert.DeserializeObject(Url); + return model.data; + } + } + catch + { + return null; + } + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs b/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs index 36829953..3fc7a34b 100644 --- a/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs +++ b/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs @@ -105,7 +105,7 @@ namespace NadekoBot.Modules.Searches var server = NadekoBot.Client.GetGuild(fs.GuildId); if (server == null) return; - var channel = await server.GetTextChannelAsync(fs.ChannelId); + var channel = server.GetTextChannel(fs.ChannelId); if (channel == null) return; try diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index 34ef1074..8abcfee8 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -117,31 +117,49 @@ namespace NadekoBot.Modules.Searches terms = WebUtility.UrlEncode(terms).Replace(' ', '+'); - var fullQueryLink = $"http://imgur.com/search?q={ terms }"; - var config = Configuration.Default.WithDefaultLoader(); - var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); + try + { + var res = await NadekoBot.Google.GetImageAsync(terms).ConfigureAwait(false); + var embed = new EmbedBuilder() + .WithOkColor() + .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50)) + .WithUrl("https://www.google.rs/search?q=" + terms + "&source=lnms&tbm=isch") + .WithIconUrl("http://i.imgur.com/G46fm8J.png")) + .WithDescription(res.Link) + .WithImageUrl(res.Link) + .WithTitle(Context.User.Mention); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + catch + { + _log.Warn("Falling back to Imgur search."); - var elems = document.QuerySelectorAll("a.image-list-link"); + var fullQueryLink = $"http://imgur.com/search?q={ terms }"; + var config = Configuration.Default.WithDefaultLoader(); + var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); - if (!elems.Any()) - return; + var elems = document.QuerySelectorAll("a.image-list-link"); - var img = (elems.FirstOrDefault()?.Children?.FirstOrDefault() as IHtmlImageElement); + if (!elems.Any()) + return; - if (img?.Source == null) - return; + var img = (elems.FirstOrDefault()?.Children?.FirstOrDefault() as IHtmlImageElement); - var source = img.Source.Replace("b.", "."); + if (img?.Source == null) + return; - var embed = new EmbedBuilder() - .WithOkColor() - .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50)) - .WithUrl(fullQueryLink) - .WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?")) - .WithDescription(source) - .WithImageUrl(source) - .WithTitle(Context.User.Mention); - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + var source = img.Source.Replace("b.", "."); + + var embed = new EmbedBuilder() + .WithOkColor() + .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50)) + .WithUrl(fullQueryLink) + .WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?")) + .WithDescription(source) + .WithImageUrl(source) + .WithTitle(Context.User.Mention); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } } [NadekoCommand, Usage, Description, Aliases] @@ -150,34 +168,51 @@ namespace NadekoBot.Modules.Searches terms = terms?.Trim(); if (string.IsNullOrWhiteSpace(terms)) return; - terms = WebUtility.UrlEncode(terms).Replace(' ', '+'); + try + { + var res = await NadekoBot.Google.GetImageAsync(terms, new NadekoRandom().Next(0, 50)).ConfigureAwait(false); + var embed = new EmbedBuilder() + .WithOkColor() + .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50)) + .WithUrl("https://www.google.rs/search?q=" + terms + "&source=lnms&tbm=isch") + .WithIconUrl("http://i.imgur.com/G46fm8J.png")) + .WithDescription(res.Link) + .WithImageUrl(res.Link) + .WithTitle(Context.User.Mention); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + catch + { + _log.Warn("Falling back to Imgur"); + terms = WebUtility.UrlEncode(terms).Replace(' ', '+'); - var fullQueryLink = $"http://imgur.com/search?q={ terms }"; - var config = Configuration.Default.WithDefaultLoader(); - var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); + var fullQueryLink = $"http://imgur.com/search?q={ terms }"; + var config = Configuration.Default.WithDefaultLoader(); + var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); - var elems = document.QuerySelectorAll("a.image-list-link").ToList(); + var elems = document.QuerySelectorAll("a.image-list-link").ToList(); - if (!elems.Any()) - return; + if (!elems.Any()) + return; - var img = (elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Count))?.Children?.FirstOrDefault() as IHtmlImageElement); + var img = (elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Count))?.Children?.FirstOrDefault() as IHtmlImageElement); - if (img?.Source == null) - return; + if (img?.Source == null) + return; - var source = img.Source.Replace("b.", "."); + var source = img.Source.Replace("b.", "."); - var embed = new EmbedBuilder() - .WithOkColor() - .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50)) - .WithUrl(fullQueryLink) - .WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?")) - .WithDescription(source) - .WithImageUrl(source) - .WithTitle(Context.User.Mention); - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + var embed = new EmbedBuilder() + .WithOkColor() + .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50)) + .WithUrl(fullQueryLink) + .WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?")) + .WithDescription(source) + .WithImageUrl(source) + .WithTitle(Context.User.Mention); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } } [NadekoCommand, Usage, Description, Aliases] @@ -260,7 +295,7 @@ namespace NadekoBot.Modules.Searches .WithTitle(Context.User.Mention) .WithFooter(efb => efb.WithText(totalResults)); - var desc = await Task.WhenAll(results.Select(async res => + var desc = await Task.WhenAll(results.Select(async res => $"[{Format.Bold(res?.Title)}]({(await NadekoBot.Google.ShortenUrl(res?.Link))})\n{res?.Text}\n\n")) .ConfigureAwait(false); await Context.Channel.EmbedAsync(embed.WithDescription(String.Concat(desc))).ConfigureAwait(false); @@ -285,10 +320,10 @@ namespace NadekoBot.Modules.Searches .ConfigureAwait(false); try { - var items = JArray.Parse(response).Shuffle().ToList(); - if (items == null) + var items = JArray.Parse(response).ToArray(); + if (items == null || items.Length == 0) throw new KeyNotFoundException("Cannot find a card by that name"); - var item = items[0]; + var item = items[new NadekoRandom().Next(0, items.Length)]; var storeUrl = await NadekoBot.Google.ShortenUrl(item["store_url"].ToString()); var cost = item["cost"].ToString(); var desc = item["text"].ToString(); @@ -344,13 +379,16 @@ namespace NadekoBot.Modules.Searches throw new KeyNotFoundException("Cannot find a card by that name"); foreach (var item in items.Where(item => item.HasValues && item["img"] != null).Take(4)) { - using (var sr = await http.GetStreamAsync(item["img"].ToString())) + await Task.Run(async () => { - var imgStream = new MemoryStream(); - await sr.CopyToAsync(imgStream); - imgStream.Position = 0; - images.Add(new ImageSharp.Image(imgStream)); - } + using (var sr = await http.GetStreamAsync(item["img"].ToString())) + { + var imgStream = new MemoryStream(); + await sr.CopyToAsync(imgStream); + imgStream.Position = 0; + images.Add(new ImageSharp.Image(imgStream)); + } + }).ConfigureAwait(false); } string msg = null; if (items.Count > 4) @@ -358,7 +396,7 @@ namespace NadekoBot.Modules.Searches msg = "⚠ Found over 4 images. Showing random 4."; } var ms = new MemoryStream(); - images.AsEnumerable().Merge().SaveAsPng(ms); + await Task.Run(() => images.AsEnumerable().Merge().SaveAsPng(ms)); ms.Position = 0; await Context.Channel.SendFileAsync(ms, arg + ".png", msg).ConfigureAwait(false); } diff --git a/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs b/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs index decd705f..194ee455 100644 --- a/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs +++ b/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs @@ -50,7 +50,7 @@ namespace NadekoBot.Modules.Utility } private static string GetText(IGuild server, ITextChannel channel, IGuildUser user, IUserMessage message) => - $"**{server.Name} | {channel.Name}** `{user.Username}`: " + message.Content; + $"**{server.Name} | {channel.Name}** `{user.Username}`: " + message.Content.SanitizeMentions(); public static readonly ConcurrentDictionary> Subscribers = new ConcurrentDictionary>(); private static Logger _log { get; } diff --git a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs index 7d57ff8a..b2594d3a 100644 --- a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs @@ -53,7 +53,7 @@ namespace NadekoBot.Modules.Utility .WithColor(NadekoBot.OkColor); if (guild.Emojis.Count() > 0) { - embed.AddField(fb => fb.WithName("**Custom Emojis**").WithValue(string.Join(" ", guild.Emojis.Select(e => $"{e.Name} <:{e.Name}:{e.Id}>")))); + embed.AddField(fb => fb.WithName($"**Custom Emojis ({guild.Emojis.Count})**").WithValue(string.Join(" ", guild.Emojis.Shuffle().Take(25).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>")))); } await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } @@ -97,8 +97,10 @@ namespace NadekoBot.Modules.Utility .AddField(fb => fb.WithName("**Joined Server**").WithValue($"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) .AddField(fb => fb.WithName("**Joined Discord**").WithValue($"{user.CreatedAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) .AddField(fb => fb.WithName("**Roles**").WithValue($"**({user.RoleIds.Count - 1})** - {string.Join("\n", user.GetRoles().Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true)) - .WithThumbnailUrl(user.RealAvatarUrl()) .WithColor(NadekoBot.OkColor); + + if (user.AvatarId != null) + embed.WithThumbnailUrl(user.RealAvatarUrl()); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } } diff --git a/src/NadekoBot/Modules/Utility/Commands/MessageRepeater.cs b/src/NadekoBot/Modules/Utility/Commands/MessageRepeater.cs index 619b71fa..b6efe2fc 100644 --- a/src/NadekoBot/Modules/Utility/Commands/MessageRepeater.cs +++ b/src/NadekoBot/Modules/Utility/Commands/MessageRepeater.cs @@ -40,7 +40,7 @@ namespace NadekoBot.Modules.Utility { _log = LogManager.GetCurrentClassLogger(); this.Repeater = repeater; - this.Channel = channel ?? NadekoBot.Client.GetGuild(repeater.GuildId)?.GetTextChannelAsync(repeater.ChannelId).GetAwaiter().GetResult(); + this.Channel = channel ?? NadekoBot.Client.GetGuild(repeater.GuildId)?.GetTextChannel(repeater.ChannelId); if (Channel == null) return; Task.Run(Run); @@ -69,12 +69,12 @@ namespace NadekoBot.Modules.Utility { oldMsg = await Channel.SendMessageAsync(toSend).ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Forbidden) + catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden) { _log.Warn("Missing permissions. Repeater stopped. ChannelId : {0}", Channel?.Id); return; } - catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound) { _log.Warn("Channel not found. Repeater stopped. ChannelId : {0}", Channel?.Id); return; diff --git a/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs b/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs index 7796571d..8add0707 100644 --- a/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs @@ -48,7 +48,7 @@ namespace NadekoBot.Modules.Utility keyword = keyword.ToUpperInvariant(); Quote quote; - using (var uow = DbHandler.Instance.GetUnitOfWork()) + using (var uow = DbHandler.UnitOfWork()) { quote = await uow.Quotes.GetRandomQuoteByKeywordAsync(Context.Guild.Id, keyword).ConfigureAwait(false); } @@ -93,24 +93,31 @@ namespace NadekoBot.Modules.Utility var isAdmin = ((IGuildUser)Context.Message.Author).GuildPermissions.Administrator; keyword = keyword.ToUpperInvariant(); + var sucess = false; string response; using (var uow = DbHandler.UnitOfWork()) { - var qs = uow.Quotes.GetAllQuotesByKeyword(Context.Guild.Id, keyword); + var qs = uow.Quotes.GetAllQuotesByKeyword(Context.Guild.Id, keyword)?.Where(elem => isAdmin || elem.AuthorId == Context.Message.Author.Id).ToArray(); if (qs == null || !qs.Any()) { - await Context.Channel.SendErrorAsync("No quotes found.").ConfigureAwait(false); - return; + sucess = false; + response = "No quotes found which you can remove."; } + else + { + var q = qs[new NadekoRandom().Next(0, qs.Length)]; - var q = qs.Shuffle().FirstOrDefault(elem => isAdmin || elem.AuthorId == Context.Message.Author.Id); - - uow.Quotes.Remove(q); - await uow.CompleteAsync().ConfigureAwait(false); - response = "🗑 **Deleted a random quote.**"; + uow.Quotes.Remove(q); + await uow.CompleteAsync().ConfigureAwait(false); + sucess = true; + response = "🗑 **Deleted a random quote.**"; + } } - await Context.Channel.SendConfirmAsync(response); + if(sucess) + await Context.Channel.SendConfirmAsync(response); + else + await Context.Channel.SendErrorAsync(response); } [NadekoCommand, Usage, Description, Aliases] @@ -126,7 +133,7 @@ namespace NadekoBot.Modules.Utility using (var uow = DbHandler.UnitOfWork()) { var quotes = uow.Quotes.GetAllQuotesByKeyword(Context.Guild.Id, keyword); - + //todo kwoth please don't be complete retard uow.Quotes.RemoveRange(quotes.ToArray());//wtf?! await uow.CompleteAsync(); diff --git a/src/NadekoBot/Modules/Utility/Commands/Remind.cs b/src/NadekoBot/Modules/Utility/Commands/Remind.cs index 7d371c22..94521dd6 100644 --- a/src/NadekoBot/Modules/Utility/Commands/Remind.cs +++ b/src/NadekoBot/Modules/Utility/Commands/Remind.cs @@ -68,9 +68,7 @@ namespace NadekoBot.Modules.Utility } else { - var t = NadekoBot.Client.GetGuild(r.ServerId)?.GetTextChannelAsync(r.ChannelId).ConfigureAwait(false); - if (t != null) - ch = await t.Value; + ch = NadekoBot.Client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId); } if (ch == null) return; diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index ef075c78..1c1af1e0 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -15,6 +15,8 @@ using System.Threading; using ImageSharp; using System.Collections.Generic; using Newtonsoft.Json; +using Discord.WebSocket; +using NadekoBot.Services; namespace NadekoBot.Modules.Utility { @@ -25,6 +27,7 @@ namespace NadekoBot.Modules.Utility [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.ManageRoles)] [OwnerOnly] public async Task RotateRoleColor(int timeout, IRole role, params string[] hexes) { @@ -112,18 +115,29 @@ namespace NadekoBot.Modules.Utility game = game.Trim().ToUpperInvariant(); if (string.IsNullOrWhiteSpace(game)) return; - var arr = (await (Context.Channel as IGuildChannel).Guild.GetUsersAsync()) + + var socketGuild = Context.Guild as SocketGuild; + if (socketGuild == null) { + _log.Warn("Can't cast guild to socket guild."); + return; + } + var rng = new NadekoRandom(); + var arr = await Task.Run(() => socketGuild.Users .Where(u => u.Game?.Name?.ToUpperInvariant() == game) .Select(u => u.Username) - .ToList(); + .OrderBy(x => rng.Next()) + .Take(60) + .ToArray()).ConfigureAwait(false); int i = 0; - if (!arr.Any()) + if (arr.Length == 0) await Context.Channel.SendErrorAsync("Nobody is playing that game.").ConfigureAwait(false); else + { await Context.Channel.SendConfirmAsync("```css\n" + string.Join("\n", arr.GroupBy(item => (i++) / 2) .Select(ig => string.Concat(ig.Select(el => $"• {el,-27}")))) + "\n```") .ConfigureAwait(false); + } } [NadekoCommand, Usage, Description, Aliases] @@ -344,4 +358,4 @@ namespace NadekoBot.Modules.Utility await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream().ConfigureAwait(false), title, title).ConfigureAwait(false); } } -} \ No newline at end of file +} diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index 2e4d3daf..02c60720 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -75,7 +75,8 @@ namespace NadekoBot //initialize Services CommandService = new CommandService(new CommandServiceConfig() { - CaseSensitiveCommands = false + CaseSensitiveCommands = false, + DefaultRunMode = RunMode.Sync }); Google = new GoogleApiService(); CommandHandler = new CommandHandler(Client, CommandService); diff --git a/src/NadekoBot/Resources/CommandStrings.Designer.cs b/src/NadekoBot/Resources/CommandStrings.Designer.cs index 5fe6ae20..e89ba368 100644 --- a/src/NadekoBot/Resources/CommandStrings.Designer.cs +++ b/src/NadekoBot/Resources/CommandStrings.Designer.cs @@ -177,7 +177,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%. + /// Looks up a localized string similar to Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %time%,%shardid%,%shardcount%, %shardguilds%. /// public static string addplaying_desc { get { @@ -879,7 +879,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to Bet to guess will the result be heads or tails. Guessing awards you 1.8x the currency you've bet.. + /// Looks up a localized string similar to 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.. /// public static string betflip_desc { get { @@ -2489,6 +2489,33 @@ namespace NadekoBot.Resources { } } + /// + /// Looks up a localized string similar to divorce. + /// + public static string divorce_cmd { + get { + return ResourceManager.GetString("divorce_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 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.. + /// + public static string divorce_desc { + get { + return ResourceManager.GetString("divorce_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}divorce @CheatingSloot`. + /// + public static string divorce_usage { + get { + return ResourceManager.GetString("divorce_usage", resourceCulture); + } + } + /// /// Looks up a localized string similar to donadd. /// @@ -4379,6 +4406,33 @@ namespace NadekoBot.Resources { } } + /// + /// Looks up a localized string similar to mal. + /// + public static string mal_cmd { + get { + return ResourceManager.GetString("mal_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows basic info from myanimelist profile.. + /// + public static string mal_desc { + get { + return ResourceManager.GetString("mal_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}mal straysocks`. + /// + public static string mal_usage { + get { + return ResourceManager.GetString("mal_usage", resourceCulture); + } + } + /// /// Looks up a localized string similar to manga mang mq. /// @@ -5091,7 +5145,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to Spend a unit of currency to plant it in this channel. (If bot is restarted or crashes, the currency will be lost). + /// Looks up a localized string similar to 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). /// public static string plant_desc { get { @@ -5100,7 +5154,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to `{0}plant`. + /// Looks up a localized string similar to `{0}plant` or `{0}plant 5`. /// public static string plant_usage { get { @@ -6728,6 +6782,33 @@ namespace NadekoBot.Resources { } } + /// + /// Looks up a localized string similar to setmusicchannel smch. + /// + public static string setmusicchannel_cmd { + get { + return ResourceManager.GetString("setmusicchannel_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 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.. + /// + public static string setmusicchannel_desc { + get { + return ResourceManager.GetString("setmusicchannel_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}smch`. + /// + public static string setmusicchannel_usage { + get { + return ResourceManager.GetString("setmusicchannel_usage", resourceCulture); + } + } + /// /// Looks up a localized string similar to setmuterole. /// @@ -8286,7 +8367,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to `{0}voice+text`. + /// Looks up a localized string similar to `{0}v+t`. /// public static string voiceplustext_usage { get { @@ -8375,6 +8456,114 @@ namespace NadekoBot.Resources { } } + /// + /// Looks up a localized string similar to claimwaifu claim. + /// + public static string waifuclaim_cmd { + get { + return ResourceManager.GetString("waifuclaim_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Claim a waifu for yourself by spending currency. You must spend atleast 10% more than her current value unless she set `{0}affinity` towards you.. + /// + public static string waifuclaim_desc { + get { + return ResourceManager.GetString("waifuclaim_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}claim 50 @Himesama`. + /// + public static string waifuclaim_usage { + get { + return ResourceManager.GetString("waifuclaim_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to affinity. + /// + public static string waifuclaimeraffinity_cmd { + get { + return ResourceManager.GetString("waifuclaimeraffinity_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 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.. + /// + public static string waifuclaimeraffinity_desc { + get { + return ResourceManager.GetString("waifuclaimeraffinity_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}affinity @MyHusband` or `{0}affinity`. + /// + public static string waifuclaimeraffinity_usage { + get { + return ResourceManager.GetString("waifuclaimeraffinity_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to waifuinfo waifustats. + /// + public static string waifuinfo_cmd { + get { + return ResourceManager.GetString("waifuinfo_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows waifu stats for a target person. Defaults to you if no user is provided.. + /// + public static string waifuinfo_desc { + get { + return ResourceManager.GetString("waifuinfo_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}waifuinfo @MyCrush` or `{0}waifuinfo`. + /// + public static string waifuinfo_usage { + get { + return ResourceManager.GetString("waifuinfo_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to waifus waifulb. + /// + public static string waifuleaderboard_cmd { + get { + return ResourceManager.GetString("waifuleaderboard_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows top 9 waifus.. + /// + public static string waifuleaderboard_desc { + get { + return ResourceManager.GetString("waifuleaderboard_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}waifus`. + /// + public static string waifuleaderboard_usage { + get { + return ResourceManager.GetString("waifuleaderboard_usage", resourceCulture); + } + } + /// /// Looks up a localized string similar to weather we. /// diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index 460d6b7c..7ee5714b 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -292,7 +292,7 @@ addplaying adpl - Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued% + Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %time%,%shardid%,%shardcount%, %shardguilds% `{0}adpl` @@ -340,7 +340,7 @@ 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. - `{0}voice+text` + `{0}v+t` scsc @@ -1183,7 +1183,7 @@ betflip bf - Bet to guess will the result be heads or tails. Guessing awards you 1.8x the currency you've bet. + 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. `{0}bf 5 heads` or `{0}bf 3 t` @@ -1372,10 +1372,10 @@ plant - Spend a unit of currency to plant it in this channel. (If bot is restarted or crashes, the currency will be lost) + 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) - `{0}plant` + `{0}plant` or `{0}plant 5` gencurrency gc @@ -2979,4 +2979,67 @@ `{0}slot 5` + + affinity + + + 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. + + + `{0}affinity @MyHusband` or `{0}affinity` + + + claimwaifu claim + + + Claim a waifu for yourself by spending currency. You must spend atleast 10% more than her current value unless she set `{0}affinity` towards you. + + + `{0}claim 50 @Himesama` + + + waifus waifulb + + + Shows top 9 waifus. + + + `{0}waifus` + + + divorce + + + Releases your claim on a specific waifu. You will get some of the money you've spent back unless that waifu has an affinity towards you. 6 hours cooldown. + + + `{0}divorce @CheatingSloot` + + + waifuinfo waifustats + + + Shows waifu stats for a target person. Defaults to you if no user is provided. + + + `{0}waifuinfo @MyCrush` or `{0}waifuinfo` + + + mal + + + Shows basic info from myanimelist profile. + + + `{0}mal straysocks` + + + 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. + + + `{0}smch` + \ No newline at end of file diff --git a/src/NadekoBot/Services/CleverBotApi/ChatterBotFactory.cs b/src/NadekoBot/Services/CleverBotApi/ChatterBotFactory.cs index 08269207..d5df8dfe 100644 --- a/src/NadekoBot/Services/CleverBotApi/ChatterBotFactory.cs +++ b/src/NadekoBot/Services/CleverBotApi/ChatterBotFactory.cs @@ -32,7 +32,7 @@ namespace Services.CleverBotApi #if GLOBAL_NADEKO var url = "http://www.cleverbot.com/webservicemin?uc=3210&botapi=nadekobot"; #else - var url = "http://www.cleverbot.com/webservicemin?uc=3210"; + var url = "http://www.cleverbot.com/webservicemin?uc=3210&botapi=chatterbotapi"; #endif switch (type) diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index 13c8bac5..09080f12 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -159,8 +159,8 @@ namespace NadekoBot.Services private async Task WordFiltered(IGuild guild, SocketUserMessage usrMsg) { - var filteredChannelWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id); - var filteredServerWords = Permissions.FilterCommands.FilteredWordsForServer(guild.Id); + var filteredChannelWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id) ?? new ConcurrentHashSet(); + var filteredServerWords = Permissions.FilterCommands.FilteredWordsForServer(guild.Id) ?? new ConcurrentHashSet(); var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' '); if (filteredChannelWords.Count != 0 || filteredServerWords.Count != 0) { @@ -197,8 +197,10 @@ namespace NadekoBot.Services if (usrMsg == null) //has to be an user message, not system/other messages. return; +#if !GLOBAL_NADEKO // track how many messagges each user is sending UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old); +#endif var channel = msg.Channel as SocketTextChannel; var guild = channel?.Guild; @@ -215,19 +217,20 @@ namespace NadekoBot.Services if (IsBlacklisted(guild, usrMsg)) return; - var cleverBotRan = await TryRunCleverbot(usrMsg, guild).ConfigureAwait(false); + var cleverBotRan = await Task.Run(() => TryRunCleverbot(usrMsg, guild)).ConfigureAwait(false); if (cleverBotRan) return; // maybe this message is a custom reaction - var crExecuted = await CustomReactions.TryExecuteCustomReaction(usrMsg).ConfigureAwait(false); + // todo log custom reaction executions. return struct with info + var crExecuted = await Task.Run(() => CustomReactions.TryExecuteCustomReaction(usrMsg)).ConfigureAwait(false); if (crExecuted) //if it was, don't execute the command return; string messageContent = usrMsg.Content; // execute the command and measure the time it took - var exec = await ExecuteCommand(new CommandContext(_client, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best); + var exec = await Task.Run(() => ExecuteCommand(new CommandContext(_client, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best)).ConfigureAwait(false); execTime = Environment.TickCount - execTime; if (exec.Result.IsSuccess) diff --git a/src/NadekoBot/Services/CurrencyHandler.cs b/src/NadekoBot/Services/CurrencyHandler.cs index f2d699a0..7e2939e4 100644 --- a/src/NadekoBot/Services/CurrencyHandler.cs +++ b/src/NadekoBot/Services/CurrencyHandler.cs @@ -4,6 +4,7 @@ using Discord; using NadekoBot.Extensions; using NadekoBot.Modules.Gambling; using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Database; namespace NadekoBot.Services { @@ -19,26 +20,36 @@ namespace NadekoBot.Services return success; } - public static async Task RemoveCurrencyAsync(ulong authorId, string reason, long amount) + public static async Task RemoveCurrencyAsync(ulong authorId, string reason, long amount, IUnitOfWork uow = null) { if (amount < 0) throw new ArgumentNullException(nameof(amount)); - using (var uow = DbHandler.UnitOfWork()) + if (uow == null) { - var success = uow.Currency.TryUpdateState(authorId, -amount); - if (!success) - return false; - uow.CurrencyTransactions.Add(new CurrencyTransaction() + using (uow = DbHandler.UnitOfWork()) { - UserId = authorId, - Reason = reason, - Amount = -amount, - }); - await uow.CompleteAsync().ConfigureAwait(false); + var toReturn = InternalRemoveCurrency(authorId, reason, amount, uow); + await uow.CompleteAsync().ConfigureAwait(false); + return toReturn; + } } + return InternalRemoveCurrency(authorId, reason, amount, uow); + } + + private static bool InternalRemoveCurrency(ulong authorId, string reason, long amount, IUnitOfWork uow) + { + var success = uow.Currency.TryUpdateState(authorId, -amount); + if (!success) + return false; + uow.CurrencyTransactions.Add(new CurrencyTransaction() + { + UserId = authorId, + Reason = reason, + Amount = -amount, + }); return true; } @@ -50,22 +61,29 @@ namespace NadekoBot.Services try { await author.SendConfirmAsync($"`You received:` {amount} {NadekoBot.BotConfig.CurrencySign}\n`Reason:` {reason}").ConfigureAwait(false); } catch { } } - public static async Task AddCurrencyAsync(ulong receiverId, string reason, long amount) + public static async Task AddCurrencyAsync(ulong receiverId, string reason, long amount, IUnitOfWork uow = null) { if (amount < 0) throw new ArgumentNullException(nameof(amount)); + var transaction = new CurrencyTransaction() + { + UserId = receiverId, + Reason = reason, + Amount = amount, + }; - using (var uow = DbHandler.UnitOfWork()) + if (uow == null) + using (uow = DbHandler.UnitOfWork()) + { + uow.Currency.TryUpdateState(receiverId, amount); + uow.CurrencyTransactions.Add(transaction); + await uow.CompleteAsync(); + } + else { uow.Currency.TryUpdateState(receiverId, amount); - uow.CurrencyTransactions.Add(new CurrencyTransaction() - { - UserId = receiverId, - Reason = reason, - Amount = amount, - }); - await uow.CompleteAsync(); + uow.CurrencyTransactions.Add(transaction); } } } diff --git a/src/NadekoBot/Services/Database/IUnitOfWork.cs b/src/NadekoBot/Services/Database/IUnitOfWork.cs index f17e1f6a..c1c9479e 100644 --- a/src/NadekoBot/Services/Database/IUnitOfWork.cs +++ b/src/NadekoBot/Services/Database/IUnitOfWork.cs @@ -21,6 +21,8 @@ namespace NadekoBot.Services.Database ICurrencyTransactionsRepository CurrencyTransactions { get; } IMusicPlaylistRepository MusicPlaylists { get; } IPokeGameRepository PokeGame { get; } + IWaifuRepository Waifus { get; } + IDiscordUserRepository DiscordUsers { get; } int Complete(); Task CompleteAsync(); diff --git a/src/NadekoBot/Services/Database/Models/BotConfig.cs b/src/NadekoBot/Services/Database/Models/BotConfig.cs index 2aa973f6..d96d4cc8 100644 --- a/src/NadekoBot/Services/Database/Models/BotConfig.cs +++ b/src/NadekoBot/Services/Database/Models/BotConfig.cs @@ -29,7 +29,7 @@ namespace NadekoBot.Services.Database.Models public float BetflipMultiplier { get; set; } = 1.95f; public int CurrencyDropAmount { get; set; } = 1; public float Betroll67Multiplier { get; set; } = 2; - public float Betroll91Multiplier { get; set; } = 3; + public float Betroll91Multiplier { get; set; } = 4; public float Betroll100Multiplier { get; set; } = 10; //public HashSet CommandCosts { get; set; } = new HashSet(); diff --git a/src/NadekoBot/Services/Database/Models/DiscordUser.cs b/src/NadekoBot/Services/Database/Models/DiscordUser.cs new file mode 100644 index 00000000..608daf81 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/DiscordUser.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Models +{ + public class DiscordUser : DbEntity + { + public ulong UserId { get; set; } + public string Username { get; set; } + public string Discriminator { get; set; } + public string AvatarId { get; set; } + + public override string ToString() => + Username + "#" + Discriminator; + } +} diff --git a/src/NadekoBot/Services/Database/Models/Waifu.cs b/src/NadekoBot/Services/Database/Models/Waifu.cs new file mode 100644 index 00000000..947bf516 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/Waifu.cs @@ -0,0 +1,49 @@ +using NadekoBot.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Models +{ + public class WaifuInfo : DbEntity + { + public int WaifuId { get; set; } + public DiscordUser Waifu { get; set; } + + public int? ClaimerId { get; set; } + public DiscordUser Claimer { get; set; } + + public int? AffinityId { get; set; } + public DiscordUser Affinity { get; set; } + + public int Price { get; set; } + + public override string ToString() + { + var claimer = "no one"; + var status = ""; + + var waifuUsername = Waifu.Username.TrimTo(20); + var claimerUsername = Claimer?.Username.TrimTo(20); + + if (Claimer != null) + { + claimer = $"{ claimerUsername }#{Claimer.Discriminator}"; + } + if (AffinityId == null) + { + status = $"... but {waifuUsername}'s heart is empty"; + } + else if (AffinityId == ClaimerId) + { + status = $"... and {waifuUsername} likes {claimerUsername} too <3"; + } + else { + status = $"... but {waifuUsername}'s heart belongs to {Affinity.Username.TrimTo(20)}#{Affinity.Discriminator}"; + } + return $"**{waifuUsername}#{Waifu.Discriminator}** - claimed by **{claimer}**\n\t{status}"; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Database/Models/WaifuUpdate.cs b/src/NadekoBot/Services/Database/Models/WaifuUpdate.cs new file mode 100644 index 00000000..1c48826e --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/WaifuUpdate.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Models +{ + public class WaifuUpdate : DbEntity + { + public int UserId { get; set; } + public DiscordUser User { get; set; } + public WaifuUpdateType UpdateType { get; set; } + + public int? OldId { get; set; } + public DiscordUser Old { get; set; } + + public int? NewId { get; set; } + public DiscordUser New { get; set; } + } + + public enum WaifuUpdateType + { + AffinityChanged, + Claimed + } +} diff --git a/src/NadekoBot/Services/Database/NadekoContext.cs b/src/NadekoBot/Services/Database/NadekoContext.cs index b49439f6..102c6703 100644 --- a/src/NadekoBot/Services/Database/NadekoContext.cs +++ b/src/NadekoBot/Services/Database/NadekoContext.cs @@ -3,9 +3,26 @@ using System.Collections.Generic; using System.Linq; using NadekoBot.Services.Database.Models; using NadekoBot.Extensions; +using Microsoft.EntityFrameworkCore.Infrastructure; namespace NadekoBot.Services.Database { + + public class NadekoContextFactory : IDbContextFactory + { + /// + /// :\ Used for migrations + /// + /// + /// + public NadekoContext Create(DbContextFactoryOptions options) + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db"); + return new NadekoContext(optionsBuilder.Options); + } + } + public class NadekoContext : DbContext { public DbSet Quotes { get; set; } @@ -22,6 +39,7 @@ namespace NadekoBot.Services.Database public DbSet CustomReactions { get; set; } public DbSet CurrencyTransactions { get; set; } public DbSet PokeGame { get; set; } + public DbSet WaifuUpdates { get; set; } //logging public DbSet LogSettings { get; set; } @@ -33,23 +51,15 @@ namespace NadekoBot.Services.Database public DbSet RaceAnimals { get; set; } public DbSet ModulePrefixes { get; set; } - public NadekoContext() + public NadekoContext() : base() { - this.Database.Migrate(); + } public NadekoContext(DbContextOptions options) : base(options) { - this.Database.Migrate(); - EnsureSeedData(); } - ////Uncomment this to db initialisation with dotnet ef migration add [module] - //protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - //{ - // optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db"); - //} - public void EnsureSeedData() { if (!BotConfig.Any()) @@ -244,6 +254,24 @@ namespace NadekoBot.Services.Database // .HasIndex(cp => cp.CommandName) // .IsUnique(); #endregion + + #region Waifus + + var wi = modelBuilder.Entity(); + wi.HasOne(x => x.Waifu) + .WithOne(); + // //.HasForeignKey(w => w.WaifuId) + // //.IsRequired(true); + + //wi.HasOne(x => x.Claimer) + // .WithOne(); + // //.HasForeignKey(w => w.ClaimerId) + // //.IsRequired(false); + + var du = modelBuilder.Entity(); + du.HasAlternateKey(w => w.UserId); + + #endregion } } } diff --git a/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs b/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs new file mode 100644 index 00000000..c80608dd --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs @@ -0,0 +1,15 @@ +using Discord; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IDiscordUserRepository : IRepository + { + DiscordUser GetOrCreate(IUser original); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IWaifuRepository.cs b/src/NadekoBot/Services/Database/Repositories/IWaifuRepository.cs new file mode 100644 index 00000000..2f2dc0f6 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IWaifuRepository.cs @@ -0,0 +1,13 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IWaifuRepository : IRepository + { + IList GetTop(int count); + WaifuInfo ByWaifuUserId(ulong userId); + IList ByClaimerUserId(ulong userId); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs new file mode 100644 index 00000000..9425f049 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs @@ -0,0 +1,36 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Discord; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class DiscordUserRepository : Repository, IDiscordUserRepository + { + public DiscordUserRepository(DbContext context) : base(context) + { + } + + public DiscordUser GetOrCreate(IUser original) + { + DiscordUser toReturn; + + toReturn = _set.FirstOrDefault(u => u.UserId == original.Id); + + if (toReturn == null) + _set.Add(toReturn = new DiscordUser() + { + AvatarId = original.AvatarId, + Discriminator = original.Discriminator, + UserId = original.Id, + Username = original.Username, + }); + + return toReturn; + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/WaifuRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/WaifuRepository.cs new file mode 100644 index 00000000..8a973202 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/WaifuRepository.cs @@ -0,0 +1,49 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class WaifuRepository : Repository, IWaifuRepository + { + public WaifuRepository(DbContext context) : base(context) + { + } + + public WaifuInfo ByWaifuUserId(ulong userId) + { + return _set.Include(wi => wi.Waifu) + .Include(wi => wi.Affinity) + .Include(wi => wi.Claimer) + .FirstOrDefault(wi => wi.Waifu.UserId == userId); + } + + public IList ByClaimerUserId(ulong userId) + { + return _set.Include(wi => wi.Waifu) + .Include(wi => wi.Affinity) + .Include(wi => wi.Claimer) + .Where(wi => wi.Claimer != null && wi.Claimer.UserId == userId) + .ToList(); + } + + public IList GetTop(int count) + { + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count)); + if (count == 0) + return new List(); + + return _set.Include(wi => wi.Waifu) + .Include(wi => wi.Affinity) + .Include(wi => wi.Claimer) + .OrderByDescending(wi => wi.Price) + .Take(count) + .ToList(); + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Database/UnitOfWork.cs b/src/NadekoBot/Services/Database/UnitOfWork.cs index 88231d6b..032c0bb7 100644 --- a/src/NadekoBot/Services/Database/UnitOfWork.cs +++ b/src/NadekoBot/Services/Database/UnitOfWork.cs @@ -48,6 +48,12 @@ namespace NadekoBot.Services.Database private IPokeGameRepository _pokegame; public IPokeGameRepository PokeGame => _pokegame ?? (_pokegame = new PokeGameRepository(_context)); + private IWaifuRepository _waifus; + public IWaifuRepository Waifus => _waifus ?? (_waifus = new WaifuRepository(_context)); + + private IDiscordUserRepository _discordUsers; + public IDiscordUserRepository DiscordUsers => _discordUsers ?? (_discordUsers = new DiscordUserRepository(_context)); + public UnitOfWork(NadekoContext context) { _context = context; diff --git a/src/NadekoBot/Services/DbHandler.cs b/src/NadekoBot/Services/DbHandler.cs index 03d12a94..9a4ab212 100644 --- a/src/NadekoBot/Services/DbHandler.cs +++ b/src/NadekoBot/Services/DbHandler.cs @@ -1,4 +1,6 @@ -using Microsoft.EntityFrameworkCore; +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; using NadekoBot.Services.Database; namespace NadekoBot.Services @@ -13,7 +15,8 @@ namespace NadekoBot.Services static DbHandler() { } - private DbHandler() { + private DbHandler() + { connectionString = NadekoBot.Credentials.Db.ConnectionString; var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite(NadekoBot.Credentials.Db.ConnectionString); @@ -32,13 +35,19 @@ namespace NadekoBot.Services //} } - public NadekoContext GetDbContext() => - new NadekoContext(options); + public NadekoContext GetDbContext() + { + var context = new NadekoContext(options); + context.Database.Migrate(); + context.EnsureSeedData(); - public IUnitOfWork GetUnitOfWork() => + return context; + } + + private IUnitOfWork GetUnitOfWork() => new UnitOfWork(GetDbContext()); public static IUnitOfWork UnitOfWork() => DbHandler.Instance.GetUnitOfWork(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/GoogleApiService.cs b/src/NadekoBot/Services/Impl/GoogleApiService.cs index 7da7ad7b..187bf272 100644 --- a/src/NadekoBot/Services/Impl/GoogleApiService.cs +++ b/src/NadekoBot/Services/Impl/GoogleApiService.cs @@ -8,13 +8,19 @@ using System.Text.RegularExpressions; using Google.Apis.Urlshortener.v1; using Google.Apis.Urlshortener.v1.Data; using NLog; +using Google.Apis.Customsearch.v1; +using Google.Apis.Customsearch.v1.Data; namespace NadekoBot.Services.Impl { public class GoogleApiService : IGoogleApiService { + const string search_engine_id = "018084019232060951019:hs5piey28-e"; + private YouTubeService yt; private UrlshortenerService sh; + private CustomsearchService cs; + private Logger _log { get; } public GoogleApiService() @@ -22,13 +28,14 @@ namespace NadekoBot.Services.Impl var bcs = new BaseClientService.Initializer { ApplicationName = "Nadeko Bot", - ApiKey = NadekoBot.Credentials.GoogleApiKey + ApiKey = NadekoBot.Credentials.GoogleApiKey, }; _log = LogManager.GetCurrentClassLogger(); yt = new YouTubeService(bcs); sh = new UrlshortenerService(bcs); + cs = new CustomsearchService(bcs); } public async Task> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1) { @@ -150,7 +157,7 @@ namespace NadekoBot.Services.Impl return toReturn; } //todo AsyncEnumerable - public async Task> GetVideoDurationsAsync(IEnumerable videoIds) + public async Task> GetVideoDurationsAsync(IEnumerable videoIds) { var videoIdsList = videoIds as List ?? videoIds.ToList(); @@ -179,5 +186,34 @@ namespace NadekoBot.Services.Impl return toReturn; } + + public struct ImageResult + { + public Result.ImageData Image { get; } + public string Link { get; } + + public ImageResult(Result.ImageData image, string link) + { + this.Image = image; + this.Link = link; + } + } + + public async Task GetImageAsync(string query, int start = 1) + { + if (string.IsNullOrWhiteSpace(query)) + throw new ArgumentNullException(nameof(query)); + + var req = cs.Cse.List(query); + req.Cx = search_engine_id; + req.Num = 1; + req.Fields = "items(image(contextLink,thumbnailLink),link)"; + req.SearchType = CseResource.ListRequest.SearchTypeEnum.Image; + req.Start = start; + + var search = await req.ExecuteAsync().ConfigureAwait(false); + + return new ImageResult(search.Items[0].Image, search.Items[0].Link); + } } } diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index 0a170399..ab72769d 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -15,7 +15,7 @@ namespace NadekoBot.Services.Impl private DiscordShardedClient client; private DateTime started; - public const string BotVersion = "1.1.3"; + public const string BotVersion = "1.1.5a"; public string Author => "Kwoth#2560"; public string Library => "Discord.Net"; diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index fc50058f..56479a6f 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -103,6 +103,9 @@ 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) => diff --git a/src/NadekoBot/_libs/32/libsodium.dll b/src/NadekoBot/_libs/32/libsodium.dll new file mode 100644 index 00000000..a9ab5078 Binary files /dev/null and b/src/NadekoBot/_libs/32/libsodium.dll differ diff --git a/src/NadekoBot/_libs/32/opus.dll b/src/NadekoBot/_libs/32/opus.dll new file mode 100644 index 00000000..f867793e Binary files /dev/null and b/src/NadekoBot/_libs/32/opus.dll differ diff --git a/src/NadekoBot/_libs/64/libsodium.dll b/src/NadekoBot/_libs/64/libsodium.dll new file mode 100644 index 00000000..f5de830e Binary files /dev/null and b/src/NadekoBot/_libs/64/libsodium.dll differ diff --git a/src/NadekoBot/_libs/64/opus.dll b/src/NadekoBot/_libs/64/opus.dll new file mode 100644 index 00000000..a962869f Binary files /dev/null and b/src/NadekoBot/_libs/64/opus.dll differ diff --git a/src/NadekoBot/data/typing_articles.json b/src/NadekoBot/data/typing_articles.json index 1a4581fe..ab34341c 100644 --- a/src/NadekoBot/data/typing_articles.json +++ b/src/NadekoBot/data/typing_articles.json @@ -237,7 +237,7 @@ }, { "Title":"Careers in Psychology: Opportunities in a Changing World", - "Text":"This text addresses the growing need among students and faculty for information about the careers available in psychology at the bachelor’s and graduate level." + "Text":"This text addresses the growing need among students and faculty for information about the careers available in psychology at the bachelors and graduate level." }, { "Title":"Philosophy of Psychology", diff --git a/src/NadekoBot/project.json b/src/NadekoBot/project.json index 17dc48f1..a88058ea 100644 --- a/src/NadekoBot/project.json +++ b/src/NadekoBot/project.json @@ -18,7 +18,7 @@ }, "dependencies": { "AngleSharp": "0.9.9", - "VideoLibrary": "1.3.4", + "libvideo": "1.0.0", "CoreCLR-NCalc": "2.1.2", "Google.Apis.Urlshortener.v1": "1.19.0.138", "Google.Apis.YouTube.v3": "1.20.0.701",