From 82000c97a4c4b30f04fc222a7dfbcc73b8ef3974 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 29 Dec 2021 06:07:16 +0100 Subject: [PATCH] Applied codestyle to all .cs files --- src/NadekoBot/.editorconfig | 2 +- src/NadekoBot/Bot.cs | 156 ++--- src/NadekoBot/Common/AddRemove.cs | 4 +- src/NadekoBot/Common/AsyncLazy.cs | 4 +- src/NadekoBot/Common/Attributes/Aliases.cs | 2 +- .../Attributes/CommandNameLoadHelper.cs | 7 +- .../Common/Attributes/NadekoCommand.cs | 8 +- .../Attributes/NadekoModuleAttribute.cs | 2 +- .../Common/Attributes/NadekoOptions.cs | 4 +- .../Common/Attributes/OwnerOnlyAttribute.cs | 5 +- src/NadekoBot/Common/Attributes/Ratelimit.cs | 2 +- src/NadekoBot/Common/Attributes/UserPerm.cs | 22 +- src/NadekoBot/Common/CmdStrings.cs | 4 +- .../Common/Collections/ConcurrentHashSet.cs | 628 +++++++++--------- .../Common/Collections/IndexedCollection.cs | 73 +- src/NadekoBot/Common/CommandData.cs | 4 +- src/NadekoBot/Common/Configs/BotConfig.cs | 53 +- src/NadekoBot/Common/Configs/IConfigSeria.cs | 12 +- src/NadekoBot/Common/Creds.cs | 106 ++- src/NadekoBot/Common/DownloadTracker.cs | 7 +- src/NadekoBot/Common/Helpers.cs | 4 +- src/NadekoBot/Common/IBotCredentials.cs | 4 +- src/NadekoBot/Common/ICloneable.cs | 4 +- src/NadekoBot/Common/IEmbedBuilder.cs | 6 +- src/NadekoBot/Common/INadekoCommandOptions.cs | 4 +- src/NadekoBot/Common/IPlaceholderProvider.cs | 2 +- src/NadekoBot/Common/ImageUrls.cs | 4 +- .../JsonConverters/CultureInfoConverter.cs | 2 +- .../Common/JsonConverters/Rgba32Converter.cs | 2 +- src/NadekoBot/Common/Kwum.cs | 14 +- src/NadekoBot/Common/LbOpts.cs | 10 +- src/NadekoBot/Common/LoginErrorHandler.cs | 26 +- .../Common/ModuleBehaviors/IEarlyBehavior.cs | 4 +- .../ModuleBehaviors/IInputTransformer.cs | 2 +- .../Common/ModuleBehaviors/ILateBlocker.cs | 2 +- .../Common/ModuleBehaviors/ILateExecutor.cs | 4 +- .../Common/ModuleBehaviors/IReadyExecutor.cs | 8 +- src/NadekoBot/Common/NadekoModule.cs | 63 +- src/NadekoBot/Common/NadekoRandom.cs | 5 +- .../Common/NoPublicBotPrecondition.cs | 9 +- src/NadekoBot/Common/OldImageUrls.cs | 2 +- src/NadekoBot/Common/OptionsParser.cs | 11 +- src/NadekoBot/Common/OsuMapData.cs | 4 +- src/NadekoBot/Common/OsuUserBets.cs | 4 +- src/NadekoBot/Common/PlatformHelper.cs | 7 +- src/NadekoBot/Common/Pokemon/PokemonNameId.cs | 4 +- src/NadekoBot/Common/Pokemon/SearchPokemon.cs | 32 +- .../Common/Pokemon/SearchPokemonAbility.cs | 2 +- src/NadekoBot/Common/PubSub/EventPubSub.cs | 16 +- src/NadekoBot/Common/PubSub/IPubSub.cs | 2 +- src/NadekoBot/Common/PubSub/ISeria.cs | 2 +- src/NadekoBot/Common/PubSub/JsonSeria.cs | 6 +- src/NadekoBot/Common/PubSub/RedisPubSub.cs | 6 +- src/NadekoBot/Common/PubSub/TypedKey.cs | 2 +- src/NadekoBot/Common/PubSub/YamlSeria.cs | 20 +- .../Common/Replacements/ReplacementBuilder.cs | 55 +- src/NadekoBot/Common/Replacements/Replacer.cs | 17 +- ...RequireObjectPropertiesContractResolver.cs | 4 +- src/NadekoBot/Common/ShmartNumber.cs | 4 +- .../Common/SmartText/SmartEmbedText.cs | 60 +- .../Common/SmartText/SmartPlainText.cs | 4 +- src/NadekoBot/Common/SmartText/SmartText.cs | 16 +- .../Common/SmartText/SmartTextEmbedAuthor.cs | 6 +- .../Common/SmartText/SmartTextEmbedField.cs | 4 +- .../Common/SmartText/SmartTextEmbedFooter.cs | 3 +- .../Common/SocketMessageEventWrapper.cs | 72 +- .../TypeReaders/BotCommandTypeReader.cs | 20 +- .../Common/TypeReaders/EmoteTypeReader.cs | 4 +- .../TypeReaders/GuildDateTimeTypeReader.cs | 8 +- .../Common/TypeReaders/GuildTypeReader.cs | 10 +- .../Common/TypeReaders/KwumTypeReader.cs | 4 +- .../TypeReaders/Models/PermissionAction.cs | 23 +- .../Common/TypeReaders/Models/StoopidTime.cs | 38 +- .../Common/TypeReaders/ModuleTypeReader.cs | 17 +- .../Common/TypeReaders/NadekoTypeReader.cs | 6 +- .../TypeReaders/PermissionActionTypeReader.cs | 9 +- .../Common/TypeReaders/Rgba32TypeReader.cs | 5 +- .../TypeReaders/ShmartNumberTypeReader.cs | 14 +- .../TypeReaders/StoopidTimeTypeReader.cs | 4 +- src/NadekoBot/Common/Yml/CommentAttribute.cs | 4 +- .../Yml/CommentGatheringTypeInspector.cs | 22 +- .../Common/Yml/CommentsObjectDescriptor.cs | 22 +- .../Common/Yml/CommentsObjectGraphVisitor.cs | 10 +- .../Yml/MultilineScalarFlowStyleEmitter.cs | 8 +- src/NadekoBot/Common/Yml/Rgba32Converter.cs | 6 +- src/NadekoBot/Common/Yml/UriConverter.cs | 4 +- src/NadekoBot/Common/Yml/Yaml.cs | 35 +- src/NadekoBot/Common/Yml/YamlHelper.cs | 28 +- src/NadekoBot/Db/Extensions/ClubExtensions.cs | 20 +- .../CurrencyTransactionExtensions.cs | 16 +- .../Extensions/CustomReactionsExtensions.cs | 6 +- src/NadekoBot/Db/Extensions/DbExtensions.cs | 4 +- .../Db/Extensions/DiscordUserExtensions.cs | 117 ++-- .../Db/Extensions/GuildConfigExtensions.cs | 133 ++-- .../MusicPlayerSettingsExtensions.cs | 9 +- .../Db/Extensions/MusicPlaylistExtensions.cs | 13 +- src/NadekoBot/Db/Extensions/PollExtensions.cs | 12 +- .../Db/Extensions/QuoteExtensions.cs | 39 +- .../Db/Extensions/ReminderExtensions.cs | 24 +- .../SelfAssignableRolesExtensions.cs | 10 +- .../Db/Extensions/UserXpExtensions.cs | 52 +- .../Db/Extensions/WaifuExtensions.cs | 171 +++-- .../Db/Extensions/WarningExtensions.cs | 44 +- src/NadekoBot/Db/Models/AntiProtection.cs | 15 +- src/NadekoBot/Db/Models/AutoCommand.cs | 4 +- .../Db/Models/AutoTranslateChannel.cs | 2 +- src/NadekoBot/Db/Models/AutoTranslateUser.cs | 2 +- src/NadekoBot/Db/Models/BanTemplate.cs | 4 +- src/NadekoBot/Db/Models/BlacklistEntry.cs | 2 +- src/NadekoBot/Db/Models/ClubInfo.cs | 7 +- src/NadekoBot/Db/Models/CommandAlias.cs | 2 +- src/NadekoBot/Db/Models/CommandCooldown.cs | 2 +- .../Db/Models/CurrencyTransaction.cs | 10 +- src/NadekoBot/Db/Models/CustomReaction.cs | 13 +- src/NadekoBot/Db/Models/DbEntity.cs | 3 +- src/NadekoBot/Db/Models/DelMsgOnCmdChannel.cs | 5 +- src/NadekoBot/Db/Models/DiscordPemOverride.cs | 4 +- src/NadekoBot/Db/Models/DiscordUser.cs | 10 +- src/NadekoBot/Db/Models/Event.cs | 20 +- src/NadekoBot/Db/Models/FeedSub.cs | 6 +- src/NadekoBot/Db/Models/FilterChannelId.cs | 14 +- .../Db/Models/FilterLinksChannelId.cs | 7 +- src/NadekoBot/Db/Models/FilteredWord.cs | 2 +- src/NadekoBot/Db/Models/FollowedStream.cs | 31 +- src/NadekoBot/Db/Models/GCChannelId.cs | 10 +- src/NadekoBot/Db/Models/GroupName.cs | 2 +- src/NadekoBot/Db/Models/GuildConfig.cs | 22 +- src/NadekoBot/Db/Models/IgnoredLogItem.cs | 4 +- .../Db/Models/IgnoredVoicePresenceChannel.cs | 2 +- src/NadekoBot/Db/Models/ImageOnlyChannel.cs | 2 +- src/NadekoBot/Db/Models/LogSetting.cs | 2 +- src/NadekoBot/Db/Models/MusicPlaylist.cs | 2 +- src/NadekoBot/Db/Models/MusicSettings.cs | 30 +- src/NadekoBot/Db/Models/MutedUserId.cs | 6 +- src/NadekoBot/Db/Models/NsfwBlacklistedTag.cs | 6 +- src/NadekoBot/Db/Models/Permission.cs | 26 +- src/NadekoBot/Db/Models/PlantedCurrency.cs | 2 +- src/NadekoBot/Db/Models/PlaylistSong.cs | 2 +- src/NadekoBot/Db/Models/Poll.cs | 2 +- src/NadekoBot/Db/Models/PollVote.cs | 6 +- src/NadekoBot/Db/Models/Quote.cs | 7 +- src/NadekoBot/Db/Models/ReactionRole.cs | 2 +- src/NadekoBot/Db/Models/Reminder.cs | 2 +- src/NadekoBot/Db/Models/Repeater.cs | 2 +- src/NadekoBot/Db/Models/RewardedUser.cs | 2 +- .../Db/Models/RotatingPlayingStatus.cs | 2 +- src/NadekoBot/Db/Models/SelfAssignableRole.cs | 4 +- src/NadekoBot/Db/Models/ShopEntry.cs | 14 +- .../Db/Models/SlowmodeIgnoredRole.cs | 7 +- .../Db/Models/SlowmodeIgnoredUser.cs | 7 +- src/NadekoBot/Db/Models/StreamRoleSettings.cs | 20 +- src/NadekoBot/Db/Models/UnbanTimer.cs | 10 +- src/NadekoBot/Db/Models/UnmuteTimer.cs | 10 +- src/NadekoBot/Db/Models/UnroleTimer.cs | 10 +- src/NadekoBot/Db/Models/UserXpStats.cs | 2 +- src/NadekoBot/Db/Models/VcRoleInfo.cs | 2 +- src/NadekoBot/Db/Models/Waifu.cs | 27 +- src/NadekoBot/Db/Models/WaifuItem.cs | 2 +- src/NadekoBot/Db/Models/WaifuUpdate.cs | 2 +- src/NadekoBot/Db/Models/WarnExpireAction.cs | 2 +- src/NadekoBot/Db/Models/Warning.cs | 2 +- src/NadekoBot/Db/Models/WarningPunishment.cs | 2 +- src/NadekoBot/Db/Models/XpSettings.cs | 8 +- src/NadekoBot/Db/NadekoContext.cs | 257 +++---- src/NadekoBot/GlobalUsings.cs | 12 +- .../Modules/Administration/Administration.cs | 155 +++-- .../Administration/AutoAssignRoleCommands.cs | 28 +- .../Modules/Administration/Common/LogType.cs | 2 +- .../Administration/Common/ProtectionStats.cs | 30 +- .../Administration/Common/PunishQueueItem.cs | 2 +- .../Administration/Common/UserSpamStats.cs | 22 +- .../Administration/DangerousCommands.cs | 111 ++-- .../DiscordPermOverrideCommands.cs | 54 +- .../Administration/GameChannelCommands.cs | 6 +- .../Administration/GreetBye/GreetGrouper.cs | 9 +- .../Administration/GreetBye/GreetSettings.cs | 3 +- .../GreetBye/GreetSettingsService.cs | 299 ++++----- .../GreetBye/ServerGreetCommands.cs | 56 +- .../Administration/LocalizationCommands.cs | 92 +-- .../Modules/Administration/LogCommands.cs | 74 ++- .../Modules/Administration/MuteCommands.cs | 67 +- .../Administration/PlayingRotateCommands.cs | 19 +- .../Modules/Administration/PrefixCommands.cs | 28 +- .../Administration/ProtectionCommands.cs | 149 ++--- .../Modules/Administration/PruneCommands.cs | 24 +- .../Modules/Administration/RoleCommands.cs | 211 +++--- .../SelfAssignedRolesCommands.cs | 164 ++--- .../Modules/Administration/SelfCommands.cs | 244 +++---- .../Services/AdministrationService.cs | 45 +- .../Services/AutoAssignRoleService.cs | 72 +- .../Services/DangerousCommandsService.cs | 84 +-- .../Services/DiscordPermOverrideService.cs | 77 +-- .../Services/GameVoiceChannelService.cs | 19 +- .../Services/GuildTimezoneService.cs | 14 +- .../Services/ImageOnlyChannelService.cs | 76 +-- .../Services/LogCommandService.cs | 514 +++++++------- .../Administration/Services/MuteService.cs | 292 ++++---- .../Services/PlayingRotateService.cs | 43 +- .../Services/ProtectionService.cs | 209 +++--- .../Administration/Services/PruneService.cs | 19 +- .../Services/RoleCommandsService.cs | 121 ++-- .../Services/SelfAssignedRolesService.cs | 80 +-- .../Administration/Services/SelfService.cs | 185 ++---- .../Services/UserPunishService.cs | 226 ++++--- .../Administration/Services/VcRoleService.cs | 99 +-- .../Administration/TimeZoneCommands.cs | 63 +- .../Administration/UserPunishCommands.cs | 555 ++++++++-------- .../Modules/Administration/VcRoleCommands.cs | 34 +- .../CustomReactions/Common/ExportedExpr.cs | 6 +- .../CustomReactions/CustomReactions.cs | 190 +++--- .../Extensions/CustomReactionExtensions.cs | 28 +- .../Services/CustomReactionsService.cs | 567 ++++++++-------- .../Modules/Gambling/AnimalRacingCommands.cs | 61 +- .../Modules/Gambling/BlackJackCommands.cs | 79 +-- .../Common/AnimalRacing/AnimalRace.cs | 33 +- .../Common/AnimalRacing/AnimalRacingUser.cs | 14 +- .../Exceptions/AlreadyJoinedException.cs | 9 +- .../Exceptions/AlreadyStartedException.cs | 8 +- .../Exceptions/AnimalRaceFullException.cs | 8 +- .../Exceptions/NotEnoughFundsException.cs | 8 +- .../Common/AnimalRacing/RaceOptions.cs | 6 +- .../Modules/Gambling/Common/BetRoll.cs | 34 +- .../Gambling/Common/Blackjack/Blackjack.cs | 68 +- .../Gambling/Common/Blackjack/Player.cs | 24 +- .../Gambling/Common/CurrencyRaffleGame.cs | 47 +- src/NadekoBot/Modules/Gambling/Common/Deck.cs | 378 ++++++----- .../Gambling/Common/Events/EventOptions.cs | 18 +- .../Gambling/Common/Events/GameStatusEvent.cs | 78 +-- .../Gambling/Common/Events/ICurrencyEvent.cs | 2 +- .../Gambling/Common/Events/ReactionEvent.cs | 86 ++- .../Modules/Gambling/Common/GamblingConfig.cs | 69 +- .../Modules/Gambling/Common/GamblingError.cs | 2 +- .../Gambling/Common/GamblingTopLevelModule.cs | 42 +- .../Modules/Gambling/Common/Payout.cs | 2 +- .../Modules/Gambling/Common/RollDuelGame.cs | 114 ++-- .../Modules/Gambling/Common/Slot/SlotGame.cs | 32 +- .../Modules/Gambling/Common/SlotResponse.cs | 2 +- .../Gambling/Common/Waifu/AffinityTitle.cs | 2 +- .../Gambling/Common/Waifu/ClaimTitle.cs | 4 +- .../Gambling/Common/Waifu/DivorceResult.cs | 2 +- .../Gambling/Common/Waifu/WaifuClaimResult.cs | 2 +- .../Common/WheelOfFortune/WheelOfFortune.cs | 28 +- .../Modules/Gambling/Connect4/Connect4.cs | 135 ++-- .../Modules/Gambling/Connect4Commands.cs | 95 ++- .../Gambling/CurrencyEventsCommands.cs | 60 +- .../Gambling/CurrencyRaffleCommands.cs | 26 +- .../Modules/Gambling/DiceRollCommands.cs | 105 +-- .../Modules/Gambling/DrawCommands.cs | 20 +- .../Modules/Gambling/FlipCoinCommands.cs | 60 +- src/NadekoBot/Modules/Gambling/Gambling.cs | 391 +++++------ .../Modules/Gambling/PlantAndPickCommands.cs | 69 +- .../Gambling/Services/AnimalRaceService.cs | 2 +- .../Gambling/Services/BlackJackService.cs | 2 +- .../Services/CurrencyEventsService.cs | 25 +- .../Services/CurrencyRaffleService.cs | 22 +- .../Services/GamblingConfigService.cs | 107 ++- .../Gambling/Services/GamblingService.cs | 120 ++-- .../Modules/Gambling/Services/IShopService.cs | 10 +- .../Gambling/Services/Impl/ShopService.cs | 14 +- .../Gambling/Services/PlantPickService.cs | 122 ++-- .../Gambling/Services/VoteRewardService.cs | 27 +- .../Modules/Gambling/Services/WaifuService.cs | 194 ++---- .../Modules/Gambling/ShopCommands.cs | 241 ++++--- .../Modules/Gambling/SlotCommands.cs | 177 ++--- .../Modules/Gambling/TestGamblingService.cs | 2 +- .../Modules/Gambling/WaifuClaimCommands.cs | 224 +++---- .../Gambling/WheelOfFortuneCommands.cs | 25 +- .../Modules/Games/AcropobiaCommands.cs | 63 +- .../Modules/Games/CleverBotCommands.cs | 8 +- .../Games/Common/Acrophobia/Acrophobia.cs | 78 ++- .../Games/Common/Acrophobia/AcrophobiaUser.cs | 12 +- .../Common/ChatterBot/ChatterBotResponse.cs | 2 +- .../Common/ChatterBot/ChatterBotSession.cs | 15 +- .../Common/ChatterBot/CleverbotResponse.cs | 2 +- .../Common/ChatterBot/IChatterBotSession.cs | 2 +- .../ChatterBot/OfficialCleverbotSession.cs | 35 +- .../Modules/Games/Common/GamesConfig.cs | 17 +- .../Modules/Games/Common/GirlRating.cs | 21 +- .../Common/Hangman/DefaultHangmanSource.cs | 12 +- .../Games/Common/Hangman/HangmanGame.cs | 56 +- .../Games/Common/Hangman/HangmanService.cs | 65 +- .../Games/Common/Hangman/HangmanTerm.cs | 2 +- .../Games/Common/Hangman/IHangmanService.cs | 2 +- .../Games/Common/Hangman/IHangmanSource.cs | 2 +- .../Modules/Games/Common/Nunchi/Nunchi.cs | 52 +- .../Modules/Games/Common/PollRunner.cs | 11 +- .../Modules/Games/Common/TicTacToe.cs | 176 ++--- .../Modules/Games/Common/Trivia/TriviaGame.cs | 118 ++-- .../Games/Common/Trivia/TriviaOptions.cs | 25 +- .../Games/Common/Trivia/TriviaQuestion.cs | 57 +- .../Games/Common/Trivia/TriviaQuestionPool.cs | 12 +- .../Modules/Games/Common/TypingArticle.cs | 2 +- .../Modules/Games/Common/TypingGame.cs | 87 +-- .../Modules/Games/Connect4Commands.cs | 3 +- src/NadekoBot/Modules/Games/Games.cs | 43 +- .../Modules/Games/HangmanCommands.cs | 51 +- src/NadekoBot/Modules/Games/NunchiCommands.cs | 28 +- src/NadekoBot/Modules/Games/PollCommands.cs | 68 +- .../Games/Services/ChatterbotService.cs | 57 +- .../Games/Services/GamesConfigService.cs | 36 +- .../Modules/Games/Services/GamesService.cs | 78 +-- .../Modules/Games/Services/PollService.cs | 48 +- .../Modules/Games/SpeedTypingCommands.cs | 45 +- .../Modules/Games/TicTacToeCommands.cs | 8 +- src/NadekoBot/Modules/Games/TriviaCommands.cs | 34 +- .../Modules/Help/Common/CommandsOptions.cs | 12 +- src/NadekoBot/Modules/Help/Help.cs | 274 ++++---- .../Modules/Help/Services/HelpService.cs | 94 ++- .../Music/Common/ICachableTrackData.cs | 2 +- .../Music/Common/ILocalTrackResolver.cs | 2 +- .../Modules/Music/Common/IMusicPlayer.cs | 13 +- .../Modules/Music/Common/IMusicQueue.cs | 10 +- .../Music/Common/IPlatformQueryResolver.cs | 2 +- .../Modules/Music/Common/IQueuedTrackInfo.cs | 2 +- .../Modules/Music/Common/IRadioResolver.cs | 3 +- .../Music/Common/ISoundcloudResolver.cs | 2 +- .../Modules/Music/Common/ITrackCacher.cs | 13 +- .../Modules/Music/Common/ITrackInfo.cs | 2 +- .../Music/Common/ITrackResolveProvider.cs | 2 +- .../Modules/Music/Common/IVoiceProxy.cs | 2 +- .../Modules/Music/Common/IYoutubeResolver.cs | 2 +- .../Music/Common/Impl/CachableTrackData.cs | 7 +- .../Music/Common/Impl/MultimediaTimer.cs | 121 ++-- .../Music/Common/Impl/MusicExtensions.cs | 8 +- .../Music/Common/Impl/MusicPlatform.cs | 4 +- .../Modules/Music/Common/Impl/MusicPlayer.cs | 181 +++-- .../Modules/Music/Common/Impl/MusicQueue.cs | 49 +- .../Music/Common/Impl/RedisTrackCacher.cs | 49 +- .../Music/Common/Impl/RemoteTrackInfo.cs | 12 +- .../Music/Common/Impl/SimpleTrackInfo.cs | 15 +- .../Modules/Music/Common/Impl/VoiceProxy.cs | 24 +- .../Common/Resolvers/LocalTrackResolver.cs | 38 +- .../Common/Resolvers/RadioResolveStrategy.cs | 53 +- .../Common/Resolvers/SoundcloudResolver.cs | 41 +- .../Common/Resolvers/TrackResolveProvider.cs | 23 +- .../Common/Resolvers/YtdlYoutubeResolver.cs | 232 +++---- src/NadekoBot/Modules/Music/Music.cs | 361 +++++----- .../Modules/Music/PlaylistCommands.cs | 123 ++-- .../Music/Services/AyuVoiceStateService.cs | 81 ++- .../Modules/Music/Services/IMusicService.cs | 12 +- .../Modules/Music/Services/MusicService.cs | 203 +++--- .../Modules/Music/Services/extractor/Misc.cs | 3 +- .../Music/Services/extractor/YtLoader.cs | 48 +- src/NadekoBot/Modules/Nsfw/Common/Booru.cs | 2 +- .../Modules/Nsfw/Common/DapiImageObject.cs | 9 +- src/NadekoBot/Modules/Nsfw/Common/DapiTag.cs | 2 +- .../Modules/Nsfw/Common/DerpiContainer.cs | 4 +- .../Downloaders/DanbooruImageDownloader.cs | 26 +- .../Common/Downloaders/DapiImageDownloader.cs | 19 +- .../Downloaders/DerpibooruImageDownloader.cs | 22 +- .../Common/Downloaders/E621ImageDownloader.cs | 17 +- .../Nsfw/Common/Downloaders/E621Response.cs | 2 +- .../Downloaders/GelbooruImageDownloader.cs | 17 +- .../Common/Downloaders/IImageDownloader.cs | 9 +- .../Common/Downloaders/ImageDownloader.cs | 21 +- .../Downloaders/ImageDownloaderHelper.cs | 4 +- .../Downloaders/KonachanImageDownloader.cs | 12 +- .../Downloaders/Rule34ImageDownloader.cs | 21 +- .../Downloaders/SafebooruImageDownloader.cs | 18 +- .../Downloaders/SankakuImageDownloader.cs | 16 +- .../Downloaders/YandereImageDownloader.cs | 14 +- .../Modules/Nsfw/Common/E621Object.cs | 16 +- .../Modules/Nsfw/Common/IImageData.cs | 2 +- .../Modules/Nsfw/Common/ImageData.cs | 32 +- .../Modules/Nsfw/Common/Rule34Object.cs | 8 +- .../Modules/Nsfw/Common/SafebooruElement.cs | 10 +- .../Modules/Nsfw/Common/SankakuImageObject.cs | 30 +- .../Modules/Nsfw/ISearchImagesService.cs | 8 +- src/NadekoBot/Modules/Nsfw/Nsfw.cs | 290 ++++---- src/NadekoBot/Modules/Nsfw/NsfwService.cs | 6 +- .../Modules/Nsfw/SearchImageCacher.cs | 113 ++-- .../Modules/Nsfw/SearchImagesService.cs | 172 ++--- .../Modules/Permissions/BlacklistCommands.cs | 135 ++-- .../Modules/Permissions/CmdCdsCommands.cs | 34 +- .../Permissions/Common/PermissionCache.cs | 2 +- .../Common/PermissionExtensions.cs | 40 +- .../Common/PermissionsCollection.cs | 46 +- .../Modules/Permissions/FilterCommands.cs | 81 +-- .../Permissions/GlobalPermissionCommands.cs | 27 +- .../Modules/Permissions/Permissions.cs | 437 ++++++------ .../Permissions/ResetPermissionsCommands.cs | 8 +- .../Permissions/Services/BlacklistService.cs | 64 +- .../Permissions/Services/CmdCdService.cs | 25 +- .../Permissions/Services/FilterService.cs | 149 ++--- .../Services/GlobalPermissionService.cs | 32 +- .../Services/PermissionsService.cs | 163 +++-- .../Modules/Searches/AnimeSearchCommands.cs | 136 ++-- .../Modules/Searches/Common/AnimeResult.cs | 20 +- .../Modules/Searches/Common/BibleVerses.cs | 3 +- .../Modules/Searches/Common/CryptoData.cs | 3 +- .../Modules/Searches/Common/DefineModel.cs | 4 +- .../Modules/Searches/Common/E621Object.cs | 10 +- .../Exceptions/StreamNotFoundException.cs | 8 +- .../Modules/Searches/Common/Gallery.cs | 2 +- .../Searches/Common/GatariUserResponse.cs | 44 +- .../Common/GatariUserStatsResponse.cs | 68 +- .../Searches/Common/GoogleSearchResult.cs | 8 +- .../Searches/Common/HearthstoneCardData.cs | 2 +- .../Searches/Common/ImageCacherObject.cs | 32 +- .../Searches/Common/LowerCaseNamingPolicy.cs | 8 +- .../Modules/Searches/Common/MagicItem.cs | 2 +- .../Modules/Searches/Common/MangaResult.cs | 17 +- .../Modules/Searches/Common/MtgData.cs | 6 +- .../Searches/Common/NhentaiApiModel.cs | 92 ++- .../Modules/Searches/Common/NovelData.cs | 2 +- .../Modules/Searches/Common/OmdbMovie.cs | 2 +- .../Modules/Searches/Common/OsuUserData.cs | 65 +- .../Searches/Common/PathOfExileModels.cs | 42 +- .../Modules/Searches/Common/SteamGameId.cs | 4 +- .../Models/PicartoChannelResponse.cs | 138 ++-- .../StreamNotifications/Models/StreamData.cs | 5 +- .../Models/StreamDataKey.cs | 6 +- .../Models/TwitchResponseHelix.cs | 2 +- .../Models/TwitchResponseV5.cs | 95 ++- .../Models/TwitchUsersResponseV5.cs | 9 +- .../StreamNotifications/NotifChecker.cs | 224 +++---- .../Providers/PicartoProvider.cs | 29 +- .../StreamNotifications/Providers/Provider.cs | 50 +- .../Providers/TwitchHelixProvider.cs | 2 +- .../Providers/TwitchProvider.cs | 25 +- .../Modules/Searches/Common/TimeData.cs | 2 +- .../Modules/Searches/Common/TimeModels.cs | 3 +- .../Modules/Searches/Common/UrbanDef.cs | 3 +- .../Modules/Searches/Common/WeatherModels.cs | 4 +- .../Searches/Common/WikipediaApiModel.cs | 2 +- .../Modules/Searches/Common/WoWJoke.cs | 6 +- .../Modules/Searches/CryptoCommands.cs | 39 +- .../Modules/Searches/FeedCommands.cs | 56 +- .../Modules/Searches/JokeCommands.cs | 20 +- .../Modules/Searches/MemegenCommands.cs | 69 +- src/NadekoBot/Modules/Searches/OsuCommands.cs | 144 ++-- .../Modules/Searches/PathOfExileCommands.cs | 291 ++++---- .../Modules/Searches/PlaceCommands.cs | 21 +- .../Modules/Searches/PokemonSearchCommands.cs | 60 +- src/NadekoBot/Modules/Searches/Searches.cs | 451 +++++++------ .../Searches/Services/AnimeSearchService.cs | 59 +- .../Searches/Services/AtlExtensions.cs | 6 +- .../Searches/Services/CryptoService.cs | 77 ++- .../Modules/Searches/Services/FeedsService.cs | 153 ++--- .../Searches/Services/ITranslateService.cs | 10 +- .../Searches/Services/SearchesService.cs | 427 ++++++------ .../Services/StreamNotificationService.cs | 282 ++++---- .../Searches/Services/TranslateService.cs | 122 ++-- .../Searches/Services/YtTrackService.cs | 2 +- .../Searches/StreamNotificationCommands.cs | 120 ++-- .../Modules/Searches/TranslatorCommands.cs | 54 +- .../Modules/Searches/XkcdCommands.cs | 40 +- .../Modules/Searches/YtUploadCommands.cs | 2 +- src/NadekoBot/Modules/Utility/CalcCommands.cs | 29 +- .../Modules/Utility/CommandMapCommands.cs | 97 ++- .../Modules/Utility/Common/ConvertUnit.cs | 2 +- .../Exceptions/StreamRoleNotFoundException.cs | 11 +- .../StreamRolePermissionException.cs | 11 +- .../Utility/Common/Patreon/PatreonData.cs | 12 +- .../Utility/Common/StreamRoleListType.cs | 4 +- .../Modules/Utility/ConfigCommands.cs | 74 +-- src/NadekoBot/Modules/Utility/InfoCommands.cs | 105 ++- .../Modules/Utility/InviteCommands.cs | 70 +- .../Modules/Utility/QuoteCommands.cs | 191 +++--- .../Modules/Utility/RemindCommands.cs | 138 ++-- .../Modules/Utility/RepeatCommands.cs | 137 ++-- .../Utility/Services/CommandMapService.cs | 43 +- .../Utility/Services/ConverterService.cs | 44 +- .../Modules/Utility/Services/InviteService.cs | 31 +- .../Utility/Services/PatreonRewardsService.cs | 158 +++-- .../Modules/Utility/Services/RemindService.cs | 87 ++- .../Utility/Services/RepeaterService.cs | 164 ++--- .../Utility/Services/RunningRepeater.cs | 25 +- .../Utility/Services/StreamRoleService.cs | 98 ++- .../Utility/Services/VerboseErrorsService.cs | 28 +- .../Modules/Utility/StreamRoleCommands.cs | 59 +- .../Modules/Utility/UnitConversionCommands.cs | 43 +- src/NadekoBot/Modules/Utility/Utility.cs | 336 +++++----- .../Modules/Utility/VerboseErrorCommands.cs | 5 +- src/NadekoBot/Modules/Xp/Club.cs | 225 +++---- .../Modules/Xp/Common/FullUserStats.cs | 13 +- src/NadekoBot/Modules/Xp/Common/LevelStats.cs | 2 +- src/NadekoBot/Modules/Xp/Common/XpConfig.cs | 4 +- src/NadekoBot/Modules/Xp/Common/XpTemplate.cs | 202 +----- .../Modules/Xp/Extensions/Extensions.cs | 4 +- src/NadekoBot/Modules/Xp/ResetCommands.cs | 20 +- .../Modules/Xp/Services/ClubService.cs | 58 +- .../Modules/Xp/Services/UserCacheItem.cs | 2 +- .../Modules/Xp/Services/XpConfigService.cs | 37 +- .../Modules/Xp/Services/XpService.cs | 516 +++++--------- src/NadekoBot/Modules/Xp/Xp.cs | 313 +++++---- src/NadekoBot/Program.cs | 2 +- src/NadekoBot/Services/CommandHandler.cs | 192 +++--- src/NadekoBot/Services/DbService.cs | 10 +- src/NadekoBot/Services/IBehaviourExecutor.cs | 2 +- src/NadekoBot/Services/ICoordinator.cs | 6 +- src/NadekoBot/Services/ICurrencyService.cs | 45 +- src/NadekoBot/Services/IDataCache.cs | 10 +- .../Services/IEmbedBuilderService.cs | 12 +- src/NadekoBot/Services/IGoogleApiService.cs | 6 +- src/NadekoBot/Services/IImageCache.cs | 2 +- src/NadekoBot/Services/ILocalDataCache.cs | 2 +- src/NadekoBot/Services/ILocalization.cs | 2 +- src/NadekoBot/Services/INService.cs | 4 +- src/NadekoBot/Services/IStatsService.cs | 22 +- .../Services/Impl/BehaviorExecutor.cs | 23 +- .../Services/Impl/BotCredsProvider.cs | 134 ++-- .../Services/Impl/CurrencyService.cs | 118 ++-- src/NadekoBot/Services/Impl/FontProvider.cs | 40 +- .../Services/Impl/GoogleApiService.cs | 327 +++++---- src/NadekoBot/Services/Impl/Localization.cs | 77 ++- src/NadekoBot/Services/Impl/RedisCache.cs | 49 +- .../Services/Impl/RedisImageExtensions.cs | 4 +- .../Services/Impl/RedisImagesCache.cs | 99 ++- .../Services/Impl/RedisLocalDataCache.cs | 25 +- .../Services/Impl/RemoteGrpcCoordinator.cs | 60 +- .../Services/Impl/SingleProcessCoordinator.cs | 7 +- .../Services/Impl/SoundCloudApiService.cs | 20 +- .../Impl/StartingGuildsListService.cs | 14 +- src/NadekoBot/Services/Impl/StatsService.cs | 88 ++- src/NadekoBot/Services/Impl/YtdlOperation.cs | 17 +- src/NadekoBot/Services/LogSetup.cs | 30 +- .../Services/Settings/BotConfigService.cs | 16 +- .../Services/Settings/ConfigParsers.cs | 10 +- .../Services/Settings/ConfigServiceBase.cs | 82 +-- .../Services/Settings/IConfigMigrator.cs | 2 +- .../Services/Settings/IConfigService.cs | 21 +- .../Services/Settings/SettingParser.cs | 4 +- src/NadekoBot/Services/StandardConversions.cs | 2 +- src/NadekoBot/Services/strings/IBotStrings.cs | 4 +- .../Services/strings/IBotStringsProvider.cs | 12 +- .../Services/strings/IStringsSource.cs | 6 +- .../Services/strings/impl/BotStrings.cs | 40 +- .../strings/impl/LocalBotStringsProvider.cs | 13 +- .../strings/impl/LocalFileStringsSource.cs | 20 +- .../strings/impl/RedisBotStringsProvider.cs | 48 +- src/NadekoBot/_Extensions/ArrayExtensions.cs | 13 +- .../_Extensions/BotCredentialsExtensions.cs | 2 +- .../_Extensions/EnumerableExtensions.cs | 59 +- src/NadekoBot/_Extensions/Extensions.cs | 128 ++-- .../_Extensions/IMessageChannelExtensions.cs | 47 +- .../_Extensions/LinkedListExtensions.cs | 2 +- src/NadekoBot/_Extensions/NumberExtensions.cs | 11 +- .../_Extensions/ProcessExtensions.cs | 37 +- src/NadekoBot/_Extensions/Rgba32Extensions.cs | 2 +- .../ServiceCollectionExtensions.cs | 40 +- src/NadekoBot/_Extensions/StringExtensions.cs | 62 +- src/NadekoBot/_Extensions/UserExtensions.cs | 5 +- 543 files changed, 13221 insertions(+), 14059 deletions(-) diff --git a/src/NadekoBot/.editorconfig b/src/NadekoBot/.editorconfig index 9a51317bb..0d3ab18fa 100644 --- a/src/NadekoBot/.editorconfig +++ b/src/NadekoBot/.editorconfig @@ -339,7 +339,7 @@ resharper_keep_existing_linebreaks = false resharper_max_formal_parameters_on_line = 3 resharper_wrap_chained_binary_expressions = chop_if_long resharper_wrap_chained_binary_patterns = chop_if_long -resharper_wrap_chained_method_calls = wrap_if_long +resharper_wrap_chained_method_calls = chop_if_long resharper_csharp_wrap_before_first_type_parameter_constraint = true resharper_csharp_place_type_constraints_on_same_line = false diff --git a/src/NadekoBot/Bot.cs b/src/NadekoBot/Bot.cs index 414a41922..d3edd37f8 100644 --- a/src/NadekoBot/Bot.cs +++ b/src/NadekoBot/Bot.cs @@ -1,35 +1,35 @@ #nullable disable +using Discord.Interactions; using Microsoft.Extensions.DependencyInjection; +using NadekoBot.Common.Configs; +using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Db; +using NadekoBot.Modules.Administration.Services; using NadekoBot.Services.Database.Models; using System.Collections.Immutable; using System.Diagnostics; using System.Reflection; -using NadekoBot.Common.ModuleBehaviors; -using NadekoBot.Common.Configs; -using NadekoBot.Db; -using NadekoBot.Modules.Administration.Services; -using Discord.Interactions; +using RunMode = Discord.Commands.RunMode; namespace NadekoBot; public sealed class Bot { + public event Func JoinedGuild = delegate { return Task.CompletedTask; }; + + public DiscordSocketClient Client { get; } + public ImmutableArray AllGuildConfigs { get; private set; } + + private IServiceProvider Services { get; set; } + + public string Mention { get; private set; } + public bool IsReady { get; private set; } private readonly IBotCredentials _creds; private readonly CommandService _commandService; private readonly DbService _db; private readonly IBotCredsProvider _credsProvider; private readonly InteractionService _interactionService; - public event Func JoinedGuild = delegate { return Task.CompletedTask; }; - - public DiscordSocketClient Client { get; } - public ImmutableArray AllGuildConfigs { get; private set; } - - private IServiceProvider Services { get; set; } - - public string Mention { get; private set; } - public bool IsReady { get; private set; } - public Bot(int shardId, int? totalShards) { if (shardId < 0) @@ -37,13 +37,10 @@ public sealed class Bot _credsProvider = new BotCredsProvider(totalShards); _creds = _credsProvider.GetCreds(); - + _db = new(_creds); - if (shardId == 0) - { - _db.Setup(); - } + if (shardId == 0) _db.Setup(); Client = new(new() { @@ -55,14 +52,10 @@ public sealed class Bot AlwaysDownloadUsers = false, AlwaysResolveStickers = false, AlwaysDownloadDefaultStickers = false, - GatewayIntents = GatewayIntents.All, + GatewayIntents = GatewayIntents.All }); - _commandService = new(new() - { - CaseSensitiveCommands = false, - DefaultRunMode = Discord.Commands.RunMode.Sync, - }); + _commandService = new(new() { CaseSensitiveCommands = false, DefaultRunMode = RunMode.Sync }); _interactionService = new(Client.Rest); @@ -85,49 +78,41 @@ public sealed class Bot uow.EnsureUserCreated(bot.Id, bot.Username, bot.Discriminator, bot.AvatarId); AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray(); } - - var svcs = new ServiceCollection() - .AddTransient(_ => _credsProvider.GetCreds()) // bot creds - .AddSingleton(_credsProvider) - .AddSingleton(_db) // database - .AddRedis(_creds.RedisOptions) // redis - .AddSingleton(Client) // discord socket client - .AddSingleton(_commandService) - .AddSingleton(_interactionService) - .AddSingleton(this) - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddBotStringsServices(_creds.TotalShards) - .AddConfigServices() - .AddConfigMigrators() - .AddMemoryCache() - // music - .AddMusic() - // admin + + var svcs = new ServiceCollection().AddTransient(_ => _credsProvider.GetCreds()) // bot creds + .AddSingleton(_credsProvider) + .AddSingleton(_db) // database + .AddRedis(_creds.RedisOptions) // redis + .AddSingleton(Client) // discord socket client + .AddSingleton(_commandService) + .AddSingleton(_interactionService) + .AddSingleton(this) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddBotStringsServices(_creds.TotalShards) + .AddConfigServices() + .AddConfigMigrators() + .AddMemoryCache() + // music + .AddMusic(); + // admin #if GLOBAL_NADEKO - .AddSingleton() + svcs.AddSingleton(); #else - .AddSingleton() + svcs.AddSingleton(); #endif - ; svcs.AddHttpClient(); - svcs.AddHttpClient("memelist").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler - { - AllowAutoRedirect = false - }); + svcs.AddHttpClient("memelist") + .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false }); if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1") - { svcs.AddSingleton(); - } else - { svcs.AddSingleton() .AddSingleton(x => x.GetRequiredService()) .AddSingleton(x => x.GetRequiredService()); - } svcs.AddSingleton() .AddSingleton(x => x.GetRequiredService()) @@ -135,37 +120,31 @@ public sealed class Bot .AddSingleton(x => x.GetRequiredService()) .AddSingleton(x => x.GetRequiredService()) .AddSingleton(); - - svcs.Scan(scan => scan - .FromAssemblyOf() - .AddClasses(classes => classes - .AssignableToAny( - // services - typeof(INService), - - // behaviours - typeof(IEarlyBehavior), - typeof(ILateBlocker), - typeof(IInputTransformer), - typeof(ILateExecutor)) + + svcs.Scan(scan => scan.FromAssemblyOf() + .AddClasses(classes => classes.AssignableToAny( + // services + typeof(INService), + + // behaviours + typeof(IEarlyBehavior), + typeof(ILateBlocker), + typeof(IInputTransformer), + typeof(ILateExecutor)) #if GLOBAL_NADEKO .WithoutAttribute() #endif - ) - .AsSelfWithInterfaces() - .WithSingletonLifetime() - ); + ) + .AsSelfWithInterfaces() + .WithSingletonLifetime()); //initialize Services Services = svcs.BuildServiceProvider(); var exec = Services.GetRequiredService(); exec.Initialize(); - if (Client.ShardId == 0) - { - ApplyConfigMigrations(); - } - + if (Client.ShardId == 0) ApplyConfigMigrations(); + _ = LoadTypeReaders(typeof(Bot).Assembly); sw.Stop(); @@ -176,10 +155,7 @@ public sealed class Bot { // execute all migrators var migrators = Services.GetServices(); - foreach (var migrator in migrators) - { - migrator.EnsureMigrated(); - } + foreach (var migrator in migrators) migrator.EnsureMigrated(); } private IEnumerable LoadTypeReaders(Assembly assembly) @@ -225,10 +201,7 @@ public sealed class Bot clientReady.TrySetResult(true); try { - foreach (var chan in await Client.GetDMChannelsAsync()) - { - await chan.CloseAsync(); - } + foreach (var chan in await Client.GetDMChannelsAsync()) await chan.CloseAsync(); } catch { @@ -259,10 +232,10 @@ public sealed class Bot Client.Ready += SetClientReady; await clientReady.Task; Client.Ready -= SetClientReady; - + Client.JoinedGuild += Client_JoinedGuild; Client.LeftGuild += Client_LeftGuild; - + Log.Information("Shard {ShardId} logged in", Client.ShardId); } @@ -282,6 +255,7 @@ public sealed class Bot { gc = uow.GuildConfigsForId(arg.Id, null); } + await JoinedGuild.Invoke(gc); }); return Task.CompletedTask; @@ -323,7 +297,7 @@ public sealed class Bot private Task ExecuteReadySubscriptions() { var readyExecutors = Services.GetServices(); - var tasks = readyExecutors.Select(async toExec => + var tasks = readyExecutors.Select(async toExec => { try { @@ -356,4 +330,4 @@ public sealed class Bot await RunAsync(); await Task.Delay(-1); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/AddRemove.cs b/src/NadekoBot/Common/AddRemove.cs index a8f558fdf..1b43eec9e 100644 --- a/src/NadekoBot/Common/AddRemove.cs +++ b/src/NadekoBot/Common/AddRemove.cs @@ -6,5 +6,5 @@ public enum AddRemove Add = int.MinValue, Remove = int.MinValue + 1, Rem = int.MinValue + 1, - Rm = int.MinValue + 1, -} + Rm = int.MinValue + 1 +} \ No newline at end of file diff --git a/src/NadekoBot/Common/AsyncLazy.cs b/src/NadekoBot/Common/AsyncLazy.cs index 05cf162ae..54adcf9a2 100644 --- a/src/NadekoBot/Common/AsyncLazy.cs +++ b/src/NadekoBot/Common/AsyncLazy.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using System.Runtime.CompilerServices; namespace NadekoBot.Common; @@ -17,4 +17,4 @@ public class AsyncLazy : Lazy> public TaskAwaiter GetAwaiter() => Value.GetAwaiter(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Attributes/Aliases.cs b/src/NadekoBot/Common/Attributes/Aliases.cs index 407e6f783..512caa9a8 100644 --- a/src/NadekoBot/Common/Attributes/Aliases.cs +++ b/src/NadekoBot/Common/Attributes/Aliases.cs @@ -9,4 +9,4 @@ public sealed class AliasesAttribute : AliasAttribute : base(CommandNameLoadHelper.GetAliasesFor(memberName)) { } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Attributes/CommandNameLoadHelper.cs b/src/NadekoBot/Common/Attributes/CommandNameLoadHelper.cs index b8bed1ebb..94e2fd614 100644 --- a/src/NadekoBot/Common/Attributes/CommandNameLoadHelper.cs +++ b/src/NadekoBot/Common/Attributes/CommandNameLoadHelper.cs @@ -1,9 +1,10 @@ +using YamlDotNet.Serialization; + namespace NadekoBot.Common.Attributes; public static class CommandNameLoadHelper { - private static readonly YamlDotNet.Serialization.IDeserializer _deserializer = - new YamlDotNet.Serialization.Deserializer(); + private static readonly IDeserializer _deserializer = new Deserializer(); public static Lazy> LazyCommandAliases = new(() => LoadCommandNames()); @@ -26,4 +27,4 @@ public static class CommandNameLoadHelper : methodName; return toReturn; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Attributes/NadekoCommand.cs b/src/NadekoBot/Common/Attributes/NadekoCommand.cs index 4bd6bd6c9..486bddf25 100644 --- a/src/NadekoBot/Common/Attributes/NadekoCommand.cs +++ b/src/NadekoBot/Common/Attributes/NadekoCommand.cs @@ -5,9 +5,9 @@ namespace NadekoBot.Common.Attributes; [AttributeUsage(AttributeTargets.Method)] public sealed class NadekoCommandAttribute : CommandAttribute { + public string MethodName { get; } + public NadekoCommandAttribute([CallerMemberName] string memberName = "") : base(CommandNameLoadHelper.GetCommandNameFor(memberName)) - => this.MethodName = memberName.ToLowerInvariant(); - - public string MethodName { get; } -} + => MethodName = memberName.ToLowerInvariant(); +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Attributes/NadekoModuleAttribute.cs b/src/NadekoBot/Common/Attributes/NadekoModuleAttribute.cs index 39e859bbd..70976f425 100644 --- a/src/NadekoBot/Common/Attributes/NadekoModuleAttribute.cs +++ b/src/NadekoBot/Common/Attributes/NadekoModuleAttribute.cs @@ -7,4 +7,4 @@ internal sealed class NadekoModuleAttribute : GroupAttribute : base(moduleName) { } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Attributes/NadekoOptions.cs b/src/NadekoBot/Common/Attributes/NadekoOptions.cs index a071f9900..a5c0db5b1 100644 --- a/src/NadekoBot/Common/Attributes/NadekoOptions.cs +++ b/src/NadekoBot/Common/Attributes/NadekoOptions.cs @@ -6,5 +6,5 @@ public sealed class NadekoOptionsAttribute : Attribute public Type OptionType { get; set; } public NadekoOptionsAttribute(Type t) - => this.OptionType = t; -} + => OptionType = t; +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Attributes/OwnerOnlyAttribute.cs b/src/NadekoBot/Common/Attributes/OwnerOnlyAttribute.cs index 533b6a820..13b4dfb7a 100644 --- a/src/NadekoBot/Common/Attributes/OwnerOnlyAttribute.cs +++ b/src/NadekoBot/Common/Attributes/OwnerOnlyAttribute.cs @@ -14,7 +14,6 @@ public sealed class OwnerOnlyAttribute : PreconditionAttribute return Task.FromResult(creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() - : PreconditionResult.FromError("Not owner") - ); + : PreconditionResult.FromError("Not owner")); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Attributes/Ratelimit.cs b/src/NadekoBot/Common/Attributes/Ratelimit.cs index e03aa6b58..e33b86ba8 100644 --- a/src/NadekoBot/Common/Attributes/Ratelimit.cs +++ b/src/NadekoBot/Common/Attributes/Ratelimit.cs @@ -33,4 +33,4 @@ public sealed class RatelimitAttribute : PreconditionAttribute return Task.FromResult(PreconditionResult.FromError(msgContent)); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Attributes/UserPerm.cs b/src/NadekoBot/Common/Attributes/UserPerm.cs index cd393dbf1..5ea5257b2 100644 --- a/src/NadekoBot/Common/Attributes/UserPerm.cs +++ b/src/NadekoBot/Common/Attributes/UserPerm.cs @@ -6,6 +6,16 @@ namespace Discord; [AttributeUsage(AttributeTargets.Method)] public class UserPermAttribute : RequireUserPermissionAttribute { + public UserPermAttribute(GuildPerm permission) + : base(permission) + { + } + + public UserPermAttribute(ChannelPerm permission) + : base(permission) + { + } + public override Task CheckPermissionsAsync( ICommandContext context, CommandInfo command, @@ -17,14 +27,4 @@ public class UserPermAttribute : RequireUserPermissionAttribute return base.CheckPermissionsAsync(context, command, services); } - - public UserPermAttribute(GuildPerm permission) - : base(permission) - { - } - - public UserPermAttribute(ChannelPerm permission) - : base(permission) - { - } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/CmdStrings.cs b/src/NadekoBot/Common/CmdStrings.cs index 5f120a7d6..c1e9a93b0 100644 --- a/src/NadekoBot/Common/CmdStrings.cs +++ b/src/NadekoBot/Common/CmdStrings.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using Newtonsoft.Json; namespace NadekoBot.Common; @@ -14,4 +14,4 @@ public class CmdStrings Usages = usages; Description = description; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Collections/ConcurrentHashSet.cs b/src/NadekoBot/Common/Collections/ConcurrentHashSet.cs index f87b756ac..8216acc59 100644 --- a/src/NadekoBot/Common/Collections/ConcurrentHashSet.cs +++ b/src/NadekoBot/Common/Collections/ConcurrentHashSet.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable // License MIT // Source: https://github.com/i3arnon/ConcurrentHashSet @@ -7,12 +7,12 @@ using System.Diagnostics; namespace System.Collections.Generic; /// -/// Represents a thread-safe hash-based unique collection. +/// Represents a thread-safe hash-based unique collection. /// /// The type of the items in the collection. /// -/// All public members of are thread-safe and may be used -/// concurrently from multiple threads. +/// All public members of are thread-safe and may be used +/// concurrently from multiple threads. /// [DebuggerDisplay("Count = {Count}")] public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection @@ -20,53 +20,16 @@ public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection _comparer; - private readonly bool _growLockArray; - - private int _budget; - private volatile Tables _tables; - private static int DefaultConcurrencyLevel => PlatformHelper.ProcessorCount; /// - /// Gets the number of items contained in the . + /// Gets a value that indicates whether the is empty. /// - /// The number of items contained in the . - /// Count has snapshot semantics and represents the number of items in the - /// at the moment when Count was accessed. - public int Count - { - get - { - var count = 0; - var acquiredLocks = 0; - try - { - AcquireAllLocks(ref acquiredLocks); - - for (var i = 0; i < _tables.CountPerLock.Length; i++) - { - count += _tables.CountPerLock[i]; - } - } - finally - { - ReleaseLocks(0, acquiredLocks); - } - - return count; - } - } - - /// - /// Gets a value that indicates whether the is empty. - /// - /// true if the is empty; otherwise, - /// false. + /// + /// true if the is empty; otherwise, + /// false. + /// public bool IsEmpty { get @@ -77,12 +40,8 @@ public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection.IsReadOnly + => false; + /// - /// Initializes a new instance of the - /// class that is empty, has the default concurrency level, has the default initial capacity, and - /// uses the default comparer for the item type. + /// Gets the number of items contained in the + /// + /// . + /// + /// + /// The number of items contained in the + /// + /// . + /// + /// + /// Count has snapshot semantics and represents the number of items in the + /// + /// at the moment when Count was accessed. + /// + public int Count + { + get + { + var count = 0; + var acquiredLocks = 0; + try + { + AcquireAllLocks(ref acquiredLocks); + + for (var i = 0; i < _tables.CountPerLock.Length; i++) count += _tables.CountPerLock[i]; + } + finally + { + ReleaseLocks(0, acquiredLocks); + } + + return count; + } + } + + private readonly IEqualityComparer _comparer; + private readonly bool _growLockArray; + + private int _budget; + private volatile Tables _tables; + + /// + /// Initializes a new instance of the + /// + /// class that is empty, has the default concurrency level, has the default initial capacity, and + /// uses the default comparer for the item type. /// public ConcurrentHashSet() - : this(DefaultConcurrencyLevel, - DefaultCapacity, - true, - EqualityComparer.Default) + : this(DefaultConcurrencyLevel, DefaultCapacity, true, EqualityComparer.Default) { } /// - /// Initializes a new instance of the - /// class that is empty, has the specified concurrency level and capacity, and uses the default - /// comparer for the item type. + /// Initializes a new instance of the + /// + /// class that is empty, has the specified concurrency level and capacity, and uses the default + /// comparer for the item type. /// - /// The estimated number of threads that will update the - /// concurrently. - /// The initial number of elements that the - /// can contain. - /// is - /// less than 1. - /// is less than - /// 0. + /// + /// The estimated number of threads that will update the + /// concurrently. + /// + /// + /// The initial number of elements that the + /// + /// can contain. + /// + /// + /// is + /// less than 1. + /// + /// + /// is less than + /// 0. + /// public ConcurrentHashSet(int concurrencyLevel, int capacity) - : this(concurrencyLevel, - capacity, - false, - EqualityComparer.Default) + : this(concurrencyLevel, capacity, false, EqualityComparer.Default) { } /// - /// Initializes a new instance of the - /// class that contains elements copied from the specified , has the default concurrency - /// level, has the default initial capacity, and uses the default comparer for the item type. + /// Initializes a new instance of the + /// class that contains elements copied from the specified + /// + /// , has the default concurrency + /// level, has the default initial capacity, and uses the default comparer for the item type. /// - /// The whose elements are copied to - /// the new - /// . - /// is a null reference. + /// + /// The + /// + /// whose elements are copied to + /// the new + /// . + /// + /// is a null reference. public ConcurrentHashSet(IEnumerable collection) : this(collection, EqualityComparer.Default) { } /// - /// Initializes a new instance of the - /// class that is empty, has the specified concurrency level and capacity, and uses the specified - /// . + /// Initializes a new instance of the + /// class that is empty, has the specified concurrency level and capacity, and uses the specified + /// . /// - /// The - /// implementation to use when comparing items. - /// is a null reference. + /// + /// The + /// implementation to use when comparing items. + /// + /// is a null reference. public ConcurrentHashSet(IEqualityComparer comparer) - : this(DefaultConcurrencyLevel, - DefaultCapacity, - true, - comparer) + : this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer) { } /// - /// Initializes a new instance of the - /// class that contains elements copied from the specified , has the default concurrency level, has the default - /// initial capacity, and uses the specified - /// . + /// Initializes a new instance of the + /// class that contains elements copied from the specified + /// + /// , has the default concurrency level, has the default + /// initial capacity, and uses the specified + /// . /// - /// The whose elements are copied to - /// the new - /// . - /// The - /// implementation to use when comparing items. - /// is a null reference - /// (Nothing in Visual Basic). -or- - /// is a null reference (Nothing in Visual Basic). + /// + /// The + /// + /// whose elements are copied to + /// the new + /// . + /// + /// + /// The + /// implementation to use when comparing items. + /// + /// + /// is a null reference + /// (Nothing in Visual Basic). -or- + /// is a null reference (Nothing in Visual Basic). /// public ConcurrentHashSet(IEnumerable collection, IEqualityComparer comparer) : this(comparer) @@ -189,30 +215,33 @@ public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection - /// Initializes a new instance of the - /// class that contains elements copied from the specified , - /// has the specified concurrency level, has the specified initial capacity, and uses the specified - /// . + /// Initializes a new instance of the + /// class that contains elements copied from the specified , + /// has the specified concurrency level, has the specified initial capacity, and uses the specified + /// . /// - /// The estimated number of threads that will update the - /// concurrently. - /// The whose elements are copied to the new - /// . - /// The implementation to use - /// when comparing items. + /// + /// The estimated number of threads that will update the + /// concurrently. + /// + /// + /// The whose elements are copied to the new + /// . + /// + /// + /// The implementation to use + /// when comparing items. + /// /// - /// is a null reference. - /// -or- - /// is a null reference. + /// is a null reference. + /// -or- + /// is a null reference. /// /// - /// is less than 1. + /// is less than 1. /// public ConcurrentHashSet(int concurrencyLevel, IEnumerable collection, IEqualityComparer comparer) - : this(concurrencyLevel, - DefaultCapacity, - false, - comparer) + : this(concurrencyLevel, DefaultCapacity, false, comparer) { if (collection is null) throw new ArgumentNullException(nameof(collection)); if (comparer is null) throw new ArgumentNullException(nameof(comparer)); @@ -221,47 +250,49 @@ public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection - /// Initializes a new instance of the - /// class that is empty, has the specified concurrency level, has the specified initial capacity, and - /// uses the specified . + /// Initializes a new instance of the + /// class that is empty, has the specified concurrency level, has the specified initial capacity, and + /// uses the specified . /// - /// The estimated number of threads that will update the - /// concurrently. - /// The initial number of elements that the - /// can contain. - /// The - /// implementation to use when comparing items. + /// + /// The estimated number of threads that will update the + /// concurrently. + /// + /// + /// The initial number of elements that the + /// + /// can contain. + /// + /// + /// The + /// implementation to use when comparing items. + /// /// - /// is less than 1. -or- - /// is less than 0. + /// is less than 1. -or- + /// is less than 0. /// - /// is a null reference. + /// is a null reference. public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer comparer) - : this(concurrencyLevel, - capacity, - false, - comparer) + : this(concurrencyLevel, capacity, false, comparer) { } - private ConcurrentHashSet(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer comparer) + private ConcurrentHashSet( + int concurrencyLevel, + int capacity, + bool growLockArray, + IEqualityComparer comparer) { if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(nameof(concurrencyLevel)); if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity)); // The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard // any buckets. - if (capacity < concurrencyLevel) - { - capacity = concurrencyLevel; - } + if (capacity < concurrencyLevel) capacity = concurrencyLevel; var locks = new object[concurrencyLevel]; - for (var i = 0; i < locks.Length; i++) - { - locks[i] = new(); - } + for (var i = 0; i < locks.Length; i++) locks[i] = new(); var countPerLock = new int[locks.Length]; var buckets = new Node[capacity]; @@ -273,18 +304,7 @@ public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection - /// Adds the specified item to the . - /// - /// The item to add. - /// true if the items was added to the - /// successfully; false if it already exists. - /// The - /// contains too many items. - public bool Add(T item) - => AddInternal(item, _comparer.GetHashCode(item), true); - - /// - /// Removes all items from the . + /// Removes all items from the . /// public void Clear() { @@ -304,11 +324,11 @@ public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection - /// Determines whether the contains the specified - /// item. + /// Determines whether the contains the specified + /// item. /// - /// The item to locate in the . - /// true if the contains the item; otherwise, false. + /// The item to locate in the . + /// true if the contains the item; otherwise, false. public bool Contains(T item) { var hashcode = _comparer.GetHashCode(item); @@ -324,11 +344,8 @@ public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection - /// Attempts to remove the item from the . - /// - /// The item to remove. - /// true if an item was removed successfully; otherwise, false. - public bool TryRemove(T item) + void ICollection.Add(T item) + => Add(item); + + void ICollection.CopyTo(T[] array, int arrayIndex) { - var hashcode = _comparer.GetHashCode(item); - while (true) + if (array is null) throw new ArgumentNullException(nameof(array)); + if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + + var locksAcquired = 0; + try { - var tables = _tables; + AcquireAllLocks(ref locksAcquired); - GetBucketAndLockNo(hashcode, - out var bucketNo, - out var lockNo, - tables.Buckets.Length, - tables.Locks.Length); + var count = 0; - lock (tables.Locks[lockNo]) - { - // If the table just got resized, we may not be holding the right lock, and must retry. - // This should be a rare occurrence. - if (tables != _tables) - { - continue; - } + for (var i = 0; i < _tables.Locks.Length && count >= 0; i++) count += _tables.CountPerLock[i]; - Node previous = null; - for (var current = tables.Buckets[bucketNo]; current != null; current = current.Next) - { - Debug.Assert((previous is null && current == tables.Buckets[bucketNo]) || previous.Next == current); + if (array.Length - count < arrayIndex || count < 0) //"count" itself or "count + arrayIndex" can overflow + throw new ArgumentException( + "The index is equal to or greater than the length of the array, or the number of elements in the set is greater than the available space from index to the end of the destination array."); - if (hashcode == current.Hashcode && - _comparer.Equals(current.Item, item)) - { - if (previous is null) - { - Volatile.Write(ref tables.Buckets[bucketNo], current.Next); - } - else - { - previous.Next = current.Next; - } - - tables.CountPerLock[lockNo]--; - return true; - } - - previous = current; - } - } - - return false; + CopyToItems(array, arrayIndex); + } + finally + { + ReleaseLocks(0, locksAcquired); } } + bool ICollection.Remove(T item) + => TryRemove(item); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - /// Returns an enumerator that iterates through the . - /// An enumerator for the . + /// + /// Returns an enumerator that iterates through the + /// + /// . + /// + /// An enumerator for the . /// - /// The enumerator returned from the collection is safe to use concurrently with - /// reads and writes to the collection, however it does not represent a moment-in-time snapshot - /// of the collection. The contents exposed through the enumerator may contain modifications - /// made to the collection after was called. + /// The enumerator returned from the collection is safe to use concurrently with + /// reads and writes to the collection, however it does not represent a moment-in-time snapshot + /// of the collection. The contents exposed through the enumerator may contain modifications + /// made to the collection after was called. /// public IEnumerator GetEnumerator() { @@ -421,58 +418,70 @@ public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection.Add(T item) - => Add(item); + /// + /// Adds the specified item to the . + /// + /// The item to add. + /// + /// true if the items was added to the + /// successfully; false if it already exists. + /// + /// + /// The + /// contains too many items. + /// + public bool Add(T item) + => AddInternal(item, _comparer.GetHashCode(item), true); - bool ICollection.IsReadOnly - => false; - - void ICollection.CopyTo(T[] array, int arrayIndex) + /// + /// Attempts to remove the item from the . + /// + /// The item to remove. + /// true if an item was removed successfully; otherwise, false. + public bool TryRemove(T item) { - if (array is null) throw new ArgumentNullException(nameof(array)); - if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - - var locksAcquired = 0; - try + var hashcode = _comparer.GetHashCode(item); + while (true) { - AcquireAllLocks(ref locksAcquired); + var tables = _tables; - var count = 0; + GetBucketAndLockNo(hashcode, out var bucketNo, out var lockNo, tables.Buckets.Length, tables.Locks.Length); - for (var i = 0; i < _tables.Locks.Length && count >= 0; i++) + lock (tables.Locks[lockNo]) { - count += _tables.CountPerLock[i]; + // If the table just got resized, we may not be holding the right lock, and must retry. + // This should be a rare occurrence. + if (tables != _tables) continue; + + Node previous = null; + for (var current = tables.Buckets[bucketNo]; current != null; current = current.Next) + { + Debug.Assert((previous is null && current == tables.Buckets[bucketNo]) || previous.Next == current); + + if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item)) + { + if (previous is null) + Volatile.Write(ref tables.Buckets[bucketNo], current.Next); + else + previous.Next = current.Next; + + tables.CountPerLock[lockNo]--; + return true; + } + + previous = current; + } } - if (array.Length - count < arrayIndex || - count < 0) //"count" itself or "count + arrayIndex" can overflow - { - throw new ArgumentException( - "The index is equal to or greater than the length of the array, or the number of elements in the set is greater than the available space from index to the end of the destination array."); - } - - CopyToItems(array, arrayIndex); - } - finally - { - ReleaseLocks(0, locksAcquired); + return false; } } - bool ICollection.Remove(T item) - => TryRemove(item); - private void InitializeFromCollection(IEnumerable collection) { - foreach (var item in collection) - { - AddInternal(item, _comparer.GetHashCode(item), false); - } + foreach (var item in collection) AddInternal(item, _comparer.GetHashCode(item), false); - if (_budget == 0) - { - _budget = _tables.Buckets.Length / _tables.Locks.Length; - } + if (_budget == 0) _budget = _tables.Buckets.Length / _tables.Locks.Length; } private bool AddInternal(T item, int hashcode, bool acquireLock) @@ -480,11 +489,7 @@ public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection _budget) - { - resizeDesired = true; - } + if (tables.CountPerLock[lockNo] > _budget) resizeDesired = true; } finally { @@ -545,10 +541,7 @@ public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection maxArrayLength) - { - maximizeTableSize = true; - } + if (newLength > maxArrayLength) maximizeTableSize = true; } } catch (OverflowException) @@ -662,15 +645,8 @@ public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection : IReadOnlyCollection, ICollection : IList where T : class, IIndexed { public List Source { get; } - private readonly object _locker = new(); public int Count => Source.Count; @@ -16,8 +15,20 @@ public class IndexedCollection : IList public bool IsReadOnly => false; - public int IndexOf([NotNull] T item) - => item.Index; + public virtual T this[int index] + { + get => Source[index]; + set + { + lock (_locker) + { + value.Index = index; + Source[index] = value; + } + } + } + + private readonly object _locker = new(); public IndexedCollection() => Source = new(); @@ -31,23 +42,8 @@ public class IndexedCollection : IList } } - public void UpdateIndexes() - { - lock (_locker) - { - for (var i = 0; i < Source.Count; i++) - { - if (Source[i].Index != i) - Source[i].Index = i; - } - } - } - - public static implicit operator List(IndexedCollection x) - => x.Source; - - public List ToList() - => Source.ToList(); + public int IndexOf([NotNull] T item) + => item.Index; public IEnumerator GetEnumerator() => Source.GetEnumerator(); @@ -95,10 +91,8 @@ public class IndexedCollection : IList if (Source.Remove(item)) { for (var i = 0; i < Source.Count; i++) - { if (Source[i].Index != i) Source[i].Index = i; - } return true; } @@ -112,10 +106,7 @@ public class IndexedCollection : IList lock (_locker) { Source.Insert(index, item); - for (var i = index; i < Source.Count; i++) - { - Source[i].Index = i; - } + for (var i = index; i < Source.Count; i++) Source[i].Index = i; } } @@ -124,23 +115,23 @@ public class IndexedCollection : IList lock (_locker) { Source.RemoveAt(index); - for (var i = index; i < Source.Count; i++) - { - Source[i].Index = i; - } + for (var i = index; i < Source.Count; i++) Source[i].Index = i; } } - public virtual T this[int index] + public void UpdateIndexes() { - get => Source[index]; - set + lock (_locker) { - lock (_locker) - { - value.Index = index; - Source[index] = value; - } + for (var i = 0; i < Source.Count; i++) + if (Source[i].Index != i) + Source[i].Index = i; } } -} + + public static implicit operator List(IndexedCollection x) + => x.Source; + + public List ToList() + => Source.ToList(); +} \ No newline at end of file diff --git a/src/NadekoBot/Common/CommandData.cs b/src/NadekoBot/Common/CommandData.cs index a20fe3e44..4264e316c 100644 --- a/src/NadekoBot/Common/CommandData.cs +++ b/src/NadekoBot/Common/CommandData.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common; public class CommandData @@ -6,4 +6,4 @@ public class CommandData public string Cmd { get; set; } public string Desc { get; set; } public string[] Usage { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Configs/BotConfig.cs b/src/NadekoBot/Common/Configs/BotConfig.cs index 23558e29a..2801420bb 100644 --- a/src/NadekoBot/Common/Configs/BotConfig.cs +++ b/src/NadekoBot/Common/Configs/BotConfig.cs @@ -1,8 +1,8 @@ #nullable disable -using System.Globalization; using Cloneable; using NadekoBot.Common.Yml; using SixLabors.ImageSharp.PixelFormats; +using System.Globalization; using YamlDotNet.Core; using YamlDotNet.Serialization; @@ -19,16 +19,14 @@ next to the response. The color depends whether the command is completed, errored or in progress (pending) Color settings below are for the color of those lines. To get color's hex, you can go here https://htmlcolorcodes.com/ -and copy the hex code fo your selected color (marked as #)" - )] +and copy the hex code fo your selected color (marked as #)")] public ColorConfig Color { get; set; } [Comment("Default bot language. It has to be in the list of supported languages (.langli)")] public CultureInfo DefaultLocale { get; set; } [Comment(@"Style in which executed commands will show up in the console. -Allowed values: Simple, Normal, None" - )] +Allowed values: Simple, Normal, None")] public ConsoleOutputType ConsoleOutputType { get; set; } // [Comment(@"For what kind of updates will the bot check. @@ -43,21 +41,18 @@ Allowed values: Simple, Normal, None" [Comment( @"Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml), -or all owners? (this might cause the bot to lag if there's a lot of owners specified)" - )] +or all owners? (this might cause the bot to lag if there's a lot of owners specified)")] public bool ForwardToAllOwners { get; set; } [Comment(@"When a user DMs the bot with a message which is not a command they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot. -Supports embeds. How it looks: https://puu.sh/B0BLV.png" - )] +Supports embeds. How it looks: https://puu.sh/B0BLV.png")] [YamlMember(ScalarStyle = ScalarStyle.Literal)] public string DmHelpText { get; set; } [Comment(@"Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response. Case insensitive. -Leave empty to reply with DmHelpText to every DM." - )] +Leave empty to reply with DmHelpText to every DM.")] public List DmHelpTextKeywords { get; set; } [Comment(@"This is the response for the .h command")] @@ -78,29 +73,14 @@ Keep in mind this might break some of your embeds - for example if you have %use it will become invalid, as it will resolve to a list of avatars of grouped users. note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some servers might get hundreds of people join at once. This is used to prevent the bot from getting ratelimited, - and (slightly) reduce the greet spam in those servers." - )] + and (slightly) reduce the greet spam in those servers.")] public bool GroupGreets { get; set; } [Comment(@"Whether the bot will rotate through all specified statuses. This setting can be changed via .rots command. -See RotatingStatuses submodule in Administration." - )] +See RotatingStatuses submodule in Administration.")] public bool RotateStatuses { get; set; } -// [Comment(@"Whether the prefix will be a suffix, or prefix. -// For example, if your prefix is ! you will run a command called 'cash' by typing either -// '!cash @Someone' if your prefixIsSuffix: false or -// 'cash @Someone!' if your prefixIsSuffix: true")] -// public bool PrefixIsSuffix { get; set; } - - // public string Prefixed(string text) => PrefixIsSuffix - // ? text + Prefix - // : Prefix + text; - - public string Prefixed(string text) - => Prefix + text; - public BotConfig() { var color = new ColorConfig(); @@ -149,6 +129,19 @@ See RotatingStatuses submodule in Administration." "can you do" }; } + +// [Comment(@"Whether the prefix will be a suffix, or prefix. +// For example, if your prefix is ! you will run a command called 'cash' by typing either +// '!cash @Someone' if your prefixIsSuffix: false or +// 'cash @Someone!' if your prefixIsSuffix: true")] +// public bool PrefixIsSuffix { get; set; } + + // public string Prefixed(string text) => PrefixIsSuffix + // ? text + Prefix + // : Prefix + text; + + public string Prefixed(string text) + => Prefix + text; } [Cloneable] @@ -188,5 +181,5 @@ public enum ConsoleOutputType { Normal = 0, Simple = 1, - None = 2, -} + None = 2 +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Configs/IConfigSeria.cs b/src/NadekoBot/Common/Configs/IConfigSeria.cs index 7e08bc857..9fb3d5a42 100644 --- a/src/NadekoBot/Common/Configs/IConfigSeria.cs +++ b/src/NadekoBot/Common/Configs/IConfigSeria.cs @@ -1,18 +1,18 @@ namespace NadekoBot.Common.Configs; /// -/// Base interface for available config serializers +/// Base interface for available config serializers /// public interface IConfigSeria { /// - /// Serialize the object to string + /// Serialize the object to string /// public string Serialize(T obj) - where T: notnull; - + where T : notnull; + /// - /// Deserialize string data into an object of the specified type + /// Deserialize string data into an object of the specified type /// public T Deserialize(string data); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Creds.cs b/src/NadekoBot/Common/Creds.cs index a12ac5ce5..78a6a4244 100644 --- a/src/NadekoBot/Common/Creds.cs +++ b/src/NadekoBot/Common/Creds.cs @@ -5,33 +5,6 @@ namespace NadekoBot.Common; public sealed class Creds : IBotCredentials { - public Creds() - { - Version = 1; - Token = string.Empty; - OwnerIds = new List(); - TotalShards = 1; - GoogleApiKey = string.Empty; - Votes = new(string.Empty, - string.Empty, - string.Empty, - string.Empty - ); - Patreon = new(string.Empty, - string.Empty, - string.Empty, - string.Empty - ); - BotListToken = string.Empty; - CleverbotApiKey = string.Empty; - RedisOptions = "localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password="; - Db = new() { Type = "sqlite", ConnectionString = "Data Source=data/NadekoBot.db" }; - - CoordinatorUrl = "http://localhost:3442"; - - RestartCommand = new(); - } - [Comment(@"DO NOT CHANGE")] public int Version { get; set; } @@ -39,28 +12,24 @@ public sealed class Creds : IBotCredentials public string Token { get; set; } [Comment(@"List of Ids of the users who have bot owner permissions -**DO NOT ADD PEOPLE YOU DON'T TRUST**" - )] +**DO NOT ADD PEOPLE YOU DON'T TRUST**")] public ICollection OwnerIds { get; set; } [Comment(@"The number of shards that the bot will running on. -Leave at 1 if you don't know what you're doing." - )] +Leave at 1 if you don't know what you're doing.")] public int TotalShards { get; set; } [Comment( @"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it. Then, go to APIs and Services -> Credentials and click Create credentials -> API key. -Used only for Youtube Data Api (at the moment)." - )] +Used only for Youtube Data Api (at the moment).")] public string GoogleApiKey { get; set; } [Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")] public VotesSettings Votes { get; set; } [Comment(@"Patreon auto reward system settings. -go to https://www.patreon.com/portal -> my clients -> create client" - )] +go to https://www.patreon.com/portal -> my clients -> create client")] public PatreonSettings Patreon { get; set; } [Comment(@"Api key for sending stats to DiscordBotList.")] @@ -76,27 +45,23 @@ go to https://www.patreon.com/portal -> my clients -> create client" public DbOptions Db { get; set; } [Comment(@"Address and port of the coordinator endpoint. Leave empty for default. -Change only if you've changed the coordinator address or port." - )] +Change only if you've changed the coordinator address or port.")] public string CoordinatorUrl { get; set; } - [Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)" - )] + [Comment( + @"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")] public string RapidApiKey { get; set; } [Comment(@"https://locationiq.com api key (register and you will receive the token in the email). -Used only for .time command." - )] +Used only for .time command.")] public string LocationIqApiKey { get; set; } [Comment(@"https://timezonedb.com api key (register and you will receive the token in the email). -Used only for .time command" - )] +Used only for .time command")] public string TimezoneDbApiKey { get; set; } [Comment(@"https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use. -Used for cryptocurrency related commands." - )] +Used for cryptocurrency related commands.")] public string CoinmarketcapApiKey { get; set; } [Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")] @@ -112,10 +77,28 @@ Linux default args: ""NadekoBot.dll -- {0}"" Windows default cmd: NadekoBot.exe - args: {0}" - )] + args: {0}")] public RestartConfig RestartCommand { get; set; } + public Creds() + { + Version = 1; + Token = string.Empty; + OwnerIds = new List(); + TotalShards = 1; + GoogleApiKey = string.Empty; + Votes = new(string.Empty, string.Empty, string.Empty, string.Empty); + Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty); + BotListToken = string.Empty; + CleverbotApiKey = string.Empty; + RedisOptions = "localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password="; + Db = new() { Type = "sqlite", ConnectionString = "Data Source=data/NadekoBot.db" }; + + CoordinatorUrl = "http://localhost:3442"; + + RestartCommand = new(); + } + public class DbOptions { @@ -134,8 +117,7 @@ Windows default public string ClientSecret { get; set; } [Comment( - @"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)" - )] + @"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)")] public string CampaignId { get; set; } public PatreonSettings( @@ -159,24 +141,20 @@ Windows default { [Comment(@"top.gg votes service url This is the url of your instance of the NadekoBot.Votes api -Example: https://votes.my.cool.bot.com" - )] +Example: https://votes.my.cool.bot.com")] public string TopggServiceUrl { get; set; } [Comment(@"Authorization header value sent to the TopGG service url with each request -This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file" - )] +This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file")] public string TopggKey { get; set; } [Comment(@"discords.com votes service url This is the url of your instance of the NadekoBot.Votes api -Example: https://votes.my.cool.bot.com" - )] +Example: https://votes.my.cool.bot.com")] public string DiscordsServiceUrl { get; set; } [Comment(@"Authorization header value sent to the Discords service url with each request -This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file" - )] +This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file")] public string DiscordsKey { get; set; } public VotesSettings() @@ -229,14 +207,14 @@ This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsett public class RestartConfig { - public RestartConfig(string cmd, string args) - { - this.Cmd = cmd; - this.Args = args; - } - public string Cmd { get; set; } public string Args { get; set; } + + public RestartConfig(string cmd, string args) + { + Cmd = cmd; + Args = args; + } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/DownloadTracker.cs b/src/NadekoBot/Common/DownloadTracker.cs index 9ccdcd1bd..f0cd72a92 100644 --- a/src/NadekoBot/Common/DownloadTracker.cs +++ b/src/NadekoBot/Common/DownloadTracker.cs @@ -7,7 +7,7 @@ public class DownloadTracker : INService private readonly SemaphoreSlim _downloadUsersSemaphore = new(1, 1); /// - /// Ensures all users on the specified guild were downloaded within the last hour. + /// Ensures all users on the specified guild were downloaded within the last hour. /// /// Guild to check and potentially download users from /// Task representing download state @@ -21,8 +21,7 @@ public class DownloadTracker : INService // download once per hour at most var added = LastDownloads.AddOrUpdate(guild.Id, now, - (_, old) => now - old > TimeSpan.FromHours(1) ? now : old - ); + (_, old) => now - old > TimeSpan.FromHours(1) ? now : old); // means that this entry was just added - download the users if (added == now) @@ -33,4 +32,4 @@ public class DownloadTracker : INService _downloadUsersSemaphore.Release(); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Helpers.cs b/src/NadekoBot/Common/Helpers.cs index 446216134..b092a0068 100644 --- a/src/NadekoBot/Common/Helpers.cs +++ b/src/NadekoBot/Common/Helpers.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common; public static class Helpers @@ -10,4 +10,4 @@ public static class Helpers Environment.Exit(exitCode); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/IBotCredentials.cs b/src/NadekoBot/Common/IBotCredentials.cs index 1de96cb65..da1ddeb74 100644 --- a/src/NadekoBot/Common/IBotCredentials.cs +++ b/src/NadekoBot/Common/IBotCredentials.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot; public interface IBotCredentials @@ -27,4 +27,4 @@ public class RestartConfig { public string Cmd { get; set; } public string Args { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/ICloneable.cs b/src/NadekoBot/Common/ICloneable.cs index 95010347f..ba6a3cc11 100644 --- a/src/NadekoBot/Common/ICloneable.cs +++ b/src/NadekoBot/Common/ICloneable.cs @@ -1,8 +1,8 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common; public interface ICloneable where T : new() { public T Clone(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/IEmbedBuilder.cs b/src/NadekoBot/Common/IEmbedBuilder.cs index 5434bcbb7..2eca32430 100644 --- a/src/NadekoBot/Common/IEmbedBuilder.cs +++ b/src/NadekoBot/Common/IEmbedBuilder.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot; public interface IEmbedBuilder @@ -19,5 +19,5 @@ public enum EmbedColor { Ok, Pending, - Error, -} + Error +} \ No newline at end of file diff --git a/src/NadekoBot/Common/INadekoCommandOptions.cs b/src/NadekoBot/Common/INadekoCommandOptions.cs index be44c6618..fda2b4caf 100644 --- a/src/NadekoBot/Common/INadekoCommandOptions.cs +++ b/src/NadekoBot/Common/INadekoCommandOptions.cs @@ -1,7 +1,7 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common; public interface INadekoCommandOptions { void NormalizeOptions(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/IPlaceholderProvider.cs b/src/NadekoBot/Common/IPlaceholderProvider.cs index b33d8c026..e6073c31e 100644 --- a/src/NadekoBot/Common/IPlaceholderProvider.cs +++ b/src/NadekoBot/Common/IPlaceholderProvider.cs @@ -4,4 +4,4 @@ namespace NadekoBot.Common; public interface IPlaceholderProvider { public IEnumerable<(string Name, Func Func)> GetPlaceholders(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/ImageUrls.cs b/src/NadekoBot/Common/ImageUrls.cs index 2c022508e..9cdd8d801 100644 --- a/src/NadekoBot/Common/ImageUrls.cs +++ b/src/NadekoBot/Common/ImageUrls.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using NadekoBot.Common.Yml; namespace NadekoBot.Common; @@ -46,4 +46,4 @@ public class ImageUrls { public Uri Bg { get; set; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/JsonConverters/CultureInfoConverter.cs b/src/NadekoBot/Common/JsonConverters/CultureInfoConverter.cs index 896ab5137..452bc7bb0 100644 --- a/src/NadekoBot/Common/JsonConverters/CultureInfoConverter.cs +++ b/src/NadekoBot/Common/JsonConverters/CultureInfoConverter.cs @@ -11,4 +11,4 @@ public class CultureInfoConverter : JsonConverter public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options) => writer.WriteStringValue(value.Name); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/JsonConverters/Rgba32Converter.cs b/src/NadekoBot/Common/JsonConverters/Rgba32Converter.cs index 40091d746..b37c7219e 100644 --- a/src/NadekoBot/Common/JsonConverters/Rgba32Converter.cs +++ b/src/NadekoBot/Common/JsonConverters/Rgba32Converter.cs @@ -11,4 +11,4 @@ public class Rgba32Converter : JsonConverter public override void Write(Utf8JsonWriter writer, Rgba32 value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToHex()); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Kwum.cs b/src/NadekoBot/Common/Kwum.cs index 0b27c1615..5ae773126 100644 --- a/src/NadekoBot/Common/Kwum.cs +++ b/src/NadekoBot/Common/Kwum.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using System.Runtime.CompilerServices; namespace NadekoBot.Common; @@ -10,8 +10,8 @@ namespace NadekoBot.Common; public readonly struct kwum : IEquatable #pragma warning restore IDE1006 { - private readonly int _value; private const string VALID_CHARACTERS = "23456789abcdefghijkmnpqrstuvwxyz"; + private readonly int _value; public kwum(int num) => _value = num; @@ -24,10 +24,6 @@ public readonly struct kwum : IEquatable _value = InternalCharToValue(c); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int InternalCharToValue(in char c) - => VALID_CHARACTERS.IndexOf(c); - public kwum(in ReadOnlySpan input) { _value = 0; @@ -41,6 +37,10 @@ public readonly struct kwum : IEquatable } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int InternalCharToValue(in char c) + => VALID_CHARACTERS.IndexOf(c); + public static bool TryParse(in ReadOnlySpan input, out kwum value) { value = default; @@ -96,4 +96,4 @@ public readonly struct kwum : IEquatable public override int GetHashCode() => _value.GetHashCode(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/LbOpts.cs b/src/NadekoBot/Common/LbOpts.cs index d47beb8e0..26a4c28f8 100644 --- a/src/NadekoBot/Common/LbOpts.cs +++ b/src/NadekoBot/Common/LbOpts.cs @@ -1,18 +1,14 @@ -#nullable disable +#nullable disable using CommandLine; namespace NadekoBot.Common; public class LbOpts : INadekoCommandOptions { - [Option('c', - "clean", - Default = false, - HelpText = "Only show users who are on the server." - )] + [Option('c', "clean", Default = false, HelpText = "Only show users who are on the server.")] public bool Clean { get; set; } public void NormalizeOptions() { } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/LoginErrorHandler.cs b/src/NadekoBot/Common/LoginErrorHandler.cs index 032149241..56a970a13 100644 --- a/src/NadekoBot/Common/LoginErrorHandler.cs +++ b/src/NadekoBot/Common/LoginErrorHandler.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using System.Net; using System.Runtime.CompilerServices; @@ -16,23 +16,20 @@ public class LoginErrorHandler switch (ex.HttpCode) { case HttpStatusCode.Unauthorized: - Log.Error("Your bot token is wrong.\n" + - "You can find the bot token under the Bot tab in the developer page.\n" + - "Fix your token in the credentials file and restart the bot" - ); + Log.Error("Your bot token is wrong.\n" + + "You can find the bot token under the Bot tab in the developer page.\n" + + "Fix your token in the credentials file and restart the bot"); break; case HttpStatusCode.BadRequest: - Log.Error("Something has been incorrectly formatted in your credentials file.\n" + - "Use the JSON Guide as reference to fix it and restart the bot" - ); + Log.Error("Something has been incorrectly formatted in your credentials file.\n" + + "Use the JSON Guide as reference to fix it and restart the bot"); Log.Error("If you are on Linux, make sure Redis is installed and running"); break; case HttpStatusCode.RequestTimeout: - Log.Error("The request timed out. Make sure you have no external program blocking the bot " + - "from connecting to the internet" - ); + Log.Error("The request timed out. Make sure you have no external program blocking the bot " + + "from connecting to the internet"); break; case HttpStatusCode.ServiceUnavailable: @@ -41,9 +38,8 @@ public class LoginErrorHandler break; case HttpStatusCode.TooManyRequests: - Log.Error("Your bot has been ratelimited by Discord. Please, try again later.\n" + - "Global ratelimits usually last for an hour" - ); + Log.Error("Your bot has been ratelimited by Discord. Please, try again later.\n" + + "Global ratelimits usually last for an hour"); break; default: @@ -53,4 +49,4 @@ public class LoginErrorHandler Log.Fatal(ex, "Fatal error occurred while loading credentials"); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/ModuleBehaviors/IEarlyBehavior.cs b/src/NadekoBot/Common/ModuleBehaviors/IEarlyBehavior.cs index 28884689a..9236a22d4 100644 --- a/src/NadekoBot/Common/ModuleBehaviors/IEarlyBehavior.cs +++ b/src/NadekoBot/Common/ModuleBehaviors/IEarlyBehavior.cs @@ -1,10 +1,10 @@ namespace NadekoBot.Common.ModuleBehaviors; /// -/// Implemented by modules which block execution before anything is executed +/// Implemented by modules which block execution before anything is executed /// public interface IEarlyBehavior { int Priority { get; } Task RunBehavior(IGuild guild, IUserMessage msg); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/ModuleBehaviors/IInputTransformer.cs b/src/NadekoBot/Common/ModuleBehaviors/IInputTransformer.cs index 46e4627e8..c8446f633 100644 --- a/src/NadekoBot/Common/ModuleBehaviors/IInputTransformer.cs +++ b/src/NadekoBot/Common/ModuleBehaviors/IInputTransformer.cs @@ -7,4 +7,4 @@ public interface IInputTransformer IMessageChannel channel, IUser user, string input); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/ModuleBehaviors/ILateBlocker.cs b/src/NadekoBot/Common/ModuleBehaviors/ILateBlocker.cs index 6603e5ffe..a9e8c617a 100644 --- a/src/NadekoBot/Common/ModuleBehaviors/ILateBlocker.cs +++ b/src/NadekoBot/Common/ModuleBehaviors/ILateBlocker.cs @@ -5,4 +5,4 @@ public interface ILateBlocker public int Priority { get; } Task TryBlockLate(ICommandContext context, string moduleName, CommandInfo command); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/ModuleBehaviors/ILateExecutor.cs b/src/NadekoBot/Common/ModuleBehaviors/ILateExecutor.cs index 7d855751f..d4ce7bccf 100644 --- a/src/NadekoBot/Common/ModuleBehaviors/ILateExecutor.cs +++ b/src/NadekoBot/Common/ModuleBehaviors/ILateExecutor.cs @@ -1,9 +1,9 @@ namespace NadekoBot.Common.ModuleBehaviors; /// -/// Last thing to be executed, won't stop further executions +/// Last thing to be executed, won't stop further executions /// public interface ILateExecutor { Task LateExecute(IGuild guild, IUserMessage msg); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/ModuleBehaviors/IReadyExecutor.cs b/src/NadekoBot/Common/ModuleBehaviors/IReadyExecutor.cs index fcd553664..78640b4d6 100644 --- a/src/NadekoBot/Common/ModuleBehaviors/IReadyExecutor.cs +++ b/src/NadekoBot/Common/ModuleBehaviors/IReadyExecutor.cs @@ -1,13 +1,13 @@ namespace NadekoBot.Common.ModuleBehaviors; /// -/// All services which need to execute something after -/// the bot is ready should implement this interface +/// All services which need to execute something after +/// the bot is ready should implement this interface /// public interface IReadyExecutor { /// - /// Executed when bot is ready + /// Executed when bot is ready /// public Task OnReadyAsync(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/NadekoModule.cs b/src/NadekoBot/Common/NadekoModule.cs index d0f4d7256..5fb4ab536 100644 --- a/src/NadekoBot/Common/NadekoModule.cs +++ b/src/NadekoBot/Common/NadekoModule.cs @@ -1,13 +1,13 @@ #nullable disable using System.Globalization; + // ReSharper disable InconsistentNaming namespace NadekoBot.Modules; -[UsedImplicitly(ImplicitUseTargetFlags.Default | - ImplicitUseTargetFlags.WithInheritors | - ImplicitUseTargetFlags.WithMembers -)] +[UsedImplicitly(ImplicitUseTargetFlags.Default + | ImplicitUseTargetFlags.WithInheritors + | ImplicitUseTargetFlags.WithMembers)] public abstract class NadekoModule : ModuleBase { protected CultureInfo Culture { get; set; } @@ -36,12 +36,7 @@ public abstract class NadekoModule : ModuleBase string error, string url = null, string footer = null) - => ctx.Channel.SendErrorAsync(_eb, - title, - error, - url, - footer - ); + => ctx.Channel.SendErrorAsync(_eb, title, error, url, footer); public Task SendConfirmAsync(string text) => ctx.Channel.SendConfirmAsync(_eb, text); @@ -51,12 +46,7 @@ public abstract class NadekoModule : ModuleBase string text, string url = null, string footer = null) - => ctx.Channel.SendConfirmAsync(_eb, - title, - text, - url, - footer - ); + => ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer); public Task SendPendingAsync(string text) => ctx.Channel.SendPendingAsync(_eb, text); @@ -81,8 +71,7 @@ public abstract class NadekoModule : ModuleBase public async Task PromptUserConfirmAsync(IEmbedBuilder embed) { - embed.WithPendingColor() - .WithFooter("yes/no"); + embed.WithPendingColor().WithFooter("yes/no"); var msg = await ctx.Channel.EmbedAsync(embed); try @@ -90,11 +79,8 @@ public abstract class NadekoModule : ModuleBase var input = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id); input = input?.ToUpperInvariant(); - if (input != "YES" && - input != "Y") - { + if (input != "YES" && input != "Y") return false; - } return true; } @@ -113,11 +99,8 @@ public abstract class NadekoModule : ModuleBase { dsc.MessageReceived += MessageReceived; - if (await Task.WhenAny(userInputTask.Task, Task.Delay(10000)) != - userInputTask.Task) - { + if (await Task.WhenAny(userInputTask.Task, Task.Delay(10000)) != userInputTask.Task) return null; - } return await userInputTask.Task; } @@ -129,23 +112,17 @@ public abstract class NadekoModule : ModuleBase Task MessageReceived(SocketMessage arg) { var _ = Task.Run(() => - { - if (arg is not SocketUserMessage userMsg || - userMsg.Channel is not ITextChannel || - userMsg.Author.Id != userId || - userMsg.Channel.Id != channelId) - { - return Task.CompletedTask; - } - - if (userInputTask.TrySetResult(arg.Content)) - { - userMsg.DeleteAfter(1); - } - + { + if (arg is not SocketUserMessage userMsg + || userMsg.Channel is not ITextChannel + || userMsg.Author.Id != userId + || userMsg.Channel.Id != channelId) return Task.CompletedTask; - } - ); + + if (userInputTask.TrySetResult(arg.Content)) userMsg.DeleteAfter(1); + + return Task.CompletedTask; + }); return Task.CompletedTask; } } @@ -162,4 +139,4 @@ public abstract class NadekoSubmodule : NadekoModule public abstract class NadekoSubmodule : NadekoModule { -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/NadekoRandom.cs b/src/NadekoBot/Common/NadekoRandom.cs index 59d17ef29..7094171b2 100644 --- a/src/NadekoBot/Common/NadekoRandom.cs +++ b/src/NadekoBot/Common/NadekoRandom.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using System.Security.Cryptography; namespace NadekoBot.Common; @@ -8,7 +8,6 @@ public class NadekoRandom : Random private readonly RandomNumberGenerator _rng; public NadekoRandom() - : base() => _rng = RandomNumberGenerator.Create(); public override int Next() @@ -67,4 +66,4 @@ public class NadekoRandom : Random _rng.GetBytes(bytes); return BitConverter.ToDouble(bytes, 0); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/NoPublicBotPrecondition.cs b/src/NadekoBot/Common/NoPublicBotPrecondition.cs index 271e71590..8eca3e031 100644 --- a/src/NadekoBot/Common/NoPublicBotPrecondition.cs +++ b/src/NadekoBot/Common/NoPublicBotPrecondition.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using System.Diagnostics.CodeAnalysis; namespace NadekoBot.Common; @@ -7,7 +7,10 @@ namespace NadekoBot.Common; [SuppressMessage("Style", "IDE0022:Use expression body for methods")] public sealed class NoPublicBotAttribute : PreconditionAttribute { - public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) + public override Task CheckPermissionsAsync( + ICommandContext context, + CommandInfo command, + IServiceProvider services) { #if GLOBAL_NADEKO return Task.FromResult(PreconditionResult.FromError("Not available on the public bot. To learn how to selfhost a private bot, click [here](https://nadekobot.readthedocs.io/en/latest/).")); @@ -15,4 +18,4 @@ public sealed class NoPublicBotAttribute : PreconditionAttribute return Task.FromResult(PreconditionResult.FromSuccess()); #endif } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/OldImageUrls.cs b/src/NadekoBot/Common/OldImageUrls.cs index 4e31905c0..dfae53834 100644 --- a/src/NadekoBot/Common/OldImageUrls.cs +++ b/src/NadekoBot/Common/OldImageUrls.cs @@ -44,4 +44,4 @@ public class OldImageUrls { public Uri Bg { get; set; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/OptionsParser.cs b/src/NadekoBot/Common/OptionsParser.cs index 39b62fcfa..6fc6fcf1c 100644 --- a/src/NadekoBot/Common/OptionsParser.cs +++ b/src/NadekoBot/Common/OptionsParser.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using CommandLine; namespace NadekoBot.Common; @@ -13,13 +13,12 @@ public static class OptionsParser where T : INadekoCommandOptions { using var p = new Parser(x => - { - x.HelpWriter = null; - } - ); + { + x.HelpWriter = null; + }); var res = p.ParseArguments(args); options = res.MapResult(x => x, x => options); options.NormalizeOptions(); return (options, res.Tag == ParserResultType.Parsed); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/OsuMapData.cs b/src/NadekoBot/Common/OsuMapData.cs index 004c005f0..73a17afa4 100644 --- a/src/NadekoBot/Common/OsuMapData.cs +++ b/src/NadekoBot/Common/OsuMapData.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common; public class OsuMapData @@ -6,4 +6,4 @@ public class OsuMapData public string Title { get; set; } public string Artist { get; set; } public string Version { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/OsuUserBets.cs b/src/NadekoBot/Common/OsuUserBets.cs index a2b26521d..d81aaf1f2 100644 --- a/src/NadekoBot/Common/OsuUserBets.cs +++ b/src/NadekoBot/Common/OsuUserBets.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using Newtonsoft.Json; namespace NadekoBot.Common; @@ -55,4 +55,4 @@ public class OsuUserBests [JsonProperty("replay_available")] public string ReplayAvailable { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/PlatformHelper.cs b/src/NadekoBot/Common/PlatformHelper.cs index c067cd47d..19592e5d2 100644 --- a/src/NadekoBot/Common/PlatformHelper.cs +++ b/src/NadekoBot/Common/PlatformHelper.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common; public static class PlatformHelper @@ -13,8 +13,7 @@ public static class PlatformHelper get { var now = Environment.TickCount; - if (processorCount == 0 || - now - lastProcessorCountRefreshTicks >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS) + if (processorCount == 0 || now - lastProcessorCountRefreshTicks >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS) { processorCount = Environment.ProcessorCount; lastProcessorCountRefreshTicks = now; @@ -23,4 +22,4 @@ public static class PlatformHelper return processorCount; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Pokemon/PokemonNameId.cs b/src/NadekoBot/Common/Pokemon/PokemonNameId.cs index 5219a4196..09f065cff 100644 --- a/src/NadekoBot/Common/Pokemon/PokemonNameId.cs +++ b/src/NadekoBot/Common/Pokemon/PokemonNameId.cs @@ -1,8 +1,8 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common.Pokemon; public class PokemonNameId { public int Id { get; set; } public string Name { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Pokemon/SearchPokemon.cs b/src/NadekoBot/Common/Pokemon/SearchPokemon.cs index e189a5a7d..2cdc88d3a 100644 --- a/src/NadekoBot/Common/Pokemon/SearchPokemon.cs +++ b/src/NadekoBot/Common/Pokemon/SearchPokemon.cs @@ -1,10 +1,24 @@ -#nullable disable +#nullable disable using Newtonsoft.Json; namespace NadekoBot.Common.Pokemon; public class SearchPokemon { + [JsonProperty("num")] + public int Id { get; set; } + + public string Species { get; set; } + public string[] Types { get; set; } + public GenderRatioClass GenderRatio { get; set; } + public BaseStatsClass BaseStats { get; set; } + public Dictionary Abilities { get; set; } + public float HeightM { get; set; } + public float WeightKg { get; set; } + public string Color { get; set; } + public string[] Evos { get; set; } + public string[] EggGroups { get; set; } + public class GenderRatioClass { public float M { get; set; } @@ -24,18 +38,4 @@ public class SearchPokemon => $@"💚**HP:** {Hp,-4} ⚔**ATK:** {Atk,-4} 🛡**DEF:** {Def,-4} ✨**SPA:** {Spa,-4} 🎇**SPD:** {Spd,-4} 💨**SPE:** {Spe,-4}"; } - - [JsonProperty("num")] - public int Id { get; set; } - - public string Species { get; set; } - public string[] Types { get; set; } - public GenderRatioClass GenderRatio { get; set; } - public BaseStatsClass BaseStats { get; set; } - public Dictionary Abilities { get; set; } - public float HeightM { get; set; } - public float WeightKg { get; set; } - public string Color { get; set; } - public string[] Evos { get; set; } - public string[] EggGroups { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Pokemon/SearchPokemonAbility.cs b/src/NadekoBot/Common/Pokemon/SearchPokemonAbility.cs index 2d4d83ba1..1ae22c901 100644 --- a/src/NadekoBot/Common/Pokemon/SearchPokemonAbility.cs +++ b/src/NadekoBot/Common/Pokemon/SearchPokemonAbility.cs @@ -7,4 +7,4 @@ public class SearchPokemonAbility public string ShortDesc { get; set; } public string Name { get; set; } public float Rating { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/PubSub/EventPubSub.cs b/src/NadekoBot/Common/PubSub/EventPubSub.cs index 5ca371664..d9291e17c 100644 --- a/src/NadekoBot/Common/PubSub/EventPubSub.cs +++ b/src/NadekoBot/Common/PubSub/EventPubSub.cs @@ -33,15 +33,10 @@ public class EventPubSub : IPubSub lock (_locker) { if (_actions.TryGetValue(key.Key, out var actions)) - { // if this class ever gets used, this needs to be properly implemented // 1. ignore all valuetasks which are completed // 2. run all other tasks in parallel - return actions - .SelectMany(kvp => kvp.Value) - .Select(action => action(data).AsTask()) - .WhenAll(); - } + return actions.SelectMany(kvp => kvp.Value).Select(action => action(data).AsTask()).WhenAll(); return Task.CompletedTask; } @@ -53,7 +48,6 @@ public class EventPubSub : IPubSub { // get subscriptions for this action if (_actions.TryGetValue(key.Key, out var actions)) - { // get subscriptions which have the same action hash code // note: having this as a list allows for multiple subscriptions of // the same insance's/static method @@ -71,15 +65,11 @@ public class EventPubSub : IPubSub // if our dictionary has no more elements after // removing the entry // it's safe to remove it from the key's subscriptions - if (actions.Count == 0) - { - _actions.Remove(key.Key); - } + if (actions.Count == 0) _actions.Remove(key.Key); } } - } return Task.CompletedTask; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/PubSub/IPubSub.cs b/src/NadekoBot/Common/PubSub/IPubSub.cs index a9154d5fd..1b4a9311d 100644 --- a/src/NadekoBot/Common/PubSub/IPubSub.cs +++ b/src/NadekoBot/Common/PubSub/IPubSub.cs @@ -4,4 +4,4 @@ public interface IPubSub { public Task Pub(in TypedKey key, TData data); public Task Sub(in TypedKey key, Func action); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/PubSub/ISeria.cs b/src/NadekoBot/Common/PubSub/ISeria.cs index 7a538aaef..4847327bb 100644 --- a/src/NadekoBot/Common/PubSub/ISeria.cs +++ b/src/NadekoBot/Common/PubSub/ISeria.cs @@ -4,4 +4,4 @@ public interface ISeria { byte[] Serialize(T data); T? Deserialize(byte[]? data); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/PubSub/JsonSeria.cs b/src/NadekoBot/Common/PubSub/JsonSeria.cs index eda11f9ed..63d14c210 100644 --- a/src/NadekoBot/Common/PubSub/JsonSeria.cs +++ b/src/NadekoBot/Common/PubSub/JsonSeria.cs @@ -1,5 +1,5 @@ -using System.Text.Json; using NadekoBot.Common.JsonConverters; +using System.Text.Json; namespace NadekoBot.Common; @@ -7,7 +7,7 @@ public class JsonSeria : ISeria { private readonly JsonSerializerOptions _serializerOptions = new() { - Converters = { new Rgba32Converter(), new CultureInfoConverter(), } + Converters = { new Rgba32Converter(), new CultureInfoConverter() } }; public byte[] Serialize(T data) @@ -20,4 +20,4 @@ public class JsonSeria : ISeria return JsonSerializer.Deserialize(data, _serializerOptions); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/PubSub/RedisPubSub.cs b/src/NadekoBot/Common/PubSub/RedisPubSub.cs index 29c997377..4ffdd35b9 100644 --- a/src/NadekoBot/Common/PubSub/RedisPubSub.cs +++ b/src/NadekoBot/Common/PubSub/RedisPubSub.cs @@ -4,9 +4,9 @@ namespace NadekoBot.Common; public sealed class RedisPubSub : IPubSub { + private readonly IBotCredentials _creds; private readonly ConnectionMultiplexer _multi; private readonly ISeria _serializer; - private readonly IBotCredentials _creds; public RedisPubSub(ConnectionMultiplexer multi, ISeria serializer, IBotCredentials creds) { @@ -19,7 +19,7 @@ public sealed class RedisPubSub : IPubSub { var serialized = _serializer.Serialize(data); return _multi.GetSubscriber() - .PublishAsync($"{_creds.RedisKey()}:{key.Key}", serialized, CommandFlags.FireAndForget); + .PublishAsync($"{_creds.RedisKey()}:{key.Key}", serialized, CommandFlags.FireAndForget); } public Task Sub(in TypedKey key, Func action) @@ -41,4 +41,4 @@ public sealed class RedisPubSub : IPubSub return _multi.GetSubscriber().SubscribeAsync($"{_creds.RedisKey()}:{eventName}", OnSubscribeHandler); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/PubSub/TypedKey.cs b/src/NadekoBot/Common/PubSub/TypedKey.cs index b7d2cc054..177efdf16 100644 --- a/src/NadekoBot/Common/PubSub/TypedKey.cs +++ b/src/NadekoBot/Common/PubSub/TypedKey.cs @@ -27,4 +27,4 @@ public readonly struct TypedKey public override string ToString() => Key; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/PubSub/YamlSeria.cs b/src/NadekoBot/Common/PubSub/YamlSeria.cs index b885ee666..42c60842d 100644 --- a/src/NadekoBot/Common/PubSub/YamlSeria.cs +++ b/src/NadekoBot/Common/PubSub/YamlSeria.cs @@ -1,19 +1,18 @@ -using System.Text.RegularExpressions; -using NadekoBot.Common.Yml; using NadekoBot.Common.Configs; +using NadekoBot.Common.Yml; +using System.Text.RegularExpressions; using YamlDotNet.Serialization; namespace NadekoBot.Common; public class YamlSeria : IConfigSeria { - private readonly ISerializer _serializer; - private readonly IDeserializer _deserializer; - private static readonly Regex _codePointRegex = new(@"(\\U(?[a-zA-Z0-9]{8})|\\u(?[a-zA-Z0-9]{4})|\\x(?[a-zA-Z0-9]{2}))", - RegexOptions.Compiled - ); + RegexOptions.Compiled); + + private readonly IDeserializer _deserializer; + private readonly ISerializer _serializer; public YamlSeria() { @@ -22,7 +21,7 @@ public class YamlSeria : IConfigSeria } public string Serialize(T obj) - where T: notnull + where T : notnull { var escapedOutput = _serializer.Serialize(obj); var output = _codePointRegex.Replace(escapedOutput, @@ -31,11 +30,10 @@ public class YamlSeria : IConfigSeria var str = me.Groups["code"].Value; var newString = YamlHelper.UnescapeUnicodeCodePoint(str); return newString; - } - ); + }); return output; } public T Deserialize(string data) => _deserializer.Deserialize(data); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Replacements/ReplacementBuilder.cs b/src/NadekoBot/Common/Replacements/ReplacementBuilder.cs index fa3abf8b0..9210cc191 100644 --- a/src/NadekoBot/Common/Replacements/ReplacementBuilder.cs +++ b/src/NadekoBot/Common/Replacements/ReplacementBuilder.cs @@ -7,11 +7,11 @@ namespace NadekoBot.Common; public class ReplacementBuilder { private static readonly Regex _rngRegex = new("%rng(?:(?(?:-)?\\d+)-(?(?:-)?\\d+))?%", - RegexOptions.Compiled - ); + RegexOptions.Compiled); + + private readonly ConcurrentDictionary> _regex = new(); private readonly ConcurrentDictionary> _reps = new(); - private readonly ConcurrentDictionary> _regex = new(); public ReplacementBuilder() => WithRngRegex(); @@ -21,14 +21,10 @@ public class ReplacementBuilder IMessageChannel ch, SocketGuild g, DiscordSocketClient client) - => this.WithUser(usr).WithChannel(ch).WithServer(client, g).WithClient(client); + => WithUser(usr).WithChannel(ch).WithServer(client, g).WithClient(client); public ReplacementBuilder WithDefault(ICommandContext ctx) - => WithDefault(ctx.User, - ctx.Channel, - ctx.Guild as SocketGuild, - (DiscordSocketClient)ctx.Client - ); + => WithDefault(ctx.User, ctx.Channel, ctx.Guild as SocketGuild, (DiscordSocketClient)ctx.Client); public ReplacementBuilder WithMention(DiscordSocketClient client) { @@ -45,8 +41,7 @@ public class ReplacementBuilder _reps.TryAdd("%bot.name%", () => client.CurrentUser.Username); _reps.TryAdd("%bot.fullname%", () => client.CurrentUser.ToString()); _reps.TryAdd("%bot.time%", - () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) - ); + () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials())); _reps.TryAdd("%bot.discrim%", () => client.CurrentUser.Discriminator); _reps.TryAdd("%bot.id%", () => client.CurrentUser.Id.ToString()); _reps.TryAdd("%bot.avatar%", () => client.CurrentUser.RealAvatarUrl()?.ToString()); @@ -68,15 +63,12 @@ public class ReplacementBuilder { var to = TimeZoneInfo.Local; if (g != null) - { if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz)) to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local; - } - return TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, to).ToString("HH:mm ") + - to.StandardName.GetInitials(); - } - ); + return TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, to).ToString("HH:mm ") + + to.StandardName.GetInitials(); + }); return this; } @@ -107,17 +99,14 @@ public class ReplacementBuilder _reps.TryAdd("%user.avatar%", () => string.Join(" ", users.Select(user => user.RealAvatarUrl()?.ToString()))); _reps.TryAdd("%user.id%", () => string.Join(" ", users.Select(user => user.Id.ToString()))); _reps.TryAdd("%user.created_time%", - () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("HH:mm"))) - ); + () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("HH:mm")))); _reps.TryAdd("%user.created_date%", - () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("dd.MM.yyyy"))) - ); + () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("dd.MM.yyyy")))); _reps.TryAdd("%user.joined_time%", - () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-")) - ); + () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-"))); _reps.TryAdd("%user.joined_date%", - () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-")) - ); + () => string.Join(" ", + users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-"))); return this; } @@ -140,16 +129,14 @@ public class ReplacementBuilder if (!int.TryParse(match.Groups["to"].ToString(), out var to)) to = 0; - if (from == 0 && - to == 0) + if (from == 0 && to == 0) return rng.Next(0, 11).ToString(); if (from >= to) return string.Empty; return rng.Next(from, to + 1).ToString(); - } - ); + }); return this; } @@ -165,13 +152,9 @@ public class ReplacementBuilder public ReplacementBuilder WithProviders(IEnumerable phProviders) { foreach (var provider in phProviders) - { - foreach (var ovr in provider.GetPlaceholders()) - { - _reps.TryAdd(ovr.Name, ovr.Func); - } - } + foreach (var ovr in provider.GetPlaceholders()) + _reps.TryAdd(ovr.Name, ovr.Func); return this; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Replacements/Replacer.cs b/src/NadekoBot/Common/Replacements/Replacer.cs index ce253a42b..eb1eee6d7 100644 --- a/src/NadekoBot/Common/Replacements/Replacer.cs +++ b/src/NadekoBot/Common/Replacements/Replacer.cs @@ -1,12 +1,12 @@ -#nullable disable +#nullable disable using System.Text.RegularExpressions; namespace NadekoBot.Common; public class Replacer { - private readonly IEnumerable<(string Key, Func Text)> _replacements; private readonly IEnumerable<(Regex Regex, Func Replacement)> _regex; + private readonly IEnumerable<(string Key, Func Text)> _replacements; public Replacer(IEnumerable<(string, Func)> replacements, IEnumerable<(Regex, Func)> regex) { @@ -20,15 +20,10 @@ public class Replacer return input; foreach (var (key, text) in _replacements) - { if (input.Contains(key)) input = input.Replace(key, text(), StringComparison.InvariantCulture); - } - foreach (var item in _regex) - { - input = item.Regex.Replace(input, m => item.Replacement(m)); - } + foreach (var item in _regex) input = item.Regex.Replace(input, m => item.Replacement(m)); return input; } @@ -56,12 +51,10 @@ public class Replacer Url = Replace(embedData.Url) }; if (embedData.Author != null) - { newEmbedData.Author = new() { Name = Replace(embedData.Author.Name), IconUrl = Replace(embedData.Author.IconUrl) }; - } if (embedData.Fields != null) { @@ -79,15 +72,13 @@ public class Replacer } if (embedData.Footer != null) - { newEmbedData.Footer = new() { Text = Replace(embedData.Footer.Text), IconUrl = Replace(embedData.Footer.IconUrl) }; - } newEmbedData.Color = embedData.Color; return newEmbedData; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/RequireObjectPropertiesContractResolver.cs b/src/NadekoBot/Common/RequireObjectPropertiesContractResolver.cs index 1f57ac1d0..ed17ba834 100644 --- a/src/NadekoBot/Common/RequireObjectPropertiesContractResolver.cs +++ b/src/NadekoBot/Common/RequireObjectPropertiesContractResolver.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -12,4 +12,4 @@ public class RequireObjectPropertiesContractResolver : DefaultContractResolver contract.ItemRequired = Required.DisallowNull; return contract; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/ShmartNumber.cs b/src/NadekoBot/Common/ShmartNumber.cs index 72dcc4f5a..aa6b48c34 100644 --- a/src/NadekoBot/Common/ShmartNumber.cs +++ b/src/NadekoBot/Common/ShmartNumber.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common; public struct ShmartNumber : IEquatable @@ -38,4 +38,4 @@ public struct ShmartNumber : IEquatable public static bool operator !=(ShmartNumber left, ShmartNumber right) => !(left == right); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/SmartText/SmartEmbedText.cs b/src/NadekoBot/Common/SmartText/SmartEmbedText.cs index 91176f0fe..9a6534570 100644 --- a/src/NadekoBot/Common/SmartText/SmartEmbedText.cs +++ b/src/NadekoBot/Common/SmartText/SmartEmbedText.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot; public sealed record SmartEmbedText : SmartText @@ -17,12 +17,14 @@ public sealed record SmartEmbedText : SmartText public uint Color { get; set; } = 7458112; public bool IsValid - => !string.IsNullOrWhiteSpace(Title) || !string.IsNullOrWhiteSpace(Description) || - !string.IsNullOrWhiteSpace(Url) || !string.IsNullOrWhiteSpace(Thumbnail) || - !string.IsNullOrWhiteSpace(Image) || - (Footer != null && - (!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl))) || - Fields is { Length: > 0 }; + => !string.IsNullOrWhiteSpace(Title) + || !string.IsNullOrWhiteSpace(Description) + || !string.IsNullOrWhiteSpace(Url) + || !string.IsNullOrWhiteSpace(Thumbnail) + || !string.IsNullOrWhiteSpace(Image) + || (Footer != null + && (!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl))) + || Fields is { Length: > 0 }; public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null) { @@ -40,9 +42,11 @@ public sealed record SmartEmbedText : SmartText if (eb.Fields.Length > 0) set.Fields = eb.Fields.Select(field - => new SmartTextEmbedField() { Inline = field.Inline, Name = field.Name, Value = field.Value, } - ) - .ToArray(); + => new SmartTextEmbedField + { + Inline = field.Inline, Name = field.Name, Value = field.Value + }) + .ToArray(); set.Color = eb.Color?.RawValue ?? 0; return set; @@ -58,31 +62,24 @@ public sealed record SmartEmbedText : SmartText if (!string.IsNullOrWhiteSpace(Description)) embed.WithDescription(Description); - if (Url != null && - Uri.IsWellFormedUriString(Url, UriKind.Absolute)) + if (Url != null && Uri.IsWellFormedUriString(Url, UriKind.Absolute)) embed.WithUrl(Url); if (Footer != null) - { embed.WithFooter(efb => - { - efb.WithText(Footer.Text); - if (Uri.IsWellFormedUriString(Footer.IconUrl, UriKind.Absolute)) - efb.WithIconUrl(Footer.IconUrl); - } - ); - } + { + efb.WithText(Footer.Text); + if (Uri.IsWellFormedUriString(Footer.IconUrl, UriKind.Absolute)) + efb.WithIconUrl(Footer.IconUrl); + }); - if (Thumbnail != null && - Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute)) + if (Thumbnail != null && Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute)) embed.WithThumbnailUrl(Thumbnail); - if (Image != null && - Uri.IsWellFormedUriString(Image, UriKind.Absolute)) + if (Image != null && Uri.IsWellFormedUriString(Image, UriKind.Absolute)) embed.WithImageUrl(Image); - if (Author != null && - !string.IsNullOrWhiteSpace(Author.Name)) + if (Author != null && !string.IsNullOrWhiteSpace(Author.Name)) { if (!Uri.IsWellFormedUriString(Author.IconUrl, UriKind.Absolute)) Author.IconUrl = null; @@ -93,14 +90,9 @@ public sealed record SmartEmbedText : SmartText } if (Fields != null) - { foreach (var f in Fields) - { - if (!string.IsNullOrWhiteSpace(f.Name) && - !string.IsNullOrWhiteSpace(f.Value)) + if (!string.IsNullOrWhiteSpace(f.Name) && !string.IsNullOrWhiteSpace(f.Value)) embed.AddField(f.Name, f.Value, f.Inline); - } - } return embed; } @@ -108,12 +100,10 @@ public sealed record SmartEmbedText : SmartText public void NormalizeFields() { if (Fields is { Length: > 0 }) - { foreach (var f in Fields) { f.Name = f.Name.TrimTo(256); f.Value = f.Value.TrimTo(1024); } - } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/SmartText/SmartPlainText.cs b/src/NadekoBot/Common/SmartText/SmartPlainText.cs index dd67a9e99..e2d00db80 100644 --- a/src/NadekoBot/Common/SmartText/SmartPlainText.cs +++ b/src/NadekoBot/Common/SmartText/SmartPlainText.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot; public sealed record SmartPlainText : SmartText @@ -16,4 +16,4 @@ public sealed record SmartPlainText : SmartText public override string ToString() => Text; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/SmartText/SmartText.cs b/src/NadekoBot/Common/SmartText/SmartText.cs index 61109c8c4..991656c6b 100644 --- a/src/NadekoBot/Common/SmartText/SmartText.cs +++ b/src/NadekoBot/Common/SmartText/SmartText.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using Newtonsoft.Json; namespace NadekoBot; @@ -29,11 +29,8 @@ public abstract record SmartText public static SmartText CreateFrom(string input) { - if (string.IsNullOrWhiteSpace(input) || - !input.TrimStart().StartsWith("{")) - { + if (string.IsNullOrWhiteSpace(input) || !input.TrimStart().StartsWith("{")) return new SmartPlainText(input); - } try { @@ -41,13 +38,10 @@ public abstract record SmartText if (smartEmbedText is null) throw new(); - + smartEmbedText.NormalizeFields(); - if (!smartEmbedText.IsValid) - { - return new SmartPlainText(input); - } + if (!smartEmbedText.IsValid) return new SmartPlainText(input); return smartEmbedText; } @@ -56,4 +50,4 @@ public abstract record SmartText return new SmartPlainText(input); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/SmartText/SmartTextEmbedAuthor.cs b/src/NadekoBot/Common/SmartText/SmartTextEmbedAuthor.cs index 366b3747f..51e8369ea 100644 --- a/src/NadekoBot/Common/SmartText/SmartTextEmbedAuthor.cs +++ b/src/NadekoBot/Common/SmartText/SmartTextEmbedAuthor.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using Newtonsoft.Json; namespace NadekoBot; @@ -6,7 +6,9 @@ namespace NadekoBot; public class SmartTextEmbedAuthor { public string Name { get; set; } + [JsonProperty("icon_url")] public string IconUrl { get; set; } + public string Url { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/SmartText/SmartTextEmbedField.cs b/src/NadekoBot/Common/SmartText/SmartTextEmbedField.cs index 04d0e476c..9a59020cd 100644 --- a/src/NadekoBot/Common/SmartText/SmartTextEmbedField.cs +++ b/src/NadekoBot/Common/SmartText/SmartTextEmbedField.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot; public class SmartTextEmbedField @@ -6,4 +6,4 @@ public class SmartTextEmbedField public string Name { get; set; } public string Value { get; set; } public bool Inline { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/SmartText/SmartTextEmbedFooter.cs b/src/NadekoBot/Common/SmartText/SmartTextEmbedFooter.cs index 0d2eff8a6..374b8d3d1 100644 --- a/src/NadekoBot/Common/SmartText/SmartTextEmbedFooter.cs +++ b/src/NadekoBot/Common/SmartText/SmartTextEmbedFooter.cs @@ -8,6 +8,7 @@ namespace NadekoBot; public class SmartTextEmbedFooter { public string Text { get; set; } + [JsonProperty("icon_url")] public string IconUrl { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/SocketMessageEventWrapper.cs b/src/NadekoBot/Common/SocketMessageEventWrapper.cs index b07e6c745..8af7ff474 100644 --- a/src/NadekoBot/Common/SocketMessageEventWrapper.cs +++ b/src/NadekoBot/Common/SocketMessageEventWrapper.cs @@ -1,13 +1,17 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common; public sealed class ReactionEventWrapper : IDisposable { - public IUserMessage Message { get; } public event Action OnReactionAdded = delegate { }; public event Action OnReactionRemoved = delegate { }; public event Action OnReactionsCleared = delegate { }; + public IUserMessage Message { get; } + private readonly DiscordSocketClient _client; + + private bool disposing; + public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg) { Message = msg ?? throw new ArgumentNullException(nameof(msg)); @@ -18,18 +22,25 @@ public sealed class ReactionEventWrapper : IDisposable _client.ReactionsCleared += Discord_ReactionsCleared; } + public void Dispose() + { + if (disposing) + return; + disposing = true; + UnsubAll(); + } + private Task Discord_ReactionsCleared(Cacheable msg, Cacheable channel) { Task.Run(() => + { + try { - try - { - if (msg.Id == Message.Id) - OnReactionsCleared?.Invoke(); - } - catch { } + if (msg.Id == Message.Id) + OnReactionsCleared?.Invoke(); } - ); + catch { } + }); return Task.CompletedTask; } @@ -40,15 +51,14 @@ public sealed class ReactionEventWrapper : IDisposable SocketReaction reaction) { Task.Run(() => + { + try { - try - { - if (msg.Id == Message.Id) - OnReactionRemoved?.Invoke(reaction); - } - catch { } + if (msg.Id == Message.Id) + OnReactionRemoved?.Invoke(reaction); } - ); + catch { } + }); return Task.CompletedTask; } @@ -59,17 +69,16 @@ public sealed class ReactionEventWrapper : IDisposable SocketReaction reaction) { Task.Run(() => + { + try { - try - { - if (msg.Id == Message.Id) - OnReactionAdded?.Invoke(reaction); - } - catch - { - } + if (msg.Id == Message.Id) + OnReactionAdded?.Invoke(reaction); } - ); + catch + { + } + }); return Task.CompletedTask; } @@ -83,15 +92,4 @@ public sealed class ReactionEventWrapper : IDisposable OnReactionRemoved = null; OnReactionsCleared = null; } - - private bool disposing = false; - private readonly DiscordSocketClient _client; - - public void Dispose() - { - if (disposing) - return; - disposing = true; - UnsubAll(); - } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/BotCommandTypeReader.cs b/src/NadekoBot/Common/TypeReaders/BotCommandTypeReader.cs index 180fba46b..005d633dd 100644 --- a/src/NadekoBot/Common/TypeReaders/BotCommandTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/BotCommandTypeReader.cs @@ -5,8 +5,8 @@ namespace NadekoBot.Common.TypeReaders; public sealed class CommandTypeReader : NadekoTypeReader { - private readonly CommandHandler _handler; private readonly CommandService _cmds; + private readonly CommandHandler _handler; public CommandTypeReader(CommandHandler handler, CommandService cmds) { @@ -34,8 +34,8 @@ public sealed class CommandTypeReader : NadekoTypeReader public sealed class CommandOrCrTypeReader : NadekoTypeReader { private readonly CommandService _cmds; - private readonly CustomReactionsService _crs; private readonly CommandHandler _commandHandler; + private readonly CustomReactionsService _crs; public CommandOrCrTypeReader(CommandService cmds, CustomReactionsService crs, CommandHandler commandHandler) { @@ -49,18 +49,12 @@ public sealed class CommandOrCrTypeReader : NadekoTypeReader input = input.ToUpperInvariant(); if (_crs.ReactionExists(context.Guild?.Id, input)) - { return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input, CommandOrCrInfo.Type.Custom)); - } var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(context, input); if (cmd.IsSuccess) - { return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name, - CommandOrCrInfo.Type.Normal - ) - ); - } + CommandOrCrInfo.Type.Normal)); return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found."); } @@ -71,7 +65,7 @@ public class CommandOrCrInfo public enum Type { Normal, - Custom, + Custom } public string Name { get; set; } @@ -82,7 +76,7 @@ public class CommandOrCrInfo public CommandOrCrInfo(string input, Type type) { - this.Name = input; - this.CmdType = type; + Name = input; + CmdType = type; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/EmoteTypeReader.cs b/src/NadekoBot/Common/TypeReaders/EmoteTypeReader.cs index 3b3908f6d..662dec5dc 100644 --- a/src/NadekoBot/Common/TypeReaders/EmoteTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/EmoteTypeReader.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common.TypeReaders; public sealed class EmoteTypeReader : NadekoTypeReader @@ -10,4 +10,4 @@ public sealed class EmoteTypeReader : NadekoTypeReader return Task.FromResult(TypeReaderResult.FromSuccess(emote)); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/GuildDateTimeTypeReader.cs b/src/NadekoBot/Common/TypeReaders/GuildDateTimeTypeReader.cs index 75526728d..3e53f2120 100644 --- a/src/NadekoBot/Common/TypeReaders/GuildDateTimeTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/GuildDateTimeTypeReader.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using NadekoBot.Modules.Administration.Services; namespace NadekoBot.Common.TypeReaders; @@ -15,9 +15,7 @@ public sealed class GuildDateTimeTypeReader : NadekoTypeReader var gdt = Parse(context.Guild.Id, input); if (gdt is null) return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, - "Input string is in an incorrect format." - ) - ); + "Input string is in an incorrect format.")); return Task.FromResult(TypeReaderResult.FromSuccess(gdt)); } @@ -48,4 +46,4 @@ public class GuildDateTime InputTime = inputTime; InputTimeUtc = TimeZoneInfo.ConvertTime(inputTime, Timezone, TimeZoneInfo.Utc); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/GuildTypeReader.cs b/src/NadekoBot/Common/TypeReaders/GuildTypeReader.cs index 2e69afab2..8cf1279ba 100644 --- a/src/NadekoBot/Common/TypeReaders/GuildTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/GuildTypeReader.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common.TypeReaders; public sealed class GuildTypeReader : NadekoTypeReader @@ -12,12 +12,14 @@ public sealed class GuildTypeReader : NadekoTypeReader { input = input.Trim().ToUpperInvariant(); var guilds = _client.Guilds; - var guild = guilds.FirstOrDefault(g => g.Id.ToString().Trim().ToUpperInvariant() == input) ?? //by id + var guild = guilds.FirstOrDefault(g => g.Id.ToString().Trim().ToUpperInvariant() == input) + ?? //by id guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == input); //by name if (guild != null) return Task.FromResult(TypeReaderResult.FromSuccess(guild)); - return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No guild by that name or Id found")); + return Task.FromResult( + TypeReaderResult.FromError(CommandError.ParseFailed, "No guild by that name or Id found")); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/KwumTypeReader.cs b/src/NadekoBot/Common/TypeReaders/KwumTypeReader.cs index 5c76ca1e2..5115d3b57 100644 --- a/src/NadekoBot/Common/TypeReaders/KwumTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/KwumTypeReader.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common.TypeReaders; public sealed class KwumTypeReader : NadekoTypeReader @@ -16,4 +16,4 @@ public sealed class SmartTextTypeReader : NadekoTypeReader { public override Task ReadAsync(ICommandContext ctx, string input) => Task.FromResult(TypeReaderResult.FromSuccess(SmartText.CreateFrom(input))); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/Models/PermissionAction.cs b/src/NadekoBot/Common/TypeReaders/Models/PermissionAction.cs index 807da90bf..476be8b7d 100644 --- a/src/NadekoBot/Common/TypeReaders/Models/PermissionAction.cs +++ b/src/NadekoBot/Common/TypeReaders/Models/PermissionAction.cs @@ -1,25 +1,26 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common.TypeReaders.Models; public class PermissionAction { - public static PermissionAction Enable => new(true); - public static PermissionAction Disable => new(false); + public static PermissionAction Enable + => new(true); + + public static PermissionAction Disable + => new(false); public bool Value { get; } public PermissionAction(bool value) - => this.Value = value; + => Value = value; public override bool Equals(object obj) { - if (obj is null || GetType() != obj.GetType()) - { - return false; - } + if (obj is null || GetType() != obj.GetType()) return false; - return this.Value == ((PermissionAction)obj).Value; + return Value == ((PermissionAction)obj).Value; } - public override int GetHashCode() => Value.GetHashCode(); -} + public override int GetHashCode() + => Value.GetHashCode(); +} \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/Models/StoopidTime.cs b/src/NadekoBot/Common/TypeReaders/Models/StoopidTime.cs index 01fb506f6..fc9cbfab9 100644 --- a/src/NadekoBot/Common/TypeReaders/Models/StoopidTime.cs +++ b/src/NadekoBot/Common/TypeReaders/Models/StoopidTime.cs @@ -1,27 +1,24 @@ -#nullable disable +#nullable disable using System.Text.RegularExpressions; namespace NadekoBot.Common.TypeReaders.Models; public class StoopidTime { - public string Input { get; set; } - public TimeSpan Time { get; set; } - private static readonly Regex _regex = new( @"^(?:(?\d)mo)?(?:(?\d{1,2})w)?(?:(?\d{1,2})d)?(?:(?\d{1,4})h)?(?:(?\d{1,5})m)?(?:(?\d{1,6})s)?$", RegexOptions.Compiled | RegexOptions.Multiline); + public string Input { get; set; } + public TimeSpan Time { get; set; } + private StoopidTime() { } public static StoopidTime FromInput(string input) { var m = _regex.Match(input); - if (m.Length == 0) - { - throw new ArgumentException("Invalid string input format."); - } + if (m.Length == 0) throw new ArgumentException("Invalid string input format."); var output = string.Empty; var namesAndValues = new Dictionary(); @@ -35,29 +32,18 @@ public class StoopidTime continue; } - if (value < 1) - { - throw new ArgumentException($"Invalid {groupName} value."); - } - + if (value < 1) throw new ArgumentException($"Invalid {groupName} value."); + namesAndValues[groupName] = value; output += m.Groups[groupName].Value + " " + groupName + " "; } - var ts = new TimeSpan((30 * namesAndValues["months"]) + - (7 * namesAndValues["weeks"]) + - namesAndValues["days"], + + var ts = new TimeSpan((30 * namesAndValues["months"]) + (7 * namesAndValues["weeks"]) + namesAndValues["days"], namesAndValues["hours"], namesAndValues["minutes"], namesAndValues["seconds"]); - if (ts > TimeSpan.FromDays(90)) - { - throw new ArgumentException("Time is too long."); - } + if (ts > TimeSpan.FromDays(90)) throw new ArgumentException("Time is too long."); - return new() - { - Input = input, - Time = ts, - }; + return new() { Input = input, Time = ts }; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/ModuleTypeReader.cs b/src/NadekoBot/Common/TypeReaders/ModuleTypeReader.cs index 567b5469d..026e87910 100644 --- a/src/NadekoBot/Common/TypeReaders/ModuleTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/ModuleTypeReader.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common.TypeReaders; public sealed class ModuleTypeReader : NadekoTypeReader @@ -12,8 +12,8 @@ public sealed class ModuleTypeReader : NadekoTypeReader { input = input.ToUpperInvariant(); var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()) - .FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input) - ?.Key; + .FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input) + ?.Key; if (module is null) return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found.")); @@ -32,17 +32,16 @@ public sealed class ModuleOrCrTypeReader : NadekoTypeReader { input = input.ToUpperInvariant(); var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()) - .FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input) - ?.Key; - if (module is null && - input != "ACTUALCUSTOMREACTIONS") + .FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input) + ?.Key; + if (module is null && input != "ACTUALCUSTOMREACTIONS") return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found.")); - return Task.FromResult(TypeReaderResult.FromSuccess(new ModuleOrCrInfo { Name = input, })); + return Task.FromResult(TypeReaderResult.FromSuccess(new ModuleOrCrInfo { Name = input })); } } public sealed class ModuleOrCrInfo { public string Name { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/NadekoTypeReader.cs b/src/NadekoBot/Common/TypeReaders/NadekoTypeReader.cs index 60e657751..2d5d26a43 100644 --- a/src/NadekoBot/Common/TypeReaders/NadekoTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/NadekoTypeReader.cs @@ -1,11 +1,11 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common.TypeReaders; -[MeansImplicitUse(ImplicitUseTargetFlags.Default | ImplicitUseTargetFlags.WithInheritors )] +[MeansImplicitUse(ImplicitUseTargetFlags.Default | ImplicitUseTargetFlags.WithInheritors)] public abstract class NadekoTypeReader : TypeReader { public abstract Task ReadAsync(ICommandContext ctx, string input); public override Task ReadAsync(ICommandContext ctx, string input, IServiceProvider services) => ReadAsync(ctx, input); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/PermissionActionTypeReader.cs b/src/NadekoBot/Common/TypeReaders/PermissionActionTypeReader.cs index d151f133a..ef7440f12 100644 --- a/src/NadekoBot/Common/TypeReaders/PermissionActionTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/PermissionActionTypeReader.cs @@ -1,10 +1,10 @@ -#nullable disable +#nullable disable using NadekoBot.Common.TypeReaders.Models; namespace NadekoBot.Common.TypeReaders; /// -/// Used instead of bool for more flexible keywords for true/false only in the permission module +/// Used instead of bool for more flexible keywords for true/false only in the permission module /// public sealed class PermissionActionTypeReader : NadekoTypeReader { @@ -32,7 +32,8 @@ public sealed class PermissionActionTypeReader : NadekoTypeReader { @@ -18,4 +19,4 @@ public sealed class Rgba32TypeReader : NadekoTypeReader return TypeReaderResult.FromError(CommandError.ParseFailed, "Parameter is not a valid color hex."); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/ShmartNumberTypeReader.cs b/src/NadekoBot/Common/TypeReaders/ShmartNumberTypeReader.cs index d190464a8..bdd86823b 100644 --- a/src/NadekoBot/Common/TypeReaders/ShmartNumberTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/ShmartNumberTypeReader.cs @@ -1,12 +1,14 @@ -#nullable disable -using System.Text.RegularExpressions; +#nullable disable using NadekoBot.Db; using NadekoBot.Modules.Gambling.Services; +using NCalc; +using System.Text.RegularExpressions; namespace NadekoBot.Common.TypeReaders; public sealed class ShmartNumberTypeReader : NadekoTypeReader { + private static readonly Regex _percentRegex = new(@"^((?100|\d{1,2})%)$", RegexOptions.Compiled); private readonly DbService _db; private readonly GamblingConfigService _gambling; @@ -33,7 +35,7 @@ public sealed class ShmartNumberTypeReader : NadekoTypeReader return TypeReaderResult.FromSuccess(new ShmartNumber(num, i)); try { - var expr = new NCalc.Expression(i, NCalc.EvaluateOptions.IgnoreCase); + var expr = new Expression(i, EvaluateOptions.IgnoreCase); expr.EvaluateParameter += (str, ev) => EvaluateParam(str, ev, context); var lon = (long)decimal.Parse(expr.Evaluate().ToString()); return TypeReaderResult.FromSuccess(new ShmartNumber(lon, input)); @@ -44,7 +46,7 @@ public sealed class ShmartNumberTypeReader : NadekoTypeReader } } - private void EvaluateParam(string name, NCalc.ParameterArgs args, ICommandContext ctx) + private void EvaluateParam(string name, ParameterArgs args, ICommandContext ctx) { switch (name.ToUpperInvariant()) { @@ -67,8 +69,6 @@ public sealed class ShmartNumberTypeReader : NadekoTypeReader } } - private static readonly Regex _percentRegex = new(@"^((?100|\d{1,2})%)$", RegexOptions.Compiled); - private long Cur(ICommandContext ctx) { using var uow = _db.GetDbContext(); @@ -97,4 +97,4 @@ public sealed class ShmartNumberTypeReader : NadekoTypeReader return false; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/StoopidTimeTypeReader.cs b/src/NadekoBot/Common/TypeReaders/StoopidTimeTypeReader.cs index 9e39dcfce..0f2cdb951 100644 --- a/src/NadekoBot/Common/TypeReaders/StoopidTimeTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/StoopidTimeTypeReader.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using NadekoBot.Common.TypeReaders.Models; namespace NadekoBot.Common.TypeReaders; @@ -19,4 +19,4 @@ public sealed class StoopidTimeTypeReader : NadekoTypeReader return Task.FromResult(TypeReaderResult.FromError(CommandError.Exception, ex.Message)); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Yml/CommentAttribute.cs b/src/NadekoBot/Common/Yml/CommentAttribute.cs index 54862d0f3..e67026474 100644 --- a/src/NadekoBot/Common/Yml/CommentAttribute.cs +++ b/src/NadekoBot/Common/Yml/CommentAttribute.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common.Yml; public class CommentAttribute : Attribute @@ -7,4 +7,4 @@ public class CommentAttribute : Attribute public CommentAttribute(string comment) => Comment = comment; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Yml/CommentGatheringTypeInspector.cs b/src/NadekoBot/Common/Yml/CommentGatheringTypeInspector.cs index 19c637537..db96d7fb6 100644 --- a/src/NadekoBot/Common/Yml/CommentGatheringTypeInspector.cs +++ b/src/NadekoBot/Common/Yml/CommentGatheringTypeInspector.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using YamlDotNet.Core; using YamlDotNet.Serialization; using YamlDotNet.Serialization.TypeInspectors; @@ -17,15 +17,7 @@ public class CommentGatheringTypeInspector : TypeInspectorSkeleton private sealed class CommentsPropertyDescriptor : IPropertyDescriptor { - private readonly IPropertyDescriptor baseDescriptor; - - public CommentsPropertyDescriptor(IPropertyDescriptor baseDescriptor) - { - this.baseDescriptor = baseDescriptor; - Name = baseDescriptor.Name; - } - - public string Name { get; set; } + public string Name { get; } public Type Type => baseDescriptor.Type; @@ -47,6 +39,14 @@ public class CommentGatheringTypeInspector : TypeInspectorSkeleton public bool CanWrite => baseDescriptor.CanWrite; + private readonly IPropertyDescriptor baseDescriptor; + + public CommentsPropertyDescriptor(IPropertyDescriptor baseDescriptor) + { + this.baseDescriptor = baseDescriptor; + Name = baseDescriptor.Name; + } + public void Write(object target, object value) => baseDescriptor.Write(target, value); @@ -62,4 +62,4 @@ public class CommentGatheringTypeInspector : TypeInspectorSkeleton : baseDescriptor.Read(target); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Yml/CommentsObjectDescriptor.cs b/src/NadekoBot/Common/Yml/CommentsObjectDescriptor.cs index c27f4e631..21c6270cf 100644 --- a/src/NadekoBot/Common/Yml/CommentsObjectDescriptor.cs +++ b/src/NadekoBot/Common/Yml/CommentsObjectDescriptor.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using YamlDotNet.Core; using YamlDotNet.Serialization; @@ -6,15 +6,7 @@ namespace NadekoBot.Common.Yml; public sealed class CommentsObjectDescriptor : IObjectDescriptor { - private readonly IObjectDescriptor innerDescriptor; - - public CommentsObjectDescriptor(IObjectDescriptor innerDescriptor, string comment) - { - this.innerDescriptor = innerDescriptor; - this.Comment = comment; - } - - public string Comment { get; private set; } + public string Comment { get; } public object Value => innerDescriptor.Value; @@ -27,4 +19,12 @@ public sealed class CommentsObjectDescriptor : IObjectDescriptor public ScalarStyle ScalarStyle => innerDescriptor.ScalarStyle; -} + + private readonly IObjectDescriptor innerDescriptor; + + public CommentsObjectDescriptor(IObjectDescriptor innerDescriptor, string comment) + { + this.innerDescriptor = innerDescriptor; + Comment = comment; + } +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Yml/CommentsObjectGraphVisitor.cs b/src/NadekoBot/Common/Yml/CommentsObjectGraphVisitor.cs index e9effdba2..840780484 100644 --- a/src/NadekoBot/Common/Yml/CommentsObjectGraphVisitor.cs +++ b/src/NadekoBot/Common/Yml/CommentsObjectGraphVisitor.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Serialization; @@ -15,12 +15,10 @@ public class CommentsObjectGraphVisitor : ChainedObjectGraphVisitor public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context) { - if (value is CommentsObjectDescriptor commentsDescriptor && - !string.IsNullOrWhiteSpace(commentsDescriptor.Comment)) - { + if (value is CommentsObjectDescriptor commentsDescriptor + && !string.IsNullOrWhiteSpace(commentsDescriptor.Comment)) context.Emit(new Comment(commentsDescriptor.Comment.Replace("\n", "\n# "), false)); - } return base.EnterMapping(key, value, context); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Yml/MultilineScalarFlowStyleEmitter.cs b/src/NadekoBot/Common/Yml/MultilineScalarFlowStyleEmitter.cs index 5e502dd3b..a3d830845 100644 --- a/src/NadekoBot/Common/Yml/MultilineScalarFlowStyleEmitter.cs +++ b/src/NadekoBot/Common/Yml/MultilineScalarFlowStyleEmitter.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using YamlDotNet.Core; using YamlDotNet.Serialization; using YamlDotNet.Serialization.EventEmitters; @@ -19,12 +19,12 @@ public class MultilineScalarFlowStyleEmitter : ChainedEventEmitter var value = eventInfo.Source.Value as string; if (!string.IsNullOrEmpty(value)) { - var isMultiLine = value.IndexOfAny(new char[] { '\r', '\n', '\x85', '\x2028', '\x2029' }) >= 0; + var isMultiLine = value.IndexOfAny(new[] { '\r', '\n', '\x85', '\x2028', '\x2029' }) >= 0; if (isMultiLine) - eventInfo = new(eventInfo.Source) { Style = ScalarStyle.Literal, }; + eventInfo = new(eventInfo.Source) { Style = ScalarStyle.Literal }; } } nextEmitter.Emit(eventInfo, emitter); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Yml/Rgba32Converter.cs b/src/NadekoBot/Common/Yml/Rgba32Converter.cs index 53cfe3fff..13b2ddb12 100644 --- a/src/NadekoBot/Common/Yml/Rgba32Converter.cs +++ b/src/NadekoBot/Common/Yml/Rgba32Converter.cs @@ -1,6 +1,6 @@ -#nullable disable -using System.Globalization; +#nullable disable using SixLabors.ImageSharp.PixelFormats; +using System.Globalization; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Serialization; @@ -44,4 +44,4 @@ public class CultureInfoConverter : IYamlTypeConverter var ci = (CultureInfo)value; emitter.Emit(new Scalar(ci.Name)); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Yml/UriConverter.cs b/src/NadekoBot/Common/Yml/UriConverter.cs index 6488e9f3c..3b62f3f6a 100644 --- a/src/NadekoBot/Common/Yml/UriConverter.cs +++ b/src/NadekoBot/Common/Yml/UriConverter.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Serialization; @@ -22,4 +22,4 @@ public class UriConverter : IYamlTypeConverter var uri = (Uri)value; emitter.Emit(new Scalar(uri.ToString())); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Yml/Yaml.cs b/src/NadekoBot/Common/Yml/Yaml.cs index 3a9e0c043..25fdc9a55 100644 --- a/src/NadekoBot/Common/Yml/Yaml.cs +++ b/src/NadekoBot/Common/Yml/Yaml.cs @@ -1,5 +1,6 @@ -#nullable disable +#nullable disable using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; namespace NadekoBot.Common.Yml; @@ -7,21 +8,21 @@ public class Yaml { public static ISerializer Serializer => new SerializerBuilder().WithTypeInspector(inner => new CommentGatheringTypeInspector(inner)) - .WithEmissionPhaseObjectGraphVisitor(args => new CommentsObjectGraphVisitor(args.InnerVisitor)) - .WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args)) - .WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance) - .WithIndentedSequences() - .WithTypeConverter(new Rgba32Converter()) - .WithTypeConverter(new CultureInfoConverter()) - .WithTypeConverter(new UriConverter()) - .Build(); + .WithEmissionPhaseObjectGraphVisitor(args + => new CommentsObjectGraphVisitor(args.InnerVisitor)) + .WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args)) + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithIndentedSequences() + .WithTypeConverter(new Rgba32Converter()) + .WithTypeConverter(new CultureInfoConverter()) + .WithTypeConverter(new UriConverter()) + .Build(); public static IDeserializer Deserializer - => new DeserializerBuilder() - .WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance) - .WithTypeConverter(new Rgba32Converter()) - .WithTypeConverter(new CultureInfoConverter()) - .WithTypeConverter(new UriConverter()) - .IgnoreUnmatchedProperties() - .Build(); -} + => new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeConverter(new Rgba32Converter()) + .WithTypeConverter(new CultureInfoConverter()) + .WithTypeConverter(new UriConverter()) + .IgnoreUnmatchedProperties() + .Build(); +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Yml/YamlHelper.cs b/src/NadekoBot/Common/Yml/YamlHelper.cs index 39a42b609..f5841e5fb 100644 --- a/src/NadekoBot/Common/Yml/YamlHelper.cs +++ b/src/NadekoBot/Common/Yml/YamlHelper.cs @@ -1,12 +1,12 @@ -#nullable disable +#nullable disable namespace NadekoBot.Common.Yml; public class YamlHelper { // https://github.com/aaubry/YamlDotNet/blob/0f4cc205e8b2dd8ef6589d96de32bf608a687c6f/YamlDotNet/Core/Scanner.cs#L1687 /// - /// This is modified code from yamldotnet's repo which handles parsing unicode code points - /// it is needed as yamldotnet doesn't support unescaped unicode characters + /// This is modified code from yamldotnet's repo which handles parsing unicode code points + /// it is needed as yamldotnet doesn't support unescaped unicode characters /// /// Unicode code point /// Actual character @@ -18,20 +18,14 @@ public class YamlHelper foreach (var c in point) { - if (!IsHex(c)) - { - return point; - } + if (!IsHex(c)) return point; character = (character << 4) + AsHex(c); } // Check the value and write the character. - if (character is (>= 0xD800 and <= 0xDFFF) or > 0x10FFFF) - { - return point; - } + if (character is (>= 0xD800 and <= 0xDFFF) or > 0x10FFFF) return point; return char.ConvertFromUtf32(character); } @@ -41,16 +35,10 @@ public class YamlHelper public static int AsHex(char c) { - if (c <= '9') - { - return c - '0'; - } + if (c <= '9') return c - '0'; - if (c <= 'F') - { - return c - 'A' + 10; - } + if (c <= 'F') return c - 'A' + 10; return c - 'a' + 10; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/ClubExtensions.cs b/src/NadekoBot/Db/Extensions/ClubExtensions.cs index 64731b651..2ccb9e33e 100644 --- a/src/NadekoBot/Db/Extensions/ClubExtensions.cs +++ b/src/NadekoBot/Db/Extensions/ClubExtensions.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using Microsoft.EntityFrameworkCore; using NadekoBot.Db.Models; @@ -8,12 +8,12 @@ public static class ClubExtensions { private static IQueryable Include(this DbSet clubs) => clubs.Include(x => x.Owner) - .Include(x => x.Applicants) - .ThenInclude(x => x.User) - .Include(x => x.Bans) - .ThenInclude(x => x.User) - .Include(x => x.Users) - .AsQueryable(); + .Include(x => x.Applicants) + .ThenInclude(x => x.User) + .Include(x => x.Bans) + .ThenInclude(x => x.User) + .Include(x => x.Users) + .AsQueryable(); public static ClubInfo GetByOwner(this DbSet clubs, ulong userId) => Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId); @@ -29,9 +29,9 @@ public static class ClubExtensions => Include(clubs).FirstOrDefault(c => c.Name.ToUpper() == name.ToUpper() && c.Discrim == discrim); public static int GetNextDiscrim(this DbSet clubs, string name) - => Include(clubs).Where(x => x.Name.ToUpper() == name.ToUpper()).Select(x => x.Discrim).DefaultIfEmpty().Max() + - 1; + => Include(clubs).Where(x => x.Name.ToUpper() == name.ToUpper()).Select(x => x.Discrim).DefaultIfEmpty().Max() + + 1; public static List GetClubLeaderboardPage(this DbSet clubs, int page) => clubs.AsNoTracking().OrderByDescending(x => x.Xp).Skip(page * 9).Take(9).ToList(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/CurrencyTransactionExtensions.cs b/src/NadekoBot/Db/Extensions/CurrencyTransactionExtensions.cs index 8cb1f0da6..22ff3a3bf 100644 --- a/src/NadekoBot/Db/Extensions/CurrencyTransactionExtensions.cs +++ b/src/NadekoBot/Db/Extensions/CurrencyTransactionExtensions.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Database.Models; @@ -8,10 +8,10 @@ public static class CurrencyTransactionExtensions { public static List GetPageFor(this DbSet set, ulong userId, int page) => set.AsQueryable() - .AsNoTracking() - .Where(x => x.UserId == userId) - .OrderByDescending(x => x.DateAdded) - .Skip(15 * page) - .Take(15) - .ToList(); -} + .AsNoTracking() + .Where(x => x.UserId == userId) + .OrderByDescending(x => x.DateAdded) + .Skip(15 * page) + .Take(15) + .ToList(); +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/CustomReactionsExtensions.cs b/src/NadekoBot/Db/Extensions/CustomReactionsExtensions.cs index 02c43e43d..bbdf21291 100644 --- a/src/NadekoBot/Db/Extensions/CustomReactionsExtensions.cs +++ b/src/NadekoBot/Db/Extensions/CustomReactionsExtensions.cs @@ -1,6 +1,6 @@ -#nullable disable -using Microsoft.EntityFrameworkCore; +#nullable disable using LinqToDB; +using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Database.Models; namespace NadekoBot.Db; @@ -15,4 +15,4 @@ public static class CustomReactionsExtensions public static CustomReaction GetByGuildIdAndInput(this DbSet crs, ulong? guildId, string input) => crs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/DbExtensions.cs b/src/NadekoBot/Db/Extensions/DbExtensions.cs index 50bc73023..c69f3e51b 100644 --- a/src/NadekoBot/Db/Extensions/DbExtensions.cs +++ b/src/NadekoBot/Db/Extensions/DbExtensions.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Database.Models; @@ -9,4 +9,4 @@ public static class DbExtensions public static T GetById(this DbSet set, int id) where T : DbEntity => set.FirstOrDefault(x => x.Id == id); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/DiscordUserExtensions.cs b/src/NadekoBot/Db/Extensions/DiscordUserExtensions.cs index da4f89864..a15d84529 100644 --- a/src/NadekoBot/Db/Extensions/DiscordUserExtensions.cs +++ b/src/NadekoBot/Db/Extensions/DiscordUserExtensions.cs @@ -1,8 +1,8 @@ -#nullable disable -using NadekoBot.Db.Models; -using Microsoft.EntityFrameworkCore; +#nullable disable using LinqToDB; using LinqToDB.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using NadekoBot.Db.Models; using NadekoBot.Services.Database; namespace NadekoBot.Db; @@ -16,19 +16,18 @@ public static class DiscordUserExtensions string discrim, string avatarId) => ctx.DiscordUser.ToLinqToDBTable() - .InsertOrUpdate( - () => new() - { - UserId = userId, - Username = username, - Discriminator = discrim, - AvatarId = avatarId, - TotalXp = 0, - CurrencyAmount = 0 - }, - old => new() { Username = username, Discriminator = discrim, AvatarId = avatarId, }, - () => new() { UserId = userId } - ); + .InsertOrUpdate( + () => new() + { + UserId = userId, + Username = username, + Discriminator = discrim, + AvatarId = avatarId, + TotalXp = 0, + CurrencyAmount = 0 + }, + old => new() { Username = username, Discriminator = discrim, AvatarId = avatarId }, + () => new() { UserId = userId }); //temp is only used in updatecurrencystate, so that i don't overwrite real usernames/discrims with Unknown public static DiscordUser GetOrCreateUser( @@ -38,40 +37,22 @@ public static class DiscordUserExtensions string discrim, string avatarId) { - ctx.EnsureUserCreated(userId, - username, - discrim, - avatarId - ); - return ctx.DiscordUser.Include(x => x.Club) - .First(u => u.UserId == userId); + ctx.EnsureUserCreated(userId, username, discrim, avatarId); + return ctx.DiscordUser.Include(x => x.Club).First(u => u.UserId == userId); } public static DiscordUser GetOrCreateUser(this NadekoContext ctx, IUser original) - => ctx.GetOrCreateUser(original.Id, - original.Username, - original.Discriminator, - original.AvatarId - ); + => ctx.GetOrCreateUser(original.Id, original.Username, original.Discriminator, original.AvatarId); public static int GetUserGlobalRank(this DbSet users, ulong id) => users.AsQueryable() - .Where(x => x.TotalXp > - users.AsQueryable() - .Where(y => y.UserId == id) - .Select(y => y.TotalXp) - .FirstOrDefault() - ) - .Count() + - 1; + .Where(x => x.TotalXp + > users.AsQueryable().Where(y => y.UserId == id).Select(y => y.TotalXp).FirstOrDefault()) + .Count() + + 1; public static DiscordUser[] GetUsersXpLeaderboardFor(this DbSet users, int page) - => users.AsQueryable() - .OrderByDescending(x => x.TotalXp) - .Skip(page * 9) - .Take(9) - .AsEnumerable() - .ToArray(); + => users.AsQueryable().OrderByDescending(x => x.TotalXp).Skip(page * 9).Take(9).AsEnumerable().ToArray(); public static List GetTopRichest( this DbSet users, @@ -79,26 +60,19 @@ public static class DiscordUserExtensions int count, int page = 0) => users.AsQueryable() - .Where(c => c.CurrencyAmount > 0 && botId != c.UserId) - .OrderByDescending(c => c.CurrencyAmount) - .Skip(page * 9) - .Take(count) - .ToList(); + .Where(c => c.CurrencyAmount > 0 && botId != c.UserId) + .OrderByDescending(c => c.CurrencyAmount) + .Skip(page * 9) + .Take(count) + .ToList(); public static long GetUserCurrency(this DbSet users, ulong userId) - => users.AsNoTracking() - .FirstOrDefault(x => x.UserId == userId) - ?.CurrencyAmount ?? - 0; + => users.AsNoTracking().FirstOrDefault(x => x.UserId == userId)?.CurrencyAmount ?? 0; public static void RemoveFromMany(this DbSet users, IEnumerable ids) { - var items = users.AsQueryable() - .Where(x => ids.Contains(x.UserId)); - foreach (var item in items) - { - item.CurrencyAmount = 0; - } + var items = users.AsQueryable().Where(x => ids.Contains(x.UserId)); + foreach (var item in items) item.CurrencyAmount = 0; } public static bool TryUpdateCurrencyState( @@ -115,14 +89,12 @@ public static class DiscordUserExtensions // if remove - try to remove if he has more or equal than the amount // and return number of rows > 0 (was there a change) - if (amount < 0 && - !allowNegative) + if (amount < 0 && !allowNegative) { var rows = ctx.Database.ExecuteSqlInterpolated($@" UPDATE DiscordUser SET CurrencyAmount=CurrencyAmount+{amount} -WHERE UserId={userId} AND CurrencyAmount>={-amount};" - ); +WHERE UserId={userId} AND CurrencyAmount>={-amount};"); return rows > 0; } @@ -132,8 +104,7 @@ WHERE UserId={userId} AND CurrencyAmount>={-amount};" var rows = ctx.Database.ExecuteSqlInterpolated($@" UPDATE DiscordUser SET CurrencyAmount=CurrencyAmount+{amount} -WHERE UserId={userId};" - ); +WHERE UserId={userId};"); return rows > 0; } @@ -147,7 +118,6 @@ WHERE UserId={userId};" // just update the amount, there is no new user data if (!updatedUserData) - { ctx.Database.ExecuteSqlInterpolated($@" UPDATE OR IGNORE DiscordUser SET CurrencyAmount=CurrencyAmount+{amount} @@ -155,11 +125,8 @@ WHERE UserId={userId}; INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp) VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0); -" - ); - } +"); else - { ctx.Database.ExecuteSqlInterpolated($@" UPDATE OR IGNORE DiscordUser SET CurrencyAmount=CurrencyAmount+{amount}, @@ -170,9 +137,7 @@ WHERE UserId={userId}; INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp) VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0); -" - ); - } +"); return true; } @@ -182,8 +147,8 @@ VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0); public static decimal GetTopOnePercentCurrency(this DbSet users, ulong botId) => users.AsQueryable() - .Where(x => x.UserId != botId) - .OrderByDescending(x => x.CurrencyAmount) - .Take(users.Count() / 100 == 0 ? 1 : users.Count() / 100) - .Sum(x => x.CurrencyAmount); -} + .Where(x => x.UserId != botId) + .OrderByDescending(x => x.CurrencyAmount) + .Take(users.Count() / 100 == 0 ? 1 : users.Count() / 100) + .Sum(x => x.CurrencyAmount); +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/GuildConfigExtensions.cs b/src/NadekoBot/Db/Extensions/GuildConfigExtensions.cs index d756fd1b1..fbe9fd837 100644 --- a/src/NadekoBot/Db/Extensions/GuildConfigExtensions.cs +++ b/src/NadekoBot/Db/Extensions/GuildConfigExtensions.cs @@ -1,21 +1,22 @@ #nullable disable -using NadekoBot.Services.Database.Models; using Microsoft.EntityFrameworkCore; -using NadekoBot.Services.Database; using NadekoBot.Db.Models; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Db; public static class GuildConfigExtensions { - public class GeneratingChannel - { - public ulong GuildId { get; set; } - public ulong ChannelId { get; set; } - } + private static List DefaultWarnPunishments + => new() + { + new() { Count = 3, Punishment = PunishmentAction.Kick }, + new() { Count = 5, Punishment = PunishmentAction.Ban } + }; /// - /// Gets full stream role settings for the guild with the specified id. + /// Gets full stream role settings for the guild with the specified id. /// /// Db Context /// Id of the guild to get stream role settings for. @@ -24,9 +25,8 @@ public static class GuildConfigExtensions { var conf = ctx.GuildConfigsForId(guildId, set => set.Include(y => y.StreamRole) - .Include(y => y.StreamRole.Whitelist) - .Include(y => y.StreamRole.Blacklist) - ); + .Include(y => y.StreamRole.Whitelist) + .Include(y => y.StreamRole.Blacklist)); if (conf.StreamRole is null) conf.StreamRole = new(); @@ -34,35 +34,25 @@ public static class GuildConfigExtensions return conf.StreamRole; } - private static List DefaultWarnPunishments - => new() - { - new() { Count = 3, Punishment = PunishmentAction.Kick }, - new() { Count = 5, Punishment = PunishmentAction.Ban } - }; - private static IQueryable IncludeEverything(this DbSet configs) => configs.AsQueryable() - .AsSplitQuery() - .Include(gc => gc.CommandCooldowns) - .Include(gc => gc.FollowedStreams) - .Include(gc => gc.StreamRole) - .Include(gc => gc.XpSettings) - .ThenInclude(x => x.ExclusionList) - .Include(gc => gc.DelMsgOnCmdChannels) - .Include(gc => gc.ReactionRoleMessages) - .ThenInclude(x => x.ReactionRoles); + .AsSplitQuery() + .Include(gc => gc.CommandCooldowns) + .Include(gc => gc.FollowedStreams) + .Include(gc => gc.StreamRole) + .Include(gc => gc.XpSettings) + .ThenInclude(x => x.ExclusionList) + .Include(gc => gc.DelMsgOnCmdChannels) + .Include(gc => gc.ReactionRoleMessages) + .ThenInclude(x => x.ReactionRoles); public static IEnumerable GetAllGuildConfigs( this DbSet configs, List availableGuilds) - => configs.IncludeEverything() - .AsNoTracking() - .Where(x => availableGuilds.Contains(x.GuildId)) - .ToList(); + => configs.IncludeEverything().AsNoTracking().Where(x => availableGuilds.Contains(x.GuildId)).ToList(); /// - /// Gets and creates if it doesn't exist a config for a guild. + /// Gets and creates if it doesn't exist a config for a guild. /// /// Context /// Id of the guide @@ -77,8 +67,7 @@ public static class GuildConfigExtensions if (includes is null) { - config = ctx.GuildConfigs.IncludeEverything() - .FirstOrDefault(c => c.GuildId == guildId); + config = ctx.GuildConfigs.IncludeEverything().FirstOrDefault(c => c.GuildId == guildId); } else { @@ -89,13 +78,12 @@ public static class GuildConfigExtensions if (config is null) { ctx.GuildConfigs.Add(config = new() - { - GuildId = guildId, - Permissions = Permissionv2.GetDefaultPermlist, - WarningsInitialized = true, - WarnPunishments = DefaultWarnPunishments, - } - ); + { + GuildId = guildId, + Permissions = Permissionv2.GetDefaultPermlist, + WarningsInitialized = true, + WarnPunishments = DefaultWarnPunishments + }); ctx.SaveChanges(); } @@ -111,9 +99,9 @@ public static class GuildConfigExtensions public static LogSetting LogSettingsFor(this NadekoContext ctx, ulong guildId) { var logSetting = ctx.LogSettings.AsQueryable() - .Include(x => x.LogIgnores) - .Where(x => x.GuildId == guildId) - .FirstOrDefault(); + .Include(x => x.LogIgnores) + .Where(x => x.GuildId == guildId) + .FirstOrDefault(); if (logSetting is null) { @@ -126,9 +114,7 @@ public static class GuildConfigExtensions public static IEnumerable Permissionsv2ForAll(this DbSet configs, List include) { - var query = configs.AsQueryable() - .Where(x => include.Contains(x.GuildId)) - .Include(gc => gc.Permissions); + var query = configs.AsQueryable().Where(x => include.Contains(x.GuildId)).Include(gc => gc.Permissions); return query.ToList(); } @@ -136,17 +122,16 @@ public static class GuildConfigExtensions public static GuildConfig GcWithPermissionsv2For(this NadekoContext ctx, ulong guildId) { var config = ctx.GuildConfigs.AsQueryable() - .Where(gc => gc.GuildId == guildId) - .Include(gc => gc.Permissions) - .FirstOrDefault(); + .Where(gc => gc.GuildId == guildId) + .Include(gc => gc.Permissions) + .FirstOrDefault(); if (config is null) // if there is no guildconfig, create new one { ctx.GuildConfigs.Add(config = new() { GuildId = guildId, Permissions = Permissionv2.GetDefaultPermlist }); ctx.SaveChanges(); } - else if (config.Permissions is null || - !config.Permissions.Any()) // if no perms, add default ones + else if (config.Permissions is null || !config.Permissions.Any()) // if no perms, add default ones { config.Permissions = Permissionv2.GetDefaultPermlist; ctx.SaveChanges(); @@ -156,17 +141,14 @@ public static class GuildConfigExtensions } public static IEnumerable GetFollowedStreams(this DbSet configs) - => configs.AsQueryable() - .Include(x => x.FollowedStreams) - .SelectMany(gc => gc.FollowedStreams) - .ToArray(); + => configs.AsQueryable().Include(x => x.FollowedStreams).SelectMany(gc => gc.FollowedStreams).ToArray(); public static IEnumerable GetFollowedStreams(this DbSet configs, List included) => configs.AsQueryable() - .Where(gc => included.Contains(gc.GuildId)) - .Include(gc => gc.FollowedStreams) - .SelectMany(gc => gc.FollowedStreams) - .ToList(); + .Where(gc => included.Contains(gc.GuildId)) + .Include(gc => gc.FollowedStreams) + .SelectMany(gc => gc.FollowedStreams) + .ToList(); public static void SetCleverbotEnabled(this DbSet configs, ulong id, bool cleverbotEnabled) { @@ -182,24 +164,29 @@ public static class GuildConfigExtensions { var gc = ctx.GuildConfigsForId(guildId, set => set.Include(x => x.XpSettings) - .ThenInclude(x => x.RoleRewards) - .Include(x => x.XpSettings) - .ThenInclude(x => x.CurrencyRewards) - .Include(x => x.XpSettings) - .ThenInclude(x => x.ExclusionList) - ); + .ThenInclude(x => x.RoleRewards) + .Include(x => x.XpSettings) + .ThenInclude(x => x.CurrencyRewards) + .Include(x => x.XpSettings) + .ThenInclude(x => x.ExclusionList)); if (gc.XpSettings is null) - gc.XpSettings = new XpSettings(); + gc.XpSettings = new(); return gc.XpSettings; } public static IEnumerable GetGeneratingChannels(this DbSet configs) => configs.AsQueryable() - .Include(x => x.GenerateCurrencyChannelIds) - .Where(x => x.GenerateCurrencyChannelIds.Any()) - .SelectMany(x => x.GenerateCurrencyChannelIds) - .Select(x => new GeneratingChannel() { ChannelId = x.ChannelId, GuildId = x.GuildConfig.GuildId }) - .ToArray(); -} + .Include(x => x.GenerateCurrencyChannelIds) + .Where(x => x.GenerateCurrencyChannelIds.Any()) + .SelectMany(x => x.GenerateCurrencyChannelIds) + .Select(x => new GeneratingChannel { ChannelId = x.ChannelId, GuildId = x.GuildConfig.GuildId }) + .ToArray(); + + public class GeneratingChannel + { + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/MusicPlayerSettingsExtensions.cs b/src/NadekoBot/Db/Extensions/MusicPlayerSettingsExtensions.cs index 8f3ec8910..3a026024a 100644 --- a/src/NadekoBot/Db/Extensions/MusicPlayerSettingsExtensions.cs +++ b/src/NadekoBot/Db/Extensions/MusicPlayerSettingsExtensions.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Database.Models; @@ -8,12 +8,11 @@ public static class MusicPlayerSettingsExtensions { public static async Task ForGuildAsync(this DbSet settings, ulong guildId) { - var toReturn = await settings.AsQueryable() - .FirstOrDefaultAsync(x => x.GuildId == guildId); + var toReturn = await settings.AsQueryable().FirstOrDefaultAsync(x => x.GuildId == guildId); if (toReturn is null) { - var newSettings = new MusicPlayerSettings() { GuildId = guildId, PlayerRepeat = PlayerRepeatType.Queue }; + var newSettings = new MusicPlayerSettings { GuildId = guildId, PlayerRepeat = PlayerRepeatType.Queue }; await settings.AddAsync(newSettings); return newSettings; @@ -21,4 +20,4 @@ public static class MusicPlayerSettingsExtensions return toReturn; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/MusicPlaylistExtensions.cs b/src/NadekoBot/Db/Extensions/MusicPlaylistExtensions.cs index bcb9a8e37..64a801143 100644 --- a/src/NadekoBot/Db/Extensions/MusicPlaylistExtensions.cs +++ b/src/NadekoBot/Db/Extensions/MusicPlaylistExtensions.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Database.Models; @@ -11,14 +11,9 @@ public static class MusicPlaylistExtensions if (num < 1) throw new IndexOutOfRangeException(); - return playlists.AsQueryable() - .Skip((num - 1) * 20) - .Take(20) - .Include(pl => pl.Songs) - .ToList(); + return playlists.AsQueryable().Skip((num - 1) * 20).Take(20).Include(pl => pl.Songs).ToList(); } public static MusicPlaylist GetWithSongs(this DbSet playlists, int id) - => playlists.Include(mpl => mpl.Songs) - .FirstOrDefault(mpl => mpl.Id == id); -} + => playlists.Include(mpl => mpl.Songs).FirstOrDefault(mpl => mpl.Id == id); +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/PollExtensions.cs b/src/NadekoBot/Db/Extensions/PollExtensions.cs index b0fd2d41d..9a80d82ea 100644 --- a/src/NadekoBot/Db/Extensions/PollExtensions.cs +++ b/src/NadekoBot/Db/Extensions/PollExtensions.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Database; using NadekoBot.Services.Database.Models; @@ -8,15 +8,11 @@ namespace NadekoBot.Db; public static class PollExtensions { public static IEnumerable GetAllPolls(this DbSet polls) - => polls.Include(x => x.Answers) - .Include(x => x.Votes) - .ToArray(); + => polls.Include(x => x.Answers).Include(x => x.Votes).ToArray(); public static void RemovePoll(this NadekoContext ctx, int id) { - var p = ctx.Poll.Include(x => x.Answers) - .Include(x => x.Votes) - .FirstOrDefault(x => x.Id == id); + var p = ctx.Poll.Include(x => x.Answers).Include(x => x.Votes).FirstOrDefault(x => x.Id == id); if (p is null) return; @@ -35,4 +31,4 @@ public static class PollExtensions ctx.Poll.Remove(p); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/QuoteExtensions.cs b/src/NadekoBot/Db/Extensions/QuoteExtensions.cs index 717cd135b..a114fc827 100644 --- a/src/NadekoBot/Db/Extensions/QuoteExtensions.cs +++ b/src/NadekoBot/Db/Extensions/QuoteExtensions.cs @@ -1,14 +1,13 @@ #nullable disable -using NadekoBot.Services.Database.Models; using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Db; public static class QuoteExtensions { public static IEnumerable GetForGuild(this DbSet quotes, ulong guildId) - => quotes.AsQueryable() - .Where(x => x.GuildId == guildId); + => quotes.AsQueryable().Where(x => x.GuildId == guildId); public static IEnumerable GetGroup( this DbSet quotes, @@ -16,16 +15,13 @@ public static class QuoteExtensions int page, OrderType order) { - var q = quotes.AsQueryable() - .Where(x => x.GuildId == guildId); + var q = quotes.AsQueryable().Where(x => x.GuildId == guildId); if (order == OrderType.Keyword) q = q.OrderBy(x => x.Keyword); else q = q.OrderBy(x => x.Id); - return q.Skip(15 * page) - .Take(15) - .ToArray(); + return q.Skip(15 * page).Take(15).ToArray(); } public static async Task GetRandomQuoteByKeywordAsync( @@ -34,10 +30,9 @@ public static class QuoteExtensions string keyword) { var rng = new NadekoRandom(); - return (await quotes.AsQueryable() - .Where(q => q.GuildId == guildId && q.Keyword == keyword) - .ToListAsync()).OrderBy(q => rng.Next()) - .FirstOrDefault(); + return (await quotes.AsQueryable().Where(q => q.GuildId == guildId && q.Keyword == keyword).ToListAsync()) + .OrderBy(q => rng.Next()) + .FirstOrDefault(); } public static async Task SearchQuoteKeywordTextAsync( @@ -48,17 +43,15 @@ public static class QuoteExtensions { var rngk = new NadekoRandom(); return (await quotes.AsQueryable() - .Where(q => q.GuildId == guildId && - q.Keyword == keyword && - EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%") - // && q.Text.Contains(text, StringComparison.OrdinalIgnoreCase) - ) - .ToListAsync()).OrderBy(q => rngk.Next()) - .FirstOrDefault(); + .Where(q => q.GuildId == guildId + && q.Keyword == keyword + && EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%") + // && q.Text.Contains(text, StringComparison.OrdinalIgnoreCase) + ) + .ToListAsync()).OrderBy(q => rngk.Next()) + .FirstOrDefault(); } public static void RemoveAllByKeyword(this DbSet quotes, ulong guildId, string keyword) - => quotes.RemoveRange(quotes.AsQueryable() - .Where(x => x.GuildId == guildId && x.Keyword.ToUpper() == keyword) - ); -} + => quotes.RemoveRange(quotes.AsQueryable().Where(x => x.GuildId == guildId && x.Keyword.ToUpper() == keyword)); +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/ReminderExtensions.cs b/src/NadekoBot/Db/Extensions/ReminderExtensions.cs index 2deccecb8..312c642e9 100644 --- a/src/NadekoBot/Db/Extensions/ReminderExtensions.cs +++ b/src/NadekoBot/Db/Extensions/ReminderExtensions.cs @@ -1,6 +1,6 @@ -#nullable disable -using NadekoBot.Services.Database.Models; +#nullable disable using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Db; @@ -9,21 +9,15 @@ public static class ReminderExtensions public static IEnumerable GetIncludedReminders( this DbSet reminders, IEnumerable guildIds) - => reminders.AsQueryable() - .Where(x => guildIds.Contains(x.ServerId) || x.ServerId == 0) - .ToList(); + => reminders.AsQueryable().Where(x => guildIds.Contains(x.ServerId) || x.ServerId == 0).ToList(); public static IEnumerable RemindersFor(this DbSet reminders, ulong userId, int page) - => reminders.AsQueryable() - .Where(x => x.UserId == userId) - .OrderBy(x => x.DateAdded) - .Skip(page * 10) - .Take(10); + => reminders.AsQueryable().Where(x => x.UserId == userId).OrderBy(x => x.DateAdded).Skip(page * 10).Take(10); public static IEnumerable RemindersForServer(this DbSet reminders, ulong serverId, int page) => reminders.AsQueryable() - .Where(x => x.ServerId == serverId) - .OrderBy(x => x.DateAdded) - .Skip(page * 10) - .Take(10); -} + .Where(x => x.ServerId == serverId) + .OrderBy(x => x.DateAdded) + .Skip(page * 10) + .Take(10); +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/SelfAssignableRolesExtensions.cs b/src/NadekoBot/Db/Extensions/SelfAssignableRolesExtensions.cs index d49b0df35..e13c4559e 100644 --- a/src/NadekoBot/Db/Extensions/SelfAssignableRolesExtensions.cs +++ b/src/NadekoBot/Db/Extensions/SelfAssignableRolesExtensions.cs @@ -1,6 +1,6 @@ -#nullable disable -using NadekoBot.Services.Database.Models; +#nullable disable using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Db; @@ -18,7 +18,5 @@ public static class SelfAssignableRolesExtensions } public static IEnumerable GetFromGuild(this DbSet roles, ulong guildId) - => roles.AsQueryable() - .Where(s => s.GuildId == guildId) - .ToArray(); -} + => roles.AsQueryable().Where(s => s.GuildId == guildId).ToArray(); +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/UserXpExtensions.cs b/src/NadekoBot/Db/Extensions/UserXpExtensions.cs index beffcba56..5d2dcbf85 100644 --- a/src/NadekoBot/Db/Extensions/UserXpExtensions.cs +++ b/src/NadekoBot/Db/Extensions/UserXpExtensions.cs @@ -1,6 +1,6 @@ -#nullable disable -using Microsoft.EntityFrameworkCore; +#nullable disable using LinqToDB; +using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Database; using NadekoBot.Services.Database.Models; @@ -13,33 +13,30 @@ public static class UserXpExtensions var usr = ctx.UserXpStats.FirstOrDefault(x => x.UserId == userId && x.GuildId == guildId); if (usr is null) - { ctx.Add(usr = new() - { - Xp = 0, UserId = userId, NotifyOnLevelUp = XpNotificationLocation.None, GuildId = guildId, - } - ); - } + { + Xp = 0, UserId = userId, NotifyOnLevelUp = XpNotificationLocation.None, GuildId = guildId + }); return usr; } public static List GetUsersFor(this DbSet xps, ulong guildId, int page) => xps.AsQueryable() - .AsNoTracking() - .Where(x => x.GuildId == guildId) - .OrderByDescending(x => x.Xp + x.AwardedXp) - .Skip(page * 9) - .Take(9) - .ToList(); + .AsNoTracking() + .Where(x => x.GuildId == guildId) + .OrderByDescending(x => x.Xp + x.AwardedXp) + .Skip(page * 9) + .Take(9) + .ToList(); public static List GetTopUserXps(this DbSet xps, ulong guildId, int count) => xps.AsQueryable() - .AsNoTracking() - .Where(x => x.GuildId == guildId) - .OrderByDescending(x => x.Xp + x.AwardedXp) - .Take(count) - .ToList(); + .AsNoTracking() + .Where(x => x.GuildId == guildId) + .OrderByDescending(x => x.Xp + x.AwardedXp) + .Take(count) + .ToList(); public static int GetUserGuildRanking(this DbSet xps, ulong userId, ulong guildId) // @"SELECT COUNT(*) + 1 @@ -49,20 +46,19 @@ public static class UserXpExtensions // WHERE UserId = @p2 AND GuildId = @p1 // LIMIT 1));"; => xps.AsQueryable() - .AsNoTracking() - .Where(x => x.GuildId == guildId && - x.Xp + x.AwardedXp > - xps.AsQueryable() + .AsNoTracking() + .Where(x => x.GuildId == guildId + && x.Xp + x.AwardedXp + > xps.AsQueryable() .Where(y => y.UserId == userId && y.GuildId == guildId) .Select(y => y.Xp + y.AwardedXp) - .FirstOrDefault() - ) - .Count() + - 1; + .FirstOrDefault()) + .Count() + + 1; public static void ResetGuildUserXp(this DbSet xps, ulong userId, ulong guildId) => xps.Delete(x => x.UserId == userId && x.GuildId == guildId); public static void ResetGuildXp(this DbSet xps, ulong guildId) => xps.Delete(x => x.GuildId == guildId); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/WaifuExtensions.cs b/src/NadekoBot/Db/Extensions/WaifuExtensions.cs index ce017ecf5..8e606edcc 100644 --- a/src/NadekoBot/Db/Extensions/WaifuExtensions.cs +++ b/src/NadekoBot/Db/Extensions/WaifuExtensions.cs @@ -1,8 +1,8 @@ -#nullable disable +#nullable disable using Microsoft.EntityFrameworkCore; +using NadekoBot.Db.Models; using NadekoBot.Services.Database; using NadekoBot.Services.Database.Models; -using NadekoBot.Db.Models; namespace NadekoBot.Db; @@ -28,17 +28,13 @@ public static class WaifuExtensions Func, IQueryable> includes = null) { if (includes is null) - { return waifus.Include(wi => wi.Waifu) - .Include(wi => wi.Affinity) - .Include(wi => wi.Claimer) - .Include(wi => wi.Items) - .FirstOrDefault(wi => wi.Waifu.UserId == userId); - } + .Include(wi => wi.Affinity) + .Include(wi => wi.Claimer) + .Include(wi => wi.Items) + .FirstOrDefault(wi => wi.Waifu.UserId == userId); - return includes(waifus) - .AsQueryable() - .FirstOrDefault(wi => wi.Waifu.UserId == userId); + return includes(waifus).AsQueryable().FirstOrDefault(wi => wi.Waifu.UserId == userId); } public static IEnumerable GetTop(this DbSet waifus, int count, int skip = 0) @@ -49,99 +45,98 @@ public static class WaifuExtensions return new List(); return waifus.Include(wi => wi.Waifu) - .Include(wi => wi.Affinity) - .Include(wi => wi.Claimer) - .OrderByDescending(wi => wi.Price) - .Skip(skip) - .Take(count) - .Select(x => new WaifuLbResult - { - Affinity = x.Affinity == null ? null : x.Affinity.Username, - AffinityDiscrim = x.Affinity == null ? null : x.Affinity.Discriminator, - Claimer = x.Claimer == null ? null : x.Claimer.Username, - ClaimerDiscrim = x.Claimer == null ? null : x.Claimer.Discriminator, - Username = x.Waifu.Username, - Discrim = x.Waifu.Discriminator, - Price = x.Price, - } - ) - .ToList(); + .Include(wi => wi.Affinity) + .Include(wi => wi.Claimer) + .OrderByDescending(wi => wi.Price) + .Skip(skip) + .Take(count) + .Select(x => new WaifuLbResult + { + Affinity = x.Affinity == null ? null : x.Affinity.Username, + AffinityDiscrim = x.Affinity == null ? null : x.Affinity.Discriminator, + Claimer = x.Claimer == null ? null : x.Claimer.Username, + ClaimerDiscrim = x.Claimer == null ? null : x.Claimer.Discriminator, + Username = x.Waifu.Username, + Discrim = x.Waifu.Discriminator, + Price = x.Price + }) + .ToList(); } public static decimal GetTotalValue(this DbSet waifus) - => waifus.AsQueryable() - .Where(x => x.ClaimerId != null) - .Sum(x => x.Price); + => waifus.AsQueryable().Where(x => x.ClaimerId != null).Sum(x => x.Price); public static ulong GetWaifuUserId(this DbSet waifus, ulong ownerId, string name) => waifus.AsQueryable() - .AsNoTracking() - .Where(x => x.Claimer.UserId == ownerId && x.Waifu.Username + "#" + x.Waifu.Discriminator == name) - .Select(x => x.Waifu.UserId) - .FirstOrDefault(); + .AsNoTracking() + .Where(x => x.Claimer.UserId == ownerId && x.Waifu.Username + "#" + x.Waifu.Discriminator == name) + .Select(x => x.Waifu.UserId) + .FirstOrDefault(); public static WaifuInfoStats GetWaifuInfo(this NadekoContext ctx, ulong userId) { ctx.Database.ExecuteSqlInterpolated($@" INSERT OR IGNORE INTO WaifuInfo (AffinityId, ClaimerId, Price, WaifuId) -VALUES ({null}, {null}, {1}, (SELECT Id FROM DiscordUser WHERE UserId={userId}));" - ); +VALUES ({null}, {null}, {1}, (SELECT Id FROM DiscordUser WHERE UserId={userId}));"); var toReturn = ctx.WaifuInfo.AsQueryable() - .Where(w => w.WaifuId == - ctx.Set() - .AsQueryable() - .Where(u => u.UserId == userId) - .Select(u => u.Id) - .FirstOrDefault() - ) - .Select(w => new WaifuInfoStats - { - FullName = ctx.Set() - .AsQueryable() - .Where(u => u.UserId == userId) - .Select(u => u.Username + "#" + u.Discriminator) - .FirstOrDefault(), - AffinityCount = ctx.Set() - .AsQueryable() - .Count(x => x.UserId == w.WaifuId && - x.UpdateType == WaifuUpdateType.AffinityChanged && - x.NewId != null - ), - AffinityName = ctx.Set() - .AsQueryable() - .Where(u => u.Id == w.AffinityId) - .Select(u => u.Username + "#" + u.Discriminator) - .FirstOrDefault(), - ClaimCount = ctx.WaifuInfo.AsQueryable() - .Count(x => x.ClaimerId == w.WaifuId), - ClaimerName = ctx.Set() - .AsQueryable() - .Where(u => u.Id == w.ClaimerId) - .Select(u => u.Username + "#" + u.Discriminator) - .FirstOrDefault(), - DivorceCount = ctx.Set() - .AsQueryable() - .Count(x => x.OldId == w.WaifuId && x.NewId == null && x.UpdateType == WaifuUpdateType.Claimed), - Price = w.Price, - Claims = ctx.WaifuInfo.AsQueryable() - .Include(x => x.Waifu) - .Where(x => x.ClaimerId == w.WaifuId) - .Select(x => x.Waifu.Username + "#" + x.Waifu.Discriminator) - .ToList(), - Fans = ctx.WaifuInfo.AsQueryable() - .Include(x => x.Waifu) - .Where(x => x.AffinityId == w.WaifuId) - .Select(x => x.Waifu.Username + "#" + x.Waifu.Discriminator) - .ToList(), - Items = w.Items, - } - ) - .FirstOrDefault(); + .Where(w => w.WaifuId + == ctx.Set() + .AsQueryable() + .Where(u => u.UserId == userId) + .Select(u => u.Id) + .FirstOrDefault()) + .Select(w => new WaifuInfoStats + { + FullName = + ctx.Set() + .AsQueryable() + .Where(u => u.UserId == userId) + .Select(u => u.Username + "#" + u.Discriminator) + .FirstOrDefault(), + AffinityCount = + ctx.Set() + .AsQueryable() + .Count(x => x.UserId == w.WaifuId + && x.UpdateType == WaifuUpdateType.AffinityChanged + && x.NewId != null), + AffinityName = + ctx.Set() + .AsQueryable() + .Where(u => u.Id == w.AffinityId) + .Select(u => u.Username + "#" + u.Discriminator) + .FirstOrDefault(), + ClaimCount = ctx.WaifuInfo.AsQueryable().Count(x => x.ClaimerId == w.WaifuId), + ClaimerName = + ctx.Set() + .AsQueryable() + .Where(u => u.Id == w.ClaimerId) + .Select(u => u.Username + "#" + u.Discriminator) + .FirstOrDefault(), + DivorceCount = + ctx.Set() + .AsQueryable() + .Count(x => x.OldId == w.WaifuId + && x.NewId == null + && x.UpdateType == WaifuUpdateType.Claimed), + Price = w.Price, + Claims = ctx.WaifuInfo.AsQueryable() + .Include(x => x.Waifu) + .Where(x => x.ClaimerId == w.WaifuId) + .Select(x => x.Waifu.Username + "#" + x.Waifu.Discriminator) + .ToList(), + Fans = ctx.WaifuInfo.AsQueryable() + .Include(x => x.Waifu) + .Where(x => x.AffinityId == w.WaifuId) + .Select(x => x.Waifu.Username + "#" + x.Waifu.Discriminator) + .ToList(), + Items = w.Items + }) + .FirstOrDefault(); if (toReturn is null) return null; return toReturn; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Extensions/WarningExtensions.cs b/src/NadekoBot/Db/Extensions/WarningExtensions.cs index c3d00f04e..abaaea0d3 100644 --- a/src/NadekoBot/Db/Extensions/WarningExtensions.cs +++ b/src/NadekoBot/Db/Extensions/WarningExtensions.cs @@ -1,6 +1,6 @@ -#nullable disable -using NadekoBot.Services.Database.Models; +#nullable disable using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Db; @@ -9,8 +9,8 @@ public static class WarningExtensions public static Warning[] ForId(this DbSet warnings, ulong guildId, ulong userId) { var query = warnings.AsQueryable() - .Where(x => x.GuildId == guildId && x.UserId == userId) - .OrderByDescending(x => x.DateAdded); + .Where(x => x.GuildId == guildId && x.UserId == userId) + .OrderByDescending(x => x.DateAdded); return query.ToArray(); } @@ -26,13 +26,12 @@ public static class WarningExtensions throw new ArgumentOutOfRangeException(nameof(index)); var warn = warnings.AsQueryable() - .Where(x => x.GuildId == guildId && x.UserId == userId) - .OrderByDescending(x => x.DateAdded) - .Skip(index) - .FirstOrDefault(); + .Where(x => x.GuildId == guildId && x.UserId == userId) + .OrderByDescending(x => x.DateAdded) + .Skip(index) + .FirstOrDefault(); - if (warn is null || - warn.Forgiven) + if (warn is null || warn.Forgiven) return false; warn.Forgiven = true; @@ -46,19 +45,16 @@ public static class WarningExtensions ulong userId, string mod) => await warnings.AsQueryable() - .Where(x => x.GuildId == guildId && x.UserId == userId) - .ForEachAsync(x => - { - if (x.Forgiven != true) - { - x.Forgiven = true; - x.ForgivenBy = mod; - } - } - ); + .Where(x => x.GuildId == guildId && x.UserId == userId) + .ForEachAsync(x => + { + if (x.Forgiven != true) + { + x.Forgiven = true; + x.ForgivenBy = mod; + } + }); public static Warning[] GetForGuild(this DbSet warnings, ulong id) - => warnings.AsQueryable() - .Where(x => x.GuildId == id) - .ToArray(); -} + => warnings.AsQueryable().Where(x => x.GuildId == id).ToArray(); +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/AntiProtection.cs b/src/NadekoBot/Db/Models/AntiProtection.cs index aed73ef4b..ad642611a 100644 --- a/src/NadekoBot/Db/Models/AntiProtection.cs +++ b/src/NadekoBot/Db/Models/AntiProtection.cs @@ -9,10 +9,10 @@ public class AntiRaidSetting : DbEntity public int UserThreshold { get; set; } public int Seconds { get; set; } public PunishmentAction Action { get; set; } - + /// - /// Duration of the punishment, in minutes. This works only for supported Actions, like: - /// Mute, Chatmute, Voicemute, etc... + /// Duration of the punishment, in minutes. This works only for supported Actions, like: + /// Mute, Chatmute, Voicemute, etc... /// public int PunishDuration { get; set; } } @@ -55,10 +55,9 @@ public class AntiSpamIgnore : DbEntity { public ulong ChannelId { get; set; } - public override int GetHashCode() => ChannelId.GetHashCode(); + public override int GetHashCode() + => ChannelId.GetHashCode(); public override bool Equals(object obj) - => obj is AntiSpamIgnore inst - ? inst.ChannelId == ChannelId - : false; -} + => obj is AntiSpamIgnore inst ? inst.ChannelId == ChannelId : false; +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/AutoCommand.cs b/src/NadekoBot/Db/Models/AutoCommand.cs index 75cc27041..7252a8207 100644 --- a/src/NadekoBot/Db/Models/AutoCommand.cs +++ b/src/NadekoBot/Db/Models/AutoCommand.cs @@ -8,7 +8,7 @@ public class AutoCommand : DbEntity public string ChannelName { get; set; } public ulong? GuildId { get; set; } public string GuildName { get; set; } - public ulong? VoiceChannelId {get; set; } + public ulong? VoiceChannelId { get; set; } public string VoiceChannelName { get; set; } public int Interval { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/AutoTranslateChannel.cs b/src/NadekoBot/Db/Models/AutoTranslateChannel.cs index 3107a3e02..a86a933b9 100644 --- a/src/NadekoBot/Db/Models/AutoTranslateChannel.cs +++ b/src/NadekoBot/Db/Models/AutoTranslateChannel.cs @@ -7,4 +7,4 @@ public class AutoTranslateChannel : DbEntity public ulong ChannelId { get; set; } public bool AutoDelete { get; set; } public IList Users { get; set; } = new List(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/AutoTranslateUser.cs b/src/NadekoBot/Db/Models/AutoTranslateUser.cs index 03f061543..a040822a9 100644 --- a/src/NadekoBot/Db/Models/AutoTranslateUser.cs +++ b/src/NadekoBot/Db/Models/AutoTranslateUser.cs @@ -8,4 +8,4 @@ public class AutoTranslateUser : DbEntity public ulong UserId { get; set; } public string Source { get; set; } public string Target { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/BanTemplate.cs b/src/NadekoBot/Db/Models/BanTemplate.cs index 0b3415953..fe4c3a01f 100644 --- a/src/NadekoBot/Db/Models/BanTemplate.cs +++ b/src/NadekoBot/Db/Models/BanTemplate.cs @@ -1,8 +1,8 @@ #nullable disable namespace NadekoBot.Services.Database.Models; -public class BanTemplate : DbEntity +public class BanTemplate : DbEntity { public ulong GuildId { get; set; } public string Text { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/BlacklistEntry.cs b/src/NadekoBot/Db/Models/BlacklistEntry.cs index ff7958cd0..8dfbec8c5 100644 --- a/src/NadekoBot/Db/Models/BlacklistEntry.cs +++ b/src/NadekoBot/Db/Models/BlacklistEntry.cs @@ -12,4 +12,4 @@ public enum BlacklistType Server, Channel, User -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/ClubInfo.cs b/src/NadekoBot/Db/Models/ClubInfo.cs index e787eda2a..db8bee883 100644 --- a/src/NadekoBot/Db/Models/ClubInfo.cs +++ b/src/NadekoBot/Db/Models/ClubInfo.cs @@ -1,6 +1,6 @@ #nullable disable -using System.ComponentModel.DataAnnotations; using NadekoBot.Services.Database.Models; +using System.ComponentModel.DataAnnotations; namespace NadekoBot.Db.Models; @@ -8,12 +8,13 @@ public class ClubInfo : DbEntity { [MaxLength(20)] public string Name { get; set; } + public int Discrim { get; set; } public string ImageUrl { get; set; } = string.Empty; public int MinimumLevelReq { get; set; } = 5; public int Xp { get; set; } = 0; - + public int OwnerId { get; set; } public DiscordUser Owner { get; set; } @@ -43,4 +44,4 @@ public class ClubBans public int UserId { get; set; } public DiscordUser User { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/CommandAlias.cs b/src/NadekoBot/Db/Models/CommandAlias.cs index f94be8afd..1bbeb62e3 100644 --- a/src/NadekoBot/Db/Models/CommandAlias.cs +++ b/src/NadekoBot/Db/Models/CommandAlias.cs @@ -5,4 +5,4 @@ public class CommandAlias : DbEntity { public string Trigger { get; set; } public string Mapping { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/CommandCooldown.cs b/src/NadekoBot/Db/Models/CommandCooldown.cs index 5854f0cdd..d54c8e1ca 100644 --- a/src/NadekoBot/Db/Models/CommandCooldown.cs +++ b/src/NadekoBot/Db/Models/CommandCooldown.cs @@ -5,4 +5,4 @@ public class CommandCooldown : DbEntity { public int Seconds { get; set; } public string CommandName { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/CurrencyTransaction.cs b/src/NadekoBot/Db/Models/CurrencyTransaction.cs index 76cdf42a2..a2da4e1a1 100644 --- a/src/NadekoBot/Db/Models/CurrencyTransaction.cs +++ b/src/NadekoBot/Db/Models/CurrencyTransaction.cs @@ -7,10 +7,6 @@ public class CurrencyTransaction : DbEntity public string Reason { get; set; } public ulong UserId { get; set; } - public CurrencyTransaction Clone() => new() - { - Amount = Amount, - Reason = Reason, - UserId = UserId, - }; -} + public CurrencyTransaction Clone() + => new() { Amount = Amount, Reason = Reason, UserId = UserId }; +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/CustomReaction.cs b/src/NadekoBot/Db/Models/CustomReaction.cs index bf689d657..96afcb5f7 100644 --- a/src/NadekoBot/Db/Models/CustomReaction.cs +++ b/src/NadekoBot/Db/Models/CustomReaction.cs @@ -13,16 +13,15 @@ public class CustomReaction : DbEntity public bool AllowTarget { get; set; } public string Reactions { get; set; } - public string[] GetReactions() => - string.IsNullOrWhiteSpace(Reactions) - ? Array.Empty() - : Reactions.Split("@@@"); - - public bool IsGlobal() => GuildId is null or 0; + public string[] GetReactions() + => string.IsNullOrWhiteSpace(Reactions) ? Array.Empty() : Reactions.Split("@@@"); + + public bool IsGlobal() + => GuildId is null or 0; } public class ReactionResponse : DbEntity { public bool OwnerOnly { get; set; } public string Text { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/DbEntity.cs b/src/NadekoBot/Db/Models/DbEntity.cs index c255fdc3c..5e0882cf3 100644 --- a/src/NadekoBot/Db/Models/DbEntity.cs +++ b/src/NadekoBot/Db/Models/DbEntity.cs @@ -7,5 +7,6 @@ public class DbEntity { [Key] public int Id { get; set; } + public DateTime? DateAdded { get; set; } = DateTime.UtcNow; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/DelMsgOnCmdChannel.cs b/src/NadekoBot/Db/Models/DelMsgOnCmdChannel.cs index a77180f81..32458cb24 100644 --- a/src/NadekoBot/Db/Models/DelMsgOnCmdChannel.cs +++ b/src/NadekoBot/Db/Models/DelMsgOnCmdChannel.cs @@ -10,6 +10,5 @@ public class DelMsgOnCmdChannel : DbEntity => ChannelId.GetHashCode(); public override bool Equals(object obj) - => obj is DelMsgOnCmdChannel x - && x.ChannelId == ChannelId; -} + => obj is DelMsgOnCmdChannel x && x.ChannelId == ChannelId; +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/DiscordPemOverride.cs b/src/NadekoBot/Db/Models/DiscordPemOverride.cs index e8dd9c489..c3d2ba404 100644 --- a/src/NadekoBot/Db/Models/DiscordPemOverride.cs +++ b/src/NadekoBot/Db/Models/DiscordPemOverride.cs @@ -4,7 +4,7 @@ namespace NadekoBot.Services.Database.Models; public class DiscordPermOverride : DbEntity { public GuildPerm Perm { get; set; } - + public ulong? GuildId { get; set; } public string Command { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/DiscordUser.cs b/src/NadekoBot/Db/Models/DiscordUser.cs index cfdf76ff7..ae0b4ae54 100644 --- a/src/NadekoBot/Db/Models/DiscordUser.cs +++ b/src/NadekoBot/Db/Models/DiscordUser.cs @@ -21,13 +21,11 @@ public class DiscordUser : DbEntity public long CurrencyAmount { get; set; } public override bool Equals(object obj) - => obj is DiscordUser du - ? du.UserId == UserId - : false; + => obj is DiscordUser du ? du.UserId == UserId : false; public override int GetHashCode() => UserId.GetHashCode(); - public override string ToString() => - Username + "#" + Discriminator; -} + public override string ToString() + => Username + "#" + Discriminator; +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/Event.cs b/src/NadekoBot/Db/Models/Event.cs index cc5bbfe5b..bb3c00eec 100644 --- a/src/NadekoBot/Db/Models/Event.cs +++ b/src/NadekoBot/Db/Models/Event.cs @@ -6,7 +6,8 @@ public class CurrencyEvent public enum Type { Reaction, - GameStatus, + + GameStatus //NotRaid, } @@ -16,30 +17,33 @@ public class CurrencyEvent public Type EventType { get; set; } /// - /// Amount of currency that the user will be rewarded. + /// Amount of currency that the user will be rewarded. /// public long Amount { get; set; } + /// - /// Maximum amount of currency that can be handed out. + /// Maximum amount of currency that can be handed out. /// public long PotSize { get; set; } + public List AwardedUsers { get; set; } /// - /// Used as extra data storage for events which need it. + /// Used as extra data storage for events which need it. /// public ulong ExtraId { get; set; } + /// - /// May be used for some future event. + /// May be used for some future event. /// public ulong ExtraId2 { get; set; } + /// - /// May be used for some future event. + /// May be used for some future event. /// public string ExtraString { get; set; } } public class AwardedUser { - -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/FeedSub.cs b/src/NadekoBot/Db/Models/FeedSub.cs index cc65945ca..f44b66017 100644 --- a/src/NadekoBot/Db/Models/FeedSub.cs +++ b/src/NadekoBot/Db/Models/FeedSub.cs @@ -13,7 +13,5 @@ public class FeedSub : DbEntity => Url.GetHashCode(StringComparison.InvariantCulture) ^ GuildConfigId.GetHashCode(); public override bool Equals(object obj) - => obj is FeedSub s - && s.Url.ToLower() == Url.ToLower() - && s.GuildConfigId == GuildConfigId; -} + => obj is FeedSub s && s.Url.ToLower() == Url.ToLower() && s.GuildConfigId == GuildConfigId; +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/FilterChannelId.cs b/src/NadekoBot/Db/Models/FilterChannelId.cs index 51ce27ca2..b63a56da9 100644 --- a/src/NadekoBot/Db/Models/FilterChannelId.cs +++ b/src/NadekoBot/Db/Models/FilterChannelId.cs @@ -5,13 +5,13 @@ public class FilterChannelId : DbEntity { public ulong ChannelId { get; set; } - public bool Equals(FilterChannelId other) + public bool Equals(FilterChannelId other) => ChannelId == other.ChannelId; public override bool Equals(object obj) - => obj is FilterChannelId fci && Equals(fci); + => obj is FilterChannelId fci && Equals(fci); - public override int GetHashCode() + public override int GetHashCode() => ChannelId.GetHashCode(); } @@ -19,12 +19,12 @@ public class FilterInvitesChannelId : DbEntity { public ulong ChannelId { get; set; } - public bool Equals(FilterInvitesChannelId other) + public bool Equals(FilterInvitesChannelId other) => ChannelId == other.ChannelId; public override bool Equals(object obj) - => obj is FilterInvitesChannelId fci && Equals(fci); + => obj is FilterInvitesChannelId fci && Equals(fci); - public override int GetHashCode() + public override int GetHashCode() => ChannelId.GetHashCode(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/FilterLinksChannelId.cs b/src/NadekoBot/Db/Models/FilterLinksChannelId.cs index d39320871..7dad917d8 100644 --- a/src/NadekoBot/Db/Models/FilterLinksChannelId.cs +++ b/src/NadekoBot/Db/Models/FilterLinksChannelId.cs @@ -6,9 +6,8 @@ public class FilterLinksChannelId : DbEntity public ulong ChannelId { get; set; } public override bool Equals(object obj) - => obj is FilterLinksChannelId f - && f.ChannelId == ChannelId; + => obj is FilterLinksChannelId f && f.ChannelId == ChannelId; - public override int GetHashCode() + public override int GetHashCode() => ChannelId.GetHashCode(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/FilteredWord.cs b/src/NadekoBot/Db/Models/FilteredWord.cs index bfdd166c5..af8d3b5c0 100644 --- a/src/NadekoBot/Db/Models/FilteredWord.cs +++ b/src/NadekoBot/Db/Models/FilteredWord.cs @@ -4,4 +4,4 @@ namespace NadekoBot.Services.Database.Models; public class FilteredWord : DbEntity { public string Word { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/FollowedStream.cs b/src/NadekoBot/Db/Models/FollowedStream.cs index 14472d875..ddf0ed44e 100644 --- a/src/NadekoBot/Db/Models/FollowedStream.cs +++ b/src/NadekoBot/Db/Models/FollowedStream.cs @@ -1,35 +1,36 @@ #nullable disable -using NadekoBot.Services.Database.Models; using NadekoBot.Modules.Searches.Common; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Db.Models; public class FollowedStream : DbEntity { + public enum FType + { + Twitch = 0, + Picarto = 3, + Youtube = 4, + Facebook = 5 + } + public ulong GuildId { get; set; } public ulong ChannelId { get; set; } public string Username { get; set; } public FType Type { get; set; } public string Message { get; set; } - public enum FType - { - Twitch = 0, - Picarto = 3, - Youtube = 4, - Facebook = 5, - } - protected bool Equals(FollowedStream other) - => ChannelId == other.ChannelId - && Username.Trim().ToUpperInvariant() == other.Username.Trim().ToUpperInvariant() + => ChannelId == other.ChannelId + && Username.Trim().ToUpperInvariant() == other.Username.Trim().ToUpperInvariant() && Type == other.Type; public override int GetHashCode() - => HashCode.Combine(ChannelId, Username, (int) Type); + => HashCode.Combine(ChannelId, Username, (int)Type); - public override bool Equals(object obj) + public override bool Equals(object obj) => obj is FollowedStream fs && Equals(fs); - public StreamDataKey CreateKey() => new(Type, Username.ToLower()); -} + public StreamDataKey CreateKey() + => new(Type, Username.ToLower()); +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/GCChannelId.cs b/src/NadekoBot/Db/Models/GCChannelId.cs index 077d4165e..b1a180258 100644 --- a/src/NadekoBot/Db/Models/GCChannelId.cs +++ b/src/NadekoBot/Db/Models/GCChannelId.cs @@ -7,10 +7,8 @@ public class GCChannelId : DbEntity public ulong ChannelId { get; set; } public override bool Equals(object obj) - => obj is GCChannelId gc - ? gc.ChannelId == ChannelId - : false; + => obj is GCChannelId gc ? gc.ChannelId == ChannelId : false; - public override int GetHashCode() => - this.ChannelId.GetHashCode(); -} + public override int GetHashCode() + => ChannelId.GetHashCode(); +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/GroupName.cs b/src/NadekoBot/Db/Models/GroupName.cs index e1b84e3d4..893bac755 100644 --- a/src/NadekoBot/Db/Models/GroupName.cs +++ b/src/NadekoBot/Db/Models/GroupName.cs @@ -8,4 +8,4 @@ public class GroupName : DbEntity public int Number { get; set; } public string Name { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/GuildConfig.cs b/src/NadekoBot/Db/Models/GuildConfig.cs index 825e7301a..ef08cba9e 100644 --- a/src/NadekoBot/Db/Models/GuildConfig.cs +++ b/src/NadekoBot/Db/Models/GuildConfig.cs @@ -12,7 +12,9 @@ public class GuildConfig : DbEntity public bool DeleteMessageOnCommand { get; set; } public HashSet DelMsgOnCmdChannels { get; set; } = new(); + public string AutoAssignRoleIds { get; set; } + //greet stuff public bool AutoDeleteGreetMessages { get; set; } //unused public bool AutoDeleteByeMessages { get; set; } // unused @@ -28,15 +30,6 @@ public class GuildConfig : DbEntity public bool SendChannelGreetMessage { get; set; } public string ChannelGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; - #region Boost Message - - public bool SendBoostMessage { get; set; } - public string BoostMessage { get; set; } = "%user% just boosted this server!"; - public ulong BoostMessageChannelId { get; set; } - public int BoostMessageDeleteAfter { get; set; } - - #endregion - public bool SendChannelByeMessage { get; set; } public string ChannelByeMessageText { get; set; } = "%user% has left!"; @@ -104,4 +97,13 @@ public class GuildConfig : DbEntity public List SelfAssignableRoleGroupNames { get; set; } public int WarnExpireHours { get; set; } = 0; public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear; -} + + #region Boost Message + + public bool SendBoostMessage { get; set; } + public string BoostMessage { get; set; } = "%user% just boosted this server!"; + public ulong BoostMessageChannelId { get; set; } + public int BoostMessageDeleteAfter { get; set; } + + #endregion +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/IgnoredLogItem.cs b/src/NadekoBot/Db/Models/IgnoredLogItem.cs index 415827436..a157e4e88 100644 --- a/src/NadekoBot/Db/Models/IgnoredLogItem.cs +++ b/src/NadekoBot/Db/Models/IgnoredLogItem.cs @@ -12,5 +12,5 @@ public class IgnoredLogItem : DbEntity public enum IgnoredItemType { Channel, - User, -} + User +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/IgnoredVoicePresenceChannel.cs b/src/NadekoBot/Db/Models/IgnoredVoicePresenceChannel.cs index 396276bd6..0b1eefe94 100644 --- a/src/NadekoBot/Db/Models/IgnoredVoicePresenceChannel.cs +++ b/src/NadekoBot/Db/Models/IgnoredVoicePresenceChannel.cs @@ -5,4 +5,4 @@ public class IgnoredVoicePresenceChannel : DbEntity { public LogSetting LogSetting { get; set; } public ulong ChannelId { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/ImageOnlyChannel.cs b/src/NadekoBot/Db/Models/ImageOnlyChannel.cs index 5a4ae6909..02b9df481 100644 --- a/src/NadekoBot/Db/Models/ImageOnlyChannel.cs +++ b/src/NadekoBot/Db/Models/ImageOnlyChannel.cs @@ -5,4 +5,4 @@ public class ImageOnlyChannel : DbEntity { public ulong GuildId { get; set; } public ulong ChannelId { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/LogSetting.cs b/src/NadekoBot/Db/Models/LogSetting.cs index 4e2988419..26d545068 100644 --- a/src/NadekoBot/Db/Models/LogSetting.cs +++ b/src/NadekoBot/Db/Models/LogSetting.cs @@ -29,4 +29,4 @@ public class LogSetting : DbEntity public ulong? LogVoicePresenceId { get; set; } public ulong? LogVoicePresenceTTSId { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/MusicPlaylist.cs b/src/NadekoBot/Db/Models/MusicPlaylist.cs index b8804d34c..bbc0f3e80 100644 --- a/src/NadekoBot/Db/Models/MusicPlaylist.cs +++ b/src/NadekoBot/Db/Models/MusicPlaylist.cs @@ -7,4 +7,4 @@ public class MusicPlaylist : DbEntity public string Author { get; set; } public ulong AuthorId { get; set; } public List Songs { get; set; } = new(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/MusicSettings.cs b/src/NadekoBot/Db/Models/MusicSettings.cs index b1e19452d..e4d27f33c 100644 --- a/src/NadekoBot/Db/Models/MusicSettings.cs +++ b/src/NadekoBot/Db/Models/MusicSettings.cs @@ -4,42 +4,42 @@ namespace NadekoBot.Services.Database.Models; public class MusicPlayerSettings { /// - /// Auto generated Id + /// Auto generated Id /// public int Id { get; set; } - + /// - /// Id of the guild + /// Id of the guild /// public ulong GuildId { get; set; } - + /// - /// Queue repeat type + /// Queue repeat type /// public PlayerRepeatType PlayerRepeat { get; set; } = PlayerRepeatType.Queue; - + /// - /// Channel id the bot will always try to send track related messages to + /// Channel id the bot will always try to send track related messages to /// public ulong? MusicChannelId { get; set; } = null; - + /// - /// Default volume player will be created with + /// Default volume player will be created with /// public int Volume { get; set; } = 100; - + /// - /// Whether the bot should auto disconnect from the voice channel once the queue is done - /// This only has effect if + /// Whether the bot should auto disconnect from the voice channel once the queue is done + /// This only has effect if /// public bool AutoDisconnect { get; set; } = false; /// - /// Selected quality preset for the music player + /// Selected quality preset for the music player /// public QualityPreset QualityPreset { get; set; } } - + public enum QualityPreset { Highest, @@ -53,4 +53,4 @@ public enum PlayerRepeatType None, Track, Queue -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/MutedUserId.cs b/src/NadekoBot/Db/Models/MutedUserId.cs index 556584455..9c399f632 100644 --- a/src/NadekoBot/Db/Models/MutedUserId.cs +++ b/src/NadekoBot/Db/Models/MutedUserId.cs @@ -9,7 +9,5 @@ public class MutedUserId : DbEntity => UserId.GetHashCode(); public override bool Equals(object obj) - => obj is MutedUserId mui - ? mui.UserId == UserId - : false; -} + => obj is MutedUserId mui ? mui.UserId == UserId : false; +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/NsfwBlacklistedTag.cs b/src/NadekoBot/Db/Models/NsfwBlacklistedTag.cs index 51d7946bd..46f9af5c9 100644 --- a/src/NadekoBot/Db/Models/NsfwBlacklistedTag.cs +++ b/src/NadekoBot/Db/Models/NsfwBlacklistedTag.cs @@ -6,9 +6,9 @@ public class NsfwBlacklistedTag : DbEntity public ulong GuildId { get; set; } public string Tag { get; set; } - public override int GetHashCode() + public override int GetHashCode() => Tag.GetHashCode(StringComparison.InvariantCulture); - public override bool Equals(object obj) + public override bool Equals(object obj) => obj is NsfwBlacklistedTag x && x.Tag == Tag; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/Permission.cs b/src/NadekoBot/Db/Models/Permission.cs index 615ec1e00..11082fa42 100644 --- a/src/NadekoBot/Db/Models/Permission.cs +++ b/src/NadekoBot/Db/Models/Permission.cs @@ -26,21 +26,19 @@ public class Permissionv2 : DbEntity, IIndexed public bool State { get; set; } [NotMapped] - public static Permissionv2 AllowAllPerm => new() - { - PrimaryTarget = PrimaryPermissionType.Server, - PrimaryTargetId = 0, - SecondaryTarget = SecondaryPermissionType.AllModules, - SecondaryTargetName = "*", - State = true, - Index = 0, - }; - - public static List GetDefaultPermlist => - new() + public static Permissionv2 AllowAllPerm + => new() { - AllowAllPerm + PrimaryTarget = PrimaryPermissionType.Server, + PrimaryTargetId = 0, + SecondaryTarget = SecondaryPermissionType.AllModules, + SecondaryTargetName = "*", + State = true, + Index = 0 }; + + public static List GetDefaultPermlist + => new() { AllowAllPerm }; } public enum PrimaryPermissionType @@ -56,4 +54,4 @@ public enum SecondaryPermissionType Module, Command, AllModules -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/PlantedCurrency.cs b/src/NadekoBot/Db/Models/PlantedCurrency.cs index b2b3e1dc5..911d6ac22 100644 --- a/src/NadekoBot/Db/Models/PlantedCurrency.cs +++ b/src/NadekoBot/Db/Models/PlantedCurrency.cs @@ -9,4 +9,4 @@ public class PlantedCurrency : DbEntity public ulong ChannelId { get; set; } public ulong UserId { get; set; } public ulong MessageId { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/PlaylistSong.cs b/src/NadekoBot/Db/Models/PlaylistSong.cs index 399b97214..a538b3409 100644 --- a/src/NadekoBot/Db/Models/PlaylistSong.cs +++ b/src/NadekoBot/Db/Models/PlaylistSong.cs @@ -16,4 +16,4 @@ public enum MusicType YouTube, Local, Soundcloud -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/Poll.cs b/src/NadekoBot/Db/Models/Poll.cs index 69a2a6e4e..271777a38 100644 --- a/src/NadekoBot/Db/Models/Poll.cs +++ b/src/NadekoBot/Db/Models/Poll.cs @@ -16,4 +16,4 @@ public class PollAnswer : DbEntity, IIndexed { public int Index { get; set; } public string Text { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/PollVote.cs b/src/NadekoBot/Db/Models/PollVote.cs index 06a21dd90..d0fdb056c 100644 --- a/src/NadekoBot/Db/Models/PollVote.cs +++ b/src/NadekoBot/Db/Models/PollVote.cs @@ -10,7 +10,5 @@ public class PollVote : DbEntity => UserId.GetHashCode(); public override bool Equals(object obj) - => obj is PollVote p - ? p.UserId == UserId - : false; -} + => obj is PollVote p ? p.UserId == UserId : false; +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/Quote.cs b/src/NadekoBot/Db/Models/Quote.cs index 53906228b..0caf0cc8e 100644 --- a/src/NadekoBot/Db/Models/Quote.cs +++ b/src/NadekoBot/Db/Models/Quote.cs @@ -6,18 +6,21 @@ namespace NadekoBot.Services.Database.Models; public class Quote : DbEntity { public ulong GuildId { get; set; } + [Required] public string Keyword { get; set; } + [Required] public string AuthorName { get; set; } + public ulong AuthorId { get; set; } + [Required] public string Text { get; set; } } - public enum OrderType { Id = -1, Keyword = -2 -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/ReactionRole.cs b/src/NadekoBot/Db/Models/ReactionRole.cs index a2ad1a768..19468100c 100644 --- a/src/NadekoBot/Db/Models/ReactionRole.cs +++ b/src/NadekoBot/Db/Models/ReactionRole.cs @@ -19,4 +19,4 @@ public class ReactionRole : DbEntity { public string EmoteName { get; set; } public ulong RoleId { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/Reminder.cs b/src/NadekoBot/Db/Models/Reminder.cs index 0d2a7cf1d..f2004493d 100644 --- a/src/NadekoBot/Db/Models/Reminder.cs +++ b/src/NadekoBot/Db/Models/Reminder.cs @@ -9,4 +9,4 @@ public class Reminder : DbEntity public ulong UserId { get; set; } public string Message { get; set; } public bool IsPrivate { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/Repeater.cs b/src/NadekoBot/Db/Models/Repeater.cs index d61dc7b07..0957871b3 100644 --- a/src/NadekoBot/Db/Models/Repeater.cs +++ b/src/NadekoBot/Db/Models/Repeater.cs @@ -12,4 +12,4 @@ public class Repeater public TimeSpan? StartTimeOfDay { get; set; } public bool NoRedundant { get; set; } public DateTime DateAdded { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/RewardedUser.cs b/src/NadekoBot/Db/Models/RewardedUser.cs index dbad7268d..f0a8f926d 100644 --- a/src/NadekoBot/Db/Models/RewardedUser.cs +++ b/src/NadekoBot/Db/Models/RewardedUser.cs @@ -7,4 +7,4 @@ public class RewardedUser : DbEntity public string PatreonUserId { get; set; } public int AmountRewardedThisMonth { get; set; } public DateTime LastReward { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/RotatingPlayingStatus.cs b/src/NadekoBot/Db/Models/RotatingPlayingStatus.cs index cce017d32..7350e5961 100644 --- a/src/NadekoBot/Db/Models/RotatingPlayingStatus.cs +++ b/src/NadekoBot/Db/Models/RotatingPlayingStatus.cs @@ -5,4 +5,4 @@ public class RotatingPlayingStatus : DbEntity { public string Status { get; set; } public ActivityType Type { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/SelfAssignableRole.cs b/src/NadekoBot/Db/Models/SelfAssignableRole.cs index 0218596fa..8cdb88189 100644 --- a/src/NadekoBot/Db/Models/SelfAssignableRole.cs +++ b/src/NadekoBot/Db/Models/SelfAssignableRole.cs @@ -5,7 +5,7 @@ public class SelfAssignedRole : DbEntity { public ulong GuildId { get; set; } public ulong RoleId { get; set; } - + public int Group { get; set; } public int LevelRequirement { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/ShopEntry.cs b/src/NadekoBot/Db/Models/ShopEntry.cs index 44d3533b5..3c644146f 100644 --- a/src/NadekoBot/Db/Models/ShopEntry.cs +++ b/src/NadekoBot/Db/Models/ShopEntry.cs @@ -4,7 +4,8 @@ namespace NadekoBot.Services.Database.Models; public enum ShopEntryType { Role, - List, + + List //Infinite_List, } @@ -31,13 +32,10 @@ public class ShopEntryItem : DbEntity public override bool Equals(object obj) { - if (obj is null || GetType() != obj.GetType()) - { - return false; - } + if (obj is null || GetType() != obj.GetType()) return false; return ((ShopEntryItem)obj).Text == Text; } - public override int GetHashCode() => - Text.GetHashCode(StringComparison.InvariantCulture); -} + public override int GetHashCode() + => Text.GetHashCode(StringComparison.InvariantCulture); +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/SlowmodeIgnoredRole.cs b/src/NadekoBot/Db/Models/SlowmodeIgnoredRole.cs index 8463f74ae..2398045d6 100644 --- a/src/NadekoBot/Db/Models/SlowmodeIgnoredRole.cs +++ b/src/NadekoBot/Db/Models/SlowmodeIgnoredRole.cs @@ -8,10 +8,7 @@ public class SlowmodeIgnoredRole : DbEntity // override object.Equals public override bool Equals(object obj) { - if (obj is null || GetType() != obj.GetType()) - { - return false; - } + if (obj is null || GetType() != obj.GetType()) return false; return ((SlowmodeIgnoredRole)obj).RoleId == RoleId; } @@ -19,4 +16,4 @@ public class SlowmodeIgnoredRole : DbEntity // override object.GetHashCode public override int GetHashCode() => RoleId.GetHashCode(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/SlowmodeIgnoredUser.cs b/src/NadekoBot/Db/Models/SlowmodeIgnoredUser.cs index af65ec70f..8e0b68e9c 100644 --- a/src/NadekoBot/Db/Models/SlowmodeIgnoredUser.cs +++ b/src/NadekoBot/Db/Models/SlowmodeIgnoredUser.cs @@ -8,10 +8,7 @@ public class SlowmodeIgnoredUser : DbEntity // override object.Equals public override bool Equals(object obj) { - if (obj is null || GetType() != obj.GetType()) - { - return false; - } + if (obj is null || GetType() != obj.GetType()) return false; return ((SlowmodeIgnoredUser)obj).UserId == UserId; } @@ -19,4 +16,4 @@ public class SlowmodeIgnoredUser : DbEntity // override object.GetHashCode public override int GetHashCode() => UserId.GetHashCode(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/StreamRoleSettings.cs b/src/NadekoBot/Db/Models/StreamRoleSettings.cs index 25387de32..4db2dade3 100644 --- a/src/NadekoBot/Db/Models/StreamRoleSettings.cs +++ b/src/NadekoBot/Db/Models/StreamRoleSettings.cs @@ -7,33 +7,33 @@ public class StreamRoleSettings : DbEntity public GuildConfig GuildConfig { get; set; } /// - /// Whether the feature is enabled in the guild. + /// Whether the feature is enabled in the guild. /// public bool Enabled { get; set; } /// - /// Id of the role to give to the users in the role 'FromRole' when they start streaming + /// Id of the role to give to the users in the role 'FromRole' when they start streaming /// public ulong AddRoleId { get; set; } /// - /// Id of the role whose users are eligible to get the 'AddRole' + /// Id of the role whose users are eligible to get the 'AddRole' /// public ulong FromRoleId { get; set; } /// - /// If set, feature will only apply to users who have this keyword in their streaming status. + /// If set, feature will only apply to users who have this keyword in their streaming status. /// public string Keyword { get; set; } /// - /// A collection of whitelisted users' IDs. Whitelisted users don't require 'keyword' in - /// order to get the stream role. + /// A collection of whitelisted users' IDs. Whitelisted users don't require 'keyword' in + /// order to get the stream role. /// public HashSet Whitelist { get; set; } = new(); /// - /// A collection of blacklisted users' IDs. Blacklisted useres will never get the stream role. + /// A collection of blacklisted users' IDs. Blacklisted useres will never get the stream role. /// public HashSet Blacklist { get; set; } = new(); } @@ -61,10 +61,8 @@ public class StreamRoleWhitelistedUser : DbEntity public string Username { get; set; } public override bool Equals(object obj) - => obj is StreamRoleWhitelistedUser x - ? x.UserId == UserId - : false; + => obj is StreamRoleWhitelistedUser x ? x.UserId == UserId : false; public override int GetHashCode() => UserId.GetHashCode(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/UnbanTimer.cs b/src/NadekoBot/Db/Models/UnbanTimer.cs index c7ea97573..3c6a5d984 100644 --- a/src/NadekoBot/Db/Models/UnbanTimer.cs +++ b/src/NadekoBot/Db/Models/UnbanTimer.cs @@ -6,11 +6,9 @@ public class UnbanTimer : DbEntity public ulong UserId { get; set; } public DateTime UnbanAt { get; set; } - public override int GetHashCode() => - UserId.GetHashCode(); + public override int GetHashCode() + => UserId.GetHashCode(); public override bool Equals(object obj) - => obj is UnbanTimer ut - ? ut.UserId == UserId - : false; -} + => obj is UnbanTimer ut ? ut.UserId == UserId : false; +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/UnmuteTimer.cs b/src/NadekoBot/Db/Models/UnmuteTimer.cs index 64af55b6a..e99ef02f0 100644 --- a/src/NadekoBot/Db/Models/UnmuteTimer.cs +++ b/src/NadekoBot/Db/Models/UnmuteTimer.cs @@ -6,11 +6,9 @@ public class UnmuteTimer : DbEntity public ulong UserId { get; set; } public DateTime UnmuteAt { get; set; } - public override int GetHashCode() => - UserId.GetHashCode(); + public override int GetHashCode() + => UserId.GetHashCode(); public override bool Equals(object obj) - => obj is UnmuteTimer ut - ? ut.UserId == UserId - : false; -} + => obj is UnmuteTimer ut ? ut.UserId == UserId : false; +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/UnroleTimer.cs b/src/NadekoBot/Db/Models/UnroleTimer.cs index 3bf30d7ed..c037ca1ff 100644 --- a/src/NadekoBot/Db/Models/UnroleTimer.cs +++ b/src/NadekoBot/Db/Models/UnroleTimer.cs @@ -7,11 +7,9 @@ public class UnroleTimer : DbEntity public ulong RoleId { get; set; } public DateTime UnbanAt { get; set; } - public override int GetHashCode() => - UserId.GetHashCode() ^ RoleId.GetHashCode(); + public override int GetHashCode() + => UserId.GetHashCode() ^ RoleId.GetHashCode(); public override bool Equals(object obj) - => obj is UnroleTimer ut - ? ut.UserId == UserId && ut.RoleId == RoleId - : false; -} + => obj is UnroleTimer ut ? ut.UserId == UserId && ut.RoleId == RoleId : false; +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/UserXpStats.cs b/src/NadekoBot/Db/Models/UserXpStats.cs index 84c5c46ab..6dc8ead24 100644 --- a/src/NadekoBot/Db/Models/UserXpStats.cs +++ b/src/NadekoBot/Db/Models/UserXpStats.cs @@ -11,4 +11,4 @@ public class UserXpStats : DbEntity public DateTime LastLevelUp { get; set; } = DateTime.UtcNow; } -public enum XpNotificationLocation { None, Dm, Channel } +public enum XpNotificationLocation { None, Dm, Channel } \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/VcRoleInfo.cs b/src/NadekoBot/Db/Models/VcRoleInfo.cs index 8a1c2d442..fdd825651 100644 --- a/src/NadekoBot/Db/Models/VcRoleInfo.cs +++ b/src/NadekoBot/Db/Models/VcRoleInfo.cs @@ -5,4 +5,4 @@ public class VcRoleInfo : DbEntity { public ulong VoiceChannelId { get; set; } public ulong RoleId { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/Waifu.cs b/src/NadekoBot/Db/Models/Waifu.cs index 4947b3a86..947b0499b 100644 --- a/src/NadekoBot/Db/Models/Waifu.cs +++ b/src/NadekoBot/Db/Models/Waifu.cs @@ -25,22 +25,14 @@ public class WaifuInfo : DbEntity var waifuUsername = Waifu.Username.TrimTo(20); var claimerUsername = Claimer?.Username.TrimTo(20); - if (ClaimerId != null) - { - claimer = $"{ claimerUsername }#{Claimer.Discriminator}"; - } + if (ClaimerId != null) claimer = $"{claimerUsername}#{Claimer.Discriminator}"; if (AffinityId is 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}"; - } + status = + $"... but {waifuUsername}'s heart belongs to {Affinity.Username.TrimTo(20)}#{Affinity.Discriminator}"; return $"**{waifuUsername}#{Waifu.Discriminator}** - claimed by **{claimer}**\n\t{status}"; } } @@ -66,22 +58,13 @@ public class WaifuLbResult var waifuUsername = Username.TrimTo(20); var claimerUsername = Claimer?.TrimTo(20); - if (Claimer != null) - { - claimer = $"{ claimerUsername }#{ClaimerDiscrim}"; - } + if (Claimer != null) claimer = $"{claimerUsername}#{ClaimerDiscrim}"; if (Affinity is null) - { status = $"... but {waifuUsername}'s heart is empty"; - } else if (Affinity + AffinityDiscrim == Claimer + ClaimerDiscrim) - { status = $"... and {waifuUsername} likes {claimerUsername} too <3"; - } else - { status = $"... but {waifuUsername}'s heart belongs to {Affinity.TrimTo(20)}#{AffinityDiscrim}"; - } return $"**{waifuUsername}#{Discrim}** - claimed by **{claimer}**\n\t{status}"; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/WaifuItem.cs b/src/NadekoBot/Db/Models/WaifuItem.cs index a2fa14794..0cb5c9a93 100644 --- a/src/NadekoBot/Db/Models/WaifuItem.cs +++ b/src/NadekoBot/Db/Models/WaifuItem.cs @@ -6,4 +6,4 @@ public class WaifuItem : DbEntity public int? WaifuInfoId { get; set; } public string ItemEmoji { get; set; } public string Name { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/WaifuUpdate.cs b/src/NadekoBot/Db/Models/WaifuUpdate.cs index d1dfe5fec..f5558e371 100644 --- a/src/NadekoBot/Db/Models/WaifuUpdate.cs +++ b/src/NadekoBot/Db/Models/WaifuUpdate.cs @@ -20,4 +20,4 @@ public enum WaifuUpdateType { AffinityChanged, Claimed -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/WarnExpireAction.cs b/src/NadekoBot/Db/Models/WarnExpireAction.cs index d185cb7f5..b8d9e5b36 100644 --- a/src/NadekoBot/Db/Models/WarnExpireAction.cs +++ b/src/NadekoBot/Db/Models/WarnExpireAction.cs @@ -5,4 +5,4 @@ public enum WarnExpireAction { Clear, Delete -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/Warning.cs b/src/NadekoBot/Db/Models/Warning.cs index 6a9982963..9f478ee86 100644 --- a/src/NadekoBot/Db/Models/Warning.cs +++ b/src/NadekoBot/Db/Models/Warning.cs @@ -10,4 +10,4 @@ public class Warning : DbEntity public string ForgivenBy { get; set; } public string Moderator { get; set; } public int Weight { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/WarningPunishment.cs b/src/NadekoBot/Db/Models/WarningPunishment.cs index d8f502ce0..8c78afce6 100644 --- a/src/NadekoBot/Db/Models/WarningPunishment.cs +++ b/src/NadekoBot/Db/Models/WarningPunishment.cs @@ -7,4 +7,4 @@ public class WarningPunishment : DbEntity public PunishmentAction Punishment { get; set; } public int Time { get; set; } public ulong? RoleId { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/Models/XpSettings.cs b/src/NadekoBot/Db/Models/XpSettings.cs index ae2d9608e..d1887ad94 100644 --- a/src/NadekoBot/Db/Models/XpSettings.cs +++ b/src/NadekoBot/Db/Models/XpSettings.cs @@ -21,12 +21,12 @@ public class XpRoleReward : DbEntity public int Level { get; set; } public ulong RoleId { get; set; } - + /// - /// Whether the role should be removed (true) or added (false) + /// Whether the role should be removed (true) or added (false) /// public bool Remove { get; set; } - + public override int GetHashCode() => Level.GetHashCode() ^ XpSettingsId.GetHashCode(); @@ -59,4 +59,4 @@ public class ExcludedItem : DbEntity public override bool Equals(object obj) => obj is ExcludedItem ei && ei.ItemId == ItemId && ei.ItemType == ItemType; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Db/NadekoContext.cs b/src/NadekoBot/Db/NadekoContext.cs index e7c7ff49c..07ae202a1 100644 --- a/src/NadekoBot/Db/NadekoContext.cs +++ b/src/NadekoBot/Db/NadekoContext.cs @@ -2,9 +2,10 @@ using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; -using NadekoBot.Services.Database.Models; using Microsoft.Extensions.Logging; using NadekoBot.Db.Models; +using NadekoBot.Services.Database.Models; + // ReSharper disable UnusedAutoPropertyAccessor.Global namespace NadekoBot.Services.Database; @@ -47,7 +48,7 @@ public class NadekoContext : DbContext public DbSet RotatingStatus { get; set; } public DbSet Blacklist { get; set; } - public DbSet AutoCommands { get; set; } + public DbSet AutoCommands { get; set; } public DbSet RewardedUsers { get; set; } public DbSet PlantedCurrency { get; set; } public DbSet BanTemplates { get; set; } @@ -62,16 +63,10 @@ public class NadekoContext : DbContext public DbSet AutoTranslateChannels { get; set; } public DbSet AutoTranslateUsers { get; set; } - public NadekoContext(DbContextOptions options) : base(options) + public NadekoContext(DbContextOptions options) + : base(options) { } - -#if DEBUG - private static readonly ILoggerFactory _debugLoggerFactory = - LoggerFactory.Create(x => x.AddConsole()); - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseLoggerFactory(_debugLoggerFactory); -#endif protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -86,75 +81,56 @@ public class NadekoContext : DbContext #region GuildConfig var configEntity = modelBuilder.Entity(); - configEntity - .HasIndex(c => c.GuildId) - .IsUnique(); + configEntity.HasIndex(c => c.GuildId).IsUnique(); - modelBuilder.Entity() - .HasOne(x => x.GuildConfig) - .WithOne(x => x.AntiSpamSetting); + modelBuilder.Entity().HasOne(x => x.GuildConfig).WithOne(x => x.AntiSpamSetting); - modelBuilder.Entity() - .HasOne(x => x.GuildConfig) - .WithOne(x => x.AntiRaidSetting); + modelBuilder.Entity().HasOne(x => x.GuildConfig).WithOne(x => x.AntiRaidSetting); modelBuilder.Entity() - .HasOne(x => x.AntiAltSetting) - .WithOne() - .HasForeignKey(x => x.GuildConfigId) - .OnDelete(DeleteBehavior.Cascade); + .HasOne(x => x.AntiAltSetting) + .WithOne() + .HasForeignKey(x => x.GuildConfigId) + .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasAlternateKey(x => new { x.GuildConfigId, x.Url }); + modelBuilder.Entity().HasAlternateKey(x => new { x.GuildConfigId, x.Url }); - modelBuilder.Entity() - .HasIndex(x => x.MessageId) - .IsUnique(); + modelBuilder.Entity().HasIndex(x => x.MessageId).IsUnique(); - modelBuilder.Entity() - .HasIndex(x => x.ChannelId); + modelBuilder.Entity().HasIndex(x => x.ChannelId); - configEntity.HasIndex(x => x.WarnExpireHours) - .IsUnique(false); + configEntity.HasIndex(x => x.WarnExpireHours).IsUnique(false); #endregion #region streamrole - modelBuilder.Entity() - .HasOne(x => x.GuildConfig) - .WithOne(x => x.StreamRole); + + modelBuilder.Entity().HasOne(x => x.GuildConfig).WithOne(x => x.StreamRole); + #endregion - + #region Self Assignable Roles var selfassignableRolesEntity = modelBuilder.Entity(); - selfassignableRolesEntity - .HasIndex(s => new { s.GuildId, s.RoleId }) - .IsUnique(); + selfassignableRolesEntity.HasIndex(s => new { s.GuildId, s.RoleId }).IsUnique(); - selfassignableRolesEntity - .Property(x => x.Group) - .HasDefaultValue(0); + selfassignableRolesEntity.Property(x => x.Group).HasDefaultValue(0); #endregion #region MusicPlaylists + var musicPlaylistEntity = modelBuilder.Entity(); - musicPlaylistEntity - .HasMany(p => p.Songs) - .WithOne() - .OnDelete(DeleteBehavior.Cascade); - + musicPlaylistEntity.HasMany(p => p.Songs).WithOne().OnDelete(DeleteBehavior.Cascade); #endregion #region Waifus var wi = modelBuilder.Entity(); - wi.HasOne(x => x.Waifu) - .WithOne(); + wi.HasOne(x => x.Waifu).WithOne(); wi.HasIndex(x => x.Price); wi.HasIndex(x => x.ClaimerId); @@ -168,29 +144,22 @@ public class NadekoContext : DbContext modelBuilder.Entity(du => { - du.Property(x => x.IsClubAdmin) - .HasDefaultValue(false); + du.Property(x => x.IsClubAdmin).HasDefaultValue(false); - du.Property(x => x.NotifyOnLevelUp) - .HasDefaultValue(XpNotificationLocation.None); + du.Property(x => x.NotifyOnLevelUp).HasDefaultValue(XpNotificationLocation.None); - du.Property(x => x.LastXpGain) - .HasDefaultValueSql("datetime('now', '-1 years')"); + du.Property(x => x.LastXpGain).HasDefaultValueSql("datetime('now', '-1 years')"); - du.Property(x => x.LastLevelUp) - .HasDefaultValueSql("datetime('now')"); + du.Property(x => x.LastLevelUp).HasDefaultValueSql("datetime('now')"); du.HasAlternateKey(w => w.UserId); - du.HasOne(x => x.Club) - .WithMany(x => x.Users) - .IsRequired(false); + du.HasOne(x => x.Club).WithMany(x => x.Users).IsRequired(false); du.HasIndex(x => x.TotalXp); du.HasIndex(x => x.CurrencyAmount); du.HasIndex(x => x.UserId); }); - #endregion #region Warnings @@ -200,27 +169,25 @@ public class NadekoContext : DbContext warn.HasIndex(x => x.GuildId); warn.HasIndex(x => x.UserId); warn.HasIndex(x => x.DateAdded); - warn.Property(x => x.Weight) - .HasDefaultValue(1); + warn.Property(x => x.Weight).HasDefaultValue(1); }); #endregion #region PatreonRewards + var pr = modelBuilder.Entity(); - pr.HasIndex(x => x.PatreonUserId) - .IsUnique(); + pr.HasIndex(x => x.PatreonUserId).IsUnique(); + #endregion #region XpStats - var xps = modelBuilder.Entity(); - xps - .HasIndex(x => new { x.UserId, x.GuildId }) - .IsUnique(); - xps - .Property(x => x.LastLevelUp) - .HasDefaultValue(new DateTime(2017, 9, 21, 20, 53, 13, 307, DateTimeKind.Local)); + var xps = modelBuilder.Entity(); + xps.HasIndex(x => new { x.UserId, x.GuildId }).IsUnique(); + + xps.Property(x => x.LastLevelUp) + .HasDefaultValue(new DateTime(2017, 9, 21, 20, 53, 13, 307, DateTimeKind.Local)); xps.HasIndex(x => x.UserId); xps.HasIndex(x => x.GuildId); @@ -230,156 +197,134 @@ public class NadekoContext : DbContext #endregion #region XpSettings - modelBuilder.Entity() - .HasOne(x => x.GuildConfig) - .WithOne(x => x.XpSettings); + + modelBuilder.Entity().HasOne(x => x.GuildConfig).WithOne(x => x.XpSettings); + #endregion #region XpRoleReward - modelBuilder.Entity() - .HasIndex(x => new { x.XpSettingsId, x.Level }) - .IsUnique(); + + modelBuilder.Entity().HasIndex(x => new { x.XpSettingsId, x.Level }).IsUnique(); + #endregion #region Club + var ci = modelBuilder.Entity(); - ci.HasOne(x => x.Owner) - .WithOne() - .HasForeignKey(x => x.OwnerId); + ci.HasOne(x => x.Owner).WithOne().HasForeignKey(x => x.OwnerId); ci.HasAlternateKey(x => new { x.Name, x.Discrim }); + #endregion #region ClubManytoMany - modelBuilder.Entity() - .HasKey(t => new { t.ClubId, t.UserId }); + modelBuilder.Entity().HasKey(t => new { t.ClubId, t.UserId }); - modelBuilder.Entity() - .HasOne(pt => pt.User) - .WithMany(); + modelBuilder.Entity().HasOne(pt => pt.User).WithMany(); - modelBuilder.Entity() - .HasOne(pt => pt.Club) - .WithMany(x => x.Applicants); + modelBuilder.Entity().HasOne(pt => pt.Club).WithMany(x => x.Applicants); - modelBuilder.Entity() - .HasKey(t => new { t.ClubId, t.UserId }); + modelBuilder.Entity().HasKey(t => new { t.ClubId, t.UserId }); - modelBuilder.Entity() - .HasOne(pt => pt.User) - .WithMany(); + modelBuilder.Entity().HasOne(pt => pt.User).WithMany(); - modelBuilder.Entity() - .HasOne(pt => pt.Club) - .WithMany(x => x.Bans); + modelBuilder.Entity().HasOne(pt => pt.Club).WithMany(x => x.Bans); #endregion #region Polls - modelBuilder.Entity() - .HasIndex(x => x.GuildId) - .IsUnique(); + + modelBuilder.Entity().HasIndex(x => x.GuildId).IsUnique(); + #endregion #region CurrencyTransactions - modelBuilder.Entity() - .HasIndex(x => x.UserId) - .IsUnique(false); + + modelBuilder.Entity().HasIndex(x => x.UserId).IsUnique(false); + #endregion #region Reminders - modelBuilder.Entity() - .HasIndex(x => x.When); + + modelBuilder.Entity().HasIndex(x => x.When); + #endregion - #region GroupName - modelBuilder.Entity() - .HasIndex(x => new { x.GuildConfigId, x.Number }) - .IsUnique(); + #region GroupName + + modelBuilder.Entity().HasIndex(x => new { x.GuildConfigId, x.Number }).IsUnique(); modelBuilder.Entity() - .HasOne(x => x.GuildConfig) - .WithMany(x => x.SelfAssignableRoleGroupNames) - .IsRequired(); + .HasOne(x => x.GuildConfig) + .WithMany(x => x.SelfAssignableRoleGroupNames) + .IsRequired(); + #endregion - + #region BanTemplate - modelBuilder.Entity() - .HasIndex(x => x.GuildId) - .IsUnique(); + modelBuilder.Entity().HasIndex(x => x.GuildId).IsUnique(); #endregion - + #region Perm Override - modelBuilder.Entity() - .HasIndex(x => new {x.GuildId, x.Command}) - .IsUnique(); + modelBuilder.Entity().HasIndex(x => new { x.GuildId, x.Command }).IsUnique(); #endregion - + #region Music - modelBuilder.Entity() - .HasIndex(x => x.GuildId) - .IsUnique(); + modelBuilder.Entity().HasIndex(x => x.GuildId).IsUnique(); - modelBuilder.Entity() - .Property(x => x.Volume) - .HasDefaultValue(100); + modelBuilder.Entity().Property(x => x.Volume).HasDefaultValue(100); #endregion #region Reaction roles modelBuilder.Entity(rrm => rrm - .HasMany(x => x.ReactionRoles) - .WithOne() - .OnDelete(DeleteBehavior.Cascade)); + .HasMany(x => x.ReactionRoles) + .WithOne() + .OnDelete(DeleteBehavior.Cascade)); #endregion #region LogSettings + modelBuilder.Entity(ls => ls.HasIndex(x => x.GuildId).IsUnique()); + modelBuilder.Entity(ls => ls - .HasIndex(x => x.GuildId) - .IsUnique()); - - modelBuilder.Entity(ls => ls - .HasMany(x => x.LogIgnores) - .WithOne(x => x.LogSetting) - .OnDelete(DeleteBehavior.Cascade)); + .HasMany(x => x.LogIgnores) + .WithOne(x => x.LogSetting) + .OnDelete(DeleteBehavior.Cascade)); modelBuilder.Entity(ili => ili - .HasIndex(x => new { x.LogSettingId, x.LogItemId, x.ItemType }) - .IsUnique()); + .HasIndex(x => new { x.LogSettingId, x.LogItemId, x.ItemType }) + .IsUnique()); #endregion - modelBuilder.Entity(ioc => ioc - .HasIndex(x => x.ChannelId) - .IsUnique()); + modelBuilder.Entity(ioc => ioc.HasIndex(x => x.ChannelId).IsUnique()); - modelBuilder.Entity(nbt => nbt - .HasIndex(x => x.GuildId) - .IsUnique(false)); + modelBuilder.Entity(nbt => nbt.HasIndex(x => x.GuildId).IsUnique(false)); var atch = modelBuilder.Entity(); - atch.HasIndex(x => x.GuildId) - .IsUnique(false); + atch.HasIndex(x => x.GuildId).IsUnique(false); - atch.HasIndex(x => x.ChannelId) - .IsUnique(); + atch.HasIndex(x => x.ChannelId).IsUnique(); - atch - .HasMany(x => x.Users) - .WithOne(x => x.Channel) - .OnDelete(DeleteBehavior.Cascade); + atch.HasMany(x => x.Users).WithOne(x => x.Channel).OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity(atu => atu - .HasAlternateKey(x => new { x.ChannelId, x.UserId })); + modelBuilder.Entity(atu => atu.HasAlternateKey(x => new { x.ChannelId, x.UserId })); } -} + +#if DEBUG + private static readonly ILoggerFactory _debugLoggerFactory = LoggerFactory.Create(x => x.AddConsole()); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseLoggerFactory(_debugLoggerFactory); +#endif +} \ No newline at end of file diff --git a/src/NadekoBot/GlobalUsings.cs b/src/NadekoBot/GlobalUsings.cs index e330cb5ee..4b5971fad 100644 --- a/src/NadekoBot/GlobalUsings.cs +++ b/src/NadekoBot/GlobalUsings.cs @@ -1,23 +1,27 @@ +global using System.Collections.Concurrent; + +// packages global using Serilog; global using Humanizer; +// nadekobot global using NadekoBot; - global using NadekoBot.Services; global using NadekoBot.Common; global using NadekoBot.Common.Attributes; global using NadekoBot.Extensions; +// discord global using Discord; global using Discord.Commands; global using Discord.Net; global using Discord.WebSocket; +// aliases global using GuildPerm = Discord.GuildPermission; global using ChannelPerm = Discord.ChannelPermission; global using BotPermAttribute = Discord.Commands.RequireBotPermissionAttribute; global using LeftoverAttribute = Discord.Commands.RemainderAttribute; -global using System.Collections.Concurrent; - -global using JetBrains.Annotations; +// non-essential +global using JetBrains.Annotations; \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Administration.cs b/src/NadekoBot/Modules/Administration/Administration.cs index 095082f52..e52eae951 100644 --- a/src/NadekoBot/Modules/Administration/Administration.cs +++ b/src/NadekoBot/Modules/Administration/Administration.cs @@ -6,10 +6,13 @@ namespace NadekoBot.Modules.Administration; public partial class Administration : NadekoModule { - private readonly ImageOnlyChannelService _imageOnly; - - public Administration(ImageOnlyChannelService imageOnly) - => _imageOnly = imageOnly; + public enum Channel + { + Channel, + Ch, + Chnl, + Chan + } public enum List { @@ -17,7 +20,25 @@ public partial class Administration : NadekoModule Ls = 0 } - [NadekoCommand, Aliases] + public enum Server + { + Server + } + + public enum State + { + Enable, + Disable, + Inherit + } + + private readonly ImageOnlyChannelService _imageOnly; + + public Administration(ImageOnlyChannelService imageOnly) + => _imageOnly = imageOnly; + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [BotPerm(GuildPerm.Administrator)] @@ -30,7 +51,8 @@ public partial class Administration : NadekoModule await ReplyPendingLocalizedAsync(strs.imageonly_disable); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(ChannelPerm.ManageChannels)] [BotPerm(ChannelPerm.ManageChannels)] @@ -39,9 +61,9 @@ public partial class Administration : NadekoModule var seconds = (int?)time?.Time.TotalSeconds ?? 0; if (time is not null && (time.Time < TimeSpan.FromSeconds(0) || time.Time > TimeSpan.FromHours(6))) return; - - await ((ITextChannel) ctx.Channel).ModifyAsync(tcp => + + await ((ITextChannel)ctx.Channel).ModifyAsync(tcp => { tcp.SlowModeInterval = seconds; }); @@ -49,26 +71,26 @@ public partial class Administration : NadekoModule await ctx.OkAsync(); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [BotPerm(GuildPerm.ManageMessages)] [Priority(2)] public async Task Delmsgoncmd(List _) { - var guild = (SocketGuild) ctx.Guild; + var guild = (SocketGuild)ctx.Guild; var (enabled, channels) = _service.GetDelMsgOnCmdData(ctx.Guild.Id); var embed = _eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.server_delmsgoncmd)) - .WithDescription(enabled ? "✅" : "❌"); + .WithOkColor() + .WithTitle(GetText(strs.server_delmsgoncmd)) + .WithDescription(enabled ? "✅" : "❌"); - var str = string.Join("\n", channels - .Select(x => + var str = string.Join("\n", + channels.Select(x => { - var ch = guild.GetChannel(x.ChannelId)?.ToString() - ?? x.ChannelId.ToString(); + var ch = guild.GetChannel(x.ChannelId)?.ToString() ?? x.ChannelId.ToString(); var prefix = x.State ? "✅ " : "❌ "; return prefix + ch; })); @@ -81,12 +103,8 @@ public partial class Administration : NadekoModule await ctx.Channel.EmbedAsync(embed); } - public enum Server - { - Server - } - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [BotPerm(GuildPerm.ManageMessages)] @@ -105,22 +123,8 @@ public partial class Administration : NadekoModule } } - public enum Channel - { - Channel, - Ch, - Chnl, - Chan - } - - public enum State - { - Enable, - Disable, - Inherit - } - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [BotPerm(GuildPerm.ManageMessages)] @@ -128,7 +132,8 @@ public partial class Administration : NadekoModule public Task Delmsgoncmd(Channel _, State s, ITextChannel ch) => Delmsgoncmd(_, s, ch.Id); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [BotPerm(GuildPerm.ManageMessages)] @@ -139,20 +144,15 @@ public partial class Administration : NadekoModule await _service.SetDelMsgOnCmdState(ctx.Guild.Id, actualChId, s); if (s == State.Disable) - { await ReplyConfirmLocalizedAsync(strs.delmsg_channel_off); - } else if (s == State.Enable) - { await ReplyConfirmLocalizedAsync(strs.delmsg_channel_on); - } else - { await ReplyConfirmLocalizedAsync(strs.delmsg_channel_inherit); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.DeafenMembers)] [BotPerm(GuildPerm.DeafenMembers)] @@ -162,7 +162,8 @@ public partial class Administration : NadekoModule await ReplyConfirmLocalizedAsync(strs.deafen); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.DeafenMembers)] [BotPerm(GuildPerm.DeafenMembers)] @@ -172,7 +173,8 @@ public partial class Administration : NadekoModule await ReplyConfirmLocalizedAsync(strs.undeafen); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageChannels)] [BotPerm(GuildPerm.ManageChannels)] @@ -182,7 +184,8 @@ public partial class Administration : NadekoModule await ReplyConfirmLocalizedAsync(strs.delvoich(Format.Bold(voiceChannel.Name))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageChannels)] [BotPerm(GuildPerm.ManageChannels)] @@ -192,7 +195,8 @@ public partial class Administration : NadekoModule await ReplyConfirmLocalizedAsync(strs.createvoich(Format.Bold(ch.Name))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageChannels)] [BotPerm(GuildPerm.ManageChannels)] @@ -202,7 +206,8 @@ public partial class Administration : NadekoModule await ReplyConfirmLocalizedAsync(strs.deltextchan(Format.Bold(toDelete.Name))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageChannels)] [BotPerm(GuildPerm.ManageChannels)] @@ -212,36 +217,39 @@ public partial class Administration : NadekoModule await ReplyConfirmLocalizedAsync(strs.createtextchan(Format.Bold(txtCh.Name))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageChannels)] [BotPerm(GuildPerm.ManageChannels)] public async Task SetTopic([Leftover] string topic = null) { - var channel = (ITextChannel) ctx.Channel; + var channel = (ITextChannel)ctx.Channel; topic ??= ""; await channel.ModifyAsync(c => c.Topic = topic); await ReplyConfirmLocalizedAsync(strs.set_topic); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageChannels)] [BotPerm(GuildPerm.ManageChannels)] public async Task SetChanlName([Leftover] string name) { - var channel = (ITextChannel) ctx.Channel; + var channel = (ITextChannel)ctx.Channel; await channel.ModifyAsync(c => c.Name = name); await ReplyConfirmLocalizedAsync(strs.set_channel_name); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageChannels)] [BotPerm(GuildPerm.ManageChannels)] public async Task NsfwToggle() { - var channel = (ITextChannel) ctx.Channel; + var channel = (ITextChannel)ctx.Channel; var isEnabled = channel.IsNsfw; await channel.ModifyAsync(c => c.IsNsfw = !isEnabled); @@ -252,20 +260,22 @@ public partial class Administration : NadekoModule await ReplyConfirmLocalizedAsync(strs.nsfw_set_true); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(ChannelPerm.ManageMessages)] [Priority(0)] public Task Edit(ulong messageId, [Leftover] string text) - => Edit((ITextChannel) ctx.Channel, messageId, text); + => Edit((ITextChannel)ctx.Channel, messageId, text); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] public async Task Edit(ITextChannel channel, ulong messageId, [Leftover] string text) { - var userPerms = ((SocketGuildUser) ctx.User).GetPermissions(channel); - var botPerms = ((SocketGuild) ctx.Guild).CurrentUser.GetPermissions(channel); + var userPerms = ((SocketGuildUser)ctx.User).GetPermissions(channel); + var botPerms = ((SocketGuild)ctx.Guild).CurrentUser.GetPermissions(channel); if (!userPerms.Has(ChannelPermission.ManageMessages)) { await ReplyErrorLocalizedAsync(strs.insuf_perms_u); @@ -281,23 +291,28 @@ public partial class Administration : NadekoModule await _service.EditMessage(ctx, channel, messageId, text); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(ChannelPerm.ManageMessages)] [BotPerm(ChannelPerm.ManageMessages)] public Task Delete(ulong messageId, StoopidTime time = null) - => Delete((ITextChannel) ctx.Channel, messageId, time); + => Delete((ITextChannel)ctx.Channel, messageId, time); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Delete(ITextChannel channel, ulong messageId, StoopidTime time = null) => await InternalMessageAction(channel, messageId, time, msg => msg.DeleteAsync()); - private async Task InternalMessageAction(ITextChannel channel, ulong messageId, StoopidTime time, + private async Task InternalMessageAction( + ITextChannel channel, + ulong messageId, + StoopidTime time, Func func) { - var userPerms = ((SocketGuildUser) ctx.User).GetPermissions(channel); - var botPerms = ((SocketGuild) ctx.Guild).CurrentUser.GetPermissions(channel); + var userPerms = ((SocketGuildUser)ctx.User).GetPermissions(channel); + var botPerms = ((SocketGuild)ctx.Guild).CurrentUser.GetPermissions(channel); if (!userPerms.Has(ChannelPermission.ManageMessages)) { await ReplyErrorLocalizedAsync(strs.insuf_perms_u); @@ -338,4 +353,4 @@ public partial class Administration : NadekoModule await ctx.OkAsync(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/AutoAssignRoleCommands.cs b/src/NadekoBot/Modules/Administration/AutoAssignRoleCommands.cs index 39f366017..defc82356 100644 --- a/src/NadekoBot/Modules/Administration/AutoAssignRoleCommands.cs +++ b/src/NadekoBot/Modules/Administration/AutoAssignRoleCommands.cs @@ -8,13 +8,14 @@ public partial class Administration [Group] public class AutoAssignRoleCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] public async Task AutoAssignRole([Leftover] IRole role) { - var guser = (IGuildUser) ctx.User; + var guser = (IGuildUser)ctx.User; if (role.Id == ctx.Guild.EveryoneRole.Id) return; @@ -27,20 +28,15 @@ public partial class Administration var roles = await _service.ToggleAarAsync(ctx.Guild.Id, role.Id); if (roles.Count == 0) - { await ReplyConfirmLocalizedAsync(strs.aar_disabled); - } else if (roles.Contains(role.Id)) - { await AutoAssignRole(); - } else - { await ReplyConfirmLocalizedAsync(strs.aar_role_removed(Format.Bold(role.ToString()))); - } } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] @@ -51,18 +47,14 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.aar_none); return; } - - var existing = roles.Select(rid => ctx.Guild.GetRole(rid)).Where(r => r is not null) - .ToList(); + + var existing = roles.Select(rid => ctx.Guild.GetRole(rid)).Where(r => r is not null).ToList(); if (existing.Count != roles.Count) - { await _service.SetAarRolesAsync(ctx.Guild.Id, existing.Select(x => x.Id)); - } await ReplyConfirmLocalizedAsync(strs.aar_roles( - '\n' + existing.Select(x => Format.Bold(x.ToString())) - .Join(",\n"))); + '\n' + existing.Select(x => Format.Bold(x.ToString())).Join(",\n"))); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Common/LogType.cs b/src/NadekoBot/Modules/Administration/Common/LogType.cs index e79c48087..494318da9 100644 --- a/src/NadekoBot/Modules/Administration/Common/LogType.cs +++ b/src/NadekoBot/Modules/Administration/Common/LogType.cs @@ -18,4 +18,4 @@ public enum LogType VoicePresence, VoicePresenceTTS, UserMuted -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Common/ProtectionStats.cs b/src/NadekoBot/Modules/Administration/Common/ProtectionStats.cs index c70695afd..36761b502 100644 --- a/src/NadekoBot/Modules/Administration/Common/ProtectionStats.cs +++ b/src/NadekoBot/Modules/Administration/Common/ProtectionStats.cs @@ -25,18 +25,28 @@ public class AntiSpamStats public class AntiAltStats { + public PunishmentAction Action + => _setting.Action; + + public int ActionDurationMinutes + => _setting.ActionDurationMinutes; + + public ulong? RoleId + => _setting.RoleId; + + public TimeSpan MinAge + => _setting.MinAge; + + public int Counter + => _counter; + private readonly AntiAltSetting _setting; - public PunishmentAction Action => _setting.Action; - public int ActionDurationMinutes => _setting.ActionDurationMinutes; - public ulong? RoleId => _setting.RoleId; - public TimeSpan MinAge => _setting.MinAge; - - private int _counter = 0; - public int Counter => _counter; + + private int _counter; public AntiAltStats(AntiAltSetting setting) => _setting = setting; - public void Increment() => Interlocked.Increment(ref _counter); - -} + public void Increment() + => Interlocked.Increment(ref _counter); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Common/PunishQueueItem.cs b/src/NadekoBot/Modules/Administration/Common/PunishQueueItem.cs index 87f6c5cab..97dcbe81e 100644 --- a/src/NadekoBot/Modules/Administration/Common/PunishQueueItem.cs +++ b/src/NadekoBot/Modules/Administration/Common/PunishQueueItem.cs @@ -10,4 +10,4 @@ public class PunishQueueItem public int MuteTime { get; set; } public ulong? RoleId { get; set; } public IGuildUser User { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Common/UserSpamStats.cs b/src/NadekoBot/Modules/Administration/Common/UserSpamStats.cs index 14aa8b0ad..63661b3c4 100644 --- a/src/NadekoBot/Modules/Administration/Common/UserSpamStats.cs +++ b/src/NadekoBot/Modules/Administration/Common/UserSpamStats.cs @@ -3,11 +3,15 @@ namespace NadekoBot.Modules.Administration.Common; public sealed class UserSpamStats : IDisposable { - public int Count => timers.Count; + public int Count + => timers.Count; + public string LastMessage { get; set; } private ConcurrentQueue timers { get; } + private readonly object applyLock = new(); + public UserSpamStats(IUserMessage msg) { LastMessage = msg.Content.ToUpperInvariant(); @@ -16,7 +20,6 @@ public sealed class UserSpamStats : IDisposable ApplyNextMessage(msg); } - private readonly object applyLock = new(); public void ApplyNextMessage(IUserMessage message) { lock (applyLock) @@ -28,10 +31,15 @@ public sealed class UserSpamStats : IDisposable while (timers.TryDequeue(out var old)) old.Change(Timeout.Infinite, Timeout.Infinite); } - var t = new Timer(_ => { - if (timers.TryDequeue(out var old)) - old.Change(Timeout.Infinite, Timeout.Infinite); - }, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(30)); + + var t = new Timer(_ => + { + if (timers.TryDequeue(out var old)) + old.Change(Timeout.Infinite, Timeout.Infinite); + }, + null, + TimeSpan.FromMinutes(30), + TimeSpan.FromMinutes(30)); timers.Enqueue(t); } } @@ -41,4 +49,4 @@ public sealed class UserSpamStats : IDisposable while (timers.TryDequeue(out var old)) old.Change(Timeout.Infinite, Timeout.Infinite); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/DangerousCommands.cs b/src/NadekoBot/Modules/Administration/DangerousCommands.cs index 1b43e8617..e0ef3cbee 100644 --- a/src/NadekoBot/Modules/Administration/DangerousCommands.cs +++ b/src/NadekoBot/Modules/Administration/DangerousCommands.cs @@ -10,20 +10,17 @@ namespace NadekoBot.Modules.Administration [OwnerOnly] public class DangerousCommands : NadekoSubmodule { - private async Task InternalExecSql(string sql, params object[] reps) { sql = string.Format(sql, reps); try { var embed = _eb.Create() - .WithTitle(GetText(strs.sql_confirm_exec)) - .WithDescription(Format.Code(sql)); + .WithTitle(GetText(strs.sql_confirm_exec)) + .WithDescription(Format.Code(sql)); if (!await PromptUserConfirmAsync(embed)) - { return; - } var res = await _service.ExecuteSql(sql); await SendConfirmAsync(res.ToString()); @@ -34,87 +31,91 @@ namespace NadekoBot.Modules.Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] - public Task SqlSelect([Leftover]string sql) + public Task SqlSelect([Leftover] string sql) { var result = _service.SelectSql(sql); - return ctx.SendPaginatedConfirmAsync(0, cur => - { - var items = result.Results.Skip(cur * 20).Take(20); - - if (!items.Any()) + return ctx.SendPaginatedConfirmAsync(0, + cur => { + var items = result.Results.Skip(cur * 20).Take(20).ToList(); + + if (!items.Any()) + return _eb.Create().WithErrorColor().WithFooter(sql).WithDescription("-"); + return _eb.Create() - .WithErrorColor() - .WithFooter(sql) - .WithDescription("-"); - } - - return _eb.Create() - .WithOkColor() - .WithFooter(sql) - .WithTitle(string.Join(" ║ ", result.ColumnNames)) - .WithDescription(string.Join('\n', items.Select(x => string.Join(" ║ ", x)))); - - }, result.Results.Count, 20); + .WithOkColor() + .WithFooter(sql) + .WithTitle(string.Join(" ║ ", result.ColumnNames)) + .WithDescription(string.Join('\n', items.Select(x => string.Join(" ║ ", x)))); + }, + result.Results.Count, + 20); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] - public Task SqlExec([Leftover]string sql) => - InternalExecSql(sql); + public Task SqlExec([Leftover] string sql) + => InternalExecSql(sql); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] - public Task DeleteWaifus() => - SqlExec(DangerousCommandsService.WaifusDeleteSql); + public Task DeleteWaifus() + => SqlExec(DangerousCommandsService.WaifusDeleteSql); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] - public Task DeleteWaifu(IUser user) => - DeleteWaifu(user.Id); + public Task DeleteWaifu(IUser user) + => DeleteWaifu(user.Id); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] - public Task DeleteWaifu(ulong userId) => - InternalExecSql(DangerousCommandsService.WaifuDeleteSql, userId); + public Task DeleteWaifu(ulong userId) + => InternalExecSql(DangerousCommandsService.WaifuDeleteSql, userId); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] - public Task DeleteCurrency() => - SqlExec(DangerousCommandsService.CurrencyDeleteSql); + public Task DeleteCurrency() + => SqlExec(DangerousCommandsService.CurrencyDeleteSql); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] - public Task DeletePlaylists() => - SqlExec(DangerousCommandsService.MusicPlaylistDeleteSql); + public Task DeletePlaylists() + => SqlExec(DangerousCommandsService.MusicPlaylistDeleteSql); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] - public Task DeleteXp() => - SqlExec(DangerousCommandsService.XpDeleteSql); + public Task DeleteXp() + => SqlExec(DangerousCommandsService.XpDeleteSql); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task PurgeUser(ulong userId) { var embed = _eb.Create() - .WithDescription(GetText(strs.purge_user_confirm(Format.Bold(userId.ToString())))); + .WithDescription(GetText(strs.purge_user_confirm(Format.Bold(userId.ToString())))); + + if (!await PromptUserConfirmAsync(embed)) return; - if (!await PromptUserConfirmAsync(embed)) - { - return; - } - await _service.PurgeUserAsync(userId); await ctx.OkAsync(); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] - public Task PurgeUser([Leftover]IUser user) + public Task PurgeUser([Leftover] IUser user) => PurgeUser(user.Id); //[NadekoCommand, Usage, Description, Aliases] //[OwnerOnly] @@ -123,4 +124,4 @@ namespace NadekoBot.Modules.Administration } } } -#endif +#endif \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/DiscordPermOverrideCommands.cs b/src/NadekoBot/Modules/Administration/DiscordPermOverrideCommands.cs index 9c72987de..eafa1875d 100644 --- a/src/NadekoBot/Modules/Administration/DiscordPermOverrideCommands.cs +++ b/src/NadekoBot/Modules/Administration/DiscordPermOverrideCommands.cs @@ -11,7 +11,8 @@ public partial class Administration { // override stats, it should require that the user has managessages guild permission // .po 'stats' add user guild managemessages - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task DiscordPermOverride(CommandOrCrInfo cmd, params GuildPerm[] perms) @@ -26,20 +27,20 @@ public partial class Administration var aggregatePerms = perms.Aggregate((acc, seed) => seed | acc); await _service.AddOverride(ctx.Guild.Id, cmd.Name, aggregatePerms); - await ReplyConfirmLocalizedAsync(strs.perm_override( - Format.Bold(aggregatePerms.ToString()), + await ReplyConfirmLocalizedAsync(strs.perm_override(Format.Bold(aggregatePerms.ToString()), Format.Code(cmd.Name))); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task DiscordPermOverrideReset() { var result = await PromptUserConfirmAsync(_eb.Create() - .WithOkColor() - .WithDescription(GetText(strs.perm_override_all_confirm))); - + .WithOkColor() + .WithDescription(GetText(strs.perm_override_all_confirm))); + if (!result) return; await _service.ClearAllOverrides(ctx.Guild.Id); @@ -47,35 +48,34 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.perm_override_all); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task DiscordPermOverrideList(int page = 1) { if (--page < 0) return; - + var overrides = await _service.GetAllOverrides(ctx.Guild.Id); - await ctx.SendPaginatedConfirmAsync(page, curPage => - { - var eb = _eb.Create() - .WithTitle(GetText(strs.perm_overrides)) - .WithOkColor(); + await ctx.SendPaginatedConfirmAsync(page, + curPage => + { + var eb = _eb.Create().WithTitle(GetText(strs.perm_overrides)).WithOkColor(); - var thisPageOverrides = overrides - .Skip(9 * curPage) - .Take(9) - .ToList(); + var thisPageOverrides = overrides.Skip(9 * curPage).Take(9).ToList(); - if (thisPageOverrides.Count == 0) - eb.WithDescription(GetText(strs.perm_override_page_none)); - else - eb.WithDescription(string.Join("\n", - thisPageOverrides.Select(ov => $"{ov.Command} => {ov.Perm.ToString()}"))); + if (thisPageOverrides.Count == 0) + eb.WithDescription(GetText(strs.perm_override_page_none)); + else + eb.WithDescription(string.Join("\n", + thisPageOverrides.Select(ov => $"{ov.Command} => {ov.Perm.ToString()}"))); - return eb; - }, overrides.Count, 9, true); + return eb; + }, + overrides.Count, + 9); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/GameChannelCommands.cs b/src/NadekoBot/Modules/Administration/GameChannelCommands.cs index accee8ef9..3ccb868fa 100644 --- a/src/NadekoBot/Modules/Administration/GameChannelCommands.cs +++ b/src/NadekoBot/Modules/Administration/GameChannelCommands.cs @@ -8,7 +8,8 @@ public partial class Administration [Group] public class GameChannelCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [BotPerm(GuildPerm.MoveMembers)] @@ -21,6 +22,7 @@ public partial class Administration await ReplyErrorLocalizedAsync(strs.not_in_voice); return; } + var id = _service.ToggleGameVoiceChannel(ctx.Guild.Id, vch.Id); if (id is null) @@ -34,4 +36,4 @@ public partial class Administration } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/GreetBye/GreetGrouper.cs b/src/NadekoBot/Modules/Administration/GreetBye/GreetGrouper.cs index 90294c09c..a6300e0d1 100644 --- a/src/NadekoBot/Modules/Administration/GreetBye/GreetGrouper.cs +++ b/src/NadekoBot/Modules/Administration/GreetBye/GreetGrouper.cs @@ -10,7 +10,7 @@ public class GreetGrouper /// - /// Creates a group, if group already exists, adds the specified user + /// Creates a group, if group already exists, adds the specified user /// /// Id of the server for which to create group for /// User to add if group already exists @@ -31,7 +31,7 @@ public class GreetGrouper } /// - /// Remove the specified amount of items from the group. If all items are removed, group will be removed. + /// Remove the specified amount of items from the group. If all items are removed, group will be removed. /// /// Id of the group /// Maximum number of items to retrieve @@ -54,8 +54,7 @@ public class GreetGrouper // if there are more in the group than what's needed // take the requested number, remove them from the set // and return them - var toReturn = set.TakeWhile(_ => count-- != 0) - .ToList(); + var toReturn = set.TakeWhile(_ => count-- != 0).ToList(); foreach (var item in toReturn) set.Remove(item); @@ -69,4 +68,4 @@ public class GreetGrouper return true; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/GreetBye/GreetSettings.cs b/src/NadekoBot/Modules/Administration/GreetBye/GreetSettings.cs index 70ead129f..17d746cb3 100644 --- a/src/NadekoBot/Modules/Administration/GreetBye/GreetSettings.cs +++ b/src/NadekoBot/Modules/Administration/GreetBye/GreetSettings.cs @@ -1,4 +1,3 @@ - using NadekoBot.Services.Database.Models; namespace NadekoBot.Services; @@ -43,4 +42,4 @@ public class GreetSettings BoostMessageDeleteAfter = g.BoostMessageDeleteAfter, BoostMessageChannelId = g.BoostMessageChannelId }; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/GreetBye/GreetSettingsService.cs b/src/NadekoBot/Modules/Administration/GreetBye/GreetSettingsService.cs index cea45a6af..cf5a37044 100644 --- a/src/NadekoBot/Modules/Administration/GreetBye/GreetSettingsService.cs +++ b/src/NadekoBot/Modules/Administration/GreetBye/GreetSettingsService.cs @@ -1,10 +1,13 @@ -using NadekoBot.Services.Database.Models; using NadekoBot.Db; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Services; public class GreetSettingsService : INService { + public bool GroupGreets + => _bss.Data.GroupGreets; + private readonly DbService _db; private readonly ConcurrentDictionary _guildConfigsCache; @@ -14,9 +17,6 @@ public class GreetSettingsService : INService private readonly GreetGrouper _byes = new(); private readonly BotConfigService _bss; - public bool GroupGreets - => _bss.Data.GroupGreets; - public GreetSettingsService( DiscordSocketClient client, Bot bot, @@ -42,8 +42,10 @@ public class GreetSettingsService : INService { // if user is a new booster // or boosted again the same server - if ((optOldUser.Value is { PremiumSince: null } && newUser is { PremiumSince: not null }) || - (optOldUser.Value?.PremiumSince is { } oldDate && newUser.PremiumSince is { } newDate && newDate > oldDate)) + if ((optOldUser.Value is { PremiumSince: null } && newUser is { PremiumSince: not null }) + || (optOldUser.Value?.PremiumSince is { } oldDate + && newUser.PremiumSince is { } newDate + && newDate > oldDate)) { var conf = GetOrAddSettingsForGuild(newUser.Guild.Id); if (!conf.SendBoostMessage) return Task.CompletedTask; @@ -65,20 +67,12 @@ public class GreetSettingsService : INService return; var toSend = SmartText.CreateFrom(conf.BoostMessage); - var rep = new ReplacementBuilder().WithDefault(user, - channel, - user.Guild, - _client - ) - .Build(); + var rep = new ReplacementBuilder().WithDefault(user, channel, user.Guild, _client).Build(); try { var toDelete = await channel.SendAsync(rep.Replace(toSend)); - if (conf.BoostMessageDeleteAfter > 0) - { - toDelete.DeleteAfter(conf.BoostMessageDeleteAfter); - } + if (conf.BoostMessageDeleteAfter > 0) toDelete.DeleteAfter(conf.BoostMessageDeleteAfter); } catch (Exception ex) { @@ -96,76 +90,71 @@ public class GreetSettingsService : INService { _guildConfigsCache.AddOrUpdate(gc.GuildId, GreetSettings.Create(gc), - delegate { return GreetSettings.Create(gc); } - ); + delegate { return GreetSettings.Create(gc); }); return Task.CompletedTask; } private Task UserLeft(SocketGuild guild, SocketUser user) { var _ = Task.Run(async () => + { + try { - try + var conf = GetOrAddSettingsForGuild(guild.Id); + + if (!conf.SendChannelByeMessage) return; + var channel = guild.TextChannels.FirstOrDefault(c => c.Id == conf.ByeMessageChannelId); + + if (channel is null) //maybe warn the server owner that the channel is missing + return; + + if (GroupGreets) { - var conf = GetOrAddSettingsForGuild(guild.Id); - - if (!conf.SendChannelByeMessage) return; - var channel = guild.TextChannels.FirstOrDefault(c => c.Id == conf.ByeMessageChannelId); - - if (channel is null) //maybe warn the server owner that the channel is missing - return; - - if (GroupGreets) + // if group is newly created, greet that user right away, + // but any user which joins in the next 5 seconds will + // be greeted in a group greet + if (_byes.CreateOrAdd(guild.Id, user)) { - // if group is newly created, greet that user right away, - // but any user which joins in the next 5 seconds will - // be greeted in a group greet - if (_byes.CreateOrAdd(guild.Id, user)) + // greet single user + await ByeUsers(conf, channel, new[] { user }); + var groupClear = false; + while (!groupClear) { - // greet single user - await ByeUsers(conf, channel, new[] { user }); - var groupClear = false; - while (!groupClear) - { - await Task.Delay(5000); - groupClear = _byes.ClearGroup(guild.Id, 5, out var toBye); - await ByeUsers(conf, channel, toBye); - } + await Task.Delay(5000); + groupClear = _byes.ClearGroup(guild.Id, 5, out var toBye); + await ByeUsers(conf, channel, toBye); } } - else - { - await ByeUsers(conf, channel, new[] { user }); - } } - catch + else { - // ignored + await ByeUsers(conf, channel, new[] { user }); } } - ); + catch + { + // ignored + } + }); return Task.CompletedTask; } public string? GetDmGreetMsg(ulong id) { using var uow = _db.GetDbContext(); - return uow.GuildConfigsForId(id, set => set) - ?.DmGreetMessageText; + return uow.GuildConfigsForId(id, set => set)?.DmGreetMessageText; } public string? GetGreetMsg(ulong gid) { using var uow = _db.GetDbContext(); - return uow.GuildConfigsForId(gid, set => set) - .ChannelGreetMessageText; + return uow.GuildConfigsForId(gid, set => set).ChannelGreetMessageText; } public string? GetBoostMessage(ulong gid) { using var uow = _db.GetDbContext(); - return uow.GuildConfigsForId(gid, set => set) - .BoostMessage; + return uow.GuildConfigsForId(gid, set => set).BoostMessage; } private Task ByeUsers(GreetSettings conf, ITextChannel channel, IUser user) @@ -177,20 +166,17 @@ public class GreetSettingsService : INService return; var rep = new ReplacementBuilder().WithChannel(channel) - .WithClient(_client) - .WithServer(_client, (SocketGuild)channel.Guild) - .WithManyUsers(users) - .Build(); + .WithClient(_client) + .WithServer(_client, (SocketGuild)channel.Guild) + .WithManyUsers(users) + .Build(); var text = SmartText.CreateFrom(conf.ChannelByeMessageText); text = rep.Replace(text); try { var toDelete = await channel.SendAsync(text); - if (conf.AutoDeleteByeMessagesTimer > 0) - { - toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer); - } + if (conf.AutoDeleteByeMessagesTimer > 0) toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer); } catch (Exception ex) { @@ -207,20 +193,17 @@ public class GreetSettingsService : INService return; var rep = new ReplacementBuilder().WithChannel(channel) - .WithClient(_client) - .WithServer(_client, (SocketGuild)channel.Guild) - .WithManyUsers(users) - .Build(); + .WithClient(_client) + .WithServer(_client, (SocketGuild)channel.Guild) + .WithManyUsers(users) + .Build(); var text = SmartText.CreateFrom(conf.ChannelGreetMessageText); text = rep.Replace(text); try { var toDelete = await channel.SendAsync(text); - if (conf.AutoDeleteGreetMessagesTimer > 0) - { - toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer); - } + if (conf.AutoDeleteGreetMessagesTimer > 0) toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer); } catch (Exception ex) { @@ -230,12 +213,7 @@ public class GreetSettingsService : INService private async Task GreetDmUser(GreetSettings conf, IDMChannel channel, IGuildUser user) { - var rep = new ReplacementBuilder().WithDefault(user, - channel, - (SocketGuild)user.Guild, - _client - ) - .Build(); + var rep = new ReplacementBuilder().WithDefault(user, channel, (SocketGuild)user.Guild, _client).Build(); var text = SmartText.CreateFrom(conf.DmGreetMessageText); rep.Replace(text); @@ -254,65 +232,60 @@ public class GreetSettingsService : INService private Task UserJoined(IGuildUser user) { var _ = Task.Run(async () => + { + try { - try - { - var conf = GetOrAddSettingsForGuild(user.GuildId); + var conf = GetOrAddSettingsForGuild(user.GuildId); - if (conf.SendChannelGreetMessage) + if (conf.SendChannelGreetMessage) + { + var channel = await user.Guild.GetTextChannelAsync(conf.GreetMessageChannelId); + if (channel != null) { - var channel = await user.Guild.GetTextChannelAsync(conf.GreetMessageChannelId); - if (channel != null) + if (GroupGreets) { - if (GroupGreets) + // if group is newly created, greet that user right away, + // but any user which joins in the next 5 seconds will + // be greeted in a group greet + if (_greets.CreateOrAdd(user.GuildId, user)) { - // if group is newly created, greet that user right away, - // but any user which joins in the next 5 seconds will - // be greeted in a group greet - if (_greets.CreateOrAdd(user.GuildId, user)) + // greet single user + await GreetUsers(conf, channel, new[] { user }); + var groupClear = false; + while (!groupClear) { - // greet single user - await GreetUsers(conf, channel, new[] { user }); - var groupClear = false; - while (!groupClear) - { - await Task.Delay(5000); - groupClear = _greets.ClearGroup(user.GuildId, 5, out var toGreet); - await GreetUsers(conf, channel, toGreet); - } + await Task.Delay(5000); + groupClear = _greets.ClearGroup(user.GuildId, 5, out var toGreet); + await GreetUsers(conf, channel, toGreet); } } - else - { - await GreetUsers(conf, channel, new[] { user }); - } } - } - - if (conf.SendDmGreetMessage) - { - var channel = await user.CreateDMChannelAsync(); - - if (channel is not null) + else { - await GreetDmUser(conf, channel, user); + await GreetUsers(conf, channel, new[] { user }); } } } - catch + + if (conf.SendDmGreetMessage) { - // ignored + var channel = await user.CreateDMChannelAsync(); + + if (channel is not null) await GreetDmUser(conf, channel, user); } } - ); + catch + { + // ignored + } + }); return Task.CompletedTask; } public string? GetByeMessage(ulong gid) { using var uow = _db.GetDbContext(); - return uow.GuildConfigsForId(gid, set => set) - .ChannelByeMessageText; + return uow.GuildConfigsForId(gid, set => set).ChannelByeMessageText; } public GreetSettings GetOrAddSettingsForGuild(ulong guildId) @@ -332,11 +305,9 @@ public class GreetSettingsService : INService public async Task SetSettings(ulong guildId, GreetSettings settings) { - if (settings.AutoDeleteByeMessagesTimer is > 600 or < 0 || - settings.AutoDeleteGreetMessagesTimer is > 600 or < 0) - { + if (settings.AutoDeleteByeMessagesTimer is > 600 or < 0 + || settings.AutoDeleteGreetMessagesTimer is > 600 or < 0) return false; - } await using var uow = _db.GetDbContext(); var conf = uow.GuildConfigsForId(guildId, set => set); @@ -411,53 +382,6 @@ public class GreetSettingsService : INService return enabled; } - #region Get Enabled Status - - public bool GetGreetDmEnabled(ulong guildId) - { - using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - return conf.SendDmGreetMessage; - } - - public bool GetGreetEnabled(ulong guildId) - { - using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - return conf.SendChannelGreetMessage; - } - - public bool GetByeEnabled(ulong guildId) - { - using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - return conf.SendChannelByeMessage; - } - - #endregion - - #region Test Messages - - public Task ByeTest(ITextChannel channel, IGuildUser user) - { - var conf = GetOrAddSettingsForGuild(user.GuildId); - return ByeUsers(conf, channel, user); - } - - public Task GreetTest(ITextChannel channel, IGuildUser user) - { - var conf = GetOrAddSettingsForGuild(user.GuildId); - return GreetUsers(conf, channel, user); - } - - public Task GreetDmTest(IDMChannel channel, IGuildUser user) - { - var conf = GetOrAddSettingsForGuild(user.GuildId); - return GreetDmUser(conf, channel, user); - } - - #endregion - public bool SetGreetDmMessage(ulong guildId, ref string? message) { message = message?.SanitizeMentions(); @@ -580,4 +504,51 @@ public class GreetSettingsService : INService _guildConfigsCache.AddOrUpdate(guildId, toAdd, (_, _) => toAdd); return conf.SendBoostMessage; } + + #region Get Enabled Status + + public bool GetGreetDmEnabled(ulong guildId) + { + using var uow = _db.GetDbContext(); + var conf = uow.GuildConfigsForId(guildId, set => set); + return conf.SendDmGreetMessage; + } + + public bool GetGreetEnabled(ulong guildId) + { + using var uow = _db.GetDbContext(); + var conf = uow.GuildConfigsForId(guildId, set => set); + return conf.SendChannelGreetMessage; + } + + public bool GetByeEnabled(ulong guildId) + { + using var uow = _db.GetDbContext(); + var conf = uow.GuildConfigsForId(guildId, set => set); + return conf.SendChannelByeMessage; + } + + #endregion + + #region Test Messages + + public Task ByeTest(ITextChannel channel, IGuildUser user) + { + var conf = GetOrAddSettingsForGuild(user.GuildId); + return ByeUsers(conf, channel, user); + } + + public Task GreetTest(ITextChannel channel, IGuildUser user) + { + var conf = GetOrAddSettingsForGuild(user.GuildId); + return GreetUsers(conf, channel, user); + } + + public Task GreetDmTest(IDMChannel channel, IGuildUser user) + { + var conf = GetOrAddSettingsForGuild(user.GuildId); + return GreetDmUser(conf, channel, user); + } + + #endregion } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/GreetBye/ServerGreetCommands.cs b/src/NadekoBot/Modules/Administration/GreetBye/ServerGreetCommands.cs index 79f08d76c..b089cee89 100644 --- a/src/NadekoBot/Modules/Administration/GreetBye/ServerGreetCommands.cs +++ b/src/NadekoBot/Modules/Administration/GreetBye/ServerGreetCommands.cs @@ -5,7 +5,8 @@ public partial class Administration [Group] public class ServerGreetCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] public async Task Boost() @@ -18,7 +19,8 @@ public partial class Administration await ReplyPendingLocalizedAsync(strs.boost_off); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] public async Task BoostDel(int timer = 30) @@ -34,7 +36,8 @@ public partial class Administration await ReplyPendingLocalizedAsync(strs.boostdel_off); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] public async Task BoostMsg([Leftover] string? text = null) @@ -53,7 +56,8 @@ public partial class Administration await ReplyPendingLocalizedAsync(strs.boostmsg_enable($"`{Prefix}boost`")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] public async Task GreetDel(int timer = 30) @@ -69,7 +73,8 @@ public partial class Administration await ReplyPendingLocalizedAsync(strs.greetdel_off); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] public async Task Greet() @@ -82,7 +87,8 @@ public partial class Administration await ReplyPendingLocalizedAsync(strs.greet_off); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] public async Task GreetMsg([Leftover] string? text = null) @@ -97,12 +103,13 @@ public partial class Administration var sendGreetEnabled = _service.SetGreetMessage(ctx.Guild.Id, ref text); await ReplyConfirmLocalizedAsync(strs.greetmsg_new); - + if (!sendGreetEnabled) await ReplyPendingLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] public async Task GreetDm() @@ -115,7 +122,8 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.greetdm_off); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] public async Task GreetDmMsg([Leftover] string? text = null) @@ -134,7 +142,8 @@ public partial class Administration await ReplyPendingLocalizedAsync(strs.greetdmmsg_enable($"`{Prefix}greetdm`")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] public async Task Bye() @@ -147,7 +156,8 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.bye_off); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] public async Task ByeMsg([Leftover] string? text = null) @@ -166,7 +176,8 @@ public partial class Administration await ReplyPendingLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] public async Task ByeDel(int timer = 30) @@ -180,7 +191,8 @@ public partial class Administration } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] [Ratelimit(5)] @@ -190,13 +202,11 @@ public partial class Administration await _service.ByeTest((ITextChannel)ctx.Channel, user); var enabled = _service.GetByeEnabled(ctx.Guild.Id); - if (!enabled) - { - await ReplyPendingLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`")); - } + if (!enabled) await ReplyPendingLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] [Ratelimit(5)] @@ -206,13 +216,11 @@ public partial class Administration await _service.GreetTest((ITextChannel)ctx.Channel, user); var enabled = _service.GetGreetEnabled(ctx.Guild.Id); - if (!enabled) - { - await ReplyPendingLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`")); - } + if (!enabled) await ReplyPendingLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageGuild)] [Ratelimit(5)] @@ -231,4 +239,4 @@ public partial class Administration await ReplyPendingLocalizedAsync(strs.greetdmmsg_enable($"`{Prefix}greetdm`")); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/LocalizationCommands.cs b/src/NadekoBot/Modules/Administration/LocalizationCommands.cs index 3b4136e1c..1b4e90a01 100644 --- a/src/NadekoBot/Modules/Administration/LocalizationCommands.cs +++ b/src/NadekoBot/Modules/Administration/LocalizationCommands.cs @@ -8,46 +8,46 @@ public partial class Administration [Group] public class LocalizationCommands : NadekoSubmodule { - private static readonly IReadOnlyDictionary supportedLocales = - new Dictionary() - { - {"ar", "العربية"}, - {"zh-TW", "繁體中文, 台灣"}, - {"zh-CN", "简体中文, 中华人民共和国"}, - {"nl-NL", "Nederlands, Nederland"}, - {"en-US", "English, United States"}, - {"fr-FR", "Français, France"}, - {"cs-CZ", "Čeština, Česká republika"}, - {"da-DK", "Dansk, Danmark"}, - {"de-DE", "Deutsch, Deutschland"}, - {"he-IL", "עברית, ישראל"}, - {"hu-HU", "Magyar, Magyarország"}, - {"id-ID", "Bahasa Indonesia, Indonesia"}, - {"it-IT", "Italiano, Italia"}, - {"ja-JP", "日本語, 日本"}, - {"ko-KR", "한국어, 대한민국"}, - {"nb-NO", "Norsk, Norge"}, - {"pl-PL", "Polski, Polska"}, - {"pt-BR", "Português Brasileiro, Brasil"}, - {"ro-RO", "Română, România"}, - {"ru-RU", "Русский, Россия"}, - {"sr-Cyrl-RS", "Српски, Србија"}, - {"es-ES", "Español, España"}, - {"sv-SE", "Svenska, Sverige"}, - {"tr-TR", "Türkçe, Türkiye"}, - {"ts-TS", "Tsundere, You Baka"}, - {"uk-UA", "Українська, Україна"} - }; + private static readonly IReadOnlyDictionary supportedLocales = new Dictionary + { + { "ar", "العربية" }, + { "zh-TW", "繁體中文, 台灣" }, + { "zh-CN", "简体中文, 中华人民共和国" }, + { "nl-NL", "Nederlands, Nederland" }, + { "en-US", "English, United States" }, + { "fr-FR", "Français, France" }, + { "cs-CZ", "Čeština, Česká republika" }, + { "da-DK", "Dansk, Danmark" }, + { "de-DE", "Deutsch, Deutschland" }, + { "he-IL", "עברית, ישראל" }, + { "hu-HU", "Magyar, Magyarország" }, + { "id-ID", "Bahasa Indonesia, Indonesia" }, + { "it-IT", "Italiano, Italia" }, + { "ja-JP", "日本語, 日本" }, + { "ko-KR", "한국어, 대한민국" }, + { "nb-NO", "Norsk, Norge" }, + { "pl-PL", "Polski, Polska" }, + { "pt-BR", "Português Brasileiro, Brasil" }, + { "ro-RO", "Română, România" }, + { "ru-RU", "Русский, Россия" }, + { "sr-Cyrl-RS", "Српски, Србија" }, + { "es-ES", "Español, España" }, + { "sv-SE", "Svenska, Sverige" }, + { "tr-TR", "Türkçe, Türkiye" }, + { "ts-TS", "Tsundere, You Baka" }, + { "uk-UA", "Українська, Україна" } + }; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] public async Task LanguageSet() - => await ReplyConfirmLocalizedAsync(strs.lang_set_show( - Format.Bold(Culture.ToString()), + => await ReplyConfirmLocalizedAsync(strs.lang_set_show(Format.Bold(Culture.ToString()), Format.Bold(Culture.NativeName))); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Priority(1)] @@ -67,8 +67,7 @@ public partial class Administration Localization.SetGuildCulture(ctx.Guild, ci); } - await ReplyConfirmLocalizedAsync(strs.lang_set(Format.Bold(ci.ToString()), - Format.Bold(ci.NativeName))); + await ReplyConfirmLocalizedAsync(strs.lang_set(Format.Bold(ci.ToString()), Format.Bold(ci.NativeName))); } catch (Exception) { @@ -76,14 +75,16 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task LanguageSetDefault() { var cul = Localization.DefaultCultureInfo; await ReplyErrorLocalizedAsync(strs.lang_set_bot_show(cul, cul.NativeName)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task LanguageSetDefault(string name) { @@ -110,12 +111,15 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task LanguagesList() - => await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithTitle(GetText(strs.lang_list)) - .WithDescription(string.Join("\n", - supportedLocales.Select(x => $"{Format.Code(x.Key),-10} => {x.Value}")))); + => await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.lang_list)) + .WithDescription(string.Join("\n", + supportedLocales.Select( + x => $"{Format.Code(x.Key),-10} => {x.Value}")))); } } /* list of language codes for reference. @@ -248,4 +252,4 @@ public partial class Administration { "YE", "ar-YE" }, { "ZA", "af-ZA" }, { "ZW", "en-ZW" } - */ + */ \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/LogCommands.cs b/src/NadekoBot/Modules/Administration/LogCommands.cs index 2f530d721..8aec0ee71 100644 --- a/src/NadekoBot/Modules/Administration/LogCommands.cs +++ b/src/NadekoBot/Modules/Administration/LogCommands.cs @@ -1,7 +1,7 @@ #nullable disable using NadekoBot.Common.TypeReaders.Models; -using NadekoBot.Services.Database.Models; using NadekoBot.Modules.Administration.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration; @@ -11,7 +11,8 @@ public partial class Administration [NoPublicBot] public class LogCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [OwnerOnly] @@ -24,7 +25,8 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.log_disabled); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [OwnerOnly] @@ -38,63 +40,74 @@ public partial class Administration ?? new List(); var eb = _eb.Create(ctx) - .WithOkColor() - .AddField(GetText(strs.log_ignored_channels), - chs.Count == 0 ? "-" : string.Join('\n', chs.Select(x => $"{x.LogItemId} | <#{x.LogItemId}>"))) - .AddField(GetText(strs.log_ignored_users), - usrs.Count == 0 ? "-" : string.Join('\n', usrs.Select(x => $"{x.LogItemId} | <@{x.LogItemId}>"))); + .WithOkColor() + .AddField(GetText(strs.log_ignored_channels), + chs.Count == 0 + ? "-" + : string.Join('\n', chs.Select(x => $"{x.LogItemId} | <#{x.LogItemId}>"))) + .AddField(GetText(strs.log_ignored_users), + usrs.Count == 0 + ? "-" + : string.Join('\n', usrs.Select(x => $"{x.LogItemId} | <@{x.LogItemId}>"))); await ctx.Channel.EmbedAsync(eb); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [OwnerOnly] - public async Task LogIgnore([Leftover]ITextChannel target) + public async Task LogIgnore([Leftover] ITextChannel target) { target ??= (ITextChannel)ctx.Channel; var removed = _service.LogIgnore(ctx.Guild.Id, target.Id, IgnoredItemType.Channel); if (!removed) - await ReplyConfirmLocalizedAsync(strs.log_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")"))); + await ReplyConfirmLocalizedAsync( + strs.log_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")"))); else - await ReplyConfirmLocalizedAsync(strs.log_not_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")"))); + await ReplyConfirmLocalizedAsync( + strs.log_not_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")"))); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [OwnerOnly] - public async Task LogIgnore([Leftover]IUser target) + public async Task LogIgnore([Leftover] IUser target) { var removed = _service.LogIgnore(ctx.Guild.Id, target.Id, IgnoredItemType.User); if (!removed) - await ReplyConfirmLocalizedAsync(strs.log_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")"))); + await ReplyConfirmLocalizedAsync( + strs.log_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")"))); else - await ReplyConfirmLocalizedAsync(strs.log_not_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")"))); + await ReplyConfirmLocalizedAsync( + strs.log_not_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")"))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [OwnerOnly] public async Task LogEvents() { var logSetting = _service.GetGuildLogSettings(ctx.Guild.Id); - var str = string.Join("\n", Enum.GetNames(typeof(LogType)) - .Select(x => - { - var val = logSetting is null ? null : GetLogProperty(logSetting, Enum.Parse(x)); - if (val != null) - return $"{Format.Bold(x)} <#{val}>"; - return Format.Bold(x); - })); + var str = string.Join("\n", + Enum.GetNames(typeof(LogType)) + .Select(x => + { + var val = logSetting is null ? null : GetLogProperty(logSetting, Enum.Parse(x)); + if (val != null) + return $"{Format.Bold(x)} <#{val}>"; + return Format.Bold(x); + })); - await SendConfirmAsync(Format.Bold(GetText(strs.log_events)) + "\n" + - str); + await SendConfirmAsync(Format.Bold(GetText(strs.log_events)) + "\n" + str); } private static ulong? GetLogProperty(LogSetting l, LogType type) @@ -136,7 +149,8 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [OwnerOnly] @@ -150,4 +164,4 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.log_stop(Format.Bold(type.ToString()))); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/MuteCommands.cs b/src/NadekoBot/Modules/Administration/MuteCommands.cs index 17709e06a..07f2c9c08 100644 --- a/src/NadekoBot/Modules/Administration/MuteCommands.cs +++ b/src/NadekoBot/Modules/Administration/MuteCommands.cs @@ -13,8 +13,8 @@ public partial class Administration { var runnerUserRoles = runnerUser.GetRoles(); var targetUserRoles = targetUser.GetRoles(); - if (runnerUser.Id != ctx.Guild.OwnerId && - runnerUserRoles.Max(x => x.Position) <= targetUserRoles.Max(x => x.Position)) + if (runnerUser.Id != ctx.Guild.OwnerId + && runnerUserRoles.Max(x => x.Position) <= targetUserRoles.Max(x => x.Position)) { await ReplyErrorLocalizedAsync(strs.mute_perms); return false; @@ -23,7 +23,8 @@ public partial class Administration return true; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] public async Task MuteRole([Leftover] IRole role = null) @@ -34,20 +35,21 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.mute_role(Format.Code(muteRole.Name))); return; } - - if (ctx.User.Id != ctx.Guild.OwnerId && - role.Position >= ((SocketGuildUser) ctx.User).Roles.Max(x => x.Position)) + + if (ctx.User.Id != ctx.Guild.OwnerId + && role.Position >= ((SocketGuildUser)ctx.User).Roles.Max(x => x.Position)) { await ReplyErrorLocalizedAsync(strs.insuf_perms_u); return; } - + await _service.SetMuteRoleAsync(ctx.Guild.Id, role.Name); await ReplyConfirmLocalizedAsync(strs.mute_role_set); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)] [Priority(0)] @@ -68,7 +70,8 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)] [Priority(1)] @@ -82,7 +85,8 @@ public partial class Administration return; await _service.TimedMute(user, ctx.User, time.Time, reason: reason); - await ReplyConfirmLocalizedAsync(strs.user_muted_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes)); + await ReplyConfirmLocalizedAsync(strs.user_muted_time(Format.Bold(user.ToString()), + (int)time.Time.TotalMinutes)); } catch (Exception ex) { @@ -91,7 +95,8 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)] public async Task Unmute(IGuildUser user, [Leftover] string reason = "") @@ -107,7 +112,8 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [Priority(0)] @@ -118,7 +124,7 @@ public partial class Administration if (!await VerifyMutePermissions((IGuildUser)ctx.User, user)) return; - await _service.MuteUser(user, ctx.User, MuteType.Chat, reason: reason); + await _service.MuteUser(user, ctx.User, MuteType.Chat, reason); await ReplyConfirmLocalizedAsync(strs.user_chat_mute(Format.Bold(user.ToString()))); } catch (Exception ex) @@ -128,7 +134,8 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [Priority(1)] @@ -141,8 +148,9 @@ public partial class Administration if (!await VerifyMutePermissions((IGuildUser)ctx.User, user)) return; - await _service.TimedMute(user, ctx.User, time.Time, MuteType.Chat, reason: reason); - await ReplyConfirmLocalizedAsync(strs.user_chat_mute_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes)); + await _service.TimedMute(user, ctx.User, time.Time, MuteType.Chat, reason); + await ReplyConfirmLocalizedAsync(strs.user_chat_mute_time(Format.Bold(user.ToString()), + (int)time.Time.TotalMinutes)); } catch (Exception ex) { @@ -151,14 +159,15 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] public async Task ChatUnmute(IGuildUser user, [Leftover] string reason = "") { try { - await _service.UnmuteUser(user.Guild.Id, user.Id, ctx.User, MuteType.Chat, reason: reason); + await _service.UnmuteUser(user.Guild.Id, user.Id, ctx.User, MuteType.Chat, reason); await ReplyConfirmLocalizedAsync(strs.user_chat_unmute(Format.Bold(user.ToString()))); } catch @@ -167,7 +176,8 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.MuteMembers)] [Priority(0)] @@ -178,7 +188,7 @@ public partial class Administration if (!await VerifyMutePermissions((IGuildUser)ctx.User, user)) return; - await _service.MuteUser(user, ctx.User, MuteType.Voice, reason: reason); + await _service.MuteUser(user, ctx.User, MuteType.Voice, reason); await ReplyConfirmLocalizedAsync(strs.user_voice_mute(Format.Bold(user.ToString()))); } catch @@ -187,11 +197,12 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.MuteMembers)] [Priority(1)] - public async Task VoiceMute(StoopidTime time,IGuildUser user, [Leftover] string reason = "") + public async Task VoiceMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "") { if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49)) return; @@ -200,8 +211,9 @@ public partial class Administration if (!await VerifyMutePermissions((IGuildUser)ctx.User, user)) return; - await _service.TimedMute(user, ctx.User, time.Time, MuteType.Voice, reason: reason); - await ReplyConfirmLocalizedAsync(strs.user_voice_mute_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes)); + await _service.TimedMute(user, ctx.User, time.Time, MuteType.Voice, reason); + await ReplyConfirmLocalizedAsync(strs.user_voice_mute_time(Format.Bold(user.ToString()), + (int)time.Time.TotalMinutes)); } catch { @@ -209,14 +221,15 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.MuteMembers)] public async Task VoiceUnmute(IGuildUser user, [Leftover] string reason = "") { try { - await _service.UnmuteUser(user.GuildId, user.Id, ctx.User, MuteType.Voice, reason: reason); + await _service.UnmuteUser(user.GuildId, user.Id, ctx.User, MuteType.Voice, reason); await ReplyConfirmLocalizedAsync(strs.user_voice_unmute(Format.Bold(user.ToString()))); } catch @@ -225,4 +238,4 @@ public partial class Administration } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/PlayingRotateCommands.cs b/src/NadekoBot/Modules/Administration/PlayingRotateCommands.cs index 66beb766b..ee83868d9 100644 --- a/src/NadekoBot/Modules/Administration/PlayingRotateCommands.cs +++ b/src/NadekoBot/Modules/Administration/PlayingRotateCommands.cs @@ -8,7 +8,8 @@ public partial class Administration [Group] public class PlayingRotateCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task RotatePlaying() { @@ -18,7 +19,8 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.ropl_disabled); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task AddPlaying(ActivityType t, [Leftover] string status) { @@ -27,7 +29,8 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.ropl_added); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task ListPlaying() { @@ -40,13 +43,13 @@ public partial class Administration else { var i = 1; - await ReplyConfirmLocalizedAsync(strs.ropl_list( - string.Join("\n\t", statuses.Select(rs => $"`{i++}.` *{rs.Type}* {rs.Status}")))); + await ReplyConfirmLocalizedAsync(strs.ropl_list(string.Join("\n\t", + statuses.Select(rs => $"`{i++}.` *{rs.Type}* {rs.Status}")))); } - } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task RemovePlaying(int index) { @@ -60,4 +63,4 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.reprm(msg)); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/PrefixCommands.cs b/src/NadekoBot/Modules/Administration/PrefixCommands.cs index dee07fcf9..5e3117956 100644 --- a/src/NadekoBot/Modules/Administration/PrefixCommands.cs +++ b/src/NadekoBot/Modules/Administration/PrefixCommands.cs @@ -6,41 +6,45 @@ public partial class Administration [Group] public class PrefixCommands : NadekoSubmodule { - [NadekoCommand, Aliases] - [Priority(1)] - public async Task PrefixCommand() - => await ReplyConfirmLocalizedAsync(strs.prefix_current(Format.Code(CmdHandler.GetPrefix(ctx.Guild)))); - public enum Set { Set } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] + [Priority(1)] + public async Task PrefixCommand() + => await ReplyConfirmLocalizedAsync(strs.prefix_current(Format.Code(CmdHandler.GetPrefix(ctx.Guild)))); + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Priority(0)] public Task PrefixCommand(Set _, [Leftover] string prefix) => PrefixCommand(prefix); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Priority(0)] - public async Task PrefixCommand([Leftover]string prefix) + public async Task PrefixCommand([Leftover] string prefix) { if (string.IsNullOrWhiteSpace(prefix)) return; - var oldPrefix = base.Prefix; + var oldPrefix = Prefix; var newPrefix = CmdHandler.SetPrefix(ctx.Guild, prefix); await ReplyConfirmLocalizedAsync(strs.prefix_new(Format.Code(oldPrefix), Format.Code(newPrefix))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] - public async Task DefPrefix([Leftover]string prefix = null) + public async Task DefPrefix([Leftover] string prefix = null) { if (string.IsNullOrWhiteSpace(prefix)) { @@ -54,4 +58,4 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.defprefix_new(Format.Code(oldPrefix), Format.Code(newPrefix))); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/ProtectionCommands.cs b/src/NadekoBot/Modules/Administration/ProtectionCommands.cs index 62ffece2d..581eddb47 100644 --- a/src/NadekoBot/Modules/Administration/ProtectionCommands.cs +++ b/src/NadekoBot/Modules/Administration/ProtectionCommands.cs @@ -1,8 +1,8 @@ #nullable disable -using NadekoBot.Services.Database.Models; +using NadekoBot.Common.TypeReaders.Models; using NadekoBot.Modules.Administration.Common; using NadekoBot.Modules.Administration.Services; -using NadekoBot.Common.TypeReaders.Models; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration; @@ -11,7 +11,8 @@ public partial class Administration [Group] public class ProtectionCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task AntiAlt() @@ -25,26 +26,31 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.protection_not_running("Anti-Alt")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task AntiAlt(StoopidTime minAge, PunishmentAction action, [Leftover] StoopidTime punishTime = null) { var minAgeMinutes = (int)minAge.Time.TotalMinutes; - var punishTimeMinutes = (int?) punishTime?.Time.TotalMinutes ?? 0; + var punishTimeMinutes = (int?)punishTime?.Time.TotalMinutes ?? 0; if (minAgeMinutes < 1 || punishTimeMinutes < 0) return; - await _service.StartAntiAltAsync(ctx.Guild.Id, minAgeMinutes, action, (int?)punishTime?.Time.TotalMinutes ?? 0); + await _service.StartAntiAltAsync(ctx.Guild.Id, + minAgeMinutes, + action, + (int?)punishTime?.Time.TotalMinutes ?? 0); await ctx.OkAsync(); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] - public async Task AntiAlt(StoopidTime minAge, PunishmentAction action, [Leftover]IRole role) + public async Task AntiAlt(StoopidTime minAge, PunishmentAction action, [Leftover] IRole role) { var minAgeMinutes = (int)minAge.Time.TotalMinutes; @@ -55,46 +61,50 @@ public partial class Administration await ctx.OkAsync(); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public Task AntiRaid() { if (_service.TryStopAntiRaid(ctx.Guild.Id)) - { return ReplyConfirmLocalizedAsync(strs.prot_disable("Anti-Raid")); - } - else - { - return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Raid")); - } + return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Raid")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Priority(1)] - public Task AntiRaid(int userThreshold, int seconds, - PunishmentAction action, [Leftover] StoopidTime punishTime) - => InternalAntiRaid(userThreshold, seconds, action, punishTime: punishTime); + public Task AntiRaid( + int userThreshold, + int seconds, + PunishmentAction action, + [Leftover] StoopidTime punishTime) + => InternalAntiRaid(userThreshold, seconds, action, punishTime); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Priority(2)] public Task AntiRaid(int userThreshold, int seconds, PunishmentAction action) => InternalAntiRaid(userThreshold, seconds, action); - - private async Task InternalAntiRaid(int userThreshold, int seconds = 10, - PunishmentAction action = PunishmentAction.Mute, StoopidTime punishTime = null) + + private async Task InternalAntiRaid( + int userThreshold, + int seconds = 10, + PunishmentAction action = PunishmentAction.Mute, + StoopidTime punishTime = null) { if (action == PunishmentAction.AddRole) { await ReplyErrorLocalizedAsync(strs.punishment_unsupported(action)); return; } - + if (userThreshold is < 2 or > 30) { await ReplyErrorLocalizedAsync(strs.raid_cnt(2, 30)); @@ -106,47 +116,36 @@ public partial class Administration await ReplyErrorLocalizedAsync(strs.raid_time(2, 300)); return; } - + if (punishTime is not null) - { if (!_service.IsDurationAllowed(action)) - { await ReplyErrorLocalizedAsync(strs.prot_cant_use_time); - } - } - - var time = (int?) punishTime?.Time.TotalMinutes ?? 0; + + var time = (int?)punishTime?.Time.TotalMinutes ?? 0; if (time is < 0 or > 60 * 24) return; - var stats = await _service.StartAntiRaidAsync(ctx.Guild.Id, userThreshold, seconds, - action, time); + var stats = await _service.StartAntiRaidAsync(ctx.Guild.Id, userThreshold, seconds, action, time); - if (stats is null) - { - return; - } + if (stats is null) return; await SendConfirmAsync(GetText(strs.prot_enable("Anti-Raid")), - $"{ctx.User.Mention} {GetAntiRaidString(stats)}"); + $"{ctx.User.Mention} {GetAntiRaidString(stats)}"); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public Task AntiSpam() { if (_service.TryStopAntiSpam(ctx.Guild.Id)) - { return ReplyConfirmLocalizedAsync(strs.prot_disable("Anti-Spam")); - } - else - { - return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Spam")); - } + return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Spam")); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Priority(0)] @@ -158,35 +157,36 @@ public partial class Administration return InternalAntiSpam(messageCount, action, null, role); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Priority(1)] public Task AntiSpam(int messageCount, PunishmentAction action, [Leftover] StoopidTime punishTime) - => InternalAntiSpam(messageCount, action, punishTime, null); + => InternalAntiSpam(messageCount, action, punishTime); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Priority(2)] public Task AntiSpam(int messageCount, PunishmentAction action) => InternalAntiSpam(messageCount, action); - public async Task InternalAntiSpam(int messageCount, PunishmentAction action, - StoopidTime timeData = null, IRole role = null) + public async Task InternalAntiSpam( + int messageCount, + PunishmentAction action, + StoopidTime timeData = null, + IRole role = null) { if (messageCount is < 2 or > 10) return; if (timeData is not null) - { if (!_service.IsDurationAllowed(action)) - { await ReplyErrorLocalizedAsync(strs.prot_cant_use_time); - } - } - var time = (int?) timeData?.Time.TotalMinutes ?? 0; + var time = (int?)timeData?.Time.TotalMinutes ?? 0; if (time is < 0 or > 60 * 24) return; @@ -196,14 +196,15 @@ public partial class Administration $"{ctx.User.Mention} {GetAntiSpamString(stats)}"); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task AntispamIgnore() { var added = await _service.AntiSpamIgnoreAsync(ctx.Guild.Id, ctx.Channel.Id); - if(added is null) + if (added is null) { await ReplyErrorLocalizedAsync(strs.protection_not_running("Anti-Spam")); return; @@ -215,7 +216,8 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.spam_not_ignore("Anti-Spam")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task AntiList() { @@ -227,8 +229,7 @@ public partial class Administration return; } - var embed = _eb.Create().WithOkColor() - .WithTitle(GetText(strs.prot_active)); + var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.prot_active)); if (spam != null) embed.AddField("Anti-Spam", GetAntiSpamString(spam).TrimTo(1024), true); @@ -242,9 +243,8 @@ public partial class Administration await ctx.Channel.EmbedAsync(embed); } - private string GetAntiAltString(AntiAltStats alt) - => GetText(strs.anti_alt_status( - Format.Bold(alt.MinAge.ToString(@"dd\d\ hh\h\ mm\m\ ")), + private string GetAntiAltString(AntiAltStats alt) + => GetText(strs.anti_alt_status(Format.Bold(alt.MinAge.ToString(@"dd\d\ hh\h\ mm\m\ ")), Format.Bold(alt.Action.ToString()), Format.Bold(alt.Counter.ToString()))); @@ -257,13 +257,9 @@ public partial class Administration ignoredString = "none"; var add = string.Empty; - if (settings.MuteTime > 0) - { - add = $" ({TimeSpan.FromMinutes(settings.MuteTime):hh\\hmm\\m})"; - } + if (settings.MuteTime > 0) add = $" ({TimeSpan.FromMinutes(settings.MuteTime):hh\\hmm\\m})"; - return GetText(strs.spam_stats( - Format.Bold(settings.MessageThreshold.ToString()), + return GetText(strs.spam_stats(Format.Bold(settings.MessageThreshold.ToString()), Format.Bold(settings.Action + add), ignoredString)); } @@ -273,14 +269,11 @@ public partial class Administration var actionString = Format.Bold(stats.AntiRaidSettings.Action.ToString()); if (stats.AntiRaidSettings.PunishDuration > 0) - { actionString += $" **({TimeSpan.FromMinutes(stats.AntiRaidSettings.PunishDuration):hh\\hmm\\m})**"; - } - return GetText(strs.raid_stats( - Format.Bold(stats.AntiRaidSettings.UserThreshold.ToString()), + return GetText(strs.raid_stats(Format.Bold(stats.AntiRaidSettings.UserThreshold.ToString()), Format.Bold(stats.AntiRaidSettings.Seconds.ToString()), actionString)); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/PruneCommands.cs b/src/NadekoBot/Modules/Administration/PruneCommands.cs index 10f1eea11..555ea81d5 100644 --- a/src/NadekoBot/Modules/Administration/PruneCommands.cs +++ b/src/NadekoBot/Modules/Administration/PruneCommands.cs @@ -1,6 +1,5 @@ #nullable disable using NadekoBot.Modules.Administration.Services; -using ITextChannel = Discord.ITextChannel; namespace NadekoBot.Modules.Administration; @@ -12,7 +11,8 @@ public partial class Administration private static readonly TimeSpan twoWeeks = TimeSpan.FromDays(14); //delets her own messages, no perm required - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Prune(string parameter = null) { @@ -24,8 +24,10 @@ public partial class Administration await _service.PruneWhere((ITextChannel)ctx.Channel, 100, x => x.Author.Id == user.Id); ctx.Message.DeleteAfter(3); } + // prune x - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(ChannelPerm.ManageMessages)] [BotPerm(ChannelPerm.ManageMessages)] @@ -45,7 +47,8 @@ public partial class Administration } //prune @user [x] - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(ChannelPerm.ManageMessages)] [BotPerm(ChannelPerm.ManageMessages)] @@ -54,7 +57,8 @@ public partial class Administration => Prune(user.Id, count, parameter); //prune userid [x] - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(ChannelPerm.ManageMessages)] [BotPerm(ChannelPerm.ManageMessages)] @@ -71,9 +75,13 @@ public partial class Administration count = 1000; if (parameter is "-s" or "--safe") - await _service.PruneWhere((ITextChannel)ctx.Channel, count, m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks && !m.IsPinned); + await _service.PruneWhere((ITextChannel)ctx.Channel, + count, + m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks && !m.IsPinned); else - await _service.PruneWhere((ITextChannel)ctx.Channel, count, m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks); + await _service.PruneWhere((ITextChannel)ctx.Channel, + count, + m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/RoleCommands.cs b/src/NadekoBot/Modules/Administration/RoleCommands.cs index ecc2dc31c..0111d7bde 100644 --- a/src/NadekoBot/Modules/Administration/RoleCommands.cs +++ b/src/NadekoBot/Modules/Administration/RoleCommands.cs @@ -1,8 +1,9 @@ #nullable disable -using NadekoBot.Services.Database.Models; using NadekoBot.Modules.Administration.Services; +using NadekoBot.Services.Database.Models; using SixLabors.ImageSharp.PixelFormats; using System.Net; +using Color = SixLabors.ImageSharp.Color; namespace NadekoBot.Modules.Administration; @@ -10,9 +11,10 @@ public partial class Administration { public class RoleCommands : NadekoSubmodule { - private IServiceProvider _services; public enum Exclude { Excl } + private IServiceProvider _services; + public RoleCommands(IServiceProvider services) => _services = services; @@ -20,34 +22,32 @@ public partial class Administration { var target = messageId is { } msgId ? await ctx.Channel.GetMessageAsync(msgId) - : (await ctx.Channel.GetMessagesAsync(2).FlattenAsync()) - .Skip(1) - .FirstOrDefault(); + : (await ctx.Channel.GetMessagesAsync(2).FlattenAsync()).Skip(1).FirstOrDefault(); if (input.Length % 2 != 0) return; - var all = await input - .Chunk(input.Length / 2) - .Select(async x => - { - var inputRoleStr = x.First(); - var roleReader = new RoleTypeReader(); - var roleResult = await roleReader.ReadAsync(ctx, inputRoleStr, _services); - if (!roleResult.IsSuccess) - { - Log.Warning("Role {0} not found.", inputRoleStr); - return null; - } - var role = (IRole)roleResult.BestMatch; - if (role.Position > ((IGuildUser)ctx.User).GetRoles().Select(r => r.Position).Max() - && ctx.User.Id != ctx.Guild.OwnerId) - return null; - var emote = x.Last().ToIEmote(); - return new { role, emote }; - }) - .Where(x => x != null) - .WhenAll(); + var all = await input.Chunk(input.Length / 2) + .Select(async x => + { + var inputRoleStr = x.First(); + var roleReader = new RoleTypeReader(); + var roleResult = await roleReader.ReadAsync(ctx, inputRoleStr, _services); + if (!roleResult.IsSuccess) + { + Log.Warning("Role {0} not found.", inputRoleStr); + return null; + } + + var role = (IRole)roleResult.BestMatch; + if (role.Position > ((IGuildUser)ctx.User).GetRoles().Select(r => r.Position).Max() + && ctx.User.Id != ctx.Guild.OwnerId) + return null; + var emote = x.Last().ToIEmote(); + return new { role, emote }; + }) + .Where(x => x != null) + .WhenAll(); if (!all.Any()) return; @@ -56,12 +56,10 @@ public partial class Administration { try { - await target.AddReactionAsync(x.emote, new() - { - RetryMode = RetryMode.Retry502 | RetryMode.RetryRatelimit - }); + await target.AddReactionAsync(x.emote, + new() { RetryMode = RetryMode.Retry502 | RetryMode.RetryRatelimit }); } - catch (Discord.Net.HttpException ex) when(ex.HttpCode == HttpStatusCode.BadRequest) + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.BadRequest) { await ReplyErrorLocalizedAsync(strs.reaction_cant_access(Format.Code(x.emote.ToString()))); return; @@ -70,75 +68,75 @@ public partial class Administration await Task.Delay(500); } - if (_service.Add(ctx.Guild.Id, new() - { - Exclusive = exclusive, - MessageId = target.Id, - ChannelId = target.Channel.Id, - ReactionRoles = all.Select(x => + if (_service.Add(ctx.Guild.Id, + new() { - return new ReactionRole() - { - EmoteName = x.emote.ToString(), - RoleId = x.role.Id, - }; - }).ToList(), - })) - { + Exclusive = exclusive, + MessageId = target.Id, + ChannelId = target.Channel.Id, + ReactionRoles = all.Select(x => + { + return new ReactionRole + { + EmoteName = x.emote.ToString(), RoleId = x.role.Id + }; + }) + .ToList() + })) await ctx.OkAsync(); - } else - { await ReplyErrorLocalizedAsync(strs.reaction_roles_full); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [NoPublicBot] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] [Priority(0)] - public Task ReactionRoles(ulong messageId, params string[] input) => - InternalReactionRoles(false, messageId, input); - - [NadekoCommand, Aliases] + public Task ReactionRoles(ulong messageId, params string[] input) + => InternalReactionRoles(false, messageId, input); + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [NoPublicBot] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] [Priority(1)] - public Task ReactionRoles(ulong messageId, Exclude _, params string[] input) => - InternalReactionRoles(true, messageId, input); - - [NadekoCommand, Aliases] + public Task ReactionRoles(ulong messageId, Exclude _, params string[] input) + => InternalReactionRoles(true, messageId, input); + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [NoPublicBot] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] [Priority(0)] - public Task ReactionRoles(params string[] input) => - InternalReactionRoles(false, null, input); + public Task ReactionRoles(params string[] input) + => InternalReactionRoles(false, null, input); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [NoPublicBot] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] [Priority(1)] - public Task ReactionRoles(Exclude _, params string[] input) => - InternalReactionRoles(true, null, input); + public Task ReactionRoles(Exclude _, params string[] input) + => InternalReactionRoles(true, null, input); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [NoPublicBot] [UserPerm(GuildPerm.ManageRoles)] public async Task ReactionRolesList() { - var embed = _eb.Create() - .WithOkColor(); - if (!_service.Get(ctx.Guild.Id, out var rrs) || - !rrs.Any()) + var embed = _eb.Create().WithOkColor(); + if (!_service.Get(ctx.Guild.Id, out var rrs) || !rrs.Any()) { embed.WithDescription(GetText(strs.no_reaction_roles)); } @@ -149,37 +147,33 @@ public partial class Administration { var ch = g.GetTextChannel(rr.ChannelId); IUserMessage msg = null; - if (ch is not null) - { - msg = await ch.GetMessageAsync(rr.MessageId) as IUserMessage; - } + if (ch is not null) msg = await ch.GetMessageAsync(rr.MessageId) as IUserMessage; var content = msg?.Content.TrimTo(30) ?? "DELETED!"; embed.AddField($"**{rr.Index + 1}.** {ch?.Name ?? "DELETED!"}", GetText(strs.reaction_roles_message(rr.ReactionRoles?.Count ?? 0, content))); } } + await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [NoPublicBot] [UserPerm(GuildPerm.ManageRoles)] public async Task ReactionRolesRemove(int index) { - if (index < 1 || - !_service.Get(ctx.Guild.Id, out var rrs) || - !rrs.Any() || rrs.Count < index) - { + if (index < 1 || !_service.Get(ctx.Guild.Id, out var rrs) || !rrs.Any() || rrs.Count < index) return; - } index--; var rr = rrs[index]; _service.Remove(ctx.Guild.Id, index); await ReplyConfirmLocalizedAsync(strs.reaction_role_removed(index + 1)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] @@ -193,9 +187,8 @@ public partial class Administration { await targetUser.AddRoleAsync(roleToAdd); - await ReplyConfirmLocalizedAsync( - strs.setrole(Format.Bold(roleToAdd.Name), Format.Bold(targetUser.ToString())) - ); + await ReplyConfirmLocalizedAsync(strs.setrole(Format.Bold(roleToAdd.Name), + Format.Bold(targetUser.ToString()))); } catch (Exception ex) { @@ -204,19 +197,22 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] public async Task RemoveRole(IGuildUser targetUser, [Leftover] IRole roleToRemove) { var runnerUser = (IGuildUser)ctx.User; - if (ctx.User.Id != runnerUser.Guild.OwnerId && runnerUser.GetRoles().Max(x => x.Position) <= roleToRemove.Position) + if (ctx.User.Id != runnerUser.Guild.OwnerId + && runnerUser.GetRoles().Max(x => x.Position) <= roleToRemove.Position) return; try { await targetUser.RemoveRoleAsync(roleToRemove); - await ReplyConfirmLocalizedAsync(strs.remrole(Format.Bold(roleToRemove.Name), Format.Bold(targetUser.ToString()))); + await ReplyConfirmLocalizedAsync(strs.remrole(Format.Bold(roleToRemove.Name), + Format.Bold(targetUser.ToString()))); } catch { @@ -224,11 +220,12 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] - public async Task RenameRole(IRole roleToEdit, [Leftover]string newname) + public async Task RenameRole(IRole roleToEdit, [Leftover] string newname) { var guser = (IGuildUser)ctx.User; if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= roleToEdit.Position) @@ -240,6 +237,7 @@ public partial class Administration await ReplyErrorLocalizedAsync(strs.renrole_perms); return; } + await roleToEdit.ModifyAsync(g => g.Name = newname); await ReplyConfirmLocalizedAsync(strs.renrole); } @@ -249,7 +247,8 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] @@ -257,11 +256,11 @@ public partial class Administration { var guser = (IGuildUser)ctx.User; - var userRoles = user.GetRoles() - .Where(x => !x.IsManaged && x != x.Guild.EveryoneRole) - .ToList(); - - if (user.Id == ctx.Guild.OwnerId || (ctx.User.Id != ctx.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position))) + var userRoles = user.GetRoles().Where(x => !x.IsManaged && x != x.Guild.EveryoneRole).ToList(); + + if (user.Id == ctx.Guild.OwnerId + || (ctx.User.Id != ctx.Guild.OwnerId + && guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position))) return; try { @@ -274,7 +273,8 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] @@ -287,22 +287,23 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.cr(Format.Bold(r.Name))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] public async Task DeleteRole([Leftover] IRole role) { var guser = (IGuildUser)ctx.User; - if (ctx.User.Id != guser.Guild.OwnerId - && guser.GetRoles().Max(x => x.Position) <= role.Position) + if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position) return; await role.DeleteAsync(); await ReplyConfirmLocalizedAsync(strs.dr(Format.Bold(role.Name))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] @@ -311,32 +312,30 @@ public partial class Administration var newHoisted = !role.IsHoisted; await role.ModifyAsync(r => r.Hoist = newHoisted); if (newHoisted) - { await ReplyConfirmLocalizedAsync(strs.rolehoist_enabled(Format.Bold(role.Name))); - } else - { await ReplyConfirmLocalizedAsync(strs.rolehoist_disabled(Format.Bold(role.Name))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] public async Task RoleColor([Leftover] IRole role) => await SendConfirmAsync("Role Color", role.Color.RawValue.ToString("x6")); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] [Priority(0)] - public async Task RoleColor(SixLabors.ImageSharp.Color color, [Leftover]IRole role) + public async Task RoleColor(Color color, [Leftover] IRole role) { try { var rgba32 = color.ToPixel(); - await role.ModifyAsync(r => r.Color = new Color(rgba32.R, rgba32.G, rgba32.B)); + await role.ModifyAsync(r => r.Color = new Discord.Color(rgba32.R, rgba32.G, rgba32.B)); await ReplyConfirmLocalizedAsync(strs.rc(Format.Bold(role.Name))); } catch (Exception) @@ -345,4 +344,4 @@ public partial class Administration } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/SelfAssignedRolesCommands.cs b/src/NadekoBot/Modules/Administration/SelfAssignedRolesCommands.cs index 1003e6d9f..109b37249 100644 --- a/src/NadekoBot/Modules/Administration/SelfAssignedRolesCommands.cs +++ b/src/NadekoBot/Modules/Administration/SelfAssignedRolesCommands.cs @@ -1,6 +1,6 @@ #nullable disable -using System.Text; using NadekoBot.Modules.Administration.Services; +using System.Text; namespace NadekoBot.Modules.Administration; @@ -9,7 +9,8 @@ public partial class Administration [Group] public class SelfAssignedRolesCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] [BotPerm(GuildPerm.ManageMessages)] @@ -18,24 +19,22 @@ public partial class Administration var newVal = _service.ToggleAdSarm(ctx.Guild.Id); if (newVal) - { await ReplyConfirmLocalizedAsync(strs.adsarm_enable(Prefix)); - } else - { await ReplyConfirmLocalizedAsync(strs.adsarm_disable(Prefix)); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] [Priority(1)] - public Task Asar([Leftover] IRole role) => - Asar(0, role); + public Task Asar([Leftover] IRole role) + => Asar(0, role); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] @@ -49,16 +48,14 @@ public partial class Administration var succ = _service.AddNew(ctx.Guild.Id, role, group); if (succ) - { - await ReplyConfirmLocalizedAsync(strs.role_added(Format.Bold(role.Name), Format.Bold(group.ToString()))); - } + await ReplyConfirmLocalizedAsync(strs.role_added(Format.Bold(role.Name), + Format.Bold(group.ToString()))); else - { await ReplyErrorLocalizedAsync(strs.role_in_list(Format.Bold(role.Name))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] @@ -70,16 +67,14 @@ public partial class Administration var set = await _service.SetNameAsync(ctx.Guild.Id, group, name); if (set) - { - await ReplyConfirmLocalizedAsync(strs.group_name_added(Format.Bold(group.ToString()), Format.Bold(name.ToString()))); - } + await ReplyConfirmLocalizedAsync( + strs.group_name_added(Format.Bold(group.ToString()), Format.Bold(name))); else - { await ReplyConfirmLocalizedAsync(strs.group_name_removed(Format.Bold(group.ToString()))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] public async Task Rsar([Leftover] IRole role) @@ -90,16 +85,13 @@ public partial class Administration var success = _service.RemoveSar(role.Guild.Id, role.Id); if (!success) - { await ReplyErrorLocalizedAsync(strs.self_assign_not); - } else - { await ReplyConfirmLocalizedAsync(strs.self_assign_rem(Format.Bold(role.Name))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Lsar(int page = 1) { @@ -108,57 +100,55 @@ public partial class Administration var (exclusive, roles, groups) = _service.GetRoles(ctx.Guild); - await ctx.SendPaginatedConfirmAsync(page, cur => - { - var rolesStr = new StringBuilder(); - var roleGroups = roles - .OrderBy(x => x.Model.Group) - .Skip(cur * 20) - .Take(20) - .GroupBy(x => x.Model.Group) - .OrderBy(x => x.Key); - - foreach (var kvp in roleGroups) + await ctx.SendPaginatedConfirmAsync(page, + cur => { - var groupNameText = string.Empty; - if (!groups.TryGetValue(kvp.Key, out var name)) - { - groupNameText = Format.Bold(GetText(strs.self_assign_group(kvp.Key))); - } - else - { - groupNameText = Format.Bold($"{kvp.Key} - {name.TrimTo(25, true)}"); - } + var rolesStr = new StringBuilder(); + var roleGroups = roles.OrderBy(x => x.Model.Group) + .Skip(cur * 20) + .Take(20) + .GroupBy(x => x.Model.Group) + .OrderBy(x => x.Key); - rolesStr.AppendLine("\t\t\t\t ⟪" + groupNameText + "⟫"); - foreach (var (model, role) in kvp.AsEnumerable()) + foreach (var kvp in roleGroups) { - if (role is null) - { - continue; - } + var groupNameText = string.Empty; + if (!groups.TryGetValue(kvp.Key, out var name)) + groupNameText = Format.Bold(GetText(strs.self_assign_group(kvp.Key))); else - { - // first character is invisible space - if (model.LevelRequirement == 0) - rolesStr.AppendLine("‌‌ " + role.Name); - else - rolesStr.AppendLine("‌‌ " + role.Name + $" (lvl {model.LevelRequirement}+)"); - } - } - rolesStr.AppendLine(); - } + groupNameText = Format.Bold($"{kvp.Key} - {name.TrimTo(25, true)}"); - return _eb.Create().WithOkColor() - .WithTitle(Format.Bold(GetText(strs.self_assign_list(roles.Count())))) - .WithDescription(rolesStr.ToString()) - .WithFooter(exclusive - ? GetText(strs.self_assign_are_exclusive) - : GetText(strs.self_assign_are_not_exclusive)); - }, roles.Count(), 20); + rolesStr.AppendLine("\t\t\t\t ⟪" + groupNameText + "⟫"); + foreach (var (model, role) in kvp.AsEnumerable()) + if (role is null) + { + } + else + { + // first character is invisible space + if (model.LevelRequirement == 0) + rolesStr.AppendLine("‌‌ " + role.Name); + else + rolesStr.AppendLine("‌‌ " + role.Name + $" (lvl {model.LevelRequirement}+)"); + } + + rolesStr.AppendLine(); + } + + return _eb.Create() + .WithOkColor() + .WithTitle(Format.Bold(GetText(strs.self_assign_list(roles.Count())))) + .WithDescription(rolesStr.ToString()) + .WithFooter(exclusive + ? GetText(strs.self_assign_are_exclusive) + : GetText(strs.self_assign_are_not_exclusive)); + }, + roles.Count(), + 20); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] @@ -171,7 +161,8 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.self_assign_no_excl); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] @@ -188,12 +179,12 @@ public partial class Administration return; } - await ReplyConfirmLocalizedAsync(strs.self_assign_level_req( - Format.Bold(role.Name), + await ReplyConfirmLocalizedAsync(strs.self_assign_level_req(Format.Bold(role.Name), Format.Bold(level.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Iam([Leftover] IRole role) { @@ -203,25 +194,15 @@ public partial class Administration IUserMessage msg; if (result == SelfAssignedRolesService.AssignResult.Err_Not_Assignable) - { msg = await ReplyErrorLocalizedAsync(strs.self_assign_not); - } else if (result == SelfAssignedRolesService.AssignResult.Err_Lvl_Req) - { msg = await ReplyErrorLocalizedAsync(strs.self_assign_not_level(Format.Bold(extra.ToString()))); - } else if (result == SelfAssignedRolesService.AssignResult.Err_Already_Have) - { msg = await ReplyErrorLocalizedAsync(strs.self_assign_already(Format.Bold(role.Name))); - } else if (result == SelfAssignedRolesService.AssignResult.Err_Not_Perms) - { msg = await ReplyErrorLocalizedAsync(strs.self_assign_perms); - } else - { msg = await ReplyConfirmLocalizedAsync(strs.self_assign_success(Format.Bold(role.Name))); - } if (autoDelete) { @@ -230,7 +211,8 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Iamnot([Leftover] IRole role) { @@ -240,21 +222,13 @@ public partial class Administration IUserMessage msg; if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Assignable) - { msg = await ReplyErrorLocalizedAsync(strs.self_assign_not); - } else if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Have) - { msg = await ReplyErrorLocalizedAsync(strs.self_assign_not_have(Format.Bold(role.Name))); - } else if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Perms) - { msg = await ReplyErrorLocalizedAsync(strs.self_assign_perms); - } else - { msg = await ReplyConfirmLocalizedAsync(strs.self_assign_remove(Format.Bold(role.Name))); - } if (autoDelete) { @@ -263,4 +237,4 @@ public partial class Administration } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/SelfCommands.cs b/src/NadekoBot/Modules/Administration/SelfCommands.cs index 565ac3a63..39466bfb8 100644 --- a/src/NadekoBot/Modules/Administration/SelfCommands.cs +++ b/src/NadekoBot/Modules/Administration/SelfCommands.cs @@ -1,6 +1,6 @@ #nullable disable -using NadekoBot.Services.Database.Models; using NadekoBot.Modules.Administration.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration; @@ -9,6 +9,14 @@ public partial class Administration [Group] public class SelfCommands : NadekoSubmodule { + public enum SettableUserStatus + { + Online, + Invisible, + Idle, + Dnd + } + private readonly DiscordSocketClient _client; private readonly IBotStrings _strings; private readonly ICoordinator _coord; @@ -20,7 +28,8 @@ public partial class Administration _coord = coord; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [OwnerOnly] @@ -30,7 +39,7 @@ public partial class Administration return; var guser = (IGuildUser)ctx.User; - var cmd = new AutoCommand() + var cmd = new AutoCommand { CommandText = cmdText, ChannelId = ctx.Channel.Id, @@ -39,18 +48,22 @@ public partial class Administration GuildName = ctx.Guild?.Name, VoiceChannelId = guser.VoiceChannel?.Id, VoiceChannelName = guser.VoiceChannel?.Name, - Interval = 0, + Interval = 0 }; _service.AddNewAutoCommand(cmd); - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithTitle(GetText(strs.scadd)) - .AddField(GetText(strs.server), cmd.GuildId is null ? $"-" : $"{cmd.GuildName}/{cmd.GuildId}", true) - .AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true) - .AddField(GetText(strs.command_text), cmdText, false)); + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.scadd)) + .AddField(GetText(strs.server), + cmd.GuildId is null ? "-" : $"{cmd.GuildName}/{cmd.GuildId}", + true) + .AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true) + .AddField(GetText(strs.command_text), cmdText)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [OwnerOnly] @@ -63,7 +76,7 @@ public partial class Administration return; var guser = (IGuildUser)ctx.User; - var cmd = new AutoCommand() + var cmd = new AutoCommand { CommandText = cmdText, ChannelId = ctx.Channel.Id, @@ -72,14 +85,15 @@ public partial class Administration GuildName = ctx.Guild?.Name, VoiceChannelId = guser.VoiceChannel?.Id, VoiceChannelName = guser.VoiceChannel?.Name, - Interval = interval, + Interval = interval }; _service.AddNewAutoCommand(cmd); await ReplyConfirmLocalizedAsync(strs.autocmd_add(Format.Code(Format.Sanitize(cmdText)), cmd.Interval)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] public async Task StartupCommandsList(int page = 1) @@ -87,11 +101,8 @@ public partial class Administration if (page-- < 1) return; - var scmds = _service.GetStartupCommands() - .Skip(page * 5) - .Take(5) - .ToList(); - + var scmds = _service.GetStartupCommands().Skip(page * 5).Take(5).ToList(); + if (scmds.Count == 0) { await ReplyErrorLocalizedAsync(strs.startcmdlist_none); @@ -99,19 +110,19 @@ public partial class Administration else { var i = 0; - await SendConfirmAsync( - text: string.Join("\n", scmds - .Select(x => $@"```css + await SendConfirmAsync(text: string.Join("\n", + scmds.Select(x => $@"```css #{++i + (page * 5)} [{GetText(strs.server)}]: {(x.GuildId.HasValue ? $"{x.GuildName} #{x.GuildId}" : "-")} [{GetText(strs.channel)}]: {x.ChannelName} #{x.ChannelId} [{GetText(strs.command_text)}]: {x.CommandText}```")), - title: string.Empty, - footer: GetText(strs.page(page + 1))); + title: string.Empty, + footer: GetText(strs.page(page + 1))); } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] public async Task AutoCommandsList(int page = 1) @@ -119,10 +130,7 @@ public partial class Administration if (page-- < 1) return; - var scmds = _service.GetAutoCommands() - .Skip(page * 5) - .Take(5) - .ToList(); + var scmds = _service.GetAutoCommands().Skip(page * 5).Take(5).ToList(); if (!scmds.Any()) { await ReplyErrorLocalizedAsync(strs.autocmdlist_none); @@ -130,23 +138,23 @@ public partial class Administration else { var i = 0; - await SendConfirmAsync( - text: string.Join("\n", scmds - .Select(x => $@"```css + await SendConfirmAsync(text: string.Join("\n", + scmds.Select(x => $@"```css #{++i + (page * 5)} [{GetText(strs.server)}]: {(x.GuildId.HasValue ? $"{x.GuildName} #{x.GuildId}" : "-")} [{GetText(strs.channel)}]: {x.ChannelName} #{x.ChannelId} {GetIntervalText(x.Interval)} [{GetText(strs.command_text)}]: {x.CommandText}```")), - title: string.Empty, - footer: GetText(strs.page(page + 1))); + title: string.Empty, + footer: GetText(strs.page(page + 1))); } } private string GetIntervalText(int interval) => $"[{GetText(strs.interval)}]: {interval}"; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task Wait(int miliseconds) { @@ -162,8 +170,9 @@ public partial class Administration await Task.Delay(miliseconds); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [OwnerOnly] @@ -174,11 +183,12 @@ public partial class Administration await ReplyErrorLocalizedAsync(strs.acrm_fail); return; } - + await ctx.OkAsync(); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] public async Task StartupCommandRemove([Leftover] int index) @@ -189,7 +199,8 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.scrm); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [OwnerOnly] @@ -200,7 +211,8 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.startcmds_cleared); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task ForwardMessages() { @@ -212,7 +224,8 @@ public partial class Administration await ReplyPendingLocalizedAsync(strs.fwdm_stop); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task ForwardToAll() { @@ -222,10 +235,10 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.fwall_start); else await ReplyPendingLocalizedAsync(strs.fwall_stop); - } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ShardStats(int page = 1) { if (--page < 0) @@ -233,35 +246,36 @@ public partial class Administration var statuses = _coord.GetAllShardStatuses(); - var status = string.Join(" : ", statuses - .Select(x => (ConnectionStateToEmoji(x), x)) - .GroupBy(x => x.Item1) - .Select(x => $"`{x.Count()} {x.Key}`") - .ToArray()); + var status = string.Join(" : ", + statuses.Select(x => (ConnectionStateToEmoji(x), x)) + .GroupBy(x => x.Item1) + .Select(x => $"`{x.Count()} {x.Key}`") + .ToArray()); - var allShardStrings = statuses - .Select(st => + var allShardStrings = statuses.Select(st => + { + var stateStr = ConnectionStateToEmoji(st); + var timeDiff = DateTime.UtcNow - st.LastUpdate; + var maxGuildCountLength = + statuses.Max(x => x.GuildCount).ToString().Length; + return $"`{stateStr} " + + $"| #{st.ShardId.ToString().PadBoth(3)} " + + $"| {timeDiff:mm\\:ss} " + + $"| {st.GuildCount.ToString().PadBoth(maxGuildCountLength)} `"; + }) + .ToArray(); + await ctx.SendPaginatedConfirmAsync(page, + curPage => { - var stateStr = ConnectionStateToEmoji(st); - var timeDiff = DateTime.UtcNow - st.LastUpdate; - var maxGuildCountLength = statuses.Max(x => x.GuildCount).ToString().Length; - return $"`{stateStr} " + - $"| #{st.ShardId.ToString().PadBoth(3)} " + - $"| {timeDiff:mm\\:ss} " + - $"| {st.GuildCount.ToString().PadBoth(maxGuildCountLength)} `"; - }) - .ToArray(); - await ctx.SendPaginatedConfirmAsync(page, curPage => - { - var str = string.Join("\n", allShardStrings.Skip(25 * curPage).Take(25)); + var str = string.Join("\n", allShardStrings.Skip(25 * curPage).Take(25)); - if (string.IsNullOrWhiteSpace(str)) - str = GetText(strs.no_shards_on_page); + if (string.IsNullOrWhiteSpace(str)) + str = GetText(strs.no_shards_on_page); - return _eb.Create() - .WithOkColor() - .WithDescription($"{status}\n\n{str}"); - }, allShardStrings.Length, 25); + return _eb.Create().WithOkColor().WithDescription($"{status}\n\n{str}"); + }, + allShardStrings.Length, + 25); } private static string ConnectionStateToEmoji(ShardStatus status) @@ -276,28 +290,27 @@ public partial class Administration }; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task RestartShard(int shardId) { var success = _coord.RestartShard(shardId); if (success) - { await ReplyConfirmLocalizedAsync(strs.shard_reconnecting(Format.Bold("#" + shardId))); - } else - { await ReplyErrorLocalizedAsync(strs.no_shard_id); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public Task Leave([Leftover] string guildStr) => _service.LeaveGuild(guildStr); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task Die(bool graceful = false) { @@ -309,11 +322,13 @@ public partial class Administration { // ignored } + await Task.Delay(2000); _coord.Die(graceful); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task Restart() { @@ -324,10 +339,12 @@ public partial class Administration return; } - try { await ReplyConfirmLocalizedAsync(strs.restarting); } catch { } + try { await ReplyConfirmLocalizedAsync(strs.restarting); } + catch { } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task SetName([Leftover] string newName) { @@ -346,7 +363,8 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.bot_name(Format.Bold(newName))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [UserPerm(GuildPerm.ManageNicknames)] [BotPerm(GuildPerm.ChangeNickname)] [Priority(0)] @@ -360,26 +378,28 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.bot_nick(Format.Bold(newNick) ?? "-")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [BotPerm(GuildPerm.ManageNicknames)] [UserPerm(GuildPerm.ManageNicknames)] [Priority(1)] public async Task SetNick(IGuildUser gu, [Leftover] string newNick = null) { - var sg = (SocketGuild) ctx.Guild; - if (sg.OwnerId == gu.Id || - gu.GetRoles().Max(r => r.Position) >= sg.CurrentUser.GetRoles().Max(r => r.Position)) + var sg = (SocketGuild)ctx.Guild; + if (sg.OwnerId == gu.Id + || gu.GetRoles().Max(r => r.Position) >= sg.CurrentUser.GetRoles().Max(r => r.Position)) { await ReplyErrorLocalizedAsync(strs.insuf_perms_i); return; } - + await gu.ModifyAsync(u => u.Nickname = newNick); await ReplyConfirmLocalizedAsync(strs.user_nick(Format.Bold(gu.ToString()), Format.Bold(newNick) ?? "-")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task SetStatus([Leftover] SettableUserStatus status) { @@ -388,32 +408,30 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.bot_status(Format.Bold(status.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task SetAvatar([Leftover] string img = null) { var success = await _service.SetAvatar(img); - if (success) - { - await ReplyConfirmLocalizedAsync(strs.set_avatar); - } + if (success) await ReplyConfirmLocalizedAsync(strs.set_avatar); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task SetGame(ActivityType type, [Leftover] string game = null) { - var rep = new ReplacementBuilder() - .WithDefault(Context) - .Build(); + var rep = new ReplacementBuilder().WithDefault(Context).Build(); await _service.SetGameAsync(game is null ? game : rep.Replace(game), type); await ReplyConfirmLocalizedAsync(strs.set_game); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task SetStream(string url, [Leftover] string name = null) { @@ -424,23 +442,22 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.set_stream); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task Send(string where, [Leftover] SmartText text = null) { var ids = where.Split('|'); if (ids.Length != 2) return; - + var sid = ulong.Parse(ids[0]); var server = _client.Guilds.FirstOrDefault(s => s.Id == sid); if (server is null) return; - var rep = new ReplacementBuilder() - .WithDefault(Context) - .Build(); + var rep = new ReplacementBuilder().WithDefault(Context).Build(); if (ids[1].ToUpperInvariant().StartsWith("C:", StringComparison.InvariantCulture)) { @@ -450,7 +467,7 @@ public partial class Administration return; text = rep.Replace(text); - await ch.SendAsync(text, sanitizeAll: false); + await ch.SendAsync(text); } else if (ids[1].ToUpperInvariant().StartsWith("U:", StringComparison.InvariantCulture)) { @@ -468,27 +485,30 @@ public partial class Administration await ReplyErrorLocalizedAsync(strs.invalid_format); return; } - + await ReplyConfirmLocalizedAsync(strs.message_sent); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task ImagesReload() { await _service.ReloadImagesAsync(); await ReplyConfirmLocalizedAsync(strs.images_loading); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task StringsReload() { _strings.Reload(); await ReplyConfirmLocalizedAsync(strs.bot_strings_reloaded); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task CoordReload() { @@ -512,13 +532,5 @@ public partial class Administration return UserStatus.Online; } - - public enum SettableUserStatus - { - Online, - Invisible, - Idle, - Dnd - } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/AdministrationService.cs b/src/NadekoBot/Modules/Administration/Services/AdministrationService.cs index 870a2cac6..deb74cd5e 100644 --- a/src/NadekoBot/Modules/Administration/Services/AdministrationService.cs +++ b/src/NadekoBot/Modules/Administration/Services/AdministrationService.cs @@ -1,7 +1,7 @@ #nullable disable using Microsoft.EntityFrameworkCore; -using NadekoBot.Services.Database.Models; using NadekoBot.Db; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration.Services; @@ -13,19 +13,20 @@ public class AdministrationService : INService private readonly DbService _db; private readonly ILogCommandService _logService; - public AdministrationService(Bot bot, CommandHandler cmdHandler, DbService db, ILogCommandService logService) + public AdministrationService( + Bot bot, + CommandHandler cmdHandler, + DbService db, + ILogCommandService logService) { _db = db; _logService = logService; - DeleteMessagesOnCommand = new(bot.AllGuildConfigs - .Where(g => g.DeleteMessageOnCommand) - .Select(g => g.GuildId)); + DeleteMessagesOnCommand = new(bot.AllGuildConfigs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId)); - DeleteMessagesOnCommandChannels = new(bot.AllGuildConfigs - .SelectMany(x => x.DelMsgOnCmdChannels) - .ToDictionary(x => x.ChannelId, x => x.State) - .ToConcurrent()); + DeleteMessagesOnCommandChannels = new(bot.AllGuildConfigs.SelectMany(x => x.DelMsgOnCmdChannels) + .ToDictionary(x => x.ChannelId, x => x.State) + .ToConcurrent()); cmdHandler.CommandExecuted += DelMsgOnCmd_Handler; } @@ -33,8 +34,7 @@ public class AdministrationService : INService public (bool DelMsgOnCmd, IEnumerable channels) GetDelMsgOnCmdData(ulong guildId) { using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, - set => set.Include(x => x.DelMsgOnCmdChannels)); + var conf = uow.GuildConfigsForId(guildId, set => set.Include(x => x.DelMsgOnCmdChannels)); return (conf.DeleteMessageOnCommand, conf.DelMsgOnCmdChannels); } @@ -52,14 +52,16 @@ public class AdministrationService : INService if (state && cmd.Name != "prune" && cmd.Name != "pick") { _logService.AddDeleteIgnore(msg.Id); - try { await msg.DeleteAsync(); } catch { } + try { await msg.DeleteAsync(); } + catch { } } //if state is false, that means do not do it } else if (DeleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune" && cmd.Name != "pick") { _logService.AddDeleteIgnore(msg.Id); - try { await msg.DeleteAsync(); } catch { } + try { await msg.DeleteAsync(); } + catch { } } }); return Task.CompletedTask; @@ -80,8 +82,7 @@ public class AdministrationService : INService { await using (var uow = _db.GetDbContext()) { - var conf = uow.GuildConfigsForId(guildId, - set => set.Include(x => x.DelMsgOnCmdChannels)); + var conf = uow.GuildConfigsForId(guildId, set => set.Include(x => x.DelMsgOnCmdChannels)); var old = conf.DelMsgOnCmdChannels.FirstOrDefault(x => x.ChannelId == chId); if (newState == Administration.State.Inherit) @@ -125,7 +126,6 @@ public class AdministrationService : INService if (!users.Any()) return; foreach (var u in users) - { try { await u.ModifyAsync(usr => usr.Deaf = value); @@ -134,23 +134,24 @@ public class AdministrationService : INService { // ignored } - } } - public async Task EditMessage(ICommandContext context, ITextChannel chanl, ulong messageId, string input) + public async Task EditMessage( + ICommandContext context, + ITextChannel chanl, + ulong messageId, + string input) { var msg = await chanl.GetMessageAsync(messageId); if (msg is not IUserMessage umsg || msg.Author.Id != context.Client.CurrentUser.Id) return; - var rep = new ReplacementBuilder() - .WithDefault(context) - .Build(); + var rep = new ReplacementBuilder().WithDefault(context).Build(); var text = SmartText.CreateFrom(input); text = rep.Replace(text); await umsg.EditAsync(text); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/AutoAssignRoleService.cs b/src/NadekoBot/Modules/Administration/Services/AutoAssignRoleService.cs index 3d338893a..87d9cbcaf 100644 --- a/src/NadekoBot/Modules/Administration/Services/AutoAssignRoleService.cs +++ b/src/NadekoBot/Modules/Administration/Services/AutoAssignRoleService.cs @@ -1,9 +1,10 @@ #nullable disable -using System.Threading.Channels; using LinqToDB; using Microsoft.EntityFrameworkCore; -using NadekoBot.Services.Database.Models; using NadekoBot.Db; +using NadekoBot.Services.Database.Models; +using System.Net; +using System.Threading.Channels; namespace NadekoBot.Modules.Administration.Services; @@ -18,9 +19,7 @@ public sealed class AutoAssignRoleService : INService private readonly Channel _assignQueue = Channel.CreateBounded( new BoundedChannelOptions(100) { - FullMode = BoundedChannelFullMode.DropOldest, - SingleReader = true, - SingleWriter = false, + FullMode = BoundedChannelFullMode.DropOldest, SingleReader = true, SingleWriter = false }); public AutoAssignRoleService(DiscordSocketClient client, Bot bot, DbService db) @@ -28,10 +27,10 @@ public sealed class AutoAssignRoleService : INService _client = client; _db = db; - _autoAssignableRoles = bot.AllGuildConfigs - .Where(x => !string.IsNullOrWhiteSpace(x.AutoAssignRoleIds)) - .ToDictionary>(k => k.GuildId, v => v.GetAutoAssignableRoles()) - .ToConcurrent(); + _autoAssignableRoles = bot.AllGuildConfigs.Where(x => !string.IsNullOrWhiteSpace(x.AutoAssignRoleIds)) + .ToDictionary>(k => k.GuildId, + v => v.GetAutoAssignableRoles()) + .ToConcurrent(); _ = Task.Run(async () => { @@ -40,14 +39,13 @@ public sealed class AutoAssignRoleService : INService var user = await _assignQueue.Reader.ReadAsync(); if (!_autoAssignableRoles.TryGetValue(user.Guild.Id, out var savedRoleIds)) continue; - + try { - var roleIds = savedRoleIds - .Select(roleId => user.Guild.GetRole(roleId)) - .Where(x => x is not null) - .ToList(); - + var roleIds = savedRoleIds.Select(roleId => user.Guild.GetRole(roleId)) + .Where(x => x is not null) + .ToList(); + if (roleIds.Any()) { await user.AddRolesAsync(roleIds); @@ -59,16 +57,17 @@ public sealed class AutoAssignRoleService : INService "Disabled 'Auto assign role' feature on {GuildName} [{GuildId}] server the roles dont exist", user.Guild.Name, user.Guild.Id); - + await DisableAarAsync(user.Guild.Id); } } - catch (Discord.Net.HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden) + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden) { - Log.Warning("Disabled 'Auto assign role' feature on {GuildName} [{GuildId}] server because I don't have role management permissions", + Log.Warning( + "Disabled 'Auto assign role' feature on {GuildName} [{GuildId}] server because I don't have role management permissions", user.Guild.Name, user.Guild.Id); - + await DisableAarAsync(user.Guild.Id); } catch (Exception ex) @@ -84,16 +83,13 @@ public sealed class AutoAssignRoleService : INService private async Task OnClientRoleDeleted(SocketRole role) { - if (_autoAssignableRoles.TryGetValue(role.Guild.Id, out var roles) - && roles.Contains(role.Id)) - { + if (_autoAssignableRoles.TryGetValue(role.Guild.Id, out var roles) && roles.Contains(role.Id)) await ToggleAarAsync(role.Guild.Id, role.Id); - } } private async Task OnClientOnUserJoined(SocketGuildUser user) { - if (_autoAssignableRoles.TryGetValue(user.Guild.Id, out _)) + if (_autoAssignableRoles.TryGetValue(user.Guild.Id, out _)) await _assignQueue.Writer.WriteAsync(user); } @@ -102,42 +98,40 @@ public sealed class AutoAssignRoleService : INService await using var uow = _db.GetDbContext(); var gc = uow.GuildConfigsForId(guildId, set => set); var roles = gc.GetAutoAssignableRoles(); - if(!roles.Remove(roleId) && roles.Count < 3) + if (!roles.Remove(roleId) && roles.Count < 3) roles.Add(roleId); - + gc.SetAutoAssignableRoles(roles); await uow.SaveChangesAsync(); - + if (roles.Count > 0) _autoAssignableRoles[guildId] = roles; else _autoAssignableRoles.TryRemove(guildId, out _); - + return roles; } public async Task DisableAarAsync(ulong guildId) { await using var uow = _db.GetDbContext(); - - await uow - .GuildConfigs - .AsNoTracking() - .Where(x => x.GuildId == guildId) - .UpdateAsync(_ => new(){ AutoAssignRoleIds = null}); - + + await uow.GuildConfigs.AsNoTracking() + .Where(x => x.GuildId == guildId) + .UpdateAsync(_ => new() { AutoAssignRoleIds = null }); + _autoAssignableRoles.TryRemove(guildId, out _); - + await uow.SaveChangesAsync(); } public async Task SetAarRolesAsync(ulong guildId, IEnumerable newRoles) { await using var uow = _db.GetDbContext(); - + var gc = uow.GuildConfigsForId(guildId, set => set); gc.SetAutoAssignableRoles(newRoles); - + await uow.SaveChangesAsync(); } @@ -157,4 +151,4 @@ public static class GuildConfigExtensions public static void SetAutoAssignableRoles(this GuildConfig gc, IEnumerable roles) => gc.AutoAssignRoleIds = roles.Join(','); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/DangerousCommandsService.cs b/src/NadekoBot/Modules/Administration/Services/DangerousCommandsService.cs index 44b3ccf86..e49d8e4af 100644 --- a/src/NadekoBot/Modules/Administration/Services/DangerousCommandsService.cs +++ b/src/NadekoBot/Modules/Administration/Services/DangerousCommandsService.cs @@ -1,7 +1,7 @@ #nullable disable -using Microsoft.EntityFrameworkCore; using LinqToDB; using LinqToDB.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration.Services; @@ -11,12 +11,18 @@ public class DangerousCommandsService : INService public const string WaifusDeleteSql = @"DELETE FROM WaifuUpdates; DELETE FROM WaifuItem; DELETE FROM WaifuInfo;"; - public const string WaifuDeleteSql = @"DELETE FROM WaifuUpdates WHERE UserId=(SELECT Id FROM DiscordUser WHERE UserId={0}); + + public const string WaifuDeleteSql = + @"DELETE FROM WaifuUpdates WHERE UserId=(SELECT Id FROM DiscordUser WHERE UserId={0}); DELETE FROM WaifuItem WHERE WaifuInfoId=(SELECT Id FROM WaifuInfo WHERE WaifuId=(SELECT Id FROM DiscordUser WHERE UserId={0})); UPDATE WaifuInfo SET ClaimerId=NULL WHERE ClaimerId=(SELECT Id FROM DiscordUser WHERE UserId={0}); DELETE FROM WaifuInfo WHERE WaifuId=(SELECT Id FROM DiscordUser WHERE UserId={0});"; - public const string CurrencyDeleteSql = "UPDATE DiscordUser SET CurrencyAmount=0; DELETE FROM CurrencyTransactions; DELETE FROM PlantedCurrency;"; + + public const string CurrencyDeleteSql = + "UPDATE DiscordUser SET CurrencyAmount=0; DELETE FROM CurrencyTransactions; DELETE FROM PlantedCurrency;"; + public const string MusicPlaylistDeleteSql = "DELETE FROM MusicPlaylists;"; + public const string XpDeleteSql = @"DELETE FROM UserXpStats; UPDATE DiscordUser SET ClubId=NULL, @@ -44,19 +50,9 @@ DELETE FROM Clubs;"; return res; } - public class SelectResult - { - public List ColumnNames { get; set; } - public List Results { get; set; } - } - public SelectResult SelectSql(string sql) { - var result = new SelectResult() - { - ColumnNames = new(), - Results = new(), - }; + var result = new SelectResult { ColumnNames = new(), Results = new() }; using var uow = _db.GetDbContext(); var conn = uow.Database.GetDbConnection(); @@ -65,10 +61,7 @@ DELETE FROM Clubs;"; using var reader = cmd.ExecuteReader(); if (reader.HasRows) { - for (var i = 0; i < reader.FieldCount; i++) - { - result.ColumnNames.Add(reader.GetName(i)); - } + for (var i = 0; i < reader.FieldCount; i++) result.ColumnNames.Add(reader.GetName(i)); while (reader.Read()) { var obj = new object[reader.FieldCount]; @@ -83,50 +76,45 @@ DELETE FROM Clubs;"; public async Task PurgeUserAsync(ulong userId) { await using var uow = _db.GetDbContext(); - + // get waifu info - var wi = await uow.Set() - .FirstOrDefaultAsyncEF(x => x.Waifu.UserId == userId); + var wi = await uow.Set().FirstOrDefaultAsyncEF(x => x.Waifu.UserId == userId); // if it exists, delete waifu related things if (wi is not null) { // remove updates which have new or old as this waifu - await uow - .WaifuUpdates - .DeleteAsync(wu => wu.New.UserId == userId || wu.Old.UserId == userId); + await uow.WaifuUpdates.DeleteAsync(wu => wu.New.UserId == userId || wu.Old.UserId == userId); // delete all items this waifu owns - await uow - .Set() - .DeleteAsync(x => x.WaifuInfoId == wi.Id); + await uow.Set().DeleteAsync(x => x.WaifuInfoId == wi.Id); // all waifus this waifu claims are released - await uow - .Set() - .AsQueryable() - .Where(x => x.Claimer.UserId == userId) - .UpdateAsync(x => new() {ClaimerId = null}); - + await uow.Set() + .AsQueryable() + .Where(x => x.Claimer.UserId == userId) + .UpdateAsync(x => new() { ClaimerId = null }); + // all affinities set to this waifu are reset - await uow - .Set() - .AsQueryable() - .Where(x => x.Affinity.UserId == userId) - .UpdateAsync(x => new() {AffinityId = null}); + await uow.Set() + .AsQueryable() + .Where(x => x.Affinity.UserId == userId) + .UpdateAsync(x => new() { AffinityId = null }); } // delete guild xp - await uow - .UserXpStats - .DeleteAsync(x => x.UserId == userId); - + await uow.UserXpStats.DeleteAsync(x => x.UserId == userId); + // delete currency transactions - await uow.Set() - .DeleteAsync(x => x.UserId == userId); - + await uow.Set().DeleteAsync(x => x.UserId == userId); + // delete user, currency, and clubs go away with it - await uow.DiscordUser - .DeleteAsync(u => u.UserId == userId); + await uow.DiscordUser.DeleteAsync(u => u.UserId == userId); } -} + + public class SelectResult + { + public List ColumnNames { get; set; } + public List Results { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/DiscordPermOverrideService.cs b/src/NadekoBot/Modules/Administration/Services/DiscordPermOverrideService.cs index 39568c975..5cd349339 100644 --- a/src/NadekoBot/Modules/Administration/Services/DiscordPermOverrideService.cs +++ b/src/NadekoBot/Modules/Administration/Services/DiscordPermOverrideService.cs @@ -7,11 +7,10 @@ namespace NadekoBot.Modules.Administration.Services; public class DiscordPermOverrideService : INService, ILateBlocker { + public int Priority { get; } = int.MaxValue; private readonly DbService _db; private readonly IServiceProvider _services; - public int Priority { get; } = int.MaxValue; - private readonly ConcurrentDictionary<(ulong, string), DiscordPermOverride> _overrides; public DiscordPermOverrideService(DbService db, IServiceProvider services) @@ -19,11 +18,10 @@ public class DiscordPermOverrideService : INService, ILateBlocker _db = db; _services = services; using var uow = _db.GetDbContext(); - _overrides = uow.DiscordPermOverrides - .AsNoTracking() - .AsEnumerable() - .ToDictionary(o => (o.GuildId ?? 0, o.Command), o => o) - .ToConcurrent(); + _overrides = uow.DiscordPermOverrides.AsNoTracking() + .AsEnumerable() + .ToDictionary(o => (o.GuildId ?? 0, o.Command), o => o) + .ToConcurrent(); } public bool TryGetOverrides(ulong guildId, string commandName, out GuildPerm? perm) @@ -39,8 +37,11 @@ public class DiscordPermOverrideService : INService, ILateBlocker return false; } - public Task ExecuteOverrides(ICommandContext ctx, CommandInfo command, - GuildPerm perms, IServiceProvider services) + public Task ExecuteOverrides( + ICommandContext ctx, + CommandInfo command, + GuildPerm perms, + IServiceProvider services) { var rupa = new RequireUserPermissionAttribute(perms); return rupa.CheckPermissionsAsync(ctx, command, services); @@ -50,20 +51,14 @@ public class DiscordPermOverrideService : INService, ILateBlocker { commandName = commandName.ToLowerInvariant(); await using var uow = _db.GetDbContext(); - var over = await uow - .Set() - .AsQueryable() - .FirstOrDefaultAsync(x => x.GuildId == guildId && commandName == x.Command); + var over = await uow.Set() + .AsQueryable() + .FirstOrDefaultAsync(x => x.GuildId == guildId && commandName == x.Command); if (over is null) - { - uow.Set() - .Add(over = new() { Command = commandName, Perm = perm, GuildId = guildId, }); - } + uow.Set().Add(over = new() { Command = commandName, Perm = perm, GuildId = guildId }); else - { over.Perm = perm; - } _overrides[(guildId, commandName)] = over; @@ -73,20 +68,16 @@ public class DiscordPermOverrideService : INService, ILateBlocker public async Task ClearAllOverrides(ulong guildId) { await using var uow = _db.GetDbContext(); - var overrides = await uow - .Set() - .AsQueryable() - .AsNoTracking() - .Where(x => x.GuildId == guildId) - .ToListAsync(); + var overrides = await uow.Set() + .AsQueryable() + .AsNoTracking() + .Where(x => x.GuildId == guildId) + .ToListAsync(); uow.RemoveRange(overrides); await uow.SaveChangesAsync(); - foreach (var over in overrides) - { - _overrides.TryRemove((guildId, over.Command), out _); - } + foreach (var over in overrides) _overrides.TryRemove((guildId, over.Command), out _); } public async Task RemoveOverride(ulong guildId, string commandName) @@ -94,11 +85,10 @@ public class DiscordPermOverrideService : INService, ILateBlocker commandName = commandName.ToLowerInvariant(); await using var uow = _db.GetDbContext(); - var over = await uow - .Set() - .AsQueryable() - .AsNoTracking() - .FirstOrDefaultAsync(x => x.GuildId == guildId && x.Command == commandName); + var over = await uow.Set() + .AsQueryable() + .AsNoTracking() + .FirstOrDefaultAsync(x => x.GuildId == guildId && x.Command == commandName); if (over is null) return; @@ -112,24 +102,25 @@ public class DiscordPermOverrideService : INService, ILateBlocker public async Task> GetAllOverrides(ulong guildId) { await using var uow = _db.GetDbContext(); - return await uow - .Set() - .AsQueryable() - .AsNoTracking() - .Where(x => x.GuildId == guildId) - .ToListAsync(); + return await uow.Set() + .AsQueryable() + .AsNoTracking() + .Where(x => x.GuildId == guildId) + .ToListAsync(); } public async Task TryBlockLate(ICommandContext context, string moduleName, CommandInfo command) { if (TryGetOverrides(context.Guild?.Id ?? 0, command.Name, out var perm) && perm is not null) { - var result = await new RequireUserPermissionAttribute((GuildPermission)perm) - .CheckPermissionsAsync(context, command, _services); + var result = + await new RequireUserPermissionAttribute((GuildPermission)perm).CheckPermissionsAsync(context, + command, + _services); return !result.IsSuccess; } return false; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/GameVoiceChannelService.cs b/src/NadekoBot/Modules/Administration/Services/GameVoiceChannelService.cs index 71c985430..6b53cef60 100644 --- a/src/NadekoBot/Modules/Administration/Services/GameVoiceChannelService.cs +++ b/src/NadekoBot/Modules/Administration/Services/GameVoiceChannelService.cs @@ -16,9 +16,8 @@ public class GameVoiceChannelService : INService _db = db; _client = client; - GameVoiceChannels = new( - bot.AllGuildConfigs.Where(gc => gc.GameVoiceChannel != null) - .Select(gc => gc.GameVoiceChannel.Value)); + GameVoiceChannels = new(bot.AllGuildConfigs.Where(gc => gc.GameVoiceChannel != null) + .Select(gc => gc.GameVoiceChannel.Value)); _client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated; _client.GuildMemberUpdated += _client_GuildMemberUpdated; @@ -39,11 +38,8 @@ public class GameVoiceChannelService : INService var oldActivity = before.Value.Activities.FirstOrDefault(); var newActivity = after.Activities.FirstOrDefault(); if (oldActivity != newActivity && newActivity is { Type: ActivityType.Playing }) - { //trigger gvc await TriggerGvc(after, newActivity.Name); - } - } catch (Exception ex) { @@ -87,12 +83,10 @@ public class GameVoiceChannelService : INService var game = gUser.Activities.FirstOrDefault()?.Name; - if (oldState.VoiceChannel == newState.VoiceChannel || - newState.VoiceChannel is null) + if (oldState.VoiceChannel == newState.VoiceChannel || newState.VoiceChannel is null) return; - if (!GameVoiceChannels.Contains(newState.VoiceChannel.Id) || - string.IsNullOrWhiteSpace(game)) + if (!GameVoiceChannels.Contains(newState.VoiceChannel.Id) || string.IsNullOrWhiteSpace(game)) return; await TriggerGvc(gUser, game); @@ -112,8 +106,7 @@ public class GameVoiceChannelService : INService return; game = game.TrimTo(50).ToLowerInvariant(); - var vch = gUser.Guild.VoiceChannels - .FirstOrDefault(x => x.Name.ToLowerInvariant() == game); + var vch = gUser.Guild.VoiceChannels.FirstOrDefault(x => x.Name.ToLowerInvariant() == game); if (vch is null) return; @@ -121,4 +114,4 @@ public class GameVoiceChannelService : INService await Task.Delay(1000); await gUser.ModifyAsync(gu => gu.Channel = vch); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/GuildTimezoneService.cs b/src/NadekoBot/Modules/Administration/Services/GuildTimezoneService.cs index d20021947..ab032c004 100644 --- a/src/NadekoBot/Modules/Administration/Services/GuildTimezoneService.cs +++ b/src/NadekoBot/Modules/Administration/Services/GuildTimezoneService.cs @@ -1,6 +1,6 @@ #nullable disable -using NadekoBot.Services.Database.Models; using NadekoBot.Db; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration.Services; @@ -12,11 +12,10 @@ public class GuildTimezoneService : INService public GuildTimezoneService(DiscordSocketClient client, Bot bot, DbService db) { - _timezones = bot.AllGuildConfigs - .Select(GetTimzezoneTuple) - .Where(x => x.Timezone != null) - .ToDictionary(x => x.GuildId, x => x.Timezone) - .ToConcurrent(); + _timezones = bot.AllGuildConfigs.Select(GetTimzezoneTuple) + .Where(x => x.Timezone != null) + .ToDictionary(x => x.GuildId, x => x.Timezone) + .ToConcurrent(); var curUser = client.CurrentUser; if (curUser != null) @@ -48,6 +47,7 @@ public class GuildTimezoneService : INService { tz = null; } + return (x.GuildId, Timezone: tz); } @@ -74,4 +74,4 @@ public class GuildTimezoneService : INService public TimeZoneInfo GetTimeZoneOrUtc(ulong guildId) => GetTimeZoneOrDefault(guildId) ?? TimeZoneInfo.Utc; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/ImageOnlyChannelService.cs b/src/NadekoBot/Modules/Administration/Services/ImageOnlyChannelService.cs index 90609c732..080cc91d6 100644 --- a/src/NadekoBot/Modules/Administration/Services/ImageOnlyChannelService.cs +++ b/src/NadekoBot/Modules/Administration/Services/ImageOnlyChannelService.cs @@ -1,25 +1,25 @@ #nullable disable -using System.Net; -using System.Threading.Channels; using LinqToDB; using Microsoft.Extensions.Caching.Memory; using NadekoBot.Common.ModuleBehaviors; +using System.Net; +using System.Threading.Channels; namespace NadekoBot.Modules.Administration.Services; public sealed class ImageOnlyChannelService : IEarlyBehavior { + public int Priority { get; } = 0; private readonly IMemoryCache _ticketCache; private readonly DiscordSocketClient _client; private readonly DbService _db; private readonly ConcurrentDictionary> _enabledOn; - private readonly Channel _deleteQueue = Channel.CreateBounded(new BoundedChannelOptions(100) - { - FullMode = BoundedChannelFullMode.DropOldest, - SingleReader = true, - SingleWriter = false, - }); + private readonly Channel _deleteQueue = Channel.CreateBounded( + new BoundedChannelOptions(100) + { + FullMode = BoundedChannelFullMode.DropOldest, SingleReader = true, SingleWriter = false + }); public ImageOnlyChannelService(IMemoryCache ticketCache, DiscordSocketClient client, DbService db) @@ -29,14 +29,13 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior _db = db; var uow = _db.GetDbContext(); - _enabledOn = uow.ImageOnlyChannels - .ToList() - .GroupBy(x => x.GuildId) - .ToDictionary(x => x.Key, x => new ConcurrentHashSet(x.Select(x => x.ChannelId))) - .ToConcurrent(); - + _enabledOn = uow.ImageOnlyChannels.ToList() + .GroupBy(x => x.GuildId) + .ToDictionary(x => x.Key, x => new ConcurrentHashSet(x.Select(x => x.ChannelId))) + .ToConcurrent(); + _ = Task.Run(DeleteQueueRunner); - + _client.ChannelDestroyed += ClientOnChannelDestroyed; } @@ -73,25 +72,19 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior { var newState = false; using var uow = _db.GetDbContext(); - if (forceDisable - || (_enabledOn.TryGetValue(guildId, out var channels) - && channels.TryRemove(channelId))) + if (forceDisable || (_enabledOn.TryGetValue(guildId, out var channels) && channels.TryRemove(channelId))) { uow.ImageOnlyChannels.Delete(x => x.ChannelId == channelId); } else { - uow.ImageOnlyChannels.Add(new() - { - GuildId = guildId, - ChannelId = channelId - }); + uow.ImageOnlyChannels.Add(new() { GuildId = guildId, ChannelId = channelId }); channels = _enabledOn.GetOrAdd(guildId, new ConcurrentHashSet()); channels.Add(channelId); newState = true; } - + uow.SaveChanges(); return newState; } @@ -100,20 +93,19 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior { if (msg.Channel is not ITextChannel tch) return false; - + if (msg.Attachments.Any(x => x is { Height: > 0, Width: > 0 })) return false; - if (!_enabledOn.TryGetValue(tch.GuildId, out var chs) - || !chs.Contains(msg.Channel.Id)) + if (!_enabledOn.TryGetValue(tch.GuildId, out var chs) || !chs.Contains(msg.Channel.Id)) return false; var user = await tch.Guild.GetUserAsync(msg.Author.Id) ?? await _client.Rest.GetGuildUserAsync(tch.GuildId, msg.Author.Id); - + if (user is null) return false; - + // ignore owner and admin if (user.Id == tch.Guild.OwnerId || user.GuildPermissions.Administrator) { @@ -129,7 +121,8 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior // can't modify channel perms if not admin apparently if (!botUser.GuildPermissions.ManageGuild) { - ToggleImageOnlyChannel( tch.GuildId, tch.Id, true);; + ToggleImageOnlyChannel(tch.GuildId, tch.Id, true); + ; return false; } @@ -142,16 +135,14 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior msg.Author.Id, msg.Channel.Id); } - + try { await _deleteQueue.Writer.WriteAsync(msg); } catch (Exception ex) { - Log.Error(ex, "Error deleting message {MessageId} in image-only channel {ChannelId}.", - msg.Id, - tch.Id); + Log.Error(ex, "Error deleting message {MessageId} in image-only channel {ChannelId}.", msg.Id, tch.Id); } return true; @@ -159,18 +150,17 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior private bool AddUserTicket(ulong guildId, ulong userId) { - var old = _ticketCache.GetOrCreate($"{guildId}_{userId}", entry => - { - entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1); - return 0; - }); + var old = _ticketCache.GetOrCreate($"{guildId}_{userId}", + entry => + { + entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1); + return 0; + }); _ticketCache.Set($"{guildId}_{userId}", ++old); - + // if this is the third time that the user posts a // non image in an image-only channel on this server return old > 2; } - - public int Priority { get; } = 0; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/LogCommandService.cs b/src/NadekoBot/Modules/Administration/Services/LogCommandService.cs index 9a9d56db3..74e95ef42 100644 --- a/src/NadekoBot/Modules/Administration/Services/LogCommandService.cs +++ b/src/NadekoBot/Modules/Administration/Services/LogCommandService.cs @@ -1,9 +1,9 @@ #nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; -using NadekoBot.Services.Database.Models; using NadekoBot.Db; using NadekoBot.Modules.Administration.Common; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration.Services; @@ -15,7 +15,7 @@ public interface ILogCommandService LogSetting GetGuildLogSettings(ulong guildId); bool Log(ulong guildId, ulong? channelId, LogType type); } - + public sealed class DummyLogCommandService : ILogCommandService { public void AddDeleteIgnore(ulong xId) @@ -34,14 +34,13 @@ public sealed class DummyLogCommandService : ILogCommandService public bool Log(ulong guildId, ulong? channelId, LogType type) => false; } - + public sealed class LogCommandService : ILogCommandService { - private readonly DiscordSocketClient _client; - public ConcurrentDictionary GuildLogSettings { get; } private ConcurrentDictionary> PresenceUpdates { get; } = new(); + private readonly DiscordSocketClient _client; private readonly Timer _timerReference; private readonly IBotStrings _strings; @@ -51,13 +50,19 @@ public sealed class LogCommandService : ILogCommandService private readonly GuildTimezoneService _tz; private readonly IEmbedBuilderService _eb; private readonly IMemoryCache _memoryCache; - + private readonly Timer _clearTimer; private readonly ConcurrentHashSet _ignoreMessageIds = new(); - public LogCommandService(DiscordSocketClient client, IBotStrings strings, - DbService db, MuteService mute, ProtectionService prot, GuildTimezoneService tz, - IMemoryCache memoryCache, IEmbedBuilderService eb) + public LogCommandService( + DiscordSocketClient client, + IBotStrings strings, + DbService db, + MuteService mute, + ProtectionService prot, + GuildTimezoneService tz, + IMemoryCache memoryCache, + IEmbedBuilderService eb) { _client = client; _memoryCache = memoryCache; @@ -69,41 +74,41 @@ public sealed class LogCommandService : ILogCommandService _tz = tz; #if !GLOBAL_NADEKO - + using (var uow = db.GetDbContext()) { var guildIds = client.Guilds.Select(x => x.Id).ToList(); - var configs = uow - .LogSettings - .AsQueryable() - .AsNoTracking() - .Where(x => guildIds.Contains(x.GuildId)) - .Include(ls => ls.LogIgnores) - .ToList(); + var configs = uow.LogSettings.AsQueryable() + .AsNoTracking() + .Where(x => guildIds.Contains(x.GuildId)) + .Include(ls => ls.LogIgnores) + .ToList(); - GuildLogSettings = configs - .ToDictionary(ls => ls.GuildId) - .ToConcurrent(); + GuildLogSettings = configs.ToDictionary(ls => ls.GuildId).ToConcurrent(); } _timerReference = new(async state => - { - var keys = PresenceUpdates.Keys.ToList(); - - await keys.Select(key => { - if (!((SocketGuild) key.Guild).CurrentUser.GetPermissions(key).SendMessages) - return Task.CompletedTask; - if (PresenceUpdates.TryRemove(key, out var msgs)) - { - var title = GetText(key.Guild, strs.presence_updates); - var desc = string.Join(Environment.NewLine, msgs); - return key.SendConfirmAsync(_eb, title, desc.TrimTo(2048)); - } + var keys = PresenceUpdates.Keys.ToList(); - return Task.CompletedTask; - }).WhenAll(); - }, null, TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15)); + await keys.Select(key => + { + if (!((SocketGuild)key.Guild).CurrentUser.GetPermissions(key).SendMessages) + return Task.CompletedTask; + if (PresenceUpdates.TryRemove(key, out var msgs)) + { + var title = GetText(key.Guild, strs.presence_updates); + var desc = string.Join(Environment.NewLine, msgs); + return key.SendConfirmAsync(_eb, title, desc.TrimTo(2048)); + } + + return Task.CompletedTask; + }) + .WhenAll(); + }, + null, + TimeSpan.FromSeconds(15), + TimeSpan.FromSeconds(15)); //_client.MessageReceived += _client_MessageReceived; _client.MessageUpdated += _client_MessageUpdated; @@ -128,10 +133,13 @@ public sealed class LogCommandService : ILogCommandService _prot.OnAntiProtectionTriggered += TriggeredAntiProtection; _clearTimer = new(_ => - { - _ignoreMessageIds.Clear(); - }, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); - + { + _ignoreMessageIds.Clear(); + }, + null, + TimeSpan.FromHours(1), + TimeSpan.FromHours(1)); + #endif } @@ -150,12 +158,11 @@ public sealed class LogCommandService : ILogCommandService using (var uow = _db.GetDbContext()) { var logSetting = uow.LogSettingsFor(gid); - removed = logSetting.LogIgnores - .RemoveAll(x => x.ItemType == itemType && itemId == x.LogItemId); - + removed = logSetting.LogIgnores.RemoveAll(x => x.ItemType == itemType && itemId == x.LogItemId); + if (removed == 0) { - var toAdd = new IgnoredLogItem { LogItemId = itemId, ItemType = itemType}; + var toAdd = new IgnoredLogItem { LogItemId = itemId, ItemType = itemType }; logSetting.LogIgnores.Add(toAdd); } @@ -165,9 +172,9 @@ public sealed class LogCommandService : ILogCommandService return removed > 0; } - - private string GetText(IGuild guild, LocStr str) => - _strings.GetText(str, guild.Id); + + private string GetText(IGuild guild, LocStr str) + => _strings.GetText(str, guild.Id); private string PrettyCurrentTime(IGuild g) { @@ -190,23 +197,12 @@ public sealed class LogCommandService : ILogCommandService { await using var uow = _db.GetDbContext(); var logSetting = uow.LogSettingsFor(guildId); - - logSetting.LogOtherId = - logSetting.MessageUpdatedId = - logSetting.MessageDeletedId = - logSetting.UserJoinedId = - logSetting.UserLeftId = - logSetting.UserBannedId = - logSetting.UserUnbannedId = - logSetting.UserUpdatedId = - logSetting.ChannelCreatedId = - logSetting.ChannelDestroyedId = - logSetting.ChannelUpdatedId = - logSetting.LogUserPresenceId = - logSetting.LogVoicePresenceId = - logSetting.UserMutedId = - logSetting.LogVoicePresenceTTSId = - value ? channelId : null; + + logSetting.LogOtherId = logSetting.MessageUpdatedId = logSetting.MessageDeletedId = logSetting.UserJoinedId = + logSetting.UserLeftId = logSetting.UserBannedId = logSetting.UserUnbannedId = logSetting.UserUpdatedId = + logSetting.ChannelCreatedId = logSetting.ChannelDestroyedId = logSetting.ChannelUpdatedId = + logSetting.LogUserPresenceId = logSetting.LogVoicePresenceId = logSetting.UserMutedId = + logSetting.LogVoicePresenceTTSId = value ? channelId : null; ; await uow.SaveChangesAsync(); GuildLogSettings.AddOrUpdate(guildId, id => logSetting, (id, old) => logSetting); @@ -223,13 +219,11 @@ public sealed class LogCommandService : ILogCommandService var g = after.Guild; - if (!GuildLogSettings.TryGetValue(g.Id, out var logSetting) - || logSetting.UserUpdatedId is null) + if (!GuildLogSettings.TryGetValue(g.Id, out var logSetting) || logSetting.UserUpdatedId is null) return; ITextChannel logChannel; - if ((logChannel = - await TryGetLogChannel(g, logSetting, LogType.UserUpdated)) is null) + if ((logChannel = await TryGetLogChannel(g, logSetting, LogType.UserUpdated)) is null) return; var embed = _eb.Create(); @@ -237,18 +231,18 @@ public sealed class LogCommandService : ILogCommandService if (before.Username != after.Username) { embed.WithTitle("👥 " + GetText(g, strs.username_changed)) - .WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}") - .AddField("Old Name", $"{before.Username}", true) - .AddField("New Name", $"{after.Username}", true) - .WithFooter(CurrentTime(g)) - .WithOkColor(); + .WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}") + .AddField("Old Name", $"{before.Username}", true) + .AddField("New Name", $"{after.Username}", true) + .WithFooter(CurrentTime(g)) + .WithOkColor(); } else if (before.AvatarId != after.AvatarId) { embed.WithTitle("👥" + GetText(g, strs.avatar_changed)) - .WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}") - .WithFooter(CurrentTime(g)) - .WithOkColor(); + .WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}") + .WithFooter(CurrentTime(g)) + .WithOkColor(); var bav = before.RealAvatarUrl(); if (bav != null && bav.IsAbsoluteUri) @@ -273,7 +267,7 @@ public sealed class LogCommandService : ILogCommandService return Task.CompletedTask; } - public bool Log(ulong gid, ulong? cid, LogType type/*, string options*/) + public bool Log(ulong gid, ulong? cid, LogType type /*, string options*/) { ulong? channelId = null; using (var uow = _db.GetDbContext()) @@ -314,19 +308,16 @@ public sealed class LogCommandService : ILogCommandService channelId = logSetting.ChannelCreatedId = logSetting.ChannelCreatedId is null ? cid : default; break; case LogType.ChannelDestroyed: - channelId = logSetting.ChannelDestroyedId = - logSetting.ChannelDestroyedId is null ? cid : default; + channelId = logSetting.ChannelDestroyedId = logSetting.ChannelDestroyedId is null ? cid : default; break; case LogType.ChannelUpdated: channelId = logSetting.ChannelUpdatedId = logSetting.ChannelUpdatedId is null ? cid : default; break; case LogType.UserPresence: - channelId = logSetting.LogUserPresenceId = - logSetting.LogUserPresenceId is null ? cid : default; + channelId = logSetting.LogUserPresenceId = logSetting.LogUserPresenceId is null ? cid : default; break; case LogType.VoicePresence: - channelId = logSetting.LogVoicePresenceId = - logSetting.LogVoicePresenceId is null ? cid : default; + channelId = logSetting.LogVoicePresenceId = logSetting.LogVoicePresenceId is null ? cid : default; break; case LogType.VoicePresenceTTS: channelId = logSetting.LogVoicePresenceTTSId = @@ -365,17 +356,11 @@ public sealed class LogCommandService : ILogCommandService var str = string.Empty; if (beforeVch?.Guild == afterVch?.Guild) - { str = GetText(logChannel.Guild, strs.log_vc_moved(usr.Username, beforeVch?.Name, afterVch?.Name)); - } else if (beforeVch is null) - { str = GetText(logChannel.Guild, strs.log_vc_joined(usr.Username, afterVch.Name)); - } else if (afterVch is null) - { str = GetText(logChannel.Guild, strs.log_vc_left(usr.Username, beforeVch.Name)); - } var toDelete = await logChannel.SendMessageAsync(str, true); toDelete.DeleteAfter(5); @@ -388,14 +373,17 @@ public sealed class LogCommandService : ILogCommandService return Task.CompletedTask; } - private void MuteCommands_UserMuted(IGuildUser usr, IUser mod, MuteType muteType, string reason) + private void MuteCommands_UserMuted( + IGuildUser usr, + IUser mod, + MuteType muteType, + string reason) { var _ = Task.Run(async () => { try { - if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting) - || logSetting.UserMutedId is null) + if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting) || logSetting.UserMutedId is null) return; ITextChannel logChannel; @@ -412,15 +400,16 @@ public sealed class LogCommandService : ILogCommandService mutes = "🔇 " + GetText(logChannel.Guild, strs.xmuted_text(mutedLocalized, mod.ToString())); break; case MuteType.All: - mutes = "🔇 " + GetText(logChannel.Guild, strs.xmuted_text_and_voice(mutedLocalized, - mod.ToString())); + mutes = "🔇 " + + GetText(logChannel.Guild, strs.xmuted_text_and_voice(mutedLocalized, mod.ToString())); break; } - var embed = _eb.Create().WithAuthor(mutes) - .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}") - .WithFooter(CurrentTime(usr.Guild)) - .WithOkColor(); + var embed = _eb.Create() + .WithAuthor(mutes) + .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}") + .WithFooter(CurrentTime(usr.Guild)) + .WithOkColor(); await logChannel.EmbedAsync(embed); } @@ -431,14 +420,17 @@ public sealed class LogCommandService : ILogCommandService }); } - private void MuteCommands_UserUnmuted(IGuildUser usr, IUser mod, MuteType muteType, string reason) + private void MuteCommands_UserUnmuted( + IGuildUser usr, + IUser mod, + MuteType muteType, + string reason) { var _ = Task.Run(async () => { try { - if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting) - || logSetting.UserMutedId is null) + if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting) || logSetting.UserMutedId is null) return; ITextChannel logChannel; @@ -456,15 +448,17 @@ public sealed class LogCommandService : ILogCommandService mutes = "🔊 " + GetText(logChannel.Guild, strs.xmuted_text(unmutedLocalized, mod.ToString())); break; case MuteType.All: - mutes = "🔊 " + GetText(logChannel.Guild, strs.xmuted_text_and_voice(unmutedLocalized, - mod.ToString())); + mutes = "🔊 " + + GetText(logChannel.Guild, + strs.xmuted_text_and_voice(unmutedLocalized, mod.ToString())); break; } - var embed = _eb.Create().WithAuthor(mutes) - .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}") - .WithFooter($"{CurrentTime(usr.Guild)}") - .WithOkColor(); + var embed = _eb.Create() + .WithAuthor(mutes) + .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}") + .WithFooter($"{CurrentTime(usr.Guild)}") + .WithOkColor(); if (!string.IsNullOrWhiteSpace(reason)) embed.WithDescription(reason); @@ -478,8 +472,7 @@ public sealed class LogCommandService : ILogCommandService }); } - public Task TriggeredAntiProtection(PunishmentAction action, ProtectionType protection, - params IGuildUser[] users) + public Task TriggeredAntiProtection(PunishmentAction action, ProtectionType protection, params IGuildUser[] users) { var _ = Task.Run(async () => { @@ -515,11 +508,12 @@ public sealed class LogCommandService : ILogCommandService break; } - var embed = _eb.Create().WithAuthor($"🛡 Anti-{protection}") - .WithTitle(GetText(logChannel.Guild, strs.users) + " " + punishment) - .WithDescription(string.Join("\n", users.Select(u => u.ToString()))) - .WithFooter(CurrentTime(logChannel.Guild)) - .WithOkColor(); + var embed = _eb.Create() + .WithAuthor($"🛡 Anti-{protection}") + .WithTitle(GetText(logChannel.Guild, strs.users) + " " + punishment) + .WithDescription(string.Join("\n", users.Select(u => u.ToString()))) + .WithFooter(CurrentTime(logChannel.Guild)) + .WithOkColor(); await logChannel.EmbedAsync(embed); } @@ -533,13 +527,11 @@ public sealed class LogCommandService : ILogCommandService private string GetRoleDeletedKey(ulong roleId) => $"role_deleted_{roleId}"; - + private Task _client_RoleDeleted(SocketRole socketRole) { Serilog.Log.Information("Role deleted {RoleId}", socketRole.Id); - _memoryCache.Set(GetRoleDeletedKey(socketRole.Id), - true, - TimeSpan.FromMinutes(5)); + _memoryCache.Set(GetRoleDeletedKey(socketRole.Id), true, TimeSpan.FromMinutes(5)); return Task.CompletedTask; } @@ -559,25 +551,27 @@ public sealed class LogCommandService : ILogCommandService if (before is null) return; - + if (!GuildLogSettings.TryGetValue(before.Guild.Id, out var logSetting) - || logSetting.LogIgnores.Any(ilc => ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.User)) + || logSetting.LogIgnores.Any(ilc + => ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.User)) return; ITextChannel logChannel; - if (logSetting.UserUpdatedId != null && - (logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) != null) + if (logSetting.UserUpdatedId != null + && (logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) != null) { - var embed = _eb.Create().WithOkColor() - .WithFooter(CurrentTime(before.Guild)) - .WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}"); + var embed = _eb.Create() + .WithOkColor() + .WithFooter(CurrentTime(before.Guild)) + .WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}"); if (before.Nickname != after.Nickname) { embed.WithAuthor("👥 " + GetText(logChannel.Guild, strs.nick_change)) - .AddField(GetText(logChannel.Guild, strs.old_nick) - , $"{before.Nickname}#{before.Discriminator}") - .AddField(GetText(logChannel.Guild, strs.new_nick) - , $"{after.Nickname}#{after.Discriminator}"); + .AddField(GetText(logChannel.Guild, strs.old_nick), + $"{before.Nickname}#{before.Discriminator}") + .AddField(GetText(logChannel.Guild, strs.new_nick), + $"{after.Nickname}#{after.Discriminator}"); await logChannel.EmbedAsync(embed); } @@ -587,22 +581,21 @@ public sealed class LogCommandService : ILogCommandService { var diffRoles = after.Roles.Where(r => !before.Roles.Contains(r)).Select(r => r.Name); embed.WithAuthor("⚔ " + GetText(logChannel.Guild, strs.user_role_add)) - .WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); + .WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); await logChannel.EmbedAsync(embed); } else if (before.Roles.Count > after.Roles.Count) { await Task.Delay(1000); - var diffRoles = before.Roles - .Where(r => !after.Roles.Contains(r) && !IsRoleDeleted(r.Id)) - .Select(r => r.Name) - .ToList(); + var diffRoles = before.Roles.Where(r => !after.Roles.Contains(r) && !IsRoleDeleted(r.Id)) + .Select(r => r.Name) + .ToList(); if (diffRoles.Any()) { embed.WithAuthor("⚔ " + GetText(logChannel.Guild, strs.user_role_rem)) - .WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); + .WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); await logChannel.EmbedAsync(embed); } @@ -611,17 +604,20 @@ public sealed class LogCommandService : ILogCommandService } logChannel = null; - if (!before.IsBot && logSetting.LogUserPresenceId != null && (logChannel = - await TryGetLogChannel(before.Guild, logSetting, LogType.UserPresence)) != null) + if (!before.IsBot + && logSetting.LogUserPresenceId != null + && (logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserPresence)) != null) { if (before.Status != after.Status) { - var str = "🎭" + Format.Code(PrettyCurrentTime(after.Guild)) + - GetText(logChannel.Guild, strs.user_status_change( - "👤" + Format.Bold(after.Username), - Format.Bold(after.Status.ToString()))); + var str = "🎭" + + Format.Code(PrettyCurrentTime(after.Guild)) + + GetText(logChannel.Guild, + strs.user_status_change("👤" + Format.Bold(after.Username), + Format.Bold(after.Status.ToString()))); PresenceUpdates.AddOrUpdate(logChannel, - new List() {str}, (id, list) => + new List { str }, + (id, list) => { list.Add(str); return list; @@ -632,7 +628,8 @@ public sealed class LogCommandService : ILogCommandService var str = $"👾`{PrettyCurrentTime(after.Guild)}`👤__**{after.Username}**__ is now playing **{after.Activities.FirstOrDefault()?.Name ?? "-"}**."; PresenceUpdates.AddOrUpdate(logChannel, - new List() {str}, (id, list) => + new List { str }, + (id, list) => { list.Add(str); return list; @@ -657,36 +654,32 @@ public sealed class LogCommandService : ILogCommandService if (cbefore is not IGuildChannel before) return; - var after = (IGuildChannel) cafter; + var after = (IGuildChannel)cafter; if (!GuildLogSettings.TryGetValue(before.Guild.Id, out var logSetting) || logSetting.ChannelUpdatedId is null - || logSetting.LogIgnores.Any(ilc => ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.Channel)) + || logSetting.LogIgnores.Any(ilc + => ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.Channel)) return; ITextChannel logChannel; if ((logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.ChannelUpdated)) is null) return; - var embed = _eb.Create().WithOkColor() - .WithFooter(CurrentTime(before.Guild)); + var embed = _eb.Create().WithOkColor().WithFooter(CurrentTime(before.Guild)); var beforeTextChannel = cbefore as ITextChannel; var afterTextChannel = cafter as ITextChannel; if (before.Name != after.Name) - { embed.WithTitle("ℹ️ " + GetText(logChannel.Guild, strs.ch_name_change)) - .WithDescription($"{after} | {after.Id}") - .AddField(GetText(logChannel.Guild, strs.ch_old_name), before.Name); - } + .WithDescription($"{after} | {after.Id}") + .AddField(GetText(logChannel.Guild, strs.ch_old_name), before.Name); else if (beforeTextChannel?.Topic != afterTextChannel?.Topic) - { embed.WithTitle("ℹ️ " + GetText(logChannel.Guild, strs.ch_topic_change)) - .WithDescription($"{after} | {after.Id}") - .AddField(GetText(logChannel.Guild, strs.old_topic) , beforeTextChannel?.Topic ?? "-") - .AddField(GetText(logChannel.Guild, strs.new_topic), afterTextChannel?.Topic ?? "-"); - } + .WithDescription($"{after} | {after.Id}") + .AddField(GetText(logChannel.Guild, strs.old_topic), beforeTextChannel?.Topic ?? "-") + .AddField(GetText(logChannel.Guild, strs.new_topic), afterTextChannel?.Topic ?? "-"); else return; @@ -711,7 +704,8 @@ public sealed class LogCommandService : ILogCommandService if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out var logSetting) || logSetting.ChannelDestroyedId is null - || logSetting.LogIgnores.Any(ilc => ilc.LogItemId == ch.Id && ilc.ItemType == IgnoredItemType.Channel)) + || logSetting.LogIgnores.Any(ilc + => ilc.LogItemId == ch.Id && ilc.ItemType == IgnoredItemType.Channel)) return; ITextChannel logChannel; @@ -719,17 +713,15 @@ public sealed class LogCommandService : ILogCommandService return; string title; if (ch is IVoiceChannel) - { title = GetText(logChannel.Guild, strs.voice_chan_destroyed); - } else title = GetText(logChannel.Guild, strs.text_chan_destroyed); await logChannel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle("🆕 " + title) - .WithDescription($"{ch.Name} | {ch.Id}") - .WithFooter(CurrentTime(ch.Guild))); + .WithOkColor() + .WithTitle("🆕 " + title) + .WithDescription($"{ch.Name} | {ch.Id}") + .WithFooter(CurrentTime(ch.Guild))); } catch { @@ -757,17 +749,15 @@ public sealed class LogCommandService : ILogCommandService return; string title; if (ch is IVoiceChannel) - { title = GetText(logChannel.Guild, strs.voice_chan_created); - } else title = GetText(logChannel.Guild, strs.text_chan_created); await logChannel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle("🆕 " + title) - .WithDescription($"{ch.Name} | {ch.Id}") - .WithFooter(CurrentTime(ch.Guild))); + .WithOkColor() + .WithTitle("🆕 " + title) + .WithDescription($"{ch.Name} | {ch.Id}") + .WithFooter(CurrentTime(ch.Guild))); } catch (Exception) { @@ -794,7 +784,8 @@ public sealed class LogCommandService : ILogCommandService if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting) || logSetting.LogVoicePresenceId is null - || logSetting.LogIgnores.Any(ilc => ilc.LogItemId == iusr.Id && ilc.ItemType == IgnoredItemType.User)) + || logSetting.LogIgnores.Any( + ilc => ilc.LogItemId == iusr.Id && ilc.ItemType == IgnoredItemType.User)) return; ITextChannel logChannel; @@ -803,32 +794,33 @@ public sealed class LogCommandService : ILogCommandService string str = null; if (beforeVch?.Guild == afterVch?.Guild) - { - str = "🎙" + Format.Code(PrettyCurrentTime(usr.Guild)) + GetText(logChannel.Guild, - strs.user_vmoved( - "👤" + Format.Bold(usr.Username + "#" + usr.Discriminator), - Format.Bold(beforeVch?.Name ?? ""), Format.Bold(afterVch?.Name ?? ""))); - } + str = "🎙" + + Format.Code(PrettyCurrentTime(usr.Guild)) + + GetText(logChannel.Guild, + strs.user_vmoved("👤" + Format.Bold(usr.Username + "#" + usr.Discriminator), + Format.Bold(beforeVch?.Name ?? ""), + Format.Bold(afterVch?.Name ?? ""))); else if (beforeVch is null) - { - str = "🎙" + Format.Code(PrettyCurrentTime(usr.Guild)) + GetText(logChannel.Guild, - strs.user_vjoined( - "👤" + Format.Bold(usr.Username + "#" + usr.Discriminator), - Format.Bold(afterVch.Name ?? ""))); - } + str = "🎙" + + Format.Code(PrettyCurrentTime(usr.Guild)) + + GetText(logChannel.Guild, + strs.user_vjoined("👤" + Format.Bold(usr.Username + "#" + usr.Discriminator), + Format.Bold(afterVch.Name ?? ""))); else if (afterVch is null) - { - str = "🎙" + Format.Code(PrettyCurrentTime(usr.Guild)) + GetText(logChannel.Guild, - strs.user_vleft("👤" + Format.Bold(usr.Username + "#" + usr.Discriminator), - Format.Bold(beforeVch.Name ?? ""))); - } + str = "🎙" + + Format.Code(PrettyCurrentTime(usr.Guild)) + + GetText(logChannel.Guild, + strs.user_vleft("👤" + Format.Bold(usr.Username + "#" + usr.Discriminator), + Format.Bold(beforeVch.Name ?? ""))); if (!string.IsNullOrWhiteSpace(str)) - PresenceUpdates.AddOrUpdate(logChannel, new List() {str}, (id, list) => - { - list.Add(str); - return list; - }); + PresenceUpdates.AddOrUpdate(logChannel, + new List { str }, + (id, list) => + { + list.Add(str); + return list; + }); } catch { @@ -846,18 +838,19 @@ public sealed class LogCommandService : ILogCommandService { if (!GuildLogSettings.TryGetValue(guild.Id, out var logSetting) || logSetting.UserLeftId is null - || logSetting.LogIgnores.Any(ilc => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User)) + || logSetting.LogIgnores.Any(ilc + => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User)) return; ITextChannel logChannel; if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserLeft)) is null) return; var embed = _eb.Create() - .WithOkColor() - .WithTitle("❌ " + GetText(logChannel.Guild, strs.user_left)) - .WithDescription(usr.ToString()) - .AddField("Id", usr.Id.ToString()) - .WithFooter(CurrentTime(guild)); + .WithOkColor() + .WithTitle("❌ " + GetText(logChannel.Guild, strs.user_left)) + .WithDescription(usr.ToString()) + .AddField("Id", usr.Id.ToString()) + .WithFooter(CurrentTime(guild)); if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(usr.GetAvatarUrl()); @@ -878,8 +871,7 @@ public sealed class LogCommandService : ILogCommandService { try { - if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting) - || logSetting.UserJoinedId is null) + if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting) || logSetting.UserJoinedId is null) return; ITextChannel logChannel; @@ -887,17 +879,17 @@ public sealed class LogCommandService : ILogCommandService return; var embed = _eb.Create() - .WithOkColor() - .WithTitle("✅ " + GetText(logChannel.Guild, strs.user_joined)) - .WithDescription($"{usr.Mention} `{usr}`") - .AddField("Id", usr.Id.ToString()) - .AddField(GetText(logChannel.Guild, strs.joined_server), - $"{usr.JoinedAt?.ToString("dd.MM.yyyy HH:mm" ?? "?")}", - true) - .AddField(GetText(logChannel.Guild, strs.joined_discord), - $"{usr.CreatedAt:dd.MM.yyyy HH:mm}", - true) - .WithFooter(CurrentTime(usr.Guild)); + .WithOkColor() + .WithTitle("✅ " + GetText(logChannel.Guild, strs.user_joined)) + .WithDescription($"{usr.Mention} `{usr}`") + .AddField("Id", usr.Id.ToString()) + .AddField(GetText(logChannel.Guild, strs.joined_server), + $"{usr.JoinedAt?.ToString("dd.MM.yyyy HH:mm" ?? "?")}", + true) + .AddField(GetText(logChannel.Guild, strs.joined_discord), + $"{usr.CreatedAt:dd.MM.yyyy HH:mm}", + true) + .WithFooter(CurrentTime(usr.Guild)); if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(usr.GetAvatarUrl()); @@ -920,18 +912,19 @@ public sealed class LogCommandService : ILogCommandService { if (!GuildLogSettings.TryGetValue(guild.Id, out var logSetting) || logSetting.UserUnbannedId is null - || logSetting.LogIgnores.Any(ilc => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User)) + || logSetting.LogIgnores.Any(ilc + => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User)) return; ITextChannel logChannel; if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserUnbanned)) is null) return; var embed = _eb.Create() - .WithOkColor() - .WithTitle("♻️ " + GetText(logChannel.Guild, strs.user_unbanned)) - .WithDescription(usr.ToString()) - .AddField("Id", usr.Id.ToString()) - .WithFooter(CurrentTime(guild)); + .WithOkColor() + .WithTitle("♻️ " + GetText(logChannel.Guild, strs.user_unbanned)) + .WithDescription(usr.ToString()) + .AddField("Id", usr.Id.ToString()) + .WithFooter(CurrentTime(guild)); if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(usr.GetAvatarUrl()); @@ -954,20 +947,19 @@ public sealed class LogCommandService : ILogCommandService { if (!GuildLogSettings.TryGetValue(guild.Id, out var logSetting) || logSetting.UserBannedId is null - || logSetting.LogIgnores.Any(ilc => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User)) + || logSetting.LogIgnores.Any(ilc + => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User)) return; ITextChannel logChannel; - if ((logChannel = - await TryGetLogChannel(guild, logSetting, LogType.UserBanned)) == - null) + if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserBanned)) == null) return; var embed = _eb.Create() - .WithOkColor() - .WithTitle("🚫 " + GetText(logChannel.Guild, strs.user_banned)) - .WithDescription(usr.ToString()) - .AddField("Id", usr.Id.ToString()) - .WithFooter(CurrentTime(guild)); + .WithOkColor() + .WithTitle("🚫 " + GetText(logChannel.Guild, strs.user_banned)) + .WithDescription(usr.ToString()) + .AddField("Id", usr.Id.ToString()) + .WithFooter(CurrentTime(guild)); var avatarUrl = usr.GetAvatarUrl(); @@ -1002,27 +994,28 @@ public sealed class LogCommandService : ILogCommandService if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out var logSetting) || logSetting.MessageDeletedId is null - || logSetting.LogIgnores.Any(ilc => ilc.LogItemId == channel.Id && ilc.ItemType == IgnoredItemType.Channel)) + || logSetting.LogIgnores.Any(ilc + => ilc.LogItemId == channel.Id && ilc.ItemType == IgnoredItemType.Channel)) return; ITextChannel logChannel; - if ((logChannel = await TryGetLogChannel(channel.Guild, logSetting, LogType.MessageDeleted)) is null || logChannel.Id == msg.Id) + if ((logChannel = await TryGetLogChannel(channel.Guild, logSetting, LogType.MessageDeleted)) is null + || logChannel.Id == msg.Id) return; - var resolvedMessage = msg.Resolve(userHandling: TagHandling.FullName); + var resolvedMessage = msg.Resolve(TagHandling.FullName); var embed = _eb.Create() - .WithOkColor() - .WithTitle("🗑 " + GetText(logChannel.Guild, strs.msg_del(((ITextChannel) msg.Channel).Name))) - .WithDescription(msg.Author.ToString()) - .AddField(GetText(logChannel.Guild, strs.content), - string.IsNullOrWhiteSpace(resolvedMessage) ? "-" : resolvedMessage, - false) - .AddField("Id", msg.Id.ToString(), false) - .WithFooter(CurrentTime(channel.Guild)); + .WithOkColor() + .WithTitle("🗑 " + + GetText(logChannel.Guild, strs.msg_del(((ITextChannel)msg.Channel).Name))) + .WithDescription(msg.Author.ToString()) + .AddField(GetText(logChannel.Guild, strs.content), + string.IsNullOrWhiteSpace(resolvedMessage) ? "-" : resolvedMessage) + .AddField("Id", msg.Id.ToString()) + .WithFooter(CurrentTime(channel.Guild)); if (msg.Attachments.Any()) embed.AddField(GetText(logChannel.Guild, strs.attachments), - string.Join(", ", msg.Attachments.Select(a => a.Url)), - false); + string.Join(", ", msg.Attachments.Select(a => a.Url))); await logChannel.EmbedAsync(embed); } @@ -1034,7 +1027,9 @@ public sealed class LogCommandService : ILogCommandService return Task.CompletedTask; } - private Task _client_MessageUpdated(Cacheable optmsg, SocketMessage imsg2, + private Task _client_MessageUpdated( + Cacheable optmsg, + SocketMessage imsg2, ISocketMessageChannel ch) { var _ = Task.Run(async () => @@ -1058,30 +1053,29 @@ public sealed class LogCommandService : ILogCommandService if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out var logSetting) || logSetting.MessageUpdatedId is null - || logSetting.LogIgnores.Any(ilc => ilc.LogItemId == channel.Id && ilc.ItemType == IgnoredItemType.Channel)) + || logSetting.LogIgnores.Any(ilc + => ilc.LogItemId == channel.Id && ilc.ItemType == IgnoredItemType.Channel)) return; ITextChannel logChannel; - if ((logChannel = await TryGetLogChannel(channel.Guild, logSetting, LogType.MessageUpdated)) is null || logChannel.Id == after.Channel.Id) + if ((logChannel = await TryGetLogChannel(channel.Guild, logSetting, LogType.MessageUpdated)) is null + || logChannel.Id == after.Channel.Id) return; var embed = _eb.Create() - .WithOkColor() - .WithTitle("📝 " + GetText(logChannel.Guild, strs.msg_update(((ITextChannel)after.Channel).Name))) - .WithDescription(after.Author.ToString()) - .AddField(GetText(logChannel.Guild, strs.old_msg), - string.IsNullOrWhiteSpace(before.Content) - ? "-" - : before.Resolve(userHandling: TagHandling.FullName), - false) - .AddField( - GetText(logChannel.Guild, strs.new_msg), - string.IsNullOrWhiteSpace(after.Content) - ? "-" - : after.Resolve(userHandling: TagHandling.FullName), - false) - .AddField("Id", after.Id.ToString(), false) - .WithFooter(CurrentTime(channel.Guild)); + .WithOkColor() + .WithTitle("📝 " + + GetText(logChannel.Guild, + strs.msg_update(((ITextChannel)after.Channel).Name))) + .WithDescription(after.Author.ToString()) + .AddField(GetText(logChannel.Guild, strs.old_msg), + string.IsNullOrWhiteSpace(before.Content) + ? "-" + : before.Resolve(TagHandling.FullName)) + .AddField(GetText(logChannel.Guild, strs.new_msg), + string.IsNullOrWhiteSpace(after.Content) ? "-" : after.Resolve(TagHandling.FullName)) + .AddField("Id", after.Id.ToString()) + .WithFooter(CurrentTime(channel.Guild)); await logChannel.EmbedAsync(embed); } @@ -1158,8 +1152,8 @@ public sealed class LogCommandService : ILogCommandService UnsetLogSetting(guild.Id, logChannelType); return null; } - else - return channel; + + return channel; } private void UnsetLogSetting(ulong guildId, LogType logChannelType) @@ -1218,4 +1212,4 @@ public sealed class LogCommandService : ILogCommandService GuildLogSettings.AddOrUpdate(guildId, newLogSetting, (gid, old) => newLogSetting); uow.SaveChanges(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/MuteService.cs b/src/NadekoBot/Modules/Administration/Services/MuteService.cs index b58447109..9c0b7153c 100644 --- a/src/NadekoBot/Modules/Administration/Services/MuteService.cs +++ b/src/NadekoBot/Modules/Administration/Services/MuteService.cs @@ -1,7 +1,7 @@ #nullable disable using Microsoft.EntityFrameworkCore; -using NadekoBot.Services.Database.Models; using NadekoBot.Db; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration.Services; @@ -14,17 +14,19 @@ public enum MuteType public class MuteService : INService { - public ConcurrentDictionary GuildMuteRoles { get; } - public ConcurrentDictionary> MutedUsers { get; } + public enum TimerType { Mute, Ban, AddRole } - public ConcurrentDictionary> Un_Timers { get; } = new(); + private static readonly OverwritePermissions denyOverwrite = new(addReactions: PermValue.Deny, + sendMessages: PermValue.Deny, + attachFiles: PermValue.Deny); public event Action UserMuted = delegate { }; public event Action UserUnmuted = delegate { }; - private static readonly OverwritePermissions denyOverwrite = - new(addReactions: PermValue.Deny, sendMessages: PermValue.Deny, - attachFiles: PermValue.Deny); + public ConcurrentDictionary GuildMuteRoles { get; } + public ConcurrentDictionary> MutedUsers { get; } + + public ConcurrentDictionary> Un_Timers { get; } = new(); private readonly DiscordSocketClient _client; private readonly DbService _db; @@ -39,24 +41,21 @@ public class MuteService : INService using (var uow = db.GetDbContext()) { var guildIds = client.Guilds.Select(x => x.Id).ToList(); - var configs = uow.Set().AsQueryable() - .Include(x => x.MutedUsers) - .Include(x => x.UnbanTimer) - .Include(x => x.UnmuteTimers) - .Include(x => x.UnroleTimer) - .Where(x => guildIds.Contains(x.GuildId)) - .ToList(); + var configs = uow.Set() + .AsQueryable() + .Include(x => x.MutedUsers) + .Include(x => x.UnbanTimer) + .Include(x => x.UnmuteTimers) + .Include(x => x.UnroleTimer) + .Where(x => guildIds.Contains(x.GuildId)) + .ToList(); - GuildMuteRoles = configs - .Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName)) - .ToDictionary(c => c.GuildId, c => c.MuteRoleName) - .ToConcurrent(); + GuildMuteRoles = configs.Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName)) + .ToDictionary(c => c.GuildId, c => c.MuteRoleName) + .ToConcurrent(); - MutedUsers = new(configs - .ToDictionary( - k => k.GuildId, - v => new ConcurrentHashSet(v.MutedUsers.Select(m => m.UserId)) - )); + MutedUsers = new(configs.ToDictionary(k => k.GuildId, + v => new ConcurrentHashSet(v.MutedUsers.Select(m => m.UserId)))); var max = TimeSpan.FromDays(49); @@ -118,30 +117,40 @@ public class MuteService : INService UserUnmuted += OnUserUnmuted; } - private void OnUserMuted(IGuildUser user, IUser mod, MuteType type, string reason) + private void OnUserMuted( + IGuildUser user, + IUser mod, + MuteType type, + string reason) { if (string.IsNullOrWhiteSpace(reason)) return; - + var _ = Task.Run(() => user.SendMessageAsync(embed: _eb.Create() - .WithDescription($"You've been muted in {user.Guild} server") - .AddField("Mute Type", type.ToString()) - .AddField("Moderator", mod.ToString()) - .AddField("Reason", reason) - .Build())); + .WithDescription( + $"You've been muted in {user.Guild} server") + .AddField("Mute Type", type.ToString()) + .AddField("Moderator", mod.ToString()) + .AddField("Reason", reason) + .Build())); } - private void OnUserUnmuted(IGuildUser user, IUser mod, MuteType type, string reason) + private void OnUserUnmuted( + IGuildUser user, + IUser mod, + MuteType type, + string reason) { if (string.IsNullOrWhiteSpace(reason)) return; - + var _ = Task.Run(() => user.SendMessageAsync(embed: _eb.Create() - .WithDescription($"You've been unmuted in {user.Guild} server") - .AddField("Unmute Type", type.ToString()) - .AddField("Moderator", mod.ToString()) - .AddField("Reason", reason) - .Build())); + .WithDescription( + $"You've been unmuted in {user.Guild} server") + .AddField("Unmute Type", type.ToString()) + .AddField("Moderator", mod.ToString()) + .AddField("Reason", reason) + .Build())); } private Task Client_UserJoined(IGuildUser usr) @@ -158,6 +167,7 @@ public class MuteService : INService { Log.Warning(ex, "Error in MuteService UserJoined event"); } + return Task.CompletedTask; } @@ -170,11 +180,17 @@ public class MuteService : INService await uow.SaveChangesAsync(); } - public async Task MuteUser(IGuildUser usr, IUser mod, MuteType type = MuteType.All, string reason = "") + public async Task MuteUser( + IGuildUser usr, + IUser mod, + MuteType type = MuteType.All, + string reason = "") { if (type == MuteType.All) { - try { await usr.ModifyAsync(x => x.Mute = true); } catch { } + try { await usr.ModifyAsync(x => x.Mute = true); } + catch { } + var muteRole = await GetMuteRole(usr.Guild); if (!usr.RoleIds.Contains(muteRole.Id)) await usr.AddRoleAsync(muteRole); @@ -182,12 +198,8 @@ public class MuteService : INService await using (var uow = _db.GetDbContext()) { var config = uow.GuildConfigsForId(usr.Guild.Id, - set => set.Include(gc => gc.MutedUsers) - .Include(gc => gc.UnmuteTimers)); - config.MutedUsers.Add(new() - { - UserId = usr.Id - }); + set => set.Include(gc => gc.MutedUsers).Include(gc => gc.UnmuteTimers)); + config.MutedUsers.Add(new() { UserId = usr.Id }); if (MutedUsers.TryGetValue(usr.Guild.Id, out var muted)) muted.Add(usr.Id); @@ -195,6 +207,7 @@ public class MuteService : INService await uow.SaveChangesAsync(); } + UserMuted(usr, mod, MuteType.All, reason); } else if (type == MuteType.Voice) @@ -213,7 +226,12 @@ public class MuteService : INService } } - public async Task UnmuteUser(ulong guildId, ulong usrId, IUser mod, MuteType type = MuteType.All, string reason = "") + public async Task UnmuteUser( + ulong guildId, + ulong usrId, + IUser mod, + MuteType type = MuteType.All, + string reason = "") { var usr = _client.GetGuild(guildId)?.GetUser(usrId); if (type == MuteType.All) @@ -221,17 +239,11 @@ public class MuteService : INService StopTimer(guildId, usrId, TimerType.Mute); await using (var uow = _db.GetDbContext()) { - var config = uow.GuildConfigsForId(guildId, set => set.Include(gc => gc.MutedUsers) - .Include(gc => gc.UnmuteTimers)); - var match = new MutedUserId() - { - UserId = usrId - }; + var config = uow.GuildConfigsForId(guildId, + set => set.Include(gc => gc.MutedUsers).Include(gc => gc.UnmuteTimers)); + var match = new MutedUserId { UserId = usrId }; var toRemove = config.MutedUsers.FirstOrDefault(x => x.Equals(match)); - if (toRemove != null) - { - uow.Remove(toRemove); - } + if (toRemove != null) uow.Remove(toRemove); if (MutedUsers.TryGetValue(guildId, out var muted)) muted.TryRemove(usrId); @@ -239,10 +251,18 @@ public class MuteService : INService await uow.SaveChangesAsync(); } + if (usr != null) { - try { await usr.ModifyAsync(x => x.Mute = false); } catch { } - try { await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild)); } catch { /*ignore*/ } + try { await usr.ModifyAsync(x => x.Mute = false); } + catch { } + + try { await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild)); } + catch + { + /*ignore*/ + } + UserUnmuted(usr, mod, MuteType.All, reason); } } @@ -277,20 +297,16 @@ public class MuteService : INService var muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName); if (muteRole is null) - { - //if it doesn't exist, create it try { muteRole = await guild.CreateRoleAsync(muteRoleName, isMentionable: false); } catch { //if creations fails, maybe the name is not correct, find default one, if doesn't work, create default one - muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) ?? - await guild.CreateRoleAsync(defaultMuteRoleName, isMentionable: false); + muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) + ?? await guild.CreateRoleAsync(defaultMuteRoleName, isMentionable: false); } - } foreach (var toOverwrite in await guild.GetTextChannelsAsync()) - { try { if (!toOverwrite.PermissionOverwrites.Any(x => x.TargetId == muteRole.Id @@ -305,12 +321,16 @@ public class MuteService : INService { // ignored } - } return muteRole; } - public async Task TimedMute(IGuildUser user, IUser mod, TimeSpan after, MuteType muteType = MuteType.All, string reason = "") + public async Task TimedMute( + IGuildUser user, + IUser mod, + TimeSpan after, + MuteType muteType = MuteType.All, + string reason = "") { await MuteUser(user, mod, muteType, reason); // mute the user. This will also remove any previous unmute timers await using (var uow = _db.GetDbContext()) @@ -318,8 +338,7 @@ public class MuteService : INService var config = uow.GuildConfigsForId(user.GuildId, set => set.Include(x => x.UnmuteTimers)); config.UnmuteTimers.Add(new() { - UserId = user.Id, - UnmuteAt = DateTime.UtcNow + after, + UserId = user.Id, UnmuteAt = DateTime.UtcNow + after }); // add teh unmute timer to the database uow.SaveChanges(); } @@ -327,7 +346,11 @@ public class MuteService : INService StartUn_Timer(user.GuildId, user.Id, after, TimerType.Mute); // start the timer } - public async Task TimedBan(IGuild guild, IUser user, TimeSpan after, string reason) + public async Task TimedBan( + IGuild guild, + IUser user, + TimeSpan after, + string reason) { await guild.AddBanAsync(user.Id, 0, reason); await using (var uow = _db.GetDbContext()) @@ -335,8 +358,7 @@ public class MuteService : INService var config = uow.GuildConfigsForId(guild.Id, set => set.Include(x => x.UnbanTimer)); config.UnbanTimer.Add(new() { - UserId = user.Id, - UnbanAt = DateTime.UtcNow + after, + UserId = user.Id, UnbanAt = DateTime.UtcNow + after }); // add teh unmute timer to the database uow.SaveChanges(); } @@ -344,7 +366,11 @@ public class MuteService : INService StartUn_Timer(guild.Id, user.Id, after, TimerType.Ban); // start the timer } - public async Task TimedRole(IGuildUser user, TimeSpan after, string reason, IRole role) + public async Task TimedRole( + IGuildUser user, + TimeSpan after, + string reason, + IRole role) { await user.AddRoleAsync(role); await using (var uow = _db.GetDbContext()) @@ -352,81 +378,78 @@ public class MuteService : INService var config = uow.GuildConfigsForId(user.GuildId, set => set.Include(x => x.UnroleTimer)); config.UnroleTimer.Add(new() { - UserId = user.Id, - UnbanAt = DateTime.UtcNow + after, - RoleId = role.Id + UserId = user.Id, UnbanAt = DateTime.UtcNow + after, RoleId = role.Id }); // add teh unmute timer to the database uow.SaveChanges(); } + StartUn_Timer(user.GuildId, user.Id, after, TimerType.AddRole, role.Id); // start the timer } - public enum TimerType { Mute, Ban, AddRole } - public void StartUn_Timer(ulong guildId, ulong userId, TimeSpan after, TimerType type, ulong? roleId = null) + public void StartUn_Timer( + ulong guildId, + ulong userId, + TimeSpan after, + TimerType type, + ulong? roleId = null) { //load the unmute timers for this guild var userUnTimers = Un_Timers.GetOrAdd(guildId, new ConcurrentDictionary<(ulong, TimerType), Timer>()); //unmute timer to be added var toAdd = new Timer(async _ => - { - if (type == TimerType.Ban) { - try - { - RemoveTimerFromDb(guildId, userId, type); - StopTimer(guildId, userId, type); - var guild = _client.GetGuild(guildId); // load the guild - if (guild != null) + if (type == TimerType.Ban) + try { - await guild.RemoveBanAsync(userId); + RemoveTimerFromDb(guildId, userId, type); + StopTimer(guildId, userId, type); + var guild = _client.GetGuild(guildId); // load the guild + if (guild != null) await guild.RemoveBanAsync(userId); } - } - catch (Exception ex) - { - Log.Warning(ex, "Couldn't unban user {0} in guild {1}", userId, guildId); - } - } - else if (type == TimerType.AddRole) - { - try - { - RemoveTimerFromDb(guildId, userId, type); - StopTimer(guildId, userId, type); - var guild = _client.GetGuild(guildId); - var user = guild?.GetUser(userId); - var role = guild.GetRole(roleId.Value); - if (guild != null && user != null && user.Roles.Contains(role)) + catch (Exception ex) { - await user.RemoveRoleAsync(role); + Log.Warning(ex, "Couldn't unban user {0} in guild {1}", userId, guildId); } - } - catch (Exception ex) - { - Log.Warning(ex, "Couldn't remove role from user {0} in guild {1}", userId, guildId); - } - } - else - { - try - { - // unmute the user, this will also remove the timer from the db - await UnmuteUser(guildId, userId, _client.CurrentUser, reason: "Timed mute expired"); - } - catch (Exception ex) - { - RemoveTimerFromDb(guildId, userId, type); // if unmute errored, just remove unmute from db - Log.Warning(ex, "Couldn't unmute user {0} in guild {1}", userId, guildId); - } - } - }, null, after, Timeout.InfiniteTimeSpan); + else if (type == TimerType.AddRole) + try + { + RemoveTimerFromDb(guildId, userId, type); + StopTimer(guildId, userId, type); + var guild = _client.GetGuild(guildId); + var user = guild?.GetUser(userId); + var role = guild.GetRole(roleId.Value); + if (guild != null && user != null && user.Roles.Contains(role)) + await user.RemoveRoleAsync(role); + } + catch (Exception ex) + { + Log.Warning(ex, "Couldn't remove role from user {0} in guild {1}", userId, guildId); + } + else + try + { + // unmute the user, this will also remove the timer from the db + await UnmuteUser(guildId, userId, _client.CurrentUser, reason: "Timed mute expired"); + } + catch (Exception ex) + { + RemoveTimerFromDb(guildId, userId, type); // if unmute errored, just remove unmute from db + Log.Warning(ex, "Couldn't unmute user {0} in guild {1}", userId, guildId); + } + }, + null, + after, + Timeout.InfiniteTimeSpan); //add it, or stop the old one and add this one - userUnTimers.AddOrUpdate((userId, type), key => toAdd, (key, old) => - { - old.Change(Timeout.Infinite, Timeout.Infinite); - return toAdd; - }); + userUnTimers.AddOrUpdate((userId, type), + key => toAdd, + (key, old) => + { + old.Change(Timeout.Infinite, Timeout.Infinite); + return toAdd; + }); } public void StopTimer(ulong guildId, ulong userId, TimerType type) @@ -434,10 +457,7 @@ public class MuteService : INService if (!Un_Timers.TryGetValue(guildId, out var userTimer)) return; - if (userTimer.TryRemove((userId, type), out var removed)) - { - removed.Change(Timeout.Infinite, Timeout.Infinite); - } + if (userTimer.TryRemove((userId, type), out var removed)) removed.Change(Timeout.Infinite, Timeout.Infinite); } private void RemoveTimerFromDb(ulong guildId, ulong userId, TimerType type) @@ -454,10 +474,8 @@ public class MuteService : INService var config = uow.GuildConfigsForId(guildId, set => set.Include(x => x.UnbanTimer)); toDelete = config.UnbanTimer.FirstOrDefault(x => x.UserId == userId); } - if (toDelete != null) - { - uow.Remove(toDelete); - } + + if (toDelete != null) uow.Remove(toDelete); uow.SaveChanges(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/PlayingRotateService.cs b/src/NadekoBot/Modules/Administration/Services/PlayingRotateService.cs index 0d28b274c..1a6d6bd71 100644 --- a/src/NadekoBot/Modules/Administration/Services/PlayingRotateService.cs +++ b/src/NadekoBot/Modules/Administration/Services/PlayingRotateService.cs @@ -1,6 +1,6 @@ #nullable disable -using NadekoBot.Services.Database.Models; using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration.Services; @@ -13,13 +13,13 @@ public sealed class PlayingRotateService : INService private readonly DbService _db; private readonly Bot _bot; - private class TimerState - { - public int Index { get; set; } - } - - public PlayingRotateService(DiscordSocketClient client, DbService db, Bot bot, - BotConfigService bss, IEnumerable phProviders, SelfService selfService) + public PlayingRotateService( + DiscordSocketClient client, + DbService db, + Bot bot, + BotConfigService bss, + IEnumerable phProviders, + SelfService selfService) { _db = db; _bot = bot; @@ -28,10 +28,7 @@ public sealed class PlayingRotateService : INService if (client.ShardId == 0) { - _rep = new ReplacementBuilder() - .WithClient(client) - .WithProviders(phProviders) - .Build(); + _rep = new ReplacementBuilder().WithClient(client).WithProviders(phProviders).Build(); _t = new(RotatingStatuses, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); } @@ -41,17 +38,14 @@ public sealed class PlayingRotateService : INService { try { - var state = (TimerState) objState; + var state = (TimerState)objState; if (!_bss.Data.RotateStatuses) return; IReadOnlyList rotatingStatuses; await using (var uow = _db.GetDbContext()) { - rotatingStatuses = uow.RotatingStatus - .AsNoTracking() - .OrderBy(x => x.Id) - .ToList(); + rotatingStatuses = uow.RotatingStatus.AsNoTracking().OrderBy(x => x.Id).ToList(); } if (rotatingStatuses.Count == 0) @@ -76,11 +70,7 @@ public sealed class PlayingRotateService : INService throw new ArgumentOutOfRangeException(nameof(index)); await using var uow = _db.GetDbContext(); - var toRemove = await uow.RotatingStatus - .AsQueryable() - .AsNoTracking() - .Skip(index) - .FirstOrDefaultAsync(); + var toRemove = await uow.RotatingStatus.AsQueryable().AsNoTracking().Skip(index).FirstOrDefaultAsync(); if (toRemove is null) return null; @@ -93,7 +83,7 @@ public sealed class PlayingRotateService : INService public async Task AddPlaying(ActivityType t, string status) { await using var uow = _db.GetDbContext(); - var toAdd = new RotatingPlayingStatus {Status = status, Type = t}; + var toAdd = new RotatingPlayingStatus { Status = status, Type = t }; uow.Add(toAdd); await uow.SaveChangesAsync(); } @@ -110,4 +100,9 @@ public sealed class PlayingRotateService : INService using var uow = _db.GetDbContext(); return uow.RotatingStatus.AsNoTracking().ToList(); } -} + + private class TimerState + { + public int Index { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/ProtectionService.cs b/src/NadekoBot/Modules/Administration/Services/ProtectionService.cs index dbe679340..6e1a44d95 100644 --- a/src/NadekoBot/Modules/Administration/Services/ProtectionService.cs +++ b/src/NadekoBot/Modules/Administration/Services/ProtectionService.cs @@ -1,38 +1,40 @@ #nullable disable -using System.Threading.Channels; -using NadekoBot.Modules.Administration.Common; -using NadekoBot.Services.Database.Models; using Microsoft.EntityFrameworkCore; using NadekoBot.Db; +using NadekoBot.Modules.Administration.Common; +using NadekoBot.Services.Database.Models; +using System.Threading.Channels; namespace NadekoBot.Modules.Administration.Services; public class ProtectionService : INService { + public event Func OnAntiProtectionTriggered = delegate + { + return Task.CompletedTask; + }; + private readonly ConcurrentDictionary _antiRaidGuilds = new(); private readonly ConcurrentDictionary _antiSpamGuilds = new(); private readonly ConcurrentDictionary _antiAltGuilds = new(); - - public event Func OnAntiProtectionTriggered - = delegate { return Task.CompletedTask; }; private readonly DiscordSocketClient _client; private readonly MuteService _mute; private readonly DbService _db; private readonly UserPunishService _punishService; - - private readonly Channel PunishUserQueue = - System.Threading.Channels.Channel.CreateUnbounded(new() - { - SingleReader = true, - SingleWriter = false - }); - public ProtectionService(DiscordSocketClient client, Bot bot, - MuteService mute, DbService db, UserPunishService punishService) - { + private readonly Channel PunishUserQueue = + Channel.CreateUnbounded(new() { SingleReader = true, SingleWriter = false }); + + public ProtectionService( + DiscordSocketClient client, + Bot bot, + MuteService mute, + DbService db, + UserPunishService punishService) + { _client = client; _mute = mute; _db = db; @@ -42,18 +44,15 @@ public class ProtectionService : INService using (var uow = db.GetDbContext()) { var configs = uow.Set() - .AsQueryable() - .Include(x => x.AntiRaidSetting) - .Include(x => x.AntiSpamSetting) - .ThenInclude(x => x.IgnoredChannels) - .Include(x => x.AntiAltSetting) - .Where(x => ids.Contains(x.GuildId)) - .ToList(); + .AsQueryable() + .Include(x => x.AntiRaidSetting) + .Include(x => x.AntiSpamSetting) + .ThenInclude(x => x.IgnoredChannels) + .Include(x => x.AntiAltSetting) + .Where(x => ids.Contains(x.GuildId)) + .ToList(); - foreach (var gc in configs) - { - Initialize(gc); - } + foreach (var gc in configs) Initialize(gc); } _client.MessageReceived += HandleAntiSpam; @@ -61,7 +60,7 @@ public class ProtectionService : INService bot.JoinedGuild += _bot_JoinedGuild; _client.LeftGuild += _client_LeftGuild; - + _ = Task.Run(RunQueue); } @@ -75,8 +74,13 @@ public class ProtectionService : INService var gu = item.User; try { - await _punishService.ApplyPunishment(gu.Guild, gu, _client.CurrentUser, - item.Action, muteTime, item.RoleId, $"{item.Type} Protection"); + await _punishService.ApplyPunishment(gu.Guild, + gu, + _client.CurrentUser, + item.Action, + muteTime, + item.RoleId, + $"{item.Type} Protection"); } catch (Exception ex) { @@ -104,11 +108,10 @@ public class ProtectionService : INService { using var uow = _db.GetDbContext(); var gcWithData = uow.GuildConfigsForId(gc.GuildId, - set => set - .Include(x => x.AntiRaidSetting) - .Include(x => x.AntiAltSetting) - .Include(x => x.AntiSpamSetting) - .ThenInclude(x => x.IgnoredChannels)); + set => set.Include(x => x.AntiRaidSetting) + .Include(x => x.AntiAltSetting) + .Include(x => x.AntiSpamSetting) + .ThenInclude(x => x.IgnoredChannels)); Initialize(gcWithData); return Task.CompletedTask; @@ -121,7 +124,7 @@ public class ProtectionService : INService if (raid != null) { - var raidStats = new AntiRaidStats() { AntiRaidSettings = raid }; + var raidStats = new AntiRaidStats { AntiRaidSettings = raid }; _antiRaidGuilds[gc.GuildId] = raidStats; } @@ -137,41 +140,38 @@ public class ProtectionService : INService { if (user.IsBot) return Task.CompletedTask; - + _antiRaidGuilds.TryGetValue(user.Guild.Id, out var maybeStats); _antiAltGuilds.TryGetValue(user.Guild.Id, out var maybeAlts); - + if (maybeStats is null && maybeAlts is null) return Task.CompletedTask; _ = Task.Run(async () => { if (maybeAlts is { } alts) - { if (user.CreatedAt != default) { var diff = DateTime.UtcNow - user.CreatedAt.UtcDateTime; if (diff < alts.MinAge) { alts.Increment(); - - await PunishUsers( - alts.Action, + + await PunishUsers(alts.Action, ProtectionType.Alting, - alts.ActionDurationMinutes, + alts.ActionDurationMinutes, alts.RoleId, user); - + return; } } - } - + try { if (maybeStats is not { } stats || !stats.RaidUsers.Add(user)) return; - + ++stats.UsersCount; if (stats.UsersCount >= stats.AntiRaidSettings.UserThreshold) @@ -180,14 +180,13 @@ public class ProtectionService : INService stats.RaidUsers.Clear(); var settings = stats.AntiRaidSettings; - await PunishUsers(settings.Action, ProtectionType.Raiding, - settings.PunishDuration, null, users); + await PunishUsers(settings.Action, ProtectionType.Raiding, settings.PunishDuration, null, users); } + await Task.Delay(1000 * stats.AntiRaidSettings.Seconds); stats.RaidUsers.TryRemove(user); --stats.UsersCount; - } catch { @@ -208,29 +207,29 @@ public class ProtectionService : INService { try { - if (!_antiSpamGuilds.TryGetValue(channel.Guild.Id, out var spamSettings) || - spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new() - { - ChannelId = channel.Id - })) + if (!_antiSpamGuilds.TryGetValue(channel.Guild.Id, out var spamSettings) + || spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new() { ChannelId = channel.Id })) return; - var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, id => new(msg), + var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, + id => new(msg), (id, old) => { - old.ApplyNextMessage(msg); return old; + old.ApplyNextMessage(msg); + return old; }); if (stats.Count >= spamSettings.AntiSpamSettings.MessageThreshold) - { if (spamSettings.UserStats.TryRemove(msg.Author.Id, out stats)) { stats.Dispose(); var settings = spamSettings.AntiSpamSettings; - await PunishUsers(settings.Action, ProtectionType.Spamming, settings.MuteTime, - settings.RoleId, (IGuildUser)msg.Author); + await PunishUsers(settings.Action, + ProtectionType.Spamming, + settings.MuteTime, + settings.RoleId, + (IGuildUser)msg.Author); } - } } catch { @@ -240,18 +239,20 @@ public class ProtectionService : INService return Task.CompletedTask; } - private async Task PunishUsers(PunishmentAction action, ProtectionType pt, int muteTime, ulong? roleId, + private async Task PunishUsers( + PunishmentAction action, + ProtectionType pt, + int muteTime, + ulong? roleId, params IGuildUser[] gus) { - Log.Information( - "[{PunishType}] - Punishing [{Count}] users with [{PunishAction}] in {GuildName} guild", + Log.Information("[{PunishType}] - Punishing [{Count}] users with [{PunishAction}] in {GuildName} guild", pt, gus.Length, action, gus[0].Guild.Name); - + foreach (var gu in gus) - { await PunishUserQueue.Writer.WriteAsync(new() { Action = action, @@ -260,24 +261,27 @@ public class ProtectionService : INService MuteTime = muteTime, RoleId = roleId }); - } _ = OnAntiProtectionTriggered(action, pt, gus); } - public async Task StartAntiRaidAsync(ulong guildId, int userThreshold, int seconds, - PunishmentAction action, int minutesDuration) + public async Task StartAntiRaidAsync( + ulong guildId, + int userThreshold, + int seconds, + PunishmentAction action, + int minutesDuration) { var g = _client.GetGuild(guildId); await _mute.GetMuteRole(g); if (action == PunishmentAction.AddRole) return null; - + if (!IsDurationAllowed(action)) minutesDuration = 0; - var stats = new AntiRaidStats() + var stats = new AntiRaidStats { AntiRaidSettings = new() { @@ -310,6 +314,7 @@ public class ProtectionService : INService uow.SaveChanges(); return true; } + return false; } @@ -317,24 +322,26 @@ public class ProtectionService : INService { if (_antiSpamGuilds.TryRemove(guildId, out var removed)) { - foreach (var (_, val) in removed.UserStats) - { - val.Dispose(); - } - + foreach (var (_, val) in removed.UserStats) val.Dispose(); + using var uow = _db.GetDbContext(); - var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting) - .ThenInclude(x => x.IgnoredChannels)); + var gc = uow.GuildConfigsForId(guildId, + set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels)); gc.AntiSpamSetting = null; uow.SaveChanges(); return true; } + return false; } - public async Task StartAntiSpamAsync(ulong guildId, int messageCount, PunishmentAction action, - int punishDurationMinutes, ulong? roleId) + public async Task StartAntiSpamAsync( + ulong guildId, + int messageCount, + PunishmentAction action, + int punishDurationMinutes, + ulong? roleId) { var g = _client.GetGuild(guildId); await _mute.GetMuteRole(g); @@ -349,15 +356,17 @@ public class ProtectionService : INService Action = action, MessageThreshold = messageCount, MuteTime = punishDurationMinutes, - RoleId = roleId, + RoleId = roleId } }; - stats = _antiSpamGuilds.AddOrUpdate(guildId, stats, (key, old) => - { - stats.AntiSpamSettings.IgnoredChannels = old.AntiSpamSettings.IgnoredChannels; - return stats; - }); + stats = _antiSpamGuilds.AddOrUpdate(guildId, + stats, + (key, old) => + { + stats.AntiSpamSettings.IgnoredChannels = old.AntiSpamSettings.IgnoredChannels; + return stats; + }); await using var uow = _db.GetDbContext(); var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting)); @@ -373,24 +382,20 @@ public class ProtectionService : INService { gc.AntiSpamSetting = stats.AntiSpamSettings; } + await uow.SaveChangesAsync(); return stats; } public async Task AntiSpamIgnoreAsync(ulong guildId, ulong channelId) { - var obj = new AntiSpamIgnore() - { - ChannelId = channelId - }; + var obj = new AntiSpamIgnore { ChannelId = channelId }; bool added; await using var uow = _db.GetDbContext(); - var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels)); + var gc = uow.GuildConfigsForId(guildId, + set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels)); var spam = gc.AntiSpamSetting; - if (spam is null) - { - return null; - } + if (spam is null) return null; if (spam.IgnoredChannels.Add(obj)) // if adding to db is successful { @@ -403,9 +408,7 @@ public class ProtectionService : INService var toRemove = spam.IgnoredChannels.First(x => x.ChannelId == channelId); uow.Set().Remove(toRemove); // remove from db if (_antiSpamGuilds.TryGetValue(guildId, out var temp)) - { temp.AntiSpamSettings.IgnoredChannels.Remove(toRemove); // remove from local cache - } added = false; } @@ -437,8 +440,12 @@ public class ProtectionService : INService } } - public async Task StartAntiAltAsync(ulong guildId, int minAgeMinutes, PunishmentAction action, - int actionDurationMinutes = 0, ulong? roleId = null) + public async Task StartAntiAltAsync( + ulong guildId, + int minAgeMinutes, + PunishmentAction action, + int actionDurationMinutes = 0, + ulong? roleId = null) { await using var uow = _db.GetDbContext(); var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiAltSetting)); @@ -447,7 +454,7 @@ public class ProtectionService : INService Action = action, ActionDurationMinutes = actionDurationMinutes, MinAge = TimeSpan.FromMinutes(minAgeMinutes), - RoleId = roleId, + RoleId = roleId }; await uow.SaveChangesAsync(); @@ -465,4 +472,4 @@ public class ProtectionService : INService await uow.SaveChangesAsync(); return true; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/PruneService.cs b/src/NadekoBot/Modules/Administration/Services/PruneService.cs index e061850db..4f21d0669 100644 --- a/src/NadekoBot/Modules/Administration/Services/PruneService.cs +++ b/src/NadekoBot/Modules/Administration/Services/PruneService.cs @@ -9,12 +9,12 @@ public class PruneService : INService private readonly ILogCommandService _logService; public PruneService(ILogCommandService logService) - => this._logService = logService; + => _logService = logService; public async Task PruneWhere(ITextChannel channel, int amount, Func predicate) { ArgumentNullException.ThrowIfNull(channel, nameof(channel)); - + if (amount <= 0) throw new ArgumentOutOfRangeException(nameof(amount)); @@ -46,17 +46,16 @@ public class PruneService : INService await Task.WhenAll(Task.Delay(1000), channel.DeleteMessagesAsync(bulkDeletable)); foreach (var group in singleDeletable.Chunk(5)) - await Task.WhenAll( - Task.Delay(1000), - group.Select(x => x.DeleteAsync()) - .WhenAll() - ); + await Task.WhenAll(Task.Delay(1000), group.Select(x => x.DeleteAsync()).WhenAll()); //this isn't good, because this still work as if i want to remove only specific user's messages from the last //100 messages, Maybe this needs to be reduced by msgs.Length instead of 100 amount -= 50; - if(amount > 0) - msgs = (await channel.GetMessagesAsync(lastMessage, Direction.Before, 50).FlattenAsync()).Where(predicate).Take(amount).ToArray(); + if (amount > 0) + msgs = (await channel.GetMessagesAsync(lastMessage, Direction.Before, 50).FlattenAsync()) + .Where(predicate) + .Take(amount) + .ToArray(); } } catch @@ -68,4 +67,4 @@ public class PruneService : INService _pruningGuilds.TryRemove(channel.GuildId); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/RoleCommandsService.cs b/src/NadekoBot/Modules/Administration/Services/RoleCommandsService.cs index 2176f614b..1c0c8e1e8 100644 --- a/src/NadekoBot/Modules/Administration/Services/RoleCommandsService.cs +++ b/src/NadekoBot/Modules/Administration/Services/RoleCommandsService.cs @@ -1,10 +1,10 @@ #nullable disable -using Microsoft.EntityFrameworkCore; -using NadekoBot.Common.Collections; -using NadekoBot.Services.Database.Models; using LinqToDB; using LinqToDB.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using NadekoBot.Common.Collections; using NadekoBot.Db; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration.Services; @@ -15,36 +15,34 @@ public class RoleCommandsService : INService private readonly ConcurrentDictionary> _models; /// - /// Contains the (Message ID, User ID) of reaction roles that are currently being processed. + /// Contains the (Message ID, User ID) of reaction roles that are currently being processed. /// private readonly ConcurrentHashSet<(ulong, ulong)> _reacting = new(); - public RoleCommandsService(DiscordSocketClient client, DbService db, - Bot bot) + public RoleCommandsService(DiscordSocketClient client, DbService db, Bot bot) { _db = db; _client = client; #if !GLOBAL_NADEKO - _models = bot.AllGuildConfigs.ToDictionary(x => x.GuildId, - x => x.ReactionRoleMessages) - .ToConcurrent(); + _models = bot.AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.ReactionRoleMessages).ToConcurrent(); _client.ReactionAdded += _client_ReactionAdded; _client.ReactionRemoved += _client_ReactionRemoved; #endif } - private Task _client_ReactionAdded(Cacheable msg, + private Task _client_ReactionAdded( + Cacheable msg, Cacheable chan, SocketReaction reaction) { _ = Task.Run(async () => { - if (!reaction.User.IsSpecified || - reaction.User.Value.IsBot || - reaction.User.Value is not SocketGuildUser gusr || - chan.Value is not SocketGuildChannel gch || - !_models.TryGetValue(gch.Guild.Id, out var confs)) + if (!reaction.User.IsSpecified + || reaction.User.Value.IsBot + || reaction.User.Value is not SocketGuildUser gusr + || chan.Value is not SocketGuildChannel gch + || !_models.TryGetValue(gch.Guild.Id, out var confs)) return; var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id); @@ -53,7 +51,8 @@ public class RoleCommandsService : INService return; // compare emote names for backwards compatibility :facepalm: - var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString()); + var reactionRole = conf.ReactionRoles.FirstOrDefault(x + => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString()); if (reactionRole != null) { @@ -69,7 +68,12 @@ public class RoleCommandsService : INService try { - var removeExclusiveTask = RemoveExclusiveReactionRoleAsync(msg, gusr, reaction, conf, reactionRole, CancellationToken.None); + var removeExclusiveTask = RemoveExclusiveReactionRoleAsync(msg, + gusr, + reaction, + conf, + reactionRole, + CancellationToken.None); var addRoleTask = AddReactionRoleAsync(gusr, reactionRole); await Task.WhenAll(removeExclusiveTask, addRoleTask); @@ -83,11 +87,9 @@ public class RoleCommandsService : INService else { var dl = await msg.GetOrDownloadAsync(); - await dl.RemoveReactionAsync(reaction.Emote, dl.Author, - new() - { - RetryMode = RetryMode.RetryRatelimit | RetryMode.Retry502 - }); + await dl.RemoveReactionAsync(reaction.Emote, + dl.Author, + new() { RetryMode = RetryMode.RetryRatelimit | RetryMode.Retry502 }); Log.Warning("User {0} is adding unrelated reactions to the reaction roles message.", dl.Author); } }); @@ -95,7 +97,8 @@ public class RoleCommandsService : INService return Task.CompletedTask; } - private Task _client_ReactionRemoved(Cacheable msg, + private Task _client_ReactionRemoved( + Cacheable msg, Cacheable chan, SocketReaction reaction) { @@ -103,9 +106,9 @@ public class RoleCommandsService : INService { try { - if (!reaction.User.IsSpecified || - reaction.User.Value.IsBot || - reaction.User.Value is not SocketGuildUser gusr) + if (!reaction.User.IsSpecified + || reaction.User.Value.IsBot + || reaction.User.Value is not SocketGuildUser gusr) return; if (chan.Value is not SocketGuildChannel gch) @@ -119,7 +122,8 @@ public class RoleCommandsService : INService if (conf is null) return; - var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString()); + var reactionRole = conf.ReactionRoles.FirstOrDefault(x + => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString()); if (reactionRole != null) { @@ -143,20 +147,17 @@ public class RoleCommandsService : INService using var uow = _db.GetDbContext(); var table = uow.GetTable(); table.Delete(x => x.MessageId == rrm.MessageId); - - var gc = uow.GuildConfigsForId(id, set => set - .Include(x => x.ReactionRoleMessages) - .ThenInclude(x => x.ReactionRoles)); - + + var gc = uow.GuildConfigsForId(id, + set => set.Include(x => x.ReactionRoleMessages).ThenInclude(x => x.ReactionRoles)); + if (gc.ReactionRoleMessages.Count >= 10) return false; - + gc.ReactionRoleMessages.Add(rrm); uow.SaveChanges(); - - _models.AddOrUpdate(id, - gc.ReactionRoleMessages, - delegate { return gc.ReactionRoleMessages; }); + + _models.AddOrUpdate(id, gc.ReactionRoleMessages, delegate { return gc.ReactionRoleMessages; }); return true; } @@ -164,19 +165,15 @@ public class RoleCommandsService : INService { using var uow = _db.GetDbContext(); var gc = uow.GuildConfigsForId(id, - set => set.Include(x => x.ReactionRoleMessages) - .ThenInclude(x => x.ReactionRoles)); - uow.Set() - .RemoveRange(gc.ReactionRoleMessages[index].ReactionRoles); + set => set.Include(x => x.ReactionRoleMessages).ThenInclude(x => x.ReactionRoles)); + uow.Set().RemoveRange(gc.ReactionRoleMessages[index].ReactionRoles); gc.ReactionRoleMessages.RemoveAt(index); - _models.AddOrUpdate(id, - gc.ReactionRoleMessages, - delegate { return gc.ReactionRoleMessages; }); + _models.AddOrUpdate(id, gc.ReactionRoleMessages, delegate { return gc.ReactionRoleMessages; }); uow.SaveChanges(); } /// - /// Adds a reaction role to the specified user. + /// Adds a reaction role to the specified user. /// /// A Discord guild user. /// The database settings of this reaction role. @@ -184,13 +181,11 @@ public class RoleCommandsService : INService { var toAdd = user.Guild.GetRole(dbRero.RoleId); - return toAdd != null && !user.Roles.Contains(toAdd) - ? user.AddRoleAsync(toAdd) - : Task.CompletedTask; + return toAdd != null && !user.Roles.Contains(toAdd) ? user.AddRoleAsync(toAdd) : Task.CompletedTask; } /// - /// Removes the exclusive reaction roles and reactions from the specified user. + /// Removes the exclusive reaction roles and reactions from the specified user. /// /// The Discord message that contains the reaction roles. /// A Discord guild user. @@ -200,14 +195,20 @@ public class RoleCommandsService : INService /// A cancellation token to cancel the operation. /// Occurs when the operation is cancelled before it began. /// Occurs when the operation is cancelled while it's still executing. - private Task RemoveExclusiveReactionRoleAsync(Cacheable reactionMessage, SocketGuildUser user, SocketReaction reaction, ReactionRoleMessage dbReroMsg, ReactionRole dbRero, CancellationToken cToken = default) + private Task RemoveExclusiveReactionRoleAsync( + Cacheable reactionMessage, + SocketGuildUser user, + SocketReaction reaction, + ReactionRoleMessage dbReroMsg, + ReactionRole dbRero, + CancellationToken cToken = default) { cToken.ThrowIfCancellationRequested(); var roleIds = dbReroMsg.ReactionRoles.Select(x => x.RoleId) - .Where(x => x != dbRero.RoleId) - .Select(x => user.Guild.GetRole(x)) - .Where(x => x != null); + .Where(x => x != dbRero.RoleId) + .Select(x => user.Guild.GetRole(x)) + .Where(x => x != null); var removeReactionsTask = RemoveOldReactionsAsync(reactionMessage, user, reaction, cToken); @@ -217,7 +218,7 @@ public class RoleCommandsService : INService } /// - /// Removes old reactions from an exclusive reaction role. + /// Removes old reactions from an exclusive reaction role. /// /// The Discord message that contains the reaction roles. /// A Discord guild user. @@ -225,7 +226,11 @@ public class RoleCommandsService : INService /// A cancellation token to cancel the operation. /// Occurs when the operation is cancelled before it began. /// Occurs when the operation is cancelled while it's still executing. - private async Task RemoveOldReactionsAsync(Cacheable reactionMessage, SocketGuildUser user, SocketReaction reaction, CancellationToken cToken = default) + private async Task RemoveOldReactionsAsync( + Cacheable reactionMessage, + SocketGuildUser user, + SocketReaction reaction, + CancellationToken cToken = default) { cToken.ThrowIfCancellationRequested(); @@ -236,8 +241,10 @@ public class RoleCommandsService : INService { if (r.Key.Name == reaction.Emote.Name) continue; - try { await dl.RemoveReactionAsync(r.Key, user); } catch { } + try { await dl.RemoveReactionAsync(r.Key, user); } + catch { } + await Task.Delay(100, cToken); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/SelfAssignedRolesService.cs b/src/NadekoBot/Modules/Administration/Services/SelfAssignedRolesService.cs index a97b891a8..03a9a4b8c 100644 --- a/src/NadekoBot/Modules/Administration/Services/SelfAssignedRolesService.cs +++ b/src/NadekoBot/Modules/Administration/Services/SelfAssignedRolesService.cs @@ -1,32 +1,32 @@ #nullable disable -using NadekoBot.Services.Database.Models; using Microsoft.EntityFrameworkCore; using NadekoBot.Db; using NadekoBot.Modules.Xp; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration.Services; public class SelfAssignedRolesService : INService { - private readonly DbService _db; - - public enum RemoveResult - { - Removed, // successfully removed - Err_Not_Assignable, // not assignable (error) - Err_Not_Have, // you don't have a role you want to remove (error) - Err_Not_Perms, // bot doesn't have perms (error) - } - public enum AssignResult { Assigned, // successfully removed Err_Not_Assignable, // not assignable (error) Err_Already_Have, // you already have that role (error) Err_Not_Perms, // bot doesn't have perms (error) - Err_Lvl_Req, // you are not required level (error) + Err_Lvl_Req // you are not required level (error) } + public enum RemoveResult + { + Removed, // successfully removed + Err_Not_Assignable, // not assignable (error) + Err_Not_Have, // you don't have a role you want to remove (error) + Err_Not_Perms // bot doesn't have perms (error) + } + + private readonly DbService _db; + public SelfAssignedRolesService(DbService db) => _db = db; @@ -34,17 +34,9 @@ public class SelfAssignedRolesService : INService { using var uow = _db.GetDbContext(); var roles = uow.SelfAssignableRoles.GetFromGuild(guildId); - if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id)) - { - return false; - } + if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id)) return false; - uow.SelfAssignableRoles.Add(new() - { - Group = @group, - RoleId = role.Id, - GuildId = role.Guild.Id - }); + uow.SelfAssignableRoles.Add(new() { Group = group, RoleId = role.Id, GuildId = role.Guild.Id }); uow.SaveChanges(); return true; } @@ -72,31 +64,20 @@ public class SelfAssignedRolesService : INService var theRoleYouWant = roles.FirstOrDefault(r => r.RoleId == role.Id); if (theRoleYouWant is null) - { return (AssignResult.Err_Not_Assignable, autoDelete, null); - } - else if (theRoleYouWant.LevelRequirement > userLevelData.Level) - { + if (theRoleYouWant.LevelRequirement > userLevelData.Level) return (AssignResult.Err_Lvl_Req, autoDelete, theRoleYouWant.LevelRequirement); - } - else if (guildUser.RoleIds.Contains(role.Id)) - { - return (AssignResult.Err_Already_Have, autoDelete, null); - } + if (guildUser.RoleIds.Contains(role.Id)) return (AssignResult.Err_Already_Have, autoDelete, null); - var roleIds = roles - .Where(x => x.Group == theRoleYouWant.Group) - .Select(x => x.RoleId).ToArray(); + var roleIds = roles.Where(x => x.Group == theRoleYouWant.Group).Select(x => x.RoleId).ToArray(); if (exclusive) { - var sameRoles = guildUser.RoleIds - .Where(r => roleIds.Contains(r)); + var sameRoles = guildUser.RoleIds.Where(r => roleIds.Contains(r)); foreach (var roleId in sameRoles) { var sameRole = guildUser.Guild.GetRole(roleId); if (sameRole != null) - { try { await guildUser.RemoveRoleAsync(sameRole); @@ -106,9 +87,9 @@ public class SelfAssignedRolesService : INService { // ignored } - } } } + try { await guildUser.AddRoleAsync(role); @@ -126,7 +107,7 @@ public class SelfAssignedRolesService : INService var set = false; await using var uow = _db.GetDbContext(); var gc = uow.GuildConfigsForId(guildId, y => y.Include(x => x.SelfAssignableRoleGroupNames)); - var toUpdate = gc.SelfAssignableRoleGroupNames.FirstOrDefault(x => x.Number == @group); + var toUpdate = gc.SelfAssignableRoleGroupNames.FirstOrDefault(x => x.Number == group); if (string.IsNullOrWhiteSpace(name)) { @@ -135,11 +116,7 @@ public class SelfAssignedRolesService : INService } else if (toUpdate is null) { - gc.SelfAssignableRoleGroupNames.Add(new() - { - Name = name, - Number = @group, - }); + gc.SelfAssignableRoleGroupNames.Add(new() { Name = name, Number = group }); set = true; } else @@ -158,13 +135,8 @@ public class SelfAssignedRolesService : INService var (autoDelete, _, roles) = GetAdAndRoles(guildUser.Guild.Id); if (roles.FirstOrDefault(r => r.RoleId == role.Id) is null) - { return (RemoveResult.Err_Not_Assignable, autoDelete); - } - if (!guildUser.RoleIds.Contains(role.Id)) - { - return (RemoveResult.Err_Not_Have, autoDelete); - } + if (!guildUser.RoleIds.Contains(role.Id)) return (RemoveResult.Err_Not_Have, autoDelete); try { await guildUser.RemoveRoleAsync(role); @@ -226,7 +198,8 @@ public class SelfAssignedRolesService : INService return areExclusive; } - public (bool Exclusive, IEnumerable<(SelfAssignedRole Model, IRole Role)> Roles, IDictionary GroupNames) GetRoles(IGuild guild) + public (bool Exclusive, IEnumerable<(SelfAssignedRole Model, IRole Role)> Roles, IDictionary GroupNames + ) GetRoles(IGuild guild) { var exclusive = false; @@ -238,12 +211,11 @@ public class SelfAssignedRolesService : INService exclusive = gc.ExclusiveSelfAssignedRoles; groupNames = gc.SelfAssignableRoleGroupNames.ToDictionary(x => x.Number, x => x.Name); var roleModels = uow.SelfAssignableRoles.GetFromGuild(guild.Id); - roles = roleModels - .Select(x => (Model: x, Role: guild.GetRole(x.RoleId))); + roles = roleModels.Select(x => (Model: x, Role: guild.GetRole(x.RoleId))); uow.SelfAssignableRoles.RemoveRange(roles.Where(x => x.Role is null).Select(x => x.Model).ToArray()); uow.SaveChanges(); } return (exclusive, roles.Where(x => x.Role != null), groupNames); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/SelfService.cs b/src/NadekoBot/Modules/Administration/Services/SelfService.cs index 05ab7a8fc..e1a949ca1 100644 --- a/src/NadekoBot/Modules/Administration/Services/SelfService.cs +++ b/src/NadekoBot/Modules/Administration/Services/SelfService.cs @@ -1,8 +1,8 @@ #nullable disable -using System.Collections.Immutable; -using NadekoBot.Common.ModuleBehaviors; using Microsoft.EntityFrameworkCore; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Services.Database.Models; +using System.Collections.Immutable; namespace NadekoBot.Modules.Administration.Services; @@ -56,58 +56,49 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService _activitySetKey = new("activity.set"); _imagesReloadKey = new("images.reload"); _guildLeaveKey = new("guild.leave"); - + HandleStatusChanges(); - - if (_client.ShardId == 0) - { - _pubSub.Sub(_imagesReloadKey, async _ => await _imgs.Reload()); - } - _pubSub.Sub(_guildLeaveKey, async input => - { - var guildStr = input.ToString().Trim().ToUpperInvariant(); - if (string.IsNullOrWhiteSpace(guildStr)) - return; + if (_client.ShardId == 0) _pubSub.Sub(_imagesReloadKey, async _ => await _imgs.Reload()); - var server = _client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr - || g.Name.Trim().ToUpperInvariant() == guildStr); - if (server is null) + _pubSub.Sub(_guildLeaveKey, + async input => { - return; - } + var guildStr = input.ToString().Trim().ToUpperInvariant(); + if (string.IsNullOrWhiteSpace(guildStr)) + return; - if (server.OwnerId != _client.CurrentUser.Id) - { - await server.LeaveAsync(); - Log.Information($"Left server {server.Name} [{server.Id}]"); - } - else - { - await server.DeleteAsync(); - Log.Information($"Deleted server {server.Name} [{server.Id}]"); - } - }); + var server = _client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr + || g.Name.Trim().ToUpperInvariant() == guildStr); + if (server is null) return; + + if (server.OwnerId != _client.CurrentUser.Id) + { + await server.LeaveAsync(); + Log.Information($"Left server {server.Name} [{server.Id}]"); + } + else + { + await server.DeleteAsync(); + Log.Information($"Deleted server {server.Name} [{server.Id}]"); + } + }); } public async Task OnReadyAsync() { await using var uow = _db.GetDbContext(); - _autoCommands = uow - .AutoCommands - .AsNoTracking() - .Where(x => x.Interval >= 5) - .AsEnumerable() - .GroupBy(x => x.GuildId) - .ToDictionary(x => x.Key, - y => y.ToDictionary(x => x.Id, TimerFromAutoCommand) - .ToConcurrent()) - .ToConcurrent(); + _autoCommands = uow.AutoCommands.AsNoTracking() + .Where(x => x.Interval >= 5) + .AsEnumerable() + .GroupBy(x => x.GuildId) + .ToDictionary(x => x.Key, + y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent()) + .ToConcurrent(); var startupCommands = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0); foreach (var cmd in startupCommands) - { try { await ExecuteCommand(cmd); @@ -115,19 +106,12 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService catch { } - } - if (_client.ShardId == 0) - { - await LoadOwnerChannels(); - } + if (_client.ShardId == 0) await LoadOwnerChannels(); } private Timer TimerFromAutoCommand(AutoCommand x) - => new(async obj => await ExecuteCommand((AutoCommand) obj), - x, - x.Interval * 1000, - x.Interval * 1000); + => new(async obj => await ExecuteCommand((AutoCommand)obj), x, x.Interval * 1000, x.Interval * 1000); private async Task ExecuteCommand(AutoCommand cmd) { @@ -136,7 +120,7 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService if (cmd.GuildId is null) return; - var guildShard = (int) ((cmd.GuildId.Value >> 22) % (ulong) _creds.TotalShards); + var guildShard = (int)((cmd.GuildId.Value >> 22) % (ulong)_creds.TotalShards); if (guildShard != _client.ShardId) return; var prefix = _cmdHandler.GetPrefix(cmd.GuildId); @@ -162,34 +146,26 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService if (cmd.Interval >= 5) { var autos = _autoCommands.GetOrAdd(cmd.GuildId, new ConcurrentDictionary()); - autos.AddOrUpdate(cmd.Id, key => TimerFromAutoCommand(cmd), (key, old) => - { - old.Change(Timeout.Infinite, Timeout.Infinite); - return TimerFromAutoCommand(cmd); - }); + autos.AddOrUpdate(cmd.Id, + key => TimerFromAutoCommand(cmd), + (key, old) => + { + old.Change(Timeout.Infinite, Timeout.Infinite); + return TimerFromAutoCommand(cmd); + }); } } public IEnumerable GetStartupCommands() { using var uow = _db.GetDbContext(); - return uow - .AutoCommands - .AsNoTracking() - .Where(x => x.Interval == 0) - .OrderBy(x => x.Id) - .ToList(); + return uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0).OrderBy(x => x.Id).ToList(); } - + public IEnumerable GetAutoCommands() { using var uow = _db.GetDbContext(); - return uow - .AutoCommands - .AsNoTracking() - .Where(x => x.Interval >= 5) - .OrderBy(x => x.Id) - .ToList(); + return uow.AutoCommands.AsNoTracking().Where(x => x.Interval >= 5).OrderBy(x => x.Id).ToList(); } private async Task LoadOwnerChannels() @@ -204,8 +180,8 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService })); ownerChannels = channels.Where(x => x != null) - .ToDictionary(x => x.Recipient.Id, x => x) - .ToImmutableDictionary(); + .ToDictionary(x => x.Recipient.Id, x => x) + .ToImmutableDictionary(); if (!ownerChannels.Any()) Log.Warning( @@ -214,7 +190,7 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService Log.Information($"Created {ownerChannels.Count} out of {_creds.OwnerIds.Count} owner message channels."); } - public Task LeaveGuild(string guildStr) + public Task LeaveGuild(string guildStr) => _pubSub.Pub(_guildLeaveKey, guildStr); // forwards dms @@ -223,25 +199,21 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService var bs = _bss.Data; if (msg.Channel is IDMChannel && bs.ForwardMessages && ownerChannels.Any()) { - var title = _strings.GetText(strs.dm_from) + - $" [{msg.Author}]({msg.Author.Id})"; + var title = _strings.GetText(strs.dm_from) + $" [{msg.Author}]({msg.Author.Id})"; var attachamentsTxt = _strings.GetText(strs.attachments); var toSend = msg.Content; if (msg.Attachments.Count > 0) - { - toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n" + - string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl)); - } + toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n" + + string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl)); if (bs.ForwardToAllOwners) { var allOwnerChannels = ownerChannels.Values; foreach (var ownerCh in allOwnerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id)) - { try { await ownerCh.SendConfirmAsync(_eb, title, toSend); @@ -250,13 +222,11 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService { Log.Warning("Can't contact owner with id {0}", ownerCh.Recipient.Id); } - } } else { var firstOwnerChannel = ownerChannels.Values.First(); if (firstOwnerChannel.Recipient.Id != msg.Author.Id) - { try { await firstOwnerChannel.SendConfirmAsync(_eb, title, toSend); @@ -265,7 +235,6 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService { // ignored } - } } } } @@ -273,11 +242,7 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService public bool RemoveStartupCommand(int index, out AutoCommand cmd) { using var uow = _db.GetDbContext(); - cmd = uow.AutoCommands - .AsNoTracking() - .Where(x => x.Interval == 0) - .Skip(index) - .FirstOrDefault(); + cmd = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0).Skip(index).FirstOrDefault(); if (cmd != null) { @@ -288,15 +253,11 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService return false; } - + public bool RemoveAutoCommand(int index, out AutoCommand cmd) { using var uow = _db.GetDbContext(); - cmd = uow.AutoCommands - .AsNoTracking() - .Where(x => x.Interval >= 5) - .Skip(index) - .FirstOrDefault(); + cmd = uow.AutoCommands.AsNoTracking().Where(x => x.Interval >= 5).Skip(index).FirstOrDefault(); if (cmd != null) { @@ -337,16 +298,13 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService public void ClearStartupCommands() { using var uow = _db.GetDbContext(); - var toRemove = uow - .AutoCommands - .AsNoTracking() - .Where(x => x.Interval == 0); + var toRemove = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0); uow.AutoCommands.RemoveRange(toRemove); uow.SaveChanges(); } - public Task ReloadImagesAsync() + public Task ReloadImagesAsync() => _pubSub.Pub(_imagesReloadKey, true); public bool ForwardMessages() @@ -363,22 +321,23 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService _bss.ModifyConfig(config => { isToAll = config.ForwardToAllOwners = !config.ForwardToAllOwners; }); return isToAll; } - - private void HandleStatusChanges() - => _pubSub.Sub(_activitySetKey, async data => - { - try - { - await _client.SetGameAsync(data.Name, data.Link, type: data.Type); - } - catch (Exception ex) - { - Log.Warning(ex, "Error setting activity"); - } - }); - public Task SetGameAsync(string game, ActivityType type) - => _pubSub.Pub(_activitySetKey, new() {Name = game, Link = null, Type = type}); + private void HandleStatusChanges() + => _pubSub.Sub(_activitySetKey, + async data => + { + try + { + await _client.SetGameAsync(data.Name, data.Link, data.Type); + } + catch (Exception ex) + { + Log.Warning(ex, "Error setting activity"); + } + }); + + public Task SetGameAsync(string game, ActivityType type) + => _pubSub.Pub(_activitySetKey, new() { Name = game, Link = null, Type = type }); public Task SetStreamAsync(string name, string link) => _pubSub.Pub(_activitySetKey, new() { Name = name, Link = link, Type = ActivityType.Streaming }); @@ -389,4 +348,4 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService public string Link { get; init; } public ActivityType Type { get; init; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/UserPunishService.cs b/src/NadekoBot/Modules/Administration/Services/UserPunishService.cs index c4f560cbe..f5a94ea67 100644 --- a/src/NadekoBot/Modules/Administration/Services/UserPunishService.cs +++ b/src/NadekoBot/Modules/Administration/Services/UserPunishService.cs @@ -1,9 +1,9 @@ #nullable disable using Microsoft.EntityFrameworkCore; using NadekoBot.Common.TypeReaders.Models; -using NadekoBot.Services.Database.Models; using NadekoBot.Db; using NadekoBot.Modules.Permissions.Services; +using NadekoBot.Services.Database.Models; using Newtonsoft.Json; namespace NadekoBot.Modules.Administration.Services; @@ -16,7 +16,11 @@ public class UserPunishService : INService private readonly BotConfigService _bcs; private readonly Timer _warnExpiryTimer; - public UserPunishService(MuteService mute, DbService db, BlacklistService blacklistService, BotConfigService bcs) + public UserPunishService( + MuteService mute, + DbService db, + BlacklistService blacklistService, + BotConfigService bcs) { _mute = mute; _db = db; @@ -24,16 +28,24 @@ public class UserPunishService : INService _bcs = bcs; _warnExpiryTimer = new(async _ => - { - await CheckAllWarnExpiresAsync(); - }, null, TimeSpan.FromSeconds(0), TimeSpan.FromHours(12)); + { + await CheckAllWarnExpiresAsync(); + }, + null, + TimeSpan.FromSeconds(0), + TimeSpan.FromHours(12)); } - public async Task Warn(IGuild guild, ulong userId, IUser mod, int weight, string reason) + public async Task Warn( + IGuild guild, + ulong userId, + IUser mod, + int weight, + string reason) { if (weight <= 0) throw new ArgumentOutOfRangeException(nameof(weight)); - + var modName = mod.ToString(); if (string.IsNullOrWhiteSpace(reason)) @@ -41,28 +53,25 @@ public class UserPunishService : INService var guildId = guild.Id; - var warn = new Warning() + var warn = new Warning { UserId = userId, GuildId = guildId, Forgiven = false, Reason = reason, Moderator = modName, - Weight = weight, + Weight = weight }; var warnings = 1; List ps; await using (var uow = _db.GetDbContext()) { - ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)) - .WarnPunishments; + ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments; - warnings += uow - .Warnings - .ForId(guildId, userId) - .Where(w => !w.Forgiven && w.UserId == userId) - .Sum(x => x.Weight); + warnings += uow.Warnings.ForId(guildId, userId) + .Where(w => !w.Forgiven && w.UserId == userId) + .Sum(x => x.Weight); uow.Warnings.Add(warn); @@ -84,13 +93,18 @@ public class UserPunishService : INService return null; } - public async Task ApplyPunishment(IGuild guild, IGuildUser user, IUser mod, PunishmentAction p, int minutes, - ulong? roleId, string reason) + public async Task ApplyPunishment( + IGuild guild, + IGuildUser user, + IUser mod, + PunishmentAction p, + int minutes, + ulong? roleId, + string reason) { - if (!await CheckPermission(guild, p)) return; - + switch (p) { case PunishmentAction.Mute: @@ -121,7 +135,7 @@ public class UserPunishService : INService await _mute.TimedBan(user.Guild, user, TimeSpan.FromMinutes(minutes), reason); break; case PunishmentAction.Softban: - await guild.AddBanAsync(user, 7, reason: $"Softban | {reason}"); + await guild.AddBanAsync(user, 7, $"Softban | {reason}"); try { await guild.RemoveBanAsync(user); @@ -151,22 +165,19 @@ public class UserPunishService : INService Log.Warning($"Can't find role {roleId.Value} on server {guild.Id} to apply punishment."); } - break; - default: break; } } /// - /// Used to prevent the bot from hitting 403's when it needs to - /// apply punishments with insufficient permissions + /// Used to prevent the bot from hitting 403's when it needs to + /// apply punishments with insufficient permissions /// /// Guild the punishment is applied in /// Punishment to apply /// Whether the bot has sufficient permissions private async Task CheckPermission(IGuild guild, PunishmentAction punish) { - var botUser = await guild.GetCurrentUserAsync(); switch (punish) { @@ -194,21 +205,19 @@ public class UserPunishService : INService public async Task CheckAllWarnExpiresAsync() { await using var uow = _db.GetDbContext(); - var cleared = await uow.Database.ExecuteSqlRawAsync($@"UPDATE Warnings + var cleared = await uow.Database.ExecuteSqlRawAsync(@"UPDATE Warnings SET Forgiven = 1, ForgivenBy = 'Expiry' WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND WarnExpireAction = 0) AND Forgiven = 0 AND DateAdded < datetime('now', (SELECT '-' || WarnExpireHours || ' hours' FROM GuildConfigs as gc WHERE gc.GuildId = Warnings.GuildId));"); - var deleted = await uow.Database.ExecuteSqlRawAsync($@"DELETE FROM Warnings + var deleted = await uow.Database.ExecuteSqlRawAsync(@"DELETE FROM Warnings WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND WarnExpireAction = 1) AND DateAdded < datetime('now', (SELECT '-' || WarnExpireHours || ' hours' FROM GuildConfigs as gc WHERE gc.GuildId = Warnings.GuildId));"); - if(cleared > 0 || deleted > 0) - { + if (cleared > 0 || deleted > 0) Log.Information($"Cleared {cleared} warnings and deleted {deleted} warnings due to expiry."); - } } public async Task CheckWarnExpiresAsync(ulong guildId) @@ -221,20 +230,16 @@ WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND var hours = $"{-config.WarnExpireHours} hours"; if (config.WarnExpireAction == WarnExpireAction.Clear) - { await uow.Database.ExecuteSqlInterpolatedAsync($@"UPDATE warnings SET Forgiven = 1, ForgivenBy = 'Expiry' WHERE GuildId={guildId} AND Forgiven = 0 AND DateAdded < datetime('now', {hours})"); - } else if (config.WarnExpireAction == WarnExpireAction.Delete) - { await uow.Database.ExecuteSqlInterpolatedAsync($@"DELETE FROM warnings WHERE GuildId={guildId} AND DateAdded < datetime('now', {hours})"); - } await uow.SaveChangesAsync(); } @@ -245,7 +250,7 @@ WHERE GuildId={guildId} var config = uow.GuildConfigsForId(guildId, set => set); return Task.FromResult(config.WarnExpireHours / 24); } - + public async Task WarnExpireAsync(ulong guildId, int days, bool delete) { await using (var uow = _db.GetDbContext()) @@ -276,23 +281,28 @@ WHERE GuildId={guildId} return uow.Warnings.ForId(gid, userId); } - public async Task WarnClearAsync(ulong guildId, ulong userId, int index, string moderator) + public async Task WarnClearAsync( + ulong guildId, + ulong userId, + int index, + string moderator) { var toReturn = true; await using var uow = _db.GetDbContext(); if (index == 0) - { await uow.Warnings.ForgiveAll(guildId, userId, moderator); - } else - { toReturn = uow.Warnings.Forgive(guildId, userId, moderator, index - 1); - } uow.SaveChanges(); return toReturn; } - public bool WarnPunish(ulong guildId, int number, PunishmentAction punish, StoopidTime time, IRole role = null) + public bool WarnPunish( + ulong guildId, + int number, + PunishmentAction punish, + StoopidTime time, + IRole role = null) { // these 3 don't make sense with time if (punish is PunishmentAction.Softban or PunishmentAction.Kick or PunishmentAction.RemoveRoles && time != null) @@ -311,7 +321,7 @@ WHERE GuildId={guildId} Count = number, Punishment = punish, Time = (int?)time?.Time.TotalMinutes ?? 0, - RoleId = punish == PunishmentAction.AddRole ? role.Id : default(ulong?), + RoleId = punish == PunishmentAction.AddRole ? role.Id : default(ulong?) }); uow.SaveChanges(); return true; @@ -339,42 +349,37 @@ WHERE GuildId={guildId} { using var uow = _db.GetDbContext(); return uow.GuildConfigsForId(guildId, gc => gc.Include(x => x.WarnPunishments)) - .WarnPunishments - .OrderBy(x => x.Count) - .ToArray(); + .WarnPunishments.OrderBy(x => x.Count) + .ToArray(); } - public (IEnumerable<(string Original, ulong? Id, string Reason)> Bans, int Missing) MassKill(SocketGuild guild, string people) + public (IEnumerable<(string Original, ulong? Id, string Reason)> Bans, int Missing) MassKill( + SocketGuild guild, + string people) { var gusers = guild.Users; //get user objects and reasons var bans = people.Split("\n") - .Select(x => - { - var split = x.Trim().Split(" "); + .Select(x => + { + var split = x.Trim().Split(" "); - var reason = string.Join(" ", split.Skip(1)); + var reason = string.Join(" ", split.Skip(1)); - if (ulong.TryParse(split[0], out var id)) - return (Original: split[0], Id: id, Reason: reason); + if (ulong.TryParse(split[0], out var id)) + return (Original: split[0], Id: id, Reason: reason); - return (Original: split[0], - Id: gusers - .FirstOrDefault(u => u.ToString().ToLowerInvariant() == x) - ?.Id, - Reason: reason); - }) - .ToArray(); + return (Original: split[0], + gusers.FirstOrDefault(u => u.ToString().ToLowerInvariant() == x)?.Id, + Reason: reason); + }) + .ToArray(); //if user is null, means that person couldn't be found - var missing = bans - .Count(x => !x.Id.HasValue); + var missing = bans.Count(x => !x.Id.HasValue); //get only data for found users - var found = bans - .Where(x => x.Id.HasValue) - .Select(x => x.Id.Value) - .ToList(); + var found = bans.Where(x => x.Id.HasValue).Select(x => x.Id.Value).ToList(); _blacklistService.BlacklistUsers(found); @@ -384,33 +389,25 @@ WHERE GuildId={guildId} public string GetBanTemplate(ulong guildId) { using var uow = _db.GetDbContext(); - var template = uow.BanTemplates - .AsQueryable() - .FirstOrDefault(x => x.GuildId == guildId); + var template = uow.BanTemplates.AsQueryable().FirstOrDefault(x => x.GuildId == guildId); return template?.Text; } public void SetBanTemplate(ulong guildId, string text) { using var uow = _db.GetDbContext(); - var template = uow.BanTemplates - .AsQueryable() - .FirstOrDefault(x => x.GuildId == guildId); + var template = uow.BanTemplates.AsQueryable().FirstOrDefault(x => x.GuildId == guildId); if (text is null) { if (template is null) return; - + uow.Remove(template); } else if (template is null) { - uow.BanTemplates.Add(new() - { - GuildId = guildId, - Text = text, - }); + uow.BanTemplates.Add(new() { GuildId = guildId, Text = text }); } else { @@ -420,67 +417,66 @@ WHERE GuildId={guildId} uow.SaveChanges(); } - public SmartText GetBanUserDmEmbed(ICommandContext context, IGuildUser target, string defaultMessage, - string banReason, TimeSpan? duration) - => GetBanUserDmEmbed( - (DiscordSocketClient) context.Client, - (SocketGuild) context.Guild, - (IGuildUser) context.User, + public SmartText GetBanUserDmEmbed( + ICommandContext context, + IGuildUser target, + string defaultMessage, + string banReason, + TimeSpan? duration) + => GetBanUserDmEmbed((DiscordSocketClient)context.Client, + (SocketGuild)context.Guild, + (IGuildUser)context.User, target, defaultMessage, banReason, duration); - public SmartText GetBanUserDmEmbed(DiscordSocketClient client, SocketGuild guild, - IGuildUser moderator, IGuildUser target, string defaultMessage, string banReason, TimeSpan? duration) + public SmartText GetBanUserDmEmbed( + DiscordSocketClient client, + SocketGuild guild, + IGuildUser moderator, + IGuildUser target, + string defaultMessage, + string banReason, + TimeSpan? duration) { var template = GetBanTemplate(guild.Id); - banReason = string.IsNullOrWhiteSpace(banReason) - ? "-" - : banReason; + banReason = string.IsNullOrWhiteSpace(banReason) ? "-" : banReason; - var replacer = new ReplacementBuilder() - .WithServer(client, guild) - .WithOverride("%ban.mod%", () => moderator.ToString()) - .WithOverride("%ban.mod.fullname%", () => moderator.ToString()) - .WithOverride("%ban.mod.name%", () => moderator.Username) - .WithOverride("%ban.mod.discrim%", () => moderator.Discriminator) - .WithOverride("%ban.user%", () => target.ToString()) - .WithOverride("%ban.user.fullname%", () => target.ToString()) - .WithOverride("%ban.user.name%", () => target.Username) - .WithOverride("%ban.user.discrim%", () => target.Discriminator) - .WithOverride("%reason%", () => banReason) - .WithOverride("%ban.reason%", () => banReason) - .WithOverride("%ban.duration%", () => duration?.ToString(@"d\.hh\:mm")?? "perma") - .Build(); + var replacer = new ReplacementBuilder().WithServer(client, guild) + .WithOverride("%ban.mod%", () => moderator.ToString()) + .WithOverride("%ban.mod.fullname%", () => moderator.ToString()) + .WithOverride("%ban.mod.name%", () => moderator.Username) + .WithOverride("%ban.mod.discrim%", () => moderator.Discriminator) + .WithOverride("%ban.user%", () => target.ToString()) + .WithOverride("%ban.user.fullname%", () => target.ToString()) + .WithOverride("%ban.user.name%", () => target.Username) + .WithOverride("%ban.user.discrim%", () => target.Discriminator) + .WithOverride("%reason%", () => banReason) + .WithOverride("%ban.reason%", () => banReason) + .WithOverride("%ban.duration%", + () => duration?.ToString(@"d\.hh\:mm") ?? "perma") + .Build(); // if template isn't set, use the old message style if (string.IsNullOrWhiteSpace(template)) - { template = JsonConvert.SerializeObject(new { - color = _bcs.Data.Color.Error.PackedValue >> 8, - description = defaultMessage + color = _bcs.Data.Color.Error.PackedValue >> 8, description = defaultMessage }); - } // if template is set to "-" do not dm the user else if (template == "-") - { return default; - } // if template is an embed, send that embed with replacements // otherwise, treat template as a regular string with replacements else if (!SmartText.CreateFrom(template).IsEmbed) - { template = JsonConvert.SerializeObject(new { - color = _bcs.Data.Color.Error.PackedValue >> 8, - description = template + color = _bcs.Data.Color.Error.PackedValue >> 8, description = template }); - } - + var output = SmartText.CreateFrom(template); return replacer.Replace(output); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Services/VcRoleService.cs b/src/NadekoBot/Modules/Administration/Services/VcRoleService.cs index b6b4bb312..769658622 100644 --- a/src/NadekoBot/Modules/Administration/Services/VcRoleService.cs +++ b/src/NadekoBot/Modules/Administration/Services/VcRoleService.cs @@ -1,17 +1,16 @@ #nullable disable using Microsoft.EntityFrameworkCore; -using NadekoBot.Services.Database.Models; using NadekoBot.Db; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration.Services; public class VcRoleService : INService { - private readonly DbService _db; - private readonly DiscordSocketClient _client; - public ConcurrentDictionary> VcRoles { get; } public ConcurrentDictionary> ToAssign { get; } + private readonly DbService _db; + private readonly DiscordSocketClient _client; public VcRoleService(DiscordSocketClient client, Bot bot, DbService db) { @@ -26,12 +25,12 @@ public class VcRoleService : INService { var guildIds = client.Guilds.Select(x => x.Id).ToList(); uow.Set() - .AsQueryable() - .Include(x => x.VcRoleInfos) - .Where(x => guildIds.Contains(x.GuildId)) - .AsEnumerable() - .Select(InitializeVcRole) - .WhenAll(); + .AsQueryable() + .Include(x => x.VcRoleInfos) + .Where(x => guildIds.Contains(x.GuildId)) + .AsEnumerable() + .Select(InitializeVcRole) + .WhenAll(); } Task.Run(async () => @@ -39,42 +38,34 @@ public class VcRoleService : INService while (true) { Task Selector(ConcurrentQueue<(bool, IGuildUser, IRole)> queue) - => Task.Run(async () => + { + return Task.Run(async () => + { + while (queue.TryDequeue(out var item)) { - while (queue.TryDequeue(out var item)) + var (add, user, role) = item; + + try { - var (add, user, role) = item; - - try + if (add) { - if (add) - { - if (!user.RoleIds.Contains(role.Id)) - { - await user.AddRoleAsync(role); - } - } - else - { - if (user.RoleIds.Contains(role.Id)) - { - await user.RemoveRoleAsync(role); - - } - } + if (!user.RoleIds.Contains(role.Id)) await user.AddRoleAsync(role); } - catch + else { + if (user.RoleIds.Contains(role.Id)) await user.RemoveRoleAsync(role); } - - await Task.Delay(250); } - } - ); + catch + { + } - await ToAssign.Values.Select(Selector) - .Append(Task.Delay(1000)) - .WhenAll(); + await Task.Delay(250); + } + }); + } + + await ToAssign.Values.Select(Selector).Append(Task.Delay(1000)).WhenAll(); } }); @@ -88,10 +79,7 @@ public class VcRoleService : INService // need to load new guildconfig with vc role included using (var uow = _db.GetDbContext()) { - var configWithVcRole = uow.GuildConfigsForId( - arg.GuildId, - set => set.Include(x => x.VcRoleInfos) - ); + var configWithVcRole = uow.GuildConfigsForId(arg.GuildId, set => set.Include(x => x.VcRoleInfos)); var _ = InitializeVcRole(configWithVcRole); } @@ -132,8 +120,7 @@ public class VcRoleService : INService await using var uow = _db.GetDbContext(); Log.Warning("Removing {MissingRoleCount} missing roles from {ServiceName}", missingRoles.Count, - nameof(VcRoleService) - ); + nameof(VcRoleService)); uow.RemoveRange(missingRoles); await uow.SaveChangesAsync(); } @@ -150,15 +137,8 @@ public class VcRoleService : INService using var uow = _db.GetDbContext(); var conf = uow.GuildConfigsForId(guildId, set => set.Include(x => x.VcRoleInfos)); var toDelete = conf.VcRoleInfos.FirstOrDefault(x => x.VoiceChannelId == vcId); // remove old one - if(toDelete != null) - { - uow.Remove(toDelete); - } - conf.VcRoleInfos.Add(new() - { - VoiceChannelId = vcId, - RoleId = role.Id, - }); // add new one + if (toDelete != null) uow.Remove(toDelete); + conf.VcRoleInfos.Add(new() { VoiceChannelId = vcId, RoleId = role.Id }); // add new one uow.SaveChanges(); } @@ -179,8 +159,7 @@ public class VcRoleService : INService return true; } - private Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState, - SocketVoiceState newState) + private Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState, SocketVoiceState newState) { if (usr is not SocketGuildUser gusr) return Task.CompletedTask; @@ -200,15 +179,9 @@ public class VcRoleService : INService { //remove old if (oldVc != null && guildVcRoles.TryGetValue(oldVc.Id, out var role)) - { Assign(false, gusr, role); - } //add new - if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role)) - { - Assign(true, gusr, role); - } - + if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role)) Assign(true, gusr, role); } } } @@ -225,4 +198,4 @@ public class VcRoleService : INService var queue = ToAssign.GetOrAdd(gusr.Guild.Id, new ConcurrentQueue<(bool, IGuildUser, IRole)>()); queue.Enqueue((v, gusr, role)); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/TimeZoneCommands.cs b/src/NadekoBot/Modules/Administration/TimeZoneCommands.cs index 4016d06e0..45741d5ae 100644 --- a/src/NadekoBot/Modules/Administration/TimeZoneCommands.cs +++ b/src/NadekoBot/Modules/Administration/TimeZoneCommands.cs @@ -8,7 +8,8 @@ public partial class Administration [Group] public class TimeZoneCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Timezones(int page = 1) { @@ -17,55 +18,50 @@ public partial class Administration if (page is < 0 or > 20) return; - var timezones = TimeZoneInfo.GetSystemTimeZones() - .OrderBy(x => x.BaseUtcOffset) - .ToArray(); + var timezones = TimeZoneInfo.GetSystemTimeZones().OrderBy(x => x.BaseUtcOffset).ToArray(); var timezonesPerPage = 20; var curTime = DateTimeOffset.UtcNow; var i = 0; - var timezoneStrings = timezones - .Select(x => (x, ++i % 2 == 0)) - .Select(data => - { - var (tzInfo, flip) = data; - var nameStr = $"{tzInfo.Id,-30}"; - var offset = curTime.ToOffset(tzInfo.GetUtcOffset(curTime)).ToString("zzz"); - if (flip) - { - return $"{offset} {Format.Code(nameStr)}"; - } - else - { - return $"{Format.Code(offset)} {nameStr}"; - } - }); - - - + var timezoneStrings = timezones.Select(x => (x, ++i % 2 == 0)) + .Select(data => + { + var (tzInfo, flip) = data; + var nameStr = $"{tzInfo.Id,-30}"; + var offset = curTime.ToOffset(tzInfo.GetUtcOffset(curTime)) + .ToString("zzz"); + if (flip) + return $"{offset} {Format.Code(nameStr)}"; + return $"{Format.Code(offset)} {nameStr}"; + }); + + await ctx.SendPaginatedConfirmAsync(page, curPage => _eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.timezones_available)) - .WithDescription(string.Join("\n", timezoneStrings - .Skip(curPage * timezonesPerPage) - .Take(timezonesPerPage))), - timezones.Length, timezonesPerPage); + .WithOkColor() + .WithTitle(GetText(strs.timezones_available)) + .WithDescription(string.Join("\n", + timezoneStrings.Skip(curPage * timezonesPerPage).Take(timezonesPerPage))), + timezones.Length, + timezonesPerPage); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Timezone() => await ReplyConfirmLocalizedAsync(strs.timezone_guild(_service.GetTimeZoneOrUtc(ctx.Guild.Id))); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task Timezone([Leftover] string id) { TimeZoneInfo tz; - try { tz = TimeZoneInfo.FindSystemTimeZoneById(id); } catch { tz = null; } + try { tz = TimeZoneInfo.FindSystemTimeZoneById(id); } + catch { tz = null; } if (tz is null) @@ -73,9 +69,10 @@ public partial class Administration await ReplyErrorLocalizedAsync(strs.timezone_not_found); return; } + _service.SetTimeZone(ctx.Guild.Id, tz); await SendConfirmAsync(tz.ToString()); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/UserPunishCommands.cs b/src/NadekoBot/Modules/Administration/UserPunishCommands.cs index 5c6a78765..6c144dbdf 100644 --- a/src/NadekoBot/Modules/Administration/UserPunishCommands.cs +++ b/src/NadekoBot/Modules/Administration/UserPunishCommands.cs @@ -2,9 +2,9 @@ using CommandLine; using Humanizer.Localisation; using NadekoBot.Common.TypeReaders.Models; -using NadekoBot.Services.Database.Models; using NadekoBot.Modules.Administration.Services; using NadekoBot.Modules.Permissions.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration; @@ -13,6 +13,11 @@ public partial class Administration [Group] public class UserPunishCommands : NadekoSubmodule { + public enum AddRole + { + AddRole + } + private readonly MuteService _mute; private readonly BlacklistService _blacklistService; @@ -24,47 +29,52 @@ public partial class Administration private async Task CheckRoleHierarchy(IGuildUser target) { - var curUser = ((SocketGuild) ctx.Guild).CurrentUser; + var curUser = ((SocketGuild)ctx.Guild).CurrentUser; var ownerId = ctx.Guild.OwnerId; - var modMaxRole = ((IGuildUser) ctx.User).GetRoles().Max(r => r.Position); + var modMaxRole = ((IGuildUser)ctx.User).GetRoles().Max(r => r.Position); var targetMaxRole = target.GetRoles().Max(r => r.Position); var botMaxRole = curUser.GetRoles().Max(r => r.Position); // bot can't punish a user who is higher in the hierarchy. Discord will return 403 // moderator can be owner, in which case role hierarchy doesn't matter // otherwise, moderator has to have a higher role - if (botMaxRole <= targetMaxRole || (ctx.User.Id != ownerId && targetMaxRole >= modMaxRole) || target.Id == ownerId) + if (botMaxRole <= targetMaxRole + || (ctx.User.Id != ownerId && targetMaxRole >= modMaxRole) + || target.Id == ownerId) { await ReplyErrorLocalizedAsync(strs.hierarchy); return false; } - + return true; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] public Task Warn(IGuildUser user, [Leftover] string reason = null) => Warn(1, user, reason); - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] public async Task Warn(int weight, IGuildUser user, [Leftover] string reason = null) { if (weight <= 0) return; - + if (!await CheckRoleHierarchy(user)) return; var dmFailed = false; try { - await user.EmbedAsync(_eb.Create().WithErrorColor() - .WithDescription(GetText(strs.warned_on(ctx.Guild.ToString()))) - .AddField(GetText(strs.moderator), ctx.User.ToString()) - .AddField(GetText(strs.reason), reason ?? "-")); + await user.EmbedAsync(_eb.Create() + .WithErrorColor() + .WithDescription(GetText(strs.warned_on(ctx.Guild.ToString()))) + .AddField(GetText(strs.moderator), ctx.User.ToString()) + .AddField(GetText(strs.reason), reason ?? "-")); } catch { @@ -79,50 +89,28 @@ public partial class Administration catch (Exception ex) { Log.Warning(ex.Message); - var errorEmbed = _eb.Create() - .WithErrorColor() - .WithDescription(GetText(strs.cant_apply_punishment)); - - if (dmFailed) - { - errorEmbed.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user)); - } - + var errorEmbed = _eb.Create().WithErrorColor().WithDescription(GetText(strs.cant_apply_punishment)); + + if (dmFailed) errorEmbed.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user)); + await ctx.Channel.EmbedAsync(errorEmbed); return; } - var embed = _eb.Create() - .WithOkColor(); + var embed = _eb.Create().WithOkColor(); if (punishment is null) - { embed.WithDescription(GetText(strs.user_warned(Format.Bold(user.ToString())))); - } else - { embed.WithDescription(GetText(strs.user_warned_and_punished(Format.Bold(user.ToString()), Format.Bold(punishment.Punishment.ToString())))); - } - if (dmFailed) - { - embed.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user)); - } + if (dmFailed) embed.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user)); await ctx.Channel.EmbedAsync(embed); } - public class WarnExpireOptions : INadekoCommandOptions - { - [Option('d', "delete", Default = false, HelpText = "Delete warnings instead of clearing them.")] - public bool Delete { get; set; } = false; - public void NormalizeOptions() - { - - } - } - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [NadekoOptions(typeof(WarnExpireOptions))] @@ -137,7 +125,8 @@ public partial class Administration await ReplyErrorLocalizedAsync(strs.warns_expire_in(expireDays)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [NadekoOptions(typeof(WarnExpireOptions))] @@ -152,51 +141,53 @@ public partial class Administration await ctx.Channel.TriggerTypingAsync(); await _service.WarnExpireAsync(ctx.Guild.Id, days, opts.Delete); - if(days == 0) + if (days == 0) { await ReplyConfirmLocalizedAsync(strs.warn_expire_reset); return; } if (opts.Delete) - { await ReplyConfirmLocalizedAsync(strs.warn_expire_set_delete(Format.Bold(days.ToString()))); - } else - { await ReplyConfirmLocalizedAsync(strs.warn_expire_set_clear(Format.Bold(days.ToString()))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [Priority(2)] public Task Warnlog(int page, [Leftover] IGuildUser user = null) { - user ??= (IGuildUser) ctx.User; + user ??= (IGuildUser)ctx.User; return Warnlog(page, user.Id); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(3)] public Task Warnlog(IGuildUser user = null) { - user ??= (IGuildUser) ctx.User; + user ??= (IGuildUser)ctx.User; - return ctx.User.Id == user.Id || ((IGuildUser)ctx.User).GuildPermissions.BanMembers ? Warnlog(user.Id) : Task.CompletedTask; + return ctx.User.Id == user.Id || ((IGuildUser)ctx.User).GuildPermissions.BanMembers + ? Warnlog(user.Id) + : Task.CompletedTask; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [Priority(0)] public Task Warnlog(int page, ulong userId) => InternalWarnlog(userId, page - 1); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [Priority(1)] @@ -207,58 +198,54 @@ public partial class Administration { if (inputPage < 0) return; - + var allWarnings = _service.UserWarnings(ctx.Guild.Id, userId); - await ctx.SendPaginatedConfirmAsync(inputPage, page => - { - var warnings = allWarnings - .Skip(page * 9) - .Take(9) - .ToArray(); - - var user = (ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString(); - var embed = _eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.warnlog_for(user))); - - if (!warnings.Any()) + await ctx.SendPaginatedConfirmAsync(inputPage, + page => { - embed.WithDescription(GetText(strs.warnings_none)); - } - else - { - var descText = GetText(strs.warn_count( - Format.Bold(warnings.Where(x => !x.Forgiven).Sum(x => x.Weight).ToString()), - Format.Bold(warnings.Sum(x => x.Weight).ToString()))); - - embed.WithDescription(descText); - - var i = page * 9; - foreach (var w in warnings) + var warnings = allWarnings.Skip(page * 9).Take(9).ToArray(); + + var user = (ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString(); + var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.warnlog_for(user))); + + if (!warnings.Any()) { - i++; - var name = GetText(strs.warned_on_by( - w.DateAdded?.ToString("dd.MM.yyy"), - w.DateAdded?.ToString("HH:mm"), - w.Moderator)); - - if (w.Forgiven) - name = $"{Format.Strikethrough(name)} {GetText(strs.warn_cleared_by(w.ForgivenBy))}"; - - - embed.AddField($"#`{i}` " + name, - Format.Code(GetText(strs.warn_weight(w.Weight))) + - '\n' + - w.Reason.TrimTo(1000)); + embed.WithDescription(GetText(strs.warnings_none)); } - } + else + { + var descText = GetText(strs.warn_count( + Format.Bold(warnings.Where(x => !x.Forgiven).Sum(x => x.Weight).ToString()), + Format.Bold(warnings.Sum(x => x.Weight).ToString()))); - return embed; - }, allWarnings.Length, 9); + embed.WithDescription(descText); + + var i = page * 9; + foreach (var w in warnings) + { + i++; + var name = GetText(strs.warned_on_by(w.DateAdded?.ToString("dd.MM.yyy"), + w.DateAdded?.ToString("HH:mm"), + w.Moderator)); + + if (w.Forgiven) + name = $"{Format.Strikethrough(name)} {GetText(strs.warn_cleared_by(w.ForgivenBy))}"; + + + embed.AddField($"#`{i}` " + name, + Format.Code(GetText(strs.warn_weight(w.Weight))) + '\n' + w.Reason.TrimTo(1000)); + } + } + + return embed; + }, + allWarnings.Length, + 9); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] public async Task WarnlogAll(int page = 1) @@ -267,33 +254,40 @@ public partial class Administration return; var warnings = _service.WarnlogAll(ctx.Guild.Id); - await ctx.SendPaginatedConfirmAsync(page, curPage => - { - var ws = warnings.Skip(curPage * 15) - .Take(15) - .ToArray() - .Select(x => - { - var all = x.Count(); - var forgiven = x.Count(y => y.Forgiven); - var total = all - forgiven; - var usr = ((SocketGuild)ctx.Guild).GetUser(x.Key); - return (usr?.ToString() ?? x.Key.ToString()) + $" | {total} ({all} - {forgiven})"; - }); + await ctx.SendPaginatedConfirmAsync(page, + curPage => + { + var ws = warnings.Skip(curPage * 15) + .Take(15) + .ToArray() + .Select(x => + { + var all = x.Count(); + var forgiven = x.Count(y => y.Forgiven); + var total = all - forgiven; + var usr = ((SocketGuild)ctx.Guild).GetUser(x.Key); + return (usr?.ToString() ?? x.Key.ToString()) + + $" | {total} ({all} - {forgiven})"; + }); - return _eb.Create().WithOkColor() - .WithTitle(GetText(strs.warnings_list)) - .WithDescription(string.Join("\n", ws)); - }, warnings.Length, 15); + return _eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.warnings_list)) + .WithDescription(string.Join("\n", ws)); + }, + warnings.Length, + 15); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] public Task Warnclear(IGuildUser user, int index = 0) => Warnclear(user.Id, index); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] public async Task Warnclear(ulong userId, int index = 0) @@ -309,57 +303,48 @@ public partial class Administration else { if (success) - { await ReplyConfirmLocalizedAsync(strs.warning_cleared(Format.Bold(index.ToString()), userStr)); - } else - { await ReplyErrorLocalizedAsync(strs.warning_clear_fail); - } } } - public enum AddRole - { - AddRole - } - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [Priority(1)] - public async Task WarnPunish(int number, AddRole _, IRole role, StoopidTime time = null) + public async Task WarnPunish( + int number, + AddRole _, + IRole role, + StoopidTime time = null) { var punish = PunishmentAction.AddRole; - if (ctx.Guild.OwnerId != ctx.User.Id && - role.Position >= ((IGuildUser)ctx.User).GetRoles().Max(x => x.Position)) + if (ctx.Guild.OwnerId != ctx.User.Id + && role.Position >= ((IGuildUser)ctx.User).GetRoles().Max(x => x.Position)) { await ReplyErrorLocalizedAsync(strs.role_too_high); return; } - + var success = _service.WarnPunish(ctx.Guild.Id, number, punish, time, role); if (!success) return; if (time is null) - { - await ReplyConfirmLocalizedAsync(strs.warn_punish_set( - Format.Bold(punish.ToString()), + await ReplyConfirmLocalizedAsync(strs.warn_punish_set(Format.Bold(punish.ToString()), Format.Bold(number.ToString()))); - } else - { - await ReplyConfirmLocalizedAsync(strs.warn_punish_set_timed( - Format.Bold(punish.ToString()), + await ReplyConfirmLocalizedAsync(strs.warn_punish_set_timed(Format.Bold(punish.ToString()), Format.Bold(number.ToString()), Format.Bold(time.Input))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] public async Task WarnPunish(int number, PunishmentAction punish, StoopidTime time = null) @@ -374,35 +359,27 @@ public partial class Administration return; if (time is null) - { - await ReplyConfirmLocalizedAsync(strs.warn_punish_set( - Format.Bold(punish.ToString()), + await ReplyConfirmLocalizedAsync(strs.warn_punish_set(Format.Bold(punish.ToString()), Format.Bold(number.ToString()))); - } else - { - await ReplyConfirmLocalizedAsync(strs.warn_punish_set_timed( - Format.Bold(punish.ToString()), + await ReplyConfirmLocalizedAsync(strs.warn_punish_set_timed(Format.Bold(punish.ToString()), Format.Bold(number.ToString()), Format.Bold(time.Input))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] public async Task WarnPunish(int number) { - if (!_service.WarnPunishRemove(ctx.Guild.Id, number)) - { - return; - } + if (!_service.WarnPunishRemove(ctx.Guild.Id, number)) return; - await ReplyConfirmLocalizedAsync(strs.warn_punish_rem( - Format.Bold(number.ToString()))); + await ReplyConfirmLocalizedAsync(strs.warn_punish_rem(Format.Bold(number.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task WarnPunishList() { @@ -410,20 +387,16 @@ public partial class Administration string list; if (ps.Any()) - { - - list = string.Join("\n", ps.Select(x => $"{x.Count} -> {x.Punishment} {(x.Punishment == PunishmentAction.AddRole ? $"<@&{x.RoleId}>" : "")} {(x.Time <= 0 ? "" : x.Time.ToString() + "m")} ")); - } + list = string.Join("\n", + ps.Select(x + => $"{x.Count} -> {x.Punishment} {(x.Punishment == PunishmentAction.AddRole ? $"<@&{x.RoleId}>" : "")} {(x.Time <= 0 ? "" : x.Time + "m")} ")); else - { list = GetText(strs.warnpl_none); - } - await SendConfirmAsync( - GetText(strs.warn_punish_list), - list); + await SendConfirmAsync(GetText(strs.warn_punish_list), list); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [BotPerm(GuildPerm.BanMembers)] @@ -441,42 +414,34 @@ public partial class Administration var dmFailed = false; if (guildUser != null) - { try { var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg)); var embed = _service.GetBanUserDmEmbed(Context, guildUser, defaultMessage, msg, time.Time); - if (embed is not null) - { - await guildUser.SendAsync(embed); - } + if (embed is not null) await guildUser.SendAsync(embed); } catch { dmFailed = true; } - } - await _mute.TimedBan(ctx.Guild, user, time.Time, (ctx.User.ToString() + " | " + msg).TrimTo(512)); - var toSend = _eb.Create().WithOkColor() - .WithTitle("⛔️ " + GetText(strs.banned_user)) - .AddField(GetText(strs.username), user.ToString(), true) - .AddField("ID", user.Id.ToString(), true) - .AddField(GetText(strs.duration), - time.Time.Humanize(3, - minUnit: TimeUnit.Minute, - culture: Culture), - true); + await _mute.TimedBan(ctx.Guild, user, time.Time, (ctx.User + " | " + msg).TrimTo(512)); + var toSend = _eb.Create() + .WithOkColor() + .WithTitle("⛔️ " + GetText(strs.banned_user)) + .AddField(GetText(strs.username), user.ToString(), true) + .AddField("ID", user.Id.ToString(), true) + .AddField(GetText(strs.duration), + time.Time.Humanize(3, minUnit: TimeUnit.Minute, culture: Culture), + true); - if (dmFailed) - { - toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user)); - } + if (dmFailed) toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user)); await ctx.Channel.EmbedAsync(toSend); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [BotPerm(GuildPerm.BanMembers)] @@ -486,11 +451,12 @@ public partial class Administration var user = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId); if (user is null) { - await ctx.Guild.AddBanAsync(userId, 7, (ctx.User.ToString() + " | " + msg).TrimTo(512)); - - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithTitle("⛔️ " + GetText(strs.banned_user)) - .AddField("ID", userId.ToString(), true)); + await ctx.Guild.AddBanAsync(userId, 7, (ctx.User + " | " + msg).TrimTo(512)); + + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle("⛔️ " + GetText(strs.banned_user)) + .AddField("ID", userId.ToString(), true)); } else { @@ -498,7 +464,8 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [BotPerm(GuildPerm.BanMembers)] @@ -514,32 +481,28 @@ public partial class Administration { var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg)); var embed = _service.GetBanUserDmEmbed(Context, user, defaultMessage, msg, null); - if (embed is not null) - { - await ctx.User.SendAsync(embed); - } + if (embed is not null) await ctx.User.SendAsync(embed); } catch { dmFailed = true; } - await ctx.Guild.AddBanAsync(user, 7, (ctx.User.ToString() + " | " + msg).TrimTo(512)); + await ctx.Guild.AddBanAsync(user, 7, (ctx.User + " | " + msg).TrimTo(512)); - var toSend = _eb.Create().WithOkColor() - .WithTitle("⛔️ " + GetText(strs.banned_user)) - .AddField(GetText(strs.username), user.ToString(), true) - .AddField("ID", user.Id.ToString(), true); + var toSend = _eb.Create() + .WithOkColor() + .WithTitle("⛔️ " + GetText(strs.banned_user)) + .AddField(GetText(strs.username), user.ToString(), true) + .AddField("ID", user.Id.ToString(), true); + + if (dmFailed) toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user)); - if (dmFailed) - { - toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user)); - } - await ctx.Channel.EmbedAsync(toSend); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [BotPerm(GuildPerm.BanMembers)] @@ -557,12 +520,13 @@ public partial class Administration await SendConfirmAsync(template); return; } - + _service.SetBanTemplate(ctx.Guild.Id, message); await ctx.OkAsync(); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [BotPerm(GuildPerm.BanMembers)] @@ -572,30 +536,28 @@ public partial class Administration await ctx.OkAsync(); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [BotPerm(GuildPerm.BanMembers)] [Priority(0)] public Task BanMessageTest([Leftover] string reason = null) => InternalBanMessageTest(reason, null); - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [BotPerm(GuildPerm.BanMembers)] [Priority(1)] public Task BanMessageTest(StoopidTime duration, [Leftover] string reason = null) => InternalBanMessageTest(reason, duration.Time); - + private async Task InternalBanMessageTest(string reason, TimeSpan? duration) { var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), reason)); - var embed = _service.GetBanUserDmEmbed(Context, - (IGuildUser)ctx.User, - defaultMessage, - reason, - duration); + var embed = _service.GetBanUserDmEmbed(Context, (IGuildUser)ctx.User, defaultMessage, reason, duration); if (embed is null) { @@ -617,7 +579,8 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [BotPerm(GuildPerm.BanMembers)] @@ -636,7 +599,8 @@ public partial class Administration await UnbanInternal(bun.User); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [BotPerm(GuildPerm.BanMembers)] @@ -662,14 +626,16 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.unbanned_user(Format.Bold(user.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.KickMembers | GuildPerm.ManageMessages)] [BotPerm(GuildPerm.BanMembers)] public Task Softban(IGuildUser user, [Leftover] string msg = null) => SoftbanInternal(user, msg); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.KickMembers | GuildPerm.ManageMessages)] [BotPerm(GuildPerm.BanMembers)] @@ -681,7 +647,7 @@ public partial class Administration await SoftbanInternal(user, msg); } - + private async Task SoftbanInternal(IGuildUser user, [Leftover] string msg = null) { if (!await CheckRoleHierarchy(user)) @@ -698,24 +664,23 @@ public partial class Administration dmFailed = true; } - await ctx.Guild.AddBanAsync(user, 7, ("Softban | " + ctx.User.ToString() + " | " + msg).TrimTo(512)); + await ctx.Guild.AddBanAsync(user, 7, ("Softban | " + ctx.User + " | " + msg).TrimTo(512)); try { await ctx.Guild.RemoveBanAsync(user); } catch { await ctx.Guild.RemoveBanAsync(user); } - var toSend = _eb.Create().WithOkColor() - .WithTitle("☣ " + GetText(strs.sb_user)) - .AddField(GetText(strs.username), user.ToString(), true) - .AddField("ID", user.Id.ToString(), true); - - if (dmFailed) - { - toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user)); - } - + var toSend = _eb.Create() + .WithOkColor() + .WithTitle("☣ " + GetText(strs.sb_user)) + .AddField(GetText(strs.username), user.ToString(), true) + .AddField("ID", user.Id.ToString(), true); + + if (dmFailed) toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user)); + await ctx.Channel.EmbedAsync(toSend); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.KickMembers)] [BotPerm(GuildPerm.KickMembers)] @@ -723,7 +688,8 @@ public partial class Administration public Task Kick(IGuildUser user, [Leftover] string msg = null) => KickInternal(user, msg); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.KickMembers)] [BotPerm(GuildPerm.KickMembers)] @@ -733,7 +699,7 @@ public partial class Administration var user = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId); if (user is null) return; - + await KickInternal(user, msg); } @@ -749,26 +715,25 @@ public partial class Administration await user.SendErrorAsync(_eb, GetText(strs.kickdm(Format.Bold(ctx.Guild.Name), msg))); } catch - { + { dmFailed = true; } - - await user.KickAsync((ctx.User.ToString() + " | " + msg).TrimTo(512)); - - var toSend = _eb.Create().WithOkColor() - .WithTitle(GetText(strs.kicked_user)) - .AddField(GetText(strs.username), user.ToString(), true) - .AddField("ID", user.Id.ToString(), true); - if (dmFailed) - { - toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user)); - } - + await user.KickAsync((ctx.User + " | " + msg).TrimTo(512)); + + var toSend = _eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.kicked_user)) + .AddField(GetText(strs.username), user.ToString(), true) + .AddField("ID", user.Id.ToString(), true); + + if (dmFailed) toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user)); + await ctx.Channel.EmbedAsync(toSend); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [BotPerm(GuildPerm.BanMembers)] @@ -783,11 +748,11 @@ public partial class Administration await ctx.Channel.TriggerTypingAsync(); foreach (var userStr in userStrings) - { if (ulong.TryParse(userStr, out var userId)) { - IUser user = await ctx.Guild.GetUserAsync(userId) ?? - await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId); + IUser user = await ctx.Guild.GetUserAsync(userId) + ?? await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, + userId); if (user is null) { @@ -800,14 +765,10 @@ public partial class Administration missing.Add(userStr); continue; } - } //Hierachy checks only if the user is in the guild - if (user is IGuildUser gu && !await CheckRoleHierarchy(gu)) - { - return; - } + if (user is IGuildUser gu && !await CheckRoleHierarchy(gu)) return; banning.Add(user); } @@ -815,41 +776,38 @@ public partial class Administration { missing.Add(userStr); } - } var missStr = string.Join("\n", missing); if (string.IsNullOrWhiteSpace(missStr)) missStr = "-"; - + var toSend = _eb.Create(ctx) - .WithDescription(GetText(strs.mass_ban_in_progress(banning.Count))) - .AddField(GetText(strs.invalid(missing.Count)), missStr) - .WithPendingColor(); + .WithDescription(GetText(strs.mass_ban_in_progress(banning.Count))) + .AddField(GetText(strs.invalid(missing.Count)), missStr) + .WithPendingColor(); var banningMessage = await ctx.Channel.EmbedAsync(toSend); foreach (var toBan in banning) - { try { await ctx.Guild.AddBanAsync(toBan.Id, 7, $"{ctx.User} | Massban"); } catch (Exception ex) { - Log.Warning(ex, "Error banning {User} user in {GuildId} server", - toBan.Id, - ctx.Guild.Id); + Log.Warning(ex, "Error banning {User} user in {GuildId} server", toBan.Id, ctx.Guild.Id); } - } - + await banningMessage.ModifyAsync(x => x.Embed = _eb.Create() - .WithDescription(GetText(strs.mass_ban_completed(banning.Count()))) - .AddField(GetText(strs.invalid(missing.Count)), missStr) - .WithOkColor() - .Build()); + .WithDescription( + GetText(strs.mass_ban_completed(banning.Count()))) + .AddField(GetText(strs.invalid(missing.Count)), missStr) + .WithOkColor() + .Build()); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] [BotPerm(GuildPerm.BanMembers)] @@ -867,26 +825,37 @@ public partial class Administration //send a message but don't wait for it var banningMessageTask = ctx.Channel.EmbedAsync(_eb.Create() - .WithDescription(GetText(strs.mass_kill_in_progress(bans.Count()))) - .AddField(GetText(strs.invalid(missing)), missStr) - .WithPendingColor()); + .WithDescription( + GetText(strs.mass_kill_in_progress(bans.Count()))) + .AddField(GetText(strs.invalid(missing)), missStr) + .WithPendingColor()); //do the banning - await Task.WhenAll(bans - .Where(x => x.Id.HasValue) - .Select(x => ctx.Guild.AddBanAsync(x.Id.Value, 7, x.Reason, new() - { - RetryMode = RetryMode.AlwaysRetry, - }))); + await Task.WhenAll(bans.Where(x => x.Id.HasValue) + .Select(x => ctx.Guild.AddBanAsync(x.Id.Value, + 7, + x.Reason, + new() { RetryMode = RetryMode.AlwaysRetry }))); //wait for the message and edit it var banningMessage = await banningMessageTask; await banningMessage.ModifyAsync(x => x.Embed = _eb.Create() - .WithDescription(GetText(strs.mass_kill_completed(bans.Count()))) - .AddField(GetText(strs.invalid(missing)), missStr) - .WithOkColor() - .Build()); + .WithDescription( + GetText(strs.mass_kill_completed(bans.Count()))) + .AddField(GetText(strs.invalid(missing)), missStr) + .WithOkColor() + .Build()); + } + + public class WarnExpireOptions : INadekoCommandOptions + { + [Option('d', "delete", Default = false, HelpText = "Delete warnings instead of clearing them.")] + public bool Delete { get; set; } = false; + + public void NormalizeOptions() + { + } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/VcRoleCommands.cs b/src/NadekoBot/Modules/Administration/VcRoleCommands.cs index a08a74858..b930e7929 100644 --- a/src/NadekoBot/Modules/Administration/VcRoleCommands.cs +++ b/src/NadekoBot/Modules/Administration/VcRoleCommands.cs @@ -8,23 +8,21 @@ public partial class Administration [Group] public class VcRoleCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] [RequireContext(ContextType.Guild)] public async Task VcRoleRm(ulong vcId) { if (_service.RemoveVcRole(ctx.Guild.Id, vcId)) - { await ReplyConfirmLocalizedAsync(strs.vcrole_removed(Format.Bold(vcId.ToString()))); - } else - { await ReplyErrorLocalizedAsync(strs.vcrole_not_found); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [UserPerm(GuildPerm.ManageRoles)] [BotPerm(GuildPerm.ManageRoles)] [RequireContext(ContextType.Guild)] @@ -43,9 +41,7 @@ public partial class Administration if (role is null) { if (_service.RemoveVcRole(ctx.Guild.Id, vc.Id)) - { await ReplyConfirmLocalizedAsync(strs.vcrole_removed(Format.Bold(vc.Name))); - } } else { @@ -54,7 +50,8 @@ public partial class Administration } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task VcRoleList() { @@ -63,22 +60,21 @@ public partial class Administration if (_service.VcRoles.TryGetValue(ctx.Guild.Id, out var roles)) { if (!roles.Any()) - { text = GetText(strs.no_vcroles); - } else - { - text = string.Join("\n", roles.Select(x => - $"{Format.Bold(guild.GetVoiceChannel(x.Key)?.Name ?? x.Key.ToString())} => {x.Value}")); - } + text = string.Join("\n", + roles.Select(x + => $"{Format.Bold(guild.GetVoiceChannel(x.Key)?.Name ?? x.Key.ToString())} => {x.Value}")); } else { text = GetText(strs.no_vcroles); } - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithTitle(GetText(strs.vc_role_list)) - .WithDescription(text)); + + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.vc_role_list)) + .WithDescription(text)); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/CustomReactions/Common/ExportedExpr.cs b/src/NadekoBot/Modules/CustomReactions/Common/ExportedExpr.cs index c70b59997..4f6a0f14e 100644 --- a/src/NadekoBot/Modules/CustomReactions/Common/ExportedExpr.cs +++ b/src/NadekoBot/Modules/CustomReactions/Common/ExportedExpr.cs @@ -22,8 +22,6 @@ public class ExportedExpr At = cr.AllowTarget, Ca = cr.ContainsAnywhere, Dm = cr.DmResponse, - React = string.IsNullOrWhiteSpace(cr.Reactions) - ? null - : cr.GetReactions(), + React = string.IsNullOrWhiteSpace(cr.Reactions) ? null : cr.GetReactions() }; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs index 38c2b2a9d..4d7bc3e15 100644 --- a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs +++ b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs @@ -5,6 +5,11 @@ namespace NadekoBot.Modules.CustomReactions; public class CustomReactions : NadekoModule { + public enum All + { + All + } + private readonly IBotCredentials _creds; private readonly IHttpClientFactory _clientFactory; @@ -14,10 +19,12 @@ public class CustomReactions : NadekoModule _clientFactory = clientFactory; } - private bool AdminInGuildOrOwnerInDm() => (ctx.Guild is null && _creds.IsOwner(ctx.User)) - || (ctx.Guild != null && ((IGuildUser)ctx.User).GuildPermissions.Administrator); + private bool AdminInGuildOrOwnerInDm() + => (ctx.Guild is null && _creds.IsOwner(ctx.User)) + || (ctx.Guild != null && ((IGuildUser)ctx.User).GuildPermissions.Administrator); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task AddCustReact(string key, [Leftover] string message) { var channel = ctx.Channel as ITextChannel; @@ -32,22 +39,25 @@ public class CustomReactions : NadekoModule var cr = await _service.AddAsync(ctx.Guild?.Id, key, message); - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithTitle(GetText(strs.new_cust_react)) - .WithDescription($"#{cr.Id}") - .AddField(GetText(strs.trigger), key) - .AddField(GetText(strs.response), message.Length > 1024 ? GetText(strs.redacted_too_long) : message) - ); + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.new_cust_react)) + .WithDescription($"#{cr.Id}") + .AddField(GetText(strs.trigger), key) + .AddField(GetText(strs.response), + message.Length > 1024 ? GetText(strs.redacted_too_long) : message)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task EditCustReact(kwum id, [Leftover] string message) { var channel = ctx.Channel as ITextChannel; if (string.IsNullOrWhiteSpace(message) || id < 0) return; - if ((channel is null && !_creds.IsOwner(ctx.User)) || (channel != null && !((IGuildUser)ctx.User).GuildPermissions.Administrator)) + if ((channel is null && !_creds.IsOwner(ctx.User)) + || (channel != null && !((IGuildUser)ctx.User).GuildPermissions.Administrator)) { await ReplyErrorLocalizedAsync(strs.insuff_perms); return; @@ -55,21 +65,19 @@ public class CustomReactions : NadekoModule var cr = await _service.EditAsync(ctx.Guild?.Id, id, message); if (cr != null) - { - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithTitle(GetText(strs.edited_cust_react)) - .WithDescription($"#{id}") - .AddField(GetText(strs.trigger), cr.Trigger) - .AddField(GetText(strs.response), message.Length > 1024 ? GetText(strs.redacted_too_long) : message) - ); - } + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.edited_cust_react)) + .WithDescription($"#{id}") + .AddField(GetText(strs.trigger), cr.Trigger) + .AddField(GetText(strs.response), + message.Length > 1024 ? GetText(strs.redacted_too_long) : message)); else - { await ReplyErrorLocalizedAsync(strs.edit_fail); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(1)] public async Task ListCustReact(int page = 1) { @@ -84,33 +92,29 @@ public class CustomReactions : NadekoModule return; } - await ctx.SendPaginatedConfirmAsync(page, pageFunc: curPage => - { - var desc = customReactions.OrderBy(cr => cr.Trigger) - .Skip(curPage * 20) - .Take(20) - .Select(cr => $"{(cr.ContainsAnywhere ? "🗯" : "◾")}" + - $"{(cr.DmResponse ? "✉" : "◾")}" + - $"{(cr.AutoDeleteTrigger ? "❌" : "◾")}" + - $"`{(kwum) cr.Id}` {cr.Trigger}" - + (string.IsNullOrWhiteSpace(cr.Reactions) - ? string.Empty - : " // " + string.Join(" ", cr.GetReactions()))) - .Join('\n'); + await ctx.SendPaginatedConfirmAsync(page, + curPage => + { + var desc = customReactions.OrderBy(cr => cr.Trigger) + .Skip(curPage * 20) + .Take(20) + .Select(cr => $"{(cr.ContainsAnywhere ? "🗯" : "◾")}" + + $"{(cr.DmResponse ? "✉" : "◾")}" + + $"{(cr.AutoDeleteTrigger ? "❌" : "◾")}" + + $"`{(kwum)cr.Id}` {cr.Trigger}" + + (string.IsNullOrWhiteSpace(cr.Reactions) + ? string.Empty + : " // " + string.Join(" ", cr.GetReactions()))) + .Join('\n'); - return _eb.Create().WithOkColor() - .WithTitle(GetText(strs.custom_reactions)) - .WithDescription(desc); - - }, customReactions.Length, 20); + return _eb.Create().WithOkColor().WithTitle(GetText(strs.custom_reactions)).WithDescription(desc); + }, + customReactions.Length, + 20); } - public enum All - { - All - } - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ShowCustReact(kwum id) { var found = _service.GetCustomReaction(ctx.Guild?.Id, id); @@ -120,17 +124,17 @@ public class CustomReactions : NadekoModule await ReplyErrorLocalizedAsync(strs.no_found_id); return; } - else - { - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithDescription($"#{id}") - .AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024)) - .AddField(GetText(strs.response), found.Response.TrimTo(1000).Replace("](", "]\\(")) - ); - } + + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithDescription($"#{id}") + .AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024)) + .AddField(GetText(strs.response), + found.Response.TrimTo(1000).Replace("](", "]\\("))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task DelCustReact(kwum id) { if (!AdminInGuildOrOwnerInDm()) @@ -142,20 +146,18 @@ public class CustomReactions : NadekoModule var cr = await _service.DeleteAsync(ctx.Guild?.Id, id); if (cr != null) - { - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithTitle(GetText(strs.deleted)) - .WithDescription($"#{id}") - .AddField(GetText(strs.trigger), cr.Trigger.TrimTo(1024)) - .AddField(GetText(strs.response), cr.Response.TrimTo(1024))); - } + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.deleted)) + .WithDescription($"#{id}") + .AddField(GetText(strs.trigger), cr.Trigger.TrimTo(1024)) + .AddField(GetText(strs.response), cr.Response.TrimTo(1024))); else - { await ReplyErrorLocalizedAsync(strs.no_found_id); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task CrReact(kwum id, params string[] emojiStrs) { if (!AdminInGuildOrOwnerInDm()) @@ -181,7 +183,6 @@ public class CustomReactions : NadekoModule var succ = new List(); foreach (var emojiStr in emojiStrs) { - var emote = emojiStr.ToIEmote(); // i should try adding these emojis right away to the message, to make sure the bot can react with these emojis. If it fails, skip that emoji @@ -197,7 +198,7 @@ public class CustomReactions : NadekoModule catch { } } - if(succ.Count == 0) + if (succ.Count == 0) { await ReplyErrorLocalizedAsync(strs.invalid_emojis); return; @@ -206,27 +207,32 @@ public class CustomReactions : NadekoModule await _service.SetCrReactions(ctx.Guild?.Id, id, succ); - await ReplyConfirmLocalizedAsync(strs.crr_set(Format.Bold(id.ToString()), string.Join(", ", succ.Select(x => x.ToString())))); - + await ReplyConfirmLocalizedAsync(strs.crr_set(Format.Bold(id.ToString()), + string.Join(", ", succ.Select(x => x.ToString())))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public Task CrCa(kwum id) => InternalCrEdit(id, CustomReactionsService.CrField.ContainsAnywhere); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public Task CrDm(kwum id) => InternalCrEdit(id, CustomReactionsService.CrField.DmResponse); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public Task CrAd(kwum id) => InternalCrEdit(id, CustomReactionsService.CrField.AutoDelete); - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] public Task CrAt(kwum id) => InternalCrEdit(id, CustomReactionsService.CrField.AllowTarget); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task CrsReload() { @@ -243,6 +249,7 @@ public class CustomReactions : NadekoModule await ReplyErrorLocalizedAsync(strs.insuff_perms); return; } + var (success, newVal) = await _service.ToggleCrOptionAsync(id, option); if (!success) { @@ -251,30 +258,30 @@ public class CustomReactions : NadekoModule } if (newVal) - { - await ReplyConfirmLocalizedAsync(strs.option_enabled(Format.Code(option.ToString()), Format.Code(id.ToString()))); - } + await ReplyConfirmLocalizedAsync(strs.option_enabled(Format.Code(option.ToString()), + Format.Code(id.ToString()))); else - { - await ReplyConfirmLocalizedAsync(strs.option_disabled(Format.Code(option.ToString()), Format.Code(id.ToString()))); - } + await ReplyConfirmLocalizedAsync(strs.option_disabled(Format.Code(option.ToString()), + Format.Code(id.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task CrClear() { if (await PromptUserConfirmAsync(_eb.Create() - .WithTitle("Custom reaction clear") - .WithDescription("This will delete all custom reactions on this server."))) + .WithTitle("Custom reaction clear") + .WithDescription("This will delete all custom reactions on this server."))) { var count = _service.DeleteAllCustomReactions(ctx.Guild.Id); await ReplyConfirmLocalizedAsync(strs.cleared(count)); } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task CrsExport() { if (!AdminInGuildOrOwnerInDm()) @@ -282,19 +289,20 @@ public class CustomReactions : NadekoModule await ReplyErrorLocalizedAsync(strs.insuff_perms); return; } - + _ = ctx.Channel.TriggerTypingAsync(); var serialized = _service.ExportCrs(ctx.Guild?.Id); await using var stream = await serialized.ToStream(); - await ctx.Channel.SendFileAsync(stream, "crs-export.yml", text: null); + await ctx.Channel.SendFileAsync(stream, "crs-export.yml"); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] #if GLOBAL_NADEKO [OwnerOnly] #endif - public async Task CrsImport([Leftover]string input = null) + public async Task CrsImport([Leftover] string input = null) { if (!AdminInGuildOrOwnerInDm()) { @@ -331,7 +339,7 @@ public class CustomReactions : NadekoModule await ReplyErrorLocalizedAsync(strs.expr_import_invalid_data); return; } - + await ctx.OkAsync(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/CustomReactions/Extensions/CustomReactionExtensions.cs b/src/NadekoBot/Modules/CustomReactions/Extensions/CustomReactionExtensions.cs index e33188d9c..b267455bf 100644 --- a/src/NadekoBot/Modules/CustomReactions/Extensions/CustomReactionExtensions.cs +++ b/src/NadekoBot/Modules/CustomReactions/Extensions/CustomReactionExtensions.cs @@ -9,12 +9,13 @@ public static class CustomReactionExtensions private static string ResolveTriggerString(this string str, DiscordSocketClient client) => str.Replace("%bot.mention%", client.CurrentUser.Mention, StringComparison.Ordinal); - public static async Task Send(this CustomReaction cr, IUserMessage ctx, - DiscordSocketClient client, bool sanitize) + public static async Task Send( + this CustomReaction cr, + IUserMessage ctx, + DiscordSocketClient client, + bool sanitize) { - var channel = cr.DmResponse - ? await ctx.Author.CreateDMChannelAsync() - : ctx.Channel; + var channel = cr.DmResponse ? await ctx.Author.CreateDMChannelAsync() : ctx.Channel; var trigger = cr.Trigger.ResolveTriggerString(client); var substringIndex = trigger.Length; @@ -32,11 +33,12 @@ public static class CustomReactionExtensions var canMentionEveryone = (ctx.Author as IGuildUser)?.GuildPermissions.MentionEveryone ?? true; var rep = new ReplacementBuilder() - .WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild as SocketGuild, client) - .WithOverride("%target%", () => canMentionEveryone - ? ctx.Content[substringIndex..].Trim() - : ctx.Content[substringIndex..].Trim().SanitizeMentions(true)) - .Build(); + .WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild as SocketGuild, client) + .WithOverride("%target%", + () => canMentionEveryone + ? ctx.Content[substringIndex..].Trim() + : ctx.Content[substringIndex..].Trim().SanitizeMentions(true)) + .Build(); var text = SmartText.CreateFrom(cr.Response); text = rep.Replace(text); @@ -62,7 +64,9 @@ public static class CustomReactionExtensions return WordPosition.End; } else if (str.isValidWordDivider(wordIndex - 1) && str.isValidWordDivider(wordIndex + word.Length)) + { return WordPosition.Middle; + } return WordPosition.None; } @@ -82,5 +86,5 @@ public enum WordPosition None, Start, Middle, - End, -} + End +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs b/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs index 074ff3f2f..f4fa2ac87 100644 --- a/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs +++ b/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs @@ -1,14 +1,15 @@ #nullable disable +using Microsoft.EntityFrameworkCore; using NadekoBot.Common.ModuleBehaviors; -using NadekoBot.Services.Database.Models; +using NadekoBot.Common.Yml; +using NadekoBot.Db; using NadekoBot.Modules.CustomReactions.Extensions; using NadekoBot.Modules.Permissions.Common; using NadekoBot.Modules.Permissions.Services; +using NadekoBot.Services.Database.Models; using System.Runtime.CompilerServices; -using Microsoft.EntityFrameworkCore; -using NadekoBot.Common.Yml; -using NadekoBot.Db; using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; namespace NadekoBot.Modules.CustomReactions.Services; @@ -20,16 +21,45 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor DmResponse, AllowTarget, ContainsAnywhere, - Message, + Message } + private const string MentionPh = "%bot.mention%"; + + private const string _prependExport = + @"# Keys are triggers, Each key has a LIST of custom reactions in the following format: +# - res: Response string +# id: Alphanumeric id used for commands related to the custom reaction. (Note, when using .crsimport, a new id will be generated.) +# react: +# - +# at: Whether custom reaction allows targets (see .h .crat) +# ca: Whether custom reaction expects trigger anywhere (see .h .crca) +# dm: Whether custom reaction DMs the response (see .h .crdm) +# ad: Whether custom reaction automatically deletes triggering message (see .h .crad) + +"; + + private static readonly ISerializer _exportSerializer = new SerializerBuilder() + .WithEventEmitter(args + => new MultilineScalarFlowStyleEmitter(args)) + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithIndentedSequences() + .ConfigureDefaultValuesHandling(DefaultValuesHandling + .OmitDefaults) + .DisableAliases() + .Build(); + + public int Priority + => 0; + private readonly object _gcrWriteLock = new(); private readonly TypedKey _gcrAddedKey = new("gcr.added"); private readonly TypedKey _gcrDeletedkey = new("gcr.deleted"); private readonly TypedKey _gcrEditedKey = new("gcr.edited"); private readonly TypedKey _crsReloadedKey = new("crs.reloaded"); - private const string MentionPh = "%bot.mention%"; // it is perfectly fine to have global customreactions as an array // 1. custom reactions are almost never added (compared to how many times they are being looped through) @@ -38,8 +68,6 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor private CustomReaction[] _globalReactions; private ConcurrentDictionary _newGuildReactions; - public int Priority => 0; - private readonly DbService _db; private readonly DiscordSocketClient _client; private readonly PermissionService _perms; @@ -52,9 +80,19 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor private readonly IEmbedBuilderService _eb; private readonly Random _rng; - public CustomReactionsService(PermissionService perms, DbService db, IBotStrings strings, Bot bot, - DiscordSocketClient client, CommandHandler cmd, GlobalPermissionService gperm, CmdCdService cmdCds, - IPubSub pubSub, IEmbedBuilderService eb) + private bool ready; + + public CustomReactionsService( + PermissionService perms, + DbService db, + IBotStrings strings, + Bot bot, + DiscordSocketClient client, + CommandHandler cmd, + GlobalPermissionService gperm, + CmdCdService cmdCds, + IPubSub pubSub, + IEmbedBuilderService eb) { _db = db; _client = client; @@ -80,34 +118,31 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor private async Task ReloadInternal(IReadOnlyList allGuildIds) { await using var uow = _db.GetDbContext(); - var guildItems = await uow.CustomReactions - .AsNoTracking() - .Where(x => allGuildIds.Contains(x.GuildId.Value)) - .ToListAsync(); + var guildItems = await uow.CustomReactions.AsNoTracking() + .Where(x => allGuildIds.Contains(x.GuildId.Value)) + .ToListAsync(); - _newGuildReactions = guildItems - .GroupBy(k => k.GuildId!.Value) - .ToDictionary(g => g.Key, - g => g.Select(x => - { - x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention); - return x; - }).ToArray()) - .ToConcurrent(); + _newGuildReactions = guildItems.GroupBy(k => k.GuildId!.Value) + .ToDictionary(g => g.Key, + g => g.Select(x => + { + x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention); + return x; + }) + .ToArray()) + .ToConcurrent(); lock (_gcrWriteLock) { - var globalItems = uow - .CustomReactions - .AsNoTracking() - .Where(x => x.GuildId == null || x.GuildId == 0) - .AsEnumerable() - .Select(x => - { - x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention); - return x; - }) - .ToArray(); + var globalItems = uow.CustomReactions.AsNoTracking() + .Where(x => x.GuildId == null || x.GuildId == 0) + .AsEnumerable() + .Select(x => + { + x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention); + return x; + }) + .ToArray(); _globalReactions = globalItems; } @@ -115,176 +150,6 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor ready = true; } - #region Event Handlers - - public Task OnReadyAsync() - => ReloadInternal(_bot.GetCurrentGuildIds()); - - private ValueTask OnCrsShouldReload(bool _) => new(ReloadInternal(_bot.GetCurrentGuildIds())); - - private ValueTask OnGcrAdded(CustomReaction c) - { - lock (_gcrWriteLock) - { - var newGlobalReactions = new CustomReaction[_globalReactions.Length + 1]; - Array.Copy(_globalReactions, newGlobalReactions, _globalReactions.Length); - newGlobalReactions[_globalReactions.Length] = c; - _globalReactions = newGlobalReactions; - } - - return default; - } - - private ValueTask OnGcrEdited(CustomReaction c) - { - lock (_gcrWriteLock) - { - for (var i = 0; i < _globalReactions.Length; i++) - { - if (_globalReactions[i].Id == c.Id) - { - _globalReactions[i] = c; - return default; - } - } - - // if edited cr is not found?! - // add it - OnGcrAdded(c); - } - - return default; - } - - private ValueTask OnGcrDeleted(int id) - { - lock (_gcrWriteLock) - { - var newGlobalReactions = DeleteInternal(_globalReactions, id, out _); - _globalReactions = newGlobalReactions; - } - - return default; - } - - public Task TriggerReloadCustomReactions() - => _pubSub.Pub(_crsReloadedKey, true); - - #endregion - - #region Client Event Handlers - - private Task OnLeftGuild(SocketGuild arg) - { - _newGuildReactions.TryRemove(arg.Id, out _); - - return Task.CompletedTask; - } - - private async Task OnJoinedGuild(GuildConfig gc) - { - await using var uow = _db.GetDbContext(); - var crs = await uow - .CustomReactions - .AsNoTracking() - .Where(x => x.GuildId == gc.GuildId) - .ToArrayAsync(); - - _newGuildReactions[gc.GuildId] = crs; - } - - #endregion - - #region Basic Operations - - public async Task AddAsync(ulong? guildId, string key, string message) - { - key = key.ToLowerInvariant(); - var cr = new CustomReaction() - { - GuildId = guildId, - Trigger = key, - Response = message, - }; - - if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase)) - cr.AllowTarget = true; - - await using (var uow = _db.GetDbContext()) - { - uow.CustomReactions.Add(cr); - await uow.SaveChangesAsync(); - } - - await AddInternalAsync(guildId, cr); - - return cr; - } - - public async Task EditAsync(ulong? guildId, int id, string message) - { - await using var uow = _db.GetDbContext(); - var cr = uow.CustomReactions.GetById(id); - - if (cr is null || cr.GuildId != guildId) - return null; - - // disable allowtarget if message had target, but it was removed from it - if (!message.Contains("%target%", StringComparison.OrdinalIgnoreCase) - && cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase)) - { - cr.AllowTarget = false; - } - - cr.Response = message; - - // enable allow target if message is edited to contain target - if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase)) - cr.AllowTarget = true; - - await uow.SaveChangesAsync(); - await UpdateInternalAsync(guildId, cr); - - return cr; - } - - - public async Task DeleteAsync(ulong? guildId, int id) - { - await using var uow = _db.GetDbContext(); - var toDelete = uow.CustomReactions.GetById(id); - - if (toDelete is null) - return null; - - if ((toDelete.IsGlobal() && guildId is null) || guildId == toDelete.GuildId) - { - uow.CustomReactions.Remove(toDelete); - await uow.SaveChangesAsync(); - await DeleteInternalAsync(guildId, id); - return toDelete; - } - - return null; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CustomReaction[] GetCustomReactionsFor(ulong? maybeGuildId) - { - if (maybeGuildId is { } guildId) - { - return _newGuildReactions.TryGetValue(guildId, out var crs) - ? crs - : Array.Empty(); - } - - return _globalReactions; - } - - #endregion - - private bool ready; - private CustomReaction TryGetCustomReaction(IUserMessage umsg) { if (!ready) @@ -294,7 +159,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor return null; var content = umsg.Content.Trim().ToLowerInvariant(); - + if (_newGuildReactions.TryGetValue(channel.Guild.Id, out var reactions) && reactions.Length > 0) { var cr = MatchCustomReactions(content, reactions); @@ -325,10 +190,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor var wp = content.GetWordPosition(trigger); // if it is, then that's valid - if (wp != WordPosition.None) - { - result.Add(cr); - } + if (wp != WordPosition.None) result.Add(cr); // if it's not, then it cant' work under any circumstance, // because content is greater than the trigger length @@ -338,11 +200,10 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor // if CA is disabled, and CR has AllowTarget, then the // content has to start with the trigger followed by a space - if (cr.AllowTarget && content.StartsWith(trigger, StringComparison.OrdinalIgnoreCase) - && content[trigger.Length] == ' ') - { + if (cr.AllowTarget + && content.StartsWith(trigger, StringComparison.OrdinalIgnoreCase) + && content[trigger.Length] == ' ') result.Add(cr); - } } else if (content.Length < cr.Trigger.Length) { @@ -353,10 +214,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor { // if input length is the same as trigger length // reaction can only trigger if the strings are equal - if (content.SequenceEqual(cr.Trigger)) - { - result.Add(cr); - } + if (content.SequenceEqual(cr.Trigger)) result.Add(cr); } } @@ -366,7 +224,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor var cancelled = result.FirstOrDefault(x => x.Response == "-"); if (cancelled is not null) return cancelled; - + return result[_rng.Next(0, result.Count)]; } @@ -377,34 +235,33 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor if (cr is null || cr.Response == "-") return false; - - if(await _cmdCds.TryBlock(guild, msg.Author, cr.Trigger)) + + if (await _cmdCds.TryBlock(guild, msg.Author, cr.Trigger)) return false; - + try { if (_gperm.BlockedModules.Contains("ActualCustomReactions")) { - Log.Information("User {UserName} [{UserId}] tried to use a custom reaction but 'ActualCustomReactions' are globally disabled.", + Log.Information( + "User {UserName} [{UserId}] tried to use a custom reaction but 'ActualCustomReactions' are globally disabled.", msg.Author.ToString(), msg.Author.Id); - + return true; } if (guild is SocketGuild sg) { var pc = _perms.GetCacheFor(guild.Id); - if (!pc.Permissions.CheckPermissions(msg, cr.Trigger, "ActualCustomReactions", - out var index)) + if (!pc.Permissions.CheckPermissions(msg, cr.Trigger, "ActualCustomReactions", out var index)) { if (pc.Verbose) { - var returnMsg = _strings.GetText( - strs.perm_prevent(index + 1, + var returnMsg = _strings.GetText(strs.perm_prevent(index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg))), sg.Id); - + try { await msg.Channel.SendErrorAsync(_eb, returnMsg); @@ -431,7 +288,8 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor } catch { - Log.Warning("Unable to add reactions to message {Message} in server {GuildId}", sentMsg.Id, + Log.Warning("Unable to add reactions to message {Message} in server {GuildId}", + sentMsg.Id, cr.GuildId); break; } @@ -440,7 +298,6 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor } if (cr.AutoDeleteTrigger) - { try { await msg.DeleteAsync(); @@ -448,7 +305,6 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor catch { } - } Log.Information("s: {GuildId} c: {ChannelId} u: {UserId} | {UserName} executed expression {Expr}", guild.Id, @@ -456,7 +312,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor msg.Author.Id, msg.Author.ToString(), cr.Trigger); - + return true; } catch (Exception ex) @@ -493,31 +349,24 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor private void UpdateInternal(ulong? maybeGuildId, CustomReaction cr) { if (maybeGuildId is { } guildId) - { - _newGuildReactions.AddOrUpdate(guildId, new[] {cr}, + _newGuildReactions.AddOrUpdate(guildId, + new[] { cr }, (key, old) => { var newArray = old.ToArray(); for (var i = 0; i < newArray.Length; i++) - { if (newArray[i].Id == cr.Id) newArray[i] = cr; - } return newArray; }); - } else - { lock (_gcrWriteLock) { var crs = _globalReactions; for (var i = 0; i < crs.Length; i++) - { if (crs[i].Id == cr.Id) crs[i] = cr; - } } - } } private Task AddInternalAsync(ulong? maybeGuildId, CustomReaction cr) @@ -526,19 +375,13 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor cr.Trigger = cr.Trigger.Replace(MentionPh, _client.CurrentUser.Mention); if (maybeGuildId is { } guildId) - { - _newGuildReactions.AddOrUpdate(guildId, - new[] {cr}, - (key, old) => old.With(cr)); - } + _newGuildReactions.AddOrUpdate(guildId, new[] { cr }, (key, old) => old.With(cr)); else - { return _pubSub.Pub(_gcrAddedKey, cr); - } return Task.CompletedTask; } - + private Task DeleteInternalAsync(ulong? maybeGuildId, int id) { if (maybeGuildId is { } guildId) @@ -546,17 +389,14 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor _newGuildReactions.AddOrUpdate(guildId, Array.Empty(), (key, old) => DeleteInternal(old, id, out _)); - + return Task.CompletedTask; } lock (_gcrWriteLock) { var cr = Array.Find(_globalReactions, item => item.Id == id); - if (cr is not null) - { - return _pubSub.Pub(_gcrDeletedkey, cr.Id); - } + if (cr is not null) return _pubSub.Pub(_gcrDeletedkey, cr.Id); } return Task.CompletedTask; @@ -567,7 +407,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor deleted = null; if (crs is null || crs.Count == 0) return crs as CustomReaction[] ?? crs?.ToArray(); - + var newCrs = new CustomReaction[crs.Count - 1]; for (int i = 0, k = 0; i < crs.Count; i++, k++) { @@ -636,13 +476,13 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor return cr; } - + public int DeleteAllCustomReactions(ulong guildId) { using var uow = _db.GetDbContext(); var count = uow.CustomReactions.ClearFromGuild(guildId); uow.SaveChanges(); - + _newGuildReactions.TryRemove(guildId, out _); return count; @@ -655,39 +495,13 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor return cr != null; } - private static readonly ISerializer _exportSerializer = new SerializerBuilder() - .WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args)) - .WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance) - .WithIndentedSequences() - .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults) - .DisableAliases() - .Build(); - - private const string _prependExport = - @"# Keys are triggers, Each key has a LIST of custom reactions in the following format: -# - res: Response string -# id: Alphanumeric id used for commands related to the custom reaction. (Note, when using .crsimport, a new id will be generated.) -# react: -# - -# at: Whether custom reaction allows targets (see .h .crat) -# ca: Whether custom reaction expects trigger anywhere (see .h .crca) -# dm: Whether custom reaction DMs the response (see .h .crdm) -# ad: Whether custom reaction automatically deletes triggering message (see .h .crad) - -"; public string ExportCrs(ulong? guildId) { var crs = GetCustomReactionsFor(guildId); - var crsDict = crs - .GroupBy(x => x.Trigger) - .ToDictionary(x => x.Key, x => x.Select(ExportedExpr.FromModel)); - - return _prependExport + _exportSerializer - .Serialize(crsDict) - .UnescapeUnicodeCodePoints(); + var crsDict = crs.GroupBy(x => x.Trigger).ToDictionary(x => x.Key, x => x.Select(ExportedExpr.FromModel)); + + return _prependExport + _exportSerializer.Serialize(crsDict).UnescapeUnicodeCodePoints(); } public async Task ImportCrsAsync(ulong? guildId, string input) @@ -708,23 +522,174 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor foreach (var entry in data) { var trigger = entry.Key; - await uow.CustomReactions.AddRangeAsync(entry.Value - .Where(cr => !string.IsNullOrWhiteSpace(cr.Res)) - .Select(cr => new CustomReaction() - { - GuildId = guildId, - Response = cr.Res, - Reactions = cr.React?.Join("@@@"), - Trigger = trigger, - AllowTarget = cr.At, - ContainsAnywhere = cr.Ca, - DmResponse = cr.Dm, - AutoDeleteTrigger = cr.Ad, - })); + await uow.CustomReactions.AddRangeAsync(entry.Value.Where(cr => !string.IsNullOrWhiteSpace(cr.Res)) + .Select(cr => new CustomReaction + { + GuildId = guildId, + Response = cr.Res, + Reactions = cr.React?.Join("@@@"), + Trigger = trigger, + AllowTarget = cr.At, + ContainsAnywhere = cr.Ca, + DmResponse = cr.Dm, + AutoDeleteTrigger = cr.Ad + })); } await uow.SaveChangesAsync(); await TriggerReloadCustomReactions(); return true; } -} + + #region Event Handlers + + public Task OnReadyAsync() + => ReloadInternal(_bot.GetCurrentGuildIds()); + + private ValueTask OnCrsShouldReload(bool _) + => new(ReloadInternal(_bot.GetCurrentGuildIds())); + + private ValueTask OnGcrAdded(CustomReaction c) + { + lock (_gcrWriteLock) + { + var newGlobalReactions = new CustomReaction[_globalReactions.Length + 1]; + Array.Copy(_globalReactions, newGlobalReactions, _globalReactions.Length); + newGlobalReactions[_globalReactions.Length] = c; + _globalReactions = newGlobalReactions; + } + + return default; + } + + private ValueTask OnGcrEdited(CustomReaction c) + { + lock (_gcrWriteLock) + { + for (var i = 0; i < _globalReactions.Length; i++) + if (_globalReactions[i].Id == c.Id) + { + _globalReactions[i] = c; + return default; + } + + // if edited cr is not found?! + // add it + OnGcrAdded(c); + } + + return default; + } + + private ValueTask OnGcrDeleted(int id) + { + lock (_gcrWriteLock) + { + var newGlobalReactions = DeleteInternal(_globalReactions, id, out _); + _globalReactions = newGlobalReactions; + } + + return default; + } + + public Task TriggerReloadCustomReactions() + => _pubSub.Pub(_crsReloadedKey, true); + + #endregion + + #region Client Event Handlers + + private Task OnLeftGuild(SocketGuild arg) + { + _newGuildReactions.TryRemove(arg.Id, out _); + + return Task.CompletedTask; + } + + private async Task OnJoinedGuild(GuildConfig gc) + { + await using var uow = _db.GetDbContext(); + var crs = await uow.CustomReactions.AsNoTracking().Where(x => x.GuildId == gc.GuildId).ToArrayAsync(); + + _newGuildReactions[gc.GuildId] = crs; + } + + #endregion + + #region Basic Operations + + public async Task AddAsync(ulong? guildId, string key, string message) + { + key = key.ToLowerInvariant(); + var cr = new CustomReaction { GuildId = guildId, Trigger = key, Response = message }; + + if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase)) + cr.AllowTarget = true; + + await using (var uow = _db.GetDbContext()) + { + uow.CustomReactions.Add(cr); + await uow.SaveChangesAsync(); + } + + await AddInternalAsync(guildId, cr); + + return cr; + } + + public async Task EditAsync(ulong? guildId, int id, string message) + { + await using var uow = _db.GetDbContext(); + var cr = uow.CustomReactions.GetById(id); + + if (cr is null || cr.GuildId != guildId) + return null; + + // disable allowtarget if message had target, but it was removed from it + if (!message.Contains("%target%", StringComparison.OrdinalIgnoreCase) + && cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase)) + cr.AllowTarget = false; + + cr.Response = message; + + // enable allow target if message is edited to contain target + if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase)) + cr.AllowTarget = true; + + await uow.SaveChangesAsync(); + await UpdateInternalAsync(guildId, cr); + + return cr; + } + + + public async Task DeleteAsync(ulong? guildId, int id) + { + await using var uow = _db.GetDbContext(); + var toDelete = uow.CustomReactions.GetById(id); + + if (toDelete is null) + return null; + + if ((toDelete.IsGlobal() && guildId is null) || guildId == toDelete.GuildId) + { + uow.CustomReactions.Remove(toDelete); + await uow.SaveChangesAsync(); + await DeleteInternalAsync(guildId, id); + return toDelete; + } + + return null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CustomReaction[] GetCustomReactionsFor(ulong? maybeGuildId) + { + if (maybeGuildId is { } guildId) + return _newGuildReactions.TryGetValue(guildId, out var crs) ? crs : Array.Empty(); + + return _globalReactions; + } + + #endregion +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/AnimalRacingCommands.cs b/src/NadekoBot/Modules/Gambling/AnimalRacingCommands.cs index 76d2f678d..56e532fca 100644 --- a/src/NadekoBot/Modules/Gambling/AnimalRacingCommands.cs +++ b/src/NadekoBot/Modules/Gambling/AnimalRacingCommands.cs @@ -1,8 +1,8 @@ #nullable disable using NadekoBot.Modules.Gambling.Common; using NadekoBot.Modules.Gambling.Common.AnimalRacing; -using NadekoBot.Modules.Gambling.Services; using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions; +using NadekoBot.Modules.Gambling.Services; using NadekoBot.Modules.Games.Services; namespace NadekoBot.Modules.Gambling; @@ -17,17 +17,22 @@ public partial class Gambling private readonly DiscordSocketClient _client; private readonly GamesConfigService _gamesConf; - public AnimalRacingCommands(ICurrencyService cs, DiscordSocketClient client, - GamblingConfigService gamblingConf, GamesConfigService gamesConf) : base(gamblingConf) + private IUserMessage raceMessage; + + public AnimalRacingCommands( + ICurrencyService cs, + DiscordSocketClient client, + GamblingConfigService gamblingConf, + GamesConfigService gamesConf) + : base(gamblingConf) { _cs = cs; _client = client; _gamesConf = gamesConf; } - private IUserMessage raceMessage = null; - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [NadekoOptionsAttribute(typeof(RaceOptions))] public Task Race(params string[] args) @@ -41,6 +46,7 @@ public partial class Gambling ar.Initialize(); var count = 0; + Task _client_MessageReceived(SocketMessage arg) { var _ = Task.Run(() => @@ -48,12 +54,8 @@ public partial class Gambling try { if (arg.Channel.Id == ctx.Channel.Id) - { if (ar.CurrentPhase == AnimalRace.Phase.Running && ++count % 9 == 0) - { raceMessage = null; - } - } } catch { } }); @@ -66,18 +68,12 @@ public partial class Gambling _service.AnimalRaces.TryRemove(ctx.Guild.Id, out _); var winner = race.FinishedUsers[0]; if (race.FinishedUsers[0].Bet > 0) - { return SendConfirmAsync(GetText(strs.animal_race), - GetText(strs.animal_race_won_money( - Format.Bold(winner.Username), + GetText(strs.animal_race_won_money(Format.Bold(winner.Username), winner.Animal.Icon, (race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign))); - } - else - { - return SendConfirmAsync(GetText(strs.animal_race), - GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon))); - } + return SendConfirmAsync(GetText(strs.animal_race), + GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon))); } ar.OnStartingFailed += Ar_OnStartingFailed; @@ -86,7 +82,8 @@ public partial class Gambling ar.OnStarted += Ar_OnStarted; _client.MessageReceived += _client_MessageReceived; - return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_starting(options.StartTime)), + return SendConfirmAsync(GetText(strs.animal_race), + GetText(strs.animal_race_starting(options.StartTime)), footer: GetText(strs.animal_race_join_instr(Prefix))); } @@ -94,14 +91,14 @@ public partial class Gambling { if (race.Users.Count == race.MaxUsers) return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_full)); - else - return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_starting_with_x(race.Users.Count))); + return SendConfirmAsync(GetText(strs.animal_race), + GetText(strs.animal_race_starting_with_x(race.Users.Count))); } private async Task Ar_OnStateUpdate(AnimalRace race) { var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚| -{String.Join("\n", race.Users.Select(p => +{string.Join("\n", race.Users.Select(p => { var index = race.FinishedUsers.IndexOf(p); var extra = index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}"; @@ -115,10 +112,10 @@ public partial class Gambling raceMessage = await SendConfirmAsync(text); else await msg.ModifyAsync(x => x.Embed = _eb.Create() - .WithTitle(GetText(strs.animal_race)) - .WithDescription(text) - .WithOkColor() - .Build()); + .WithTitle(GetText(strs.animal_race)) + .WithDescription(text) + .WithOkColor() + .Build()); } private Task Ar_OnStartingFailed(AnimalRace race) @@ -127,7 +124,8 @@ public partial class Gambling return ReplyErrorLocalizedAsync(strs.animal_race_failed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task JoinRace(ShmartNumber amount = default) { @@ -139,11 +137,14 @@ public partial class Gambling await ReplyErrorLocalizedAsync(strs.race_not_exist); return; } + try { var user = await ar.JoinRace(ctx.User.Id, ctx.User.ToString(), amount); if (amount > 0) - await SendConfirmAsync(GetText(strs.animal_race_join_bet(ctx.User.Mention, user.Animal.Icon, amount + CurrencySign))); + await SendConfirmAsync(GetText(strs.animal_race_join_bet(ctx.User.Mention, + user.Animal.Icon, + amount + CurrencySign))); else await SendConfirmAsync(GetText(strs.animal_race_join(ctx.User.Mention, user.Animal.Icon))); } @@ -169,4 +170,4 @@ public partial class Gambling } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/BlackJackCommands.cs b/src/NadekoBot/Modules/Gambling/BlackJackCommands.cs index 840ab64e6..844b3dae9 100644 --- a/src/NadekoBot/Modules/Gambling/BlackJackCommands.cs +++ b/src/NadekoBot/Modules/Gambling/BlackJackCommands.cs @@ -9,25 +9,26 @@ public partial class Gambling { public class BlackJackCommands : GamblingSubmodule { - private readonly ICurrencyService _cs; - private readonly DbService _db; - private IUserMessage _msg; - public enum BjAction { Hit = int.MinValue, Stand, - Double, + Double } - public BlackJackCommands(ICurrencyService cs, DbService db, - GamblingConfigService gamblingConf) : base(gamblingConf) + private readonly ICurrencyService _cs; + private readonly DbService _db; + private IUserMessage _msg; + + public BlackJackCommands(ICurrencyService cs, DbService db, GamblingConfigService gamblingConf) + : base(gamblingConf) { _cs = cs; _db = db; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task BlackJack(ShmartNumber amount) { @@ -44,6 +45,7 @@ public partial class Gambling await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); return; } + bj.StateUpdated += Bj_StateUpdated; bj.GameEnded += Bj_GameEnded; bj.Start(); @@ -55,9 +57,8 @@ public partial class Gambling if (await bj.Join(ctx.User, amount)) await ReplyConfirmLocalizedAsync(strs.bj_joined); else - { - Log.Information($"{ctx.User} can't join a blackjack game as it's in " + bj.State.ToString() + " state already."); - } + Log.Information( + $"{ctx.User} can't join a blackjack game as it's in " + bj.State + " state already."); } await ctx.Message.DeleteAsync(); @@ -93,14 +94,11 @@ public partial class Gambling var cStr = string.Concat(c.Select(x => x[..^1] + " ")); cStr += "\n" + string.Concat(c.Select(x => x.Last() + " ")); var embed = _eb.Create() - .WithOkColor() - .WithTitle("BlackJack") - .AddField($"{dealerIcon} Dealer's Hand | Value: {bj.Dealer.GetHandValue()}", cStr); + .WithOkColor() + .WithTitle("BlackJack") + .AddField($"{dealerIcon} Dealer's Hand | Value: {bj.Dealer.GetHandValue()}", cStr); - if (bj.CurrentUser != null) - { - embed.WithFooter($"Player to make a choice: {bj.CurrentUser.DiscordUser.ToString()}"); - } + if (bj.CurrentUser != null) embed.WithFooter($"Player to make a choice: {bj.CurrentUser.DiscordUser}"); foreach (var p in bj.Players) { @@ -111,37 +109,42 @@ public partial class Gambling if (bj.State == Blackjack.GameState.Ended) { if (p.State == User.UserState.Lost) - { full = "❌ " + full; - } else - { full = "✅ " + full; - } } else if (p == bj.CurrentUser) + { full = "▶ " + full; + } else if (p.State == User.UserState.Stand) + { full = "⏹ " + full; + } else if (p.State == User.UserState.Bust) + { full = "💥 " + full; + } else if (p.State == User.UserState.Blackjack) + { full = "💰 " + full; + } + embed.AddField(full, cStr); } + _msg = await ctx.Channel.EmbedAsync(embed); } catch { - } } private string UserToString(User x) { - var playerName = x.State == User.UserState.Bust ? - Format.Strikethrough(x.DiscordUser.ToString().TrimTo(30)) : - x.DiscordUser.ToString(); + var playerName = x.State == User.UserState.Bust + ? Format.Strikethrough(x.DiscordUser.ToString().TrimTo(30)) + : x.DiscordUser.ToString(); var hand = $"{string.Concat(x.Cards.Select(y => "〖" + y.GetEmojiString() + "〗"))}"; @@ -149,17 +152,23 @@ public partial class Gambling return $"{playerName} | Bet: {x.Bet}\n"; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] - public Task Hit() => InternalBlackJack(BjAction.Hit); + public Task Hit() + => InternalBlackJack(BjAction.Hit); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] - public Task Stand() => InternalBlackJack(BjAction.Stand); + public Task Stand() + => InternalBlackJack(BjAction.Stand); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] - public Task Double() => InternalBlackJack(BjAction.Double); + public Task Double() + => InternalBlackJack(BjAction.Double); public async Task InternalBlackJack(BjAction a) { @@ -171,14 +180,10 @@ public partial class Gambling else if (a == BjAction.Stand) await bj.Stand(ctx.User); else if (a == BjAction.Double) - { if (!await bj.Double(ctx.User)) - { await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); - } - } await ctx.Message.DeleteAsync(); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs index ced726234..1e8ec58b6 100644 --- a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs @@ -10,34 +10,36 @@ public sealed class AnimalRace : IDisposable { WaitingForPlayers, Running, - Ended, + Ended } - public Phase CurrentPhase { get; private set; } = Phase.WaitingForPlayers; - public event Func OnStarted = delegate { return Task.CompletedTask; }; public event Func OnStartingFailed = delegate { return Task.CompletedTask; }; public event Func OnStateUpdate = delegate { return Task.CompletedTask; }; public event Func OnEnded = delegate { return Task.CompletedTask; }; - public IReadOnlyCollection Users => _users.ToList(); + public Phase CurrentPhase { get; private set; } = Phase.WaitingForPlayers; + + public IReadOnlyCollection Users + => _users.ToList(); + public List FinishedUsers { get; } = new(); + public int MaxUsers { get; } private readonly SemaphoreSlim _locker = new(1, 1); private readonly HashSet _users = new(); private readonly ICurrencyService _currency; private readonly RaceOptions _options; private readonly Queue _animalsQueue; - public int MaxUsers { get; } public AnimalRace(RaceOptions options, ICurrencyService currency, IEnumerable availableAnimals) { - this._currency = currency; - this._options = options; - this._animalsQueue = new(availableAnimals); - this.MaxUsers = _animalsQueue.Count; + _currency = currency; + _options = options; + _animalsQueue = new(availableAnimals); + MaxUsers = _animalsQueue.Count; - if (this._animalsQueue.Count == 0) + if (_animalsQueue.Count == 0) CurrentPhase = Phase.Ended; } @@ -99,10 +101,8 @@ public sealed class AnimalRace : IDisposable if (_users.Count <= 1) { foreach (var user in _users) - { if (user.Bet > 0) await _currency.AddAsync(user.UserId, "Race refund", user.Bet); - } var _sf = OnStartingFailed?.Invoke(this); CurrentPhase = Phase.Ended; @@ -122,8 +122,7 @@ public sealed class AnimalRace : IDisposable user.Progress = 60; } - var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x)) - .Shuffle(); + var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x)).Shuffle(); FinishedUsers.AddRange(finished); @@ -132,7 +131,9 @@ public sealed class AnimalRace : IDisposable } if (FinishedUsers[0].Bet > 0) - await _currency.AddAsync(FinishedUsers[0].UserId, "Won a Race", FinishedUsers[0].Bet * (_users.Count - 1)); + await _currency.AddAsync(FinishedUsers[0].UserId, + "Won a Race", + FinishedUsers[0].Bet * (_users.Count - 1)); var _ended = OnEnded?.Invoke(this); }); @@ -148,4 +149,4 @@ public sealed class AnimalRace : IDisposable _locker.Dispose(); _users.Clear(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRacingUser.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRacingUser.cs index e07125f78..2d5d51493 100644 --- a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRacingUser.cs +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRacingUser.cs @@ -13,16 +13,14 @@ public class AnimalRacingUser public AnimalRacingUser(string username, ulong userId, long bet) { - this.Bet = bet; - this.Username = username; - this.UserId = userId; + Bet = bet; + Username = username; + UserId = userId; } public override bool Equals(object obj) - => obj is AnimalRacingUser x - ? x.UserId == this.UserId - : false; + => obj is AnimalRacingUser x ? x.UserId == UserId : false; public override int GetHashCode() - => this.UserId.GetHashCode(); -} + => UserId.GetHashCode(); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyJoinedException.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyJoinedException.cs index 3888b5ebc..faddc8bfe 100644 --- a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyJoinedException.cs +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyJoinedException.cs @@ -5,14 +5,15 @@ public class AlreadyJoinedException : Exception { public AlreadyJoinedException() { - } - public AlreadyJoinedException(string message) : base(message) + public AlreadyJoinedException(string message) + : base(message) { } - public AlreadyJoinedException(string message, Exception innerException) : base(message, innerException) + public AlreadyJoinedException(string message, Exception innerException) + : base(message, innerException) { } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyStartedException.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyStartedException.cs index ac37c164b..b61258280 100644 --- a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyStartedException.cs +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyStartedException.cs @@ -7,11 +7,13 @@ public class AlreadyStartedException : Exception { } - public AlreadyStartedException(string message) : base(message) + public AlreadyStartedException(string message) + : base(message) { } - public AlreadyStartedException(string message, Exception innerException) : base(message, innerException) + public AlreadyStartedException(string message, Exception innerException) + : base(message, innerException) { } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AnimalRaceFullException.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AnimalRaceFullException.cs index f48f0cf80..4fcb0f51a 100644 --- a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AnimalRaceFullException.cs +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AnimalRaceFullException.cs @@ -7,11 +7,13 @@ public class AnimalRaceFullException : Exception { } - public AnimalRaceFullException(string message) : base(message) + public AnimalRaceFullException(string message) + : base(message) { } - public AnimalRaceFullException(string message, Exception innerException) : base(message, innerException) + public AnimalRaceFullException(string message, Exception innerException) + : base(message, innerException) { } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/NotEnoughFundsException.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/NotEnoughFundsException.cs index 10f28643b..6188c8028 100644 --- a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/NotEnoughFundsException.cs +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/NotEnoughFundsException.cs @@ -7,11 +7,13 @@ public class NotEnoughFundsException : Exception { } - public NotEnoughFundsException(string message) : base(message) + public NotEnoughFundsException(string message) + : base(message) { } - public NotEnoughFundsException(string message, Exception innerException) : base(message, innerException) + public NotEnoughFundsException(string message, Exception innerException) + : base(message, innerException) { } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/RaceOptions.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/RaceOptions.cs index 3fb51dca9..8163830bf 100644 --- a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/RaceOptions.cs +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/RaceOptions.cs @@ -10,7 +10,7 @@ public class RaceOptions : INadekoCommandOptions public void NormalizeOptions() { - if (this.StartTime is < 10 or > 120) - this.StartTime = 20; + if (StartTime is < 10 or > 120) + StartTime = 20; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/BetRoll.cs b/src/NadekoBot/Modules/Gambling/Common/BetRoll.cs index db9a1b474..86735a823 100644 --- a/src/NadekoBot/Modules/Gambling/Common/BetRoll.cs +++ b/src/NadekoBot/Modules/Gambling/Common/BetRoll.cs @@ -3,17 +3,9 @@ namespace NadekoBot.Modules.Gambling.Common; public class Betroll { - public class Result - { - public int Roll { get; set; } - public float Multiplier { get; set; } - public int Threshold { get; set; } - } - - private readonly IOrderedEnumerable _thresholdPairs; private readonly Random _rng; - + public Betroll(BetRollConfig settings) { _thresholdPairs = settings.Pairs.OrderByDescending(x => x.WhenAbove); @@ -26,19 +18,15 @@ public class Betroll var pair = _thresholdPairs.FirstOrDefault(x => x.WhenAbove < roll); if (pair is null) - { - return new() - { - Multiplier = 0, - Roll = roll, - }; - } + return new() { Multiplier = 0, Roll = roll }; - return new() - { - Multiplier = pair.MultiplyBy, - Roll = roll, - Threshold = pair.WhenAbove, - }; + return new() { Multiplier = pair.MultiplyBy, Roll = roll, Threshold = pair.WhenAbove }; } -} + + public class Result + { + public int Roll { get; set; } + public float Multiplier { get; set; } + public int Threshold { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Blackjack/Blackjack.cs b/src/NadekoBot/Modules/Gambling/Common/Blackjack/Blackjack.cs index fcf1037e0..ab9ddf5d4 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Blackjack/Blackjack.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Blackjack/Blackjack.cs @@ -10,10 +10,13 @@ public class Blackjack Ended } - private Deck Deck { get; set; } = new QuadDeck(); + public event Func StateUpdated; + public event Func GameEnded; + + private Deck Deck { get; } = new QuadDeck(); public Dealer Dealer { get; set; } - + public List Players { get; set; } = new(); public GameState State { get; set; } = GameState.Starting; public User CurrentUser { get; private set; } @@ -22,9 +25,6 @@ public class Blackjack private readonly ICurrencyService _cs; private readonly DbService _db; - public event Func StateUpdated; - public event Func GameEnded; - private readonly SemaphoreSlim locker = new(1, 1); public Blackjack(ICurrencyService cs, DbService db) @@ -54,6 +54,7 @@ public class Blackjack { locker.Release(); } + await PrintState(); //if no users joined the game, end it if (!Players.Any()) @@ -62,6 +63,7 @@ public class Blackjack var end = GameEnded?.Invoke(this); return; } + //give 1 card to the dealer and 2 to each player Dealer.Cards.Add(Deck.Draw()); foreach (var usr in Players) @@ -72,15 +74,15 @@ public class Blackjack if (usr.GetHandValue() == 21) usr.State = User.UserState.Blackjack; } + //go through all users and ask them what they want to do foreach (var usr in Players.Where(x => !x.Done)) - { while (!usr.Done) { Log.Information($"Waiting for {usr.DiscordUser}'s move"); await PromptUserMove(usr); } - } + await PrintState(); State = GameState.Ended; await Task.Delay(2500); @@ -106,10 +108,7 @@ public class Blackjack // either wait for the user to make an action and // if he doesn't - stand var finished = await Task.WhenAny(pause, _currentUserMove.Task); - if (finished == pause) - { - await Stand(usr); - } + if (finished == pause) await Stand(usr); CurrentUser = null; _currentUserMove = null; } @@ -125,10 +124,7 @@ public class Blackjack if (Players.Count >= 5) return false; - if (!await _cs.RemoveAsync(user, "BlackJack-gamble", bet, gamble: true)) - { - return false; - } + if (!await _cs.RemoveAsync(user, "BlackJack-gamble", bet, gamble: true)) return false; Players.Add(new(user, bet)); var _ = PrintState(); @@ -146,7 +142,7 @@ public class Blackjack if (cu != null && cu.DiscordUser == u) return await Stand(cu); - + return false; } @@ -175,7 +171,8 @@ public class Blackjack { var hw = Dealer.GetHandValue(); while (hw < 17 - || (hw == 17 && Dealer.Cards.Count(x => x.Number == 1) > (Dealer.GetRawHandValue() - 17) / 10))// hit on soft 17 + || (hw == 17 + && Dealer.Cards.Count(x => x.Number == 1) > (Dealer.GetRawHandValue() - 17) / 10)) // hit on soft 17 { /* Dealer has A 6 @@ -201,37 +198,23 @@ public class Blackjack } if (hw > 21) - { foreach (var usr in Players) - { if (usr.State is User.UserState.Stand or User.UserState.Blackjack) usr.State = User.UserState.Won; else usr.State = User.UserState.Lost; - } - } else - { foreach (var usr in Players) - { if (usr.State == User.UserState.Blackjack) usr.State = User.UserState.Won; else if (usr.State == User.UserState.Stand) - usr.State = hw < usr.GetHandValue() - ? User.UserState.Won - : User.UserState.Lost; + usr.State = hw < usr.GetHandValue() ? User.UserState.Won : User.UserState.Lost; else usr.State = User.UserState.Lost; - } - } foreach (var usr in Players) - { if (usr.State is User.UserState.Won or User.UserState.Blackjack) - { - await _cs.AddAsync(usr.DiscordUser.Id, "BlackJack-win", usr.Bet * 2, gamble: true); - } - } + await _cs.AddAsync(usr.DiscordUser.Id, "BlackJack-win", usr.Bet * 2, true); } public async Task Double(IUser u) @@ -240,7 +223,7 @@ public class Blackjack if (cu != null && cu.DiscordUser == u) return await Double(cu); - + return false; } @@ -263,20 +246,14 @@ public class Blackjack u.Cards.Add(Deck.Draw()); if (u.GetHandValue() == 21) - { //blackjack u.State = User.UserState.Blackjack; - } else if (u.GetHandValue() > 21) - { // user busted u.State = User.UserState.Bust; - } else - { //with double you just get one card, and then you're done u.State = User.UserState.Stand; - } _currentUserMove.TrySetResult(true); return true; @@ -311,19 +288,12 @@ public class Blackjack u.Cards.Add(Deck.Draw()); if (u.GetHandValue() == 21) - { //blackjack u.State = User.UserState.Blackjack; - } else if (u.GetHandValue() > 21) - { // user busted u.State = User.UserState.Bust; - } - else - { - //you can hit or stand again - } + _currentUserMove.TrySetResult(true); return true; @@ -340,4 +310,4 @@ public class Blackjack return Task.CompletedTask; return StateUpdated.Invoke(this); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Blackjack/Player.cs b/src/NadekoBot/Modules/Gambling/Common/Blackjack/Player.cs index 4374d6d13..60f3e407f 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Blackjack/Player.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Blackjack/Player.cs @@ -13,10 +13,7 @@ public abstract class Player // reduce the value by 10 until it drops below 22 // (emulating the fact that ace is either a 1 or a 11) var i = Cards.Count(x => x.Number == 1); - while (val > 21 && i-- > 0) - { - val -= 10; - } + while (val > 21 && i-- > 0) val -= 10; return val; } @@ -26,7 +23,6 @@ public abstract class Player public class Dealer : Player { - } public class User : Player @@ -41,17 +37,19 @@ public class User : Player Lost } + public UserState State { get; set; } = UserState.Waiting; + public long Bet { get; set; } + public IUser DiscordUser { get; } + + public bool Done + => State != UserState.Waiting; + public User(IUser user, long bet) { if (bet <= 0) throw new ArgumentOutOfRangeException(nameof(bet)); - this.Bet = bet; - this.DiscordUser = user; + Bet = bet; + DiscordUser = user; } - - public UserState State { get; set; } = UserState.Waiting; - public long Bet { get; set; } - public IUser DiscordUser { get; } - public bool Done => State != UserState.Waiting; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/CurrencyRaffleGame.cs b/src/NadekoBot/Modules/Gambling/Common/CurrencyRaffleGame.cs index 02e15091c..8ba4538c5 100644 --- a/src/NadekoBot/Modules/Gambling/Common/CurrencyRaffleGame.cs +++ b/src/NadekoBot/Modules/Gambling/Common/CurrencyRaffleGame.cs @@ -3,28 +3,18 @@ namespace NadekoBot.Modules.Gambling.Common; public class CurrencyRaffleGame { - public enum Type { + public enum Type + { Mixed, Normal } - public class User - { - public IUser DiscordUser { get; set; } - public long Amount { get; set; } + public IEnumerable Users + => _users; - public override int GetHashCode() - => DiscordUser.GetHashCode(); - - public override bool Equals(object obj) - => obj is User u - ? u.DiscordUser == DiscordUser - : false; - } + public Type GameType { get; } private readonly HashSet _users = new(); - public IEnumerable Users => _users; - public Type GameType { get; } public CurrencyRaffleGame(Type type) => GameType = type; @@ -33,19 +23,12 @@ public class CurrencyRaffleGame { // if game type is normal, and someone already joined the game // (that's the user who created it) - if (GameType == Type.Normal && _users.Count > 0 && - _users.First().Amount != amount) + if (GameType == Type.Normal && _users.Count > 0 && _users.First().Amount != amount) return false; - if (!_users.Add(new() - { - DiscordUser = usr, - Amount = amount, - })) - { + if (!_users.Add(new() { DiscordUser = usr, Amount = amount })) return false; - } - + return true; } @@ -67,4 +50,16 @@ public class CurrencyRaffleGame var usrs = _users.ToArray(); return usrs[rng.Next(0, usrs.Length)]; } -} + + public class User + { + public IUser DiscordUser { get; set; } + public long Amount { get; set; } + + public override int GetHashCode() + => DiscordUser.GetHashCode(); + + public override bool Equals(object obj) + => obj is User u ? u.DiscordUser == DiscordUser : false; + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Deck.cs b/src/NadekoBot/Modules/Gambling/Common/Deck.cs index f3d6fd04e..c5c232345 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Deck.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Deck.cs @@ -7,21 +7,28 @@ public class QuadDeck : Deck { CardPool = new(52 * 4); for (var j = 1; j < 14; j++) + for (var i = 1; i < 5; i++) { - for (var i = 1; i < 5; i++) - { - CardPool.Add(new((CardSuit)i, j)); - CardPool.Add(new((CardSuit)i, j)); - CardPool.Add(new((CardSuit)i, j)); - CardPool.Add(new((CardSuit)i, j)); - } + CardPool.Add(new((CardSuit)i, j)); + CardPool.Add(new((CardSuit)i, j)); + CardPool.Add(new((CardSuit)i, j)); + CardPool.Add(new((CardSuit)i, j)); } } } public class Deck { - private static readonly Dictionary _cardNames = new() { + public enum CardSuit + { + Spades = 1, + Hearts = 2, + Diamonds = 3, + Clubs = 4 + } + + private static readonly Dictionary _cardNames = new() + { { 1, "Ace" }, { 2, "Two" }, { 3, "Three" }, @@ -36,171 +43,50 @@ public class Deck { 12, "Queen" }, { 13, "King" } }; + private static Dictionary, bool>> _handValues; - - public enum CardSuit - { - Spades = 1, - Hearts = 2, - Diamonds = 3, - Clubs = 4 - } - - public class Card : IComparable - { - public CardSuit Suit { get; } - public int Number { get; } - - public string FullName - { - get - { - var str = string.Empty; - - if (Number is <= 10 and > 1) - { - str += "_" + Number; - } - else - { - str += GetValueText().ToLowerInvariant(); - } - return str + "_of_" + Suit.ToString().ToLowerInvariant(); - } - } - - public Card(CardSuit s, int cardNum) - { - this.Suit = s; - this.Number = cardNum; - } - - public string GetValueText() => _cardNames[Number]; - - public override string ToString() => _cardNames[Number] + " Of " + Suit; - - public int CompareTo(object obj) - { - if (obj is not Card card) return 0; - return this.Number - card.Number; - } - - public static Card Parse(string input) - { - if (string.IsNullOrWhiteSpace(input)) - throw new ArgumentNullException(nameof(input)); - - if (input.Length != 2 - || !_numberCharToNumber.TryGetValue(input[0], out var n) - || !_suitCharToSuit.TryGetValue(input[1].ToString(), out var s)) - { - throw new ArgumentException("Invalid input", nameof(input)); - } - - return new(s, n); - } - - public string GetEmojiString() - { - var str = string.Empty; - - str += _regIndicators[this.Number - 1]; - str += _suitToSuitChar[this.Suit]; - - return str; - } - private readonly string[] _regIndicators = new[] - { - "🇦", - ":two:", - ":three:", - ":four:", - ":five:", - ":six:", - ":seven:", - ":eight:", - ":nine:", - ":keycap_ten:", - "🇯", - "🇶", - "🇰" - }; - private static readonly IReadOnlyDictionary _suitToSuitChar = new Dictionary - { - {CardSuit.Diamonds, "♦"}, - {CardSuit.Clubs, "♣"}, - {CardSuit.Spades, "♠"}, - {CardSuit.Hearts, "♥"}, - }; - private static readonly IReadOnlyDictionary _suitCharToSuit = new Dictionary - { - {"♦", CardSuit.Diamonds }, - {"d", CardSuit.Diamonds }, - {"♣", CardSuit.Clubs }, - {"c", CardSuit.Clubs }, - {"♠", CardSuit.Spades }, - {"s", CardSuit.Spades }, - {"♥", CardSuit.Hearts }, - {"h", CardSuit.Hearts }, - }; - private static readonly IReadOnlyDictionary _numberCharToNumber = new Dictionary() - { - {'a', 1 }, - {'2', 2 }, - {'3', 3 }, - {'4', 4 }, - {'5', 5 }, - {'6', 6 }, - {'7', 7 }, - {'8', 8 }, - {'9', 9 }, - {'t', 10 }, - {'j', 11 }, - {'q', 12 }, - {'k', 13 }, - }; - } - public List CardPool { get; set; } - /// - /// Creates a new instance of the BlackJackGame, this allows you to create multiple games running at one time. - /// - public Deck() - => RefillPool(); + private readonly Random _r = new NadekoRandom(); static Deck() => InitHandValues(); /// - /// Restart the game of blackjack. It will only refill the pool for now. Probably wont be used, unless you want to have only 1 bjg running at one time, - /// then you will restart the same game every time. + /// Creates a new instance of the BlackJackGame, this allows you to create multiple games running at one time. /// - public void Restart() => RefillPool(); + public Deck() + => RefillPool(); /// - /// Removes all cards from the pool and refills the pool with all of the possible cards. NOTE: I think this is too expensive. - /// We should probably make it so it copies another premade list with all the cards, or something. + /// Restart the game of blackjack. It will only refill the pool for now. Probably wont be used, unless you want to have + /// only 1 bjg running at one time, + /// then you will restart the same game every time. + /// + public void Restart() + => RefillPool(); + + /// + /// Removes all cards from the pool and refills the pool with all of the possible cards. NOTE: I think this is too + /// expensive. + /// We should probably make it so it copies another premade list with all the cards, or something. /// protected virtual void RefillPool() { CardPool = new(52); //foreach suit for (var j = 1; j < 14; j++) - { // and number - for (var i = 1; i < 5; i++) - { - //generate a card of that suit and number and add it to the pool + for (var i = 1; i < 5; i++) + //generate a card of that suit and number and add it to the pool - // the pool will go from ace of spades,hears,diamonds,clubs all the way to the king of spades. hearts, ... - CardPool.Add(new((CardSuit)i, j)); - } - } + // the pool will go from ace of spades,hears,diamonds,clubs all the way to the king of spades. hearts, ... + CardPool.Add(new((CardSuit)i, j)); } - private readonly Random _r = new NadekoRandom(); + /// - /// Take a card from the pool, you either take it from the top if the deck is shuffled, or from a random place if the deck is in the default order. + /// Take a card from the pool, you either take it from the top if the deck is shuffled, or from a random place if the + /// deck is in the default order. /// /// A card from the pool public Card Draw() @@ -221,8 +107,10 @@ public class Deck return c; */ } + /// - /// Shuffles the deck. Use this if you want to take cards from the top of the deck, instead of randomly. See DrawACard method. + /// Shuffles the deck. Use this if you want to take cards from the top of the deck, instead of randomly. See DrawACard + /// method. /// private void Shuffle() { @@ -230,51 +118,79 @@ public class Deck var orderedPool = CardPool.Shuffle(); CardPool ??= orderedPool.ToList(); } - public override string ToString() => string.Concat(CardPool.Select(c => c.ToString())) + Environment.NewLine; + + public override string ToString() + => string.Concat(CardPool.Select(c => c.ToString())) + Environment.NewLine; private static void InitHandValues() { - bool HasPair(List cards) => cards.GroupBy(card => card.Number) - .Count(group => group.Count() == 2) == 1; - bool IsPair(List cards) => cards.GroupBy(card => card.Number) - .Count(group => group.Count() == 3) == 0 - && HasPair(cards); + bool HasPair(List cards) + { + return cards.GroupBy(card => card.Number).Count(group => group.Count() == 2) == 1; + } - bool IsTwoPair(List cards) => cards.GroupBy(card => card.Number) - .Count(group => group.Count() == 2) == 2; + bool IsPair(List cards) + { + return cards.GroupBy(card => card.Number).Count(group => group.Count() == 3) == 0 && HasPair(cards); + } + + bool IsTwoPair(List cards) + { + return cards.GroupBy(card => card.Number).Count(group => group.Count() == 2) == 2; + } bool IsStraight(List cards) { if (cards.GroupBy(card => card.Number).Count() != cards.Count()) return false; - var toReturn = cards.Max(card => card.Number) - - cards.Min(card => card.Number) == 4; + var toReturn = cards.Max(card => card.Number) - cards.Min(card => card.Number) == 4; if (toReturn || cards.All(c => c.Number != 1)) return toReturn; var newCards = cards.Select(c => c.Number == 1 ? new(c.Suit, 14) : c); - return newCards.Max(card => card.Number) - - newCards.Min(card => card.Number) == 4; + return newCards.Max(card => card.Number) - newCards.Min(card => card.Number) == 4; } - bool HasThreeOfKind(List cards) => cards.GroupBy(card => card.Number) - .Any(group => group.Count() == 3); + bool HasThreeOfKind(List cards) + { + return cards.GroupBy(card => card.Number).Any(group => group.Count() == 3); + } - bool IsThreeOfKind(List cards) => HasThreeOfKind(cards) && !HasPair(cards); + bool IsThreeOfKind(List cards) + { + return HasThreeOfKind(cards) && !HasPair(cards); + } - bool IsFlush(List cards) => cards.GroupBy(card => card.Suit).Count() == 1; + bool IsFlush(List cards) + { + return cards.GroupBy(card => card.Suit).Count() == 1; + } - bool IsFourOfKind(List cards) => cards.GroupBy(card => card.Number) - .Any(group => group.Count() == 4); + bool IsFourOfKind(List cards) + { + return cards.GroupBy(card => card.Number).Any(group => group.Count() == 4); + } - bool IsFullHouse(List cards) => HasPair(cards) && HasThreeOfKind(cards); + bool IsFullHouse(List cards) + { + return HasPair(cards) && HasThreeOfKind(cards); + } - bool HasStraightFlush(List cards) => IsFlush(cards) && IsStraight(cards); + bool HasStraightFlush(List cards) + { + return IsFlush(cards) && IsStraight(cards); + } - bool IsRoyalFlush(List cards) => cards.Min(card => card.Number) == 1 && - cards.Max(card => card.Number) == 13 - && HasStraightFlush(cards); + bool IsRoyalFlush(List cards) + { + return cards.Min(card => card.Number) == 1 + && cards.Max(card => card.Number) == 13 + && HasStraightFlush(cards); + } - bool IsStraightFlush(List cards) => HasStraightFlush(cards) && !IsRoyalFlush(cards); + bool IsStraightFlush(List cards) + { + return HasStraightFlush(cards) && !IsRoyalFlush(cards); + } _handValues = new() { @@ -294,10 +210,108 @@ public class Deck { if (_handValues is null) InitHandValues(); - foreach (var kvp in _handValues.Where(x => x.Value(cards))) - { - return kvp.Key; - } + foreach (var kvp in _handValues.Where(x => x.Value(cards))) return kvp.Key; return "High card " + (cards.FirstOrDefault(c => c.Number == 1)?.GetValueText() ?? cards.Max().GetValueText()); } -} + + public class Card : IComparable + { + private static readonly IReadOnlyDictionary _suitToSuitChar = new Dictionary + { + { CardSuit.Diamonds, "♦" }, { CardSuit.Clubs, "♣" }, { CardSuit.Spades, "♠" }, { CardSuit.Hearts, "♥" } + }; + + private static readonly IReadOnlyDictionary _suitCharToSuit = new Dictionary + { + { "♦", CardSuit.Diamonds }, + { "d", CardSuit.Diamonds }, + { "♣", CardSuit.Clubs }, + { "c", CardSuit.Clubs }, + { "♠", CardSuit.Spades }, + { "s", CardSuit.Spades }, + { "♥", CardSuit.Hearts }, + { "h", CardSuit.Hearts } + }; + + private static readonly IReadOnlyDictionary _numberCharToNumber = new Dictionary + { + { 'a', 1 }, + { '2', 2 }, + { '3', 3 }, + { '4', 4 }, + { '5', 5 }, + { '6', 6 }, + { '7', 7 }, + { '8', 8 }, + { '9', 9 }, + { 't', 10 }, + { 'j', 11 }, + { 'q', 12 }, + { 'k', 13 } + }; + + public CardSuit Suit { get; } + public int Number { get; } + + public string FullName + { + get + { + var str = string.Empty; + + if (Number is <= 10 and > 1) + str += "_" + Number; + else + str += GetValueText().ToLowerInvariant(); + return str + "_of_" + Suit.ToString().ToLowerInvariant(); + } + } + + private readonly string[] _regIndicators = + { + "🇦", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:", ":keycap_ten:", + "🇯", "🇶", "🇰" + }; + + public Card(CardSuit s, int cardNum) + { + Suit = s; + Number = cardNum; + } + + public string GetValueText() + => _cardNames[Number]; + + public override string ToString() + => _cardNames[Number] + " Of " + Suit; + + public int CompareTo(object obj) + { + if (obj is not Card card) return 0; + return Number - card.Number; + } + + public static Card Parse(string input) + { + if (string.IsNullOrWhiteSpace(input)) + throw new ArgumentNullException(nameof(input)); + + if (input.Length != 2 + || !_numberCharToNumber.TryGetValue(input[0], out var n) + || !_suitCharToSuit.TryGetValue(input[1].ToString(), out var s)) + throw new ArgumentException("Invalid input", nameof(input)); + + return new(s, n); + } + + public string GetEmojiString() + { + var str = string.Empty; + + str += _regIndicators[Number - 1]; + str += _suitToSuitChar[Suit]; + + return str; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Events/EventOptions.cs b/src/NadekoBot/Modules/Gambling/Common/Events/EventOptions.cs index 331380a51..b21413109 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Events/EventOptions.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Events/EventOptions.cs @@ -7,11 +7,21 @@ public class EventOptions : INadekoCommandOptions { [Option('a', "amount", Required = false, Default = 100, HelpText = "Amount of currency each user receives.")] public long Amount { get; set; } = 100; - [Option('p', "pot-size", Required = false, Default = 0, HelpText = "The maximum amount of currency that can be rewarded. 0 means no limit.")] - public long PotSize { get; set; } = 0; + + [Option('p', + "pot-size", + Required = false, + Default = 0, + HelpText = "The maximum amount of currency that can be rewarded. 0 means no limit.")] + public long PotSize { get; set; } + //[Option('t', "type", Required = false, Default = "reaction", HelpText = "Type of the event. reaction, gamestatus or joinserver.")] //public string TypeString { get; set; } = "reaction"; - [Option('d', "duration", Required = false, Default = 24, HelpText = "Number of hours the event should run for. Default 24.")] + [Option('d', + "duration", + Required = false, + Default = 24, + HelpText = "Number of hours the event should run for. Default 24.")] public int Hours { get; set; } = 24; @@ -26,4 +36,4 @@ public class EventOptions : INadekoCommandOptions if (PotSize != 0 && PotSize < Amount) PotSize = 0; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Events/GameStatusEvent.cs b/src/NadekoBot/Modules/Gambling/Common/Events/GameStatusEvent.cs index b44f772eb..2dc26728e 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Events/GameStatusEvent.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Events/GameStatusEvent.cs @@ -5,37 +5,44 @@ namespace NadekoBot.Modules.Gambling.Common.Events; public class GameStatusEvent : ICurrencyEvent { + public event Func OnEnded; + private long PotSize { get; set; } + public bool Stopped { get; private set; } + public bool PotEmptied { get; private set; } private readonly DiscordSocketClient _client; private readonly IGuild _guild; private IUserMessage _msg; private readonly ICurrencyService _cs; private readonly long _amount; - private long PotSize { get; set; } - public bool Stopped { get; private set; } - public bool PotEmptied { get; private set; } = false; - private readonly Func _embedFunc; private readonly bool _isPotLimited; private readonly ITextChannel _channel; private readonly ConcurrentHashSet _awardedUsers = new(); private readonly ConcurrentQueue _toAward = new(); private readonly Timer _t; - private readonly Timer _timeout = null; + private readonly Timer _timeout; private readonly EventOptions _opts; private readonly string _code; - public event Func OnEnded; - private readonly char[] _sneakyGameStatusChars = Enumerable.Range(48, 10) - .Concat(Enumerable.Range(65, 26)) - .Concat(Enumerable.Range(97, 26)) - .Select(x => (char)x) - .ToArray(); + .Concat(Enumerable.Range(65, 26)) + .Concat(Enumerable.Range(97, 26)) + .Select(x => (char)x) + .ToArray(); - public GameStatusEvent(DiscordSocketClient client, ICurrencyService cs,SocketGuild g, ITextChannel ch, - EventOptions opt, Func embedFunc) + private readonly object stopLock = new(); + + private readonly object potLock = new(); + + public GameStatusEvent( + DiscordSocketClient client, + ICurrencyService cs, + SocketGuild g, + ITextChannel ch, + EventOptions opt, + Func embedFunc) { _client = client; _guild = g; @@ -51,9 +58,7 @@ public class GameStatusEvent : ICurrencyEvent _t = new(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2)); if (_opts.Hours > 0) - { _timeout = new(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan); - } } private void EventTimeout(object state) @@ -65,10 +70,7 @@ public class GameStatusEvent : ICurrencyEvent { var potEmpty = PotEmptied; var toAward = new List(); - while (_toAward.TryDequeue(out var x)) - { - toAward.Add(x); - } + while (_toAward.TryDequeue(out var x)) toAward.Add(x); if (!toAward.Any()) return; @@ -78,15 +80,14 @@ public class GameStatusEvent : ICurrencyEvent await _cs.AddBulkAsync(toAward, toAward.Select(x => "GameStatus Event"), toAward.Select(x => _amount), - gamble: true); + true); if (_isPotLimited) - { await _msg.ModifyAsync(m => - { - m.Embed = GetEmbed(PotSize).Build(); - }, new() { RetryMode = RetryMode.AlwaysRetry }); - } + { + m.Embed = GetEmbed(PotSize).Build(); + }, + new() { RetryMode = RetryMode.AlwaysRetry }); Log.Information("Awarded {0} users {1} currency.{2}", toAward.Count, @@ -97,7 +98,6 @@ public class GameStatusEvent : ICurrencyEvent { var _ = StopEvent(); } - } catch (Exception ex) { @@ -119,13 +119,9 @@ public class GameStatusEvent : ICurrencyEvent private async Task OnMessageDeleted(Cacheable msg, Cacheable cacheable) { - if (msg.Id == _msg.Id) - { - await StopEvent(); - } + if (msg.Id == _msg.Id) await StopEvent(); } - private readonly object stopLock = new(); public async Task StopEvent() { await Task.Yield(); @@ -139,7 +135,12 @@ public class GameStatusEvent : ICurrencyEvent _client.SetGameAsync(null); _t.Change(Timeout.Infinite, Timeout.Infinite); _timeout?.Change(Timeout.Infinite, Timeout.Infinite); - try { var _ = _msg.DeleteAsync(); } catch { } + try + { + var _ = _msg.DeleteAsync(); + } + catch { } + var os = OnEnded(_guild.Id); } } @@ -152,9 +153,7 @@ public class GameStatusEvent : ICurrencyEvent || gu.IsBot // no bots || msg.Content != _code // code has to be the same || (DateTime.UtcNow - gu.CreatedAt).TotalDays <= 5) // no recently created accounts - { return; - } // there has to be money left in the pot // and the user wasn't rewarded if (_awardedUsers.Add(msg.Author.Id) && TryTakeFromPot()) @@ -166,21 +165,16 @@ public class GameStatusEvent : ICurrencyEvent try { - await msg.DeleteAsync(new() - { - RetryMode = RetryMode.AlwaysFail - }); + await msg.DeleteAsync(new() { RetryMode = RetryMode.AlwaysFail }); } catch { } }); return Task.CompletedTask; } - private readonly object potLock = new(); private bool TryTakeFromPot() { if (_isPotLimited) - { lock (potLock) { if (PotSize < _amount) @@ -189,7 +183,7 @@ public class GameStatusEvent : ICurrencyEvent PotSize -= _amount; return true; } - } + return true; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Events/ICurrencyEvent.cs b/src/NadekoBot/Modules/Gambling/Common/Events/ICurrencyEvent.cs index 9a6620eb9..33e99f8d8 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Events/ICurrencyEvent.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Events/ICurrencyEvent.cs @@ -6,4 +6,4 @@ public interface ICurrencyEvent event Func OnEnded; Task StopEvent(); Task StartEvent(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Events/ReactionEvent.cs b/src/NadekoBot/Modules/Gambling/Common/Events/ReactionEvent.cs index 089a7e85d..5ef8bd5f7 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Events/ReactionEvent.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Events/ReactionEvent.cs @@ -5,6 +5,10 @@ namespace NadekoBot.Modules.Gambling.Common.Events; public class ReactionEvent : ICurrencyEvent { + public event Func OnEnded; + private long PotSize { get; set; } + public bool Stopped { get; private set; } + public bool PotEmptied { get; private set; } private readonly DiscordSocketClient _client; private readonly IGuild _guild; private IUserMessage _msg; @@ -12,25 +16,28 @@ public class ReactionEvent : ICurrencyEvent private readonly ICurrencyService _cs; private readonly long _amount; - private long PotSize { get; set; } - public bool Stopped { get; private set; } - public bool PotEmptied { get; private set; } = false; - private readonly Func _embedFunc; private readonly bool _isPotLimited; private readonly ITextChannel _channel; private readonly ConcurrentHashSet _awardedUsers = new(); private readonly ConcurrentQueue _toAward = new(); private readonly Timer _t; - private readonly Timer _timeout = null; + private readonly Timer _timeout; private readonly bool _noRecentlyJoinedServer; private readonly EventOptions _opts; private readonly GamblingConfig _config; - public event Func OnEnded; + private readonly object stopLock = new(); - public ReactionEvent(DiscordSocketClient client, ICurrencyService cs, - SocketGuild g, ITextChannel ch, EventOptions opt, GamblingConfig config, + private readonly object potLock = new(); + + public ReactionEvent( + DiscordSocketClient client, + ICurrencyService cs, + SocketGuild g, + ITextChannel ch, + EventOptions opt, + GamblingConfig config, Func embedFunc) { _client = client; @@ -47,9 +54,7 @@ public class ReactionEvent : ICurrencyEvent _t = new(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2)); if (_opts.Hours > 0) - { _timeout = new(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan); - } } private void EventTimeout(object state) @@ -61,28 +66,21 @@ public class ReactionEvent : ICurrencyEvent { var potEmpty = PotEmptied; var toAward = new List(); - while (_toAward.TryDequeue(out var x)) - { - toAward.Add(x); - } + while (_toAward.TryDequeue(out var x)) toAward.Add(x); if (!toAward.Any()) return; try { - await _cs.AddBulkAsync(toAward, - toAward.Select(x => "Reaction Event"), - toAward.Select(x => _amount), - gamble: true); + await _cs.AddBulkAsync(toAward, toAward.Select(x => "Reaction Event"), toAward.Select(x => _amount), true); if (_isPotLimited) - { await _msg.ModifyAsync(m => - { - m.Embed = GetEmbed(PotSize).Build(); - }, new() { RetryMode = RetryMode.AlwaysRetry }); - } + { + m.Embed = GetEmbed(PotSize).Build(); + }, + new() { RetryMode = RetryMode.AlwaysRetry }); Log.Information("Awarded {0} users {1} currency.{2}", toAward.Count, @@ -103,13 +101,9 @@ public class ReactionEvent : ICurrencyEvent public async Task StartEvent() { if (Emote.TryParse(_config.Currency.Sign, out var emote)) - { _emote = emote; - } else - { _emote = new Emoji(_config.Currency.Sign); - } _msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize)); await _msg.AddReactionAsync(_emote); _client.MessageDeleted += OnMessageDeleted; @@ -122,13 +116,9 @@ public class ReactionEvent : ICurrencyEvent private async Task OnMessageDeleted(Cacheable msg, Cacheable cacheable) { - if (msg.Id == _msg.Id) - { - await StopEvent(); - } + if (msg.Id == _msg.Id) await StopEvent(); } - private readonly object stopLock = new(); public async Task StopEvent() { await Task.Yield(); @@ -141,27 +131,37 @@ public class ReactionEvent : ICurrencyEvent _client.ReactionAdded -= HandleReaction; _t.Change(Timeout.Infinite, Timeout.Infinite); _timeout?.Change(Timeout.Infinite, Timeout.Infinite); - try { var _ = _msg.DeleteAsync(); } catch { } + try + { + var _ = _msg.DeleteAsync(); + } + catch { } + var os = OnEnded(_guild.Id); } } - private Task HandleReaction(Cacheable msg, - Cacheable cacheable, SocketReaction r) + private Task HandleReaction( + Cacheable msg, + Cacheable cacheable, + SocketReaction r) { var _ = Task.Run(() => { if (_emote.Name != r.Emote.Name) return; - if ((r.User.IsSpecified ? r.User.Value : null) is not IGuildUser gu // no unknown users, as they could be bots, or alts + if ((r.User.IsSpecified + ? r.User.Value + : null) is not IGuildUser gu // no unknown users, as they could be bots, or alts || msg.Id != _msg.Id // same message || gu.IsBot // no bots || (DateTime.UtcNow - gu.CreatedAt).TotalDays <= 5 // no recently created accounts - || (_noRecentlyJoinedServer && // if specified, no users who joined the server in the last 24h - (gu.JoinedAt is null || (DateTime.UtcNow - gu.JoinedAt.Value).TotalDays < 1))) // and no users for who we don't know when they joined - { + || (_noRecentlyJoinedServer + && // if specified, no users who joined the server in the last 24h + (gu.JoinedAt is null + || (DateTime.UtcNow - gu.JoinedAt.Value).TotalDays + < 1))) // and no users for who we don't know when they joined return; - } // there has to be money left in the pot // and the user wasn't rewarded if (_awardedUsers.Add(r.UserId) && TryTakeFromPot()) @@ -174,11 +174,9 @@ public class ReactionEvent : ICurrencyEvent return Task.CompletedTask; } - private readonly object potLock = new(); private bool TryTakeFromPot() { if (_isPotLimited) - { lock (potLock) { if (PotSize < _amount) @@ -187,7 +185,7 @@ public class ReactionEvent : ICurrencyEvent PotSize -= _amount; return true; } - } + return true; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/GamblingConfig.cs b/src/NadekoBot/Modules/Gambling/Common/GamblingConfig.cs index d78b06675..b897bf223 100644 --- a/src/NadekoBot/Modules/Gambling/Common/GamblingConfig.cs +++ b/src/NadekoBot/Modules/Gambling/Common/GamblingConfig.cs @@ -3,25 +3,13 @@ using Cloneable; using NadekoBot.Common.Yml; using SixLabors.ImageSharp.PixelFormats; using YamlDotNet.Serialization; +using Color = SixLabors.ImageSharp.Color; namespace NadekoBot.Modules.Gambling.Common; [Cloneable] public sealed partial class GamblingConfig : ICloneable { - public GamblingConfig() - { - BetRoll = new(); - WheelOfFortune = new(); - Waifu = new(); - Currency = new(); - BetFlip = new(); - Generation = new(); - Timely = new(); - Decay = new(); - Slots = new(); - } - [Comment(@"DO NOT CHANGE")] public int Version { get; set; } = 2; @@ -60,13 +48,26 @@ Set 0 for unlimited")] [Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT. 1 = 100 currency per $. Used almost exclusively on public nadeko.")] public decimal PatreonCurrencyPerCent { get; set; } = 1; - + [Comment(@"Currency reward per vote. This will work only if you've set up VotesApi and correct credentials for topgg and/or discords voting")] public long VoteReward { get; set; } = 100; - + [Comment(@"Slot config")] public SlotsConfig Slots { get; set; } + + public GamblingConfig() + { + BetRoll = new(); + WheelOfFortune = new(); + Waifu = new(); + Currency = new(); + BetFlip = new(); + Generation = new(); + Timely = new(); + Decay = new(); + Slots = new(); + } } public class CurrencyConfig @@ -108,8 +109,7 @@ Doesn't have to be ordered.")] public BetRollConfig() => Pairs = new BetRollPair[] { - new() { WhenAbove = 99, MultiplyBy = 10 }, - new() { WhenAbove = 90, MultiplyBy = 4 }, + new() { WhenAbove = 99, MultiplyBy = 10 }, new() { WhenAbove = 90, MultiplyBy = 4 }, new() { WhenAbove = 66, MultiplyBy = 2 } }; } @@ -162,17 +162,7 @@ public partial class WheelOfFortuneSettings public decimal[] Multipliers { get; set; } public WheelOfFortuneSettings() - => Multipliers = new decimal[] - { - 1.7M, - 1.5M, - 0.2M, - 0.1M, - 0.3M, - 0.5M, - 1.2M, - 2.4M, - }; + => Multipliers = new[] { 1.7M, 1.5M, 0.2M, 0.1M, 0.3M, 0.5M, 1.2M, 2.4M }; } [Cloneable] @@ -263,17 +253,17 @@ Default 1 (meaning no effect)")] Default 0.95 (meaning 95%) Example: If a waifu is worth 1000, and she receives a gift worth 100, her new value will be 1095)")] public decimal GiftEffect { get; set; } = 0.95M; - + [Comment(@"What percentage of the value of the gift will a waifu lose when she's gifted a gift marked as 'negative'. Default 0.5 (meaning 50%) Example: If a waifu is worth 1000, and she receives a negative gift worth 100, her new value will be 950)")] public decimal NegativeGiftEffect { get; set; } = 0.50M; } -public sealed partial class SlotsConfig +public sealed class SlotsConfig { [Comment(@"Hex value of the color which the numbers on the slot image will have.")] - public Rgba32 CurrencyFontColor { get; set; } = SixLabors.ImageSharp.Color.Red; + public Rgba32 CurrencyFontColor { get; set; } = Color.Red; } [Cloneable] @@ -282,16 +272,19 @@ public sealed partial class WaifuItemModel public string ItemEmoji { get; set; } public int Price { get; set; } public string Name { get; set; } - + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public bool Negative { get; set; } public WaifuItemModel() { - } - - public WaifuItemModel(string itemEmoji, int price, string name, bool negative = false) + + public WaifuItemModel( + string itemEmoji, + int price, + string name, + bool negative = false) { ItemEmoji = itemEmoji; Price = price; @@ -300,13 +293,13 @@ public sealed partial class WaifuItemModel } - public override string ToString() => Name; + public override string ToString() + => Name; } - [Cloneable] public sealed partial class BetRollPair { public int WhenAbove { get; set; } public float MultiplyBy { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/GamblingError.cs b/src/NadekoBot/Modules/Gambling/Common/GamblingError.cs index b768a747a..cf74c09b7 100644 --- a/src/NadekoBot/Modules/Gambling/Common/GamblingError.cs +++ b/src/NadekoBot/Modules/Gambling/Common/GamblingError.cs @@ -5,4 +5,4 @@ public enum GamblingError { None, NotEnough -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/GamblingTopLevelModule.cs b/src/NadekoBot/Modules/Gambling/Common/GamblingTopLevelModule.cs index 50d3b6aed..9282f3ebe 100644 --- a/src/NadekoBot/Modules/Gambling/Common/GamblingTopLevelModule.cs +++ b/src/NadekoBot/Modules/Gambling/Common/GamblingTopLevelModule.cs @@ -5,57 +5,55 @@ namespace NadekoBot.Modules.Gambling.Common; public abstract class GamblingModule : NadekoModule { + protected GamblingConfig _config + => _lazyConfig.Value; + + protected string CurrencySign + => _config.Currency.Sign; + + protected string CurrencyName + => _config.Currency.Name; + private readonly Lazy _lazyConfig; - protected GamblingConfig _config => _lazyConfig.Value; - protected string CurrencySign => _config.Currency.Sign; - protected string CurrencyName => _config.Currency.Name; - + protected GamblingModule(GamblingConfigService gambService) => _lazyConfig = new(() => gambService.Data); private async Task InternalCheckBet(long amount) { - if (amount < 1) - { - return false; - } + if (amount < 1) return false; if (amount < _config.MinBet) { - await ReplyErrorLocalizedAsync(strs.min_bet_limit( - Format.Bold(_config.MinBet.ToString()) + CurrencySign)); + await ReplyErrorLocalizedAsync(strs.min_bet_limit(Format.Bold(_config.MinBet.ToString()) + CurrencySign)); return false; } + if (_config.MaxBet > 0 && amount > _config.MaxBet) { - await ReplyErrorLocalizedAsync(strs.max_bet_limit( - Format.Bold(_config.MaxBet.ToString()) + CurrencySign)); + await ReplyErrorLocalizedAsync(strs.max_bet_limit(Format.Bold(_config.MaxBet.ToString()) + CurrencySign)); return false; } + return true; } protected Task CheckBetMandatory(long amount) { - if (amount < 1) - { - return Task.FromResult(false); - } + if (amount < 1) return Task.FromResult(false); return InternalCheckBet(amount); } protected Task CheckBetOptional(long amount) { - if (amount == 0) - { - return Task.FromResult(true); - } + if (amount == 0) return Task.FromResult(true); return InternalCheckBet(amount); } } public abstract class GamblingSubmodule : GamblingModule { - protected GamblingSubmodule(GamblingConfigService gamblingConfService) : base(gamblingConfService) + protected GamblingSubmodule(GamblingConfigService gamblingConfService) + : base(gamblingConfService) { } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Payout.cs b/src/NadekoBot/Modules/Gambling/Common/Payout.cs index 1d405fbed..218e5ea1b 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Payout.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Payout.cs @@ -5,4 +5,4 @@ public class Payout { public string User { get; set; } public int Amount { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/RollDuelGame.cs b/src/NadekoBot/Modules/Gambling/Common/RollDuelGame.cs index 7abac2c50..12bad0575 100644 --- a/src/NadekoBot/Modules/Gambling/Common/RollDuelGame.cs +++ b/src/NadekoBot/Modules/Gambling/Common/RollDuelGame.cs @@ -3,64 +3,72 @@ namespace NadekoBot.Modules.Gambling.Common; public class RollDuelGame { - public ulong P1 { get; } - public ulong P2 { get; } - - private readonly ulong _botId; - - public long Amount { get; } - - private readonly ICurrencyService _cs; + public enum Reason + { + Normal, + NoFunds, + Timeout + } public enum State { Waiting, Running, - Ended, + Ended } - public enum Reason - { - Normal, - NoFunds, - Timeout, - } - - private readonly Timer _timeoutTimer; - private readonly NadekoRandom _rng = new(); - private readonly SemaphoreSlim _locker = new(1, 1); - public event Func OnGameTick; public event Func OnEnded; + public ulong P1 { get; } + public ulong P2 { get; } + + public long Amount { get; } + public List<(int, int)> Rolls { get; } = new(); public State CurrentState { get; private set; } public ulong Winner { get; private set; } - public RollDuelGame(ICurrencyService cs, ulong botId, ulong p1, ulong p2, long amount) + private readonly ulong _botId; + + private readonly ICurrencyService _cs; + + private readonly Timer _timeoutTimer; + private readonly NadekoRandom _rng = new(); + private readonly SemaphoreSlim _locker = new(1, 1); + + public RollDuelGame( + ICurrencyService cs, + ulong botId, + ulong p1, + ulong p2, + long amount) { - this.P1 = p1; - this.P2 = p2; - this._botId = botId; - this.Amount = amount; + P1 = p1; + P2 = p2; + _botId = botId; + Amount = amount; _cs = cs; _timeoutTimer = new(async delegate - { - await _locker.WaitAsync(); - try { - if (CurrentState != State.Waiting) - return; - CurrentState = State.Ended; - await (OnEnded?.Invoke(this, Reason.Timeout)); - } - catch { } - finally - { - _locker.Release(); - } - }, null, TimeSpan.FromSeconds(15), TimeSpan.FromMilliseconds(-1)); + await _locker.WaitAsync(); + try + { + if (CurrentState != State.Waiting) + return; + CurrentState = State.Ended; + await OnEnded?.Invoke(this, Reason.Timeout); + } + catch { } + finally + { + _locker.Release(); + } + }, + null, + TimeSpan.FromSeconds(15), + TimeSpan.FromMilliseconds(-1)); } public async Task StartGame() @@ -78,16 +86,17 @@ public class RollDuelGame _locker.Release(); } - if(!await _cs.RemoveAsync(P1, "Roll Duel", Amount)) + if (!await _cs.RemoveAsync(P1, "Roll Duel", Amount)) { - await (OnEnded?.Invoke(this, Reason.NoFunds)); + await OnEnded?.Invoke(this, Reason.NoFunds); CurrentState = State.Ended; return; } - if(!await _cs.RemoveAsync(P2, "Roll Duel", Amount)) + + if (!await _cs.RemoveAsync(P2, "Roll Duel", Amount)) { await _cs.AddAsync(P1, "Roll Duel - refund", Amount); - await (OnEnded?.Invoke(this, Reason.NoFunds)); + await OnEnded?.Invoke(this, Reason.NoFunds); CurrentState = State.Ended; return; } @@ -101,26 +110,25 @@ public class RollDuelGame if (n1 != n2) { if (n1 > n2) - { - Winner = P1; - } + Winner = P1; else - { Winner = P2; - } var won = (long)(Amount * 2 * 0.98f); await _cs.AddAsync(Winner, "Roll Duel win", won); await _cs.AddAsync(_botId, "Roll Duel fee", (Amount * 2) - won); } - try { await (OnGameTick?.Invoke(this)); } catch { } + + try { await OnGameTick?.Invoke(this); } + catch { } + await Task.Delay(2500); if (n1 != n2) break; - } - while (true); + } while (true); + CurrentState = State.Ended; - await (OnEnded?.Invoke(this, Reason.Normal)); + await OnEnded?.Invoke(this, Reason.Normal); } } @@ -128,4 +136,4 @@ public struct RollDuelChallenge { public ulong Player1 { get; set; } public ulong Player2 { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Slot/SlotGame.cs b/src/NadekoBot/Modules/Gambling/Common/Slot/SlotGame.cs index aedbeec2a..f2000568a 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Slot/SlotGame.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Slot/SlotGame.cs @@ -3,27 +3,11 @@ namespace NadekoBot.Modules.Gambling.Common.Slot; public class SlotGame { - public class Result - { - public float Multiplier { get; } - public int[] Rolls { get; } - - public Result(float multiplier, int[] rolls) - { - Multiplier = multiplier; - Rolls = rolls; - } - } - private static readonly Random _rng = new NadekoRandom(); - public SlotGame() - { - } - public Result Spin() { - var rolls = new int[] { _rng.Next(0, 6), _rng.Next(0, 6), _rng.Next(0, 6) }; + var rolls = new[] { _rng.Next(0, 6), _rng.Next(0, 6), _rng.Next(0, 6) }; var multi = 0; if (rolls.All(x => x == 5)) @@ -37,4 +21,16 @@ public class SlotGame return new(multi, rolls); } -} + + public class Result + { + public float Multiplier { get; } + public int[] Rolls { get; } + + public Result(float multiplier, int[] rolls) + { + Multiplier = multiplier; + Rolls = rolls; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/SlotResponse.cs b/src/NadekoBot/Modules/Gambling/Common/SlotResponse.cs index b91d5bc39..6d1770652 100644 --- a/src/NadekoBot/Modules/Gambling/Common/SlotResponse.cs +++ b/src/NadekoBot/Modules/Gambling/Common/SlotResponse.cs @@ -7,4 +7,4 @@ public class SlotResponse public long Won { get; set; } public List Rolls { get; set; } = new(); public GamblingError Error { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Waifu/AffinityTitle.cs b/src/NadekoBot/Modules/Gambling/Common/Waifu/AffinityTitle.cs index 84b3a7b6d..9a566fb30 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Waifu/AffinityTitle.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Waifu/AffinityTitle.cs @@ -13,4 +13,4 @@ public enum AffinityTitle Sloot, Depraved, Harlot -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Waifu/ClaimTitle.cs b/src/NadekoBot/Modules/Gambling/Common/Waifu/ClaimTitle.cs index 03cebe23e..81f82446f 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Waifu/ClaimTitle.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Waifu/ClaimTitle.cs @@ -14,5 +14,5 @@ public enum ClaimTitle Veteran, Incubis, Harem_King, - Harem_God, -} + Harem_God +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Waifu/DivorceResult.cs b/src/NadekoBot/Modules/Gambling/Common/Waifu/DivorceResult.cs index 3ebfddbf3..ec2104ffb 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Waifu/DivorceResult.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Waifu/DivorceResult.cs @@ -7,4 +7,4 @@ public enum DivorceResult SucessWithPenalty, NotYourWife, Cooldown -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/Waifu/WaifuClaimResult.cs b/src/NadekoBot/Modules/Gambling/Common/Waifu/WaifuClaimResult.cs index fb4e14054..3f64c0e47 100644 --- a/src/NadekoBot/Modules/Gambling/Common/Waifu/WaifuClaimResult.cs +++ b/src/NadekoBot/Modules/Gambling/Common/Waifu/WaifuClaimResult.cs @@ -6,4 +6,4 @@ public enum WaifuClaimResult Success, NotEnoughFunds, InsufficientAmount -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/WheelOfFortune/WheelOfFortune.cs b/src/NadekoBot/Modules/Gambling/Common/WheelOfFortune/WheelOfFortune.cs index 6ea3259f4..cc3d3e07f 100644 --- a/src/NadekoBot/Modules/Gambling/Common/WheelOfFortune/WheelOfFortune.cs +++ b/src/NadekoBot/Modules/Gambling/Common/WheelOfFortune/WheelOfFortune.cs @@ -3,19 +3,17 @@ namespace NadekoBot.Modules.Gambling.Common.WheelOfFortune; public class WheelOfFortuneGame { - public class Result - { - public int Index { get; set; } - public long Amount { get; set; } - } - private readonly NadekoRandom _rng; private readonly ICurrencyService _cs; private readonly long _bet; private readonly GamblingConfig _config; private readonly ulong _userId; - public WheelOfFortuneGame(ulong userId, long bet, GamblingConfig config, ICurrencyService cs) + public WheelOfFortuneGame( + ulong userId, + long bet, + GamblingConfig config, + ICurrencyService cs) { _rng = new(); _cs = cs; @@ -31,12 +29,14 @@ public class WheelOfFortuneGame var amount = (long)(_bet * _config.WheelOfFortune.Multipliers[result]); if (amount > 0) - await _cs.AddAsync(_userId, "Wheel Of Fortune - won", amount, gamble: true); + await _cs.AddAsync(_userId, "Wheel Of Fortune - won", amount, true); - return new() - { - Index = result, - Amount = amount, - }; + return new() { Index = result, Amount = amount }; } -} + + public class Result + { + public int Index { get; set; } + public long Amount { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Connect4/Connect4.cs b/src/NadekoBot/Modules/Gambling/Connect4/Connect4.cs index a3614aa54..8e7cd63d2 100644 --- a/src/NadekoBot/Modules/Gambling/Connect4/Connect4.cs +++ b/src/NadekoBot/Modules/Gambling/Connect4/Connect4.cs @@ -6,53 +6,54 @@ namespace NadekoBot.Modules.Gambling.Common.Connect4; public sealed class Connect4Game : IDisposable { + public enum Field //temporary most likely + { + Empty, + P1, + P2 + } + public enum Phase { Joining, // waiting for second player to join P1Move, P2Move, - Ended, - } - - public enum Field //temporary most likely - { - Empty, - P1, - P2, + Ended } public enum Result { Draw, CurrentPlayerWon, - OtherPlayerWon, + OtherPlayerWon } public const int NumberOfColumns = 7; public const int NumberOfRows = 6; - public Phase CurrentPhase { get; private set; } = Phase.Joining; - - //state is bottom to top, left to right - private readonly Field[] _gameState = new Field[NumberOfRows * NumberOfColumns]; - private readonly (ulong UserId, string Username)?[] _players = new(ulong, string)?[2]; - - public ImmutableArray GameState => _gameState.ToImmutableArray(); - public ImmutableArray<(ulong UserId, string Username)?> Players => _players.ToImmutableArray(); - - public (ulong UserId, string Username) CurrentPlayer => CurrentPhase == Phase.P1Move - ? _players[0].Value - : _players[1].Value; - - public (ulong UserId, string Username) OtherPlayer => CurrentPhase == Phase.P2Move - ? _players[0].Value - : _players[1].Value; - //public event Func OnGameStarted; public event Func OnGameStateUpdated; public event Func OnGameFailedToStart; public event Func OnGameEnded; + public Phase CurrentPhase { get; private set; } = Phase.Joining; + + public ImmutableArray GameState + => _gameState.ToImmutableArray(); + + public ImmutableArray<(ulong UserId, string Username)?> Players + => _players.ToImmutableArray(); + + public (ulong UserId, string Username) CurrentPlayer + => CurrentPhase == Phase.P1Move ? _players[0].Value : _players[1].Value; + + public (ulong UserId, string Username) OtherPlayer + => CurrentPhase == Phase.P2Move ? _players[0].Value : _players[1].Value; + + //state is bottom to top, left to right + private readonly Field[] _gameState = new Field[NumberOfRows * NumberOfColumns]; + private readonly (ulong UserId, string Username)?[] _players = new (ulong, string)?[2]; + private readonly SemaphoreSlim _locker = new(1, 1); private readonly Options _options; private readonly ICurrencyService _cs; @@ -69,17 +70,18 @@ public sealed class Connect4Game : IDisposable * [ ][ ][ ][ ][ ][ ] */ - public Connect4Game(ulong userId, string userName, Options options, ICurrencyService cs) + public Connect4Game( + ulong userId, + string userName, + Options options, + ICurrencyService cs) { _players[0] = (userId, userName); _options = options; _cs = cs; _rng = new(); - for (var i = 0; i < NumberOfColumns * NumberOfRows; i++) - { - _gameState[i] = Field.Empty; - } + for (var i = 0; i < NumberOfColumns * NumberOfRows; i++) _gameState[i] = Field.Empty; } public void Initialize() @@ -97,7 +99,6 @@ public sealed class Connect4Game : IDisposable var __ = OnGameFailedToStart?.Invoke(this); CurrentPhase = Phase.Ended; await _cs.AddAsync(_players[0].Value.UserId, "Connect4-refund", _options.Bet, true); - return; } } finally { _locker.Release(); } @@ -127,18 +128,23 @@ public sealed class Connect4Game : IDisposable _players[0] = (userId, userName); } else //else join as a second player + { _players[1] = (userId, userName); + } CurrentPhase = Phase.P1Move; //start the game _playerTimeoutTimer = new(async state => - { - await _locker.WaitAsync(); - try { - EndGame(Result.OtherPlayerWon, OtherPlayer.UserId); - } - finally { _locker.Release(); } - }, null, TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer)); + await _locker.WaitAsync(); + try + { + EndGame(Result.OtherPlayerWon, OtherPlayer.UserId); + } + finally { _locker.Release(); } + }, + null, + TimeSpan.FromSeconds(_options.TurnTimer), + TimeSpan.FromSeconds(_options.TurnTimer)); var __ = OnGameStateUpdated?.Invoke(this); return true; @@ -167,13 +173,11 @@ public sealed class Connect4Game : IDisposable var start = NumberOfRows * inputCol; for (var i = start; i < start + NumberOfRows; i++) - { if (_gameState[i] == Field.Empty) { _gameState[i] = GetPlayerPiece(userId); break; } - } //check winnning condition // ok, i'll go from [0-2] in rows (and through all columns) and check upward if 4 are connected @@ -190,7 +194,6 @@ public sealed class Connect4Game : IDisposable var first = _gameState[i + (j * NumberOfRows)]; if (first != Field.Empty) - { for (var k = 1; k < 4; k++) { var next = _gameState[i + k + (j * NumberOfRows)]; @@ -201,9 +204,11 @@ public sealed class Connect4Game : IDisposable else continue; } - else break; + else + { + break; + } } - } } } @@ -220,7 +225,6 @@ public sealed class Connect4Game : IDisposable var first = _gameState[j + (i * NumberOfRows)]; if (first != Field.Empty) - { for (var k = 1; k < 4; k++) { var next = _gameState[j + ((i + k) * NumberOfRows)]; @@ -231,7 +235,6 @@ public sealed class Connect4Game : IDisposable continue; else break; } - } } } @@ -308,10 +311,7 @@ public sealed class Connect4Game : IDisposable } //check draw? if it's even possible - if (_gameState.All(x => x != Field.Empty)) - { - EndGame(Result.Draw, null); - } + if (_gameState.All(x => x != Field.Empty)) EndGame(Result.Draw, null); if (CurrentPhase != Phase.Ended) { @@ -322,6 +322,7 @@ public sealed class Connect4Game : IDisposable ResetTimer(); } + var _ = OnGameStateUpdated?.Invoke(this); return true; } @@ -329,7 +330,8 @@ public sealed class Connect4Game : IDisposable } private void ResetTimer() - => _playerTimeoutTimer.Change(TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer)); + => _playerTimeoutTimer.Change(TimeSpan.FromSeconds(_options.TurnTimer), + TimeSpan.FromSeconds(_options.TurnTimer)); private void EndGame(Result result, ulong? winId) { @@ -340,27 +342,25 @@ public sealed class Connect4Game : IDisposable if (result == Result.Draw) { - _cs.AddAsync(CurrentPlayer.UserId, "Connect4-draw", this._options.Bet, true); - _cs.AddAsync(OtherPlayer.UserId, "Connect4-draw", this._options.Bet, true); + _cs.AddAsync(CurrentPlayer.UserId, "Connect4-draw", _options.Bet, true); + _cs.AddAsync(OtherPlayer.UserId, "Connect4-draw", _options.Bet, true); return; } + if (winId != null) - _cs.AddAsync(winId.Value, "Connnect4-win", (long)(this._options.Bet * 1.98), true); + _cs.AddAsync(winId.Value, "Connnect4-win", (long)(_options.Bet * 1.98), true); } - private Field GetPlayerPiece(ulong userId) => _players[0].Value.UserId == userId - ? Field.P1 - : Field.P2; + private Field GetPlayerPiece(ulong userId) + => _players[0].Value.UserId == userId ? Field.P1 : Field.P2; //column is full if there are no empty fields private bool IsColumnFull(int column) { var start = NumberOfRows * column; for (var i = start; i < start + NumberOfRows; i++) - { if (_gameState[i] == Field.Empty) return false; - } return true; } @@ -375,6 +375,16 @@ public sealed class Connect4Game : IDisposable public class Options : INadekoCommandOptions { + [Option('t', + "turn-timer", + Required = false, + Default = 15, + HelpText = "Turn time in seconds. It has to be between 5 and 60. Default 15.")] + public int TurnTimer { get; set; } = 15; + + [Option('b', "bet", Required = false, Default = 0, HelpText = "Amount you bet. Default 0.")] + public int Bet { get; set; } + public void NormalizeOptions() { if (TurnTimer is < 5 or > 60) @@ -383,10 +393,5 @@ public sealed class Connect4Game : IDisposable if (Bet < 0) Bet = 0; } - - [Option('t', "turn-timer", Required = false, Default = 15, HelpText = "Turn time in seconds. It has to be between 5 and 60. Default 15.")] - public int TurnTimer { get; set; } = 15; - [Option('b', "bet", Required = false, Default = 0, HelpText = "Amount you bet. Default 0.")] - public int Bet { get; set; } = 0; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Connect4Commands.cs b/src/NadekoBot/Modules/Gambling/Connect4Commands.cs index 5805db166..b35d30289 100644 --- a/src/NadekoBot/Modules/Gambling/Connect4Commands.cs +++ b/src/NadekoBot/Modules/Gambling/Connect4Commands.cs @@ -11,9 +11,28 @@ public partial class Gambling [Group] public class Connect4Commands : GamblingSubmodule { + private static readonly string[] numbers = + { + ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:" + }; + + private int RepostCounter + { + get => _repostCounter; + set + { + if (value is < 0 or > 7) + _repostCounter = 0; + else _repostCounter = value; + } + } + private readonly DiscordSocketClient _client; private readonly ICurrencyService _cs; - private static readonly string[] numbers = new string[] { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:" }; + + private IUserMessage msg; + + private int _repostCounter; public Connect4Commands(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService gamb) : base(gamb) @@ -22,7 +41,8 @@ public partial class Gambling _cs = cs; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [NadekoOptionsAttribute(typeof(Connect4Game.Options))] public async Task Connect4(params string[] args) @@ -45,7 +65,6 @@ public partial class Gambling } if (options.Bet > 0) - { if (!await _cs.RemoveAsync(ctx.User.Id, "Connect4-bet", options.Bet, true)) { await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); @@ -53,7 +72,6 @@ public partial class Gambling game.Dispose(); return; } - } game.OnGameStateUpdated += Game_OnGameStateUpdated; game.OnGameFailedToStart += Game_OnGameFailedToStart; @@ -62,13 +80,9 @@ public partial class Gambling game.Initialize(); if (options.Bet == 0) - { await ReplyConfirmLocalizedAsync(strs.connect4_created); - } else - { await ReplyErrorLocalizedAsync(strs.connect4_created_bet(options.Bet + CurrencySign)); - } Task _client_MessageReceived(SocketMessage arg) { @@ -78,22 +92,20 @@ public partial class Gambling var _ = Task.Run(async () => { var success = false; - if (int.TryParse(arg.Content, out var col)) - { - success = await game.Input(arg.Author.Id, col); - } + if (int.TryParse(arg.Content, out var col)) success = await game.Input(arg.Author.Id, col); if (success) - try { await arg.DeleteAsync(); } catch { } + { + try { await arg.DeleteAsync(); } + catch { } + } else { - if (game.CurrentPhase is Connect4Game.Phase.Joining or Connect4Game.Phase.Ended) - { - return; - } + if (game.CurrentPhase is Connect4Game.Phase.Joining or Connect4Game.Phase.Ended) return; RepostCounter++; if (RepostCounter == 0) - try { msg = await ctx.Channel.SendMessageAsync("", embed: (Embed)msg.Embeds.First()); } catch { } + try { msg = await ctx.Channel.SendMessageAsync("", embed: (Embed)msg.Embeds.First()); } + catch { } } }); return Task.CompletedTask; @@ -106,6 +118,7 @@ public partial class Gambling _client.MessageReceived -= _client_MessageReceived; toDispose.Dispose(); } + return ErrorLocalizedAsync(strs.connect4_failed_to_start); } @@ -119,44 +132,28 @@ public partial class Gambling string title; if (result == Connect4Game.Result.CurrentPlayerWon) - { - title = GetText(strs.connect4_won(Format.Bold(arg.CurrentPlayer.Username), Format.Bold(arg.OtherPlayer.Username))); - } + title = GetText(strs.connect4_won(Format.Bold(arg.CurrentPlayer.Username), + Format.Bold(arg.OtherPlayer.Username))); else if (result == Connect4Game.Result.OtherPlayerWon) - { - title = GetText(strs.connect4_won(Format.Bold(arg.OtherPlayer.Username), Format.Bold(arg.CurrentPlayer.Username))); - } + title = GetText(strs.connect4_won(Format.Bold(arg.OtherPlayer.Username), + Format.Bold(arg.CurrentPlayer.Username))); else title = GetText(strs.connect4_draw); return msg.ModifyAsync(x => x.Embed = _eb.Create() - .WithTitle(title) - .WithDescription(GetGameStateText(game)) - .WithOkColor() - .Build()); - } - } - - private IUserMessage msg; - - private int _repostCounter = 0; - private int RepostCounter - { - get => _repostCounter; - set - { - if (value is < 0 or > 7) - _repostCounter = 0; - else _repostCounter = value; + .WithTitle(title) + .WithDescription(GetGameStateText(game)) + .WithOkColor() + .Build()); } } private async Task Game_OnGameStateUpdated(Connect4Game game) { var embed = _eb.Create() - .WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}") - .WithDescription(GetGameStateText(game)) - .WithOkColor(); + .WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}") + .WithDescription(GetGameStateText(game)) + .WithOkColor(); if (msg is null) @@ -185,14 +182,12 @@ public partial class Gambling else sb.Append("🔵"); //blue circle } + sb.AppendLine(); } - for (var i = 0; i < Connect4Game.NumberOfColumns; i++) - { - sb.Append(numbers[i]); - } + for (var i = 0; i < Connect4Game.NumberOfColumns; i++) sb.Append(numbers[i]); return sb.ToString(); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/CurrencyEventsCommands.cs b/src/NadekoBot/Modules/Gambling/CurrencyEventsCommands.cs index 4f43db3e8..808103cf3 100644 --- a/src/NadekoBot/Modules/Gambling/CurrencyEventsCommands.cs +++ b/src/NadekoBot/Modules/Gambling/CurrencyEventsCommands.cs @@ -1,8 +1,8 @@ #nullable disable -using NadekoBot.Modules.Gambling.Services; -using NadekoBot.Modules.Gambling.Common.Events; -using NadekoBot.Services.Database.Models; using NadekoBot.Modules.Gambling.Common; +using NadekoBot.Modules.Gambling.Common.Events; +using NadekoBot.Modules.Gambling.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Gambling; @@ -11,65 +11,51 @@ public partial class Gambling [Group] public class CurrencyEventsCommands : GamblingSubmodule { - public CurrencyEventsCommands(GamblingConfigService gamblingConf) : base(gamblingConf) + public CurrencyEventsCommands(GamblingConfigService gamblingConf) + : base(gamblingConf) { } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [NadekoOptionsAttribute(typeof(EventOptions))] [OwnerOnly] public async Task EventStart(CurrencyEvent.Type ev, params string[] options) { var (opts, _) = OptionsParser.ParseFrom(new EventOptions(), options); - if (!await _service.TryCreateEventAsync(ctx.Guild.Id, - ctx.Channel.Id, - ev, - opts, - GetEmbed)) - { + if (!await _service.TryCreateEventAsync(ctx.Guild.Id, ctx.Channel.Id, ev, opts, GetEmbed)) await ReplyErrorLocalizedAsync(strs.start_event_fail); - } } private IEmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot) => type switch { CurrencyEvent.Type.Reaction => _eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.event_title(type.ToString()))) - .WithDescription(GetReactionDescription(opts.Amount, currentPot)) - .WithFooter(GetText(strs.event_duration_footer(opts.Hours))), + .WithOkColor() + .WithTitle(GetText(strs.event_title(type.ToString()))) + .WithDescription(GetReactionDescription(opts.Amount, currentPot)) + .WithFooter(GetText(strs.event_duration_footer(opts.Hours))), CurrencyEvent.Type.GameStatus => _eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.event_title(type.ToString()))) - .WithDescription(GetGameStatusDescription(opts.Amount, currentPot)) - .WithFooter(GetText(strs.event_duration_footer(opts.Hours))), + .WithOkColor() + .WithTitle(GetText(strs.event_title(type.ToString()))) + .WithDescription(GetGameStatusDescription(opts.Amount, currentPot)) + .WithFooter(GetText(strs.event_duration_footer(opts.Hours))), _ => throw new ArgumentOutOfRangeException(nameof(type)) }; private string GetReactionDescription(long amount, long potSize) { - var potSizeStr = Format.Bold(potSize == 0 - ? "∞" + CurrencySign - : potSize + CurrencySign); - - return GetText(strs.new_reaction_event( - CurrencySign, - Format.Bold(amount + CurrencySign), - potSizeStr)); + var potSizeStr = Format.Bold(potSize == 0 ? "∞" + CurrencySign : potSize + CurrencySign); + + return GetText(strs.new_reaction_event(CurrencySign, Format.Bold(amount + CurrencySign), potSizeStr)); } private string GetGameStatusDescription(long amount, long potSize) { - var potSizeStr = Format.Bold(potSize == 0 - ? "∞" + CurrencySign - : potSize + CurrencySign); - - return GetText(strs.new_gamestatus_event( - CurrencySign, - Format.Bold(amount + CurrencySign), - potSizeStr)); + var potSizeStr = Format.Bold(potSize == 0 ? "∞" + CurrencySign : potSize + CurrencySign); + + return GetText(strs.new_gamestatus_event(CurrencySign, Format.Bold(amount + CurrencySign), potSizeStr)); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/CurrencyRaffleCommands.cs b/src/NadekoBot/Modules/Gambling/CurrencyRaffleCommands.cs index 7ee972b2d..06ed0eba6 100644 --- a/src/NadekoBot/Modules/Gambling/CurrencyRaffleCommands.cs +++ b/src/NadekoBot/Modules/Gambling/CurrencyRaffleCommands.cs @@ -1,6 +1,6 @@ #nullable disable -using NadekoBot.Modules.Gambling.Services; using NadekoBot.Modules.Gambling.Common; +using NadekoBot.Modules.Gambling.Services; namespace NadekoBot.Modules.Gambling; @@ -10,29 +10,35 @@ public partial class Gambling { public enum Mixed { Mixed } - public CurrencyRaffleCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService) + public CurrencyRaffleCommands(GamblingConfigService gamblingConfService) + : base(gamblingConfService) { } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] - public Task RaffleCur(Mixed _, ShmartNumber amount) => - RaffleCur(amount, true); + public Task RaffleCur(Mixed _, ShmartNumber amount) + => RaffleCur(amount, true); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] public async Task RaffleCur(ShmartNumber amount, bool mixed = false) { if (!await CheckBetMandatory(amount)) return; + async Task OnEnded(IUser arg, long won) { - await SendConfirmAsync(GetText(strs.rafflecur_ended(CurrencyName, Format.Bold(arg.ToString()), won + CurrencySign))); + await SendConfirmAsync(GetText(strs.rafflecur_ended(CurrencyName, + Format.Bold(arg.ToString()), + won + CurrencySign))); } - var res = await _service.JoinOrCreateGame(ctx.Channel.Id, - ctx.User, amount, mixed, OnEnded); + + var res = await _service.JoinOrCreateGame(ctx.Channel.Id, ctx.User, amount, mixed, OnEnded); if (res.Item1 != null) { @@ -49,4 +55,4 @@ public partial class Gambling } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/DiceRollCommands.cs b/src/NadekoBot/Modules/Gambling/DiceRollCommands.cs index a61fa2e93..04d84cab5 100644 --- a/src/NadekoBot/Modules/Gambling/DiceRollCommands.cs +++ b/src/NadekoBot/Modules/Gambling/DiceRollCommands.cs @@ -11,7 +11,9 @@ public partial class Gambling [Group] public class DiceRollCommands : NadekoSubmodule { - private static readonly Regex dndRegex = new(@"^(?\d+)d(?\d+)(?:\+(?\d+))?(?:\-(?\d+))?$", RegexOptions.Compiled); + private static readonly Regex dndRegex = new(@"^(?\d+)d(?\d+)(?:\+(?\d+))?(?:\-(?\d+))?$", + RegexOptions.Compiled); + private static readonly Regex fudgeRegex = new(@"^(?\d+)d(?:F|f)$", RegexOptions.Compiled); private static readonly char[] _fateRolls = { '-', ' ', '+' }; @@ -20,7 +22,8 @@ public partial class Gambling public DiceRollCommands(IDataCache data) => _images = data.LocalImages; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Roll() { var rng = new NadekoRandom(); @@ -38,23 +41,27 @@ public partial class Gambling Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString())))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(1)] public async Task Roll(int num) => await InternalRoll(num, true); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(1)] public async Task Rolluo(int num = 1) => await InternalRoll(num, false); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(0)] public async Task Roll(string arg) => await InternallDndRoll(arg, true); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(0)] public async Task Rolluo(string arg) => await InternallDndRoll(arg, false); @@ -81,65 +88,66 @@ public partial class Gambling toInsert = 0; else if (randomNumber != 1) for (var j = 0; j < dice.Count; j++) - { if (values[j] < randomNumber) { toInsert = j; break; } - } } else { toInsert = dice.Count; } + dice.Insert(toInsert, GetDice(randomNumber)); values.Insert(toInsert, randomNumber); } using var bitmap = dice.Merge(out var format); await using var ms = bitmap.ToStream(format); - foreach (var d in dice) - { - d.Dispose(); - } + foreach (var d in dice) d.Dispose(); - await ctx.Channel.SendFileAsync(ms, $"dice.{format.FileExtensions.First()}", - Format.Bold(ctx.User.ToString()) + " " + - GetText(strs.dice_rolled_num(Format.Bold(values.Count.ToString()))) + - " " + GetText(strs.total_average( - Format.Bold(values.Sum().ToString()), + await ctx.Channel.SendFileAsync(ms, + $"dice.{format.FileExtensions.First()}", + Format.Bold(ctx.User.ToString()) + + " " + + GetText(strs.dice_rolled_num(Format.Bold(values.Count.ToString()))) + + " " + + GetText(strs.total_average(Format.Bold(values.Sum().ToString()), Format.Bold((values.Sum() / (1.0f * values.Count)).ToString("N2"))))); } private async Task InternallDndRoll(string arg, bool ordered) { Match match; - if ((match = fudgeRegex.Match(arg)).Length != 0 && - int.TryParse(match.Groups["n1"].ToString(), out var n1) && - n1 is > 0 and < 500) + if ((match = fudgeRegex.Match(arg)).Length != 0 + && int.TryParse(match.Groups["n1"].ToString(), out var n1) + && n1 is > 0 and < 500) { var rng = new NadekoRandom(); var rolls = new List(); - for (var i = 0; i < n1; i++) - { - rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]); - } + for (var i = 0; i < n1; i++) rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]); var embed = _eb.Create() - .WithOkColor() - .WithDescription(ctx.User.Mention + " " + GetText(strs.dice_rolled_num(Format.Bold(n1.ToString())))) - .AddField(Format.Bold("Result"), string.Join(" ", rolls.Select(c => Format.Code($"[{c}]")))); - + .WithOkColor() + .WithDescription(ctx.User.Mention + + " " + + GetText(strs.dice_rolled_num(Format.Bold(n1.ToString())))) + .AddField(Format.Bold("Result"), + string.Join(" ", rolls.Select(c => Format.Code($"[{c}]")))); + await ctx.Channel.EmbedAsync(embed); } else if ((match = dndRegex.Match(arg)).Length != 0) { var rng = new NadekoRandom(); - if (int.TryParse(match.Groups["n1"].ToString(), out n1) && - int.TryParse(match.Groups["n2"].ToString(), out var n2) && - n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0) + if (int.TryParse(match.Groups["n1"].ToString(), out n1) + && int.TryParse(match.Groups["n2"].ToString(), out var n2) + && n1 <= 50 + && n2 <= 100000 + && n1 > 0 + && n2 > 0) { if (!int.TryParse(match.Groups["add"].Value, out var add)) add = 0; @@ -147,39 +155,39 @@ public partial class Gambling sub = 0; var arr = new int[n1]; - for (var i = 0; i < n1; i++) - { - arr[i] = rng.Next(1, n2 + 1); - } + for (var i = 0; i < n1; i++) arr[i] = rng.Next(1, n2 + 1); var sum = arr.Sum(); - var embed = _eb.Create().WithOkColor() - .WithDescription(ctx.User.Mention + " " + GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`"))) - .AddField(Format.Bold("Rolls"), string.Join(" ", - (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => - Format.Code(x.ToString())))) - .AddField(Format.Bold("Sum"), - sum + " + " + add + " - " + sub + " = " + (sum + add - sub)); + var embed = _eb.Create() + .WithOkColor() + .WithDescription(ctx.User.Mention + + " " + + GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`"))) + .AddField(Format.Bold("Rolls"), + string.Join(" ", + (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x + => Format.Code(x.ToString())))) + .AddField(Format.Bold("Sum"), + sum + " + " + add + " - " + sub + " = " + (sum + add - sub)); await ctx.Channel.EmbedAsync(embed); } } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task NRoll([Leftover] string range) { int rolled; if (range.Contains("-")) { - var arr = range.Split('-') - .Take(2) - .Select(int.Parse) - .ToArray(); + var arr = range.Split('-').Take(2).Select(int.Parse).ToArray(); if (arr[0] > arr[1]) { await ReplyErrorLocalizedAsync(strs.second_larger_than_first); return; } + rolled = new NadekoRandom().Next(arr[0], arr[1] + 1); } else @@ -202,7 +210,8 @@ public partial class Gambling using var imgZero = Image.Load(images[0]); return new[] { imgOne, imgZero }.Merge(); } + return Image.Load(_images.Dice[num]); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/DrawCommands.cs b/src/NadekoBot/Modules/Gambling/DrawCommands.cs index 75713cd8f..b35ee3268 100644 --- a/src/NadekoBot/Modules/Gambling/DrawCommands.cs +++ b/src/NadekoBot/Modules/Gambling/DrawCommands.cs @@ -1,8 +1,8 @@ #nullable disable using NadekoBot.Modules.Gambling.Common; -using Image = SixLabors.ImageSharp.Image; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; +using Image = SixLabors.ImageSharp.Image; namespace NadekoBot.Modules.Gambling; @@ -37,18 +37,17 @@ public partial class Gambling { // ignored } + break; } + var currentCard = cards.Draw(); cardObjects.Add(currentCard); images.Add(Image.Load(_images.GetCard(currentCard.ToString().ToLowerInvariant().Replace(' ', '_')))); } using var img = images.Merge(); - foreach (var i in images) - { - i.Dispose(); - } + foreach (var i in images) i.Dispose(); var toSend = $"{Format.Bold(ctx.User.ToString())}"; if (cardObjects.Count == 5) @@ -60,7 +59,8 @@ public partial class Gambling return (img.ToStream(), toSend); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Draw(int num = 1) { @@ -76,7 +76,8 @@ public partial class Gambling } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task DrawNew(int num = 1) { if (num < 1) @@ -91,7 +92,8 @@ public partial class Gambling } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task DeckShuffle() { @@ -108,4 +110,4 @@ public partial class Gambling await ReplyConfirmLocalizedAsync(strs.deck_reshuffled); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/FlipCoinCommands.cs b/src/NadekoBot/Modules/Gambling/FlipCoinCommands.cs index ac6b90e80..1724c7633 100644 --- a/src/NadekoBot/Modules/Gambling/FlipCoinCommands.cs +++ b/src/NadekoBot/Modules/Gambling/FlipCoinCommands.cs @@ -12,17 +12,29 @@ public partial class Gambling [Group] public class FlipCoinCommands : GamblingSubmodule { + public enum BetFlipGuess + { + H = 1, + Head = 1, + Heads = 1, + T = 2, + Tail = 2, + Tails = 2 + } + + private static readonly NadekoRandom rng = new(); private readonly IImageCache _images; private readonly ICurrencyService _cs; - private static readonly NadekoRandom rng = new(); - public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss) : base(gss) + public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss) + : base(gss) { _images = data.LocalImages; _cs = cs; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Flip(int count = 1) { if (count is > 10 or < 1) @@ -30,6 +42,7 @@ public partial class Gambling await ReplyErrorLocalizedAsync(strs.flip_invalid(10)); return; } + var headCount = 0; var tailCount = 0; var imgs = new Image[count]; @@ -51,40 +64,31 @@ public partial class Gambling using var img = imgs.Merge(out var format); await using var stream = img.ToStream(format); - foreach (var i in imgs) - { - i.Dispose(); - } + foreach (var i in imgs) i.Dispose(); var msg = count != 1 ? Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_results(count, headCount, tailCount)) - : Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flipped(headCount > 0 - ? Format.Bold(GetText(strs.heads)) - : Format.Bold(GetText(strs.tails)))); + : Format.Bold(ctx.User.ToString()) + + " " + + GetText(strs.flipped(headCount > 0 + ? Format.Bold(GetText(strs.heads)) + : Format.Bold(GetText(strs.tails)))); await ctx.Channel.SendFileAsync(stream, $"{count} coins.{format.FileExtensions.First()}", msg); } - public enum BetFlipGuess - { - H = 1, - Head = 1, - Heads = 1, - T = 2, - Tail = 2, - Tails = 2 - } - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Betflip(ShmartNumber amount, BetFlipGuess guess) { if (!await CheckBetMandatory(amount) || amount == 1) return; - var removed = await _cs.RemoveAsync(ctx.User, "Betflip Gamble", amount, false, gamble: true); + var removed = await _cs.RemoveAsync(ctx.User, "Betflip Gamble", amount, false, true); if (!removed) { await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); return; } + BetFlipGuess result; Uri imageToSend; var coins = _images.ImageUrls.Coins; @@ -104,17 +108,17 @@ public partial class Gambling { var toWin = (long)(amount * _config.BetFlip.Multiplier); str = Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_guess(toWin + CurrencySign)); - await _cs.AddAsync(ctx.User, "Betflip Gamble", toWin, false, gamble: true); + await _cs.AddAsync(ctx.User, "Betflip Gamble", toWin, false, true); } else { - str = ctx.User.ToString() + " " + GetText(strs.better_luck); + str = ctx.User + " " + GetText(strs.better_luck); } await ctx.Channel.EmbedAsync(_eb.Create() - .WithDescription(str) - .WithOkColor() - .WithImageUrl(imageToSend.ToString())); + .WithDescription(str) + .WithOkColor() + .WithImageUrl(imageToSend.ToString())); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Gambling.cs b/src/NadekoBot/Modules/Gambling/Gambling.cs index 630b2dd8a..7a6c3404c 100644 --- a/src/NadekoBot/Modules/Gambling/Gambling.cs +++ b/src/NadekoBot/Modules/Gambling/Gambling.cs @@ -1,16 +1,35 @@ #nullable disable -using NadekoBot.Modules.Gambling.Common; +using NadekoBot.Db; using NadekoBot.Db.Models; +using NadekoBot.Modules.Gambling.Common; using NadekoBot.Modules.Gambling.Services; +using NadekoBot.Services.Database.Models; using System.Globalization; using System.Numerics; -using NadekoBot.Services.Database.Models; -using NadekoBot.Db; namespace NadekoBot.Modules.Gambling; public partial class Gambling : GamblingModule { + public enum RpsPick + { + R = 0, + Rock = 0, + Rocket = 0, + P = 1, + Paper = 1, + Paperclip = 1, + S = 2, + Scissors = 2 + } + + public enum RpsResult + { + Win, + Loss, + Draw + } + private readonly DbService _db; private readonly ICurrencyService _cs; private readonly IDataCache _cache; @@ -19,9 +38,16 @@ public partial class Gambling : GamblingModule private readonly DownloadTracker _tracker; private readonly GamblingConfigService _configService; - public Gambling(DbService db, ICurrencyService currency, - IDataCache cache, DiscordSocketClient client, - DownloadTracker tracker, GamblingConfigService configService) : base(configService) + private IUserMessage rdMsg; + + public Gambling( + DbService db, + ICurrencyService currency, + IDataCache cache, + DiscordSocketClient client, + DownloadTracker tracker, + GamblingConfigService configService) + : base(configService) { _db = db; _cs = currency; @@ -48,30 +74,33 @@ public partial class Gambling : GamblingModule return n(uow.DiscordUser.GetUserCurrency(id)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Economy() { var ec = _service.GetEconomy(); decimal onePercent = 0; if (ec.Cash > 0) - { - onePercent = ec.OnePercent / (ec.Cash-ec.Bot); // This stops the top 1% from owning more than 100% of the money - // [21:03] Bob Page: Kinda remids me of US economy - } + onePercent = + ec.OnePercent / (ec.Cash - ec.Bot); // This stops the top 1% from owning more than 100% of the money + // [21:03] Bob Page: Kinda remids me of US economy var embed = _eb.Create() - .WithTitle(GetText(strs.economy_state)) - .AddField(GetText(strs.currency_owned), ((BigInteger)(ec.Cash - ec.Bot)).ToString("N", Culture) + CurrencySign) - .AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%") - .AddField(GetText(strs.currency_planted), (BigInteger)ec.Planted) - .AddField(GetText(strs.owned_waifus_total), (BigInteger)ec.Waifus + CurrencySign) - .AddField(GetText(strs.bot_currency), n(ec.Bot)) - .AddField(GetText(strs.total), ((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", Culture) + CurrencySign) - .WithOkColor(); + .WithTitle(GetText(strs.economy_state)) + .AddField(GetText(strs.currency_owned), + ((BigInteger)(ec.Cash - ec.Bot)).ToString("N", Culture) + CurrencySign) + .AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%") + .AddField(GetText(strs.currency_planted), (BigInteger)ec.Planted) + .AddField(GetText(strs.owned_waifus_total), (BigInteger)ec.Waifus + CurrencySign) + .AddField(GetText(strs.bot_currency), n(ec.Bot)) + .AddField(GetText(strs.total), + ((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", Culture) + CurrencySign) + .WithOkColor(); // ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Timely() { var val = _config.Timely.Amount; @@ -94,7 +123,8 @@ public partial class Gambling : GamblingModule await ReplyConfirmLocalizedAsync(strs.timely(n(val), period)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task TimelyReset() { @@ -102,26 +132,28 @@ public partial class Gambling : GamblingModule await ReplyConfirmLocalizedAsync(strs.timely_reset); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task TimelySet(int amount, int period = 24) { if (amount < 0 || period < 0) return; - + _configService.ModifyConfig(gs => { gs.Timely.Amount = amount; gs.Timely.Cooldown = period; }); - + if (amount == 0) await ReplyConfirmLocalizedAsync(strs.timely_set_none); else await ReplyConfirmLocalizedAsync(strs.timely_set(Format.Bold(n(amount)), Format.Bold(period.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Raffle([Leftover] IRole role = null) { @@ -129,15 +161,15 @@ public partial class Gambling : GamblingModule var members = (await role.GetMembersAsync()).Where(u => u.Status != UserStatus.Offline); var membersArray = members as IUser[] ?? members.ToArray(); - if (membersArray.Length == 0) - { - return; - } + if (membersArray.Length == 0) return; var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)]; - await SendConfirmAsync("🎟 " + GetText(strs.raffled_user), $"**{usr.Username}#{usr.Discriminator}**", footer: $"ID: {usr.Id}"); + await SendConfirmAsync("🎟 " + GetText(strs.raffled_user), + $"**{usr.Username}#{usr.Discriminator}**", + footer: $"ID: {usr.Id}"); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task RaffleAny([Leftover] IRole role = null) { @@ -145,30 +177,32 @@ public partial class Gambling : GamblingModule var members = await role.GetMembersAsync(); var membersArray = members as IUser[] ?? members.ToArray(); - if (membersArray.Length == 0) - { - return; - } + if (membersArray.Length == 0) return; var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)]; - await SendConfirmAsync("🎟 " + GetText(strs.raffled_user), $"**{usr.Username}#{usr.Discriminator}**", footer: $"ID: {usr.Id}"); + await SendConfirmAsync("🎟 " + GetText(strs.raffled_user), + $"**{usr.Username}#{usr.Discriminator}**", + footer: $"ID: {usr.Id}"); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(2)] - public Task CurrencyTransactions(int page = 1) => - InternalCurrencyTransactions(ctx.User.Id, page); + public Task CurrencyTransactions(int page = 1) + => InternalCurrencyTransactions(ctx.User.Id, page); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] [Priority(0)] - public Task CurrencyTransactions([Leftover] IUser usr) => - InternalCurrencyTransactions(usr.Id, 1); + public Task CurrencyTransactions([Leftover] IUser usr) + => InternalCurrencyTransactions(usr.Id, 1); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] [Priority(1)] - public Task CurrencyTransactions(IUser usr, int page) => - InternalCurrencyTransactions(usr.Id, page); + public Task CurrencyTransactions(IUser usr, int page) + => InternalCurrencyTransactions(usr.Id, page); private async Task InternalCurrencyTransactions(ulong userId, int page) { @@ -182,9 +216,9 @@ public partial class Gambling : GamblingModule } var embed = _eb.Create() - .WithTitle(GetText(strs.transactions( - ((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString() ?? $"{userId}"))) - .WithOkColor(); + .WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString() + ?? $"{userId}"))) + .WithOkColor(); var desc = string.Empty; foreach (var tr in trs) @@ -199,12 +233,14 @@ public partial class Gambling : GamblingModule await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(0)] public async Task Cash(ulong userId) => await ReplyConfirmLocalizedAsync(strs.has(Format.Code(userId.ToString()), $"{GetCurrency(userId)}")); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(1)] public async Task Cash([Leftover] IUser user = null) { @@ -212,44 +248,51 @@ public partial class Gambling : GamblingModule await ConfirmLocalizedAsync(strs.has(Format.Bold(user.ToString()), $"{GetCurrency(user.Id)}")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] public async Task Give(ShmartNumber amount, IGuildUser receiver, [Leftover] string msg = null) { if (amount <= 0 || ctx.User.Id == receiver.Id || receiver.IsBot) return; - var success = await _cs.RemoveAsync((IGuildUser)ctx.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount, false); + var success = + await _cs.RemoveAsync((IGuildUser)ctx.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount); if (!success) { await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); return; } + await _cs.AddAsync(receiver, $"Gift from {ctx.User.Username} ({ctx.User.Id}) - {msg}.", amount, true); await ReplyConfirmLocalizedAsync(strs.gifted(n(amount), Format.Bold(receiver.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] public Task Give(ShmartNumber amount, [Leftover] IGuildUser receiver) => Give(amount, receiver, null); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] [Priority(0)] - public Task Award(long amount, IGuildUser usr, [Leftover] string msg) => - Award(amount, usr.Id, msg); + public Task Award(long amount, IGuildUser usr, [Leftover] string msg) + => Award(amount, usr.Id, msg); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] [Priority(1)] - public Task Award(long amount, [Leftover] IGuildUser usr) => - Award(amount, usr.Id); + public Task Award(long amount, [Leftover] IGuildUser usr) + => Award(amount, usr.Id); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] [Priority(2)] public async Task Award(long amount, ulong usrId, [Leftover] string msg = null) @@ -259,7 +302,7 @@ public partial class Gambling : GamblingModule var usr = await ((DiscordSocketClient)Context.Client).Rest.GetUserAsync(usrId); - if(usr is null) + if (usr is null) { await ReplyErrorLocalizedAsync(strs.user_not_found); return; @@ -272,28 +315,27 @@ public partial class Gambling : GamblingModule await ReplyConfirmLocalizedAsync(strs.awarded(n(amount), $"<@{usrId}>")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] [Priority(3)] public async Task Award(long amount, [Leftover] IRole role) { - var users = (await ctx.Guild.GetUsersAsync()) - .Where(u => u.GetRoles().Contains(role)) - .ToList(); + var users = (await ctx.Guild.GetUsersAsync()).Where(u => u.GetRoles().Contains(role)).ToList(); await _cs.AddBulkAsync(users.Select(x => x.Id), - users.Select(x => $"Awarded by bot owner to **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"), - users.Select(x => amount), - gamble: true); + users.Select(x => $"Awarded by bot owner to **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"), + users.Select(x => amount), + true); - await ReplyConfirmLocalizedAsync(strs.mass_award( - n(amount), + await ReplyConfirmLocalizedAsync(strs.mass_award(n(amount), Format.Bold(users.Count.ToString()), Format.Bold(role.Name))); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] [Priority(0)] @@ -302,17 +344,17 @@ public partial class Gambling : GamblingModule var users = (await role.GetMembersAsync()).ToList(); await _cs.RemoveBulkAsync(users.Select(x => x.Id), - users.Select(x => $"Taken by bot owner from **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"), - users.Select(x => amount), - gamble: true); + users.Select(x => $"Taken by bot owner from **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"), + users.Select(x => amount), + true); - await ReplyConfirmLocalizedAsync(strs.mass_take( - n(amount), + await ReplyConfirmLocalizedAsync(strs.mass_take(n(amount), Format.Bold(users.Count.ToString()), Format.Bold(role.Name))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] [Priority(1)] @@ -321,7 +363,9 @@ public partial class Gambling : GamblingModule if (amount <= 0) return; - if (await _cs.RemoveAsync(user, $"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})", amount, + if (await _cs.RemoveAsync(user, + $"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})", + amount, gamble: ctx.Client.CurrentUser.Id != user.Id)) await ReplyConfirmLocalizedAsync(strs.take(n(amount), Format.Bold(user.ToString()))); else @@ -329,23 +373,25 @@ public partial class Gambling : GamblingModule } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task Take(long amount, [Leftover] ulong usrId) { if (amount <= 0) return; - if (await _cs.RemoveAsync(usrId, $"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})", amount, - gamble: ctx.Client.CurrentUser.Id != usrId)) + if (await _cs.RemoveAsync(usrId, + $"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})", + amount, + ctx.Client.CurrentUser.Id != usrId)) await ReplyConfirmLocalizedAsync(strs.take(n(amount), $"<@{usrId}>")); else await ReplyErrorLocalizedAsync(strs.take_fail(n(amount), Format.Code(usrId.ToString()), CurrencySign)); } - private IUserMessage rdMsg = null; - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task RollDuel(IUser u) { @@ -354,13 +400,11 @@ public partial class Gambling : GamblingModule //since the challenge is created by another user, we need to reverse the ids //if it gets removed, means challenge is accepted - if (_service.Duels.TryRemove((ctx.User.Id, u.Id), out var game)) - { - await game.StartGame(); - } + if (_service.Duels.TryRemove((ctx.User.Id, u.Id), out var game)) await game.StartGame(); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task RollDuel(ShmartNumber amount, IUser u) { @@ -370,9 +414,7 @@ public partial class Gambling : GamblingModule if (amount <= 0) return; - var embed = _eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.roll_duel)); + var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.roll_duel)); var description = string.Empty; @@ -381,22 +423,18 @@ public partial class Gambling : GamblingModule if (_service.Duels.TryGetValue((ctx.User.Id, u.Id), out var other)) { if (other.Amount != amount) - { await ReplyErrorLocalizedAsync(strs.roll_duel_already_challenged); - } else - { await RollDuel(u); - } return; } + if (_service.Duels.TryAdd((u.Id, ctx.User.Id), game)) { game.OnGameTick += Game_OnGameTick; game.OnEnded += Game_OnEnded; - await ReplyConfirmLocalizedAsync(strs.roll_duel_challenge( - Format.Bold(ctx.User.ToString()), + await ReplyConfirmLocalizedAsync(strs.roll_duel_challenge(Format.Bold(ctx.User.ToString()), Format.Bold(u.ToString()), Format.Bold(n(amount)))); } @@ -411,16 +449,12 @@ public partial class Gambling : GamblingModule embed = embed.WithDescription(description); if (rdMsg is null) - { rdMsg = await ctx.Channel.EmbedAsync(embed); - } else - { await rdMsg.ModifyAsync(x => { x.Embed = embed.Build(); }); - } } async Task Game_OnEnded(RollDuelGame rdGame, RollDuelGame.Reason reason) @@ -429,13 +463,11 @@ public partial class Gambling : GamblingModule { if (reason == RollDuelGame.Reason.Normal) { - var winner = rdGame.Winner == rdGame.P1 - ? ctx.User - : u; + var winner = rdGame.Winner == rdGame.P1 ? ctx.User : u; description += $"\n**{winner}** Won {n((long)(rdGame.Amount * 2 * 0.98))}"; embed = embed.WithDescription(description); - + await rdMsg.ModifyAsync(x => x.Embed = embed.Build()); } else if (reason == RollDuelGame.Reason.Timeout) @@ -459,13 +491,13 @@ public partial class Gambling : GamblingModule if (!await CheckBetMandatory(amount)) return; - if (!await _cs.RemoveAsync(ctx.User, "Betroll Gamble", amount, false, gamble: true)) + if (!await _cs.RemoveAsync(ctx.User, "Betroll Gamble", amount, false, true)) { await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); return; } - var br = new Betroll(base._config.BetRoll); + var br = new Betroll(_config.BetRoll); var result = br.Roll(); @@ -474,31 +506,31 @@ public partial class Gambling : GamblingModule if (result.Multiplier > 0) { var win = (long)(amount * result.Multiplier); - str += GetText(strs.br_win( - n(win), - result.Threshold + (result.Roll == 100 ? " 👑" : ""))); - await _cs.AddAsync(ctx.User, "Betroll Gamble", - win, false, gamble: true); + str += GetText(strs.br_win(n(win), result.Threshold + (result.Roll == 100 ? " 👑" : ""))); + await _cs.AddAsync(ctx.User, "Betroll Gamble", win, false, true); } else { str += GetText(strs.better_luck); } - + await SendConfirmAsync(str); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public Task BetRoll(ShmartNumber amount) => InternallBetroll(amount); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [NadekoOptions(typeof(LbOpts))] [Priority(0)] public Task Leaderboard(params string[] args) => Leaderboard(1, args); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [NadekoOptions(typeof(LbOpts))] [Priority(1)] public async Task Leaderboard(int page = 1, params string[] args) @@ -510,10 +542,7 @@ public partial class Gambling : GamblingModule var cleanRichest = new List(); // it's pointless to have clean on dm context - if (ctx.Guild is null) - { - opts.Clean = false; - } + if (ctx.Guild is null) opts.Clean = false; if (opts.Clean) { @@ -523,13 +552,12 @@ public partial class Gambling : GamblingModule { cleanRichest = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 10_000); } - + await ctx.Channel.TriggerTypingAsync(); await _tracker.EnsureUsersDownloadedAsync(ctx.Guild); var sg = (SocketGuild)ctx.Guild; - cleanRichest = cleanRichest.Where(x => sg.GetUser(x.UserId) != null) - .ToList(); + cleanRichest = cleanRichest.Where(x => sg.GetUser(x.UserId) != null).ToList(); } else { @@ -537,62 +565,46 @@ public partial class Gambling : GamblingModule cleanRichest = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, page).ToList(); } - await ctx.SendPaginatedConfirmAsync(page, curPage => - { - var embed = _eb.Create() - .WithOkColor() - .WithTitle(CurrencySign + " " + GetText(strs.leaderboard)); + await ctx.SendPaginatedConfirmAsync(page, + curPage => + { + var embed = _eb.Create().WithOkColor().WithTitle(CurrencySign + " " + GetText(strs.leaderboard)); + + List toSend; + if (!opts.Clean) + { + using var uow = _db.GetDbContext(); + toSend = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, curPage); + } + else + { + toSend = cleanRichest.Skip(curPage * 9).Take(9).ToList(); + } + + if (!toSend.Any()) + { + embed.WithDescription(GetText(strs.no_user_on_this_page)); + return embed; + } + + for (var i = 0; i < toSend.Count; i++) + { + var x = toSend[i]; + var usrStr = x.ToString().TrimTo(20, true); + + var j = i; + embed.AddField("#" + ((9 * curPage) + j + 1) + " " + usrStr, n(x.CurrencyAmount), true); + } - List toSend; - if (!opts.Clean) - { - using var uow = _db.GetDbContext(); - toSend = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, curPage); - } - else - { - toSend = cleanRichest.Skip(curPage * 9).Take(9).ToList(); - } - if (!toSend.Any()) - { - embed.WithDescription(GetText(strs.no_user_on_this_page)); return embed; - } - - for (var i = 0; i < toSend.Count; i++) - { - var x = toSend[i]; - var usrStr = x.ToString().TrimTo(20, true); - - var j = i; - embed.AddField("#" + ((9 * curPage) + j + 1) + " " + usrStr, n(x.CurrencyAmount), true); - } - - return embed; - }, opts.Clean ? cleanRichest.Count() : 9000, 9, opts.Clean); + }, + opts.Clean ? cleanRichest.Count() : 9000, + 9, + opts.Clean); } - - public enum RpsPick - { - R = 0, - Rock = 0, - Rocket = 0, - P = 1, - Paper = 1, - Paperclip = 1, - S = 2, - Scissors = 2 - } - - public enum RpsResult - { - Win, - Loss, - Draw, - } - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Rps(RpsPick pick, ShmartNumber amount = default) { long oldAmount = amount; @@ -611,35 +623,31 @@ public partial class Gambling : GamblingModule return "✂️"; } } + var embed = _eb.Create(); var nadekoPick = (RpsPick)new NadekoRandom().Next(0, 3); if (amount > 0) - { - if (!await _cs.RemoveAsync(ctx.User.Id, - "Rps-bet", amount, gamble: true)) + if (!await _cs.RemoveAsync(ctx.User.Id, "Rps-bet", amount, true)) { await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); return; } - } string msg; if (pick == nadekoPick) { - await _cs.AddAsync(ctx.User.Id, - "Rps-draw", amount, gamble: true); + await _cs.AddAsync(ctx.User.Id, "Rps-draw", amount, true); embed.WithOkColor(); msg = GetText(strs.rps_draw(getRpsPick(pick))); } - else if ((pick == RpsPick.Paper && nadekoPick == RpsPick.Rock) || - (pick == RpsPick.Rock && nadekoPick == RpsPick.Scissors) || - (pick == RpsPick.Scissors && nadekoPick == RpsPick.Paper)) + else if ((pick == RpsPick.Paper && nadekoPick == RpsPick.Rock) + || (pick == RpsPick.Rock && nadekoPick == RpsPick.Scissors) + || (pick == RpsPick.Scissors && nadekoPick == RpsPick.Paper)) { - amount = (long)(amount * base._config.BetFlip.Multiplier); - await _cs.AddAsync(ctx.User.Id, - "Rps-win", amount, gamble: true); + amount = (long)(amount * _config.BetFlip.Multiplier); + await _cs.AddAsync(ctx.User.Id, "Rps-win", amount, true); embed.WithOkColor(); embed.AddField(GetText(strs.won), n(amount)); msg = GetText(strs.rps_win(ctx.User.Mention, getRpsPick(pick), getRpsPick(nadekoPick))); @@ -651,9 +659,8 @@ public partial class Gambling : GamblingModule msg = GetText(strs.rps_win(ctx.Client.CurrentUser.Mention, getRpsPick(nadekoPick), getRpsPick(pick))); } - embed - .WithDescription(msg); + embed.WithDescription(msg); await ctx.Channel.EmbedAsync(embed); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/PlantAndPickCommands.cs b/src/NadekoBot/Modules/Gambling/PlantAndPickCommands.cs index 9f68d9474..12ada77ae 100644 --- a/src/NadekoBot/Modules/Gambling/PlantAndPickCommands.cs +++ b/src/NadekoBot/Modules/Gambling/PlantAndPickCommands.cs @@ -1,7 +1,7 @@ #nullable disable using NadekoBot.Modules.Administration.Services; -using NadekoBot.Modules.Gambling.Services; using NadekoBot.Modules.Gambling.Common; +using NadekoBot.Modules.Gambling.Services; namespace NadekoBot.Modules.Gambling; @@ -12,17 +12,16 @@ public partial class Gambling { private readonly ILogCommandService logService; - public PlantPickCommands(ILogCommandService logService, GamblingConfigService gss) : base(gss) + public PlantPickCommands(ILogCommandService logService, GamblingConfigService gss) + : base(gss) => this.logService = logService; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Pick(string pass = null) { - if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric()) - { - return; - } + if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric()) return; var picked = await _service.PickAsync(ctx.Guild.Id, (ITextChannel)ctx.Channel, ctx.User.Id, pass); @@ -33,27 +32,23 @@ public partial class Gambling } if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages) - { try { logService.AddDeleteIgnore(ctx.Message.Id); await ctx.Message.DeleteAsync(); } catch { } - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Plant(ShmartNumber amount, string pass = null) { if (amount < 1) return; - if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric()) - { - return; - } + if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric()) return; if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages) { @@ -61,14 +56,17 @@ public partial class Gambling await ctx.Message.DeleteAsync(); } - var success = await _service.PlantAsync(ctx.Guild.Id, ctx.Channel, ctx.User.Id, ctx.User.ToString(), amount, pass); - if (!success) - { - await ReplyErrorLocalizedAsync(strs.not_enough( CurrencySign)); - } + var success = await _service.PlantAsync(ctx.Guild.Id, + ctx.Channel, + ctx.User.Id, + ctx.User.ToString(), + amount, + pass); + if (!success) await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] #if GLOBAL_NADEKO @@ -78,16 +76,13 @@ public partial class Gambling { var enabled = _service.ToggleCurrencyGeneration(ctx.Guild.Id, ctx.Channel.Id); if (enabled) - { await ReplyConfirmLocalizedAsync(strs.curgen_enabled); - } else - { await ReplyConfirmLocalizedAsync(strs.curgen_disabled); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] [OwnerOnly] @@ -97,19 +92,19 @@ public partial class Gambling return Task.CompletedTask; var enabledIn = _service.GetAllGeneratingChannels(); - return ctx.SendPaginatedConfirmAsync(page, cur => - { - var items = enabledIn.Skip(page * 9).Take(9); - - if (!items.Any()) + return ctx.SendPaginatedConfirmAsync(page, + cur => { - return _eb.Create().WithErrorColor() - .WithDescription("-"); - } + var items = enabledIn.Skip(page * 9).Take(9); - return items.Aggregate(_eb.Create().WithOkColor(), - (eb, i) => eb.AddField(i.GuildId.ToString(), i.ChannelId)); - }, enabledIn.Count(), 9); + if (!items.Any()) + return _eb.Create().WithErrorColor().WithDescription("-"); + + return items.Aggregate(_eb.Create().WithOkColor(), + (eb, i) => eb.AddField(i.GuildId.ToString(), i.ChannelId)); + }, + enabledIn.Count(), + 9); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Services/AnimalRaceService.cs b/src/NadekoBot/Modules/Gambling/Services/AnimalRaceService.cs index a7018f3fd..7814bc044 100644 --- a/src/NadekoBot/Modules/Gambling/Services/AnimalRaceService.cs +++ b/src/NadekoBot/Modules/Gambling/Services/AnimalRaceService.cs @@ -6,4 +6,4 @@ namespace NadekoBot.Modules.Gambling.Services; public class AnimalRaceService : INService { public ConcurrentDictionary AnimalRaces { get; } = new(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Services/BlackJackService.cs b/src/NadekoBot/Modules/Gambling/Services/BlackJackService.cs index 6c1f4bd4e..4220b7e13 100644 --- a/src/NadekoBot/Modules/Gambling/Services/BlackJackService.cs +++ b/src/NadekoBot/Modules/Gambling/Services/BlackJackService.cs @@ -6,4 +6,4 @@ namespace NadekoBot.Modules.Gambling.Services; public class BlackJackService : INService { public ConcurrentDictionary Games { get; } = new(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Services/CurrencyEventsService.cs b/src/NadekoBot/Modules/Gambling/Services/CurrencyEventsService.cs index 5adb53cc4..e9d5c7f93 100644 --- a/src/NadekoBot/Modules/Gambling/Services/CurrencyEventsService.cs +++ b/src/NadekoBot/Modules/Gambling/Services/CurrencyEventsService.cs @@ -1,6 +1,6 @@ #nullable disable -using NadekoBot.Modules.Gambling.Common.Events; using NadekoBot.Modules.Gambling.Common; +using NadekoBot.Modules.Gambling.Common.Events; using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Gambling.Services; @@ -14,18 +14,19 @@ public class CurrencyEventsService : INService private readonly ConcurrentDictionary _events = new(); - public CurrencyEventsService( - DiscordSocketClient client, - ICurrencyService cs, - GamblingConfigService configService) + public CurrencyEventsService(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService configService) { _client = client; _cs = cs; _configService = configService; } - public async Task TryCreateEventAsync(ulong guildId, ulong channelId, CurrencyEvent.Type type, - EventOptions opts, Func embed) + public async Task TryCreateEventAsync( + ulong guildId, + ulong channelId, + CurrencyEvent.Type type, + EventOptions opts, + Func embed) { var g = _client.GetGuild(guildId); if (g?.GetChannel(channelId) is not SocketTextChannel ch) @@ -34,21 +35,14 @@ public class CurrencyEventsService : INService ICurrencyEvent ce; if (type == CurrencyEvent.Type.Reaction) - { ce = new ReactionEvent(_client, _cs, g, ch, opts, _configService.Data, embed); - } else if (type == CurrencyEvent.Type.GameStatus) - { ce = new GameStatusEvent(_client, _cs, g, ch, opts, embed); - } else - { return false; - } var added = _events.TryAdd(guildId, ce); if (added) - { try { ce.OnEnded += OnEventEnded; @@ -60,7 +54,6 @@ public class CurrencyEventsService : INService _events.TryRemove(guildId, out ce); return false; } - } return added; } @@ -70,4 +63,4 @@ public class CurrencyEventsService : INService _events.TryRemove(gid, out _); return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Services/CurrencyRaffleService.cs b/src/NadekoBot/Modules/Gambling/Services/CurrencyRaffleService.cs index 9484c2660..38c0a2db7 100644 --- a/src/NadekoBot/Modules/Gambling/Services/CurrencyRaffleService.cs +++ b/src/NadekoBot/Modules/Gambling/Services/CurrencyRaffleService.cs @@ -10,19 +10,24 @@ public class CurrencyRaffleService : INService NotEnoughCurrency, AlreadyJoinedOrInvalidAmount } + + public Dictionary Games { get; } = new(); private readonly SemaphoreSlim _locker = new(1, 1); private readonly DbService _db; private readonly ICurrencyService _cs; - public Dictionary Games { get; } = new(); - public CurrencyRaffleService(DbService db, ICurrencyService cs) { _db = db; _cs = cs; } - public async Task<(CurrencyRaffleGame, JoinErrorType?)> JoinOrCreateGame(ulong channelId, IUser user, long amount, bool mixed, Func onEnded) + public async Task<(CurrencyRaffleGame, JoinErrorType?)> JoinOrCreateGame( + ulong channelId, + IUser user, + long amount, + bool mixed, + Func onEnded) { await _locker.WaitAsync(); try @@ -31,9 +36,7 @@ public class CurrencyRaffleService : INService if (!Games.TryGetValue(channelId, out var crg)) { newGame = true; - crg = new(mixed - ? CurrencyRaffleGame.Type.Mixed - : CurrencyRaffleGame.Type.Normal); + crg = new(mixed ? CurrencyRaffleGame.Type.Mixed : CurrencyRaffleGame.Type.Normal); Games.Add(channelId, crg); } @@ -51,6 +54,7 @@ public class CurrencyRaffleService : INService await _cs.AddAsync(user.Id, "Curency Raffle Refund", amount); return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount); } + if (newGame) { var _t = Task.Run(async () => @@ -62,8 +66,7 @@ public class CurrencyRaffleService : INService var winner = crg.GetWinner(); var won = crg.Users.Sum(x => x.Amount); - await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win", - won); + await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win", won); Games.Remove(channelId, out _); var oe = onEnded(winner.DiscordUser, won); } @@ -71,6 +74,7 @@ public class CurrencyRaffleService : INService finally { _locker.Release(); } }); } + return (crg, null); } finally @@ -78,4 +82,4 @@ public class CurrencyRaffleService : INService _locker.Release(); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Services/GamblingConfigService.cs b/src/NadekoBot/Modules/Gambling/Services/GamblingConfigService.cs index 82a13e25c..e81391372 100644 --- a/src/NadekoBot/Modules/Gambling/Services/GamblingConfigService.cs +++ b/src/NadekoBot/Modules/Gambling/Services/GamblingConfigService.cs @@ -6,74 +6,119 @@ namespace NadekoBot.Modules.Gambling.Services; public sealed class GamblingConfigService : ConfigServiceBase { - public override string Name { get; } = "gambling"; private const string FilePath = "data/gambling.yml"; private static readonly TypedKey changeKey = new("config.gambling.updated"); + public override string Name { get; } = "gambling"; + + private readonly IEnumerable antiGiftSeed = new[] + { + new WaifuItemModel("🥀", 100, "WiltedRose", true), new WaifuItemModel("✂️", 1000, "Haircut", true), + new WaifuItemModel("🧻", 10000, "ToiletPaper", true) + }; + - public GamblingConfigService(IConfigSeria serializer, IPubSub pubSub) : base(FilePath, serializer, pubSub, changeKey) { AddParsedProp("currency.name", gs => gs.Currency.Name, ConfigParsers.String, ConfigPrinters.ToString); AddParsedProp("currency.sign", gs => gs.Currency.Sign, ConfigParsers.String, ConfigPrinters.ToString); - + AddParsedProp("minbet", gs => gs.MinBet, int.TryParse, ConfigPrinters.ToString, val => val >= 0); AddParsedProp("maxbet", gs => gs.MaxBet, int.TryParse, ConfigPrinters.ToString, val => val >= 0); - + AddParsedProp("gen.min", gs => gs.Generation.MinAmount, int.TryParse, ConfigPrinters.ToString, val => val >= 1); AddParsedProp("gen.max", gs => gs.Generation.MaxAmount, int.TryParse, ConfigPrinters.ToString, val => val >= 1); AddParsedProp("gen.cd", gs => gs.Generation.GenCooldown, int.TryParse, ConfigPrinters.ToString, val => val > 0); - AddParsedProp("gen.chance", gs => gs.Generation.Chance, decimal.TryParse, ConfigPrinters.ToString, val => val is >= 0 and <= 1); + AddParsedProp("gen.chance", + gs => gs.Generation.Chance, + decimal.TryParse, + ConfigPrinters.ToString, + val => val is >= 0 and <= 1); AddParsedProp("gen.has_pw", gs => gs.Generation.HasPassword, bool.TryParse, ConfigPrinters.ToString); - AddParsedProp("bf.multi", gs => gs.BetFlip.Multiplier, decimal.TryParse, ConfigPrinters.ToString, val => val >= 1); - AddParsedProp("waifu.min_price", gs => gs.Waifu.MinPrice, int.TryParse, ConfigPrinters.ToString, val => val >= 0); - AddParsedProp("waifu.multi.reset", gs => gs.Waifu.Multipliers.WaifuReset, int.TryParse, ConfigPrinters.ToString, val => val >= 0); - AddParsedProp("waifu.multi.crush_claim", gs => gs.Waifu.Multipliers.CrushClaim, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0); - AddParsedProp("waifu.multi.normal_claim", gs => gs.Waifu.Multipliers.NormalClaim, decimal.TryParse, ConfigPrinters.ToString, val => val > 0); - AddParsedProp("waifu.multi.divorce_value", gs => gs.Waifu.Multipliers.DivorceNewValue, decimal.TryParse, ConfigPrinters.ToString, val => val > 0); - AddParsedProp("waifu.multi.all_gifts", gs => gs.Waifu.Multipliers.AllGiftPrices, decimal.TryParse, ConfigPrinters.ToString, val => val > 0); - AddParsedProp("waifu.multi.gift_effect", gs => gs.Waifu.Multipliers.GiftEffect, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0); - AddParsedProp("waifu.multi.negative_gift_effect", gs => gs.Waifu.Multipliers.NegativeGiftEffect, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0); - AddParsedProp("decay.percent", gs => gs.Decay.Percent, decimal.TryParse, ConfigPrinters.ToString, val => val is >= 0 and <= 1); - AddParsedProp("decay.maxdecay", gs => gs.Decay.MaxDecay, int.TryParse, ConfigPrinters.ToString, val => val >= 0); - AddParsedProp("decay.threshold", gs => gs.Decay.MinThreshold, int.TryParse, ConfigPrinters.ToString, val => val >= 0); + AddParsedProp("bf.multi", + gs => gs.BetFlip.Multiplier, + decimal.TryParse, + ConfigPrinters.ToString, + val => val >= 1); + AddParsedProp("waifu.min_price", + gs => gs.Waifu.MinPrice, + int.TryParse, + ConfigPrinters.ToString, + val => val >= 0); + AddParsedProp("waifu.multi.reset", + gs => gs.Waifu.Multipliers.WaifuReset, + int.TryParse, + ConfigPrinters.ToString, + val => val >= 0); + AddParsedProp("waifu.multi.crush_claim", + gs => gs.Waifu.Multipliers.CrushClaim, + decimal.TryParse, + ConfigPrinters.ToString, + val => val >= 0); + AddParsedProp("waifu.multi.normal_claim", + gs => gs.Waifu.Multipliers.NormalClaim, + decimal.TryParse, + ConfigPrinters.ToString, + val => val > 0); + AddParsedProp("waifu.multi.divorce_value", + gs => gs.Waifu.Multipliers.DivorceNewValue, + decimal.TryParse, + ConfigPrinters.ToString, + val => val > 0); + AddParsedProp("waifu.multi.all_gifts", + gs => gs.Waifu.Multipliers.AllGiftPrices, + decimal.TryParse, + ConfigPrinters.ToString, + val => val > 0); + AddParsedProp("waifu.multi.gift_effect", + gs => gs.Waifu.Multipliers.GiftEffect, + decimal.TryParse, + ConfigPrinters.ToString, + val => val >= 0); + AddParsedProp("waifu.multi.negative_gift_effect", + gs => gs.Waifu.Multipliers.NegativeGiftEffect, + decimal.TryParse, + ConfigPrinters.ToString, + val => val >= 0); + AddParsedProp("decay.percent", + gs => gs.Decay.Percent, + decimal.TryParse, + ConfigPrinters.ToString, + val => val is >= 0 and <= 1); + AddParsedProp("decay.maxdecay", + gs => gs.Decay.MaxDecay, + int.TryParse, + ConfigPrinters.ToString, + val => val >= 0); + AddParsedProp("decay.threshold", + gs => gs.Decay.MinThreshold, + int.TryParse, + ConfigPrinters.ToString, + val => val >= 0); Migrate(); } - private readonly IEnumerable antiGiftSeed = new[] - { - new WaifuItemModel("🥀", 100, "WiltedRose", true), - new WaifuItemModel("✂️", 1000, "Haircut", true), - new WaifuItemModel("🧻", 10000, "ToiletPaper", true), - }; - public void Migrate() { if (data.Version < 2) - { ModifyConfig(c => { c.Waifu.Items = c.Waifu.Items.Concat(antiGiftSeed).ToList(); c.Version = 2; }); - } if (data.Version < 3) - { ModifyConfig(c => { c.Version = 3; c.VoteReward = 100; }); - } if (data.Version < 4) - { ModifyConfig(c => { c.Version = 4; }); - } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Services/GamblingService.cs b/src/NadekoBot/Modules/Gambling/Services/GamblingService.cs index cf7e9345a..bb07ae58f 100644 --- a/src/NadekoBot/Modules/Gambling/Services/GamblingService.cs +++ b/src/NadekoBot/Modules/Gambling/Services/GamblingService.cs @@ -1,16 +1,18 @@ #nullable disable -using NadekoBot.Modules.Gambling.Common; -using NadekoBot.Modules.Gambling.Common.Connect4; -using NadekoBot.Modules.Gambling.Common.WheelOfFortune; -using Newtonsoft.Json; using Microsoft.EntityFrameworkCore; using NadekoBot.Db; +using NadekoBot.Modules.Gambling.Common; +using NadekoBot.Modules.Gambling.Common.Connect4; using NadekoBot.Modules.Gambling.Common.Slot; +using NadekoBot.Modules.Gambling.Common.WheelOfFortune; +using Newtonsoft.Json; namespace NadekoBot.Modules.Gambling.Services; public class GamblingService : INService { + public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new(); + public ConcurrentDictionary Connect4Games { get; } = new(); private readonly DbService _db; private readonly ICurrencyService _cs; private readonly Bot _bot; @@ -18,13 +20,15 @@ public class GamblingService : INService private readonly IDataCache _cache; private readonly GamblingConfigService _gss; - public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new(); - public ConcurrentDictionary Connect4Games { get; } = new(); - private readonly Timer _decayTimer; - public GamblingService(DbService db, Bot bot, ICurrencyService cs, - DiscordSocketClient client, IDataCache cache, GamblingConfigService gss) + public GamblingService( + DbService db, + Bot bot, + ICurrencyService cs, + DiscordSocketClient client, + IDataCache cache, + GamblingConfigService gss) { _db = db; _cs = cs; @@ -32,30 +36,29 @@ public class GamblingService : INService _client = client; _cache = cache; _gss = gss; - - if (_bot.Client.ShardId == 0) - { - _decayTimer = new(_ => - { - var config = _gss.Data; - var maxDecay = config.Decay.MaxDecay; - if (config.Decay.Percent is <= 0 or > 1 || maxDecay < 0) - return; - using var uow = _db.GetDbContext(); - var lastCurrencyDecay = _cache.GetLastCurrencyDecay(); - - if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval)) - return; - - Log.Information($"Decaying users' currency - decay: {config.Decay.Percent * 100}% " + - $"| max: {maxDecay} " + - $"| threshold: {config.Decay.MinThreshold}"); - - if (maxDecay == 0) - maxDecay = int.MaxValue; - - uow.Database.ExecuteSqlInterpolated($@" + if (_bot.Client.ShardId == 0) + _decayTimer = new(_ => + { + var config = _gss.Data; + var maxDecay = config.Decay.MaxDecay; + if (config.Decay.Percent is <= 0 or > 1 || maxDecay < 0) + return; + + using var uow = _db.GetDbContext(); + var lastCurrencyDecay = _cache.GetLastCurrencyDecay(); + + if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval)) + return; + + Log.Information($"Decaying users' currency - decay: {config.Decay.Percent * 100}% " + + $"| max: {maxDecay} " + + $"| threshold: {config.Decay.MinThreshold}"); + + if (maxDecay == 0) + maxDecay = int.MaxValue; + + uow.Database.ExecuteSqlInterpolated($@" UPDATE DiscordUser SET CurrencyAmount= CASE WHEN @@ -67,23 +70,20 @@ SET CurrencyAmount= END WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentUser.Id};"); - _cache.SetLastCurrencyDecay(); - uow.SaveChanges(); - }, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); - } + _cache.SetLastCurrencyDecay(); + uow.SaveChanges(); + }, + null, + TimeSpan.FromMinutes(5), + TimeSpan.FromMinutes(5)); } - + public async Task SlotAsync(ulong userId, long amount) { var takeRes = await _cs.RemoveAsync(userId, "Slot Machine", amount, true); - + if (!takeRes) - { - return new() - { - Error = GamblingError.NotEnough - }; - } + return new() { Error = GamblingError.NotEnough }; var game = new SlotGame(); var result = game.Spin(); @@ -96,37 +96,21 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU await _cs.AddAsync(userId, $"Slot Machine x{result.Multiplier}", won, true); } - var toReturn = new SlotResponse - { - Multiplier = result.Multiplier, - Won = won, - }; + var toReturn = new SlotResponse { Multiplier = result.Multiplier, Won = won }; toReturn.Rolls.AddRange(result.Rolls); return toReturn; } - - public struct EconomyResult - { - public decimal Cash { get; set; } - public decimal Planted { get; set; } - public decimal Waifus { get; set; } - public decimal OnePercent { get; set; } - public long Bot { get; set; } - } - public EconomyResult GetEconomy() { if (_cache.TryGetEconomy(out var data)) - { try { return JsonConvert.DeserializeObject(data); } catch { } - } decimal cash; decimal onePercent; @@ -149,7 +133,7 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU Planted = planted, Bot = bot, Waifus = waifus, - OnePercent = onePercent, + OnePercent = onePercent }; _cache.SetEconomy(JsonConvert.SerializeObject(result)); @@ -158,4 +142,14 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU public Task WheelOfFortuneSpinAsync(ulong userId, long bet) => new WheelOfFortuneGame(userId, bet, _gss.Data, _cs).SpinAsync(); -} + + + public struct EconomyResult + { + public decimal Cash { get; set; } + public decimal Planted { get; set; } + public decimal Waifus { get; set; } + public decimal OnePercent { get; set; } + public long Bot { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Services/IShopService.cs b/src/NadekoBot/Modules/Gambling/Services/IShopService.cs index 200debb69..5f2013d32 100644 --- a/src/NadekoBot/Modules/Gambling/Services/IShopService.cs +++ b/src/NadekoBot/Modules/Gambling/Services/IShopService.cs @@ -4,7 +4,7 @@ namespace NadekoBot.Modules.Gambling.Services; public interface IShopService { /// - /// Changes the price of a shop item + /// Changes the price of a shop item /// /// Id of the guild in which the shop is /// Index of the item @@ -13,7 +13,7 @@ public interface IShopService Task ChangeEntryPriceAsync(ulong guildId, int index, int newPrice); /// - /// Changes the name of a shop item + /// Changes the name of a shop item /// /// Id of the guild in which the shop is /// Index of the item @@ -22,7 +22,7 @@ public interface IShopService Task ChangeEntryNameAsync(ulong guildId, int index, string newName); /// - /// Swaps indexes of 2 items in the shop + /// Swaps indexes of 2 items in the shop /// /// Id of the guild in which the shop is /// First entry's index @@ -31,11 +31,11 @@ public interface IShopService Task SwapEntriesAsync(ulong guildId, int index1, int index2); /// - /// Swaps indexes of 2 items in the shop + /// Swaps indexes of 2 items in the shop /// /// Id of the guild in which the shop is /// Current index of the entry to move /// Destination index of the entry /// Whether swap was successful Task MoveEntryAsync(ulong guildId, int fromIndex, int toIndex); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Services/Impl/ShopService.cs b/src/NadekoBot/Modules/Gambling/Services/Impl/ShopService.cs index 0acd4839c..d9dc9d171 100644 --- a/src/NadekoBot/Modules/Gambling/Services/Impl/ShopService.cs +++ b/src/NadekoBot/Modules/Gambling/Services/Impl/ShopService.cs @@ -1,9 +1,9 @@ #nullable disable using Microsoft.EntityFrameworkCore; using NadekoBot.Common.Collections; +using NadekoBot.Db; using NadekoBot.Services.Database; using NadekoBot.Services.Database.Models; -using NadekoBot.Db; namespace NadekoBot.Modules.Gambling.Services; @@ -14,13 +14,9 @@ public class ShopService : IShopService, INService public ShopService(DbService db) => _db = db; - private IndexedCollection GetEntriesInternal(NadekoContext uow, ulong guildId) => - uow.GuildConfigsForId( - guildId, - set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items) - ) - .ShopEntries - .ToIndexed(); + private IndexedCollection GetEntriesInternal(NadekoContext uow, ulong guildId) + => uow.GuildConfigsForId(guildId, set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items)) + .ShopEntries.ToIndexed(); public async Task ChangeEntryPriceAsync(ulong guildId, int index, int newPrice) { @@ -98,4 +94,4 @@ public class ShopService : IShopService, INService await uow.SaveChangesAsync(); return true; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Services/PlantPickService.cs b/src/NadekoBot/Modules/Gambling/Services/PlantPickService.cs index fad4b83ad..9c084e563 100644 --- a/src/NadekoBot/Modules/Gambling/Services/PlantPickService.cs +++ b/src/NadekoBot/Modules/Gambling/Services/PlantPickService.cs @@ -1,19 +1,21 @@ #nullable disable using Microsoft.EntityFrameworkCore; +using NadekoBot.Db; using NadekoBot.Services.Database.Models; using SixLabors.Fonts; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using NadekoBot.Db; -using Image = SixLabors.ImageSharp.Image; using Color = SixLabors.ImageSharp.Color; +using Image = SixLabors.ImageSharp.Image; namespace NadekoBot.Modules.Gambling.Services; public class PlantPickService : INService { + //channelId/last generation + public ConcurrentDictionary LastGenerations { get; } = new(); private readonly DbService _db; private readonly IBotStrings _strings; private readonly IImageCache _images; @@ -25,13 +27,18 @@ public class PlantPickService : INService private readonly GamblingConfigService _gss; public readonly ConcurrentHashSet _generationChannels = new(); - //channelId/last generation - public ConcurrentDictionary LastGenerations { get; } = new(); private readonly SemaphoreSlim pickLock = new(1, 1); - public PlantPickService(DbService db, CommandHandler cmd, IBotStrings strings, - IDataCache cache, FontProvider fonts, ICurrencyService cs, - CommandHandler cmdHandler, DiscordSocketClient client, GamblingConfigService gss) + public PlantPickService( + DbService db, + CommandHandler cmd, + IBotStrings strings, + IDataCache cache, + FontProvider fonts, + ICurrencyService cs, + CommandHandler cmdHandler, + DiscordSocketClient client, + GamblingConfigService gss) { _db = db; _strings = strings; @@ -47,13 +54,12 @@ public class PlantPickService : INService using var uow = db.GetDbContext(); var guildIds = client.Guilds.Select(x => x.Id).ToList(); var configs = uow.Set() - .AsQueryable() - .Include(x => x.GenerateCurrencyChannelIds) - .Where(x => guildIds.Contains(x.GuildId)) - .ToList(); - - _generationChannels = new(configs - .SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId))); + .AsQueryable() + .Include(x => x.GenerateCurrencyChannelIds) + .Where(x => guildIds.Contains(x.GuildId)) + .ToList(); + + _generationChannels = new(configs.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId))); } private string GetText(ulong gid, LocStr str) @@ -65,7 +71,7 @@ public class PlantPickService : INService using var uow = _db.GetDbContext(); var guildConfig = uow.GuildConfigsForId(gid, set => set.Include(gc => gc.GenerateCurrencyChannelIds)); - var toAdd = new GCChannelId() { ChannelId = cid }; + var toAdd = new GCChannelId { ChannelId = cid }; if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd)) { guildConfig.GenerateCurrencyChannelIds.Add(toAdd); @@ -75,13 +81,11 @@ public class PlantPickService : INService else { var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd)); - if (toDelete != null) - { - uow.Remove(toDelete); - } + if (toDelete != null) uow.Remove(toDelete); _generationChannels.TryRemove(cid); enabled = false; } + uow.SaveChanges(); return enabled; } @@ -94,7 +98,7 @@ public class PlantPickService : INService } /// - /// Get a random currency image stream, with an optional password sticked onto it. + /// Get a random currency image stream, with an optional password sticked onto it. /// /// Optional password to add to top left corner. /// Stream of the currency image @@ -111,6 +115,7 @@ public class PlantPickService : INService { extension = format.FileExtensions.FirstOrDefault() ?? "png"; } + // return the image return curImg.ToStream(); } @@ -124,7 +129,7 @@ public class PlantPickService : INService } /// - /// Add a password to the image. + /// Add a password to the image. /// /// Image to add password to. /// Password to add to top left corner. @@ -149,10 +154,7 @@ public class PlantPickService : INService new PointF(0, size.Height + 10)); // draw the password over the background - x.DrawText(pass, - font, - SixLabors.ImageSharp.Color.White, - new(0, 0)); + x.DrawText(pass, font, Color.White, new(0, 0)); }); // return image as a stream for easy sending return (img.ToStream(format), format.FileExtensions.FirstOrDefault() ?? "png"); @@ -177,7 +179,8 @@ public class PlantPickService : INService var lastGeneration = LastGenerations.GetOrAdd(channel.Id, DateTime.MinValue); var rng = new NadekoRandom(); - if (DateTime.UtcNow - TimeSpan.FromSeconds(config.Generation.GenCooldown) < lastGeneration) //recently generated in this channel, don't generate again + if (DateTime.UtcNow - TimeSpan.FromSeconds(config.Generation.GenCooldown) + < lastGeneration) //recently generated in this channel, don't generate again return; var num = rng.Next(1, 101) + (config.Generation.Chance * 100); @@ -194,9 +197,11 @@ public class PlantPickService : INService var prefix = _cmdHandler.GetPrefix(channel.Guild.Id); var toSend = dropAmount == 1 ? GetText(channel.GuildId, strs.curgen_sn(config.Currency.Sign)) - + " " + GetText(channel.GuildId, strs.pick_sn(prefix)) + + " " + + GetText(channel.GuildId, strs.pick_sn(prefix)) : GetText(channel.GuildId, strs.curgen_pl(dropAmount, config.Currency.Sign)) - + " " + GetText(channel.GuildId, strs.pick_pl(prefix)); + + " " + + GetText(channel.GuildId, strs.pick_pl(prefix)); var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null; @@ -223,7 +228,7 @@ public class PlantPickService : INService } /// - /// Generate a hexadecimal string from 1000 to ffff. + /// Generate a hexadecimal string from 1000 to ffff. /// /// A hexadecimal string from 1000 to ffff private string GenerateCurrencyPassword() @@ -234,7 +239,11 @@ public class PlantPickService : INService return num.ToString("x4"); } - public async Task PickAsync(ulong gid, ITextChannel ch, ulong uid, string pass) + public async Task PickAsync( + ulong gid, + ITextChannel ch, + ulong uid, + string pass) { await pickLock.WaitAsync(); try @@ -246,12 +255,11 @@ public class PlantPickService : INService // this method will sum all plants with that password, // remove them, and get messageids of the removed plants - pass = pass?.Trim().TrimTo(10, hideDots: true).ToUpperInvariant(); + pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant(); // gets all plants in this channel with the same password - var entries = uow.PlantedCurrency - .AsQueryable() - .Where(x => x.ChannelId == ch.Id && pass == x.Password) - .ToList(); + var entries = uow.PlantedCurrency.AsQueryable() + .Where(x => x.ChannelId == ch.Id && pass == x.Password) + .ToList(); // sum how much currency that is, and get all of the message ids (so that i can delete them) amount = entries.Sum(x => x.Amount); ids = entries.Select(x => x.MessageId).ToArray(); @@ -260,10 +268,8 @@ public class PlantPickService : INService if (amount > 0) - { // give the picked currency to the user - await _cs.AddAsync(uid, "Picked currency", amount, gamble: false); - } + await _cs.AddAsync(uid, "Picked currency", amount); uow.SaveChanges(); } @@ -283,16 +289,18 @@ public class PlantPickService : INService } } - public async Task SendPlantMessageAsync(ulong gid, IMessageChannel ch, string user, long amount, string pass) + public async Task SendPlantMessageAsync( + ulong gid, + IMessageChannel ch, + string user, + long amount, + string pass) { try { // get the text var prefix = _cmdHandler.GetPrefix(gid); - var msgToSend = GetText(gid, - strs.planted( - Format.Bold(user), - amount + _gss.Data.Currency.Sign)); + var msgToSend = GetText(gid, strs.planted(Format.Bold(user), amount + _gss.Data.Currency.Sign)); if (amount > 1) msgToSend += " " + GetText(gid, strs.pick_pl(prefix)); @@ -313,34 +321,48 @@ public class PlantPickService : INService } } - public async Task PlantAsync(ulong gid, IMessageChannel ch, ulong uid, string user, long amount, string pass) + public async Task PlantAsync( + ulong gid, + IMessageChannel ch, + ulong uid, + string user, + long amount, + string pass) { // normalize it - no more than 10 chars, uppercase - pass = pass?.Trim().TrimTo(10, hideDots: true).ToUpperInvariant(); + pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant(); // has to be either null or alphanumeric if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric()) return false; // remove currency from the user who's planting - if (await _cs.RemoveAsync(uid, "Planted currency", amount, gamble: false)) + if (await _cs.RemoveAsync(uid, "Planted currency", amount)) { // try to send the message with the currency image var msgId = await SendPlantMessageAsync(gid, ch, user, amount, pass); if (msgId is null) { // if it fails it will return null, if it returns null, refund - await _cs.AddAsync(uid, "Planted currency refund", amount, gamble: false); + await _cs.AddAsync(uid, "Planted currency refund", amount); return false; } + // if it doesn't fail, put the plant in the database for other people to pick await AddPlantToDatabase(gid, ch.Id, uid, msgId.Value, amount, pass); return true; } + // if user doesn't have enough currency, fail return false; } - private async Task AddPlantToDatabase(ulong gid, ulong cid, ulong uid, ulong mid, long amount, string pass) + private async Task AddPlantToDatabase( + ulong gid, + ulong cid, + ulong uid, + ulong mid, + long amount, + string pass) { await using var uow = _db.GetDbContext(); uow.PlantedCurrency.Add(new() @@ -350,8 +372,8 @@ public class PlantPickService : INService ChannelId = cid, Password = pass, UserId = uid, - MessageId = mid, + MessageId = mid }); await uow.SaveChangesAsync(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Services/VoteRewardService.cs b/src/NadekoBot/Modules/Gambling/Services/VoteRewardService.cs index 243ecd273..4ff35c44f 100644 --- a/src/NadekoBot/Modules/Gambling/Services/VoteRewardService.cs +++ b/src/NadekoBot/Modules/Gambling/Services/VoteRewardService.cs @@ -1,7 +1,7 @@ #nullable disable +using NadekoBot.Common.ModuleBehaviors; using System.Text.Json; using System.Text.Json.Serialization; -using NadekoBot.Common.ModuleBehaviors; namespace NadekoBot.Modules.Gambling.Services; @@ -10,7 +10,7 @@ public class VoteModel [JsonPropertyName("userId")] public ulong UserId { get; set; } } - + public class VoteRewardService : INService, IReadyExecutor { private readonly DiscordSocketClient _client; @@ -33,18 +33,17 @@ public class VoteRewardService : INService, IReadyExecutor _currencyService = currencyService; _gamb = gamb; } - + public async Task OnReadyAsync() { if (_client.ShardId != 0) return; - - _http = new(new HttpClientHandler() + + _http = new(new HttpClientHandler { - AllowAutoRedirect = false, - ServerCertificateCustomValidationCallback = delegate { return true; } + AllowAutoRedirect = false, ServerCertificateCustomValidationCallback = delegate { return true; } }); - + while (true) { await Task.Delay(30000); @@ -54,8 +53,7 @@ public class VoteRewardService : INService, IReadyExecutor try { - if (!string.IsNullOrWhiteSpace(topggKey) - && !string.IsNullOrWhiteSpace(topggServiceUrl)) + if (!string.IsNullOrWhiteSpace(topggKey) && !string.IsNullOrWhiteSpace(topggServiceUrl)) { _http.DefaultRequestHeaders.Authorization = new(topggKey); var uri = new Uri(new(topggServiceUrl), "topgg/new"); @@ -82,11 +80,10 @@ public class VoteRewardService : INService, IReadyExecutor var discordsKey = _creds.Votes?.DiscordsKey; var discordsServiceUrl = _creds.Votes?.DiscordsServiceUrl; - + try { - if (!string.IsNullOrWhiteSpace(discordsKey) - && !string.IsNullOrWhiteSpace(discordsServiceUrl)) + if (!string.IsNullOrWhiteSpace(discordsKey) && !string.IsNullOrWhiteSpace(discordsServiceUrl)) { _http.DefaultRequestHeaders.Authorization = new(discordsKey); var res = await _http.GetStringAsync(new Uri(new(discordsServiceUrl), "discords/new")); @@ -95,7 +92,7 @@ public class VoteRewardService : INService, IReadyExecutor if (data is { Count: > 0 }) { var ids = data.Select(x => x.UserId).ToList(); - + await _currencyService.AddBulkAsync(ids, data.Select(_ => "discords.com vote reward"), data.Select(x => _gamb.Data.VoteReward), @@ -111,4 +108,4 @@ public class VoteRewardService : INService, IReadyExecutor } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Services/WaifuService.cs b/src/NadekoBot/Modules/Gambling/Services/WaifuService.cs index a82d9e8d3..9bcee0b80 100644 --- a/src/NadekoBot/Modules/Gambling/Services/WaifuService.cs +++ b/src/NadekoBot/Modules/Gambling/Services/WaifuService.cs @@ -1,28 +1,24 @@ #nullable disable -using NadekoBot.Modules.Gambling.Common.Waifu; -using NadekoBot.Services.Database.Models; using Microsoft.EntityFrameworkCore; using NadekoBot.Db; -using NadekoBot.Modules.Gambling.Common; using NadekoBot.Db.Models; +using NadekoBot.Modules.Gambling.Common; +using NadekoBot.Modules.Gambling.Common.Waifu; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Gambling.Services; public class WaifuService : INService { - public class FullWaifuInfo - { - public WaifuInfo Waifu { get; set; } - public IEnumerable Claims { get; set; } - public int Divorces { get; set; } - } - private readonly DbService _db; private readonly ICurrencyService _cs; private readonly IDataCache _cache; private readonly GamblingConfigService _gss; - public WaifuService(DbService db, ICurrencyService cs, IDataCache cache, + public WaifuService( + DbService db, + ICurrencyService cs, + IDataCache cache, GamblingConfigService gss) { _db = db; @@ -45,18 +41,13 @@ public class WaifuService : INService // owner has to be the owner of the waifu if (waifu is null || waifu.ClaimerId != ownerUser.Id) return false; - + // if waifu likes the person, gotta pay the penalty if (waifu.AffinityId == ownerUser.Id) { - if (!await _cs.RemoveAsync(owner.Id, - "Waifu Transfer - affinity penalty", - (int)(waifu.Price * 0.6), - true)) - { + if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer - affinity penalty", (int)(waifu.Price * 0.6), true)) // unable to pay 60% penalty return false; - } waifu.Price = (int)(waifu.Price * 0.7); // half of 60% = 30% price reduction if (waifu.Price < settings.Waifu.MinPrice) @@ -64,12 +55,9 @@ public class WaifuService : INService } else // if not, pay 10% fee { - if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, gamble: true)) - { - return false; - } + if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, true)) return false; - waifu.Price = (int) (waifu.Price * 0.95); // half of 10% = 5% price reduction + waifu.Price = (int)(waifu.Price * 0.95); // half of 10% = 5% price reduction if (waifu.Price < settings.Waifu.MinPrice) waifu.Price = settings.Waifu.MinPrice; } @@ -92,41 +80,36 @@ public class WaifuService : INService if (waifu is null) return settings.Waifu.MinPrice; - var divorces = uow.WaifuUpdates.Count(x => x.Old != null && - x.Old.UserId == user.Id && - x.UpdateType == WaifuUpdateType.Claimed && - x.New == null); - var affs = uow.WaifuUpdates - .AsQueryable() - .Where(w => w.User.UserId == user.Id && w.UpdateType == WaifuUpdateType.AffinityChanged && - w.New != null) - .ToList() - .GroupBy(x => x.New) - .Count(); + var divorces = uow.WaifuUpdates.Count(x + => x.Old != null && x.Old.UserId == user.Id && x.UpdateType == WaifuUpdateType.Claimed && x.New == null); + var affs = uow.WaifuUpdates.AsQueryable() + .Where(w => w.User.UserId == user.Id + && w.UpdateType == WaifuUpdateType.AffinityChanged + && w.New != null) + .ToList() + .GroupBy(x => x.New) + .Count(); - return (int) Math.Ceiling(waifu.Price * 1.25f) + - ((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset); + return (int)Math.Ceiling(waifu.Price * 1.25f) + ((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset); } public async Task TryReset(IUser user) { await using var uow = _db.GetDbContext(); var price = GetResetPrice(user); - if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, gamble: true)) + if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, true)) return false; - var affs = uow.WaifuUpdates - .AsQueryable() - .Where(w => w.User.UserId == user.Id - && w.UpdateType == WaifuUpdateType.AffinityChanged - && w.New != null); + var affs = uow.WaifuUpdates.AsQueryable() + .Where(w => w.User.UserId == user.Id + && w.UpdateType == WaifuUpdateType.AffinityChanged + && w.New != null); - var divorces = uow.WaifuUpdates - .AsQueryable() - .Where(x => x.Old != null && - x.Old.UserId == user.Id && - x.UpdateType == WaifuUpdateType.Claimed && - x.New == null); + var divorces = uow.WaifuUpdates.AsQueryable() + .Where(x => x.Old != null + && x.Old.UserId == user.Id + && x.UpdateType == WaifuUpdateType.Claimed + && x.New == null); //reset changes of heart to 0 uow.WaifuUpdates.RemoveRange(affs); @@ -161,32 +144,23 @@ public class WaifuService : INService { var claimer = uow.GetOrCreateUser(user); var waifu = uow.GetOrCreateUser(target); - if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true)) + if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, true)) { result = WaifuClaimResult.NotEnoughFunds; } else { - uow.WaifuInfo.Add(w = new() - { - Waifu = waifu, - Claimer = claimer, - Affinity = null, - Price = amount - }); + uow.WaifuInfo.Add(w = new() { Waifu = waifu, Claimer = claimer, Affinity = null, Price = amount }); uow.WaifuUpdates.Add(new() { - User = waifu, - Old = null, - New = claimer, - UpdateType = WaifuUpdateType.Claimed + User = waifu, Old = null, New = claimer, UpdateType = WaifuUpdateType.Claimed }); result = WaifuClaimResult.Success; } } else if (isAffinity && amount > w.Price * settings.Waifu.Multipliers.CrushClaim) { - if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true)) + if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, true)) { result = WaifuClaimResult.NotEnoughFunds; } @@ -199,16 +173,13 @@ public class WaifuService : INService uow.WaifuUpdates.Add(new() { - User = w.Waifu, - Old = oldClaimer, - New = w.Claimer, - UpdateType = WaifuUpdateType.Claimed + User = w.Waifu, Old = oldClaimer, New = w.Claimer, UpdateType = WaifuUpdateType.Claimed }); } } else if (amount >= w.Price * settings.Waifu.Multipliers.NormalClaim) // if no affinity { - if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true)) + if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, true)) { result = WaifuClaimResult.NotEnoughFunds; } @@ -221,15 +192,14 @@ public class WaifuService : INService uow.WaifuUpdates.Add(new() { - User = w.Waifu, - Old = oldClaimer, - New = w.Claimer, - UpdateType = WaifuUpdateType.Claimed + User = w.Waifu, Old = oldClaimer, New = w.Claimer, UpdateType = WaifuUpdateType.Claimed }); } } else + { result = WaifuClaimResult.InsufficientAmount; + } await uow.SaveChangesAsync(); @@ -256,21 +226,12 @@ public class WaifuService : INService else if (w is null) { var thisUser = uow.GetOrCreateUser(user); - uow.WaifuInfo.Add(new() - { - Affinity = newAff, - Waifu = thisUser, - Price = 1, - Claimer = null - }); + uow.WaifuInfo.Add(new() { Affinity = newAff, Waifu = thisUser, Price = 1, Claimer = null }); success = true; uow.WaifuUpdates.Add(new() { - User = thisUser, - Old = null, - New = newAff, - UpdateType = WaifuUpdateType.AffinityChanged + User = thisUser, Old = null, New = newAff, UpdateType = WaifuUpdateType.AffinityChanged }); } else @@ -282,10 +243,7 @@ public class WaifuService : INService uow.WaifuUpdates.Add(new() { - User = w.Waifu, - Old = oldAff, - New = newAff, - UpdateType = WaifuUpdateType.AffinityChanged + User = w.Waifu, Old = oldAff, New = newAff, UpdateType = WaifuUpdateType.AffinityChanged }); } @@ -318,7 +276,9 @@ public class WaifuService : INService w = uow.WaifuInfo.ByWaifuUserId(targetId); var now = DateTime.UtcNow; if (w?.Claimer is null || w.Claimer.UserId != user.Id) + { result = DivorceResult.NotYourWife; + } else if (!_cache.TryAddDivorceCooldown(user.Id, out remaining)) { result = DivorceResult.Cooldown; @@ -329,13 +289,13 @@ public class WaifuService : INService if (w.Affinity?.UserId == user.Id) { - await _cs.AddAsync(w.Waifu.UserId, "Waifu Compensation", amount, gamble: true); - w.Price = (int) Math.Floor(w.Price * _gss.Data.Waifu.Multipliers.DivorceNewValue); + await _cs.AddAsync(w.Waifu.UserId, "Waifu Compensation", amount, true); + w.Price = (int)Math.Floor(w.Price * _gss.Data.Waifu.Multipliers.DivorceNewValue); result = DivorceResult.SucessWithPenalty; } else { - await _cs.AddAsync(user.Id, "Waifu Refund", amount, gamble: true); + await _cs.AddAsync(user.Id, "Waifu Refund", amount, true); result = DivorceResult.Success; } @@ -345,10 +305,7 @@ public class WaifuService : INService uow.WaifuUpdates.Add(new() { - User = w.Waifu, - Old = oldClaimer, - New = null, - UpdateType = WaifuUpdateType.Claimed + User = w.Waifu, Old = oldClaimer, New = null, UpdateType = WaifuUpdateType.Claimed }); } @@ -360,42 +317,24 @@ public class WaifuService : INService public async Task GiftWaifuAsync(IUser from, IUser giftedWaifu, WaifuItemModel itemObj) { - if (!await _cs.RemoveAsync(from, "Bought waifu item", itemObj.Price, gamble: true)) - { - return false; - } + if (!await _cs.RemoveAsync(from, "Bought waifu item", itemObj.Price, gamble: true)) return false; await using var uow = _db.GetDbContext(); - var w = uow.WaifuInfo.ByWaifuUserId(giftedWaifu.Id, - set => set.Include(x => x.Items) - .Include(x => x.Claimer)); + var w = uow.WaifuInfo.ByWaifuUserId(giftedWaifu.Id, set => set.Include(x => x.Items).Include(x => x.Claimer)); if (w is null) - { uow.WaifuInfo.Add(w = new() { - Affinity = null, - Claimer = null, - Price = 1, - Waifu = uow.GetOrCreateUser(giftedWaifu), + Affinity = null, Claimer = null, Price = 1, Waifu = uow.GetOrCreateUser(giftedWaifu) }); - } if (!itemObj.Negative) { - w.Items.Add(new() - { - Name = itemObj.Name.ToLowerInvariant(), - ItemEmoji = itemObj.ItemEmoji, - }); - - if (w.Claimer?.UserId == @from.Id) - { + w.Items.Add(new() { Name = itemObj.Name.ToLowerInvariant(), ItemEmoji = itemObj.ItemEmoji }); + + if (w.Claimer?.UserId == from.Id) w.Price += (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect); - } else - { w.Price += itemObj.Price / 2; - } } else { @@ -414,7 +353,6 @@ public class WaifuService : INService using var uow = _db.GetDbContext(); var wi = uow.GetWaifuInfo(targetId); if (wi is null) - { wi = new() { AffinityCount = 0, @@ -428,15 +366,15 @@ public class WaifuService : INService Items = new(), Price = 1 }; - } return wi; } + public WaifuInfoStats GetFullWaifuInfoAsync(IGuildUser target) { using var uow = _db.GetDbContext(); var du = uow.GetOrCreateUser(target); - + return GetFullWaifuInfoAsync(target.Id); } @@ -501,8 +439,18 @@ public class WaifuService : INService public IReadOnlyList GetWaifuItems() { var conf = _gss.Data; - return conf.Waifu.Items - .Select(x => new WaifuItemModel(x.ItemEmoji, (int)(x.Price * conf.Waifu.Multipliers.AllGiftPrices), x.Name, x.Negative)) - .ToList(); + return conf.Waifu.Items.Select(x + => new WaifuItemModel(x.ItemEmoji, + (int)(x.Price * conf.Waifu.Multipliers.AllGiftPrices), + x.Name, + x.Negative)) + .ToList(); } -} + + public class FullWaifuInfo + { + public WaifuInfo Waifu { get; set; } + public IEnumerable Claims { get; set; } + public int Divorces { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/ShopCommands.cs b/src/NadekoBot/Modules/Gambling/ShopCommands.cs index 6cada8cd4..938e67ed9 100644 --- a/src/NadekoBot/Modules/Gambling/ShopCommands.cs +++ b/src/NadekoBot/Modules/Gambling/ShopCommands.cs @@ -1,10 +1,10 @@ #nullable disable using Microsoft.EntityFrameworkCore; using NadekoBot.Common.Collections; +using NadekoBot.Db; using NadekoBot.Modules.Gambling.Common; using NadekoBot.Modules.Gambling.Services; using NadekoBot.Services.Database.Models; -using NadekoBot.Db; namespace NadekoBot.Modules.Gambling; @@ -13,69 +13,71 @@ public partial class Gambling [Group] public class ShopCommands : GamblingSubmodule { - private readonly DbService _db; - private readonly ICurrencyService _cs; + public enum List + { + List + } public enum Role { Role } - public enum List - { - List - } + private readonly DbService _db; + private readonly ICurrencyService _cs; public ShopCommands(DbService db, ICurrencyService cs, GamblingConfigService gamblingConf) - : base(gamblingConf) + : base(gamblingConf) { _db = db; _cs = cs; } - + private Task ShopInternalAsync(int page = 0) { if (page < 0) throw new ArgumentOutOfRangeException(nameof(page)); - + using var uow = _db.GetDbContext(); var entries = uow.GuildConfigsForId(ctx.Guild.Id, - set => set.Include(x => x.ShopEntries) - .ThenInclude(x => x.Items)).ShopEntries - .ToIndexed(); - return ctx.SendPaginatedConfirmAsync(page, curPage => - { - var theseEntries = entries.Skip(curPage * 9).Take(9).ToArray(); - - if (!theseEntries.Any()) - return _eb.Create().WithErrorColor() - .WithDescription(GetText(strs.shop_none)); - var embed = _eb.Create().WithOkColor() - .WithTitle(GetText(strs.shop)); - - for (var i = 0; i < theseEntries.Length; i++) + set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items)) + .ShopEntries.ToIndexed(); + return ctx.SendPaginatedConfirmAsync(page, + curPage => { - var entry = theseEntries[i]; - embed.AddField( - $"#{(curPage * 9) + i + 1} - {entry.Price}{CurrencySign}", - EntryToString(entry), - true); - } - return embed; - }, entries.Count, 9, true); + var theseEntries = entries.Skip(curPage * 9).Take(9).ToArray(); + + if (!theseEntries.Any()) + return _eb.Create().WithErrorColor().WithDescription(GetText(strs.shop_none)); + var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.shop)); + + for (var i = 0; i < theseEntries.Length; i++) + { + var entry = theseEntries[i]; + embed.AddField($"#{(curPage * 9) + i + 1} - {entry.Price}{CurrencySign}", + EntryToString(entry), + true); + } + + return embed; + }, + entries.Count, + 9); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public Task Shop(int page = 1) { if (--page < 0) return Task.CompletedTask; - + return ShopInternalAsync(page); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Buy(int index) { @@ -85,9 +87,8 @@ public partial class Gambling ShopEntry entry; await using (var uow = _db.GetDbContext()) { - var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set - .Include(x => x.ShopEntries) - .ThenInclude(x => x.Items)); + var config = uow.GuildConfigsForId(ctx.Guild.Id, + set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items)); var entries = new IndexedCollection(config.ShopEntries); entry = entries.ElementAtOrDefault(index); uow.SaveChanges(); @@ -109,7 +110,7 @@ public partial class Gambling await ReplyErrorLocalizedAsync(strs.shop_role_not_found); return; } - + if (guser.RoleIds.Any(id => id == role.Id)) { await ReplyErrorLocalizedAsync(strs.shop_role_already_bought); @@ -125,23 +126,23 @@ public partial class Gambling catch (Exception ex) { Log.Warning(ex, "Error adding shop role"); - await _cs.AddAsync(ctx.User.Id, $"Shop error refund", entry.Price); + await _cs.AddAsync(ctx.User.Id, "Shop error refund", entry.Price); await ReplyErrorLocalizedAsync(strs.shop_role_purchase_error); return; } + var profit = GetProfitAmount(entry.Price); await _cs.AddAsync(entry.AuthorId, $"Shop sell item - {entry.Type}", profit); - await _cs.AddAsync(ctx.Client.CurrentUser.Id, $"Shop sell item - cut", entry.Price - profit); + await _cs.AddAsync(ctx.Client.CurrentUser.Id, "Shop sell item - cut", entry.Price - profit); await ReplyConfirmLocalizedAsync(strs.shop_role_purchase(Format.Bold(role.Name))); return; } - else - { - await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); - return; - } + + await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); + return; } - else if (entry.Type == ShopEntryType.List) + + if (entry.Type == ShopEntryType.List) { if (entry.Items.Count == 0) { @@ -158,14 +159,15 @@ public partial class Gambling var x = uow.Set().Remove(item); uow.SaveChanges(); } + try { - await ctx.User - .EmbedAsync(_eb.Create().WithOkColor() - .WithTitle(GetText(strs.shop_purchase(ctx.Guild.Name))) - .AddField(GetText(strs.item), item.Text, false) - .AddField(GetText(strs.price), entry.Price.ToString(), true) - .AddField(GetText(strs.name), entry.Name, true)); + await ctx.User.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.shop_purchase(ctx.Guild.Name))) + .AddField(GetText(strs.item), item.Text) + .AddField(GetText(strs.price), entry.Price.ToString(), true) + .AddField(GetText(strs.name), entry.Name, true)); await _cs.AddAsync(entry.AuthorId, $"Shop sell item - {entry.Name}", @@ -173,41 +175,37 @@ public partial class Gambling } catch { - await _cs.AddAsync(ctx.User.Id, - $"Shop error refund - {entry.Name}", - entry.Price); + await _cs.AddAsync(ctx.User.Id, $"Shop error refund - {entry.Name}", entry.Price); await using (var uow = _db.GetDbContext()) { var entries = new IndexedCollection(uow.GuildConfigsForId(ctx.Guild.Id, - set => set.Include(x => x.ShopEntries) - .ThenInclude(x => x.Items)).ShopEntries); + set => set.Include(x => x.ShopEntries) + .ThenInclude(x => x.Items)) + .ShopEntries); entry = entries.ElementAtOrDefault(index); if (entry != null) - { if (entry.Items.Add(item)) - { uow.SaveChanges(); - } - } } + await ReplyErrorLocalizedAsync(strs.shop_buy_error); return; } + await ReplyConfirmLocalizedAsync(strs.shop_item_purchase); } else { await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); - return; } } - } - private static long GetProfitAmount(int price) => - (int)Math.Ceiling(0.90 * price); + private static long GetProfitAmount(int price) + => (int)Math.Ceiling(0.90 * price); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [BotPerm(GuildPerm.ManageRoles)] @@ -215,8 +213,8 @@ public partial class Gambling { if (price < 1) return; - - var entry = new ShopEntry() + + var entry = new ShopEntry { Name = "-", Price = price, @@ -228,19 +226,18 @@ public partial class Gambling await using (var uow = _db.GetDbContext()) { var entries = new IndexedCollection(uow.GuildConfigsForId(ctx.Guild.Id, - set => set.Include(x => x.ShopEntries) - .ThenInclude(x => x.Items)).ShopEntries) - { - entry - }; + set => set.Include(x => x.ShopEntries) + .ThenInclude(x => x.Items)) + .ShopEntries) { entry }; uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries; uow.SaveChanges(); } - await ctx.Channel.EmbedAsync(EntryToEmbed(entry) - .WithTitle(GetText(strs.shop_item_add))); + + await ctx.Channel.EmbedAsync(EntryToEmbed(entry).WithTitle(GetText(strs.shop_item_add))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task ShopAdd(List _, int price, [Leftover] string name) @@ -248,31 +245,29 @@ public partial class Gambling if (price < 1) return; - var entry = new ShopEntry() + var entry = new ShopEntry { Name = name.TrimTo(100), Price = price, Type = ShopEntryType.List, AuthorId = ctx.User.Id, - Items = new(), + Items = new() }; await using (var uow = _db.GetDbContext()) { var entries = new IndexedCollection(uow.GuildConfigsForId(ctx.Guild.Id, - set => set.Include(x => x.ShopEntries) - .ThenInclude(x => x.Items)).ShopEntries) - { - entry - }; + set => set.Include(x => x.ShopEntries) + .ThenInclude(x => x.Items)) + .ShopEntries) { entry }; uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries; uow.SaveChanges(); } - await ctx.Channel.EmbedAsync(EntryToEmbed(entry) - .WithTitle(GetText(strs.shop_item_add))); + await ctx.Channel.EmbedAsync(EntryToEmbed(entry).WithTitle(GetText(strs.shop_item_add))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task ShopListAdd(int index, [Leftover] string itemText) @@ -280,27 +275,22 @@ public partial class Gambling index -= 1; if (index < 0) return; - var item = new ShopEntryItem() - { - Text = itemText - }; + var item = new ShopEntryItem { Text = itemText }; ShopEntry entry; var rightType = false; var added = false; await using (var uow = _db.GetDbContext()) { var entries = new IndexedCollection(uow.GuildConfigsForId(ctx.Guild.Id, - set => set.Include(x => x.ShopEntries) - .ThenInclude(x => x.Items)).ShopEntries); + set => set.Include(x => x.ShopEntries) + .ThenInclude(x => x.Items)) + .ShopEntries); entry = entries.ElementAtOrDefault(index); if (entry != null && (rightType = entry.Type == ShopEntryType.List)) - { if (added = entry.Items.Add(item)) - { uow.SaveChanges(); - } - } } + if (entry is null) await ReplyErrorLocalizedAsync(strs.shop_item_not_found); else if (!rightType) @@ -311,7 +301,8 @@ public partial class Gambling await ReplyConfirmLocalizedAsync(strs.shop_list_item_added); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task ShopRemove(int index) @@ -322,9 +313,8 @@ public partial class Gambling ShopEntry removed; await using (var uow = _db.GetDbContext()) { - var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set - .Include(x => x.ShopEntries) - .ThenInclude(x => x.Items)); + var config = uow.GuildConfigsForId(ctx.Guild.Id, + set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items)); var entries = new IndexedCollection(config.ShopEntries); removed = entries.ElementAtOrDefault(index); @@ -339,11 +329,11 @@ public partial class Gambling if (removed is null) await ReplyErrorLocalizedAsync(strs.shop_item_not_found); else - await ctx.Channel.EmbedAsync(EntryToEmbed(removed) - .WithTitle(GetText(strs.shop_item_rm))); + await ctx.Channel.EmbedAsync(EntryToEmbed(removed).WithTitle(GetText(strs.shop_item_rm))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task ShopChangePrice(int index, int price) @@ -363,14 +353,15 @@ public partial class Gambling } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task ShopChangeName(int index, [Leftover] string newName) { if (--index < 0 || string.IsNullOrWhiteSpace(newName)) return; - + var succ = await _service.ChangeEntryNameAsync(ctx.Guild.Id, index, newName); if (succ) { @@ -383,14 +374,15 @@ public partial class Gambling } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task ShopSwap(int index1, int index2) { if (--index1 < 0 || --index2 < 0 || index1 == index2) return; - + var succ = await _service.SwapEntriesAsync(ctx.Guild.Id, index1, index2); if (succ) { @@ -402,8 +394,9 @@ public partial class Gambling await ctx.ErrorAsync(); } } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task ShopMove(int fromIndex, int toIndex) @@ -422,36 +415,36 @@ public partial class Gambling await ctx.ErrorAsync(); } } - + public IEmbedBuilder EntryToEmbed(ShopEntry entry) { var embed = _eb.Create().WithOkColor(); if (entry.Type == ShopEntryType.Role) - return embed.AddField(GetText(strs.name), GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE"))), true) - .AddField(GetText(strs.price), entry.Price.ToString(), true) - .AddField(GetText(strs.type), entry.Type.ToString(), true); - else if (entry.Type == ShopEntryType.List) + return embed + .AddField(GetText(strs.name), + GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name + ?? "MISSING_ROLE"))), + true) + .AddField(GetText(strs.price), entry.Price.ToString(), true) + .AddField(GetText(strs.type), entry.Type.ToString(), true); + if (entry.Type == ShopEntryType.List) return embed.AddField(GetText(strs.name), entry.Name, true) - .AddField(GetText(strs.price), entry.Price.ToString(), true) - .AddField(GetText(strs.type), GetText(strs.random_unique_item), true); + .AddField(GetText(strs.price), entry.Price.ToString(), true) + .AddField(GetText(strs.type), GetText(strs.random_unique_item), true); //else if (entry.Type == ShopEntryType.Infinite_List) // return embed.AddField(GetText(strs.name), GetText(strs.shop_role(Format.Bold(entry.RoleName)), true)) // .AddField(GetText(strs.price), entry.Price.ToString(), true) // .AddField(GetText(strs.type), entry.Type.ToString(), true); - else return null; + return null; } public string EntryToString(ShopEntry entry) { if (entry.Type == ShopEntryType.Role) - { return GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE"))); - } - else if (entry.Type == ShopEntryType.List) - { + if (entry.Type == ShopEntryType.List) return GetText(strs.unique_items_left(entry.Items.Count)) + "\n" + entry.Name; - } //else if (entry.Type == ShopEntryType.Infinite_List) //{ @@ -459,4 +452,4 @@ public partial class Gambling return ""; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/SlotCommands.cs b/src/NadekoBot/Modules/Gambling/SlotCommands.cs index 51921a7b9..80e3d29f0 100644 --- a/src/NadekoBot/Modules/Gambling/SlotCommands.cs +++ b/src/NadekoBot/Modules/Gambling/SlotCommands.cs @@ -1,15 +1,15 @@ #nullable disable -using System.Text; using NadekoBot.Db.Models; -using NadekoBot.Modules.Gambling.Services; using NadekoBot.Modules.Gambling.Common; +using NadekoBot.Modules.Gambling.Services; using SixLabors.Fonts; -using Image = SixLabors.ImageSharp.Image; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System.Text; using Color = SixLabors.ImageSharp.Color; +using Image = SixLabors.ImageSharp.Image; namespace NadekoBot.Modules.Gambling; @@ -31,64 +31,23 @@ public partial class Gambling private FontProvider _fonts; private readonly DbService _db; - public SlotCommands(IDataCache data, - FontProvider fonts, DbService db, - GamblingConfigService gamb) : base(gamb) + public SlotCommands( + IDataCache data, + FontProvider fonts, + DbService db, + GamblingConfigService gamb) + : base(gamb) { _images = data.LocalImages; _fonts = fonts; _db = db; } - public sealed class SlotMachine - { - public const int MAX_VALUE = 5; + public Task Test() + => Task.CompletedTask; - private static readonly List> _winningCombos = new() - { - //three flowers - arr => arr.All(a=>a==MAX_VALUE) ? 30 : 0, - //three of the same - arr => !arr.Any(a => a != arr[0]) ? 10 : 0, - //two flowers - arr => arr.Count(a => a == MAX_VALUE) == 2 ? 4 : 0, - //one flower - arr => arr.Any(a => a == MAX_VALUE) ? 1 : 0, - }; - - public static SlotResult Pull() - { - var numbers = new int[3]; - for (var i = 0; i < numbers.Length; i++) - { - numbers[i] = new NadekoRandom().Next(0, MAX_VALUE + 1); - } - var multi = 0; - foreach (var t in _winningCombos) - { - multi = t(numbers); - if (multi != 0) - break; - } - - return new(numbers, multi); - } - - public struct SlotResult - { - public int[] Numbers { get; } - public int Multiplier { get; } - public SlotResult(int[] nums, int multi) - { - Numbers = nums; - Multiplier = multi; - } - } - } - - public Task Test() => Task.CompletedTask; - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task SlotStats() { @@ -100,16 +59,17 @@ public partial class Gambling bet = 1; var embed = _eb.Create() - .WithOkColor() - .WithTitle("Slot Stats") - .AddField("Total Bet", bet.ToString(), true) - .AddField("Paid Out", paid.ToString(), true) - .WithFooter($"Payout Rate: {paid * 1.0 / bet * 100:f4}%"); + .WithOkColor() + .WithTitle("Slot Stats") + .AddField("Total Bet", bet.ToString(), true) + .AddField("Paid Out", paid.ToString(), true) + .WithFooter($"Payout Rate: {paid * 1.0 / bet * 100:f4}%"); await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task SlotTest(int tests = 1000) { @@ -133,21 +93,24 @@ public partial class Gambling sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%"); payout += key * dict[key]; } - await SendConfirmAsync("Slot Test Results", sb.ToString(), + + await SendConfirmAsync("Slot Test Results", + sb.ToString(), footer: $"Total Bet: {tests} | Payout: {payout} | {payout * 1.0f / tests * 100}%"); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Slot(ShmartNumber amount) { if (!_runningUsers.Add(ctx.User.Id)) return; - + try { if (!await CheckBetMandatory(amount)) return; - + await ctx.Channel.TriggerTypingAsync(); var result = await _service.SlotAsync(ctx.User.Id, amount); @@ -155,9 +118,7 @@ public partial class Gambling if (result.Error != GamblingError.None) { if (result.Error == GamblingError.NotEnough) - { await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); - } return; } @@ -168,40 +129,45 @@ public partial class Gambling long ownedAmount; await using (var uow = _db.GetDbContext()) { - ownedAmount = uow.Set() - .FirstOrDefault(x => x.UserId == ctx.User.Id) - ?.CurrencyAmount ?? 0; + ownedAmount = uow.Set().FirstOrDefault(x => x.UserId == ctx.User.Id)?.CurrencyAmount + ?? 0; } - + using (var bgImage = Image.Load(_images.SlotBackground, out var format)) { var numbers = new int[3]; result.Rolls.CopyTo(numbers, 0); Color fontColor = _config.Slots.CurrencyFontColor; - + bgImage.Mutate(x => x.DrawText(new() { TextOptions = new() { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - WrapTextWidth = 140, + WrapTextWidth = 140 } - }, result.Won.ToString(), _fonts.DottyFont.CreateFont(65), fontColor, + }, + result.Won.ToString(), + _fonts.DottyFont.CreateFont(65), + fontColor, new(227, 92))); var bottomFont = _fonts.DottyFont.CreateFont(50); - + bgImage.Mutate(x => x.DrawText(new() { TextOptions = new() { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - WrapTextWidth = 135, + WrapTextWidth = 135 } - }, amount.ToString(), bottomFont, fontColor, + }, + amount.ToString(), + bottomFont, + fontColor, new(129, 472))); bgImage.Mutate(x => x.DrawText(new() @@ -210,9 +176,12 @@ public partial class Gambling { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - WrapTextWidth = 135, + WrapTextWidth = 135 } - }, ownedAmount.ToString(), bottomFont, fontColor, + }, + ownedAmount.ToString(), + bottomFont, + fontColor, new(325, 472))); //sw.PrintLap("drew red text"); @@ -238,8 +207,8 @@ public partial class Gambling await using (var imgStream = bgImage.ToStream()) { await ctx.Channel.SendFileAsync(imgStream, - filename: "result.png", - text: Format.Bold(ctx.User.ToString()) + " " + msg); + "result.png", + Format.Bold(ctx.User.ToString()) + " " + msg); } } } @@ -252,5 +221,49 @@ public partial class Gambling }); } } + + public sealed class SlotMachine + { + public const int MAX_VALUE = 5; + + private static readonly List> _winningCombos = new() + { + //three flowers + arr => arr.All(a => a == MAX_VALUE) ? 30 : 0, + //three of the same + arr => !arr.Any(a => a != arr[0]) ? 10 : 0, + //two flowers + arr => arr.Count(a => a == MAX_VALUE) == 2 ? 4 : 0, + //one flower + arr => arr.Any(a => a == MAX_VALUE) ? 1 : 0 + }; + + public static SlotResult Pull() + { + var numbers = new int[3]; + for (var i = 0; i < numbers.Length; i++) numbers[i] = new NadekoRandom().Next(0, MAX_VALUE + 1); + var multi = 0; + foreach (var t in _winningCombos) + { + multi = t(numbers); + if (multi != 0) + break; + } + + return new(numbers, multi); + } + + public struct SlotResult + { + public int[] Numbers { get; } + public int Multiplier { get; } + + public SlotResult(int[] nums, int multi) + { + Numbers = nums; + Multiplier = multi; + } + } + } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/TestGamblingService.cs b/src/NadekoBot/Modules/Gambling/TestGamblingService.cs index b8cd28f1c..23f368621 100644 --- a/src/NadekoBot/Modules/Gambling/TestGamblingService.cs +++ b/src/NadekoBot/Modules/Gambling/TestGamblingService.cs @@ -8,4 +8,4 @@ public class TestGamblingService : InteractionModuleBase [SlashCommand("test", "uwu")] public async Task Test(string input1, int input2) => await RespondAsync("Bravo " + input1 + input2); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/WaifuClaimCommands.cs b/src/NadekoBot/Modules/Gambling/WaifuClaimCommands.cs index 0b3c7fb8e..e86cdef38 100644 --- a/src/NadekoBot/Modules/Gambling/WaifuClaimCommands.cs +++ b/src/NadekoBot/Modules/Gambling/WaifuClaimCommands.cs @@ -1,7 +1,7 @@ #nullable disable +using NadekoBot.Modules.Gambling.Common; using NadekoBot.Modules.Gambling.Common.Waifu; using NadekoBot.Modules.Gambling.Services; -using NadekoBot.Modules.Gambling.Common; namespace NadekoBot.Modules.Gambling; @@ -10,18 +10,19 @@ public partial class Gambling [Group] public class WaifuClaimCommands : GamblingSubmodule { - - public WaifuClaimCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService) + public WaifuClaimCommands(GamblingConfigService gamblingConfService) + : base(gamblingConfService) { } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task WaifuReset() { var price = _service.GetResetPrice(ctx.User); var embed = _eb.Create() - .WithTitle(GetText(strs.waifu_reset_confirm)) - .WithDescription(GetText(strs.waifu_reset_price(Format.Bold(price + CurrencySign)))); + .WithTitle(GetText(strs.waifu_reset_confirm)) + .WithDescription(GetText(strs.waifu_reset_price(Format.Bold(price + CurrencySign)))); if (!await PromptUserConfirmAsync(embed)) return; @@ -31,12 +32,14 @@ public partial class Gambling await ReplyConfirmLocalizedAsync(strs.waifu_reset); return; } + await ReplyErrorLocalizedAsync(strs.waifu_reset_fail); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] - public async Task WaifuClaim(int amount, [Leftover]IUser target) + public async Task WaifuClaim(int amount, [Leftover] IUser target) { if (amount < _config.Waifu.MinPrice) { @@ -54,43 +57,44 @@ public partial class Gambling if (result == WaifuClaimResult.InsufficientAmount) { - await ReplyErrorLocalizedAsync(strs.waifu_not_enough(Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f)))); + await ReplyErrorLocalizedAsync( + strs.waifu_not_enough(Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f)))); return; } + if (result == WaifuClaimResult.NotEnoughFunds) { await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); return; } - var msg = GetText(strs.waifu_claimed( - Format.Bold(target.ToString()), - amount + CurrencySign)); + + var msg = GetText(strs.waifu_claimed(Format.Bold(target.ToString()), amount + CurrencySign)); if (w.Affinity?.UserId == ctx.User.Id) msg += "\n" + GetText(strs.waifu_fulfilled(target, w.Price + CurrencySign)); else msg = " " + msg; await SendConfirmAsync(ctx.User.Mention + msg); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] public async Task WaifuTransfer(ulong waifuId, IUser newOwner) { - if (!await _service.WaifuTransfer(ctx.User, waifuId, newOwner) - ) + if (!await _service.WaifuTransfer(ctx.User, waifuId, newOwner)) { await ReplyErrorLocalizedAsync(strs.waifu_transfer_fail); return; } - await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success( - Format.Bold(waifuId.ToString()), + await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(Format.Bold(waifuId.ToString()), Format.Bold(ctx.User.ToString()), Format.Bold(newOwner.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] public async Task WaifuTransfer(IUser waifu, IUser newOwner) @@ -101,36 +105,35 @@ public partial class Gambling return; } - await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success( - Format.Bold(waifu.ToString()), + await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(Format.Bold(waifu.ToString()), Format.Bold(ctx.User.ToString()), Format.Bold(newOwner.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(-1)] public Task Divorce([Leftover] string target) { var waifuUserId = _service.GetWaifuUserId(ctx.User.Id, target); - if (waifuUserId == default) - { - return ReplyErrorLocalizedAsync(strs.waifu_not_yours); - } + if (waifuUserId == default) return ReplyErrorLocalizedAsync(strs.waifu_not_yours); return Divorce(waifuUserId); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] - public Task Divorce([Leftover]IGuildUser target) + public Task Divorce([Leftover] IGuildUser target) => Divorce(target.Id); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] - public async Task Divorce([Leftover]ulong targetId) + public async Task Divorce([Leftover] ulong targetId) { if (targetId == ctx.User.Id) return; @@ -138,64 +141,52 @@ public partial class Gambling var (w, result, amount, remaining) = await _service.DivorceWaifuAsync(ctx.User, targetId); if (result == DivorceResult.SucessWithPenalty) - { - await ReplyConfirmLocalizedAsync(strs.waifu_divorced_like(Format.Bold(w.Waifu.ToString()), amount + CurrencySign)); - } + await ReplyConfirmLocalizedAsync(strs.waifu_divorced_like(Format.Bold(w.Waifu.ToString()), + amount + CurrencySign)); else if (result == DivorceResult.Success) - { await ReplyConfirmLocalizedAsync(strs.waifu_divorced_notlike(amount + CurrencySign)); - } else if (result == DivorceResult.NotYourWife) - { await ReplyErrorLocalizedAsync(strs.waifu_not_yours); - } else - { await ReplyErrorLocalizedAsync(strs.waifu_recent_divorce( Format.Bold(((int)remaining?.TotalHours).ToString()), Format.Bold(remaining?.Minutes.ToString()))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] - public async Task Affinity([Leftover]IGuildUser u = null) + public async Task Affinity([Leftover] IGuildUser u = null) { if (u?.Id == ctx.User.Id) { await ReplyErrorLocalizedAsync(strs.waifu_egomaniac); return; } + var (oldAff, sucess, remaining) = await _service.ChangeAffinityAsync(ctx.User, u); if (!sucess) { if (remaining != null) - { await ReplyErrorLocalizedAsync(strs.waifu_affinity_cooldown( Format.Bold(((int)remaining?.TotalHours).ToString()), Format.Bold(remaining?.Minutes.ToString()))); - } else - { await ReplyErrorLocalizedAsync(strs.waifu_affinity_already); - } return; } + if (u is null) - { await ReplyConfirmLocalizedAsync(strs.waifu_affinity_reset); - } else if (oldAff is null) - { await ReplyConfirmLocalizedAsync(strs.waifu_affinity_set(Format.Bold(u.ToString()))); - } else - { - await ReplyConfirmLocalizedAsync(strs.waifu_affinity_changed(Format.Bold(oldAff.ToString()), Format.Bold(u.ToString()))); - } + await ReplyConfirmLocalizedAsync(strs.waifu_affinity_changed(Format.Bold(oldAff.ToString()), + Format.Bold(u.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task WaifuLb(int page = 1) { @@ -215,32 +206,32 @@ public partial class Gambling return; } - var embed = _eb.Create() - .WithTitle(GetText(strs.waifus_top_waifus)) - .WithOkColor(); + var embed = _eb.Create().WithTitle(GetText(strs.waifus_top_waifus)).WithOkColor(); var i = 0; foreach (var w in waifus) { var j = i++; - embed.AddField("#" + ((page * 9) + j + 1) + " - " + w.Price + CurrencySign, w.ToString(), false); + embed.AddField("#" + ((page * 9) + j + 1) + " - " + w.Price + CurrencySign, w.ToString()); } await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] - public Task WaifuInfo([Leftover]IUser target = null) + public Task WaifuInfo([Leftover] IUser target = null) { if (target is null) target = ctx.User; return InternalWaifuInfo(target.Id, target.ToString()); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] public Task WaifuInfo(ulong targetId) @@ -251,52 +242,50 @@ public partial class Gambling var wi = _service.GetFullWaifuInfoAsync(targetId); var affInfo = _service.GetAffinityTitle(wi.AffinityCount); - var waifuItems = _service.GetWaifuItems() - .ToDictionary(x => x.ItemEmoji, x => x); - - + var waifuItems = _service.GetWaifuItems().ToDictionary(x => x.ItemEmoji, x => x); + + var nobody = GetText(strs.nobody); var itemsStr = !wi.Items.Any() ? "-" : string.Join("\n", wi.Items.Where(x => waifuItems.TryGetValue(x.ItemEmoji, out _)) - .OrderBy(x => waifuItems[x.ItemEmoji].Price) - .GroupBy(x => x.ItemEmoji) - .Select(x => $"{x.Key} x{x.Count(),-3}") - .Chunk(2) - .Select(x => string.Join(" ", x)) - ); + .OrderBy(x => waifuItems[x.ItemEmoji].Price) + .GroupBy(x => x.ItemEmoji) + .Select(x => $"{x.Key} x{x.Count(),-3}") + .Chunk(2) + .Select(x => string.Join(" ", x))); + + var fansStr = wi.Fans.Shuffle().Take(30).Select(x => wi.Claims.Contains(x) ? $"{x} 💞" : x).Join('\n'); - var fansStr = wi - .Fans - .Shuffle() - .Take(30) - .Select(x => wi.Claims.Contains(x) ? $"{x} 💞" : x) - .Join('\n'); - if (string.IsNullOrWhiteSpace(fansStr)) fansStr = "-"; var embed = _eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.waifu) + " " + (wi.FullName ?? name ?? targetId.ToString()) + " - \"the " + - _service.GetClaimTitle(wi.ClaimCount) + "\"") - .AddField(GetText(strs.price), wi.Price.ToString(), true) - .AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true) - .AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true) - .AddField(GetText(strs.changes_of_heart), $"{wi.AffinityCount} - \"the {affInfo}\"", true) - .AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true) - .AddField("\u200B", "\u200B", true) - .AddField(GetText(strs.fans(wi.Fans.Count)), fansStr, true) - .AddField($"Waifus ({wi.ClaimCount})", wi.ClaimCount == 0 - ? nobody - : string.Join("\n", wi.Claims.Shuffle().Take(30)), true) - .AddField(GetText(strs.gifts), itemsStr, true); + .WithOkColor() + .WithTitle(GetText(strs.waifu) + + " " + + (wi.FullName ?? name ?? targetId.ToString()) + + " - \"the " + + _service.GetClaimTitle(wi.ClaimCount) + + "\"") + .AddField(GetText(strs.price), wi.Price.ToString(), true) + .AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true) + .AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true) + .AddField(GetText(strs.changes_of_heart), $"{wi.AffinityCount} - \"the {affInfo}\"", true) + .AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true) + .AddField("\u200B", "\u200B", true) + .AddField(GetText(strs.fans(wi.Fans.Count)), fansStr, true) + .AddField($"Waifus ({wi.ClaimCount})", + wi.ClaimCount == 0 ? nobody : string.Join("\n", wi.Claims.Shuffle().Take(30)), + true) + .AddField(GetText(strs.gifts), itemsStr, true); return ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] public async Task WaifuGift(int page = 1) @@ -305,28 +294,29 @@ public partial class Gambling return; var waifuItems = _service.GetWaifuItems(); - await ctx.SendPaginatedConfirmAsync(page, cur => - { - var embed = _eb.Create() - .WithTitle(GetText(strs.waifu_gift_shop)) - .WithOkColor(); + await ctx.SendPaginatedConfirmAsync(page, + cur => + { + var embed = _eb.Create().WithTitle(GetText(strs.waifu_gift_shop)).WithOkColor(); - waifuItems.OrderBy(x => x.Negative) - .ThenBy(x => x.Price) - .Skip(9 * cur) - .Take(9) - .ToList() - .ForEach(x => embed.AddField($"{(!x.Negative ? string.Empty : "\\💔")} {x.ItemEmoji} {x.Name}", - Format.Bold(x.Price.ToString()) + _config.Currency.Sign, - true - ) - ); + waifuItems.OrderBy(x => x.Negative) + .ThenBy(x => x.Price) + .Skip(9 * cur) + .Take(9) + .ToList() + .ForEach(x => embed.AddField( + $"{(!x.Negative ? string.Empty : "\\💔")} {x.ItemEmoji} {x.Name}", + Format.Bold(x.Price.ToString()) + _config.Currency.Sign, + true)); - return embed; - }, waifuItems.Count, 9); + return embed; + }, + waifuItems.Count, + 9); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] public async Task WaifuGift(string itemName, [Leftover] IUser waifu) @@ -341,18 +331,14 @@ public partial class Gambling await ReplyErrorLocalizedAsync(strs.waifu_gift_not_exist); return; } + var sucess = await _service.GiftWaifuAsync(ctx.User, waifu, item); if (sucess) - { - await ReplyConfirmLocalizedAsync(strs.waifu_gift( - Format.Bold(item.ToString() + " " + item.ItemEmoji), + await ReplyConfirmLocalizedAsync(strs.waifu_gift(Format.Bold(item + " " + item.ItemEmoji), Format.Bold(waifu.ToString()))); - } else - { await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); - } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/WheelOfFortuneCommands.cs b/src/NadekoBot/Modules/Gambling/WheelOfFortuneCommands.cs index ff629aa6e..792527399 100644 --- a/src/NadekoBot/Modules/Gambling/WheelOfFortuneCommands.cs +++ b/src/NadekoBot/Modules/Gambling/WheelOfFortuneCommands.cs @@ -1,8 +1,8 @@ #nullable disable -using Wof = NadekoBot.Modules.Gambling.Common.WheelOfFortune.WheelOfFortuneGame; -using NadekoBot.Modules.Gambling.Services; using NadekoBot.Modules.Gambling.Common; +using NadekoBot.Modules.Gambling.Services; using System.Collections.Immutable; +using Wof = NadekoBot.Modules.Gambling.Common.WheelOfFortune.WheelOfFortuneGame; namespace NadekoBot.Modules.Gambling; @@ -10,15 +10,8 @@ public partial class Gambling { public class WheelOfFortuneCommands : GamblingSubmodule { - private static readonly ImmutableArray _emojis = new string[] { - "⬆", - "↖", - "⬅", - "↙", - "⬇", - "↘", - "➡", - "↗" }.ToImmutableArray(); + private static readonly ImmutableArray _emojis = + new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray(); private readonly ICurrencyService _cs; private readonly DbService _db; @@ -30,13 +23,14 @@ public partial class Gambling _db = db; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task WheelOfFortune(ShmartNumber amount) { if (!await CheckBetMandatory(amount)) return; - if (!await _cs.RemoveAsync(ctx.User.Id, "Wheel Of Fortune - bet", amount, gamble: true)) + if (!await _cs.RemoveAsync(ctx.User.Id, "Wheel Of Fortune - bet", amount, true)) { await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); return; @@ -45,8 +39,7 @@ public partial class Gambling var result = await _service.WheelOfFortuneSpinAsync(ctx.User.Id, amount); var wofMultipliers = _config.WheelOfFortune.Multipliers; - await SendConfirmAsync( - Format.Bold($@"{ctx.User.ToString()} won: {result.Amount + CurrencySign} + await SendConfirmAsync(Format.Bold($@"{ctx.User.ToString()} won: {result.Amount + CurrencySign} 『{wofMultipliers[1]}』 『{wofMultipliers[0]}』 『{wofMultipliers[7]}』 @@ -55,4 +48,4 @@ public partial class Gambling 『{wofMultipliers[3]}』 『{wofMultipliers[4]}』 『{wofMultipliers[5]}』")); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/AcropobiaCommands.cs b/src/NadekoBot/Modules/Games/AcropobiaCommands.cs index ef4599bb3..3d25dcc6a 100644 --- a/src/NadekoBot/Modules/Games/AcropobiaCommands.cs +++ b/src/NadekoBot/Modules/Games/AcropobiaCommands.cs @@ -1,7 +1,7 @@ #nullable disable -using System.Collections.Immutable; using NadekoBot.Modules.Games.Common.Acrophobia; using NadekoBot.Modules.Games.Services; +using System.Collections.Immutable; namespace NadekoBot.Modules.Games; @@ -15,7 +15,8 @@ public partial class Games public AcropobiaCommands(DiscordSocketClient client) => _client = client; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [NadekoOptions(typeof(AcrophobiaGame.Options))] public async Task Acrophobia(params string[] args) @@ -25,7 +26,6 @@ public partial class Games var game = new AcrophobiaGame(options); if (_service.AcrophobiaGames.TryAdd(channel.Id, game)) - { try { game.OnStarted += Game_OnStarted; @@ -41,11 +41,8 @@ public partial class Games _service.AcrophobiaGames.TryRemove(channel.Id, out game); game.Dispose(); } - } else - { await ReplyErrorLocalizedAsync(strs.acro_running); - } Task _client_MessageReceived(SocketMessage msg) { @@ -69,44 +66,52 @@ public partial class Games private Task Game_OnStarted(AcrophobiaGame game) { - var embed = _eb.Create().WithOkColor() - .WithTitle(GetText(strs.acrophobia)) - .WithDescription(GetText(strs.acro_started(Format.Bold(string.Join(".", game.StartingLetters))))) - .WithFooter(GetText(strs.acro_started_footer(game.Opts.SubmissionTime))); + var embed = _eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.acrophobia)) + .WithDescription( + GetText(strs.acro_started(Format.Bold(string.Join(".", game.StartingLetters))))) + .WithFooter(GetText(strs.acro_started_footer(game.Opts.SubmissionTime))); return ctx.Channel.EmbedAsync(embed); } private Task Game_OnUserVoted(string user) - => SendConfirmAsync( - GetText(strs.acrophobia), - GetText(strs.acro_vote_cast(Format.Bold(user)))); + => SendConfirmAsync(GetText(strs.acrophobia), GetText(strs.acro_vote_cast(Format.Bold(user)))); - private async Task Game_OnVotingStarted(AcrophobiaGame game, ImmutableArray> submissions) + private async Task Game_OnVotingStarted( + AcrophobiaGame game, + ImmutableArray> submissions) { if (submissions.Length == 0) { await SendErrorAsync(GetText(strs.acrophobia), GetText(strs.acro_ended_no_sub)); return; } + if (submissions.Length == 1) { - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithDescription(GetText(strs.acro_winner_only(Format.Bold(submissions.First().Key.UserName)))) - .WithFooter(submissions.First().Key.Input)); + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithDescription(GetText( + strs.acro_winner_only( + Format.Bold(submissions.First().Key.UserName)))) + .WithFooter(submissions.First().Key.Input)); return; } var i = 0; var embed = _eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.acrophobia) + " - " + GetText(strs.submissions_closed)) - .WithDescription(GetText(strs.acro_nym_was(Format.Bold(string.Join(".", game.StartingLetters)) + "\n" + - $@"-- + .WithOkColor() + .WithTitle(GetText(strs.acrophobia) + " - " + GetText(strs.submissions_closed)) + .WithDescription(GetText(strs.acro_nym_was( + Format.Bold(string.Join(".", game.StartingLetters)) + + "\n" + + $@"-- {submissions.Aggregate("", (agg, cur) => agg + $"`{++i}.` **{cur.Key.Input}**\n")} --"))) - .WithFooter(GetText(strs.acro_vote)); + .WithFooter(GetText(strs.acro_vote)); await ctx.Channel.EmbedAsync(embed); } @@ -118,15 +123,17 @@ public partial class Games await SendErrorAsync(GetText(strs.acrophobia), GetText(strs.acro_no_votes_cast)); return; } + var table = votes.OrderByDescending(v => v.Value); var winner = table.First(); - var embed = _eb.Create().WithOkColor() - .WithTitle(GetText(strs.acrophobia)) - .WithDescription(GetText(strs.acro_winner(Format.Bold(winner.Key.UserName), - Format.Bold(winner.Value.ToString())))) - .WithFooter(winner.Key.Input); + var embed = _eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.acrophobia)) + .WithDescription(GetText(strs.acro_winner(Format.Bold(winner.Key.UserName), + Format.Bold(winner.Value.ToString())))) + .WithFooter(winner.Key.Input); await ctx.Channel.EmbedAsync(embed); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/CleverBotCommands.cs b/src/NadekoBot/Modules/Games/CleverBotCommands.cs index 4f1a0361f..34431bfc4 100644 --- a/src/NadekoBot/Modules/Games/CleverBotCommands.cs +++ b/src/NadekoBot/Modules/Games/CleverBotCommands.cs @@ -15,7 +15,8 @@ public partial class Games => _db = db; [NoPublicBot] - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task Cleverbot() @@ -29,6 +30,7 @@ public partial class Games uow.GuildConfigs.SetCleverbotEnabled(ctx.Guild.Id, false); await uow.SaveChangesAsync(); } + await ReplyConfirmLocalizedAsync(strs.cleverbot_disabled); return; } @@ -44,6 +46,4 @@ public partial class Games await ReplyConfirmLocalizedAsync(strs.cleverbot_enabled); } } - - -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Acrophobia/Acrophobia.cs b/src/NadekoBot/Modules/Games/Common/Acrophobia/Acrophobia.cs index e98905cf4..cac8d5ba6 100644 --- a/src/NadekoBot/Modules/Games/Common/Acrophobia/Acrophobia.cs +++ b/src/NadekoBot/Modules/Games/Common/Acrophobia/Acrophobia.cs @@ -1,28 +1,11 @@ #nullable disable -using System.Collections.Immutable; using CommandLine; +using System.Collections.Immutable; namespace NadekoBot.Modules.Games.Common.Acrophobia; public sealed class AcrophobiaGame : IDisposable { - public class Options : INadekoCommandOptions - { - [Option('s', "submission-time", Required = false, Default = 60, HelpText = "Time after which the submissions are closed and voting starts.")] - public int SubmissionTime { get; set; } = 60; - - [Option('v', "vote-time", Required = false, Default = 60, HelpText = "Time after which the voting is closed and the winner is declared.")] - public int VoteTime { get; set; } = 30; - - public void NormalizeOptions() - { - if (SubmissionTime is < 15 or > 300) - SubmissionTime = 60; - if (VoteTime is < 15 or > 120) - VoteTime = 30; - } - } - public enum Phase { Submission, @@ -39,19 +22,26 @@ public sealed class AcrophobiaGame : IDisposable Failed } + public event Func OnStarted = delegate { return Task.CompletedTask; }; + + public event Func>, Task> OnVotingStarted = + delegate { return Task.CompletedTask; }; + + public event Func OnUserVoted = delegate { return Task.CompletedTask; }; + + public event Func>, Task> OnEnded = delegate + { + return Task.CompletedTask; + }; + public Phase CurrentPhase { get; private set; } = Phase.Submission; public ImmutableArray StartingLetters { get; private set; } + public Options Opts { get; } private readonly Dictionary submissions = new(); private readonly SemaphoreSlim locker = new(1, 1); - public Options Opts { get; } private readonly NadekoRandom _rng; - public event Func OnStarted = delegate { return Task.CompletedTask; }; - public event Func>, Task> OnVotingStarted = delegate { return Task.CompletedTask; }; - public event Func OnUserVoted = delegate { return Task.CompletedTask; }; - public event Func>, Task> OnEnded = delegate { return Task.CompletedTask; }; - private readonly HashSet _usersWhoVoted = new(); public AcrophobiaGame(Options options) @@ -74,6 +64,7 @@ public sealed class AcrophobiaGame : IDisposable await OnVotingStarted(this, ImmutableArray.Create>()); return; } + if (submissions.Count == 1) { CurrentPhase = Phase.Ended; @@ -108,6 +99,7 @@ public sealed class AcrophobiaGame : IDisposable var randChar = (char)_rng.Next(65, 91); lettersArr[i] = randChar == 'X' ? (char)_rng.Next(65, 88) : randChar; } + StartingLetters = lettersArr.ToImmutableArray(); } @@ -137,9 +129,8 @@ public sealed class AcrophobiaGame : IDisposable ++submissions[toVoteFor]; var _ = Task.Run(() => OnUserVoted(userName)); return true; - default: - break; } + return false; } finally @@ -154,14 +145,16 @@ public sealed class AcrophobiaGame : IDisposable var inputWords = input.Split(' '); - if (inputWords.Length != StartingLetters.Length) // number of words must be the same as the number of the starting letters + if (inputWords.Length + != StartingLetters.Length) // number of words must be the same as the number of the starting letters return false; for (var i = 0; i < StartingLetters.Length; i++) { var letter = StartingLetters[i]; - if (!inputWords[i].StartsWith(letter.ToString(), StringComparison.InvariantCulture)) // all first letters must match + if (!inputWords[i] + .StartsWith(letter.ToString(), StringComparison.InvariantCulture)) // all first letters must match return false; } @@ -170,7 +163,7 @@ public sealed class AcrophobiaGame : IDisposable public void Dispose() { - this.CurrentPhase = Phase.Ended; + CurrentPhase = Phase.Ended; OnStarted = null; OnEnded = null; OnUserVoted = null; @@ -179,4 +172,29 @@ public sealed class AcrophobiaGame : IDisposable submissions.Clear(); locker.Dispose(); } -} + + public class Options : INadekoCommandOptions + { + [Option('s', + "submission-time", + Required = false, + Default = 60, + HelpText = "Time after which the submissions are closed and voting starts.")] + public int SubmissionTime { get; set; } = 60; + + [Option('v', + "vote-time", + Required = false, + Default = 60, + HelpText = "Time after which the voting is closed and the winner is declared.")] + public int VoteTime { get; set; } = 30; + + public void NormalizeOptions() + { + if (SubmissionTime is < 15 or > 300) + SubmissionTime = 60; + if (VoteTime is < 15 or > 120) + VoteTime = 30; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Acrophobia/AcrophobiaUser.cs b/src/NadekoBot/Modules/Games/Common/Acrophobia/AcrophobiaUser.cs index b2577477d..5eee75452 100644 --- a/src/NadekoBot/Modules/Games/Common/Acrophobia/AcrophobiaUser.cs +++ b/src/NadekoBot/Modules/Games/Common/Acrophobia/AcrophobiaUser.cs @@ -9,16 +9,14 @@ public class AcrophobiaUser public AcrophobiaUser(ulong userId, string userName, string input) { - this.UserName = userName; - this.UserId = userId; - this.Input = input; + UserName = userName; + UserId = userId; + Input = input; } public override int GetHashCode() => UserId.GetHashCode(); public override bool Equals(object obj) - => obj is AcrophobiaUser x - ? x.UserId == this.UserId - : false; -} + => obj is AcrophobiaUser x ? x.UserId == UserId : false; +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotResponse.cs b/src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotResponse.cs index e116d07f5..3836f34e2 100644 --- a/src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotResponse.cs +++ b/src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotResponse.cs @@ -5,4 +5,4 @@ public class ChatterBotResponse { public string Convo_id { get; set; } public string BotSay { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotSession.cs b/src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotSession.cs index 03eed45cb..eca281a2c 100644 --- a/src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotSession.cs +++ b/src/NadekoBot/Modules/Games/Common/ChatterBot/ChatterBotSession.cs @@ -7,6 +7,13 @@ public class ChatterBotSession : IChatterBotSession { private static NadekoRandom Rng { get; } = new(); + private string ApiEndpoint + => "http://api.program-o.com/v2/chatbot/" + + $"?bot_id={_botId}&" + + "say={0}&" + + $"convo_id=nadekobot_{_chatterBotId}&" + + "format=json"; + private readonly string _chatterBotId; private readonly IHttpClientFactory _httpFactory; private readonly int _botId = 6; @@ -17,12 +24,6 @@ public class ChatterBotSession : IChatterBotSession _httpFactory = httpFactory; } - private string ApiEndpoint => "http://api.program-o.com/v2/chatbot/" + - $"?bot_id={_botId}&" + - "say={0}&" + - $"convo_id=nadekobot_{_chatterBotId}&" + - "format=json"; - public async Task Think(string message) { using var http = _httpFactory.CreateClient(); @@ -30,4 +31,4 @@ public class ChatterBotSession : IChatterBotSession var cbr = JsonConvert.DeserializeObject(res); return cbr.BotSay.Replace("
", "\n", StringComparison.InvariantCulture); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/ChatterBot/CleverbotResponse.cs b/src/NadekoBot/Modules/Games/Common/ChatterBot/CleverbotResponse.cs index b45a0a1af..9581ab73f 100644 --- a/src/NadekoBot/Modules/Games/Common/ChatterBot/CleverbotResponse.cs +++ b/src/NadekoBot/Modules/Games/Common/ChatterBot/CleverbotResponse.cs @@ -17,4 +17,4 @@ public class CleverbotIOAskResponse { public string Status { get; set; } public string Response { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/ChatterBot/IChatterBotSession.cs b/src/NadekoBot/Modules/Games/Common/ChatterBot/IChatterBotSession.cs index 237cb07d8..5fff26978 100644 --- a/src/NadekoBot/Modules/Games/Common/ChatterBot/IChatterBotSession.cs +++ b/src/NadekoBot/Modules/Games/Common/ChatterBot/IChatterBotSession.cs @@ -4,4 +4,4 @@ namespace NadekoBot.Modules.Games.Common.ChatterBot; public interface IChatterBotSession { Task Think(string input); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/ChatterBot/OfficialCleverbotSession.cs b/src/NadekoBot/Modules/Games/Common/ChatterBot/OfficialCleverbotSession.cs index aff95253c..8e9b24fb9 100644 --- a/src/NadekoBot/Modules/Games/Common/ChatterBot/OfficialCleverbotSession.cs +++ b/src/NadekoBot/Modules/Games/Common/ChatterBot/OfficialCleverbotSession.cs @@ -5,19 +5,17 @@ namespace NadekoBot.Modules.Games.Common.ChatterBot; public class OfficialCleverbotSession : IChatterBotSession { + private string QueryString + => $"https://www.cleverbot.com/getreply?key={_apiKey}" + "&wrapper=nadekobot" + "&input={0}" + "&cs={1}"; + private readonly string _apiKey; private readonly IHttpClientFactory _httpFactory; - private string _cs = null; - - private string QueryString => $"https://www.cleverbot.com/getreply?key={_apiKey}" + - "&wrapper=nadekobot" + - "&input={0}" + - "&cs={1}"; + private string _cs; public OfficialCleverbotSession(string apiKey, IHttpClientFactory factory) { - this._apiKey = apiKey; - this._httpFactory = factory; + _apiKey = apiKey; + _httpFactory = factory; } public async Task Think(string input) @@ -47,14 +45,14 @@ public class CleverbotIOSession : IChatterBotSession private readonly IHttpClientFactory _httpFactory; private readonly AsyncLazy _nick; - private readonly string _createEndpoint = $"https://cleverbot.io/1.0/create"; - private readonly string _askEndpoint = $"https://cleverbot.io/1.0/ask"; + private readonly string _createEndpoint = "https://cleverbot.io/1.0/create"; + private readonly string _askEndpoint = "https://cleverbot.io/1.0/ask"; public CleverbotIOSession(string user, string key, IHttpClientFactory factory) { - this._key = key; - this._user = user; - this._httpFactory = factory; + _key = key; + _user = user; + _httpFactory = factory; _nick = new(GetNick); } @@ -64,8 +62,7 @@ public class CleverbotIOSession : IChatterBotSession using var _http = _httpFactory.CreateClient(); using var msg = new FormUrlEncodedContent(new[] { - new KeyValuePair("user", _user), - new KeyValuePair("key", _key), + new KeyValuePair("user", _user), new KeyValuePair("key", _key) }); using var data = await _http.PostAsync(_createEndpoint, msg); var str = await data.Content.ReadAsStringAsync(); @@ -81,10 +78,8 @@ public class CleverbotIOSession : IChatterBotSession using var _http = _httpFactory.CreateClient(); using var msg = new FormUrlEncodedContent(new[] { - new KeyValuePair("user", _user), - new KeyValuePair("key", _key), - new KeyValuePair("nick", await _nick), - new KeyValuePair("text", input), + new KeyValuePair("user", _user), new KeyValuePair("key", _key), + new KeyValuePair("nick", await _nick), new KeyValuePair("text", input) }); using var data = await _http.PostAsync(_askEndpoint, msg); var str = await data.Content.ReadAsStringAsync(); @@ -94,4 +89,4 @@ public class CleverbotIOSession : IChatterBotSession return obj.Response; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/GamesConfig.cs b/src/NadekoBot/Modules/Games/Common/GamesConfig.cs index be31c7480..f826627ef 100644 --- a/src/NadekoBot/Modules/Games/Common/GamesConfig.cs +++ b/src/NadekoBot/Modules/Games/Common/GamesConfig.cs @@ -11,17 +11,10 @@ public sealed partial class GamesConfig : ICloneable public int Version { get; set; } [Comment("Hangman related settings (.hangman command)")] - public HangmanConfig Hangman { get; set; } = new() - { - CurrencyReward = 0 - }; - + public HangmanConfig Hangman { get; set; } = new() { CurrencyReward = 0 }; + [Comment("Trivia related settings (.t command)")] - public TriviaConfig Trivia { get; set; } = new() - { - CurrencyReward = 0, - MinimumWinReq = 1, - }; + public TriviaConfig Trivia { get; set; } = new() { CurrencyReward = 0, MinimumWinReq = 1 }; [Comment("List of responses for the .8ball command. A random one will be selected every time")] public List EightBallResponses { get; set; } = new() @@ -76,7 +69,7 @@ public sealed partial class HangmanConfig public sealed partial class TriviaConfig { [Comment("The amount of currency awarded to the winner of the trivia game.")] - public long CurrencyReward { get; set; } = 0; + public long CurrencyReward { get; set; } [Comment(@"Users won't be able to start trivia games which have a smaller win requirement than the one specified by this setting.")] @@ -88,4 +81,4 @@ public sealed partial class RaceAnimal { public string Icon { get; set; } public string Name { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/GirlRating.cs b/src/NadekoBot/Modules/Games/Common/GirlRating.cs index bd02851fc..23db54f78 100644 --- a/src/NadekoBot/Modules/Games/Common/GirlRating.cs +++ b/src/NadekoBot/Modules/Games/Common/GirlRating.cs @@ -1,24 +1,29 @@ #nullable disable -using Image = SixLabors.ImageSharp.Image; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; +using Image = SixLabors.ImageSharp.Image; namespace NadekoBot.Modules.Games.Common; public class GirlRating { - private readonly IImageCache _images; - public double Crazy { get; } public double Hot { get; } public int Roll { get; } public string Advice { get; } + public AsyncLazy Stream { get; } + private readonly IImageCache _images; + private readonly IHttpClientFactory _httpFactory; - public AsyncLazy Stream { get; } - - public GirlRating(IImageCache images, IHttpClientFactory factory, double crazy, double hot, int roll, string advice) + public GirlRating( + IImageCache images, + IHttpClientFactory factory, + double crazy, + double hot, + int roll, + string advice) { _images = images; Crazy = crazy; @@ -26,7 +31,7 @@ public class GirlRating Roll = roll; Advice = advice; // convenient to have it here, even though atm there are only few different ones. _httpFactory = factory; - + Stream = new(() => { try @@ -64,4 +69,4 @@ public class GirlRating } }); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Hangman/DefaultHangmanSource.cs b/src/NadekoBot/Modules/Games/Common/Hangman/DefaultHangmanSource.cs index 8068e3926..81c47e674 100644 --- a/src/NadekoBot/Modules/Games/Common/Hangman/DefaultHangmanSource.cs +++ b/src/NadekoBot/Modules/Games/Common/Hangman/DefaultHangmanSource.cs @@ -1,5 +1,5 @@ -using System.Diagnostics.CodeAnalysis; -using NadekoBot.Common.Yml; +using NadekoBot.Common.Yml; +using System.Diagnostics.CodeAnalysis; namespace NadekoBot.Modules.Games.Hangman; @@ -24,7 +24,6 @@ public sealed class DefaultHangmanSource : IHangmanSource var qs = new Dictionary(); foreach (var file in Directory.EnumerateFiles("data/hangman/", "*.yml")) - { try { var data = Yaml.Deserializer.Deserialize(File.ReadAllText(file)); @@ -34,14 +33,13 @@ public sealed class DefaultHangmanSource : IHangmanSource { Log.Error(ex, "Loading {HangmanFile} failed.", file); } - } _terms = qs; - + Log.Information("Loaded {HangmanCategoryCount} hangman categories.", qs.Count); } - public IReadOnlyCollection GetCategories() + public IReadOnlyCollection GetCategories() => _terms.Keys.ToList(); public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term) @@ -61,4 +59,4 @@ public sealed class DefaultHangmanSource : IHangmanSource term = null; return false; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Hangman/HangmanGame.cs b/src/NadekoBot/Modules/Games/Common/Hangman/HangmanGame.cs index d570a10fd..61651a6cd 100644 --- a/src/NadekoBot/Modules/Games/Common/Hangman/HangmanGame.cs +++ b/src/NadekoBot/Modules/Games/Common/Hangman/HangmanGame.cs @@ -5,22 +5,12 @@ namespace NadekoBot.Modules.Games.Hangman; public sealed class HangmanGame { - public enum Phase { Running, Ended } public enum GuessResult { NoAction, AlreadyTried, Incorrect, Guess, Win } - public record State( - int Errors, - Phase Phase, - string Word, - GuessResult GuessResult, - List missedLetters, - string ImageUrl) - { - public bool Failed => Errors > 5; - } + public enum Phase { Running, Ended } private Phase CurrentPhase { get; set; } - + private readonly HashSet _incorrect = new(); private readonly HashSet _correct = new(); private readonly HashSet _remaining = new(); @@ -32,25 +22,17 @@ public sealed class HangmanGame { _word = term.Word; _imageUrl = term.ImageUrl; - - _remaining = _word - .ToLowerInvariant() - .Where(x => x.IsLetter()) - .Select(char.ToLowerInvariant) - .ToHashSet(); + + _remaining = _word.ToLowerInvariant().Where(x => x.IsLetter()).Select(char.ToLowerInvariant).ToHashSet(); } public State GetState(GuessResult guessResult = GuessResult.NoAction) => new(_incorrect.Count, CurrentPhase, - CurrentPhase == Phase.Ended - ? _word - : GetScrambledWord(), + CurrentPhase == Phase.Ended ? _word : GetScrambledWord(), guessResult, _incorrect.ToList(), - CurrentPhase == Phase.Ended - ? _imageUrl - : string.Empty); + CurrentPhase == Phase.Ended ? _imageUrl : string.Empty); private string GetScrambledWord() { @@ -59,11 +41,11 @@ public sealed class HangmanGame { var ch = _word[i]; if (ch == ' ') - output[i*2] = ' '; + output[i * 2] = ' '; if (!ch.IsLetter() || !_remaining.Contains(char.ToLowerInvariant(ch))) - output[i*2] = ch; + output[i * 2] = ch; else - output[i*2] = '_'; + output[i * 2] = '_'; output[(i * 2) + 1] = ' '; } @@ -74,7 +56,7 @@ public sealed class HangmanGame public State Guess(string guess) { if (CurrentPhase != Phase.Running) - return GetState(GuessResult.NoAction); + return GetState(); guess = guess.Trim(); if (guess.Length > 1) @@ -85,12 +67,12 @@ public sealed class HangmanGame return GetState(GuessResult.Win); } - return GetState(GuessResult.NoAction); + return GetState(); } var charGuess = guess[0]; if (!char.IsLetter(charGuess)) - return GetState(GuessResult.NoAction); + return GetState(); if (_incorrect.Contains(charGuess) || _correct.Contains(charGuess)) return GetState(GuessResult.AlreadyTried); @@ -116,4 +98,16 @@ public sealed class HangmanGame return GetState(GuessResult.Incorrect); } -} + + public record State( + int Errors, + Phase Phase, + string Word, + GuessResult GuessResult, + List missedLetters, + string ImageUrl) + { + public bool Failed + => Errors > 5; + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Hangman/HangmanService.cs b/src/NadekoBot/Modules/Games/Common/Hangman/HangmanService.cs index 7e9d7a1a1..e07a3156f 100644 --- a/src/NadekoBot/Modules/Games/Common/Hangman/HangmanService.cs +++ b/src/NadekoBot/Modules/Games/Common/Hangman/HangmanService.cs @@ -1,7 +1,7 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Memory; using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Modules.Games.Services; +using System.Diagnostics.CodeAnalysis; namespace NadekoBot.Modules.Games.Hangman; @@ -15,8 +15,12 @@ public sealed class HangmanService : IHangmanService, ILateExecutor private readonly IMemoryCache _cdCache; private readonly object _locker = new(); - public HangmanService(IHangmanSource source, IEmbedBuilderService eb, GamesConfigService gcs, - ICurrencyService cs, IMemoryCache cdCache) + public HangmanService( + IHangmanSource source, + IEmbedBuilderService eb, + GamesConfigService gcs, + ICurrencyService cs, + IMemoryCache cdCache) { _source = source; _eb = eb; @@ -25,10 +29,7 @@ public sealed class HangmanService : IHangmanService, ILateExecutor _cdCache = cdCache; } - public bool StartHangman( - ulong channelId, - string? category, - [NotNullWhen(true)] out HangmanGame.State? state) + public bool StartHangman(ulong channelId, string? category, [NotNullWhen(true)] out HangmanGame.State? state) { state = null; if (!_source.GetTerm(category, out var term)) @@ -53,10 +54,7 @@ public sealed class HangmanService : IHangmanService, ILateExecutor { lock (_locker) { - if (_hangmanGames.TryRemove(channelId, out var game)) - { - return new(true); - } + if (_hangmanGames.TryRemove(channelId, out var game)) return new(true); } return new(false); @@ -74,7 +72,7 @@ public sealed class HangmanService : IHangmanService, ILateExecutor if (_cdCache.TryGetValue(msg.Author.Id, out _)) return; - + HangmanGame.State state; long rew = 0; lock (_locker) @@ -88,13 +86,10 @@ public sealed class HangmanService : IHangmanService, ILateExecutor return; if (state.GuessResult is HangmanGame.GuessResult.Incorrect or HangmanGame.GuessResult.AlreadyTried) - { - _cdCache.Set(msg.Author.Id, string.Empty, new MemoryCacheEntryOptions() - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3) - }); - } - + _cdCache.Set(msg.Author.Id, + string.Empty, + new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3) }); + if (state.Phase == HangmanGame.Phase.Ended) if (_hangmanGames.TryRemove(msg.Channel.Id, out _)) rew = _gcs.Data.Hangman.CurrencyReward; @@ -106,32 +101,28 @@ public sealed class HangmanService : IHangmanService, ILateExecutor await SendState((ITextChannel)msg.Channel, msg.Author, msg.Content, state); } } - - private Task SendState(ITextChannel channel, IUser user, string content, HangmanGame.State state) + + private Task SendState( + ITextChannel channel, + IUser user, + string content, + HangmanGame.State state) { var embed = Games.HangmanCommands.GetEmbed(_eb, state); if (state.GuessResult == HangmanGame.GuessResult.Guess) - embed.WithDescription($"{user} guessed the letter {content}!") - .WithOkColor(); + embed.WithDescription($"{user} guessed the letter {content}!").WithOkColor(); else if (state.GuessResult == HangmanGame.GuessResult.Incorrect && state.Failed) - embed.WithDescription($"{user} Letter {content} doesn't exist! Game over!") - .WithErrorColor(); + embed.WithDescription($"{user} Letter {content} doesn't exist! Game over!").WithErrorColor(); else if (state.GuessResult == HangmanGame.GuessResult.Incorrect) - embed.WithDescription($"{user} Letter {content} doesn't exist!") - .WithErrorColor(); + embed.WithDescription($"{user} Letter {content} doesn't exist!").WithErrorColor(); else if (state.GuessResult == HangmanGame.GuessResult.AlreadyTried) - embed.WithDescription($"{user} Letter {content} has already been used.") - .WithPendingColor(); + embed.WithDescription($"{user} Letter {content} has already been used.").WithPendingColor(); else if (state.GuessResult == HangmanGame.GuessResult.Win) - embed.WithDescription($"{user} won!") - .WithOkColor(); + embed.WithDescription($"{user} won!").WithOkColor(); - if (!string.IsNullOrWhiteSpace(state.ImageUrl) - && Uri.IsWellFormedUriString(state.ImageUrl, UriKind.Absolute)) - { + if (!string.IsNullOrWhiteSpace(state.ImageUrl) && Uri.IsWellFormedUriString(state.ImageUrl, UriKind.Absolute)) embed.WithImageUrl(state.ImageUrl); - } return channel.EmbedAsync(embed); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Hangman/HangmanTerm.cs b/src/NadekoBot/Modules/Games/Common/Hangman/HangmanTerm.cs index dcc7c4f8c..495f160e1 100644 --- a/src/NadekoBot/Modules/Games/Common/Hangman/HangmanTerm.cs +++ b/src/NadekoBot/Modules/Games/Common/Hangman/HangmanTerm.cs @@ -5,4 +5,4 @@ public sealed class HangmanTerm { public string Word { get; set; } public string ImageUrl { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Hangman/IHangmanService.cs b/src/NadekoBot/Modules/Games/Common/Hangman/IHangmanService.cs index a0046c7a7..e7c5bd813 100644 --- a/src/NadekoBot/Modules/Games/Common/Hangman/IHangmanService.cs +++ b/src/NadekoBot/Modules/Games/Common/Hangman/IHangmanService.cs @@ -7,4 +7,4 @@ public interface IHangmanService bool StartHangman(ulong channelId, string? category, [NotNullWhen(true)] out HangmanGame.State? hangmanController); ValueTask StopHangman(ulong channelId); IReadOnlyCollection GetHangmanTypes(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Hangman/IHangmanSource.cs b/src/NadekoBot/Modules/Games/Common/Hangman/IHangmanSource.cs index dce34199e..68efa6ce5 100644 --- a/src/NadekoBot/Modules/Games/Common/Hangman/IHangmanSource.cs +++ b/src/NadekoBot/Modules/Games/Common/Hangman/IHangmanSource.cs @@ -7,4 +7,4 @@ public interface IHangmanSource : INService public IReadOnlyCollection GetCategories(); public void Reload(); public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Nunchi/Nunchi.cs b/src/NadekoBot/Modules/Games/Common/Nunchi/Nunchi.cs index ada3e3d86..d0d359f04 100644 --- a/src/NadekoBot/Modules/Games/Common/Nunchi/Nunchi.cs +++ b/src/NadekoBot/Modules/Games/Common/Nunchi/Nunchi.cs @@ -10,11 +10,11 @@ public sealed class NunchiGame : IDisposable Joining, Playing, WaitingForNextRound, - Ended, + Ended } - public int CurrentNumber { get; private set; } = new NadekoRandom().Next(0, 100); - public Phase CurrentPhase { get; private set; } = Phase.Joining; + private const int _killTimeout = 20 * 1000; + private const int _nextRoundTimeout = 5 * 1000; public event Func OnGameStarted; public event Func OnRoundStarted; @@ -22,16 +22,19 @@ public sealed class NunchiGame : IDisposable public event Func OnRoundEnded; // tuple of the user who failed public event Func OnGameEnded; // name of the user who won + public int CurrentNumber { get; private set; } = new NadekoRandom().Next(0, 100); + public Phase CurrentPhase { get; private set; } = Phase.Joining; + + public ImmutableArray<(ulong Id, string Name)> Participants + => _participants.ToImmutableArray(); + + public int ParticipantCount + => _participants.Count; + private readonly SemaphoreSlim _locker = new(1, 1); private HashSet<(ulong Id, string Name)> _participants = new(); private readonly HashSet<(ulong Id, string Name)> _passed = new(); - - public ImmutableArray<(ulong Id, string Name)> Participants => _participants.ToImmutableArray(); - public int ParticipantCount => _participants.Count; - - private const int _killTimeout = 20 * 1000; - private const int _nextRoundTimeout = 5 * 1000; private Timer _killTimer; public NunchiGame(ulong creatorId, string creatorName) @@ -64,19 +67,22 @@ public sealed class NunchiGame : IDisposable } _killTimer = new(async state => - { - await _locker.WaitAsync(); - try { - if (CurrentPhase != Phase.Playing) - return; + await _locker.WaitAsync(); + try + { + if (CurrentPhase != Phase.Playing) + return; - //if some players took too long to type a number, boot them all out and start a new round - _participants = new HashSet<(ulong, string)>(_passed); - EndRound(); - } - finally { _locker.Release(); } - }, null, _killTimeout, _killTimeout); + //if some players took too long to type a number, boot them all out and start a new round + _participants = new HashSet<(ulong, string)>(_passed); + EndRound(); + } + finally { _locker.Release(); } + }, + null, + _killTimeout, + _killTimeout); CurrentPhase = Phase.Playing; var _ = OnGameStarted?.Invoke(this); @@ -145,7 +151,7 @@ public sealed class NunchiGame : IDisposable _killTimer.Change(_killTimeout, _killTimeout); CurrentNumber = new NadekoRandom().Next(0, 100); // reset the counter _passed.Clear(); // reset all users who passed (new round starts) - if(failure != null) + if (failure != null) _participants.Remove(failure.Value); // remove the dude who failed from the list of players var __ = OnRoundEnded?.Invoke(this, failure); @@ -156,6 +162,7 @@ public sealed class NunchiGame : IDisposable var _ = OnGameEnded?.Invoke(this, _participants.Count > 0 ? _participants.First().Name : null); return; } + CurrentPhase = Phase.WaitingForNextRound; var throwawayDelay = Task.Run(async () => { @@ -163,7 +170,6 @@ public sealed class NunchiGame : IDisposable CurrentPhase = Phase.Playing; var ___ = OnRoundStarted?.Invoke(this, CurrentNumber); }); - } public void Dispose() @@ -174,4 +180,4 @@ public sealed class NunchiGame : IDisposable OnRoundStarted = null; OnUserGuessed = null; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/PollRunner.cs b/src/NadekoBot/Modules/Games/Common/PollRunner.cs index 08fcd1f8f..197c46d1a 100644 --- a/src/NadekoBot/Modules/Games/Common/PollRunner.cs +++ b/src/NadekoBot/Modules/Games/Common/PollRunner.cs @@ -5,11 +5,10 @@ namespace NadekoBot.Modules.Games.Common; public class PollRunner { + public event Func OnVoted; public Poll Poll { get; } private readonly DbService _db; - public event Func OnVoted; - private readonly SemaphoreSlim _locker = new(1, 1); public PollRunner(DbService db, Poll poll) @@ -40,11 +39,7 @@ public class PollRunner if (usr is null) return false; - voteObj = new() - { - UserId = msg.Author.Id, - VoteIndex = vote, - }; + voteObj = new() { UserId = msg.Author.Id, VoteIndex = vote }; if (!Poll.Votes.Add(voteObj)) return false; @@ -61,4 +56,4 @@ public class PollRunner public void End() => OnVoted = null; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/TicTacToe.cs b/src/NadekoBot/Modules/Games/Common/TicTacToe.cs index c8644cb3b..e90593388 100644 --- a/src/NadekoBot/Modules/Games/Common/TicTacToe.cs +++ b/src/NadekoBot/Modules/Games/Common/TicTacToe.cs @@ -1,30 +1,12 @@ #nullable disable -using System.Text; using CommandLine; +using System.Text; namespace NadekoBot.Modules.Games.Common; public class TicTacToe { - public class Options : INadekoCommandOptions - { - public void NormalizeOptions() - { - if (TurnTimer is < 5 or > 60) - TurnTimer = 15; - } - - [Option('t', "turn-timer", Required = false, Default = 15, HelpText = "Turn time in seconds. Default 15.")] - public int TurnTimer { get; set; } = 15; - } - - private enum Phase - { - Starting, - Started, - Ended - } - + public event Action OnEnded; private readonly ITextChannel _channel; private readonly IGuildUser[] _users; private readonly int?[,] _state; @@ -34,9 +16,10 @@ public class TicTacToe private IGuildUser _winner; - private readonly string[] _numbers = { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" }; - - public event Action OnEnded; + private readonly string[] _numbers = + { + ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" + }; private IUserMessage _previousMessage; private Timer _timeoutTimer; @@ -45,8 +28,13 @@ public class TicTacToe private readonly Options _options; private readonly IEmbedBuilderService _eb; - public TicTacToe(IBotStrings strings, DiscordSocketClient client, ITextChannel channel, - IGuildUser firstUser, Options options, IEmbedBuilderService eb) + public TicTacToe( + IBotStrings strings, + DiscordSocketClient client, + ITextChannel channel, + IGuildUser firstUser, + Options options, + IEmbedBuilderService eb) { _channel = channel; _strings = strings; @@ -55,11 +43,7 @@ public class TicTacToe _eb = eb; _users = new[] { firstUser, null }; - _state = new int?[,] { - { null, null, null }, - { null, null, null }, - { null, null, null }, - }; + _state = new int?[,] { { null, null, null }, { null, null, null }, { null, null, null } }; _phase = Phase.Starting; _moveLock = new(1, 1); @@ -79,6 +63,7 @@ public class TicTacToe if (j < _state.GetLength(1) - 1) sb.Append("┃"); } + if (i < _state.GetLength(0) - 1) sb.AppendLine("\n──────────"); } @@ -89,9 +74,9 @@ public class TicTacToe public IEmbedBuilder GetEmbed(string title = null) { var embed = _eb.Create() - .WithOkColor() - .WithDescription(Environment.NewLine + GetState()) - .WithAuthor(GetText(strs.vs(_users[0], _users[1]))); + .WithOkColor() + .WithDescription(Environment.NewLine + GetState()) + .WithAuthor(GetText(strs.vs(_users[0], _users[1]))); if (!string.IsNullOrWhiteSpace(title)) embed.WithTitle(title); @@ -104,7 +89,9 @@ public class TicTacToe embed.WithFooter(GetText(strs.ttt_users_move(_users[_curUserIndex]))); } else + { embed.WithFooter(GetText(strs.ttt_has_won(_winner))); + } return embed; } @@ -133,7 +120,8 @@ public class TicTacToe await _channel.SendErrorAsync(_eb, user.Mention + GetText(strs.ttt_already_running)); return; } - else if (_users[0] == user) + + if (_users[0] == user) { await _channel.SendErrorAsync(_eb, user.Mention + GetText(strs.ttt_against_yourself)); return; @@ -144,35 +132,38 @@ public class TicTacToe _phase = Phase.Started; _timeoutTimer = new(async _ => - { - await _moveLock.WaitAsync(); - try { - if (_phase == Phase.Ended) - return; - - _phase = Phase.Ended; - if (_users[1] != null) + await _moveLock.WaitAsync(); + try { - _winner = _users[_curUserIndex ^= 1]; - var del = _previousMessage?.DeleteAsync(); - try - { - await _channel.EmbedAsync(GetEmbed(GetText(strs.ttt_time_expired))); - if (del != null) - await del; - } - catch { } - } + if (_phase == Phase.Ended) + return; - OnEnded?.Invoke(this); - } - catch { } - finally - { - _moveLock.Release(); - } - }, null, _options.TurnTimer * 1000, Timeout.Infinite); + _phase = Phase.Ended; + if (_users[1] != null) + { + _winner = _users[_curUserIndex ^= 1]; + var del = _previousMessage?.DeleteAsync(); + try + { + await _channel.EmbedAsync(GetEmbed(GetText(strs.ttt_time_expired))); + if (del != null) + await del; + } + catch { } + } + + OnEnded?.Invoke(this); + } + catch { } + finally + { + _moveLock.Release(); + } + }, + null, + _options.TurnTimer * 1000, + Timeout.Infinite); _client.MessageReceived += Client_MessageReceived; @@ -183,13 +174,9 @@ public class TicTacToe private bool IsDraw() { for (var i = 0; i < 3; i++) - { - for (var j = 0; j < 3; j++) - { - if (_state[i, j] is null) - return false; - } - } + for (var j = 0; j < 3; j++) + if (_state[i, j] is null) + return false; return true; } @@ -204,10 +191,10 @@ public class TicTacToe if (_phase == Phase.Ended || msg.Author?.Id != curUser.Id) return; - if (int.TryParse(msg.Content, out var index) && - --index >= 0 && - index <= 9 && - _state[index / 3, index % 3] is null) + if (int.TryParse(msg.Content, out var index) + && --index >= 0 + && index <= 9 + && _state[index / 3, index % 3] is null) { _state[index / 3, index % 3] = _curUserIndex; @@ -220,7 +207,8 @@ public class TicTacToe _phase = Phase.Ended; } - else if (_state[0, index % 3] == _state[1, index % 3] && _state[1, index % 3] == _state[2, index % 3]) + else if (_state[0, index % 3] == _state[1, index % 3] + && _state[1, index % 3] == _state[2, index % 3]) { _state[0, index % 3] = _curUserIndex + 2; _state[1, index % 3] = _curUserIndex + 2; @@ -228,7 +216,9 @@ public class TicTacToe _phase = Phase.Ended; } - else if (_curUserIndex == _state[0, 0] && _state[0, 0] == _state[1, 1] && _state[1, 1] == _state[2, 2]) + else if (_curUserIndex == _state[0, 0] + && _state[0, 0] == _state[1, 1] + && _state[1, 1] == _state[2, 2]) { _state[0, 0] = _curUserIndex + 2; _state[1, 1] = _curUserIndex + 2; @@ -236,7 +226,9 @@ public class TicTacToe _phase = Phase.Ended; } - else if (_curUserIndex == _state[0, 2] && _state[0, 2] == _state[1, 1] && _state[1, 1] == _state[2, 0]) + else if (_curUserIndex == _state[0, 2] + && _state[0, 2] == _state[1, 1] + && _state[1, 1] == _state[2, 0]) { _state[0, 2] = _curUserIndex + 2; _state[1, 1] = _curUserIndex + 2; @@ -244,6 +236,7 @@ public class TicTacToe _phase = Phase.Ended; } + var reason = string.Empty; if (_phase == Phase.Ended) // if user won, stop receiving moves @@ -265,9 +258,17 @@ public class TicTacToe { var del1 = msg.DeleteAsync(); var del2 = _previousMessage?.DeleteAsync(); - try { _previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); } catch { } - try { await del1; } catch { } - try { if (del2 != null) await del2; } catch { } + try { _previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); } + catch { } + + try { await del1; } + catch { } + + try + { + if (del2 != null) await del2; + } + catch { } }); _curUserIndex ^= 1; @@ -282,4 +283,23 @@ public class TicTacToe return Task.CompletedTask; } -} + + public class Options : INadekoCommandOptions + { + [Option('t', "turn-timer", Required = false, Default = 15, HelpText = "Turn time in seconds. Default 15.")] + public int TurnTimer { get; set; } = 15; + + public void NormalizeOptions() + { + if (TurnTimer is < 5 or > 60) + TurnTimer = 15; + } + } + + private enum Phase + { + Starting, + Started, + Ended + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Trivia/TriviaGame.cs b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaGame.cs index 5887d0ab4..983be63dd 100644 --- a/src/NadekoBot/Modules/Games/Common/Trivia/TriviaGame.cs +++ b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaGame.cs @@ -1,23 +1,14 @@ #nullable disable +using System.Net; using System.Text; namespace NadekoBot.Modules.Games.Common.Trivia; public class TriviaGame { - private readonly SemaphoreSlim _guessLock = new(1, 1); - private readonly IDataCache _cache; - private readonly IBotStrings _strings; - private readonly DiscordSocketClient _client; - private readonly GamesConfig _config; - private readonly ICurrencyService _cs; - private readonly TriviaOptions _options; - public IGuild Guild { get; } public ITextChannel Channel { get; } - private CancellationTokenSource _triviaCancelSource; - public TriviaQuestion CurrentQuestion { get; private set; } public HashSet OldQuestions { get; } = new(); @@ -25,15 +16,32 @@ public class TriviaGame public bool GameActive { get; private set; } public bool ShouldStopGame { get; private set; } + private readonly SemaphoreSlim _guessLock = new(1, 1); + private readonly IDataCache _cache; + private readonly IBotStrings _strings; + private readonly DiscordSocketClient _client; + private readonly GamesConfig _config; + private readonly ICurrencyService _cs; + private readonly TriviaOptions _options; + + private CancellationTokenSource _triviaCancelSource; private readonly TriviaQuestionPool _questionPool; - private int _timeoutCount = 0; + private int _timeoutCount; private readonly string _quitCommand; private readonly IEmbedBuilderService _eb; - public TriviaGame(IBotStrings strings, DiscordSocketClient client, GamesConfig config, - IDataCache cache, ICurrencyService cs, IGuild guild, ITextChannel channel, - TriviaOptions options, string quitCommand, IEmbedBuilderService eb) + public TriviaGame( + IBotStrings strings, + DiscordSocketClient client, + GamesConfig config, + IDataCache cache, + ICurrencyService cs, + IGuild guild, + ITextChannel channel, + TriviaOptions options, + string quitCommand, + IEmbedBuilderService eb) { _cache = cache; _questionPool = new(_cache); @@ -63,31 +71,36 @@ public class TriviaGame // load question CurrentQuestion = _questionPool.GetRandomQuestion(OldQuestions, _options.IsPokemon); - if (string.IsNullOrWhiteSpace(CurrentQuestion?.Answer) || string.IsNullOrWhiteSpace(CurrentQuestion.Question)) + if (string.IsNullOrWhiteSpace(CurrentQuestion?.Answer) + || string.IsNullOrWhiteSpace(CurrentQuestion.Question)) { await Channel.SendErrorAsync(_eb, GetText(strs.trivia_game), GetText(strs.failed_loading_question)); return; } + OldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again IEmbedBuilder questionEmbed; IUserMessage questionMessage; try { - questionEmbed = _eb.Create().WithOkColor() - .WithTitle(GetText(strs.trivia_game)) - .AddField(GetText(strs.category), CurrentQuestion.Category) - .AddField(GetText(strs.question), CurrentQuestion.Question); + questionEmbed = _eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.trivia_game)) + .AddField(GetText(strs.category), CurrentQuestion.Category) + .AddField(GetText(strs.question), CurrentQuestion.Question); if (showHowToQuit) questionEmbed.WithFooter(GetText(strs.trivia_quit(_quitCommand))); - + if (Uri.IsWellFormedUriString(CurrentQuestion.ImageUrl, UriKind.Absolute)) questionEmbed.WithImageUrl(CurrentQuestion.ImageUrl); questionMessage = await Channel.EmbedAsync(questionEmbed); } - catch (HttpException ex) when (ex.HttpCode is System.Net.HttpStatusCode.NotFound or System.Net.HttpStatusCode.Forbidden or System.Net.HttpStatusCode.BadRequest) + catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.NotFound + or HttpStatusCode.Forbidden + or HttpStatusCode.BadRequest) { return; } @@ -112,9 +125,11 @@ public class TriviaGame if (!_options.NoHint) try { - await questionMessage.ModifyAsync(m => m.Embed = questionEmbed.WithFooter(CurrentQuestion.GetHint()).Build()); + await questionMessage.ModifyAsync(m + => m.Embed = questionEmbed.WithFooter(CurrentQuestion.GetHint()).Build()); } - catch (HttpException ex) when (ex.HttpCode is System.Net.HttpStatusCode.NotFound or System.Net.HttpStatusCode.Forbidden) + catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.NotFound + or HttpStatusCode.Forbidden) { break; } @@ -122,7 +137,6 @@ public class TriviaGame //timeout await Task.Delay(_options.QuestionTimer * 1000 / 2, _triviaCancelSource.Token); - } catch (TaskCanceledException) { _timeoutCount = 0; } //means someone guessed the answer } @@ -131,13 +145,14 @@ public class TriviaGame GameActive = false; _client.MessageReceived -= PotentialGuess; } + if (!_triviaCancelSource.IsCancellationRequested) - { try { - var embed = _eb.Create().WithErrorColor() - .WithTitle(GetText(strs.trivia_game)) - .WithDescription(GetText(strs.trivia_times_up(Format.Bold(CurrentQuestion.Answer)))); + var embed = _eb.Create() + .WithErrorColor() + .WithTitle(GetText(strs.trivia_game)) + .WithDescription(GetText(strs.trivia_times_up(Format.Bold(CurrentQuestion.Answer)))); if (Uri.IsWellFormedUriString(CurrentQuestion.AnswerImageUrl, UriKind.Absolute)) embed.WithImageUrl(CurrentQuestion.AnswerImageUrl); @@ -150,7 +165,7 @@ public class TriviaGame { Log.Warning(ex, "Error sending trivia time's up message"); } - } + await Task.Delay(5000); } } @@ -159,10 +174,11 @@ public class TriviaGame { ShouldStopGame = true; - await Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithAuthor("Trivia Game Ended") - .WithTitle("Final Results") - .WithDescription(GetLeaderboard())); + await Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithAuthor("Trivia Game Ended") + .WithTitle("Final Results") + .WithDescription(GetLeaderboard())); } public async Task StopGame() @@ -170,19 +186,14 @@ public class TriviaGame var old = ShouldStopGame; ShouldStopGame = true; if (!old) - { try { - await Channel.SendConfirmAsync(_eb, - GetText(strs.trivia_game), - GetText(strs.trivia_stopping)); - + await Channel.SendConfirmAsync(_eb, GetText(strs.trivia_game), GetText(strs.trivia_stopping)); } catch (Exception ex) { Log.Warning(ex, "Error sending trivia stopping message"); } - } } private Task PotentialGuess(SocketMessage imsg) @@ -205,13 +216,16 @@ public class TriviaGame await _guessLock.WaitAsync(); try { - if (GameActive && CurrentQuestion.IsAnswerCorrect(umsg.Content) && !_triviaCancelSource.IsCancellationRequested) + if (GameActive + && CurrentQuestion.IsAnswerCorrect(umsg.Content) + && !_triviaCancelSource.IsCancellationRequested) { Users.AddOrUpdate(guildUser, 1, (gu, old) => ++old); guess = true; } } finally { _guessLock.Release(); } + if (!guess) return; _triviaCancelSource.Cancel(); @@ -221,11 +235,11 @@ public class TriviaGame ShouldStopGame = true; try { - var embedS = _eb.Create().WithOkColor() - .WithTitle(GetText(strs.trivia_game)) - .WithDescription(GetText(strs.trivia_win( - guildUser.Mention, - Format.Bold(CurrentQuestion.Answer)))); + var embedS = _eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.trivia_game)) + .WithDescription(GetText(strs.trivia_win(guildUser.Mention, + Format.Bold(CurrentQuestion.Answer)))); if (Uri.IsWellFormedUriString(CurrentQuestion.AnswerImageUrl, UriKind.Absolute)) embedS.WithImageUrl(CurrentQuestion.AnswerImageUrl); await Channel.EmbedAsync(embedS); @@ -234,14 +248,18 @@ public class TriviaGame { // ignored } + var reward = _config.Trivia.CurrencyReward; if (reward > 0) await _cs.AddAsync(guildUser, "Won trivia", reward, true); return; } - var embed = _eb.Create().WithOkColor() - .WithTitle(GetText(strs.trivia_game)) - .WithDescription(GetText(strs.trivia_guess(guildUser.Mention, Format.Bold(CurrentQuestion.Answer)))); + + var embed = _eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.trivia_game)) + .WithDescription(GetText(strs.trivia_guess(guildUser.Mention, + Format.Bold(CurrentQuestion.Answer)))); if (Uri.IsWellFormedUriString(CurrentQuestion.AnswerImageUrl, UriKind.Absolute)) embed.WithImageUrl(CurrentQuestion.AnswerImageUrl); await Channel.EmbedAsync(embed); @@ -259,10 +277,8 @@ public class TriviaGame var sb = new StringBuilder(); foreach (var kvp in Users.OrderByDescending(kvp => kvp.Value)) - { sb.AppendLine(GetText(strs.trivia_points(Format.Bold(kvp.Key.ToString()), kvp.Value))); - } return sb.ToString(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Trivia/TriviaOptions.cs b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaOptions.cs index fdafde22b..735f3d67b 100644 --- a/src/NadekoBot/Modules/Games/Common/Trivia/TriviaOptions.cs +++ b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaOptions.cs @@ -7,13 +7,29 @@ public class TriviaOptions : INadekoCommandOptions { [Option('p', "pokemon", Required = false, Default = false, HelpText = "Whether it's 'Who's that pokemon?' trivia.")] public bool IsPokemon { get; set; } = false; + [Option("nohint", Required = false, Default = false, HelpText = "Don't show any hints.")] public bool NoHint { get; set; } = false; - [Option('w', "win-req", Required = false, Default = 10, HelpText = "Winning requirement. Set 0 for an infinite game. Default 10.")] + + [Option('w', + "win-req", + Required = false, + Default = 10, + HelpText = "Winning requirement. Set 0 for an infinite game. Default 10.")] public int WinRequirement { get; set; } = 10; - [Option('q', "question-timer", Required = false, Default = 30, HelpText = "How long until the question ends. Default 30.")] + + [Option('q', + "question-timer", + Required = false, + Default = 30, + HelpText = "How long until the question ends. Default 30.")] public int QuestionTimer { get; set; } = 30; - [Option('t', "timeout", Required = false, Default = 10, HelpText = "Number of questions of inactivity in order stop. Set 0 for never. Default 10.")] + + [Option('t', + "timeout", + Required = false, + Default = 10, + HelpText = "Number of questions of inactivity in order stop. Set 0 for never. Default 10.")] public int Timeout { get; set; } = 10; public void NormalizeOptions() @@ -24,6 +40,5 @@ public class TriviaOptions : INadekoCommandOptions QuestionTimer = 30; if (Timeout is < 0 or > 20) Timeout = 10; - } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestion.cs b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestion.cs index 32461e437..c7d08a0f9 100644 --- a/src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestion.cs +++ b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestion.cs @@ -6,46 +6,47 @@ namespace NadekoBot.Modules.Games.Common.Trivia; public class TriviaQuestion { + public const int maxStringLength = 22; + //represents the min size to judge levDistance with private static readonly HashSet> strictness = new() { - new(9, 0), - new(14, 1), - new(19, 2), - new(22, 3), + new(9, 0), new(14, 1), new(19, 2), new(22, 3) }; - public const int maxStringLength = 22; public string Category { get; set; } public string Question { get; set; } public string ImageUrl { get; set; } public string AnswerImageUrl { get; set; } public string Answer { get; set; } - private string _cleanAnswer; - public string CleanAnswer => _cleanAnswer ?? (_cleanAnswer = Clean(Answer)); - public TriviaQuestion(string q, string a, string c, string img = null, string answerImage = null) + public string CleanAnswer + => _cleanAnswer ?? (_cleanAnswer = Clean(Answer)); + + private string _cleanAnswer; + + public TriviaQuestion( + string q, + string a, + string c, + string img = null, + string answerImage = null) { - this.Question = q; - this.Answer = a; - this.Category = c; - this.ImageUrl = img; - this.AnswerImageUrl = answerImage ?? img; + Question = q; + Answer = a; + Category = c; + ImageUrl = img; + AnswerImageUrl = answerImage ?? img; } - public string GetHint() => Scramble(Answer); + public string GetHint() + => Scramble(Answer); public bool IsAnswerCorrect(string guess) { - if (Answer.Equals(guess, StringComparison.InvariantCulture)) - { - return true; - } + if (Answer.Equals(guess, StringComparison.InvariantCulture)) return true; var cleanGuess = Clean(guess); - if (CleanAnswer.Equals(cleanGuess, StringComparison.InvariantCulture)) - { - return true; - } + if (CleanAnswer.Equals(cleanGuess, StringComparison.InvariantCulture)) return true; var levDistanceClean = CleanAnswer.LevenshteinDistance(cleanGuess); var levDistanceNormal = Answer.LevenshteinDistance(guess); @@ -56,15 +57,13 @@ public class TriviaQuestion private static bool JudgeGuess(int guessLength, int answerLength, int levDistance) { foreach (var level in strictness) - { if (guessLength <= level.Item1 || answerLength <= level.Item1) { if (levDistance <= level.Item2) return true; - else - return false; + return false; } - } + return false; } @@ -102,6 +101,8 @@ public class TriviaQuestion if (letters[i] != ' ') letters[i] = '_'; } - return string.Join(" ", new string(letters).Replace(" ", " \u2000", StringComparison.InvariantCulture).AsEnumerable()); + + return string.Join(" ", + new string(letters).Replace(" ", " \u2000", StringComparison.InvariantCulture).AsEnumerable()); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestionPool.cs b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestionPool.cs index 1e9d325b6..981b6d3b2 100644 --- a/src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestionPool.cs +++ b/src/NadekoBot/Modules/Games/Common/Trivia/TriviaQuestionPool.cs @@ -3,14 +3,17 @@ namespace NadekoBot.Modules.Games.Common.Trivia; public class TriviaQuestionPool { + private TriviaQuestion[] Pool + => _cache.LocalData.TriviaQuestions; + + private IReadOnlyDictionary Map + => _cache.LocalData.PokemonMap; + private readonly IDataCache _cache; private readonly int maxPokemonId; private readonly NadekoRandom _rng = new(); - private TriviaQuestion[] Pool => _cache.LocalData.TriviaQuestions; - private IReadOnlyDictionary Map => _cache.LocalData.PokemonMap; - public TriviaQuestionPool(IDataCache cache) { _cache = cache; @@ -31,9 +34,10 @@ public class TriviaQuestionPool $@"https://nadeko.bot/images/pokemon/shadows/{num}.png", $@"https://nadeko.bot/images/pokemon/real/{num}.png"); } + TriviaQuestion randomQuestion; while (exclude.Contains(randomQuestion = Pool[_rng.Next(0, Pool.Length)])) ; return randomQuestion; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/TypingArticle.cs b/src/NadekoBot/Modules/Games/Common/TypingArticle.cs index e060c750c..01aef3c66 100644 --- a/src/NadekoBot/Modules/Games/Common/TypingArticle.cs +++ b/src/NadekoBot/Modules/Games/Common/TypingArticle.cs @@ -6,4 +6,4 @@ public class TypingArticle public string Source { get; set; } public string Extra { get; set; } public string Text { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Common/TypingGame.cs b/src/NadekoBot/Modules/Games/Common/TypingGame.cs index 8f60202c4..947507409 100644 --- a/src/NadekoBot/Modules/Games/Common/TypingGame.cs +++ b/src/NadekoBot/Modules/Games/Common/TypingGame.cs @@ -1,24 +1,12 @@ #nullable disable -using System.Diagnostics; -using NadekoBot.Modules.Games.Services; using CommandLine; +using NadekoBot.Modules.Games.Services; +using System.Diagnostics; namespace NadekoBot.Modules.Games.Common; public class TypingGame { - public class Options : INadekoCommandOptions - { - [Option('s', "start-time", Default = 5, Required = false, HelpText = "How long does it take for the race to start. Default 5.")] - public int StartTime { get; set; } = 5; - - public void NormalizeOptions() - { - if (StartTime is < 3 or > 30) - StartTime = 5; - } - } - public const float WORD_VALUE = 4.5f; public ITextChannel Channel { get; } public string CurrentSentence { get; private set; } @@ -31,8 +19,13 @@ public class TypingGame private readonly Options _options; private readonly IEmbedBuilderService _eb; - public TypingGame(GamesService games, DiscordSocketClient client, ITextChannel channel, - string prefix, Options options, IEmbedBuilderService eb) + public TypingGame( + GamesService games, + DiscordSocketClient client, + ITextChannel channel, + string prefix, + Options options, + IEmbedBuilderService eb) { _games = games; _client = client; @@ -40,7 +33,7 @@ public class TypingGame _options = options; _eb = eb; - this.Channel = channel; + Channel = channel; IsActive = false; sw = new(); finishedUserIds = new(); @@ -80,19 +73,19 @@ public class TypingGame var time = _options.StartTime; - var msg = await Channel.SendMessageAsync($"Starting new typing contest in **{time}**...", options: new() - { - RetryMode = RetryMode.AlwaysRetry - }); + var msg = await Channel.SendMessageAsync($"Starting new typing contest in **{time}**...", + options: new() { RetryMode = RetryMode.AlwaysRetry }); do { await Task.Delay(2000); time -= 2; - try { await msg.ModifyAsync(m => m.Content = $"Starting new typing contest in **{time}**.."); } catch { } + try { await msg.ModifyAsync(m => m.Content = $"Starting new typing contest in **{time}**.."); } + catch { } } while (time > 2); - await msg.ModifyAsync(m => { + await msg.ModifyAsync(m => + { m.Content = CurrentSentence.Replace(" ", " \x200B", StringComparison.InvariantCulture); }); sw.Start(); @@ -105,7 +98,6 @@ public class TypingGame if (!IsActive) return; } - } catch { } finally @@ -118,9 +110,7 @@ public class TypingGame { if (_games.TypingArticles.Any()) return _games.TypingArticles[new NadekoRandom().Next(0, _games.TypingArticles.Count)].Text; - else - return $"No typing articles found. Use {_prefix}typeadd command to add a new article for typing."; - + return $"No typing articles found. Use {_prefix}typeadd command to add a new article for typing."; } private void HandleAnswers() @@ -137,7 +127,7 @@ public class TypingGame if (imsg is not SocketUserMessage msg) return; - if (this.Channel is null || this.Channel.Id != msg.Channel.Id) return; + if (Channel is null || Channel.Id != msg.Channel.Id) return; var guess = msg.Content; @@ -148,18 +138,17 @@ public class TypingGame var elapsed = sw.Elapsed; var wpm = CurrentSentence.Length / WORD_VALUE / elapsed.TotalSeconds * 60; finishedUserIds.Add(msg.Author.Id); - await this.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithTitle($"{msg.Author} finished the race!") - .AddField("Place", $"#{finishedUserIds.Count}", true) - .AddField("WPM", $"{wpm:F1} *[{elapsed.TotalSeconds:F2}sec]*", true) - .AddField("Errors", distance.ToString(), true)); - + await Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle($"{msg.Author} finished the race!") + .AddField("Place", $"#{finishedUserIds.Count}", true) + .AddField("WPM", $"{wpm:F1} *[{elapsed.TotalSeconds:F2}sec]*", true) + .AddField("Errors", distance.ToString(), true)); + if (finishedUserIds.Count % 4 == 0) - { - await this.Channel.SendConfirmAsync(_eb, - $":exclamation: A lot of people finished, here is the text for those still typing:" + - $"\n\n**{Format.Sanitize(CurrentSentence.Replace(" ", " \x200B", StringComparison.InvariantCulture)).SanitizeMentions(true)}**"); - } + await Channel.SendConfirmAsync(_eb, + ":exclamation: A lot of people finished, here is the text for those still typing:" + + $"\n\n**{Format.Sanitize(CurrentSentence.Replace(" ", " \x200B", StringComparison.InvariantCulture)).SanitizeMentions(true)}**"); } } catch (Exception ex) @@ -170,6 +159,22 @@ public class TypingGame return Task.CompletedTask; } - private static bool Judge(int errors, int textLength) => errors <= textLength / 25; + private static bool Judge(int errors, int textLength) + => errors <= textLength / 25; -} + public class Options : INadekoCommandOptions + { + [Option('s', + "start-time", + Default = 5, + Required = false, + HelpText = "How long does it take for the race to start. Default 5.")] + public int StartTime { get; set; } = 5; + + public void NormalizeOptions() + { + if (StartTime is < 3 or > 30) + StartTime = 5; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Connect4Commands.cs b/src/NadekoBot/Modules/Games/Connect4Commands.cs index a5b008f7b..f9233c190 100644 --- a/src/NadekoBot/Modules/Games/Connect4Commands.cs +++ b/src/NadekoBot/Modules/Games/Connect4Commands.cs @@ -1,2 +1 @@ -#nullable disable - +#nullable disable \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Games.cs b/src/NadekoBot/Modules/Games/Games.cs index c89be6ce9..309aefadc 100644 --- a/src/NadekoBot/Modules/Games/Games.cs +++ b/src/NadekoBot/Modules/Games/Games.cs @@ -20,7 +20,8 @@ public partial class Games : NadekoModule _httpFactory = factory; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Choose([Leftover] string list = null) { if (string.IsNullOrWhiteSpace(list)) @@ -32,20 +33,23 @@ public partial class Games : NadekoModule await SendConfirmAsync("🤔", listArr[rng.Next(0, listArr.Length)]); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task EightBall([Leftover] string question = null) { if (string.IsNullOrWhiteSpace(question)) return; var res = _service.GetEightballResponse(ctx.User.Id, question); - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithDescription(ctx.User.ToString()) - .AddField("❓ " + GetText(strs.question), question, false) - .AddField("🎱 " + GetText(strs._8ball), res, false)); + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithDescription(ctx.User.ToString()) + .AddField("❓ " + GetText(strs.question), question) + .AddField("🎱 " + GetText(strs._8ball), res)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task RateGirl([Leftover] IGuildUser usr) { @@ -64,16 +68,17 @@ public partial class Games : NadekoModule originalStream.Position = 0; originalStream.CopyTo(imgStream); } + imgStream.Position = 0; - await ctx.Channel.SendFileAsync(stream: imgStream, - filename: $"girl_{usr}.png", - text: Format.Bold($"{ctx.User.Mention} Girl Rating For {usr}"), + await ctx.Channel.SendFileAsync(imgStream, + $"girl_{usr}.png", + Format.Bold($"{ctx.User.Mention} Girl Rating For {usr}"), embed: _eb.Create() - .WithOkColor() - .AddField("Hot", gr.Hot.ToString("F2"), true) - .AddField("Crazy", gr.Crazy.ToString("F2"), true) - .AddField("Advice", gr.Advice, false) - .Build()); + .WithOkColor() + .AddField("Hot", gr.Hot.ToString("F2"), true) + .AddField("Crazy", gr.Crazy.ToString("F2"), true) + .AddField("Advice", gr.Advice) + .Build()); } private double NextDouble(double x, double y) @@ -136,13 +141,13 @@ public partial class Games : NadekoModule return new(_images, _httpFactory, crazy, hot, roll, advice); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Linux(string guhnoo, string loonix) => await SendConfirmAsync( $@"I'd just like to interject for moment. What you're refering to as {loonix}, is in fact, {guhnoo}/{loonix}, or as I've recently taken to calling it, {guhnoo} plus {loonix}. {loonix} is not an operating system unto itself, but rather another free component of a fully functioning {guhnoo} system made useful by the {guhnoo} corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. Many computer users run a modified version of the {guhnoo} system every day, without realizing it. Through a peculiar turn of events, the version of {guhnoo} which is widely used today is often called {loonix}, and many of its users are not aware that it is basically the {guhnoo} system, developed by the {guhnoo} Project. -There really is a {loonix}, and these people are using it, but it is just a part of the system they use. {loonix} is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. {loonix} is normally used in combination with the {guhnoo} operating system: the whole system is basically {guhnoo} with {loonix} added, or {guhnoo}/{loonix}. All the so-called {loonix} distributions are really distributions of {guhnoo}/{loonix}." - ); -} +There really is a {loonix}, and these people are using it, but it is just a part of the system they use. {loonix} is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. {loonix} is normally used in combination with the {guhnoo} operating system: the whole system is basically {guhnoo} with {loonix} added, or {guhnoo}/{loonix}. All the so-called {loonix} distributions are really distributions of {guhnoo}/{loonix}."); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/HangmanCommands.cs b/src/NadekoBot/Modules/Games/HangmanCommands.cs index 0452c404d..1f8c436d2 100644 --- a/src/NadekoBot/Modules/Games/HangmanCommands.cs +++ b/src/NadekoBot/Modules/Games/HangmanCommands.cs @@ -7,12 +7,11 @@ public partial class Games [Group] public class HangmanCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Hangmanlist() - => await SendConfirmAsync( - GetText(strs.hangman_types(Prefix)), - _service.GetHangmanTypes().Join('\n')); + => await SendConfirmAsync(GetText(strs.hangman_types(Prefix)), _service.GetHangmanTypes().Join('\n')); private static string Draw(HangmanGame.State state) => $@". ┌─────┐ @@ -27,28 +26,26 @@ public partial class Games { if (state.Phase == HangmanGame.Phase.Running) return eb.Create() - .WithOkColor() - .AddField("Hangman", Draw(state)) - .AddField("Guess", Format.Code(state.Word)) - .WithFooter(state.missedLetters.Join(' ')); - + .WithOkColor() + .AddField("Hangman", Draw(state)) + .AddField("Guess", Format.Code(state.Word)) + .WithFooter(state.missedLetters.Join(' ')); + if (state.Phase == HangmanGame.Phase.Ended && state.Failed) return eb.Create() - .WithErrorColor() - .AddField("Hangman", Draw(state)) - .AddField("Guess", Format.Code(state.Word)) - .WithFooter(state.missedLetters.Join(' ')); - else - { - return eb.Create() - .WithOkColor() - .AddField("Hangman", Draw(state)) - .AddField("Guess", Format.Code(state.Word)) - .WithFooter(state.missedLetters.Join(' ')); - } + .WithErrorColor() + .AddField("Hangman", Draw(state)) + .AddField("Guess", Format.Code(state.Word)) + .WithFooter(state.missedLetters.Join(' ')); + return eb.Create() + .WithOkColor() + .AddField("Hangman", Draw(state)) + .AddField("Guess", Format.Code(state.Word)) + .WithFooter(state.missedLetters.Join(' ')); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Hangman([Leftover] string? type = null) { @@ -63,14 +60,12 @@ public partial class Games await ctx.Channel.EmbedAsync(eb); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task HangmanStop() { - if (await _service.StopHangman(ctx.Channel.Id)) - { - await ReplyConfirmLocalizedAsync(strs.hangman_stopped); - } + if (await _service.StopHangman(ctx.Channel.Id)) await ReplyConfirmLocalizedAsync(strs.hangman_stopped); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/NunchiCommands.cs b/src/NadekoBot/Modules/Games/NunchiCommands.cs index 558738279..14896298f 100644 --- a/src/NadekoBot/Modules/Games/NunchiCommands.cs +++ b/src/NadekoBot/Modules/Games/NunchiCommands.cs @@ -14,7 +14,8 @@ public partial class Games public NunchiCommands(DiscordSocketClient client) => _client = client; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Nunchi() { @@ -26,18 +27,17 @@ public partial class Games { // join it if (!await nunchi.Join(ctx.User.Id, ctx.User.ToString())) - { // if you failed joining, that means game is running or just ended // await ReplyErrorLocalized("nunchi_already_started"); return; - } await ReplyErrorLocalizedAsync(strs.nunchi_joined(nunchi.ParticipantCount)); return; } - try { await ConfirmLocalizedAsync(strs.nunchi_created); } catch { } + try { await ConfirmLocalizedAsync(strs.nunchi_created); } + catch { } nunchi.OnGameEnded += Nunchi_OnGameEnded; //nunchi.OnGameStarted += Nunchi_OnGameStarted; @@ -45,7 +45,7 @@ public partial class Games nunchi.OnUserGuessed += Nunchi_OnUserGuessed; nunchi.OnRoundStarted += Nunchi_OnRoundStarted; _client.MessageReceived += _client_MessageReceived; - + var success = await nunchi.Initialize(); if (!success) { @@ -84,14 +84,12 @@ public partial class Games if (arg2 is null) return ConfirmLocalizedAsync(strs.nunchi_ended_no_winner); - else - return ConfirmLocalizedAsync(strs.nunchi_ended(Format.Bold(arg2))); + return ConfirmLocalizedAsync(strs.nunchi_ended(Format.Bold(arg2))); } } private Task Nunchi_OnRoundStarted(NunchiGame arg, int cur) - => ConfirmLocalizedAsync(strs.nunchi_round_started( - Format.Bold(arg.ParticipantCount.ToString()), + => ConfirmLocalizedAsync(strs.nunchi_round_started(Format.Bold(arg.ParticipantCount.ToString()), Format.Bold(cur.ToString()))); private Task Nunchi_OnUserGuessed(NunchiGame arg) @@ -99,14 +97,16 @@ public partial class Games private Task Nunchi_OnRoundEnded(NunchiGame arg1, (ulong Id, string Name)? arg2) { - if(arg2.HasValue) + if (arg2.HasValue) return ConfirmLocalizedAsync(strs.nunchi_round_ended(Format.Bold(arg2.Value.Name))); - else - return ConfirmLocalizedAsync(strs.nunchi_round_ended_boot( - Format.Bold("\n" + string.Join("\n, ", arg1.Participants.Select(x => x.Name))))); // this won't work if there are too many users + return ConfirmLocalizedAsync(strs.nunchi_round_ended_boot( + Format.Bold("\n" + + string.Join("\n, ", + arg1.Participants.Select(x + => x.Name))))); // this won't work if there are too many users } private Task Nunchi_OnGameStarted(NunchiGame arg) => ConfirmLocalizedAsync(strs.nunchi_started(Format.Bold(arg.ParticipantCount.ToString()))); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/PollCommands.cs b/src/NadekoBot/Modules/Games/PollCommands.cs index e57a47cb8..f1d2d5579 100644 --- a/src/NadekoBot/Modules/Games/PollCommands.cs +++ b/src/NadekoBot/Modules/Games/PollCommands.cs @@ -15,7 +15,8 @@ public partial class Games public PollCommands(DiscordSocketClient client) => _client = client; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [UserPerm(GuildPerm.ManageMessages)] [RequireContext(ContextType.Guild)] public async Task Poll([Leftover] string arg) @@ -23,31 +24,28 @@ public partial class Games if (string.IsNullOrWhiteSpace(arg)) return; - var poll = _service.CreatePoll(ctx.Guild.Id, - ctx.Channel.Id, arg); - if(poll is null) + var poll = _service.CreatePoll(ctx.Guild.Id, ctx.Channel.Id, arg); + if (poll is null) { await ReplyErrorLocalizedAsync(strs.poll_invalid_input); return; } + if (_service.StartPoll(poll)) - { - await ctx.Channel - .EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.poll_created(ctx.User.ToString()))) - .WithDescription( - Format.Bold(poll.Question) + "\n\n" + - string.Join("\n", poll.Answers - .Select(x => $"`{x.Index + 1}.` {Format.Bold(x.Text)}")))); - } + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.poll_created(ctx.User.ToString()))) + .WithDescription(Format.Bold(poll.Question) + + "\n\n" + + string.Join("\n", + poll.Answers.Select(x + => $"`{x.Index + 1}.` {Format.Bold(x.Text)}")))); else - { await ReplyErrorLocalizedAsync(strs.poll_already_running); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [UserPerm(GuildPerm.ManageMessages)] [RequireContext(ContextType.Guild)] public async Task PollStats() @@ -58,7 +56,8 @@ public partial class Games await ctx.Channel.EmbedAsync(GetStats(pr.Poll, GetText(strs.current_poll_results))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [UserPerm(GuildPerm.ManageMessages)] [RequireContext(ContextType.Guild)] public async Task Pollend() @@ -75,39 +74,32 @@ public partial class Games public IEmbedBuilder GetStats(Poll poll, string title) { - var results = poll.Votes.GroupBy(kvp => kvp.VoteIndex) - .ToDictionary(x => x.Key, x => x.Sum(kvp => 1)); + var results = poll.Votes.GroupBy(kvp => kvp.VoteIndex).ToDictionary(x => x.Key, x => x.Sum(kvp => 1)); var totalVotesCast = results.Sum(x => x.Value); var eb = _eb.Create().WithTitle(title); - var sb = new StringBuilder() - .AppendLine(Format.Bold(poll.Question)) - .AppendLine(); + var sb = new StringBuilder().AppendLine(Format.Bold(poll.Question)).AppendLine(); - var stats = poll.Answers - .Select(x => - { - results.TryGetValue(x.Index, out var votes); + var stats = poll.Answers.Select(x => + { + results.TryGetValue(x.Index, out var votes); - return (x.Index, votes, x.Text); - }) - .OrderByDescending(x => x.votes) - .ToArray(); + return (x.Index, votes, x.Text); + }) + .OrderByDescending(x => x.votes) + .ToArray(); for (var i = 0; i < stats.Length; i++) { var (Index, votes, Text) = stats[i]; - sb.AppendLine(GetText(strs.poll_result( - Index + 1, - Format.Bold(Text), - Format.Bold(votes.ToString())))); + sb.AppendLine(GetText(strs.poll_result(Index + 1, Format.Bold(Text), Format.Bold(votes.ToString())))); } return eb.WithDescription(sb.ToString()) - .WithFooter(GetText(strs.x_votes_cast(totalVotesCast))) - .WithOkColor(); + .WithFooter(GetText(strs.x_votes_cast(totalVotesCast))) + .WithOkColor(); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Services/ChatterbotService.cs b/src/NadekoBot/Modules/Games/Services/ChatterbotService.cs index a11fce3fb..5b510f73d 100644 --- a/src/NadekoBot/Modules/Games/Services/ChatterbotService.cs +++ b/src/NadekoBot/Modules/Games/Services/ChatterbotService.cs @@ -1,13 +1,18 @@ #nullable disable using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Modules.Games.Common.ChatterBot; using NadekoBot.Modules.Permissions.Common; using NadekoBot.Modules.Permissions.Services; -using NadekoBot.Modules.Games.Common.ChatterBot; namespace NadekoBot.Modules.Games.Services; public class ChatterBotService : IEarlyBehavior { + public ConcurrentDictionary> ChatterBotGuilds { get; } + + public int Priority + => 1; + private readonly DiscordSocketClient _client; private readonly PermissionService _perms; private readonly CommandHandler _cmd; @@ -16,13 +21,15 @@ public class ChatterBotService : IEarlyBehavior private readonly IEmbedBuilderService _eb; private readonly IHttpClientFactory _httpFactory; - public ConcurrentDictionary> ChatterBotGuilds { get; } - - public int Priority => 1; - - public ChatterBotService(DiscordSocketClient client, PermissionService perms, - Bot bot, CommandHandler cmd, IBotStrings strings, IHttpClientFactory factory, - IBotCredentials creds, IEmbedBuilderService eb) + public ChatterBotService( + DiscordSocketClient client, + PermissionService perms, + Bot bot, + CommandHandler cmd, + IBotStrings strings, + IHttpClientFactory factory, + IBotCredentials creds, + IEmbedBuilderService eb) { _client = client; _perms = perms; @@ -32,18 +39,16 @@ public class ChatterBotService : IEarlyBehavior _eb = eb; _httpFactory = factory; - ChatterBotGuilds = new( - bot.AllGuildConfigs - .Where(gc => gc.CleverbotEnabled) - .ToDictionary(gc => gc.GuildId, gc => new Lazy(() => CreateSession(), true))); + ChatterBotGuilds = new(bot.AllGuildConfigs.Where(gc => gc.CleverbotEnabled) + .ToDictionary(gc => gc.GuildId, + gc => new Lazy(() => CreateSession(), true))); } public IChatterBotSession CreateSession() { if (!string.IsNullOrWhiteSpace(_creds.CleverbotApiKey)) return new OfficialCleverbotSession(_creds.CleverbotApiKey, _httpFactory); - else - return new CleverbotIOSession("GAh3wUfzDCpDpdpT", "RStKgqn7tcO9blbrv4KbXM8NDlb7H37C", _httpFactory); + return new CleverbotIOSession("GAh3wUfzDCpDpdpT", "RStKgqn7tcO9blbrv4KbXM8NDlb7H37C", _httpFactory); } public string PrepareMessage(IUserMessage msg, out IChatterBotSession cleverbot) @@ -64,17 +69,11 @@ public class ChatterBotService : IEarlyBehavior var nickMention = $"<@!{nadekoId}> "; string message; if (msg.Content.StartsWith(normalMention, StringComparison.InvariantCulture)) - { message = msg.Content[normalMention.Length..].Trim(); - } else if (msg.Content.StartsWith(nickMention, StringComparison.InvariantCulture)) - { message = msg.Content[nickMention.Length..].Trim(); - } else - { return null; - } return message; } @@ -92,6 +91,7 @@ public class ChatterBotService : IEarlyBehavior { await channel.SendConfirmAsync(_eb, response.SanitizeMentions(true)); // try twice :\ } + return true; } @@ -106,19 +106,19 @@ public class ChatterBotService : IEarlyBehavior return false; var pc = _perms.GetCacheFor(guild.Id); - if (!pc.Permissions.CheckPermissions(usrMsg, - "cleverbot", - "Games".ToLowerInvariant(), - out var index)) + if (!pc.Permissions.CheckPermissions(usrMsg, "cleverbot", "Games".ToLowerInvariant(), out var index)) { if (pc.Verbose) { var returnMsg = _strings.GetText(strs.perm_prevent(index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(sg), sg)))); - - try { await usrMsg.Channel.SendErrorAsync(_eb, returnMsg); } catch { } + + try { await usrMsg.Channel.SendErrorAsync(_eb, returnMsg); } + catch { } + Log.Information(returnMsg); } + return true; } @@ -135,8 +135,9 @@ Message: {usrMsg.Content}"); } catch (Exception ex) { - Log.Warning(ex,"Error in cleverbot"); + Log.Warning(ex, "Error in cleverbot"); } + return false; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Services/GamesConfigService.cs b/src/NadekoBot/Modules/Games/Services/GamesConfigService.cs index e944a9965..4984abbad 100644 --- a/src/NadekoBot/Modules/Games/Services/GamesConfigService.cs +++ b/src/NadekoBot/Modules/Games/Services/GamesConfigService.cs @@ -6,35 +6,39 @@ namespace NadekoBot.Modules.Games.Services; public sealed class GamesConfigService : ConfigServiceBase { - public override string Name { get; } = "games"; private const string FilePath = "data/games.yml"; private static readonly TypedKey changeKey = new("config.games.updated"); + public override string Name { get; } = "games"; public GamesConfigService(IConfigSeria serializer, IPubSub pubSub) : base(FilePath, serializer, pubSub, changeKey) { - AddParsedProp("trivia.min_win_req", gs => gs.Trivia.MinimumWinReq, int.TryParse, - ConfigPrinters.ToString, val => val > 0); - AddParsedProp("trivia.currency_reward", gs => gs.Trivia.CurrencyReward, long.TryParse, - ConfigPrinters.ToString, val => val >= 0); - AddParsedProp("hangman.currency_reward", gs => gs.Hangman.CurrencyReward, long.TryParse, - ConfigPrinters.ToString, val => val >= 0); - + AddParsedProp("trivia.min_win_req", + gs => gs.Trivia.MinimumWinReq, + int.TryParse, + ConfigPrinters.ToString, + val => val > 0); + AddParsedProp("trivia.currency_reward", + gs => gs.Trivia.CurrencyReward, + long.TryParse, + ConfigPrinters.ToString, + val => val >= 0); + AddParsedProp("hangman.currency_reward", + gs => gs.Hangman.CurrencyReward, + long.TryParse, + ConfigPrinters.ToString, + val => val >= 0); + Migrate(); } - + private void Migrate() { if (data.Version < 1) - { ModifyConfig(c => { c.Version = 1; - c.Hangman = new() - { - CurrencyReward = 0 - }; + c.Hangman = new() { CurrencyReward = 0 }; }); - } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Services/GamesService.cs b/src/NadekoBot/Modules/Games/Services/GamesService.cs index f762df469..5fd52d761 100644 --- a/src/NadekoBot/Modules/Games/Services/GamesService.cs +++ b/src/NadekoBot/Modules/Games/Services/GamesService.cs @@ -1,27 +1,21 @@ #nullable disable +using Microsoft.Extensions.Caching.Memory; using NadekoBot.Modules.Games.Common; using NadekoBot.Modules.Games.Common.Acrophobia; using NadekoBot.Modules.Games.Common.Nunchi; using NadekoBot.Modules.Games.Common.Trivia; using Newtonsoft.Json; -using Microsoft.Extensions.Caching.Memory; namespace NadekoBot.Modules.Games.Services; public class GamesService : INService { - private readonly GamesConfigService _gamesConfig; + private const string TypingArticlesPath = "data/typing_articles3.json"; public ConcurrentDictionary GirlRatings { get; } = new(); - public IReadOnlyList EightBallResponses => _gamesConfig.Data.EightBallResponses; - - private readonly Timer _t; - private readonly IHttpClientFactory _httpFactory; - private readonly IMemoryCache _8BallCache; - private readonly Random _rng; - - private const string TypingArticlesPath = "data/typing_articles3.json"; + public IReadOnlyList EightBallResponses + => _gamesConfig.Data.EightBallResponses; public List TypingArticles { get; } = new(); @@ -33,36 +27,30 @@ public class GamesService : INService public ConcurrentDictionary NunchiGames { get; } = new(); public AsyncLazy Ratings { get; } + private readonly GamesConfigService _gamesConfig; - public class RatingTexts - { - public string Nog { get; set; } - public string Tra { get; set; } - public string Fun { get; set; } - public string Uni { get; set; } - public string Wif { get; set; } - public string Dat { get; set; } - public string Dan { get; set; } - } + private readonly Timer _t; + private readonly IHttpClientFactory _httpFactory; + private readonly IMemoryCache _8BallCache; + private readonly Random _rng; public GamesService(GamesConfigService gamesConfig, IHttpClientFactory httpFactory) { _gamesConfig = gamesConfig; _httpFactory = httpFactory; - _8BallCache = new MemoryCache(new MemoryCacheOptions() - { - SizeLimit = 500_000 - }); + _8BallCache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 500_000 }); Ratings = new(GetRatingTexts); _rng = new NadekoRandom(); //girl ratings _t = new(_ => - { - GirlRatings.Clear(); - - }, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1)); + { + GirlRatings.Clear(); + }, + null, + TimeSpan.FromDays(1), + TimeSpan.FromDays(1)); try { @@ -78,7 +66,8 @@ public class GamesService : INService private async Task GetRatingTexts() { using var http = _httpFactory.CreateClient(); - var text = await http.GetStringAsync("https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rategirl/rates.json"); + var text = await http.GetStringAsync( + "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rategirl/rates.json"); return JsonConvert.DeserializeObject(text); } @@ -88,19 +77,21 @@ public class GamesService : INService { Source = user.ToString(), Extra = $"Text added on {DateTime.UtcNow} by {user}.", - Text = text.SanitizeMentions(true), + Text = text.SanitizeMentions(true) }); File.WriteAllText(TypingArticlesPath, JsonConvert.SerializeObject(TypingArticles)); } public string GetEightballResponse(ulong userId, string question) - => _8BallCache.GetOrCreate($"8ball:{userId}:{question}", e => - { - e.Size = question.Length; - e.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12); - return EightBallResponses[_rng.Next(0, EightBallResponses.Count)];; - }); + => _8BallCache.GetOrCreate($"8ball:{userId}:{question}", + e => + { + e.Size = question.Length; + e.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12); + return EightBallResponses[_rng.Next(0, EightBallResponses.Count)]; + ; + }); public TypingArticle RemoveTypingArticle(int index) { @@ -110,8 +101,19 @@ public class GamesService : INService var removed = articles[index]; TypingArticles.RemoveAt(index); - + File.WriteAllText(TypingArticlesPath, JsonConvert.SerializeObject(articles)); return removed; } -} + + public class RatingTexts + { + public string Nog { get; set; } + public string Tra { get; set; } + public string Fun { get; set; } + public string Uni { get; set; } + public string Wif { get; set; } + public string Dat { get; set; } + public string Dan { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Services/PollService.cs b/src/NadekoBot/Modules/Games/Services/PollService.cs index d6b353881..e1a1549c3 100644 --- a/src/NadekoBot/Modules/Games/Services/PollService.cs +++ b/src/NadekoBot/Modules/Games/Services/PollService.cs @@ -1,9 +1,9 @@ #nullable disable -using NadekoBot.Common.ModuleBehaviors; -using NadekoBot.Modules.Games.Common; using NadekoBot.Common.Collections; -using NadekoBot.Services.Database.Models; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; +using NadekoBot.Modules.Games.Common; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Games.Services; @@ -11,7 +11,8 @@ public class PollService : IEarlyBehavior { public ConcurrentDictionary ActivePolls { get; } = new(); - public int Priority => 5; + public int Priority + => 5; private readonly DbService _db; private readonly IBotStrings _strs; @@ -25,13 +26,14 @@ public class PollService : IEarlyBehavior using var uow = db.GetDbContext(); ActivePolls = uow.Poll.GetAllPolls() - .ToDictionary(x => x.GuildId, x => - { - var pr = new PollRunner(db, x); - pr.OnVoted += Pr_OnVoted; - return pr; - }) - .ToConcurrent(); + .ToDictionary(x => x.GuildId, + x => + { + var pr = new PollRunner(db, x); + pr.OnVoted += Pr_OnVoted; + return pr; + }) + .ToConcurrent(); } public Poll CreatePoll(ulong guildId, ulong channelId, string input) @@ -42,8 +44,7 @@ public class PollService : IEarlyBehavior if (data.Length < 3) return null; - var col = new IndexedCollection(data.Skip(1) - .Select(x => new PollAnswer() { Text = x })); + var col = new IndexedCollection(data.Skip(1).Select(x => new PollAnswer { Text = x })); return new() { @@ -57,7 +58,7 @@ public class PollService : IEarlyBehavior public bool StartPoll(Poll p) { - var pr = new PollRunner(_db, p); + var pr = new PollRunner(_db, p); if (ActivePolls.TryAdd(p.GuildId, pr)) { using (var uow = _db.GetDbContext()) @@ -69,6 +70,7 @@ public class PollService : IEarlyBehavior pr.OnVoted += Pr_OnVoted; return true; } + return false; } @@ -77,22 +79,24 @@ public class PollService : IEarlyBehavior if (ActivePolls.TryRemove(guildId, out var pr)) { pr.OnVoted -= Pr_OnVoted; - + using var uow = _db.GetDbContext(); uow.RemovePoll(pr.Poll.Id); uow.SaveChanges(); - + return pr.Poll; } + return null; } private async Task Pr_OnVoted(IUserMessage msg, IGuildUser usr) { - var toDelete = await msg.Channel.SendConfirmAsync(_eb, - _strs.GetText(strs.poll_voted(Format.Bold(usr.ToString())), usr.GuildId)); + var toDelete = await msg.Channel.SendConfirmAsync(_eb, + _strs.GetText(strs.poll_voted(Format.Bold(usr.ToString())), usr.GuildId)); toDelete.DeleteAfter(5); - try { await msg.DeleteAsync(); } catch { } + try { await msg.DeleteAsync(); } + catch { } } public async Task RunBehavior(IGuild guild, IUserMessage msg) @@ -108,14 +112,12 @@ public class PollService : IEarlyBehavior var voted = await poll.TryVote(msg); if (voted) - { Log.Information("User {UserName} [{UserId}] voted in a poll on {GuildName} [{GuildId}] server", msg.Author.ToString(), msg.Author.Id, guild.Name, guild.Id); - } - + return voted; } catch (Exception ex) @@ -125,4 +127,4 @@ public class PollService : IEarlyBehavior return false; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/SpeedTypingCommands.cs b/src/NadekoBot/Modules/Games/SpeedTypingCommands.cs index c4502fa6c..120209e57 100644 --- a/src/NadekoBot/Modules/Games/SpeedTypingCommands.cs +++ b/src/NadekoBot/Modules/Games/SpeedTypingCommands.cs @@ -18,7 +18,8 @@ public partial class Games _client = client; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [NadekoOptionsAttribute(typeof(TypingGame.Options))] public async Task TypeStart(params string[] args) @@ -26,19 +27,17 @@ public partial class Games var (options, _) = OptionsParser.ParseFrom(new TypingGame.Options(), args); var channel = (ITextChannel)ctx.Channel; - var game = _service.RunningContests.GetOrAdd(ctx.Guild.Id, id => new(_games, _client, channel, Prefix, options, _eb)); + var game = _service.RunningContests.GetOrAdd(ctx.Guild.Id, + id => new(_games, _client, channel, Prefix, options, _eb)); if (game.IsActive) - { await SendErrorAsync($"Contest already running in {game.Channel.Mention} channel."); - } else - { await game.Start(); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task TypeStop() { @@ -47,12 +46,13 @@ public partial class Games await game.Stop(); return; } - + await SendErrorAsync("No contest to stop on this channel."); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] public async Task Typeadd([Leftover] string text) @@ -60,12 +60,13 @@ public partial class Games if (string.IsNullOrWhiteSpace(text)) return; - _games.AddTypingArticle(ctx.User, text); + _games.AddTypingArticle(ctx.User, text); await SendConfirmAsync("Added new article for typing game."); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Typelist(int page = 1) { @@ -79,28 +80,28 @@ public partial class Games await SendErrorAsync($"{ctx.User.Mention} `No articles found on that page.`"); return; } + var i = (page - 1) * 15; - await SendConfirmAsync("List of articles for Type Race", string.Join("\n", articles.Select(a => $"`#{++i}` - {a.Text.TrimTo(50)}"))); + await SendConfirmAsync("List of articles for Type Race", + string.Join("\n", articles.Select(a => $"`#{++i}` - {a.Text.TrimTo(50)}"))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] public async Task Typedel(int index) { var removed = _service.RemoveTypingArticle(--index); - - if (removed is null) - { - return; - } + + if (removed is null) return; var embed = _eb.Create() - .WithTitle($"Removed typing article #{index + 1}") - .WithDescription(removed.Text.TrimTo(50)) - .WithOkColor(); + .WithTitle($"Removed typing article #{index + 1}") + .WithDescription(removed.Text.TrimTo(50)) + .WithOkColor(); await ctx.Channel.EmbedAsync(embed); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/TicTacToeCommands.cs b/src/NadekoBot/Modules/Games/TicTacToeCommands.cs index 178ca1517..f41adc6da 100644 --- a/src/NadekoBot/Modules/Games/TicTacToeCommands.cs +++ b/src/NadekoBot/Modules/Games/TicTacToeCommands.cs @@ -15,7 +15,8 @@ public partial class Games public TicTacToeCommands(DiscordSocketClient client) => _client = client; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [NadekoOptions(typeof(TicTacToe.Options))] public async Task TicTacToe(params string[] args) @@ -34,7 +35,8 @@ public partial class Games }); return; } - game = new(base.Strings, this._client, channel, (IGuildUser)ctx.User, options, _eb); + + game = new(Strings, _client, channel, (IGuildUser)ctx.User, options, _eb); _service.TicTacToeGames.Add(channel.Id, game); await ReplyConfirmLocalizedAsync(strs.ttt_created); @@ -50,4 +52,4 @@ public partial class Games } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/TriviaCommands.cs b/src/NadekoBot/Modules/Games/TriviaCommands.cs index d6bd1db6f..d17d8300d 100644 --- a/src/NadekoBot/Modules/Games/TriviaCommands.cs +++ b/src/NadekoBot/Modules/Games/TriviaCommands.cs @@ -14,7 +14,10 @@ public partial class Games private readonly GamesConfigService _gamesConfig; private readonly DiscordSocketClient _client; - public TriviaCommands(DiscordSocketClient client, IDataCache cache, ICurrencyService cs, + public TriviaCommands( + DiscordSocketClient client, + IDataCache cache, + ICurrencyService cs, GamesConfigService gamesConfig) { _cache = cache; @@ -23,7 +26,8 @@ public partial class Games _client = client; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] [NadekoOptionsAttribute(typeof(TriviaOptions))] @@ -37,13 +41,18 @@ public partial class Games var (opts, _) = OptionsParser.ParseFrom(new TriviaOptions(), args); var config = _gamesConfig.Data; - if (config.Trivia.MinimumWinReq > 0 && config.Trivia.MinimumWinReq > opts.WinRequirement) - { - return; - } + if (config.Trivia.MinimumWinReq > 0 && config.Trivia.MinimumWinReq > opts.WinRequirement) return; - var trivia = new TriviaGame(Strings, _client, config, _cache, _cs, channel.Guild, channel, opts, - Prefix + "tq", _eb); + var trivia = new TriviaGame(Strings, + _client, + config, + _cache, + _cs, + channel.Guild, + channel, + opts, + Prefix + "tq", + _eb); if (_service.RunningTrivias.TryAdd(channel.Guild.Id, trivia)) { try @@ -55,13 +64,15 @@ public partial class Games _service.RunningTrivias.TryRemove(channel.Guild.Id, out trivia); await trivia.EnsureStopped(); } + return; } await SendErrorAsync(GetText(strs.trivia_already_running) + "\n" + trivia.CurrentQuestion); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Tl() { @@ -74,7 +85,8 @@ public partial class Games await ReplyErrorLocalizedAsync(strs.trivia_none); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Tq() { @@ -89,4 +101,4 @@ public partial class Games await ReplyErrorLocalizedAsync(strs.trivia_none); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Help/Common/CommandsOptions.cs b/src/NadekoBot/Modules/Help/Common/CommandsOptions.cs index f3c783084..b65df3b25 100644 --- a/src/NadekoBot/Modules/Help/Common/CommandsOptions.cs +++ b/src/NadekoBot/Modules/Help/Common/CommandsOptions.cs @@ -9,14 +9,18 @@ public class CommandsOptions : INadekoCommandOptions { Hide, Cross, - All, + All } - [Option('v', "view", Required = false, Default = ViewType.Hide, HelpText = "Specifies how to output the list of commands. 0 - Hide commands which you can't use, 1 - Cross out commands which you can't use, 2 - Show all.")] + [Option('v', + "view", + Required = false, + Default = ViewType.Hide, + HelpText = + "Specifies how to output the list of commands. 0 - Hide commands which you can't use, 1 - Cross out commands which you can't use, 2 - Show all.")] public ViewType View { get; set; } = ViewType.Hide; public void NormalizeOptions() { - } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Help/Help.cs b/src/NadekoBot/Modules/Help/Help.cs index 5f8a20780..81ef9e03a 100644 --- a/src/NadekoBot/Modules/Help/Help.cs +++ b/src/NadekoBot/Modules/Help/Help.cs @@ -1,11 +1,12 @@ #nullable disable +using Amazon.S3; using NadekoBot.Modules.Help.Common; using NadekoBot.Modules.Help.Services; using NadekoBot.Modules.Permissions.Services; using Newtonsoft.Json; using System.Text; using System.Text.Json; -using Amazon.S3; +using JsonSerializer = System.Text.Json.JsonSerializer; namespace NadekoBot.Modules.Help; @@ -22,8 +23,13 @@ public class Help : NadekoModule private readonly AsyncLazy _lazyClientId; - public Help(GlobalPermissionService perms, CommandService cmds, BotConfigService bss, - IServiceProvider services, DiscordSocketClient client, IBotStrings strings) + public Help( + GlobalPermissionService perms, + CommandService cmds, + BotConfigService bss, + IServiceProvider services, + DiscordSocketClient client, + IBotStrings strings) { _cmds = cmds; _bss = bss; @@ -40,57 +46,57 @@ public class Help : NadekoModule var botSettings = _bss.Data; if (string.IsNullOrWhiteSpace(botSettings.HelpText) || botSettings.HelpText == "-") return default; - + var clientId = await _lazyClientId.Value; - var r = new ReplacementBuilder() - .WithDefault(Context) - .WithOverride("{0}", () => clientId.ToString()) - .WithOverride("{1}", () => Prefix) - .WithOverride("%prefix%", () => Prefix) - .WithOverride("%bot.prefix%", () => Prefix) - .Build(); + var r = new ReplacementBuilder().WithDefault(Context) + .WithOverride("{0}", () => clientId.ToString()) + .WithOverride("{1}", () => Prefix) + .WithOverride("%prefix%", () => Prefix) + .WithOverride("%bot.prefix%", () => Prefix) + .Build(); var text = SmartText.CreateFrom(botSettings.HelpText); return r.Replace(text); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Modules(int page = 1) { if (--page < 0) return; var topLevelModules = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()) - .Where(m => !_perms.BlockedModules.Contains(m.Key.Name.ToLowerInvariant())) - .Select(x => x.Key) - .ToList(); - - await ctx.SendPaginatedConfirmAsync(page, cur => - { - var embed = _eb.Create().WithOkColor() - .WithTitle(GetText(strs.list_of_modules)); + .Where(m => !_perms.BlockedModules.Contains(m.Key.Name.ToLowerInvariant())) + .Select(x => x.Key) + .ToList(); - var localModules = topLevelModules.Skip(12 * cur) - .Take(12) - .ToList(); - - if (!localModules.Any()) + await ctx.SendPaginatedConfirmAsync(page, + cur => { - embed = embed.WithOkColor() - .WithDescription(GetText(strs.module_page_empty)); - return embed; - } - - localModules - .OrderBy(module => module.Name) - .ToList() - .ForEach(module => embed.AddField($"{GetModuleEmoji(module.Name)} {module.Name}", - GetText(GetModuleLocStr(module.Name)) + "\n" + - Format.Code(GetText(strs.module_footer(Prefix, module.Name.ToLowerInvariant()))), - true)); + var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.list_of_modules)); - return embed; - }, topLevelModules.Count(), 12, false); + var localModules = topLevelModules.Skip(12 * cur).Take(12).ToList(); + + if (!localModules.Any()) + { + embed = embed.WithOkColor().WithDescription(GetText(strs.module_page_empty)); + return embed; + } + + localModules.OrderBy(module => module.Name) + .ToList() + .ForEach(module => embed.AddField($"{GetModuleEmoji(module.Name)} {module.Name}", + GetText(GetModuleLocStr(module.Name)) + + "\n" + + Format.Code(GetText(strs.module_footer(Prefix, module.Name.ToLowerInvariant()))), + true)); + + return embed; + }, + topLevelModules.Count(), + 12, + false); } private LocStr GetModuleLocStr(string moduleName) @@ -121,9 +127,9 @@ public class Help : NadekoModule return strs.module_description_xp; default: return strs.module_description_missing; - } } + private string GetModuleEmoji(string moduleName) { moduleName = moduleName.ToLowerInvariant(); @@ -153,11 +159,11 @@ public class Help : NadekoModule return "📝"; default: return "📖"; - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [NadekoOptions(typeof(CommandsOptions))] public async Task Commands(string module = null, params string[] args) { @@ -173,11 +179,14 @@ public class Help : NadekoModule // Find commands for that module // don't show commands which are blocked // order by name - var cmds = _cmds.Commands.Where(c => c.Module.GetTopLevelModule().Name.ToUpperInvariant().StartsWith(module, StringComparison.InvariantCulture)) - .Where(c => !_perms.BlockedCommands.Contains(c.Aliases[0].ToLowerInvariant())) - .OrderBy(c => c.Aliases[0]) - .DistinctBy(x => x.Aliases[0]) - .ToList(); + var cmds = _cmds.Commands + .Where(c => c.Module.GetTopLevelModule() + .Name.ToUpperInvariant() + .StartsWith(module, StringComparison.InvariantCulture)) + .Where(c => !_perms.BlockedCommands.Contains(c.Aliases[0].ToLowerInvariant())) + .OrderBy(c => c.Aliases[0]) + .DistinctBy(x => x.Aliases[0]) + .ToList(); // check preconditions for all commands, but only if it's not 'all' @@ -186,22 +195,21 @@ public class Help : NadekoModule if (opts.View != CommandsOptions.ViewType.All) { succ = new((await cmds.Select(async x => - { - var pre = await x.CheckPreconditionsAsync(Context, _services); - return (Cmd: x, Succ: pre.IsSuccess); - }).WhenAll()) - .Where(x => x.Succ) - .Select(x => x.Cmd)); + { + var pre = await x.CheckPreconditionsAsync(Context, _services); + return (Cmd: x, Succ: pre.IsSuccess); + }) + .WhenAll()).Where(x => x.Succ) + .Select(x => x.Cmd)); if (opts.View == CommandsOptions.ViewType.Hide) - { // if hidden is specified, completely remove these commands from the list cmds = cmds.Where(x => succ.Contains(x)).ToList(); - } } + var cmdsWithGroup = cmds.GroupBy(c => c.Module.Name.Replace("Commands", "", StringComparison.InvariantCulture)) - .OrderBy(x => x.Key == x.First().Module.Name ? int.MaxValue : x.Count()) - .ToList(); + .OrderBy(x => x.Key == x.First().Module.Name ? int.MaxValue : x.Count()) + .ToList(); if (cmdsWithGroup.Count == 0) { @@ -220,40 +228,40 @@ public class Help : NadekoModule var last = g.Count(); for (var i = 0; i < last; i++) { - var transformed = g.ElementAt(i).Select(x => - { - //if cross is specified, and the command doesn't satisfy the requirements, cross it out - if (opts.View == CommandsOptions.ViewType.Cross) - { - return $"{(succ.Contains(x) ? "✅" : "❌")}{Prefix + x.Aliases.First(),-15} {"[" + x.Aliases.Skip(1).FirstOrDefault() + "]",-8}"; - } - return $"{Prefix + x.Aliases.First(),-15} {"[" + x.Aliases.Skip(1).FirstOrDefault() + "]",-8}"; - }); + var transformed = g.ElementAt(i) + .Select(x => + { + //if cross is specified, and the command doesn't satisfy the requirements, cross it out + if (opts.View == CommandsOptions.ViewType.Cross) + return + $"{(succ.Contains(x) ? "✅" : "❌")}{Prefix + x.Aliases.First(),-15} {"[" + x.Aliases.Skip(1).FirstOrDefault() + "]",-8}"; + return + $"{Prefix + x.Aliases.First(),-15} {"[" + x.Aliases.Skip(1).FirstOrDefault() + "]",-8}"; + }); if (i == last - 1 && (i + 1) % 2 != 0) - { - transformed = transformed - .Chunk(2) - .Select(x => - { - if (x.Count() == 1) - return $"{x.First()}"; - else - return String.Concat(x); - }); - } + transformed = transformed.Chunk(2) + .Select(x => + { + if (x.Count() == 1) + return $"{x.First()}"; + return string.Concat(x); + }); embed.AddField(g.ElementAt(i).Key, "```css\n" + string.Join("\n", transformed) + "\n```", true); } } + embed.WithFooter(GetText(strs.commands_instr(Prefix))); await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(0)] public async Task H([Leftover] string fail) { - var prefixless = _cmds.Commands.FirstOrDefault(x => x.Aliases.Any(cmdName => cmdName.ToLowerInvariant() == fail)); + var prefixless = + _cmds.Commands.FirstOrDefault(x => x.Aliases.Any(cmdName => cmdName.ToLowerInvariant() == fail)); if (prefixless != null) { await H(prefixless); @@ -263,7 +271,8 @@ public class Help : NadekoModule await ReplyErrorLocalizedAsync(strs.command_not_found); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(1)] public async Task H([Leftover] CommandInfo com = null) { @@ -271,29 +280,30 @@ public class Help : NadekoModule if (com is null) { - var ch = channel is ITextChannel - ? await ctx.User.CreateDMChannelAsync() - : channel; + var ch = channel is ITextChannel ? await ctx.User.CreateDMChannelAsync() : channel; try { var data = await GetHelpString(); if (data == default) return; await ch.SendAsync(data); - try{ await ctx.OkAsync(); } catch { } // ignore if bot can't react + try { await ctx.OkAsync(); } + catch { } // ignore if bot can't react } catch (Exception) { await ReplyErrorLocalizedAsync(strs.cant_dm); } + return; } var embed = _service.GetCommandHelp(com, ctx.Guild); await channel.EmbedAsync(embed); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task GenCmdList() { @@ -301,35 +311,30 @@ public class Help : NadekoModule // order commands by top level module name // and make a dictionary of > - var cmdData = _cmds - .Commands - .GroupBy(x => x.Module.GetTopLevelModule().Name) - .OrderBy(x => x.Key) - .ToDictionary( - x => x.Key, - x => x.DistinctBy(c => c.Aliases.First()) - .Select(com => - { - List optHelpStr = null; - var opt = ((NadekoOptionsAttribute)com.Attributes.FirstOrDefault(x => x is NadekoOptionsAttribute))?.OptionType; - if (opt != null) - { - optHelpStr = HelpService.GetCommandOptionHelpList(opt); - } - - return new CommandJsonObject - { - Aliases = com.Aliases.Select(alias => Prefix + alias).ToArray(), - Description = com.RealSummary(_strings, ctx.Guild?.Id, Prefix), - Usage = com.RealRemarksArr(_strings, ctx.Guild?.Id, Prefix), - Submodule = com.Module.Name, - Module = com.Module.GetTopLevelModule().Name, - Options = optHelpStr, - Requirements = HelpService.GetCommandRequirements(com), - }; - }) - .ToList() - ); + var cmdData = _cmds.Commands.GroupBy(x => x.Module.GetTopLevelModule().Name) + .OrderBy(x => x.Key) + .ToDictionary(x => x.Key, + x => x.DistinctBy(c => c.Aliases.First()) + .Select(com => + { + List optHelpStr = null; + var opt = ((NadekoOptionsAttribute)com.Attributes.FirstOrDefault(x + => x is NadekoOptionsAttribute)) + ?.OptionType; + if (opt != null) optHelpStr = HelpService.GetCommandOptionHelpList(opt); + + return new CommandJsonObject + { + Aliases = com.Aliases.Select(alias => Prefix + alias).ToArray(), + Description = com.RealSummary(_strings, ctx.Guild?.Id, Prefix), + Usage = com.RealRemarksArr(_strings, ctx.Guild?.Id, Prefix), + Submodule = com.Module.Name, + Module = com.Module.GetTopLevelModule().Name, + Options = optHelpStr, + Requirements = HelpService.GetCommandRequirements(com) + }; + }) + .ToList()); var readableData = JsonConvert.SerializeObject(cmdData, Formatting.Indented); var uploadData = JsonConvert.SerializeObject(cmdData, Formatting.None); @@ -345,15 +350,14 @@ public class Help : NadekoModule // if all env vars are set, upload the unindented file (to save space) there if (!(serviceUrl is null || accessKey is null || secretAcccessKey is null)) { - var config = new AmazonS3Config {ServiceURL = serviceUrl}; - + var config = new AmazonS3Config { ServiceURL = serviceUrl }; + using var dlClient = new AmazonS3Client(accessKey, secretAcccessKey, config); var oldVersionObject = await dlClient.GetObjectAsync(new() { - BucketName = "nadeko-pictures", - Key = "cmds/versions.json", + BucketName = "nadeko-pictures", Key = "cmds/versions.json" }); - + using (var client = new AmazonS3Client(accessKey, secretAcccessKey, config)) { await client.PutObjectAsync(new() @@ -370,19 +374,15 @@ public class Help : NadekoModule await using var ms = new MemoryStream(); await oldVersionObject.ResponseStream.CopyToAsync(ms); var versionListString = Encoding.UTF8.GetString(ms.ToArray()); - - var versionList = System.Text.Json.JsonSerializer.Deserialize>(versionListString); + + var versionList = JsonSerializer.Deserialize>(versionListString); if (!versionList.Contains(StatsService.BotVersion)) { // save the file with new version added // versionList.Add(StatsService.BotVersion); - versionListString = System.Text.Json.JsonSerializer.Serialize( - versionList.Prepend(StatsService.BotVersion), - new JsonSerializerOptions() - { - WriteIndented = true - }); - + versionListString = JsonSerializer.Serialize(versionList.Prepend(StatsService.BotVersion), + new JsonSerializerOptions { WriteIndented = true }); + // upload the updated version list using var client = new AmazonS3Client(accessKey, secretAcccessKey, config); await client.PutObjectAsync(new() @@ -397,8 +397,9 @@ public class Help : NadekoModule } else { - Log.Warning("Version {Version} already exists in the version file. " + - "Did you forget to increment it?", StatsService.BotVersion); + Log.Warning( + "Version {Version} already exists in the version file. " + "Did you forget to increment it?", + StatsService.BotVersion); } } @@ -407,13 +408,14 @@ public class Help : NadekoModule await ctx.Channel.SendFileAsync(rDataStream, "cmds.json", GetText(strs.commandlist_regen)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Guide() - => await ConfirmLocalizedAsync(strs.guide( - "https://nadeko.bot/commands", + => await ConfirmLocalizedAsync(strs.guide("https://nadeko.bot/commands", "http://nadekobot.readthedocs.io/en/latest/")); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Donate() => await ReplyConfirmLocalizedAsync(strs.donate(PatreonUrl, PaypalUrl)); } @@ -427,4 +429,4 @@ internal class CommandJsonObject public string Module { get; set; } public List Options { get; set; } public string[] Requirements { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Help/Services/HelpService.cs b/src/NadekoBot/Modules/Help/Services/HelpService.cs index d45f1b08d..8029911bc 100644 --- a/src/NadekoBot/Modules/Help/Services/HelpService.cs +++ b/src/NadekoBot/Modules/Help/Services/HelpService.cs @@ -1,6 +1,6 @@ #nullable disable -using NadekoBot.Common.ModuleBehaviors; using CommandLine; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Modules.Administration.Services; namespace NadekoBot.Modules.Help.Services; @@ -13,7 +13,8 @@ public class HelpService : ILateExecutor, INService private readonly BotConfigService _bss; private readonly IEmbedBuilderService _eb; - public HelpService(CommandHandler ch, + public HelpService( + CommandHandler ch, IBotStrings strings, DiscordPermOverrideService dpos, BotConfigService bss, @@ -33,23 +34,23 @@ public class HelpService : ILateExecutor, INService { if (string.IsNullOrWhiteSpace(settings.DmHelpText) || settings.DmHelpText == "-") return Task.CompletedTask; - + // only send dm help text if it contains one of the keywords, if they're specified // if they're not, then reply to every DM if (settings.DmHelpTextKeywords.Any() && !settings.DmHelpTextKeywords.Any(k => msg.Content.Contains(k))) return Task.CompletedTask; - var rep = new ReplacementBuilder() - .WithOverride("%prefix%", () => _bss.Data.Prefix) - .WithOverride("%bot.prefix%", () => _bss.Data.Prefix) - .WithUser(msg.Author) - .Build(); - + var rep = new ReplacementBuilder().WithOverride("%prefix%", () => _bss.Data.Prefix) + .WithOverride("%bot.prefix%", () => _bss.Data.Prefix) + .WithUser(msg.Author) + .Build(); + var text = SmartText.CreateFrom(settings.DmHelpText); text = rep.Replace(text); return msg.Channel.SendAsync(text); } + return Task.CompletedTask; } @@ -61,30 +62,24 @@ public class HelpService : ILateExecutor, INService var alias = com.Aliases.Skip(1).FirstOrDefault(); if (alias != null) str += $" **/ `{prefix + alias}`**"; - var em = _eb.Create() - .AddField(str, $"{com.RealSummary(_strings, guild?.Id, prefix)}", true); + var em = _eb.Create().AddField(str, $"{com.RealSummary(_strings, guild?.Id, prefix)}", true); _dpos.TryGetOverrides(guild?.Id ?? 0, com.Name, out var overrides); var reqs = GetCommandRequirements(com, overrides); - if(reqs.Any()) - { - em.AddField(GetText(strs.requires, guild), string.Join("\n", reqs)); - } + if (reqs.Any()) em.AddField(GetText(strs.requires, guild), string.Join("\n", reqs)); - em - .AddField(_strings.GetText(strs.usage), - string.Join("\n", Array.ConvertAll(com.RealRemarksArr(_strings, guild?.Id, prefix), - arg => Format.Code(arg))), - false) - .WithFooter(GetText(strs.module(com.Module.GetTopLevelModule().Name), guild)) - .WithOkColor(); + em.AddField(_strings.GetText(strs.usage), + string.Join("\n", + Array.ConvertAll(com.RealRemarksArr(_strings, guild?.Id, prefix), arg => Format.Code(arg)))) + .WithFooter(GetText(strs.module(com.Module.GetTopLevelModule().Name), guild)) + .WithOkColor(); var opt = ((NadekoOptionsAttribute)com.Attributes.FirstOrDefault(x => x is NadekoOptionsAttribute))?.OptionType; if (opt != null) { var hs = GetCommandOptionHelp(opt); - if(!string.IsNullOrWhiteSpace(hs)) - em.AddField(GetText(strs.options, guild), hs, false); + if (!string.IsNullOrWhiteSpace(hs)) + em.AddField(GetText(strs.options, guild), hs); } return em; @@ -100,34 +95,33 @@ public class HelpService : ILateExecutor, INService public static List GetCommandOptionHelpList(Type opt) { var strs = opt.GetProperties() - .Select(x => x.GetCustomAttributes(true).FirstOrDefault(a => a is OptionAttribute)) - .Where(x => x != null) - .Cast() - .Select(x => - { - var toReturn = $"`--{x.LongName}`"; + .Select(x => x.GetCustomAttributes(true).FirstOrDefault(a => a is OptionAttribute)) + .Where(x => x != null) + .Cast() + .Select(x => + { + var toReturn = $"`--{x.LongName}`"; - if (!string.IsNullOrWhiteSpace(x.ShortName)) - toReturn += $" (`-{x.ShortName}`)"; + if (!string.IsNullOrWhiteSpace(x.ShortName)) + toReturn += $" (`-{x.ShortName}`)"; - toReturn += $" {x.HelpText} "; - return toReturn; - }) - .ToList(); + toReturn += $" {x.HelpText} "; + return toReturn; + }) + .ToList(); return strs; } - + public static string[] GetCommandRequirements(CommandInfo cmd, GuildPerm? overrides = null) { var toReturn = new List(); - if(cmd.Preconditions.Any(x => x is OwnerOnlyAttribute)) + if (cmd.Preconditions.Any(x => x is OwnerOnlyAttribute)) toReturn.Add("Bot Owner Only"); - - var userPerm = (UserPermAttribute)cmd.Preconditions - .FirstOrDefault(ca => ca is UserPermAttribute); + + var userPerm = (UserPermAttribute)cmd.Preconditions.FirstOrDefault(ca => ca is UserPermAttribute); var userPermString = string.Empty; if (userPerm is not null) @@ -140,14 +134,14 @@ public class HelpService : ILateExecutor, INService if (overrides is null) { - if(!string.IsNullOrWhiteSpace(userPermString)) + if (!string.IsNullOrWhiteSpace(userPermString)) toReturn.Add(userPermString); } else { - if(!string.IsNullOrWhiteSpace(userPermString)) + if (!string.IsNullOrWhiteSpace(userPermString)) toReturn.Add(Format.Strikethrough(userPermString)); - + toReturn.Add(GetPreconditionString(overrides.Value)); } @@ -155,13 +149,11 @@ public class HelpService : ILateExecutor, INService } public static string GetPreconditionString(ChannelPerm perm) - => (perm.ToString() + " Channel Permission") - .Replace("Guild", "Server", StringComparison.InvariantCulture); + => (perm + " Channel Permission").Replace("Guild", "Server", StringComparison.InvariantCulture); public static string GetPreconditionString(GuildPerm perm) - => (perm.ToString() + " Server Permission") - .Replace("Guild", "Server", StringComparison.InvariantCulture); + => (perm + " Server Permission").Replace("Guild", "Server", StringComparison.InvariantCulture); - private string GetText(LocStr str, IGuild guild, params object[] replacements) => - _strings.GetText(str, guild?.Id); -} + private string GetText(LocStr str, IGuild guild, params object[] replacements) + => _strings.GetText(str, guild?.Id); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/ICachableTrackData.cs b/src/NadekoBot/Modules/Music/Common/ICachableTrackData.cs index 84b1d3150..e41a2da29 100644 --- a/src/NadekoBot/Modules/Music/Common/ICachableTrackData.cs +++ b/src/NadekoBot/Modules/Music/Common/ICachableTrackData.cs @@ -9,4 +9,4 @@ public interface ICachableTrackData public TimeSpan Duration { get; } MusicPlatform Platform { get; set; } string Title { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/ILocalTrackResolver.cs b/src/NadekoBot/Modules/Music/Common/ILocalTrackResolver.cs index e17940493..2224ffa71 100644 --- a/src/NadekoBot/Modules/Music/Common/ILocalTrackResolver.cs +++ b/src/NadekoBot/Modules/Music/Common/ILocalTrackResolver.cs @@ -4,4 +4,4 @@ namespace NadekoBot.Modules.Music; public interface ILocalTrackResolver : IPlatformQueryResolver { IAsyncEnumerable ResolveDirectoryAsync(string dirPath); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/IMusicPlayer.cs b/src/NadekoBot/Modules/Music/Common/IMusicPlayer.cs index 24e9e62aa..0f409f269 100644 --- a/src/NadekoBot/Modules/Music/Common/IMusicPlayer.cs +++ b/src/NadekoBot/Modules/Music/Common/IMusicPlayer.cs @@ -21,9 +21,14 @@ public interface IMusicPlayer : IDisposable void Kill(); bool TryRemoveTrackAt(int index, out IQueuedTrackInfo? trackInfo); - - - Task<(IQueuedTrackInfo? QueuedTrack, int Index)> TryEnqueueTrackAsync(string query, string queuer, bool asNext, MusicPlatform? forcePlatform = null); + + + Task<(IQueuedTrackInfo? QueuedTrack, int Index)> TryEnqueueTrackAsync( + string query, + string queuer, + bool asNext, + MusicPlatform? forcePlatform = null); + Task EnqueueManyAsync(IEnumerable<(string Query, MusicPlatform Platform)> queries, string queuer); bool TogglePause(); IQueuedTrackInfo? MoveTrack(int from, int to); @@ -31,4 +36,4 @@ public interface IMusicPlayer : IDisposable void EnqueueTracks(IEnumerable tracks, string queuer); void SetRepeat(PlayerRepeatType type); void ShuffleQueue(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/IMusicQueue.cs b/src/NadekoBot/Modules/Music/Common/IMusicQueue.cs index 16431b92b..d4276e370 100644 --- a/src/NadekoBot/Modules/Music/Common/IMusicQueue.cs +++ b/src/NadekoBot/Modules/Music/Common/IMusicQueue.cs @@ -2,21 +2,21 @@ public interface IMusicQueue { + int Index { get; } + int Count { get; } IQueuedTrackInfo Enqueue(ITrackInfo trackInfo, string queuer, out int index); IQueuedTrackInfo EnqueueNext(ITrackInfo song, string queuer, out int index); - + void EnqueueMany(IEnumerable tracks, string queuer); - + public IReadOnlyCollection List(); IQueuedTrackInfo? GetCurrent(out int index); void Advance(); void Clear(); bool SetIndex(int index); bool TryRemoveAt(int index, out IQueuedTrackInfo? trackInfo, out bool isCurrent); - int Index { get; } - int Count { get; } void RemoveCurrent(); IQueuedTrackInfo? MoveTrack(int from, int to); void Shuffle(Random rng); bool IsLast(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/IPlatformQueryResolver.cs b/src/NadekoBot/Modules/Music/Common/IPlatformQueryResolver.cs index 62a2ae032..ec918c817 100644 --- a/src/NadekoBot/Modules/Music/Common/IPlatformQueryResolver.cs +++ b/src/NadekoBot/Modules/Music/Common/IPlatformQueryResolver.cs @@ -3,4 +3,4 @@ public interface IPlatformQueryResolver { Task ResolveByQueryAsync(string query); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/IQueuedTrackInfo.cs b/src/NadekoBot/Modules/Music/Common/IQueuedTrackInfo.cs index 6595084cc..a7577e424 100644 --- a/src/NadekoBot/Modules/Music/Common/IQueuedTrackInfo.cs +++ b/src/NadekoBot/Modules/Music/Common/IQueuedTrackInfo.cs @@ -6,4 +6,4 @@ public interface IQueuedTrackInfo : ITrackInfo public ITrackInfo TrackInfo { get; } public string Queuer { get; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/IRadioResolver.cs b/src/NadekoBot/Modules/Music/Common/IRadioResolver.cs index 085f610fc..46a17b2c6 100644 --- a/src/NadekoBot/Modules/Music/Common/IRadioResolver.cs +++ b/src/NadekoBot/Modules/Music/Common/IRadioResolver.cs @@ -3,5 +3,4 @@ namespace NadekoBot.Modules.Music; public interface IRadioResolver : IPlatformQueryResolver { - -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/ISoundcloudResolver.cs b/src/NadekoBot/Modules/Music/Common/ISoundcloudResolver.cs index 05148dc72..3669f9ebd 100644 --- a/src/NadekoBot/Modules/Music/Common/ISoundcloudResolver.cs +++ b/src/NadekoBot/Modules/Music/Common/ISoundcloudResolver.cs @@ -5,4 +5,4 @@ public interface ISoundcloudResolver : IPlatformQueryResolver { bool IsSoundCloudLink(string url); IAsyncEnumerable ResolvePlaylistAsync(string playlist); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/ITrackCacher.cs b/src/NadekoBot/Modules/Music/Common/ITrackCacher.cs index ffbfd7f61..478794e18 100644 --- a/src/NadekoBot/Modules/Music/Common/ITrackCacher.cs +++ b/src/NadekoBot/Modules/Music/Common/ITrackCacher.cs @@ -5,16 +5,21 @@ public interface ITrackCacher Task GetOrCreateStreamLink( string id, MusicPlatform platform, - Func> streamUrlFactory - ); + Func> streamUrlFactory); Task CacheTrackDataAsync(ICachableTrackData data); Task GetCachedDataByIdAsync(string id, MusicPlatform platform); Task GetCachedDataByQueryAsync(string query, MusicPlatform platform); Task CacheTrackDataByQueryAsync(string query, ICachableTrackData data); - Task CacheStreamUrlAsync(string id, MusicPlatform platform, string url, TimeSpan expiry); + + Task CacheStreamUrlAsync( + string id, + MusicPlatform platform, + string url, + TimeSpan expiry); + Task> GetPlaylistTrackIdsAsync(string playlistId, MusicPlatform platform); Task CachePlaylistTrackIdsAsync(string playlistId, MusicPlatform platform, IEnumerable ids); Task CachePlaylistIdByQueryAsync(string query, MusicPlatform platform, string playlistId); Task GetPlaylistIdByQueryAsync(string query, MusicPlatform platform); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/ITrackInfo.cs b/src/NadekoBot/Modules/Music/Common/ITrackInfo.cs index efed1e1ad..1f2791872 100644 --- a/src/NadekoBot/Modules/Music/Common/ITrackInfo.cs +++ b/src/NadekoBot/Modules/Music/Common/ITrackInfo.cs @@ -8,4 +8,4 @@ public interface ITrackInfo public TimeSpan Duration { get; } public MusicPlatform Platform { get; } public ValueTask GetStreamUrl(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/ITrackResolveProvider.cs b/src/NadekoBot/Modules/Music/Common/ITrackResolveProvider.cs index 1a7ce5410..511cd94ef 100644 --- a/src/NadekoBot/Modules/Music/Common/ITrackResolveProvider.cs +++ b/src/NadekoBot/Modules/Music/Common/ITrackResolveProvider.cs @@ -3,4 +3,4 @@ public interface ITrackResolveProvider { Task QuerySongAsync(string query, MusicPlatform? forcePlatform); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/IVoiceProxy.cs b/src/NadekoBot/Modules/Music/Common/IVoiceProxy.cs index 0160e5078..d178bc3c9 100644 --- a/src/NadekoBot/Modules/Music/Common/IVoiceProxy.cs +++ b/src/NadekoBot/Modules/Music/Common/IVoiceProxy.cs @@ -12,4 +12,4 @@ public interface IVoiceProxy Task StopSpeakingAsync(); public Task StartGateway(); Task StopGateway(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/IYoutubeResolver.cs b/src/NadekoBot/Modules/Music/Common/IYoutubeResolver.cs index 103cbb6f1..877dde671 100644 --- a/src/NadekoBot/Modules/Music/Common/IYoutubeResolver.cs +++ b/src/NadekoBot/Modules/Music/Common/IYoutubeResolver.cs @@ -8,4 +8,4 @@ public interface IYoutubeResolver : IPlatformQueryResolver public Task ResolveByIdAsync(string id); IAsyncEnumerable ResolveTracksFromPlaylistAsync(string query); Task ResolveByQueryAsync(string query, bool tryExtractingId); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Impl/CachableTrackData.cs b/src/NadekoBot/Modules/Music/Common/Impl/CachableTrackData.cs index 9ac80cff5..1504fd8e9 100644 --- a/src/NadekoBot/Modules/Music/Common/Impl/CachableTrackData.cs +++ b/src/NadekoBot/Modules/Music/Common/Impl/CachableTrackData.cs @@ -10,7 +10,10 @@ public sealed class CachableTrackData : ICachableTrackData public string Url { get; set; } = string.Empty; public string Thumbnail { get; set; } = string.Empty; public double TotalDurationMs { get; set; } + [JsonIgnore] - public TimeSpan Duration => TimeSpan.FromMilliseconds(TotalDurationMs); + public TimeSpan Duration + => TimeSpan.FromMilliseconds(TotalDurationMs); + public MusicPlatform Platform { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Impl/MultimediaTimer.cs b/src/NadekoBot/Modules/Music/Common/Impl/MultimediaTimer.cs index 7aaf571b9..9809232d8 100644 --- a/src/NadekoBot/Modules/Music/Common/Impl/MultimediaTimer.cs +++ b/src/NadekoBot/Modules/Music/Common/Impl/MultimediaTimer.cs @@ -5,61 +5,6 @@ namespace NadekoBot.Modules.Music.Common; public sealed class MultimediaTimer : IDisposable { - private delegate void LpTimeProcDelegate(uint uTimerID, uint uMsg, int dwUser, int dw1, int dw2); - - /// - /// The timeSetEvent function starts a specified timer event. The multimedia timer runs in its own thread. - /// After the event is activated, it calls the specified callback function or sets or pulses the specified - /// event object. - /// - /// - /// Event delay, in milliseconds. If this value is not in the range of the minimum and - /// maximum event delays supported by the timer, the function returns an error. - /// - /// - /// Resolution of the timer event, in milliseconds. The resolution increases with - /// smaller values; a resolution of 0 indicates periodic events should occur with the greatest possible accuracy. - /// To reduce system overhead, however, you should use the maximum value appropriate for your application. - /// - /// - /// Pointer to a callback function that is called once upon expiration of a single event or periodically upon - /// expiration of periodic events. If fuEvent specifies the TIME_CALLBACK_EVENT_SET or TIME_CALLBACK_EVENT_PULSE - /// flag, then the lpTimeProc parameter is interpreted as a handle to an event object. The event will be set or - /// pulsed upon completion of a single event or periodically upon completion of periodic events. - /// For any other value of fuEvent, the lpTimeProc parameter is a pointer to a callback function of type - /// LPTIMECALLBACK. - /// - /// User-supplied callback data. - /// - /// Timer event type. This parameter may include one of the following values. - [DllImport("Winmm.dll")] - private static extern uint timeSetEvent( - uint uDelay, - uint uResolution, - LpTimeProcDelegate lpTimeProc, - int dwUser, - TimerMode fuEvent - ); - - private enum TimerMode - { - OneShot, - Periodic, - } - - /// - /// The timeKillEvent function cancels a specified timer event. - /// - /// - /// Identifier of the timer event to cancel. - /// This identifier was returned by the timeSetEvent function when the timer event was set up. - /// - /// Returns TIMERR_NOERROR if successful or MMSYSERR_INVALPARAM if the specified timer event does not exist. - [DllImport("Winmm.dll")] - private static extern int timeKillEvent( - uint uTimerID - ); - private LpTimeProcDelegate _lpTimeProc; private readonly uint _eventId; private readonly Action _callback; @@ -77,7 +22,56 @@ public sealed class MultimediaTimer : IDisposable _eventId = timeSetEvent((uint)period, 1, _lpTimeProc, 0, TimerMode.Periodic); } - private void CallbackInternal(uint uTimerId, uint uMsg, int dwUser, int dw1, int dw2) + /// + /// The timeSetEvent function starts a specified timer event. The multimedia timer runs in its own thread. + /// After the event is activated, it calls the specified callback function or sets or pulses the specified + /// event object. + /// + /// + /// Event delay, in milliseconds. If this value is not in the range of the minimum and + /// maximum event delays supported by the timer, the function returns an error. + /// + /// + /// Resolution of the timer event, in milliseconds. The resolution increases with + /// smaller values; a resolution of 0 indicates periodic events should occur with the greatest possible accuracy. + /// To reduce system overhead, however, you should use the maximum value appropriate for your application. + /// + /// + /// Pointer to a callback function that is called once upon expiration of a single event or periodically upon + /// expiration of periodic events. If fuEvent specifies the TIME_CALLBACK_EVENT_SET or TIME_CALLBACK_EVENT_PULSE + /// flag, then the lpTimeProc parameter is interpreted as a handle to an event object. The event will be set or + /// pulsed upon completion of a single event or periodically upon completion of periodic events. + /// For any other value of fuEvent, the lpTimeProc parameter is a pointer to a callback function of type + /// LPTIMECALLBACK. + /// + /// User-supplied callback data. + /// + /// Timer event type. This parameter may include one of the following values. + [DllImport("Winmm.dll")] + private static extern uint timeSetEvent( + uint uDelay, + uint uResolution, + LpTimeProcDelegate lpTimeProc, + int dwUser, + TimerMode fuEvent); + + /// + /// The timeKillEvent function cancels a specified timer event. + /// + /// + /// Identifier of the timer event to cancel. + /// This identifier was returned by the timeSetEvent function when the timer event was set up. + /// + /// Returns TIMERR_NOERROR if successful or MMSYSERR_INVALPARAM if the specified timer event does not exist. + [DllImport("Winmm.dll")] + private static extern int timeKillEvent(uint uTimerID); + + private void CallbackInternal( + uint uTimerId, + uint uMsg, + int dwUser, + int dw1, + int dw2) => _callback(_state); public void Dispose() @@ -85,4 +79,17 @@ public sealed class MultimediaTimer : IDisposable _lpTimeProc = default; timeKillEvent(_eventId); } -} + + private delegate void LpTimeProcDelegate( + uint uTimerID, + uint uMsg, + int dwUser, + int dw1, + int dw2); + + private enum TimerMode + { + OneShot, + Periodic + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Impl/MusicExtensions.cs b/src/NadekoBot/Modules/Music/Common/Impl/MusicExtensions.cs index e9005f3ca..bfa8acc98 100644 --- a/src/NadekoBot/Modules/Music/Common/Impl/MusicExtensions.cs +++ b/src/NadekoBot/Modules/Music/Common/Impl/MusicExtensions.cs @@ -20,8 +20,8 @@ public static class MusicExtensions } public static string PrettyVolume(this IMusicPlayer mp) - => $"🔉 {(int) (mp.Volume * 100)}%"; - + => $"🔉 {(int)(mp.Volume * 100)}%"; + public static string PrettyName(this ITrackInfo trackInfo) => $"**[{trackInfo.Title.TrimTo(60).Replace("[", "\\[").Replace("]", "\\]")}]({trackInfo.Url.TrimTo(50, true)})**"; @@ -45,7 +45,7 @@ public static class MusicExtensions } public static ICachableTrackData ToCachedData(this ITrackInfo trackInfo, string id) - => new CachableTrackData() + => new CachableTrackData { TotalDurationMs = trackInfo.Duration.TotalMilliseconds, Id = id, @@ -54,4 +54,4 @@ public static class MusicExtensions Platform = trackInfo.Platform, Title = trackInfo.Title }; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Impl/MusicPlatform.cs b/src/NadekoBot/Modules/Music/Common/Impl/MusicPlatform.cs index 22d64948f..55968086b 100644 --- a/src/NadekoBot/Modules/Music/Common/Impl/MusicPlatform.cs +++ b/src/NadekoBot/Modules/Music/Common/Impl/MusicPlatform.cs @@ -6,5 +6,5 @@ public enum MusicPlatform Radio, Youtube, Local, - SoundCloud, -} + SoundCloud +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Impl/MusicPlayer.cs b/src/NadekoBot/Modules/Music/Common/Impl/MusicPlayer.cs index 8761775ae..f808d0b84 100644 --- a/src/NadekoBot/Modules/Music/Common/Impl/MusicPlayer.cs +++ b/src/NadekoBot/Modules/Music/Common/Impl/MusicPlayer.cs @@ -1,28 +1,29 @@ +using Ayu.Discord.Voice; +using NadekoBot.Services.Database.Models; using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using Ayu.Discord.Voice; -using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Music; public sealed class MusicPlayer : IMusicPlayer { - private delegate void AdjustVolumeDelegate(Span data, float volume); - - private readonly AdjustVolumeDelegate AdjustVolume; - private readonly VoiceClient _vc; - + public event Func? OnCompleted; + public event Func? OnStarted; + public event Func? OnQueueStopped; public bool IsKilled { get; private set; } public bool IsStopped { get; private set; } public bool IsPaused { get; private set; } public PlayerRepeatType Repeat { get; private set; } - - public int CurrentIndex => _queue.Index; - - public float Volume => _volume; - private float _volume = 1.0f; + + public int CurrentIndex + => _queue.Index; + + public float Volume { get; private set; } = 1.0f; + + private readonly AdjustVolumeDelegate AdjustVolume; + private readonly VoiceClient _vc; private readonly IMusicQueue _queue; private readonly ITrackResolveProvider _trackResolveProvider; @@ -50,7 +51,7 @@ public sealed class MusicPlayer : IMusicPlayer AdjustVolume = AdjustVolumeInt16; else AdjustVolume = AdjustVolumeFloat32; - + _songBuffer = new PoopyBufferImmortalized(_vc.InputLength); _thread = new(async () => @@ -63,34 +64,18 @@ public sealed class MusicPlayer : IMusicPlayer private static VoiceClient GetVoiceClient(QualityPreset qualityPreset) => qualityPreset switch { - QualityPreset.Highest => new( - SampleRate._48k, - Bitrate._192k, - Channels.Two, - FrameDelay.Delay20, - BitDepthEnum.Float32 - ), - QualityPreset.High => new( - SampleRate._48k, - Bitrate._128k, - Channels.Two, - FrameDelay.Delay40, - BitDepthEnum.Float32 - ), - QualityPreset.Medium => new( - SampleRate._48k, + QualityPreset.Highest => new(), + QualityPreset.High => new(SampleRate._48k, Bitrate._128k, Channels.Two, FrameDelay.Delay40), + QualityPreset.Medium => new(SampleRate._48k, Bitrate._96k, Channels.Two, FrameDelay.Delay40, - BitDepthEnum.UInt16 - ), - QualityPreset.Low => new( - SampleRate._48k, + BitDepthEnum.UInt16), + QualityPreset.Low => new(SampleRate._48k, Bitrate._64k, Channels.Two, FrameDelay.Delay40, - BitDepthEnum.UInt16 - ), + BitDepthEnum.UInt16), _ => throw new ArgumentOutOfRangeException(nameof(qualityPreset), qualityPreset, null) }; @@ -103,7 +88,7 @@ public sealed class MusicPlayer : IMusicPlayer // wait until a song is available in the queue // or until the queue is resumed var track = _queue.GetCurrent(out var index); - + if (track is null || IsStopped) { await Task.Delay(500); @@ -134,9 +119,8 @@ public sealed class MusicPlayer : IMusicPlayer using var source = FfmpegTrackDataSource.CreateAsync( _vc.BitDepth, streamUrl, - track.Platform == MusicPlatform.Local - ); - + track.Platform == MusicPlatform.Local); + // start moving data from the source into the buffer // this method will return once the sufficient prebuffering is done await _songBuffer.BufferAsync(source, token); @@ -191,10 +175,8 @@ public sealed class MusicPlayer : IMusicPlayer sw.Start(); Thread.Sleep(2); - var delay = sw.ElapsedTicks * ticksPerMs > 3f - ? _vc.Delay - 16 - : _vc.Delay - 3; - + var delay = sw.ElapsedTicks * ticksPerMs > 3f ? _vc.Delay - 16 : _vc.Delay - 3; + var errorCount = 0; while (!IsStopped && !IsKilled) { @@ -205,23 +187,23 @@ public sealed class MusicPlayer : IMusicPlayer _skipped = false; break; } - + if (IsPaused) { await Task.Delay(200); continue; } - + sw.Restart(); var ticks = sw.ElapsedTicks; try { var result = CopyChunkToOutput(_songBuffer, _vc); - + // if song is finished if (result is null) break; - + if (result is true) { if (errorCount > 0) @@ -229,12 +211,12 @@ public sealed class MusicPlayer : IMusicPlayer _ = _proxy.StartSpeakingAsync(); errorCount = 0; } - + // todo future windows multimedia api - + // wait for slightly less than the latency Thread.Sleep(delay); - + // and then spin out the rest while ((sw.ElapsedTicks - ticks) * ticksPerMs <= _vc.Delay - 0.1f) Thread.SpinWait(100); @@ -243,16 +225,16 @@ public sealed class MusicPlayer : IMusicPlayer { // result is false is either when the gateway is being swapped // or if the bot is reconnecting, or just disconnected for whatever reason - + // tolerate up to 15x200ms of failures (3 seconds) if (++errorCount <= 15) { await Task.Delay(200); continue; } - + Log.Warning("Can't send data to voice channel"); - + IsStopped = true; // if errors are happening for more than 3 seconds // Stop the player @@ -268,8 +250,8 @@ public sealed class MusicPlayer : IMusicPlayer catch (Win32Exception) { IsStopped = true; - Log.Error("Please install ffmpeg and make sure it's added to your " + - "PATH environment variable before trying again"); + Log.Error("Please install ffmpeg and make sure it's added to your " + + "PATH environment variable before trying again"); } catch (OperationCanceledException) { @@ -284,12 +266,13 @@ public sealed class MusicPlayer : IMusicPlayer cancellationTokenSource.Cancel(); // turn off green in vc _ = OnCompleted?.Invoke(this, track); - + HandleQueuePostTrack(); _skipped = false; - - _ = _proxy.StopSpeakingAsync();; - + + _ = _proxy.StopSpeakingAsync(); + ; + await Task.Delay(100); } } @@ -300,12 +283,9 @@ public sealed class MusicPlayer : IMusicPlayer var data = sb.Read(vc.InputLength, out var length); // if nothing is read from the buffer, song is finished - if (data.Length == 0) - { - return null; - } + if (data.Length == 0) return null; - AdjustVolume(data, _volume); + AdjustVolume(data, Volume); return _proxy.SendPcmFrame(vc, data, length); } @@ -322,7 +302,7 @@ public sealed class MusicPlayer : IMusicPlayer if (repeat == PlayerRepeatType.Track || isStopped) return; - + // if queue is being repeated, advance no matter what if (repeat == PlayerRepeatType.None) { @@ -334,37 +314,36 @@ public sealed class MusicPlayer : IMusicPlayer OnQueueStopped?.Invoke(this); return; } - + _queue.Advance(); return; } - + _queue.Advance(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void AdjustVolumeInt16(Span audioSamples, float volume) { if (Math.Abs(volume - 1f) < 0.0001f) return; - + var samples = MemoryMarshal.Cast(audioSamples); - + for (var i = 0; i < samples.Length; i++) { ref var sample = ref samples[i]; - sample = (short) (sample * volume); + sample = (short)(sample * volume); } } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void AdjustVolumeFloat32(Span audioSamples, float volume) { if (Math.Abs(volume - 1f) < 0.0001f) return; - + var samples = MemoryMarshal.Cast(audioSamples); - + for (var i = 0; i < samples.Length; i++) { ref var sample = ref samples[i]; @@ -373,7 +352,7 @@ public sealed class MusicPlayer : IMusicPlayer } public async Task<(IQueuedTrackInfo? QueuedTrack, int Index)> TryEnqueueTrackAsync( - string query, + string query, string queuer, bool asNext, MusicPlatform? forcePlatform = null) @@ -389,7 +368,7 @@ public sealed class MusicPlayer : IMusicPlayer return (_queue.Enqueue(song, queuer, out index), index); } - + public async Task EnqueueManyAsync(IEnumerable<(string Query, MusicPlatform Platform)> queries, string queuer) { var errorCount = 0; @@ -397,30 +376,31 @@ public sealed class MusicPlayer : IMusicPlayer { if (IsKilled) break; - + await chunk.Select(async data => - { - var (query, platform) = data; - try - { - await TryEnqueueTrackAsync(query, queuer, false, forcePlatform: platform); - errorCount = 0; - } - catch (Exception ex) - { - Log.Warning(ex, "Error resolving {MusicPlatform} Track {TrackQuery}", platform, query); - ++errorCount; - } - }).WhenAll(); + { + var (query, platform) = data; + try + { + await TryEnqueueTrackAsync(query, queuer, false, platform); + errorCount = 0; + } + catch (Exception ex) + { + Log.Warning(ex, "Error resolving {MusicPlatform} Track {TrackQuery}", platform, query); + ++errorCount; + } + }) + .WhenAll(); await Task.Delay(1000); - + // > 10 errors in a row = kill if (errorCount > 10) break; } } - + public void EnqueueTrack(ITrackInfo track, string queuer) => _queue.Enqueue(track, queuer, out _); @@ -475,7 +455,7 @@ public sealed class MusicPlayer : IMusicPlayer if (normalizedVolume is < 0f or > 1f) throw new ArgumentOutOfRangeException(nameof(newVolume), "Volume must be in range 0-100"); - _volume = normalizedVolume; + Volume = normalizedVolume; } public void Kill() @@ -496,9 +476,12 @@ public sealed class MusicPlayer : IMusicPlayer return true; } - - public bool TogglePause() => IsPaused = !IsPaused; - public IQueuedTrackInfo? MoveTrack(int from, int to) => _queue.MoveTrack(from, to); + + public bool TogglePause() + => IsPaused = !IsPaused; + + public IQueuedTrackInfo? MoveTrack(int from, int to) + => _queue.MoveTrack(from, to); public void Dispose() { @@ -511,7 +494,5 @@ public sealed class MusicPlayer : IMusicPlayer _vc.Dispose(); } - public event Func? OnCompleted; - public event Func? OnStarted; - public event Func? OnQueueStopped; -} + private delegate void AdjustVolumeDelegate(Span data, float volume); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Impl/MusicQueue.cs b/src/NadekoBot/Modules/Music/Common/Impl/MusicQueue.cs index 09b5c8ac5..0e5b6c541 100644 --- a/src/NadekoBot/Modules/Music/Common/Impl/MusicQueue.cs +++ b/src/NadekoBot/Modules/Music/Common/Impl/MusicQueue.cs @@ -7,11 +7,20 @@ public sealed partial class MusicQueue public ITrackInfo TrackInfo { get; } public string Queuer { get; } - public string Title => TrackInfo.Title; - public string Url => TrackInfo.Url; - public string Thumbnail => TrackInfo.Thumbnail; - public TimeSpan Duration => TrackInfo.Duration; - public MusicPlatform Platform => TrackInfo.Platform; + public string Title + => TrackInfo.Title; + + public string Url + => TrackInfo.Url; + + public string Thumbnail + => TrackInfo.Thumbnail; + + public TimeSpan Duration + => TrackInfo.Duration; + + public MusicPlatform Platform + => TrackInfo.Platform; public QueuedTrackInfo(ITrackInfo trackInfo, string queuer) @@ -20,14 +29,13 @@ public sealed partial class MusicQueue Queuer = queuer; } - public ValueTask GetStreamUrl() => TrackInfo.GetStreamUrl(); + public ValueTask GetStreamUrl() + => TrackInfo.GetStreamUrl(); } } public sealed partial class MusicQueue : IMusicQueue { - private LinkedList _tracks; - public int Index { get @@ -41,8 +49,6 @@ public sealed partial class MusicQueue : IMusicQueue } } - private int _index; - public int Count { get @@ -54,6 +60,10 @@ public sealed partial class MusicQueue : IMusicQueue } } + private LinkedList _tracks; + + private int _index; + private readonly object locker = new(); public MusicQueue() @@ -77,17 +87,12 @@ public sealed partial class MusicQueue : IMusicQueue { lock (locker) { - if (_tracks.Count == 0) - { - return Enqueue(trackInfo, queuer, out index); - } + if (_tracks.Count == 0) return Enqueue(trackInfo, queuer, out index); var currentNode = _tracks.First!; int i; for (i = 1; i <= _index; i++) - { currentNode = currentNode.Next!; // can't be null because index is always in range of the count - } var added = new QueuedTrackInfo(trackInfo, queuer); index = i; @@ -160,10 +165,7 @@ public sealed partial class MusicQueue : IMusicQueue { var removedNode = _tracks.First!; int i; - for (i = 0; i < index; i++) - { - removedNode = removedNode.Next!; - } + for (i = 0; i < index; i++) removedNode = removedNode.Next!; trackInfo = removedNode.Value; _tracks.Remove(removedNode); @@ -303,14 +305,11 @@ public sealed partial class MusicQueue : IMusicQueue if (index < 0 || index >= _tracks.Count) return false; - if (index == _index) - { - isCurrent = true; - } + if (index == _index) isCurrent = true; RemoveAtInternal(index, out trackInfo); return true; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Impl/RedisTrackCacher.cs b/src/NadekoBot/Modules/Music/Common/Impl/RedisTrackCacher.cs index eadc3f19b..8960616d5 100644 --- a/src/NadekoBot/Modules/Music/Common/Impl/RedisTrackCacher.cs +++ b/src/NadekoBot/Modules/Music/Common/Impl/RedisTrackCacher.cs @@ -1,6 +1,6 @@ -using System.Runtime.CompilerServices; +using StackExchange.Redis; +using System.Runtime.CompilerServices; using System.Text.Json; -using StackExchange.Redis; namespace NadekoBot.Modules.Music; @@ -14,13 +14,12 @@ public sealed class RedisTrackCacher : ITrackCacher public async Task GetOrCreateStreamLink( string id, MusicPlatform platform, - Func> streamUrlFactory - ) + Func> streamUrlFactory) { var trackStreamKey = CreateStreamKey(id, platform); - + var value = await GetStreamFromCacheInternalAsync(trackStreamKey); - + // if there is no cached value if (value == default) { @@ -28,17 +27,16 @@ public sealed class RedisTrackCacher : ITrackCacher var success = await CreateAndCacheStreamUrlAsync(trackStreamKey, streamUrlFactory); if (!success) return null; - + return await GetOrCreateStreamLink(id, platform, streamUrlFactory); } // cache new one for future use _ = Task.Run(() => CreateAndCacheStreamUrlAsync(trackStreamKey, streamUrlFactory)); - + return value; } - - + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string CreateStreamKey(string id, MusicPlatform platform) @@ -64,7 +62,11 @@ public sealed class RedisTrackCacher : ITrackCacher } } - public Task CacheStreamUrlAsync(string id, MusicPlatform platform, string url, TimeSpan expiry) + public Task CacheStreamUrlAsync( + string id, + MusicPlatform platform, + string url, + TimeSpan expiry) => CacheStreamUrlInternalAsync(CreateStreamKey(id, platform), url, expiry); private async Task CacheStreamUrlInternalAsync(string trackStreamKey, string url, TimeSpan expiry) @@ -73,10 +75,10 @@ public sealed class RedisTrackCacher : ITrackCacher // to make sure client doesn't get an expired stream url // to achieve this, track keys will be just pointers to real data // but that data will expire - + var db = _multiplexer.GetDatabase(); var dataKey = $"entry:{Guid.NewGuid()}:{trackStreamKey}"; - await db.StringSetAsync(dataKey, url, expiry: expiry); + await db.StringSetAsync(dataKey, url, expiry); await db.ListRightPushAsync(trackStreamKey, dataKey); } @@ -86,7 +88,7 @@ public sealed class RedisTrackCacher : ITrackCacher // from the list of cached trackurls until it finds a non-expired key var db = _multiplexer.GetDatabase(); - while(true) + while (true) { string? dataKey = await db.ListLeftPopAsync(trackStreamKey); if (dataKey == default) @@ -103,7 +105,7 @@ public sealed class RedisTrackCacher : ITrackCacher [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string CreateCachedDataKey(string id, MusicPlatform platform) => $"track:data:{platform}:{id}"; - + public Task CacheTrackDataAsync(ICachableTrackData data) { var db = _multiplexer.GetDatabase(); @@ -111,13 +113,13 @@ public sealed class RedisTrackCacher : ITrackCacher var trackDataKey = CreateCachedDataKey(data.Id, data.Platform); var dataString = JsonSerializer.Serialize((object)data); // cache for 1 day - return db.StringSetAsync(trackDataKey, dataString, expiry: TimeSpan.FromDays(1)); + return db.StringSetAsync(trackDataKey, dataString, TimeSpan.FromDays(1)); } public async Task GetCachedDataByIdAsync(string id, MusicPlatform platform) { var db = _multiplexer.GetDatabase(); - + var trackDataKey = CreateCachedDataKey(id, platform); var data = await db.StringGetAsync(trackDataKey); if (data == default) @@ -129,10 +131,11 @@ public sealed class RedisTrackCacher : ITrackCacher [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string CreateCachedQueryDataKey(string query, MusicPlatform platform) => $"track:query_to_id:{platform}:{query}"; + public async Task GetCachedDataByQueryAsync(string query, MusicPlatform platform) { query = Uri.EscapeDataString(query.Trim()); - + var db = _multiplexer.GetDatabase(); var queryDataKey = CreateCachedQueryDataKey(query, platform); @@ -149,17 +152,18 @@ public sealed class RedisTrackCacher : ITrackCacher // first cache the data await CacheTrackDataAsync(data); - + // then map the query to cached data's id var db = _multiplexer.GetDatabase(); var queryDataKey = CreateCachedQueryDataKey(query, data.Platform); await db.StringSetAsync(queryDataKey, data.Id, TimeSpan.FromDays(7)); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string CreateCachedPlaylistKey(string playlistId, MusicPlatform platform) => $"playlist:{platform}:{playlistId}"; + public async Task> GetPlaylistTrackIdsAsync(string playlistId, MusicPlatform platform) { var db = _multiplexer.GetDatabase(); @@ -175,13 +179,14 @@ public sealed class RedisTrackCacher : ITrackCacher { var db = _multiplexer.GetDatabase(); var key = CreateCachedPlaylistKey(playlistId, platform); - await db.ListRightPushAsync(key, ids.Select(x => (RedisValue) x).ToArray()); + await db.ListRightPushAsync(key, ids.Select(x => (RedisValue)x).ToArray()); await db.KeyExpireAsync(key, TimeSpan.FromDays(7)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string CreateCachedPlaylistQueryKey(string query, MusicPlatform platform) => $"playlist:query:{platform}:{query}"; + public Task CachePlaylistIdByQueryAsync(string query, MusicPlatform platform, string playlistId) { query = Uri.EscapeDataString(query.Trim()); @@ -201,4 +206,4 @@ public sealed class RedisTrackCacher : ITrackCacher return val; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Impl/RemoteTrackInfo.cs b/src/NadekoBot/Modules/Music/Common/Impl/RemoteTrackInfo.cs index c2255c5b2..abfbe0c78 100644 --- a/src/NadekoBot/Modules/Music/Common/Impl/RemoteTrackInfo.cs +++ b/src/NadekoBot/Modules/Music/Common/Impl/RemoteTrackInfo.cs @@ -10,7 +10,12 @@ public sealed class RemoteTrackInfo : ITrackInfo private readonly Func> _streamFactory; - public RemoteTrackInfo(string title, string url, string thumbnail, TimeSpan duration, MusicPlatform platform, + public RemoteTrackInfo( + string title, + string url, + string thumbnail, + TimeSpan duration, + MusicPlatform platform, Func> streamFactory) { _streamFactory = streamFactory; @@ -21,5 +26,6 @@ public sealed class RemoteTrackInfo : ITrackInfo Platform = platform; } - public async ValueTask GetStreamUrl() => await _streamFactory(); -} + public async ValueTask GetStreamUrl() + => await _streamFactory(); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Impl/SimpleTrackInfo.cs b/src/NadekoBot/Modules/Music/Common/Impl/SimpleTrackInfo.cs index dfe9874fd..4402aac3e 100644 --- a/src/NadekoBot/Modules/Music/Common/Impl/SimpleTrackInfo.cs +++ b/src/NadekoBot/Modules/Music/Common/Impl/SimpleTrackInfo.cs @@ -8,10 +8,14 @@ public sealed class SimpleTrackInfo : ITrackInfo public TimeSpan Duration { get; } public MusicPlatform Platform { get; } public string? StreamUrl { get; } - public ValueTask GetStreamUrl() => new(StreamUrl); - public SimpleTrackInfo(string title, string url, string thumbnail, TimeSpan duration, - MusicPlatform platform, string streamUrl) + public SimpleTrackInfo( + string title, + string url, + string thumbnail, + TimeSpan duration, + MusicPlatform platform, + string streamUrl) { Title = title; Url = url; @@ -20,4 +24,7 @@ public sealed class SimpleTrackInfo : ITrackInfo Platform = platform; StreamUrl = streamUrl; } -} + + public ValueTask GetStreamUrl() + => new(StreamUrl); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Impl/VoiceProxy.cs b/src/NadekoBot/Modules/Music/Common/Impl/VoiceProxy.cs index 418912d03..2b74424a9 100644 --- a/src/NadekoBot/Modules/Music/Common/Impl/VoiceProxy.cs +++ b/src/NadekoBot/Modules/Music/Common/Impl/VoiceProxy.cs @@ -12,19 +12,19 @@ public sealed class VoiceProxy : IVoiceProxy Started, Stopped } - + private const int MAX_ERROR_COUNT = 20; private const int DELAY_ON_ERROR_MILISECONDS = 200; public VoiceProxyState State => _gateway switch { - {Started: true, Stopped: false} => VoiceProxyState.Started, - {Stopped: false} => VoiceProxyState.Created, + { Started: true, Stopped: false } => VoiceProxyState.Started, + { Stopped: false } => VoiceProxyState.Created, _ => VoiceProxyState.Stopped }; - - + + private VoiceGateway _gateway; public VoiceProxy(VoiceGateway initial) @@ -35,10 +35,7 @@ public sealed class VoiceProxy : IVoiceProxy try { var gw = _gateway; - if (gw is null || gw.Stopped || !gw.Started) - { - return false; - } + if (gw is null || gw.Stopped || !gw.Started) return false; vc.SendPcmFrame(gw, data, 0, length); return true; @@ -54,10 +51,7 @@ public sealed class VoiceProxy : IVoiceProxy var errorCount = 0; do { - if (State == VoiceProxyState.Stopped) - { - break; - } + if (State == VoiceProxyState.Stopped) break; try { @@ -98,9 +92,9 @@ public sealed class VoiceProxy : IVoiceProxy public Task StopGateway() { - if(_gateway is { } gw) + if (_gateway is { } gw) return gw.StopAsync(); return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Resolvers/LocalTrackResolver.cs b/src/NadekoBot/Modules/Music/Common/Resolvers/LocalTrackResolver.cs index 62c0e0fd3..3a6ddec13 100644 --- a/src/NadekoBot/Modules/Music/Common/Resolvers/LocalTrackResolver.cs +++ b/src/NadekoBot/Modules/Music/Common/Resolvers/LocalTrackResolver.cs @@ -8,25 +8,22 @@ public sealed class LocalTrackResolver : ILocalTrackResolver { private static readonly HashSet _musicExtensions = new[] { - ".MP4", ".MP3", ".FLAC", ".OGG", ".WAV", ".WMA", ".WMV", - ".AAC", ".MKV", ".WEBM", ".M4A", ".AA", ".AAX", + ".MP4", ".MP3", ".FLAC", ".OGG", ".WAV", ".WMA", ".WMV", ".AAC", ".MKV", ".WEBM", ".M4A", ".AA", ".AAX", ".ALAC", ".AIFF", ".MOV", ".FLV", ".OGG", ".M4V" }.ToHashSet(); - + public async Task ResolveByQueryAsync(string query) { if (!File.Exists(query)) return null; var trackDuration = await Ffprobe.GetTrackDurationAsync(query); - return new SimpleTrackInfo( - Path.GetFileNameWithoutExtension(query), + return new SimpleTrackInfo(Path.GetFileNameWithoutExtension(query), $"https://google.com?q={Uri.EscapeDataString(Path.GetFileNameWithoutExtension(query))}", "https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png", trackDuration, MusicPlatform.Local, - $"\"{Path.GetFullPath(query)}\"" - ); + $"\"{Path.GetFullPath(query)}\""); } public async IAsyncEnumerable ResolveDirectoryAsync(string dirPath) @@ -43,17 +40,17 @@ public sealed class LocalTrackResolver : ILocalTrackResolver } var files = dir.EnumerateFiles() - .Where(x => - { - if (!x.Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System) - && _musicExtensions.Contains(x.Extension.ToUpperInvariant())) return true; - return false; - }); + .Where(x => + { + if (!x.Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System) + && _musicExtensions.Contains(x.Extension.ToUpperInvariant())) return true; + return false; + }); var firstFile = files.FirstOrDefault()?.FullName; if (firstFile is null) yield break; - + var firstData = await ResolveByQueryAsync(firstFile); if (firstData is not null) yield return firstData; @@ -61,9 +58,8 @@ public sealed class LocalTrackResolver : ILocalTrackResolver var fileChunks = files.Skip(1).Chunk(10); foreach (var chunk in fileChunks) { - var part = await chunk.Select(x => ResolveByQueryAsync(x.FullName)) - .WhenAll(); - + var part = await chunk.Select(x => ResolveByQueryAsync(x.FullName)).WhenAll(); + // nullable reference types being annoying foreach (var p in part) { @@ -84,7 +80,7 @@ public static class Ffprobe try { - using var p = Process.Start(new ProcessStartInfo() + using var p = Process.Start(new ProcessStartInfo { FileName = "ffprobe", Arguments = @@ -94,7 +90,7 @@ public static class Ffprobe RedirectStandardError = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8, - CreateNoWindow = true, + CreateNoWindow = true }); if (p is null) @@ -118,7 +114,7 @@ public static class Ffprobe { Log.Error(ex, "Unknown exception running ffprobe; {ErrorMessage}", ex.Message); } - + return TimeSpan.Zero; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Resolvers/RadioResolveStrategy.cs b/src/NadekoBot/Modules/Music/Common/Resolvers/RadioResolveStrategy.cs index 07bc61d81..941ddd988 100644 --- a/src/NadekoBot/Modules/Music/Common/Resolvers/RadioResolveStrategy.cs +++ b/src/NadekoBot/Modules/Music/Common/Resolvers/RadioResolveStrategy.cs @@ -10,33 +10,23 @@ public class RadioResolver : IRadioResolver private readonly Regex asxRegex = new(".*?)\"", RegexOptions.Compiled); private readonly Regex xspfRegex = new("(?.*?)", RegexOptions.Compiled); - public RadioResolver() - { - } - public async Task ResolveByQueryAsync(string query) { if (IsRadioLink(query)) query = await HandleStreamContainers(query); - return new SimpleTrackInfo( - query.TrimTo(50), + return new SimpleTrackInfo(query.TrimTo(50), query, "https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png", TimeSpan.MaxValue, MusicPlatform.Radio, - query - ); + query); } - public static bool IsRadioLink(string query) => - (query.StartsWith("http", StringComparison.InvariantCulture) || - query.StartsWith("ww", StringComparison.InvariantCulture)) - && - (query.Contains(".pls") || - query.Contains(".m3u") || - query.Contains(".asx") || - query.Contains(".xspf")); + public static bool IsRadioLink(string query) + => (query.StartsWith("http", StringComparison.InvariantCulture) + || query.StartsWith("ww", StringComparison.InvariantCulture)) + && (query.Contains(".pls") || query.Contains(".m3u") || query.Contains(".asx") || query.Contains(".xspf")); private async Task HandleStreamContainers(string query) { @@ -50,8 +40,8 @@ public class RadioResolver : IRadioResolver { return query; } + if (query.Contains(".pls")) - { //File1=http://armitunes.com:8000/ //Regex.Match(query) try @@ -65,14 +55,13 @@ public class RadioResolver : IRadioResolver Log.Warning($"Failed reading .pls:\n{file}"); return null; } - } + if (query.Contains(".m3u")) - { /* -# This is a comment - C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3 - C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3 - */ + # This is a comment + C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3 + C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3 + */ try { var m = m3uRegex.Match(file); @@ -85,9 +74,7 @@ public class RadioResolver : IRadioResolver return null; } - } if (query.Contains(".asx")) - { // try { @@ -100,15 +87,14 @@ public class RadioResolver : IRadioResolver Log.Warning($"Failed reading .asx:\n{file}"); return null; } - } + if (query.Contains(".xspf")) - { /* - - - - file:///mp3s/song_1.mp3 - */ + + + + file:///mp3s/song_1.mp3 + */ try { var m = xspfRegex.Match(file); @@ -120,8 +106,7 @@ public class RadioResolver : IRadioResolver Log.Warning($"Failed reading .xspf:\n{file}"); return null; } - } return query; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Resolvers/SoundcloudResolver.cs b/src/NadekoBot/Modules/Music/Common/Resolvers/SoundcloudResolver.cs index b0244435f..0ee1ff945 100644 --- a/src/NadekoBot/Modules/Music/Common/Resolvers/SoundcloudResolver.cs +++ b/src/NadekoBot/Modules/Music/Common/Resolvers/SoundcloudResolver.cs @@ -1,5 +1,6 @@ -using System.Runtime.CompilerServices; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Linq; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; namespace NadekoBot.Modules.Music.Resolvers; @@ -16,37 +17,29 @@ public sealed class SoundcloudResolver : ISoundcloudResolver _httpFactory = httpFactory; } - public bool IsSoundCloudLink(string url) => - System.Text.RegularExpressions.Regex.IsMatch(url, "(.*)(soundcloud.com|snd.sc)(.*)"); + public bool IsSoundCloudLink(string url) + => Regex.IsMatch(url, "(.*)(soundcloud.com|snd.sc)(.*)"); public async IAsyncEnumerable ResolvePlaylistAsync(string playlist) { playlist = Uri.EscapeDataString(playlist); - + using var http = _httpFactory.CreateClient(); var responseString = await http.GetStringAsync($"https://scapi.nadeko.bot/resolve?url={playlist}"); var scvids = JObject.Parse(responseString)["tracks"]?.ToObject(); - if (scvids is null) - { - yield break; - } + if (scvids is null) yield break; foreach (var videosChunk in scvids.Where(x => x.Streamable is true).Chunk(5)) { - var cachableTracks = videosChunk - .Select(VideoModelToCachedData) - .ToList(); + var cachableTracks = videosChunk.Select(VideoModelToCachedData).ToList(); await cachableTracks.Select(_trackCacher.CacheTrackDataAsync).WhenAll(); - foreach(var info in cachableTracks.Select(CachableDataToTrackInfo)) - { - yield return info; - } + foreach (var info in cachableTracks.Select(CachableDataToTrackInfo)) yield return info; } } private ICachableTrackData VideoModelToCachedData(SoundCloudVideo svideo) - => new CachableTrackData() + => new CachableTrackData { Title = svideo.FullName, Url = svideo.TrackLink, @@ -55,16 +48,14 @@ public sealed class SoundcloudResolver : ISoundcloudResolver Id = svideo.Id.ToString(), Platform = MusicPlatform.SoundCloud }; - + private ITrackInfo CachableDataToTrackInfo(ICachableTrackData trackData) - => new SimpleTrackInfo( - trackData.Title, + => new SimpleTrackInfo(trackData.Title, trackData.Url, trackData.Thumbnail, trackData.Duration, trackData.Platform, - GetStreamUrl(trackData.Id) - ); + GetStreamUrl(trackData.Id)); [MethodImpl(MethodImplOptions.AggressiveInlining)] private string GetStreamUrl(string trackId) @@ -75,7 +66,7 @@ public sealed class SoundcloudResolver : ISoundcloudResolver var cached = await _trackCacher.GetCachedDataByQueryAsync(query, MusicPlatform.SoundCloud); if (cached is not null) return CachableDataToTrackInfo(cached); - + var svideo = !IsSoundCloudLink(query) ? await _sc.GetVideoByQueryAsync(query) : await _sc.ResolveVideoAsync(query); @@ -85,7 +76,7 @@ public sealed class SoundcloudResolver : ISoundcloudResolver var cachableData = VideoModelToCachedData(svideo); await _trackCacher.CacheTrackDataByQueryAsync(query, cachableData); - + return CachableDataToTrackInfo(cachableData); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Resolvers/TrackResolveProvider.cs b/src/NadekoBot/Modules/Music/Common/Resolvers/TrackResolveProvider.cs index 931a0019b..d45c87678 100644 --- a/src/NadekoBot/Modules/Music/Common/Resolvers/TrackResolveProvider.cs +++ b/src/NadekoBot/Modules/Music/Common/Resolvers/TrackResolveProvider.cs @@ -7,8 +7,11 @@ public sealed class TrackResolveProvider : ITrackResolveProvider private readonly ISoundcloudResolver _soundcloudResolver; private readonly IRadioResolver _radioResolver; - public TrackResolveProvider(IYoutubeResolver ytResolver, ILocalTrackResolver localResolver, - ISoundcloudResolver soundcloudResolver, IRadioResolver radioResolver) + public TrackResolveProvider( + IYoutubeResolver ytResolver, + ILocalTrackResolver localResolver, + ISoundcloudResolver soundcloudResolver, + IRadioResolver radioResolver) { _ytResolver = ytResolver; _localResolver = localResolver; @@ -45,13 +48,9 @@ public sealed class TrackResolveProvider : ITrackResolveProvider return Task.FromResult(null); } } - - public static bool IsRadioLink(string query) => - (query.StartsWith("http", StringComparison.InvariantCulture) || - query.StartsWith("ww", StringComparison.InvariantCulture)) - && - (query.Contains(".pls") || - query.Contains(".m3u") || - query.Contains(".asx") || - query.Contains(".xspf")); -} + + public static bool IsRadioLink(string query) + => (query.StartsWith("http", StringComparison.InvariantCulture) + || query.StartsWith("ww", StringComparison.InvariantCulture)) + && (query.Contains(".pls") || query.Contains(".m3u") || query.Contains(".asx") || query.Contains(".xspf")); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Common/Resolvers/YtdlYoutubeResolver.cs b/src/NadekoBot/Modules/Music/Common/Resolvers/YtdlYoutubeResolver.cs index 466429c5c..dd18195c8 100644 --- a/src/NadekoBot/Modules/Music/Common/Resolvers/YtdlYoutubeResolver.cs +++ b/src/NadekoBot/Modules/Music/Common/Resolvers/YtdlYoutubeResolver.cs @@ -5,17 +5,22 @@ namespace NadekoBot.Modules.Music; public sealed class YtdlYoutubeResolver : IYoutubeResolver { - private static readonly string[] durationFormats = new[] - {"ss", "m\\:ss", "mm\\:ss", "h\\:mm\\:ss", "hh\\:mm\\:ss", "hhh\\:mm\\:ss"}; + private static readonly string[] durationFormats = + { + "ss", "m\\:ss", "mm\\:ss", "h\\:mm\\:ss", "hh\\:mm\\:ss", "hhh\\:mm\\:ss" + }; - public Regex YtVideoIdRegex { get; } - = new( - @"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)(?[a-zA-Z0-9_-]{6,11})", - RegexOptions.Compiled - ); + private static readonly Regex expiryRegex = new(@"(?:[\?\&]expire\=(?\d+))"); + + + private static readonly Regex _simplePlaylistRegex = new(@"&list=(?[\w\-]{12,})", RegexOptions.Compiled); + + public Regex YtVideoIdRegex { get; } = + new(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)(?[a-zA-Z0-9_-]{6,11})", + RegexOptions.Compiled); private readonly ITrackCacher _trackCacher; - + private readonly YtdlOperation _ytdlPlaylistOperation; private readonly YtdlOperation _ytdlIdOperation; private readonly YtdlOperation _ytdlSearchOperation; @@ -27,72 +32,51 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver _trackCacher = trackCacher; _google = google; - _ytdlPlaylistOperation = - new("-4 " + - "--geo-bypass " + - "--encoding UTF8 " + - "-f bestaudio " + - "-e " + - "--get-url " + - "--get-id " + - "--get-thumbnail " + - "--get-duration " + - "--no-check-certificate " + - "-i " + - "--yes-playlist " + - "-- \"{0}\""); + _ytdlPlaylistOperation = new("-4 " + + "--geo-bypass " + + "--encoding UTF8 " + + "-f bestaudio " + + "-e " + + "--get-url " + + "--get-id " + + "--get-thumbnail " + + "--get-duration " + + "--no-check-certificate " + + "-i " + + "--yes-playlist " + + "-- \"{0}\""); - _ytdlIdOperation = - new("-4 " + - "--geo-bypass " + - "--encoding UTF8 " + - "-f bestaudio " + - "-e " + - "--get-url " + - "--get-id " + - "--get-thumbnail " + - "--get-duration " + - "--no-check-certificate " + - "-- \"{0}\""); - - _ytdlSearchOperation = - new("-4 " + - "--geo-bypass " + - "--encoding UTF8 " + - "-f bestaudio " + - "-e " + - "--get-url " + - "--get-id " + - "--get-thumbnail " + - "--get-duration " + - "--no-check-certificate " + - "--default-search " + - "\"ytsearch:\" -- \"{0}\""); + _ytdlIdOperation = new("-4 " + + "--geo-bypass " + + "--encoding UTF8 " + + "-f bestaudio " + + "-e " + + "--get-url " + + "--get-id " + + "--get-thumbnail " + + "--get-duration " + + "--no-check-certificate " + + "-- \"{0}\""); + + _ytdlSearchOperation = new("-4 " + + "--geo-bypass " + + "--encoding UTF8 " + + "-f bestaudio " + + "-e " + + "--get-url " + + "--get-id " + + "--get-thumbnail " + + "--get-duration " + + "--no-check-certificate " + + "--default-search " + + "\"ytsearch:\" -- \"{0}\""); } - private readonly struct YtTrackData - { - public readonly string Title; - public readonly string Id; - public readonly string Thumbnail; - public readonly string? StreamUrl; - public readonly TimeSpan Duration; - - public YtTrackData(string title, string id, string thumbnail, string? streamUrl, TimeSpan duration) - { - Title = title.Trim(); - Id = id.Trim(); - Thumbnail = thumbnail; - StreamUrl = streamUrl; - Duration = duration; - } - } - private YtTrackData ResolveYtdlData(string ytdlOutputString) { if (string.IsNullOrWhiteSpace(ytdlOutputString)) return default; - + var dataArray = ytdlOutputString.Trim().Split('\n'); if (dataArray.Length < 5) @@ -102,26 +86,15 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver } if (!TimeSpan.TryParseExact(dataArray[4], durationFormats, CultureInfo.InvariantCulture, out var time)) - { time = TimeSpan.Zero; - } - var thumbnail = Uri.IsWellFormedUriString(dataArray[3], UriKind.Absolute) - ? dataArray[3].Trim() - : string.Empty; + var thumbnail = Uri.IsWellFormedUriString(dataArray[3], UriKind.Absolute) ? dataArray[3].Trim() : string.Empty; - return new( - dataArray[0], - dataArray[1], - thumbnail, - dataArray[2], - time - ); + return new(dataArray[0], dataArray[1], thumbnail, dataArray[2], time); } private ITrackInfo DataToInfo(in YtTrackData trackData) - => new RemoteTrackInfo( - trackData.Title, + => new RemoteTrackInfo(trackData.Title, $"https://youtube.com/watch?v={trackData.Id}", trackData.Thumbnail, trackData.Duration, @@ -129,13 +102,10 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver CreateCacherFactory(trackData.Id)); private Func> CreateCacherFactory(string id) - => () => _trackCacher.GetOrCreateStreamLink( - id, + => () => _trackCacher.GetOrCreateStreamLink(id, MusicPlatform.Youtube, - async () => await ExtractNewStreamUrlAsync(id) - ); + async () => await ExtractNewStreamUrlAsync(id)); - private static readonly Regex expiryRegex = new(@"(?:[\?\&]expire\=(?\d+))"); private static TimeSpan GetExpiry(string streamUrl) { var match = expiryRegex.Match(streamUrl); @@ -157,54 +127,41 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver var trackInfo = ResolveYtdlData(data); if (string.IsNullOrWhiteSpace(trackInfo.StreamUrl)) return default; - + return (trackInfo.StreamUrl!, GetExpiry(trackInfo.StreamUrl!)); } public async Task ResolveByIdAsync(string id) { id = id.Trim(); - + var cachedData = await _trackCacher.GetCachedDataByIdAsync(id, MusicPlatform.Youtube); if (cachedData is null) { Log.Information("Resolving youtube track by Id: {YoutubeId}", id); var data = await _ytdlIdOperation.GetDataAsync(id); - + var trackInfo = ResolveYtdlData(data); if (string.IsNullOrWhiteSpace(trackInfo.Title)) return default; - + var toReturn = DataToInfo(in trackInfo); - await Task.WhenAll( - _trackCacher.CacheTrackDataAsync(toReturn.ToCachedData(id)), - CacheStreamUrlAsync(trackInfo) - ); - + await Task.WhenAll(_trackCacher.CacheTrackDataAsync(toReturn.ToCachedData(id)), + CacheStreamUrlAsync(trackInfo)); + return toReturn; } - return DataToInfo(new( - cachedData.Title, - cachedData.Id, - cachedData.Thumbnail, - null, - cachedData.Duration - )); + return DataToInfo(new(cachedData.Title, cachedData.Id, cachedData.Thumbnail, null, cachedData.Duration)); } private Task CacheStreamUrlAsync(YtTrackData trackInfo) - => _trackCacher.CacheStreamUrlAsync( - trackInfo.Id, + => _trackCacher.CacheStreamUrlAsync(trackInfo.Id, MusicPlatform.Youtube, trackInfo.StreamUrl!, - GetExpiry(trackInfo.StreamUrl!) - ); - - - private static readonly Regex _simplePlaylistRegex = new(@"&list=(?[\w\-]{12,})", RegexOptions.Compiled); + GetExpiry(trackInfo.StreamUrl!)); public async IAsyncEnumerable ResolveTracksByPlaylistIdAsync(string playlistId) { @@ -241,11 +198,9 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver continue; var info = DataToInfo(in trackData); - await Task.WhenAll( - _trackCacher.CacheTrackDataAsync(info.ToCachedData(trackData.Id)), - CacheStreamUrlAsync(trackData) - ); - + await Task.WhenAll(_trackCacher.CacheTrackDataAsync(info.ToCachedData(trackData.Id)), + CacheStreamUrlAsync(trackData)); + trackIds.Add(trackData.Id); yield return info; } @@ -306,7 +261,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver public Task ResolveByQueryAsync(string query) => ResolveByQueryAsync(query, true); - + public async Task ResolveByQueryAsync(string query, bool tryResolving) { if (tryResolving) @@ -315,29 +270,44 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver if (match.Success) return await ResolveByIdAsync(match.Groups["id"].Value); } - + Log.Information("Resolving youtube song by search term: {YoutubeQuery}", query); - + var cachedData = await _trackCacher.GetCachedDataByQueryAsync(query, MusicPlatform.Youtube); if (cachedData is null) { var stringData = await _ytdlSearchOperation.GetDataAsync(query); var trackData = ResolveYtdlData(stringData); - + var trackInfo = DataToInfo(trackData); - await Task.WhenAll( - _trackCacher.CacheTrackDataByQueryAsync(query, trackInfo.ToCachedData(trackData.Id)), - CacheStreamUrlAsync(trackData) - ); + await Task.WhenAll(_trackCacher.CacheTrackDataByQueryAsync(query, trackInfo.ToCachedData(trackData.Id)), + CacheStreamUrlAsync(trackData)); return trackInfo; } - - return DataToInfo(new( - cachedData.Title, - cachedData.Id, - cachedData.Thumbnail, - null, - cachedData.Duration - )); + + return DataToInfo(new(cachedData.Title, cachedData.Id, cachedData.Thumbnail, null, cachedData.Duration)); } -} + + private readonly struct YtTrackData + { + public readonly string Title; + public readonly string Id; + public readonly string Thumbnail; + public readonly string? StreamUrl; + public readonly TimeSpan Duration; + + public YtTrackData( + string title, + string id, + string thumbnail, + string? streamUrl, + TimeSpan duration) + { + Title = title.Trim(); + Id = id.Trim(); + Thumbnail = thumbnail; + StreamUrl = streamUrl; + Duration = duration; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index 299d5a711..427033001 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -1,14 +1,27 @@ #nullable disable -using NadekoBot.Services.Database.Models; using NadekoBot.Modules.Administration.Services; using NadekoBot.Modules.Music.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Music; [NoPublicBot] public sealed partial class Music : NadekoModule { + public enum All { All = -1 } + + public enum InputRepeatType + { + N = 0, No = 0, None = 0, + T = 1, Track = 1, S = 1, Song = 1, + Q = 2, Queue = 2, Playlist = 2, Pl = 2 + } + public const string MusicIconUrl = "http://i.imgur.com/nhKS3PT.png"; + + private const int LQ_ITEMS_PER_PAGE = 9; + + private static readonly SemaphoreSlim voiceChannelLock = new(1, 1); private readonly ILogCommandService _logService; public Music(ILogCommandService _logService) @@ -16,9 +29,9 @@ public sealed partial class Music : NadekoModule private async Task ValidateAsync() { - var user = (IGuildUser) ctx.User; + var user = (IGuildUser)ctx.User; var userVoiceChannelId = user.VoiceChannel?.Id; - + if (userVoiceChannelId is null) { await ReplyErrorLocalizedAsync(strs.must_be_in_voice); @@ -35,7 +48,6 @@ public sealed partial class Music : NadekoModule return true; } - private static readonly SemaphoreSlim voiceChannelLock = new(1, 1); private async Task EnsureBotInVoiceChannelAsync(ulong voiceChannelId, IGuildUser botUser = null) { botUser ??= await ctx.Guild.GetCurrentUserAsync(); @@ -50,12 +62,12 @@ public sealed partial class Music : NadekoModule voiceChannelLock.Release(); } } - + private async Task QueuePreconditionInternalAsync() { - var user = (IGuildUser) ctx.User; + var user = (IGuildUser)ctx.User; var voiceChannelId = user.VoiceChannel?.Id; - + if (voiceChannelId is null) { await ReplyErrorLocalizedAsync(strs.must_be_in_voice); @@ -63,10 +75,10 @@ public sealed partial class Music : NadekoModule } _ = ctx.Channel.TriggerTypingAsync(); - + var botUser = await ctx.Guild.GetCurrentUserAsync(); await EnsureBotInVoiceChannelAsync(voiceChannelId!.Value, botUser); - + if (botUser.VoiceChannel?.Id != voiceChannelId) { await ReplyErrorLocalizedAsync(strs.not_with_bot_in_voice); @@ -81,18 +93,15 @@ public sealed partial class Music : NadekoModule var succ = await QueuePreconditionInternalAsync(); if (!succ) return; - - var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel) ctx.Channel); + + var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel)ctx.Channel); if (mp is null) { await ReplyErrorLocalizedAsync(strs.no_player); return; } - - var (trackInfo, index) = await mp.TryEnqueueTrackAsync(query, - ctx.User.ToString(), - asNext, - forcePlatform); + + var (trackInfo, index) = await mp.TryEnqueueTrackAsync(query, ctx.User.ToString(), asNext, forcePlatform); if (trackInfo is null) { await ReplyErrorLocalizedAsync(strs.song_not_found); @@ -102,10 +111,10 @@ public sealed partial class Music : NadekoModule try { var embed = _eb.Create() - .WithOkColor() - .WithAuthor(GetText(strs.queued_song) + " #" + (index + 1), MusicIconUrl) - .WithDescription($"{trackInfo.PrettyName()}\n{GetText(strs.queue)} ") - .WithFooter(trackInfo.Platform.ToString()); + .WithOkColor() + .WithAuthor(GetText(strs.queued_song) + " #" + (index + 1), MusicIconUrl) + .WithDescription($"{trackInfo.PrettyName()}\n{GetText(strs.queue)} ") + .WithFooter(trackInfo.Platform.ToString()); if (!string.IsNullOrWhiteSpace(trackInfo.Thumbnail)) embed.WithThumbnailUrl(trackInfo.Thumbnail); @@ -128,12 +137,12 @@ public sealed partial class Music : NadekoModule { if (--index < 0) return; - + var succ = await QueuePreconditionInternalAsync(); if (!succ) return; - - var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel) ctx.Channel); + + var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel)ctx.Channel); if (mp is null) { await ReplyErrorLocalizedAsync(strs.no_player); @@ -142,13 +151,14 @@ public sealed partial class Music : NadekoModule mp.MoveTo(index); } - + // join vc - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Join() { - var user = (IGuildUser) ctx.User; + var user = (IGuildUser)ctx.User; var voiceChannelId = user.VoiceChannel?.Id; @@ -162,7 +172,8 @@ public sealed partial class Music : NadekoModule } // leave vc (destroy) - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Destroy() { @@ -172,39 +183,45 @@ public sealed partial class Music : NadekoModule await _service.LeaveVoiceChannelAsync(ctx.Guild.Id); } - + // play - no args = next - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(2)] public Task Play() => Next(); - + // play - index = skip to that index - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] public Task Play(int index) => MoveToIndex(index); - + // play - query = q(query) - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] public Task Play([Leftover] string query) - => QueueByQuery(query); - - [NadekoCommand, Aliases] + => QueueByQuery(query); + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public Task Queue([Leftover] string query) => QueueByQuery(query); - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public Task QueueNext([Leftover] string query) - => QueueByQuery(query, asNext: true); + => QueueByQuery(query, true); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Volume(int vol) { @@ -213,7 +230,7 @@ public sealed partial class Music : NadekoModule await ReplyErrorLocalizedAsync(strs.volume_input_invalid); return; } - + var valid = await ValidateAsync(); if (!valid) return; @@ -222,7 +239,8 @@ public sealed partial class Music : NadekoModule await ReplyConfirmLocalizedAsync(strs.volume_set(vol)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Next() { @@ -231,17 +249,12 @@ public sealed partial class Music : NadekoModule return; var success = await _service.PlayAsync(ctx.Guild.Id, ((IGuildUser)ctx.User).VoiceChannel.Id); - if (!success) - { - await ReplyErrorLocalizedAsync(strs.no_player); - return; - } + if (!success) await ReplyErrorLocalizedAsync(strs.no_player); } - private const int LQ_ITEMS_PER_PAGE = 9; - // list queue, relevant page - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task ListQueue() { @@ -251,12 +264,13 @@ public sealed partial class Music : NadekoModule await ReplyErrorLocalizedAsync(strs.no_player); return; } - + await ListQueue((mp.CurrentIndex / LQ_ITEMS_PER_PAGE) + 1); } - + // list queue, specify page - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task ListQueue(int page) { @@ -269,15 +283,12 @@ public sealed partial class Music : NadekoModule await ReplyErrorLocalizedAsync(strs.no_player); return; } - + IEmbedBuilder printAction(int curPage) { var desc = string.Empty; var current = mp.GetCurrentTrack(out var currentIndex); - if (current is not null) - { - desc = $"`🔊` {current.PrettyFullName()}\n\n" + desc; - } + if (current is not null) desc = $"`🔊` {current.PrettyFullName()}\n\n" + desc; var repeatType = mp.Repeat; var add = string.Empty; @@ -301,42 +312,37 @@ public sealed partial class Music : NadekoModule } - desc += tracks - .Skip(LQ_ITEMS_PER_PAGE * curPage) - .Take(LQ_ITEMS_PER_PAGE) - .Select((v, index) => - { - index += LQ_ITEMS_PER_PAGE * curPage; - if (index == currentIndex) - return $"**⇒**`{index + 1}.` {v.PrettyFullName()}"; - - return $"`{index + 1}.` {v.PrettyFullName()}"; - }) - .Join('\n'); - + desc += tracks.Skip(LQ_ITEMS_PER_PAGE * curPage) + .Take(LQ_ITEMS_PER_PAGE) + .Select((v, index) => + { + index += LQ_ITEMS_PER_PAGE * curPage; + if (index == currentIndex) + return $"**⇒**`{index + 1}.` {v.PrettyFullName()}"; + + return $"`{index + 1}.` {v.PrettyFullName()}"; + }) + .Join('\n'); + if (!string.IsNullOrWhiteSpace(add)) desc = add + "\n" + desc; var embed = _eb.Create() - .WithAuthor(GetText(strs.player_queue(curPage + 1, (tracks.Count / LQ_ITEMS_PER_PAGE) + 1)), - MusicIconUrl) - .WithDescription(desc) - .WithFooter($" {mp.PrettyVolume()} | 🎶 {tracks.Count} | ⌛ {mp.PrettyTotalTime()} ") - .WithOkColor(); + .WithAuthor(GetText(strs.player_queue(curPage + 1, (tracks.Count / LQ_ITEMS_PER_PAGE) + 1)), + MusicIconUrl) + .WithDescription(desc) + .WithFooter($" {mp.PrettyVolume()} | 🎶 {tracks.Count} | ⌛ {mp.PrettyTotalTime()} ") + .WithOkColor(); return embed; } - await ctx.SendPaginatedConfirmAsync( - page, - printAction, - tracks.Count, - LQ_ITEMS_PER_PAGE, - false); + await ctx.SendPaginatedConfirmAsync(page, printAction, tracks.Count, LQ_ITEMS_PER_PAGE, false); } // search - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task QueueSearch([Leftover] string query) { @@ -350,19 +356,14 @@ public sealed partial class Music : NadekoModule return; } - var resultsString = videos - .Select((x, i) => $"`{i + 1}.`\n\t{Format.Bold(x.Title)}\n\t{x.Url}") - .Join('\n'); - + var resultsString = videos.Select((x, i) => $"`{i + 1}.`\n\t{Format.Bold(x.Title)}\n\t{x.Url}").Join('\n'); + var msg = await SendConfirmAsync(resultsString); try { var input = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id); - if (input is null - || !int.TryParse(input, out var index) - || (index -= 1) < 0 - || index >= videos.Count) + if (input is null || !int.TryParse(input, out var index) || (index -= 1) < 0 || index >= videos.Count) { _logService.AddDeleteIgnore(msg.Id); try @@ -375,6 +376,7 @@ public sealed partial class Music : NadekoModule return; } + query = videos[index].Url; await Play(query); @@ -392,7 +394,8 @@ public sealed partial class Music : NadekoModule } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] public async Task TrackRemove(int index) @@ -402,7 +405,7 @@ public sealed partial class Music : NadekoModule await ReplyErrorLocalizedAsync(strs.removed_song_error); return; } - + var valid = await ValidateAsync(); if (!valid) return; @@ -412,24 +415,24 @@ public sealed partial class Music : NadekoModule await ReplyErrorLocalizedAsync(strs.no_player); return; } - + if (!mp.TryRemoveTrackAt(index - 1, out var song)) { await ReplyErrorLocalizedAsync(strs.removed_song_error); return; } - + var embed = _eb.Create() - .WithAuthor(GetText(strs.removed_song) + " #" + index, MusicIconUrl) - .WithDescription(song.PrettyName()) - .WithFooter(song.PrettyInfo()) - .WithErrorColor(); + .WithAuthor(GetText(strs.removed_song) + " #" + index, MusicIconUrl) + .WithDescription(song.PrettyName()) + .WithFooter(song.PrettyInfo()) + .WithErrorColor(); await _service.SendToOutputAsync(ctx.Guild.Id, embed); } - public enum All { All = -1 } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] public async Task TrackRemove(All _ = All.All) @@ -443,12 +446,13 @@ public sealed partial class Music : NadekoModule await ReplyErrorLocalizedAsync(strs.no_player); return; } - + mp.Clear(); await ReplyConfirmLocalizedAsync(strs.queue_cleared); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Stop() { @@ -461,33 +465,28 @@ public sealed partial class Music : NadekoModule await ReplyErrorLocalizedAsync(strs.no_player); return; } - + mp.Stop(); } - public enum InputRepeatType - { - N = 0, No = 0, None = 0, - T = 1, Track = 1, S = 1, Song = 1, - Q = 2, Queue = 2, Playlist = 2, Pl = 2, - } + private PlayerRepeatType InputToDbType(InputRepeatType type) + => type switch + { + InputRepeatType.None => PlayerRepeatType.None, + InputRepeatType.Queue => PlayerRepeatType.Queue, + InputRepeatType.Track => PlayerRepeatType.Track, + _ => PlayerRepeatType.Queue + }; - private PlayerRepeatType InputToDbType(InputRepeatType type) => type switch - { - InputRepeatType.None => PlayerRepeatType.None, - InputRepeatType.Queue => PlayerRepeatType.Queue, - InputRepeatType.Track => PlayerRepeatType.Track, - _ => PlayerRepeatType.Queue - }; - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task QueueRepeat(InputRepeatType type = InputRepeatType.Queue) { var valid = await ValidateAsync(); if (!valid) return; - + await _service.SetRepeatAsync(ctx.Guild.Id, InputToDbType(type)); if (type == InputRepeatType.None) @@ -497,8 +496,9 @@ public sealed partial class Music : NadekoModule else await ReplyConfirmLocalizedAsync(strs.repeating_track); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Pause() { @@ -514,19 +514,22 @@ public sealed partial class Music : NadekoModule mp.TogglePause(); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public Task Radio(string radioLink) => QueueByQuery(radioLink, false, MusicPlatform.Radio); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] public Task Local([Leftover] string path) => QueueByQuery(path, false, MusicPlatform.Local); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] public async Task LocalPlaylist([Leftover] string dirPath) @@ -534,9 +537,9 @@ public sealed partial class Music : NadekoModule if (string.IsNullOrWhiteSpace(dirPath)) return; - var user = (IGuildUser) ctx.User; + var user = (IGuildUser)ctx.User; var voiceChannelId = user.VoiceChannel?.Id; - + if (voiceChannelId is null) { await ReplyErrorLocalizedAsync(strs.must_be_in_voice); @@ -544,29 +547,30 @@ public sealed partial class Music : NadekoModule } _ = ctx.Channel.TriggerTypingAsync(); - + var botUser = await ctx.Guild.GetCurrentUserAsync(); await EnsureBotInVoiceChannelAsync(voiceChannelId!.Value, botUser); - + if (botUser.VoiceChannel?.Id != voiceChannelId) { await ReplyErrorLocalizedAsync(strs.not_with_bot_in_voice); return; } - - var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel) ctx.Channel); + + var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel)ctx.Channel); if (mp is null) { await ReplyErrorLocalizedAsync(strs.no_player); return; } - + await _service.EnqueueDirectoryAsync(mp, dirPath, ctx.User.ToString()); - + await ReplyConfirmLocalizedAsync(strs.dir_queue_complete); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task MoveSong(int from, int to) { @@ -579,8 +583,8 @@ public sealed partial class Music : NadekoModule var valid = await ValidateAsync(); if (!valid) return; - - var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel) ctx.Channel); + + var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel)ctx.Channel); if (mp is null) { await ReplyErrorLocalizedAsync(strs.no_player); @@ -593,13 +597,13 @@ public sealed partial class Music : NadekoModule await ReplyErrorLocalizedAsync(strs.invalid_input); return; } - + var embed = _eb.Create() - .WithTitle(track.Title.TrimTo(65)) - .WithAuthor(GetText(strs.song_moved), MusicIconUrl) - .AddField(GetText(strs.from_position), $"#{from + 1}", true) - .AddField(GetText(strs.to_position), $"#{to + 1}", true) - .WithOkColor(); + .WithTitle(track.Title.TrimTo(65)) + .WithAuthor(GetText(strs.song_moved), MusicIconUrl) + .AddField(GetText(strs.from_position), $"#{from + 1}", true) + .AddField(GetText(strs.to_position), $"#{to + 1}", true) + .WithOkColor(); if (Uri.IsWellFormedUriString(track.Url, UriKind.Absolute)) embed.WithUrl(track.Url); @@ -607,12 +611,14 @@ public sealed partial class Music : NadekoModule await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public Task SoundCloudQueue([Leftover] string query) => QueueByQuery(query, false, MusicPlatform.SoundCloud); - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task SoundCloudPl([Leftover] string playlist) { @@ -623,13 +629,13 @@ public sealed partial class Music : NadekoModule if (!succ) return; - var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel) ctx.Channel); + var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel)ctx.Channel); if (mp is null) { await ReplyErrorLocalizedAsync(strs.no_player); return; } - + _ = ctx.Channel.TriggerTypingAsync(); await _service.EnqueueSoundcloudPlaylistAsync(mp, playlist, ctx.User.ToString()); @@ -637,7 +643,8 @@ public sealed partial class Music : NadekoModule await ctx.OkAsync(); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Playlist([Leftover] string playlistQuery) { @@ -648,7 +655,7 @@ public sealed partial class Music : NadekoModule if (!succ) return; - var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel) ctx.Channel); + var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel)ctx.Channel); if (mp is null) { await ReplyErrorLocalizedAsync(strs.no_player); @@ -664,14 +671,16 @@ public sealed partial class Music : NadekoModule await ReplyErrorLocalizedAsync(strs.no_search_results); return; } + await ctx.OkAsync(); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task NowPlaying() { - var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel) ctx.Channel); + var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel)ctx.Channel); if (mp is null) { await ReplyErrorLocalizedAsync(strs.no_player); @@ -682,35 +691,39 @@ public sealed partial class Music : NadekoModule if (currentTrack is null) return; - var embed = _eb.Create().WithOkColor() - .WithAuthor(GetText(strs.now_playing), MusicIconUrl) - .WithDescription(currentTrack.PrettyName()) - .WithThumbnailUrl(currentTrack.Thumbnail) - .WithFooter($"{mp.PrettyVolume()} | {mp.PrettyTotalTime()} | {currentTrack.Platform} | {currentTrack.Queuer}"); + var embed = _eb.Create() + .WithOkColor() + .WithAuthor(GetText(strs.now_playing), MusicIconUrl) + .WithDescription(currentTrack.PrettyName()) + .WithThumbnailUrl(currentTrack.Thumbnail) + .WithFooter( + $"{mp.PrettyVolume()} | {mp.PrettyTotalTime()} | {currentTrack.Platform} | {currentTrack.Queuer}"); await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task PlaylistShuffle() { var valid = await ValidateAsync(); if (!valid) return; - - var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel) ctx.Channel); + + var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel)ctx.Channel); if (mp is null) { await ReplyErrorLocalizedAsync(strs.no_player); return; } - + mp.ShuffleQueue(); await ReplyConfirmLocalizedAsync(strs.queue_shuffled); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task SetMusicChannel() @@ -719,8 +732,9 @@ public sealed partial class Music : NadekoModule await ReplyConfirmLocalizedAsync(strs.set_music_channel); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task UnsetMusicChannel() @@ -730,19 +744,21 @@ public sealed partial class Music : NadekoModule await ReplyConfirmLocalizedAsync(strs.unset_music_channel); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task AutoDisconnect() { var newState = await _service.ToggleAutoDisconnectAsync(ctx.Guild.Id); - if(newState) + if (newState) await ReplyConfirmLocalizedAsync(strs.autodc_enable); else await ReplyConfirmLocalizedAsync(strs.autodc_disable); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task MusicQuality() @@ -750,8 +766,9 @@ public sealed partial class Music : NadekoModule var quality = await _service.GetMusicQualityAsync(ctx.Guild.Id); await ReplyConfirmLocalizedAsync(strs.current_music_quality(Format.Bold(quality.ToString()))); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task MusicQuality(QualityPreset preset) @@ -759,4 +776,4 @@ public sealed partial class Music : NadekoModule await _service.SetMusicQualityAsync(ctx.Guild.Id, preset); await ReplyConfirmLocalizedAsync(strs.music_quality_set(Format.Bold(preset.ToString()))); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/PlaylistCommands.cs b/src/NadekoBot/Modules/Music/PlaylistCommands.cs index 0727ccef4..ce4c85508 100644 --- a/src/NadekoBot/Modules/Music/PlaylistCommands.cs +++ b/src/NadekoBot/Modules/Music/PlaylistCommands.cs @@ -1,7 +1,7 @@ #nullable disable -using NadekoBot.Services.Database.Models; using NadekoBot.Db; using NadekoBot.Modules.Music.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Music; @@ -10,6 +10,7 @@ public sealed partial class Music [Group] public sealed class PlaylistCommands : NadekoModule { + private static readonly SemaphoreSlim _playlistLock = new(1, 1); private readonly DbService _db; private readonly IBotCredentials _creds; @@ -18,7 +19,7 @@ public sealed partial class Music _db = db; _creds = creds; } - + private async Task EnsureBotInVoiceChannelAsync(ulong voiceChannelId, IGuildUser botUser = null) { botUser ??= await ctx.Guild.GetCurrentUserAsync(); @@ -34,7 +35,8 @@ public sealed partial class Music } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Playlists([Leftover] int num = 1) { @@ -48,17 +50,17 @@ public sealed partial class Music playlists = uow.MusicPlaylists.GetPlaylistsOnPage(num); } - var embed = _eb - .Create(ctx) - .WithAuthor(GetText(strs.playlists_page(num)), MusicIconUrl) - .WithDescription(string.Join("\n", playlists.Select(r => - GetText(strs.playlists(r.Id, r.Name, r.Author, r.Songs.Count))))) - .WithOkColor(); - + var embed = _eb.Create(ctx) + .WithAuthor(GetText(strs.playlists_page(num)), MusicIconUrl) + .WithDescription(string.Join("\n", + playlists.Select(r => GetText(strs.playlists(r.Id, r.Name, r.Author, r.Songs.Count))))) + .WithOkColor(); + await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task DeletePlaylist([Leftover] int id) { @@ -69,14 +71,12 @@ public sealed partial class Music var pl = uow.MusicPlaylists.FirstOrDefault(x => x.Id == id); if (pl != null) - { if (_creds.IsOwner(ctx.User) || pl.AuthorId == ctx.User.Id) { uow.MusicPlaylists.Remove(pl); await uow.SaveChangesAsync(); success = true; } - } } catch (Exception ex) { @@ -89,7 +89,8 @@ public sealed partial class Music await ReplyConfirmLocalizedAsync(strs.playlist_deleted); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task PlaylistShow(int id, int page = 1) { @@ -102,21 +103,22 @@ public sealed partial class Music mpl = uow.MusicPlaylists.GetWithSongs(id); } - await ctx.SendPaginatedConfirmAsync(page, cur => - { - var i = 0; - var str = string.Join("\n", mpl.Songs - .Skip(cur * 20) - .Take(20) - .Select(x => $"`{++i}.` [{x.Title.TrimTo(45)}]({x.Query}) `{x.Provider}`")); - return _eb.Create() - .WithTitle($"\"{mpl.Name}\" by {mpl.Author}") - .WithOkColor() - .WithDescription(str); - }, mpl.Songs.Count, 20); + await ctx.SendPaginatedConfirmAsync(page, + cur => + { + var i = 0; + var str = string.Join("\n", + mpl.Songs.Skip(cur * 20) + .Take(20) + .Select(x => $"`{++i}.` [{x.Title.TrimTo(45)}]({x.Query}) `{x.Provider}`")); + return _eb.Create().WithTitle($"\"{mpl.Name}\" by {mpl.Author}").WithOkColor().WithDescription(str); + }, + mpl.Songs.Count, + 20); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Save([Leftover] string name) { @@ -127,49 +129,45 @@ public sealed partial class Music } var songs = mp.GetQueuedTracks() - .Select(s => new PlaylistSong() - { - Provider = s.Platform.ToString(), - ProviderType = (MusicType)s.Platform, - Title = s.Title, - Query = s.Platform == MusicPlatform.Local ? s.GetStreamUrl().Result!.Trim('"') : s.Url, - }).ToList(); + .Select(s => new PlaylistSong + { + Provider = s.Platform.ToString(), + ProviderType = (MusicType)s.Platform, + Title = s.Title, + Query = s.Platform == MusicPlatform.Local ? s.GetStreamUrl().Result!.Trim('"') : s.Url + }) + .ToList(); MusicPlaylist playlist; await using (var uow = _db.GetDbContext()) { playlist = new() { - Name = name, - Author = ctx.User.Username, - AuthorId = ctx.User.Id, - Songs = songs.ToList(), + Name = name, Author = ctx.User.Username, AuthorId = ctx.User.Id, Songs = songs.ToList() }; uow.MusicPlaylists.Add(playlist); await uow.SaveChangesAsync(); } await ctx.Channel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.playlist_saved)) - .AddField(GetText(strs.name), name) - .AddField(GetText(strs.id), playlist.Id.ToString())); + .WithOkColor() + .WithTitle(GetText(strs.playlist_saved)) + .AddField(GetText(strs.name), name) + .AddField(GetText(strs.id), playlist.Id.ToString())); } - - private static readonly SemaphoreSlim _playlistLock = new(1, 1); - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Load([Leftover] int id) { // expensive action, 1 at a time await _playlistLock.WaitAsync(); try - { - var user = (IGuildUser) ctx.User; + { + var user = (IGuildUser)ctx.User; var voiceChannelId = user.VoiceChannel?.Id; - + if (voiceChannelId is null) { await ReplyErrorLocalizedAsync(strs.must_be_in_voice); @@ -177,23 +175,23 @@ public sealed partial class Music } _ = ctx.Channel.TriggerTypingAsync(); - + var botUser = await ctx.Guild.GetCurrentUserAsync(); await EnsureBotInVoiceChannelAsync(voiceChannelId!.Value, botUser); - + if (botUser.VoiceChannel?.Id != voiceChannelId) { await ReplyErrorLocalizedAsync(strs.not_with_bot_in_voice); return; } - - var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel) ctx.Channel); + + var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel)ctx.Channel); if (mp is null) { await ReplyErrorLocalizedAsync(strs.no_player); return; } - + MusicPlaylist mpl; await using (var uow = _db.GetDbContext()) { @@ -209,22 +207,17 @@ public sealed partial class Music IUserMessage msg = null; try { - msg = await ctx.Channel - .SendMessageAsync(GetText(strs.attempting_to_queue(Format.Bold(mpl.Songs.Count.ToString())))); + msg = await ctx.Channel.SendMessageAsync( + GetText(strs.attempting_to_queue(Format.Bold(mpl.Songs.Count.ToString())))); } catch (Exception) { } - await mp.EnqueueManyAsync( - mpl.Songs.Select(x => (x.Query, (MusicPlatform) x.ProviderType)), - ctx.User.ToString() - ); + await mp.EnqueueManyAsync(mpl.Songs.Select(x => (x.Query, (MusicPlatform)x.ProviderType)), + ctx.User.ToString()); - if (msg != null) - { - await msg.ModifyAsync(m => m.Content = GetText(strs.playlist_queue_complete)); - } + if (msg != null) await msg.ModifyAsync(m => m.Content = GetText(strs.playlist_queue_complete)); } finally { @@ -232,4 +225,4 @@ public sealed partial class Music } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Services/AyuVoiceStateService.cs b/src/NadekoBot/Modules/Music/Services/AyuVoiceStateService.cs index d8ea6d502..6a6320448 100644 --- a/src/NadekoBot/Modules/Music/Services/AyuVoiceStateService.cs +++ b/src/NadekoBot/Modules/Music/Services/AyuVoiceStateService.cs @@ -1,6 +1,6 @@ #nullable disable -using System.Reflection; using Ayu.Discord.Voice; +using System.Reflection; namespace NadekoBot.Modules.Music.Services; @@ -8,10 +8,10 @@ public sealed class AyuVoiceStateService : INService { // public delegate Task VoiceProxyUpdatedDelegate(ulong guildId, IVoiceProxy proxy); // public event VoiceProxyUpdatedDelegate OnVoiceProxyUpdate = delegate { return Task.CompletedTask; }; - + private readonly ConcurrentDictionary _voiceProxies = new(); private readonly ConcurrentDictionary _voiceGatewayLocks = new(); - + private readonly DiscordSocketClient _client; private readonly MethodInfo _sendVoiceStateUpdateMethodInfo; private readonly object _dnetApiClient; @@ -23,13 +23,17 @@ public sealed class AyuVoiceStateService : INService _currentUserId = _client.CurrentUser.Id; var prop = _client.GetType() - .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance) - .First(x => x.Name == "ApiClient" && x.PropertyType.Name == "DiscordSocketApiClient"); + .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance) + .First(x => x.Name == "ApiClient" && x.PropertyType.Name == "DiscordSocketApiClient"); _dnetApiClient = prop.GetValue(_client, null); _sendVoiceStateUpdateMethodInfo = _dnetApiClient.GetType() - .GetMethod("SendVoiceStateUpdateAsync", - types: new[] { typeof(ulong), typeof(ulong?), typeof(bool), typeof(bool), typeof(RequestOptions) }); - + .GetMethod("SendVoiceStateUpdateAsync", + new[] + { + typeof(ulong), typeof(ulong?), typeof(bool), + typeof(bool), typeof(RequestOptions) + }); + _client.LeftGuild += ClientOnLeftGuild; } @@ -44,29 +48,32 @@ public sealed class AyuVoiceStateService : INService return Task.CompletedTask; } - private Task InvokeSendVoiceStateUpdateAsync(ulong guildId, ulong? channelId = null, bool isDeafened = false, bool isMuted = false) + private Task InvokeSendVoiceStateUpdateAsync( + ulong guildId, + ulong? channelId = null, + bool isDeafened = false, + bool isMuted = false) // return _voiceStateUpdate(guildId, channelId, isDeafened, isMuted); - => (Task) _sendVoiceStateUpdateMethodInfo.Invoke(_dnetApiClient, new object[] {guildId, channelId, isMuted, isDeafened, null}); + => (Task)_sendVoiceStateUpdateMethodInfo.Invoke(_dnetApiClient, + new object[] { guildId, channelId, isMuted, isDeafened, null }); private Task SendLeaveVoiceChannelInternalAsync(ulong guildId) => InvokeSendVoiceStateUpdateAsync(guildId); private Task SendJoinVoiceChannelInternalAsync(ulong guildId, ulong channelId) => InvokeSendVoiceStateUpdateAsync(guildId, channelId); - - private SemaphoreSlim GetVoiceGatewayLock(ulong guildId) => _voiceGatewayLocks.GetOrAdd(guildId, new SemaphoreSlim(1, 1)); - + + private SemaphoreSlim GetVoiceGatewayLock(ulong guildId) + => _voiceGatewayLocks.GetOrAdd(guildId, new SemaphoreSlim(1, 1)); + private async Task LeaveVoiceChannelInternalAsync(ulong guildId) { var complete = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + Task OnUserVoiceStateUpdated(SocketUser user, SocketVoiceState oldState, SocketVoiceState newState) { - if (user is SocketGuildUser guildUser - && guildUser.Guild.Id == guildId - && newState.VoiceChannel?.Id is null) - { + if (user is SocketGuildUser guildUser && guildUser.Guild.Id == guildId && newState.VoiceChannel?.Id is null) complete.TrySetResult(true); - } return Task.CompletedTask; } @@ -89,6 +96,7 @@ public sealed class AyuVoiceStateService : INService _client.UserVoiceStateUpdated -= OnUserVoiceStateUpdated; } } + public async Task LeaveVoiceChannel(ulong guildId) { var gwLock = GetVoiceGatewayLock(guildId); @@ -105,8 +113,10 @@ public sealed class AyuVoiceStateService : INService private async Task InternalConnectToVcAsync(ulong guildId, ulong channelId) { - var voiceStateUpdatedSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var voiceServerUpdatedSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var voiceStateUpdatedSource = + new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var voiceServerUpdatedSource = + new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Task OnUserVoiceStateUpdated(SocketUser user, SocketVoiceState oldState, SocketVoiceState newState) { @@ -123,14 +133,11 @@ public sealed class AyuVoiceStateService : INService Task OnVoiceServerUpdated(SocketVoiceServer data) { - if (data.Guild.Id == guildId) - { - voiceServerUpdatedSource.TrySetResult(data); - } + if (data.Guild.Id == guildId) voiceServerUpdatedSource.TrySetResult(data); return Task.CompletedTask; } - + try { _client.VoiceServerUpdated += OnVoiceServerUpdated; @@ -149,10 +156,8 @@ public sealed class AyuVoiceStateService : INService // wait for both to end (max 1s) and check if either of them is a delay task var results = await Task.WhenAll(maybeUpdateTask, maybeServerTask); if (results[0] == delayTask || results[1] == delayTask) - { // if either is delay, return null - connection unsuccessful return null; - } // if both are succesful, that means we can safely get // the values from completion sources @@ -164,26 +169,20 @@ public sealed class AyuVoiceStateService : INService return null; var voiceServerData = await voiceServerUpdatedSource.Task; - - VoiceGateway CreateVoiceGatewayLocal() => - new( - guildId, - _currentUserId, - session, - voiceServerData.Token, - voiceServerData.Endpoint - ); - var current = _voiceProxies.AddOrUpdate( - guildId, + VoiceGateway CreateVoiceGatewayLocal() + { + return new(guildId, _currentUserId, session, voiceServerData.Token, voiceServerData.Endpoint); + } + + var current = _voiceProxies.AddOrUpdate(guildId, gid => new VoiceProxy(CreateVoiceGatewayLocal()), (gid, currentProxy) => { _ = currentProxy.StopGateway(); currentProxy.SetGateway(CreateVoiceGatewayLocal()); return currentProxy; - } - ); + }); _ = current.StartGateway(); // don't await, this blocks until gateway is closed return current; @@ -212,4 +211,4 @@ public sealed class AyuVoiceStateService : INService public bool TryGetProxy(ulong guildId, out IVoiceProxy proxy) => _voiceProxies.TryGetValue(guildId, out proxy); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Services/IMusicService.cs b/src/NadekoBot/Modules/Music/Services/IMusicService.cs index 9405e8760..ba8d429d4 100644 --- a/src/NadekoBot/Modules/Music/Services/IMusicService.cs +++ b/src/NadekoBot/Modules/Music/Services/IMusicService.cs @@ -1,23 +1,23 @@ -using System.Diagnostics.CodeAnalysis; -using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Database.Models; +using System.Diagnostics.CodeAnalysis; namespace NadekoBot.Modules.Music.Services; public interface IMusicService : IPlaceholderProvider { /// - /// Leave voice channel in the specified guild if it's connected to one + /// Leave voice channel in the specified guild if it's connected to one /// /// Id of the guild public Task LeaveVoiceChannelAsync(ulong guildId); /// - /// Joins the voice channel with the specified id + /// Joins the voice channel with the specified id /// /// Id of the guild where the voice channel is /// Id of the voice channel public Task JoinVoiceChannelAsync(ulong guildId, ulong voiceChannelId); - + Task GetOrCreateMusicPlayerAsync(ITextChannel contextChannel); bool TryGetMusicPlayer(ulong guildId, [MaybeNullWhen(false)] out IMusicPlayer musicPlayer); Task EnqueueYoutubePlaylistAsync(IMusicPlayer mp, string playlistId, string queuer); @@ -32,4 +32,4 @@ public interface IMusicService : IPlaceholderProvider Task ToggleAutoDisconnectAsync(ulong guildId); Task GetMusicQualityAsync(ulong guildId); Task SetMusicQualityAsync(ulong guildId, QualityPreset preset); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Services/MusicService.cs b/src/NadekoBot/Modules/Music/Services/MusicService.cs index 196b0d58b..7692d08c1 100644 --- a/src/NadekoBot/Modules/Music/Services/MusicService.cs +++ b/src/NadekoBot/Modules/Music/Services/MusicService.cs @@ -1,6 +1,6 @@ -using System.Diagnostics.CodeAnalysis; +using NadekoBot.Db; using NadekoBot.Services.Database.Models; -using NadekoBot.Db; +using System.Diagnostics.CodeAnalysis; namespace NadekoBot.Modules.Music.Services; @@ -22,9 +22,17 @@ public sealed class MusicService : IMusicService private readonly ConcurrentDictionary _outputChannels; private readonly ConcurrentDictionary _settings; - public MusicService(AyuVoiceStateService voiceStateService, ITrackResolveProvider trackResolveProvider, - DbService db, IYoutubeResolver ytResolver, ILocalTrackResolver localResolver, ISoundcloudResolver scResolver, - DiscordSocketClient client, IBotStrings strings, IGoogleApiService googleApiService, YtLoader ytLoader, + public MusicService( + AyuVoiceStateService voiceStateService, + ITrackResolveProvider trackResolveProvider, + DbService db, + IYoutubeResolver ytResolver, + ILocalTrackResolver localResolver, + ISoundcloudResolver scResolver, + DiscordSocketClient client, + IBotStrings strings, + IGoogleApiService googleApiService, + YtLoader ytLoader, IEmbedBuilderService eb) { _voiceStateService = voiceStateService; @@ -42,10 +50,10 @@ public sealed class MusicService : IMusicService _players = new(); _outputChannels = new ConcurrentDictionary(); _settings = new(); - + _client.LeftGuild += ClientOnLeftGuild; } - + private void DisposeMusicPlayer(IMusicPlayer musicPlayer) { musicPlayer.Kill(); @@ -55,10 +63,7 @@ public sealed class MusicService : IMusicService private void RemoveMusicPlayer(ulong guildId) { _outputChannels.TryRemove(guildId, out _); - if (_players.TryRemove(guildId, out var mp)) - { - DisposeMusicPlayer(mp); - } + if (_players.TryRemove(guildId, out var mp)) DisposeMusicPlayer(mp); } private Task ClientOnLeftGuild(SocketGuild guild) @@ -73,7 +78,7 @@ public sealed class MusicService : IMusicService await _voiceStateService.LeaveVoiceChannel(guildId); } - public Task JoinVoiceChannelAsync(ulong guildId, ulong voiceChannelId) + public Task JoinVoiceChannelAsync(ulong guildId, ulong voiceChannelId) => _voiceStateService.JoinVoiceChannel(guildId, voiceChannelId); public async Task GetOrCreateMusicPlayerAsync(ITextChannel contextChannel) @@ -81,7 +86,7 @@ public sealed class MusicService : IMusicService var newPLayer = await CreateMusicPlayerInternalAsync(contextChannel.GuildId, contextChannel); if (newPLayer is null) return null; - + return _players.GetOrAdd(contextChannel.GuildId, newPLayer); } @@ -109,7 +114,7 @@ public sealed class MusicService : IMusicService { if (mp.IsKilled) break; - + mp.EnqueueTrack(track, queuer); } } @@ -121,7 +126,7 @@ public sealed class MusicService : IMusicService { if (mp.IsKilled) break; - + mp.EnqueueTrack(track, queuer); ++i; } @@ -134,10 +139,7 @@ public sealed class MusicService : IMusicService var queue = new MusicQueue(); var resolver = _trackResolveProvider; - if (!_voiceStateService.TryGetProxy(guildId, out var proxy)) - { - return null; - } + if (!_voiceStateService.TryGetProxy(guildId, out var proxy)) return null; var settings = await GetSettingsInternalAsync(guildId); @@ -147,30 +149,19 @@ public sealed class MusicService : IMusicService overrideChannel = _client.GetGuild(guildId)?.GetTextChannel(channelId); if (overrideChannel is null) - { Log.Warning("Saved music output channel doesn't exist, falling back to current channel"); - } } - + _outputChannels[guildId] = (defaultChannel, overrideChannel); - var mp = new MusicPlayer( - queue, - resolver, - proxy, - settings.QualityPreset - ); - + var mp = new MusicPlayer(queue, resolver, proxy, settings.QualityPreset); + mp.SetRepeat(settings.PlayerRepeat); if (settings.Volume is >= 0 and <= 100) - { mp.SetVolume(settings.Volume); - } else - { Log.Error("Saved Volume is outside of valid range >= 0 && <=100 ({Volume})", settings.Volume); - } mp.OnCompleted += OnTrackCompleted(guildId); mp.OnStarted += OnTrackStarted(guildId); @@ -197,10 +188,10 @@ public sealed class MusicService : IMusicService { _ = lastFinishedMessage?.DeleteAsync(); var embed = _eb.Create() - .WithOkColor() - .WithAuthor(GetText(guildId, strs.finished_song), Music.MusicIconUrl) - .WithDescription(trackInfo.PrettyName()) - .WithFooter(trackInfo.PrettyTotalTime()); + .WithOkColor() + .WithAuthor(GetText(guildId, strs.finished_song), Music.MusicIconUrl) + .WithDescription(trackInfo.PrettyName()) + .WithFooter(trackInfo.PrettyTotalTime()); lastFinishedMessage = await SendToOutputAsync(guildId, embed); }; @@ -212,10 +203,11 @@ public sealed class MusicService : IMusicService return async (mp, trackInfo, index) => { _ = lastPlayingMessage?.DeleteAsync(); - var embed = _eb.Create().WithOkColor() - .WithAuthor(GetText(guildId, strs.playing_song(index + 1)), Music.MusicIconUrl) - .WithDescription(trackInfo.PrettyName()) - .WithFooter($"{mp.PrettyVolume()} | {trackInfo.PrettyInfo()}"); + var embed = _eb.Create() + .WithOkColor() + .WithAuthor(GetText(guildId, strs.playing_song(index + 1)), Music.MusicIconUrl) + .WithDescription(trackInfo.PrettyName()) + .WithFooter($"{mp.PrettyVolume()} | {trackInfo.PrettyInfo()}"); lastPlayingMessage = await SendToOutputAsync(guildId, embed); }; @@ -225,12 +217,8 @@ public sealed class MusicService : IMusicService => mp => { if (_settings.TryGetValue(guildId, out var settings)) - { if (settings.AutoDisconnect) - { return LeaveVoiceChannelAsync(guildId); - } - } return Task.CompletedTask; }; @@ -238,19 +226,12 @@ public sealed class MusicService : IMusicService // this has to be done because dragging bot to another vc isn't supported yet public async Task PlayAsync(ulong guildId, ulong voiceChannelId) { - if (!TryGetMusicPlayer(guildId, out var mp)) - { - return false; - } + if (!TryGetMusicPlayer(guildId, out var mp)) return false; if (mp.IsStopped) - { - if (!_voiceStateService.TryGetProxy(guildId, out var proxy) + if (!_voiceStateService.TryGetProxy(guildId, out var proxy) || proxy.State == VoiceProxy.VoiceProxyState.Stopped) - { await JoinVoiceChannelAsync(guildId, voiceChannelId); - } - } mp.Next(); return true; @@ -261,22 +242,19 @@ public sealed class MusicService : IMusicService var result = await _ytLoader.LoadResultsAsync(query); return result.Select(x => (x.Title, x.Url)).ToList(); } - + private async Task> SearchGoogleApiVideosAsync(string query) { var result = await _googleApiService.GetVideoInfosByKeywordAsync(query, 5); return result.Select(x => (x.Name, x.Url)).ToList(); } - + public async Task> SearchVideosAsync(string query) { try { IList<(string, string)> videos = await SearchYtLoaderVideosAsync(query); - if (videos.Count > 0) - { - return videos; - } + if (videos.Count > 0) return videos; } catch (Exception ex) { @@ -289,13 +267,14 @@ public sealed class MusicService : IMusicService } catch (Exception ex) { - Log.Warning("Failed getting video results with Google Api. " + - "Probably google api key missing: {ErrorMessage}", ex.Message); + Log.Warning("Failed getting video results with Google Api. " + + "Probably google api key missing: {ErrorMessage}", + ex.Message); } - + return Array.Empty<(string, string)>(); } - + private string GetText(ulong guildId, LocStr str) => _strings.GetText(str, guildId); @@ -304,11 +283,10 @@ public sealed class MusicService : IMusicService // random song that's playing yield return ("%music.playing%", () => { - var randomPlayingTrack = _players - .Select(x => x.Value.GetCurrentTrack(out _)) - .Where(x => x is not null) - .Shuffle() - .FirstOrDefault(); + var randomPlayingTrack = _players.Select(x => x.Value.GetCurrentTrack(out _)) + .Where(x => x is not null) + .Shuffle() + .FirstOrDefault(); if (randomPlayingTrack is null) return "-"; @@ -319,17 +297,14 @@ public sealed class MusicService : IMusicService // number of servers currently listening to music yield return ("%music.servers%", () => { - var count = _players - .Select(x => x.Value.GetCurrentTrack(out _)) - .Count(x => x is not null); + var count = _players.Select(x => x.Value.GetCurrentTrack(out _)).Count(x => x is not null); return count.ToString(); }); - + yield return ("%music.queued%", () => { - var count = _players - .Sum(x => x.Value.GetQueuedTracks().Count); + var count = _players.Sum(x => x.Value.GetQueuedTracks().Count); return count.ToString(); }); @@ -348,7 +323,7 @@ public sealed class MusicService : IMusicService return toReturn; } - + private async Task ModifySettingsInternalAsync( ulong guildId, Action action, @@ -360,7 +335,7 @@ public sealed class MusicService : IMusicService await uow.SaveChangesAsync(); _settings[guildId] = ms; } - + public async Task SetMusicChannelAsync(ulong guildId, ulong? channelId) { if (channelId is null) @@ -368,29 +343,31 @@ public sealed class MusicService : IMusicService await UnsetMusicChannelAsync(guildId); return true; } - + var channel = _client.GetGuild(guildId)?.GetTextChannel(channelId.Value); if (channel is null) return false; - await ModifySettingsInternalAsync(guildId, (settings, chId) => - { - settings.MusicChannelId = chId; - }, channelId); + await ModifySettingsInternalAsync(guildId, + (settings, chId) => + { + settings.MusicChannelId = chId; + }, + channelId); + + _outputChannels.AddOrUpdate(guildId, (channel, channel), (key, old) => (old.Default, channel)); - _outputChannels.AddOrUpdate(guildId, - (channel, channel), - (key, old) => (old.Default, channel)); - return true; } public async Task UnsetMusicChannelAsync(ulong guildId) { - await ModifySettingsInternalAsync(guildId, (settings, _) => - { - settings.MusicChannelId = null; - }, (ulong?)null); + await ModifySettingsInternalAsync(guildId, + (settings, _) => + { + settings.MusicChannelId = null; + }, + (ulong?)null); if (_outputChannels.TryGetValue(guildId, out var old)) _outputChannels[guildId] = (old.Default, null); @@ -398,10 +375,12 @@ public sealed class MusicService : IMusicService public async Task SetRepeatAsync(ulong guildId, PlayerRepeatType repeatType) { - await ModifySettingsInternalAsync(guildId, (settings, type) => - { - settings.PlayerRepeat = type; - }, repeatType); + await ModifySettingsInternalAsync(guildId, + (settings, type) => + { + settings.PlayerRepeat = type; + }, + repeatType); if (TryGetMusicPlayer(guildId, out var mp)) mp.SetRepeat(repeatType); @@ -411,12 +390,14 @@ public sealed class MusicService : IMusicService { if (value is < 0 or > 100) throw new ArgumentOutOfRangeException(nameof(value)); - - await ModifySettingsInternalAsync(guildId, (settings, newValue) => - { - settings.Volume = newValue; - }, value); - + + await ModifySettingsInternalAsync(guildId, + (settings, newValue) => + { + settings.Volume = newValue; + }, + value); + if (TryGetMusicPlayer(guildId, out var mp)) mp.SetVolume(value); } @@ -424,10 +405,12 @@ public sealed class MusicService : IMusicService public async Task ToggleAutoDisconnectAsync(ulong guildId) { var newState = false; - await ModifySettingsInternalAsync(guildId, (settings, _) => - { - newState = settings.AutoDisconnect = !settings.AutoDisconnect; - }, default(object)); + await ModifySettingsInternalAsync(guildId, + (settings, _) => + { + newState = settings.AutoDisconnect = !settings.AutoDisconnect; + }, + default(object)); return newState; } @@ -440,10 +423,12 @@ public sealed class MusicService : IMusicService } public Task SetMusicQualityAsync(ulong guildId, QualityPreset preset) - => ModifySettingsInternalAsync(guildId, (settings, _) => - { - settings.QualityPreset = preset; - }, preset); + => ModifySettingsInternalAsync(guildId, + (settings, _) => + { + settings.QualityPreset = preset; + }, + preset); #endregion -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Services/extractor/Misc.cs b/src/NadekoBot/Modules/Music/Services/extractor/Misc.cs index f545d30bf..e064fcb8f 100644 --- a/src/NadekoBot/Modules/Music/Services/extractor/Misc.cs +++ b/src/NadekoBot/Modules/Music/Services/extractor/Misc.cs @@ -3,7 +3,6 @@ namespace NadekoBot.Modules.Music.Services; public sealed partial class YtLoader { - public class InitRange { public string Start { get; set; } @@ -69,4 +68,4 @@ public sealed partial class YtLoader _videoId = videoId; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Services/extractor/YtLoader.cs b/src/NadekoBot/Modules/Music/Services/extractor/YtLoader.cs index 316847df5..e1fd93b6b 100644 --- a/src/NadekoBot/Modules/Music/Services/extractor/YtLoader.cs +++ b/src/NadekoBot/Modules/Music/Services/extractor/YtLoader.cs @@ -7,15 +7,16 @@ namespace NadekoBot.Modules.Music.Services; public sealed partial class YtLoader { - private readonly IHttpClientFactory _httpFactory; private static readonly byte[] YT_RESULT_INITIAL_DATA = Encoding.UTF8.GetBytes("var ytInitialData = "); private static readonly byte[] YT_RESULT_JSON_END = Encoding.UTF8.GetBytes(";<"); - - private static readonly string[] durationFormats = new[] + + private static readonly string[] durationFormats = { @"m\:ss", @"mm\:ss", @"h\:mm\:ss", @"hh\:mm\:ss", @"hhh\:mm\:ss" }; + private readonly IHttpClientFactory _httpFactory; + public YtLoader(IHttpClientFactory httpFactory) => _httpFactory = httpFactory; @@ -54,7 +55,7 @@ public sealed partial class YtLoader public async Task> LoadResultsAsync(string query) { query = Uri.EscapeDataString(query); - + using var http = _httpFactory.CreateClient(); http.DefaultRequestHeaders.Add("Cookie", "CONSENT=YES+cb.20210530-19-p0.en+FX+071;"); @@ -76,20 +77,19 @@ public sealed partial class YtLoader var root = JsonDocument.Parse(mem).RootElement; var tracksJsonItems = root - .GetProperty("contents") - .GetProperty("twoColumnSearchResultsRenderer") - .GetProperty("primaryContents") - .GetProperty("sectionListRenderer") - .GetProperty("contents") - [0] - .GetProperty("itemSectionRenderer") - .GetProperty("contents") - .EnumerateArray(); - + .GetProperty("contents") + .GetProperty("twoColumnSearchResultsRenderer") + .GetProperty("primaryContents") + .GetProperty("sectionListRenderer") + .GetProperty("contents")[0] + .GetProperty("itemSectionRenderer") + .GetProperty("contents") + .EnumerateArray(); + var tracks = new List(); foreach (var track in tracksJsonItems) { - if(!track.TryGetProperty("videoRenderer", out var elem)) + if (!track.TryGetProperty("videoRenderer", out var elem)) continue; var videoId = elem.GetProperty("videoId").GetString(); @@ -97,18 +97,20 @@ public sealed partial class YtLoader var title = elem.GetProperty("title").GetProperty("runs")[0].GetProperty("text").GetString(); var durationString = elem.GetProperty("lengthText").GetProperty("simpleText").GetString(); - if (!TimeSpan.TryParseExact(durationString, durationFormats, CultureInfo.InvariantCulture, + if (!TimeSpan.TryParseExact(durationString, + durationFormats, + CultureInfo.InvariantCulture, out var duration)) { Log.Warning("Cannot parse duration: {DurationString}", durationString); continue; } - + tracks.Add(new YtTrackInfo(title, videoId, duration)); if (tracks.Count >= 5) break; } - + return tracks; } @@ -120,11 +122,9 @@ public sealed partial class YtLoader return null; // todo future try selecting html startIndex += YT_RESULT_INITIAL_DATA.Length; - var endIndex = 140_000 + startIndex + responseSpan[(startIndex + 20_000)..].IndexOf(YT_RESULT_JSON_END) + 20_000; + var endIndex = + 140_000 + startIndex + responseSpan[(startIndex + 20_000)..].IndexOf(YT_RESULT_JSON_END) + 20_000; startIndex += 140_000; - return response.AsMemory( - startIndex, - endIndex - startIndex - ); + return response.AsMemory(startIndex, endIndex - startIndex); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Booru.cs b/src/NadekoBot/Modules/Nsfw/Common/Booru.cs index c32be45dc..2c92ccee6 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Booru.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Booru.cs @@ -12,4 +12,4 @@ public enum Booru Yandere, Danbooru, Sankaku -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/DapiImageObject.cs b/src/NadekoBot/Modules/Nsfw/Common/DapiImageObject.cs index 68c0890b4..1e852fd20 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/DapiImageObject.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/DapiImageObject.cs @@ -7,12 +7,15 @@ public class DapiImageObject : IImageData { [JsonPropertyName("File_Url")] public string FileUrl { get; set; } + public string Tags { get; set; } + [JsonPropertyName("Tag_String")] public string TagString { get; set; } + public int Score { get; set; } public string Rating { get; set; } - + public ImageData ToCachedImageData(Booru type) - => new(this.FileUrl, type, this.Tags?.Split(' ') ?? this.TagString?.Split(' '), Score.ToString() ?? Rating); -} + => new(FileUrl, type, Tags?.Split(' ') ?? TagString?.Split(' '), Score.ToString() ?? Rating); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/DapiTag.cs b/src/NadekoBot/Modules/Nsfw/Common/DapiTag.cs index 446a0298b..3febac369 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/DapiTag.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/DapiTag.cs @@ -10,4 +10,4 @@ public readonly struct DapiTag [JsonConstructor] public DapiTag(string name) => Name = name; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/DerpiContainer.cs b/src/NadekoBot/Modules/Nsfw/Common/DerpiContainer.cs index e68abc201..f6c41226f 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/DerpiContainer.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/DerpiContainer.cs @@ -12,8 +12,10 @@ public class DerpiImageObject : IImageData { [JsonPropertyName("view_url")] public string ViewUrl { get; set; } + public string[] Tags { get; set; } public int Score { get; set; } + public ImageData ToCachedImageData(Booru type) => new(ViewUrl, type, Tags, Score.ToString("F1")); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/DanbooruImageDownloader.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/DanbooruImageDownloader.cs index 7492a3aab..dc283e798 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/DanbooruImageDownloader.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/DanbooruImageDownloader.cs @@ -9,6 +9,11 @@ public sealed class DanbooruImageDownloader : DapiImageDownloader private static readonly ConcurrentDictionary _existentTags = new(); private static readonly ConcurrentDictionary _nonexistentTags = new(); + public DanbooruImageDownloader(HttpClient http) + : base(Booru.Danbooru, http, "http://danbooru.donmai.us") + { + } + public override async Task IsTagValid(string tag, CancellationToken cancel = default) { if (_existentTags.ContainsKey(tag)) @@ -17,21 +22,12 @@ public sealed class DanbooruImageDownloader : DapiImageDownloader if (_nonexistentTags.ContainsKey(tag)) return false; - var tags = await _http.GetFromJsonAsync(_baseUrl + - "/tags.json" + - $"?search[name_or_alias_matches]={tag}", - options: this._serializerOptions, - cancellationToken: cancel); - if (tags is {Length: > 0}) - { - return _existentTags[tag] = true; - } + var tags = await _http.GetFromJsonAsync( + _baseUrl + "/tags.json" + $"?search[name_or_alias_matches]={tag}", + _serializerOptions, + cancel); + if (tags is { Length: > 0 }) return _existentTags[tag] = true; return _nonexistentTags[tag] = false; } - - public DanbooruImageDownloader(HttpClient http) - : base(Booru.Danbooru, http, "http://danbooru.donmai.us") - { - } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/DapiImageDownloader.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/DapiImageDownloader.cs index ec41a20e3..17530562b 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/DapiImageDownloader.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/DapiImageDownloader.cs @@ -7,26 +7,29 @@ public abstract class DapiImageDownloader : ImageDownloader { protected readonly string _baseUrl; - public DapiImageDownloader(Booru booru, HttpClient http, string baseUrl) : base(booru, http) + public DapiImageDownloader(Booru booru, HttpClient http, string baseUrl) + : base(booru, http) => _baseUrl = baseUrl; public abstract Task IsTagValid(string tag, CancellationToken cancel = default); + protected async Task AllTagsValid(string[] tags, CancellationToken cancel = default) { var results = await tags.Select(tag => IsTagValid(tag, cancel)).WhenAll(); // if any of the tags is not valid, the query is not valid foreach (var result in results) - { if (!result) return false; - } return true; } - public override async Task> DownloadImagesAsync(string[] tags, int page, - bool isExplicit = false, CancellationToken cancel = default) + public override async Task> DownloadImagesAsync( + string[] tags, + int page, + bool isExplicit = false, + CancellationToken cancel = default) { // up to 2 tags allowed on danbooru if (tags.Length > 2) @@ -41,8 +44,6 @@ public abstract class DapiImageDownloader : ImageDownloader var imageObjects = await _http.GetFromJsonAsync(uri, _serializerOptions, cancel); if (imageObjects is null) return new(); - return imageObjects - .Where(x => x.FileUrl is not null) - .ToList(); + return imageObjects.Where(x => x.FileUrl is not null).ToList(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/DerpibooruImageDownloader.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/DerpibooruImageDownloader.cs index a51984552..826e0ebc3 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/DerpibooruImageDownloader.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/DerpibooruImageDownloader.cs @@ -5,25 +5,29 @@ namespace NadekoBot.Modules.Nsfw.Common; public class DerpibooruImageDownloader : ImageDownloader { - public DerpibooruImageDownloader(HttpClient http) : base(Booru.Derpibooru, http) + public DerpibooruImageDownloader(HttpClient http) + : base(Booru.Derpibooru, http) { } - public override async Task> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default) + public override async Task> DownloadImagesAsync( + string[] tags, + int page, + bool isExplicit = false, + CancellationToken cancel = default) { var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit); - var uri = $"https://www.derpibooru.org/api/v1/json/search/images?q={tagString.Replace('+', ',')}&per_page=49&page={page}"; + var uri = + $"https://www.derpibooru.org/api/v1/json/search/images?q={tagString.Replace('+', ',')}&per_page=49&page={page}"; using var req = new HttpRequestMessage(HttpMethod.Get, uri); req.Headers.AddFakeHeaders(); using var res = await _http.SendAsync(req, cancel); res.EnsureSuccessStatusCode(); - + var container = await res.Content.ReadFromJsonAsync(_serializerOptions, cancel); if (container?.Images is null) return new(); - - return container.Images - .Where(x => !string.IsNullOrWhiteSpace(x.ViewUrl)) - .ToList(); + + return container.Images.Where(x => !string.IsNullOrWhiteSpace(x.ViewUrl)).ToList(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/E621ImageDownloader.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/E621ImageDownloader.cs index 18b9be837..16c807e97 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/E621ImageDownloader.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/E621ImageDownloader.cs @@ -5,13 +5,18 @@ namespace NadekoBot.Modules.Nsfw.Common; public class E621ImageDownloader : ImageDownloader { - public E621ImageDownloader(HttpClient http) : base(Booru.E621, http) + public E621ImageDownloader(HttpClient http) + : base(Booru.E621, http) { } - public override async Task> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default) + public override async Task> DownloadImagesAsync( + string[] tags, + int page, + bool isExplicit = false, + CancellationToken cancel = default) { - var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit: isExplicit); + var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit); var uri = $"https://e621.net/posts.json?limit=32&tags={tagString}&page={page}"; using var req = new HttpRequestMessage(HttpMethod.Get, uri); req.Headers.AddFakeHeaders(); @@ -22,8 +27,6 @@ public class E621ImageDownloader : ImageDownloader if (data?.Posts is null) return new(); - return data.Posts - .Where(x => !string.IsNullOrWhiteSpace(x.File?.Url)) - .ToList(); + return data.Posts.Where(x => !string.IsNullOrWhiteSpace(x.File?.Url)).ToList(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/E621Response.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/E621Response.cs index b7c7f6065..4d29b9aff 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/E621Response.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/E621Response.cs @@ -4,4 +4,4 @@ namespace NadekoBot.Modules.Nsfw.Common; public class E621Response { public List Posts { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/GelbooruImageDownloader.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/GelbooruImageDownloader.cs index eb4cf7c60..80086329d 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/GelbooruImageDownloader.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/GelbooruImageDownloader.cs @@ -5,26 +5,31 @@ namespace NadekoBot.Modules.Nsfw.Common; public class GelbooruImageDownloader : ImageDownloader { - public GelbooruImageDownloader(HttpClient http) : base(Booru.Gelbooru, http) + public GelbooruImageDownloader(HttpClient http) + : base(Booru.Gelbooru, http) { } - public override async Task> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default) + public override async Task> DownloadImagesAsync( + string[] tags, + int page, + bool isExplicit = false, + CancellationToken cancel = default) { var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit); - var uri = $"http://gelbooru.com/index.php?page=dapi&s=post&json=1&q=index&limit=100" + - $"&tags={tagString}&pid={page}"; + var uri = "http://gelbooru.com/index.php?page=dapi&s=post&json=1&q=index&limit=100" + + $"&tags={tagString}&pid={page}"; using var req = new HttpRequestMessage(HttpMethod.Get, uri); using var res = await _http.SendAsync(req, cancel); res.EnsureSuccessStatusCode(); var resString = await res.Content.ReadAsStringAsync(cancel); if (string.IsNullOrWhiteSpace(resString)) return new(); - + var images = JsonSerializer.Deserialize>(resString, _serializerOptions); if (images is null) return new(); return images.Where(x => x.FileUrl is not null).ToList(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/IImageDownloader.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/IImageDownloader.cs index 968927135..9529a4614 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/IImageDownloader.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/IImageDownloader.cs @@ -3,6 +3,9 @@ namespace NadekoBot.Modules.Nsfw.Common; public interface IImageDownloader { - Task> DownloadImageDataAsync(string[] tags, int page = 0, - bool isExplicit = false, CancellationToken cancel = default); -} + Task> DownloadImageDataAsync( + string[] tags, + int page = 0, + bool isExplicit = false, + CancellationToken cancel = default); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/ImageDownloader.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/ImageDownloader.cs index a22fe466f..e6ecd3157 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/ImageDownloader.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/ImageDownloader.cs @@ -7,29 +7,34 @@ namespace NadekoBot.Modules.Nsfw.Common; public abstract class ImageDownloader : IImageDownloader where T : IImageData { + public Booru Booru { get; } protected readonly HttpClient _http; protected JsonSerializerOptions _serializerOptions = new() { PropertyNameCaseInsensitive = true, - NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString, - + NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString }; - - public Booru Booru { get; } public ImageDownloader(Booru booru, HttpClient http) { _http = http; - this.Booru = booru; + Booru = booru; } - public abstract Task> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default); + public abstract Task> DownloadImagesAsync( + string[] tags, + int page, + bool isExplicit = false, + CancellationToken cancel = default); - public async Task> DownloadImageDataAsync(string[] tags, int page, bool isExplicit = false, + public async Task> DownloadImageDataAsync( + string[] tags, + int page, + bool isExplicit = false, CancellationToken cancel = default) { var images = await DownloadImagesAsync(tags, page, isExplicit, cancel); return images.Select(x => x.ToCachedImageData(Booru)).ToList(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/ImageDownloaderHelper.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/ImageDownloaderHelper.cs index c9679466d..3bf8ef7af 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/ImageDownloaderHelper.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/ImageDownloaderHelper.cs @@ -7,7 +7,7 @@ public static class ImageDownloaderHelper { if (isExplicit) tags = tags.Append("rating:explicit"); - + return string.Join('+', tags.Select(x => x.ToLowerInvariant())); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/KonachanImageDownloader.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/KonachanImageDownloader.cs index c2e8c7d8a..412685bb7 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/KonachanImageDownloader.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/KonachanImageDownloader.cs @@ -11,15 +11,17 @@ public sealed class KonachanImageDownloader : ImageDownloader : base(Booru.Konachan, http) => _baseUrl = "https://konachan.com"; - public override async Task> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default) + public override async Task> DownloadImagesAsync( + string[] tags, + int page, + bool isExplicit = false, + CancellationToken cancel = default) { var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit); var uri = $"{_baseUrl}/post.json?s=post&q=index&limit=200&tags={tagString}&page={page}"; var imageObjects = await _http.GetFromJsonAsync(uri, _serializerOptions, cancel); if (imageObjects is null) return new(); - return imageObjects - .Where(x => x.FileUrl is not null) - .ToList(); + return imageObjects.Where(x => x.FileUrl is not null).ToList(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/Rule34ImageDownloader.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/Rule34ImageDownloader.cs index 1beb07bff..ed69773ca 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/Rule34ImageDownloader.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/Rule34ImageDownloader.cs @@ -5,22 +5,25 @@ namespace NadekoBot.Modules.Nsfw.Common; public class Rule34ImageDownloader : ImageDownloader { - public Rule34ImageDownloader(HttpClient http) : base(Booru.Rule34, http) + public Rule34ImageDownloader(HttpClient http) + : base(Booru.Rule34, http) { } - public override async Task> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default) + public override async Task> DownloadImagesAsync( + string[] tags, + int page, + bool isExplicit = false, + CancellationToken cancel = default) { var tagString = ImageDownloaderHelper.GetTagString(tags); - var uri = $"https://rule34.xxx/index.php?page=dapi&s=post&q=index&json=1&limit=100" + - $"&tags={tagString}&pid={page}"; + var uri = "https://rule34.xxx/index.php?page=dapi&s=post&q=index&json=1&limit=100" + + $"&tags={tagString}&pid={page}"; var images = await _http.GetFromJsonAsync>(uri, _serializerOptions, cancel); if (images is null) return new(); - - return images - .Where(img => !string.IsNullOrWhiteSpace(img.Image)) - .ToList(); + + return images.Where(img => !string.IsNullOrWhiteSpace(img.Image)).ToList(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/SafebooruImageDownloader.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/SafebooruImageDownloader.cs index 749806394..b02d7eb1c 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/SafebooruImageDownloader.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/SafebooruImageDownloader.cs @@ -5,18 +5,24 @@ namespace NadekoBot.Modules.Nsfw.Common; public class SafebooruImageDownloader : ImageDownloader { - public SafebooruImageDownloader(HttpClient http) : base(Booru.Safebooru, http) + public SafebooruImageDownloader(HttpClient http) + : base(Booru.Safebooru, http) { } - public override async Task> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default) + public override async Task> DownloadImagesAsync( + string[] tags, + int page, + bool isExplicit = false, + CancellationToken cancel = default) { - var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit: false); - var uri = $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=200&tags={tagString}&json=1&pid={page}"; - var images = await _http.GetFromJsonAsync>(uri, _serializerOptions, cancellationToken: cancel); + var tagString = ImageDownloaderHelper.GetTagString(tags); + var uri = + $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=200&tags={tagString}&json=1&pid={page}"; + var images = await _http.GetFromJsonAsync>(uri, _serializerOptions, cancel); if (images is null) return new(); return images; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/SankakuImageDownloader.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/SankakuImageDownloader.cs index 6ae37c580..b8dd23691 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/SankakuImageDownloader.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/SankakuImageDownloader.cs @@ -13,16 +13,20 @@ public sealed class SankakuImageDownloader : ImageDownloader _baseUrl = "https://capi-v2.sankakucomplex.com"; _http.AddFakeHeaders(); } - - public override async Task> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default) + + public override async Task> DownloadImagesAsync( + string[] tags, + int page, + bool isExplicit = false, + CancellationToken cancel = default) { // explicit probably not supported - var tagString = ImageDownloaderHelper.GetTagString(tags, false); + var tagString = ImageDownloaderHelper.GetTagString(tags); var uri = $"{_baseUrl}/posts?tags={tagString}&limit=50"; var data = await _http.GetStringAsync(uri); return JsonSerializer.Deserialize(data, _serializerOptions) - .Where(x => !string.IsNullOrWhiteSpace(x.FileUrl) && x.FileType.StartsWith("image")) - .ToList(); + .Where(x => !string.IsNullOrWhiteSpace(x.FileUrl) && x.FileType.StartsWith("image")) + .ToList(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/YandereImageDownloader.cs b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/YandereImageDownloader.cs index 375f8e046..7b819be4d 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Downloaders/YandereImageDownloader.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Downloaders/YandereImageDownloader.cs @@ -11,16 +11,18 @@ public sealed class YandereImageDownloader : ImageDownloader : base(Booru.Yandere, http) => _baseUrl = "https://yande.re"; - public override async Task> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default) + public override async Task> DownloadImagesAsync( + string[] tags, + int page, + bool isExplicit = false, + CancellationToken cancel = default) { var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit); - + var uri = $"{_baseUrl}/post.json?limit=200&tags={tagString}&page={page}"; var imageObjects = await _http.GetFromJsonAsync(uri, _serializerOptions, cancel); if (imageObjects is null) return new(); - return imageObjects - .Where(x => x.FileUrl is not null) - .ToList(); + return imageObjects.Where(x => x.FileUrl is not null).ToList(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/E621Object.cs b/src/NadekoBot/Modules/Nsfw/Common/E621Object.cs index 51af105dd..c6220cbed 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/E621Object.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/E621Object.cs @@ -3,6 +3,13 @@ namespace NadekoBot.Modules.Nsfw.Common; public class E621Object : IImageData { + public FileData File { get; set; } + public TagData Tags { get; set; } + public ScoreData Score { get; set; } + + public ImageData ToCachedImageData(Booru type) + => new(File.Url, Booru.E621, Tags.General, Score.Total.ToString()); + public class FileData { public string Url { get; set; } @@ -17,11 +24,4 @@ public class E621Object : IImageData { public int Total { get; set; } } - - public FileData File { get; set; } - public TagData Tags { get; set; } - public ScoreData Score { get; set; } - - public ImageData ToCachedImageData(Booru type) - => new(File.Url, Booru.E621, Tags.General, Score.Total.ToString()); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/IImageData.cs b/src/NadekoBot/Modules/Nsfw/Common/IImageData.cs index 91bce6420..12414c85d 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/IImageData.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/IImageData.cs @@ -4,4 +4,4 @@ namespace NadekoBot.Modules.Nsfw.Common; public interface IImageData { ImageData ToCachedImageData(Booru type); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/ImageData.cs b/src/NadekoBot/Modules/Nsfw/Common/ImageData.cs index dded492c8..ddc64c5c0 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/ImageData.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/ImageData.cs @@ -8,30 +8,32 @@ public class ImageData : IComparable public HashSet Tags { get; } public string Rating { get; } - public ImageData(string url, Booru type, string[] tags, string rating) + public ImageData( + string url, + Booru type, + string[] tags, + string rating) { if (type == Booru.Danbooru && !Uri.IsWellFormedUriString(url, UriKind.Absolute)) - { - this.FileUrl = "https://danbooru.donmai.us" + url; - } + FileUrl = "https://danbooru.donmai.us" + url; else - { - this.FileUrl = url.StartsWith("http", StringComparison.InvariantCulture) ? url : "https:" + url; - } - - this.SearchType = type; - this.FileUrl = url; - this.Tags = tags.ToHashSet(); - this.Rating = rating; + FileUrl = url.StartsWith("http", StringComparison.InvariantCulture) ? url : "https:" + url; + + SearchType = type; + FileUrl = url; + Tags = tags.ToHashSet(); + Rating = rating; } public override string ToString() => FileUrl; - public override int GetHashCode() => FileUrl.GetHashCode(); + public override int GetHashCode() + => FileUrl.GetHashCode(); + public override bool Equals(object obj) - => obj is ImageData ico && ico.FileUrl == this.FileUrl; + => obj is ImageData ico && ico.FileUrl == FileUrl; public int CompareTo(ImageData other) => string.Compare(FileUrl, other.FileUrl, StringComparison.InvariantCulture); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/Rule34Object.cs b/src/NadekoBot/Modules/Nsfw/Common/Rule34Object.cs index 429a5d947..2d8bdcbd9 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/Rule34Object.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/Rule34Object.cs @@ -9,9 +9,5 @@ public class Rule34Object : IImageData public int Score { get; init; } public ImageData ToCachedImageData(Booru type) - => new( - $"https://img.rule34.xxx//images/{Directory}/{Image}", - Booru.Rule34, - Tags.Split(' '), - Score.ToString()); -} + => new($"https://img.rule34.xxx//images/{Directory}/{Image}", Booru.Rule34, Tags.Split(' '), Score.ToString()); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/SafebooruElement.cs b/src/NadekoBot/Modules/Nsfw/Common/SafebooruElement.cs index 20732b24a..4ae83d1fc 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/SafebooruElement.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/SafebooruElement.cs @@ -7,8 +7,12 @@ public class SafebooruElement : IImageData public string Image { get; set; } - public string FileUrl => $"https://safebooru.org/images/{Directory}/{Image}"; + public string FileUrl + => $"https://safebooru.org/images/{Directory}/{Image}"; + public string Rating { get; set; } public string Tags { get; set; } - public ImageData ToCachedImageData(Booru type) => new(FileUrl, Booru.Safebooru, this.Tags.Split(' '), Rating); -} + + public ImageData ToCachedImageData(Booru type) + => new(FileUrl, Booru.Safebooru, Tags.Split(' '), Rating); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Common/SankakuImageObject.cs b/src/NadekoBot/Modules/Nsfw/Common/SankakuImageObject.cs index ff9ff0a58..d5d5949dd 100644 --- a/src/NadekoBot/Modules/Nsfw/Common/SankakuImageObject.cs +++ b/src/NadekoBot/Modules/Nsfw/Common/SankakuImageObject.cs @@ -5,22 +5,22 @@ namespace NadekoBot.Modules.Nsfw.Common; public class SankakuImageObject : IImageData { + [JsonPropertyName("file_url")] + public string FileUrl { get; set; } + + [JsonPropertyName("file_type")] + public string FileType { get; set; } + + public Tag[] Tags { get; set; } + + [JsonPropertyName("total_score")] + public int Score { get; set; } + + public ImageData ToCachedImageData(Booru type) + => new(FileUrl, Booru.Sankaku, Tags.Select(x => x.Name).ToArray(), Score.ToString()); + public class Tag { public string Name { get; set; } } - - [JsonPropertyName("file_url")] - public string FileUrl { get; set; } - - [JsonPropertyName("file_type")] - public string FileType { get; set; } - - public Tag[] Tags { get; set; } - - [JsonPropertyName("total_score")] - public int Score { get; set; } - - public ImageData ToCachedImageData(Nsfw.Common.Booru type) - => new(FileUrl, Nsfw.Common.Booru.Sankaku, Tags.Select(x => x.Name).ToArray(), Score.ToString()); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/ISearchImagesService.cs b/src/NadekoBot/Modules/Nsfw/ISearchImagesService.cs index 0e3ac06dd..d0e56792e 100644 --- a/src/NadekoBot/Modules/Nsfw/ISearchImagesService.cs +++ b/src/NadekoBot/Modules/Nsfw/ISearchImagesService.cs @@ -5,6 +5,9 @@ namespace NadekoBot.Modules.Nsfw; public interface ISearchImagesService { + ConcurrentDictionary AutoHentaiTimers { get; } + ConcurrentDictionary AutoBoobTimers { get; } + ConcurrentDictionary AutoButtTimers { get; } Task Gelbooru(ulong? guildId, bool forceExplicit, string[] tags); Task Danbooru(ulong? guildId, bool forceExplicit, string[] tags); Task Konachan(ulong? guildId, bool forceExplicit, string[] tags); @@ -21,7 +24,4 @@ public interface ISearchImagesService Task Butts(); Task GetNhentaiByIdAsync(uint id); Task GetNhentaiBySearchAsync(string search); - ConcurrentDictionary AutoHentaiTimers { get; } - ConcurrentDictionary AutoBoobTimers { get; } - ConcurrentDictionary AutoButtTimers { get; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Nsfw.cs b/src/NadekoBot/Modules/Nsfw/Nsfw.cs index bb5e51525..93b8453e2 100644 --- a/src/NadekoBot/Modules/Nsfw/Nsfw.cs +++ b/src/NadekoBot/Modules/Nsfw/Nsfw.cs @@ -24,8 +24,8 @@ public class NSFW : NadekoModule JToken obj; using (var http = _httpFactory.CreateClient()) { - obj = JArray.Parse(await http - .GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 10330)}"))[0]; + obj = JArray.Parse( + await http.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 10330)}"))[0]; } await ctx.Channel.SendMessageAsync($"http://media.oboobs.ru/{obj["preview"]}"); @@ -43,8 +43,8 @@ public class NSFW : NadekoModule JToken obj; using (var http = _httpFactory.CreateClient()) { - obj = JArray.Parse(await http - .GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 4335)}"))[0]; + obj = JArray.Parse( + await http.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 4335)}"))[0]; } await Channel.SendMessageAsync($"http://media.obutts.ru/{obj["preview"]}"); @@ -55,7 +55,8 @@ public class NSFW : NadekoModule } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireNsfw] [RequireContext(ContextType.Guild)] [UserPerm(ChannelPerm.ManageMessages)] @@ -76,36 +77,42 @@ public class NSFW : NadekoModule return; t = new(async state => - { - try { - if (tags is null || tags.Length == 0) - await InternalDapiCommand(null, true, _service.Hentai); - else + try { - var groups = tags.Split('|'); - var group = groups[_rng.Next(0, groups.Length)]; - await InternalDapiCommand(group.Split(' '), true, _service.Hentai); + if (tags is null || tags.Length == 0) + { + await InternalDapiCommand(null, true, _service.Hentai); + } + else + { + var groups = tags.Split('|'); + var group = groups[_rng.Next(0, groups.Length)]; + await InternalDapiCommand(group.Split(' '), true, _service.Hentai); + } } - } - catch + catch + { + // ignored + } + }, + null, + interval * 1000, + interval * 1000); + + _service.AutoHentaiTimers.AddOrUpdate(ctx.Channel.Id, + t, + (key, old) => { - // ignored - } - }, null, interval * 1000, interval * 1000); + old.Change(Timeout.Infinite, Timeout.Infinite); + return t; + }); - _service.AutoHentaiTimers.AddOrUpdate(ctx.Channel.Id, t, (key, old) => - { - old.Change(Timeout.Infinite, Timeout.Infinite); - return t; - }); - - await ReplyConfirmLocalizedAsync(strs.autohentai_started( - interval, - string.Join(", ", tags))); + await ReplyConfirmLocalizedAsync(strs.autohentai_started(interval, string.Join(", ", tags))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireNsfw] [RequireContext(ContextType.Guild)] [UserPerm(ChannelPerm.ManageMessages)] @@ -126,28 +133,35 @@ public class NSFW : NadekoModule return; t = new(async state => - { - try { - await InternalBoobs(); - } - catch - { - // ignored - } - }, null, interval * 1000, interval * 1000); + try + { + await InternalBoobs(); + } + catch + { + // ignored + } + }, + null, + interval * 1000, + interval * 1000); - _service.AutoBoobTimers.AddOrUpdate(ctx.Channel.Id, t, (key, old) => - { - old.Change(Timeout.Infinite, Timeout.Infinite); - return t; - }); + _service.AutoBoobTimers.AddOrUpdate(ctx.Channel.Id, + t, + (key, old) => + { + old.Change(Timeout.Infinite, Timeout.Infinite); + return t; + }); await ReplyConfirmLocalizedAsync(strs.started(interval)); } - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] [UserPerm(ChannelPerm.ManageMessages)] public async Task AutoButts(int interval = 0) { @@ -166,33 +180,42 @@ public class NSFW : NadekoModule return; t = new(async state => - { - try { - await InternalButts(ctx.Channel); - } - catch - { - // ignored - } - }, null, interval * 1000, interval * 1000); + try + { + await InternalButts(ctx.Channel); + } + catch + { + // ignored + } + }, + null, + interval * 1000, + interval * 1000); - _service.AutoButtTimers.AddOrUpdate(ctx.Channel.Id, t, (key, old) => - { - old.Change(Timeout.Infinite, Timeout.Infinite); - return t; - }); + _service.AutoButtTimers.AddOrUpdate(ctx.Channel.Id, + t, + (key, old) => + { + old.Change(Timeout.Infinite, Timeout.Infinite); + return t; + }); await ReplyConfirmLocalizedAsync(strs.started(interval)); } - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] - public Task Hentai(params string[] tags) + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + public Task Hentai(params string[] tags) => InternalDapiCommand(tags, true, _service.Hentai); - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] public async Task HentaiBomb(params string[] tags) { if (!_hentaiBombBlacklist.Add(ctx.Guild?.Id ?? ctx.User.Id)) @@ -219,53 +242,73 @@ public class NSFW : NadekoModule } } - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] public Task Yandere(params string[] tags) => InternalDapiCommand(tags, false, _service.Yandere); - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] public Task Konachan(params string[] tags) => InternalDapiCommand(tags, false, _service.Konachan); - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] public Task Sankaku(params string[] tags) => InternalDapiCommand(tags, false, _service.Sankaku); - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] public Task E621(params string[] tags) => InternalDapiCommand(tags, false, _service.E621); - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] public Task Rule34(params string[] tags) => InternalDapiCommand(tags, false, _service.Rule34); - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] public Task Danbooru(params string[] tags) => InternalDapiCommand(tags, false, _service.Danbooru); - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] public Task Gelbooru(params string[] tags) => InternalDapiCommand(tags, false, _service.Gelbooru); - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] public Task Derpibooru(params string[] tags) => InternalDapiCommand(tags, false, _service.DerpiBooru); - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] public Task Safebooru(params string[] tags) => InternalDapiCommand(tags, false, _service.SafeBooru); - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] public async Task Boobs() { try @@ -273,8 +316,8 @@ public class NSFW : NadekoModule JToken obj; using (var http = _httpFactory.CreateClient()) { - obj = JArray.Parse(await http - .GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 12000)}"))[0]; + obj = JArray.Parse( + await http.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 12000)}"))[0]; } await ctx.Channel.SendMessageAsync($"http://media.oboobs.ru/{obj["preview"]}"); @@ -285,8 +328,10 @@ public class NSFW : NadekoModule } } - [NadekoCommand, Aliases] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [NadekoCommand] + [Aliases] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] public async Task Butts() { try @@ -294,8 +339,8 @@ public class NSFW : NadekoModule JToken obj; using (var http = _httpFactory.CreateClient()) { - obj = JArray.Parse(await http - .GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 6100)}"))[0]; + obj = JArray.Parse( + await http.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 6100)}"))[0]; } await ctx.Channel.SendMessageAsync($"http://media.obutts.ru/{obj["preview"]}"); @@ -306,7 +351,8 @@ public class NSFW : NadekoModule } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task NsfwTagBlacklist([Leftover] string tag = null) @@ -314,10 +360,7 @@ public class NSFW : NadekoModule if (string.IsNullOrWhiteSpace(tag)) { var blTags = await _service.GetBlacklistedTags(ctx.Guild.Id); - await SendConfirmAsync(GetText(strs.blacklisted_tag_list), - blTags.Any() - ? string.Join(", ", blTags) - : "-"); + await SendConfirmAsync(GetText(strs.blacklisted_tag_list), blTags.Any() ? string.Join(", ", blTags) : "-"); } else { @@ -331,9 +374,11 @@ public class NSFW : NadekoModule } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] [Priority(1)] public async Task Nhentai(uint id) { @@ -348,9 +393,11 @@ public class NSFW : NadekoModule await SendNhentaiGalleryInternalAsync(g); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] - [RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + [RequireNsfw(Group = "nsfw_or_dm")] + [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] [Priority(0)] public async Task Nhentai([Leftover] string query) { @@ -368,42 +415,43 @@ public class NSFW : NadekoModule private async Task SendNhentaiGalleryInternalAsync(Gallery g) { var count = 0; - var tagString = g.Tags - .Shuffle() - .Select(tag => $"[{tag.Name}]({tag.Url})") - .TakeWhile(tag => (count += tag.Length) < 1000) - .Join(" "); + var tagString = g.Tags.Shuffle() + .Select(tag => $"[{tag.Name}]({tag.Url})") + .TakeWhile(tag => (count += tag.Length) < 1000) + .Join(" "); var embed = _eb.Create() - .WithTitle(g.Title) - .WithDescription(g.FullTitle) - .WithImageUrl(g.Thumbnail) - .WithUrl(g.Url) - .AddField(GetText(strs.favorites), g.Likes, true) - .AddField(GetText(strs.pages), g.PageCount, true) - .AddField(GetText(strs.tags), tagString, true) - .WithFooter(g.UploadedAt.ToString("f")) - .WithOkColor(); + .WithTitle(g.Title) + .WithDescription(g.FullTitle) + .WithImageUrl(g.Thumbnail) + .WithUrl(g.Url) + .AddField(GetText(strs.favorites), g.Likes, true) + .AddField(GetText(strs.pages), g.PageCount, true) + .AddField(GetText(strs.tags), tagString, true) + .WithFooter(g.UploadedAt.ToString("f")) + .WithOkColor(); await ctx.Channel.EmbedAsync(embed); } - private async Task InternalDapiCommand(string[] tags, + private async Task InternalDapiCommand( + string[] tags, bool forceExplicit, Func> func) { var data = await func(ctx.Guild?.Id, forceExplicit, tags); - + if (data is null || !string.IsNullOrWhiteSpace(data.Error)) { await ReplyErrorLocalizedAsync(strs.no_results); return; } - await ctx.Channel.EmbedAsync(_eb - .Create(ctx) - .WithOkColor() - .WithImageUrl(data.Url) - .WithDescription($"[link]({data.Url})") - .WithFooter($"{data.Rating} ({data.Provider}) | {string.Join(" | ", data.Tags.Where(x => !string.IsNullOrWhiteSpace(x)).Take(5))}")); + + await ctx.Channel.EmbedAsync(_eb.Create(ctx) + .WithOkColor() + .WithImageUrl(data.Url) + .WithDescription($"[link]({data.Url})") + .WithFooter( + $"{data.Rating} ({data.Provider}) | {string.Join(" | ", data.Tags.Where(x => !string.IsNullOrWhiteSpace(x)).Take(5))}")); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/NsfwService.cs b/src/NadekoBot/Modules/Nsfw/NsfwService.cs index 74a5ca4ae..a58ae21e1 100644 --- a/src/NadekoBot/Modules/Nsfw/NsfwService.cs +++ b/src/NadekoBot/Modules/Nsfw/NsfwService.cs @@ -3,10 +3,8 @@ namespace NadekoBot.Modules.Nsfw; public interface INsfwService { - } - + public class NsfwService { - -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/SearchImageCacher.cs b/src/NadekoBot/Modules/Nsfw/SearchImageCacher.cs index 5c32c269d..2d0ef23f1 100644 --- a/src/NadekoBot/Modules/Nsfw/SearchImageCacher.cs +++ b/src/NadekoBot/Modules/Nsfw/SearchImageCacher.cs @@ -5,10 +5,7 @@ namespace NadekoBot.Modules.Nsfw.Common; public class SearchImageCacher : INService { - private readonly IHttpClientFactory _httpFactory; - private readonly Random _rng; - - private static readonly ISet defaultTagBlacklist = new HashSet() + private static readonly ISet defaultTagBlacklist = new HashSet { "loli", "lolicon", @@ -17,10 +14,15 @@ public class SearchImageCacher : INService "cub" }; + private readonly IHttpClientFactory _httpFactory; + private readonly Random _rng; + private readonly Dictionary _typeLocks = new(); private readonly Dictionary> _usedTags = new(); private readonly IMemoryCache _cache; + private readonly ConcurrentDictionary<(Booru, string), int> maxPages = new(); + public SearchImageCacher(IHttpClientFactory httpFactory, IMemoryCache cache) { _httpFactory = httpFactory; @@ -39,21 +41,23 @@ public class SearchImageCacher : INService => $"booru:{boory}__tag:{tag}"; /// - /// Download images of the specified type, and cache them. + /// Download images of the specified type, and cache them. /// /// Required tags /// Whether images will be forced to be explicit /// Provider type /// Cancellation token /// Whether any image is found. - private async Task UpdateImagesInternalAsync(string[] tags, bool forceExplicit, Booru type, CancellationToken cancel) + private async Task UpdateImagesInternalAsync( + string[] tags, + bool forceExplicit, + Booru type, + CancellationToken cancel) { var images = await DownloadImagesAsync(tags, forceExplicit, type, cancel); if (images is null || images.Count == 0) - { // Log.Warning("Got no images for {0}, tags: {1}", type, string.Join(", ", tags)); return false; - } Log.Information("Updating {0}...", type); lock (_typeLocks[type]) @@ -65,12 +69,7 @@ public class SearchImageCacher : INService // if user uses no tags for the hentai command and there are no used // tags atm, just select 50 random tags from downloaded images to seed if (typeUsedTags.Count == 0) - images.SelectMany(x => x.Tags) - .Distinct() - .Shuffle() - .Take(50) - .ToList() - .ForEach(x => typeUsedTags.Add(x)); + images.SelectMany(x => x.Tags).Distinct().Shuffle().Take(50).ToList().ForEach(x => typeUsedTags.Add(x)); foreach (var img in images) { @@ -78,7 +77,7 @@ public class SearchImageCacher : INService // do not put that image in the cache if (defaultTagBlacklist.Overlaps(img.Tags)) continue; - + // if image doesn't have a proper absolute uri, skip it if (!Uri.IsWellFormedUriString(img.FileUrl, UriKind.Absolute)) continue; @@ -88,26 +87,29 @@ public class SearchImageCacher : INService // both 'kiss' (real tag returned by the image) and 'kissing' will be populated with // retreived images foreach (var tag in img.Tags.Concat(tags).Distinct()) - { if (typeUsedTags.Contains(tag)) { - var set = _cache.GetOrCreate>(Key(type, tag), e => - { - e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); - return new(); - }); - - if(set.Count < 100) + var set = _cache.GetOrCreate>(Key(type, tag), + e => + { + e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); + return new(); + }); + + if (set.Count < 100) set.Add(img); } - } } } return true; } - private ImageData QueryLocal(string[] tags, bool forceExplicit, Booru type, HashSet blacklistedTags) + private ImageData QueryLocal( + string[] tags, + bool forceExplicit, + Booru type, + HashSet blacklistedTags) { var setList = new List>(); @@ -118,25 +120,18 @@ public class SearchImageCacher : INService if (tags.Length == 0) { // get all tags in the cache - if (_usedTags.TryGetValue(type, out var allTags) - && allTags.Count > 0) - { - tags = new[] {allTags.ToList()[_rng.Next(0, allTags.Count)]}; - } + if (_usedTags.TryGetValue(type, out var allTags) && allTags.Count > 0) + tags = new[] { allTags.ToList()[_rng.Next(0, allTags.Count)] }; else - { return null; - } } foreach (var tag in tags) - { // if any tag is missing from cache, that means there is no result if (_cache.TryGetValue>(Key(type, tag), out var set)) setList.Add(set); else return null; - } if (setList.Count == 0) return null; @@ -152,14 +147,11 @@ public class SearchImageCacher : INService // go through all other sets, and for (var i = 1; i < setList.Count; ++i) - { // if any of the elements in result set are not present in the current set // remove it from the result set resultSet.IntersectWith(setList[i]); - } resultList = resultSet.ToList(); - } else { @@ -173,30 +165,26 @@ public class SearchImageCacher : INService // if no items in the set -> not found if (resultList.Count == 0) return null; - + var toReturn = resultList[_rng.Next(0, resultList.Count)]; // remove from cache foreach (var tag in tags) - { if (_cache.TryGetValue>(Key(type, tag), out var items)) - { items.Remove(toReturn); - } - } return toReturn; } } - public async Task GetImageNew(string[] tags, bool forceExplicit, Booru type, - HashSet blacklistedTags, CancellationToken cancel) + public async Task GetImageNew( + string[] tags, + bool forceExplicit, + Booru type, + HashSet blacklistedTags, + CancellationToken cancel) { // make sure tags are proper - tags = tags - .Where(x => x is not null) - .Select(tag => tag.ToLowerInvariant().Trim()) - .Distinct() - .ToArray(); + tags = tags.Where(x => x is not null).Select(tag => tag.ToLowerInvariant().Trim()).Distinct().ToArray(); if (tags.Length > 2 && type == Booru.Danbooru) tags = tags[..2]; @@ -222,15 +210,17 @@ public class SearchImageCacher : INService if (!success) return default; - + image = QueryLocal(tags, forceExplicit, type, blacklistedTags); return image; } - private readonly ConcurrentDictionary<(Booru, string), int> maxPages = new(); - - public async Task> DownloadImagesAsync(string[] tags, bool isExplicit, Booru type, CancellationToken cancel) + public async Task> DownloadImagesAsync( + string[] tags, + bool isExplicit, + Booru type, + CancellationToken cancel) { var tagStr = string.Join(' ', tags.OrderByDescending(x => x)); var page = 0; @@ -257,7 +247,10 @@ public class SearchImageCacher : INService if (result is null or { Count: 0 }) { - Log.Information("Tag {0}, page {1} has no result on {2}.", string.Join(", ", tags), page, type.ToString()); + Log.Information("Tag {0}, page {1} has no result on {2}.", + string.Join(", ", tags), + page, + type.ToString()); continue; } @@ -282,7 +275,12 @@ public class SearchImageCacher : INService _ => throw new NotImplementedException($"{booru} downloader not implemented.") }; - private async Task> DownloadImagesAsync(string[] tags, bool isExplicit, Booru type, int page, CancellationToken cancel) + private async Task> DownloadImagesAsync( + string[] tags, + bool isExplicit, + Booru type, + int page, + CancellationToken cancel) { try { @@ -306,7 +304,8 @@ public class SearchImageCacher : INService } catch (Exception ex) { - Log.Error(ex, "Error downloading an image:\nTags: {0}\nType: {1}\nPage: {2}\nMessage: {3}", + Log.Error(ex, + "Error downloading an image:\nTags: {0}\nType: {1}\nPage: {2}\nMessage: {3}", string.Join(", ", tags), type, page, @@ -314,4 +313,4 @@ public class SearchImageCacher : INService return new(); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/SearchImagesService.cs b/src/NadekoBot/Modules/Nsfw/SearchImagesService.cs index 01d7186a8..c1ca1a25d 100644 --- a/src/NadekoBot/Modules/Nsfw/SearchImagesService.cs +++ b/src/NadekoBot/Modules/Nsfw/SearchImagesService.cs @@ -1,14 +1,18 @@ #nullable disable -using Newtonsoft.Json.Linq; using LinqToDB; +using NadekoBot.Modules.Nsfw.Common; using NadekoBot.Modules.Searches.Common; using Newtonsoft.Json; -using Booru = NadekoBot.Modules.Nsfw.Common.Booru; -using SearchImageCacher = NadekoBot.Modules.Nsfw.Common.SearchImageCacher; +using Newtonsoft.Json.Linq; namespace NadekoBot.Modules.Nsfw; -public record TagRequest(ulong GuildId, bool ForceExplicit, Booru SearchType, params string[] Tags); +public record TagRequest( + ulong GuildId, + bool ForceExplicit, + Booru SearchType, + params string[] Tags); + public record UrlReply { public string Error { get; init; } @@ -20,18 +24,21 @@ public record UrlReply public class SearchImagesService : ISearchImagesService, INService { - private readonly Random _rng; - private readonly HttpClient _http; - private readonly SearchImageCacher _cache; - private readonly IHttpClientFactory _httpFactory; - private readonly DbService _db; private ConcurrentDictionary> BlacklistedTags { get; } public ConcurrentDictionary AutoHentaiTimers { get; } = new(); public ConcurrentDictionary AutoBoobTimers { get; } = new(); public ConcurrentDictionary AutoButtTimers { get; } = new(); + private readonly Random _rng; + private readonly HttpClient _http; + private readonly SearchImageCacher _cache; + private readonly IHttpClientFactory _httpFactory; + private readonly DbService _db; - public SearchImagesService(DbService db, + private readonly object taglock = new(); + + public SearchImagesService( + DbService db, IHttpClientFactory http, SearchImageCacher cacher, IHttpClientFactory httpFactory) @@ -44,19 +51,21 @@ public class SearchImagesService : ISearchImagesService, INService _httpFactory = httpFactory; using var uow = db.GetDbContext(); - BlacklistedTags = new( - uow.NsfwBlacklistedTags - .AsEnumerable() - .GroupBy(x => x.GuildId) - .ToDictionary( - x => x.Key, - x => new HashSet(x.Select(x => x.Tag)))); + BlacklistedTags = new(uow.NsfwBlacklistedTags.AsEnumerable() + .GroupBy(x => x.GuildId) + .ToDictionary(x => x.Key, x => new HashSet(x.Select(x => x.Tag)))); } - private Task GetNsfwImageAsync(ulong? guildId, bool forceExplicit, string[] tags, Booru dapi, CancellationToken cancel = default) + private Task GetNsfwImageAsync( + ulong? guildId, + bool forceExplicit, + string[] tags, + Booru dapi, + CancellationToken cancel = default) => GetNsfwImageAsync(guildId ?? 0, tags ?? Array.Empty(), forceExplicit, dapi, cancel); - private bool IsValidTag(string tag) => tag.All(x => x != '+' && x != '?' && x != '/'); // tags mustn't contain + or ? or / + private bool IsValidTag(string tag) + => tag.All(x => x != '+' && x != '?' && x != '/'); // tags mustn't contain + or ? or / private async Task GetNsfwImageAsync( ulong guildId, @@ -66,64 +75,41 @@ public class SearchImagesService : ISearchImagesService, INService CancellationToken cancel) { if (!tags.All(x => IsValidTag(x))) - { - return new() - { - Error = "One or more tags are invalid.", - Url = "" - }; - } + return new() { Error = "One or more tags are invalid.", Url = "" }; Log.Information("Getting {V} image for Guild: {GuildId}...", dapi.ToString(), guildId); try { BlacklistedTags.TryGetValue(guildId, out var blTags); - if (dapi == Booru.E621) { + if (dapi == Booru.E621) for (var i = 0; i < tags.Length; ++i) if (tags[i] == "yuri") tags[i] = "female/female"; - } if (dapi == Booru.Derpibooru) - { for (var i = 0; i < tags.Length; ++i) if (tags[i] == "yuri") tags[i] = "lesbian"; - } var result = await _cache.GetImageNew(tags, forceExplicit, dapi, blTags ?? new HashSet(), cancel); if (result is null) - { - return new() - { - Error = "Image not found.", - Url = "" - }; - } + return new() { Error = "Image not found.", Url = "" }; var reply = new UrlReply { - Error = "", - Url = result.FileUrl, - Rating = result.Rating, - Provider = result.SearchType.ToString() + Error = "", Url = result.FileUrl, Rating = result.Rating, Provider = result.SearchType.ToString() }; reply.Tags.AddRange(result.Tags); return reply; - } catch (Exception ex) { Log.Error(ex, "Failed getting {Dapi} image: {Message}", dapi, ex.Message); - return new() - { - Error = ex.Message, - Url = "" - }; + return new() { Error = ex.Message, Url = "" }; } } @@ -150,18 +136,13 @@ public class SearchImagesService : ISearchImagesService, INService public Task SafeBooru(ulong? guildId, bool forceExplicit, string[] tags) => GetNsfwImageAsync(guildId, forceExplicit, tags, Booru.Safebooru); - + public Task Sankaku(ulong? guildId, bool forceExplicit, string[] tags) => GetNsfwImageAsync(guildId, forceExplicit, tags, Booru.Sankaku); public async Task Hentai(ulong? guildId, bool forceExplicit, string[] tags) { - var providers = new[] { - Booru.Danbooru, - Booru.Konachan, - Booru.Gelbooru, - Booru.Yandere - }; + var providers = new[] { Booru.Danbooru, Booru.Konachan, Booru.Gelbooru, Booru.Yandere }; using var cancelSource = new CancellationTokenSource(); @@ -174,7 +155,7 @@ public class SearchImagesService : ISearchImagesService, INService // get its result var result = task.GetAwaiter().GetResult(); - if(result.Error == "") + if (result.Error == "") { // if we have a non-error result, cancel other searches and return the result cancelSource.Cancel(); @@ -184,14 +165,10 @@ public class SearchImagesService : ISearchImagesService, INService // if the result is an error, remove that task from the waiting list, // and wait for another task to complete tasks.Remove(task); - } - while (tasks.Count > 0); // keep looping as long as there is any task remaining to be attempted + } while (tasks.Count > 0); // keep looping as long as there is any task remaining to be attempted // if we ran out of tasks, that means all tasks failed - return an error - return new() - { - Error = "No hentai image found." - }; + return new() { Error = "No hentai image found." }; } public async Task Boobs() @@ -200,24 +177,15 @@ public class SearchImagesService : ISearchImagesService, INService { JToken obj; obj = JArray.Parse(await _http.GetStringAsync($"http://api.oboobs.ru/boobs/{_rng.Next(0, 12000)}"))[0]; - return new() - { - Error = "", - Url = $"http://media.oboobs.ru/{obj["preview"]}", - }; + return new() { Error = "", Url = $"http://media.oboobs.ru/{obj["preview"]}" }; } catch (Exception ex) { Log.Error(ex, "Error retreiving boob image: {Message}", ex.Message); - return new() - { - Error = ex.Message, - Url = "", - }; + return new() { Error = ex.Message, Url = "" }; } } - private readonly object taglock = new(); public ValueTask ToggleBlacklistTag(ulong guildId, string tag) { lock (taglock) @@ -235,28 +203,20 @@ public class SearchImagesService : ISearchImagesService, INService } else { - uow.NsfwBlacklistedTags.Add(new() - { - Tag = tag, - GuildId = guildId - }); + uow.NsfwBlacklistedTags.Add(new() { Tag = tag, GuildId = guildId }); uow.SaveChanges(); } return new(isAdded); } - } public ValueTask GetBlacklistedTags(ulong guildId) { lock (taglock) { - if (BlacklistedTags.TryGetValue(guildId, out var tags)) - { - return new(tags.ToArray()); - } + if (BlacklistedTags.TryGetValue(guildId, out var tags)) return new(tags.ToArray()); return new(Array.Empty()); } @@ -268,24 +228,17 @@ public class SearchImagesService : ISearchImagesService, INService { JToken obj; obj = JArray.Parse(await _http.GetStringAsync($"http://api.obutts.ru/butts/{_rng.Next(0, 6100)}"))[0]; - return new() - { - Error = "", - Url = $"http://media.obutts.ru/{obj["preview"]}", - }; + return new() { Error = "", Url = $"http://media.obutts.ru/{obj["preview"]}" }; } catch (Exception ex) { Log.Error(ex, "Error retreiving butt image: {Message}", ex.Message); - return new() - { - Error = ex.Message, - Url = "", - }; + return new() { Error = ex.Message, Url = "" }; } } - + #region Nhentai + private string GetNhentaiExtensionInternal(string s) => s switch { @@ -294,15 +247,14 @@ public class SearchImagesService : ISearchImagesService, INService "g" => "gif", _ => "jpg" }; - + private Gallery ModelToGallery(NhentaiApiModel.Gallery model) { var thumbnail = $"https://t.nhentai.net/galleries/{model.MediaId}/thumb." + GetNhentaiExtensionInternal(model.Images.Thumbnail.T); var url = $"https://nhentai.net/g/{model.Id}"; - return new( - model.Id.ToString(), + return new(model.Id.ToString(), url, model.Title.English, model.Title.Pretty, @@ -310,11 +262,7 @@ public class SearchImagesService : ISearchImagesService, INService model.NumPages, model.NumFavorites, model.UploadDate.ToUnixTimestamp().UtcDateTime, - model.Tags.Map(x => new Tag() - { - Name = x.Name, - Url = "https://nhentai.com/" + x.Url - })); + model.Tags.Map(x => new Tag { Name = x.Name, Url = "https://nhentai.com/" + x.Url })); } private async Task GetNhentaiByIdInternalAsync(uint id) @@ -331,7 +279,7 @@ public class SearchImagesService : ISearchImagesService, INService return null; } } - + private async Task SearchNhentaiInternalAsync(string search) { using var http = _httpFactory.CreateClient(); @@ -346,7 +294,7 @@ public class SearchImagesService : ISearchImagesService, INService return null; } } - + public async Task GetNhentaiByIdAsync(uint id) { var model = await GetNhentaiByIdInternalAsync(id); @@ -354,25 +302,19 @@ public class SearchImagesService : ISearchImagesService, INService return ModelToGallery(model); } - private static readonly string[] _bannedTags = - { - "loli", - "lolicon", - "shota", - "shotacon", - "cub" - }; - + private static readonly string[] _bannedTags = { "loli", "lolicon", "shota", "shotacon", "cub" }; + public async Task GetNhentaiBySearchAsync(string search) { var models = await SearchNhentaiInternalAsync(search); models = models.Where(x => !x.Tags.Any(t => _bannedTags.Contains(t.Name))).ToArray(); - + if (models.Length == 0) return null; - + return ModelToGallery(models[_rng.Next(0, models.Length)]); } + #endregion -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/BlacklistCommands.cs b/src/NadekoBot/Modules/Permissions/BlacklistCommands.cs index 1a6854e97..2fc49ed3d 100644 --- a/src/NadekoBot/Modules/Permissions/BlacklistCommands.cs +++ b/src/NadekoBot/Modules/Permissions/BlacklistCommands.cs @@ -1,7 +1,6 @@ #nullable disable -using NadekoBot.Common.TypeReaders; -using NadekoBot.Services.Database.Models; using NadekoBot.Modules.Permissions.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Permissions; @@ -19,107 +18,113 @@ public partial class Permissions { if (page < 0) throw new ArgumentOutOfRangeException(nameof(page)); - - var list = _service.GetBlacklist(); - var items = await list - .Where(x => x.Type == type) - .Select(async i => - { - try - { - return i.Type switch - { - BlacklistType.Channel => Format.Code(i.ItemId.ToString()) - + " " + (_client.GetChannel(i.ItemId)?.ToString() ?? ""), - BlacklistType.User => Format.Code(i.ItemId.ToString()) - + " " + - ((await _client.Rest.GetUserAsync(i.ItemId))?.ToString() ?? ""), - BlacklistType.Server => Format.Code(i.ItemId.ToString()) - + " " + (_client.GetGuild(i.ItemId)?.ToString() ?? ""), - _ => Format.Code(i.ItemId.ToString()) - }; - } - catch - { - Log.Warning("Can't get {BlacklistType} [{BlacklistItemId}]", i.Type, i.ItemId); - return Format.Code(i.ItemId.ToString()); - } - }) - .WhenAll(); - await ctx.SendPaginatedConfirmAsync(page, (int curPage) => - { - var pageItems = items - .Skip(10 * curPage) - .Take(10) - .ToList(); - - if (pageItems.Count == 0) + var list = _service.GetBlacklist(); + var items = await list.Where(x => x.Type == type) + .Select(async i => + { + try + { + return i.Type switch + { + BlacklistType.Channel => Format.Code(i.ItemId.ToString()) + + " " + + (_client.GetChannel(i.ItemId)?.ToString() + ?? ""), + BlacklistType.User => Format.Code(i.ItemId.ToString()) + + " " + + ((await _client.Rest.GetUserAsync(i.ItemId)) + ?.ToString() + ?? ""), + BlacklistType.Server => Format.Code(i.ItemId.ToString()) + + " " + + (_client.GetGuild(i.ItemId)?.ToString() ?? ""), + _ => Format.Code(i.ItemId.ToString()) + }; + } + catch + { + Log.Warning("Can't get {BlacklistType} [{BlacklistItemId}]", + i.Type, + i.ItemId); + return Format.Code(i.ItemId.ToString()); + } + }) + .WhenAll(); + + await ctx.SendPaginatedConfirmAsync(page, + curPage => { - return _eb.Create() - .WithOkColor() - .WithTitle(title) - .WithDescription(GetText(strs.empty_page)); - } - - return _eb.Create() - .WithTitle(title) - .WithDescription(pageItems.Join('\n')) - .WithOkColor(); - }, items.Length, 10); + var pageItems = items.Skip(10 * curPage).Take(10).ToList(); + + if (pageItems.Count == 0) + return _eb.Create().WithOkColor().WithTitle(title).WithDescription(GetText(strs.empty_page)); + + return _eb.Create().WithTitle(title).WithDescription(pageItems.Join('\n')).WithOkColor(); + }, + items.Length, + 10); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [OwnerOnly] public Task UserBlacklist(int page = 1) { if (--page < 0) return Task.CompletedTask; - + return ListBlacklistInternal(GetText(strs.blacklisted_users), BlacklistType.User, page); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [OwnerOnly] public Task ChannelBlacklist(int page = 1) { if (--page < 0) return Task.CompletedTask; - + return ListBlacklistInternal(GetText(strs.blacklisted_channels), BlacklistType.Channel, page); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [OwnerOnly] public Task ServerBlacklist(int page = 1) { if (--page < 0) return Task.CompletedTask; - + return ListBlacklistInternal(GetText(strs.blacklisted_servers), BlacklistType.Server, page); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public Task UserBlacklist(AddRemove action, ulong id) => Blacklist(action, id, BlacklistType.User); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public Task UserBlacklist(AddRemove action, IUser usr) => Blacklist(action, usr.Id, BlacklistType.User); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public Task ChannelBlacklist(AddRemove action, ulong id) => Blacklist(action, id, BlacklistType.Channel); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public Task ServerBlacklist(AddRemove action, ulong id) => Blacklist(action, id, BlacklistType.Server); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public Task ServerBlacklist(AddRemove action, IGuild guild) => Blacklist(action, guild.Id, BlacklistType.Server); @@ -127,13 +132,9 @@ public partial class Permissions private async Task Blacklist(AddRemove action, ulong id, BlacklistType type) { if (action == AddRemove.Add) - { _service.Blacklist(type, id); - } else - { _service.UnBlacklist(type, id); - } if (action == AddRemove.Add) await ReplyConfirmLocalizedAsync(strs.blacklisted(Format.Code(type.ToString()), @@ -143,4 +144,4 @@ public partial class Permissions Format.Code(id.ToString()))); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/CmdCdsCommands.cs b/src/NadekoBot/Modules/Permissions/CmdCdsCommands.cs index a2ddf2f78..5a166f14c 100644 --- a/src/NadekoBot/Modules/Permissions/CmdCdsCommands.cs +++ b/src/NadekoBot/Modules/Permissions/CmdCdsCommands.cs @@ -1,9 +1,9 @@ #nullable disable using Microsoft.EntityFrameworkCore; -using NadekoBot.Services.Database.Models; using NadekoBot.Common.TypeReaders; using NadekoBot.Db; using NadekoBot.Modules.Permissions.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Permissions; @@ -12,21 +12,23 @@ public partial class Permissions [Group] public class CmdCdsCommands : NadekoSubmodule { - private readonly DbService _db; - private readonly CmdCdService _service; - private ConcurrentDictionary> CommandCooldowns => _service.CommandCooldowns; + private ConcurrentDictionary> ActiveCooldowns => _service.ActiveCooldowns; + private readonly DbService _db; + private readonly CmdCdService _service; + public CmdCdsCommands(CmdCdService service, DbService db) { _service = service; _db = db; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task CmdCooldown(CommandOrCrInfo command, int secs) { @@ -49,16 +51,14 @@ public partial class Permissions localSet.RemoveWhere(cc => cc.CommandName == name); if (secs != 0) { - var cc = new CommandCooldown() - { - CommandName = name, - Seconds = secs, - }; + var cc = new CommandCooldown { CommandName = name, Seconds = secs }; config.CommandCooldowns.Add(cc); localSet.Add(cc); } + await uow.SaveChangesAsync(); } + if (secs == 0) { var activeCds = ActiveCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); @@ -67,13 +67,12 @@ public partial class Permissions } else { - await ReplyConfirmLocalizedAsync(strs.cmdcd_add( - Format.Bold(name), - Format.Bold(secs.ToString()))); + await ReplyConfirmLocalizedAsync(strs.cmdcd_add(Format.Bold(name), Format.Bold(secs.ToString()))); } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task AllCmdCooldowns() { @@ -83,7 +82,10 @@ public partial class Permissions if (!localSet.Any()) await ReplyConfirmLocalizedAsync(strs.cmdcd_none); else - await channel.SendTableAsync("", localSet.Select(c => c.CommandName + ": " + c.Seconds + GetText(strs.sec)), s => $"{s,-30}", 2); + await channel.SendTableAsync("", + localSet.Select(c => c.CommandName + ": " + c.Seconds + GetText(strs.sec)), + s => $"{s,-30}", + 2); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/Common/PermissionCache.cs b/src/NadekoBot/Modules/Permissions/Common/PermissionCache.cs index 63bb3bb0a..806146457 100644 --- a/src/NadekoBot/Modules/Permissions/Common/PermissionCache.cs +++ b/src/NadekoBot/Modules/Permissions/Common/PermissionCache.cs @@ -8,4 +8,4 @@ public class PermissionCache public string PermRole { get; set; } public bool Verbose { get; set; } = true; public PermissionsCollection Permissions { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/Common/PermissionExtensions.cs b/src/NadekoBot/Modules/Permissions/Common/PermissionExtensions.cs index 59929ad2e..f0c652cf8 100644 --- a/src/NadekoBot/Modules/Permissions/Common/PermissionExtensions.cs +++ b/src/NadekoBot/Modules/Permissions/Common/PermissionExtensions.cs @@ -5,8 +5,12 @@ namespace NadekoBot.Modules.Permissions.Common; public static class PermissionExtensions { - public static bool CheckPermissions(this IEnumerable permsEnumerable, IUserMessage message, - string commandName, string moduleName, out int permIndex) + public static bool CheckPermissions( + this IEnumerable permsEnumerable, + IUserMessage message, + string commandName, + string moduleName, + out int permIndex) { var perms = permsEnumerable as List ?? permsEnumerable.ToList(); @@ -16,13 +20,11 @@ public static class PermissionExtensions var result = perm.CheckPermission(message, commandName, moduleName); - if (result is null) - { - continue; - } + if (result is null) continue; permIndex = i; return result.Value; } + permIndex = -1; //defaut behaviour return true; } @@ -30,13 +32,17 @@ public static class PermissionExtensions //null = not applicable //true = applicable, allowed //false = applicable, not allowed - public static bool? CheckPermission(this Permissionv2 perm, IUserMessage message, string commandName, string moduleName) + public static bool? CheckPermission( + this Permissionv2 perm, + IUserMessage message, + string commandName, + string moduleName) { - if (!((perm.SecondaryTarget == SecondaryPermissionType.Command && - perm.SecondaryTargetName.ToLowerInvariant() == commandName.ToLowerInvariant()) || - (perm.SecondaryTarget == SecondaryPermissionType.Module && - perm.SecondaryTargetName.ToLowerInvariant() == moduleName.ToLowerInvariant()) || - perm.SecondaryTarget == SecondaryPermissionType.AllModules)) + if (!((perm.SecondaryTarget == SecondaryPermissionType.Command + && perm.SecondaryTargetName.ToLowerInvariant() == commandName.ToLowerInvariant()) + || (perm.SecondaryTarget == SecondaryPermissionType.Module + && perm.SecondaryTargetName.ToLowerInvariant() == moduleName.ToLowerInvariant()) + || perm.SecondaryTarget == SecondaryPermissionType.AllModules)) return null; var guildUser = message.Author as IGuildUser; @@ -51,7 +57,7 @@ public static class PermissionExtensions if (perm.PrimaryTargetId == message.Channel.Id) return perm.State; break; - case PrimaryPermissionType.Role: + case PrimaryPermissionType.Role: if (guildUser is null) break; if (guildUser.RoleIds.Contains(perm.PrimaryTargetId)) @@ -62,6 +68,7 @@ public static class PermissionExtensions break; return perm.State; } + return null; } @@ -97,8 +104,9 @@ public static class PermissionExtensions break; } - var secName = perm.SecondaryTarget == SecondaryPermissionType.Command && !perm.IsCustomCommand ? - prefix + perm.SecondaryTargetName : perm.SecondaryTargetName; + var secName = perm.SecondaryTarget == SecondaryPermissionType.Command && !perm.IsCustomCommand + ? prefix + perm.SecondaryTargetName + : perm.SecondaryTargetName; com += " " + (perm.SecondaryTargetName != "*" ? secName + " " : "") + (perm.State ? "enable" : "disable") + " "; switch (perm.PrimaryTarget) @@ -118,4 +126,4 @@ public static class PermissionExtensions return prefix + com; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/Common/PermissionsCollection.cs b/src/NadekoBot/Modules/Permissions/Common/PermissionsCollection.cs index eb1fc4d10..d80b590a2 100644 --- a/src/NadekoBot/Modules/Permissions/Common/PermissionsCollection.cs +++ b/src/NadekoBot/Modules/Permissions/Common/PermissionsCollection.cs @@ -4,15 +4,32 @@ using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Permissions.Common; -public class PermissionsCollection : IndexedCollection where T : class, IIndexed +public class PermissionsCollection : IndexedCollection + where T : class, IIndexed { + public override T this[int index] + { + get => Source[index]; + set + { + lock (_localLocker) + { + if (index == 0) // can't set first element. It's always allow all + throw new IndexOutOfRangeException(nameof(index)); + base[index] = value; + } + } + } + private readonly object _localLocker = new(); - public PermissionsCollection(IEnumerable source) : base(source) + + public PermissionsCollection(IEnumerable source) + : base(source) { } - public static implicit operator List(PermissionsCollection x) => - x.Source; + public static implicit operator List(PermissionsCollection x) + => x.Source; public override void Clear() { @@ -29,10 +46,11 @@ public class PermissionsCollection : IndexedCollection where T : class, II bool removed; lock (_localLocker) { - if(Source.IndexOf(item) == 0) + if (Source.IndexOf(item) == 0) throw new ArgumentException("You can't remove first permsission (allow all)"); removed = base.Remove(item); } + return removed; } @@ -40,7 +58,7 @@ public class PermissionsCollection : IndexedCollection where T : class, II { lock (_localLocker) { - if(index == 0) // can't insert on first place. Last item is always allow all. + if (index == 0) // can't insert on first place. Last item is always allow all. throw new IndexOutOfRangeException(nameof(index)); base.Insert(index, item); } @@ -50,22 +68,10 @@ public class PermissionsCollection : IndexedCollection where T : class, II { lock (_localLocker) { - if(index == 0) // you can't remove first permission (allow all) + if (index == 0) // you can't remove first permission (allow all) throw new IndexOutOfRangeException(nameof(index)); base.RemoveAt(index); } } - - public override T this[int index] { - get => Source[index]; - set { - lock (_localLocker) - { - if(index == 0) // can't set first element. It's always allow all - throw new IndexOutOfRangeException(nameof(index)); - base[index] = value; - } - } - } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/FilterCommands.cs b/src/NadekoBot/Modules/Permissions/FilterCommands.cs index 8c1f30623..3b5d09644 100644 --- a/src/NadekoBot/Modules/Permissions/FilterCommands.cs +++ b/src/NadekoBot/Modules/Permissions/FilterCommands.cs @@ -1,8 +1,8 @@ #nullable disable using Microsoft.EntityFrameworkCore; +using NadekoBot.Db; using NadekoBot.Modules.Permissions.Services; using NadekoBot.Services.Database.Models; -using NadekoBot.Db; namespace NadekoBot.Modules.Permissions; @@ -16,7 +16,8 @@ public partial class Permissions public FilterCommands(DbService db) => _db = db; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task FwClear() @@ -25,7 +26,8 @@ public partial class Permissions await ReplyConfirmLocalizedAsync(strs.fw_cleared); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task SrvrFilterInv() { @@ -51,7 +53,8 @@ public partial class Permissions } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task ChnlFilterInv() { @@ -60,21 +63,15 @@ public partial class Permissions FilterChannelId removed; await using (var uow = _db.GetDbContext()) { - var config = uow.GuildConfigsForId(channel.Guild.Id, set => set.Include(gc => gc.FilterInvitesChannelIds)); - var match = new FilterChannelId() - { - ChannelId = channel.Id - }; + var config = uow.GuildConfigsForId(channel.Guild.Id, + set => set.Include(gc => gc.FilterInvitesChannelIds)); + var match = new FilterChannelId { ChannelId = channel.Id }; removed = config.FilterInvitesChannelIds.FirstOrDefault(fc => fc.Equals(match)); if (removed is null) - { config.FilterInvitesChannelIds.Add(match); - } else - { uow.Remove(removed); - } await uow.SaveChangesAsync(); } @@ -90,7 +87,8 @@ public partial class Permissions } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task SrvrFilterLin() { @@ -116,7 +114,8 @@ public partial class Permissions } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task ChnlFilterLin() { @@ -125,21 +124,15 @@ public partial class Permissions FilterLinksChannelId removed; await using (var uow = _db.GetDbContext()) { - var config = uow.GuildConfigsForId(channel.Guild.Id, set => set.Include(gc => gc.FilterLinksChannelIds)); - var match = new FilterLinksChannelId() - { - ChannelId = channel.Id - }; + var config = + uow.GuildConfigsForId(channel.Guild.Id, set => set.Include(gc => gc.FilterLinksChannelIds)); + var match = new FilterLinksChannelId { ChannelId = channel.Id }; removed = config.FilterLinksChannelIds.FirstOrDefault(fc => fc.Equals(match)); if (removed is null) - { config.FilterLinksChannelIds.Add(match); - } else - { uow.Remove(removed); - } await uow.SaveChangesAsync(); } @@ -155,7 +148,8 @@ public partial class Permissions } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task SrvrFilterWords() { @@ -181,7 +175,8 @@ public partial class Permissions } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task ChnlFilterWords() { @@ -190,21 +185,15 @@ public partial class Permissions FilterChannelId removed; await using (var uow = _db.GetDbContext()) { - var config = uow.GuildConfigsForId(channel.Guild.Id, set => set.Include(gc => gc.FilterWordsChannelIds)); + var config = + uow.GuildConfigsForId(channel.Guild.Id, set => set.Include(gc => gc.FilterWordsChannelIds)); - var match = new FilterChannelId() - { - ChannelId = channel.Id - }; + var match = new FilterChannelId { ChannelId = channel.Id }; removed = config.FilterWordsChannelIds.FirstOrDefault(fc => fc.Equals(match)); if (removed is null) - { config.FilterWordsChannelIds.Add(match); - } else - { uow.Remove(removed); - } await uow.SaveChangesAsync(); } @@ -220,7 +209,8 @@ public partial class Permissions } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task FilterWord([Leftover] string word) { @@ -241,14 +231,13 @@ public partial class Permissions if (removed is null) config.FilteredWords.Add(new() { Word = word }); else - { uow.Remove(removed); - } await uow.SaveChangesAsync(); } - var filteredWords = _service.ServerFilteredWords.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); + var filteredWords = + _service.ServerFilteredWords.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); if (removed is null) { @@ -262,7 +251,8 @@ public partial class Permissions } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task LstFilterWords(int page = 1) { @@ -278,10 +268,11 @@ public partial class Permissions await ctx.SendPaginatedConfirmAsync(page, curPage => _eb.Create() - .WithTitle(GetText(strs.filter_word_list)) - .WithDescription(string.Join("\n", fws.Skip(curPage * 10).Take(10))) - .WithOkColor() - , fws.Length, 10); + .WithTitle(GetText(strs.filter_word_list)) + .WithDescription(string.Join("\n", fws.Skip(curPage * 10).Take(10))) + .WithOkColor(), + fws.Length, + 10); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/GlobalPermissionCommands.cs b/src/NadekoBot/Modules/Permissions/GlobalPermissionCommands.cs index 776e16dac..ba86d1549 100644 --- a/src/NadekoBot/Modules/Permissions/GlobalPermissionCommands.cs +++ b/src/NadekoBot/Modules/Permissions/GlobalPermissionCommands.cs @@ -18,7 +18,8 @@ public partial class Permissions _db = db; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task GlobalPermList() { @@ -33,49 +34,47 @@ public partial class Permissions var embed = _eb.Create().WithOkColor(); if (blockedModule.Any()) - embed.AddField(GetText(strs.blocked_modules) - , string.Join("\n", _service.BlockedModules) - , false); + embed.AddField(GetText(strs.blocked_modules), string.Join("\n", _service.BlockedModules)); if (blockedCommands.Any()) - embed.AddField(GetText(strs.blocked_commands) - , string.Join("\n", _service.BlockedCommands) - , false); + embed.AddField(GetText(strs.blocked_commands), string.Join("\n", _service.BlockedCommands)); await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task GlobalModule(ModuleOrCrInfo module) { var moduleName = module.Name.ToLowerInvariant(); var added = _service.ToggleModule(moduleName); - + if (added) { await ReplyConfirmLocalizedAsync(strs.gmod_add(Format.Bold(module.Name))); return; } - + await ReplyConfirmLocalizedAsync(strs.gmod_remove(Format.Bold(module.Name))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task GlobalCommand(CommandOrCrInfo cmd) { var commandName = cmd.Name.ToLowerInvariant(); var added = _service.ToggleCommand(commandName); - + if (added) { await ReplyConfirmLocalizedAsync(strs.gcmd_add(Format.Bold(cmd.Name))); return; } - + await ReplyConfirmLocalizedAsync(strs.gcmd_remove(Format.Bold(cmd.Name))); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/Permissions.cs b/src/NadekoBot/Modules/Permissions/Permissions.cs index 6998d644b..4ffe72d5b 100644 --- a/src/NadekoBot/Modules/Permissions/Permissions.cs +++ b/src/NadekoBot/Modules/Permissions/Permissions.cs @@ -1,21 +1,24 @@ #nullable disable -using NadekoBot.Services.Database.Models; using NadekoBot.Common.TypeReaders; using NadekoBot.Common.TypeReaders.Models; using NadekoBot.Db; using NadekoBot.Modules.Permissions.Common; using NadekoBot.Modules.Permissions.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Permissions; public partial class Permissions : NadekoModule { + public enum Reset { Reset } + private readonly DbService _db; public Permissions(DbService db) => _db = db; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Verbose(PermissionAction action = null) { @@ -27,17 +30,15 @@ public partial class Permissions : NadekoModule await uow.SaveChangesAsync(); _service.UpdateCache(config); } + if (action.Value) - { await ReplyConfirmLocalizedAsync(strs.verbose_true); - } else - { await ReplyConfirmLocalizedAsync(strs.verbose_false); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Priority(0)] @@ -45,19 +46,15 @@ public partial class Permissions : NadekoModule { if (role != null && role == role.Guild.EveryoneRole) return; - + if (role is null) { var cache = _service.GetCacheFor(ctx.Guild.Id); - if (!ulong.TryParse(cache.PermRole, out var roleId) || - (role = ((SocketGuild)ctx.Guild).GetRole(roleId)) is null) - { + if (!ulong.TryParse(cache.PermRole, out var roleId) + || (role = ((SocketGuild)ctx.Guild).GetRole(roleId)) is null) await ReplyConfirmLocalizedAsync(strs.permrole_not_set); - } else - { await ReplyConfirmLocalizedAsync(strs.permrole(Format.Bold(role.ToString()))); - } return; } @@ -72,9 +69,8 @@ public partial class Permissions : NadekoModule await ReplyConfirmLocalizedAsync(strs.permrole_changed(Format.Bold(role.Name))); } - public enum Reset { Reset }; - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Priority(1)] @@ -91,7 +87,8 @@ public partial class Permissions : NadekoModule await ReplyConfirmLocalizedAsync(strs.permrole_reset); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task ListPerms(int page = 1) { @@ -101,32 +98,31 @@ public partial class Permissions : NadekoModule IList perms; if (_service.Cache.TryGetValue(ctx.Guild.Id, out var permCache)) - { perms = permCache.Permissions.Source.ToList(); - } else - { perms = Permissionv2.GetDefaultPermlist; - } var startPos = 20 * (page - 1); - var toSend = Format.Bold(GetText(strs.page(page))) + "\n\n" + string.Join("\n", - perms.Reverse() - .Skip(startPos) - .Take(20) - .Select(p => - { - var str = - $"`{p.Index + 1}.` {Format.Bold(p.GetCommand(Prefix, (SocketGuild)ctx.Guild))}"; - if (p.Index == 0) - str += $" [{GetText(strs.uneditable)}]"; - return str; - })); + var toSend = Format.Bold(GetText(strs.page(page))) + + "\n\n" + + string.Join("\n", + perms.Reverse() + .Skip(startPos) + .Take(20) + .Select(p => + { + var str = + $"`{p.Index + 1}.` {Format.Bold(p.GetCommand(Prefix, (SocketGuild)ctx.Guild))}"; + if (p.Index == 0) + str += $" [{GetText(strs.uneditable)}]"; + return str; + })); await ctx.Channel.SendMessageAsync(toSend); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task RemovePerm(int index) { @@ -147,8 +143,7 @@ public partial class Permissions : NadekoModule _service.UpdateCache(config); } - await ReplyConfirmLocalizedAsync(strs.removed( - index + 1, + await ReplyConfirmLocalizedAsync(strs.removed(index + 1, Format.Code(p.GetCommand(Prefix, (SocketGuild)ctx.Guild)))); } catch (IndexOutOfRangeException) @@ -157,14 +152,14 @@ public partial class Permissions : NadekoModule } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task MovePerm(int from, int to) { from -= 1; to -= 1; if (!(from == to || from < 0 || to < 0)) - { try { Permissionv2 fromPerm; @@ -187,6 +182,7 @@ public partial class Permissions : NadekoModule await ReplyErrorLocalizedAsync(strs.perm_not_found(++to)); return; } + fromPerm = permsCol[from]; permsCol.RemoveAt(from); @@ -199,334 +195,287 @@ public partial class Permissions : NadekoModule Format.Code(fromPerm.GetCommand(Prefix, (SocketGuild)ctx.Guild)), ++from, ++to)); - + return; } catch (Exception e) when (e is ArgumentOutOfRangeException or IndexOutOfRangeException) { } - } + await ReplyConfirmLocalizedAsync(strs.perm_out_of_range); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task SrvrCmd(CommandOrCrInfo command, PermissionAction action) { - await _service.AddPermissions(ctx.Guild.Id, new Permissionv2 - { - PrimaryTarget = PrimaryPermissionType.Server, - PrimaryTargetId = 0, - SecondaryTarget = SecondaryPermissionType.Command, - SecondaryTargetName = command.Name.ToLowerInvariant(), - State = action.Value, - IsCustomCommand = command.IsCustom, - }); + await _service.AddPermissions(ctx.Guild.Id, + new Permissionv2 + { + PrimaryTarget = PrimaryPermissionType.Server, + PrimaryTargetId = 0, + SecondaryTarget = SecondaryPermissionType.Command, + SecondaryTargetName = command.Name.ToLowerInvariant(), + State = action.Value, + IsCustomCommand = command.IsCustom + }); if (action.Value) - { - await ReplyConfirmLocalizedAsync(strs.sx_enable( - Format.Code(command.Name), - GetText(strs.of_command))); - } + await ReplyConfirmLocalizedAsync(strs.sx_enable(Format.Code(command.Name), GetText(strs.of_command))); else - { - await ReplyConfirmLocalizedAsync(strs.sx_disable( - Format.Code(command.Name), - GetText(strs.of_command))); - } + await ReplyConfirmLocalizedAsync(strs.sx_disable(Format.Code(command.Name), GetText(strs.of_command))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task SrvrMdl(ModuleOrCrInfo module, PermissionAction action) { - await _service.AddPermissions(ctx.Guild.Id, new Permissionv2 - { - PrimaryTarget = PrimaryPermissionType.Server, - PrimaryTargetId = 0, - SecondaryTarget = SecondaryPermissionType.Module, - SecondaryTargetName = module.Name.ToLowerInvariant(), - State = action.Value, - }); + await _service.AddPermissions(ctx.Guild.Id, + new Permissionv2 + { + PrimaryTarget = PrimaryPermissionType.Server, + PrimaryTargetId = 0, + SecondaryTarget = SecondaryPermissionType.Module, + SecondaryTargetName = module.Name.ToLowerInvariant(), + State = action.Value + }); if (action.Value) - { - await ReplyConfirmLocalizedAsync(strs.sx_enable( - Format.Code(module.Name), - GetText(strs.of_module))); - } + await ReplyConfirmLocalizedAsync(strs.sx_enable(Format.Code(module.Name), GetText(strs.of_module))); else - { - await ReplyConfirmLocalizedAsync(strs.sx_disable( - Format.Code(module.Name), - GetText(strs.of_module))); - } + await ReplyConfirmLocalizedAsync(strs.sx_disable(Format.Code(module.Name), GetText(strs.of_module))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task UsrCmd(CommandOrCrInfo command, PermissionAction action, [Leftover] IGuildUser user) { - await _service.AddPermissions(ctx.Guild.Id, new Permissionv2 - { - PrimaryTarget = PrimaryPermissionType.User, - PrimaryTargetId = user.Id, - SecondaryTarget = SecondaryPermissionType.Command, - SecondaryTargetName = command.Name.ToLowerInvariant(), - State = action.Value, - IsCustomCommand = command.IsCustom, - }); + await _service.AddPermissions(ctx.Guild.Id, + new Permissionv2 + { + PrimaryTarget = PrimaryPermissionType.User, + PrimaryTargetId = user.Id, + SecondaryTarget = SecondaryPermissionType.Command, + SecondaryTargetName = command.Name.ToLowerInvariant(), + State = action.Value, + IsCustomCommand = command.IsCustom + }); if (action.Value) - { - await ReplyConfirmLocalizedAsync(strs.ux_enable( - Format.Code(command.Name), + await ReplyConfirmLocalizedAsync(strs.ux_enable(Format.Code(command.Name), GetText(strs.of_command), Format.Code(user.ToString()))); - } else - { - await ReplyConfirmLocalizedAsync(strs.ux_disable( - Format.Code(command.Name), + await ReplyConfirmLocalizedAsync(strs.ux_disable(Format.Code(command.Name), GetText(strs.of_command), Format.Code(user.ToString()))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task UsrMdl(ModuleOrCrInfo module, PermissionAction action, [Leftover] IGuildUser user) { - await _service.AddPermissions(ctx.Guild.Id, new Permissionv2 - { - PrimaryTarget = PrimaryPermissionType.User, - PrimaryTargetId = user.Id, - SecondaryTarget = SecondaryPermissionType.Module, - SecondaryTargetName = module.Name.ToLowerInvariant(), - State = action.Value, - }); + await _service.AddPermissions(ctx.Guild.Id, + new Permissionv2 + { + PrimaryTarget = PrimaryPermissionType.User, + PrimaryTargetId = user.Id, + SecondaryTarget = SecondaryPermissionType.Module, + SecondaryTargetName = module.Name.ToLowerInvariant(), + State = action.Value + }); if (action.Value) - { - await ReplyConfirmLocalizedAsync(strs.ux_enable( - Format.Code(module.Name), + await ReplyConfirmLocalizedAsync(strs.ux_enable(Format.Code(module.Name), GetText(strs.of_module), Format.Code(user.ToString()))); - } else - { - await ReplyConfirmLocalizedAsync(strs.ux_disable( - Format.Code(module.Name), + await ReplyConfirmLocalizedAsync(strs.ux_disable(Format.Code(module.Name), GetText(strs.of_module), Format.Code(user.ToString()))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task RoleCmd(CommandOrCrInfo command, PermissionAction action, [Leftover] IRole role) { if (role == role.Guild.EveryoneRole) return; - await _service.AddPermissions(ctx.Guild.Id, new Permissionv2 - { - PrimaryTarget = PrimaryPermissionType.Role, - PrimaryTargetId = role.Id, - SecondaryTarget = SecondaryPermissionType.Command, - SecondaryTargetName = command.Name.ToLowerInvariant(), - State = action.Value, - IsCustomCommand = command.IsCustom, - }); + await _service.AddPermissions(ctx.Guild.Id, + new Permissionv2 + { + PrimaryTarget = PrimaryPermissionType.Role, + PrimaryTargetId = role.Id, + SecondaryTarget = SecondaryPermissionType.Command, + SecondaryTargetName = command.Name.ToLowerInvariant(), + State = action.Value, + IsCustomCommand = command.IsCustom + }); if (action.Value) - { - await ReplyConfirmLocalizedAsync(strs.rx_enable( - Format.Code(command.Name), + await ReplyConfirmLocalizedAsync(strs.rx_enable(Format.Code(command.Name), GetText(strs.of_command), Format.Code(role.Name))); - } else - { - await ReplyConfirmLocalizedAsync(strs.rx_disable( - Format.Code(command.Name), + await ReplyConfirmLocalizedAsync(strs.rx_disable(Format.Code(command.Name), GetText(strs.of_command), Format.Code(role.Name))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task RoleMdl(ModuleOrCrInfo module, PermissionAction action, [Leftover] IRole role) { if (role == role.Guild.EveryoneRole) return; - await _service.AddPermissions(ctx.Guild.Id, new Permissionv2 - { - PrimaryTarget = PrimaryPermissionType.Role, - PrimaryTargetId = role.Id, - SecondaryTarget = SecondaryPermissionType.Module, - SecondaryTargetName = module.Name.ToLowerInvariant(), - State = action.Value, - }); + await _service.AddPermissions(ctx.Guild.Id, + new Permissionv2 + { + PrimaryTarget = PrimaryPermissionType.Role, + PrimaryTargetId = role.Id, + SecondaryTarget = SecondaryPermissionType.Module, + SecondaryTargetName = module.Name.ToLowerInvariant(), + State = action.Value + }); if (action.Value) - { - await ReplyConfirmLocalizedAsync(strs.rx_enable( - Format.Code(module.Name), + await ReplyConfirmLocalizedAsync(strs.rx_enable(Format.Code(module.Name), GetText(strs.of_module), Format.Code(role.Name))); - } else - { - await ReplyConfirmLocalizedAsync(strs.rx_disable( - Format.Code(module.Name), + await ReplyConfirmLocalizedAsync(strs.rx_disable(Format.Code(module.Name), GetText(strs.of_module), Format.Code(role.Name))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task ChnlCmd(CommandOrCrInfo command, PermissionAction action, [Leftover] ITextChannel chnl) { - await _service.AddPermissions(ctx.Guild.Id, new Permissionv2 - { - PrimaryTarget = PrimaryPermissionType.Channel, - PrimaryTargetId = chnl.Id, - SecondaryTarget = SecondaryPermissionType.Command, - SecondaryTargetName = command.Name.ToLowerInvariant(), - State = action.Value, - IsCustomCommand = command.IsCustom, - }); + await _service.AddPermissions(ctx.Guild.Id, + new Permissionv2 + { + PrimaryTarget = PrimaryPermissionType.Channel, + PrimaryTargetId = chnl.Id, + SecondaryTarget = SecondaryPermissionType.Command, + SecondaryTargetName = command.Name.ToLowerInvariant(), + State = action.Value, + IsCustomCommand = command.IsCustom + }); if (action.Value) - { - await ReplyConfirmLocalizedAsync(strs.cx_enable( - Format.Code(command.Name), + await ReplyConfirmLocalizedAsync(strs.cx_enable(Format.Code(command.Name), GetText(strs.of_command), Format.Code(chnl.Name))); - } else - { - await ReplyConfirmLocalizedAsync(strs.cx_disable( - Format.Code(command.Name), + await ReplyConfirmLocalizedAsync(strs.cx_disable(Format.Code(command.Name), GetText(strs.of_command), Format.Code(chnl.Name))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task ChnlMdl(ModuleOrCrInfo module, PermissionAction action, [Leftover] ITextChannel chnl) { - await _service.AddPermissions(ctx.Guild.Id, new Permissionv2 - { - PrimaryTarget = PrimaryPermissionType.Channel, - PrimaryTargetId = chnl.Id, - SecondaryTarget = SecondaryPermissionType.Module, - SecondaryTargetName = module.Name.ToLowerInvariant(), - State = action.Value, - }); + await _service.AddPermissions(ctx.Guild.Id, + new Permissionv2 + { + PrimaryTarget = PrimaryPermissionType.Channel, + PrimaryTargetId = chnl.Id, + SecondaryTarget = SecondaryPermissionType.Module, + SecondaryTargetName = module.Name.ToLowerInvariant(), + State = action.Value + }); if (action.Value) - { - await ReplyConfirmLocalizedAsync(strs.cx_enable( - Format.Code(module.Name), + await ReplyConfirmLocalizedAsync(strs.cx_enable(Format.Code(module.Name), GetText(strs.of_module), Format.Code(chnl.Name))); - } else - { - await ReplyConfirmLocalizedAsync(strs.cx_disable( - Format.Code(module.Name), + await ReplyConfirmLocalizedAsync(strs.cx_disable(Format.Code(module.Name), GetText(strs.of_module), Format.Code(chnl.Name))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task AllChnlMdls(PermissionAction action, [Leftover] ITextChannel chnl) { - await _service.AddPermissions(ctx.Guild.Id, new Permissionv2 - { - PrimaryTarget = PrimaryPermissionType.Channel, - PrimaryTargetId = chnl.Id, - SecondaryTarget = SecondaryPermissionType.AllModules, - SecondaryTargetName = "*", - State = action.Value, - }); + await _service.AddPermissions(ctx.Guild.Id, + new Permissionv2 + { + PrimaryTarget = PrimaryPermissionType.Channel, + PrimaryTargetId = chnl.Id, + SecondaryTarget = SecondaryPermissionType.AllModules, + SecondaryTargetName = "*", + State = action.Value + }); if (action.Value) - { - await ReplyConfirmLocalizedAsync(strs.acm_enable( - Format.Code(chnl.Name))); - } + await ReplyConfirmLocalizedAsync(strs.acm_enable(Format.Code(chnl.Name))); else - { - await ReplyConfirmLocalizedAsync(strs.acm_disable( - Format.Code(chnl.Name))); - } + await ReplyConfirmLocalizedAsync(strs.acm_disable(Format.Code(chnl.Name))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task AllRoleMdls(PermissionAction action, [Leftover] IRole role) { if (role == role.Guild.EveryoneRole) return; - await _service.AddPermissions(ctx.Guild.Id, new Permissionv2 - { - PrimaryTarget = PrimaryPermissionType.Role, - PrimaryTargetId = role.Id, - SecondaryTarget = SecondaryPermissionType.AllModules, - SecondaryTargetName = "*", - State = action.Value, - }); + await _service.AddPermissions(ctx.Guild.Id, + new Permissionv2 + { + PrimaryTarget = PrimaryPermissionType.Role, + PrimaryTargetId = role.Id, + SecondaryTarget = SecondaryPermissionType.AllModules, + SecondaryTargetName = "*", + State = action.Value + }); if (action.Value) - { - await ReplyConfirmLocalizedAsync(strs.arm_enable( - Format.Code(role.Name))); - } + await ReplyConfirmLocalizedAsync(strs.arm_enable(Format.Code(role.Name))); else - { - await ReplyConfirmLocalizedAsync(strs.arm_disable( - Format.Code(role.Name))); - } + await ReplyConfirmLocalizedAsync(strs.arm_disable(Format.Code(role.Name))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task AllUsrMdls(PermissionAction action, [Leftover] IUser user) { - await _service.AddPermissions(ctx.Guild.Id, new Permissionv2 - { - PrimaryTarget = PrimaryPermissionType.User, - PrimaryTargetId = user.Id, - SecondaryTarget = SecondaryPermissionType.AllModules, - SecondaryTargetName = "*", - State = action.Value, - }); + await _service.AddPermissions(ctx.Guild.Id, + new Permissionv2 + { + PrimaryTarget = PrimaryPermissionType.User, + PrimaryTargetId = user.Id, + SecondaryTarget = SecondaryPermissionType.AllModules, + SecondaryTargetName = "*", + State = action.Value + }); if (action.Value) - { - await ReplyConfirmLocalizedAsync(strs.aum_enable( - Format.Code(user.ToString()))); - } + await ReplyConfirmLocalizedAsync(strs.aum_enable(Format.Code(user.ToString()))); else - { - await ReplyConfirmLocalizedAsync(strs.aum_disable( - Format.Code(user.ToString()))); - } + await ReplyConfirmLocalizedAsync(strs.aum_disable(Format.Code(user.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task AllSrvrMdls(PermissionAction action) { @@ -536,7 +485,7 @@ public partial class Permissions : NadekoModule PrimaryTargetId = 0, SecondaryTarget = SecondaryPermissionType.AllModules, SecondaryTargetName = "*", - State = action.Value, + State = action.Value }; var allowUser = new Permissionv2 @@ -545,20 +494,14 @@ public partial class Permissions : NadekoModule PrimaryTargetId = ctx.User.Id, SecondaryTarget = SecondaryPermissionType.AllModules, SecondaryTargetName = "*", - State = true, + State = true }; - await _service.AddPermissions(ctx.Guild.Id, - newPerm, - allowUser); + await _service.AddPermissions(ctx.Guild.Id, newPerm, allowUser); if (action.Value) - { await ReplyConfirmLocalizedAsync(strs.asm_enable); - } else - { await ReplyConfirmLocalizedAsync(strs.asm_disable); - } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/ResetPermissionsCommands.cs b/src/NadekoBot/Modules/Permissions/ResetPermissionsCommands.cs index ffefb515a..c3a724d60 100644 --- a/src/NadekoBot/Modules/Permissions/ResetPermissionsCommands.cs +++ b/src/NadekoBot/Modules/Permissions/ResetPermissionsCommands.cs @@ -17,7 +17,8 @@ public partial class Permissions _perms = perms; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task ResetPerms() @@ -26,7 +27,8 @@ public partial class Permissions await ReplyConfirmLocalizedAsync(strs.perms_reset); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task ResetGlobalPerms() { @@ -34,4 +36,4 @@ public partial class Permissions await ReplyConfirmLocalizedAsync(strs.global_perms_reset); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/Services/BlacklistService.cs b/src/NadekoBot/Modules/Permissions/Services/BlacklistService.cs index e293c24bd..dedfe6df3 100644 --- a/src/NadekoBot/Modules/Permissions/Services/BlacklistService.cs +++ b/src/NadekoBot/Modules/Permissions/Services/BlacklistService.cs @@ -1,20 +1,23 @@ #nullable disable -using NadekoBot.Common.ModuleBehaviors; -using NadekoBot.Services.Database.Models; using Microsoft.EntityFrameworkCore; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Permissions.Services; public sealed class BlacklistService : IEarlyBehavior { + public int Priority + => int.MaxValue; + private readonly DbService _db; private readonly IPubSub _pubSub; private readonly IBotCredentials _creds; private IReadOnlyList _blacklist; - public int Priority => int.MaxValue; private readonly TypedKey blPubKey = new("blacklist.reload"); + public BlacklistService(DbService db, IPubSub pubSub, IBotCredentials creds) { _db = db; @@ -37,10 +40,8 @@ public sealed class BlacklistService : IEarlyBehavior { if (guild != null && bl.Type == BlacklistType.Server && bl.ItemId == guild.Id) { - Log.Information("Blocked input from blacklisted guild: {GuildName} [{GuildId}]", - guild.Name, - guild.Id); - + Log.Information("Blocked input from blacklisted guild: {GuildName} [{GuildId}]", guild.Name, guild.Id); + return Task.FromResult(true); } @@ -49,16 +50,16 @@ public sealed class BlacklistService : IEarlyBehavior Log.Information("Blocked input from blacklisted channel: {ChannelName} [{ChannelId}]", usrMsg.Channel.Name, usrMsg.Channel.Id); - + return Task.FromResult(true); } if (bl.Type == BlacklistType.User && bl.ItemId == usrMsg.Author.Id) { - Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]", + Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]", usrMsg.Author.ToString(), usrMsg.Author.Id); - + return Task.FromResult(true); } } @@ -74,57 +75,48 @@ public sealed class BlacklistService : IEarlyBehavior using var uow = _db.GetDbContext(); var toPublish = uow.Blacklist.AsNoTracking().ToArray(); _blacklist = toPublish; - if (publish) - { - _pubSub.Pub(blPubKey, toPublish); - } + if (publish) _pubSub.Pub(blPubKey, toPublish); } public void Blacklist(BlacklistType type, ulong id) { if (_creds.OwnerIds.Contains(id)) return; - + using var uow = _db.GetDbContext(); var item = new BlacklistEntry { ItemId = id, Type = type }; uow.Blacklist.Add(item); uow.SaveChanges(); - - Reload(true); + + Reload(); } - + public void UnBlacklist(BlacklistType type, ulong id) { using var uow = _db.GetDbContext(); - var toRemove = uow.Blacklist - .FirstOrDefault(bi => bi.ItemId == id && bi.Type == type); - + var toRemove = uow.Blacklist.FirstOrDefault(bi => bi.ItemId == id && bi.Type == type); + if (toRemove is not null) uow.Blacklist.Remove(toRemove); - + uow.SaveChanges(); - - Reload(true); + + Reload(); } - + public void BlacklistUsers(IReadOnlyCollection toBlacklist) { - using (var uow = _db.GetDbContext()) + using (var uow = _db.GetDbContext()) { var bc = uow.Blacklist; //blacklist the users - bc.AddRange(toBlacklist.Select(x => - new BlacklistEntry - { - ItemId = x, - Type = BlacklistType.User, - })); - + bc.AddRange(toBlacklist.Select(x => new BlacklistEntry { ItemId = x, Type = BlacklistType.User })); + //clear their currencies uow.DiscordUser.RemoveFromMany(toBlacklist); uow.SaveChanges(); } - - Reload(true); + + Reload(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/Services/CmdCdService.cs b/src/NadekoBot/Modules/Permissions/Services/CmdCdService.cs index b0457d09c..dbdae116b 100644 --- a/src/NadekoBot/Modules/Permissions/Services/CmdCdService.cs +++ b/src/NadekoBot/Modules/Permissions/Services/CmdCdService.cs @@ -10,33 +10,26 @@ public class CmdCdService : ILateBlocker, INService public ConcurrentDictionary> ActiveCooldowns { get; } = new(); public int Priority { get; } = 0; - + public CmdCdService(Bot bot) - => CommandCooldowns = new( - bot.AllGuildConfigs.ToDictionary(k => k.GuildId, - v => new ConcurrentHashSet(v.CommandCooldowns))); + => CommandCooldowns = new(bot.AllGuildConfigs.ToDictionary(k => k.GuildId, + v => new ConcurrentHashSet(v.CommandCooldowns))); public Task TryBlock(IGuild guild, IUser user, string commandName) { if (guild is null) return Task.FromResult(false); - + var cmdcds = CommandCooldowns.GetOrAdd(guild.Id, new ConcurrentHashSet()); CommandCooldown cdRule; if ((cdRule = cmdcds.FirstOrDefault(cc => cc.CommandName == commandName)) != null) { var activeCdsForGuild = ActiveCooldowns.GetOrAdd(guild.Id, new ConcurrentHashSet()); if (activeCdsForGuild.FirstOrDefault(ac => ac.UserId == user.Id && ac.Command == commandName) != null) - { return Task.FromResult(true); - } - - activeCdsForGuild.Add(new() - { - UserId = user.Id, - Command = commandName, - }); - + + activeCdsForGuild.Add(new() { UserId = user.Id, Command = commandName }); + var _ = Task.Run(async () => { try @@ -53,7 +46,7 @@ public class CmdCdService : ILateBlocker, INService return Task.FromResult(false); } - + public Task TryBlockLate(ICommandContext ctx, string moduleName, CommandInfo command) { var guild = ctx.Guild; @@ -68,4 +61,4 @@ public class ActiveCooldown { public string Command { get; set; } public ulong UserId { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/Services/FilterService.cs b/src/NadekoBot/Modules/Permissions/Services/FilterService.cs index 06cdc3bf6..3cec52deb 100644 --- a/src/NadekoBot/Modules/Permissions/Services/FilterService.cs +++ b/src/NadekoBot/Modules/Permissions/Services/FilterService.cs @@ -1,15 +1,13 @@ #nullable disable -using NadekoBot.Common.ModuleBehaviors; using Microsoft.EntityFrameworkCore; -using NadekoBot.Services.Database.Models; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Permissions.Services; public sealed class FilterService : IEarlyBehavior { - private readonly DbService _db; - public ConcurrentHashSet InviteFilteringChannels { get; } public ConcurrentHashSet InviteFilteringServers { get; } @@ -22,75 +20,44 @@ public sealed class FilterService : IEarlyBehavior public ConcurrentHashSet LinkFilteringChannels { get; } public ConcurrentHashSet LinkFilteringServers { get; } - public int Priority => int.MaxValue - 1; + public int Priority + => int.MaxValue - 1; - public ConcurrentHashSet FilteredWordsForChannel(ulong channelId, ulong guildId) - { - var words = new ConcurrentHashSet(); - if (WordFilteringChannels.Contains(channelId)) - ServerFilteredWords.TryGetValue(guildId, out words); - return words; - } - - public void ClearFilteredWords(ulong guildId) - { - using var uow = _db.GetDbContext(); - var gc = uow.GuildConfigsForId(guildId, - set => set.Include(x => x.FilteredWords) - .Include(x => x.FilterWordsChannelIds)); - - WordFilteringServers.TryRemove(guildId); - ServerFilteredWords.TryRemove(guildId, out _); - - foreach (var c in gc.FilterWordsChannelIds) - { - WordFilteringChannels.TryRemove(c.ChannelId); - } - - gc.FilterWords = false; - gc.FilteredWords.Clear(); - gc.FilterWordsChannelIds.Clear(); - - uow.SaveChanges(); - } - - public ConcurrentHashSet FilteredWordsForServer(ulong guildId) - { - var words = new ConcurrentHashSet(); - if (WordFilteringServers.Contains(guildId)) - ServerFilteredWords.TryGetValue(guildId, out words); - return words; - } + private readonly DbService _db; public FilterService(DiscordSocketClient client, DbService db) { _db = db; - using(var uow = db.GetDbContext()) + using (var uow = db.GetDbContext()) { var ids = client.GetGuildIds(); var configs = uow.Set() - .AsQueryable() - .Include(x => x.FilteredWords) - .Include(x => x.FilterLinksChannelIds) - .Include(x => x.FilterWordsChannelIds) - .Include(x => x.FilterInvitesChannelIds) - .Where(gc => ids.Contains(gc.GuildId)) - .ToList(); - + .AsQueryable() + .Include(x => x.FilteredWords) + .Include(x => x.FilterLinksChannelIds) + .Include(x => x.FilterWordsChannelIds) + .Include(x => x.FilterInvitesChannelIds) + .Where(gc => ids.Contains(gc.GuildId)) + .ToList(); + InviteFilteringServers = new(configs.Where(gc => gc.FilterInvites).Select(gc => gc.GuildId)); - InviteFilteringChannels = new(configs.SelectMany(gc => gc.FilterInvitesChannelIds.Select(fci => fci.ChannelId))); + InviteFilteringChannels = + new(configs.SelectMany(gc => gc.FilterInvitesChannelIds.Select(fci => fci.ChannelId))); LinkFilteringServers = new(configs.Where(gc => gc.FilterLinks).Select(gc => gc.GuildId)); - LinkFilteringChannels = new(configs.SelectMany(gc => gc.FilterLinksChannelIds.Select(fci => fci.ChannelId))); + LinkFilteringChannels = + new(configs.SelectMany(gc => gc.FilterLinksChannelIds.Select(fci => fci.ChannelId))); - var dict = configs.ToDictionary(gc => gc.GuildId, gc => new ConcurrentHashSet(gc.FilteredWords.Select(fw => fw.Word))); + var dict = configs.ToDictionary(gc => gc.GuildId, + gc => new ConcurrentHashSet(gc.FilteredWords.Select(fw => fw.Word))); ServerFilteredWords = new(dict); var serverFiltering = configs.Where(gc => gc.FilterWords); WordFilteringServers = new(serverFiltering.Select(gc => gc.GuildId)); - WordFilteringChannels = new(configs.SelectMany(gc => gc.FilterWordsChannelIds.Select(fwci => fwci.ChannelId))); + WordFilteringChannels = + new(configs.SelectMany(gc => gc.FilterWordsChannelIds.Select(fwci => fwci.ChannelId))); } client.MessageUpdated += (oldData, newMsg, channel) => @@ -108,16 +75,47 @@ public sealed class FilterService : IEarlyBehavior }; } + public ConcurrentHashSet FilteredWordsForChannel(ulong channelId, ulong guildId) + { + var words = new ConcurrentHashSet(); + if (WordFilteringChannels.Contains(channelId)) + ServerFilteredWords.TryGetValue(guildId, out words); + return words; + } + + public void ClearFilteredWords(ulong guildId) + { + using var uow = _db.GetDbContext(); + var gc = uow.GuildConfigsForId(guildId, + set => set.Include(x => x.FilteredWords).Include(x => x.FilterWordsChannelIds)); + + WordFilteringServers.TryRemove(guildId); + ServerFilteredWords.TryRemove(guildId, out _); + + foreach (var c in gc.FilterWordsChannelIds) WordFilteringChannels.TryRemove(c.ChannelId); + + gc.FilterWords = false; + gc.FilteredWords.Clear(); + gc.FilterWordsChannelIds.Clear(); + + uow.SaveChanges(); + } + + public ConcurrentHashSet FilteredWordsForServer(ulong guildId) + { + var words = new ConcurrentHashSet(); + if (WordFilteringServers.Contains(guildId)) + ServerFilteredWords.TryGetValue(guildId, out words); + return words; + } + public async Task RunBehavior(IGuild guild, IUserMessage msg) { if (msg.Author is not IGuildUser gu || gu.GuildPermissions.Administrator) return false; - var results = await Task.WhenAll( - FilterInvites(guild, msg), - FilterWords(guild, msg), - FilterLinks(guild, msg)); - + var results = await Task.WhenAll(FilterInvites(guild, msg), FilterWords(guild, msg), FilterLinks(guild, msg)); + return results.Any(x => x); } @@ -128,33 +126,32 @@ public sealed class FilterService : IEarlyBehavior if (usrMsg is null) return false; - var filteredChannelWords = FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id) ?? new ConcurrentHashSet(); + var filteredChannelWords = + FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id) ?? new ConcurrentHashSet(); var filteredServerWords = FilteredWordsForServer(guild.Id) ?? new ConcurrentHashSet(); var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' '); if (filteredChannelWords.Count != 0 || filteredServerWords.Count != 0) - { foreach (var word in wordsInMessage) - { - if (filteredChannelWords.Contains(word) || - filteredServerWords.Contains(word)) + if (filteredChannelWords.Contains(word) || filteredServerWords.Contains(word)) { Log.Information("User {UserName} [{UserId}] used a filtered word in {ChannelId} channel", usrMsg.Author.ToString(), usrMsg.Author.Id, usrMsg.Channel.Id); - + try { await usrMsg.DeleteAsync(); } catch (HttpException ex) { - Log.Warning("I do not have permission to filter words in channel with id " + usrMsg.Channel.Id, ex); + Log.Warning("I do not have permission to filter words in channel with id " + usrMsg.Channel.Id, + ex); } + return true; } - } - } + return false; } @@ -165,15 +162,14 @@ public sealed class FilterService : IEarlyBehavior if (usrMsg is null) return false; - if ((InviteFilteringChannels.Contains(usrMsg.Channel.Id) - || InviteFilteringServers.Contains(guild.Id)) + if ((InviteFilteringChannels.Contains(usrMsg.Channel.Id) || InviteFilteringServers.Contains(guild.Id)) && usrMsg.Content.IsDiscordInvite()) { Log.Information("User {UserName} [{UserId}] sent a filtered invite to {ChannelId} channel", usrMsg.Author.ToString(), usrMsg.Author.Id, usrMsg.Channel.Id); - + try { await usrMsg.DeleteAsync(); @@ -185,6 +181,7 @@ public sealed class FilterService : IEarlyBehavior return true; } } + return false; } @@ -195,15 +192,14 @@ public sealed class FilterService : IEarlyBehavior if (usrMsg is null) return false; - if ((LinkFilteringChannels.Contains(usrMsg.Channel.Id) - || LinkFilteringServers.Contains(guild.Id)) + if ((LinkFilteringChannels.Contains(usrMsg.Channel.Id) || LinkFilteringServers.Contains(guild.Id)) && usrMsg.Content.TryGetUrlPath(out _)) { Log.Information("User {UserName} [{UserId}] sent a filtered link to {ChannelId} channel", usrMsg.Author.ToString(), usrMsg.Author.Id, usrMsg.Channel.Id); - + try { await usrMsg.DeleteAsync(); @@ -215,6 +211,7 @@ public sealed class FilterService : IEarlyBehavior return true; } } + return false; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/Services/GlobalPermissionService.cs b/src/NadekoBot/Modules/Permissions/Services/GlobalPermissionService.cs index 10565919e..e33d25988 100644 --- a/src/NadekoBot/Modules/Permissions/Services/GlobalPermissionService.cs +++ b/src/NadekoBot/Modules/Permissions/Services/GlobalPermissionService.cs @@ -5,11 +5,15 @@ namespace NadekoBot.Modules.Permissions.Services; public class GlobalPermissionService : ILateBlocker, INService { - private readonly BotConfigService _bss; public int Priority { get; } = 0; - public HashSet BlockedCommands => _bss.Data.Blocked.Commands; - public HashSet BlockedModules => _bss.Data.Blocked.Modules; + public HashSet BlockedCommands + => _bss.Data.Blocked.Commands; + + public HashSet BlockedModules + => _bss.Data.Blocked.Modules; + + private readonly BotConfigService _bss; public GlobalPermissionService(BotConfigService bss) => _bss = bss; @@ -17,21 +21,19 @@ public class GlobalPermissionService : ILateBlocker, INService public Task TryBlockLate(ICommandContext ctx, string moduleName, CommandInfo command) { - var settings = _bss.Data; + var settings = _bss.Data; var commandName = command.Name.ToLowerInvariant(); - if (commandName != "resetglobalperms" && - (settings.Blocked.Commands.Contains(commandName) || - settings.Blocked.Modules.Contains(moduleName.ToLowerInvariant()))) - { + if (commandName != "resetglobalperms" + && (settings.Blocked.Commands.Contains(commandName) + || settings.Blocked.Modules.Contains(moduleName.ToLowerInvariant()))) return Task.FromResult(true); - } - + return Task.FromResult(false); } /// - /// Toggles module blacklist + /// Toggles module blacklist /// /// Lowercase module name /// Whether the module is added @@ -53,9 +55,9 @@ public class GlobalPermissionService : ILateBlocker, INService return added; } - + /// - /// Toggles command blacklist + /// Toggles command blacklist /// /// Lowercase command name /// Whether the command is added @@ -79,7 +81,7 @@ public class GlobalPermissionService : ILateBlocker, INService } /// - /// Resets all global permissions + /// Resets all global permissions /// public Task Reset() { @@ -91,4 +93,4 @@ public class GlobalPermissionService : ILateBlocker, INService return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Permissions/Services/PermissionsService.cs b/src/NadekoBot/Modules/Permissions/Services/PermissionsService.cs index c289b1e47..b3d0917fc 100644 --- a/src/NadekoBot/Modules/Permissions/Services/PermissionsService.cs +++ b/src/NadekoBot/Modules/Permissions/Services/PermissionsService.cs @@ -1,25 +1,26 @@ #nullable disable using Microsoft.EntityFrameworkCore; using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Db; using NadekoBot.Modules.Permissions.Common; using NadekoBot.Services.Database.Models; -using NadekoBot.Db; namespace NadekoBot.Modules.Permissions.Services; public class PermissionService : ILateBlocker, INService { public int Priority { get; } = 0; - + + //guildid, root permission + public ConcurrentDictionary Cache { get; } = new(); + private readonly DbService _db; private readonly CommandHandler _cmd; private readonly IBotStrings _strings; private readonly IEmbedBuilderService _eb; - //guildid, root permission - public ConcurrentDictionary Cache { get; } = new(); - - public PermissionService(DiscordSocketClient client, + public PermissionService( + DiscordSocketClient client, DbService db, CommandHandler cmd, IBotStrings strings, @@ -31,16 +32,12 @@ public class PermissionService : ILateBlocker, INService _eb = eb; using var uow = _db.GetDbContext(); - foreach (var x in uow.GuildConfigs.Permissionsv2ForAll(client.Guilds.ToArray().Select(x => x.Id) - .ToList())) - { - Cache.TryAdd(x.GuildId, new() - { - Verbose = x.VerbosePermissions, - PermRole = x.PermissionRole, - Permissions = new(x.Permissions) - }); - } + foreach (var x in uow.GuildConfigs.Permissionsv2ForAll(client.Guilds.ToArray().Select(x => x.Id).ToList())) + Cache.TryAdd(x.GuildId, + new() + { + Verbose = x.VerbosePermissions, PermRole = x.PermissionRole, Permissions = new(x.Permissions) + }); } public PermissionCache GetCacheFor(ulong guildId) @@ -49,14 +46,15 @@ public class PermissionService : ILateBlocker, INService { using (var uow = _db.GetDbContext()) { - var config = uow.GuildConfigsForId(guildId, - set => set.Include(x => x.Permissions)); + var config = uow.GuildConfigsForId(guildId, set => set.Include(x => x.Permissions)); UpdateCache(config); } + Cache.TryGetValue(guildId, out pc); if (pc is null) throw new("Cache is null."); } + return pc; } @@ -71,23 +69,26 @@ public class PermissionService : ILateBlocker, INService perm.Index = ++max; config.Permissions.Add(perm); } + await uow.SaveChangesAsync(); UpdateCache(config); } public void UpdateCache(GuildConfig config) - => Cache.AddOrUpdate(config.GuildId, new PermissionCache() - { - Permissions = new(config.Permissions), - PermRole = config.PermissionRole, - Verbose = config.VerbosePermissions - }, (id, old) => - { - old.Permissions = new(config.Permissions); - old.PermRole = config.PermissionRole; - old.Verbose = config.VerbosePermissions; - return old; - }); + => Cache.AddOrUpdate(config.GuildId, + new PermissionCache + { + Permissions = new(config.Permissions), + PermRole = config.PermissionRole, + Verbose = config.VerbosePermissions + }, + (id, old) => + { + old.Permissions = new(config.Permissions); + old.PermRole = config.PermissionRole; + old.Verbose = config.VerbosePermissions; + return old; + }); public async Task TryBlockLate(ICommandContext ctx, string moduleName, CommandInfo command) { @@ -96,68 +97,66 @@ public class PermissionService : ILateBlocker, INService var user = ctx.User; var channel = ctx.Channel; var commandName = command.Name.ToLowerInvariant(); - - await Task.Yield(); - if (guild is null) - { - return false; - } - else - { - var resetCommand = commandName == "resetperms"; - var pc = GetCacheFor(guild.Id); - if (!resetCommand && !pc.Permissions.CheckPermissions(msg, commandName, moduleName, out var index)) - { - if (pc.Verbose) + await Task.Yield(); + if (guild is null) return false; + + var resetCommand = commandName == "resetperms"; + + var pc = GetCacheFor(guild.Id); + if (!resetCommand && !pc.Permissions.CheckPermissions(msg, commandName, moduleName, out var index)) + { + if (pc.Verbose) + try { - try - { - await channel.SendErrorAsync(_eb, - _strings.GetText(strs.perm_prevent(index + 1, + await channel.SendErrorAsync(_eb, + _strings.GetText(strs.perm_prevent(index + 1, Format.Bold(pc.Permissions[index] - .GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild))), guild.Id)); - } - catch - { - } + .GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild))), + guild.Id)); } + catch + { + } + + return true; + } + + + if (moduleName == nameof(Permissions)) + { + if (user is not IGuildUser guildUser) + return true; + + if (guildUser.GuildPermissions.Administrator) + return false; + + var permRole = pc.PermRole; + if (!ulong.TryParse(permRole, out var rid)) + rid = 0; + string returnMsg; + IRole role; + if (string.IsNullOrWhiteSpace(permRole) || (role = guild.GetRole(rid)) is null) + { + returnMsg = "You need Admin permissions in order to use permission commands."; + if (pc.Verbose) + try { await channel.SendErrorAsync(_eb, returnMsg); } + catch { } return true; } - - if (moduleName == nameof(Permissions)) + if (!guildUser.RoleIds.Contains(rid)) { - if (user is not IGuildUser guildUser) - return true; + returnMsg = $"You need the {Format.Bold(role.Name)} role in order to use permission commands."; + if (pc.Verbose) + try { await channel.SendErrorAsync(_eb, returnMsg); } + catch { } - if (guildUser.GuildPermissions.Administrator) - return false; - - var permRole = pc.PermRole; - if (!ulong.TryParse(permRole, out var rid)) - rid = 0; - string returnMsg; - IRole role; - if (string.IsNullOrWhiteSpace(permRole) || (role = guild.GetRole(rid)) is null) - { - returnMsg = $"You need Admin permissions in order to use permission commands."; - if (pc.Verbose) - try { await channel.SendErrorAsync(_eb, returnMsg); } catch { } - - return true; - } - else if (!guildUser.RoleIds.Contains(rid)) - { - returnMsg = $"You need the {Format.Bold(role.Name)} role in order to use permission commands."; - if (pc.Verbose) - try { await channel.SendErrorAsync(_eb, returnMsg); } catch { } - - return true; - } - return false; + return true; } + + return false; } return false; @@ -171,4 +170,4 @@ public class PermissionService : ILateBlocker, INService await uow.SaveChangesAsync(); UpdateCache(config); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/AnimeSearchCommands.cs b/src/NadekoBot/Modules/Searches/AnimeSearchCommands.cs index 8db7d9eee..9e1044c0b 100644 --- a/src/NadekoBot/Modules/Searches/AnimeSearchCommands.cs +++ b/src/NadekoBot/Modules/Searches/AnimeSearchCommands.cs @@ -1,7 +1,7 @@ #nullable disable using AngleSharp; -using NadekoBot.Modules.Searches.Services; using AngleSharp.Html.Dom; +using NadekoBot.Modules.Searches.Services; namespace NadekoBot.Modules.Searches; @@ -38,7 +38,8 @@ public partial class Searches // await ctx.Channel.EmbedAsync(embed); // } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(0)] public async Task Mal([Leftover] string name) { @@ -49,59 +50,64 @@ public partial class Searches var config = Configuration.Default.WithDefaultLoader(); using 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 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 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 = GetText(strs.anime_no_fav); if (favorites.Length > 0 && favorites[0].QuerySelector("p") is 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})"; - })); + 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 info = document.QuerySelectorAll("ul.user-status:nth-child(3) > li.clearfix") - .Select(x => Tuple.Create(x.Children[0].InnerHtml, x.Children[1].InnerHtml)) - .ToList(); + .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(); + .Select(x => x.TextContent.Split(':').Select(y => y.Trim()).ToArray()) + .ToArray(); var embed = _eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.mal_profile(name))) - .AddField("💚 " + GetText(strs.watching), stats[0], true) - .AddField("💙 " + GetText(strs.completed), stats[1], true); + .WithOkColor() + .WithTitle(GetText(strs.mal_profile(name))) + .AddField("💚 " + GetText(strs.watching), stats[0], true) + .AddField("💙 " + GetText(strs.completed), stats[1], true); if (info.Count < 3) embed.AddField("💛 " + GetText(strs.on_hold), stats[2], true); - embed - .AddField("💔 " + GetText(strs.dropped), stats[3], true) - .AddField("⚪ " + GetText(strs.plan_to_watch), stats[4], true) - .AddField("🕐 " + daysAndMean[0][0], daysAndMean[0][1], true) - .AddField("📊 " + daysAndMean[1][0], daysAndMean[1][1], true) - .AddField(MalInfoToEmoji(info[0].Item1) + " " + info[0].Item1, info[0].Item2.TrimTo(20), true) - .AddField(MalInfoToEmoji(info[1].Item1) + " " + info[1].Item1, info[1].Item2.TrimTo(20), true); + embed.AddField("💔 " + GetText(strs.dropped), stats[3], true) + .AddField("⚪ " + GetText(strs.plan_to_watch), stats[4], true) + .AddField("🕐 " + daysAndMean[0][0], daysAndMean[0][1], true) + .AddField("📊 " + daysAndMean[1][0], daysAndMean[1][1], true) + .AddField(MalInfoToEmoji(info[0].Item1) + " " + info[0].Item1, info[0].Item2.TrimTo(20), true) + .AddField(MalInfoToEmoji(info[1].Item1) + " " + info[1].Item1, info[1].Item2.TrimTo(20), true); if (info.Count > 2) embed.AddField(MalInfoToEmoji(info[2].Item1) + " " + info[2].Item1, info[2].Item2.TrimTo(20), true); - embed - .WithDescription($@" -** https://myanimelist.net/animelist/{ name } ** + embed.WithDescription($@" +** https://myanimelist.net/animelist/{name} ** **{GetText(strs.top_3_fav_anime)}** -{favAnime}" - - ) - .WithUrl(fullQueryLink) - .WithImageUrl(imageUrl); +{favAnime}") + .WithUrl(fullQueryLink) + .WithImageUrl(imageUrl); await ctx.Channel.EmbedAsync(embed); } @@ -124,12 +130,15 @@ public partial class Searches } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] - public Task Mal(IGuildUser usr) => Mal(usr.Username); + public Task Mal(IGuildUser usr) + => Mal(usr.Username); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Anime([Leftover] string query) { if (string.IsNullOrWhiteSpace(query)) @@ -144,19 +153,24 @@ public partial class Searches } var embed = _eb.Create() - .WithOkColor() - .WithDescription(animeData.Synopsis.Replace("
", Environment.NewLine, StringComparison.InvariantCulture)) - .WithTitle(animeData.TitleEnglish) - .WithUrl(animeData.Link) - .WithImageUrl(animeData.ImageUrlLarge) - .AddField(GetText(strs.episodes), animeData.TotalEpisodes.ToString(), true) - .AddField(GetText(strs.status), animeData.AiringStatus.ToString(), true) - .AddField(GetText(strs.genres), string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : new[] { "none" }), true) - .WithFooter($"{GetText(strs.score)} {animeData.AverageScore} / 100"); + .WithOkColor() + .WithDescription(animeData.Synopsis.Replace("
", + Environment.NewLine, + StringComparison.InvariantCulture)) + .WithTitle(animeData.TitleEnglish) + .WithUrl(animeData.Link) + .WithImageUrl(animeData.ImageUrlLarge) + .AddField(GetText(strs.episodes), animeData.TotalEpisodes.ToString(), true) + .AddField(GetText(strs.status), animeData.AiringStatus, true) + .AddField(GetText(strs.genres), + string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : new[] { "none" }), + true) + .WithFooter($"{GetText(strs.score)} {animeData.AverageScore} / 100"); await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Manga([Leftover] string query) { @@ -172,17 +186,21 @@ public partial class Searches } var embed = _eb.Create() - .WithOkColor() - .WithDescription(mangaData.Synopsis.Replace("
", Environment.NewLine, StringComparison.InvariantCulture)) - .WithTitle(mangaData.TitleEnglish) - .WithUrl(mangaData.Link) - .WithImageUrl(mangaData.ImageUrlLge) - .AddField(GetText(strs.chapters), mangaData.TotalChapters.ToString(), true) - .AddField(GetText(strs.status), mangaData.PublishingStatus.ToString(), true) - .AddField(GetText(strs.genres), string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : new[] { "none" }), true) - .WithFooter($"{GetText(strs.score)} {mangaData.AverageScore} / 100"); + .WithOkColor() + .WithDescription(mangaData.Synopsis.Replace("
", + Environment.NewLine, + StringComparison.InvariantCulture)) + .WithTitle(mangaData.TitleEnglish) + .WithUrl(mangaData.Link) + .WithImageUrl(mangaData.ImageUrlLge) + .AddField(GetText(strs.chapters), mangaData.TotalChapters.ToString(), true) + .AddField(GetText(strs.status), mangaData.PublishingStatus, true) + .AddField(GetText(strs.genres), + string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : new[] { "none" }), + true) + .WithFooter($"{GetText(strs.score)} {mangaData.AverageScore} / 100"); await ctx.Channel.EmbedAsync(embed); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/AnimeResult.cs b/src/NadekoBot/Modules/Searches/Common/AnimeResult.cs index a9290c741..bd7216965 100644 --- a/src/NadekoBot/Modules/Searches/Common/AnimeResult.cs +++ b/src/NadekoBot/Modules/Searches/Common/AnimeResult.cs @@ -6,20 +6,32 @@ namespace NadekoBot.Modules.Searches.Common; public class AnimeResult { public int Id { get; set; } - public string AiringStatus => AiringStatusParsed.ToTitleCase(); + + public string AiringStatus + => AiringStatusParsed.ToTitleCase(); + [JsonProperty("airing_status")] public string AiringStatusParsed { get; set; } + [JsonProperty("title_english")] public string TitleEnglish { get; set; } + [JsonProperty("total_episodes")] public int TotalEpisodes { get; set; } + public string Description { get; set; } + [JsonProperty("image_url_lge")] public string ImageUrlLarge { get; set; } + public string[] Genres { get; set; } + [JsonProperty("average_score")] public string AverageScore { get; set; } - public string Link => "http://anilist.co/anime/" + Id; - public string Synopsis => Description?[..(Description.Length > 500 ? 500 : Description.Length)] + "..."; -} + public string Link + => "http://anilist.co/anime/" + Id; + + public string Synopsis + => Description?[..(Description.Length > 500 ? 500 : Description.Length)] + "..."; +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/BibleVerses.cs b/src/NadekoBot/Modules/Searches/Common/BibleVerses.cs index 314babdab..d11a9b5f3 100644 --- a/src/NadekoBot/Modules/Searches/Common/BibleVerses.cs +++ b/src/NadekoBot/Modules/Searches/Common/BibleVerses.cs @@ -13,7 +13,8 @@ public class BibleVerse { [JsonProperty("book_name")] public string BookName { get; set; } + public int Chapter { get; set; } public int Verse { get; set; } public string Text { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/CryptoData.cs b/src/NadekoBot/Modules/Searches/Common/CryptoData.cs index 84bf5aca7..e62674784 100644 --- a/src/NadekoBot/Modules/Searches/Common/CryptoData.cs +++ b/src/NadekoBot/Modules/Searches/Common/CryptoData.cs @@ -17,6 +17,7 @@ public class CryptoResponseData [JsonProperty("cmc_rank")] public int Rank { get; set; } + public CurrencyQuotes Quote { get; set; } } @@ -33,4 +34,4 @@ public class Quote public string Percent_Change_24h { get; set; } public string Percent_Change_7d { get; set; } public double? Volume_24h { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/DefineModel.cs b/src/NadekoBot/Modules/Searches/Common/DefineModel.cs index 059461c34..8c27feee9 100644 --- a/src/NadekoBot/Modules/Searches/Common/DefineModel.cs +++ b/src/NadekoBot/Modules/Searches/Common/DefineModel.cs @@ -23,6 +23,7 @@ public class Sens { public object Definition { get; set; } public List Examples { get; set; } + [JsonProperty("gramatical_info")] public GramaticalInfo GramaticalInfo { get; set; } } @@ -31,6 +32,7 @@ public class Result { [JsonProperty("part_of_speech")] public string PartOfSpeech { get; set; } + public List Senses { get; set; } public string Url { get; set; } } @@ -38,4 +40,4 @@ public class Result public class DefineModel { public List Results { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/E621Object.cs b/src/NadekoBot/Modules/Searches/Common/E621Object.cs index f66c6b0df..ad0cf4ca1 100644 --- a/src/NadekoBot/Modules/Searches/Common/E621Object.cs +++ b/src/NadekoBot/Modules/Searches/Common/E621Object.cs @@ -3,6 +3,10 @@ namespace NadekoBot.Modules.Searches.Common; public class E621Object { + public FileData File { get; set; } + public TagData Tags { get; set; } + public ScoreData Score { get; set; } + public class FileData { public string Url { get; set; } @@ -17,8 +21,4 @@ public class E621Object { public string Total { get; set; } } - - public FileData File { get; set; } - public TagData Tags { get; set; } - public ScoreData Score { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/Exceptions/StreamNotFoundException.cs b/src/NadekoBot/Modules/Searches/Common/Exceptions/StreamNotFoundException.cs index 27eac5baf..9d8bd05a4 100644 --- a/src/NadekoBot/Modules/Searches/Common/Exceptions/StreamNotFoundException.cs +++ b/src/NadekoBot/Modules/Searches/Common/Exceptions/StreamNotFoundException.cs @@ -7,11 +7,13 @@ public class StreamNotFoundException : Exception { } - public StreamNotFoundException(string message) : base(message) + public StreamNotFoundException(string message) + : base(message) { } - public StreamNotFoundException(string message, Exception innerException) : base(message, innerException) + public StreamNotFoundException(string message, Exception innerException) + : base(message, innerException) { } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/Gallery.cs b/src/NadekoBot/Modules/Searches/Common/Gallery.cs index 3270da4fa..4a0141a37 100644 --- a/src/NadekoBot/Modules/Searches/Common/Gallery.cs +++ b/src/NadekoBot/Modules/Searches/Common/Gallery.cs @@ -41,4 +41,4 @@ public sealed class Gallery UploadedAt = uploadedAt; Tags = tags; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/GatariUserResponse.cs b/src/NadekoBot/Modules/Searches/Common/GatariUserResponse.cs index 60f987ddc..faa47038c 100644 --- a/src/NadekoBot/Modules/Searches/Common/GatariUserResponse.cs +++ b/src/NadekoBot/Modules/Searches/Common/GatariUserResponse.cs @@ -5,34 +5,48 @@ namespace NadekoBot.Modules.Searches.Common; public class UserData { - [JsonProperty("abbr")] public object Abbr { get; set; } + [JsonProperty("abbr")] + public object Abbr { get; set; } - [JsonProperty("clanid")] public object Clanid { get; set; } + [JsonProperty("clanid")] + public object Clanid { get; set; } - [JsonProperty("country")] public string Country { get; set; } + [JsonProperty("country")] + public string Country { get; set; } - [JsonProperty("favourite_mode")] public int FavouriteMode { get; set; } + [JsonProperty("favourite_mode")] + public int FavouriteMode { get; set; } - [JsonProperty("followers_count")] public int FollowersCount { get; set; } + [JsonProperty("followers_count")] + public int FollowersCount { get; set; } - [JsonProperty("id")] public int Id { get; set; } + [JsonProperty("id")] + public int Id { get; set; } - [JsonProperty("latest_activity")] public int LatestActivity { get; set; } + [JsonProperty("latest_activity")] + public int LatestActivity { get; set; } - [JsonProperty("play_style")] public int PlayStyle { get; set; } + [JsonProperty("play_style")] + public int PlayStyle { get; set; } - [JsonProperty("privileges")] public int Privileges { get; set; } + [JsonProperty("privileges")] + public int Privileges { get; set; } - [JsonProperty("registered_on")] public int RegisteredOn { get; set; } + [JsonProperty("registered_on")] + public int RegisteredOn { get; set; } - [JsonProperty("username")] public string Username { get; set; } + [JsonProperty("username")] + public string Username { get; set; } - [JsonProperty("username_aka")] public string UsernameAka { get; set; } + [JsonProperty("username_aka")] + public string UsernameAka { get; set; } } public class GatariUserResponse { - [JsonProperty("code")] public int Code { get; set; } + [JsonProperty("code")] + public int Code { get; set; } - [JsonProperty("users")] public List Users { get; set; } -} + [JsonProperty("users")] + public List Users { get; set; } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/GatariUserStatsResponse.cs b/src/NadekoBot/Modules/Searches/Common/GatariUserStatsResponse.cs index 212c03568..f46b06d6c 100644 --- a/src/NadekoBot/Modules/Searches/Common/GatariUserStatsResponse.cs +++ b/src/NadekoBot/Modules/Searches/Common/GatariUserStatsResponse.cs @@ -5,50 +5,72 @@ namespace NadekoBot.Modules.Searches.Common; public class UserStats { - [JsonProperty("a_count")] public int ACount { get; set; } + [JsonProperty("a_count")] + public int ACount { get; set; } - [JsonProperty("avg_accuracy")] public double AvgAccuracy { get; set; } + [JsonProperty("avg_accuracy")] + public double AvgAccuracy { get; set; } - [JsonProperty("avg_hits_play")] public double AvgHitsPlay { get; set; } + [JsonProperty("avg_hits_play")] + public double AvgHitsPlay { get; set; } - [JsonProperty("country_rank")] public int CountryRank { get; set; } + [JsonProperty("country_rank")] + public int CountryRank { get; set; } - [JsonProperty("id")] public int Id { get; set; } + [JsonProperty("id")] + public int Id { get; set; } - [JsonProperty("level")] public int Level { get; set; } + [JsonProperty("level")] + public int Level { get; set; } - [JsonProperty("level_progress")] public int LevelProgress { get; set; } + [JsonProperty("level_progress")] + public int LevelProgress { get; set; } - [JsonProperty("max_combo")] public int MaxCombo { get; set; } + [JsonProperty("max_combo")] + public int MaxCombo { get; set; } - [JsonProperty("playcount")] public int Playcount { get; set; } + [JsonProperty("playcount")] + public int Playcount { get; set; } - [JsonProperty("playtime")] public int Playtime { get; set; } + [JsonProperty("playtime")] + public int Playtime { get; set; } - [JsonProperty("pp")] public int Pp { get; set; } + [JsonProperty("pp")] + public int Pp { get; set; } - [JsonProperty("rank")] public int Rank { get; set; } + [JsonProperty("rank")] + public int Rank { get; set; } - [JsonProperty("ranked_score")] public int RankedScore { get; set; } + [JsonProperty("ranked_score")] + public int RankedScore { get; set; } - [JsonProperty("replays_watched")] public int ReplaysWatched { get; set; } + [JsonProperty("replays_watched")] + public int ReplaysWatched { get; set; } - [JsonProperty("s_count")] public int SCount { get; set; } + [JsonProperty("s_count")] + public int SCount { get; set; } - [JsonProperty("sh_count")] public int ShCount { get; set; } + [JsonProperty("sh_count")] + public int ShCount { get; set; } - [JsonProperty("total_hits")] public int TotalHits { get; set; } + [JsonProperty("total_hits")] + public int TotalHits { get; set; } - [JsonProperty("total_score")] public long TotalScore { get; set; } + [JsonProperty("total_score")] + public long TotalScore { get; set; } - [JsonProperty("x_count")] public int XCount { get; set; } + [JsonProperty("x_count")] + public int XCount { get; set; } - [JsonProperty("xh_count")] public int XhCount { get; set; } + [JsonProperty("xh_count")] + public int XhCount { get; set; } } public class GatariUserStatsResponse { - [JsonProperty("code")] public int Code { get; set; } + [JsonProperty("code")] + public int Code { get; set; } - [JsonProperty("stats")] public UserStats Stats { get; set; } -} + [JsonProperty("stats")] + public UserStats Stats { get; set; } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/GoogleSearchResult.cs b/src/NadekoBot/Modules/Searches/Common/GoogleSearchResult.cs index d0f0e8810..1cb57ef80 100644 --- a/src/NadekoBot/Modules/Searches/Common/GoogleSearchResult.cs +++ b/src/NadekoBot/Modules/Searches/Common/GoogleSearchResult.cs @@ -9,8 +9,8 @@ public sealed class GoogleSearchResult public GoogleSearchResult(string title, string link, string text) { - this.Title = title; - this.Link = link; - this.Text = text; + Title = title; + Link = link; + Text = text; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/HearthstoneCardData.cs b/src/NadekoBot/Modules/Searches/Common/HearthstoneCardData.cs index dc441e74c..4636a5032 100644 --- a/src/NadekoBot/Modules/Searches/Common/HearthstoneCardData.cs +++ b/src/NadekoBot/Modules/Searches/Common/HearthstoneCardData.cs @@ -10,4 +10,4 @@ public class HearthstoneCardData public string Img { get; set; } public string ImgGold { get; set; } public string PlayerClass { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/ImageCacherObject.cs b/src/NadekoBot/Modules/Searches/Common/ImageCacherObject.cs index 85fae1a97..3f93d168a 100644 --- a/src/NadekoBot/Modules/Searches/Common/ImageCacherObject.cs +++ b/src/NadekoBot/Modules/Searches/Common/ImageCacherObject.cs @@ -13,24 +13,26 @@ public class ImageCacherObject : IComparable public ImageCacherObject(DapiImageObject obj, Booru type) { if (type == Booru.Danbooru && !Uri.IsWellFormedUriString(obj.FileUrl, UriKind.Absolute)) - { - this.FileUrl = "https://danbooru.donmai.us" + obj.FileUrl; - } + FileUrl = "https://danbooru.donmai.us" + obj.FileUrl; else - { - this.FileUrl = obj.FileUrl.StartsWith("http", StringComparison.InvariantCulture) ? obj.FileUrl : "https:" + obj.FileUrl; - } - this.SearchType = type; - this.Rating = obj.Rating; - this.Tags = new((obj.Tags ?? obj.TagString).Split(' ')); + FileUrl = obj.FileUrl.StartsWith("http", StringComparison.InvariantCulture) + ? obj.FileUrl + : "https:" + obj.FileUrl; + SearchType = type; + Rating = obj.Rating; + Tags = new((obj.Tags ?? obj.TagString).Split(' ')); } - public ImageCacherObject(string url, Booru type, string tags, string rating) + public ImageCacherObject( + string url, + Booru type, + string tags, + string rating) { - this.SearchType = type; - this.FileUrl = url; - this.Tags = new(tags.Split(' ')); - this.Rating = rating; + SearchType = type; + FileUrl = url; + Tags = new(tags.Split(' ')); + Rating = rating; } public override string ToString() @@ -38,4 +40,4 @@ public class ImageCacherObject : IComparable public int CompareTo(ImageCacherObject other) => string.Compare(FileUrl, other.FileUrl, StringComparison.InvariantCulture); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/LowerCaseNamingPolicy.cs b/src/NadekoBot/Modules/Searches/Common/LowerCaseNamingPolicy.cs index eda709db2..0d31cfcc8 100644 --- a/src/NadekoBot/Modules/Searches/Common/LowerCaseNamingPolicy.cs +++ b/src/NadekoBot/Modules/Searches/Common/LowerCaseNamingPolicy.cs @@ -6,7 +6,7 @@ namespace SystemTextJsonSamples; public class LowerCaseNamingPolicy : JsonNamingPolicy { public static LowerCaseNamingPolicy Default = new(); - - public override string ConvertName(string name) => - name.ToLower(); -} + + public override string ConvertName(string name) + => name.ToLower(); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/MagicItem.cs b/src/NadekoBot/Modules/Searches/Common/MagicItem.cs index 48443f68f..0bfe6a2d5 100644 --- a/src/NadekoBot/Modules/Searches/Common/MagicItem.cs +++ b/src/NadekoBot/Modules/Searches/Common/MagicItem.cs @@ -5,4 +5,4 @@ public class MagicItem { public string Name { get; set; } public string Description { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/MangaResult.cs b/src/NadekoBot/Modules/Searches/Common/MangaResult.cs index 952178e58..02530a951 100644 --- a/src/NadekoBot/Modules/Searches/Common/MangaResult.cs +++ b/src/NadekoBot/Modules/Searches/Common/MangaResult.cs @@ -6,20 +6,31 @@ namespace NadekoBot.Modules.Searches.Common; public class MangaResult { public int Id { get; set; } + [JsonProperty("publishing_status")] public string PublishingStatus { get; set; } + [JsonProperty("image_url_lge")] public string ImageUrlLge { get; set; } + [JsonProperty("title_english")] public string TitleEnglish { get; set; } + [JsonProperty("total_chapters")] public int TotalChapters { get; set; } + [JsonProperty("total_volumes")] public int TotalVolumes { get; set; } + public string Description { get; set; } public string[] Genres { get; set; } + [JsonProperty("average_score")] public string AverageScore { get; set; } - public string Link => "http://anilist.co/manga/" + Id; - public string Synopsis => Description?[..(Description.Length > 500 ? 500 : Description.Length)] + "..."; -} + + public string Link + => "http://anilist.co/manga/" + Id; + + public string Synopsis + => Description?[..(Description.Length > 500 ? 500 : Description.Length)] + "..."; +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/MtgData.cs b/src/NadekoBot/Modules/Searches/Common/MtgData.cs index f4ec55069..38aa9cc50 100644 --- a/src/NadekoBot/Modules/Searches/Common/MtgData.cs +++ b/src/NadekoBot/Modules/Searches/Common/MtgData.cs @@ -13,6 +13,8 @@ public class MtgData public class MtgResponse { + public List Cards { get; set; } + public class Data { public string Name { get; set; } @@ -21,6 +23,4 @@ public class MtgResponse public List Types { get; set; } public string ImageUrl { get; set; } } - - public List Cards { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/NhentaiApiModel.cs b/src/NadekoBot/Modules/Searches/Common/NhentaiApiModel.cs index 961132387..a7852a67e 100644 --- a/src/NadekoBot/Modules/Searches/Common/NhentaiApiModel.cs +++ b/src/NadekoBot/Modules/Searches/Common/NhentaiApiModel.cs @@ -7,85 +7,115 @@ public static class NhentaiApiModel { public class Title { - [JsonProperty("english")] public string English { get; set; } + [JsonProperty("english")] + public string English { get; set; } - [JsonProperty("japanese")] public string Japanese { get; set; } + [JsonProperty("japanese")] + public string Japanese { get; set; } - [JsonProperty("pretty")] public string Pretty { get; set; } + [JsonProperty("pretty")] + public string Pretty { get; set; } } public class Page { - [JsonProperty("t")] public string T { get; set; } + [JsonProperty("t")] + public string T { get; set; } - [JsonProperty("w")] public int W { get; set; } + [JsonProperty("w")] + public int W { get; set; } - [JsonProperty("h")] public int H { get; set; } + [JsonProperty("h")] + public int H { get; set; } } public class Cover { - [JsonProperty("t")] public string T { get; set; } + [JsonProperty("t")] + public string T { get; set; } - [JsonProperty("w")] public int W { get; set; } + [JsonProperty("w")] + public int W { get; set; } - [JsonProperty("h")] public int H { get; set; } + [JsonProperty("h")] + public int H { get; set; } } public class Thumbnail { - [JsonProperty("t")] public string T { get; set; } + [JsonProperty("t")] + public string T { get; set; } - [JsonProperty("w")] public int W { get; set; } + [JsonProperty("w")] + public int W { get; set; } - [JsonProperty("h")] public int H { get; set; } + [JsonProperty("h")] + public int H { get; set; } } public class Images { - [JsonProperty("pages")] public List Pages { get; set; } + [JsonProperty("pages")] + public List Pages { get; set; } - [JsonProperty("cover")] public Cover Cover { get; set; } + [JsonProperty("cover")] + public Cover Cover { get; set; } - [JsonProperty("thumbnail")] public Thumbnail Thumbnail { get; set; } + [JsonProperty("thumbnail")] + public Thumbnail Thumbnail { get; set; } } public class Tag { - [JsonProperty("id")] public int Id { get; set; } + [JsonProperty("id")] + public int Id { get; set; } - [JsonProperty("type")] public string Type { get; set; } + [JsonProperty("type")] + public string Type { get; set; } - [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("name")] + public string Name { get; set; } - [JsonProperty("url")] public string Url { get; set; } + [JsonProperty("url")] + public string Url { get; set; } - [JsonProperty("count")] public int Count { get; set; } + [JsonProperty("count")] + public int Count { get; set; } } public class Gallery { - [JsonProperty("id")] public int Id { get; set; } + [JsonProperty("id")] + public int Id { get; set; } - [JsonProperty("media_id")] public string MediaId { get; set; } + [JsonProperty("media_id")] + public string MediaId { get; set; } - [JsonProperty("title")] public Title Title { get; set; } + [JsonProperty("title")] + public Title Title { get; set; } - [JsonProperty("images")] public Images Images { get; set; } + [JsonProperty("images")] + public Images Images { get; set; } - [JsonProperty("scanlator")] public string Scanlator { get; set; } + [JsonProperty("scanlator")] + public string Scanlator { get; set; } - [JsonProperty("upload_date")] public double UploadDate { get; set; } + [JsonProperty("upload_date")] + public double UploadDate { get; set; } - [JsonProperty("tags")] public Tag[] Tags { get; set; } + [JsonProperty("tags")] + public Tag[] Tags { get; set; } - [JsonProperty("num_pages")] public int NumPages { get; set; } + [JsonProperty("num_pages")] + public int NumPages { get; set; } - [JsonProperty("num_favorites")] public int NumFavorites { get; set; } + [JsonProperty("num_favorites")] + public int NumFavorites { get; set; } } public class SearchResult { - [JsonProperty("result")] public Gallery[] Result { get; set; } + [JsonProperty("result")] + public Gallery[] Result { get; set; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/NovelData.cs b/src/NadekoBot/Modules/Searches/Common/NovelData.cs index a13a450e8..8fb5ffc77 100644 --- a/src/NadekoBot/Modules/Searches/Common/NovelData.cs +++ b/src/NadekoBot/Modules/Searches/Common/NovelData.cs @@ -11,4 +11,4 @@ public class NovelResult public string Status { get; set; } public string[] Genres { get; set; } public string Score { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/OmdbMovie.cs b/src/NadekoBot/Modules/Searches/Common/OmdbMovie.cs index 9a456b157..1b8ba3c6e 100644 --- a/src/NadekoBot/Modules/Searches/Common/OmdbMovie.cs +++ b/src/NadekoBot/Modules/Searches/Common/OmdbMovie.cs @@ -10,4 +10,4 @@ public class OmdbMovie public string Genre { get; set; } public string Plot { get; set; } public string Poster { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/OsuUserData.cs b/src/NadekoBot/Modules/Searches/Common/OsuUserData.cs index 15c3ecf1d..e4f5de477 100644 --- a/src/NadekoBot/Modules/Searches/Common/OsuUserData.cs +++ b/src/NadekoBot/Modules/Searches/Common/OsuUserData.cs @@ -5,45 +5,66 @@ namespace NadekoBot.Modules.Searches.Common; public class OsuUserData { - [JsonProperty("user_id")] public string UserId { get; set; } + [JsonProperty("user_id")] + public string UserId { get; set; } - [JsonProperty("username")] public string Username { get; set; } + [JsonProperty("username")] + public string Username { get; set; } - [JsonProperty("join_date")] public string JoinDate { get; set; } + [JsonProperty("join_date")] + public string JoinDate { get; set; } - [JsonProperty("count300")] public string Count300 { get; set; } + [JsonProperty("count300")] + public string Count300 { get; set; } - [JsonProperty("count100")] public string Count100 { get; set; } + [JsonProperty("count100")] + public string Count100 { get; set; } - [JsonProperty("count50")] public string Count50 { get; set; } + [JsonProperty("count50")] + public string Count50 { get; set; } - [JsonProperty("playcount")] public string Playcount { get; set; } + [JsonProperty("playcount")] + public string Playcount { get; set; } - [JsonProperty("ranked_score")] public string RankedScore { get; set; } + [JsonProperty("ranked_score")] + public string RankedScore { get; set; } - [JsonProperty("total_score")] public string TotalScore { get; set; } + [JsonProperty("total_score")] + public string TotalScore { get; set; } - [JsonProperty("pp_rank")] public string PpRank { get; set; } + [JsonProperty("pp_rank")] + public string PpRank { get; set; } - [JsonProperty("level")] public double Level { get; set; } + [JsonProperty("level")] + public double Level { get; set; } - [JsonProperty("pp_raw")] public double PpRaw { get; set; } + [JsonProperty("pp_raw")] + public double PpRaw { get; set; } - [JsonProperty("accuracy")] public double Accuracy { get; set; } + [JsonProperty("accuracy")] + public double Accuracy { get; set; } - [JsonProperty("count_rank_ss")] public string CountRankSs { get; set; } + [JsonProperty("count_rank_ss")] + public string CountRankSs { get; set; } - [JsonProperty("count_rank_ssh")] public string CountRankSsh { get; set; } + [JsonProperty("count_rank_ssh")] + public string CountRankSsh { get; set; } - [JsonProperty("count_rank_s")] public string CountRankS { get; set; } + [JsonProperty("count_rank_s")] + public string CountRankS { get; set; } - [JsonProperty("count_rank_sh")] public string CountRankSh { get; set; } + [JsonProperty("count_rank_sh")] + public string CountRankSh { get; set; } - [JsonProperty("count_rank_a")] public string CountRankA { get; set; } + [JsonProperty("count_rank_a")] + public string CountRankA { get; set; } - [JsonProperty("country")] public string Country { get; set; } + [JsonProperty("country")] + public string Country { get; set; } - [JsonProperty("total_seconds_played")] public string TotalSecondsPlayed { get; set; } + [JsonProperty("total_seconds_played")] + public string TotalSecondsPlayed { get; set; } - [JsonProperty("pp_country_rank")] public string PpCountryRank { get; set; } -} + [JsonProperty("pp_country_rank")] + public string PpCountryRank { get; set; } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/PathOfExileModels.cs b/src/NadekoBot/Modules/Searches/Common/PathOfExileModels.cs index 7f60f564b..e9b6060ab 100644 --- a/src/NadekoBot/Modules/Searches/Common/PathOfExileModels.cs +++ b/src/NadekoBot/Modules/Searches/Common/PathOfExileModels.cs @@ -5,36 +5,36 @@ namespace NadekoBot.Modules.Searches.Common; public class Account { - [JsonProperty("name")] - public string Name { get; set; } + [JsonProperty("name")] + public string Name { get; set; } - [JsonProperty("league")] - public string League { get; set; } + [JsonProperty("league")] + public string League { get; set; } - [JsonProperty("classId")] - public int ClassId { get; set; } + [JsonProperty("classId")] + public int ClassId { get; set; } - [JsonProperty("ascendancyClass")] - public int AscendancyClass { get; set; } + [JsonProperty("ascendancyClass")] + public int AscendancyClass { get; set; } - [JsonProperty("class")] - public string Class { get; set; } + [JsonProperty("class")] + public string Class { get; set; } - [JsonProperty("level")] - public int Level { get; set; } + [JsonProperty("level")] + public int Level { get; set; } } public class Leagues { - [JsonProperty("id")] - public string Id { get; set; } + [JsonProperty("id")] + public string Id { get; set; } - [JsonProperty("url")] - public string Url { get; set; } + [JsonProperty("url")] + public string Url { get; set; } - [JsonProperty("startAt")] - public DateTime StartAt { get; set; } + [JsonProperty("startAt")] + public DateTime StartAt { get; set; } - [JsonProperty("endAt")] - public object EndAt { get; set; } -} + [JsonProperty("endAt")] + public object EndAt { get; set; } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/SteamGameId.cs b/src/NadekoBot/Modules/Searches/Common/SteamGameId.cs index 0119fdece..dc942e5b4 100644 --- a/src/NadekoBot/Modules/Searches/Common/SteamGameId.cs +++ b/src/NadekoBot/Modules/Searches/Common/SteamGameId.cs @@ -7,7 +7,7 @@ public class SteamGameId { [JsonProperty("name")] public string Name { get; set; } - + [JsonProperty("appid")] public int AppId { get; set; } } @@ -32,4 +32,4 @@ public enum TimeErrors ApiKeyMissing, NotFound, Unknown -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/PicartoChannelResponse.cs b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/PicartoChannelResponse.cs index 6d5f6e357..c109daa76 100644 --- a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/PicartoChannelResponse.cs +++ b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/PicartoChannelResponse.cs @@ -5,107 +5,153 @@ namespace NadekoBot.Modules.Searches.Common; public class PicartoChannelResponse { - [JsonProperty("user_id")] public int UserId { get; set; } + [JsonProperty("user_id")] + public int UserId { get; set; } - [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("name")] + public string Name { get; set; } - [JsonProperty("avatar")] public string Avatar { get; set; } + [JsonProperty("avatar")] + public string Avatar { get; set; } - [JsonProperty("online")] public bool Online { get; set; } + [JsonProperty("online")] + public bool Online { get; set; } - [JsonProperty("viewers")] public int Viewers { get; set; } + [JsonProperty("viewers")] + public int Viewers { get; set; } - [JsonProperty("viewers_total")] public int ViewersTotal { get; set; } + [JsonProperty("viewers_total")] + public int ViewersTotal { get; set; } - [JsonProperty("thumbnails")] public Thumbnails Thumbnails { get; set; } + [JsonProperty("thumbnails")] + public Thumbnails Thumbnails { get; set; } - [JsonProperty("followers")] public int Followers { get; set; } + [JsonProperty("followers")] + public int Followers { get; set; } - [JsonProperty("subscribers")] public int Subscribers { get; set; } + [JsonProperty("subscribers")] + public int Subscribers { get; set; } - [JsonProperty("adult")] public bool Adult { get; set; } + [JsonProperty("adult")] + public bool Adult { get; set; } - [JsonProperty("category")] public string Category { get; set; } + [JsonProperty("category")] + public string Category { get; set; } - [JsonProperty("account_type")] public string AccountType { get; set; } + [JsonProperty("account_type")] + public string AccountType { get; set; } - [JsonProperty("commissions")] public bool Commissions { get; set; } + [JsonProperty("commissions")] + public bool Commissions { get; set; } - [JsonProperty("recordings")] public bool Recordings { get; set; } + [JsonProperty("recordings")] + public bool Recordings { get; set; } - [JsonProperty("title")] public string Title { get; set; } + [JsonProperty("title")] + public string Title { get; set; } - [JsonProperty("description_panels")] public List DescriptionPanels { get; set; } + [JsonProperty("description_panels")] + public List DescriptionPanels { get; set; } - [JsonProperty("private")] public bool Private { get; set; } + [JsonProperty("private")] + public bool Private { get; set; } - [JsonProperty("private_message")] public string PrivateMessage { get; set; } + [JsonProperty("private_message")] + public string PrivateMessage { get; set; } - [JsonProperty("gaming")] public bool Gaming { get; set; } + [JsonProperty("gaming")] + public bool Gaming { get; set; } - [JsonProperty("chat_settings")] public ChatSettings ChatSettings { get; set; } + [JsonProperty("chat_settings")] + public ChatSettings ChatSettings { get; set; } - [JsonProperty("last_live")] public DateTime LastLive { get; set; } + [JsonProperty("last_live")] + public DateTime LastLive { get; set; } - [JsonProperty("tags")] public List Tags { get; set; } + [JsonProperty("tags")] + public List Tags { get; set; } - [JsonProperty("multistream")] public List Multistream { get; set; } + [JsonProperty("multistream")] + public List Multistream { get; set; } - [JsonProperty("languages")] public List Languages { get; set; } + [JsonProperty("languages")] + public List Languages { get; set; } - [JsonProperty("following")] public bool Following { get; set; } + [JsonProperty("following")] + public bool Following { get; set; } } + public class Thumbnails { - [JsonProperty("web")] public string Web { get; set; } + [JsonProperty("web")] + public string Web { get; set; } - [JsonProperty("web_large")] public string WebLarge { get; set; } + [JsonProperty("web_large")] + public string WebLarge { get; set; } - [JsonProperty("mobile")] public string Mobile { get; set; } + [JsonProperty("mobile")] + public string Mobile { get; set; } - [JsonProperty("tablet")] public string Tablet { get; set; } + [JsonProperty("tablet")] + public string Tablet { get; set; } } public class DescriptionPanel { - [JsonProperty("title")] public string Title { get; set; } + [JsonProperty("title")] + public string Title { get; set; } - [JsonProperty("body")] public string Body { get; set; } + [JsonProperty("body")] + public string Body { get; set; } - [JsonProperty("image")] public string Image { get; set; } + [JsonProperty("image")] + public string Image { get; set; } - [JsonProperty("image_link")] public string ImageLink { get; set; } + [JsonProperty("image_link")] + public string ImageLink { get; set; } - [JsonProperty("button_text")] public string ButtonText { get; set; } + [JsonProperty("button_text")] + public string ButtonText { get; set; } - [JsonProperty("button_link")] public string ButtonLink { get; set; } + [JsonProperty("button_link")] + public string ButtonLink { get; set; } - [JsonProperty("position")] public int Position { get; set; } + [JsonProperty("position")] + public int Position { get; set; } } public class ChatSettings { - [JsonProperty("guest_chat")] public bool GuestChat { get; set; } + [JsonProperty("guest_chat")] + public bool GuestChat { get; set; } - [JsonProperty("links")] public bool Links { get; set; } + [JsonProperty("links")] + public bool Links { get; set; } - [JsonProperty("level")] public int Level { get; set; } + [JsonProperty("level")] + public int Level { get; set; } } public class Multistream { - [JsonProperty("user_id")] public int UserId { get; set; } + [JsonProperty("user_id")] + public int UserId { get; set; } - [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("name")] + public string Name { get; set; } - [JsonProperty("online")] public bool Online { get; set; } + [JsonProperty("online")] + public bool Online { get; set; } - [JsonProperty("adult")] public bool Adult { get; set; } + [JsonProperty("adult")] + public bool Adult { get; set; } } public class Language { - [JsonProperty("id")] public int Id { get; set; } + [JsonProperty("id")] + public int Id { get; set; } - [JsonProperty("name")] public string Name { get; set; } -} + [JsonProperty("name")] + public string Name { get; set; } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/StreamData.cs b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/StreamData.cs index 471ac1179..4a619741c 100644 --- a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/StreamData.cs +++ b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/StreamData.cs @@ -16,5 +16,6 @@ public class StreamData public string StreamUrl { get; set; } public string AvatarUrl { get; set; } - public StreamDataKey CreateKey() => new(StreamType, UniqueName.ToLower()); -} + public StreamDataKey CreateKey() + => new(StreamType, UniqueName.ToLower()); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/StreamDataKey.cs b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/StreamDataKey.cs index ae0fd23d1..69845e68e 100644 --- a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/StreamDataKey.cs +++ b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/StreamDataKey.cs @@ -1,6 +1,6 @@ #nullable disable -using System.Text.Json.Serialization; using NadekoBot.Db.Models; +using System.Text.Json.Serialization; namespace NadekoBot.Modules.Searches.Common; @@ -8,11 +8,11 @@ public readonly struct StreamDataKey { public FollowedStream.FType Type { get; } public string Name { get; } - + [JsonConstructor] public StreamDataKey(FollowedStream.FType type, string name) { Type = type; Name = name; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/TwitchResponseHelix.cs b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/TwitchResponseHelix.cs index 0a383eccd..fdf979882 100644 --- a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/TwitchResponseHelix.cs +++ b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/TwitchResponseHelix.cs @@ -19,4 +19,4 @@ namespace NadekoBot.Modules.Searches.Common; // public string ThumbnailUrl { get; set; } // public DateTime StartedAt { get; set; } // } -// } +// } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/TwitchResponseV5.cs b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/TwitchResponseV5.cs index eea5e048f..6096eb455 100644 --- a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/TwitchResponseV5.cs +++ b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/TwitchResponseV5.cs @@ -9,75 +9,106 @@ public class TwitchResponseV5 public class Channel { - [JsonProperty("_id")] public int Id { get; set; } + [JsonProperty("_id")] + public int Id { get; set; } - [JsonProperty("broadcaster_language")] public string BroadcasterLanguage { get; set; } + [JsonProperty("broadcaster_language")] + public string BroadcasterLanguage { get; set; } - [JsonProperty("created_at")] public DateTime CreatedAt { get; set; } + [JsonProperty("created_at")] + public DateTime CreatedAt { get; set; } - [JsonProperty("display_name")] public string DisplayName { get; set; } + [JsonProperty("display_name")] + public string DisplayName { get; set; } - [JsonProperty("followers")] public int Followers { get; set; } + [JsonProperty("followers")] + public int Followers { get; set; } - [JsonProperty("game")] public string Game { get; set; } + [JsonProperty("game")] + public string Game { get; set; } - [JsonProperty("language")] public string Language { get; set; } + [JsonProperty("language")] + public string Language { get; set; } - [JsonProperty("logo")] public string Logo { get; set; } + [JsonProperty("logo")] + public string Logo { get; set; } - [JsonProperty("mature")] public bool Mature { get; set; } + [JsonProperty("mature")] + public bool Mature { get; set; } - [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("name")] + public string Name { get; set; } - [JsonProperty("partner")] public bool Partner { get; set; } + [JsonProperty("partner")] + public bool Partner { get; set; } - [JsonProperty("profile_banner")] public string ProfileBanner { get; set; } + [JsonProperty("profile_banner")] + public string ProfileBanner { get; set; } [JsonProperty("profile_banner_background_color")] public object ProfileBannerBackgroundColor { get; set; } - [JsonProperty("status")] public string Status { get; set; } + [JsonProperty("status")] + public string Status { get; set; } - [JsonProperty("updated_at")] public DateTime UpdatedAt { get; set; } + [JsonProperty("updated_at")] + public DateTime UpdatedAt { get; set; } - [JsonProperty("url")] public string Url { get; set; } + [JsonProperty("url")] + public string Url { get; set; } - [JsonProperty("video_banner")] public string VideoBanner { get; set; } + [JsonProperty("video_banner")] + public string VideoBanner { get; set; } - [JsonProperty("views")] public int Views { get; set; } + [JsonProperty("views")] + public int Views { get; set; } } public class Preview { - [JsonProperty("large")] public string Large { get; set; } + [JsonProperty("large")] + public string Large { get; set; } - [JsonProperty("medium")] public string Medium { get; set; } + [JsonProperty("medium")] + public string Medium { get; set; } - [JsonProperty("small")] public string Small { get; set; } + [JsonProperty("small")] + public string Small { get; set; } - [JsonProperty("template")] public string Template { get; set; } + [JsonProperty("template")] + public string Template { get; set; } } public class Stream { - [JsonProperty("_id")] public long Id { get; set; } + [JsonProperty("_id")] + public long Id { get; set; } - [JsonProperty("average_fps")] public double AverageFps { get; set; } + [JsonProperty("average_fps")] + public double AverageFps { get; set; } - [JsonProperty("channel")] public Channel Channel { get; set; } + [JsonProperty("channel")] + public Channel Channel { get; set; } - [JsonProperty("created_at")] public DateTime CreatedAt { get; set; } + [JsonProperty("created_at")] + public DateTime CreatedAt { get; set; } - [JsonProperty("delay")] public double Delay { get; set; } + [JsonProperty("delay")] + public double Delay { get; set; } - [JsonProperty("game")] public string Game { get; set; } + [JsonProperty("game")] + public string Game { get; set; } - [JsonProperty("is_playlist")] public bool IsPlaylist { get; set; } + [JsonProperty("is_playlist")] + public bool IsPlaylist { get; set; } - [JsonProperty("preview")] public Preview Preview { get; set; } + [JsonProperty("preview")] + public Preview Preview { get; set; } - [JsonProperty("video_height")] public int VideoHeight { get; set; } + [JsonProperty("video_height")] + public int VideoHeight { get; set; } - [JsonProperty("viewers")] public int Viewers { get; set; } + [JsonProperty("viewers")] + public int Viewers { get; set; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/TwitchUsersResponseV5.cs b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/TwitchUsersResponseV5.cs index 42591e479..5720d5798 100644 --- a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/TwitchUsersResponseV5.cs +++ b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Models/TwitchUsersResponseV5.cs @@ -5,13 +5,13 @@ namespace NadekoBot.Modules.Searches.Common; public class TwitchUsersResponseV5 { - [JsonProperty("users")] public List Users { get; set; } + [JsonProperty("users")] + public List Users { get; set; } public class User { - [JsonProperty("_id")] - public string Id { get; set; } + public string Id { get; set; } // [JsonProperty("bio")] // public string Bio { get; set; } @@ -33,6 +33,5 @@ public class TwitchUsersResponseV5 // // [JsonProperty("updated_at")] // public DateTime UpdatedAt { get; set; } - } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/NotifChecker.cs b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/NotifChecker.cs index 8c49dc10d..db006d172 100644 --- a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/NotifChecker.cs +++ b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/NotifChecker.cs @@ -1,5 +1,5 @@ -using NadekoBot.Modules.Searches.Common.StreamNotifications.Providers; -using NadekoBot.Db.Models; +using NadekoBot.Db.Models; +using NadekoBot.Modules.Searches.Common.StreamNotifications.Providers; using Newtonsoft.Json; using StackExchange.Redis; @@ -7,158 +7,144 @@ namespace NadekoBot.Modules.Searches.Common.StreamNotifications; public class NotifChecker { - private readonly ConnectionMultiplexer _multi; - private readonly string _key; - public event Func, Task> OnStreamsOffline = _ => Task.CompletedTask; public event Func, Task> OnStreamsOnline = _ => Task.CompletedTask; + private readonly ConnectionMultiplexer _multi; + private readonly string _key; private readonly Dictionary _streamProviders; private readonly HashSet<(FollowedStream.FType, string)> _offlineBuffer; - public NotifChecker(IHttpClientFactory httpClientFactory, ConnectionMultiplexer multi, string uniqueCacheKey, + public NotifChecker( + IHttpClientFactory httpClientFactory, + ConnectionMultiplexer multi, + string uniqueCacheKey, bool isMaster) { _multi = multi; _key = $"{uniqueCacheKey}_followed_streams_data"; _streamProviders = new() { - {FollowedStream.FType.Twitch, new TwitchProvider(httpClientFactory)}, - {FollowedStream.FType.Picarto, new PicartoProvider(httpClientFactory)} + { FollowedStream.FType.Twitch, new TwitchProvider(httpClientFactory) }, + { FollowedStream.FType.Picarto, new PicartoProvider(httpClientFactory) } }; _offlineBuffer = new(); - if (isMaster) - { - CacheClearAllData(); - } + if (isMaster) CacheClearAllData(); } - + // gets all streams which have been failing for more than the provided timespan public IEnumerable GetFailingStreams(TimeSpan duration, bool remove = false) { var toReturn = _streamProviders.SelectMany(prov => prov.Value - .FailingStreams - .Where(fs => DateTime.UtcNow - fs.ErroringSince > duration) - .Select(fs => new StreamDataKey(prov.Value.Platform, fs.Item1))) - .ToList(); + .FailingStreams + .Where(fs => DateTime.UtcNow - fs.ErroringSince + > duration) + .Select(fs => new StreamDataKey(prov.Value.Platform, + fs.Item1))) + .ToList(); if (remove) - { foreach (var toBeRemoved in toReturn) - { _streamProviders[toBeRemoved.Type].ClearErrorsFor(toBeRemoved.Name); - } - } - + return toReturn; } - public Task RunAsync() => Task.Run(async () => - { - while (true) + public Task RunAsync() + => Task.Run(async () => { - try - { - var allStreamData = CacheGetAllData(); - - var oldStreamDataDict = allStreamData - // group by type - .GroupBy(entry => entry.Key.Type) - .ToDictionary( - entry => entry.Key, - entry => entry.AsEnumerable().ToDictionary(x => x.Key.Name, x => x.Value) - ); - - var newStreamData = await oldStreamDataDict - .Select(x => - { - // get all stream data for the streams of this type - if (_streamProviders.TryGetValue(x.Key, out var provider)) - { - return provider.GetStreamDataAsync(x.Value.Select(entry => entry.Key).ToList()); - } - - // this means there's no provider for this stream data, (and there was before?) - return Task.FromResult(new List()); - }).WhenAll(); - - var newlyOnline = new List(); - var newlyOffline = new List(); - // go through all new stream data, compare them with the old ones - foreach (var newData in newStreamData.SelectMany(x => x)) + while (true) + try { - // update cached data - var key = newData.CreateKey(); - CacheAddData(key, newData, replace: true); + var allStreamData = CacheGetAllData(); - // compare old data with new data - var oldData = oldStreamDataDict[key.Type][key.Name]; + var oldStreamDataDict = allStreamData + // group by type + .GroupBy(entry => entry.Key.Type) + .ToDictionary(entry => entry.Key, + entry => entry.AsEnumerable() + .ToDictionary(x => x.Key.Name, x => x.Value)); - // this is the first pass - if (oldData is null) - continue; + var newStreamData = await oldStreamDataDict.Select(x => + { + // get all stream data for the streams of this type + if (_streamProviders.TryGetValue(x.Key, + out var provider)) + return provider.GetStreamDataAsync(x.Value + .Select(entry => entry.Key) + .ToList()); - // if the stream is offline, we need to check if it was - // marked as offline once previously - // if it was, that means this is second time we're getting offline - // status for that stream -> notify subscribers - // Note: This is done because twitch api will sometimes return an offline status - // shortly after the stream is already online, which causes duplicate notifications. - // (stream is online -> stream is offline -> stream is online again (and stays online)) - // This offlineBuffer will make it so that the stream has to be marked as offline TWICE - // before it sends an offline notification to the subscribers. - var streamId = (key.Type, key.Name); - if (!newData.IsLive && _offlineBuffer.Remove(streamId)) + // this means there's no provider for this stream data, (and there was before?) + return Task.FromResult(new List()); + }) + .WhenAll(); + + var newlyOnline = new List(); + var newlyOffline = new List(); + // go through all new stream data, compare them with the old ones + foreach (var newData in newStreamData.SelectMany(x => x)) { - newlyOffline.Add(newData); - } - else if (newData.IsLive != oldData.IsLive) - { - if (newData.IsLive) + // update cached data + var key = newData.CreateKey(); + CacheAddData(key, newData, true); + + // compare old data with new data + var oldData = oldStreamDataDict[key.Type][key.Name]; + + // this is the first pass + if (oldData is null) + continue; + + // if the stream is offline, we need to check if it was + // marked as offline once previously + // if it was, that means this is second time we're getting offline + // status for that stream -> notify subscribers + // Note: This is done because twitch api will sometimes return an offline status + // shortly after the stream is already online, which causes duplicate notifications. + // (stream is online -> stream is offline -> stream is online again (and stays online)) + // This offlineBuffer will make it so that the stream has to be marked as offline TWICE + // before it sends an offline notification to the subscribers. + var streamId = (key.Type, key.Name); + if (!newData.IsLive && _offlineBuffer.Remove(streamId)) { - _offlineBuffer.Remove(streamId); - newlyOnline.Add(newData); + newlyOffline.Add(newData); } - else + else if (newData.IsLive != oldData.IsLive) { - _offlineBuffer.Add(streamId); - // newlyOffline.Add(newData); + if (newData.IsLive) + { + _offlineBuffer.Remove(streamId); + newlyOnline.Add(newData); + } + else + { + _offlineBuffer.Add(streamId); + // newlyOffline.Add(newData); + } } } - } - - var tasks = new List - { - Task.Delay(30_000) - }; - if (newlyOnline.Count > 0) - { - tasks.Add(OnStreamsOnline(newlyOnline)); - } + var tasks = new List { Task.Delay(30_000) }; - if (newlyOffline.Count > 0) - { - tasks.Add(OnStreamsOffline(newlyOffline)); - } + if (newlyOnline.Count > 0) tasks.Add(OnStreamsOnline(newlyOnline)); - await Task.WhenAll(tasks); - } - catch (Exception ex) - { - Log.Error(ex, "Error getting stream notifications: {ErrorMessage}", ex.Message); - } - } - }); + if (newlyOffline.Count > 0) tasks.Add(OnStreamsOffline(newlyOffline)); + + await Task.WhenAll(tasks); + } + catch (Exception ex) + { + Log.Error(ex, "Error getting stream notifications: {ErrorMessage}", ex.Message); + } + }); public bool CacheAddData(StreamDataKey key, StreamData? data, bool replace) { var db = _multi.GetDatabase(); - return db.HashSet( - _key, + return db.HashSet(_key, JsonConvert.SerializeObject(key), JsonConvert.SerializeObject(data), - when: replace ? When.Always : When.NotExists); + replace ? When.Always : When.NotExists); } public void CacheDeleteData(StreamDataKey key) @@ -176,17 +162,13 @@ public class NotifChecker public Dictionary CacheGetAllData() { var db = _multi.GetDatabase(); - if (!db.KeyExists(_key)) - { - return new(); - } + if (!db.KeyExists(_key)) return new(); return db.HashGetAll(_key) - .ToDictionary( - entry => JsonConvert.DeserializeObject(entry.Name), - entry => entry.Value.IsNullOrEmpty - ? default(StreamData) - : JsonConvert.DeserializeObject(entry.Value)); + .ToDictionary(entry => JsonConvert.DeserializeObject(entry.Name), + entry => entry.Value.IsNullOrEmpty + ? default + : JsonConvert.DeserializeObject(entry.Value)); } public async Task GetStreamDataByUrlAsync(string url) @@ -207,7 +189,7 @@ public class NotifChecker } /// - /// Return currently available stream data, get new one if none available, and start tracking the stream. + /// Return currently available stream data, get new one if none available, and start tracking the stream. /// /// Url of the stream /// Stream data, if any @@ -219,7 +201,7 @@ public class NotifChecker } /// - /// Make sure a stream is tracked using its stream data. + /// Make sure a stream is tracked using its stream data. /// /// Data to try to track if not already tracked /// Whether it's newly added @@ -231,9 +213,9 @@ public class NotifChecker // if stream is found, add it to the cache for tracking only if it doesn't already exist // because stream will be checked and events will fire in a loop. We don't want to override old state - return CacheAddData(data.CreateKey(), data, replace: false); + return CacheAddData(data.CreateKey(), data, false); } public void UntrackStreamByKey(in StreamDataKey key) => CacheDeleteData(key); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/PicartoProvider.cs b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/PicartoProvider.cs index f4945bedb..0627bb9b8 100644 --- a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/PicartoProvider.cs +++ b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/PicartoProvider.cs @@ -1,17 +1,18 @@ -using System.Text.RegularExpressions; -using NadekoBot.Db.Models; +using NadekoBot.Db.Models; using Newtonsoft.Json; +using System.Text.RegularExpressions; namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers; public class PicartoProvider : Provider { - private readonly IHttpClientFactory _httpClientFactory; - private static Regex Regex { get; } = new(@"picarto.tv/(?.+[^/])/?", RegexOptions.Compiled | RegexOptions.IgnoreCase); - public override FollowedStream.FType Platform => FollowedStream.FType.Picarto; + public override FollowedStream.FType Platform + => FollowedStream.FType.Picarto; + + private readonly IHttpClientFactory _httpClientFactory; public PicartoProvider(IHttpClientFactory httpClientFactory) => _httpClientFactory = httpClientFactory; @@ -40,12 +41,12 @@ public class PicartoProvider : Provider public override async Task GetStreamDataAsync(string id) { - var data = await GetStreamDataAsync(new List {id}); + var data = await GetStreamDataAsync(new List { id }); return data.FirstOrDefault(); } - public async override Task> GetStreamDataAsync(List logins) + public override async Task> GetStreamDataAsync(List logins) { if (logins.Count == 0) return new(); @@ -53,17 +54,17 @@ public class PicartoProvider : Provider using var http = _httpClientFactory.CreateClient(); var toReturn = new List(); foreach (var login in logins) - { try { http.DefaultRequestHeaders.Accept.Add(new("application/json")); // get id based on the username var res = await http.GetAsync($"https://api.picarto.tv/v1/channel/name/{login}"); - + if (!res.IsSuccessStatusCode) continue; - - var userData = JsonConvert.DeserializeObject(await res.Content.ReadAsStringAsync())!; + + var userData = + JsonConvert.DeserializeObject(await res.Content.ReadAsStringAsync())!; toReturn.Add(ToStreamData(userData)); _failingStreams.TryRemove(login, out _); @@ -73,11 +74,9 @@ public class PicartoProvider : Provider Log.Warning("Something went wrong retreiving {StreamPlatform} stream data for {Login}: {ErrorMessage}", Platform, login, - ex.Message - ); + ex.Message); _failingStreams.TryAdd(login, DateTime.UtcNow); } - } return toReturn; } @@ -96,4 +95,4 @@ public class PicartoProvider : Provider StreamUrl = $"https://picarto.tv/{stream.Name}", AvatarUrl = stream.Avatar }; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/Provider.cs b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/Provider.cs index c94fd47d5..dc05a246f 100644 --- a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/Provider.cs +++ b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/Provider.cs @@ -3,55 +3,55 @@ namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers; /// -/// Abstract class implemented by providers of all supported platforms +/// Abstract class implemented by providers of all supported platforms /// public abstract class Provider { /// - /// Type of the platform. + /// Type of the platform. /// public abstract FollowedStream.FType Platform { get; } /// - /// Checks whether the specified url is a valid stream url for this platform. + /// Gets the stream usernames which fail to execute due to an error, and when they started throwing errors. + /// This can happen if stream name is invalid, or if the stream doesn't exist anymore. + /// + public IEnumerable<(string Login, DateTime ErroringSince)> FailingStreams + => _failingStreams.Select(entry => (entry.Key, entry.Value)).ToList(); + + /// + /// When was the first time the stream continually had errors while being retrieved + /// + protected readonly ConcurrentDictionary _failingStreams = new(); + + /// + /// Checks whether the specified url is a valid stream url for this platform. /// /// Url to check /// True if valid, otherwise false public abstract Task IsValidUrl(string url); - + /// - /// Gets stream data of the stream on the specified url on this + /// Gets stream data of the stream on the specified url on this /// /// Url of the stream - /// of the specified stream. Null if none found + /// of the specified stream. Null if none found public abstract Task GetStreamDataByUrlAsync(string url); - + /// - /// Gets stream data of the specified id/username on this + /// Gets stream data of the specified id/username on this /// /// Name (or id where applicable) of the user on the platform - /// of the user. Null if none found + /// of the user. Null if none found public abstract Task GetStreamDataAsync(string id); /// - /// Gets stream data of all specified ids/usernames on this + /// Gets stream data of all specified ids/usernames on this /// /// List of ids/usernames - /// of all users, in the same order. Null for every id/user not found. + /// of all users, in the same order. Null for every id/user not found. public abstract Task> GetStreamDataAsync(List usernames); - /// - /// Gets the stream usernames which fail to execute due to an error, and when they started throwing errors. - /// This can happen if stream name is invalid, or if the stream doesn't exist anymore. - /// - public IEnumerable<(string Login, DateTime ErroringSince)> FailingStreams => - _failingStreams.Select(entry => (entry.Key, entry.Value)).ToList(); - - /// - /// When was the first time the stream continually had errors while being retrieved - /// - protected readonly ConcurrentDictionary _failingStreams = new(); - - public void ClearErrorsFor(string login) + public void ClearErrorsFor(string login) => _failingStreams.TryRemove(login, out _); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/TwitchHelixProvider.cs b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/TwitchHelixProvider.cs index 42194c753..cf7fb3466 100644 --- a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/TwitchHelixProvider.cs +++ b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/TwitchHelixProvider.cs @@ -178,4 +178,4 @@ // public DateTime StartedAt { get; set; } // } // } -// } +// } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/TwitchProvider.cs b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/TwitchProvider.cs index ef19756d2..deffae666 100644 --- a/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/TwitchProvider.cs +++ b/src/NadekoBot/Modules/Searches/Common/StreamNotifications/Providers/TwitchProvider.cs @@ -1,17 +1,18 @@ -using System.Text.RegularExpressions; -using NadekoBot.Db.Models; +using NadekoBot.Db.Models; using Newtonsoft.Json; +using System.Text.RegularExpressions; namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers; public class TwitchProvider : Provider { - private readonly IHttpClientFactory _httpClientFactory; - private static Regex Regex { get; } = new(@"twitch.tv/(?.+[^/])/?", RegexOptions.Compiled | RegexOptions.IgnoreCase); - public override FollowedStream.FType Platform => FollowedStream.FType.Twitch; + public override FollowedStream.FType Platform + => FollowedStream.FType.Twitch; + + private readonly IHttpClientFactory _httpClientFactory; public TwitchProvider(IHttpClientFactory httpClientFactory) => _httpClientFactory = httpClientFactory; @@ -40,7 +41,7 @@ public class TwitchProvider : Provider public override async Task GetStreamDataAsync(string id) { - var data = await GetStreamDataAsync(new List {id}); + var data = await GetStreamDataAsync(new List { id }); return data.FirstOrDefault(); } @@ -56,7 +57,6 @@ public class TwitchProvider : Provider var toReturn = new List(); foreach (var login in logins) - { try { // get id based on the username @@ -70,8 +70,7 @@ public class TwitchProvider : Provider // get stream data var str = await http.GetStringAsync($"https://api.twitch.tv/kraken/streams/{user.Id}"); - var resObj = - JsonConvert.DeserializeAnonymousType(str, new {Stream = new TwitchResponseV5.Stream()}); + var resObj = JsonConvert.DeserializeAnonymousType(str, new { Stream = new TwitchResponseV5.Stream() }); // if stream is null, user is not streaming if (resObj?.Stream is null) @@ -79,7 +78,7 @@ public class TwitchProvider : Provider // if user is not streaming, get his offline banner var chStr = await http.GetStringAsync($"https://api.twitch.tv/kraken/channels/{user.Id}"); var ch = JsonConvert.DeserializeObject(chStr)!; - + toReturn.Add(new() { StreamType = FollowedStream.FType.Twitch, @@ -102,11 +101,9 @@ public class TwitchProvider : Provider Log.Warning("Something went wrong retreiving {StreamPlatform} stream data for {Login}: {ErrorMessage}", Platform, login, - ex.Message - ); + ex.Message); _failingStreams.TryAdd(login, DateTime.UtcNow); } - } return toReturn; } @@ -125,4 +122,4 @@ public class TwitchProvider : Provider StreamUrl = $"https://twitch.tv/{stream.Channel.Name}", AvatarUrl = stream.Channel.Logo }; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/TimeData.cs b/src/NadekoBot/Modules/Searches/Common/TimeData.cs index 1c7f095be..33f67a2db 100644 --- a/src/NadekoBot/Modules/Searches/Common/TimeData.cs +++ b/src/NadekoBot/Modules/Searches/Common/TimeData.cs @@ -6,4 +6,4 @@ public class TimeData public string Address { get; set; } public DateTime Time { get; set; } public string TimeZoneName { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/TimeModels.cs b/src/NadekoBot/Modules/Searches/Common/TimeModels.cs index 4fc9c6413..86d227bc9 100644 --- a/src/NadekoBot/Modules/Searches/Common/TimeModels.cs +++ b/src/NadekoBot/Modules/Searches/Common/TimeModels.cs @@ -7,6 +7,7 @@ public class TimeZoneResult { [JsonProperty("abbreviation")] public string TimezoneName { get; set; } + [JsonProperty("timestamp")] public int Timestamp { get; set; } } @@ -18,4 +19,4 @@ public class LocationIqResponse [JsonProperty("display_name")] public string DisplayName { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/UrbanDef.cs b/src/NadekoBot/Modules/Searches/Common/UrbanDef.cs index b97e6016b..d92c53d2c 100644 --- a/src/NadekoBot/Modules/Searches/Common/UrbanDef.cs +++ b/src/NadekoBot/Modules/Searches/Common/UrbanDef.cs @@ -5,9 +5,10 @@ public class UrbanResponse { public UrbanDef[] List { get; set; } } + public class UrbanDef { public string Word { get; set; } public string Definition { get; set; } public string Permalink { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/WeatherModels.cs b/src/NadekoBot/Modules/Searches/Common/WeatherModels.cs index 2aa9eebe7..669ac37fe 100644 --- a/src/NadekoBot/Modules/Searches/Common/WeatherModels.cs +++ b/src/NadekoBot/Modules/Searches/Common/WeatherModels.cs @@ -22,8 +22,10 @@ public class Main public double Temp { get; set; } public float Pressure { get; set; } public float Humidity { get; set; } + [JsonProperty("temp_min")] public double TempMin { get; set; } + [JsonProperty("temp_max")] public double TempMax { get; set; } } @@ -62,4 +64,4 @@ public class WeatherData public int Id { get; set; } public string Name { get; set; } public int Cod { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/WikipediaApiModel.cs b/src/NadekoBot/Modules/Searches/Common/WikipediaApiModel.cs index 94f016f02..b8bfab1bc 100644 --- a/src/NadekoBot/Modules/Searches/Common/WikipediaApiModel.cs +++ b/src/NadekoBot/Modules/Searches/Common/WikipediaApiModel.cs @@ -15,4 +15,4 @@ public class WikipediaApiModel public string FullUrl { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Common/WoWJoke.cs b/src/NadekoBot/Modules/Searches/Common/WoWJoke.cs index 45f42664c..eebbad410 100644 --- a/src/NadekoBot/Modules/Searches/Common/WoWJoke.cs +++ b/src/NadekoBot/Modules/Searches/Common/WoWJoke.cs @@ -5,5 +5,7 @@ public class WoWJoke { public string Question { get; set; } public string Answer { get; set; } - public override string ToString() => $"`{Question}`\n\n**{Answer}**"; -} + + public override string ToString() + => $"`{Question}`\n\n**{Answer}**"; +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/CryptoCommands.cs b/src/NadekoBot/Modules/Searches/CryptoCommands.cs index 3f9576255..07e410254 100644 --- a/src/NadekoBot/Modules/Searches/CryptoCommands.cs +++ b/src/NadekoBot/Modules/Searches/CryptoCommands.cs @@ -7,7 +7,8 @@ public partial class Searches { public class CryptoCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Crypto(string name) { name = name?.ToUpperInvariant(); @@ -20,13 +21,11 @@ public partial class Searches if (nearest is not null) { var embed = _eb.Create() - .WithTitle(GetText(strs.crypto_not_found)) - .WithDescription(GetText(strs.did_you_mean(Format.Bold($"{nearest.Name} ({nearest.Symbol})")))); + .WithTitle(GetText(strs.crypto_not_found)) + .WithDescription( + GetText(strs.did_you_mean(Format.Bold($"{nearest.Name} ({nearest.Symbol})")))); - if (await PromptUserConfirmAsync(embed)) - { - crypto = nearest; - } + if (await PromptUserConfirmAsync(embed)) crypto = nearest; } if (crypto is null) @@ -44,15 +43,21 @@ public partial class Searches : crypto.Quote.Usd.Percent_Change_24h; await ctx.Channel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle($"{crypto.Name} ({crypto.Symbol})") - .WithUrl($"https://coinmarketcap.com/currencies/{crypto.Slug}/") - .WithThumbnailUrl($"https://s3.coinmarketcap.com/static/img/coins/128x128/{crypto.Id}.png") - .AddField(GetText(strs.market_cap), $"${crypto.Quote.Usd.Market_Cap:n0}", true) - .AddField(GetText(strs.price), $"${crypto.Quote.Usd.Price}", true) - .AddField(GetText(strs.volume_24h), $"${crypto.Quote.Usd.Volume_24h:n0}", true) - .AddField(GetText(strs.change_7d_24h), $"{sevenDay}% / {lastDay}%", true) - .WithImageUrl($"https://s3.coinmarketcap.com/generated/sparklines/web/7d/usd/{crypto.Id}.png")); + .WithOkColor() + .WithTitle($"{crypto.Name} ({crypto.Symbol})") + .WithUrl($"https://coinmarketcap.com/currencies/{crypto.Slug}/") + .WithThumbnailUrl( + $"https://s3.coinmarketcap.com/static/img/coins/128x128/{crypto.Id}.png") + .AddField(GetText(strs.market_cap), + $"${crypto.Quote.Usd.Market_Cap:n0}", + true) + .AddField(GetText(strs.price), $"${crypto.Quote.Usd.Price}", true) + .AddField(GetText(strs.volume_24h), + $"${crypto.Quote.Usd.Volume_24h:n0}", + true) + .AddField(GetText(strs.change_7d_24h), $"{sevenDay}% / {lastDay}%", true) + .WithImageUrl( + $"https://s3.coinmarketcap.com/generated/sparklines/web/7d/usd/{crypto.Id}.png")); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/FeedCommands.cs b/src/NadekoBot/Modules/Searches/FeedCommands.cs index 36c663696..f25255559 100644 --- a/src/NadekoBot/Modules/Searches/FeedCommands.cs +++ b/src/NadekoBot/Modules/Searches/FeedCommands.cs @@ -1,4 +1,5 @@ #nullable disable +using CodeHollow.FeedReader; using NadekoBot.Modules.Searches.Services; using System.Text.RegularExpressions; @@ -11,36 +12,35 @@ public partial class Searches { private static readonly Regex YtChannelRegex = new(@"youtube\.com\/(?:c\/|channel\/|user\/)?(?[a-zA-Z0-9\-]{1,})"); - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public Task YtUploadNotif(string url, [Leftover] ITextChannel channel = null) { var m = YtChannelRegex.Match(url); - if (!m.Success) - { - return ReplyErrorLocalizedAsync(strs.invalid_input); - } + if (!m.Success) return ReplyErrorLocalizedAsync(strs.invalid_input); var channelId = m.Groups["channelid"].Value; return Feed("https://www.youtube.com/feeds/videos.xml?channel_id=" + channelId, channel); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task Feed(string url, [Leftover] ITextChannel channel = null) { - var success = Uri.TryCreate(url, UriKind.Absolute, out var uri) && - (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps); + var success = Uri.TryCreate(url, UriKind.Absolute, out var uri) + && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps); if (success) { channel ??= (ITextChannel)ctx.Channel; try { - var feeds = await CodeHollow.FeedReader.FeedReader.ReadAsync(url); + var feeds = await FeedReader.ReadAsync(url); } catch (Exception ex) { @@ -62,20 +62,20 @@ public partial class Searches await ReplyConfirmLocalizedAsync(strs.feed_not_valid); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task FeedRemove(int index) { if (_service.RemoveFeed(ctx.Guild.Id, --index)) - { await ReplyConfirmLocalizedAsync(strs.feed_removed); - } else await ReplyErrorLocalizedAsync(strs.feed_out_of_range); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task FeedList() @@ -84,24 +84,22 @@ public partial class Searches if (!feeds.Any()) { - await ctx.Channel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithDescription(GetText(strs.feed_no_feed))); + await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor().WithDescription(GetText(strs.feed_no_feed))); return; } - await ctx.SendPaginatedConfirmAsync(0, cur => - { - var embed = _eb.Create() - .WithOkColor(); - var i = 0; - var fs = string.Join("\n", feeds.Skip(cur * 10) - .Take(10) - .Select(x => $"`{(cur * 10) + ++i}.` <#{x.ChannelId}> {x.Url}")); + await ctx.SendPaginatedConfirmAsync(0, + cur => + { + var embed = _eb.Create().WithOkColor(); + var i = 0; + var fs = string.Join("\n", + feeds.Skip(cur * 10).Take(10).Select(x => $"`{(cur * 10) + ++i}.` <#{x.ChannelId}> {x.Url}")); - return embed.WithDescription(fs); - - }, feeds.Count, 10); + return embed.WithDescription(fs); + }, + feeds.Count, + 10); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/JokeCommands.cs b/src/NadekoBot/Modules/Searches/JokeCommands.cs index c5d82c240..e2f88de1b 100644 --- a/src/NadekoBot/Modules/Searches/JokeCommands.cs +++ b/src/NadekoBot/Modules/Searches/JokeCommands.cs @@ -8,23 +8,26 @@ public partial class Searches [Group] public class JokeCommands : NadekoSubmodule { - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Yomama() => await SendConfirmAsync(await _service.GetYomamaJoke()); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Randjoke() { var (setup, punchline) = await _service.GetRandomJoke(); await SendConfirmAsync(setup, punchline); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ChuckNorris() => await SendConfirmAsync(await _service.GetChuckNorrisJoke()); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task WowJoke() { if (!_service.WowJokes.Any()) @@ -32,11 +35,13 @@ public partial class Searches await ReplyErrorLocalizedAsync(strs.jokes_not_loaded); return; } + var joke = _service.WowJokes[new NadekoRandom().Next(0, _service.WowJokes.Count)]; await SendConfirmAsync(joke.Question, joke.Answer); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task MagicItem() { if (!_service.WowJokes.Any()) @@ -44,9 +49,10 @@ public partial class Searches await ReplyErrorLocalizedAsync(strs.magicitems_not_loaded); return; } + var item = _service.MagicItems[new NadekoRandom().Next(0, _service.MagicItems.Count)]; await SendConfirmAsync("✨" + item.Name, item.Description); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/MemegenCommands.cs b/src/NadekoBot/Modules/Searches/MemegenCommands.cs index d9d104d74..a82ff1143 100644 --- a/src/NadekoBot/Modules/Searches/MemegenCommands.cs +++ b/src/NadekoBot/Modules/Searches/MemegenCommands.cs @@ -1,7 +1,7 @@ #nullable disable +using Newtonsoft.Json; using System.Collections.Immutable; using System.Text; -using Newtonsoft.Json; namespace NadekoBot.Modules.Searches; @@ -10,29 +10,25 @@ public partial class Searches [Group] public class MemegenCommands : NadekoSubmodule { - private class MemegenTemplate + private static readonly ImmutableDictionary _map = new Dictionary { - public string Name { get; set; } - public string Id { get; set; } - } - private static readonly ImmutableDictionary _map = new Dictionary() - { - {'?', "~q"}, - {'%', "~p"}, - {'#', "~h"}, - {'/', "~s"}, - {' ', "-"}, - {'-', "--"}, - {'_', "__"}, - {'"', "''"} - + { '?', "~q" }, + { '%', "~p" }, + { '#', "~h" }, + { '/', "~s" }, + { ' ', "-" }, + { '-', "--" }, + { '_', "__" }, + { '"', "''" } }.ToImmutableDictionary(); + private readonly IHttpClientFactory _httpFactory; public MemegenCommands(IHttpClientFactory factory) => _httpFactory = factory; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Memelist(int page = 1) { if (--page < 0) @@ -42,37 +38,38 @@ public partial class Searches var res = await http.GetAsync("https://api.memegen.link/templates/"); var rawJson = await res.Content.ReadAsStringAsync(); - + var data = JsonConvert.DeserializeObject>(rawJson); - await ctx.SendPaginatedConfirmAsync(page, curPage => - { - var templates = string.Empty; - foreach (var template in data.Skip(curPage * 15).Take(15)) + await ctx.SendPaginatedConfirmAsync(page, + curPage => { - templates += $"**{template.Name}:**\n key: `{template.Id}`\n"; - } - var embed = _eb.Create() - .WithOkColor() - .WithDescription(templates); + var templates = string.Empty; + foreach (var template in data.Skip(curPage * 15).Take(15)) + templates += $"**{template.Name}:**\n key: `{template.Id}`\n"; + var embed = _eb.Create().WithOkColor().WithDescription(templates); - return embed; - }, data.Count, 15); + return embed; + }, + data.Count, + 15); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Memegen(string meme, [Leftover] string memeText = null) { var memeUrl = $"http://api.memegen.link/{meme}"; if (!string.IsNullOrWhiteSpace(memeText)) { var memeTextArray = memeText.Split(';'); - foreach(var text in memeTextArray) + foreach (var text in memeTextArray) { var newText = Replace(text); memeUrl += $"/{newText}"; } } + memeUrl += ".png"; await ctx.Channel.SendMessageAsync(memeUrl); } @@ -82,14 +79,18 @@ public partial class Searches var sb = new StringBuilder(); foreach (var c in input) - { if (_map.TryGetValue(c, out var tmp)) sb.Append(tmp); else sb.Append(c); - } return sb.ToString(); } + + private class MemegenTemplate + { + public string Name { get; set; } + public string Id { get; set; } + } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/OsuCommands.cs b/src/NadekoBot/Modules/Searches/OsuCommands.cs index 52260ebc3..f237165c1 100644 --- a/src/NadekoBot/Modules/Searches/OsuCommands.cs +++ b/src/NadekoBot/Modules/Searches/OsuCommands.cs @@ -18,16 +18,15 @@ public partial class Searches _httpFactory = factory; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Osu(string user, [Leftover] string mode = null) { if (string.IsNullOrWhiteSpace(user)) return; using var http = _httpFactory.CreateClient(); - var modeNumber = string.IsNullOrWhiteSpace(mode) - ? 0 - : ResolveGameMode(mode); + var modeNumber = string.IsNullOrWhiteSpace(mode) ? 0 : ResolveGameMode(mode); try { @@ -52,17 +51,18 @@ public partial class Searches var userId = obj.UserId; await ctx.Channel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle($"osu! {smode} profile for {user}") - .WithThumbnailUrl($"https://a.ppy.sh/{userId}") - .WithDescription($"https://osu.ppy.sh/u/{userId}") - .AddField("Official Rank", $"#{obj.PpRank}", true) - .AddField("Country Rank", $"#{obj.PpCountryRank} :flag_{obj.Country.ToLower()}:", true) - .AddField("Total PP", Math.Round(obj.PpRaw, 2), true) - .AddField("Accuracy", Math.Round(obj.Accuracy, 2) + "%", true) - .AddField("Playcount", obj.Playcount, true) - .AddField("Level", Math.Round(obj.Level), true) - ); + .WithOkColor() + .WithTitle($"osu! {smode} profile for {user}") + .WithThumbnailUrl($"https://a.ppy.sh/{userId}") + .WithDescription($"https://osu.ppy.sh/u/{userId}") + .AddField("Official Rank", $"#{obj.PpRank}", true) + .AddField("Country Rank", + $"#{obj.PpCountryRank} :flag_{obj.Country.ToLower()}:", + true) + .AddField("Total PP", Math.Round(obj.PpRaw, 2), true) + .AddField("Accuracy", Math.Round(obj.Accuracy, 2) + "%", true) + .AddField("Playcount", obj.Playcount, true) + .AddField("Level", Math.Round(obj.Level), true)); } catch (ArgumentOutOfRangeException) { @@ -75,17 +75,15 @@ public partial class Searches } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Gatari(string user, [Leftover] string mode = null) { using var http = _httpFactory.CreateClient(); - var modeNumber = string.IsNullOrWhiteSpace(mode) - ? 0 - : ResolveGameMode(mode); + var modeNumber = string.IsNullOrWhiteSpace(mode) ? 0 : ResolveGameMode(mode); var modeStr = ResolveGameMode(modeNumber); - var resString = await http - .GetStringAsync($"https://api.gatari.pw/user/stats?u={user}&mode={modeNumber}"); + var resString = await http.GetStringAsync($"https://api.gatari.pw/user/stats?u={user}&mode={modeNumber}"); var statsResponse = JsonConvert.DeserializeObject(resString); if (statsResponse.Code != 200 || statsResponse.Stats.Id == 0) @@ -100,23 +98,27 @@ public partial class Searches var userStats = statsResponse.Stats; var embed = _eb.Create() - .WithOkColor() - .WithTitle($"osu!Gatari {modeStr} profile for {user}") - .WithThumbnailUrl($"https://a.gatari.pw/{userStats.Id}") - .WithDescription($"https://osu.gatari.pw/u/{userStats.Id}") - .AddField("Official Rank", $"#{userStats.Rank}", true) - .AddField("Country Rank", $"#{userStats.CountryRank} :flag_{userData.Country.ToLower()}:", true) - .AddField("Total PP", userStats.Pp, true) - .AddField("Accuracy", $"{Math.Round(userStats.AvgAccuracy, 2)}%", true) - .AddField("Playcount", userStats.Playcount, true) - .AddField("Level", userStats.Level, true); + .WithOkColor() + .WithTitle($"osu!Gatari {modeStr} profile for {user}") + .WithThumbnailUrl($"https://a.gatari.pw/{userStats.Id}") + .WithDescription($"https://osu.gatari.pw/u/{userStats.Id}") + .AddField("Official Rank", $"#{userStats.Rank}", true) + .AddField("Country Rank", + $"#{userStats.CountryRank} :flag_{userData.Country.ToLower()}:", + true) + .AddField("Total PP", userStats.Pp, true) + .AddField("Accuracy", $"{Math.Round(userStats.AvgAccuracy, 2)}%", true) + .AddField("Playcount", userStats.Playcount, true) + .AddField("Level", userStats.Level, true); await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Osu5(string user, [Leftover] string mode = null) - {; + { + ; if (string.IsNullOrWhiteSpace(_creds.OsuApiKey)) { await SendErrorAsync("An osu! API key is required."); @@ -131,26 +133,23 @@ public partial class Searches using var http = _httpFactory.CreateClient(); var m = 0; - if (!string.IsNullOrWhiteSpace(mode)) - { - m = ResolveGameMode(mode); - } + if (!string.IsNullOrWhiteSpace(mode)) m = ResolveGameMode(mode); - var reqString = $"https://osu.ppy.sh/api/get_user_best" + - $"?k={_creds.OsuApiKey}" + - $"&u={Uri.EscapeDataString(user)}" + - $"&type=string" + - $"&limit=5" + - $"&m={m}"; + var reqString = "https://osu.ppy.sh/api/get_user_best" + + $"?k={_creds.OsuApiKey}" + + $"&u={Uri.EscapeDataString(user)}" + + "&type=string" + + "&limit=5" + + $"&m={m}"; var resString = await http.GetStringAsync(reqString); var obj = JsonConvert.DeserializeObject>(resString); var mapTasks = obj.Select(async item => { - var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps" + - $"?k={_creds.OsuApiKey}" + - $"&b={item.BeatmapId}"; + var mapReqString = "https://osu.ppy.sh/api/get_beatmaps" + + $"?k={_creds.OsuApiKey}" + + $"&b={item.BeatmapId}"; var mapResString = await http.GetStringAsync(mapReqString); var map = JsonConvert.DeserializeObject>(mapResString).FirstOrDefault(); @@ -164,23 +163,15 @@ public partial class Searches var desc = $@"[/b/{item.BeatmapId}](https://osu.ppy.sh/b/{item.BeatmapId}) {pp + "pp",-7} | {acc + "%",-7} "; - if (mods != "+") - { - desc += Format.Bold(mods); - } + if (mods != "+") desc += Format.Bold(mods); return (title, desc); }); - - var eb = _eb.Create() - .WithOkColor() - .WithTitle($"Top 5 plays for {user}"); - + + var eb = _eb.Create().WithOkColor().WithTitle($"Top 5 plays for {user}"); + var mapData = await mapTasks.WhenAll(); - foreach (var (title, desc) in mapData.Where(x => x != default)) - { - eb.AddField(title, desc, false); - } + foreach (var (title, desc) in mapData.Where(x => x != default)) eb.AddField(title, desc); await ctx.Channel.EmbedAsync(eb); } @@ -192,11 +183,8 @@ public partial class Searches double totalHits; if (mode == 0) { - hitPoints = (play.Count50 * 50) + - (play.Count100 * 100) + - (play.Count300 * 300); - totalHits = play.Count50 + play.Count100 + - play.Count300 + play.Countmiss; + hitPoints = (play.Count50 * 50) + (play.Count100 * 100) + (play.Count300 * 300); + totalHits = play.Count50 + play.Count100 + play.Count300 + play.Countmiss; totalHits *= 300; } else if (mode == 1) @@ -208,18 +196,22 @@ public partial class Searches else if (mode == 2) { hitPoints = play.Count50 + play.Count100 + play.Count300; - totalHits = play.Countmiss + play.Count50 + play.Count100 + play.Count300 + - play.Countkatu; + totalHits = play.Countmiss + play.Count50 + play.Count100 + play.Count300 + play.Countkatu; } else { - hitPoints = (play.Count50 * 50) + - (play.Count100 * 100) + - (play.Countkatu * 200) + - ((play.Count300 + play.Countgeki) * 300); + hitPoints = (play.Count50 * 50) + + (play.Count100 * 100) + + (play.Countkatu * 200) + + ((play.Count300 + play.Countgeki) * 300); - totalHits = (play.Countmiss + play.Count50 + play.Count100 + - play.Countkatu + play.Count300 + play.Countgeki) * 300; + totalHits = (play.Countmiss + + play.Count50 + + play.Count100 + + play.Countkatu + + play.Count300 + + play.Countgeki) + * 300; } @@ -266,7 +258,7 @@ public partial class Searches //https://github.com/ppy/osu-api/wiki#mods private static string ResolveMods(int mods) { - var modString = $"+"; + var modString = "+"; if (IsBitSet(mods, 0)) modString += "NF"; @@ -300,7 +292,7 @@ public partial class Searches return modString; } - private static bool IsBitSet(int mods, int pos) => - (mods & (1 << pos)) != 0; + private static bool IsBitSet(int mods, int pos) + => (mods & (1 << pos)) != 0; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/PathOfExileCommands.cs b/src/NadekoBot/Modules/Searches/PathOfExileCommands.cs index 88007cb96..d1610ce04 100644 --- a/src/NadekoBot/Modules/Searches/PathOfExileCommands.cs +++ b/src/NadekoBot/Modules/Searches/PathOfExileCommands.cs @@ -3,6 +3,8 @@ using NadekoBot.Modules.Searches.Common; using NadekoBot.Modules.Searches.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using System.Globalization; +using System.Text; using System.Text.RegularExpressions; namespace NadekoBot.Modules.Searches; @@ -15,16 +17,105 @@ public partial class Searches private const string _poeURL = "https://www.pathofexile.com/character-window/get-characters?accountName="; private const string _ponURL = "http://poe.ninja/api/Data/GetCurrencyOverview?league="; private const string _pogsURL = "http://pathofexile.gamepedia.com/api.php?action=opensearch&search="; - private const string _pogURL = "https://pathofexile.gamepedia.com/api.php?action=browsebysubject&format=json&subject="; - private const string _pogiURL = "https://pathofexile.gamepedia.com/api.php?action=query&prop=imageinfo&iiprop=url&format=json&titles=File:"; + + private const string _pogURL = + "https://pathofexile.gamepedia.com/api.php?action=browsebysubject&format=json&subject="; + + private const string _pogiURL = + "https://pathofexile.gamepedia.com/api.php?action=query&prop=imageinfo&iiprop=url&format=json&titles=File:"; + private const string _profileURL = "https://www.pathofexile.com/account/view-profile/"; private readonly IHttpClientFactory _httpFactory; + private Dictionary currencyDictionary = new(StringComparer.OrdinalIgnoreCase) + { + { "Chaos Orb", "Chaos Orb" }, + { "Orb of Alchemy", "Orb of Alchemy" }, + { "Jeweller's Orb", "Jeweller's Orb" }, + { "Exalted Orb", "Exalted Orb" }, + { "Mirror of Kalandra", "Mirror of Kalandra" }, + { "Vaal Orb", "Vaal Orb" }, + { "Orb of Alteration", "Orb of Alteration" }, + { "Orb of Scouring", "Orb of Scouring" }, + { "Divine Orb", "Divine Orb" }, + { "Orb of Annulment", "Orb of Annulment" }, + { "Master Cartographer's Sextant", "Master Cartographer's Sextant" }, + { "Journeyman Cartographer's Sextant", "Journeyman Cartographer's Sextant" }, + { "Apprentice Cartographer's Sextant", "Apprentice Cartographer's Sextant" }, + { "Blessed Orb", "Blessed Orb" }, + { "Orb of Regret", "Orb of Regret" }, + { "Gemcutter's Prism", "Gemcutter's Prism" }, + { "Glassblower's Bauble", "Glassblower's Bauble" }, + { "Orb of Fusing", "Orb of Fusing" }, + { "Cartographer's Chisel", "Cartographer's Chisel" }, + { "Chromatic Orb", "Chromatic Orb" }, + { "Orb of Augmentation", "Orb of Augmentation" }, + { "Blacksmith's Whetstone", "Blacksmith's Whetstone" }, + { "Orb of Transmutation", "Orb of Transmutation" }, + { "Armourer's Scrap", "Armourer's Scrap" }, + { "Scroll of Wisdom", "Scroll of Wisdom" }, + { "Regal Orb", "Regal Orb" }, + { "Chaos", "Chaos Orb" }, + { "Alch", "Orb of Alchemy" }, + { "Alchs", "Orb of Alchemy" }, + { "Jews", "Jeweller's Orb" }, + { "Jeweller", "Jeweller's Orb" }, + { "Jewellers", "Jeweller's Orb" }, + { "Jeweller's", "Jeweller's Orb" }, + { "X", "Exalted Orb" }, + { "Ex", "Exalted Orb" }, + { "Exalt", "Exalted Orb" }, + { "Exalts", "Exalted Orb" }, + { "Mirror", "Mirror of Kalandra" }, + { "Mirrors", "Mirror of Kalandra" }, + { "Vaal", "Vaal Orb" }, + { "Alt", "Orb of Alteration" }, + { "Alts", "Orb of Alteration" }, + { "Scour", "Orb of Scouring" }, + { "Scours", "Orb of Scouring" }, + { "Divine", "Divine Orb" }, + { "Annul", "Orb of Annulment" }, + { "Annulment", "Orb of Annulment" }, + { "Master Sextant", "Master Cartographer's Sextant" }, + { "Journeyman Sextant", "Journeyman Cartographer's Sextant" }, + { "Apprentice Sextant", "Apprentice Cartographer's Sextant" }, + { "Blessed", "Blessed Orb" }, + { "Regret", "Orb of Regret" }, + { "Regrets", "Orb of Regret" }, + { "Gcp", "Gemcutter's Prism" }, + { "Glassblowers", "Glassblower's Bauble" }, + { "Glassblower's", "Glassblower's Bauble" }, + { "Fusing", "Orb of Fusing" }, + { "Fuses", "Orb of Fusing" }, + { "Fuse", "Orb of Fusing" }, + { "Chisel", "Cartographer's Chisel" }, + { "Chisels", "Cartographer's Chisel" }, + { "Chance", "Orb of Chance" }, + { "Chances", "Orb of Chance" }, + { "Chrome", "Chromatic Orb" }, + { "Chromes", "Chromatic Orb" }, + { "Aug", "Orb of Augmentation" }, + { "Augmentation", "Orb of Augmentation" }, + { "Augment", "Orb of Augmentation" }, + { "Augments", "Orb of Augmentation" }, + { "Whetstone", "Blacksmith's Whetstone" }, + { "Whetstones", "Blacksmith's Whetstone" }, + { "Transmute", "Orb of Transmutation" }, + { "Transmutes", "Orb of Transmutation" }, + { "Armourers", "Armourer's Scrap" }, + { "Armourer's", "Armourer's Scrap" }, + { "Wisdom Scroll", "Scroll of Wisdom" }, + { "Wisdom Scrolls", "Scroll of Wisdom" }, + { "Regal", "Regal Orb" }, + { "Regals", "Regal Orb" } + }; + public PathOfExileCommands(IHttpClientFactory httpFactory) => _httpFactory = httpFactory; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task PathOfExile(string usr, string league = "", int page = 1) { if (--page < 0) @@ -46,53 +137,48 @@ public partial class Searches } catch { - var embed = _eb.Create() - .WithDescription(GetText(strs.account_not_found)) - .WithErrorColor(); + var embed = _eb.Create().WithDescription(GetText(strs.account_not_found)).WithErrorColor(); await ctx.Channel.EmbedAsync(embed); return; } - if (!string.IsNullOrWhiteSpace(league)) - { - characters.RemoveAll(c => c.League != league); - } + if (!string.IsNullOrWhiteSpace(league)) characters.RemoveAll(c => c.League != league); - await ctx.SendPaginatedConfirmAsync(page, curPage => - { - var embed = _eb.Create() - .WithAuthor($"Characters on {usr}'s account", - "https://web.poecdn.com/image/favicon/ogimage.png", - $"{_profileURL}{usr}") - .WithOkColor(); - - var tempList = characters.Skip(curPage * 9).Take(9).ToList(); - - if (characters.Count == 0) + await ctx.SendPaginatedConfirmAsync(page, + curPage => { - return embed.WithDescription("This account has no characters."); - } - else - { - var sb = new System.Text.StringBuilder(); + var embed = _eb.Create() + .WithAuthor($"Characters on {usr}'s account", + "https://web.poecdn.com/image/favicon/ogimage.png", + $"{_profileURL}{usr}") + .WithOkColor(); + + var tempList = characters.Skip(curPage * 9).Take(9).ToList(); + + if (characters.Count == 0) return embed.WithDescription("This account has no characters."); + + var sb = new StringBuilder(); sb.AppendLine($"```{"#",-5}{"Character Name",-23}{"League",-10}{"Class",-13}{"Level",-3}"); for (var i = 0; i < tempList.Count; i++) { var character = tempList[i]; - sb.AppendLine($"#{i + 1 + (curPage * 9),-4}{character.Name,-23}{ShortLeagueName(character.League),-10}{character.Class,-13}{character.Level,-3}"); + sb.AppendLine( + $"#{i + 1 + (curPage * 9),-4}{character.Name,-23}{ShortLeagueName(character.League),-10}{character.Class,-13}{character.Level,-3}"); } sb.AppendLine("```"); embed.WithDescription(sb.ToString()); return embed; - } - }, characters.Count, 9, true); + }, + characters.Count, + 9); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task PathOfExileLeagues() { var leagues = new List(); @@ -105,21 +191,19 @@ public partial class Searches } catch { - var eembed = _eb.Create() - .WithDescription(GetText(strs.leagues_not_found)) - .WithErrorColor(); + var eembed = _eb.Create().WithDescription(GetText(strs.leagues_not_found)).WithErrorColor(); await ctx.Channel.EmbedAsync(eembed); return; } var embed = _eb.Create() - .WithAuthor($"Path of Exile Leagues", - "https://web.poecdn.com/image/favicon/ogimage.png", - "https://www.pathofexile.com") - .WithOkColor(); + .WithAuthor("Path of Exile Leagues", + "https://web.poecdn.com/image/favicon/ogimage.png", + "https://www.pathofexile.com") + .WithOkColor(); - var sb = new System.Text.StringBuilder(); + var sb = new StringBuilder(); sb.AppendLine($"```{"#",-5}{"League Name",-23}"); for (var i = 0; i < leagues.Count; i++) { @@ -127,6 +211,7 @@ public partial class Searches sb.AppendLine($"#{i + 1,-4}{league.Id,-23}"); } + sb.AppendLine("```"); embed.WithDescription(sb.ToString()); @@ -134,7 +219,8 @@ public partial class Searches await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task PathOfExileCurrency(string leagueName, string currencyName, string convertName = "Chaos Orb") { if (string.IsNullOrWhiteSpace(leagueName)) @@ -142,6 +228,7 @@ public partial class Searches await SendErrorAsync("Please provide league name."); return; } + if (string.IsNullOrWhiteSpace(currencyName)) { await SendErrorAsync("Please provide currency name."); @@ -167,10 +254,12 @@ public partial class Searches } else { - var currencyInput = obj["lines"].Values() - .Where(i => i["currencyTypeName"].Value() == cleanCurrency) - .FirstOrDefault(); - chaosEquivalent = float.Parse(currencyInput["chaosEquivalent"].ToString(), System.Globalization.CultureInfo.InvariantCulture); + var currencyInput = obj["lines"] + .Values() + .Where(i => i["currencyTypeName"].Value() == cleanCurrency) + .FirstOrDefault(); + chaosEquivalent = float.Parse(currencyInput["chaosEquivalent"].ToString(), + CultureInfo.InvariantCulture); } if (cleanConvert == "Chaos Orb") @@ -179,121 +268,35 @@ public partial class Searches } else { - var currencyOutput = obj["lines"].Values() - .Where(i => i["currencyTypeName"].Value() == cleanConvert) - .FirstOrDefault(); - conversionEquivalent = float.Parse(currencyOutput["chaosEquivalent"].ToString(), System.Globalization.CultureInfo.InvariantCulture); + var currencyOutput = obj["lines"] + .Values() + .Where(i => i["currencyTypeName"].Value() == cleanConvert) + .FirstOrDefault(); + conversionEquivalent = float.Parse(currencyOutput["chaosEquivalent"].ToString(), + CultureInfo.InvariantCulture); } var embed = _eb.Create() - .WithAuthor($"{leagueName} Currency Exchange", - "https://web.poecdn.com/image/favicon/ogimage.png", - "http://poe.ninja") - .AddField("Currency Type", cleanCurrency, true) - .AddField($"{cleanConvert} Equivalent", chaosEquivalent / conversionEquivalent, true) - .WithOkColor(); + .WithAuthor($"{leagueName} Currency Exchange", + "https://web.poecdn.com/image/favicon/ogimage.png", + "http://poe.ninja") + .AddField("Currency Type", cleanCurrency, true) + .AddField($"{cleanConvert} Equivalent", chaosEquivalent / conversionEquivalent, true) + .WithOkColor(); await ctx.Channel.EmbedAsync(embed); } catch { - var embed = _eb.Create() - .WithDescription(GetText(strs.ninja_not_found)) - .WithErrorColor(); + var embed = _eb.Create().WithDescription(GetText(strs.ninja_not_found)).WithErrorColor(); await ctx.Channel.EmbedAsync(embed); } } - private Dictionary currencyDictionary = new(StringComparer.OrdinalIgnoreCase) - { - {"Chaos Orb", "Chaos Orb" }, - {"Orb of Alchemy", "Orb of Alchemy" }, - {"Jeweller's Orb", "Jeweller's Orb" }, - {"Exalted Orb", "Exalted Orb" }, - {"Mirror of Kalandra", "Mirror of Kalandra" }, - {"Vaal Orb", "Vaal Orb" }, - {"Orb of Alteration", "Orb of Alteration" }, - {"Orb of Scouring", "Orb of Scouring" }, - {"Divine Orb", "Divine Orb" }, - {"Orb of Annulment", "Orb of Annulment" }, - {"Master Cartographer's Sextant", "Master Cartographer's Sextant" }, - {"Journeyman Cartographer's Sextant", "Journeyman Cartographer's Sextant" }, - {"Apprentice Cartographer's Sextant", "Apprentice Cartographer's Sextant" }, - {"Blessed Orb", "Blessed Orb" }, - {"Orb of Regret", "Orb of Regret" }, - {"Gemcutter's Prism", "Gemcutter's Prism" }, - {"Glassblower's Bauble", "Glassblower's Bauble" }, - {"Orb of Fusing", "Orb of Fusing" }, - {"Cartographer's Chisel", "Cartographer's Chisel" }, - {"Chromatic Orb", "Chromatic Orb" }, - {"Orb of Augmentation", "Orb of Augmentation" }, - {"Blacksmith's Whetstone", "Blacksmith's Whetstone" }, - {"Orb of Transmutation", "Orb of Transmutation" }, - {"Armourer's Scrap", "Armourer's Scrap" }, - {"Scroll of Wisdom", "Scroll of Wisdom" }, - {"Regal Orb", "Regal Orb" }, - {"Chaos", "Chaos Orb" }, - {"Alch", "Orb of Alchemy" }, - {"Alchs", "Orb of Alchemy" }, - {"Jews", "Jeweller's Orb" }, - {"Jeweller", "Jeweller's Orb" }, - {"Jewellers", "Jeweller's Orb" }, - {"Jeweller's", "Jeweller's Orb" }, - {"X", "Exalted Orb" }, - {"Ex", "Exalted Orb" }, - {"Exalt", "Exalted Orb" }, - {"Exalts", "Exalted Orb" }, - {"Mirror", "Mirror of Kalandra" }, - {"Mirrors", "Mirror of Kalandra" }, - {"Vaal", "Vaal Orb" }, - {"Alt", "Orb of Alteration" }, - {"Alts", "Orb of Alteration" }, - {"Scour", "Orb of Scouring" }, - {"Scours", "Orb of Scouring" }, - {"Divine", "Divine Orb" }, - {"Annul", "Orb of Annulment" }, - {"Annulment", "Orb of Annulment" }, - {"Master Sextant", "Master Cartographer's Sextant" }, - {"Journeyman Sextant", "Journeyman Cartographer's Sextant" }, - {"Apprentice Sextant", "Apprentice Cartographer's Sextant" }, - {"Blessed", "Blessed Orb" }, - {"Regret", "Orb of Regret" }, - {"Regrets", "Orb of Regret" }, - {"Gcp", "Gemcutter's Prism" }, - {"Glassblowers", "Glassblower's Bauble" }, - {"Glassblower's", "Glassblower's Bauble" }, - {"Fusing", "Orb of Fusing" }, - {"Fuses", "Orb of Fusing" }, - {"Fuse", "Orb of Fusing" }, - {"Chisel", "Cartographer's Chisel" }, - {"Chisels", "Cartographer's Chisel" }, - {"Chance", "Orb of Chance" }, - {"Chances", "Orb of Chance" }, - {"Chrome", "Chromatic Orb" }, - {"Chromes", "Chromatic Orb" }, - {"Aug", "Orb of Augmentation" }, - {"Augmentation", "Orb of Augmentation" }, - {"Augment", "Orb of Augmentation" }, - {"Augments", "Orb of Augmentation" }, - {"Whetstone", "Blacksmith's Whetstone" }, - {"Whetstones", "Blacksmith's Whetstone" }, - {"Transmute", "Orb of Transmutation" }, - {"Transmutes", "Orb of Transmutation" }, - {"Armourers", "Armourer's Scrap" }, - {"Armourer's", "Armourer's Scrap" }, - {"Wisdom Scroll", "Scroll of Wisdom" }, - {"Wisdom Scrolls", "Scroll of Wisdom" }, - {"Regal", "Regal Orb" }, - {"Regals", "Regal Orb" } - }; - private string ShortCurrencyName(string str) { - if (currencyDictionary.ContainsValue(str)) - { - return str; - } + if (currencyDictionary.ContainsValue(str)) return str; var currency = currencyDictionary[str]; @@ -307,4 +310,4 @@ public partial class Searches return league; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/PlaceCommands.cs b/src/NadekoBot/Modules/Searches/PlaceCommands.cs index 5d9455eba..949172f38 100644 --- a/src/NadekoBot/Modules/Searches/PlaceCommands.cs +++ b/src/NadekoBot/Modules/Searches/PlaceCommands.cs @@ -6,9 +6,6 @@ public partial class Searches [Group] public class PlaceCommands : NadekoSubmodule { - private static readonly string _typesStr = - string.Join(", ", Enum.GetNames(typeof(PlaceType))); - public enum PlaceType { Cage, //http://www.placecage.com @@ -18,15 +15,18 @@ public partial class Searches Bear, //https://www.placebear.com Kitten, //http://placekitten.com Bacon, //http://baconmockup.com - Xoart, //http://xoart.link + Xoart //http://xoart.link } - [NadekoCommand, Aliases] - public async Task Placelist() - => await SendConfirmAsync(GetText(strs.list_of_place_tags(Prefix)), - _typesStr); + private static readonly string _typesStr = string.Join(", ", Enum.GetNames(typeof(PlaceType))); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] + public async Task Placelist() + => await SendConfirmAsync(GetText(strs.list_of_place_tags(Prefix)), _typesStr); + + [NadekoCommand] + [Aliases] public async Task Place(PlaceType placeType, uint width = 0, uint height = 0) { var url = string.Empty; @@ -57,6 +57,7 @@ public partial class Searches url = "http://xoart.link"; break; } + var rng = new NadekoRandom(); if (width is <= 0 or > 1000) width = (uint)rng.Next(250, 850); @@ -69,4 +70,4 @@ public partial class Searches await ctx.Channel.SendMessageAsync(url); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/PokemonSearchCommands.cs b/src/NadekoBot/Modules/Searches/PokemonSearchCommands.cs index e7aff233a..0248b08e4 100644 --- a/src/NadekoBot/Modules/Searches/PokemonSearchCommands.cs +++ b/src/NadekoBot/Modules/Searches/PokemonSearchCommands.cs @@ -1,6 +1,6 @@ #nullable disable -using NadekoBot.Modules.Searches.Services; using NadekoBot.Common.Pokemon; +using NadekoBot.Modules.Searches.Services; namespace NadekoBot.Modules.Searches; @@ -9,15 +9,19 @@ public partial class Searches [Group] public class PokemonSearchCommands : NadekoSubmodule { - private readonly IDataCache _cache; + public IReadOnlyDictionary Pokemons + => _cache.LocalData.Pokemons; - public IReadOnlyDictionary Pokemons => _cache.LocalData.Pokemons; - public IReadOnlyDictionary PokemonAbilities => _cache.LocalData.PokemonAbilities; + public IReadOnlyDictionary PokemonAbilities + => _cache.LocalData.PokemonAbilities; + + private readonly IDataCache _cache; public PokemonSearchCommands(IDataCache cache) => _cache = cache; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Pokemon([Leftover] string pokemon = null) { pokemon = pokemon?.Trim().ToUpperInvariant(); @@ -25,43 +29,51 @@ public partial class Searches return; foreach (var kvp in Pokemons) - { if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant()) { var p = kvp.Value; - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithTitle(kvp.Key.ToTitleCase()) - .WithDescription(p.BaseStats.ToString()) - .WithThumbnailUrl($"https://assets.pokemon.com/assets/cms2/img/pokedex/detail/{p.Id.ToString("000")}.png") - .AddField(GetText(strs.types), string.Join("\n", p.Types), true) - .AddField(GetText(strs.height_weight), GetText(strs.height_weight_val(p.HeightM, p.WeightKg)), true) - .AddField(GetText(strs.abilities), string.Join("\n", p.Abilities.Select(a => a.Value)), true)); + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle(kvp.Key.ToTitleCase()) + .WithDescription(p.BaseStats.ToString()) + .WithThumbnailUrl( + $"https://assets.pokemon.com/assets/cms2/img/pokedex/detail/{p.Id.ToString("000")}.png") + .AddField(GetText(strs.types), string.Join("\n", p.Types), true) + .AddField(GetText(strs.height_weight), + GetText(strs.height_weight_val(p.HeightM, p.WeightKg)), + true) + .AddField(GetText(strs.abilities), + string.Join("\n", p.Abilities.Select(a => a.Value)), + true)); return; } - } + await ReplyErrorLocalizedAsync(strs.pokemon_none); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task PokemonAbility([Leftover] string ability = null) { ability = ability?.Trim().ToUpperInvariant().Replace(" ", "", StringComparison.InvariantCulture); if (string.IsNullOrWhiteSpace(ability)) return; foreach (var kvp in PokemonAbilities) - { if (kvp.Key.ToUpperInvariant() == ability) { - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithTitle(kvp.Value.Name) - .WithDescription(string.IsNullOrWhiteSpace(kvp.Value.Desc) - ? kvp.Value.ShortDesc - : kvp.Value.Desc) - .AddField(GetText(strs.rating), kvp.Value.Rating.ToString(Culture), true)); + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle(kvp.Value.Name) + .WithDescription(string.IsNullOrWhiteSpace(kvp.Value.Desc) + ? kvp.Value.ShortDesc + : kvp.Value.Desc) + .AddField(GetText(strs.rating), + kvp.Value.Rating.ToString(Culture), + true)); return; } - } + await ReplyErrorLocalizedAsync(strs.pokemon_ability_none); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index 854644dc1..f4be8fb88 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -2,6 +2,7 @@ using AngleSharp; using AngleSharp.Html.Dom; using Microsoft.Extensions.Caching.Memory; +using NadekoBot.Modules.Administration.Services; using NadekoBot.Modules.Searches.Common; using NadekoBot.Modules.Searches.Services; using Newtonsoft.Json; @@ -11,20 +12,25 @@ using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using System.Net; -using NadekoBot.Modules.Administration.Services; +using Color = SixLabors.ImageSharp.Color; using Configuration = AngleSharp.Configuration; namespace NadekoBot.Modules.Searches; public partial class Searches : NadekoModule { + private static readonly ConcurrentDictionary cachedShortenedLinks = new(); private readonly IBotCredentials _creds; private readonly IGoogleApiService _google; private readonly IHttpClientFactory _httpFactory; private readonly IMemoryCache _cache; private readonly GuildTimezoneService _tzSvc; - public Searches(IBotCredentials creds, IGoogleApiService google, IHttpClientFactory factory, IMemoryCache cache, + public Searches( + IBotCredentials creds, + IGoogleApiService google, + IHttpClientFactory factory, + IMemoryCache cache, GuildTimezoneService tzSvc) { _creds = creds; @@ -34,21 +40,21 @@ public partial class Searches : NadekoModule _tzSvc = tzSvc; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Rip([Leftover] IGuildUser usr) { var av = usr.RealAvatarUrl(); if (av is null) return; await using var picStream = await _service.GetRipPictureAsync(usr.Nickname ?? usr.Username, av); - await ctx.Channel.SendFileAsync( - picStream, - "rip.png", - $"Rip {Format.Bold(usr.ToString())} \n\t- " + - Format.Italics(ctx.User.ToString())); + await ctx.Channel.SendFileAsync(picStream, + "rip.png", + $"Rip {Format.Bold(usr.ToString())} \n\t- " + Format.Italics(ctx.User.ToString())); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Weather([Leftover] string query) { if (!await ValidateQuery(query)) @@ -59,38 +65,47 @@ public partial class Searches : NadekoModule if (data is null) { - embed.WithDescription(GetText(strs.city_not_found)) - .WithErrorColor(); + embed.WithDescription(GetText(strs.city_not_found)).WithErrorColor(); } else { var f = StandardConversions.CelsiusToFahrenheit; - - var tz = ctx.Guild is null - ? TimeZoneInfo.Utc - : _tzSvc.GetTimeZoneOrUtc(ctx.Guild.Id); + + var tz = ctx.Guild is null ? TimeZoneInfo.Utc : _tzSvc.GetTimeZoneOrUtc(ctx.Guild.Id); var sunrise = data.Sys.Sunrise.ToUnixTimestamp(); var sunset = data.Sys.Sunset.ToUnixTimestamp(); sunrise = sunrise.ToOffset(tz.GetUtcOffset(sunrise)); sunset = sunset.ToOffset(tz.GetUtcOffset(sunset)); var timezone = $"UTC{sunrise:zzz}"; - embed.AddField("🌍 " + Format.Bold(GetText(strs.location)), $"[{data.Name + ", " + data.Sys.Country}](https://openweathermap.org/city/{data.Id})", true) + embed + .AddField("🌍 " + Format.Bold(GetText(strs.location)), + $"[{data.Name + ", " + data.Sys.Country}](https://openweathermap.org/city/{data.Id})", + true) .AddField("📏 " + Format.Bold(GetText(strs.latlong)), $"{data.Coord.Lat}, {data.Coord.Lon}", true) - .AddField("☁ " + Format.Bold(GetText(strs.condition)), string.Join(", ", data.Weather.Select(w => w.Main)), true) + .AddField("☁ " + Format.Bold(GetText(strs.condition)), + string.Join(", ", data.Weather.Select(w => w.Main)), + true) .AddField("😓 " + Format.Bold(GetText(strs.humidity)), $"{data.Main.Humidity}%", true) .AddField("💨 " + Format.Bold(GetText(strs.wind_speed)), data.Wind.Speed + " m/s", true) - .AddField("🌡 " + Format.Bold(GetText(strs.temperature)), $"{data.Main.Temp:F1}°C / {f(data.Main.Temp):F1}°F", true) - .AddField("🔆 " + Format.Bold(GetText(strs.min_max)), $"{data.Main.TempMin:F1}°C - {data.Main.TempMax:F1}°C\n{f(data.Main.TempMin):F1}°F - {f(data.Main.TempMax):F1}°F", true) + .AddField("🌡 " + Format.Bold(GetText(strs.temperature)), + $"{data.Main.Temp:F1}°C / {f(data.Main.Temp):F1}°F", + true) + .AddField("🔆 " + Format.Bold(GetText(strs.min_max)), + $"{data.Main.TempMin:F1}°C - {data.Main.TempMax:F1}°C\n{f(data.Main.TempMin):F1}°F - {f(data.Main.TempMax):F1}°F", + true) .AddField("🌄 " + Format.Bold(GetText(strs.sunrise)), $"{sunrise:HH:mm} {timezone}", true) .AddField("🌇 " + Format.Bold(GetText(strs.sunset)), $"{sunset:HH:mm} {timezone}", true) .WithOkColor() - .WithFooter("Powered by openweathermap.org", $"http://openweathermap.org/img/w/{data.Weather[0].Icon}.png"); + .WithFooter("Powered by openweathermap.org", + $"http://openweathermap.org/img/w/{data.Weather[0].Icon}.png"); } + await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Time([Leftover] string query) { if (!await ValidateQuery(query)) @@ -117,26 +132,29 @@ public partial class Searches : NadekoModule errorKey = strs.error_occured; break; } + await ReplyErrorLocalizedAsync(errorKey); return; } - else if (string.IsNullOrWhiteSpace(data.TimeZoneName)) + + if (string.IsNullOrWhiteSpace(data.TimeZoneName)) { await ReplyErrorLocalizedAsync(strs.timezone_db_api_key); return; } var eb = _eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.time_new)) - .WithDescription(Format.Code(data.Time.ToString())) - .AddField(GetText(strs.location), string.Join('\n', data.Address.Split(", ")), true) - .AddField(GetText(strs.timezone), data.TimeZoneName, true); + .WithOkColor() + .WithTitle(GetText(strs.time_new)) + .WithDescription(Format.Code(data.Time.ToString())) + .AddField(GetText(strs.location), string.Join('\n', data.Address.Split(", ")), true) + .AddField(GetText(strs.timezone), data.TimeZoneName, true); await ctx.Channel.SendMessageAsync(embed: eb.Build()); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Youtube([Leftover] string query = null) { if (!await ValidateQuery(query)) @@ -152,7 +170,8 @@ public partial class Searches : NadekoModule await ctx.Channel.SendMessageAsync(result); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Movie([Leftover] string query = null) { if (!await ValidateQuery(query)) @@ -166,37 +185,46 @@ public partial class Searches : NadekoModule await ReplyErrorLocalizedAsync(strs.imdb_fail); return; } - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .WithTitle(movie.Title) - .WithUrl($"http://www.imdb.com/title/{movie.ImdbId}/") - .WithDescription(movie.Plot.TrimTo(1000)) - .AddField("Rating", movie.ImdbRating, true) - .AddField("Genre", movie.Genre, true) - .AddField("Year", movie.Year, true) - .WithImageUrl(movie.Poster)); + + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle(movie.Title) + .WithUrl($"http://www.imdb.com/title/{movie.ImdbId}/") + .WithDescription(movie.Plot.TrimTo(1000)) + .AddField("Rating", movie.ImdbRating, true) + .AddField("Genre", movie.Genre, true) + .AddField("Year", movie.Year, true) + .WithImageUrl(movie.Poster)); } - [NadekoCommand, Aliases] - public Task RandomCat() => InternalRandomImage(SearchesService.ImageTag.Cats); + [NadekoCommand] + [Aliases] + public Task RandomCat() + => InternalRandomImage(SearchesService.ImageTag.Cats); - [NadekoCommand, Aliases] - public Task RandomDog() => InternalRandomImage(SearchesService.ImageTag.Dogs); + [NadekoCommand] + [Aliases] + public Task RandomDog() + => InternalRandomImage(SearchesService.ImageTag.Dogs); - [NadekoCommand, Aliases] - public Task RandomFood() => InternalRandomImage(SearchesService.ImageTag.Food); + [NadekoCommand] + [Aliases] + public Task RandomFood() + => InternalRandomImage(SearchesService.ImageTag.Food); - [NadekoCommand, Aliases] - public Task RandomBird() => InternalRandomImage(SearchesService.ImageTag.Birds); + [NadekoCommand] + [Aliases] + public Task RandomBird() + => InternalRandomImage(SearchesService.ImageTag.Birds); private Task InternalRandomImage(SearchesService.ImageTag tag) { var url = _service.GetRandomImageUrl(tag); - return ctx.Channel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithImageUrl(url)); + return ctx.Channel.EmbedAsync(_eb.Create().WithOkColor().WithImageUrl(url)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Image([Leftover] string query = null) { var oterms = query?.Trim(); @@ -207,20 +235,20 @@ public partial class Searches : NadekoModule { var res = await _google.GetImageAsync(oterms); var embed = _eb.Create() - .WithOkColor() - .WithAuthor(GetText(strs.image_search_for) + " " + oterms.TrimTo(50), - "http://i.imgur.com/G46fm8J.png", - $"https://www.google.rs/search?q={query}&source=lnms&tbm=isch") - .WithDescription(res.Link) - .WithImageUrl(res.Link) - .WithTitle(ctx.User.ToString()); + .WithOkColor() + .WithAuthor(GetText(strs.image_search_for) + " " + oterms.TrimTo(50), + "http://i.imgur.com/G46fm8J.png", + $"https://www.google.rs/search?q={query}&source=lnms&tbm=isch") + .WithDescription(res.Link) + .WithImageUrl(res.Link) + .WithTitle(ctx.User.ToString()); await ctx.Channel.EmbedAsync(embed); } catch { Log.Warning("Falling back to Imgur"); - var fullQueryLink = $"http://imgur.com/search?q={ query }"; + var fullQueryLink = $"http://imgur.com/search?q={query}"; var config = Configuration.Default.WithDefaultLoader(); using var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); var elems = document.QuerySelectorAll("a.image-list-link").ToList(); @@ -228,7 +256,9 @@ public partial class Searches : NadekoModule 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 is null) return; @@ -236,18 +266,19 @@ public partial class Searches : NadekoModule var source = img.Source.Replace("b.", ".", StringComparison.InvariantCulture); var embed = _eb.Create() - .WithOkColor() - .WithAuthor(GetText(strs.image_search_for) + " " + oterms.TrimTo(50), - "http://s.imgur.com/images/logo-1200-630.jpg?", - fullQueryLink) - .WithDescription(source) - .WithImageUrl(source) - .WithTitle(ctx.User.ToString()); + .WithOkColor() + .WithAuthor(GetText(strs.image_search_for) + " " + oterms.TrimTo(50), + "http://s.imgur.com/images/logo-1200-630.jpg?", + fullQueryLink) + .WithDescription(source) + .WithImageUrl(source) + .WithTitle(ctx.User.ToString()); await ctx.Channel.EmbedAsync(embed); } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Lmgtfy([Leftover] string ffs = null) { if (!await ValidateQuery(ffs)) @@ -257,15 +288,8 @@ public partial class Searches : NadekoModule await SendConfirmAsync($"<{shortenedUrl}>"); } - public class ShortenData - { - [JsonProperty("result_url")] - public string ResultUrl { get; set; } - } - - private static readonly ConcurrentDictionary cachedShortenedLinks = new(); - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Shorten([Leftover] string query) { if (!await ValidateQuery(query)) @@ -273,15 +297,11 @@ public partial class Searches : NadekoModule query = query.Trim(); if (!cachedShortenedLinks.TryGetValue(query, out var shortLink)) - { try { using var _http = _httpFactory.CreateClient(); using var req = new HttpRequestMessage(HttpMethod.Post, "https://goolnk.com/api/v1/shorten"); - var formData = new MultipartFormDataContent - { - { new StringContent(query), "url" } - }; + var formData = new MultipartFormDataContent { { new StringContent(query), "url" } }; req.Content = formData; using var res = await _http.SendAsync(req); @@ -300,15 +320,15 @@ public partial class Searches : NadekoModule Log.Error(ex, "Error shortening a link: {Message}", ex.Message); return; } - } await ctx.Channel.EmbedAsync(_eb.Create() - .WithOkColor() - .AddField(GetText(strs.original_url), $"<{query}>") - .AddField(GetText(strs.short_url), $"<{shortLink}>")); + .WithOkColor() + .AddField(GetText(strs.original_url), $"<{query}>") + .AddField(GetText(strs.short_url), $"<{shortLink}>")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Google([Leftover] string query = null) { query = query?.Trim(); @@ -316,32 +336,32 @@ public partial class Searches : NadekoModule return; _ = ctx.Channel.TriggerTypingAsync(); - + var data = await _service.GoogleSearchAsync(query); if (data is null) { await ReplyErrorLocalizedAsync(strs.no_results); return; } - - var desc = data.Results.Take(5).Select(res => - $@"[**{res.Title}**]({res.Link}) + + var desc = data.Results.Take(5) + .Select(res => $@"[**{res.Title}**]({res.Link}) {res.Text.TrimTo(400 - res.Title.Length - res.Link.Length)}"); var descStr = string.Join("\n\n", desc); var embed = _eb.Create() - .WithAuthor(ctx.User.ToString(), - iconUrl: "http://i.imgur.com/G46fm8J.png") - .WithTitle(ctx.User.ToString()) - .WithFooter(data.TotalResults) - .WithDescription($"{GetText(strs.search_for)} **{query}**\n\n" +descStr) - .WithOkColor(); + .WithAuthor(ctx.User.ToString(), "http://i.imgur.com/G46fm8J.png") + .WithTitle(ctx.User.ToString()) + .WithFooter(data.TotalResults) + .WithDescription($"{GetText(strs.search_for)} **{query}**\n\n" + descStr) + .WithOkColor(); await ctx.Channel.EmbedAsync(embed); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] public async Task DuckDuckGo([Leftover] string query = null) { query = query?.Trim(); @@ -349,30 +369,31 @@ public partial class Searches : NadekoModule return; _ = ctx.Channel.TriggerTypingAsync(); - + var data = await _service.DuckDuckGoSearchAsync(query); if (data is null) { await ReplyErrorLocalizedAsync(strs.no_results); return; } - - var desc = data.Results.Take(5).Select(res => - $@"[**{res.Title}**]({res.Link}) + + var desc = data.Results.Take(5) + .Select(res => $@"[**{res.Title}**]({res.Link}) {res.Text.TrimTo(380 - res.Title.Length - res.Link.Length)}"); var descStr = string.Join("\n\n", desc); - + var embed = _eb.Create() - .WithAuthor(ctx.User.ToString(), - iconUrl: "https://upload.wikimedia.org/wikipedia/en/9/90/The_DuckDuckGo_Duck.png") - .WithDescription($"{GetText(strs.search_for)} **{query}**\n\n" + descStr) - .WithOkColor(); + .WithAuthor(ctx.User.ToString(), + "https://upload.wikimedia.org/wikipedia/en/9/90/The_DuckDuckGo_Duck.png") + .WithDescription($"{GetText(strs.search_for)} **{query}**\n\n" + descStr) + .WithOkColor(); await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task MagicTheGathering([Leftover] string search) { if (!await ValidateQuery(search)) @@ -387,18 +408,20 @@ public partial class Searches : NadekoModule return; } - var embed = _eb.Create().WithOkColor() - .WithTitle(card.Name) - .WithDescription(card.Description) - .WithImageUrl(card.ImageUrl) - .AddField(GetText(strs.store_url), card.StoreUrl, true) - .AddField(GetText(strs.cost), card.ManaCost, true) - .AddField(GetText(strs.types), card.Types, true); + var embed = _eb.Create() + .WithOkColor() + .WithTitle(card.Name) + .WithDescription(card.Description) + .WithImageUrl(card.ImageUrl) + .AddField(GetText(strs.store_url), card.StoreUrl, true) + .AddField(GetText(strs.cost), card.ManaCost, true) + .AddField(GetText(strs.types), card.Types, true); await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Hearthstone([Leftover] string name) { var arg = name; @@ -419,8 +442,8 @@ public partial class Searches : NadekoModule await ReplyErrorLocalizedAsync(strs.card_not_found); return; } - var embed = _eb.Create().WithOkColor() - .WithImageUrl(card.Img); + + var embed = _eb.Create().WithOkColor().WithImageUrl(card.Img); if (!string.IsNullOrWhiteSpace(card.Flavor)) embed.WithDescription(card.Flavor); @@ -428,7 +451,8 @@ public partial class Searches : NadekoModule await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task UrbanDict([Leftover] string query = null) { if (!await ValidateQuery(query)) @@ -437,21 +461,25 @@ public partial class Searches : NadekoModule await ctx.Channel.TriggerTypingAsync(); using (var http = _httpFactory.CreateClient()) { - var res = await http.GetStringAsync($"http://api.urbandictionary.com/v0/define?term={Uri.EscapeDataString(query)}"); + var res = await http.GetStringAsync( + $"http://api.urbandictionary.com/v0/define?term={Uri.EscapeDataString(query)}"); try { var items = JsonConvert.DeserializeObject(res).List; if (items.Any()) { - - await ctx.SendPaginatedConfirmAsync(0, p => - { - var item = items[p]; - return _eb.Create().WithOkColor() - .WithUrl(item.Permalink) - .WithAuthor(item.Word) - .WithDescription(item.Definition); - }, items.Length, 1); + await ctx.SendPaginatedConfirmAsync(0, + p => + { + var item = items[p]; + return _eb.Create() + .WithOkColor() + .WithUrl(item.Permalink) + .WithAuthor(item.Word) + .WithDescription(item.Definition); + }, + items.Length, + 1); return; } } @@ -459,11 +487,12 @@ public partial class Searches : NadekoModule { } } - await ReplyErrorLocalizedAsync(strs.ud_error); + await ReplyErrorLocalizedAsync(strs.ud_error); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Define([Leftover] string word) { if (!await ValidateQuery(word)) @@ -473,17 +502,21 @@ public partial class Searches : NadekoModule string res; try { - res = await _cache.GetOrCreateAsync($"define_{word}", e => - { - e.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12); - return _http.GetStringAsync("https://api.pearson.com/v2/dictionaries/entries?headword=" + WebUtility.UrlEncode(word)); - }); + res = await _cache.GetOrCreateAsync($"define_{word}", + e => + { + e.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12); + return _http.GetStringAsync("https://api.pearson.com/v2/dictionaries/entries?headword=" + + WebUtility.UrlEncode(word)); + }); var data = JsonConvert.DeserializeObject(res); var datas = data.Results - .Where(x => x.Senses is not null && x.Senses.Count > 0 && x.Senses[0].Definition is not null) - .Select(x => (Sense: x.Senses[0], x.PartOfSpeech)); + .Where(x => x.Senses is not null + && x.Senses.Count > 0 + && x.Senses[0].Definition is not null) + .Select(x => (Sense: x.Senses[0], x.PartOfSpeech)); if (!datas.Any()) { @@ -493,33 +526,35 @@ public partial class Searches : NadekoModule var col = datas.Select(data => ( - Definition: data.Sense.Definition is string - ? data.Sense.Definition.ToString() - : ((JArray)JToken.Parse(data.Sense.Definition.ToString())).First.ToString(), - Example: data.Sense.Examples is null || data.Sense.Examples.Count == 0 - ? string.Empty - : data.Sense.Examples[0].Text, - Word: word, - WordType: string.IsNullOrWhiteSpace(data.PartOfSpeech) ? "-" : data.PartOfSpeech - )).ToList(); + Definition: data.Sense.Definition is string + ? data.Sense.Definition.ToString() + : ((JArray)JToken.Parse(data.Sense.Definition.ToString())).First.ToString(), + Example: data.Sense.Examples is null || data.Sense.Examples.Count == 0 + ? string.Empty + : data.Sense.Examples[0].Text, Word: word, + WordType: string.IsNullOrWhiteSpace(data.PartOfSpeech) ? "-" : data.PartOfSpeech)) + .ToList(); Log.Information($"Sending {col.Count} definition for: {word}"); - await ctx.SendPaginatedConfirmAsync(0, page => - { - var data = col.Skip(page).First(); - var embed = _eb.Create() - .WithDescription(ctx.User.Mention) - .AddField(GetText(strs.word), data.Word, true) - .AddField(GetText(strs._class), data.WordType, true) - .AddField(GetText(strs.definition), data.Definition) - .WithOkColor(); + await ctx.SendPaginatedConfirmAsync(0, + page => + { + var data = col.Skip(page).First(); + var embed = _eb.Create() + .WithDescription(ctx.User.Mention) + .AddField(GetText(strs.word), data.Word, true) + .AddField(GetText(strs._class), data.WordType, true) + .AddField(GetText(strs.definition), data.Definition) + .WithOkColor(); - if (!string.IsNullOrWhiteSpace(data.Example)) - embed.AddField(GetText(strs.example), data.Example); + if (!string.IsNullOrWhiteSpace(data.Example)) + embed.AddField(GetText(strs.example), data.Example); - return embed; - }, col.Count, 1); + return embed; + }, + col.Count, + 1); } catch (Exception ex) { @@ -527,7 +562,8 @@ public partial class Searches : NadekoModule } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Catfact() { using var http = _httpFactory.CreateClient(); @@ -540,7 +576,8 @@ public partial class Searches : NadekoModule } //done in 3.0 - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Revav([Leftover] IGuildUser usr = null) { @@ -555,7 +592,8 @@ public partial class Searches : NadekoModule } //done in 3.0 - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Revimg([Leftover] string imageLink = null) { imageLink = imageLink?.Trim() ?? ""; @@ -565,7 +603,8 @@ public partial class Searches : NadekoModule await SendConfirmAsync($"https://images.google.com/searchbyimage?image_url={imageLink}"); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Wiki([Leftover] string query = null) { query = query?.Trim(); @@ -574,7 +613,9 @@ public partial class Searches : NadekoModule return; using var http = _httpFactory.CreateClient(); - var result = await http.GetStringAsync("https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url&titles=" + Uri.EscapeDataString(query)); + var result = await http.GetStringAsync( + "https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url&titles=" + + Uri.EscapeDataString(query)); var data = JsonConvert.DeserializeObject(result); if (data.Query.Pages[0].Missing || string.IsNullOrWhiteSpace(data.Query.Pages[0].FullUrl)) await ReplyErrorLocalizedAsync(strs.wiki_page_not_found); @@ -582,32 +623,28 @@ public partial class Searches : NadekoModule await ctx.Channel.SendMessageAsync(data.Query.Pages[0].FullUrl); } - [NadekoCommand, Aliases] - public async Task Color(params SixLabors.ImageSharp.Color[] colors) + [NadekoCommand] + [Aliases] + public async Task Color(params Color[] colors) { if (!colors.Any()) return; - var colorObjects = colors.Take(10) - .ToArray(); + var colorObjects = colors.Take(10).ToArray(); using var img = new Image(colorObjects.Length * 50, 50); for (var i = 0; i < colorObjects.Length; i++) { var x = i * 50; - img.Mutate(m => m.FillPolygon(colorObjects[i], new PointF[] { - new(x, 0), - new(x + 50, 0), - new(x + 50, 50), - new(x, 50) - })); + img.Mutate(m => m.FillPolygon(colorObjects[i], new(x, 0), new(x + 50, 0), new(x + 50, 50), new(x, 50))); } await using var ms = img.ToStream(); - await ctx.Channel.SendFileAsync(ms, $"colors.png"); + await ctx.Channel.SendFileAsync(ms, "colors.png"); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Avatar([Leftover] IGuildUser usr = null) { @@ -622,13 +659,17 @@ public partial class Searches : NadekoModule return; } - await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() - .AddField("Username", usr.ToString()) - .AddField("Avatar Url", avatarUrl) - .WithThumbnailUrl(avatarUrl.ToString()), ctx.User.Mention); + await ctx.Channel.EmbedAsync( + _eb.Create() + .WithOkColor() + .AddField("Username", usr.ToString()) + .AddField("Avatar Url", avatarUrl) + .WithThumbnailUrl(avatarUrl.ToString()), + ctx.User.Mention); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] public async Task Wikia(string target, [Leftover] string query) { if (string.IsNullOrWhiteSpace(target) || string.IsNullOrWhiteSpace(query)) @@ -636,20 +677,21 @@ public partial class Searches : NadekoModule await ReplyErrorLocalizedAsync(strs.wikia_input_error); return; } + await ctx.Channel.TriggerTypingAsync(); using var http = _httpFactory.CreateClient(); http.DefaultRequestHeaders.Clear(); try { - var res = await http.GetStringAsync($"https://{Uri.EscapeDataString(target)}.fandom.com/api.php" + - $"?action=query" + - $"&format=json" + - $"&list=search" + - $"&srsearch={Uri.EscapeDataString(query)}" + - $"&srlimit=1"); + var res = await http.GetStringAsync($"https://{Uri.EscapeDataString(target)}.fandom.com/api.php" + + "?action=query" + + "&format=json" + + "&list=search" + + $"&srsearch={Uri.EscapeDataString(query)}" + + "&srlimit=1"); var items = JObject.Parse(res); var title = items["query"]?["search"]?.FirstOrDefault()?["title"]?.ToString(); - + if (string.IsNullOrWhiteSpace(title)) { await ReplyErrorLocalizedAsync(strs.wikia_error); @@ -667,7 +709,8 @@ public partial class Searches : NadekoModule } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Bible(string book, string chapterAndVerse) { @@ -675,27 +718,30 @@ public partial class Searches : NadekoModule try { using var http = _httpFactory.CreateClient(); - var res = await http - .GetStringAsync("https://bible-api.com/" + book + " " + chapterAndVerse); + var res = await http.GetStringAsync("https://bible-api.com/" + book + " " + chapterAndVerse); obj = JsonConvert.DeserializeObject(res); } catch { } + if (obj.Error != null || obj.Verses is null || obj.Verses.Length == 0) + { await SendErrorAsync(obj.Error ?? "No verse found."); + } else { var v = obj.Verses[0]; await ctx.Channel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle($"{v.BookName} {v.Chapter}:{v.Verse}") - .WithDescription(v.Text)); + .WithOkColor() + .WithTitle($"{v.BookName} {v.Chapter}:{v.Verse}") + .WithDescription(v.Text)); } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Steam([Leftover] string query) { if (string.IsNullOrWhiteSpace(query)) @@ -725,12 +771,15 @@ public partial class Searches : NadekoModule public async Task ValidateQuery(string query) { - if (!string.IsNullOrWhiteSpace(query)) - { - return true; - } + if (!string.IsNullOrWhiteSpace(query)) return true; await ErrorLocalizedAsync(strs.specify_search_params); return false; } -} + + public class ShortenData + { + [JsonProperty("result_url")] + public string ResultUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Services/AnimeSearchService.cs b/src/NadekoBot/Modules/Searches/Services/AnimeSearchService.cs index 0db4d6543..d9734438e 100644 --- a/src/NadekoBot/Modules/Searches/Services/AnimeSearchService.cs +++ b/src/NadekoBot/Modules/Searches/Services/AnimeSearchService.cs @@ -23,8 +23,8 @@ public class AnimeSearchService : INService throw new ArgumentNullException(nameof(query)); try { - - var link = "https://aniapi.nadeko.bot/anime/" + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture)); + var link = "https://aniapi.nadeko.bot/anime/" + + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture)); link = link.ToLowerInvariant(); var (ok, data) = await _cache.TryGetAnimeDataAsync(link); if (!ok) @@ -33,6 +33,7 @@ public class AnimeSearchService : INService { data = await http.GetStringAsync(link); } + await _cache.SetAnimeDataAsync(link, data); } @@ -53,10 +54,8 @@ public class AnimeSearchService : INService query = query.Replace(" ", "-", StringComparison.InvariantCulture); try { - var link = "https://www.novelupdates.com/series/" + Uri.EscapeDataString(query - .Replace(" ", "-") - .Replace("/", " ") - ); + var link = "https://www.novelupdates.com/series/" + + Uri.EscapeDataString(query.Replace(" ", "-").Replace("/", " ")); link = link.ToLowerInvariant(); var (ok, data) = await _cache.TryGetNovelDataAsync(link); if (!ok) @@ -71,32 +70,24 @@ public class AnimeSearchService : INService var descElem = document.QuerySelector("div#editdescription > p"); var desc = descElem.InnerHtml; - var genres = document.QuerySelector("div#seriesgenre").Children - .Select(x => x as IHtmlAnchorElement) - .Where(x => x != null) - .Select(x => $"[{x.InnerHtml}]({x.Href})") - .ToArray(); + var genres = document.QuerySelector("div#seriesgenre") + .Children.Select(x => x as IHtmlAnchorElement) + .Where(x => x != null) + .Select(x => $"[{x.InnerHtml}]({x.Href})") + .ToArray(); - var authors = document - .QuerySelector("div#showauthors") - .Children - .Select(x => x as IHtmlAnchorElement) - .Where(x => x != null) - .Select(x => $"[{x.InnerHtml}]({x.Href})") - .ToArray(); + var authors = document.QuerySelector("div#showauthors") + .Children.Select(x => x as IHtmlAnchorElement) + .Where(x => x != null) + .Select(x => $"[{x.InnerHtml}]({x.Href})") + .ToArray(); - var score = ((IHtmlSpanElement)document - .QuerySelector("h5.seriesother > span.uvotes")) - .InnerHtml; + var score = ((IHtmlSpanElement)document.QuerySelector("h5.seriesother > span.uvotes")).InnerHtml; - var status = document - .QuerySelector("div#editstatus") - .InnerHtml; - var title = document - .QuerySelector("div.w-blog-content > div.seriestitlenu") - .InnerHtml; + var status = document.QuerySelector("div#editstatus").InnerHtml; + var title = document.QuerySelector("div.w-blog-content > div.seriestitlenu").InnerHtml; - var obj = new NovelResult() + var obj = new NovelResult { Description = desc, Authors = authors, @@ -105,11 +96,10 @@ public class AnimeSearchService : INService Link = link, Score = score, Status = status, - Title = title, + Title = title }; - await _cache.SetNovelDataAsync(link, - JsonConvert.SerializeObject(obj)); + await _cache.SetNovelDataAsync(link, JsonConvert.SerializeObject(obj)); return obj; } @@ -129,8 +119,8 @@ public class AnimeSearchService : INService throw new ArgumentNullException(nameof(query)); try { - - var link = "https://aniapi.nadeko.bot/manga/" + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture)); + var link = "https://aniapi.nadeko.bot/manga/" + + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture)); link = link.ToLowerInvariant(); var (ok, data) = await _cache.TryGetAnimeDataAsync(link); if (!ok) @@ -139,6 +129,7 @@ public class AnimeSearchService : INService { data = await http.GetStringAsync(link); } + await _cache.SetAnimeDataAsync(link, data); } @@ -150,4 +141,4 @@ public class AnimeSearchService : INService return null; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Services/AtlExtensions.cs b/src/NadekoBot/Modules/Searches/Services/AtlExtensions.cs index 2bf363171..ff80b450b 100644 --- a/src/NadekoBot/Modules/Searches/Services/AtlExtensions.cs +++ b/src/NadekoBot/Modules/Searches/Services/AtlExtensions.cs @@ -8,7 +8,5 @@ namespace NadekoBot.Modules.Searches; public static class AtlExtensions { public static Task GetByChannelId(this IQueryable set, ulong channelId) - => set - .Include(x => x.Users) - .FirstOrDefaultAsyncEF(x => x.ChannelId == channelId); -} + => set.Include(x => x.Users).FirstOrDefaultAsyncEF(x => x.ChannelId == channelId); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Services/CryptoService.cs b/src/NadekoBot/Modules/Searches/Services/CryptoService.cs index f3c6b0243..3c20c682f 100644 --- a/src/NadekoBot/Modules/Searches/Services/CryptoService.cs +++ b/src/NadekoBot/Modules/Searches/Services/CryptoService.cs @@ -9,7 +9,9 @@ public class CryptoService : INService private readonly IDataCache _cache; private readonly IHttpClientFactory _httpFactory; private readonly IBotCredentials _creds; - + + private readonly SemaphoreSlim getCryptoLock = new(1, 1); + public CryptoService(IDataCache cache, IHttpClientFactory httpFactory, IBotCredentials creds) { _cache = cache; @@ -19,70 +21,65 @@ public class CryptoService : INService public async Task<(CryptoResponseData Data, CryptoResponseData Nearest)> GetCryptoData(string name) { - if (string.IsNullOrWhiteSpace(name)) - { - return (null, null); - } + if (string.IsNullOrWhiteSpace(name)) return (null, null); name = name.ToUpperInvariant(); var cryptos = await CryptoData(); if (cryptos is null) return (null, null); - - var crypto = cryptos - ?.FirstOrDefault(x => x.Id.ToUpperInvariant() == name || x.Name.ToUpperInvariant() == name - || x.Symbol.ToUpperInvariant() == name); + + var crypto = cryptos?.FirstOrDefault(x + => x.Id.ToUpperInvariant() == name + || x.Name.ToUpperInvariant() == name + || x.Symbol.ToUpperInvariant() == name); (CryptoResponseData Elem, int Distance)? nearest = null; if (crypto is null) { - nearest = cryptos - .Select(x => (x, Distance: x.Name.ToUpperInvariant().LevenshteinDistance(name))) - .OrderBy(x => x.Distance) - .Where(x => x.Distance <= 2) - .FirstOrDefault(); + nearest = cryptos.Select(x => (x, Distance: x.Name.ToUpperInvariant().LevenshteinDistance(name))) + .OrderBy(x => x.Distance) + .Where(x => x.Distance <= 2) + .FirstOrDefault(); crypto = nearest?.Elem; } - if (nearest != null) - { - return (null, crypto); - } + if (nearest != null) return (null, crypto); return (crypto, null); } - private readonly SemaphoreSlim getCryptoLock = new(1, 1); public async Task> CryptoData() { await getCryptoLock.WaitAsync(); try { - var fullStrData = await _cache.GetOrAddCachedDataAsync("nadeko:crypto_data", async _ => - { - try + var fullStrData = await _cache.GetOrAddCachedDataAsync("nadeko:crypto_data", + async _ => { - using var _http = _httpFactory.CreateClient(); - var strData = await _http.GetStringAsync( - $"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?" + - $"CMC_PRO_API_KEY={_creds.CoinmarketcapApiKey}" + - $"&start=1" + - $"&limit=5000" + - $"&convert=USD"); + try + { + using var _http = _httpFactory.CreateClient(); + var strData = await _http.GetStringAsync( + "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?" + + $"CMC_PRO_API_KEY={_creds.CoinmarketcapApiKey}" + + "&start=1" + + "&limit=5000" + + "&convert=USD"); - JsonConvert.DeserializeObject(strData); // just to see if its' valid + JsonConvert.DeserializeObject(strData); // just to see if its' valid - return strData; - } - catch (Exception ex) - { - Log.Error(ex, "Error getting crypto data: {Message}", ex.Message); - return default; - } - - }, "", TimeSpan.FromHours(1)); + return strData; + } + catch (Exception ex) + { + Log.Error(ex, "Error getting crypto data: {Message}", ex.Message); + return default; + } + }, + "", + TimeSpan.FromHours(1)); return JsonConvert.DeserializeObject(fullStrData).Data; } @@ -96,4 +93,4 @@ public class CryptoService : INService getCryptoLock.Release(); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Services/FeedsService.cs b/src/NadekoBot/Modules/Searches/Services/FeedsService.cs index 3d1718062..a46b02e6f 100644 --- a/src/NadekoBot/Modules/Searches/Services/FeedsService.cs +++ b/src/NadekoBot/Modules/Searches/Services/FeedsService.cs @@ -1,8 +1,9 @@ #nullable disable +using CodeHollow.FeedReader; using CodeHollow.FeedReader.Feeds; using Microsoft.EntityFrameworkCore; -using NadekoBot.Services.Database.Models; using NadekoBot.Db; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Searches.Services; @@ -15,22 +16,25 @@ public class FeedsService : INService private readonly ConcurrentDictionary _lastPosts = new(); - public FeedsService(Bot bot, DbService db, DiscordSocketClient client, IEmbedBuilderService eb) + public FeedsService( + Bot bot, + DbService db, + DiscordSocketClient client, + IEmbedBuilderService eb) { _db = db; using (var uow = db.GetDbContext()) { var guildConfigIds = bot.AllGuildConfigs.Select(x => x.Id).ToList(); - _subs = uow.GuildConfigs - .AsQueryable() - .Where(x => guildConfigIds.Contains(x.Id)) - .Include(x => x.FeedSubs) - .ToList() - .SelectMany(x => x.FeedSubs) - .GroupBy(x => x.Url.ToLower()) - .ToDictionary(x => x.Key, x => x.ToHashSet()) - .ToConcurrent(); + _subs = uow.GuildConfigs.AsQueryable() + .Where(x => guildConfigIds.Contains(x.Id)) + .Include(x => x.FeedSubs) + .ToList() + .SelectMany(x => x.FeedSubs) + .GroupBy(x => x.Url.ToLower()) + .ToDictionary(x => x.Key, x => x.ToHashSet()) + .ToConcurrent(); } _client = client; @@ -52,34 +56,27 @@ public class FeedsService : INService var rssUrl = kvp.Key; try { - var feed = await CodeHollow.FeedReader.FeedReader.ReadAsync(rssUrl); + var feed = await FeedReader.ReadAsync(rssUrl); var items = feed - .Items - .Select(item => (Item: item, LastUpdate: item.PublishingDate?.ToUniversalTime() - ?? (item.SpecificItem as AtomFeedItem)?.UpdatedDate - ?.ToUniversalTime())) - .Where(data => data.LastUpdate is not null) - .Select(data => (data.Item, LastUpdate: (DateTime) data.LastUpdate)) - .OrderByDescending(data => data.LastUpdate) - .Reverse() // start from the oldest - .ToList(); + .Items.Select(item => (Item: item, + LastUpdate: item.PublishingDate?.ToUniversalTime() + ?? (item.SpecificItem as AtomFeedItem)?.UpdatedDate?.ToUniversalTime())) + .Where(data => data.LastUpdate is not null) + .Select(data => (data.Item, LastUpdate: (DateTime)data.LastUpdate)) + .OrderByDescending(data => data.LastUpdate) + .Reverse() // start from the oldest + .ToList(); if (!_lastPosts.TryGetValue(kvp.Key, out var lastFeedUpdate)) - { lastFeedUpdate = _lastPosts[kvp.Key] = items.Any() ? items[items.Count - 1].LastUpdate : DateTime.UtcNow; - } foreach (var (feedItem, itemUpdateDate) in items) { - if (itemUpdateDate <= lastFeedUpdate) - { - continue; - } + if (itemUpdateDate <= lastFeedUpdate) continue; - var embed = _eb.Create() - .WithFooter(rssUrl); + var embed = _eb.Create().WithFooter(rssUrl); _lastPosts[kvp.Key] = itemUpdateDate; @@ -87,40 +84,36 @@ public class FeedsService : INService if (!string.IsNullOrWhiteSpace(link) && Uri.IsWellFormedUriString(link, UriKind.Absolute)) embed.WithUrl(link); - var title = string.IsNullOrWhiteSpace(feedItem.Title) - ? "-" - : feedItem.Title; + var title = string.IsNullOrWhiteSpace(feedItem.Title) ? "-" : feedItem.Title; var gotImage = false; - if (feedItem.SpecificItem is MediaRssFeedItem mrfi && - (mrfi.Enclosure?.MediaType?.StartsWith("image/") ?? false)) + if (feedItem.SpecificItem is MediaRssFeedItem mrfi + && (mrfi.Enclosure?.MediaType?.StartsWith("image/") ?? false)) { var imgUrl = mrfi.Enclosure.Url; - if (!string.IsNullOrWhiteSpace(imgUrl) && - Uri.IsWellFormedUriString(imgUrl, UriKind.Absolute)) + if (!string.IsNullOrWhiteSpace(imgUrl) + && Uri.IsWellFormedUriString(imgUrl, UriKind.Absolute)) { embed.WithImageUrl(imgUrl); gotImage = true; } } - + if (!gotImage && feedItem.SpecificItem is AtomFeedItem afi) { var previewElement = afi.Element.Elements() - .FirstOrDefault(x => x.Name.LocalName == "preview"); + .FirstOrDefault(x => x.Name.LocalName == "preview"); if (previewElement is null) - { previewElement = afi.Element.Elements() - .FirstOrDefault(x => x.Name.LocalName == "thumbnail"); - } - + .FirstOrDefault(x => x.Name.LocalName == "thumbnail"); + if (previewElement != null) { var urlAttribute = previewElement.Attribute("url"); - if (urlAttribute != null && !string.IsNullOrWhiteSpace(urlAttribute.Value) - && Uri.IsWellFormedUriString(urlAttribute.Value, - UriKind.Absolute)) + if (urlAttribute != null + && !string.IsNullOrWhiteSpace(urlAttribute.Value) + && Uri.IsWellFormedUriString(urlAttribute.Value, UriKind.Absolute)) { embed.WithImageUrl(urlAttribute.Value); gotImage = true; @@ -136,12 +129,11 @@ public class FeedsService : INService embed.WithDescription(desc.TrimTo(2048)); //send the created embed to all subscribed channels - var feedSendTasks = kvp.Value - .Where(x => x.GuildConfig != null) - .Select(x => _client.GetGuild(x.GuildConfig.GuildId) - ?.GetTextChannel(x.ChannelId)) - .Where(x => x != null) - .Select(x => x.EmbedAsync(embed)); + var feedSendTasks = kvp.Value.Where(x => x.GuildConfig != null) + .Select(x => _client.GetGuild(x.GuildConfig.GuildId) + ?.GetTextChannel(x.ChannelId)) + .Where(x => x != null) + .Select(x => x.EmbedAsync(embed)); allSendTasks.Add(feedSendTasks.WhenAll()); } @@ -158,49 +150,35 @@ public class FeedsService : INService public List GetFeeds(ulong guildId) { using var uow = _db.GetDbContext(); - return uow.GuildConfigsForId(guildId, - set => set.Include(x => x.FeedSubs) - .ThenInclude(x => x.GuildConfig)) - .FeedSubs - .OrderBy(x => x.Id) - .ToList(); + return uow.GuildConfigsForId(guildId, set => set.Include(x => x.FeedSubs).ThenInclude(x => x.GuildConfig)) + .FeedSubs.OrderBy(x => x.Id) + .ToList(); } public bool AddFeed(ulong guildId, ulong channelId, string rssFeed) { ArgumentNullException.ThrowIfNull(rssFeed, nameof(rssFeed)); - var fs = new FeedSub() - { - ChannelId = channelId, - Url = rssFeed.Trim(), - }; + var fs = new FeedSub { ChannelId = channelId, Url = rssFeed.Trim() }; using var uow = _db.GetDbContext(); - var gc = uow.GuildConfigsForId(guildId, - set => set.Include(x => x.FeedSubs) - .ThenInclude(x => x.GuildConfig)); + var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FeedSubs).ThenInclude(x => x.GuildConfig)); if (gc.FeedSubs.Any(x => x.Url.ToLower() == fs.Url.ToLower())) - { return false; - } - else if (gc.FeedSubs.Count >= 10) - { - return false; - } + if (gc.FeedSubs.Count >= 10) return false; gc.FeedSubs.Add(fs); uow.SaveChanges(); //adding all, in case bot wasn't on this guild when it started foreach (var feed in gc.FeedSubs) - { - _subs.AddOrUpdate(feed.Url.ToLower(), new HashSet() {feed}, (k, old) => - { - old.Add(feed); - return old; - }); - } + _subs.AddOrUpdate(feed.Url.ToLower(), + new HashSet { feed }, + (k, old) => + { + old.Add(feed); + return old; + }); return true; } @@ -212,21 +190,22 @@ public class FeedsService : INService using var uow = _db.GetDbContext(); var items = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FeedSubs)) - .FeedSubs - .OrderBy(x => x.Id) - .ToList(); + .FeedSubs.OrderBy(x => x.Id) + .ToList(); if (items.Count <= index) return false; var toRemove = items[index]; - _subs.AddOrUpdate(toRemove.Url.ToLower(), new HashSet(), (key, old) => - { - old.Remove(toRemove); - return old; - }); + _subs.AddOrUpdate(toRemove.Url.ToLower(), + new HashSet(), + (key, old) => + { + old.Remove(toRemove); + return old; + }); uow.Remove(toRemove); uow.SaveChanges(); return true; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Services/ITranslateService.cs b/src/NadekoBot/Modules/Searches/Services/ITranslateService.cs index 312ac70de..3a1b7350e 100644 --- a/src/NadekoBot/Modules/Searches/Services/ITranslateService.cs +++ b/src/NadekoBot/Modules/Searches/Services/ITranslateService.cs @@ -6,6 +6,12 @@ public interface ITranslateService public Task Translate(string source, string target, string text = null); Task ToggleAtl(ulong guildId, ulong channelId, bool autoDelete); IEnumerable GetLanguages(); - Task RegisterUserAsync(ulong userId, ulong channelId, string @from, string to); + + Task RegisterUserAsync( + ulong userId, + ulong channelId, + string from, + string to); + Task UnregisterUser(ulong channelId, ulong userId); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Services/SearchesService.cs b/src/NadekoBot/Modules/Searches/Services/SearchesService.cs index 6833cbb9c..267a4262f 100644 --- a/src/NadekoBot/Modules/Searches/Services/SearchesService.cs +++ b/src/NadekoBot/Modules/Searches/Services/SearchesService.cs @@ -1,21 +1,42 @@ #nullable disable +using AngleSharp.Html.Dom; +using AngleSharp.Html.Parser; +using Html2Markdown; using NadekoBot.Modules.Searches.Common; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using SixLabors.Fonts; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using System.Net; -using AngleSharp.Html.Dom; -using AngleSharp.Html.Parser; -using HorizontalAlignment = SixLabors.Fonts.HorizontalAlignment; +using Color = SixLabors.ImageSharp.Color; using Image = SixLabors.ImageSharp.Image; namespace NadekoBot.Modules.Searches.Services; public class SearchesService : INService { + public enum ImageTag + { + Food, + Dogs, + Cats, + Birds + } + + private static readonly HtmlParser _googleParser = new(new() + { + IsScripting = false, + IsEmbedded = false, + IsSupportingProcessingInstructions = false, + IsKeepingSourceReferences = false, + IsNotSupportingFrames = true + }); + + public List WowJokes { get; } = new(); + public List MagicItems { get; } = new(); private readonly IHttpClientFactory _httpFactory; private readonly IGoogleApiService _google; private readonly IImageCache _imgs; @@ -23,12 +44,13 @@ public class SearchesService : INService private readonly FontProvider _fonts; private readonly IBotCredentials _creds; private readonly NadekoRandom _rng; - - public List WowJokes { get; } = new(); - public List MagicItems { get; } = new(); private readonly List _yomamaJokes; - public SearchesService(IGoogleApiService google, + private readonly object yomamaLock = new(); + private int yomamaJokeIndex; + + public SearchesService( + IGoogleApiService google, IDataCache cache, IHttpClientFactory factory, FontProvider fonts, @@ -44,24 +66,18 @@ public class SearchesService : INService //joke commands if (File.Exists("data/wowjokes.json")) - { WowJokes = JsonConvert.DeserializeObject>(File.ReadAllText("data/wowjokes.json")); - } else Log.Warning("data/wowjokes.json is missing. WOW Jokes are not loaded."); if (File.Exists("data/magicitems.json")) - { MagicItems = JsonConvert.DeserializeObject>(File.ReadAllText("data/magicitems.json")); - } else Log.Warning("data/magicitems.json is missing. Magic items are not loaded."); if (File.Exists("data/yomama.txt")) { - _yomamaJokes = File.ReadAllLines("data/yomama.txt") - .Shuffle() - .ToList(); + _yomamaJokes = File.ReadAllLines("data/yomama.txt").Shuffle().ToList(); } else { @@ -94,12 +110,11 @@ public class SearchesService : INService data = await http.GetByteArrayAsync(avatarUrl); using (var avatarImg = Image.Load(data)) { - avatarImg.Mutate(x => x - .Resize(85, 85) - .ApplyRoundedCorners(42)); + avatarImg.Mutate(x => x.Resize(85, 85).ApplyRoundedCorners(42)); data = avatarImg.ToStream().ToArray(); DrawAvatar(bg, avatarImg); } + await _cache.SetImageDataAsync(avatarUrl, data); } else @@ -113,13 +128,12 @@ public class SearchesService : INService { TextOptions = new TextOptions { - HorizontalAlignment = HorizontalAlignment.Center, - WrapTextWidth = 190, + HorizontalAlignment = HorizontalAlignment.Center, WrapTextWidth = 190 }.WithFallbackFonts(_fonts.FallBackFonts) }, text, _fonts.RipFont, - SixLabors.ImageSharp.Color.Black, + Color.Black, new(25, 225))); //flowa @@ -138,7 +152,7 @@ public class SearchesService : INService return _cache.GetOrAddCachedDataAsync($"nadeko_weather_{query}", GetWeatherDataFactory, query, - expiry: TimeSpan.FromHours(3)); + TimeSpan.FromHours(3)); } private async Task GetWeatherDataFactory(string query) @@ -146,10 +160,10 @@ public class SearchesService : INService using var http = _httpFactory.CreateClient(); try { - var data = await http.GetStringAsync($"http://api.openweathermap.org/data/2.5/weather?" + - $"q={query}&" + - $"appid=42cd627dd60debf25a5739e50a217d74&" + - $"units=metric"); + var data = await http.GetStringAsync("http://api.openweathermap.org/data/2.5/weather?" + + $"q={query}&" + + "appid=42cd627dd60debf25a5739e50a217d74&" + + "units=metric"); if (data is null) return null; @@ -170,34 +184,34 @@ public class SearchesService : INService // GetTimeDataFactory, // arg, // TimeSpan.FromMinutes(1)); - private async Task<((string Address, DateTime Time, string TimeZoneName), TimeErrors?)> GetTimeDataFactory(string query) + private async Task<((string Address, DateTime Time, string TimeZoneName), TimeErrors?)> GetTimeDataFactory( + string query) { query = query.Trim(); - if (string.IsNullOrEmpty(query)) - { - return (default, TimeErrors.InvalidInput); - } + if (string.IsNullOrEmpty(query)) return (default, TimeErrors.InvalidInput); - if (string.IsNullOrWhiteSpace(_creds.LocationIqApiKey) - || string.IsNullOrWhiteSpace(_creds.TimezoneDbApiKey)) - { + if (string.IsNullOrWhiteSpace(_creds.LocationIqApiKey) || string.IsNullOrWhiteSpace(_creds.TimezoneDbApiKey)) return (default, TimeErrors.ApiKeyMissing); - } try { using var _http = _httpFactory.CreateClient(); - var res = await _cache.GetOrAddCachedDataAsync($"geo_{query}", _ => - { - var url = "https://eu1.locationiq.com/v1/search.php?" + - (string.IsNullOrWhiteSpace(_creds.LocationIqApiKey) ? "key=" : $"key={_creds.LocationIqApiKey}&") + - $"q={Uri.EscapeDataString(query)}&" + - $"format=json"; + var res = await _cache.GetOrAddCachedDataAsync($"geo_{query}", + _ => + { + var url = "https://eu1.locationiq.com/v1/search.php?" + + (string.IsNullOrWhiteSpace(_creds.LocationIqApiKey) + ? "key=" + : $"key={_creds.LocationIqApiKey}&") + + $"q={Uri.EscapeDataString(query)}&" + + "format=json"; - var res = _http.GetStringAsync(url); - return res; - }, "", TimeSpan.FromHours(1)); + var res = _http.GetStringAsync(url); + return res; + }, + "", + TimeSpan.FromHours(1)); var responses = JsonConvert.DeserializeObject(res); if (responses is null || responses.Length == 0) @@ -208,21 +222,18 @@ public class SearchesService : INService var geoData = responses[0]; - using var req = new HttpRequestMessage(HttpMethod.Get, "http://api.timezonedb.com/v2.1/get-time-zone?" + - $"key={_creds.TimezoneDbApiKey}&format=json&" + - "by=position&" + - $"lat={geoData.Lat}&lng={geoData.Lon}"); + using var req = new HttpRequestMessage(HttpMethod.Get, + "http://api.timezonedb.com/v2.1/get-time-zone?" + + $"key={_creds.TimezoneDbApiKey}&format=json&" + + "by=position&" + + $"lat={geoData.Lat}&lng={geoData.Lon}"); using var geoRes = await _http.SendAsync(req); var resString = await geoRes.Content.ReadAsStringAsync(); var timeObj = JsonConvert.DeserializeObject(resString); - var time = new DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc).AddSeconds(timeObj.Timestamp); + var time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timeObj.Timestamp); - return (( - Address: responses[0].DisplayName, - Time: time, - TimeZoneName: timeObj.TimezoneName - ), default); + return ((Address: responses[0].DisplayName, Time: time, TimeZoneName: timeObj.TimezoneName), default); } catch (Exception ex) { @@ -231,14 +242,6 @@ public class SearchesService : INService } } - public enum ImageTag - { - Food, - Dogs, - Cats, - Birds - } - public string GetRandomImageUrl(ImageTag tag) { var subpath = tag.ToString().ToLowerInvariant(); @@ -263,12 +266,11 @@ public class SearchesService : INService break; } - return $"https://nadeko-pictures.nyc3.digitaloceanspaces.com/{subpath}/" + - _rng.Next(1, max).ToString("000") + ".png"; + return $"https://nadeko-pictures.nyc3.digitaloceanspaces.com/{subpath}/" + + _rng.Next(1, max).ToString("000") + + ".png"; } - private readonly object yomamaLock = new(); - private int yomamaJokeIndex = 0; public Task GetYomamaJoke() { string joke; @@ -284,8 +286,9 @@ public class SearchesService : INService joke = _yomamaJokes[yomamaJokeIndex++]; } + return Task.FromResult(joke); - + // using (var http = _httpFactory.CreateClient()) // { // var response = await http.GetStringAsync(new Uri("http://api.yomomma.info/")); @@ -297,7 +300,7 @@ public class SearchesService : INService { using var http = _httpFactory.CreateClient(); var res = await http.GetStringAsync("https://official-joke-api.appspot.com/random_joke"); - var resObj = JsonConvert.DeserializeAnonymousType(res, new {setup = "", punchline = ""}); + var resObj = JsonConvert.DeserializeAnonymousType(res, new { setup = "", punchline = "" }); return (resObj.setup, resObj.punchline); } @@ -305,7 +308,7 @@ public class SearchesService : INService { using var http = _httpFactory.CreateClient(); var response = await http.GetStringAsync(new Uri("http://api.icndb.com/jokes/random/")); - return JObject.Parse(response)["value"]["joke"].ToString() + " 😆"; + return JObject.Parse(response)["value"]["joke"] + " 😆"; } public async Task GetMtgCardAsync(string search) @@ -329,11 +332,11 @@ public class SearchesService : INService string storeUrl; try { - storeUrl = await _google.ShortenUrl($"https://shop.tcgplayer.com/productcatalog/product/show?" + - $"newSearch=false&" + - $"ProductType=All&" + - $"IsProductNameExact=false&" + - $"ProductName={Uri.EscapeDataString(card.Name)}"); + storeUrl = await _google.ShortenUrl("https://shop.tcgplayer.com/productcatalog/product/show?" + + "newSearch=false&" + + "ProductType=All&" + + "IsProductNameExact=false&" + + $"ProductName={Uri.EscapeDataString(card.Name)}"); } catch { storeUrl = ""; } @@ -344,13 +347,14 @@ public class SearchesService : INService ImageUrl = card.ImageUrl, StoreUrl = storeUrl, Types = string.Join(",\n", card.Types), - ManaCost = card.ManaCost, + ManaCost = card.ManaCost }; } using var http = _httpFactory.CreateClient(); http.DefaultRequestHeaders.Clear(); - var response = await http.GetStringAsync($"https://api.magicthegathering.io/v1/cards?name={Uri.EscapeDataString(search)}"); + var response = + await http.GetStringAsync($"https://api.magicthegathering.io/v1/cards?name={Uri.EscapeDataString(search)}"); var responseObject = JsonConvert.DeserializeObject(response); if (responseObject is null) @@ -360,8 +364,7 @@ public class SearchesService : INService if (cards.Length == 0) return Array.Empty(); - return await cards.Select(GetMtgDataAsync) - .WhenAll(); + return await cards.Select(GetMtgDataAsync).WhenAll(); } public Task GetHearthstoneCardDataAsync(string name) @@ -380,25 +383,22 @@ public class SearchesService : INService http.DefaultRequestHeaders.Add("x-rapidapi-key", _creds.RapidApiKey); try { - var response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.rapidapi.com/" + - $"cards/search/{Uri.EscapeDataString(name)}"); + var response = await http.GetStringAsync("https://omgvamp-hearthstone-v1.p.rapidapi.com/" + + $"cards/search/{Uri.EscapeDataString(name)}"); var objs = JsonConvert.DeserializeObject(response); if (objs is null || objs.Length == 0) return null; var data = objs.FirstOrDefault(x => x.Collectible) - ?? objs.FirstOrDefault(x => !string.IsNullOrEmpty(x.PlayerClass)) - ?? objs.FirstOrDefault(); + ?? objs.FirstOrDefault(x => !string.IsNullOrEmpty(x.PlayerClass)) ?? objs.FirstOrDefault(); if (data is null) return null; - if (!string.IsNullOrWhiteSpace(data.Img)) - { - data.Img = await _google.ShortenUrl(data.Img); - } + if (!string.IsNullOrWhiteSpace(data.Img)) data.Img = await _google.ShortenUrl(data.Img); if (!string.IsNullOrWhiteSpace(data.Text)) { - var converter = new Html2Markdown.Converter(); + var converter = new Converter(); data.Text = converter.Convert(data.Text); } + return data; } catch (Exception ex) @@ -411,10 +411,7 @@ public class SearchesService : INService public Task GetMovieDataAsync(string name) { name = name.Trim().ToLowerInvariant(); - return _cache.GetOrAddCachedDataAsync($"nadeko_movie_{name}", - GetMovieDataFactory, - name, - TimeSpan.FromDays(1)); + return _cache.GetOrAddCachedDataAsync($"nadeko_movie_{name}", GetMovieDataFactory, name, TimeSpan.FromDays(1)); } private async Task GetMovieDataFactory(string name) @@ -451,27 +448,30 @@ public class SearchesService : INService // } //} - var gamesMap = await _cache.GetOrAddCachedDataAsync(STEAM_GAME_IDS_KEY, async _ => - { - using var http = _httpFactory.CreateClient(); - // https://api.steampowered.com/ISteamApps/GetAppList/v2/ - var gamesStr = await http.GetStringAsync("https://api.steampowered.com/ISteamApps/GetAppList/v2/"); - var apps = JsonConvert.DeserializeAnonymousType(gamesStr, new { applist = new { apps = new List() } }).applist.apps; + var gamesMap = await _cache.GetOrAddCachedDataAsync(STEAM_GAME_IDS_KEY, + async _ => + { + using var http = _httpFactory.CreateClient(); + // https://api.steampowered.com/ISteamApps/GetAppList/v2/ + var gamesStr = await http.GetStringAsync("https://api.steampowered.com/ISteamApps/GetAppList/v2/"); + var apps = JsonConvert + .DeserializeAnonymousType(gamesStr, new { applist = new { apps = new List() } }) + .applist.apps; - return apps - .OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase) - .GroupBy(x => x.Name) - .ToDictionary(x => x.Key, x => x.First().AppId); - //await db.HashSetAsync("steam_game_ids", apps.Select(app => new HashEntry(app.Name.Trim().ToLowerInvariant(), app.AppId)).ToArray()); - //await db.StringSetAsync("steam_game_ids", gamesStr, TimeSpan.FromHours(24)); - //await db.KeyExpireAsync("steam_game_ids", TimeSpan.FromHours(24), CommandFlags.FireAndForget); - }, default(string), TimeSpan.FromHours(24)); + return apps.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase) + .GroupBy(x => x.Name) + .ToDictionary(x => x.Key, x => x.First().AppId); + //await db.HashSetAsync("steam_game_ids", apps.Select(app => new HashEntry(app.Name.Trim().ToLowerInvariant(), app.AppId)).ToArray()); + //await db.StringSetAsync("steam_game_ids", gamesStr, TimeSpan.FromHours(24)); + //await db.KeyExpireAsync("steam_game_ids", TimeSpan.FromHours(24), CommandFlags.FireAndForget); + }, + default(string), + TimeSpan.FromHours(24)); if (gamesMap is null) return -1; - query = query.Trim(); var keyList = gamesMap.Keys.ToList(); @@ -503,6 +503,105 @@ public class SearchesService : INService //return gameData; } + public async Task GoogleSearchAsync(string query) + { + query = WebUtility.UrlEncode(query)?.Replace(' ', '+'); + + var fullQueryLink = $"https://www.google.ca/search?q={query}&safe=on&lr=lang_eng&hl=en&ie=utf-8&oe=utf-8"; + + using var msg = new HttpRequestMessage(HttpMethod.Get, fullQueryLink); + msg.Headers.Add("User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"); + msg.Headers.Add("Cookie", "CONSENT=YES+shp.gws-20210601-0-RC2.en+FX+423;"); + + using var http = _httpFactory.CreateClient(); + http.DefaultRequestHeaders.Clear(); + + using var response = await http.SendAsync(msg); + var content = await response.Content.ReadAsStreamAsync(); + + using var document = await _googleParser.ParseDocumentAsync(content); + var elems = document.QuerySelectorAll("div.g > div > div"); + + var resultsElem = document.QuerySelectorAll("#resultStats").FirstOrDefault(); + var totalResults = resultsElem?.TextContent; + //var time = resultsElem.Children.FirstOrDefault()?.TextContent + //^ this doesn't work for some reason, is completely missing in parsed collection + if (!elems.Any()) + return default; + + var results = elems.Select(elem => + { + var children = elem.Children.ToList(); + if (children.Count < 2) + return null; + + var href = (children[0].QuerySelector("a") as IHtmlAnchorElement)?.Href; + var name = children[0].QuerySelector("h3")?.TextContent; + + if (href is null || name is null) + return null; + + var txt = children[1].TextContent; + + if (string.IsNullOrWhiteSpace(txt)) + return null; + + return new GoogleSearchResult(name, href, txt); + }) + .Where(x => x != null) + .ToList(); + + return new(results.AsReadOnly(), fullQueryLink, totalResults); + } + + public async Task DuckDuckGoSearchAsync(string query) + { + query = WebUtility.UrlEncode(query)?.Replace(' ', '+'); + + var fullQueryLink = "https://html.duckduckgo.com/html"; + + using var http = _httpFactory.CreateClient(); + http.DefaultRequestHeaders.Clear(); + http.DefaultRequestHeaders.Add("User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"); + + using var formData = new MultipartFormDataContent(); + formData.Add(new StringContent(query), "q"); + using var response = await http.PostAsync(fullQueryLink, formData); + var content = await response.Content.ReadAsStringAsync(); + + using var document = await _googleParser.ParseDocumentAsync(content); + var searchResults = document.QuerySelector(".results"); + var elems = searchResults.QuerySelectorAll(".result"); + + if (!elems.Any()) + return default; + + var results = elems.Select(elem => + { + if (elem.QuerySelector(".result__a") is not IHtmlAnchorElement anchor) + return null; + + var href = anchor.Href; + var name = anchor.TextContent; + + if (string.IsNullOrWhiteSpace(href) || string.IsNullOrWhiteSpace(name)) + return null; + + var txt = elem.QuerySelector(".result__snippet")?.TextContent; + + if (string.IsNullOrWhiteSpace(txt)) + return null; + + return new GoogleSearchResult(name, href, txt); + }) + .Where(x => x != null) + .ToList(); + + return new(results.AsReadOnly(), fullQueryLink, "0"); + } + //private async Task SteamGameDataFactory(int appid) //{ // using (var http = _httpFactory.CreateClient()) @@ -523,7 +622,9 @@ public class SearchesService : INService public string FullQueryLink { get; } public string TotalResults { get; } - public GoogleSearchResultData(IReadOnlyList results, string fullQueryLink, + public GoogleSearchResultData( + IReadOnlyList results, + string fullQueryLink, string totalResults) { Results = results; @@ -531,116 +632,4 @@ public class SearchesService : INService TotalResults = totalResults; } } - - private static readonly HtmlParser _googleParser = new(new() - { - IsScripting = false, - IsEmbedded = false, - IsSupportingProcessingInstructions = false, - IsKeepingSourceReferences = false, - IsNotSupportingFrames = true, - }); - - public async Task GoogleSearchAsync(string query) - { - query = WebUtility.UrlEncode(query)?.Replace(' ', '+'); - - var fullQueryLink = $"https://www.google.ca/search?q={ query }&safe=on&lr=lang_eng&hl=en&ie=utf-8&oe=utf-8"; - - using var msg = new HttpRequestMessage(HttpMethod.Get, fullQueryLink); - msg.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"); - msg.Headers.Add("Cookie", "CONSENT=YES+shp.gws-20210601-0-RC2.en+FX+423;"); - - using var http = _httpFactory.CreateClient(); - http.DefaultRequestHeaders.Clear(); - - using var response = await http.SendAsync(msg); - var content = await response.Content.ReadAsStreamAsync(); - - using var document = await _googleParser.ParseDocumentAsync(content); - var elems = document.QuerySelectorAll("div.g > div > div"); - - var resultsElem = document.QuerySelectorAll("#resultStats").FirstOrDefault(); - var totalResults = resultsElem?.TextContent; - //var time = resultsElem.Children.FirstOrDefault()?.TextContent - //^ this doesn't work for some reason, is completely missing in parsed collection - if (!elems.Any()) - return default; - - var results = elems.Select(elem => - { - var children = elem.Children.ToList(); - if (children.Count < 2) - return null; - - var href = (children[0].QuerySelector("a") as IHtmlAnchorElement)?.Href; - var name = children[0].QuerySelector("h3")?.TextContent; - - if (href is null || name is null) - return null; - - var txt = children[1].TextContent; - - if (string.IsNullOrWhiteSpace(txt)) - return null; - - return new GoogleSearchResult(name, href, txt); - }) - .Where(x => x != null) - .ToList(); - - return new( - results.AsReadOnly(), - fullQueryLink, - totalResults); - } - - public async Task DuckDuckGoSearchAsync(string query) - { - query = WebUtility.UrlEncode(query)?.Replace(' ', '+'); - - var fullQueryLink = $"https://html.duckduckgo.com/html"; - - using var http = _httpFactory.CreateClient(); - http.DefaultRequestHeaders.Clear(); - http.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"); - - using var formData = new MultipartFormDataContent(); - formData.Add(new StringContent(query), "q"); - using var response = await http.PostAsync(fullQueryLink, formData); - var content = await response.Content.ReadAsStringAsync(); - - using var document = await _googleParser.ParseDocumentAsync(content); - var searchResults = document.QuerySelector(".results"); - var elems = searchResults.QuerySelectorAll(".result"); - - if (!elems.Any()) - return default; - - var results = elems.Select(elem => - { - if (elem.QuerySelector(".result__a") is not IHtmlAnchorElement anchor) - return null; - - var href = anchor.Href; - var name = anchor.TextContent; - - if (string.IsNullOrWhiteSpace(href) || string.IsNullOrWhiteSpace(name)) - return null; - - var txt = elem.QuerySelector(".result__snippet")?.TextContent; - - if (string.IsNullOrWhiteSpace(txt)) - return null; - - return new GoogleSearchResult(name, href, txt); - }) - .Where(x => x != null) - .ToList(); - - return new( - results.AsReadOnly(), - fullQueryLink, - "0"); - } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Services/StreamNotificationService.cs b/src/NadekoBot/Modules/Searches/Services/StreamNotificationService.cs index c25e8b893..1459dc88d 100644 --- a/src/NadekoBot/Modules/Searches/Services/StreamNotificationService.cs +++ b/src/NadekoBot/Modules/Searches/Services/StreamNotificationService.cs @@ -1,11 +1,11 @@ #nullable disable using Microsoft.EntityFrameworkCore; +using NadekoBot.Db; +using NadekoBot.Db.Models; using NadekoBot.Modules.Searches.Common; using NadekoBot.Modules.Searches.Common.StreamNotifications; using NadekoBot.Services.Database.Models; using StackExchange.Redis; -using NadekoBot.Db; -using NadekoBot.Db.Models; namespace NadekoBot.Modules.Searches.Services; @@ -23,7 +23,7 @@ public sealed class StreamNotificationService : INService private readonly Dictionary>> _shardTrackedStreams; private readonly ConcurrentHashSet _offlineNotificationServers; - + private readonly IBotCredentials _creds; private readonly IPubSub _pubSub; private readonly IEmbedBuilderService _eb; @@ -31,7 +31,7 @@ public sealed class StreamNotificationService : INService private readonly TypedKey> _streamsOnlineKey; private readonly TypedKey> _streamsOfflineKey; - + private readonly TypedKey _streamFollowKey; private readonly TypedKey _streamUnfollowKey; @@ -64,45 +64,36 @@ public sealed class StreamNotificationService : INService { var ids = client.GetGuildIds(); var guildConfigs = uow.Set() - .AsQueryable() - .Include(x => x.FollowedStreams) - .Where(x => ids.Contains(x.GuildId)) - .ToList(); + .AsQueryable() + .Include(x => x.FollowedStreams) + .Where(x => ids.Contains(x.GuildId)) + .ToList(); _offlineNotificationServers = new(guildConfigs - .Where(gc => gc.NotifyStreamOffline) - .Select(x => x.GuildId) - .ToList()); + .Where(gc => gc.NotifyStreamOffline) + .Select(x => x.GuildId) + .ToList()); - var followedStreams = guildConfigs - .SelectMany(x => x.FollowedStreams) - .ToList(); + var followedStreams = guildConfigs.SelectMany(x => x.FollowedStreams).ToList(); - _shardTrackedStreams = followedStreams - .GroupBy(x => new {Type = x.Type, Name = x.Username.ToLower()}) - .ToList() - .ToDictionary( - x => new StreamDataKey(x.Key.Type, x.Key.Name.ToLower()), - x => x.GroupBy(y => y.GuildId) - .ToDictionary(y => y.Key, y => y.AsEnumerable().ToHashSet())); + _shardTrackedStreams = followedStreams.GroupBy(x => new { x.Type, Name = x.Username.ToLower() }) + .ToList() + .ToDictionary( + x => new StreamDataKey(x.Key.Type, x.Key.Name.ToLower()), + x => x.GroupBy(y => y.GuildId) + .ToDictionary(y => y.Key, + y => y.AsEnumerable().ToHashSet())); // shard 0 will keep track of when there are no more guilds which track a stream if (client.ShardId == 0) { - var allFollowedStreams = uow.Set() - .AsQueryable() - .ToList(); + var allFollowedStreams = uow.Set().AsQueryable().ToList(); - foreach (var fs in allFollowedStreams) - { - _streamTracker.CacheAddData(fs.CreateKey(), null, replace: false); - } + foreach (var fs in allFollowedStreams) _streamTracker.CacheAddData(fs.CreateKey(), null, false); - _trackCounter = allFollowedStreams - .GroupBy(x => new {Type = x.Type, Name = x.Username.ToLower()}) - .ToDictionary( - x => new StreamDataKey(x.Key.Type, x.Key.Name), - x => x.Select(fs => fs.GuildId).ToHashSet()); + _trackCounter = allFollowedStreams.GroupBy(x => new { x.Type, Name = x.Username.ToLower() }) + .ToDictionary(x => new StreamDataKey(x.Key.Type, x.Key.Name), + x => x.Select(fs => fs.GuildId).ToHashSet()); } } @@ -117,43 +108,45 @@ public sealed class StreamNotificationService : INService _streamTracker.OnStreamsOnline += OnStreamsOnline; _ = _streamTracker.RunAsync(); _notifCleanupTimer = new(_ => - { - try { - var errorLimit = TimeSpan.FromHours(12); - var failingStreams = _streamTracker.GetFailingStreams(errorLimit, true) - .ToList(); - - if (!failingStreams.Any()) - return; - - var deleteGroups = failingStreams.GroupBy(x => x.Type) - .ToDictionary(x => x.Key, x => x.Select(x => x.Name).ToList()); - - using var uow = _db.GetDbContext(); - foreach (var kvp in deleteGroups) + try { - Log.Information($"Deleting {kvp.Value.Count} {kvp.Key} streams because " + - $"they've been erroring for more than {errorLimit}: {string.Join(", ", kvp.Value)}"); + var errorLimit = TimeSpan.FromHours(12); + var failingStreams = _streamTracker.GetFailingStreams(errorLimit, true).ToList(); - var toDelete = uow.Set() - .AsQueryable() - .Where(x => x.Type == kvp.Key && kvp.Value.Contains(x.Username)) - .ToList(); + if (!failingStreams.Any()) + return; - uow.RemoveRange(toDelete); - uow.SaveChanges(); - - foreach(var loginToDelete in kvp.Value) - _streamTracker.UntrackStreamByKey(new(kvp.Key, loginToDelete)); + var deleteGroups = failingStreams.GroupBy(x => x.Type) + .ToDictionary(x => x.Key, x => x.Select(x => x.Name).ToList()); + + using var uow = _db.GetDbContext(); + foreach (var kvp in deleteGroups) + { + Log.Information($"Deleting {kvp.Value.Count} {kvp.Key} streams because " + + $"they've been erroring for more than {errorLimit}: {string.Join(", ", kvp.Value)}"); + + var toDelete = uow.Set() + .AsQueryable() + .Where(x => x.Type == kvp.Key && kvp.Value.Contains(x.Username)) + .ToList(); + + uow.RemoveRange(toDelete); + uow.SaveChanges(); + + foreach (var loginToDelete in kvp.Value) + _streamTracker.UntrackStreamByKey(new(kvp.Key, loginToDelete)); + } } - } - catch (Exception ex) - { - Log.Error("Error cleaning up FollowedStreams"); - Log.Error(ex.ToString()); - } - }, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(30)); + catch (Exception ex) + { + Log.Error("Error cleaning up FollowedStreams"); + Log.Error(ex.ToString()); + } + }, + null, + TimeSpan.FromMinutes(30), + TimeSpan.FromMinutes(30)); _pubSub.Sub(_streamFollowKey, HandleFollowStream); _pubSub.Sub(_streamUnfollowKey, HandleUnfollowStream); @@ -164,36 +157,29 @@ public sealed class StreamNotificationService : INService } /// - /// Handles follow stream pubs to keep the counter up to date. - /// When counter reaches 0, stream is removed from tracking because - /// that means no guilds are subscribed to that stream anymore + /// Handles follow stream pubs to keep the counter up to date. + /// When counter reaches 0, stream is removed from tracking because + /// that means no guilds are subscribed to that stream anymore /// private ValueTask HandleFollowStream(FollowStreamPubData info) { - _streamTracker.CacheAddData(info.Key, null, replace: false); + _streamTracker.CacheAddData(info.Key, null, false); lock (_shardLock) { var key = info.Key; if (_trackCounter.ContainsKey(key)) - { _trackCounter[key].Add(info.GuildId); - } else - { - _trackCounter[key] = new() - { - info.GuildId - }; - } + _trackCounter[key] = new() { info.GuildId }; } return default; } /// - /// Handles unfollow pubs to keep the counter up to date. - /// When counter reaches 0, stream is removed from tracking because - /// that means no guilds are subscribed to that stream anymore + /// Handles unfollow pubs to keep the counter up to date. + /// When counter reaches 0, stream is removed from tracking because + /// that means no guilds are subscribed to that stream anymore /// private ValueTask HandleUnfollowStream(FollowStreamPubData info) { @@ -226,16 +212,14 @@ public sealed class StreamNotificationService : INService { var key = stream.CreateKey(); if (_shardTrackedStreams.TryGetValue(key, out var fss)) - { await fss - // send offline stream notifications only to guilds which enable it with .stoff - .SelectMany(x => x.Value) - .Where(x => _offlineNotificationServers.Contains(x.GuildId)) - .Select(fs => _client.GetGuild(fs.GuildId) - ?.GetTextChannel(fs.ChannelId) - ?.EmbedAsync(GetEmbed(fs.GuildId, stream))) - .WhenAll(); - } + // send offline stream notifications only to guilds which enable it with .stoff + .SelectMany(x => x.Value) + .Where(x => _offlineNotificationServers.Contains(x.GuildId)) + .Select(fs => _client.GetGuild(fs.GuildId) + ?.GetTextChannel(fs.ChannelId) + ?.EmbedAsync(GetEmbed(fs.GuildId, stream))) + .WhenAll(); } } @@ -245,35 +229,29 @@ public sealed class StreamNotificationService : INService { var key = stream.CreateKey(); if (_shardTrackedStreams.TryGetValue(key, out var fss)) - { - await fss - .SelectMany(x => x.Value) - .Select(fs => - { - var textChannel = _client.GetGuild(fs.GuildId)?.GetTextChannel(fs.ChannelId); - - if (textChannel is null) - return Task.CompletedTask; - - var rep = new ReplacementBuilder() - .WithOverride("%user%", () => fs.Username) - .WithOverride("%platform%", () => fs.Type.ToString()) - .Build(); - - var message = string.IsNullOrWhiteSpace(fs.Message) - ? "" - : rep.Replace(fs.Message); + await fss.SelectMany(x => x.Value) + .Select(fs => + { + var textChannel = _client.GetGuild(fs.GuildId)?.GetTextChannel(fs.ChannelId); - return textChannel.EmbedAsync(GetEmbed(fs.GuildId, stream), message); - }) - .WhenAll(); - } + if (textChannel is null) + return Task.CompletedTask; + + var rep = new ReplacementBuilder().WithOverride("%user%", () => fs.Username) + .WithOverride("%platform%", () => fs.Type.ToString()) + .Build(); + + var message = string.IsNullOrWhiteSpace(fs.Message) ? "" : rep.Replace(fs.Message); + + return textChannel.EmbedAsync(GetEmbed(fs.GuildId, stream), message); + }) + .WhenAll(); } } private Task OnStreamsOnline(List data) => _pubSub.Pub(_streamsOnlineKey, data); - + private Task OnStreamsOffline(List data) => _pubSub.Pub(_streamsOfflineKey, data); @@ -281,14 +259,13 @@ public sealed class StreamNotificationService : INService { using (var uow = _db.GetDbContext()) { - var gc = uow.GuildConfigs - .AsQueryable() - .Include(x => x.FollowedStreams) - .FirstOrDefault(x => x.GuildId == guildConfig.GuildId); + var gc = uow.GuildConfigs.AsQueryable() + .Include(x => x.FollowedStreams) + .FirstOrDefault(x => x.GuildId == guildConfig.GuildId); if (gc is null) return Task.CompletedTask; - + if (gc.NotifyStreamOffline) _offlineNotificationServers.Add(gc.GuildId); @@ -332,7 +309,7 @@ public sealed class StreamNotificationService : INService foreach (var s in gc.FollowedStreams) await PublishUnfollowStream(s); - + uow.SaveChanges(); return gc.FollowedStreams.Count; @@ -344,10 +321,10 @@ public sealed class StreamNotificationService : INService await using (var uow = _db.GetDbContext()) { var fss = uow.Set() - .AsQueryable() - .Where(x => x.GuildId == guildId) - .OrderBy(x => x.Id) - .ToList(); + .AsQueryable() + .Where(x => x.GuildId == guildId) + .OrderBy(x => x.Id) + .ToList(); // out of range if (fss.Count <= index) @@ -392,17 +369,11 @@ public sealed class StreamNotificationService : INService var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FollowedStreams)); // add it to the database - fs = new() - { - Type = data.StreamType, - Username = data.UniqueName, - ChannelId = channelId, - GuildId = guildId, - }; + fs = new() { Type = data.StreamType, Username = data.UniqueName, ChannelId = channelId, GuildId = guildId }; if (gc.FollowedStreams.Count >= 10) return null; - + gc.FollowedStreams.Add(fs); await uow.SaveChangesAsync(); @@ -425,17 +396,17 @@ public sealed class StreamNotificationService : INService public IEmbedBuilder GetEmbed(ulong guildId, StreamData status) { var embed = _eb.Create() - .WithTitle(status.Name) - .WithUrl(status.StreamUrl) - .WithDescription(status.StreamUrl) - .AddField(GetText(guildId, strs.status), status.IsLive ? "🟢 Online" : "🔴 Offline", true) - .AddField(GetText(guildId, strs.viewers), status.IsLive ? status.Viewers.ToString() : "-", true); + .WithTitle(status.Name) + .WithUrl(status.StreamUrl) + .WithDescription(status.StreamUrl) + .AddField(GetText(guildId, strs.status), status.IsLive ? "🟢 Online" : "🔴 Offline", true) + .AddField(GetText(guildId, strs.viewers), status.IsLive ? status.Viewers.ToString() : "-", true); if (status.IsLive) embed = embed.WithOkColor(); else embed = embed.WithErrorColor(); - + if (!string.IsNullOrWhiteSpace(status.Title)) embed.WithAuthor(status.Title); @@ -463,13 +434,9 @@ public sealed class StreamNotificationService : INService uow.SaveChanges(); if (newValue) - { _offlineNotificationServers.Add(guildId); - } else - { _offlineNotificationServers.TryRemove(guildId); - } return newValue; } @@ -482,32 +449,22 @@ public sealed class StreamNotificationService : INService if (_shardTrackedStreams.TryGetValue(key, out var map)) { if (map.TryGetValue(guildId, out var set)) - { return set; - } - else - { - return map[guildId] = new(); - } - } - else - { - _shardTrackedStreams[key] = new() - { - {guildId, new()} - }; - return _shardTrackedStreams[key][guildId]; + return map[guildId] = new(); } + + _shardTrackedStreams[key] = new() { { guildId, new() } }; + return _shardTrackedStreams[key][guildId]; } - public bool SetStreamMessage(ulong guildId, int index, string message, out FollowedStream fs) + public bool SetStreamMessage( + ulong guildId, + int index, + string message, + out FollowedStream fs) { using var uow = _db.GetDbContext(); - var fss = uow.Set() - .AsQueryable() - .Where(x => x.GuildId == guildId) - .OrderBy(x => x.Id) - .ToList(); + var fss = uow.Set().AsQueryable().Where(x => x.GuildId == guildId).OrderBy(x => x.Id).ToList(); if (fss.Count <= index) { @@ -536,8 +493,7 @@ public sealed class StreamNotificationService : INService { using var uow = _db.GetDbContext(); - var all = uow.Set() - .ToList(); + var all = uow.Set().ToList(); if (all.Count == 0) return 0; @@ -554,4 +510,4 @@ public sealed class StreamNotificationService : INService public StreamDataKey Key { get; init; } public ulong GuildId { get; init; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Services/TranslateService.cs b/src/NadekoBot/Modules/Searches/Services/TranslateService.cs index aa3cccd4f..ee2c65d9e 100644 --- a/src/NadekoBot/Modules/Searches/Services/TranslateService.cs +++ b/src/NadekoBot/Modules/Searches/Services/TranslateService.cs @@ -1,9 +1,9 @@ #nullable disable -using System.Net; using LinqToDB; using LinqToDB.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using NadekoBot.Common.ModuleBehaviors; +using System.Net; namespace NadekoBot.Modules.Searches; @@ -17,7 +17,8 @@ public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyE private readonly ConcurrentDictionary _atcs = new(); private readonly ConcurrentDictionary> _users = new(); - public TranslateService(IGoogleApiService google, + public TranslateService( + IGoogleApiService google, DbService db, IEmbedBuilderService eb, Bot bot) @@ -33,51 +34,48 @@ public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyE var ctx = _db.GetDbContext(); var guilds = _bot.AllGuildConfigs.Select(x => x.GuildId).ToList(); - var cs = await ctx.AutoTranslateChannels - .Include(x => x.Users) - .Where(x => guilds.Contains(x.GuildId)) - .ToListAsyncEF(); + var cs = await ctx.AutoTranslateChannels.Include(x => x.Users) + .Where(x => guilds.Contains(x.GuildId)) + .ToListAsyncEF(); foreach (var c in cs) { _atcs[c.ChannelId] = c.AutoDelete; - _users[c.ChannelId] = new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower()))); + _users[c.ChannelId] = + new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower()))); } } - + public async Task LateExecute(IGuild guild, IUserMessage msg) { if (string.IsNullOrWhiteSpace(msg.Content)) return; - + if (msg is { Channel: ITextChannel tch } um) { if (!_atcs.TryGetValue(tch.Id, out var autoDelete)) return; - if (!_users.TryGetValue(tch.Id, out var users) - || !users.TryGetValue(um.Author.Id, out var langs)) + if (!_users.TryGetValue(tch.Id, out var users) || !users.TryGetValue(um.Author.Id, out var langs)) return; var output = await _google.Translate(msg.Content, langs.From, langs.To); - if (string.IsNullOrWhiteSpace(output) + if (string.IsNullOrWhiteSpace(output) || msg.Content.Equals(output, StringComparison.InvariantCultureIgnoreCase)) return; - var embed = _eb.Create() - .WithOkColor(); - + var embed = _eb.Create().WithOkColor(); + if (autoDelete) { - embed - .WithAuthor(um.Author.ToString(), um.Author.GetAvatarUrl()) - .AddField(langs.From, um.Content) - .AddField(langs.To, output); + embed.WithAuthor(um.Author.ToString(), um.Author.GetAvatarUrl()) + .AddField(langs.From, um.Content) + .AddField(langs.To, output); await tch.EmbedAsync(embed); - + try { await um.DeleteAsync(); @@ -90,10 +88,7 @@ public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyE return; } - await um.ReplyAsync(embed: embed - .AddField(langs.To, output) - .Build(), - allowedMentions: AllowedMentions.None); + await um.ReplyAsync(embed: embed.AddField(langs.To, output).Build(), allowedMentions: AllowedMentions.None); } } @@ -110,25 +105,18 @@ public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyE { var ctx = _db.GetDbContext(); - var old = await ctx.AutoTranslateChannels - .ToLinqToDBTable() - .FirstOrDefaultAsyncLinqToDB(x => x.ChannelId == channelId); - + var old = await ctx.AutoTranslateChannels.ToLinqToDBTable() + .FirstOrDefaultAsyncLinqToDB(x => x.ChannelId == channelId); + if (old is null) { - ctx.AutoTranslateChannels - .Add(new() - { - GuildId = guildId, - ChannelId = channelId, - AutoDelete = autoDelete, - }); - + ctx.AutoTranslateChannels.Add(new() { GuildId = guildId, ChannelId = channelId, AutoDelete = autoDelete }); + await ctx.SaveChangesAsync(); _atcs[channelId] = autoDelete; _users[channelId] = new(); - + return true; } @@ -142,10 +130,8 @@ public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyE return true; } - await ctx.AutoTranslateChannels - .ToLinqToDBTable() - .DeleteAsync(x => x.ChannelId == channelId); - + await ctx.AutoTranslateChannels.ToLinqToDBTable().DeleteAsync(x => x.ChannelId == channelId); + await ctx.SaveChangesAsync(); _atcs.TryRemove(channelId, out _); _users.TryRemove(channelId, out _); @@ -154,53 +140,54 @@ public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyE } - private void UpdateUser(ulong channelId, ulong userId, string from, string to) + private void UpdateUser( + ulong channelId, + ulong userId, + string from, + string to) { var dict = _users.GetOrAdd(channelId, new ConcurrentDictionary()); dict[userId] = (from, to); } - - public async Task RegisterUserAsync(ulong userId, ulong channelId, string from, string to) + + public async Task RegisterUserAsync( + ulong userId, + ulong channelId, + string from, + string to) { if (!_google.Languages.ContainsKey(from) || !_google.Languages.ContainsKey(to)) return null; var ctx = _db.GetDbContext(); - var ch = await ctx.AutoTranslateChannels - .GetByChannelId(channelId); - + var ch = await ctx.AutoTranslateChannels.GetByChannelId(channelId); + if (ch is null) return null; - var user = ch.Users - .FirstOrDefault(x => x.UserId == userId); + var user = ch.Users.FirstOrDefault(x => x.UserId == userId); if (user is null) { - ch.Users.Add(user = new() - { - Source = from, - Target = to, - UserId = userId, - }); - + ch.Users.Add(user = new() { Source = from, Target = to, UserId = userId }); + await ctx.SaveChangesAsync(); UpdateUser(channelId, userId, from, to); - + return true; } - + // if it's different from old settings, update if (user.Source != from || user.Target != to) { user.Source = from; user.Target = to; - + await ctx.SaveChangesAsync(); UpdateUser(channelId, userId, from, to); - + return true; } @@ -210,17 +197,16 @@ public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyE public async Task UnregisterUser(ulong channelId, ulong userId) { var ctx = _db.GetDbContext(); - var rows = await ctx.AutoTranslateUsers - .ToLinqToDBTable() - .DeleteAsync(x => x.UserId == userId && - x.Channel.ChannelId == channelId); + var rows = await ctx.AutoTranslateUsers.ToLinqToDBTable() + .DeleteAsync(x => x.UserId == userId && x.Channel.ChannelId == channelId); if (_users.TryGetValue(channelId, out var inner)) inner.TryRemove(userId, out _); - + await ctx.SaveChangesAsync(); return rows > 0; } - - public IEnumerable GetLanguages() => _google.Languages.Select(x => x.Key); -} + + public IEnumerable GetLanguages() + => _google.Languages.Select(x => x.Key); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Services/YtTrackService.cs b/src/NadekoBot/Modules/Searches/Services/YtTrackService.cs index 61d9e1bef..cf2092d3d 100644 --- a/src/NadekoBot/Modules/Searches/Services/YtTrackService.cs +++ b/src/NadekoBot/Modules/Searches/Services/YtTrackService.cs @@ -132,4 +132,4 @@ namespace NadekoBot.Modules.Searches.Services; // // return true; // } -// } +// } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/StreamNotificationCommands.cs b/src/NadekoBot/Modules/Searches/StreamNotificationCommands.cs index b103f3413..a98364f15 100644 --- a/src/NadekoBot/Modules/Searches/StreamNotificationCommands.cs +++ b/src/NadekoBot/Modules/Searches/StreamNotificationCommands.cs @@ -1,8 +1,8 @@ #nullable disable using Microsoft.EntityFrameworkCore; -using NadekoBot.Modules.Searches.Services; using NadekoBot.Db; using NadekoBot.Db.Models; +using NadekoBot.Modules.Searches.Services; namespace NadekoBot.Modules.Searches; @@ -19,7 +19,8 @@ public partial class Searches // private static readonly Regex picartoRegex = new Regex(@"picarto.tv/(?.+[^/])/?", // RegexOptions.Compiled | RegexOptions.IgnoreCase); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task StreamAdd(string link) @@ -35,7 +36,8 @@ public partial class Searches await ctx.Channel.EmbedAsync(embed, GetText(strs.stream_tracked)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] [Priority(1)] @@ -43,7 +45,7 @@ public partial class Searches { if (--index < 0) return; - + var fs = await _service.UnfollowStreamAsync(ctx.Guild.Id, index); if (fs is null) { @@ -51,13 +53,11 @@ public partial class Searches return; } - await ReplyConfirmLocalizedAsync( - strs.stream_removed( - Format.Bold(fs.Username), - fs.Type)); + await ReplyConfirmLocalizedAsync(strs.stream_removed(Format.Bold(fs.Username), fs.Type)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task StreamsClear() @@ -66,107 +66,89 @@ public partial class Searches await ReplyConfirmLocalizedAsync(strs.streams_cleared); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task StreamList(int page = 1) { - if (page-- < 1) - { - return; - } + if (page-- < 1) return; var streams = new List(); await using (var uow = _db.GetDbContext()) { - var all = uow - .GuildConfigsForId(ctx.Guild.Id, set => set.Include(gc => gc.FollowedStreams)) - .FollowedStreams - .OrderBy(x => x.Id) - .ToList(); + var all = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(gc => gc.FollowedStreams)) + .FollowedStreams.OrderBy(x => x.Id) + .ToList(); for (var index = all.Count - 1; index >= 0; index--) { var fs = all[index]; - if (((SocketGuild) ctx.Guild).GetTextChannel(fs.ChannelId) is null) - { + if (((SocketGuild)ctx.Guild).GetTextChannel(fs.ChannelId) is null) await _service.UnfollowStreamAsync(fs.GuildId, index); - } else - { streams.Insert(0, fs); - } } } - await ctx.SendPaginatedConfirmAsync(page, cur => - { - var elements = streams.Skip(cur * 12).Take(12) - .ToList(); - - if (elements.Count == 0) + await ctx.SendPaginatedConfirmAsync(page, + cur => { - return _eb.Create() - .WithDescription(GetText(strs.streams_none)) - .WithErrorColor(); - } + var elements = streams.Skip(cur * 12).Take(12).ToList(); - var eb = _eb.Create() - .WithTitle(GetText(strs.streams_follow_title)) - .WithOkColor(); - for (var index = 0; index < elements.Count; index++) - { - var elem = elements[index]; - eb.AddField( - $"**#{index + 1 + (12 * cur)}** {elem.Username.ToLower()}", - $"【{elem.Type}】\n<#{elem.ChannelId}>\n{elem.Message?.TrimTo(50)}", - true); - } + if (elements.Count == 0) + return _eb.Create().WithDescription(GetText(strs.streams_none)).WithErrorColor(); - return eb; - }, streams.Count, 12); + var eb = _eb.Create().WithTitle(GetText(strs.streams_follow_title)).WithOkColor(); + for (var index = 0; index < elements.Count; index++) + { + var elem = elements[index]; + eb.AddField($"**#{index + 1 + (12 * cur)}** {elem.Username.ToLower()}", + $"【{elem.Type}】\n<#{elem.ChannelId}>\n{elem.Message?.TrimTo(50)}", + true); + } + + return eb; + }, + streams.Count, + 12); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task StreamOffline() { var newValue = _service.ToggleStreamOffline(ctx.Guild.Id); if (newValue) - { await ReplyConfirmLocalizedAsync(strs.stream_off_enabled); - } else - { await ReplyConfirmLocalizedAsync(strs.stream_off_disabled); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task StreamMessage(int index, [Leftover] string message) { if (--index < 0) return; - + if (!_service.SetStreamMessage(ctx.Guild.Id, index, message, out var fs)) { await ReplyConfirmLocalizedAsync(strs.stream_not_following); return; } - + if (string.IsNullOrWhiteSpace(message)) - { await ReplyConfirmLocalizedAsync(strs.stream_message_reset(Format.Bold(fs.Username))); - } else - { await ReplyConfirmLocalizedAsync(strs.stream_message_set(Format.Bold(fs.Username))); - } } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task StreamMessageAll([Leftover] string message) @@ -181,8 +163,9 @@ public partial class Searches await ReplyConfirmLocalizedAsync(strs.stream_message_set_all(count)); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task StreamCheck(string url) { @@ -194,17 +177,12 @@ public partial class Searches await ReplyErrorLocalizedAsync(strs.no_channel_found); return; } - + if (data.IsLive) - { - await ReplyConfirmLocalizedAsync(strs.streamer_online( - Format.Bold(data.Name), + await ReplyConfirmLocalizedAsync(strs.streamer_online(Format.Bold(data.Name), Format.Bold(data.Viewers.ToString()))); - } else - { await ReplyConfirmLocalizedAsync(strs.streamer_offline(data.Name)); - } } catch { @@ -212,4 +190,4 @@ public partial class Searches } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/TranslatorCommands.cs b/src/NadekoBot/Modules/Searches/TranslatorCommands.cs index 8b4e80cde..0bc566747 100644 --- a/src/NadekoBot/Modules/Searches/TranslatorCommands.cs +++ b/src/NadekoBot/Modules/Searches/TranslatorCommands.cs @@ -6,7 +6,14 @@ public partial class Searches [Group] public class TranslateCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + public enum AutoDeleteAutoTranslate + { + Del, + Nodel + } + + [NadekoCommand] + [Aliases] public async Task Translate(string from, string to, [Leftover] string text = null) { try @@ -14,10 +21,7 @@ public partial class Searches await ctx.Channel.TriggerTypingAsync(); var translation = await _service.Translate(from, to, text); - var embed = _eb.Create(ctx) - .WithOkColor() - .AddField(from, text, false) - .AddField(to, translation, false); + var embed = _eb.Create(ctx).WithOkColor().AddField(from, text).AddField(to, translation); await ctx.Channel.EmbedAsync(embed); } @@ -27,55 +31,44 @@ public partial class Searches } } - public enum AutoDeleteAutoTranslate - { - Del, - Nodel - } - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [BotPerm(ChannelPerm.ManageMessages)] [OwnerOnly] public async Task AutoTranslate(AutoDeleteAutoTranslate autoDelete = AutoDeleteAutoTranslate.Nodel) { - var toggle = await _service.ToggleAtl(ctx.Guild.Id, ctx.Channel.Id, autoDelete == AutoDeleteAutoTranslate.Del); + var toggle = + await _service.ToggleAtl(ctx.Guild.Id, ctx.Channel.Id, autoDelete == AutoDeleteAutoTranslate.Del); if (toggle) - { await ReplyConfirmLocalizedAsync(strs.atl_started); - } else - { await ReplyConfirmLocalizedAsync(strs.atl_stopped); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task AutoTransLang() { if (await _service.UnregisterUser(ctx.Channel.Id, ctx.User.Id)) - { await ReplyConfirmLocalizedAsync(strs.atl_removed); - } } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task AutoTransLang(string from, string to) { - var succ = await _service.RegisterUserAsync(ctx.User.Id, - ctx.Channel.Id, - from.ToLower(), - to.ToLower()); + var succ = await _service.RegisterUserAsync(ctx.User.Id, ctx.Channel.Id, from.ToLower(), to.ToLower()); if (succ is null) { await ReplyErrorLocalizedAsync(strs.atl_not_enabled); return; } - + if (succ is false) { await ReplyErrorLocalizedAsync(strs.invalid_lang); @@ -85,9 +78,10 @@ public partial class Searches await ReplyConfirmLocalizedAsync(strs.atl_set(from, to)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Translangs() - => await ctx.Channel.SendTableAsync(_service.GetLanguages(), str => $"{str,-15}", 3); + => await ctx.Channel.SendTableAsync(_service.GetLanguages(), str => $"{str,-15}"); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/XkcdCommands.cs b/src/NadekoBot/Modules/Searches/XkcdCommands.cs index b77305c7f..f39af2e76 100644 --- a/src/NadekoBot/Modules/Searches/XkcdCommands.cs +++ b/src/NadekoBot/Modules/Searches/XkcdCommands.cs @@ -14,7 +14,8 @@ public partial class Searches public XkcdCommands(IHttpClientFactory factory) => _httpFactory = factory; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(0)] public async Task Xkcd(string arg = null) { @@ -25,27 +26,31 @@ public partial class Searches using var http = _httpFactory.CreateClient(); var res = await http.GetStringAsync($"{_xkcdUrl}/info.0.json"); var comic = JsonConvert.DeserializeObject(res); - var embed = _eb.Create().WithOkColor() - .WithImageUrl(comic.ImageLink) - .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{comic.Num}") - .AddField(GetText(strs.comic_number), comic.Num.ToString(), true) - .AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true); + var embed = _eb.Create() + .WithOkColor() + .WithImageUrl(comic.ImageLink) + .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{comic.Num}") + .AddField(GetText(strs.comic_number), comic.Num.ToString(), true) + .AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true); var sent = await ctx.Channel.EmbedAsync(embed); await Task.Delay(10000); - await sent.ModifyAsync(m => m.Embed = embed.AddField("Alt", comic.Alt.ToString(), false).Build()); + await sent.ModifyAsync(m => m.Embed = embed.AddField("Alt", comic.Alt).Build()); } catch (HttpRequestException) { await ReplyErrorLocalizedAsync(strs.comic_not_found); } + return; } + await Xkcd(new NadekoRandom().Next(1, 1750)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(1)] public async Task Xkcd(int num) { @@ -58,17 +63,17 @@ public partial class Searches var comic = JsonConvert.DeserializeObject(res); var embed = _eb.Create() - .WithOkColor() - .WithImageUrl(comic.ImageLink) - .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{num}") - .AddField(GetText(strs.comic_number), comic.Num.ToString(), true) - .AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true); - + .WithOkColor() + .WithImageUrl(comic.ImageLink) + .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{num}") + .AddField(GetText(strs.comic_number), comic.Num.ToString(), true) + .AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true); + var sent = await ctx.Channel.EmbedAsync(embed); await Task.Delay(10000); - await sent.ModifyAsync(m => m.Embed = embed.AddField("Alt", comic.Alt.ToString(), false).Build()); + await sent.ModifyAsync(m => m.Embed = embed.AddField("Alt", comic.Alt).Build()); } catch (HttpRequestException) { @@ -82,10 +87,13 @@ public partial class Searches public int Num { get; set; } public string Month { get; set; } public string Year { get; set; } + [JsonProperty("safe_title")] public string Title { get; set; } + [JsonProperty("img")] public string ImageLink { get; set; } + public string Alt { get; set; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/YtUploadCommands.cs b/src/NadekoBot/Modules/Searches/YtUploadCommands.cs index 6dc5dcab6..a8e58c8d6 100644 --- a/src/NadekoBot/Modules/Searches/YtUploadCommands.cs +++ b/src/NadekoBot/Modules/Searches/YtUploadCommands.cs @@ -51,4 +51,4 @@ public partial class Searches // //} // } // } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/CalcCommands.cs b/src/NadekoBot/Modules/Utility/CalcCommands.cs index b0e0ba683..27066b214 100644 --- a/src/NadekoBot/Modules/Utility/CalcCommands.cs +++ b/src/NadekoBot/Modules/Utility/CalcCommands.cs @@ -1,4 +1,5 @@ #nullable disable +using NCalc; using System.Reflection; namespace NadekoBot.Modules.Utility; @@ -8,10 +9,11 @@ public partial class Utility [Group] public class CalcCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Calculate([Leftover] string expression) { - var expr = new NCalc.Expression(expression, NCalc.EvaluateOptions.IgnoreCase | NCalc.EvaluateOptions.NoCache); + var expr = new Expression(expression, EvaluateOptions.IgnoreCase | EvaluateOptions.NoCache); expr.EvaluateParameter += Expr_EvaluateParameter; var result = expr.Evaluate(); if (!expr.HasErrors()) @@ -20,7 +22,7 @@ public partial class Utility await SendErrorAsync("⚙ " + GetText(strs.error), expr.Error); } - private static void Expr_EvaluateParameter(string name, NCalc.ParameterArgs args) + private static void Expr_EvaluateParameter(string name, ParameterArgs args) { switch (name.ToLowerInvariant()) { @@ -30,26 +32,19 @@ public partial class Utility case "e": args.Result = Math.E; break; - default: - break; } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task CalcOps() { var selection = typeof(Math).GetTypeInfo() - .GetMethods() - .DistinctBy(x => x.Name) - .Select(x => x.Name) - .Except(new[] - { - "ToString", - "Equals", - "GetHashCode", - "GetType" - }); + .GetMethods() + .DistinctBy(x => x.Name) + .Select(x => x.Name) + .Except(new[] { "ToString", "Equals", "GetHashCode", "GetType" }); await SendConfirmAsync(GetText(strs.calcops(Prefix)), string.Join(", ", selection)); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/CommandMapCommands.cs b/src/NadekoBot/Modules/Utility/CommandMapCommands.cs index f829689f1..939a46a7a 100644 --- a/src/NadekoBot/Modules/Utility/CommandMapCommands.cs +++ b/src/NadekoBot/Modules/Utility/CommandMapCommands.cs @@ -1,8 +1,8 @@ #nullable disable using Microsoft.EntityFrameworkCore; -using NadekoBot.Services.Database.Models; using NadekoBot.Db; using NadekoBot.Modules.Utility.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Utility; @@ -20,7 +20,8 @@ public partial class Utility _client = client; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task AliasesClear() @@ -29,7 +30,8 @@ public partial class Utility await ReplyConfirmLocalizedAsync(strs.aliases_cleared(count)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [UserPerm(GuildPerm.Administrator)] [RequireContext(ContextType.Guild)] public async Task Alias(string trigger, [Leftover] string mapping = null) @@ -43,8 +45,7 @@ public partial class Utility if (string.IsNullOrWhiteSpace(mapping)) { - if (!_service.AliasMaps.TryGetValue(ctx.Guild.Id, out var maps) || - !maps.TryRemove(trigger, out _)) + if (!_service.AliasMaps.TryGetValue(ctx.Guild.Id, out var maps) || !maps.TryRemove(trigger, out _)) { await ReplyErrorLocalizedAsync(strs.alias_remove_fail(Format.Code(trigger))); return; @@ -53,11 +54,7 @@ public partial class Utility await using (var uow = _db.GetDbContext()) { var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(x => x.CommandAliases)); - var toAdd = new CommandAlias() - { - Mapping = mapping, - Trigger = trigger - }; + var toAdd = new CommandAlias { Mapping = mapping, Trigger = trigger }; var tr = config.CommandAliases.FirstOrDefault(x => x.Trigger == trigger); if (tr != null) uow.Set().Remove(tr); @@ -67,46 +64,45 @@ public partial class Utility await ReplyConfirmLocalizedAsync(strs.alias_removed(Format.Code(trigger))); return; } - _service.AliasMaps.AddOrUpdate(ctx.Guild.Id, _ => - { - using (var uow = _db.GetDbContext()) + + _service.AliasMaps.AddOrUpdate(ctx.Guild.Id, + _ => { - var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(x => x.CommandAliases)); - config.CommandAliases.Add(new() + using (var uow = _db.GetDbContext()) { - Mapping = mapping, - Trigger = trigger + var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(x => x.CommandAliases)); + config.CommandAliases.Add(new() { Mapping = mapping, Trigger = trigger }); + uow.SaveChanges(); + } + + return new(new Dictionary + { + { trigger.Trim().ToLowerInvariant(), mapping.ToLowerInvariant() } }); - uow.SaveChanges(); - } - return new(new Dictionary() { - {trigger.Trim().ToLowerInvariant(), mapping.ToLowerInvariant() }, - }); - }, (_, map) => - { - using (var uow = _db.GetDbContext()) + }, + (_, map) => { - var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(x => x.CommandAliases)); - var toAdd = new CommandAlias() + using (var uow = _db.GetDbContext()) { - Mapping = mapping, - Trigger = trigger - }; - var toRemove = config.CommandAliases.Where(x => x.Trigger == trigger); - if (toRemove.Any()) - uow.RemoveRange(toRemove.ToArray()); - config.CommandAliases.Add(toAdd); - uow.SaveChanges(); - } - map.AddOrUpdate(trigger, mapping, (key, old) => mapping); - return map; - }); + var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(x => x.CommandAliases)); + var toAdd = new CommandAlias { Mapping = mapping, Trigger = trigger }; + var toRemove = config.CommandAliases.Where(x => x.Trigger == trigger); + if (toRemove.Any()) + uow.RemoveRange(toRemove.ToArray()); + config.CommandAliases.Add(toAdd); + uow.SaveChanges(); + } + + map.AddOrUpdate(trigger, mapping, (key, old) => mapping); + return map; + }); await ReplyConfirmLocalizedAsync(strs.alias_added(Format.Code(trigger), Format.Code(mapping))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task AliasList(int page = 1) { @@ -124,14 +120,17 @@ public partial class Utility var arr = maps.ToArray(); - await ctx.SendPaginatedConfirmAsync(page, curPage => - { - return _eb.Create().WithOkColor() - .WithTitle(GetText(strs.alias_list)) - .WithDescription(string.Join("\n", - arr.Skip(curPage * 10).Take(10).Select(x => $"`{x.Key}` => `{x.Value}`"))); - - }, arr.Length, 10); + await ctx.SendPaginatedConfirmAsync(page, + curPage => + { + return _eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.alias_list)) + .WithDescription(string.Join("\n", + arr.Skip(curPage * 10).Take(10).Select(x => $"`{x.Key}` => `{x.Value}`"))); + }, + arr.Length, + 10); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Common/ConvertUnit.cs b/src/NadekoBot/Modules/Utility/Common/ConvertUnit.cs index f77678c76..264b768f2 100644 --- a/src/NadekoBot/Modules/Utility/Common/ConvertUnit.cs +++ b/src/NadekoBot/Modules/Utility/Common/ConvertUnit.cs @@ -9,4 +9,4 @@ public class ConvertUnit public string[] Triggers { get; set; } public string UnitType { get; set; } public decimal Modifier { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRoleNotFoundException.cs b/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRoleNotFoundException.cs index b924e0489..495c1d485 100644 --- a/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRoleNotFoundException.cs +++ b/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRoleNotFoundException.cs @@ -3,15 +3,18 @@ namespace NadekoBot.Modules.Utility.Common.Exceptions; public class StreamRoleNotFoundException : Exception { - public StreamRoleNotFoundException() : base("Stream role wasn't found.") + public StreamRoleNotFoundException() + : base("Stream role wasn't found.") { } - public StreamRoleNotFoundException(string message) : base(message) + public StreamRoleNotFoundException(string message) + : base(message) { } - public StreamRoleNotFoundException(string message, Exception innerException) : base(message, innerException) + public StreamRoleNotFoundException(string message, Exception innerException) + : base(message, innerException) { } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRolePermissionException.cs b/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRolePermissionException.cs index d6197a1f5..ca492ff9a 100644 --- a/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRolePermissionException.cs +++ b/src/NadekoBot/Modules/Utility/Common/Exceptions/StreamRolePermissionException.cs @@ -3,15 +3,18 @@ namespace NadekoBot.Modules.Utility.Common.Exceptions; public class StreamRolePermissionException : Exception { - public StreamRolePermissionException() : base("Stream role was unable to be applied.") + public StreamRolePermissionException() + : base("Stream role was unable to be applied.") { } - public StreamRolePermissionException(string message) : base(message) + public StreamRolePermissionException(string message) + : base(message) { } - public StreamRolePermissionException(string message, Exception innerException) : base(message, innerException) + public StreamRolePermissionException(string message, Exception innerException) + : base(message, innerException) { } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonData.cs b/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonData.cs index 5080853f7..74153f9a6 100644 --- a/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonData.cs +++ b/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonData.cs @@ -61,10 +61,10 @@ public sealed class PatreonResponse { [JsonPropertyName("data")] public List Data { get; set; } - + [JsonPropertyName("included")] public List Included { get; set; } - + [JsonPropertyName("links")] public PatreonLinks Links { get; set; } } @@ -73,12 +73,13 @@ public sealed class PatreonLinks { [JsonPropertyName("next")] public string Next { get; set; } -} +} public sealed class PatreonUser { [JsonPropertyName("attributes")] public PatreonUserAttributes Attributes { get; set; } + [JsonPropertyName("id")] public string Id { get; set; } // public string Type { get; set; } @@ -89,6 +90,7 @@ public sealed class PatreonUserAttributes [JsonPropertyName("social_connections")] public PatreonSocials SocialConnections { get; set; } } + public sealed class PatreonSocials { [JsonPropertyName("discord")] @@ -100,7 +102,7 @@ public sealed class DiscordSocial [JsonPropertyName("user_id")] public string UserId { get; set; } } - + public sealed class PatreonMember { [JsonPropertyName("attributes")] @@ -129,4 +131,4 @@ public sealed class PatreonUserData { [JsonPropertyName("id")] public string Id { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Common/StreamRoleListType.cs b/src/NadekoBot/Modules/Utility/Common/StreamRoleListType.cs index 67112c7a0..fe713018d 100644 --- a/src/NadekoBot/Modules/Utility/Common/StreamRoleListType.cs +++ b/src/NadekoBot/Modules/Utility/Common/StreamRoleListType.cs @@ -4,5 +4,5 @@ namespace NadekoBot.Modules.Utility.Common; public enum StreamRoleListType { Whitelist, - Blacklist, -} + Blacklist +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/ConfigCommands.cs b/src/NadekoBot/Modules/Utility/ConfigCommands.cs index 03b0b3944..31fe44fe7 100644 --- a/src/NadekoBot/Modules/Utility/ConfigCommands.cs +++ b/src/NadekoBot/Modules/Utility/ConfigCommands.cs @@ -10,20 +10,21 @@ public partial class Utility public ConfigCommands(IEnumerable settingServices) => _settingServices = settingServices; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task ConfigReload(string name) { - var setting = _settingServices.FirstOrDefault(x => - x.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase)); + var setting = _settingServices.FirstOrDefault(x + => x.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase)); if (setting is null) { var configNames = _settingServices.Select(x => x.Name); var embed = _eb.Create() - .WithErrorColor() - .WithDescription(GetText(strs.config_not_found(Format.Code(name)))) - .AddField(GetText(strs.config_list), string.Join("\n", configNames)); + .WithErrorColor() + .WithDescription(GetText(strs.config_not_found(Format.Code(name)))) + .AddField(GetText(strs.config_list), string.Join("\n", configNames)); await ctx.Channel.EmbedAsync(embed); return; @@ -32,36 +33,37 @@ public partial class Utility setting.Reload(); await ctx.OkAsync(); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task Config(string name = null, string prop = null, [Leftover] string value = null) { var configNames = _settingServices.Select(x => x.Name); - + // if name is not provided, print available configs name = name?.ToLowerInvariant(); if (string.IsNullOrWhiteSpace(name)) { var embed = _eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.config_list)) - .WithDescription(string.Join("\n", configNames)); + .WithOkColor() + .WithTitle(GetText(strs.config_list)) + .WithDescription(string.Join("\n", configNames)); await ctx.Channel.EmbedAsync(embed); return; } - var setting = _settingServices.FirstOrDefault(x => - x.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase)); + var setting = _settingServices.FirstOrDefault(x + => x.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase)); // if config name is not found, print error and the list of configs if (setting is null) { var embed = _eb.Create() - .WithErrorColor() - .WithDescription(GetText(strs.config_not_found(Format.Code(name)))) - .AddField(GetText(strs.config_list), string.Join("\n", configNames)); + .WithErrorColor() + .WithDescription(GetText(strs.config_not_found(Format.Code(name)))) + .AddField(GetText(strs.config_list), string.Join("\n", configNames)); await ctx.Channel.EmbedAsync(embed); return; @@ -75,31 +77,29 @@ public partial class Utility if (string.IsNullOrWhiteSpace(prop)) { var propStrings = GetPropsAndValuesString(setting, propNames); - var embed = _eb.Create() - .WithOkColor() - .WithTitle($"⚙️ {setting.Name}") - .WithDescription(propStrings); + var embed = _eb.Create().WithOkColor().WithTitle($"⚙️ {setting.Name}").WithDescription(propStrings); await ctx.Channel.EmbedAsync(embed); return; } // if the prop is invalid -> print error and list of - + var exists = propNames.Any(x => x == prop); if (!exists) { var propStrings = GetPropsAndValuesString(setting, propNames); var propErrorEmbed = _eb.Create() - .WithErrorColor() - .WithDescription(GetText(strs.config_prop_not_found(Format.Code(prop), Format.Code(name)))) - .AddField($"⚙️ {setting.Name}", propStrings); + .WithErrorColor() + .WithDescription(GetText( + strs.config_prop_not_found(Format.Code(prop), Format.Code(name)))) + .AddField($"⚙️ {setting.Name}", propStrings); await ctx.Channel.EmbedAsync(propErrorEmbed); return; } - + // if prop is sent, but value is not, then we have to check // if prop is valid -> if (string.IsNullOrWhiteSpace(value)) @@ -108,17 +108,14 @@ public partial class Utility if (string.IsNullOrWhiteSpace(value)) value = "-"; - - if (prop != "currency.sign") - { - value = Format.Code(Format.Sanitize(value?.TrimTo(1000)), "json"); - } + + if (prop != "currency.sign") value = Format.Code(Format.Sanitize(value?.TrimTo(1000)), "json"); var embed = _eb.Create() - .WithOkColor() - .AddField("Config", Format.Code(setting.Name), true) - .AddField("Prop", Format.Code(prop), true) - .AddField("Value", value); + .WithOkColor() + .AddField("Config", Format.Code(setting.Name), true) + .AddField("Prop", Format.Code(prop), true) + .AddField("Value", value); var comment = setting.GetComment(prop); if (!string.IsNullOrWhiteSpace(comment)) @@ -148,11 +145,10 @@ public partial class Utility val = val?.TrimTo(28); return val?.Replace("\n", "") ?? "-"; }); - - var strings = names.Zip(propValues, (name, value) => - $"{name, -25} = {value}\n"); + + var strings = names.Zip(propValues, (name, value) => $"{name,-25} = {value}\n"); return Format.Code(string.Concat(strings), "hs"); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/InfoCommands.cs b/src/NadekoBot/Modules/Utility/InfoCommands.cs index 2360d8ebd..862f9a1f7 100644 --- a/src/NadekoBot/Modules/Utility/InfoCommands.cs +++ b/src/NadekoBot/Modules/Utility/InfoCommands.cs @@ -17,22 +17,23 @@ public partial class Utility _stats = stats; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task ServerInfo(string guildName = null) { var channel = (ITextChannel)ctx.Channel; guildName = guildName?.ToUpperInvariant(); SocketGuild guild; - + if (string.IsNullOrWhiteSpace(guildName)) guild = (SocketGuild)channel.Guild; else guild = _client.Guilds.FirstOrDefault(g => g.Name.ToUpperInvariant() == guildName.ToUpperInvariant()); - + if (guild is null) return; - + var ownername = guild.GetUser(guild.OwnerId); var textchn = guild.TextChannels.Count; var voicechn = guild.VoiceChannels.Count; @@ -42,35 +43,30 @@ public partial class Utility var features = string.Join(", ", guild.Features); if (string.IsNullOrWhiteSpace(features)) features = "-"; - + var embed = _eb.Create() - .WithAuthor(GetText(strs.server_info)) - .WithTitle(guild.Name) - .AddField(GetText(strs.id), guild.Id.ToString(), true) - .AddField(GetText(strs.owner), ownername.ToString(), true) - .AddField(GetText(strs.members), guild.MemberCount.ToString(), true) - .AddField(GetText(strs.channels), channels, true) - .AddField(GetText(strs.created_at), $"{createdAt:dd.MM.yyyy HH:mm}", true) - .AddField(GetText(strs.roles), (guild.Roles.Count - 1).ToString(), true) - .AddField(GetText(strs.features), features) - .WithOkColor(); - + .WithAuthor(GetText(strs.server_info)) + .WithTitle(guild.Name) + .AddField(GetText(strs.id), guild.Id.ToString(), true) + .AddField(GetText(strs.owner), ownername.ToString(), true) + .AddField(GetText(strs.members), guild.MemberCount.ToString(), true) + .AddField(GetText(strs.channels), channels, true) + .AddField(GetText(strs.created_at), $"{createdAt:dd.MM.yyyy HH:mm}", true) + .AddField(GetText(strs.roles), (guild.Roles.Count - 1).ToString(), true) + .AddField(GetText(strs.features), features) + .WithOkColor(); + if (Uri.IsWellFormedUriString(guild.IconUrl, UriKind.Absolute)) embed.WithThumbnailUrl(guild.IconUrl); - + if (guild.Emotes.Any()) - { embed.AddField(GetText(strs.custom_emojis) + $"({guild.Emotes.Count})", - string.Join(" ", guild.Emotes - .Shuffle() - .Take(20) - .Select(e => $"{e.Name} {e.ToString()}")) - .TrimTo(1020)); - } + string.Join(" ", guild.Emotes.Shuffle().Take(20).Select(e => $"{e.Name} {e}")).TrimTo(1020)); await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task ChannelInfo(ITextChannel channel = null) { @@ -80,16 +76,17 @@ public partial class Utility var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(ch.Id >> 22); var usercount = (await ch.GetUsersAsync().FlattenAsync()).Count(); var embed = _eb.Create() - .WithTitle(ch.Name) - .WithDescription(ch.Topic?.SanitizeMentions(true)) - .AddField(GetText(strs.id), ch.Id.ToString(), true) - .AddField(GetText(strs.created_at), $"{createdAt:dd.MM.yyyy HH:mm}", true) - .AddField(GetText(strs.users), usercount.ToString(), true) - .WithOkColor(); + .WithTitle(ch.Name) + .WithDescription(ch.Topic?.SanitizeMentions(true)) + .AddField(GetText(strs.id), ch.Id.ToString(), true) + .AddField(GetText(strs.created_at), $"{createdAt:dd.MM.yyyy HH:mm}", true) + .AddField(GetText(strs.users), usercount.ToString(), true) + .WithOkColor(); await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task UserInfo(IGuildUser usr = null) { @@ -98,17 +95,15 @@ public partial class Utility if (user is null) return; - var embed = _eb.Create() - .AddField(GetText(strs.name), $"**{user.Username}**#{user.Discriminator}", true); - if (!string.IsNullOrWhiteSpace(user.Nickname)) - { - embed.AddField(GetText(strs.nickname), user.Nickname, true); - } + var embed = _eb.Create().AddField(GetText(strs.name), $"**{user.Username}**#{user.Discriminator}", true); + if (!string.IsNullOrWhiteSpace(user.Nickname)) embed.AddField(GetText(strs.nickname), user.Nickname, true); embed.AddField(GetText(strs.id), user.Id.ToString(), true) - .AddField(GetText(strs.joined_server), $"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm") ?? "?"}", true) - .AddField(GetText(strs.joined_discord), $"{user.CreatedAt:dd.MM.yyyy HH:mm}", true) - .AddField(GetText(strs.roles), $"**({user.RoleIds.Count - 1})** - {string.Join("\n", user.GetRoles().Take(10).Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions(true)}", true) - .WithOkColor(); + .AddField(GetText(strs.joined_server), $"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm") ?? "?"}", true) + .AddField(GetText(strs.joined_discord), $"{user.CreatedAt:dd.MM.yyyy HH:mm}", true) + .AddField(GetText(strs.roles), + $"**({user.RoleIds.Count - 1})** - {string.Join("\n", user.GetRoles().Take(10).Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions(true)}", + true) + .WithOkColor(); var av = user.RealAvatarUrl(); if (av != null && av.IsAbsoluteUri) @@ -116,7 +111,8 @@ public partial class Utility await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] public async Task Activity(int page = 1) @@ -130,19 +126,20 @@ public partial class Utility var startCount = page * activityPerPage; var str = new StringBuilder(); - foreach (var kvp in CmdHandler.UserMessagesSent.OrderByDescending(kvp => kvp.Value).Skip(page * activityPerPage).Take(activityPerPage)) - { - str.AppendLine(GetText(strs.activity_line( - ++startCount, + foreach (var kvp in CmdHandler.UserMessagesSent.OrderByDescending(kvp => kvp.Value) + .Skip(page * activityPerPage) + .Take(activityPerPage)) + str.AppendLine(GetText(strs.activity_line(++startCount, Format.Bold(kvp.Key.ToString()), - kvp.Value / _stats.GetUptime().TotalSeconds, kvp.Value))); - } + kvp.Value / _stats.GetUptime().TotalSeconds, + kvp.Value))); await ctx.Channel.EmbedAsync(_eb.Create() - .WithTitle(GetText(strs.activity_page(page + 1))) - .WithOkColor() - .WithFooter(GetText(strs.activity_users_total(CmdHandler.UserMessagesSent.Count))) - .WithDescription(str.ToString())); + .WithTitle(GetText(strs.activity_page(page + 1))) + .WithOkColor() + .WithFooter(GetText( + strs.activity_users_total(CmdHandler.UserMessagesSent.Count))) + .WithDescription(str.ToString())); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/InviteCommands.cs b/src/NadekoBot/Modules/Utility/InviteCommands.cs index 2f4062cfb..ef0c8412b 100644 --- a/src/NadekoBot/Modules/Utility/InviteCommands.cs +++ b/src/NadekoBot/Modules/Utility/InviteCommands.cs @@ -8,7 +8,8 @@ public partial class Utility [Group] public class InviteCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [BotPerm(ChannelPerm.CreateInstantInvite)] [UserPerm(ChannelPerm.CreateInstantInvite)] @@ -20,16 +21,17 @@ public partial class Utility return; var ch = (ITextChannel)ctx.Channel; - var invite = await ch.CreateInviteAsync(opts.Expire, opts.MaxUses, isTemporary: opts.Temporary, isUnique: opts.Unique); + var invite = await ch.CreateInviteAsync(opts.Expire, opts.MaxUses, opts.Temporary, opts.Unique); await SendConfirmAsync($"{ctx.User.Mention} https://discord.gg/{invite.Code}"); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [BotPerm(ChannelPerm.ManageChannels)] [UserPerm(ChannelPerm.ManageChannels)] - public async Task InviteList(int page = 1, [Leftover]ITextChannel ch = null) + public async Task InviteList(int page = 1, [Leftover] ITextChannel ch = null) { if (--page < 0) return; @@ -37,44 +39,40 @@ public partial class Utility var invites = await channel.GetInvitesAsync(); - await ctx.SendPaginatedConfirmAsync(page, cur => - { - var i = 1; - var invs = invites - .Skip(cur * 9) - .Take(9) - .ToList(); - - if (!invs.Any()) + await ctx.SendPaginatedConfirmAsync(page, + cur => { - return _eb.Create() - .WithErrorColor() - .WithDescription(GetText(strs.no_invites)); - } + var i = 1; + var invs = invites.Skip(cur * 9).Take(9).ToList(); - var embed = _eb.Create().WithOkColor(); - foreach (var inv in invites) - { - var expiryString = inv.MaxAge is null or 0 || inv.CreatedAt is null - ? "∞" - : (inv.CreatedAt.Value.AddSeconds(inv.MaxAge.Value).UtcDateTime - DateTime.UtcNow) - .ToString(@"d\.hh\:mm\:ss"); - var creator = inv.Inviter.ToString().TrimTo(25); - var usesString = $"{inv.Uses} / {(inv.MaxUses == 0 ? "∞" : inv.MaxUses?.ToString())}"; - - var desc = $@"`{GetText(strs.inv_uses)}` **{usesString}** + if (!invs.Any()) + return _eb.Create().WithErrorColor().WithDescription(GetText(strs.no_invites)); + + var embed = _eb.Create().WithOkColor(); + foreach (var inv in invites) + { + var expiryString = inv.MaxAge is null or 0 || inv.CreatedAt is null + ? "∞" + : (inv.CreatedAt.Value.AddSeconds(inv.MaxAge.Value).UtcDateTime - DateTime.UtcNow).ToString( + @"d\.hh\:mm\:ss"); + var creator = inv.Inviter.ToString().TrimTo(25); + var usesString = $"{inv.Uses} / {(inv.MaxUses == 0 ? "∞" : inv.MaxUses?.ToString())}"; + + var desc = $@"`{GetText(strs.inv_uses)}` **{usesString}** `{GetText(strs.inv_expire)}` **{expiryString}** {inv.Url} "; - embed.AddField($"#{i++} {creator}", desc); - } + embed.AddField($"#{i++} {creator}", desc); + } - return embed; - - }, invites.Count, 9); + return embed; + }, + invites.Count, + 9); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [BotPerm(ChannelPerm.ManageChannels)] [UserPerm(ChannelPerm.ManageChannels)] @@ -82,7 +80,7 @@ public partial class Utility { if (--index < 0) return; - + var ch = (ITextChannel)ctx.Channel; var invites = await ch.GetInvitesAsync(); @@ -95,4 +93,4 @@ public partial class Utility await ReplyAsync(GetText(strs.invite_deleted(Format.Bold(inv.Code)))); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/QuoteCommands.cs b/src/NadekoBot/Modules/Utility/QuoteCommands.cs index 0c7e5325e..826162005 100644 --- a/src/NadekoBot/Modules/Utility/QuoteCommands.cs +++ b/src/NadekoBot/Modules/Utility/QuoteCommands.cs @@ -1,8 +1,9 @@ #nullable disable using NadekoBot.Common.Yml; -using NadekoBot.Services.Database.Models; using NadekoBot.Db; +using NadekoBot.Services.Database.Models; using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; namespace NadekoBot.Modules.Utility; @@ -11,6 +12,25 @@ public partial class Utility [Group] public class QuoteCommands : NadekoSubmodule { + private const string _prependExport = + @"# Keys are keywords, Each key has a LIST of quotes in the following format: +# - id: Alphanumeric id used for commands related to the quote. (Note, when using .quotesimport, a new id will be generated.) +# an: Author name +# aid: Author id +# txt: Quote text +"; + + private static readonly ISerializer _exportSerializer = new SerializerBuilder() + .WithEventEmitter(args + => new MultilineScalarFlowStyleEmitter(args)) + .WithNamingConvention( + CamelCaseNamingConvention.Instance) + .WithIndentedSequences() + .ConfigureDefaultValuesHandling(DefaultValuesHandling + .OmitDefaults) + .DisableAliases() + .Build(); + private readonly DbService _db; private readonly IHttpClientFactory _http; @@ -20,13 +40,15 @@ public partial class Utility _http = http; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] public Task ListQuotes(OrderType order = OrderType.Keyword) => ListQuotes(1, order); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] public async Task ListQuotes(int page = 1, OrderType order = OrderType.Keyword) @@ -43,12 +65,15 @@ public partial class Utility if (quotes.Any()) await SendConfirmAsync(GetText(strs.quotes_page(page + 1)), - string.Join("\n", quotes.Select(q => $"`#{q.Id}` {Format.Bold(q.Keyword.SanitizeAllMentions()),-20} by {q.AuthorName.SanitizeAllMentions()}"))); + string.Join("\n", + quotes.Select(q + => $"`#{q.Id}` {Format.Bold(q.Keyword.SanitizeAllMentions()),-20} by {q.AuthorName.SanitizeAllMentions()}"))); else await ReplyErrorLocalizedAsync(strs.quotes_page_none); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task QuotePrint([Leftover] string keyword) { @@ -71,9 +96,7 @@ public partial class Utility if (quote is null) return; - var rep = new ReplacementBuilder() - .WithDefault(Context) - .Build(); + var rep = new ReplacementBuilder().WithDefault(Context).Build(); var text = SmartText.CreateFrom(quote.Text); text = rep.Replace(text); @@ -81,7 +104,8 @@ public partial class Utility await ctx.Channel.SendAsync($"`#{quote.Id}` 📣 " + text, true); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task QuoteShow(int id) { @@ -104,14 +128,16 @@ public partial class Utility private async Task ShowQuoteData(Quote data) => await ctx.Channel.EmbedAsync(_eb.Create(ctx) - .WithOkColor() - .WithTitle(GetText(strs.quote_id($"#{data.Id}"))) - .AddField(GetText(strs.trigger), data.Keyword) - .AddField(GetText(strs.response), Format.Sanitize(data.Text).Replace("](", "]\\(")) - .WithFooter(GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})"))) - ); + .WithOkColor() + .WithTitle(GetText(strs.quote_id($"#{data.Id}"))) + .AddField(GetText(strs.trigger), data.Keyword) + .AddField(GetText(strs.response), + Format.Sanitize(data.Text).Replace("](", "]\\(")) + .WithFooter( + GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})")))); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task QuoteSearch(string keyword, [Leftover] string text) { @@ -129,11 +155,14 @@ public partial class Utility if (keywordquote is null) return; - await ctx.Channel.SendMessageAsync($"`#{keywordquote.Id}` 💬 " + keyword.ToLowerInvariant() + ": " + - keywordquote.Text.SanitizeAllMentions()); + await ctx.Channel.SendMessageAsync($"`#{keywordquote.Id}` 💬 " + + keyword.ToLowerInvariant() + + ": " + + keywordquote.Text.SanitizeAllMentions()); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task QuoteId(int id) { @@ -142,9 +171,7 @@ public partial class Utility Quote quote; - var rep = new ReplacementBuilder() - .WithDefault(Context) - .Build(); + var rep = new ReplacementBuilder().WithDefault(Context).Build(); await using (var uow = _db.GetDbContext()) { @@ -157,23 +184,26 @@ public partial class Utility return; } - var infoText = $"`#{quote.Id} added by {quote.AuthorName.SanitizeAllMentions()}` 🗯️ " + quote.Keyword.ToLowerInvariant().SanitizeAllMentions() + ":\n"; + var infoText = $"`#{quote.Id} added by {quote.AuthorName.SanitizeAllMentions()}` 🗯️ " + + quote.Keyword.ToLowerInvariant().SanitizeAllMentions() + + ":\n"; + - var text = SmartText.CreateFrom(quote.Text); text = rep.Replace(text); await ctx.Channel.SendAsync(infoText + text, true); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task QuoteAdd(string keyword, [Leftover] string text) { if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text)) return; - + keyword = keyword.ToUpperInvariant(); - + Quote q; await using (var uow = _db.GetDbContext()) { @@ -183,14 +213,16 @@ public partial class Utility AuthorName = ctx.Message.Author.Username, GuildId = ctx.Guild.Id, Keyword = keyword, - Text = text, + Text = text }); await uow.SaveChangesAsync(); } + await ReplyConfirmLocalizedAsync(strs.quote_added_new(Format.Code(q.Id.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task QuoteDelete(int id) { @@ -214,13 +246,15 @@ public partial class Utility response = GetText(strs.quote_deleted(id)); } } + if (success) await SendConfirmAsync(response); else await SendErrorAsync(response); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task DelAllQuotes([Leftover] string keyword) @@ -240,39 +274,8 @@ public partial class Utility await ReplyConfirmLocalizedAsync(strs.quotes_deleted(Format.Bold(keyword.SanitizeAllMentions()))); } - public class ExportedQuote - { - public static ExportedQuote FromModel(Quote quote) - => new() - { - Id = ((kwum)quote.Id).ToString(), - An = quote.AuthorName, - Aid = quote.AuthorId, - Txt = quote.Text - }; - - public string Id { get; set; } - public string An { get; set; } - public ulong Aid { get; set; } - public string Txt { get; set; } - } - - private const string _prependExport = - @"# Keys are keywords, Each key has a LIST of quotes in the following format: -# - id: Alphanumeric id used for commands related to the quote. (Note, when using .quotesimport, a new id will be generated.) -# an: Author name -# aid: Author id -# txt: Quote text -"; - private static readonly ISerializer _exportSerializer = new SerializerBuilder() - .WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args)) - .WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance) - .WithIndentedSequences() - .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults) - .DisableAliases() - .Build(); - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task QuotesExport() @@ -280,31 +283,27 @@ public partial class Utility IEnumerable quotes; await using (var uow = _db.GetDbContext()) { - quotes = uow.Quotes - .GetForGuild(ctx.Guild.Id) - .ToList(); + quotes = uow.Quotes.GetForGuild(ctx.Guild.Id).ToList(); } - var crsDict = quotes - .GroupBy(x => x.Keyword) - .ToDictionary(x => x.Key, x => x.Select(ExportedQuote.FromModel)); - - var text = _prependExport + _exportSerializer - .Serialize(crsDict) - .UnescapeUnicodeCodePoints(); + var crsDict = quotes.GroupBy(x => x.Keyword) + .ToDictionary(x => x.Key, x => x.Select(ExportedQuote.FromModel)); + + var text = _prependExport + _exportSerializer.Serialize(crsDict).UnescapeUnicodeCodePoints(); await using var stream = await text.ToStream(); - await ctx.Channel.SendFileAsync(stream, "quote-export.yml", text: null); + await ctx.Channel.SendFileAsync(stream, "quote-export.yml"); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Ratelimit(300)] #if GLOBAL_NADEKO [OwnerOnly] #endif - public async Task QuotesImport([Leftover]string input = null) + public async Task QuotesImport([Leftover] string input = null) { input = input?.Trim(); @@ -335,10 +334,10 @@ public partial class Utility await ReplyErrorLocalizedAsync(strs.expr_import_invalid_data); return; } - + await ctx.OkAsync(); } - + public async Task ImportCrsAsync(ulong guildId, string input) { Dictionary> data; @@ -357,21 +356,33 @@ public partial class Utility foreach (var entry in data) { var keyword = entry.Key; - await uow.Quotes - .AddRangeAsync(entry.Value - .Where(quote => !string.IsNullOrWhiteSpace(quote.Txt)) - .Select(quote => new Quote() - { - GuildId = guildId, - Keyword = keyword, - Text = quote.Txt, - AuthorId = quote.Aid, - AuthorName = quote.An, - })); + await uow.Quotes.AddRangeAsync(entry.Value.Where(quote => !string.IsNullOrWhiteSpace(quote.Txt)) + .Select(quote => new Quote + { + GuildId = guildId, + Keyword = keyword, + Text = quote.Txt, + AuthorId = quote.Aid, + AuthorName = quote.An + })); } await uow.SaveChangesAsync(); return true; } + + public class ExportedQuote + { + public string Id { get; set; } + public string An { get; set; } + public ulong Aid { get; set; } + public string Txt { get; set; } + + public static ExportedQuote FromModel(Quote quote) + => new() + { + Id = ((kwum)quote.Id).ToString(), An = quote.AuthorName, Aid = quote.AuthorId, Txt = quote.Text + }; + } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/RemindCommands.cs b/src/NadekoBot/Modules/Utility/RemindCommands.cs index a33bd4918..7c890dacc 100644 --- a/src/NadekoBot/Modules/Utility/RemindCommands.cs +++ b/src/NadekoBot/Modules/Utility/RemindCommands.cs @@ -1,9 +1,9 @@ #nullable disable using Humanizer.Localisation; -using NadekoBot.Services.Database.Models; using NadekoBot.Db; using NadekoBot.Modules.Administration.Services; using NadekoBot.Modules.Utility.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Utility; @@ -12,6 +12,20 @@ public partial class Utility [Group] public class RemindCommands : NadekoSubmodule { + public enum MeOrHere + { + Me, + Here + } + + public enum Server + { + Server = int.MinValue, + Srvr = int.MinValue, + Serv = int.MinValue, + S = int.MinValue + } + private readonly DbService _db; private readonly GuildTimezoneService _tz; @@ -21,13 +35,8 @@ public partial class Utility _tz = tz; } - public enum MeOrHere - { - Me, - Here - } - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(1)] public async Task Remind(MeOrHere meorhere, [Leftover] string remindString) { @@ -36,22 +45,23 @@ public partial class Utility await ReplyErrorLocalizedAsync(strs.remind_invalid); return; } - + ulong target; target = meorhere == MeOrHere.Me ? ctx.User.Id : ctx.Channel.Id; - if (!await RemindInternal(target, meorhere == MeOrHere.Me || ctx.Guild is null, remindData.Time, remindData.What)) - { - await ReplyErrorLocalizedAsync(strs.remind_too_long); - } + if (!await RemindInternal(target, + meorhere == MeOrHere.Me || ctx.Guild is null, + remindData.Time, + remindData.What)) await ReplyErrorLocalizedAsync(strs.remind_too_long); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] [Priority(0)] public async Task Remind(ITextChannel channel, [Leftover] string remindString) { - var perms = ((IGuildUser) ctx.User).GetPermissions(channel); + var perms = ((IGuildUser)ctx.User).GetPermissions(channel); if (!perms.SendMessages || !perms.ViewChannel) { await ReplyErrorLocalizedAsync(strs.cant_read_or_send); @@ -66,55 +76,39 @@ public partial class Utility if (!await RemindInternal(channel.Id, false, remindData.Time, remindData.What)) - { await ReplyErrorLocalizedAsync(strs.remind_too_long); - } } - public enum Server - { - Server = int.MinValue, - Srvr = int.MinValue, - Serv = int.MinValue, - S = int.MinValue, - } - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Priority(0)] public Task RemindList(Server _, int page = 1) => RemindListInternal(page, true); - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [Priority(1)] public Task RemindList(int page = 1) => RemindListInternal(page, false); - + private async Task RemindListInternal(int page, bool isServer) { if (--page < 0) return; var embed = _eb.Create() - .WithOkColor() - .WithTitle(GetText(isServer ? strs.reminder_server_list : strs.reminder_list)); + .WithOkColor() + .WithTitle(GetText(isServer ? strs.reminder_server_list : strs.reminder_list)); List rems; await using (var uow = _db.GetDbContext()) { if (isServer) - { - rems = uow.Reminders - .RemindersForServer(ctx.Guild.Id, page) - .ToList(); - } + rems = uow.Reminders.RemindersForServer(ctx.Guild.Id, page).ToList(); else - { - rems = uow.Reminders - .RemindersFor(ctx.User.Id, page) - .ToList(); - } + rems = uow.Reminders.RemindersFor(ctx.User.Id, page).ToList(); } if (rems.Any()) @@ -125,11 +119,11 @@ public partial class Utility var when = rem.When; var diff = when - DateTime.UtcNow; embed.AddField( - $"#{++i + (page * 10)} {rem.When:HH:mm yyyy-MM-dd} UTC " + - $"(in {diff.Humanize(2, minUnit: TimeUnit.Minute, culture: Culture)})", + $"#{++i + (page * 10)} {rem.When:HH:mm yyyy-MM-dd} UTC " + + $"(in {diff.Humanize(2, minUnit: TimeUnit.Minute, culture: Culture)})", $@"`Target:` {(rem.IsPrivate ? "DM" : "Channel")} `TargetId:` {rem.ChannelId} -`Message:` {rem.Message?.TrimTo(50)}", false); +`Message:` {rem.Message?.TrimTo(50)}"); } } else @@ -141,18 +135,20 @@ public partial class Utility await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Priority(0)] public Task RemindDelete(Server _, int index) => RemindDelete(index, true); - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [Priority(1)] public Task RemindDelete(int index) => RemindDelete(index, false); - + private async Task RemindDelete(int index, bool isServer) { if (--index < 0) @@ -162,13 +158,9 @@ public partial class Utility await using (var uow = _db.GetDbContext()) { var rems = isServer - ? uow.Reminders - .RemindersForServer(ctx.Guild.Id, index / 10) - .ToList() - : uow.Reminders - .RemindersFor(ctx.User.Id, index / 10) - .ToList(); - + ? uow.Reminders.RemindersForServer(ctx.Guild.Id, index / 10).ToList() + : uow.Reminders.RemindersFor(ctx.User.Id, index / 10).ToList(); + var pageIndex = index % 10; if (rems.Count > pageIndex) { @@ -179,16 +171,16 @@ public partial class Utility } if (rem is null) - { await ReplyErrorLocalizedAsync(strs.reminder_not_exist); - } else - { await ReplyConfirmLocalizedAsync(strs.reminder_deleted(index + 1)); - } } - private async Task RemindInternal(ulong targetId, bool isPrivate, TimeSpan ts, string message) + private async Task RemindInternal( + ulong targetId, + bool isPrivate, + TimeSpan ts, + string message) { var time = DateTime.UtcNow + ts; @@ -197,11 +189,8 @@ public partial class Utility if (ctx.Guild != null) { - var perms = ((IGuildUser) ctx.User).GetPermissions((IGuildChannel) ctx.Channel); - if (!perms.MentionEveryone) - { - message = message.SanitizeAllMentions(); - } + var perms = ((IGuildUser)ctx.User).GetPermissions((IGuildChannel)ctx.Channel); + if (!perms.MentionEveryone) message = message.SanitizeAllMentions(); } var rem = new Reminder @@ -220,17 +209,16 @@ public partial class Utility await uow.SaveChangesAsync(); } - var gTime = ctx.Guild is null - ? time - : TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(ctx.Guild.Id)); + var gTime = ctx.Guild is null ? time : TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(ctx.Guild.Id)); try { - await SendConfirmAsync( - "⏰ " + GetText(strs.remind( - Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username), - Format.Bold(message), - ts.Humanize(3, minUnit: TimeUnit.Second, culture: Culture), - gTime, gTime))); + await SendConfirmAsync("⏰ " + + GetText(strs.remind( + Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username), + Format.Bold(message), + ts.Humanize(3, minUnit: TimeUnit.Second, culture: Culture), + gTime, + gTime))); } catch { @@ -239,4 +227,4 @@ public partial class Utility return true; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/RepeatCommands.cs b/src/NadekoBot/Modules/Utility/RepeatCommands.cs index 70745aa55..42e2b2a99 100644 --- a/src/NadekoBot/Modules/Utility/RepeatCommands.cs +++ b/src/NadekoBot/Modules/Utility/RepeatCommands.cs @@ -1,6 +1,6 @@ using NadekoBot.Common.TypeReaders; -using NadekoBot.Modules.Utility.Services; using NadekoBot.Common.TypeReaders.Models; +using NadekoBot.Modules.Utility.Services; namespace NadekoBot.Modules.Utility; @@ -9,7 +9,8 @@ public partial class Utility [Group] public class RepeatCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task RepeatInvoke(int index) @@ -18,13 +19,11 @@ public partial class Utility return; var success = await _service.TriggerExternal(ctx.Guild.Id, index); - if (!success) - { - await ReplyErrorLocalizedAsync(strs.repeat_invoke_none); - } + if (!success) await ReplyErrorLocalizedAsync(strs.repeat_invoke_none); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task RepeatRemove(int index) @@ -32,117 +31,114 @@ public partial class Utility if (--index < 0) return; - var removed = await _service.RemoveByIndexAsync(ctx.Guild.Id, index); + var removed = await _service.RemoveByIndexAsync(ctx.Guild.Id, index); if (removed is null) { await ReplyErrorLocalizedAsync(strs.repeater_remove_fail); return; } - + var description = GetRepeaterInfoString(removed); await ctx.Channel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.repeater_removed(index + 1))) - .WithDescription(description)); + .WithOkColor() + .WithTitle(GetText(strs.repeater_removed(index + 1))) + .WithDescription(description)); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task RepeatRedundant(int index) { if (--index < 0) return; - + var result = await _service.ToggleRedundantAsync(ctx.Guild.Id, index); - + if (result is null) { await ReplyErrorLocalizedAsync(strs.index_out_of_range); return; } - + if (result.Value) - { await ReplyErrorLocalizedAsync(strs.repeater_redundant_no(index + 1)); - } else - { await ReplyConfirmLocalizedAsync(strs.repeater_redundant_yes(index + 1)); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] [Priority(-1)] - public Task Repeat([Leftover]string message) + public Task Repeat([Leftover] string message) => Repeat(null, null, message); - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] [Priority(0)] - public Task Repeat(StoopidTime interval, [Leftover]string message) + public Task Repeat(StoopidTime interval, [Leftover] string message) => Repeat(null, interval, message); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] [Priority(1)] public Task Repeat(GuildDateTime dt, [Leftover] string message) => Repeat(dt, null, message); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] [Priority(2)] - public async Task Repeat(GuildDateTime? dt, StoopidTime? interval, [Leftover]string message) + public async Task Repeat(GuildDateTime? dt, StoopidTime? interval, [Leftover] string message) { var startTimeOfDay = dt?.InputTimeUtc.TimeOfDay; // if interval not null, that means user specified it (don't change it) - + // if interval is null set the default to: // if time of day is specified: 1 day // else 5 minutes - var realInterval = interval?.Time ?? (startTimeOfDay is null - ? TimeSpan.FromMinutes(5) - : TimeSpan.FromDays(1)); - - if (string.IsNullOrWhiteSpace(message) - || (interval != null && - (interval.Time > TimeSpan.FromMinutes(25000) || interval.Time < TimeSpan.FromMinutes(1)))) - { - return; - } + var realInterval = + interval?.Time ?? (startTimeOfDay is null ? TimeSpan.FromMinutes(5) : TimeSpan.FromDays(1)); - message = ((IGuildUser) ctx.User).GuildPermissions.MentionEveryone + if (string.IsNullOrWhiteSpace(message) + || (interval != null + && (interval.Time > TimeSpan.FromMinutes(25000) || interval.Time < TimeSpan.FromMinutes(1)))) + return; + + message = ((IGuildUser)ctx.User).GuildPermissions.MentionEveryone ? message : message.SanitizeMentions(true); - var runner = await _service.AddRepeaterAsync( - ctx.Channel.Id, + var runner = await _service.AddRepeaterAsync(ctx.Channel.Id, ctx.Guild.Id, realInterval, message, false, - startTimeOfDay - ); + startTimeOfDay); if (runner is null) { await ReplyErrorLocalizedAsync(strs.repeater_exceed_limit(5)); return; } - + var description = GetRepeaterInfoString(runner); await ctx.Channel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.repeater_created)) - .WithDescription(description)); + .WithOkColor() + .WithTitle(GetText(strs.repeater_created)) + .WithDescription(description)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task RepeatList() @@ -153,46 +149,37 @@ public partial class Utility await ReplyConfirmLocalizedAsync(strs.repeaters_none); return; } - - var embed = _eb.Create() - .WithTitle(GetText(strs.list_of_repeaters)) - .WithOkColor(); + + var embed = _eb.Create().WithTitle(GetText(strs.list_of_repeaters)).WithOkColor(); var i = 0; - foreach(var runner in repeaters.OrderBy(r => r.Repeater.Id)) + foreach (var runner in repeaters.OrderBy(r => r.Repeater.Id)) { var description = GetRepeaterInfoString(runner); var name = $"#`{++i}`"; - embed.AddField( - name, - description - ); + embed.AddField(name, description); } - + await ctx.Channel.EmbedAsync(embed); } - + private string GetRepeaterInfoString(RunningRepeater runner) { var intervalString = Format.Bold(runner.Repeater.Interval.ToPrettyStringHm()); - var executesIn = runner.NextTime < DateTime.UtcNow - ? TimeSpan.Zero - : runner.NextTime - DateTime.UtcNow; + var executesIn = runner.NextTime < DateTime.UtcNow ? TimeSpan.Zero : runner.NextTime - DateTime.UtcNow; var executesInString = Format.Bold(executesIn.ToPrettyStringHm()); var message = Format.Sanitize(runner.Repeater.Message.TrimTo(50)); - + var description = string.Empty; if (_service.IsNoRedundant(runner.Repeater.Id)) - { description = Format.Underline(Format.Bold(GetText(strs.no_redundant))) + "\n\n"; - } - - description += $"<#{runner.Repeater.ChannelId}>\n" + - $"`{GetText(strs.interval_colon)}` {intervalString}\n" + - $"`{GetText(strs.executes_in_colon)}` {executesInString}\n" + - $"`{GetText(strs.message_colon)}` {message}"; - + + description += $"<#{runner.Repeater.ChannelId}>\n" + + $"`{GetText(strs.interval_colon)}` {intervalString}\n" + + $"`{GetText(strs.executes_in_colon)}` {executesInString}\n" + + $"`{GetText(strs.message_colon)}` {message}"; + return description; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Services/CommandMapService.cs b/src/NadekoBot/Modules/Utility/Services/CommandMapService.cs index fa89cc290..077e416be 100644 --- a/src/NadekoBot/Modules/Utility/Services/CommandMapService.cs +++ b/src/NadekoBot/Modules/Utility/Services/CommandMapService.cs @@ -1,16 +1,15 @@ #nullable disable -using NadekoBot.Common.ModuleBehaviors; using Microsoft.EntityFrameworkCore; -using NadekoBot.Services.Database.Models; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Utility.Services; public class CommandMapService : IInputTransformer, INService { - private readonly IEmbedBuilderService _eb; - public ConcurrentDictionary> AliasMaps { get; } = new(); + private readonly IEmbedBuilderService _eb; private readonly DbService _db; @@ -22,16 +21,13 @@ public class CommandMapService : IInputTransformer, INService using var uow = db.GetDbContext(); var guildIds = client.Guilds.Select(x => x.Id).ToList(); var configs = uow.Set() - .Include(gc => gc.CommandAliases) - .Where(x => guildIds.Contains(x.GuildId)) - .ToList(); - - AliasMaps = new(configs - .ToDictionary( - x => x.GuildId, - x => new ConcurrentDictionary(x.CommandAliases - .DistinctBy(ca => ca.Trigger) - .ToDictionary(ca => ca.Trigger, ca => ca.Mapping)))); + .Include(gc => gc.CommandAliases) + .Where(x => guildIds.Contains(x.GuildId)) + .ToList(); + + AliasMaps = new(configs.ToDictionary(x => x.GuildId, + x => new ConcurrentDictionary(x.CommandAliases.DistinctBy(ca => ca.Trigger) + .ToDictionary(ca => ca.Trigger, ca => ca.Mapping)))); _db = db; } @@ -49,7 +45,11 @@ public class CommandMapService : IInputTransformer, INService return count; } - public async Task TransformInput(IGuild guild, IMessageChannel channel, IUser user, string input) + public async Task TransformInput( + IGuild guild, + IMessageChannel channel, + IUser user, + string input) { await Task.Yield(); @@ -57,11 +57,9 @@ public class CommandMapService : IInputTransformer, INService return input; if (guild != null) - { if (AliasMaps.TryGetValue(guild.Id, out var maps)) { - var keys = maps.Keys - .OrderByDescending(x => x.Length); + var keys = maps.Keys.OrderByDescending(x => x.Length); foreach (var k in keys) { @@ -79,18 +77,15 @@ public class CommandMapService : IInputTransformer, INService var _ = Task.Run(async () => { await Task.Delay(1500); - await toDelete.DeleteAsync(new() - { - RetryMode = RetryMode.AlwaysRetry - }); + await toDelete.DeleteAsync(new() { RetryMode = RetryMode.AlwaysRetry }); }); } catch { } + return newInput; } } - } return input; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Services/ConverterService.cs b/src/NadekoBot/Modules/Utility/Services/ConverterService.cs index 15cf53a7c..6ebc0bbbb 100644 --- a/src/NadekoBot/Modules/Utility/Services/ConverterService.cs +++ b/src/NadekoBot/Modules/Utility/Services/ConverterService.cs @@ -6,11 +6,8 @@ namespace NadekoBot.Modules.Utility.Services; public class ConverterService : INService { - public ConvertUnit[] Units => - _cache.Redis.GetDatabase() - .StringGet("converter_units") - .ToString() - .MapJson(); + public ConvertUnit[] Units + => _cache.Redis.GetDatabase().StringGet("converter_units").ToString().MapJson(); private readonly Timer _currencyUpdater; private readonly TimeSpan _updateInterval = new(12, 0, 0); @@ -18,20 +15,21 @@ public class ConverterService : INService private readonly IDataCache _cache; private readonly IHttpClientFactory _httpFactory; - public ConverterService(DiscordSocketClient client, DbService db, - IDataCache cache, IHttpClientFactory factory) + public ConverterService( + DiscordSocketClient client, + DbService db, + IDataCache cache, + IHttpClientFactory factory) { _db = db; _cache = cache; _httpFactory = factory; if (client.ShardId == 0) - { _currencyUpdater = new(async shouldLoad => await UpdateCurrency((bool)shouldLoad), client.ShardId == 0, TimeSpan.Zero, _updateInterval); - } } private async Task GetCurrencyRates() @@ -49,26 +47,21 @@ public class ConverterService : INService if (shouldLoad) { var currencyRates = await GetCurrencyRates(); - var baseType = new ConvertUnit() + var baseType = new ConvertUnit { - Triggers = new[] { currencyRates.Base }, - Modifier = decimal.One, - UnitType = unitTypeString + Triggers = new[] { currencyRates.Base }, Modifier = decimal.One, UnitType = unitTypeString }; - var range = currencyRates.ConversionRates.Select(u => new ConvertUnit() - { - Triggers = new[] { u.Key }, - Modifier = u.Value, - UnitType = unitTypeString - }).ToArray(); + var range = currencyRates.ConversionRates.Select(u => new ConvertUnit + { + Triggers = new[] { u.Key }, Modifier = u.Value, UnitType = unitTypeString + }) + .ToArray(); - var fileData = JsonConvert.DeserializeObject( - File.ReadAllText("data/units.json")) - .Where(x => x.UnitType != "currency"); + var fileData = JsonConvert.DeserializeObject(File.ReadAllText("data/units.json")) + .Where(x => x.UnitType != "currency"); var data = JsonConvert.SerializeObject(range.Append(baseType).Concat(fileData).ToList()); - _cache.Redis.GetDatabase() - .StringSet("converter_units", data); + _cache.Redis.GetDatabase().StringSet("converter_units", data); } } catch @@ -82,6 +75,7 @@ public class Rates { public string Base { get; set; } public DateTime Date { get; set; } + [JsonProperty("rates")] public Dictionary ConversionRates { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Services/InviteService.cs b/src/NadekoBot/Modules/Utility/Services/InviteService.cs index b89bf43f4..d26ed41b4 100644 --- a/src/NadekoBot/Modules/Utility/Services/InviteService.cs +++ b/src/NadekoBot/Modules/Utility/Services/InviteService.cs @@ -7,17 +7,34 @@ public class InviteService : INService { public class Options : INadekoCommandOptions { - [Option('m', "max-uses", Required = false, Default = 0, HelpText = "Maximum number of times the invite can be used. Default 0 (never).")] - public int MaxUses { get; set; } = 0; + [Option('m', + "max-uses", + Required = false, + Default = 0, + HelpText = "Maximum number of times the invite can be used. Default 0 (never).")] + public int MaxUses { get; set; } - [Option('u', "unique", Required = false, Default = false, HelpText = "Not setting this flag will result in bot getting the existing invite with the same settings if it exists, instead of creating a new one.")] + [Option('u', + "unique", + Required = false, + Default = false, + HelpText = + "Not setting this flag will result in bot getting the existing invite with the same settings if it exists, instead of creating a new one.")] public bool Unique { get; set; } = false; - [Option('t', "temporary", Required = false, Default = false, HelpText = "If this flag is set, the user will be kicked from the guild once they close their client.")] + [Option('t', + "temporary", + Required = false, + Default = false, + HelpText = "If this flag is set, the user will be kicked from the guild once they close their client.")] public bool Temporary { get; set; } = false; - [Option('e', "expire", Required = false, Default = 0, HelpText = "Time in seconds to expire the invite. Default 0 (no expiry).")] - public int Expire { get; set; } = 0; + [Option('e', + "expire", + Required = false, + Default = 0, + HelpText = "Time in seconds to expire the invite. Default 0 (no expiry).")] + public int Expire { get; set; } public void NormalizeOptions() { @@ -28,4 +45,4 @@ public class InviteService : INService Expire = 0; } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Services/PatreonRewardsService.cs b/src/NadekoBot/Modules/Utility/Services/PatreonRewardsService.cs index 153caacdb..894e5320a 100644 --- a/src/NadekoBot/Modules/Utility/Services/PatreonRewardsService.cs +++ b/src/NadekoBot/Modules/Utility/Services/PatreonRewardsService.cs @@ -1,23 +1,24 @@ #nullable disable -using NadekoBot.Services.Database.Models; -using NadekoBot.Modules.Utility.Common.Patreon; -using System.Net.Http.Json; -using System.Text.Json.Serialization; using LinqToDB.EntityFrameworkCore; using NadekoBot.Modules.Gambling.Services; +using NadekoBot.Modules.Utility.Common.Patreon; +using NadekoBot.Services.Database.Models; using StackExchange.Redis; -using JsonSerializer = System.Text.Json.JsonSerializer; +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace NadekoBot.Modules.Utility.Services; public class PatreonRewardsService : INService { + public TimeSpan Interval { get; } = TimeSpan.FromMinutes(3); + + public DateTime LastUpdate { get; private set; } = DateTime.UtcNow; private readonly SemaphoreSlim getPledgesLocker = new(1, 1); private readonly Timer _updater; private readonly SemaphoreSlim claimLockJustInCase = new(1, 1); - - public TimeSpan Interval { get; } = TimeSpan.FromMinutes(3); private readonly DbService _db; private readonly ICurrencyService _currency; private readonly GamblingConfigService _gamblingConfigService; @@ -27,8 +28,6 @@ public class PatreonRewardsService : INService private readonly IEmbedBuilderService _eb; private readonly DiscordSocketClient _client; - public DateTime LastUpdate { get; private set; } = DateTime.UtcNow; - public PatreonRewardsService( DbService db, ICurrencyService currency, @@ -49,8 +48,7 @@ public class PatreonRewardsService : INService _client = client; if (client.ShardId == 0) - _updater = new(async _ => await RefreshPledges(_credsProvider.GetCreds()), - null, TimeSpan.Zero, Interval); + _updater = new(async _ => await RefreshPledges(_credsProvider.GetCreds()), null, TimeSpan.Zero, Interval); } private DateTime LastAccessTokenUpdate(IBotCredentials creds) @@ -65,36 +63,17 @@ public class PatreonRewardsService : INService return lastTime; } - - private sealed class PatreonRefreshData - { - [JsonPropertyName("access_token")] - public string AccessToken { get; set; } - - [JsonPropertyName("refresh_token")] - public string RefreshToken { get; set; } - - [JsonPropertyName("expires_in")] - public long ExpiresIn { get; set; } - - [JsonPropertyName("scope")] - public string Scope { get; set; } - - [JsonPropertyName("token_type")] - public string TokenType { get; set; } - } - private async Task UpdateAccessToken(IBotCredentials creds) { Log.Information("Updating patreon access token..."); try { using var http = _httpFactory.CreateClient(); - var res = await http.PostAsync($"https://www.patreon.com/api/oauth2/token" + - $"?grant_type=refresh_token" + - $"&refresh_token={creds.Patreon.RefreshToken}" + - $"&client_id={creds.Patreon.ClientId}" + - $"&client_secret={creds.Patreon.ClientSecret}", + var res = await http.PostAsync("https://www.patreon.com/api/oauth2/token" + + "?grant_type=refresh_token" + + $"&refresh_token={creds.Patreon.RefreshToken}" + + $"&client_id={creds.Patreon.ClientId}" + + $"&client_secret={creds.Patreon.ClientSecret}", new StringContent(string.Empty)); res.EnsureSuccessStatusCode(); @@ -103,7 +82,7 @@ public class PatreonRewardsService : INService if (data is null) throw new("Invalid patreon response."); - + _credsProvider.ModifyCredsFile(oldData => { oldData.Patreon.AccessToken = data.AccessToken; @@ -126,9 +105,7 @@ public class PatreonRewardsService : INService var _1 = creds.Patreon.ClientId; var _2 = creds.Patreon.ClientSecret; var _4 = creds.Patreon.RefreshToken; - return !(string.IsNullOrWhiteSpace(_1) - || string.IsNullOrWhiteSpace(_2) - || string.IsNullOrWhiteSpace(_4)); + return !(string.IsNullOrWhiteSpace(_1) || string.IsNullOrWhiteSpace(_2) || string.IsNullOrWhiteSpace(_4)); } public async Task RefreshPledges(IBotCredentials creds) @@ -154,7 +131,6 @@ public class PatreonRewardsService : INService await getPledgesLocker.WaitAsync(); try { - var members = new List(); var users = new List(); using (var http = _httpFactory.CreateClient()) @@ -163,10 +139,10 @@ public class PatreonRewardsService : INService http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", $"Bearer {creds.Patreon.AccessToken}"); - var page = $"https://www.patreon.com/api/oauth2/v2/campaigns/{creds.Patreon.CampaignId}/members" + - "?fields%5Bmember%5D=full_name,currently_entitled_amount_cents" + - "&fields%5Buser%5D=social_connections" + - "&include=user"; + var page = $"https://www.patreon.com/api/oauth2/v2/campaigns/{creds.Patreon.CampaignId}/members" + + "?fields%5Bmember%5D=full_name,currently_entitled_amount_cents" + + "&fields%5Buser%5D=social_connections" + + "&include=user"; PatreonResponse data = null; do { @@ -175,35 +151,33 @@ public class PatreonRewardsService : INService if (data is null) break; - + members.AddRange(data.Data); users.AddRange(data.Included); } while (!string.IsNullOrWhiteSpace(page = data?.Links?.Next)); } var userData = members.Join(users, - m => m.Relationships.User.Data.Id, - u => u.Id, - (m, u) => new - { - PatreonUserId = m.Relationships.User.Data.Id, - UserId = ulong.TryParse(u.Attributes?.SocialConnections?.Discord?.UserId ?? string.Empty, - out var userId) - ? userId - : 0, - EntitledTo = m.Attributes.CurrentlyEntitledAmountCents, - }) - .Where(x => x is - { - UserId: not 0, - EntitledTo: > 0 - }) - .ToList(); + m => m.Relationships.User.Data.Id, + u => u.Id, + (m, u) => new + { + PatreonUserId = m.Relationships.User.Data.Id, + UserId = ulong.TryParse( + u.Attributes?.SocialConnections?.Discord?.UserId ?? string.Empty, + out var userId) + ? userId + : 0, + EntitledTo = m.Attributes.CurrentlyEntitledAmountCents + }) + .Where(x => x is + { + UserId: not 0, + EntitledTo: > 0 + }) + .ToList(); - foreach (var pledge in userData) - { - await ClaimReward(pledge.UserId, pledge.PatreonUserId, pledge.EntitledTo); - } + foreach (var pledge in userData) await ClaimReward(pledge.UserId, pledge.PatreonUserId, pledge.EntitledTo); } catch (Exception ex) { @@ -213,7 +187,6 @@ public class PatreonRewardsService : INService { getPledgesLocker.Release(); } - } public async Task ClaimReward(ulong userId, string patreonUserId, int cents) @@ -233,18 +206,16 @@ public class PatreonRewardsService : INService { users.Add(new() { - PatreonUserId = patreonUserId, - LastReward = now, - AmountRewardedThisMonth = eligibleFor, + PatreonUserId = patreonUserId, LastReward = now, AmountRewardedThisMonth = eligibleFor }); await uow.SaveChangesAsync(); - await _currency.AddAsync(userId, "Patreon reward - new", eligibleFor, gamble: true); - + await _currency.AddAsync(userId, "Patreon reward - new", eligibleFor, true); + Log.Information($"Sending new currency reward to {userId}"); - await SendMessageToUser(userId, $"Thank you for your pledge! " + - $"You've been awarded **{eligibleFor}**{settings.Currency.Sign} !"); + await SendMessageToUser(userId, + "Thank you for your pledge! " + $"You've been awarded **{eligibleFor}**{settings.Currency.Sign} !"); return eligibleFor; } @@ -255,11 +226,12 @@ public class PatreonRewardsService : INService await uow.SaveChangesAsync(); - await _currency.AddAsync(userId, "Patreon reward - recurring", eligibleFor, gamble: true); + await _currency.AddAsync(userId, "Patreon reward - recurring", eligibleFor, true); Log.Information($"Sending recurring currency reward to {userId}"); - await SendMessageToUser(userId, $"Thank you for your continued support! " + - $"You've been awarded **{eligibleFor}**{settings.Currency.Sign} for this month's support!"); + await SendMessageToUser(userId, + "Thank you for your continued support! " + + $"You've been awarded **{eligibleFor}**{settings.Currency.Sign} for this month's support!"); return eligibleFor; } @@ -272,11 +244,12 @@ public class PatreonRewardsService : INService usr.AmountRewardedThisMonth = toAward; await uow.SaveChangesAsync(); - await _currency.AddAsync(userId, "Patreon reward - update", toAward, gamble: true); - + await _currency.AddAsync(userId, "Patreon reward - update", toAward, true); + Log.Information($"Sending updated currency reward to {userId}"); - await SendMessageToUser(userId, $"Thank you for increasing your pledge! " + - $"You've been awarded an additional **{toAward}**{settings.Currency.Sign} !"); + await SendMessageToUser(userId, + "Thank you for increasing your pledge! " + + $"You've been awarded an additional **{toAward}**{settings.Currency.Sign} !"); return toAward; } @@ -295,7 +268,7 @@ public class PatreonRewardsService : INService var user = (IUser)_client.GetUser(userId) ?? await _client.Rest.GetUserAsync(userId); if (user is null) return; - + await user.SendConfirmAsync(_eb, message); } catch @@ -303,4 +276,23 @@ public class PatreonRewardsService : INService // ignored } } -} + + + private sealed class PatreonRefreshData + { + [JsonPropertyName("access_token")] + public string AccessToken { get; set; } + + [JsonPropertyName("refresh_token")] + public string RefreshToken { get; set; } + + [JsonPropertyName("expires_in")] + public long ExpiresIn { get; set; } + + [JsonPropertyName("scope")] + public string Scope { get; set; } + + [JsonPropertyName("token_type")] + public string TokenType { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Services/RemindService.cs b/src/NadekoBot/Modules/Utility/Services/RemindService.cs index 855eea08f..7ba46abae 100644 --- a/src/NadekoBot/Modules/Utility/Services/RemindService.cs +++ b/src/NadekoBot/Modules/Utility/Services/RemindService.cs @@ -1,21 +1,27 @@ #nullable disable -using System.Text.RegularExpressions; -using NadekoBot.Services.Database.Models; using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database.Models; +using System.Text.RegularExpressions; namespace NadekoBot.Modules.Utility.Services; public class RemindService : INService { - private readonly Regex _regex = new(@"^(?:in\s?)?\s*(?:(?\d+)(?:\s?(?:months?|mos?),?))?(?:(?:\sand\s|\s*)?(?\d+)(?:\s?(?:weeks?|w),?))?(?:(?:\sand\s|\s*)?(?\d+)(?:\s?(?:days?|d),?))?(?:(?:\sand\s|\s*)?(?\d+)(?:\s?(?:hours?|h),?))?(?:(?:\sand\s|\s*)?(?\d+)(?:\s?(?:minutes?|mins?|m),?))?\s+(?:to:?\s+)?(?(?:\r\n|[\r\n]|.)+)", - RegexOptions.Compiled | RegexOptions.Multiline); + private readonly Regex _regex = + new( + @"^(?:in\s?)?\s*(?:(?\d+)(?:\s?(?:months?|mos?),?))?(?:(?:\sand\s|\s*)?(?\d+)(?:\s?(?:weeks?|w),?))?(?:(?:\sand\s|\s*)?(?\d+)(?:\s?(?:days?|d),?))?(?:(?:\sand\s|\s*)?(?\d+)(?:\s?(?:hours?|h),?))?(?:(?:\sand\s|\s*)?(?\d+)(?:\s?(?:minutes?|mins?|m),?))?\s+(?:to:?\s+)?(?(?:\r\n|[\r\n]|.)+)", + RegexOptions.Compiled | RegexOptions.Multiline); private readonly DiscordSocketClient _client; private readonly DbService _db; private readonly IBotCredentials _creds; private readonly IEmbedBuilderService _eb; - public RemindService(DiscordSocketClient client, DbService db, IBotCredentials creds, IEmbedBuilderService eb) + public RemindService( + DiscordSocketClient client, + DbService db, + IBotCredentials creds, + IEmbedBuilderService eb) { _client = client; _db = db; @@ -35,16 +41,16 @@ public class RemindService : INService var reminders = await GetRemindersBeforeAsync(now); if (reminders.Count == 0) continue; - + Log.Information($"Executing {reminders.Count} reminders."); - + // make groups of 5, with 1.5 second inbetween each one to ensure against ratelimits foreach (var group in reminders.Chunk(5)) { var executedReminders = group.ToList(); await executedReminders.Select(ReminderTimerAction).WhenAll(); await RemoveReminders(executedReminders); - await Task.Delay(1500); + await Task.Delay(1500); } } catch (Exception ex) @@ -58,8 +64,7 @@ public class RemindService : INService private async Task RemoveReminders(List reminders) { await using var uow = _db.GetDbContext(); - uow.Set() - .RemoveRange(reminders); + uow.Set().RemoveRange(reminders); await uow.SaveChangesAsync(); } @@ -68,14 +73,9 @@ public class RemindService : INService { using var uow = _db.GetDbContext(); return uow.Reminders - .FromSqlInterpolated($"select * from reminders where ((serverid >> 22) % {_creds.TotalShards}) == {_client.ShardId} and \"when\" < {now};") - .ToListAsync(); - } - - public struct RemindObject - { - public string What { get; set; } - public TimeSpan Time { get; set; } + .FromSqlInterpolated( + $"select * from reminders where ((serverid >> 22) % {_creds.TotalShards}) == {_client.ShardId} and \"when\" < {now};") + .ToListAsync(); } public bool TryParseRemindMessage(string input, out RemindObject obj) @@ -83,13 +83,10 @@ public class RemindService : INService var m = _regex.Match(input); obj = default; - if (m.Length == 0) - { - return false; - } - + if (m.Length == 0) return false; + var values = new Dictionary(); - + var what = m.Groups["what"].Value; if (string.IsNullOrWhiteSpace(what)) @@ -97,7 +94,7 @@ public class RemindService : INService Log.Warning("No message provided for the reminder."); return false; } - + foreach (var groupName in _regex.GetGroupNames()) { if (groupName is "0" or "what") continue; @@ -106,6 +103,7 @@ public class RemindService : INService values[groupName] = 0; continue; } + if (!int.TryParse(m.Groups[groupName].Value, out var value)) { Log.Warning($"Reminder regex group {groupName} has invalid value."); @@ -117,23 +115,13 @@ public class RemindService : INService Log.Warning("Reminder time value has to be an integer greater than 0."); return false; } - + values[groupName] = value; } - - var ts = new TimeSpan - ( - (30 * values["mo"]) + (7 * values["w"]) + values["d"], - values["h"], - values["m"], - 0 - ); - obj = new() - { - Time = ts, - What = what - }; + var ts = new TimeSpan((30 * values["mo"]) + (7 * values["w"]) + values["d"], values["h"], values["m"], 0); + + obj = new() { Time = ts, What = what }; return true; } @@ -154,16 +142,25 @@ public class RemindService : INService { ch = _client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId); } + if (ch is null) return; await ch.EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle("Reminder") - .AddField("Created At", r.DateAdded.HasValue ? r.DateAdded.Value.ToLongDateString() : "?") - .AddField("By", (await ch.GetUserAsync(r.UserId))?.ToString() ?? r.UserId.ToString()), - msg: r.Message); + .WithOkColor() + .WithTitle("Reminder") + .AddField("Created At", + r.DateAdded.HasValue ? r.DateAdded.Value.ToLongDateString() : "?") + .AddField("By", + (await ch.GetUserAsync(r.UserId))?.ToString() ?? r.UserId.ToString()), + r.Message); } catch (Exception ex) { Log.Information(ex.Message + $"({r.Id})"); } } -} + + public struct RemindObject + { + public string What { get; set; } + public TimeSpan Time { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Services/RepeaterService.cs b/src/NadekoBot/Modules/Utility/Services/RepeaterService.cs index 545dc65be..dd15213e9 100644 --- a/src/NadekoBot/Modules/Utility/Services/RepeaterService.cs +++ b/src/NadekoBot/Modules/Utility/Services/RepeaterService.cs @@ -1,7 +1,6 @@ - -using Microsoft.EntityFrameworkCore; -using LinqToDB; +using LinqToDB; using LinqToDB.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Services.Database.Models; @@ -20,30 +19,29 @@ public sealed class RepeaterService : IReadyExecutor, INService private readonly object _queueLocker = new(); - public RepeaterService(DiscordSocketClient client, DbService db, IBotCredentials creds, IEmbedBuilderService eb) + public RepeaterService( + DiscordSocketClient client, + DbService db, + IBotCredentials creds, + IEmbedBuilderService eb) { _db = db; _creds = creds; _eb = eb; _client = client; - + var uow = _db.GetDbContext(); - var shardRepeaters = uow - .Set() - .FromSqlInterpolated($@"select * from repeaters + var shardRepeaters = uow.Set() + .FromSqlInterpolated($@"select * from repeaters where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") - .AsNoTracking() - .ToList(); + .AsNoTracking() + .ToList(); - _noRedundant = new(shardRepeaters - .Where(x => x.NoRedundant) - .Select(x => x.Id)); + _noRedundant = new(shardRepeaters.Where(x => x.NoRedundant).Select(x => x.Id)); - _repeaterQueue = new(shardRepeaters - .Select(rep => new RunningRepeater(rep)) - .OrderBy(x => x.NextTime)); + _repeaterQueue = new(shardRepeaters.Select(rep => new RunningRepeater(rep)).OrderBy(x => x.NextTime)); } - + public Task OnReadyAsync() { _ = Task.Run(RunRepeatersLoop); @@ -53,19 +51,16 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") private async Task RunRepeatersLoop() { while (true) - { try { // calculate timeout for the first item var timeout = GetNextTimeout(); - + // wait it out, and recalculate afterwards // because repeaters might've been modified meanwhile if (timeout > TimeSpan.Zero) { - await Task.Delay(timeout > TimeSpan.FromMinutes(1) - ? TimeSpan.FromMinutes(1) - : timeout); + await Task.Delay(timeout > TimeSpan.FromMinutes(1) ? TimeSpan.FromMinutes(1) : timeout); continue; } @@ -78,7 +73,6 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") var current = _repeaterQueue.First; while (true) { - if (current is null || current.Value.NextTime > now) break; @@ -88,23 +82,16 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") } // execute - foreach (var chunk in toExecute.Chunk(5)) - { - await chunk.Select(Trigger).WhenAll(); - } + foreach (var chunk in toExecute.Chunk(5)) await chunk.Select(Trigger).WhenAll(); // reinsert - foreach (var rep in toExecute) - { - await HandlePostExecute(rep); - } + foreach (var rep in toExecute) await HandlePostExecute(rep); } catch (Exception ex) { Log.Error(ex, "Critical error in repeater queue: {ErrorMessage}", ex.Message); await Task.Delay(5000); } - } } private async Task HandlePostExecute(RunningRepeater rep) @@ -132,12 +119,11 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") public async Task TriggerExternal(ulong guildId, int index) { await using var uow = _db.GetDbContext(); - - var toTrigger = await uow.Repeaters - .AsNoTracking() - .Where(x => x.GuildId == guildId) - .Skip(index) - .FirstOrDefaultAsyncEF(); + + var toTrigger = await uow.Repeaters.AsNoTracking() + .Where(x => x.GuildId == guildId) + .Skip(index) + .FirstOrDefaultAsyncEF(); if (toTrigger is null) return false; @@ -148,7 +134,7 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") node = _repeaterQueue.FindNode(x => x.Repeater.Id == toTrigger.Id); if (node is null) return false; - + _repeaterQueue.Remove(node); } @@ -156,7 +142,7 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") await HandlePostExecute(node.Value); return true; } - + private void AddToQueue(RunningRepeater rep) { lock (_queueLocker) @@ -191,21 +177,23 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") return first.Value.NextTime - DateTime.UtcNow; } } - + private async Task Trigger(RunningRepeater rr) { var repeater = rr.Repeater; - + void ChannelMissingError() { - rr.ErrorCount = Int32.MaxValue; - Log.Warning("[Repeater] Channel [{Channelid}] for not found or insufficient permissions. " + - "Repeater will be removed. ", repeater.ChannelId); + rr.ErrorCount = int.MaxValue; + Log.Warning("[Repeater] Channel [{Channelid}] for not found or insufficient permissions. " + + "Repeater will be removed. ", + repeater.ChannelId); } var channel = _client.GetChannel(repeater.ChannelId) as ITextChannel; if (channel is null) - try { channel = await _client.Rest.GetChannelAsync(repeater.ChannelId) as ITextChannel; } catch { } + try { channel = await _client.Rest.GetChannelAsync(repeater.ChannelId) as ITextChannel; } + catch { } if (channel is null) { @@ -219,9 +207,8 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") ChannelMissingError(); return; } - + if (_noRedundant.Contains(repeater.Id)) - { try { var lastMsgInChannel = await channel.GetMessagesAsync(2).Flatten().FirstAsync(); @@ -231,41 +218,36 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") catch (Exception ex) { Log.Warning(ex, - "[Repeater] Error while getting last channel message in {GuildId}/{ChannelId} " + - "Bot probably doesn't have the permission to read message history", + "[Repeater] Error while getting last channel message in {GuildId}/{ChannelId} " + + "Bot probably doesn't have the permission to read message history", guild.Id, channel.Id); } - } if (repeater.LastMessageId is { } lastMessageId) - { try { var oldMsg = await channel.GetMessageAsync(lastMessageId); - if (oldMsg != null) - { - await oldMsg.DeleteAsync(); - } + if (oldMsg != null) await oldMsg.DeleteAsync(); } catch (Exception ex) { - Log.Warning(ex, "[Repeater] Error while deleting previous message in {GuildId}/{ChannelId}", guild.Id, channel.Id); + Log.Warning(ex, + "[Repeater] Error while deleting previous message in {GuildId}/{ChannelId}", + guild.Id, + channel.Id); } - } - - var rep = new ReplacementBuilder() - .WithDefault(guild.CurrentUser, channel, guild, _client) - .Build(); + + var rep = new ReplacementBuilder().WithDefault(guild.CurrentUser, channel, guild, _client).Build(); try { var text = SmartText.CreateFrom(repeater.Message); text = rep.Replace(text); - + var newMsg = await channel.SendAsync(text); _ = newMsg.AddReactionAsync(new Emoji("🔄")); - + if (_noRedundant.Contains(repeater.Id)) { await SetRepeaterLastMessageInternal(repeater.Id, newMsg.Id); @@ -278,17 +260,15 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") { Log.Error(ex, "[Repeater] Error sending repeat message ({ErrorCount})", rr.ErrorCount++); } - } + } private async Task RemoveRepeaterInternal(Repeater r) { _noRedundant.TryRemove(r.Id); await using var uow = _db.GetDbContext(); - await uow - .Repeaters - .DeleteAsync(x => x.Id == r.Id); - + await uow.Repeaters.DeleteAsync(x => x.Id == r.Id); + await uow.SaveChangesAsync(); } @@ -299,7 +279,7 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") var node = _repeaterQueue.FindNode(x => x.Repeater.Id == id); if (node is null) return null; - + _repeaterQueue.Remove(node); return node.Value; } @@ -308,13 +288,9 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") private async Task SetRepeaterLastMessageInternal(int repeaterId, ulong lastMsgId) { await using var uow = _db.GetDbContext(); - await uow.Repeaters - .AsQueryable() - .Where(x => x.Id == repeaterId) - .UpdateAsync(rep => new() - { - LastMessageId = lastMsgId - }); + await uow.Repeaters.AsQueryable() + .Where(x => x.Id == repeaterId) + .UpdateAsync(rep => new() { LastMessageId = lastMsgId }); } public async Task AddRepeaterAsync( @@ -323,10 +299,9 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") TimeSpan interval, string message, bool isNoRedundant, - TimeSpan? startTimeOfDay - ) + TimeSpan? startTimeOfDay) { - var rep = new Repeater() + var rep = new Repeater { ChannelId = channelId, GuildId = guildId, @@ -360,15 +335,14 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") throw new ArgumentOutOfRangeException(nameof(index)); await using var uow = _db.GetDbContext(); - var toRemove = await uow.Repeaters - .AsNoTracking() - .Where(x => x.GuildId == guildId) - .Skip(index) - .FirstOrDefaultAsyncEF(); + var toRemove = await uow.Repeaters.AsNoTracking() + .Where(x => x.GuildId == guildId) + .Skip(index) + .FirstOrDefaultAsyncEF(); if (toRemove is null) return null; - + // first try removing from queue because it can fail // while triggering. Instruct user to try again var removed = RemoveFromQueue(toRemove.Id); @@ -392,25 +366,19 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") public async Task ToggleRedundantAsync(ulong guildId, int index) { await using var uow = _db.GetDbContext(); - var toToggle = await uow - .Repeaters - .AsQueryable() - .Where(x => x.GuildId == guildId) - .Skip(index) - .FirstOrDefaultAsyncEF(); + var toToggle = await uow.Repeaters.AsQueryable() + .Where(x => x.GuildId == guildId) + .Skip(index) + .FirstOrDefaultAsyncEF(); if (toToggle is null) return null; - + var newValue = toToggle.NoRedundant = !toToggle.NoRedundant; if (newValue) - { _noRedundant.Add(toToggle.Id); - } else - { _noRedundant.TryRemove(toToggle.Id); - } await uow.SaveChangesAsync(); return newValue; @@ -418,4 +386,4 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};") public bool IsNoRedundant(int repeaterId) => _noRedundant.Contains(repeaterId); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Services/RunningRepeater.cs b/src/NadekoBot/Modules/Utility/Services/RunningRepeater.cs index 8caa18522..eefc3aff4 100644 --- a/src/NadekoBot/Modules/Utility/Services/RunningRepeater.cs +++ b/src/NadekoBot/Modules/Utility/Services/RunningRepeater.cs @@ -6,13 +6,13 @@ namespace NadekoBot.Modules.Utility.Services; public sealed class RunningRepeater { public DateTime NextTime { get; private set; } - + public Repeater Repeater { get; } public int ErrorCount { get; set; } - + public RunningRepeater(Repeater repeater) { - this.Repeater = repeater; + Repeater = repeater; NextTime = CalculateInitialExecution(); } @@ -38,16 +38,12 @@ public sealed class RunningRepeater // if added timeofday is less than specified timeofday for initial trigger // that means the repeater first ran that same day at that exact specified time if (added.TimeOfDay <= initialTriggerTimeOfDay) - { // in that case, just add the difference to make sure the timeofday is the same initialDateTime = added + (initialTriggerTimeOfDay - added.TimeOfDay); - } else - { // if not, then it ran at that time the following day // in other words; Add one day, and subtract how much time passed since that time of day initialDateTime = added + TimeSpan.FromDays(1) - (added.TimeOfDay - initialTriggerTimeOfDay); - } return CalculateInitialInterval(initialDateTime); } @@ -57,7 +53,7 @@ public sealed class RunningRepeater } /// - /// Calculate when is the proper time to run the repeater again based on initial time repeater ran. + /// Calculate when is the proper time to run the repeater again based on initial time repeater ran. /// /// /// Initial time repeater ran at (or should run at). @@ -65,11 +61,8 @@ public sealed class RunningRepeater { // if the initial time is greater than now, that means the repeater didn't still execute a single time. // just schedule it - if (initialDateTime > DateTime.UtcNow) - { - return initialDateTime; - } - + if (initialDateTime > DateTime.UtcNow) return initialDateTime; + // else calculate based on minutes difference // get the difference @@ -92,8 +85,8 @@ public sealed class RunningRepeater } public override bool Equals(object obj) - => obj is RunningRepeater rr && rr.Repeater.Id == this.Repeater.Id; + => obj is RunningRepeater rr && rr.Repeater.Id == Repeater.Id; public override int GetHashCode() - => this.Repeater.Id; -} + => Repeater.Id; +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Services/StreamRoleService.cs b/src/NadekoBot/Modules/Utility/Services/StreamRoleService.cs index b515b3d45..e1fcd04b4 100644 --- a/src/NadekoBot/Modules/Utility/Services/StreamRoleService.cs +++ b/src/NadekoBot/Modules/Utility/Services/StreamRoleService.cs @@ -1,9 +1,9 @@ #nullable disable -using NadekoBot.Services.Database.Models; -using NadekoBot.Common.TypeReaders; +using NadekoBot.Db; using NadekoBot.Modules.Utility.Common; using NadekoBot.Modules.Utility.Common.Exceptions; -using NadekoBot.Db; +using NadekoBot.Services.Database.Models; +using System.Net; namespace NadekoBot.Modules.Utility.Services; @@ -12,16 +12,15 @@ public class StreamRoleService : INService private readonly DbService _db; private readonly DiscordSocketClient _client; private readonly ConcurrentDictionary guildSettings; - + public StreamRoleService(DiscordSocketClient client, DbService db, Bot bot) { _db = db; _client = client; - guildSettings = bot.AllGuildConfigs - .ToDictionary(x => x.GuildId, x => x.StreamRole) - .Where(x => x.Value is { Enabled: true }) - .ToConcurrent(); + guildSettings = bot.AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.StreamRole) + .Where(x => x.Value is { Enabled: true }) + .ToConcurrent(); _client.GuildMemberUpdated += Client_GuildMemberUpdated; @@ -43,23 +42,26 @@ public class StreamRoleService : INService var _ = Task.Run(async () => { //if user wasn't streaming or didn't have a game status at all - if (guildSettings.TryGetValue(after.Guild.Id, out var setting)) - { - await RescanUser(after, setting); - } + if (guildSettings.TryGetValue(after.Guild.Id, out var setting)) await RescanUser(after, setting); }); return Task.CompletedTask; } + /// - /// Adds or removes a user from a blacklist or a whitelist in the specified guild. + /// Adds or removes a user from a blacklist or a whitelist in the specified guild. /// /// Guild /// Add or rem action /// User's Id /// User's name#discrim /// Whether the operation was successful - public async Task ApplyListAction(StreamRoleListType listType, IGuild guild, AddRemove action, ulong userId, string userName) + public async Task ApplyListAction( + StreamRoleListType listType, + IGuild guild, + AddRemove action, + ulong userId, + string userName) { ArgumentNullException.ThrowIfNull(userName, nameof(userName)); @@ -70,11 +72,7 @@ public class StreamRoleService : INService if (listType == StreamRoleListType.Whitelist) { - var userObj = new StreamRoleWhitelistedUser() - { - UserId = userId, - Username = userName, - }; + var userObj = new StreamRoleWhitelistedUser { UserId = userId, Username = userName }; if (action == AddRemove.Rem) { @@ -86,15 +84,13 @@ public class StreamRoleService : INService } } else + { success = streamRoleSettings.Whitelist.Add(userObj); + } } else { - var userObj = new StreamRoleBlacklistedUser() - { - UserId = userId, - Username = userName, - }; + var userObj = new StreamRoleBlacklistedUser { UserId = userId, Username = userName }; if (action == AddRemove.Rem) { @@ -106,21 +102,21 @@ public class StreamRoleService : INService } } else + { success = streamRoleSettings.Blacklist.Add(userObj); + } } await uow.SaveChangesAsync(); UpdateCache(guild.Id, streamRoleSettings); } - if (success) - { - await RescanUsers(guild); - } + + if (success) await RescanUsers(guild); return success; } /// - /// Sets keyword on a guild and updates the cache. + /// Sets keyword on a guild and updates the cache. /// /// Guild Id /// Keyword to set @@ -143,7 +139,7 @@ public class StreamRoleService : INService } /// - /// Gets the currently set keyword on a guild. + /// Gets the currently set keyword on a guild. /// /// Guild Id /// The keyword set @@ -164,8 +160,8 @@ public class StreamRoleService : INService } /// - /// Sets the role to monitor, and a role to which to add to - /// the user who starts streaming in the monitored role. + /// Sets the role to monitor, and a role to which to add to + /// the user who starts streaming in the monitored role. /// /// Role to monitor /// Role to add to the user @@ -190,14 +186,12 @@ public class StreamRoleService : INService UpdateCache(fromRole.Guild.Id, setting); foreach (var usr in await fromRole.GetMembersAsync()) - { if (usr is { } x) await RescanUser(x, setting, addRole); - } } /// - /// Stops the stream role feature on the specified guild. + /// Stops the stream role feature on the specified guild. /// /// Guild's Id public async Task StopStreamRole(IGuild guild, bool cleanup = false) @@ -219,13 +213,13 @@ public class StreamRoleService : INService { if (user.IsBot) return; - - var g = (StreamingGame)user.Activities - .FirstOrDefault(a => a is StreamingGame && - (string.IsNullOrWhiteSpace(setting.Keyword) - || a.Name.ToUpperInvariant().Contains(setting.Keyword.ToUpperInvariant()) - || setting.Whitelist.Any(x => x.UserId == user.Id))); - + + var g = (StreamingGame)user.Activities.FirstOrDefault(a + => a is StreamingGame + && (string.IsNullOrWhiteSpace(setting.Keyword) + || a.Name.ToUpperInvariant().Contains(setting.Keyword.ToUpperInvariant()) + || setting.Whitelist.Any(x => x.UserId == user.Id))); + if (g is not null && setting.Enabled && setting.Blacklist.All(x => x.UserId != user.Id) @@ -245,11 +239,12 @@ public class StreamRoleService : INService if (!user.RoleIds.Contains(addRole.Id)) { await user.AddRoleAsync(addRole); - Log.Information("Added stream role to user {0} in {1} server", user.ToString(), + Log.Information("Added stream role to user {0} in {1} server", + user.ToString(), user.Guild.ToString()); } } - catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden) + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden) { await StopStreamRole(user.Guild); Log.Warning(ex, "Error adding stream role(s). Forcibly disabling stream role feature"); @@ -264,7 +259,6 @@ public class StreamRoleService : INService { //check if user is in the addrole if (user.RoleIds.Contains(setting.AddRoleId)) - { try { addRole ??= user.Guild.GetRole(setting.AddRoleId); @@ -272,15 +266,16 @@ public class StreamRoleService : INService throw new StreamRoleNotFoundException(); await user.RemoveRoleAsync(addRole); - Log.Information("Removed stream role from the user {0} in {1} server", user.ToString(), user.Guild.ToString()); + Log.Information("Removed stream role from the user {0} in {1} server", + user.ToString(), + user.Guild.ToString()); } - catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden) + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden) { await StopStreamRole(user.Guild); Log.Warning(ex, "Error removing stream role(s). Forcibly disabling stream role feature"); throw new StreamRolePermissionException(); } - } } } @@ -296,14 +291,13 @@ public class StreamRoleService : INService if (setting.Enabled) { var users = await guild.GetUsersAsync(CacheMode.CacheOnly); - foreach (var usr in users.Where(x => x.RoleIds.Contains(setting.FromRoleId) || x.RoleIds.Contains(addRole.Id))) - { + foreach (var usr in users.Where(x + => x.RoleIds.Contains(setting.FromRoleId) || x.RoleIds.Contains(addRole.Id))) if (usr is { } x) await RescanUser(x, setting, addRole); - } } } private void UpdateCache(ulong guildId, StreamRoleSettings setting) => guildSettings.AddOrUpdate(guildId, key => setting, (key, old) => setting); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Services/VerboseErrorsService.cs b/src/NadekoBot/Modules/Utility/Services/VerboseErrorsService.cs index e2193828e..5f986e1e2 100644 --- a/src/NadekoBot/Modules/Utility/Services/VerboseErrorsService.cs +++ b/src/NadekoBot/Modules/Utility/Services/VerboseErrorsService.cs @@ -1,6 +1,6 @@ #nullable disable -using NadekoBot.Modules.Help.Services; using NadekoBot.Db; +using NadekoBot.Modules.Help.Services; namespace NadekoBot.Modules.Utility.Services; @@ -11,7 +11,11 @@ public class VerboseErrorsService : INService private readonly CommandHandler _ch; private readonly HelpService _hs; - public VerboseErrorsService(Bot bot, DbService db, CommandHandler ch, HelpService hs) + public VerboseErrorsService( + Bot bot, + DbService db, + CommandHandler ch, + HelpService hs) { _db = db; _ch = ch; @@ -19,10 +23,7 @@ public class VerboseErrorsService : INService _ch.CommandErrored += LogVerboseError; - guildsEnabled = new(bot - .AllGuildConfigs - .Where(x => x.VerboseErrors) - .Select(x => x.GuildId)); + guildsEnabled = new(bot.AllGuildConfigs.Where(x => x.VerboseErrors).Select(x => x.GuildId)); } private async Task LogVerboseError(CommandInfo cmd, ITextChannel channel, string reason) @@ -33,9 +34,9 @@ public class VerboseErrorsService : INService try { var embed = _hs.GetCommandHelp(cmd, channel.Guild) - .WithTitle("Command Error") - .WithDescription(reason) - .WithErrorColor(); + .WithTitle("Command Error") + .WithDescription(reason) + .WithErrorColor(); await channel.EmbedAsync(embed); } @@ -45,13 +46,14 @@ public class VerboseErrorsService : INService } } - public bool ToggleVerboseErrors(ulong guildId, bool? enabled=null) + public bool ToggleVerboseErrors(ulong guildId, bool? enabled = null) { using (var uow = _db.GetDbContext()) { var gc = uow.GuildConfigsForId(guildId, set => set); - if (enabled==null) enabled = gc.VerboseErrors = !gc.VerboseErrors; // Old behaviour, now behind a condition + if (enabled == null) + enabled = gc.VerboseErrors = !gc.VerboseErrors; // Old behaviour, now behind a condition else gc.VerboseErrors = (bool)enabled; // New behaviour, just set it. uow.SaveChanges(); @@ -62,6 +64,6 @@ public class VerboseErrorsService : INService else guildsEnabled.TryRemove(guildId); - return (bool)enabled; + return (bool)enabled; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/StreamRoleCommands.cs b/src/NadekoBot/Modules/Utility/StreamRoleCommands.cs index 2b4642376..ae2b7b980 100644 --- a/src/NadekoBot/Modules/Utility/StreamRoleCommands.cs +++ b/src/NadekoBot/Modules/Utility/StreamRoleCommands.cs @@ -1,7 +1,6 @@ #nullable disable -using NadekoBot.Modules.Utility.Services; -using NadekoBot.Common.TypeReaders; using NadekoBot.Modules.Utility.Common; +using NadekoBot.Modules.Utility.Services; namespace NadekoBot.Modules.Utility; @@ -9,79 +8,91 @@ public partial class Utility { public class StreamRoleCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [BotPerm(GuildPerm.ManageRoles)] [UserPerm(GuildPerm.ManageRoles)] [RequireContext(ContextType.Guild)] public async Task StreamRole(IRole fromRole, IRole addRole) { - await this._service.SetStreamRole(fromRole, addRole); + await _service.SetStreamRole(fromRole, addRole); - await ReplyConfirmLocalizedAsync(strs.stream_role_enabled(Format.Bold(fromRole.ToString()), Format.Bold(addRole.ToString()))); + await ReplyConfirmLocalizedAsync(strs.stream_role_enabled(Format.Bold(fromRole.ToString()), + Format.Bold(addRole.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [BotPerm(GuildPerm.ManageRoles)] [UserPerm(GuildPerm.ManageRoles)] [RequireContext(ContextType.Guild)] public async Task StreamRole() { - await this._service.StopStreamRole(ctx.Guild); + await _service.StopStreamRole(ctx.Guild); await ReplyConfirmLocalizedAsync(strs.stream_role_disabled); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [BotPerm(GuildPerm.ManageRoles)] [UserPerm(GuildPerm.ManageRoles)] [RequireContext(ContextType.Guild)] - public async Task StreamRoleKeyword([Leftover]string keyword = null) + public async Task StreamRoleKeyword([Leftover] string keyword = null) { - var kw = await this._service.SetKeyword(ctx.Guild, keyword); - - if(string.IsNullOrWhiteSpace(keyword)) + var kw = await _service.SetKeyword(ctx.Guild, keyword); + + if (string.IsNullOrWhiteSpace(keyword)) await ReplyConfirmLocalizedAsync(strs.stream_role_kw_reset); else await ReplyConfirmLocalizedAsync(strs.stream_role_kw_set(Format.Bold(kw))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [BotPerm(GuildPerm.ManageRoles)] [UserPerm(GuildPerm.ManageRoles)] [RequireContext(ContextType.Guild)] public async Task StreamRoleBlacklist(AddRemove action, [Leftover] IGuildUser user) { - var success = await this._service.ApplyListAction(StreamRoleListType.Blacklist, ctx.Guild, action, user.Id, user.ToString()); + var success = await _service.ApplyListAction(StreamRoleListType.Blacklist, + ctx.Guild, + action, + user.Id, + user.ToString()); - if(action == AddRemove.Add) - if(success) + if (action == AddRemove.Add) + if (success) await ReplyConfirmLocalizedAsync(strs.stream_role_bl_add(Format.Bold(user.ToString()))); else await ReplyConfirmLocalizedAsync(strs.stream_role_bl_add_fail(Format.Bold(user.ToString()))); - else - if (success) + else if (success) await ReplyConfirmLocalizedAsync(strs.stream_role_bl_rem(Format.Bold(user.ToString()))); else await ReplyErrorLocalizedAsync(strs.stream_role_bl_rem_fail(Format.Bold(user.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [BotPerm(GuildPerm.ManageRoles)] [UserPerm(GuildPerm.ManageRoles)] [RequireContext(ContextType.Guild)] public async Task StreamRoleWhitelist(AddRemove action, [Leftover] IGuildUser user) { - var success = await this._service.ApplyListAction(StreamRoleListType.Whitelist, ctx.Guild, action, user.Id, user.ToString()); + var success = await _service.ApplyListAction(StreamRoleListType.Whitelist, + ctx.Guild, + action, + user.Id, + user.ToString()); if (action == AddRemove.Add) - if(success) + if (success) await ReplyConfirmLocalizedAsync(strs.stream_role_wl_add(Format.Bold(user.ToString()))); else await ReplyConfirmLocalizedAsync(strs.stream_role_wl_add_fail(Format.Bold(user.ToString()))); - else - if (success) + else if (success) await ReplyConfirmLocalizedAsync(strs.stream_role_wl_rem(Format.Bold(user.ToString()))); else await ReplyErrorLocalizedAsync(strs.stream_role_wl_rem_fail(Format.Bold(user.ToString()))); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/UnitConversionCommands.cs b/src/NadekoBot/Modules/Utility/UnitConversionCommands.cs index eb453c05c..7c5b16533 100644 --- a/src/NadekoBot/Modules/Utility/UnitConversionCommands.cs +++ b/src/NadekoBot/Modules/Utility/UnitConversionCommands.cs @@ -8,43 +8,49 @@ public partial class Utility [Group] public class UnitConverterCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ConvertList() { var units = _service.Units; - var embed = _eb.Create() - .WithTitle(GetText(strs.convertlist)) - .WithOkColor(); + var embed = _eb.Create().WithTitle(GetText(strs.convertlist)).WithOkColor(); foreach (var g in units.GroupBy(x => x.UnitType)) - { embed.AddField(g.Key.ToTitleCase(), - String.Join(", ", g.Select(x => x.Triggers.FirstOrDefault()).OrderBy(x => x))); - } - + string.Join(", ", g.Select(x => x.Triggers.FirstOrDefault()).OrderBy(x => x))); + await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(0)] public async Task Convert(string origin, string target, decimal value) { - var originUnit = _service.Units.FirstOrDefault(x => x.Triggers.Select(y => y.ToUpperInvariant()).Contains(origin.ToUpperInvariant())); - var targetUnit = _service.Units.FirstOrDefault(x => x.Triggers.Select(y => y.ToUpperInvariant()).Contains(target.ToUpperInvariant())); + var originUnit = _service.Units.FirstOrDefault(x + => x.Triggers.Select(y => y.ToUpperInvariant()).Contains(origin.ToUpperInvariant())); + var targetUnit = _service.Units.FirstOrDefault(x + => x.Triggers.Select(y => y.ToUpperInvariant()).Contains(target.ToUpperInvariant())); if (originUnit is null || targetUnit is null) { await ReplyErrorLocalizedAsync(strs.convert_not_found(Format.Bold(origin), Format.Bold(target))); return; } + if (originUnit.UnitType != targetUnit.UnitType) { - await ReplyErrorLocalizedAsync(strs.convert_type_error(Format.Bold(originUnit.Triggers.First()), Format.Bold(targetUnit.Triggers.First()))); + await ReplyErrorLocalizedAsync(strs.convert_type_error(Format.Bold(originUnit.Triggers.First()), + Format.Bold(targetUnit.Triggers.First()))); return; } + decimal res; - if (originUnit.Triggers == targetUnit.Triggers) res = value; + if (originUnit.Triggers == targetUnit.Triggers) + { + res = value; + } else if (originUnit.UnitType == "temperature") { //don't really care too much about efficiency, so just convert to Kelvin, then to target @@ -60,6 +66,7 @@ public partial class Utility res = value; break; } + //from Kelvin to target switch (targetUnit.Triggers.First().ToUpperInvariant()) { @@ -74,15 +81,17 @@ public partial class Utility else { if (originUnit.UnitType == "currency") - { res = value * targetUnit.Modifier / originUnit.Modifier; - } else res = value * originUnit.Modifier / targetUnit.Modifier; } + res = Math.Round(res, 4); - await SendConfirmAsync(GetText(strs.convert(value, originUnit.Triggers.Last(), res, targetUnit.Triggers.Last()))); + await SendConfirmAsync(GetText(strs.convert(value, + originUnit.Triggers.Last(), + res, + targetUnit.Triggers.Last()))); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index 6952235f9..9f6b1eb1e 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -10,6 +10,22 @@ namespace NadekoBot.Modules.Utility; public partial class Utility : NadekoModule { + public enum CreateInviteType + { + Any, + New + } + + public enum MeOrBot { Me, Bot } + + private static readonly JsonSerializerOptions _showEmbedSerializerOptions = new() + { + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = LowerCaseNamingPolicy.Default + }; + + private static SemaphoreSlim sem = new(1, 1); private readonly DiscordSocketClient _client; private readonly ICoordinator _coord; private readonly IStatsService _stats; @@ -17,8 +33,12 @@ public partial class Utility : NadekoModule private readonly DownloadTracker _tracker; private readonly IHttpClientFactory _httpFactory; - public Utility(DiscordSocketClient client, ICoordinator coord, - IStatsService stats, IBotCredentials creds, DownloadTracker tracker, + public Utility( + DiscordSocketClient client, + ICoordinator coord, + IStatsService stats, + IBotCredentials creds, + DownloadTracker tracker, IHttpClientFactory httpFactory) { _client = client; @@ -29,29 +49,32 @@ public partial class Utility : NadekoModule _httpFactory = httpFactory; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] [Priority(1)] public async Task Say(ITextChannel channel, [Leftover] SmartText message) { var rep = new ReplacementBuilder() - .WithDefault(ctx.User, channel, (SocketGuild)ctx.Guild, (DiscordSocketClient)ctx.Client) - .Build(); + .WithDefault(ctx.User, channel, (SocketGuild)ctx.Guild, (DiscordSocketClient)ctx.Client) + .Build(); message = rep.Replace(message); - + await channel.SendAsync(message, !((IGuildUser)ctx.User).GuildPermissions.MentionEveryone); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] [Priority(0)] public Task Say([Leftover] SmartText message) => Say((ITextChannel)ctx.Channel, message); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task WhosPlaying([Leftover] string game) { @@ -64,32 +87,36 @@ public partial class Utility : NadekoModule Log.Warning("Can't cast guild to socket guild."); return; } + var rng = new NadekoRandom(); var arr = await Task.Run(() => socketGuild.Users - .Where(u => u.Activities.FirstOrDefault()?.Name?.ToUpperInvariant() == game) - .Select(u => u.Username) - .OrderBy(x => rng.Next()) - .Take(60) - .ToArray()); + .Where(u => u.Activities.FirstOrDefault()?.Name?.ToUpperInvariant() + == game) + .Select(u => u.Username) + .OrderBy(x => rng.Next()) + .Take(60) + .ToArray()); var i = 0; if (arr.Length == 0) await ReplyErrorLocalizedAsync(strs.nobody_playing_game); else - { - await SendConfirmAsync("```css\n" + string.Join("\n", arr.GroupBy(item => i++ / 2) - .Select(ig => string.Concat(ig.Select(el => $"• {el,-27}")))) + "\n```"); - } + await SendConfirmAsync("```css\n" + + string.Join("\n", + arr.GroupBy(item => i++ / 2) + .Select(ig => string.Concat(ig.Select(el => $"• {el,-27}")))) + + "\n```"); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(0)] public async Task InRole(int page, [Leftover] IRole role = null) { if (--page < 0) return; - + await ctx.Channel.TriggerTypingAsync(); await _tracker.EnsureUsersDownloadedAsync(ctx.Guild); @@ -98,75 +125,80 @@ public partial class Utility : NadekoModule CacheMode.CacheOnly #endif ); - var roleUsers = users - .Where(u => role is null ? u.RoleIds.Count == 1 : u.RoleIds.Contains(role.Id)) - .Select(u => $"`{u.Id, 18}` {u}") - .ToArray(); - await ctx.SendPaginatedConfirmAsync(page, cur => - { - var pageUsers = roleUsers.Skip(cur * 20) - .Take(20) - .ToList(); + var roleUsers = users.Where(u => role is null ? u.RoleIds.Count == 1 : u.RoleIds.Contains(role.Id)) + .Select(u => $"`{u.Id,18}` {u}") + .ToArray(); - if (pageUsers.Count == 0) - return _eb.Create().WithOkColor().WithDescription(GetText(strs.no_user_on_this_page)); + await ctx.SendPaginatedConfirmAsync(page, + cur => + { + var pageUsers = roleUsers.Skip(cur * 20).Take(20).ToList(); - return _eb.Create().WithOkColor() - .WithTitle(GetText(strs.inrole_list(Format.Bold(role?.Name ?? "No Role"), roleUsers.Length))) - .WithDescription(string.Join("\n", pageUsers)); - }, roleUsers.Length, 20); + if (pageUsers.Count == 0) + return _eb.Create().WithOkColor().WithDescription(GetText(strs.no_user_on_this_page)); + + return _eb.Create() + .WithOkColor() + .WithTitle(GetText(strs.inrole_list(Format.Bold(role?.Name ?? "No Role"), roleUsers.Length))) + .WithDescription(string.Join("\n", pageUsers)); + }, + roleUsers.Length, + 20); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [Priority(1)] public Task InRole([Leftover] IRole role = null) => InRole(1, role); - public enum MeOrBot { Me, Bot } - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task CheckPerms(MeOrBot who = MeOrBot.Me) { var builder = new StringBuilder(); - var user = who == MeOrBot.Me - ? (IGuildUser)ctx.User - : ((SocketGuild)ctx.Guild).CurrentUser; + var user = who == MeOrBot.Me ? (IGuildUser)ctx.User : ((SocketGuild)ctx.Guild).CurrentUser; var perms = user.GetPermissions((ITextChannel)ctx.Channel); foreach (var p in perms.GetType().GetProperties().Where(p => !p.GetGetMethod().GetParameters().Any())) - { builder.AppendLine($"{p.Name} : {p.GetValue(perms, null)}"); - } await SendConfirmAsync(builder.ToString()); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task UserId([Leftover] IGuildUser target = null) { var usr = target ?? ctx.User; - await ReplyConfirmLocalizedAsync(strs.userid("🆔", Format.Bold(usr.ToString()), + await ReplyConfirmLocalizedAsync(strs.userid("🆔", + Format.Bold(usr.ToString()), Format.Code(usr.Id.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task RoleId([Leftover] IRole role) - => await ReplyConfirmLocalizedAsync(strs.roleid("🆔", Format.Bold(role.ToString()), + => await ReplyConfirmLocalizedAsync(strs.roleid("🆔", + Format.Bold(role.ToString()), Format.Code(role.Id.ToString()))); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ChannelId() => await ReplyConfirmLocalizedAsync(strs.channelid("🆔", Format.Code(ctx.Channel.Id.ToString()))); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task ServerId() => await ReplyConfirmLocalizedAsync(strs.serverid("🆔", Format.Code(ctx.Guild.Id.ToString()))); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Roles(IGuildUser target, int page = 1) { @@ -179,41 +211,43 @@ public partial class Utility : NadekoModule if (target != null) { - var roles = target.GetRoles().Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * rolesPerPage).Take(rolesPerPage).ToArray(); + var roles = target.GetRoles() + .Except(new[] { guild.EveryoneRole }) + .OrderBy(r => -r.Position) + .Skip((page - 1) * rolesPerPage) + .Take(rolesPerPage) + .ToArray(); if (!roles.Any()) - { await ReplyErrorLocalizedAsync(strs.no_roles_on_page); - } else - { - await SendConfirmAsync(GetText(strs.roles_page(page, Format.Bold(target.ToString()))), "\n• " + string.Join("\n• ", (IEnumerable)roles).SanitizeMentions(true)); - } } else { - var roles = guild.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * rolesPerPage).Take(rolesPerPage).ToArray(); + var roles = guild.Roles.Except(new[] { guild.EveryoneRole }) + .OrderBy(r => -r.Position) + .Skip((page - 1) * rolesPerPage) + .Take(rolesPerPage) + .ToArray(); if (!roles.Any()) - { await ReplyErrorLocalizedAsync(strs.no_roles_on_page); - } else - { await SendConfirmAsync(GetText(strs.roles_all_page(page)), "\n• " + string.Join("\n• ", (IEnumerable)roles).SanitizeMentions(true)); - } } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] - public Task Roles(int page = 1) => - Roles(null, page); + public Task Roles(int page = 1) + => Roles(null, page); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] - public async Task ChannelTopic([Leftover]ITextChannel channel = null) + public async Task ChannelTopic([Leftover] ITextChannel channel = null) { if (channel is null) channel = (ITextChannel)ctx.Channel; @@ -225,37 +259,44 @@ public partial class Utility : NadekoModule await SendConfirmAsync(GetText(strs.channel_topic), topic); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Stats() { var ownerIds = string.Join("\n", _creds.OwnerIds); if (string.IsNullOrWhiteSpace(ownerIds)) ownerIds = "-"; - await ctx.Channel.EmbedAsync( - _eb.Create().WithOkColor() - .WithAuthor($"NadekoBot v{StatsService.BotVersion}", - "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/avatar.png", - "https://nadekobot.readthedocs.io/en/latest/") - .AddField(GetText(strs.author), _stats.Author, true) - .AddField(GetText(strs.botid), _client.CurrentUser.Id.ToString(), true) - .AddField(GetText(strs.shard), $"#{_client.ShardId} / {_creds.TotalShards}", true) - .AddField(GetText(strs.commands_ran), _stats.CommandsRan.ToString(), true) - .AddField(GetText(strs.messages), $"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)", - true) - .AddField(GetText(strs.memory), FormattableString.Invariant($"{_stats.GetPrivateMemory():F2} MB"), true) - .AddField(GetText(strs.owner_ids), ownerIds, true) - .AddField(GetText(strs.uptime), _stats.GetUptimeString("\n"), true) - .AddField(GetText(strs.presence), - GetText(strs.presence_txt( - _coord.GetGuildCount(), - _stats.TextChannels, - _stats.VoiceChannels)), - true)); + await ctx.Channel.EmbedAsync(_eb.Create() + .WithOkColor() + .WithAuthor($"NadekoBot v{StatsService.BotVersion}", + "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/avatar.png", + "https://nadekobot.readthedocs.io/en/latest/") + .AddField(GetText(strs.author), _stats.Author, true) + .AddField(GetText(strs.botid), _client.CurrentUser.Id.ToString(), true) + .AddField(GetText(strs.shard), + $"#{_client.ShardId} / {_creds.TotalShards}", + true) + .AddField(GetText(strs.commands_ran), _stats.CommandsRan.ToString(), true) + .AddField(GetText(strs.messages), + $"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)", + true) + .AddField(GetText(strs.memory), + FormattableString.Invariant($"{_stats.GetPrivateMemory():F2} MB"), + true) + .AddField(GetText(strs.owner_ids), ownerIds, true) + .AddField(GetText(strs.uptime), _stats.GetUptimeString("\n"), true) + .AddField(GetText(strs.presence), + GetText(strs.presence_txt(_coord.GetGuildCount(), + _stats.TextChannels, + _stats.VoiceChannels)), + true)); } - [NadekoCommand, Aliases] - public async Task Showemojis([Leftover] string _) // need to have the parameter so that the message.tags gets populated + [NadekoCommand] + [Aliases] + public async Task + Showemojis([Leftover] string _) // need to have the parameter so that the message.tags gets populated { var tags = ctx.Message.Tags.Where(t => t.Type == TagType.Emoji).Select(t => (Emote)t.Value); @@ -267,23 +308,26 @@ public partial class Utility : NadekoModule await ctx.Channel.SendMessageAsync(result.TrimTo(2000)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [BotPerm(GuildPerm.ManageEmojisAndStickers)] [UserPerm(GuildPerm.ManageEmojisAndStickers)] [Priority(2)] public Task EmojiAdd(string name, Emote emote) => EmojiAdd(name, emote.Url); - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [BotPerm(GuildPerm.ManageEmojisAndStickers)] [UserPerm(GuildPerm.ManageEmojisAndStickers)] [Priority(1)] public Task EmojiAdd(Emote emote) => EmojiAdd(emote.Name, emote.Url); - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [BotPerm(GuildPerm.ManageEmojisAndStickers)] [UserPerm(GuildPerm.ManageEmojisAndStickers)] @@ -296,7 +340,7 @@ public partial class Utility : NadekoModule if (url is null) return; - + using var http = _httpFactory.CreateClient(); var res = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); if (!res.IsImage() || res.GetImageSize() is null or > 262_144) @@ -318,10 +362,12 @@ public partial class Utility : NadekoModule await ReplyErrorLocalizedAsync(strs.emoji_add_error); return; } + await ConfirmLocalizedAsync(strs.emoji_added(em.ToString())); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [OwnerOnly] public async Task ListServers(int page = 1) { @@ -338,29 +384,21 @@ public partial class Utility : NadekoModule return; } - var embed = _eb.Create() - .WithOkColor(); + var embed = _eb.Create().WithOkColor(); foreach (var guild in guilds) - embed.AddField(guild.Name, - GetText(strs.listservers(guild.Id, guild.MemberCount, guild.OwnerId)), - false); + embed.AddField(guild.Name, GetText(strs.listservers(guild.Id, guild.MemberCount, guild.OwnerId))); await ctx.Channel.EmbedAsync(embed); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public Task ShowEmbed(ulong messageId) => ShowEmbed((ITextChannel)ctx.Channel, messageId); - private static readonly JsonSerializerOptions _showEmbedSerializerOptions = new() - { - WriteIndented = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNamingPolicy = LowerCaseNamingPolicy.Default - }; - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task ShowEmbed(ITextChannel ch, ulong messageId) { @@ -390,7 +428,8 @@ public partial class Utility : NadekoModule await SendConfirmAsync(Format.Sanitize(json).Replace("](", "]\\(")); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] public async Task SaveChat(int cnt) @@ -400,36 +439,38 @@ public partial class Utility : NadekoModule var title = $"Chatlog-{ctx.Guild.Name}/#{ctx.Channel.Name}-{DateTime.Now}.txt"; var grouping = msgs.GroupBy(x => $"{x.CreatedAt.Date:dd.MM.yyyy}") - .Select(g => new - { - date = g.Key, - messages = g.OrderBy(x => x.CreatedAt).Select(s => - { - var msg = $"【{s.Timestamp:HH:mm:ss}】{s.Author}:"; - if (string.IsNullOrWhiteSpace(s.ToString())) - { - if (s.Attachments.Any()) - { - msg += "FILES_UPLOADED: " + string.Join("\n", s.Attachments.Select(x => x.Url)); - } - else if (s.Embeds.Any()) - { - msg += "EMBEDS: " + string.Join("\n--------\n", s.Embeds.Select(x => $"Description: {x.Description}")); - } - } - else - { - msg += s.ToString(); - } - return msg; - }) - }); - await using var stream = await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream(); - await ctx.User.SendFileAsync(stream, title, title, false); - } - private static SemaphoreSlim sem = new(1, 1); + .Select(g => new + { + date = g.Key, + messages = g.OrderBy(x => x.CreatedAt) + .Select(s => + { + var msg = $"【{s.Timestamp:HH:mm:ss}】{s.Author}:"; + if (string.IsNullOrWhiteSpace(s.ToString())) + { + if (s.Attachments.Any()) + msg += "FILES_UPLOADED: " + + string.Join("\n", s.Attachments.Select(x => x.Url)); + else if (s.Embeds.Any()) + msg += "EMBEDS: " + + string.Join("\n--------\n", + s.Embeds.Select(x + => $"Description: {x.Description}")); + } + else + { + msg += s.ToString(); + } - [NadekoCommand, Aliases] + return msg; + }) + }); + await using var stream = await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream(); + await ctx.User.SendFileAsync(stream, title, title); + } + + [NadekoCommand] + [Aliases] #if GLOBAL_NADEKO [Ratelimit(30)] #endif @@ -451,14 +492,7 @@ public partial class Utility : NadekoModule } } - public enum CreateInviteType - { - Any, - New - } - - - + // [NadekoCommand, Usage, Description, Aliases] // [RequireContext(ContextType.Guild)] // public async Task CreateMyInvite(CreateInviteType type = CreateInviteType.Any) @@ -497,4 +531,4 @@ public partial class Utility : NadekoModule // return embed; // }, inviteUsers.Count, 9); // } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/VerboseErrorCommands.cs b/src/NadekoBot/Modules/Utility/VerboseErrorCommands.cs index 138f773c5..ea64a9660 100644 --- a/src/NadekoBot/Modules/Utility/VerboseErrorCommands.cs +++ b/src/NadekoBot/Modules/Utility/VerboseErrorCommands.cs @@ -8,7 +8,8 @@ public partial class Utility [Group] public class VerboseErrorCommands : NadekoSubmodule { - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] public async Task VerboseError(bool? newstate = null) @@ -21,4 +22,4 @@ public partial class Utility await ReplyConfirmLocalizedAsync(strs.verbose_errors_disabled); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Club.cs b/src/NadekoBot/Modules/Xp/Club.cs index 47f95f428..0e8c7668a 100644 --- a/src/NadekoBot/Modules/Xp/Club.cs +++ b/src/NadekoBot/Modules/Xp/Club.cs @@ -13,20 +13,21 @@ public partial class Xp public Club(XpService xps) => _xps = xps; - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ClubTransfer([Leftover] IUser newOwner) { var club = _service.TransferClub(ctx.User, newOwner); if (club != null) - await ReplyConfirmLocalizedAsync(strs.club_transfered( - Format.Bold(club.Name), + await ReplyConfirmLocalizedAsync(strs.club_transfered(Format.Bold(club.Name), Format.Bold(newOwner.ToString()))); else await ReplyErrorLocalizedAsync(strs.club_transfer_failed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ClubAdmin([Leftover] IUser toAdmin) { bool admin; @@ -46,7 +47,8 @@ public partial class Xp await ReplyConfirmLocalizedAsync(strs.club_admin_remove(Format.Bold(toAdmin.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ClubCreate([Leftover] string clubName) { if (string.IsNullOrWhiteSpace(clubName) || clubName.Length > 20) @@ -64,7 +66,8 @@ public partial class Xp await ReplyConfirmLocalizedAsync(strs.club_created(Format.Bold(club.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ClubIcon([Leftover] string url = null) { if ((!Uri.IsWellFormedUriString(url, UriKind.Absolute) && url != null) @@ -77,7 +80,8 @@ public partial class Xp await ReplyConfirmLocalizedAsync(strs.club_icon_set); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(1)] public async Task ClubInformation(IUser user = null) { @@ -92,7 +96,8 @@ public partial class Xp await ClubInformation(club.ToString()); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(0)] public async Task ClubInformation([Leftover] string clubName = null) { @@ -109,50 +114,53 @@ public partial class Xp } var lvl = new LevelStats(club.Xp); - var users = club.Users - .OrderByDescending(x => - { - var l = new LevelStats(x.TotalXp).Level; - if (club.OwnerId == x.Id) - return int.MaxValue; - else if (x.IsClubAdmin) - return (int.MaxValue / 2) + l; - else - return l; - }); - - await ctx.SendPaginatedConfirmAsync(0, page => + var users = club.Users.OrderByDescending(x => { - var embed = _eb.Create() - .WithOkColor() - .WithTitle($"{club.ToString()}") - .WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)"))) - .AddField(GetText(strs.desc), string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description, - false) - .AddField(GetText(strs.owner), club.Owner.ToString(), true) - .AddField(GetText(strs.level_req), club.MinimumLevelReq.ToString(), true) - .AddField(GetText(strs.members), string.Join("\n", users - .Skip(page * 10) - .Take(10) - .Select(x => - { - var l = new LevelStats(x.TotalXp); - var lvlStr = Format.Bold($" ⟪{l.Level}⟫"); - if (club.OwnerId == x.Id) - return x.ToString() + "🌟" + lvlStr; - else if (x.IsClubAdmin) - return x.ToString() + "⭐" + lvlStr; - return x.ToString() + lvlStr; - })), false); + var l = new LevelStats(x.TotalXp).Level; + if (club.OwnerId == x.Id) + return int.MaxValue; + if (x.IsClubAdmin) + return (int.MaxValue / 2) + l; + return l; + }); - if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute)) - return embed.WithThumbnailUrl(club.ImageUrl); + await ctx.SendPaginatedConfirmAsync(0, + page => + { + var embed = _eb.Create() + .WithOkColor() + .WithTitle($"{club}") + .WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)"))) + .AddField(GetText(strs.desc), + string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description) + .AddField(GetText(strs.owner), club.Owner.ToString(), true) + .AddField(GetText(strs.level_req), club.MinimumLevelReq.ToString(), true) + .AddField(GetText(strs.members), + string.Join("\n", + users.Skip(page * 10) + .Take(10) + .Select(x => + { + var l = new LevelStats(x.TotalXp); + var lvlStr = Format.Bold($" ⟪{l.Level}⟫"); + if (club.OwnerId == x.Id) + return x + "🌟" + lvlStr; + if (x.IsClubAdmin) + return x + "⭐" + lvlStr; + return x + lvlStr; + }))); - return embed; - }, club.Users.Count, 10); + if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute)) + return embed.WithThumbnailUrl(club.ImageUrl); + + return embed; + }, + club.Users.Count, + 10); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public Task ClubBans(int page = 1) { if (--page < 0) @@ -162,28 +170,25 @@ public partial class Xp if (club is null) return ReplyErrorLocalizedAsync(strs.club_not_exists_owner); - var bans = club - .Bans - .Select(x => x.User) - .ToArray(); + var bans = club.Bans.Select(x => x.User).ToArray(); return ctx.SendPaginatedConfirmAsync(page, curPage => { - var toShow = string.Join("\n", bans - .Skip(page * 10) - .Take(10) - .Select(x => x.ToString())); + var toShow = string.Join("\n", bans.Skip(page * 10).Take(10).Select(x => x.ToString())); return _eb.Create() - .WithTitle(GetText(strs.club_bans_for(club.ToString()))) - .WithDescription(toShow) - .WithOkColor(); - }, bans.Length, 10); + .WithTitle(GetText(strs.club_bans_for(club.ToString()))) + .WithDescription(toShow) + .WithOkColor(); + }, + bans.Length, + 10); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public Task ClubApps(int page = 1) { if (--page < 0) @@ -193,27 +198,24 @@ public partial class Xp if (club is null) return ReplyErrorLocalizedAsync(strs.club_not_exists_owner); - var apps = club - .Applicants - .Select(x => x.User) - .ToArray(); + var apps = club.Applicants.Select(x => x.User).ToArray(); return ctx.SendPaginatedConfirmAsync(page, curPage => { - var toShow = string.Join("\n", apps - .Skip(page * 10) - .Take(10) - .Select(x => x.ToString())); + var toShow = string.Join("\n", apps.Skip(page * 10).Take(10).Select(x => x.ToString())); return _eb.Create() - .WithTitle(GetText(strs.club_apps_for(club.ToString()))) - .WithDescription(toShow) - .WithOkColor(); - }, apps.Length, 10); + .WithTitle(GetText(strs.club_apps_for(club.ToString()))) + .WithDescription(toShow) + .WithOkColor(); + }, + apps.Length, + 10); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ClubApply([Leftover] string clubName) { if (string.IsNullOrWhiteSpace(clubName)) @@ -226,33 +228,30 @@ public partial class Xp } if (_service.ApplyToClub(ctx.User, club)) - { await ReplyConfirmLocalizedAsync(strs.club_applied(Format.Bold(club.ToString()))); - } else - { await ReplyErrorLocalizedAsync(strs.club_apply_error); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(1)] public Task ClubAccept(IUser user) => ClubAccept(user.ToString()); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(0)] public async Task ClubAccept([Leftover] string userName) { if (_service.AcceptApplication(ctx.User.Id, userName, out var discordUser)) - { await ReplyConfirmLocalizedAsync(strs.club_accepted(Format.Bold(discordUser.ToString()))); - } else await ReplyErrorLocalizedAsync(strs.club_accept_error); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task Clubleave() { if (_service.LeaveClub(ctx.User)) @@ -261,94 +260,89 @@ public partial class Xp await ReplyErrorLocalizedAsync(strs.club_not_in_club); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(1)] public Task ClubKick([Leftover] IUser user) => ClubKick(user.ToString()); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(0)] public Task ClubKick([Leftover] string userName) { if (_service.Kick(ctx.User.Id, userName, out var club)) return ReplyConfirmLocalizedAsync(strs.club_user_kick(Format.Bold(userName), Format.Bold(club.ToString()))); - else - return ReplyErrorLocalizedAsync(strs.club_user_kick_fail); + return ReplyErrorLocalizedAsync(strs.club_user_kick_fail); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(1)] public Task ClubBan([Leftover] IUser user) => ClubBan(user.ToString()); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(0)] public Task ClubBan([Leftover] string userName) { if (_service.Ban(ctx.User.Id, userName, out var club)) return ReplyConfirmLocalizedAsync(strs.club_user_banned(Format.Bold(userName), Format.Bold(club.ToString()))); - else - return ReplyErrorLocalizedAsync(strs.club_user_ban_fail); + return ReplyErrorLocalizedAsync(strs.club_user_ban_fail); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(1)] public Task ClubUnBan([Leftover] IUser user) => ClubUnBan(user.ToString()); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [Priority(0)] public Task ClubUnBan([Leftover] string userName) { if (_service.UnBan(ctx.User.Id, userName, out var club)) return ReplyConfirmLocalizedAsync(strs.club_user_unbanned(Format.Bold(userName), Format.Bold(club.ToString()))); - else - return ReplyErrorLocalizedAsync(strs.club_user_unban_fail); + return ReplyErrorLocalizedAsync(strs.club_user_unban_fail); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ClubLevelReq(int level) { if (_service.ChangeClubLevelReq(ctx.User.Id, level)) - { await ReplyConfirmLocalizedAsync(strs.club_level_req_changed(Format.Bold(level.ToString()))); - } else - { await ReplyErrorLocalizedAsync(strs.club_level_req_change_error); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ClubDescription([Leftover] string desc = null) { if (_service.ChangeClubDescription(ctx.User.Id, desc)) - { await ReplyConfirmLocalizedAsync(strs.club_desc_updated(Format.Bold(desc ?? "-"))); - } else - { await ReplyErrorLocalizedAsync(strs.club_desc_update_failed); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public async Task ClubDisband() { if (_service.Disband(ctx.User.Id, out var club)) - { await ReplyConfirmLocalizedAsync(strs.club_disbanded(Format.Bold(club.ToString()))); - } else - { await ReplyErrorLocalizedAsync(strs.club_disband_error); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] public Task ClubLeaderboard(int page = 1) { if (--page < 0) @@ -356,17 +350,12 @@ public partial class Xp var clubs = _service.GetClubLeaderboardPage(page); - var embed = _eb.Create() - .WithTitle(GetText(strs.club_leaderboard(page + 1))) - .WithOkColor(); + var embed = _eb.Create().WithTitle(GetText(strs.club_leaderboard(page + 1))).WithOkColor(); var i = page * 9; - foreach (var club in clubs) - { - embed.AddField($"#{++i} " + club.ToString(), club.Xp.ToString() + " xp", false); - } + foreach (var club in clubs) embed.AddField($"#{++i} " + club, club.Xp + " xp"); return ctx.Channel.EmbedAsync(embed); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Common/FullUserStats.cs b/src/NadekoBot/Modules/Xp/Common/FullUserStats.cs index 0c12bd9be..368a305da 100644 --- a/src/NadekoBot/Modules/Xp/Common/FullUserStats.cs +++ b/src/NadekoBot/Modules/Xp/Common/FullUserStats.cs @@ -1,6 +1,6 @@ #nullable disable -using NadekoBot.Services.Database.Models; using NadekoBot.Db.Models; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Xp; @@ -13,8 +13,13 @@ public class FullUserStats public int GlobalRanking { get; } public int GuildRanking { get; } - public FullUserStats(DiscordUser usr, UserXpStats fullGuildStats, LevelStats global, - LevelStats guild, int globalRanking, int guildRanking) + public FullUserStats( + DiscordUser usr, + UserXpStats fullGuildStats, + LevelStats global, + LevelStats guild, + int globalRanking, + int guildRanking) { User = usr; Global = global; @@ -23,4 +28,4 @@ public class FullUserStats GuildRanking = guildRanking; FullGuildStats = fullGuildStats; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Common/LevelStats.cs b/src/NadekoBot/Modules/Xp/Common/LevelStats.cs index ade55844a..37a263f02 100644 --- a/src/NadekoBot/Modules/Xp/Common/LevelStats.cs +++ b/src/NadekoBot/Modules/Xp/Common/LevelStats.cs @@ -37,4 +37,4 @@ public class LevelStats LevelXp = xp - totalXp; RequiredXp = required; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Common/XpConfig.cs b/src/NadekoBot/Modules/Xp/Common/XpConfig.cs index ae41c37d6..37cbb01f4 100644 --- a/src/NadekoBot/Modules/Xp/Common/XpConfig.cs +++ b/src/NadekoBot/Modules/Xp/Common/XpConfig.cs @@ -7,7 +7,7 @@ namespace NadekoBot.Modules.Xp; [Cloneable] public sealed partial class XpConfig : ICloneable { - [Comment(@"DO NOT CHANGE")] + [Comment(@"DO NOT CHANGE")] public int Version { get; set; } = 2; [Comment(@"How much XP will the users receive per message")] @@ -24,4 +24,4 @@ public sealed partial class XpConfig : ICloneable [Comment(@"The maximum amount of minutes the bot will keep track of a user in a voice channel")] public int VoiceMaxMinutes { get; set; } = 720; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Common/XpTemplate.cs b/src/NadekoBot/Modules/Xp/Common/XpTemplate.cs index 8c2847498..6487a897b 100644 --- a/src/NadekoBot/Modules/Xp/Common/XpTemplate.cs +++ b/src/NadekoBot/Modules/Xp/Common/XpTemplate.cs @@ -1,107 +1,30 @@ #nullable disable using Newtonsoft.Json; using SixLabors.ImageSharp.PixelFormats; +using Color = SixLabors.ImageSharp.Color; namespace NadekoBot.Modules.Xp; public class XpTemplate { [JsonProperty("output_size")] - public XpTemplatePos OutputSize { get; set; } = new() - { - X = 450, - Y = 220, - }; + public XpTemplatePos OutputSize { get; set; } = new() { X = 450, Y = 220 }; + public XpTemplateUser User { get; set; } = new() { - Name = new() - { - FontSize = 50, - Show = true, - Pos = new() + Name = new() { FontSize = 50, Show = true, Pos = new() { X = 130, Y = 17 } }, + Icon = new() { Show = true, Pos = new() { X = 32, Y = 10 }, Size = new() { X = 69, Y = 70 } }, + GuildLevel = new() { Show = true, FontSize = 45, Pos = new() { X = 47, Y = 297 } }, + GlobalLevel = new() { Show = true, FontSize = 45, Pos = new() { X = 47, Y = 149 } }, + GuildRank = new() { Show = true, FontSize = 30, Pos = new() { X = 148, Y = 326 } }, + GlobalRank = new() { Show = true, FontSize = 30, Pos = new() { X = 148, Y = 179 } }, + TimeOnLevel = + new() { - X = 130, - Y = 17, - } - }, - Icon = new() - { - Show = true, - Pos = new() - { - X = 32, - Y = 10, + Format = "{0}d{1}h{2}m", + Global = new() { FontSize = 20, Show = true, Pos = new() { X = 50, Y = 204 } }, + Guild = new() { FontSize = 20, Show = true, Pos = new() { X = 50, Y = 351 } } }, - Size = new() - { - X = 69, - Y = 70, - } - }, - GuildLevel = new() - { - Show = true, - FontSize = 45, - Pos = new() - { - X = 47, - Y = 297, - } - }, - GlobalLevel = new() - { - Show = true, - FontSize = 45, - Pos = new() - { - X = 47, - Y = 149, - } - }, - GuildRank = new() - { - Show = true, - FontSize = 30, - Pos = new() - { - X = 148, - Y = 326, - } - }, - GlobalRank = new() - { - Show = true, - FontSize = 30, - Pos = new() - { - X = 148, - Y = 179, - } - }, - TimeOnLevel = new() - { - Format = "{0}d{1}h{2}m", - Global = new() - { - FontSize = 20, - Show = true, - Pos = new() - { - X = 50, - Y = 204 - } - }, - Guild = new() - { - FontSize = 20, - Show = true, - Pos = new() - { - X = 50, - Y = 351 - } - } - }, Xp = new() { Bar = new() @@ -112,92 +35,28 @@ public class XpTemplate Direction = XpTemplateDirection.Right, Length = 450, Color = new(0, 0, 0, 0.4f), - PointA = new() - { - X = 321, - Y = 104 - }, - PointB = new() - { - X = 286, - Y = 235 - } + PointA = new() { X = 321, Y = 104 }, + PointB = new() { X = 286, Y = 235 } }, Guild = new() { Direction = XpTemplateDirection.Right, Length = 450, Color = new(0, 0, 0, 0.4f), - PointA = new() - { - X = 282, - Y = 248 - }, - PointB = new() - { - X = 247, - Y = 379 - } + PointA = new() { X = 282, Y = 248 }, + PointB = new() { X = 247, Y = 379 } } }, - Global = new() - { - Show = true, - FontSize = 50, - Pos = new() - { - X = 430, - Y = 142 - } - }, - Guild = new() - { - Show = true, - FontSize = 50, - Pos = new() - { - X = 400, - Y = 282 - } - }, - Awarded = new() - { - Show = true, - FontSize = 25, - Pos = new() - { - X = 445, - Y = 347 - } - } + Global = new() { Show = true, FontSize = 50, Pos = new() { X = 430, Y = 142 } }, + Guild = new() { Show = true, FontSize = 50, Pos = new() { X = 400, Y = 282 } }, + Awarded = new() { Show = true, FontSize = 25, Pos = new() { X = 445, Y = 347 } } } }; + public XpTemplateClub Club { get; set; } = new() { - Icon = new() - { - Show = true, - Pos = new() - { - X = 722, - Y = 25, - }, - Size = new() - { - X = 45, - Y = 45, - } - }, - Name = new() - { - FontSize = 35, - Pos = new() - { - X = 650, - Y = 49 - }, - Show = true, - } + Icon = new() { Show = true, Pos = new() { X = 722, Y = 25 }, Size = new() { X = 45, Y = 45 } }, + Name = new() { FontSize = 35, Pos = new() { X = 650, Y = 49 }, Show = true } }; } @@ -243,6 +102,7 @@ public class XpTemplateText { [JsonConverter(typeof(XpRgba32Converter))] public Rgba32 Color { get; set; } = SixLabors.ImageSharp.Color.White; + public bool Show { get; set; } public int FontSize { get; set; } public XpTemplatePos Pos { get; set; } @@ -267,6 +127,7 @@ public class XpBar { [JsonConverter(typeof(XpRgba32Converter))] public Rgba32 Color { get; set; } + public XpTemplatePos PointA { get; set; } public XpTemplatePos PointB { get; set; } public int Length { get; set; } @@ -283,9 +144,14 @@ public enum XpTemplateDirection public class XpRgba32Converter : JsonConverter { - public override Rgba32 ReadJson(JsonReader reader, Type objectType, Rgba32 existingValue, bool hasExistingValue, JsonSerializer serializer) - => SixLabors.ImageSharp.Color.ParseHex(reader.Value?.ToString()); + public override Rgba32 ReadJson( + JsonReader reader, + Type objectType, + Rgba32 existingValue, + bool hasExistingValue, + JsonSerializer serializer) + => Color.ParseHex(reader.Value?.ToString()); public override void WriteJson(JsonWriter writer, Rgba32 value, JsonSerializer serializer) => writer.WriteValue(value.ToHex().ToLowerInvariant()); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Extensions/Extensions.cs b/src/NadekoBot/Modules/Xp/Extensions/Extensions.cs index 324d9af32..370b4ac35 100644 --- a/src/NadekoBot/Modules/Xp/Extensions/Extensions.cs +++ b/src/NadekoBot/Modules/Xp/Extensions/Extensions.cs @@ -9,7 +9,7 @@ public static class Extensions public static (int Level, int LevelXp, int LevelRequiredXp) GetLevelData(this UserXpStats stats) { var baseXp = XpService.XP_REQUIRED_LVL_1; - + var required = baseXp; var totalXp = 0; var lvl = 1; @@ -26,4 +26,4 @@ public static class Extensions return (lvl - 1, stats.Xp - totalXp, required); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/ResetCommands.cs b/src/NadekoBot/Modules/Xp/ResetCommands.cs index c0716caa1..45b818625 100644 --- a/src/NadekoBot/Modules/Xp/ResetCommands.cs +++ b/src/NadekoBot/Modules/Xp/ResetCommands.cs @@ -7,21 +7,20 @@ public partial class Xp { public class ResetCommands : NadekoSubmodule { - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public Task XpReset(IGuildUser user) => XpReset(user.Id); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task XpReset(ulong userId) { - var embed = _eb.Create() - .WithTitle(GetText(strs.reset)) - .WithDescription(GetText(strs.reset_user_confirm)); + var embed = _eb.Create().WithTitle(GetText(strs.reset)).WithDescription(GetText(strs.reset_user_confirm)); if (!await PromptUserConfirmAsync(embed)) return; @@ -31,14 +30,13 @@ public partial class Xp await ReplyConfirmLocalizedAsync(strs.reset_user(userId)); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task XpReset() { - var embed = _eb.Create() - .WithTitle(GetText(strs.reset)) - .WithDescription(GetText(strs.reset_server_confirm)); + var embed = _eb.Create().WithTitle(GetText(strs.reset)).WithDescription(GetText(strs.reset_server_confirm)); if (!await PromptUserConfirmAsync(embed)) return; @@ -48,4 +46,4 @@ public partial class Xp await ReplyConfirmLocalizedAsync(strs.reset_server); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Services/ClubService.cs b/src/NadekoBot/Modules/Xp/Services/ClubService.cs index 5baed38a0..9001a9440 100644 --- a/src/NadekoBot/Modules/Xp/Services/ClubService.cs +++ b/src/NadekoBot/Modules/Xp/Services/ClubService.cs @@ -1,6 +1,6 @@ #nullable disable -using NadekoBot.Db.Models; using NadekoBot.Db; +using NadekoBot.Db.Models; namespace NadekoBot.Modules.Xp.Services; @@ -28,22 +28,16 @@ public class ClubService : INService if (xp.Level >= 5 && du.Club is null) { du.IsClubAdmin = true; - du.Club = new() - { - Name = clubName, - Discrim = uow.Clubs.GetNextDiscrim(clubName), - Owner = du, - }; + du.Club = new() { Name = clubName, Discrim = uow.Clubs.GetNextDiscrim(clubName), Owner = du }; uow.Clubs.Add(du.Club); uow.SaveChanges(); } else + { return false; + } - uow.Set() - .RemoveRange(uow.Set() - .AsQueryable() - .Where(x => x.UserId == du.Id)); + uow.Set().RemoveRange(uow.Set().AsQueryable().Where(x => x.UserId == du.Id)); club = du.Club; uow.SaveChanges(); @@ -54,12 +48,10 @@ public class ClubService : INService { ClubInfo club; using var uow = _db.GetDbContext(); - club = uow.Clubs.GetByOwner(@from.Id); + club = uow.Clubs.GetByOwner(from.Id); var newOwnerUser = uow.GetOrCreateUser(newOwner); - if (club is null || - club.Owner.UserId != @from.Id || - !club.Users.Contains(newOwnerUser)) + if (club is null || club.Owner.UserId != from.Id || !club.Users.Contains(newOwnerUser)) return null; club.Owner.IsClubAdmin = true; // old owner will stay as admin @@ -76,8 +68,7 @@ public class ClubService : INService var club = uow.Clubs.GetByOwner(owner.Id); var adminUser = uow.GetOrCreateUser(toAdmin); - if (club is null || club.Owner.UserId != owner.Id || - !club.Users.Contains(adminUser)) + if (club is null || club.Owner.UserId != owner.Id || !club.Users.Contains(adminUser)) throw new InvalidOperationException(); if (club.OwnerId == adminUser.Id) @@ -134,8 +125,7 @@ public class ClubService : INService club = uow.Clubs.GetByName(name, discrim); if (club is null) return false; - else - return true; + return true; } public bool ApplyToClub(IUser user, ClubInfo club) @@ -148,17 +138,11 @@ public class ClubService : INService || new LevelStats(du.TotalXp).Level < club.MinimumLevelReq || club.Bans.Any(x => x.UserId == du.Id) || club.Applicants.Any(x => x.UserId == du.Id)) - { //user banned or a member of a club, or already applied, // or doesn't min minumum level requirement, can't apply return false; - } - var app = new ClubApplicants - { - ClubId = club.Id, - UserId = du.Id, - }; + var app = new ClubApplicants { ClubId = club.Id, UserId = du.Id }; uow.Set().Add(app); @@ -174,7 +158,8 @@ public class ClubService : INService if (club is null) return false; - var applicant = club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant()); + var applicant = + club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant()); if (applicant is null) return false; @@ -184,9 +169,7 @@ public class ClubService : INService //remove that user's all other applications uow.Set() - .RemoveRange(uow.Set() - .AsQueryable() - .Where(x => x.UserId == applicant.User.Id)); + .RemoveRange(uow.Set().AsQueryable().Where(x => x.UserId == applicant.User.Id)); discordUser = applicant.User; uow.SaveChanges(); @@ -261,18 +244,17 @@ public class ClubService : INService return false; var usr = club.Users.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant()) - ?? club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant())?.User; + ?? club.Applicants + .FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant()) + ?.User; if (usr is null) return false; - if (club.OwnerId == usr.Id || (usr.IsClubAdmin && club.Owner.UserId != bannerId)) // can't ban the owner kek, whew + if (club.OwnerId == usr.Id + || (usr.IsClubAdmin && club.Owner.UserId != bannerId)) // can't ban the owner kek, whew return false; - club.Bans.Add(new() - { - Club = club, - User = usr, - }); + club.Bans.Add(new() { Club = club, User = usr }); club.Users.Remove(usr); var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id); @@ -332,4 +314,4 @@ public class ClubService : INService using var uow = _db.GetDbContext(); return uow.Clubs.GetClubLeaderboardPage(page); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Services/UserCacheItem.cs b/src/NadekoBot/Modules/Xp/Services/UserCacheItem.cs index b314a8571..582973af2 100644 --- a/src/NadekoBot/Modules/Xp/Services/UserCacheItem.cs +++ b/src/NadekoBot/Modules/Xp/Services/UserCacheItem.cs @@ -13,4 +13,4 @@ public class UserCacheItem public override bool Equals(object obj) => obj is UserCacheItem uci && uci.User == User; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Services/XpConfigService.cs b/src/NadekoBot/Modules/Xp/Services/XpConfigService.cs index 4bdeabd15..9ac52160e 100644 --- a/src/NadekoBot/Modules/Xp/Services/XpConfigService.cs +++ b/src/NadekoBot/Modules/Xp/Services/XpConfigService.cs @@ -5,24 +5,31 @@ namespace NadekoBot.Modules.Xp.Services; public sealed class XpConfigService : ConfigServiceBase { - public override string Name { get; } = "xp"; private const string FilePath = "data/xp.yml"; private static readonly TypedKey changeKey = new("config.xp.updated"); + public override string Name { get; } = "xp"; - public XpConfigService(IConfigSeria serializer, IPubSub pubSub) + public XpConfigService(IConfigSeria serializer, IPubSub pubSub) : base(FilePath, serializer, pubSub, changeKey) { - AddParsedProp("txt.cooldown", conf => conf.MessageXpCooldown, int.TryParse, - ConfigPrinters.ToString, x => x > 0); - AddParsedProp("txt.per_msg", conf => conf.XpPerMessage, int.TryParse, - ConfigPrinters.ToString, x => x >= 0); - AddParsedProp("txt.per_image", conf => conf.XpFromImage, int.TryParse, - ConfigPrinters.ToString, x => x > 0); - - AddParsedProp("voice.per_minute", conf => conf.VoiceXpPerMinute, double.TryParse, - ConfigPrinters.ToString, x => x >= 0); - AddParsedProp("voice.max_minutes", conf => conf.VoiceMaxMinutes, int.TryParse, - ConfigPrinters.ToString, x => x > 0); + AddParsedProp("txt.cooldown", + conf => conf.MessageXpCooldown, + int.TryParse, + ConfigPrinters.ToString, + x => x > 0); + AddParsedProp("txt.per_msg", conf => conf.XpPerMessage, int.TryParse, ConfigPrinters.ToString, x => x >= 0); + AddParsedProp("txt.per_image", conf => conf.XpFromImage, int.TryParse, ConfigPrinters.ToString, x => x > 0); + + AddParsedProp("voice.per_minute", + conf => conf.VoiceXpPerMinute, + double.TryParse, + ConfigPrinters.ToString, + x => x >= 0); + AddParsedProp("voice.max_minutes", + conf => conf.VoiceMaxMinutes, + int.TryParse, + ConfigPrinters.ToString, + x => x > 0); Migrate(); } @@ -30,12 +37,10 @@ public sealed class XpConfigService : ConfigServiceBase private void Migrate() { if (data.Version < 2) - { ModifyConfig(c => { c.Version = 2; c.XpFromImage = 0; }); - } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Services/XpService.cs b/src/NadekoBot/Modules/Xp/Services/XpService.cs index 5cb5a5444..d2c10047a 100644 --- a/src/NadekoBot/Modules/Xp/Services/XpService.cs +++ b/src/NadekoBot/Modules/Xp/Services/XpService.cs @@ -1,5 +1,8 @@ #nullable disable +using Microsoft.EntityFrameworkCore; +using NadekoBot.Db; using NadekoBot.Db.Models; +using NadekoBot.Services.Database.Models; using Newtonsoft.Json; using SixLabors.Fonts; using SixLabors.ImageSharp; @@ -7,10 +10,8 @@ using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Microsoft.EntityFrameworkCore; -using NadekoBot.Services.Database.Models; -using NadekoBot.Db; using StackExchange.Redis; +using Color = SixLabors.ImageSharp.Color; using Image = SixLabors.ImageSharp.Image; namespace NadekoBot.Modules.Xp.Services; @@ -18,11 +19,7 @@ namespace NadekoBot.Modules.Xp.Services; // todo improve xp with linqtodb public class XpService : INService { - private enum NotifOf - { - Server, - Global - } // is it a server level-up or global level-up notification + public const int XP_REQUIRED_LVL_1 = 36; private readonly DbService _db; private readonly CommandHandler _cmd; @@ -38,8 +35,6 @@ public class XpService : INService private readonly IPubSub _pubSub; private readonly IEmbedBuilderService _eb; - public const int XP_REQUIRED_LVL_1 = 36; - private readonly ConcurrentDictionary> _excludedRoles; private readonly ConcurrentDictionary> _excludedChannels; @@ -87,54 +82,41 @@ public class XpService : INService InternalReloadXpTemplate(); if (client.ShardId == 0) - { - _pubSub.Sub(_xpTemplateReloadKey, _ => - { - InternalReloadXpTemplate(); - return default; - }); - } + _pubSub.Sub(_xpTemplateReloadKey, + _ => + { + InternalReloadXpTemplate(); + return default; + }); //load settings - var allGuildConfigs = bot.AllGuildConfigs - .Where(x => x.XpSettings != null) - .ToList(); - - _excludedChannels = allGuildConfigs - .ToDictionary( - x => x.GuildId, - x => new ConcurrentHashSet(x.XpSettings - .ExclusionList - .Where(ex => ex.ItemType == ExcludedItemType.Channel) - .Select(ex => ex.ItemId) - .Distinct())) - .ToConcurrent(); + var allGuildConfigs = bot.AllGuildConfigs.Where(x => x.XpSettings != null).ToList(); - _excludedRoles = allGuildConfigs - .ToDictionary( - x => x.GuildId, - x => new ConcurrentHashSet(x.XpSettings - .ExclusionList - .Where(ex => ex.ItemType == ExcludedItemType.Role) - .Select(ex => ex.ItemId) - .Distinct())) - .ToConcurrent(); + _excludedChannels = allGuildConfigs.ToDictionary(x => x.GuildId, + x => new ConcurrentHashSet(x.XpSettings.ExclusionList + .Where(ex => ex.ItemType == ExcludedItemType.Channel) + .Select(ex => ex.ItemId) + .Distinct())) + .ToConcurrent(); - _excludedServers = new( - allGuildConfigs.Where(x => x.XpSettings.ServerExcluded) - .Select(x => x.GuildId)); + _excludedRoles = allGuildConfigs.ToDictionary(x => x.GuildId, + x => new ConcurrentHashSet(x.XpSettings.ExclusionList + .Where(ex => ex.ItemType + == ExcludedItemType.Role) + .Select(ex => ex.ItemId) + .Distinct())) + .ToConcurrent(); + + _excludedServers = new(allGuildConfigs.Where(x => x.XpSettings.ServerExcluded).Select(x => x.GuildId)); _cmd.OnMessageNoTrigger += Cmd_OnMessageNoTrigger; - + #if !GLOBAL_NADEKO _client.UserVoiceStateUpdated += Client_OnUserVoiceStateUpdated; - + // Scan guilds on startup. _client.GuildAvailable += Client_OnGuildAvailable; - foreach (var guild in _client.Guilds) - { - Client_OnGuildAvailable(guild); - } + foreach (var guild in _client.Guilds) Client_OnGuildAvailable(guild); #endif _updateXpTask = Task.Run(UpdateLoop); } @@ -218,23 +200,17 @@ public class XpService : INService if (role is not null) { if (rrew.Remove) - { _ = first.User.RemoveRoleAsync(role); - } else - { _ = first.User.AddRoleAsync(role); - } } } //get currency reward for this level var crew = crews.FirstOrDefault(x => x.Level == i); if (crew != null) - { //give the user the reward if it exists await _cs.AddAsync(item.Key.User.Id, "Level-up Reward", crew.Amount); - } } } } @@ -243,45 +219,36 @@ public class XpService : INService } await toNotify.Select(async x => - { - if (x.NotifOf == NotifOf.Server) - { - if (x.NotifyType == XpNotificationLocation.Dm) - { - await x.User.SendConfirmAsync(_eb, - _strings.GetText(strs.level_up_dm( - x.User.Mention, Format.Bold(x.Level.ToString()), - Format.Bold(x.Guild.ToString() ?? "-")), - x.Guild.Id)); - } - else if (x.MessageChannel != null) // channel - { - await x.MessageChannel.SendConfirmAsync(_eb, - _strings.GetText(strs.level_up_channel( - x.User.Mention, - Format.Bold(x.Level.ToString())), - x.Guild.Id)); - } - } - else - { - IMessageChannel chan; - if (x.NotifyType == XpNotificationLocation.Dm) - { - chan = await x.User.CreateDMChannelAsync(); - } - else // channel - { - chan = x.MessageChannel; - } + { + if (x.NotifOf == NotifOf.Server) + { + if (x.NotifyType == XpNotificationLocation.Dm) + await x.User.SendConfirmAsync(_eb, + _strings.GetText(strs.level_up_dm(x.User.Mention, + Format.Bold(x.Level.ToString()), + Format.Bold(x.Guild.ToString() ?? "-")), + x.Guild.Id)); + else if (x.MessageChannel != null) // channel + await x.MessageChannel.SendConfirmAsync(_eb, + _strings.GetText(strs.level_up_channel(x.User.Mention, + Format.Bold(x.Level.ToString())), + x.Guild.Id)); + } + else + { + IMessageChannel chan; + if (x.NotifyType == XpNotificationLocation.Dm) + chan = await x.User.CreateDMChannelAsync(); + else // channel + chan = x.MessageChannel; - await chan.SendConfirmAsync(_eb, - _strings.GetText(strs.level_up_global( - x.User.Mention, - Format.Bold(x.Level.ToString())), - x.Guild.Id)); - } - }).WhenAll(); + await chan.SendConfirmAsync(_eb, + _strings.GetText(strs.level_up_global(x.User.Mention, + Format.Bold(x.Level.ToString())), + x.Guild.Id)); + } + }) + .WhenAll(); } catch (Exception ex) { @@ -289,7 +256,7 @@ public class XpService : INService } } } - + private void InternalReloadXpTemplate() { @@ -299,8 +266,8 @@ public class XpService : INService { ContractResolver = new RequireObjectPropertiesContractResolver() }; - _template = JsonConvert.DeserializeObject( - File.ReadAllText("./data/xp_template.json"), settings); + _template = JsonConvert.DeserializeObject(File.ReadAllText("./data/xp_template.json"), + settings); } catch (Exception ex) { @@ -335,11 +302,7 @@ public class XpService : INService if (rew != null) rew.Amount = amount; else - settings.CurrencyRewards.Add(new() - { - Level = level, - Amount = amount, - }); + settings.CurrencyRewards.Add(new() { Level = level, Amount = amount }); } uow.SaveChanges(); @@ -348,24 +311,20 @@ public class XpService : INService public IEnumerable GetCurrencyRewards(ulong id) { using var uow = _db.GetDbContext(); - return uow.XpSettingsFor(id) - .CurrencyRewards - .ToArray(); + return uow.XpSettingsFor(id).CurrencyRewards.ToArray(); } public IEnumerable GetRoleRewards(ulong id) { using var uow = _db.GetDbContext(); - return uow.XpSettingsFor(id) - .RoleRewards - .ToArray(); + return uow.XpSettingsFor(id).RoleRewards.ToArray(); } public void ResetRoleReward(ulong guildId, int level) { using var uow = _db.GetDbContext(); var settings = uow.XpSettingsFor(guildId); - + var toRemove = settings.RoleRewards.FirstOrDefault(x => x.Level == level); if (toRemove != null) { @@ -375,13 +334,17 @@ public class XpService : INService uow.SaveChanges(); } - - public void SetRoleReward(ulong guildId, int level, ulong roleId, bool remove) + + public void SetRoleReward( + ulong guildId, + int level, + ulong roleId, + bool remove) { using var uow = _db.GetDbContext(); var settings = uow.XpSettingsFor(guildId); - + var rew = settings.RoleRewards.FirstOrDefault(x => x.Level == level); if (rew != null) @@ -391,12 +354,7 @@ public class XpService : INService } else { - settings.RoleRewards.Add(new() - { - Level = level, - RoleId = roleId, - Remove = remove, - }); + settings.RoleRewards.Add(new() { Level = level, RoleId = roleId, Remove = remove }); } uow.SaveChanges(); @@ -427,7 +385,7 @@ public class XpService : INService user.NotifyOnLevelUp = type; await uow.SaveChangesAsync(); } - + public XpNotificationLocation GetNotificationType(ulong userId, ulong guildId) { using var uow = _db.GetDbContext(); @@ -453,15 +411,12 @@ public class XpService : INService { Task.Run(() => { - foreach (var channel in guild.VoiceChannels) - { - ScanChannelForVoiceXp(channel); - } + foreach (var channel in guild.VoiceChannels) ScanChannelForVoiceXp(channel); }); - + return Task.CompletedTask; } - + private Task Client_OnUserVoiceStateUpdated(SocketUser socketUser, SocketVoiceState before, SocketVoiceState after) { if (socketUser is not SocketGuildUser user || user.IsBot) @@ -469,59 +424,40 @@ public class XpService : INService var _ = Task.Run(() => { - if (before.VoiceChannel != null) - { - ScanChannelForVoiceXp(before.VoiceChannel); - } + if (before.VoiceChannel != null) ScanChannelForVoiceXp(before.VoiceChannel); if (after.VoiceChannel != null && after.VoiceChannel != before.VoiceChannel) - { ScanChannelForVoiceXp(after.VoiceChannel); - } else if (after.VoiceChannel is null) - { // In this case, the user left the channel and the previous for loops didn't catch // it because it wasn't in any new channel. So we need to get rid of it. UserLeftVoiceChannel(user, before.VoiceChannel); - } }); - + return Task.CompletedTask; } private void ScanChannelForVoiceXp(SocketVoiceChannel channel) { if (ShouldTrackVoiceChannel(channel)) - { foreach (var user in channel.Users) - { ScanUserForVoiceXp(user, channel); - } - } else - { foreach (var user in channel.Users) - { UserLeftVoiceChannel(user, channel); - } - } } /// - /// Assumes that the channel itself is valid and adding xp. + /// Assumes that the channel itself is valid and adding xp. /// /// /// private void ScanUserForVoiceXp(SocketGuildUser user, SocketVoiceChannel channel) { if (UserParticipatingInVoiceChannel(user) && ShouldTrackXp(user, channel.Id)) - { UserJoinedVoiceChannel(user); - } else - { UserLeftVoiceChannel(user, channel); - } } private bool ShouldTrackVoiceChannel(SocketVoiceChannel channel) @@ -535,7 +471,8 @@ public class XpService : INService var key = $"{_creds.RedisKey()}_user_xp_vc_join_{user.Id}"; var value = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - _cache.Redis.GetDatabase().StringSet(key, value, TimeSpan.FromMinutes(_xpConfig.Data.VoiceMaxMinutes), When.NotExists); + _cache.Redis.GetDatabase() + .StringSet(key, value, TimeSpan.FromMinutes(_xpConfig.Data.VoiceMaxMinutes), When.NotExists); } private void UserLeftVoiceChannel(SocketGuildUser user, SocketVoiceChannel channel) @@ -543,44 +480,35 @@ public class XpService : INService var key = $"{_creds.RedisKey()}_user_xp_vc_join_{user.Id}"; var value = _cache.Redis.GetDatabase().StringGet(key); _cache.Redis.GetDatabase().KeyDelete(key); - + // Allow for if this function gets called multiple times when a user leaves a channel. if (value.IsNull) return; - + if (!value.TryParse(out long startUnixTime)) return; - + var dateStart = DateTimeOffset.FromUnixTimeSeconds(startUnixTime); var dateEnd = DateTimeOffset.UtcNow; var minutes = (dateEnd - dateStart).TotalMinutes; var xp = _xpConfig.Data.VoiceXpPerMinute * minutes; - var actualXp = (int) Math.Floor(xp); + var actualXp = (int)Math.Floor(xp); if (actualXp > 0) - { - _addMessageXp.Enqueue(new() - { - Guild = channel.Guild, - User = user, - XpAmount = actualXp - }); - } + _addMessageXp.Enqueue(new() { Guild = channel.Guild, User = user, XpAmount = actualXp }); } private bool ShouldTrackXp(SocketGuildUser user, ulong channelId) { - if (_excludedChannels.TryGetValue(user.Guild.Id, out var chans) && - chans.Contains(channelId)) return false; - + if (_excludedChannels.TryGetValue(user.Guild.Id, out var chans) && chans.Contains(channelId)) return false; + if (_excludedServers.Contains(user.Guild.Id)) return false; - - if (_excludedRoles.TryGetValue(user.Guild.Id, out var roles) && - user.Roles.Any(x => roles.Contains(x.Id))) + + if (_excludedRoles.TryGetValue(user.Guild.Id, out var roles) && user.Roles.Any(x => roles.Contains(x.Id))) return false; return true; } - + private Task Cmd_OnMessageNoTrigger(IUserMessage arg) { if (arg.Author is not SocketGuildUser user || user.IsBot) @@ -593,15 +521,9 @@ public class XpService : INService var xpConf = _xpConfig.Data; var xp = 0; - if (arg.Attachments.Any(a => a.Height >= 128 && a.Width >= 128)) - { - xp = xpConf.XpFromImage; - } - - if (arg.Content.Contains(' ') || arg.Content.Length >= 5) - { - xp = Math.Max(xp, xpConf.XpPerMessage); - } + if (arg.Attachments.Any(a => a.Height >= 128 && a.Width >= 128)) xp = xpConf.XpFromImage; + + if (arg.Content.Contains(' ') || arg.Content.Length >= 5) xp = Math.Max(xp, xpConf.XpPerMessage); if (xp <= 0) return; @@ -609,13 +531,7 @@ public class XpService : INService if (!SetUserRewarded(user.Id)) return; - _addMessageXp.Enqueue(new() - { - Guild = user.Guild, - Channel = arg.Channel, - User = user, - XpAmount = xp - }); + _addMessageXp.Enqueue(new() { Guild = user.Guild, Channel = arg.Channel, User = user, XpAmount = xp }); }); return Task.CompletedTask; } @@ -623,16 +539,10 @@ public class XpService : INService public void AddXpDirectly(IGuildUser user, IMessageChannel channel, int amount) { if (amount <= 0) throw new ArgumentOutOfRangeException(nameof(amount)); - - _addMessageXp.Enqueue(new() - { - Guild = user.Guild, - Channel = channel, - User = user, - XpAmount = amount - }); + + _addMessageXp.Enqueue(new() { Guild = user.Guild, Channel = channel, User = user, XpAmount = amount }); } - + public void AddXp(ulong userId, ulong guildId, int amount) { using var uow = _db.GetDbContext(); @@ -667,10 +577,7 @@ public class XpService : INService var r = _cache.Redis.GetDatabase(); var key = $"{_creds.RedisKey()}_user_xp_gain_{userId}"; - return r.StringSet(key, - true, - TimeSpan.FromMinutes(_xpConfig.Data.MessageXpCooldown), - StackExchange.Redis.When.NotExists); + return r.StringSet(key, true, TimeSpan.FromMinutes(_xpConfig.Data.MessageXpCooldown), When.NotExists); } public async Task GetUserStatsAsync(IGuildUser user) @@ -690,12 +597,7 @@ public class XpService : INService await uow.SaveChangesAsync(); } - return new(du, - stats, - new(totalXp), - new(stats.Xp + stats.AwardedXp), - globalRank, - guildRank); + return new(du, stats, new(totalXp), new(stats.Xp + stats.AwardedXp), globalRank, guildRank); } public bool ToggleExcludeServer(ulong id) @@ -720,34 +622,25 @@ public class XpService : INService var roles = _excludedRoles.GetOrAdd(guildId, _ => new()); using var uow = _db.GetDbContext(); var xpSetting = uow.XpSettingsFor(guildId); - var excludeObj = new ExcludedItem - { - ItemId = rId, - ItemType = ExcludedItemType.Role, - }; + var excludeObj = new ExcludedItem { ItemId = rId, ItemType = ExcludedItemType.Role }; if (roles.Add(rId)) { - if (xpSetting.ExclusionList.Add(excludeObj)) - { - uow.SaveChanges(); - } + if (xpSetting.ExclusionList.Add(excludeObj)) uow.SaveChanges(); return true; } - else + + roles.TryRemove(rId); + + var toDelete = xpSetting.ExclusionList.FirstOrDefault(x => x.Equals(excludeObj)); + if (toDelete != null) { - roles.TryRemove(rId); - - var toDelete = xpSetting.ExclusionList.FirstOrDefault(x => x.Equals(excludeObj)); - if (toDelete != null) - { - uow.Remove(toDelete); - uow.SaveChanges(); - } - - return false; + uow.Remove(toDelete); + uow.SaveChanges(); } + + return false; } public bool ToggleExcludeChannel(ulong guildId, ulong chId) @@ -755,32 +648,20 @@ public class XpService : INService var channels = _excludedChannels.GetOrAdd(guildId, _ => new()); using var uow = _db.GetDbContext(); var xpSetting = uow.XpSettingsFor(guildId); - var excludeObj = new ExcludedItem - { - ItemId = chId, - ItemType = ExcludedItemType.Channel, - }; + var excludeObj = new ExcludedItem { ItemId = chId, ItemType = ExcludedItemType.Channel }; if (channels.Add(chId)) { - if (xpSetting.ExclusionList.Add(excludeObj)) - { - uow.SaveChanges(); - } + if (xpSetting.ExclusionList.Add(excludeObj)) uow.SaveChanges(); return true; } - else - { - channels.TryRemove(chId); - if (xpSetting.ExclusionList.Remove(excludeObj)) - { - uow.SaveChanges(); - } + channels.TryRemove(chId); - return false; - } + if (xpSetting.ExclusionList.Remove(excludeObj)) uow.SaveChanges(); + + return false; } public async Task<(Stream Image, IImageFormat Format)> GenerateXpImageAsync(IGuildUser user) @@ -790,24 +671,22 @@ public class XpService : INService } - public Task<(Stream Image, IImageFormat Format)> GenerateXpImageAsync(FullUserStats stats) => Task.Run( - async () => + public Task<(Stream Image, IImageFormat Format)> GenerateXpImageAsync(FullUserStats stats) + => Task.Run(async () => { - var usernameTextOptions = new TextGraphicsOptions() + var usernameTextOptions = new TextGraphicsOptions { TextOptions = new() { - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center } }.WithFallbackFonts(_fonts.FallBackFonts); - - var clubTextOptions = new TextGraphicsOptions() + + var clubTextOptions = new TextGraphicsOptions { TextOptions = new() { - HorizontalAlignment = HorizontalAlignment.Right, - VerticalAlignment = VerticalAlignment.Top, + HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Top } }.WithFallbackFonts(_fonts.FallBackFonts); @@ -816,16 +695,12 @@ public class XpService : INService { var fontSize = (int)(_template.User.Name.FontSize * 0.9); var username = stats.User.ToString(); - var usernameFont = _fonts.NotoSans - .CreateFont(fontSize, FontStyle.Bold); + var usernameFont = _fonts.NotoSans.CreateFont(fontSize, FontStyle.Bold); var size = TextMeasurer.Measure($"@{username}", new(usernameFont)); var scale = 400f / size.Width; if (scale < 1) - { - usernameFont = _fonts.NotoSans - .CreateFont(_template.User.Name.FontSize * scale, FontStyle.Bold); - } + usernameFont = _fonts.NotoSans.CreateFont(_template.User.Name.FontSize * scale, FontStyle.Bold); img.Mutate(x => { @@ -843,45 +718,35 @@ public class XpService : INService { var clubName = stats.User.Club?.ToString() ?? "-"; - var clubFont = _fonts.NotoSans - .CreateFont(_template.Club.Name.FontSize, FontStyle.Regular); - + var clubFont = _fonts.NotoSans.CreateFont(_template.Club.Name.FontSize, FontStyle.Regular); + img.Mutate(x => x.DrawText(clubTextOptions, clubName, clubFont, _template.Club.Name.Color, - new(_template.Club.Name.Pos.X + 50, _template.Club.Name.Pos.Y - 8)) - ); + new(_template.Club.Name.Pos.X + 50, _template.Club.Name.Pos.Y - 8))); } if (_template.User.GlobalLevel.Show) - { img.Mutate(x => { - x.DrawText( - stats.Global.Level.ToString(), + x.DrawText(stats.Global.Level.ToString(), _fonts.NotoSans.CreateFont(_template.User.GlobalLevel.FontSize, FontStyle.Bold), _template.User.GlobalLevel.Color, - new(_template.User.GlobalLevel.Pos.X, _template.User.GlobalLevel.Pos.Y) - ); //level + new(_template.User.GlobalLevel.Pos.X, _template.User.GlobalLevel.Pos.Y)); //level }); - } if (_template.User.GuildLevel.Show) - { img.Mutate(x => { - x.DrawText( - stats.Guild.Level.ToString(), + x.DrawText(stats.Guild.Level.ToString(), _fonts.NotoSans.CreateFont(_template.User.GuildLevel.FontSize, FontStyle.Bold), _template.User.GuildLevel.Color, - new(_template.User.GuildLevel.Pos.X, _template.User.GuildLevel.Pos.Y) - ); + new(_template.User.GuildLevel.Pos.X, _template.User.GuildLevel.Pos.Y)); }); - } - var pen = new Pen(SixLabors.ImageSharp.Color.Black, 1); + var pen = new Pen(Color.Black, 1); var global = stats.Global; var guild = stats.Guild; @@ -889,37 +754,31 @@ public class XpService : INService //xp bar if (_template.User.Xp.Bar.Show) { - var xpPercent = global.LevelXp / (float) global.RequiredXp; + var xpPercent = global.LevelXp / (float)global.RequiredXp; DrawXpBar(xpPercent, _template.User.Xp.Bar.Global, img); - xpPercent = guild.LevelXp / (float) guild.RequiredXp; + xpPercent = guild.LevelXp / (float)guild.RequiredXp; DrawXpBar(xpPercent, _template.User.Xp.Bar.Guild, img); } if (_template.User.Xp.Global.Show) - { img.Mutate(x => x.DrawText($"{global.LevelXp}/{global.RequiredXp}", _fonts.NotoSans.CreateFont(_template.User.Xp.Global.FontSize, FontStyle.Bold), Brushes.Solid(_template.User.Xp.Global.Color), pen, new(_template.User.Xp.Global.Pos.X, _template.User.Xp.Global.Pos.Y))); - } if (_template.User.Xp.Guild.Show) - { img.Mutate(x => x.DrawText($"{guild.LevelXp}/{guild.RequiredXp}", _fonts.NotoSans.CreateFont(_template.User.Xp.Guild.FontSize, FontStyle.Bold), Brushes.Solid(_template.User.Xp.Guild.Color), pen, new(_template.User.Xp.Guild.Pos.X, _template.User.Xp.Guild.Pos.Y))); - } if (stats.FullGuildStats.AwardedXp != 0 && _template.User.Xp.Awarded.Show) { - var sign = stats.FullGuildStats.AwardedXp > 0 - ? "+ " - : ""; - var awX = _template.User.Xp.Awarded.Pos.X - - (Math.Max(0, stats.FullGuildStats.AwardedXp.ToString().Length - 2) * 5); + var sign = stats.FullGuildStats.AwardedXp > 0 ? "+ " : ""; + var awX = _template.User.Xp.Awarded.Pos.X + - (Math.Max(0, stats.FullGuildStats.AwardedXp.ToString().Length - 2) * 5); var awY = _template.User.Xp.Awarded.Pos.Y; img.Mutate(x => x.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})", _fonts.NotoSans.CreateFont(_template.User.Xp.Awarded.FontSize, FontStyle.Bold), @@ -930,20 +789,16 @@ public class XpService : INService //ranking if (_template.User.GlobalRank.Show) - { img.Mutate(x => x.DrawText(stats.GlobalRanking.ToString(), _fonts.UniSans.CreateFont(_template.User.GlobalRank.FontSize, FontStyle.Bold), _template.User.GlobalRank.Color, new(_template.User.GlobalRank.Pos.X, _template.User.GlobalRank.Pos.Y))); - } if (_template.User.GuildRank.Show) - { img.Mutate(x => x.DrawText(stats.GuildRanking.ToString(), _fonts.UniSans.CreateFont(_template.User.GuildRank.FontSize, FontStyle.Bold), _template.User.GuildRank.Color, new(_template.User.GuildRank.Pos.X, _template.User.GuildRank.Pos.Y))); - } //time on this level @@ -954,29 +809,20 @@ public class XpService : INService } if (_template.User.TimeOnLevel.Global.Show) - { - img.Mutate(x => - x.DrawText(GetTimeSpent(stats.User.LastLevelUp, _template.User.TimeOnLevel.Format), - _fonts.NotoSans.CreateFont(_template.User.TimeOnLevel.Global.FontSize, FontStyle.Bold), - _template.User.TimeOnLevel.Global.Color, - new(_template.User.TimeOnLevel.Global.Pos.X, - _template.User.TimeOnLevel.Global.Pos.Y))); - } + img.Mutate(x => x.DrawText(GetTimeSpent(stats.User.LastLevelUp, _template.User.TimeOnLevel.Format), + _fonts.NotoSans.CreateFont(_template.User.TimeOnLevel.Global.FontSize, FontStyle.Bold), + _template.User.TimeOnLevel.Global.Color, + new(_template.User.TimeOnLevel.Global.Pos.X, _template.User.TimeOnLevel.Global.Pos.Y))); if (_template.User.TimeOnLevel.Guild.Show) - { - img.Mutate(x => - x.DrawText( - GetTimeSpent(stats.FullGuildStats.LastLevelUp, _template.User.TimeOnLevel.Format), + img.Mutate(x + => x.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp, _template.User.TimeOnLevel.Format), _fonts.NotoSans.CreateFont(_template.User.TimeOnLevel.Guild.FontSize, FontStyle.Bold), _template.User.TimeOnLevel.Guild.Color, - new(_template.User.TimeOnLevel.Guild.Pos.X, - _template.User.TimeOnLevel.Guild.Pos.Y))); - } + new(_template.User.TimeOnLevel.Guild.Pos.X, _template.User.TimeOnLevel.Guild.Pos.Y))); //avatar if (stats.User.AvatarId != null && _template.User.Icon.Show) - { try { var avatarUrl = stats.User.RealAvatarUrl(); @@ -990,9 +836,10 @@ public class XpService : INService using (var tempDraw = Image.Load(avatarData)) { tempDraw.Mutate(x => x - .Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y) - .ApplyRoundedCorners( - Math.Max(_template.User.Icon.Size.X, _template.User.Icon.Size.Y) / 2)); + .Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y) + .ApplyRoundedCorners(Math.Max(_template.User.Icon.Size.X, + _template.User.Icon.Size.Y) + / 2)); await using (var stream = tempDraw.ToStream()) { data = stream.ToArray(); @@ -1005,28 +852,22 @@ public class XpService : INService using var toDraw = Image.Load(data); if (toDraw.Size() != new Size(_template.User.Icon.Size.X, _template.User.Icon.Size.Y)) - { - toDraw.Mutate(x => - x.Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y)); - } + toDraw.Mutate(x => x.Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y)); img.Mutate(x => x.DrawImage(toDraw, - new Point(_template.User.Icon.Pos.X, _template.User.Icon.Pos.Y), 1)); + new Point(_template.User.Icon.Pos.X, _template.User.Icon.Pos.Y), + 1)); } catch (Exception ex) { Log.Warning(ex, "Error drawing avatar image"); } - } //club image - if (_template.Club.Icon.Show) - { - await DrawClubImage(img, stats); - } + if (_template.Club.Icon.Show) await DrawClubImage(img, stats); img.Mutate(x => x.Resize(_template.OutputSize.X, _template.OutputSize.Y)); - return ((Stream) img.ToStream(imageFormat), imageFormat); + return ((Stream)img.ToStream(imageFormat), imageFormat); }); private void DrawXpBar(float percent, XpBar info, Image img) @@ -1071,19 +912,15 @@ public class XpService : INService } img.Mutate(x => x.FillPolygon(info.Color, - new[] - { - new PointF(x1, y1), - new PointF(x3, y3), - new PointF(x4, y4), - new PointF(x2, y2), - })); + new PointF(x1, y1), + new PointF(x3, y3), + new PointF(x4, y4), + new PointF(x2, y2))); } private async Task DrawClubImage(Image img, FullUserStats stats) { if (!string.IsNullOrWhiteSpace(stats.User.Club?.ImageUrl)) - { try { var imgUrl = new Uri(stats.User.Club.ImageUrl); @@ -1099,9 +936,10 @@ public class XpService : INService using (var tempDraw = Image.Load(imgData)) { tempDraw.Mutate(x => x - .Resize(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y) - .ApplyRoundedCorners( - Math.Max(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y) / 2.0f)); + .Resize(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y) + .ApplyRoundedCorners(Math.Max(_template.Club.Icon.Size.X, + _template.Club.Icon.Size.Y) + / 2.0f)); ; await using (var tds = tempDraw.ToStream()) { @@ -1115,9 +953,7 @@ public class XpService : INService using var toDraw = Image.Load(data); if (toDraw.Size() != new Size(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y)) - { toDraw.Mutate(x => x.Resize(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y)); - } img.Mutate(x => x.DrawImage( toDraw, @@ -1128,7 +964,6 @@ public class XpService : INService { Log.Warning(ex, "Error drawing club image"); } - } } public void XpReset(ulong guildId, ulong userId) @@ -1148,15 +983,20 @@ public class XpService : INService public async Task ResetXpRewards(ulong guildId) { await using var uow = _db.GetDbContext(); - var guildConfig = uow.GuildConfigsForId(guildId, - set => set - .Include(x => x.XpSettings) - .ThenInclude(x => x.CurrencyRewards) - .Include(x => x.XpSettings) - .ThenInclude(x => x.RoleRewards)); - + var guildConfig = uow.GuildConfigsForId(guildId, + set => set.Include(x => x.XpSettings) + .ThenInclude(x => x.CurrencyRewards) + .Include(x => x.XpSettings) + .ThenInclude(x => x.RoleRewards)); + uow.RemoveRange(guildConfig.XpSettings.RoleRewards); uow.RemoveRange(guildConfig.XpSettings.CurrencyRewards); await uow.SaveChangesAsync(); } -} + + private enum NotifOf + { + Server, + Global + } // is it a server level-up or global level-up notification +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Xp.cs b/src/NadekoBot/Modules/Xp/Xp.cs index 2981cdada..920ab65dc 100644 --- a/src/NadekoBot/Modules/Xp/Xp.cs +++ b/src/NadekoBot/Modules/Xp/Xp.cs @@ -1,12 +1,25 @@ #nullable disable -using NadekoBot.Services.Database.Models; -using NadekoBot.Modules.Xp.Services; using NadekoBot.Modules.Gambling.Services; +using NadekoBot.Modules.Xp.Services; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Xp; public partial class Xp : NadekoModule { + public enum Channel { Channel } + + public enum NotifyPlace + { + Server = 0, + Guild = 0, + Global = 1 + } + + public enum Role { Role } + + public enum Server { Server } + private readonly DownloadTracker _tracker; private readonly GamblingConfigService _gss; @@ -16,7 +29,8 @@ public partial class Xp : NadekoModule _gss = gss; } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task Experience([Leftover] IUser user = null) { @@ -29,14 +43,15 @@ public partial class Xp : NadekoModule } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task XpRewsReset() { var reply = await PromptUserConfirmAsync(_eb.Create() - .WithPendingColor() - .WithDescription(GetText(strs.xprewsreset_confirm))); + .WithPendingColor() + .WithDescription(GetText(strs.xprewsreset_confirm))); if (!reply) return; @@ -44,8 +59,9 @@ public partial class Xp : NadekoModule await _service.ResetXpRewards(ctx.Guild.Id); await ctx.OkAsync(); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public Task XpLevelUpRewards(int page = 1) { @@ -55,58 +71,56 @@ public partial class Xp : NadekoModule return Task.CompletedTask; var allRewards = _service.GetRoleRewards(ctx.Guild.Id) - .OrderBy(x => x.Level) - .Select(x => + .OrderBy(x => x.Level) + .Select(x => + { + var sign = !x.Remove ? @"✅ " : @"❌ "; + + var str = ctx.Guild.GetRole(x.RoleId)?.ToString(); + + if (str is null) + { + str = GetText(strs.role_not_found(Format.Code(x.RoleId.ToString()))); + } + else + { + if (!x.Remove) + str = GetText(strs.xp_receive_role(Format.Bold(str))); + else + str = GetText(strs.xp_lose_role(Format.Bold(str))); + } + + return (x.Level, Text: sign + str); + }) + .Concat(_service.GetCurrencyRewards(ctx.Guild.Id) + .OrderBy(x => x.Level) + .Select(x => (x.Level, + Format.Bold(x.Amount + _gss.Data.Currency.Sign)))) + .GroupBy(x => x.Level) + .OrderBy(x => x.Key) + .ToList(); + + return Context.SendPaginatedConfirmAsync(page, + cur => { - var sign = !x.Remove - ? @"✅ " - : @"❌ "; - - var str = ctx.Guild.GetRole(x.RoleId)?.ToString(); - - if (str is null) - str = GetText(strs.role_not_found(Format.Code(x.RoleId.ToString()))); - else - { - if (!x.Remove) - str = GetText(strs.xp_receive_role(Format.Bold(str))); - else - str = GetText(strs.xp_lose_role(Format.Bold(str))); - } - return (x.Level, Text: sign + str); - }) - .Concat(_service.GetCurrencyRewards(ctx.Guild.Id) - .OrderBy(x => x.Level) - .Select(x => (x.Level, Format.Bold(x.Amount + _gss.Data.Currency.Sign)))) - .GroupBy(x => x.Level) - .OrderBy(x => x.Key) - .ToList(); + var embed = _eb.Create().WithTitle(GetText(strs.level_up_rewards)).WithOkColor(); - return Context.SendPaginatedConfirmAsync(page, cur => - { - var embed = _eb.Create() - .WithTitle(GetText(strs.level_up_rewards)) - .WithOkColor(); - - var localRewards = allRewards - .Skip(cur * 9) - .Take(9) - .ToList(); + var localRewards = allRewards.Skip(cur * 9).Take(9).ToList(); - if (!localRewards.Any()) - return embed.WithDescription(GetText(strs.no_level_up_rewards)); + if (!localRewards.Any()) + return embed.WithDescription(GetText(strs.no_level_up_rewards)); - foreach (var reward in localRewards) - { - embed.AddField(GetText(strs.level_x(reward.Key)), - string.Join("\n", reward.Select(y => y.Item2))); - } - - return embed; - }, allRewards.Count, 9); + foreach (var reward in localRewards) + embed.AddField(GetText(strs.level_x(reward.Key)), string.Join("\n", reward.Select(y => y.Item2))); + + return embed; + }, + allRewards.Count, + 9); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [UserPerm(GuildPerm.Administrator)] [BotPerm(GuildPerm.ManageRoles)] [RequireContext(ContextType.Guild)] @@ -116,8 +130,9 @@ public partial class Xp : NadekoModule _service.ResetRoleReward(ctx.Guild.Id, level); await ReplyConfirmLocalizedAsync(strs.xp_role_reward_cleared(level)); } - - [NadekoCommand, Aliases] + + [NadekoCommand] + [Aliases] [UserPerm(GuildPerm.Administrator)] [BotPerm(GuildPerm.ManageRoles)] [RequireContext(ContextType.Guild)] @@ -129,16 +144,14 @@ public partial class Xp : NadekoModule _service.SetRoleReward(ctx.Guild.Id, level, role.Id, action == AddRemove.Remove); if (action == AddRemove.Add) - await ReplyConfirmLocalizedAsync(strs.xp_role_reward_add_role( - level, - Format.Bold(role.ToString()))); + await ReplyConfirmLocalizedAsync(strs.xp_role_reward_add_role(level, Format.Bold(role.ToString()))); else - await ReplyConfirmLocalizedAsync(strs.xp_role_reward_remove_role( - Format.Bold(level.ToString()), + await ReplyConfirmLocalizedAsync(strs.xp_role_reward_remove_role(Format.Bold(level.ToString()), Format.Bold(role.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] public async Task XpCurrencyReward(int level, int amount = 0) @@ -152,33 +165,20 @@ public partial class Xp : NadekoModule if (amount == 0) await ReplyConfirmLocalizedAsync(strs.cur_reward_cleared(level, config.Currency.Sign)); else - await ReplyConfirmLocalizedAsync(strs.cur_reward_added( - level, Format.Bold(amount + config.Currency.Sign))); - } - - public enum NotifyPlace - { - Server = 0, - Guild = 0, - Global = 1, + await ReplyConfirmLocalizedAsync(strs.cur_reward_added(level, Format.Bold(amount + config.Currency.Sign))); } private string GetNotifLocationString(XpNotificationLocation loc) { - if (loc == XpNotificationLocation.Channel) - { - return GetText(strs.xpn_notif_channel); - } + if (loc == XpNotificationLocation.Channel) return GetText(strs.xpn_notif_channel); - if (loc == XpNotificationLocation.Dm) - { - return GetText(strs.xpn_notif_dm); - } + if (loc == XpNotificationLocation.Dm) return GetText(strs.xpn_notif_dm); return GetText(strs.xpn_notif_disabled); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task XpNotify() { @@ -186,14 +186,15 @@ public partial class Xp : NadekoModule var serverSetting = _service.GetNotificationType(ctx.User.Id, ctx.Guild.Id); var embed = _eb.Create() - .WithOkColor() - .AddField(GetText(strs.xpn_setting_global), GetNotifLocationString(globalSetting)) - .AddField(GetText(strs.xpn_setting_server), GetNotifLocationString(serverSetting)); + .WithOkColor() + .AddField(GetText(strs.xpn_setting_global), GetNotifLocationString(globalSetting)) + .AddField(GetText(strs.xpn_setting_server), GetNotifLocationString(serverSetting)); await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task XpNotify(NotifyPlace place, XpNotificationLocation type) { @@ -201,13 +202,12 @@ public partial class Xp : NadekoModule await _service.ChangeNotificationType(ctx.User.Id, ctx.Guild.Id, type); else await _service.ChangeNotificationType(ctx.User, type); - + await ctx.OkAsync(); } - public enum Server { Server }; - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task XpExclude(Server _) @@ -215,18 +215,13 @@ public partial class Xp : NadekoModule var ex = _service.ToggleExcludeServer(ctx.Guild.Id); if (ex) - { await ReplyConfirmLocalizedAsync(strs.excluded(Format.Bold(ctx.Guild.ToString()))); - } else - { await ReplyConfirmLocalizedAsync(strs.not_excluded(Format.Bold(ctx.Guild.ToString()))); - } } - public enum Role { Role }; - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [UserPerm(GuildPerm.ManageRoles)] [RequireContext(ContextType.Guild)] public async Task XpExclude(Role _, [Leftover] IRole role) @@ -234,18 +229,13 @@ public partial class Xp : NadekoModule var ex = _service.ToggleExcludeRole(ctx.Guild.Id, role.Id); if (ex) - { await ReplyConfirmLocalizedAsync(strs.excluded(Format.Bold(role.ToString()))); - } else - { await ReplyConfirmLocalizedAsync(strs.not_excluded(Format.Bold(role.ToString()))); - } } - public enum Channel { Channel }; - - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [UserPerm(GuildPerm.ManageChannels)] [RequireContext(ContextType.Guild)] public async Task XpExclude(Channel _, [Leftover] IChannel channel = null) @@ -256,31 +246,28 @@ public partial class Xp : NadekoModule var ex = _service.ToggleExcludeChannel(ctx.Guild.Id, channel.Id); if (ex) - { await ReplyConfirmLocalizedAsync(strs.excluded(Format.Bold(channel.ToString()))); - } else - { await ReplyConfirmLocalizedAsync(strs.not_excluded(Format.Bold(channel.ToString()))); - } } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task XpExclusionList() { var serverExcluded = _service.IsServerExcluded(ctx.Guild.Id); var roles = _service.GetExcludedRoles(ctx.Guild.Id) - .Select(x => ctx.Guild.GetRole(x)) - .Where(x => x != null) - .Select(x => $"`role` {x.Mention}") - .ToList(); + .Select(x => ctx.Guild.GetRole(x)) + .Where(x => x != null) + .Select(x => $"`role` {x.Mention}") + .ToList(); var chans = (await _service.GetExcludedChannels(ctx.Guild.Id) - .Select(x => ctx.Guild.GetChannelAsync(x)) - .WhenAll()).Where(x => x != null) - .Select(x => $"`channel` <#{x.Id}>") - .ToList(); + .Select(x => ctx.Guild.GetChannelAsync(x)) + .WhenAll()).Where(x => x != null) + .Select(x => $"`channel` <#{x.Id}>") + .ToList(); var rolesStr = roles.Any() ? string.Join("\n", roles) + "\n" : string.Empty; var chansStr = chans.Count > 0 ? string.Join("\n", chans) + "\n" : string.Empty; @@ -291,25 +278,30 @@ public partial class Xp : NadekoModule desc += "\n\n" + rolesStr + chansStr; var lines = desc.Split('\n'); - await ctx.SendPaginatedConfirmAsync(0, curpage => - { - var embed = _eb.Create() - .WithTitle(GetText(strs.exclusion_list)) - .WithDescription(string.Join('\n', lines.Skip(15 * curpage).Take(15))) - .WithOkColor(); + await ctx.SendPaginatedConfirmAsync(0, + curpage => + { + var embed = _eb.Create() + .WithTitle(GetText(strs.exclusion_list)) + .WithDescription(string.Join('\n', lines.Skip(15 * curpage).Take(15))) + .WithOkColor(); - return embed; - }, lines.Length, 15); + return embed; + }, + lines.Length, + 15); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [NadekoOptions(typeof(LbOpts))] [Priority(0)] [RequireContext(ContextType.Guild)] public Task XpLeaderboard(params string[] args) => XpLeaderboard(1, args); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [NadekoOptions(typeof(LbOpts))] [Priority(1)] [RequireContext(ContextType.Guild)] @@ -328,32 +320,25 @@ public partial class Xp : NadekoModule { await ctx.Channel.TriggerTypingAsync(); await _tracker.EnsureUsersDownloadedAsync(ctx.Guild); - + allUsers = _service.GetTopUserXps(ctx.Guild.Id, 1000) - .Where(user => socketGuild.GetUser(user.UserId) is not null) - .ToList(); + .Where(user => socketGuild.GetUser(user.UserId) is not null) + .ToList(); } - await ctx.SendPaginatedConfirmAsync(page, curPage => - { - var embed = _eb.Create() - .WithTitle(GetText(strs.server_leaderboard)) - .WithOkColor(); + await ctx.SendPaginatedConfirmAsync(page, + curPage => + { + var embed = _eb.Create().WithTitle(GetText(strs.server_leaderboard)).WithOkColor(); - List users; - if (opts.Clean) - { - users = allUsers.Skip(curPage * 9).Take(9).ToList(); - } - else - { - users = _service.GetUserXps(ctx.Guild.Id, curPage); - } + List users; + if (opts.Clean) + users = allUsers.Skip(curPage * 9).Take(9).ToList(); + else + users = _service.GetUserXps(ctx.Guild.Id, curPage); + + if (!users.Any()) return embed.WithDescription("-"); - if (!users.Any()) - return embed.WithDescription("-"); - else - { for (var i = 0; i < users.Count; i++) { var levelStats = new LevelStats(users[i].Xp + users[i].AwardedXp); @@ -367,16 +352,19 @@ public partial class Xp : NadekoModule else if (userXpData.AwardedXp < 0) awardStr = $"({userXpData.AwardedXp})"; - embed.AddField( - $"#{i + 1 + (curPage * 9)} {user?.ToString() ?? users[i].UserId.ToString()}", + embed.AddField($"#{i + 1 + (curPage * 9)} {user?.ToString() ?? users[i].UserId.ToString()}", $"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}"); } + return embed; - } - }, 900, 9, addPaginatedFooter: false); + }, + 900, + 9, + false); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] public async Task XpGlobalLeaderboard(int page = 1) { @@ -384,27 +372,23 @@ public partial class Xp : NadekoModule return; var users = _service.GetUserXps(page); - var embed = _eb.Create() - .WithTitle(GetText(strs.global_leaderboard)) - .WithOkColor(); + var embed = _eb.Create().WithTitle(GetText(strs.global_leaderboard)).WithOkColor(); if (!users.Any()) embed.WithDescription("-"); else - { for (var i = 0; i < users.Length; i++) { var user = users[i]; - embed.AddField( - $"#{i + 1 + (page * 9)} {user.ToString()}", + embed.AddField($"#{i + 1 + (page * 9)} {user.ToString()}", $"{GetText(strs.level_x(new LevelStats(users[i].TotalXp).Level))} - {users[i].TotalXp}xp"); } - } await ctx.Channel.EmbedAsync(embed); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public async Task XpAdd(int amount, ulong userId) @@ -413,18 +397,19 @@ public partial class Xp : NadekoModule return; _service.AddXp(userId, ctx.Guild.Id, amount); - var usr = ((SocketGuild)ctx.Guild).GetUser(userId)?.ToString() - ?? userId.ToString(); + var usr = ((SocketGuild)ctx.Guild).GetUser(userId)?.ToString() ?? userId.ToString(); await ReplyConfirmLocalizedAsync(strs.modified(Format.Bold(usr), Format.Bold(amount.ToString()))); } - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] public Task XpAdd(int amount, [Leftover] IGuildUser user) => XpAdd(amount, user.Id); - [NadekoCommand, Aliases] + [NadekoCommand] + [Aliases] [RequireContext(ContextType.Guild)] [OwnerOnly] public async Task XpTemplateReload() @@ -433,4 +418,4 @@ public partial class Xp : NadekoModule await Task.Delay(1000); await ReplyConfirmLocalizedAsync(strs.template_reloaded); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Program.cs b/src/NadekoBot/Program.cs index 2f9f4fd43..b9fd6889b 100644 --- a/src/NadekoBot/Program.cs +++ b/src/NadekoBot/Program.cs @@ -25,4 +25,4 @@ int? totalShards = null; // 0 to read from creds.yml LogSetup.SetupLogger(shardId); Log.Information("Pid: {ProcessId}", pid); -await new Bot(shardId, totalShards).RunAndBlockAsync(); +await new Bot(shardId, totalShards).RunAndBlockAsync(); \ No newline at end of file diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index 739dbcf30..7c5bdba2a 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -1,8 +1,10 @@ #nullable disable -using System.Collections.Immutable; +using Discord.Interactions; using NadekoBot.Common.Configs; using NadekoBot.Db; -using Discord.Interactions; +using System.Collections.Immutable; +using ExecuteResult = Discord.Commands.ExecuteResult; +using PreconditionResult = Discord.Commands.PreconditionResult; namespace NadekoBot.Services; @@ -10,14 +12,7 @@ public class CommandHandler : INService { private const int GlobalCommandsCooldown = 750; - private readonly DiscordSocketClient _client; - private readonly CommandService _commandService; - private readonly BotConfigService _bss; - private readonly Bot _bot; - private readonly IBehaviourExecutor _behaviourExecutor; - private readonly IServiceProvider _services; - - private readonly ConcurrentDictionary _prefixes; + private const float _oneThousandth = 1.0f / 1000; public event Func CommandExecuted = delegate { return Task.CompletedTask; }; public event Func CommandErrored = delegate { return Task.CompletedTask; }; @@ -27,7 +22,18 @@ public class CommandHandler : INService public ConcurrentDictionary UserMessagesSent { get; } = new(); public ConcurrentHashSet UsersOnShortCooldown { get; } = new(); + + private readonly DiscordSocketClient _client; + private readonly CommandService _commandService; + private readonly BotConfigService _bss; + private readonly Bot _bot; + private readonly IBehaviourExecutor _behaviourExecutor; + private readonly IServiceProvider _services; + + private readonly ConcurrentDictionary _prefixes; private readonly Timer _clearUsersOnShortCooldown; + private readonly DbService _db; + private readonly InteractionService _interactions; public CommandHandler( DiscordSocketClient client, @@ -49,17 +55,20 @@ public class CommandHandler : INService _interactions = interactions; _clearUsersOnShortCooldown = new(_ => - { - UsersOnShortCooldown.Clear(); - }, null, GlobalCommandsCooldown, GlobalCommandsCooldown); - - _prefixes = bot.AllGuildConfigs - .Where(x => x.Prefix != null) - .ToDictionary(x => x.GuildId, x => x.Prefix) - .ToConcurrent(); + { + UsersOnShortCooldown.Clear(); + }, + null, + GlobalCommandsCooldown, + GlobalCommandsCooldown); + + _prefixes = bot.AllGuildConfigs.Where(x => x.Prefix != null) + .ToDictionary(x => x.GuildId, x => x.Prefix) + .ToConcurrent(); } - public string GetPrefix(IGuild guild) => GetPrefix(guild?.Id); + public string GetPrefix(IGuild guild) + => GetPrefix(guild?.Id); public string GetPrefix(ulong? id = null) { @@ -81,6 +90,7 @@ public class CommandHandler : INService return prefix; } + public string SetPrefix(IGuild guild, string prefix) { if (string.IsNullOrWhiteSpace(prefix)) @@ -135,64 +145,55 @@ public class CommandHandler : INService await _interactions.ExecuteCommandAsync(ctx, _services); } - private const float _oneThousandth = 1.0f / 1000; - private readonly DbService _db; - private readonly InteractionService _interactions; - private Task LogSuccessfulExecution(IUserMessage usrMsg, ITextChannel channel, params int[] execPoints) { if (_bss.Data.ConsoleOutputType == ConsoleOutputType.Normal) - { Log.Information(@"Command Executed after {ExecTime}s User: {User} Server: {Server} Channel: {Channel} - Message: {MessageContent}", + Message: {Message}", string.Join("/", execPoints.Select(x => (x * _oneThousandth).ToString("F3"))), usrMsg.Author + " [" + usrMsg.Author.Id + "]", channel is null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]", channel is null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]", - usrMsg.Content - ); - } + usrMsg.Content); else - { - Log.Information("Succ | g:{0} | c: {1} | u: {2} | msg: {3}", + Log.Information("Succ | g:{GuildId} | c: {ChannelId} | u: {UserId} | msg: {Message}", channel?.Guild.Id.ToString() ?? "-", channel?.Id.ToString() ?? "-", usrMsg.Author.Id, usrMsg.Content.TrimTo(10)); - } return Task.CompletedTask; } - private void LogErroredExecution(string errorMessage, IUserMessage usrMsg, ITextChannel channel, params int[] execPoints) + private void LogErroredExecution( + string errorMessage, + IUserMessage usrMsg, + ITextChannel channel, + params int[] execPoints) { if (_bss.Data.ConsoleOutputType == ConsoleOutputType.Normal) - { Log.Warning(@"Command Errored after {ExecTime}s User: {User} - Server: {Server} + Server: {Guild} Channel: {Channel} - Message: {MessageContent} + Message: {Message} Error: {ErrorMessage}", string.Join("/", execPoints.Select(x => (x * _oneThousandth).ToString("F3"))), usrMsg.Author + " [" + usrMsg.Author.Id + "]", - channel is null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]", - channel is null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]", + channel is null ? "DM" : channel.Guild.Name + " [" + channel.Guild.Id + "]", + channel is null ? "DM" : channel.Name + " [" + channel.Id + "]", usrMsg.Content, - errorMessage - ); - } + errorMessage); else - { - Log.Warning("Err | g:{0} | c: {1} | u: {2} | msg: {3}\n\tErr: {4}", + Log.Warning(@"Err | g:{GuildId} | c: {ChannelId} | u: {UserId} | msg: {Message} + Err: {ErrorMessage}", channel?.Guild.Id.ToString() ?? "-", channel?.Id.ToString() ?? "-", usrMsg.Author.Id, usrMsg.Content.TrimTo(10), errorMessage); - } } private Task MessageReceivedHandler(SocketMessage msg) @@ -205,29 +206,26 @@ public class CommandHandler : INService return Task.CompletedTask; Task.Run(async () => + { + try { - try - { #if !GLOBAL_NADEKO - // track how many messagges each user is sending - UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old); + // track how many messagges each user is sending + UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old); #endif - var channel = msg.Channel; - var guild = (msg.Channel as SocketTextChannel)?.Guild; + var channel = msg.Channel; + var guild = (msg.Channel as SocketTextChannel)?.Guild; - await TryRunCommand(guild, channel, usrMsg); - } - catch (Exception ex) - { - Log.Warning(ex, "Error in CommandHandler"); - if (ex.InnerException != null) - { - Log.Warning(ex.InnerException, "Inner Exception of the error in CommandHandler"); - } - } + await TryRunCommand(guild, channel, usrMsg); } - ); + catch (Exception ex) + { + Log.Warning(ex, "Error in CommandHandler"); + if (ex.InnerException != null) + Log.Warning(ex.InnerException, "Inner Exception of the error in CommandHandler"); + } + }); return Task.CompletedTask; } @@ -243,13 +241,17 @@ public class CommandHandler : INService var blockTime = Environment.TickCount - startTime; var messageContent = await _behaviourExecutor.RunInputTransformersAsync(guild, usrMsg); - + var prefix = GetPrefix(guild?.Id); var isPrefixCommand = messageContent.StartsWith(".prefix", StringComparison.InvariantCultureIgnoreCase); // execute the command and measure the time it took if (messageContent.StartsWith(prefix, StringComparison.InvariantCulture) || isPrefixCommand) { - var (success, error, info) = await ExecuteCommandAsync(new(_client, usrMsg), messageContent, isPrefixCommand ? 1 : prefix.Length, _services, MultiMatchHandling.Best); + var (success, error, info) = await ExecuteCommandAsync(new(_client, usrMsg), + messageContent, + isPrefixCommand ? 1 : prefix.Length, + _services, + MultiMatchHandling.Best); startTime = Environment.TickCount - startTime; if (success) @@ -258,7 +260,8 @@ public class CommandHandler : INService await CommandExecuted(usrMsg, info); return; } - else if (error != null) + + if (error != null) { LogErroredExecution(error, usrMsg, channel as ITextChannel, blockTime, startTime); if (guild != null) @@ -273,34 +276,38 @@ public class CommandHandler : INService await _behaviourExecutor.RunLateExecutorsAsync(guild, usrMsg); } - public Task<(bool Success, string Error, CommandInfo Info)> ExecuteCommandAsync(CommandContext context, string input, int argPos, IServiceProvider serviceProvider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public Task<(bool Success, string Error, CommandInfo Info)> ExecuteCommandAsync( + CommandContext context, + string input, + int argPos, + IServiceProvider serviceProvider, + MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) => ExecuteCommand(context, input[argPos..], serviceProvider, multiMatchHandling); - public async Task<(bool Success, string Error, CommandInfo Info)> ExecuteCommand(CommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public async Task<(bool Success, string Error, CommandInfo Info)> ExecuteCommand( + CommandContext context, + string input, + IServiceProvider services, + MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { var searchResult = _commandService.Search(context, input); if (!searchResult.IsSuccess) return (false, null, null); var commands = searchResult.Commands; - var preconditionResults = new Dictionary(); + var preconditionResults = new Dictionary(); foreach (var match in commands) - { preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services); - } - var successfulPreconditions = preconditionResults - .Where(x => x.Value.IsSuccess) - .ToArray(); + var successfulPreconditions = preconditionResults.Where(x => x.Value.IsSuccess).ToArray(); if (successfulPreconditions.Length == 0) { //All preconditions failed, return the one from the highest priority command - var bestCandidate = preconditionResults - .OrderByDescending(x => x.Key.Command.Priority) - .FirstOrDefault(x => !x.Value.IsSuccess); + var bestCandidate = preconditionResults.OrderByDescending(x => x.Key.Command.Priority) + .FirstOrDefault(x => !x.Value.IsSuccess); return (false, bestCandidate.Value.ErrorReason, commands[0].Command); } @@ -315,8 +322,11 @@ public class CommandHandler : INService switch (multiMatchHandling) { case MultiMatchHandling.Best: - argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); - paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()) + .ToImmutableArray(); + paramList = parseResult.ParamValues + .Select(x => x.Values.OrderByDescending(y => y.Score).First()) + .ToImmutableArray(); parseResult = ParseResult.FromSuccess(argList, paramList); break; } @@ -324,6 +334,7 @@ public class CommandHandler : INService parseResultsDict[pair.Key] = parseResult; } + // Calculates the 'score' of a command given a parse result float CalculateScore(CommandMatch match, ParseResult parseResult) { @@ -331,8 +342,12 @@ public class CommandHandler : INService if (match.Command.Parameters.Count > 0) { - var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; - var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; + var argValuesSum = + parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) + ?? 0; + var paramValuesSum = + parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) + ?? 0; argValuesScore = argValuesSum / match.Command.Parameters.Count; paramValuesScore = paramValuesSum / match.Command.Parameters.Count; @@ -343,19 +358,14 @@ public class CommandHandler : INService } //Order the parse results by their score so that we choose the most likely result to execute - var parseResults = parseResultsDict - .OrderByDescending(x => CalculateScore(x.Key, x.Value)) - .ToList(); + var parseResults = parseResultsDict.OrderByDescending(x => CalculateScore(x.Key, x.Value)).ToList(); - var successfulParses = parseResults - .Where(x => x.Value.IsSuccess) - .ToArray(); + var successfulParses = parseResults.Where(x => x.Value.IsSuccess).ToArray(); if (successfulParses.Length == 0) { //All parses failed, return the one from the highest priority command, using score as a tie breaker - var bestMatch = parseResults - .FirstOrDefault(x => !x.Value.IsSuccess); + var bestMatch = parseResults.FirstOrDefault(x => !x.Value.IsSuccess); return (false, bestMatch.Value.ErrorReason, commands[0].Command); } @@ -373,13 +383,13 @@ public class CommandHandler : INService //If we get this far, at least one parse was successful. Execute the most likely overload. var chosenOverload = successfulParses[0]; - var execResult = (Discord.Commands.ExecuteResult)await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services); + var execResult = (ExecuteResult)await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services); - if (execResult.Exception != null && (execResult.Exception is not HttpException he || he.DiscordCode != DiscordErrorCode.InsufficientPermissions)) - { + if (execResult.Exception != null + && (execResult.Exception is not HttpException he + || he.DiscordCode != DiscordErrorCode.InsufficientPermissions)) Log.Warning(execResult.Exception, "Command Error"); - } return (true, null, cmd); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/DbService.cs b/src/NadekoBot/Services/DbService.cs index d30c66580..74f5e6663 100644 --- a/src/NadekoBot/Services/DbService.cs +++ b/src/NadekoBot/Services/DbService.cs @@ -1,8 +1,8 @@ #nullable disable +using LinqToDB.EntityFrameworkCore; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Database; -using LinqToDB.EntityFrameworkCore; namespace NadekoBot.Services; @@ -14,7 +14,7 @@ public class DbService public DbService(IBotCredentials creds) { LinqToDBForEFTools.Initialize(); - + var builder = new SqliteConnectionStringBuilder(creds.Db.ConnectionString); builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource); @@ -37,6 +37,7 @@ public class DbService mContext.SaveChanges(); mContext.Dispose(); } + context.Database.ExecuteSqlRaw("PRAGMA journal_mode=WAL"); context.SaveChanges(); } @@ -53,5 +54,6 @@ public class DbService return context; } - public NadekoContext GetDbContext() => GetDbContextInternal(); -} + public NadekoContext GetDbContext() + => GetDbContextInternal(); +} \ No newline at end of file diff --git a/src/NadekoBot/Services/IBehaviourExecutor.cs b/src/NadekoBot/Services/IBehaviourExecutor.cs index 7830248bc..d7340abe1 100644 --- a/src/NadekoBot/Services/IBehaviourExecutor.cs +++ b/src/NadekoBot/Services/IBehaviourExecutor.cs @@ -9,4 +9,4 @@ public interface IBehaviourExecutor Task RunLateExecutorsAsync(SocketGuild guild, IUserMessage usrMsg); public void Initialize(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/ICoordinator.cs b/src/NadekoBot/Services/ICoordinator.cs index 4c4a0d7f5..52c0eaca9 100644 --- a/src/NadekoBot/Services/ICoordinator.cs +++ b/src/NadekoBot/Services/ICoordinator.cs @@ -10,11 +10,11 @@ public interface ICoordinator int GetGuildCount(); Task Reload(); } - + public class ShardStatus { - public Discord.ConnectionState ConnectionState { get; set; } + public ConnectionState ConnectionState { get; set; } public DateTime LastUpdate { get; set; } public int ShardId { get; set; } public int GuildCount { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/ICurrencyService.cs b/src/NadekoBot/Services/ICurrencyService.cs index e2c074abc..4ec6c19d3 100644 --- a/src/NadekoBot/Services/ICurrencyService.cs +++ b/src/NadekoBot/Services/ICurrencyService.cs @@ -3,10 +3,41 @@ namespace NadekoBot.Services; public interface ICurrencyService { - Task AddAsync(ulong userId, string reason, long amount, bool gamble = false); - Task AddAsync(IUser user, string reason, long amount, bool sendMessage = false, bool gamble = false); - Task AddBulkAsync(IEnumerable userIds, IEnumerable reasons, IEnumerable amounts, bool gamble = false); - Task RemoveAsync(ulong userId, string reason, long amount, bool gamble = false); - Task RemoveAsync(IUser userId, string reason, long amount, bool sendMessage = false, bool gamble = false); - Task RemoveBulkAsync(IEnumerable userIds, IEnumerable reasons, IEnumerable amounts, bool gamble = false); -} + Task AddAsync( + ulong userId, + string reason, + long amount, + bool gamble = false); + + Task AddAsync( + IUser user, + string reason, + long amount, + bool sendMessage = false, + bool gamble = false); + + Task AddBulkAsync( + IEnumerable userIds, + IEnumerable reasons, + IEnumerable amounts, + bool gamble = false); + + Task RemoveAsync( + ulong userId, + string reason, + long amount, + bool gamble = false); + + Task RemoveAsync( + IUser userId, + string reason, + long amount, + bool sendMessage = false, + bool gamble = false); + + Task RemoveBulkAsync( + IEnumerable userIds, + IEnumerable reasons, + IEnumerable amounts, + bool gamble = false); +} \ No newline at end of file diff --git a/src/NadekoBot/Services/IDataCache.cs b/src/NadekoBot/Services/IDataCache.cs index 4d54df986..48829696e 100644 --- a/src/NadekoBot/Services/IDataCache.cs +++ b/src/NadekoBot/Services/IDataCache.cs @@ -23,7 +23,13 @@ public interface IDataCache bool TryGetEconomy(out string data); void SetEconomy(string data); - Task GetOrAddCachedDataAsync(string key, Func> factory, TParam param, TimeSpan expiry) where TOut : class; + Task GetOrAddCachedDataAsync( + string key, + Func> factory, + TParam param, + TimeSpan expiry) + where TOut : class; + DateTime GetLastCurrencyDecay(); void SetLastCurrencyDecay(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/IEmbedBuilderService.cs b/src/NadekoBot/Services/IEmbedBuilderService.cs index c021d64a9..eb766ff02 100644 --- a/src/NadekoBot/Services/IEmbedBuilderService.cs +++ b/src/NadekoBot/Services/IEmbedBuilderService.cs @@ -16,13 +16,13 @@ public class EmbedBuilderService : IEmbedBuilderService, INService public EmbedBuilderService(BotConfigService botConfigService) => _botConfigService = botConfigService; - public IEmbedBuilder Create(ICommandContext ctx = null) + public IEmbedBuilder Create(ICommandContext ctx = null) => new DiscordEmbedBuilderWrapper(_botConfigService.Data); - - public IEmbedBuilder Create(EmbedBuilder embed) + + public IEmbedBuilder Create(EmbedBuilder embed) => new DiscordEmbedBuilderWrapper(_botConfigService.Data, embed); } - + public sealed class DiscordEmbedBuilderWrapper : IEmbedBuilder { private readonly BotConfig _botConfig; @@ -57,7 +57,7 @@ public sealed class DiscordEmbedBuilderWrapper : IEmbedBuilder public IEmbedBuilder WithThumbnailUrl(string url) => Wrap(_embed.WithThumbnailUrl(url)); - + public IEmbedBuilder WithColor(EmbedColor color) => color switch { @@ -75,4 +75,4 @@ public sealed class DiscordEmbedBuilderWrapper : IEmbedBuilder _embed = eb; return this; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/IGoogleApiService.cs b/src/NadekoBot/Services/IGoogleApiService.cs index fb3696f62..55bae18a7 100644 --- a/src/NadekoBot/Services/IGoogleApiService.cs +++ b/src/NadekoBot/Services/IGoogleApiService.cs @@ -27,7 +27,7 @@ public struct ImageResult public ImageResult(Result.ImageData image, string link) { - this.Image = image; - this.Link = link; + Image = image; + Link = link; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/IImageCache.cs b/src/NadekoBot/Services/IImageCache.cs index f6e134155..05666eef2 100644 --- a/src/NadekoBot/Services/IImageCache.cs +++ b/src/NadekoBot/Services/IImageCache.cs @@ -26,4 +26,4 @@ public interface IImageCache byte[] GetCard(string key); Task Reload(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/ILocalDataCache.cs b/src/NadekoBot/Services/ILocalDataCache.cs index 445b36a93..2bcbbf3b6 100644 --- a/src/NadekoBot/Services/ILocalDataCache.cs +++ b/src/NadekoBot/Services/ILocalDataCache.cs @@ -10,4 +10,4 @@ public interface ILocalDataCache IReadOnlyDictionary PokemonAbilities { get; } IReadOnlyDictionary PokemonMap { get; } TriviaQuestion[] TriviaQuestions { get; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/ILocalization.cs b/src/NadekoBot/Services/ILocalization.cs index 39e163c2e..f5e5439da 100644 --- a/src/NadekoBot/Services/ILocalization.cs +++ b/src/NadekoBot/Services/ILocalization.cs @@ -16,4 +16,4 @@ public interface ILocalization void SetDefaultCulture(CultureInfo ci); void SetGuildCulture(IGuild guild, CultureInfo ci); void SetGuildCulture(ulong guildId, CultureInfo ci); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/INService.cs b/src/NadekoBot/Services/INService.cs index f9879b4e8..13d88b8b6 100644 --- a/src/NadekoBot/Services/INService.cs +++ b/src/NadekoBot/Services/INService.cs @@ -2,8 +2,8 @@ namespace NadekoBot.Services; /// -/// All services must implement this interface in order to be auto-discovered by the DI system +/// All services must implement this interface in order to be auto-discovered by the DI system /// public interface INService { -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/IStatsService.cs b/src/NadekoBot/Services/IStatsService.cs index edfe5eeb0..37f40d0a8 100644 --- a/src/NadekoBot/Services/IStatsService.cs +++ b/src/NadekoBot/Services/IStatsService.cs @@ -4,53 +4,53 @@ namespace NadekoBot.Services; public interface IStatsService { /// - /// The author of the bot. + /// The author of the bot. /// string Author { get; } /// - /// The total amount of commands ran since startup. + /// The total amount of commands ran since startup. /// long CommandsRan { get; } /// - /// The Discord framework used by the bot. + /// The Discord framework used by the bot. /// string Library { get; } /// - /// The amount of messages seen by the bot since startup. + /// The amount of messages seen by the bot since startup. /// long MessageCounter { get; } /// - /// The rate of messages the bot sees every second. + /// The rate of messages the bot sees every second. /// double MessagesPerSecond { get; } /// - /// The total amount of text channels the bot can see. + /// The total amount of text channels the bot can see. /// long TextChannels { get; } /// - /// The total amount of voice channels the bot can see. + /// The total amount of voice channels the bot can see. /// long VoiceChannels { get; } /// - /// Gets for how long the bot has been up since startup. + /// Gets for how long the bot has been up since startup. /// TimeSpan GetUptime(); /// - /// Gets a formatted string of how long the bot has been up since startup. + /// Gets a formatted string of how long the bot has been up since startup. /// /// The formatting separator. string GetUptimeString(string separator = ", "); /// - /// Gets total amount of private memory currently in use by the bot, in Megabytes. + /// Gets total amount of private memory currently in use by the bot, in Megabytes. /// double GetPrivateMemory(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/BehaviorExecutor.cs b/src/NadekoBot/Services/Impl/BehaviorExecutor.cs index 96b43937d..d18dca808 100644 --- a/src/NadekoBot/Services/Impl/BehaviorExecutor.cs +++ b/src/NadekoBot/Services/Impl/BehaviorExecutor.cs @@ -1,6 +1,6 @@ #nullable disable -using NadekoBot.Common.ModuleBehaviors; using Microsoft.Extensions.DependencyInjection; +using NadekoBot.Common.ModuleBehaviors; namespace NadekoBot.Services; @@ -19,20 +19,15 @@ public sealed class BehaviorExecutor : IBehaviourExecutor, INService { _lateExecutors = _services.GetServices(); _lateBlockers = _services.GetServices(); - _earlyBehaviors = _services.GetServices() - .OrderByDescending(x => x.Priority); + _earlyBehaviors = _services.GetServices().OrderByDescending(x => x.Priority); _transformers = _services.GetServices(); } public async Task RunEarlyBehavioursAsync(SocketGuild guild, IUserMessage usrMsg) { foreach (var beh in _earlyBehaviors) - { if (await beh.RunBehavior(guild, usrMsg)) - { return true; - } - } return false; } @@ -43,7 +38,7 @@ public sealed class BehaviorExecutor : IBehaviourExecutor, INService foreach (var exec in _transformers) { string newContent; - if ((newContent = await exec.TransformInput(guild, usrMsg.Channel, usrMsg.Author, messageContent)) + if ((newContent = await exec.TransformInput(guild, usrMsg.Channel, usrMsg.Author, messageContent)) != messageContent.ToLowerInvariant()) { messageContent = newContent; @@ -57,16 +52,14 @@ public sealed class BehaviorExecutor : IBehaviourExecutor, INService public async Task RunLateBlockersAsync(ICommandContext ctx, CommandInfo cmd) { foreach (var exec in _lateBlockers) - { if (await exec.TryBlockLate(ctx, cmd.Module.GetTopLevelModule().Name, cmd)) { - Log.Information("Late blocking User [{0}] Command: [{1}] in [{2}]", + Log.Information("Late blocking User [{0}] Command: [{1}] in [{2}]", ctx.User, cmd.Aliases[0], exec.GetType().Name); return true; } - } return false; } @@ -74,17 +67,13 @@ public sealed class BehaviorExecutor : IBehaviourExecutor, INService public async Task RunLateExecutorsAsync(SocketGuild guild, IUserMessage usrMsg) { foreach (var exec in _lateExecutors) - { try { await exec.LateExecute(guild, usrMsg); } catch (Exception ex) { - Log.Error(ex, "Error in {TypeName} late executor: {ErrorMessage}", - exec.GetType().Name, - ex.Message); + Log.Error(ex, "Error in {TypeName} late executor: {ErrorMessage}", exec.GetType().Name, ex.Message); } - } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/BotCredsProvider.cs b/src/NadekoBot/Services/Impl/BotCredsProvider.cs index 42f677533..8271f798c 100644 --- a/src/NadekoBot/Services/Impl/BotCredsProvider.cs +++ b/src/NadekoBot/Services/Impl/BotCredsProvider.cs @@ -12,63 +12,81 @@ public interface IBotCredsProvider public IBotCredentials GetCreds(); public void ModifyCredsFile(Action func); } - + public sealed class BotCredsProvider : IBotCredsProvider { - private readonly int? _totalShards; private const string _credsFileName = "creds.yml"; private const string _credsExampleFileName = "creds_example.yml"; - - private string CredsPath => Path.Combine(Directory.GetCurrentDirectory(), _credsFileName); - private string CredsExamplePath => Path.Combine(Directory.GetCurrentDirectory(), _credsExampleFileName); - private string OldCredsJsonPath => Path.Combine(Directory.GetCurrentDirectory(), "credentials.json"); - private string OldCredsJsonBackupPath => Path.Combine(Directory.GetCurrentDirectory(), "credentials.json.bak"); - + + private string CredsPath + => Path.Combine(Directory.GetCurrentDirectory(), _credsFileName); + + private string CredsExamplePath + => Path.Combine(Directory.GetCurrentDirectory(), _credsExampleFileName); + + private string OldCredsJsonPath + => Path.Combine(Directory.GetCurrentDirectory(), "credentials.json"); + + private string OldCredsJsonBackupPath + => Path.Combine(Directory.GetCurrentDirectory(), "credentials.json.bak"); + + private readonly int? _totalShards; + private readonly Creds _creds = new(); private readonly IConfigurationRoot _config; - + private readonly object reloadLock = new(); + + public BotCredsProvider(int? totalShards = null) + { + _totalShards = totalShards; + if (!File.Exists(CredsExamplePath)) File.WriteAllText(CredsExamplePath, Yaml.Serializer.Serialize(_creds)); + + MigrateCredentials(); + + if (!File.Exists(CredsPath)) + Log.Warning($"{CredsPath} is missing. " + + "Attempting to load creds from environment variables prefixed with 'NadekoBot_'. " + + $"Example is in {CredsExamplePath}"); + + _config = new ConfigurationBuilder().AddYamlFile(CredsPath, false, true) + .AddEnvironmentVariables("NadekoBot_") + .Build(); + + ChangeToken.OnChange(() => _config.GetReloadToken(), Reload); + + Reload(); + } + public void Reload() { lock (reloadLock) { _creds.OwnerIds.Clear(); _config.Bind(_creds); - + if (string.IsNullOrWhiteSpace(_creds.Token)) { - Log.Error("Token is missing from creds.yml or Environment variables.\n" + - "Add it and restart the program."); + Log.Error("Token is missing from creds.yml or Environment variables.\n" + + "Add it and restart the program."); Helpers.ReadErrorAndExit(5); return; } - + if (string.IsNullOrWhiteSpace(_creds.RestartCommand?.Cmd) || string.IsNullOrWhiteSpace(_creds.RestartCommand?.Args)) { if (Environment.OSVersion.Platform == PlatformID.Unix) - { - _creds.RestartCommand = new() - { - Args = "dotnet", - Cmd = "NadekoBot.dll -- {0}", - }; - } + _creds.RestartCommand = new() { Args = "dotnet", Cmd = "NadekoBot.dll -- {0}" }; else - { - _creds.RestartCommand = new() - { - Args = "NadekoBot.exe", - Cmd = "{0}", - }; - } + _creds.RestartCommand = new() { Args = "NadekoBot.exe", Cmd = "{0}" }; } - + if (string.IsNullOrWhiteSpace(_creds.RedisOptions)) _creds.RedisOptions = "127.0.0.1,syncTimeout=3000"; - + if (string.IsNullOrWhiteSpace(_creds.CoinmarketcapApiKey)) _creds.CoinmarketcapApiKey = "e79ec505-0913-439d-ae07-069e296a6079"; @@ -76,35 +94,6 @@ public sealed class BotCredsProvider : IBotCredsProvider } } - public BotCredsProvider(int? totalShards = null) - { - _totalShards = totalShards; - if (!File.Exists(CredsExamplePath)) - { - File.WriteAllText(CredsExamplePath, Yaml.Serializer.Serialize(_creds)); - } - - MigrateCredentials(); - - if (!File.Exists(CredsPath)) - { - Log.Warning($"{CredsPath} is missing. " + - $"Attempting to load creds from environment variables prefixed with 'NadekoBot_'. " + - $"Example is in {CredsExamplePath}"); - } - - _config = new ConfigurationBuilder() - .AddYamlFile(CredsPath, false, true) - .AddEnvironmentVariables("NadekoBot_") - .Build(); - - ChangeToken.OnChange( - () => _config.GetReloadToken(), - Reload); - - Reload(); - } - public void ModifyCredsFile(Action func) { var ymlData = File.ReadAllText(_credsFileName); @@ -114,13 +103,13 @@ public sealed class BotCredsProvider : IBotCredsProvider ymlData = Yaml.Serializer.Serialize(creds); File.WriteAllText(_credsFileName, ymlData); - + Reload(); } - + /// - /// Checks if there's a V2 credentials file present, loads it if it exists, - /// converts it to new model, and saves it to YAML. Also backs up old credentials to credentials.json.bak + /// Checks if there's a V2 credentials file present, loads it if it exists, + /// converts it to new model, and saves it to YAML. Also backs up old credentials to credentials.json.bak /// private void MigrateCredentials() { @@ -140,25 +129,20 @@ public sealed class BotCredsProvider : IBotCredsProvider OsuApiKey = oldCreds.OsuApiKey, CleverbotApiKey = oldCreds.CleverbotApiKey, TotalShards = oldCreds.TotalShards <= 1 ? 1 : oldCreds.TotalShards, - Patreon = new(oldCreds.PatreonAccessToken, - null, - null, - oldCreds.PatreonCampaignId), - Votes = new(oldCreds.VotesUrl, - oldCreds.VotesToken, - string.Empty, - string.Empty), + Patreon = new(oldCreds.PatreonAccessToken, null, null, oldCreds.PatreonCampaignId), + Votes = new(oldCreds.VotesUrl, oldCreds.VotesToken, string.Empty, string.Empty), BotListToken = oldCreds.BotListToken, RedisOptions = oldCreds.RedisOptions, LocationIqApiKey = oldCreds.LocationIqApiKey, TimezoneDbApiKey = oldCreds.TimezoneDbApiKey, - CoinmarketcapApiKey = oldCreds.CoinmarketcapApiKey, + CoinmarketcapApiKey = oldCreds.CoinmarketcapApiKey }; File.Move(OldCredsJsonPath, OldCredsJsonBackupPath, true); File.WriteAllText(CredsPath, Yaml.Serializer.Serialize(creds)); - Log.Warning("Data from credentials.json has been moved to creds.yml\nPlease inspect your creds.yml for correctness"); + Log.Warning( + "Data from credentials.json has been moved to creds.yml\nPlease inspect your creds.yml for correctness"); } if (File.Exists(_credsFileName)) @@ -170,8 +154,8 @@ public sealed class BotCredsProvider : IBotCredsProvider File.WriteAllText(_credsFileName, Yaml.Serializer.Serialize(creds)); } } - } - public IBotCredentials GetCreds() => _creds; -} + public IBotCredentials GetCreds() + => _creds; +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/CurrencyService.cs b/src/NadekoBot/Services/Impl/CurrencyService.cs index 8caac9090..aa4f717d3 100644 --- a/src/NadekoBot/Services/Impl/CurrencyService.cs +++ b/src/NadekoBot/Services/Impl/CurrencyService.cs @@ -1,8 +1,8 @@ #nullable disable -using NadekoBot.Services.Database; -using NadekoBot.Services.Database.Models; using NadekoBot.Db; using NadekoBot.Modules.Gambling.Services; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; namespace NadekoBot.Services; @@ -13,7 +13,11 @@ public class CurrencyService : ICurrencyService, INService private readonly IEmbedBuilderService _eb; private readonly IUser _bot; - public CurrencyService(DbService db, DiscordSocketClient c, GamblingConfigService gss, IEmbedBuilderService eb) + public CurrencyService( + DbService db, + DiscordSocketClient c, + GamblingConfigService gss, + IEmbedBuilderService eb) { _db = db; _gss = gss; @@ -21,16 +25,18 @@ public class CurrencyService : ICurrencyService, INService _bot = c.CurrentUser; } - private CurrencyTransaction GetCurrencyTransaction(ulong userId, string reason, long amount) => - new() - { - Amount = amount, - UserId = userId, - Reason = reason ?? "-", - }; + private CurrencyTransaction GetCurrencyTransaction(ulong userId, string reason, long amount) + => new() { Amount = amount, UserId = userId, Reason = reason ?? "-" }; - private bool InternalChange(ulong userId, string userName, string discrim, string avatar, - string reason, long amount, bool gamble, NadekoContext uow) + private bool InternalChange( + ulong userId, + string userName, + string discrim, + string avatar, + string reason, + long amount, + bool gamble, + NadekoContext uow) { var result = uow.TryUpdateCurrencyState(userId, userName, discrim, avatar, amount); if (result) @@ -45,47 +51,64 @@ public class CurrencyService : ICurrencyService, INService uow.TryUpdateCurrencyState(_bot.Id, _bot.Username, _bot.Discriminator, _bot.AvatarId, -amount, true); } } + return result; } - private async Task InternalAddAsync(ulong userId, string userName, string discrim, string avatar, string reason, long amount, bool gamble) + private async Task InternalAddAsync( + ulong userId, + string userName, + string discrim, + string avatar, + string reason, + long amount, + bool gamble) { if (amount < 0) - { - throw new ArgumentException("You can't add negative amounts. Use RemoveAsync method for that.", nameof(amount)); - } + throw new ArgumentException("You can't add negative amounts. Use RemoveAsync method for that.", + nameof(amount)); await using var uow = _db.GetDbContext(); InternalChange(userId, userName, discrim, avatar, reason, amount, gamble, uow); await uow.SaveChangesAsync(); } - public Task AddAsync(ulong userId, string reason, long amount, bool gamble = false) + public Task AddAsync( + ulong userId, + string reason, + long amount, + bool gamble = false) => InternalAddAsync(userId, null, null, null, reason, amount, gamble); - public async Task AddAsync(IUser user, string reason, long amount, bool sendMessage = false, bool gamble = false) + public async Task AddAsync( + IUser user, + string reason, + long amount, + bool sendMessage = false, + bool gamble = false) { await InternalAddAsync(user.Id, user.Username, user.Discriminator, user.AvatarId, reason, amount, gamble); if (sendMessage) - { try { var sign = _gss.Data.Currency.Sign; - await user - .EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle($"Received Currency") - .AddField("Amount", amount + sign) - .AddField("Reason", reason)); + await user.EmbedAsync(_eb.Create() + .WithOkColor() + .WithTitle("Received Currency") + .AddField("Amount", amount + sign) + .AddField("Reason", reason)); } catch { // ignored } - } } - public async Task AddBulkAsync(IEnumerable userIds, IEnumerable reasons, IEnumerable amounts, bool gamble = false) + public async Task AddBulkAsync( + IEnumerable userIds, + IEnumerable reasons, + IEnumerable amounts, + bool gamble = false) { var idArray = userIds as ulong[] ?? userIds.ToArray(); var reasonArray = reasons as string[] ?? reasons.ToArray(); @@ -97,15 +120,17 @@ public class CurrencyService : ICurrencyService, INService var userIdHashSet = new HashSet(idArray.Length); await using var uow = _db.GetDbContext(); for (var i = 0; i < idArray.Length; i++) - { // i have to prevent same user changing more than once as it will cause db error if (userIdHashSet.Add(idArray[i])) InternalChange(idArray[i], null, null, null, reasonArray[i], amountArray[i], gamble, uow); - } await uow.SaveChangesAsync(); } - - public async Task RemoveBulkAsync(IEnumerable userIds, IEnumerable reasons, IEnumerable amounts, bool gamble = false) + + public async Task RemoveBulkAsync( + IEnumerable userIds, + IEnumerable reasons, + IEnumerable amounts, + bool gamble = false) { var idArray = userIds as ulong[] ?? userIds.ToArray(); var reasonArray = reasons as string[] ?? reasons.ToArray(); @@ -117,20 +142,24 @@ public class CurrencyService : ICurrencyService, INService var userIdHashSet = new HashSet(idArray.Length); await using var uow = _db.GetDbContext(); for (var i = 0; i < idArray.Length; i++) - { // i have to prevent same user changing more than once as it will cause db error if (userIdHashSet.Add(idArray[i])) InternalChange(idArray[i], null, null, null, reasonArray[i], -amountArray[i], gamble, uow); - } await uow.SaveChangesAsync(); } - private async Task InternalRemoveAsync(ulong userId, string userName, string userDiscrim, string avatar, string reason, long amount, bool gamble = false) + private async Task InternalRemoveAsync( + ulong userId, + string userName, + string userDiscrim, + string avatar, + string reason, + long amount, + bool gamble = false) { if (amount < 0) - { - throw new ArgumentException("You can't remove negative amounts. Use AddAsync method for that.", nameof(amount)); - } + throw new ArgumentException("You can't remove negative amounts. Use AddAsync method for that.", + nameof(amount)); bool result; await using var uow = _db.GetDbContext(); @@ -139,9 +168,18 @@ public class CurrencyService : ICurrencyService, INService return result; } - public Task RemoveAsync(ulong userId, string reason, long amount, bool gamble = false) + public Task RemoveAsync( + ulong userId, + string reason, + long amount, + bool gamble = false) => InternalRemoveAsync(userId, null, null, null, reason, amount, gamble); - public Task RemoveAsync(IUser user, string reason, long amount, bool sendMessage = false, bool gamble = false) + public Task RemoveAsync( + IUser user, + string reason, + long amount, + bool sendMessage = false, + bool gamble = false) => InternalRemoveAsync(user.Id, user.Username, user.Discriminator, user.AvatarId, reason, amount, gamble); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/FontProvider.cs b/src/NadekoBot/Services/Impl/FontProvider.cs index d38751028..2740766ba 100644 --- a/src/NadekoBot/Services/Impl/FontProvider.cs +++ b/src/NadekoBot/Services/Impl/FontProvider.cs @@ -5,6 +5,19 @@ namespace NadekoBot.Services; public class FontProvider : INService { + public FontFamily DottyFont { get; } + + public FontFamily UniSans { get; } + + public FontFamily NotoSans { get; } + //public FontFamily Emojis { get; } + + /// + /// Font used for .rip command + /// + public Font RipFont { get; } + + public List FallBackFonts { get; } private readonly FontCollection _fonts; public FontProvider() @@ -20,44 +33,23 @@ public class FontProvider : INService // try loading some emoji and jap fonts on windows as fallback fonts if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { try { - var fontsfolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Fonts); + var fontsfolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts); FallBackFonts.Add(_fonts.Install(Path.Combine(fontsfolder, "seguiemj.ttf"))); FallBackFonts.AddRange(_fonts.InstallCollection(Path.Combine(fontsfolder, "msgothic.ttc"))); FallBackFonts.AddRange(_fonts.InstallCollection(Path.Combine(fontsfolder, "segoe.ttc"))); } catch { } - } // any fonts present in data/fonts should be added as fallback fonts // this will allow support for special characters when drawing text foreach (var font in Directory.GetFiles(@"data/fonts")) - { if (font.EndsWith(".ttf")) - { FallBackFonts.Add(_fonts.Install(font)); - } - else if (font.EndsWith(".ttc")) - { - FallBackFonts.AddRange(_fonts.InstallCollection(font)); - } - } + else if (font.EndsWith(".ttc")) FallBackFonts.AddRange(_fonts.InstallCollection(font)); RipFont = NotoSans.CreateFont(20, FontStyle.Bold); DottyFont = FallBackFonts.First(x => x.Name == "dotty"); } - - public FontFamily DottyFont { get; } - - public FontFamily UniSans { get; } - public FontFamily NotoSans { get; } - //public FontFamily Emojis { get; } - - /// - /// Font used for .rip command - /// - public Font RipFont { get; } - public List FallBackFonts { get; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/GoogleApiService.cs b/src/NadekoBot/Services/Impl/GoogleApiService.cs index f0088ef5d..7a742a4bf 100644 --- a/src/NadekoBot/Services/Impl/GoogleApiService.cs +++ b/src/NadekoBot/Services/Impl/GoogleApiService.cs @@ -7,6 +7,7 @@ using Google.Apis.YouTube.v3; using Newtonsoft.Json.Linq; using System.Net; using System.Text.RegularExpressions; +using System.Xml; namespace NadekoBot.Services; @@ -14,26 +15,162 @@ public class GoogleApiService : IGoogleApiService, INService { private const string SearchEngineId = "018084019232060951019:hs5piey28-e"; + private static readonly Regex + plRegex = new("(?:youtu\\.be\\/|list=)(?[\\da-zA-Z\\-_]*)", RegexOptions.Compiled); + + public IReadOnlyDictionary Languages { get; } = new Dictionary + { + { "afrikaans", "af" }, + { "albanian", "sq" }, + { "arabic", "ar" }, + { "armenian", "hy" }, + { "azerbaijani", "az" }, + { "basque", "eu" }, + { "belarusian", "be" }, + { "bengali", "bn" }, + { "bulgarian", "bg" }, + { "catalan", "ca" }, + { "chinese-traditional", "zh-TW" }, + { "chinese-simplified", "zh-CN" }, + { "chinese", "zh-CN" }, + { "croatian", "hr" }, + { "czech", "cs" }, + { "danish", "da" }, + { "dutch", "nl" }, + { "english", "en" }, + { "esperanto", "eo" }, + { "estonian", "et" }, + { "filipino", "tl" }, + { "finnish", "fi" }, + { "french", "fr" }, + { "galician", "gl" }, + { "german", "de" }, + { "georgian", "ka" }, + { "greek", "el" }, + { "haitian Creole", "ht" }, + { "hebrew", "iw" }, + { "hindi", "hi" }, + { "hungarian", "hu" }, + { "icelandic", "is" }, + { "indonesian", "id" }, + { "irish", "ga" }, + { "italian", "it" }, + { "japanese", "ja" }, + { "korean", "ko" }, + { "lao", "lo" }, + { "latin", "la" }, + { "latvian", "lv" }, + { "lithuanian", "lt" }, + { "macedonian", "mk" }, + { "malay", "ms" }, + { "maltese", "mt" }, + { "norwegian", "no" }, + { "persian", "fa" }, + { "polish", "pl" }, + { "portuguese", "pt" }, + { "romanian", "ro" }, + { "russian", "ru" }, + { "serbian", "sr" }, + { "slovak", "sk" }, + { "slovenian", "sl" }, + { "spanish", "es" }, + { "swahili", "sw" }, + { "swedish", "sv" }, + { "tamil", "ta" }, + { "telugu", "te" }, + { "thai", "th" }, + { "turkish", "tr" }, + { "ukrainian", "uk" }, + { "urdu", "ur" }, + { "vietnamese", "vi" }, + { "welsh", "cy" }, + { "yiddish", "yi" }, + { "af", "af" }, + { "sq", "sq" }, + { "ar", "ar" }, + { "hy", "hy" }, + { "az", "az" }, + { "eu", "eu" }, + { "be", "be" }, + { "bn", "bn" }, + { "bg", "bg" }, + { "ca", "ca" }, + { "zh-tw", "zh-TW" }, + { "zh-cn", "zh-CN" }, + { "hr", "hr" }, + { "cs", "cs" }, + { "da", "da" }, + { "nl", "nl" }, + { "en", "en" }, + { "eo", "eo" }, + { "et", "et" }, + { "tl", "tl" }, + { "fi", "fi" }, + { "fr", "fr" }, + { "gl", "gl" }, + { "de", "de" }, + { "ka", "ka" }, + { "el", "el" }, + { "ht", "ht" }, + { "iw", "iw" }, + { "hi", "hi" }, + { "hu", "hu" }, + { "is", "is" }, + { "id", "id" }, + { "ga", "ga" }, + { "it", "it" }, + { "ja", "ja" }, + { "ko", "ko" }, + { "lo", "lo" }, + { "la", "la" }, + { "lv", "lv" }, + { "lt", "lt" }, + { "mk", "mk" }, + { "ms", "ms" }, + { "mt", "mt" }, + { "no", "no" }, + { "fa", "fa" }, + { "pl", "pl" }, + { "pt", "pt" }, + { "ro", "ro" }, + { "ru", "ru" }, + { "sr", "sr" }, + { "sk", "sk" }, + { "sl", "sl" }, + { "es", "es" }, + { "sw", "sw" }, + { "sv", "sv" }, + { "ta", "ta" }, + { "te", "te" }, + { "th", "th" }, + { "tr", "tr" }, + { "uk", "uk" }, + { "ur", "ur" }, + { "vi", "vi" }, + { "cy", "cy" }, + { "yi", "yi" } + }; + private readonly YouTubeService yt; private readonly UrlshortenerService sh; private readonly CustomsearchService cs; - + + //private readonly Regex YtVideoIdRegex = new Regex(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)(?[a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled); + private readonly IBotCredentials _creds; + private readonly IHttpClientFactory _httpFactory; + public GoogleApiService(IBotCredentials creds, IHttpClientFactory factory) { _creds = creds; _httpFactory = factory; - var bcs = new BaseClientService.Initializer - { - ApplicationName = "Nadeko Bot", - ApiKey = _creds.GoogleApiKey, - }; - + var bcs = new BaseClientService.Initializer { ApplicationName = "Nadeko Bot", ApiKey = _creds.GoogleApiKey }; + yt = new(bcs); sh = new(bcs); cs = new(bcs); } - private static readonly Regex plRegex = new("(?:youtu\\.be\\/|list=)(?[\\da-zA-Z\\-_]*)", RegexOptions.Compiled); + public async Task> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1) { await Task.Yield(); @@ -44,10 +181,7 @@ public class GoogleApiService : IGoogleApiService, INService throw new ArgumentOutOfRangeException(nameof(count)); var match = plRegex.Match(keywords); - if (match.Length > 1) - { - return new[] { match.Groups["id"].Value.ToString() }; - } + if (match.Length > 1) return new[] { match.Groups["id"].Value }; var query = yt.Search.List("snippet"); query.MaxResults = count; query.Type = "playlist"; @@ -56,10 +190,6 @@ public class GoogleApiService : IGoogleApiService, INService return (await query.ExecuteAsync()).Items.Select(i => i.Id.PlaylistId); } - //private readonly Regex YtVideoIdRegex = new Regex(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)(?[a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled); - private readonly IBotCredentials _creds; - private readonly IHttpClientFactory _httpFactory; - // todo future add quota users public async Task> GetRelatedVideosAsync(string id, int count = 1) { @@ -93,7 +223,9 @@ public class GoogleApiService : IGoogleApiService, INService return (await query.ExecuteAsync()).Items.Select(i => "http://www.youtube.com/watch?v=" + i.Id.VideoId); } - public async Task> GetVideoInfosByKeywordAsync(string keywords, int count = 1) + public async Task> GetVideoInfosByKeywordAsync( + string keywords, + int count = 1) { await Task.Yield(); if (string.IsNullOrWhiteSpace(keywords)) @@ -106,10 +238,12 @@ public class GoogleApiService : IGoogleApiService, INService query.MaxResults = count; query.Q = keywords; query.Type = "video"; - return (await query.ExecuteAsync()).Items.Select(i => (i.Snippet.Title.TrimTo(50), i.Id.VideoId, "http://www.youtube.com/watch?v=" + i.Id.VideoId)); + return (await query.ExecuteAsync()).Items.Select(i + => (i.Snippet.Title.TrimTo(50), i.Id.VideoId, "http://www.youtube.com/watch?v=" + i.Id.VideoId)); } - public Task ShortenUrl(Uri url) => ShortenUrl(url.ToString()); + public Task ShortenUrl(Uri url) + => ShortenUrl(url.ToString()); public async Task ShortenUrl(string url) { @@ -163,8 +297,7 @@ public class GoogleApiService : IGoogleApiService, INService toReturn.AddRange(data.Items.Select(i => i.ContentDetails.VideoId)); nextPageToken = data.NextPageToken; - } - while (count > 0 && !string.IsNullOrWhiteSpace(nextPageToken)); + } while (count > 0 && !string.IsNullOrWhiteSpace(nextPageToken)); return toReturn; } @@ -189,12 +322,8 @@ public class GoogleApiService : IGoogleApiService, INService q.Id = string.Join(",", videoIdsList.Take(toGet)); videoIdsList = videoIdsList.Skip(toGet).ToList(); var items = (await q.ExecuteAsync()).Items; - foreach (var i in items) - { - toReturn.Add(i.Id, System.Xml.XmlConvert.ToTimeSpan(i.ContentDetails.Duration)); - } - } - while (remaining > 0); + foreach (var i in items) toReturn.Add(i.Id, XmlConvert.ToTimeSpan(i.ContentDetails.Duration)); + } while (remaining > 0); return toReturn; } @@ -218,156 +347,24 @@ public class GoogleApiService : IGoogleApiService, INService return new(search.Items[0].Image, search.Items[0].Link); } - public IReadOnlyDictionary Languages { get; } = new Dictionary() { - { "afrikaans", "af"}, - { "albanian", "sq"}, - { "arabic", "ar"}, - { "armenian", "hy"}, - { "azerbaijani", "az"}, - { "basque", "eu"}, - { "belarusian", "be"}, - { "bengali", "bn"}, - { "bulgarian", "bg"}, - { "catalan", "ca"}, - { "chinese-traditional", "zh-TW"}, - { "chinese-simplified", "zh-CN"}, - { "chinese", "zh-CN"}, - { "croatian", "hr"}, - { "czech", "cs"}, - { "danish", "da"}, - { "dutch", "nl"}, - { "english", "en"}, - { "esperanto", "eo"}, - { "estonian", "et"}, - { "filipino", "tl"}, - { "finnish", "fi"}, - { "french", "fr"}, - { "galician", "gl"}, - { "german", "de"}, - { "georgian", "ka"}, - { "greek", "el"}, - { "haitian Creole", "ht"}, - { "hebrew", "iw"}, - { "hindi", "hi"}, - { "hungarian", "hu"}, - { "icelandic", "is"}, - { "indonesian", "id"}, - { "irish", "ga"}, - { "italian", "it"}, - { "japanese", "ja"}, - { "korean", "ko"}, - { "lao", "lo"}, - { "latin", "la"}, - { "latvian", "lv"}, - { "lithuanian", "lt"}, - { "macedonian", "mk"}, - { "malay", "ms"}, - { "maltese", "mt"}, - { "norwegian", "no"}, - { "persian", "fa"}, - { "polish", "pl"}, - { "portuguese", "pt"}, - { "romanian", "ro"}, - { "russian", "ru"}, - { "serbian", "sr"}, - { "slovak", "sk"}, - { "slovenian", "sl"}, - { "spanish", "es"}, - { "swahili", "sw"}, - { "swedish", "sv"}, - { "tamil", "ta"}, - { "telugu", "te"}, - { "thai", "th"}, - { "turkish", "tr"}, - { "ukrainian", "uk"}, - { "urdu", "ur"}, - { "vietnamese", "vi"}, - { "welsh", "cy"}, - { "yiddish", "yi"}, - - { "af", "af"}, - { "sq", "sq"}, - { "ar", "ar"}, - { "hy", "hy"}, - { "az", "az"}, - { "eu", "eu"}, - { "be", "be"}, - { "bn", "bn"}, - { "bg", "bg"}, - { "ca", "ca"}, - { "zh-tw", "zh-TW"}, - { "zh-cn", "zh-CN"}, - { "hr", "hr"}, - { "cs", "cs"}, - { "da", "da"}, - { "nl", "nl"}, - { "en", "en"}, - { "eo", "eo"}, - { "et", "et"}, - { "tl", "tl"}, - { "fi", "fi"}, - { "fr", "fr"}, - { "gl", "gl"}, - { "de", "de"}, - { "ka", "ka"}, - { "el", "el"}, - { "ht", "ht"}, - { "iw", "iw"}, - { "hi", "hi"}, - { "hu", "hu"}, - { "is", "is"}, - { "id", "id"}, - { "ga", "ga"}, - { "it", "it"}, - { "ja", "ja"}, - { "ko", "ko"}, - { "lo", "lo"}, - { "la", "la"}, - { "lv", "lv"}, - { "lt", "lt"}, - { "mk", "mk"}, - { "ms", "ms"}, - { "mt", "mt"}, - { "no", "no"}, - { "fa", "fa"}, - { "pl", "pl"}, - { "pt", "pt"}, - { "ro", "ro"}, - { "ru", "ru"}, - { "sr", "sr"}, - { "sk", "sk"}, - { "sl", "sl"}, - { "es", "es"}, - { "sw", "sw"}, - { "sv", "sv"}, - { "ta", "ta"}, - { "te", "te"}, - { "th", "th"}, - { "tr", "tr"}, - { "uk", "uk"}, - { "ur", "ur"}, - { "vi", "vi"}, - { "cy", "cy"}, - { "yi", "yi"}, - }; - public async Task Translate(string sourceText, string sourceLanguage, string targetLanguage) { await Task.Yield(); string text; - if (!Languages.ContainsKey(sourceLanguage) || - !Languages.ContainsKey(targetLanguage)) + if (!Languages.ContainsKey(sourceLanguage) || !Languages.ContainsKey(targetLanguage)) throw new ArgumentException(nameof(sourceLanguage) + "/" + nameof(targetLanguage)); - var url = new Uri(string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}", + var url = new Uri(string.Format( + "https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}", ConvertToLanguageCode(sourceLanguage), ConvertToLanguageCode(targetLanguage), WebUtility.UrlEncode(sourceText))); using (var http = _httpFactory.CreateClient()) { - http.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"); + http.DefaultRequestHeaders.Add("user-agent", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"); text = await http.GetStringAsync(url); } @@ -379,4 +376,4 @@ public class GoogleApiService : IGoogleApiService, INService Languages.TryGetValue(language, out var mode); return mode; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/Localization.cs b/src/NadekoBot/Services/Impl/Localization.cs index 36979ad0b..580312e20 100644 --- a/src/NadekoBot/Services/Impl/Localization.cs +++ b/src/NadekoBot/Services/Impl/Localization.cs @@ -1,45 +1,50 @@ #nullable disable -using System.Globalization; -using Newtonsoft.Json; using NadekoBot.Db; +using Newtonsoft.Json; +using System.Globalization; namespace NadekoBot.Services; public class Localization : ILocalization, INService { - private readonly BotConfigService _bss; - private readonly DbService _db; + private static readonly Dictionary _commandData = + JsonConvert.DeserializeObject>( + File.ReadAllText("./data/strings/commands/commands.en-US.json")); public ConcurrentDictionary GuildCultureInfos { get; } - public CultureInfo DefaultCultureInfo => _bss.Data.DefaultLocale; - private static readonly Dictionary _commandData = JsonConvert.DeserializeObject>( - File.ReadAllText("./data/strings/commands/commands.en-US.json")); + public CultureInfo DefaultCultureInfo + => _bss.Data.DefaultLocale; + + private readonly BotConfigService _bss; + private readonly DbService _db; public Localization(BotConfigService bss, Bot bot, DbService db) { _bss = bss; _db = db; - var cultureInfoNames = bot.AllGuildConfigs - .ToDictionary(x => x.GuildId, x => x.Locale); - - GuildCultureInfos = new(cultureInfoNames.ToDictionary(x => x.Key, x => - { - CultureInfo cultureInfo = null; - try - { - if (x.Value is null) - return null; - cultureInfo = new(x.Value); - } - catch { } - return cultureInfo; - }).Where(x => x.Value != null)); + var cultureInfoNames = bot.AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale); + + GuildCultureInfos = new(cultureInfoNames.ToDictionary(x => x.Key, + x => + { + CultureInfo cultureInfo = null; + try + { + if (x.Value is null) + return null; + cultureInfo = new(x.Value); + } + catch { } + + return cultureInfo; + }) + .Where(x => x.Value != null)); } - public void SetGuildCulture(IGuild guild, CultureInfo ci) => - SetGuildCulture(guild.Id, ci); + public void SetGuildCulture(IGuild guild, CultureInfo ci) + => SetGuildCulture(guild.Id, ci); public void SetGuildCulture(ulong guildId, CultureInfo ci) { @@ -59,12 +64,11 @@ public class Localization : ILocalization, INService GuildCultureInfos.AddOrUpdate(guildId, ci, (id, old) => ci); } - public void RemoveGuildCulture(IGuild guild) => - RemoveGuildCulture(guild.Id); + public void RemoveGuildCulture(IGuild guild) + => RemoveGuildCulture(guild.Id); public void RemoveGuildCulture(ulong guildId) { - if (GuildCultureInfos.TryRemove(guildId, out var _)) { using var uow = _db.GetDbContext(); @@ -80,17 +84,17 @@ public class Localization : ILocalization, INService bs.DefaultLocale = ci; }); - public void ResetDefaultCulture() => - SetDefaultCulture(CultureInfo.CurrentCulture); + public void ResetDefaultCulture() + => SetDefaultCulture(CultureInfo.CurrentCulture); - public CultureInfo GetCultureInfo(IGuild guild) => - GetCultureInfo(guild?.Id); + public CultureInfo GetCultureInfo(IGuild guild) + => GetCultureInfo(guild?.Id); public CultureInfo GetCultureInfo(ulong? guildId) { if (guildId is null || !GuildCultureInfos.TryGetValue(guildId.Value, out var info) || info is null) return _bss.Data.DefaultLocale; - + return info; } @@ -99,13 +103,8 @@ public class Localization : ILocalization, INService _commandData.TryGetValue(key, out var toReturn); if (toReturn is null) - return new() - { - Cmd = key, - Desc = key, - Usage = new[] { key }, - }; + return new() { Cmd = key, Desc = key, Usage = new[] { key } }; return toReturn; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/RedisCache.cs b/src/NadekoBot/Services/Impl/RedisCache.cs index 453033d29..875c44455 100644 --- a/src/NadekoBot/Services/Impl/RedisCache.cs +++ b/src/NadekoBot/Services/Impl/RedisCache.cs @@ -15,8 +15,13 @@ public class RedisCache : IDataCache private readonly string _redisKey; private readonly EndPoint _redisEndpoint; - public RedisCache(ConnectionMultiplexer redis, IBotCredentials creds, - IImageCache imageCache, ILocalDataCache dataCache) + private readonly object timelyLock = new(); + + public RedisCache( + ConnectionMultiplexer redis, + IBotCredentials creds, + IImageCache imageCache, + ILocalDataCache dataCache) { Redis = redis; _redisEndpoint = Redis.GetEndPoints().First(); @@ -52,7 +57,7 @@ public class RedisCache : IDataCache public Task SetAnimeDataAsync(string key, string data) { var _db = Redis.GetDatabase(); - return _db.StringSetAsync("anime_" + key, data, expiry: TimeSpan.FromHours(3)); + return _db.StringSetAsync("anime_" + key, data, TimeSpan.FromHours(3)); } public async Task<(bool Success, string Data)> TryGetNovelDataAsync(string key) @@ -65,10 +70,9 @@ public class RedisCache : IDataCache public Task SetNovelDataAsync(string key, string data) { var _db = Redis.GetDatabase(); - return _db.StringSetAsync("novel_" + key, data, expiry: TimeSpan.FromHours(3)); + return _db.StringSetAsync("novel_" + key, data, TimeSpan.FromHours(3)); } - private readonly object timelyLock = new(); public TimeSpan? AddTimelyClaim(ulong id, int period) { if (period == 0) @@ -82,6 +86,7 @@ public class RedisCache : IDataCache _db.StringSet($"{_redisKey}_timelyclaim_{id}", true, time); return null; } + return _db.KeyTimeToLive($"{_redisKey}_timelyclaim_{id}"); } } @@ -91,9 +96,7 @@ public class RedisCache : IDataCache var server = Redis.GetServer(_redisEndpoint); var _db = Redis.GetDatabase(); foreach (var k in server.Keys(pattern: $"{_redisKey}_timelyclaim_*")) - { _db.KeyDelete(k, CommandFlags.FireAndForget); - } } public bool TryAddAffinityCooldown(ulong userId, out TimeSpan? time) @@ -106,6 +109,7 @@ public class RedisCache : IDataCache _db.StringSet($"{_redisKey}_affinity_{userId}", true, time); return true; } + return false; } @@ -119,13 +123,14 @@ public class RedisCache : IDataCache _db.StringSet($"{_redisKey}_divorce_{userId}", true, time); return true; } + return false; } public Task SetStreamDataAsync(string url, string data) { var _db = Redis.GetDatabase(); - return _db.StringSetAsync($"{_redisKey}_stream_{url}", data, expiry: TimeSpan.FromHours(6)); + return _db.StringSetAsync($"{_redisKey}_stream_{url}", data, TimeSpan.FromHours(6)); } public bool TryGetStreamData(string url, out string dataStr) @@ -143,9 +148,7 @@ public class RedisCache : IDataCache 0, // i don't use the value TimeSpan.FromSeconds(expireIn), When.NotExists)) - { return null; - } return _db.KeyTimeToLive($"{_redisKey}_ratelimit_{id}_{name}"); } @@ -153,10 +156,7 @@ public class RedisCache : IDataCache public bool TryGetEconomy(out string data) { var _db = Redis.GetDatabase(); - if ((data = _db.StringGet($"{_redisKey}_economy")) != null) - { - return true; - } + if ((data = _db.StringGet($"{_redisKey}_economy")) != null) return true; return false; } @@ -164,12 +164,15 @@ public class RedisCache : IDataCache public void SetEconomy(string data) { var _db = Redis.GetDatabase(); - _db.StringSet($"{_redisKey}_economy", - data, - expiry: TimeSpan.FromMinutes(3)); + _db.StringSet($"{_redisKey}_economy", data, TimeSpan.FromMinutes(3)); } - public async Task GetOrAddCachedDataAsync(string key, Func> factory, TParam param, TimeSpan expiry) where TOut : class + public async Task GetOrAddCachedDataAsync( + string key, + Func> factory, + TParam param, + TimeSpan expiry) + where TOut : class { var _db = Redis.GetDatabase(); @@ -179,13 +182,13 @@ public class RedisCache : IDataCache var obj = await factory(param); if (obj is null) - return default(TOut); + return default; - await _db.StringSetAsync(key, JsonConvert.SerializeObject(obj), - expiry: expiry); + await _db.StringSetAsync(key, JsonConvert.SerializeObject(obj), expiry); return obj; } + return (TOut)JsonConvert.DeserializeObject(data, typeof(TOut)); } @@ -194,7 +197,7 @@ public class RedisCache : IDataCache var db = Redis.GetDatabase(); var str = (string)db.StringGet($"{_redisKey}_last_currency_decay"); - if(string.IsNullOrEmpty(str)) + if (string.IsNullOrEmpty(str)) return DateTime.MinValue; return JsonConvert.DeserializeObject(str); @@ -206,4 +209,4 @@ public class RedisCache : IDataCache db.StringSet($"{_redisKey}_last_currency_decay", JsonConvert.SerializeObject(DateTime.UtcNow)); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/RedisImageExtensions.cs b/src/NadekoBot/Services/Impl/RedisImageExtensions.cs index 219a802d2..138935c32 100644 --- a/src/NadekoBot/Services/Impl/RedisImageExtensions.cs +++ b/src/NadekoBot/Services/Impl/RedisImageExtensions.cs @@ -5,7 +5,7 @@ public static class RedisImageExtensions { private const string OldCdnUrl = "nadeko-pictures.nyc3.digitaloceanspaces.com"; private const string NewCdnUrl = "cdn.nadeko.bot"; - + public static Uri ToNewCdn(this Uri uri) => new(uri.ToString().Replace(OldCdnUrl, NewCdnUrl)); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/RedisImagesCache.cs b/src/NadekoBot/Services/Impl/RedisImagesCache.cs index 9503d499b..3ccdda1c6 100644 --- a/src/NadekoBot/Services/Impl/RedisImagesCache.cs +++ b/src/NadekoBot/Services/Impl/RedisImagesCache.cs @@ -1,26 +1,13 @@ #nullable disable -using Newtonsoft.Json; -using StackExchange.Redis; using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.Yml; +using Newtonsoft.Json; +using StackExchange.Redis; namespace NadekoBot.Services; public sealed class RedisImagesCache : IImageCache, IReadyExecutor { - private readonly ConnectionMultiplexer _con; - private readonly IBotCredentials _creds; - private readonly HttpClient _http; - private readonly string _imagesPath; - - private IDatabase Db - => _con.GetDatabase(); - - private const string BASE_PATH = "data/"; - private const string CARDS_PATH = $"{BASE_PATH}images/cards"; - - public ImageUrls ImageUrls { get; private set; } - public enum ImageKeys { CoinHeads, @@ -36,6 +23,14 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor XpBg } + private const string BASE_PATH = "data/"; + private const string CARDS_PATH = $"{BASE_PATH}images/cards"; + + private IDatabase Db + => _con.GetDatabase(); + + public ImageUrls ImageUrls { get; private set; } + public IReadOnlyList Heads => GetByteArrayData(ImageKeys.CoinHeads); @@ -69,17 +64,10 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor public byte[] RipOverlay => GetByteData(ImageKeys.RipOverlay); - public byte[] GetCard(string key) - // since cards are always local for now, don't cache them - => File.ReadAllBytes(Path.Join(CARDS_PATH, key + ".jpg")); - - public async Task OnReadyAsync() - { - if (await AllKeysExist()) - return; - - await Reload(); - } + private readonly ConnectionMultiplexer _con; + private readonly IBotCredentials _creds; + private readonly HttpClient _http; + private readonly string _imagesPath; public RedisImagesCache(ConnectionMultiplexer con, IBotCredentials creds) { @@ -93,6 +81,18 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor ImageUrls = Yaml.Deserializer.Deserialize(File.ReadAllText(_imagesPath)); } + public byte[] GetCard(string key) + // since cards are always local for now, don't cache them + => File.ReadAllBytes(Path.Join(CARDS_PATH, key + ".jpg")); + + public async Task OnReadyAsync() + { + if (await AllKeysExist()) + return; + + await Reload(); + } + private void Migrate() { // migrate to yml @@ -105,21 +105,22 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor if (oldData is not null) { - var newData = new ImageUrls() + var newData = new ImageUrls { Coins = new() { - Heads = oldData.Coins.Heads.Length == 1 && - oldData.Coins.Heads[0].ToString() == - "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/heads.png" - ? new[] { new Uri("https://cdn.nadeko.bot/coins/heads3.png") } - : oldData.Coins.Heads, - Tails = oldData.Coins.Tails.Length == 1 && - oldData.Coins.Tails[0].ToString() == - "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/tails.png" + Heads = + oldData.Coins.Heads.Length == 1 + && oldData.Coins.Heads[0].ToString() + == "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/heads.png" + ? new[] { new Uri("https://cdn.nadeko.bot/coins/heads3.png") } + : oldData.Coins.Heads, + Tails = oldData.Coins.Tails.Length == 1 + && oldData.Coins.Tails[0].ToString() + == "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/tails.png" ? new[] { new Uri("https://cdn.nadeko.bot/coins/tails3.png") } - : oldData.Coins.Tails, + : oldData.Coins.Tails }, Dice = oldData.Dice.Map(x => x.ToNewCdn()), Currency = oldData.Currency.Map(x => x.ToNewCdn()), @@ -128,7 +129,7 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor { Dot = oldData.Rategirl.Dot.ToNewCdn(), Matrix = oldData.Rategirl.Matrix.ToNewCdn() }, - Rip = new() { Bg = oldData.Rip.Bg.ToNewCdn(), Overlay = oldData.Rip.Overlay.ToNewCdn(), }, + Rip = new() { Bg = oldData.Rip.Bg.ToNewCdn(), Overlay = oldData.Rip.Overlay.ToNewCdn() }, Slots = new() { Bg = new("https://cdn.nadeko.bot/slots/slots_bg.png"), @@ -139,8 +140,8 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor "https://cdn.nadeko.bot/slots/4.png", "https://cdn.nadeko.bot/slots/5.png" }.Map(x => new Uri(x)) }, - Xp = new() { Bg = oldData.Xp.Bg.ToNewCdn(), }, - Version = 2, + Xp = new() { Bg = oldData.Xp.Bg.ToNewCdn() }, + Version = 2 }; File.Move(oldFilePath, backupFilePath, true); @@ -161,7 +162,6 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor { ImageUrls = Yaml.Deserializer.Deserialize(await File.ReadAllTextAsync(_imagesPath)); foreach (var key in GetAllKeys()) - { switch (key) { case ImageKeys.CoinHeads: @@ -200,7 +200,6 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor default: throw new ArgumentOutOfRangeException(); } - } } private async Task Load(ImageKeys key, Uri uri) @@ -221,20 +220,17 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor await Db.ListRightPushAsync(GetRedisKey(key), vals); if (uris.Length != vals.Length) - { - Log.Information("{Loaded}/{Max} URIs for the key '{ImageKey}' have been loaded.\n" + - "Some of the supplied URIs are either unavailable or invalid", + Log.Information( + "{Loaded}/{Max} URIs for the key '{ImageKey}' have been loaded.\n" + + "Some of the supplied URIs are either unavailable or invalid", vals.Length, uris.Length, - key - ); - } + key); } private async Task GetImageData(Uri uri) { if (uri.IsFile) - { try { var bytes = await File.ReadAllBytesAsync(uri.LocalPath); @@ -245,7 +241,6 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor Log.Warning(ex, "Failed reading image bytes from uri: {Uri}", uri.ToString()); return null; } - } try { @@ -260,9 +255,7 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor private async Task AllKeysExist() { - var tasks = await GetAllKeys() - .Select(x => Db.KeyExistsAsync(GetRedisKey(x))) - .WhenAll(); + var tasks = await GetAllKeys().Select(x => Db.KeyExistsAsync(GetRedisKey(x))).WhenAll(); return tasks.All(exist => exist); } @@ -278,4 +271,4 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor private RedisKey GetRedisKey(ImageKeys key) => _creds.RedisKey() + "_image_" + key; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/RedisLocalDataCache.cs b/src/NadekoBot/Services/Impl/RedisLocalDataCache.cs index 81346542e..3d6852d76 100644 --- a/src/NadekoBot/Services/Impl/RedisLocalDataCache.cs +++ b/src/NadekoBot/Services/Impl/RedisLocalDataCache.cs @@ -8,9 +8,6 @@ namespace NadekoBot.Services; public class RedisLocalDataCache : ILocalDataCache { - private readonly ConnectionMultiplexer _con; - private readonly IBotCredentials _creds; - private const string POKEMON_ABILITIES_FILE = "data/pokemon/pokemon_abilities.json"; private const string POKEMON_LIST_FILE = "data/pokemon/pokemon_list.json"; private const string POKEMON_MAP_PATH = "data/pokemon/name-id_map.json"; @@ -40,6 +37,9 @@ public class RedisLocalDataCache : ILocalDataCache private init => Set("pokemon_map", value); } + private readonly ConnectionMultiplexer _con; + private readonly IBotCredentials _creds; + public RedisLocalDataCache(ConnectionMultiplexer con, IBotCredentials creds, DiscordSocketClient client) { _con = con; @@ -49,32 +49,25 @@ public class RedisLocalDataCache : ILocalDataCache if (shardId == 0) { if (!File.Exists(POKEMON_LIST_FILE)) - { Log.Warning($"{POKEMON_LIST_FILE} is missing. Pokemon abilities not loaded"); - } else - { Pokemons = - JsonConvert.DeserializeObject>(File.ReadAllText(POKEMON_LIST_FILE)); - } + JsonConvert.DeserializeObject>( + File.ReadAllText(POKEMON_LIST_FILE)); if (!File.Exists(POKEMON_ABILITIES_FILE)) - { Log.Warning($"{POKEMON_ABILITIES_FILE} is missing. Pokemon abilities not loaded."); - } else - { PokemonAbilities = JsonConvert.DeserializeObject>( - File.ReadAllText(POKEMON_ABILITIES_FILE) - ); - } + File.ReadAllText(POKEMON_ABILITIES_FILE)); try { TriviaQuestions = JsonConvert.DeserializeObject(File.ReadAllText(QUESTIONS_FILE)); PokemonMap = JsonConvert.DeserializeObject(File.ReadAllText(POKEMON_MAP_PATH)) - ?.ToDictionary(x => x.Id, x => x.Name) ?? new(); + ?.ToDictionary(x => x.Id, x => x.Name) + ?? new(); } catch (Exception ex) { @@ -90,4 +83,4 @@ public class RedisLocalDataCache : ILocalDataCache private void Set(string key, object obj) => _con.GetDatabase().StringSet($"{_creds.RedisKey()}_localdata_{key}", JsonConvert.SerializeObject(obj)); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/RemoteGrpcCoordinator.cs b/src/NadekoBot/Services/Impl/RemoteGrpcCoordinator.cs index 9890d6aaf..5b0df026a 100644 --- a/src/NadekoBot/Services/Impl/RemoteGrpcCoordinator.cs +++ b/src/NadekoBot/Services/Impl/RemoteGrpcCoordinator.cs @@ -1,5 +1,6 @@ #nullable disable using Grpc.Core; +using Grpc.Net.Client; using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Coordinator; @@ -12,37 +13,26 @@ public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor public RemoteGrpcCoordinator(IBotCredentials creds, DiscordSocketClient client) { - var coordUrl = string.IsNullOrWhiteSpace(creds.CoordinatorUrl) - ? "http://localhost:3442" - : creds.CoordinatorUrl; - - var channel = Grpc.Net.Client.GrpcChannel.ForAddress(coordUrl); + var coordUrl = string.IsNullOrWhiteSpace(creds.CoordinatorUrl) ? "http://localhost:3442" : creds.CoordinatorUrl; + + var channel = GrpcChannel.ForAddress(coordUrl); _coordClient = new(channel); _client = client; } - + public bool RestartBot() { - _coordClient.RestartAllShards(new() - { - - }); + _coordClient.RestartAllShards(new()); return true; } public void Die(bool graceful) - => _coordClient.Die(new() - { - Graceful = graceful - }); + => _coordClient.Die(new() { Graceful = graceful }); public bool RestartShard(int shardId) { - _coordClient.RestartShard(new() - { - ShardId = shardId, - }); + _coordClient.RestartShard(new() { ShardId = shardId }); return true; } @@ -51,15 +41,14 @@ public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor { var res = _coordClient.GetAllStatuses(new()); - return res.Statuses - .ToArray() - .Map(s => new ShardStatus() - { - ConnectionState = FromCoordConnState(s.State), - GuildCount = s.GuildCount, - ShardId = s.ShardId, - LastUpdate = s.LastUpdate.ToDateTime(), - }); + return res.Statuses.ToArray() + .Map(s => new ShardStatus + { + ConnectionState = FromCoordConnState(s.State), + GuildCount = s.GuildCount, + ShardId = s.ShardId, + LastUpdate = s.LastUpdate.ToDateTime() + }); } public int GetGuildCount() @@ -82,18 +71,21 @@ public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor try { var reply = await _coordClient.HeartbeatAsync(new() - { - State = ToCoordConnState(_client.ConnectionState), - GuildCount = _client.ConnectionState == ConnectionState.Connected ? _client.Guilds.Count : 0, - ShardId = _client.ShardId, - }, deadline: DateTime.UtcNow + TimeSpan.FromSeconds(10)); + { + State = ToCoordConnState(_client.ConnectionState), + GuildCount = + _client.ConnectionState == ConnectionState.Connected ? _client.Guilds.Count : 0, + ShardId = _client.ShardId + }, + deadline: DateTime.UtcNow + TimeSpan.FromSeconds(10)); gracefulImminent = reply.GracefulImminent; } catch (RpcException ex) { if (!gracefulImminent) { - Log.Warning(ex, "Hearbeat failed and graceful shutdown was not expected: {Message}", + Log.Warning(ex, + "Hearbeat failed and graceful shutdown was not expected: {Message}", ex.Message); break; } @@ -130,4 +122,4 @@ public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor ConnState.Connected => ConnectionState.Connected, _ => ConnectionState.Disconnected }; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/SingleProcessCoordinator.cs b/src/NadekoBot/Services/Impl/SingleProcessCoordinator.cs index c0418dd4a..8c43de83a 100644 --- a/src/NadekoBot/Services/Impl/SingleProcessCoordinator.cs +++ b/src/NadekoBot/Services/Impl/SingleProcessCoordinator.cs @@ -13,6 +13,7 @@ public class SingleProcessCoordinator : ICoordinator _creds = creds; _client = client; } + public bool RestartBot() { if (string.IsNullOrWhiteSpace(_creds.RestartCommand?.Cmd) @@ -21,7 +22,7 @@ public class SingleProcessCoordinator : ICoordinator Log.Error("You must set RestartCommand.Cmd and RestartCommand.Args in creds.yml"); return false; } - + Process.Start(_creds.RestartCommand.Cmd, _creds.RestartCommand.Args); _ = Task.Run(async () => { @@ -40,7 +41,7 @@ public class SingleProcessCoordinator : ICoordinator public IList GetAllShardStatuses() => new[] { - new ShardStatus() + new ShardStatus { ConnectionState = _client.ConnectionState, GuildCount = _client.Guilds.Count, @@ -54,4 +55,4 @@ public class SingleProcessCoordinator : ICoordinator public Task Reload() => Task.CompletedTask; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/SoundCloudApiService.cs b/src/NadekoBot/Services/Impl/SoundCloudApiService.cs index ea2b69d31..9bc250d26 100644 --- a/src/NadekoBot/Services/Impl/SoundCloudApiService.cs +++ b/src/NadekoBot/Services/Impl/SoundCloudApiService.cs @@ -21,7 +21,7 @@ public class SoundCloudApiService : INService { response = await http.GetStringAsync($"https://scapi.nadeko.bot/resolve?url={url}"); } - + var responseObj = JsonConvert.DeserializeObject(response); if (responseObj?.Kind != "track") throw new InvalidOperationException("Url is either not a track, or it doesn't exist."); @@ -37,12 +37,13 @@ public class SoundCloudApiService : INService var response = string.Empty; using (var http = _httpFactory.CreateClient()) { - response = await http.GetStringAsync(new Uri($"https://scapi.nadeko.bot/tracks?q={Uri.EscapeDataString(query)}")); + response = await http.GetStringAsync( + new Uri($"https://scapi.nadeko.bot/tracks?q={Uri.EscapeDataString(query)}")); } var responseObj = JsonConvert.DeserializeObject(response) - .FirstOrDefault(s => s.Streamable is true); - + .FirstOrDefault(s => s.Streamable is true); + if (responseObj?.Kind != "track") throw new InvalidOperationException("Query yielded no results."); @@ -56,17 +57,22 @@ public class SoundCloudVideo public long Id { get; set; } = 0; public SoundCloudUser User { get; set; } = new(); public string Title { get; set; } = string.Empty; - public string FullName => User.Name + " - " + Title; + + public string FullName + => User.Name + " - " + Title; + public bool? Streamable { get; set; } = false; public int Duration { get; set; } + [JsonProperty("permalink_url")] public string TrackLink { get; set; } = string.Empty; + [JsonProperty("artwork_url")] public string ArtworkUrl { get; set; } = string.Empty; } - + public class SoundCloudUser { [JsonProperty("username")] public string Name { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/StartingGuildsListService.cs b/src/NadekoBot/Services/Impl/StartingGuildsListService.cs index 584311e1d..bcb5b3ee2 100644 --- a/src/NadekoBot/Services/Impl/StartingGuildsListService.cs +++ b/src/NadekoBot/Services/Impl/StartingGuildsListService.cs @@ -1,6 +1,6 @@ #nullable disable -using System.Collections.Immutable; using System.Collections; +using System.Collections.Immutable; namespace NadekoBot.Services; @@ -9,11 +9,11 @@ public class StartingGuildsService : IEnumerable, INService private readonly ImmutableList _guilds; public StartingGuildsService(DiscordSocketClient client) - => this._guilds = client.Guilds.Select(x => x.Id).ToImmutableList(); + => _guilds = client.Guilds.Select(x => x.Id).ToImmutableList(); - public IEnumerator GetEnumerator() => - _guilds.GetEnumerator(); + public IEnumerator GetEnumerator() + => _guilds.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => - _guilds.GetEnumerator(); -} + IEnumerator IEnumerable.GetEnumerator() + => _guilds.GetEnumerator(); +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index 92d1a1e36..f8fb40723 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -6,30 +6,47 @@ namespace NadekoBot.Services; public class StatsService : IStatsService, IReadyExecutor, INService, IDisposable { + public const string BotVersion = "4.0.0"; + + public string Author + => "Kwoth#2452"; + + public string Library + => "Discord.Net"; + + public double MessagesPerSecond + => MessageCounter / GetUptime().TotalSeconds; + + public long TextChannels + => Interlocked.Read(ref _textChannels); + + public long VoiceChannels + => Interlocked.Read(ref _voiceChannels); + + public long MessageCounter + => Interlocked.Read(ref _messageCounter); + + public long CommandsRan + => Interlocked.Read(ref _commandsRan); + private readonly Process _currentProcess = Process.GetCurrentProcess(); private readonly DiscordSocketClient _client; private readonly IBotCredentials _creds; private readonly DateTime _started; - public const string BotVersion = "4.0.0"; - public string Author => "Kwoth#2452"; - public string Library => "Discord.Net"; - public double MessagesPerSecond => MessageCounter / GetUptime().TotalSeconds; - private long _textChannels; - public long TextChannels => Interlocked.Read(ref _textChannels); private long _voiceChannels; - public long VoiceChannels => Interlocked.Read(ref _voiceChannels); private long _messageCounter; - public long MessageCounter => Interlocked.Read(ref _messageCounter); private long _commandsRan; - public long CommandsRan => Interlocked.Read(ref _commandsRan); private readonly Timer _botlistTimer; private readonly IHttpClientFactory _httpFactory; - public StatsService(DiscordSocketClient client, CommandHandler cmdHandler, - IBotCredentials creds, IHttpClientFactory factory) + public StatsService( + DiscordSocketClient client, + CommandHandler cmdHandler, + IBotCredentials creds, + IHttpClientFactory factory) { _client = client; _creds = creds; @@ -116,34 +133,39 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl }; _botlistTimer = new(async state => - { - if (string.IsNullOrWhiteSpace(_creds.BotListToken)) - return; - try { - using var http = _httpFactory.CreateClient(); - using var content = new FormUrlEncodedContent( - new Dictionary { - { "shard_count", _creds.TotalShards.ToString()}, + if (string.IsNullOrWhiteSpace(_creds.BotListToken)) + return; + try + { + using var http = _httpFactory.CreateClient(); + using var content = new FormUrlEncodedContent(new Dictionary + { + { "shard_count", _creds.TotalShards.ToString() }, { "shard_id", client.ShardId.ToString() }, { "server_count", client.Guilds.Count().ToString() } }); - content.Headers.Clear(); - content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); - http.DefaultRequestHeaders.Add("Authorization", _creds.BotListToken); + content.Headers.Clear(); + content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); + http.DefaultRequestHeaders.Add("Authorization", _creds.BotListToken); - using (await http.PostAsync(new Uri($"https://discordbots.org/api/bots/{client.CurrentUser.Id}/stats"), content)) { } - } - catch (Exception ex) - { - Log.Error(ex, "Error "); - // ignored - } - }, null, TimeSpan.FromMinutes(5), TimeSpan.FromHours(1)); + using (await http.PostAsync( + new Uri($"https://discordbots.org/api/bots/{client.CurrentUser.Id}/stats"), + content)) { } + } + catch (Exception ex) + { + Log.Error(ex, "Error "); + // ignored + } + }, + null, + TimeSpan.FromMinutes(5), + TimeSpan.FromHours(1)); } - public TimeSpan GetUptime() => - DateTime.UtcNow - _started; + public TimeSpan GetUptime() + => DateTime.UtcNow - _started; public string GetUptimeString(string separator = ", ") { @@ -170,4 +192,4 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl _currentProcess.Dispose(); GC.SuppressFinalize(this); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/YtdlOperation.cs b/src/NadekoBot/Services/Impl/YtdlOperation.cs index 8bc60389e..44fcfe8a7 100644 --- a/src/NadekoBot/Services/Impl/YtdlOperation.cs +++ b/src/NadekoBot/Services/Impl/YtdlOperation.cs @@ -26,11 +26,11 @@ public class YtdlOperation RedirectStandardOutput = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8, - CreateNoWindow = true, - }, + CreateNoWindow = true + } }; } - + public async Task GetDataAsync(params string[] args) { try @@ -49,13 +49,12 @@ public class YtdlOperation } catch (Win32Exception) { - Log.Error("youtube-dl is likely not installed. " + - "Please install it before running the command again"); + Log.Error("youtube-dl is likely not installed. " + "Please install it before running the command again"); return default; } catch (Exception ex) { - Log.Error(ex , "Exception running youtube-dl: {ErrorMessage}", ex.Message); + Log.Error(ex, "Exception running youtube-dl: {ErrorMessage}", ex.Message); return default; } } @@ -66,9 +65,9 @@ public class YtdlOperation Log.Debug($"Executing {process.StartInfo.FileName} {process.StartInfo.Arguments}"); process.Start(); - + string line; - while((line = await process.StandardOutput.ReadLineAsync()) != null) + while ((line = await process.StandardOutput.ReadLineAsync()) != null) yield return line; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/LogSetup.cs b/src/NadekoBot/Services/LogSetup.cs index e0daff6ad..5ebcd009a 100644 --- a/src/NadekoBot/Services/LogSetup.cs +++ b/src/NadekoBot/Services/LogSetup.cs @@ -1,7 +1,7 @@ #nullable disable -using System.Text; using Serilog.Events; using Serilog.Sinks.SystemConsole.Themes; +using System.Text; namespace NadekoBot.Services; @@ -9,23 +9,23 @@ public static class LogSetup { public static void SetupLogger(object source) { - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Override("Microsoft", LogEventLevel.Information) - .MinimumLevel.Override("System", LogEventLevel.Information) - .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) - .Enrich.FromLogContext() - .WriteTo.Console(LogEventLevel.Information, - theme: GetTheme(), - outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] | #{LogSource} | {Message:lj}{NewLine}{Exception}") - .Enrich.WithProperty("LogSource", source) - .CreateLogger(); - - System.Console.OutputEncoding = Encoding.UTF8; + Log.Logger = new LoggerConfiguration().MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .MinimumLevel.Override("System", LogEventLevel.Information) + .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) + .Enrich.FromLogContext() + .WriteTo.Console(LogEventLevel.Information, + theme: GetTheme(), + outputTemplate: + "[{Timestamp:HH:mm:ss} {Level:u3}] | #{LogSource} | {Message:lj}{NewLine}{Exception}") + .Enrich.WithProperty("LogSource", source) + .CreateLogger(); + + Console.OutputEncoding = Encoding.UTF8; } private static ConsoleTheme GetTheme() { - if(Environment.OSVersion.Platform == PlatformID.Unix) + if (Environment.OSVersion.Platform == PlatformID.Unix) return AnsiConsoleTheme.Code; #if DEBUG return AnsiConsoleTheme.Code; @@ -33,4 +33,4 @@ public static class LogSetup return ConsoleTheme.None; #endif } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Settings/BotConfigService.cs b/src/NadekoBot/Services/Settings/BotConfigService.cs index ad83626dd..98ee0dd6c 100644 --- a/src/NadekoBot/Services/Settings/BotConfigService.cs +++ b/src/NadekoBot/Services/Settings/BotConfigService.cs @@ -5,15 +5,14 @@ using SixLabors.ImageSharp.PixelFormats; namespace NadekoBot.Services; /// -/// Settings service for bot-wide configuration. +/// Settings service for bot-wide configuration. /// public sealed class BotConfigService : ConfigServiceBase { - public override string Name { get; } = "bot"; - private const string FilePath = "data/bot.yml"; private static readonly TypedKey changeKey = new("config.bot.updated"); - + public override string Name { get; } = "bot"; + public BotConfigService(IConfigSeria serializer, IPubSub pubSub) : base(FilePath, serializer, pubSub, changeKey) { @@ -25,15 +24,12 @@ public sealed class BotConfigService : ConfigServiceBase AddParsedProp("console.type", bs => bs.ConsoleOutputType, Enum.TryParse, ConfigPrinters.ToString); AddParsedProp("locale", bs => bs.DefaultLocale, ConfigParsers.Culture, ConfigPrinters.Culture); AddParsedProp("prefix", bs => bs.Prefix, ConfigParsers.String, ConfigPrinters.ToString); - + Migrate(); } private void Migrate() { - if (data.Version < 2) - { - ModifyConfig(c => c.Version = 2); - } + if (data.Version < 2) ModifyConfig(c => c.Version = 2); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Settings/ConfigParsers.cs b/src/NadekoBot/Services/Settings/ConfigParsers.cs index 6f52eeb12..33419f7ec 100644 --- a/src/NadekoBot/Services/Settings/ConfigParsers.cs +++ b/src/NadekoBot/Services/Settings/ConfigParsers.cs @@ -1,16 +1,16 @@ #nullable disable -using System.Globalization; using SixLabors.ImageSharp.PixelFormats; +using System.Globalization; namespace NadekoBot.Services; /// -/// Custom setting value parsers for types which don't have them by default +/// Custom setting value parsers for types which don't have them by default /// public static class ConfigParsers { /// - /// Default string parser. Passes input to output and returns true. + /// Default string parser. Passes input to output and returns true. /// public static bool String(string input, out string output) { @@ -42,5 +42,5 @@ public static class ConfigPrinters => culture.Name; public static string Color(Rgba32 color) - => ((uint) ((color.B << 0) | (color.G << 8) | (color.R << 16))).ToString("X6"); -} + => ((uint)((color.B << 0) | (color.G << 8) | (color.R << 16))).ToString("X6"); +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Settings/ConfigServiceBase.cs b/src/NadekoBot/Services/Settings/ConfigServiceBase.cs index 8a63b85a8..ef2dff5dc 100644 --- a/src/NadekoBot/Services/Settings/ConfigServiceBase.cs +++ b/src/NadekoBot/Services/Settings/ConfigServiceBase.cs @@ -1,37 +1,45 @@ +using NadekoBot.Common.Configs; +using NadekoBot.Common.Yml; using System.Linq.Expressions; using System.Reflection; -using NadekoBot.Common.Yml; -using NadekoBot.Common.Configs; namespace NadekoBot.Services; /// -/// Base service for all settings services +/// Base service for all settings services /// /// Type of the settings -public abstract class ConfigServiceBase : IConfigService +public abstract class ConfigServiceBase : IConfigService where TSettings : ICloneable, new() { + // todo future config arrays are not copied - they're not protected from mutations + public TSettings Data + => data.Clone(); + + public abstract string Name { get; } protected readonly string _filePath; protected readonly IConfigSeria _serializer; protected readonly IPubSub _pubSub; private readonly TypedKey _changeKey; protected TSettings data; - - // todo future config arrays are not copied - they're not protected from mutations - public TSettings Data => data.Clone(); - - public abstract string Name { get; } + + private readonly Dictionary> _propSetters = new(); + private readonly Dictionary> _propSelectors = new(); + private readonly Dictionary> _propPrinters = new(); + private readonly Dictionary _propComments = new(); /// - /// Initialized an instance of + /// Initialized an instance of /// /// Path to the file where the settings are serialized/deserialized to and from /// Serializer which will be used /// Pubsub implementation for signaling when settings are updated /// Key used to signal changed event - protected ConfigServiceBase(string filePath, IConfigSeria serializer, IPubSub pubSub, + protected ConfigServiceBase( + string filePath, + IConfigSeria serializer, + IPubSub pubSub, TypedKey changeKey) { _filePath = filePath; @@ -55,7 +63,7 @@ public abstract class ConfigServiceBase : IConfigService } /// - /// Loads data from disk. If file doesn't exist, it will be created with default values + /// Loads data from disk. If file doesn't exist, it will be created with default values /// protected void Load() { @@ -70,7 +78,7 @@ public abstract class ConfigServiceBase : IConfigService } /// - /// Loads new data and publishes the new state + /// Loads new data and publishes the new state /// public void Reload() { @@ -79,26 +87,20 @@ public abstract class ConfigServiceBase : IConfigService } /// - /// Doesn't do anything by default. This method will be executed after - /// is reloaded from or new data is recieved - /// from the publish event + /// Doesn't do anything by default. This method will be executed after + /// is reloaded from or new data is recieved + /// from the publish event /// protected virtual void OnStateUpdate() { - } - + private void Save() { var strData = _serializer.Serialize(data); File.WriteAllText(_filePath, strData); } - - private readonly Dictionary> _propSetters = new(); - private readonly Dictionary> _propSelectors = new(); - private readonly Dictionary> _propPrinters = new(); - private readonly Dictionary _propComments = new(); - + protected void AddParsedProp( string key, Expression> selector, @@ -108,19 +110,21 @@ public abstract class ConfigServiceBase : IConfigService { checker ??= _ => true; key = key.ToLowerInvariant(); - _propPrinters[key] = obj => printer((TProp)obj); + _propPrinters[key] = obj => printer((TProp)obj); _propSelectors[key] = () => selector.Compile()(data); _propSetters[key] = Magic(selector, parser, checker); _propComments[key] = ((MemberExpression)selector.Body).Member.GetCustomAttribute()?.Comment; } - private Func Magic(Expression> selector, - SettingParser parser, Func checker) + private Func Magic( + Expression> selector, + SettingParser parser, + Func checker) => (target, input) => { if (!parser(input, out var value)) return false; - + if (!checker(value)) return false; @@ -129,22 +133,22 @@ public abstract class ConfigServiceBase : IConfigService var prop = (PropertyInfo)expr.Member; var expressions = new List(); - + while (true) { expr = expr.Expression as MemberExpression; if (expr is null) break; - + expressions.Add(expr); } - + foreach (var memberExpression in expressions.AsEnumerable().Reverse()) { - var localProp = (PropertyInfo) memberExpression.Member; + var localProp = (PropertyInfo)memberExpression.Member; targetObject = localProp.GetValue(targetObject); } - + prop!.SetValue(targetObject, value, null); return true; }; @@ -155,8 +159,7 @@ public abstract class ConfigServiceBase : IConfigService public string GetSetting(string prop) { prop = prop.ToLowerInvariant(); - if (!_propSelectors.TryGetValue(prop, out var selector) || - !_propPrinters.TryGetValue(prop, out var printer)) + if (!_propSelectors.TryGetValue(prop, out var selector) || !_propPrinters.TryGetValue(prop, out var printer)) return default; return printer(selector()); @@ -171,8 +174,7 @@ public abstract class ConfigServiceBase : IConfigService } private bool SetProperty(TSettings target, string key, string value) - => _propSetters.TryGetValue(key.ToLowerInvariant(), out var magic) - && magic(target, value); + => _propSetters.TryGetValue(key.ToLowerInvariant(), out var magic) && magic(target, value); public bool SetSetting(string prop, string newValue) { @@ -181,8 +183,8 @@ public abstract class ConfigServiceBase : IConfigService { success = SetProperty(bs, prop, newValue); }); - - if(success) + + if (success) PublishChange(); return success; @@ -196,4 +198,4 @@ public abstract class ConfigServiceBase : IConfigService Save(); PublishChange(); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Settings/IConfigMigrator.cs b/src/NadekoBot/Services/Settings/IConfigMigrator.cs index 6fc324c18..05123d4cc 100644 --- a/src/NadekoBot/Services/Settings/IConfigMigrator.cs +++ b/src/NadekoBot/Services/Settings/IConfigMigrator.cs @@ -4,4 +4,4 @@ namespace NadekoBot.Services; public interface IConfigMigrator { public void EnsureMigrated(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Settings/IConfigService.cs b/src/NadekoBot/Services/Settings/IConfigService.cs index 8ec90a31f..a4af4e6dc 100644 --- a/src/NadekoBot/Services/Settings/IConfigService.cs +++ b/src/NadekoBot/Services/Settings/IConfigService.cs @@ -2,42 +2,45 @@ namespace NadekoBot.Services; /// -/// Interface that all services which deal with configs should implement +/// Interface that all services which deal with configs should implement /// public interface IConfigService { /// - /// Name of the config + /// Name of the config /// public string Name { get; } + /// - /// Loads new data and publishes the new state + /// Loads new data and publishes the new state /// void Reload(); + /// - /// Gets the list of props you can set + /// Gets the list of props you can set /// /// List of props IReadOnlyList GetSettableProps(); + /// - /// Gets the value of the specified property + /// Gets the value of the specified property /// /// Prop name /// Value of the prop string GetSetting(string prop); /// - /// Gets the value of the specified property + /// Gets the value of the specified property /// /// Prop name /// Value of the prop string GetComment(string prop); - + /// - /// Sets the value of the specified property + /// Sets the value of the specified property /// /// Property to set /// Value to set the property to /// Success bool SetSetting(string prop, string newValue); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Settings/SettingParser.cs b/src/NadekoBot/Services/Settings/SettingParser.cs index 73783ad18..7caeec784 100644 --- a/src/NadekoBot/Services/Settings/SettingParser.cs +++ b/src/NadekoBot/Services/Settings/SettingParser.cs @@ -2,7 +2,7 @@ namespace NadekoBot.Services; /// -/// Delegate which describes a parser which can convert string input into given data type +/// Delegate which describes a parser which can convert string input into given data type /// /// Data type to convert string to -public delegate bool SettingParser(string input, out TData output); +public delegate bool SettingParser(string input, out TData output); \ No newline at end of file diff --git a/src/NadekoBot/Services/StandardConversions.cs b/src/NadekoBot/Services/StandardConversions.cs index 6dec7709c..2518fb0dd 100644 --- a/src/NadekoBot/Services/StandardConversions.cs +++ b/src/NadekoBot/Services/StandardConversions.cs @@ -5,4 +5,4 @@ public static class StandardConversions { public static double CelsiusToFahrenheit(double cel) => (cel * 1.8f) + 32; -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/strings/IBotStrings.cs b/src/NadekoBot/Services/strings/IBotStrings.cs index dcf35dd35..fb4c66162 100644 --- a/src/NadekoBot/Services/strings/IBotStrings.cs +++ b/src/NadekoBot/Services/strings/IBotStrings.cs @@ -4,7 +4,7 @@ using System.Globalization; namespace NadekoBot.Services; /// -/// Defines methods to retrieve and reload bot strings +/// Defines methods to retrieve and reload bot strings /// public interface IBotStrings { @@ -13,4 +13,4 @@ public interface IBotStrings void Reload(); CommandStrings GetCommandStrings(string commandName, ulong? guildId = null); CommandStrings GetCommandStrings(string commandName, CultureInfo cultureInfo); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/strings/IBotStringsProvider.cs b/src/NadekoBot/Services/strings/IBotStringsProvider.cs index 920fb6410..628923ac3 100644 --- a/src/NadekoBot/Services/strings/IBotStringsProvider.cs +++ b/src/NadekoBot/Services/strings/IBotStringsProvider.cs @@ -2,27 +2,27 @@ namespace NadekoBot.Services; /// -/// Implemented by classes which provide localized strings in their own ways +/// Implemented by classes which provide localized strings in their own ways /// public interface IBotStringsProvider { /// - /// Gets localized string + /// Gets localized string /// /// Language name /// String key /// Localized string string GetText(string localeName, string key); - + /// - /// Reloads string cache + /// Reloads string cache /// void Reload(); /// - /// Gets command arg examples and description + /// Gets command arg examples and description /// /// Language name /// Command name CommandStrings GetCommandStrings(string localeName, string commandName); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/strings/IStringsSource.cs b/src/NadekoBot/Services/strings/IStringsSource.cs index 7122193f2..82329f873 100644 --- a/src/NadekoBot/Services/strings/IStringsSource.cs +++ b/src/NadekoBot/Services/strings/IStringsSource.cs @@ -2,15 +2,15 @@ namespace NadekoBot.Services; /// -/// Basic interface used for classes implementing strings loading mechanism +/// Basic interface used for classes implementing strings loading mechanism /// public interface IStringsSource { /// - /// Gets all response strings + /// Gets all response strings /// /// Dictionary(localename, Dictionary(key, response)) Dictionary> GetResponseStrings(); Dictionary> GetCommandStrings(); -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/strings/impl/BotStrings.cs b/src/NadekoBot/Services/strings/impl/BotStrings.cs index 44c3c319e..2b832d495 100644 --- a/src/NadekoBot/Services/strings/impl/BotStrings.cs +++ b/src/NadekoBot/Services/strings/impl/BotStrings.cs @@ -7,9 +7,10 @@ namespace NadekoBot.Services; public class BotStrings : IBotStrings { /// - /// Used as failsafe in case response key doesn't exist in the selected or default language. + /// Used as failsafe in case response key doesn't exist in the selected or default language. /// private readonly CultureInfo _usCultureInfo = new("en-US"); + private readonly ILocalization _localization; private readonly IBotStringsProvider _stringsProvider; @@ -31,15 +32,16 @@ public class BotStrings : IBotStrings if (string.IsNullOrWhiteSpace(text)) { - Log.Warning("'{Key}' key is missing from '{LanguageName}' response strings. You may ignore this message", key, cultureInfo.Name); + Log.Warning("'{Key}' key is missing from '{LanguageName}' response strings. You may ignore this message", + key, + cultureInfo.Name); text = GetString(key, _usCultureInfo) ?? $"Error: dkey {key} not found!"; if (string.IsNullOrWhiteSpace(text)) - { return - $"I can't tell you if the command is executed, because there was an error printing out the response." + - $" Key '{key}' is missing from resources. You may ignore this message."; - } + "I can't tell you if the command is executed, because there was an error printing out the response." + + $" Key '{key}' is missing from resources. You may ignore this message."; } + return text; } @@ -51,39 +53,38 @@ public class BotStrings : IBotStrings } catch (FormatException) { - Log.Warning(" Key '{Key}' is not properly formatted in '{LanguageName}' response strings. Please report this", key, cultureInfo.Name); + Log.Warning( + " Key '{Key}' is not properly formatted in '{LanguageName}' response strings. Please report this", + key, + cultureInfo.Name); if (cultureInfo.Name != _usCultureInfo.Name) return GetText(key, _usCultureInfo, data); return - $"I can't tell you if the command is executed, because there was an error printing out the response.\n" + - $"Key '{key}' is not properly formatted. Please report this."; + "I can't tell you if the command is executed, because there was an error printing out the response.\n" + + $"Key '{key}' is not properly formatted. Please report this."; } } public CommandStrings GetCommandStrings(string commandName, ulong? guildId = null) => GetCommandStrings(commandName, _localization.GetCultureInfo(guildId)); - + public CommandStrings GetCommandStrings(string commandName, CultureInfo cultureInfo) { - var cmdStrings = _stringsProvider.GetCommandStrings(cultureInfo.Name, commandName); + var cmdStrings = _stringsProvider.GetCommandStrings(cultureInfo.Name, commandName); if (cmdStrings is null) { if (cultureInfo.Name == _usCultureInfo.Name) { Log.Warning("'{CommandName}' doesn't exist in 'en-US' command strings. Please report this", commandName); - - return new() - { - Args = new[] {""}, - Desc = "?" - }; + + return new() { Args = new[] { "" }, Desc = "?" }; } // Log.Warning(@"'{CommandName}' command strings don't exist in '{LanguageName}' culture. // This message is safe to ignore, however you can ask in Nadeko support server how you can contribute command translations", // commandName, cultureInfo.Name); - + return GetCommandStrings(commandName, _usCultureInfo); } @@ -98,6 +99,7 @@ public class CommandStrings { [YamlMember(Alias = "desc")] public string Desc { get; set; } + [YamlMember(Alias = "args")] public string[] Args { get; set; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/strings/impl/LocalBotStringsProvider.cs b/src/NadekoBot/Services/strings/impl/LocalBotStringsProvider.cs index ff958ff26..b8d570400 100644 --- a/src/NadekoBot/Services/strings/impl/LocalBotStringsProvider.cs +++ b/src/NadekoBot/Services/strings/impl/LocalBotStringsProvider.cs @@ -6,20 +6,17 @@ public class LocalBotStringsProvider : IBotStringsProvider private readonly IStringsSource _source; private IReadOnlyDictionary> responseStrings; private IReadOnlyDictionary> commandStrings; - + public LocalBotStringsProvider(IStringsSource source) { _source = source; Reload(); } - + public string GetText(string localeName, string key) { - if (responseStrings.TryGetValue(localeName, out var langStrings) - && langStrings.TryGetValue(key, out var text)) - { + if (responseStrings.TryGetValue(localeName, out var langStrings) && langStrings.TryGetValue(key, out var text)) return text; - } return null; } @@ -34,10 +31,8 @@ public class LocalBotStringsProvider : IBotStringsProvider { if (commandStrings.TryGetValue(localeName, out var langStrings) && langStrings.TryGetValue(commandName, out var strings)) - { return strings; - } return null; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/strings/impl/LocalFileStringsSource.cs b/src/NadekoBot/Services/strings/impl/LocalFileStringsSource.cs index 862fa045b..80c3914af 100644 --- a/src/NadekoBot/Services/strings/impl/LocalFileStringsSource.cs +++ b/src/NadekoBot/Services/strings/impl/LocalFileStringsSource.cs @@ -5,25 +5,25 @@ using YamlDotNet.Serialization; namespace NadekoBot.Services; /// -/// Loads strings from the local default filepath +/// Loads strings from the local default filepath /// public class LocalFileStringsSource : IStringsSource { private readonly string _responsesPath = "data/strings/responses"; private readonly string _commandsPath = "data/strings/commands"; - public LocalFileStringsSource(string responsesPath = "data/strings/responses", + public LocalFileStringsSource( + string responsesPath = "data/strings/responses", string commandsPath = "data/strings/commands") { _responsesPath = responsesPath; _commandsPath = commandsPath; } - + public Dictionary> GetResponseStrings() { var outputDict = new Dictionary>(); foreach (var file in Directory.GetFiles(_responsesPath)) - { try { var langDict = JsonConvert.DeserializeObject>(File.ReadAllText(file)); @@ -34,19 +34,16 @@ public class LocalFileStringsSource : IStringsSource { Log.Error(ex, "Error loading {FileName} response strings: {ErrorMessage}", file, ex.Message); } - } return outputDict; } public Dictionary> GetCommandStrings() { - var deserializer = new DeserializerBuilder() - .Build(); - + var deserializer = new DeserializerBuilder().Build(); + var outputDict = new Dictionary>(); foreach (var file in Directory.GetFiles(_commandsPath)) - { try { var text = File.ReadAllText(file); @@ -58,11 +55,10 @@ public class LocalFileStringsSource : IStringsSource { Log.Error(ex, "Error loading {FileName} command strings: {ErrorMessage}", file, ex.Message); } - } return outputDict; } - + private static string GetLocaleName(string fileName) { fileName = Path.GetFileName(fileName); @@ -70,4 +66,4 @@ public class LocalFileStringsSource : IStringsSource var secondDotIndex = fileName.LastIndexOf('.'); return fileName.Substring(dotIndex, secondDotIndex - dotIndex); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/strings/impl/RedisBotStringsProvider.cs b/src/NadekoBot/Services/strings/impl/RedisBotStringsProvider.cs index 2cc54f3be..a420f1d65 100644 --- a/src/NadekoBot/Services/strings/impl/RedisBotStringsProvider.cs +++ b/src/NadekoBot/Services/strings/impl/RedisBotStringsProvider.cs @@ -1,12 +1,12 @@ #nullable disable -using System.Web; using StackExchange.Redis; +using System.Web; namespace NadekoBot.Services; /// -/// Uses to load strings into redis hash (only on Shard 0) -/// and retrieves them from redis via +/// Uses to load strings into redis hash (only on Shard 0) +/// and retrieves them from redis via /// public class RedisBotStringsProvider : IBotStringsProvider { @@ -14,14 +14,17 @@ public class RedisBotStringsProvider : IBotStringsProvider private readonly IStringsSource _source; private readonly IBotCredentials _creds; - public RedisBotStringsProvider(ConnectionMultiplexer redis, DiscordSocketClient discordClient, - IStringsSource source, IBotCredentials creds) + public RedisBotStringsProvider( + ConnectionMultiplexer redis, + DiscordSocketClient discordClient, + IStringsSource source, + IBotCredentials creds) { _redis = redis; _source = source; _creds = creds; - if(discordClient.ShardId == 0) + if (discordClient.ShardId == 0) Reload(); } @@ -33,20 +36,18 @@ public class RedisBotStringsProvider : IBotStringsProvider public CommandStrings GetCommandStrings(string localeName, string commandName) { - string argsStr = _redis.GetDatabase().HashGet($"{_creds.RedisKey()}:commands:{localeName}", $"{commandName}::args"); + string argsStr = _redis.GetDatabase() + .HashGet($"{_creds.RedisKey()}:commands:{localeName}", $"{commandName}::args"); if (argsStr == default) return null; - - var descStr = _redis.GetDatabase().HashGet($"{_creds.RedisKey()}:commands:{localeName}", $"{commandName}::desc"); + + var descStr = _redis.GetDatabase() + .HashGet($"{_creds.RedisKey()}:commands:{localeName}", $"{commandName}::desc"); if (descStr == default) return null; - + var args = Array.ConvertAll(argsStr.Split('&'), HttpUtility.UrlDecode); - return new() - { - Args = args, - Desc = descStr - }; + return new() { Args = args, Desc = descStr }; } public void Reload() @@ -54,23 +55,20 @@ public class RedisBotStringsProvider : IBotStringsProvider var redisDb = _redis.GetDatabase(); foreach (var (localeName, localeStrings) in _source.GetResponseStrings()) { - var hashFields = localeStrings - .Select(x => new HashEntry(x.Key, x.Value)) - .ToArray(); + var hashFields = localeStrings.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); redisDb.HashSet($"{_creds.RedisKey()}:responses:{localeName}", hashFields); } - + foreach (var (localeName, localeStrings) in _source.GetCommandStrings()) { var hashFields = localeStrings - .Select(x => new HashEntry($"{x.Key}::args", - string.Join('&', Array.ConvertAll(x.Value.Args, HttpUtility.UrlEncode)))) - .Concat(localeStrings - .Select(x => new HashEntry($"{x.Key}::desc", x.Value.Desc))) - .ToArray(); + .Select(x => new HashEntry($"{x.Key}::args", + string.Join('&', Array.ConvertAll(x.Value.Args, HttpUtility.UrlEncode)))) + .Concat(localeStrings.Select(x => new HashEntry($"{x.Key}::desc", x.Value.Desc))) + .ToArray(); redisDb.HashSet($"{_creds.RedisKey()}:commands:{localeName}", hashFields); } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/ArrayExtensions.cs b/src/NadekoBot/_Extensions/ArrayExtensions.cs index e637d4ad8..8dc43a237 100644 --- a/src/NadekoBot/_Extensions/ArrayExtensions.cs +++ b/src/NadekoBot/_Extensions/ArrayExtensions.cs @@ -5,7 +5,7 @@ namespace NadekoBot.Extensions; public static class ArrayExtensions { /// - /// Create a new array from the old array + new element at the end + /// Create a new array from the old array + new element at the end /// /// Input array /// Item to add to the end of the output array @@ -14,18 +14,13 @@ public static class ArrayExtensions public static T[] With(this T[] input, T added) { var newCrs = new T[input.Length + 1]; - Array.Copy(input, - 0, - newCrs, - 0, - input.Length - ); + Array.Copy(input, 0, newCrs, 0, input.Length); newCrs[input.Length] = added; return newCrs; } /// - /// Creates a new array by applying the specified function to every element in the input array + /// Creates a new array by applying the specified function to every element in the input array /// /// Array to modify /// Function to apply @@ -34,4 +29,4 @@ public static class ArrayExtensions /// New array with updated elements public static TOut[] Map(this TIn[] arr, Func f) => Array.ConvertAll(arr, x => f(x)); -} +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/BotCredentialsExtensions.cs b/src/NadekoBot/_Extensions/BotCredentialsExtensions.cs index faa7b6d8b..8a2b75c11 100644 --- a/src/NadekoBot/_Extensions/BotCredentialsExtensions.cs +++ b/src/NadekoBot/_Extensions/BotCredentialsExtensions.cs @@ -4,4 +4,4 @@ public static class BotCredentialsExtensions { public static bool IsOwner(this IBotCredentials creds, IUser user) => creds.OwnerIds.Contains(user.Id); -} +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/EnumerableExtensions.cs b/src/NadekoBot/_Extensions/EnumerableExtensions.cs index 890ab213a..6fbba0ab4 100644 --- a/src/NadekoBot/_Extensions/EnumerableExtensions.cs +++ b/src/NadekoBot/_Extensions/EnumerableExtensions.cs @@ -1,35 +1,47 @@ -using System.Security.Cryptography; using NadekoBot.Common.Collections; using NadekoBot.Services.Database.Models; +using System.Security.Cryptography; namespace NadekoBot.Extensions; public static class EnumerableExtensions { /// - /// Concatenates the members of a collection, using the specified separator between each member. + /// Concatenates the members of a collection, using the specified separator between each member. /// /// Collection to join - /// The character to use as a separator. separator is included in the returned string only if values has more than one element. + /// + /// The character to use as a separator. separator is included in the returned string only if + /// values has more than one element. + /// /// Optional transformation to apply to each element before concatenation. /// The type of the members of values. - /// A string that consists of the members of values delimited by the separator character. -or- Empty if values has no elements. + /// + /// A string that consists of the members of values delimited by the separator character. -or- Empty if values has + /// no elements. + /// public static string Join(this IEnumerable data, char separator, Func? func = null) => string.Join(separator, data.Select(func ?? (x => x?.ToString() ?? string.Empty))); /// - /// Concatenates the members of a collection, using the specified separator between each member. + /// Concatenates the members of a collection, using the specified separator between each member. /// /// Collection to join - /// The string to use as a separator.separator is included in the returned string only if values has more than one element. + /// + /// The string to use as a separator.separator is included in the returned string only if values + /// has more than one element. + /// /// Optional transformation to apply to each element before concatenation. /// The type of the members of values. - /// A string that consists of the members of values delimited by the separator character. -or- Empty if values has no elements. + /// + /// A string that consists of the members of values delimited by the separator character. -or- Empty if values has + /// no elements. + /// public static string Join(this IEnumerable data, string separator, Func? func = null) => string.Join(separator, data.Select(func ?? (x => x?.ToString() ?? string.Empty))); /// - /// Randomize element order by performing the Fisher-Yates shuffle + /// Randomize element order by performing the Fisher-Yates shuffle /// /// Item type /// Items to shuffle @@ -40,13 +52,13 @@ public static class EnumerableExtensions var n = list.Count; while (n > 1) { - var box = new byte[(n / Byte.MaxValue) + 1]; + var box = new byte[(n / byte.MaxValue) + 1]; int boxSum; do { provider.GetBytes(box); boxSum = box.Sum(b => b); - } while (!(boxSum < n * (Byte.MaxValue * box.Length / n))); + } while (!(boxSum < n * (byte.MaxValue * box.Length / n))); var k = boxSum % n; n--; @@ -57,13 +69,16 @@ public static class EnumerableExtensions } /// - /// Initializes a new instance of the class - /// that contains elements copied from the specified - /// has the default concurrency level, has the default initial capacity, - /// and uses the default comparer for the key type. + /// Initializes a new instance of the class + /// that contains elements copied from the specified + /// has the default concurrency level, has the default initial capacity, + /// and uses the default comparer for the key type. /// - /// The whose elements are copied to the new . - /// A new instance of the class + /// + /// The whose elements are copied to the new + /// . + /// + /// A new instance of the class public static ConcurrentDictionary ToConcurrent( this IEnumerable> dict) where TKey : notnull @@ -76,21 +91,21 @@ public static class EnumerableExtensions // todo use this extension instead of Task.WhenAll /// - /// Creates a task that will complete when all of the objects in an enumerable - /// collection have completed + /// Creates a task that will complete when all of the objects in an enumerable + /// collection have completed /// /// The tasks to wait on for completion. /// The type of the completed task. /// A task that represents the completion of all of the supplied tasks. public static Task WhenAll(this IEnumerable> tasks) => Task.WhenAll(tasks); - + /// - /// Creates a task that will complete when all of the objects in an enumerable - /// collection have completed + /// Creates a task that will complete when all of the objects in an enumerable + /// collection have completed /// /// The tasks to wait on for completion. /// A task that represents the completion of all of the supplied tasks. public static Task WhenAll(this IEnumerable tasks) => Task.WhenAll(tasks); -} +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index 1ba8cf762..dc53aa720 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -13,7 +13,6 @@ using System.Net.Http.Headers; using System.Text.Json; using System.Text.RegularExpressions; using Color = Discord.Color; -using JsonSerializer = System.Text.Json.JsonSerializer; // todo imagesharp extensions namespace NadekoBot.Extensions; @@ -27,17 +26,15 @@ public static class Extensions => text switch { SmartEmbedText set => msg.ModifyAsync(x => - { - x.Embed = set.GetEmbed().Build(); - x.Content = set.PlainText?.SanitizeMentions() ?? ""; - } - ), + { + x.Embed = set.GetEmbed().Build(); + x.Content = set.PlainText?.SanitizeMentions() ?? ""; + }), SmartPlainText spt => msg.ModifyAsync(x => - { - x.Content = spt.Text.SanitizeMentions(); - x.Embed = null; - } - ), + { + x.Content = spt.Text.SanitizeMentions(); + x.Embed = null; + }), _ => throw new ArgumentOutOfRangeException(nameof(text)) }; @@ -45,8 +42,8 @@ public static class Extensions => client.Guilds.Select(x => x.Id).ToList(); /// - /// Generates a string in the format HHH:mm if timespan is >= 2m. - /// Generates a string in the format 00:mm:ss if timespan is less than 2m. + /// Generates a string in the format HHH:mm if timespan is >= 2m. + /// Generates a string in the format 00:mm:ss if timespan is less than 2m. /// /// Timespan to convert to string /// Formatted duration string @@ -75,18 +72,14 @@ public static class Extensions var size = ctx.GetCurrentSize(); var corners = BuildCorners(size.Width, size.Height, cornerRadius); - ctx.SetGraphicsOptions(new GraphicsOptions() - { - Antialias = true, - // enforces that any part of this shape that has color is punched out of the background - AlphaCompositionMode = PixelAlphaCompositionMode.DestOut - } - ); - - foreach (var c in corners) + ctx.SetGraphicsOptions(new GraphicsOptions { - ctx = ctx.Fill(SixLabors.ImageSharp.Color.Red, c); - } + Antialias = true, + // enforces that any part of this shape that has color is punched out of the background + AlphaCompositionMode = PixelAlphaCompositionMode.DestOut + }); + + foreach (var c in corners) ctx = ctx.Fill(SixLabors.ImageSharp.Color.Red, c); return ctx; } @@ -94,11 +87,7 @@ public static class Extensions private static IPathCollection BuildCorners(int imageWidth, int imageHeight, float cornerRadius) { // first create a square - var rect = new RectangularPolygon(-0.5f, - -0.5f, - cornerRadius, - cornerRadius - ); + var rect = new RectangularPolygon(-0.5f, -0.5f, cornerRadius, cornerRadius); // then cut out of the square a circle so we are left with a corner var cornerTopLeft = rect.Clip(new EllipsePolygon(cornerRadius - 0.5f, cornerRadius - 0.5f, cornerRadius)); @@ -114,15 +103,11 @@ public static class Extensions var cornerBottomLeft = cornerTopLeft.RotateDegree(-90).Translate(0, bottomPos); var cornerBottomRight = cornerTopLeft.RotateDegree(180).Translate(rightPos, bottomPos); - return new PathCollection(cornerTopLeft, - cornerBottomLeft, - cornerTopRight, - cornerBottomRight - ); + return new PathCollection(cornerTopLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight); } /// - /// First 10 characters of teh bot token. + /// First 10 characters of teh bot token. /// public static string RedisKey(this IBotCredentials bc) => bc.Token[..10]; @@ -143,12 +128,11 @@ public static class Extensions ulong? guildId, string prefix) => Array.ConvertAll(strings.GetCommandStrings(cmd.MethodName(), guildId).Args, - arg => GetFullUsage(cmd.Name, arg, prefix) - ); + arg => GetFullUsage(cmd.Name, arg, prefix)); private static string MethodName(this CommandInfo cmd) - => ((NadekoCommandAttribute?)cmd.Attributes.FirstOrDefault(x => x is NadekoCommandAttribute))?.MethodName ?? - cmd.Name; + => ((NadekoCommandAttribute?)cmd.Attributes.FirstOrDefault(x => x is NadekoCommandAttribute))?.MethodName + ?? cmd.Name; private static string GetFullUsage(string commandName, string args, string prefix) => $"{prefix}{commandName} {string.Format(args, prefix)}"; @@ -157,8 +141,7 @@ public static class Extensions { if (lastPage != null) return embed.WithFooter($"{curPage + 1} / {lastPage + 1}"); - else - return embed.WithFooter(curPage.ToString()); + return embed.WithFooter(curPage.ToString()); } public static Color ToDiscordColor(this Rgba32 color) @@ -205,33 +188,25 @@ public static class Extensions dict.Clear(); dict.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); dict.Add("User-Agent", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1" - ); + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1"); } public static IMessage DeleteAfter(this IUserMessage msg, int seconds, ILogCommandService? logService = null) { Task.Run(async () => - { - await Task.Delay(seconds * 1000); - if (logService != null) - { - logService.AddDeleteIgnore(msg.Id); - } + { + await Task.Delay(seconds * 1000); + if (logService != null) logService.AddDeleteIgnore(msg.Id); - try { await msg.DeleteAsync(); } - catch { } - } - ); + try { await msg.DeleteAsync(); } + catch { } + }); return msg; } public static ModuleInfo GetTopLevelModule(this ModuleInfo module) { - while (module.Parent != null) - { - module = module.Parent; - } + while (module.Parent != null) module = module.Parent; return module; } @@ -246,27 +221,24 @@ public static class Extensions => JsonSerializer.Serialize(any, options); /// - /// Adds fallback fonts to + /// Adds fallback fonts to /// - /// to which fallback fonts will be added to + /// to which fallback fonts will be added to /// List of fallback Font Families to add - /// The same to allow chaining + /// The same to allow chaining public static TextOptions WithFallbackFonts(this TextOptions opts, List fallback) { - foreach (var ff in fallback) - { - opts.FallbackFonts.Add(ff); - } + foreach (var ff in fallback) opts.FallbackFonts.Add(ff); return opts; } /// - /// Adds fallback fonts to + /// Adds fallback fonts to /// - /// to which fallback fonts will be added to + /// to which fallback fonts will be added to /// List of fallback Font Families to add - /// The same to allow chaining + /// The same to allow chaining public static TextGraphicsOptions WithFallbackFonts(this TextGraphicsOptions opts, List fallback) { opts.TextOptions.WithFallbackFonts(fallback); @@ -277,15 +249,13 @@ public static class Extensions { var imageStream = new MemoryStream(); if (format?.Name == "GIF") - { img.SaveAsGif(imageStream); - } else - { img.SaveAsPng(imageStream, - new() { ColorType = PngColorType.RgbWithAlpha, CompressionLevel = PngCompressionLevel.BestCompression } - ); - } + new() + { + ColorType = PngColorType.RgbWithAlpha, CompressionLevel = PngCompressionLevel.BestCompression + }); imageStream.Position = 0; return imageStream; @@ -307,20 +277,14 @@ public static class Extensions public static bool IsImage(this HttpResponseMessage msg, out string? mimeType) { mimeType = msg.Content.Headers.ContentType?.MediaType; - if (mimeType is "image/png" or "image/jpeg" or "image/gif") - { - return true; - } + if (mimeType is "image/png" or "image/jpeg" or "image/gif") return true; return false; } public static long? GetImageSize(this HttpResponseMessage msg) { - if (msg.Content.Headers.ContentLength is null) - { - return null; - } + if (msg.Content.Headers.ContentLength is null) return null; return msg.Content.Headers.ContentLength.Value / 1.Mb(); } @@ -330,4 +294,4 @@ public static class Extensions public static string GetText(this IBotStrings strings, in LocStr str, CultureInfo culture) => strings.GetText(str.Key, culture, str.Params); -} +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/IMessageChannelExtensions.cs b/src/NadekoBot/_Extensions/IMessageChannelExtensions.cs index a261fa449..ba8a14c55 100644 --- a/src/NadekoBot/_Extensions/IMessageChannelExtensions.cs +++ b/src/NadekoBot/_Extensions/IMessageChannelExtensions.cs @@ -2,6 +2,9 @@ namespace NadekoBot.Extensions; public static class MessageChannelExtensions { + private static readonly IEmote _arrowLeft = new Emoji("⬅"); + private static readonly IEmote _arrowRight = new Emoji("➡"); + public static Task EmbedAsync(this IMessageChannel ch, IEmbedBuilder embed, string msg = "") => ch.SendMessageAsync(msg, embed: embed.Build(), options: new() { RetryMode = RetryMode.AlwaysRetry }); @@ -37,8 +40,7 @@ public static class MessageChannelExtensions { var embed = eb.Create().WithErrorColor().WithDescription(error).WithTitle(title); - if (url != null && - Uri.IsWellFormedUriString(url, UriKind.Absolute)) + if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute)) embed.WithUrl(url); if (!string.IsNullOrWhiteSpace(footer)) @@ -63,8 +65,7 @@ public static class MessageChannelExtensions { var embed = eb.Create().WithOkColor().WithDescription(text).WithTitle(title); - if (url != null && - Uri.IsWellFormedUriString(url, UriKind.Absolute)) + if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute)) embed.WithUrl(url); if (!string.IsNullOrWhiteSpace(footer)) @@ -84,23 +85,15 @@ public static class MessageChannelExtensions int columns = 3) => ch.SendMessageAsync($@"{seed}```css {string.Join("\n", items.Chunk(columns) - .Select(ig => string.Concat(ig.Select(howToPrint))))} -```" - ); + .Select(ig => string.Concat(ig.Select(howToPrint))))} +```"); public static Task SendTableAsync( this IMessageChannel ch, IEnumerable items, Func howToPrint, int columns = 3) - => ch.SendTableAsync("", - items, - howToPrint, - columns - ); - - private static readonly IEmote _arrowLeft = new Emoji("⬅"); - private static readonly IEmote _arrowRight = new Emoji("➡"); + => ch.SendTableAsync("", items, howToPrint, columns); public static Task SendPaginatedConfirmAsync( this ICommandContext ctx, @@ -113,11 +106,10 @@ public static class MessageChannelExtensions x => Task.FromResult(pageFunc(x)), totalElements, itemsPerPage, - addPaginatedFooter - ); + addPaginatedFooter); /// - /// danny kamisama + /// danny kamisama /// public static async Task SendPaginatedConfirmAsync( this ICommandContext ctx, @@ -132,8 +124,7 @@ public static class MessageChannelExtensions var lastPage = (totalElements - 1) / itemsPerPage; var canPaginate = true; - if (ctx.Guild is SocketGuild sg && - !sg.CurrentUser.GetPermissions((IGuildChannel)ctx.Channel).AddReactions) + if (ctx.Guild is SocketGuild sg && !sg.CurrentUser.GetPermissions((IGuildChannel)ctx.Channel).AddReactions) canPaginate = false; if (!canPaginate) @@ -143,8 +134,7 @@ public static class MessageChannelExtensions var msg = await ctx.Channel.EmbedAsync(embed); - if (lastPage == 0 || - !canPaginate) + if (lastPage == 0 || !canPaginate) return; await msg.AddReactionAsync(_arrowLeft); @@ -197,17 +187,12 @@ public static class MessageChannelExtensions try { - if (msg.Channel is ITextChannel && - ((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages) - { + if (msg.Channel is ITextChannel && ((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages) await msg.RemoveAllReactionsAsync(); - } else - { await msg.Reactions.Where(x => x.Value.IsMe) - .Select(x => msg.RemoveReactionAsync(x.Key, ctx.Client.CurrentUser)) - .WhenAll(); - } + .Select(x => msg.RemoveReactionAsync(x.Key, ctx.Client.CurrentUser)) + .WhenAll(); } catch { @@ -223,4 +208,4 @@ public static class MessageChannelExtensions public static Task WarningAsync(this ICommandContext ctx) => ctx.Message.AddReactionAsync(new Emoji("⚠️")); -} +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/LinkedListExtensions.cs b/src/NadekoBot/_Extensions/LinkedListExtensions.cs index dfe3cb711..09e840c6d 100644 --- a/src/NadekoBot/_Extensions/LinkedListExtensions.cs +++ b/src/NadekoBot/_Extensions/LinkedListExtensions.cs @@ -15,4 +15,4 @@ public static class LinkedListExtensions return null; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/NumberExtensions.cs b/src/NadekoBot/_Extensions/NumberExtensions.cs index 95cc9ad13..22f8fbf25 100644 --- a/src/NadekoBot/_Extensions/NumberExtensions.cs +++ b/src/NadekoBot/_Extensions/NumberExtensions.cs @@ -42,12 +42,5 @@ public static class NumberExtensions => number == Math.Truncate(number); public static DateTimeOffset ToUnixTimestamp(this double number) - => new DateTimeOffset(1970, - 1, - 1, - 0, - 0, - 0, - TimeSpan.Zero - ).AddSeconds(number); -} + => new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero).AddSeconds(number); +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/ProcessExtensions.cs b/src/NadekoBot/_Extensions/ProcessExtensions.cs index 1ddfaf535..90ba03cb0 100644 --- a/src/NadekoBot/_Extensions/ProcessExtensions.cs +++ b/src/NadekoBot/_Extensions/ProcessExtensions.cs @@ -19,20 +19,13 @@ public static class ProcessExtensions { if (_isWindows) { - RunProcessAndWaitForExit("taskkill", - $"/T /F /PID {process.Id}", - timeout, - out _ - ); + RunProcessAndWaitForExit("taskkill", $"/T /F /PID {process.Id}", timeout, out _); } else { var children = new HashSet(); GetAllChildIdsUnix(process.Id, children, timeout); - foreach (var childId in children) - { - KillProcessUnix(childId, timeout); - } + foreach (var childId in children) KillProcessUnix(childId, timeout); KillProcessUnix(process.Id, timeout); } @@ -40,23 +33,15 @@ public static class ProcessExtensions private static void GetAllChildIdsUnix(int parentId, ISet children, TimeSpan timeout) { - var exitCode = RunProcessAndWaitForExit("pgrep", - $"-P {parentId}", - timeout, - out var stdout - ); + var exitCode = RunProcessAndWaitForExit("pgrep", $"-P {parentId}", timeout, out var stdout); - if (exitCode == 0 && - !string.IsNullOrEmpty(stdout)) + if (exitCode == 0 && !string.IsNullOrEmpty(stdout)) { using var reader = new StringReader(stdout); while (true) { var text = reader.ReadLine(); - if (text is null) - { - return; - } + if (text is null) return; if (int.TryParse(text, out var id)) { @@ -69,11 +54,7 @@ public static class ProcessExtensions } private static void KillProcessUnix(int processId, TimeSpan timeout) - => RunProcessAndWaitForExit("kill", - $"-TERM {processId}", - timeout, - out _ - ); + => RunProcessAndWaitForExit("kill", $"-TERM {processId}", timeout, out _); private static int RunProcessAndWaitForExit( string fileName, @@ -94,14 +75,10 @@ public static class ProcessExtensions return -1; if (process.WaitForExit((int)timeout.TotalMilliseconds)) - { stdout = process.StandardOutput.ReadToEnd(); - } else - { process.Kill(); - } return process.ExitCode; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/Rgba32Extensions.cs b/src/NadekoBot/_Extensions/Rgba32Extensions.cs index 227ab1510..abd4565ab 100644 --- a/src/NadekoBot/_Extensions/Rgba32Extensions.cs +++ b/src/NadekoBot/_Extensions/Rgba32Extensions.cs @@ -54,4 +54,4 @@ public static class Rgba32Extensions canvas.Frames.RemoveFrame(0); return canvas; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/ServiceCollectionExtensions.cs b/src/NadekoBot/_Extensions/ServiceCollectionExtensions.cs index 61b3227b4..f44b15891 100644 --- a/src/NadekoBot/_Extensions/ServiceCollectionExtensions.cs +++ b/src/NadekoBot/_Extensions/ServiceCollectionExtensions.cs @@ -1,9 +1,9 @@ -using System.Reflection; using Microsoft.Extensions.DependencyInjection; using NadekoBot.Modules.Music; using NadekoBot.Modules.Music.Resolvers; using NadekoBot.Modules.Music.Services; using StackExchange.Redis; +using System.Reflection; namespace NadekoBot.Extensions; @@ -12,25 +12,22 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddBotStringsServices(this IServiceCollection services, int totalShards) => totalShards <= 1 ? services.AddSingleton() - .AddSingleton() - .AddSingleton() + .AddSingleton() + .AddSingleton() : services.AddSingleton() - .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); public static IServiceCollection AddConfigServices(this IServiceCollection services) { var baseType = typeof(ConfigServiceBase<>); foreach (var type in Assembly.GetCallingAssembly().ExportedTypes.Where(x => x.IsSealed)) - { - if (type.BaseType?.IsGenericType == true && - type.BaseType.GetGenericTypeDefinition() == baseType) + if (type.BaseType?.IsGenericType == true && type.BaseType.GetGenericTypeDefinition() == baseType) { services.AddSingleton(type); services.AddSingleton(x => (IConfigService)x.GetRequiredService(type)); } - } return services; } @@ -40,26 +37,23 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddMusic(this IServiceCollection services) => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(svc => svc.GetRequiredService()); + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(svc => svc.GetRequiredService()); // consider using scrutor, because slightly different versions // of this might be needed in several different places public static IServiceCollection AddSealedSubclassesOf(this IServiceCollection services, Type baseType) { var subTypes = Assembly.GetCallingAssembly() - .ExportedTypes.Where(type => type.IsSealed && baseType.IsAssignableFrom(type)); + .ExportedTypes.Where(type => type.IsSealed && baseType.IsAssignableFrom(type)); - foreach (var subType in subTypes) - { - services.AddSingleton(baseType, subType); - } + foreach (var subType in subTypes) services.AddSingleton(baseType, subType); return services; } @@ -70,4 +64,4 @@ public static class ServiceCollectionExtensions services.AddSingleton(ConnectionMultiplexer.Connect(conf)); return services; } -} +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/StringExtensions.cs b/src/NadekoBot/_Extensions/StringExtensions.cs index 7dcb389bb..014eccfe1 100644 --- a/src/NadekoBot/_Extensions/StringExtensions.cs +++ b/src/NadekoBot/_Extensions/StringExtensions.cs @@ -1,12 +1,24 @@ +using NadekoBot.Common.Yml; using Newtonsoft.Json; using System.Text; using System.Text.RegularExpressions; -using NadekoBot.Common.Yml; namespace NadekoBot.Extensions; public static class StringExtensions { + private static readonly HashSet _lettersAndDigits = new(Enumerable.Range(48, 10) + .Concat(Enumerable.Range(65, 26)) + .Concat(Enumerable.Range(97, 26)) + .Select(x => (char)x)); + + private static readonly Regex _filterRegex = new(@"discord(?:\.gg|\.io|\.me|\.li|(?:app)?\.com\/invite)\/(\w+)", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex _codePointRegex = + new(@"(\\U(?[a-zA-Z0-9]{8})|\\u(?[a-zA-Z0-9]{4})|\\x(?[a-zA-Z0-9]{2}))", + RegexOptions.Compiled); + public static string PadBoth(this string str, int length) { var spaces = length - str.Length; @@ -17,14 +29,8 @@ public static class StringExtensions public static T? MapJson(this string str) => JsonConvert.DeserializeObject(str); - private static readonly HashSet _lettersAndDigits = new(Enumerable.Range(48, 10) - .Concat(Enumerable.Range(65, 26)) - .Concat(Enumerable.Range(97, 26)) - .Select(x => (char)x) - ); - public static string StripHtml(this string input) - => Regex.Replace(input, "<.*?>", String.Empty); + => Regex.Replace(input, "<.*?>", string.Empty); public static string? TrimTo(this string? str, int maxLength, bool hideDots = false) => hideDots ? str?.Truncate(maxLength, string.Empty) : str?.Truncate(maxLength); @@ -49,15 +55,9 @@ public static class StringExtensions var d = new int[n + 1, m + 1]; // Step 1 - if (n == 0) - { - return m; - } + if (n == 0) return m; - if (m == 0) - { - return n; - } + if (m == 0) return n; // Step 2 for (var i = 0; i <= n; d[i, 0] = i++) @@ -70,16 +70,14 @@ public static class StringExtensions // Step 3 for (var i = 1; i <= n; i++) - { //Step 4 - for (var j = 1; j <= m; j++) - { - // Step 5 - var cost = t[j - 1] == s[i - 1] ? 0 : 1; + for (var j = 1; j <= m; j++) + { + // Step 5 + var cost = t[j - 1] == s[i - 1] ? 0 : 1; - // Step 6 - d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost); - } + // Step 6 + d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost); } // Step 7 @@ -96,10 +94,6 @@ public static class StringExtensions return ms; } - private static readonly Regex _filterRegex = new(@"discord(?:\.gg|\.io|\.me|\.li|(?:app)?\.com\/invite)\/(\w+)", - RegexOptions.Compiled | RegexOptions.IgnoreCase - ); - public static bool IsDiscordInvite(this string str) => _filterRegex.IsMatch(str); @@ -109,7 +103,7 @@ public static class StringExtensions public static string SanitizeMentions(this string str, bool sanitizeRoleMentions = false) { str = str.Replace("@everyone", "@everyοne", StringComparison.InvariantCultureIgnoreCase) - .Replace("@here", "@һere", StringComparison.InvariantCultureIgnoreCase); + .Replace("@here", "@һere", StringComparison.InvariantCultureIgnoreCase); if (sanitizeRoleMentions) str = str.SanitizeRoleMentions(); @@ -134,11 +128,6 @@ public static class StringExtensions public static bool IsAlphaNumeric(this string txt) => txt.All(c => _lettersAndDigits.Contains(c)); - private static readonly Regex _codePointRegex = - new(@"(\\U(?[a-zA-Z0-9]{8})|\\u(?[a-zA-Z0-9]{4})|\\x(?[a-zA-Z0-9]{2}))", - RegexOptions.Compiled - ); - public static string UnescapeUnicodeCodePoints(this string input) => _codePointRegex.Replace(input, me => @@ -146,6 +135,5 @@ public static class StringExtensions var str = me.Groups["code"].Value; var newString = YamlHelper.UnescapeUnicodeCodePoint(str); return newString; - } - ); -} + }); +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/UserExtensions.cs b/src/NadekoBot/_Extensions/UserExtensions.cs index 465f56e22..8fc82c7a4 100644 --- a/src/NadekoBot/_Extensions/UserExtensions.cs +++ b/src/NadekoBot/_Extensions/UserExtensions.cs @@ -35,6 +35,5 @@ public static class UserExtensions ? null : new Uri(usr.AvatarId.StartsWith("a_", StringComparison.InvariantCulture) ? $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.gif" - : $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.png" - ); -} + : $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.png"); +} \ No newline at end of file