diff --git a/dotfiles/.config/BetterDiscord/betterdiscord.asar b/dotfiles/.config/BetterDiscord/betterdiscord.asar new file mode 100644 index 0000000..234cc15 Binary files /dev/null and b/dotfiles/.config/BetterDiscord/betterdiscord.asar differ diff --git a/dotfiles/.config/BetterDiscord/data/betterdiscord.asar b/dotfiles/.config/BetterDiscord/data/betterdiscord.asar new file mode 100644 index 0000000..1e95add Binary files /dev/null and b/dotfiles/.config/BetterDiscord/data/betterdiscord.asar differ diff --git a/dotfiles/.config/BetterDiscord/data/emotes.asar b/dotfiles/.config/BetterDiscord/data/emotes.asar new file mode 100644 index 0000000..f1f2e82 Binary files /dev/null and b/dotfiles/.config/BetterDiscord/data/emotes.asar differ diff --git a/dotfiles/.config/BetterDiscord/data/stable/addon-store.json b/dotfiles/.config/BetterDiscord/data/stable/addon-store.json new file mode 100644 index 0000000..fe16134 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/data/stable/addon-store.json @@ -0,0 +1,9863 @@ +{ + "known": [ + "NoReplyPing.plugin.js", + "BetterVolume.plugin.js", + "MessageUtilities.plugin.js", + "Cyan.theme.css", + "CustomQuoter.plugin.js", + "BetterImageViewer.plugin.js", + "RoleMembers.plugin.js", + "Timezones.plugin.js", + "SpotifyListenAlong.plugin.js", + "BetterInvites.plugin.js", + "Unity.theme.css", + "RoundedDiscord.theme.css", + "PinchToZoom.plugin.js", + "DashToSpaceInChannelName.plugin.js", + "VoiceActivity.plugin.js", + "Copier.plugin.js", + "Novum.theme.css", + "VoiceChatNotifications.plugin.js", + "Glass Wave.theme.css", + "Synthesis.theme.css", + "discolored.theme.css", + "JumpToTop.plugin.js", + "Translucence.theme.css", + "NewBNHA.theme.css", + "GameTimeTracker.plugin.js", + "StickerSnatcher.plugin.js", + "ShowSessions.plugin.js", + "HideIconBadge.plugin.js", + "dtm-16.theme.css", + "FrostedGlass.theme.css", + "MinimalCord.theme.css", + "kaleidoscope.theme.css", + "SoundpackControl.plugin.js", + "GameActivityToggle.plugin.js", + "AutoScroll.plugin.js", + "NoMosaic.plugin.js", + "MixPack.theme.css", + "VoiceUsersCounter.plugin.js", + "QuickView.plugin.js", + "alanWalker.theme.css", + "FavoriteMedia.plugin.js", + "system24.theme.css", + "RoleMentionIcons.plugin.js", + "CollapsibleUI.plugin.js", + "PersonalPins.plugin.js", + "Pesterchum.theme.css", + "PermissionsViewer.plugin.js", + "BetterFolders.plugin.js", + "RemoveNicknames.plugin.js", + "MessagePeek.plugin.js", + "FriendNotifications.plugin.js", + "Exponent.theme.css", + "ColorIndicator.plugin.js", + "ChatFilter.plugin.js", + "EmojiParty.plugin.js", + "HideChatIcons.plugin.js", + "BetterRoleColors.plugin.js", + "ReplaceTimestamps.plugin.js", + "Removenorole.plugin.js", + "EditServers.plugin.js", + "BetterStats.plugin.js", + "Solana.theme.css", + "Old-BNHA-Theme.theme.css", + "pyrite.theme.css", + "HideSidebar.plugin.js", + "DarkNeon.theme.css", + "open-in-mpv.plugin.js", + "Memessages.plugin.js", + "MinimalImprovementBorderless.theme.css", + "Steam.theme.css", + "Tritone.theme.css", + "UserVoiceShow.plugin.js", + "AutoSilentMessage.plugin.js", + "GitHub-Dark.theme.css", + "MinimalMode.theme.css", + "BetterFormattingRedux.plugin.js", + "Neobrutal.theme.css", + "surCord.theme.css", + "DiscordFont.plugin.js", + "DoNotTrack.plugin.js", + "DiscordRecolor.theme.css", + "lilypichu.theme.css", + "VoiceEvents.plugin.js", + "EzLight.theme.css", + "UserNotes.plugin.js", + "CallTimeCounter.plugin.js", + "LastFMRichPresence.plugin.js", + "DeezerRP.plugin.js", + "URLDecode.plugin.js", + "Wildberry.theme.css", + "zFloat.theme.css", + "ClickToChat.plugin.js", + "Moon-Rabbits-Dream-About-Virtual.theme.css", + "serversMenuExtender.plugin.js", + "comfy.theme.css", + "BetterAudioPlayer.plugin.js", + "ServerFolders.plugin.js", + "UnicodeEmojis.plugin.js", + "BetterChannelList.plugin.js", + "EnhanceCodeBlocks.plugin.js", + "TeX.plugin.js", + "BetterChatNames.plugin.js", + "noctis-viola.theme.css", + "RevealAllSpoilers.plugin.js", + "TimedLightDarkMode.plugin.js", + "0BDFDB.plugin.js", + "MemeSounds.plugin.js", + "EditRoles.plugin.js", + "SocialMediaLinkConverter.plugin.js", + "MemberCount.plugin.js", + "discord-mica.theme.css", + "KeyboardClick.plugin.js", + "PingNotification.plugin.js", + "NewAkameGaKill.theme.css", + "SettingsModal.theme.css", + "midnight.theme.css", + "0PluginLibrary.plugin.js", + "BubbleThemev2.theme.css", + "chillax.theme.css", + "LastMessageDate.plugin.js", + "MessageScanAI.plugin.js", + "ChannelsPreview.plugin.js", + "RemoveBlockedUsers.plugin.js", + "TextReplacer.plugin.js", + "OSX.theme.css", + "Materialistic.theme.css", + "toggleYourStuff.plugin.js", + "Nox.theme.css", + "UncompressedImages.plugin.js", + "discordia.theme.css", + "GameZen.plugin.js", + "GGO_Kirito.theme.css", + "DiscordPlus.theme.css", + "spectra.theme.css", + "EditChannels.plugin.js", + "ActivityIcons.plugin.js", + "OperaGX.theme.css", + "WhoReacted.plugin.js", + "DiscordFreeEmojis64px.plugin.js", + "dtm-08.theme.css", + "CompleteTimestamps.plugin.js", + "amoled-cord.theme.css", + "NieR-Light-Source.theme.css", + "InMyVoice.plugin.js", + "linkProfilePicture.plugin.js", + "RightClickJoin.plugin.js", + "ZipPreview.plugin.js", + "GuildAndFriendRemovalAlerts.plugin.js", + "Fluent.theme.css", + "BetterAnimations.plugin.js", + "roundmoled.theme.css", + "CanaryLinks.plugin.js", + "LaTeX.plugin.js", + "serverthemes.plugin.js", + "ServerCounter.plugin.js", + "DestinyLFGJoinInviteCopier.plugin.js", + "Material-Discord.theme.css", + "BlurpleRecolor.theme.css", + "tokyo-night.theme.css", + "ExtendedTypingSounds.plugin.js", + "Nord.theme.css", + "RadialStatus.theme.css", + "piOS.theme.css", + "KeywordTracker.plugin.js", + "NineX.theme.css", + "DoubleClickToEdit.plugin.js", + "HideStreamPreview.plugin.js", + "SecretRingTone.plugin.js", + "TypingUsersPopouts.plugin.js", + "FileViewer.plugin.js", + "UserDetails.plugin.js", + "FVUI.theme.css", + "MemberCounter.plugin.js", + "nocturnal.theme.css", + "nordic.theme.css", + "amethyst.theme.css", + "NewRemTheme.theme.css", + "MentionFilter.plugin.js", + "ClearVision_v6.theme.css", + "PreviewMessage.plugin.js", + "DateViewer.plugin.js", + "BetterBannedUsers.plugin.js", + "MaterialDesign.theme.css", + "PinDMs.plugin.js", + "Ultra.theme.css", + "NotificationWhitelist.plugin.js", + "QuickLastMessage.plugin.js", + "UserAffinities.plugin.js", + "AutoIdleOnAFK.plugin.js", + "DisplayServersAsChannels.plugin.js", + "RosyNight.theme.css", + "NotificationSounds.plugin.js", + "VoiceModeAnnounce.plugin.js", + "HideDisabledEmojis.plugin.js", + "Zalgo.plugin.js", + "RoleFilter.plugin.js", + "TypingIndicator.plugin.js", + "ShowConnections.plugin.js", + "HideServersChannelsRedux.plugin.js", + "neutron.theme.css", + "EmojiReplace.theme.css", + "Snowfall.plugin.js", + "WiderUserArea.plugin.js", + "ChannelsBadges.plugin.js", + "RemovedConnectionAlerts.plugin.js", + "BetterFriendList.plugin.js", + "Translator.plugin.js", + "FriendCodes.plugin.js", + "BetterRoleDot.plugin.js", + "Ocean.theme.css", + "SkeuoCord.theme.css", + "SpellCheck.plugin.js", + "FileNameRandomization.plugin.js", + "HideEmbedLink.plugin.js", + "StickerEmojiPreview.plugin.js", + "RedditMentions.plugin.js", + "BetterSyntax.plugin.js", + "MinimalImprovement.theme.css", + "SpotifyControls.plugin.js", + "Fluent-Discord.theme.css", + "Youtube_Nation.theme.css", + "BackgroundManager.plugin.js", + "QuickMention.plugin.js", + "OldTitleBar.plugin.js", + "discorddark.theme.css", + "ChatUserIDsRedux.plugin.js", + "KeepCurrentChannel.plugin.js", + "Neptune.theme.css", + "TopRoleEverywhere.plugin.js", + "DiscordNight.theme.css", + "Azurite.theme.css", + "InsertTimestamps.plugin.js", + "ChannelTabs.plugin.js", + "SpotifyEnhance.plugin.js", + "UserTags.plugin.js", + "RemoveChatButtons.plugin.js", + "removeTrackingURL.plugin.js", + "StatusEverywhere.plugin.js", + "CollapseEmbeds.plugin.js", + "BetterMediaPlayer.plugin.js", + "ShowAllActivities.plugin.js", + "AlwaysOnTop.plugin.js", + "couve.theme.css", + "Slate.theme.css", + "EditUsers.plugin.js", + "GifCaptioner.plugin.js", + "NotifyWhenMuted.plugin.js", + "OpenSteamLinksInApp.plugin.js", + "BetterUserDates.plugin.js", + "SuppressReplyMentions.plugin.js", + "add_reaction.plugin.js", + "CustomStatusPresets.plugin.js", + "Old-AkameGaKill-Theme.theme.css", + "ShowImagesAnyway.plugin.js", + "AuroraGSI.plugin.js", + "GoogleSearchReplace.plugin.js", + "ShowAllMessageButtons.plugin.js", + "WayAFK.plugin.js", + "MoreRoleColors.plugin.js", + "StaffTag.plugin.js", + "PasteAndSend.plugin.js", + "EmoteReplacer.plugin.js", + "AlternativeVistaAutoUpdate.theme.css", + "SilentTyping.plugin.js", + "ChannelPermissions.plugin.js", + "ColorSighted.plugin.js", + "ShowBadgesInChat.plugin.js", + "Spotify-Discord.theme.css", + "BetterMessageLinks.plugin.js", + "ProProfile.plugin.js", + "Discord11.theme.css", + "ChatAliases.plugin.js", + "HorizontalServerList.theme.css", + "BetterGuildTooltip.plugin.js", + "RelativeTimestamps.plugin.js", + "SplitLargeMessages.plugin.js", + "Reminder.plugin.js", + "SpotiCord.theme.css", + "HideMutedCategories.plugin.js", + "NotAnotherAnimeTheme.theme.css", + "ShutUpClyde.plugin.js", + "Fallout4Terminal.theme.css", + "AllCallTimeCounter.plugin.js", + "Dark+.theme.css", + "virtual-boy.theme.css", + "DisplayUsernames.plugin.js", + "DoubleClickVoiceChannels.plugin.js", + "AvatarSettingsButton.plugin.js", + "NewKemonoFriends.theme.css", + "EmojiStatistics.plugin.js", + "NoSpotifyPause.plugin.js", + "HideUtils.plugin.js", + "BlurNSFW.plugin.js", + "ImageFolder.plugin.js", + "Material Blue Discord.theme.css", + "CopyImageLink.plugin.js", + "HideChannels.plugin.js", + "ServerConfig.plugin.js", + "AnotherEternity.theme.css", + "SoftX.theme.css", + "OpenInApp.plugin.js", + "EmbedMoreImages.plugin.js", + "ImageUtilities.plugin.js", + "BetterSearchPage.plugin.js", + "DarkMatter.theme.css", + "UnreadCountBadges.plugin.js", + "BasicBackground.theme.css", + "InvisibleTyping.plugin.js", + "SendStickersAsLinks.plugin.js", + "FactOfTheDay.plugin.js", + "PronounDB.plugin.js", + "ClickableMentions.plugin.js", + "cyberpunk2077.theme.css", + "ServerHider.plugin.js", + "APlatformIndicators.plugin.js", + "dtm-18.theme.css", + "WriteUpperCase.plugin.js", + "dyslexia.theme.css", + "BetterNsfwTag.plugin.js", + "pretty-folders.plugin.js", + "LazyLoadChannels.plugin.js", + "simplify.theme.css", + "Eris.theme.css", + "ViewProfilePicture.plugin.js", + "LinkChannels.plugin.js", + "NotificationPrompt.plugin.js", + "glass_local.theme.css", + "ServerDetails.plugin.js", + "OnlineFriendCount.plugin.js", + "friends-since.plugin.js", + "PinIcon.plugin.js", + "ChannelDms.plugin.js", + "BetterPictureInPicture.plugin.js", + "StreamerModeOnLive.plugin.js", + "ReadAllNotificationsButton.plugin.js", + "T1.theme.css", + "BetterTTS.plugin.js", + "ServerColumns.theme.css", + "ActivityFilter.plugin.js", + "PasscodeLock.plugin.js", + "Quiet.theme.css", + "WizardUI.theme.css", + "roundmoledV2.theme.css", + "Outlook.theme.css", + "CharCounter.plugin.js", + "UrbanDictionary.plugin.js" + ], + "addons": { + "noreplyping.plugin.js": { + "id": 938, + "name": "NoReplyPing", + "file_name": "NoReplyPing.plugin.js", + "type": "plugin", + "description": "Automatically sets replies to not ping the target.", + "version": "1.0.1", + "author": { + "github_id": "5641607", + "github_name": "QbDesu", + "display_name": "Qb", + "discord_name": "iamqb", + "discord_avatar_hash": "97fda6e87e7d3d0fdf0b7fa0f0bd30bc", + "discord_snowflake": "133659541198864384", + "guild": { + "name": "Qb's Pillow Fort", + "snowflake": "371380008226127877", + "invite_link": "https://discord.gg/gj7JFa6mF8", + "avatar_hash": "23e1de8040d9e6ae15ffbafc372d16cd " + } + }, + "likes": 16, + "downloads": 8723, + "tags": [ + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1286.png", + "latest_source_url": "https://raw.githubusercontent.com/BleedingBD/plugin-NoReplyPing/7e267de092a5631e8864c043a1b596a26b3a111c/NoReplyPing.plugin.js", + "initial_release_date": "2023-04-22T21:10:57.711298Z", + "latest_release_date": "2023-11-01T02:17:04.637678Z", + "guild": null + }, + "bettervolume.plugin.js": { + "id": 184, + "name": "BetterVolume", + "file_name": "BetterVolume.plugin.js", + "type": "plugin", + "description": "Set user volume values manually instead of using a slider. Allows setting volumes higher than 200%.", + "version": "3.2.3", + "author": { + "github_id": "19844016", + "github_name": "Zerthox", + "display_name": "Zerthox", + "discord_name": "zerthox", + "discord_avatar_hash": "3966dfed9ef64656359792e34af73305", + "discord_snowflake": "144881947557101568", + "guild": null + }, + "likes": 549, + "downloads": 1187131, + "tags": [ + "voice", + "enhancement", + "members", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/393.png", + "latest_source_url": "https://raw.githubusercontent.com/Zerthox/BetterDiscord-Plugins/87a0cedf72140f033dbe6ddba018e5c11faedbea/dist/bd/BetterVolume.plugin.js", + "initial_release_date": "2021-05-01T13:38:56.150868Z", + "latest_release_date": "2026-01-24T17:24:51.953317Z", + "guild": null + }, + "messageutilities.plugin.js": { + "id": 87, + "name": "MessageUtilities", + "file_name": "MessageUtilities.plugin.js", + "type": "plugin", + "description": "Adds several Quick Actions for Messages (Delete, Edit, Pin, etc.)", + "version": "2.0.1", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 73, + "downloads": 72336, + "tags": [ + "edit", + "shortcut", + "text", + "chat", + "organization" + ], + "thumbnail_url": "/resources/thumbnails/272.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/3793cd86b60db12f4e2916f4f21dc7d9a88a1483/Plugins/MessageUtilities/MessageUtilities.plugin.js", + "initial_release_date": "2021-03-06T17:42:27.275547Z", + "latest_release_date": "2026-02-08T18:51:00.222014Z", + "guild": null + }, + "nospotifypause.plugin.js": { + "id": 1269, + "name": "NoSpotifyPause", + "file_name": "NoSpotifyPause.plugin.js", + "type": "plugin", + "description": "Prevents Discord from pausing your Spotify when streaming or gaming.", + "version": "1.0.1", + "author": { + "github_id": "61830443", + "github_name": "nicola02nb", + "display_name": "nicola02nb", + "discord_name": "nicola02nb", + "discord_avatar_hash": "b0e2a870015d31c91f2d8895d80b2113", + "discord_snowflake": "257900031351193600", + "guild": { + "name": "nicola02nb Dev Support", + "snowflake": "1329222382279327825", + "invite_link": "https://discord.gg/hFuY8DfDGK", + "avatar_hash": null + } + }, + "likes": 6, + "downloads": 23307, + "tags": [ + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1597.png", + "latest_source_url": "https://raw.githubusercontent.com/nicola02nb/BetterDiscord-Stuff/3f833bbfc7b6c165cb8eae009ee9cbed1f3df779/Plugins/NoSpotifyPause/NoSpotifyPause.plugin.js", + "initial_release_date": "2025-03-24T21:41:59.272995Z", + "latest_release_date": "2025-08-08T07:54:13.790179Z", + "guild": null + }, + "cyan.theme.css": { + "id": 753, + "name": "Cyan", + "file_name": "Cyan.theme.css", + "type": "theme", + "description": "Powerful UX, stunning visuals, like never before", + "version": "7.0.0", + "author": { + "github_id": "73998678", + "github_name": "DaBluLite", + "display_name": "DaBluLite", + "discord_name": "dablulite", + "discord_avatar_hash": "d3bb87c70cd5326f09105eba73ef4ff0", + "discord_snowflake": "582170007505731594", + "guild": null + }, + "likes": 23, + "downloads": 38421, + "tags": [ + "transparent", + "layout", + "customizable", + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/1690.png", + "latest_source_url": "https://raw.githubusercontent.com/DaBluLite/Cyan/99b73509a193f7d29b387a74d473acc11d0ca14d/Cyan.theme.css", + "initial_release_date": "2022-07-23T06:40:37.16828Z", + "latest_release_date": "2025-06-13T21:50:22.687521Z", + "guild": null + }, + "quoter.plugin.js": { + "id": 1437, + "name": "Quoter", + "file_name": "Quoter.plugin.js", + "type": "plugin", + "description": "Quote your friends wild statements.", + "version": "1.0.2", + "author": { + "github_id": "90235641", + "github_name": "zrodevkaan", + "display_name": "Arven", + "discord_name": "zrodevkaan", + "discord_avatar_hash": "c088651f69e5f7f9291dcedba65ce8d8", + "discord_snowflake": "917630027477159986", + "guild": { + "name": "Froggy Support", + "snowflake": "1142561913453166794", + "invite_link": "https://discord.gg/t3zMgv7Nvb", + "avatar_hash": "4a4284a4e8e19853afcba9451e2d5fbb " + } + }, + "likes": 1, + "downloads": 1902, + "tags": [ + "fun", + "chat", + "friends" + ], + "thumbnail_url": "/resources/thumbnails/1769.png", + "latest_source_url": "https://raw.githubusercontent.com/zrodevkaan/BDPlugins/7e025009f078bf36270565e4d1a7830587419c22/Plugins/Quoter/Quoter.plugin.js", + "initial_release_date": "2025-11-29T16:40:05.800343Z", + "latest_release_date": "2025-12-11T02:03:15.867203Z", + "guild": null + }, + "customquoter.plugin.js": { + "id": 70, + "name": "CustomQuoter", + "file_name": "CustomQuoter.plugin.js", + "type": "plugin", + "description": "Brings back the Quote Feature and allows you to set your own Quote Formats", + "version": "1.4.1", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 19, + "downloads": 13337, + "tags": [ + "shortcut", + "enhancement", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/245.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/3793cd86b60db12f4e2916f4f21dc7d9a88a1483/Plugins/CustomQuoter/CustomQuoter.plugin.js", + "initial_release_date": "2021-03-06T09:51:39.489865Z", + "latest_release_date": "2026-02-08T18:50:55.883564Z", + "guild": null + }, + "betterimageviewer.plugin.js": { + "id": 1075, + "name": "BetterImageViewer", + "file_name": "BetterImageViewer.plugin.js", + "type": "plugin", + "description": "Better image viewer", + "version": "1.8", + "author": { + "github_id": "68118705", + "github_name": "Legend-Master", + "display_name": ".legend_master", + "discord_name": ".legend_master", + "discord_avatar_hash": "6d94e1a121d1ba84d664379c3a7f3561", + "discord_snowflake": "553594771608698915", + "guild": null + }, + "likes": 25, + "downloads": 38058, + "tags": [ + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1461.gif", + "latest_source_url": "https://raw.githubusercontent.com/Legend-Master/discord-better-image-viewer/93edf7cda3cb863458c26bd6504d133a4110d1ec/BetterImageViewer.plugin.js", + "initial_release_date": "2024-02-19T16:01:15.159361Z", + "latest_release_date": "2026-02-02T15:13:01.618957Z", + "guild": null + }, + "rolemembers.plugin.js": { + "id": 190, + "name": "RoleMembers", + "file_name": "RoleMembers.plugin.js", + "type": "plugin", + "description": "Allows you to see the members of each role on a server. Can also see members from a mention.", + "version": "0.2.1", + "author": { + "github_id": "6865942", + "github_name": "zerebos", + "display_name": "Zerebos", + "discord_name": "zerebos", + "discord_avatar_hash": "e204ec2e923c4e4dfbf98c1eeaaa04f6", + "discord_snowflake": "249746236008169473", + "guild": null + }, + "likes": 105, + "downloads": 123014, + "tags": [ + "chat", + "members", + "utility", + "search", + "roles" + ], + "thumbnail_url": "/resources/thumbnails/402.gif", + "latest_source_url": "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/34a7ae023a35fbc72f8179631b126001e8c2ec6c/Plugins/RoleMembers/RoleMembers.plugin.js", + "initial_release_date": "2021-05-02T02:01:26.170845Z", + "latest_release_date": "2026-02-02T08:05:36.810865Z", + "guild": null + }, + "unity.theme.css": { + "id": 858, + "name": "Unity", + "file_name": "Unity.theme.css", + "type": "theme", + "description": "All for one, One for all. A theme inspired by Samsung's OneUI.", + "version": "1.5", + "author": { + "github_id": "29195927", + "github_name": "joshuah345", + "display_name": "Superuser", + "discord_name": "super.user", + "discord_avatar_hash": "98a3c12cc70fe19812dc8be67bbfc5c9", + "discord_snowflake": "419610859392860162", + "guild": null + }, + "likes": 41, + "downloads": 44444, + "tags": [ + "layout", + "customizable", + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/1431.png", + "latest_source_url": "https://raw.githubusercontent.com/joshuah345/Discord-Stuff/3b4843879a804dc7c9130953607e1780e3b8af83/Themes/Unity/Unity.theme.css", + "initial_release_date": "2022-12-25T22:27:38.171314Z", + "latest_release_date": "2024-03-08T07:22:27.59377Z", + "guild": null + }, + "roundeddiscord.theme.css": { + "id": 1128, + "name": "Rounded Discord", + "file_name": "RoundedDiscord.theme.css", + "type": "theme", + "description": "A theme which make your Discord becomes rounded. Highly customizable and can be used with both Nitro and third-party theme.", + "version": "1.1.1", + "author": { + "github_id": "82872146", + "github_name": "MarkChan0225", + "display_name": "markchan0225", + "discord_name": "markchan0225", + "discord_avatar_hash": "df8e91451bb625bdbaae9eb0c4023691", + "discord_snowflake": "608821416212692993", + "guild": null + }, + "likes": 6, + "downloads": 10510, + "tags": [ + "customizable" + ], + "thumbnail_url": "/resources/thumbnails/1712.png", + "latest_source_url": "https://raw.githubusercontent.com/MarkChan0225/RoundedDiscord/5bebeaa60e5789f9feddb5c5b552c96cd26c87d4/RoundedDiscord.theme.css", + "initial_release_date": "2024-07-06T07:11:47.784784Z", + "latest_release_date": "2024-07-06T07:11:48.653658Z", + "guild": null + }, + "voiceactivity.plugin.js": { + "id": 670, + "name": "VoiceActivity", + "file_name": "VoiceActivity.plugin.js", + "type": "plugin", + "description": "Shows icons and info in popouts, the member list, and more when someone is in a voice channel.", + "version": "1.12.1", + "author": { + "github_id": "68879269", + "github_name": "Neodymium7", + "display_name": "Neodymium", + "discord_name": "neodymium_", + "discord_avatar_hash": "9c50bfb13c623c676697300b65741f71", + "discord_snowflake": "340614112331694081", + "guild": { + "name": "Neodymium's Support Server", + "snowflake": "965006167917068400", + "invite_link": "https://discord.gg/fRbsqH87Av", + "avatar_hash": "940c1cba79a578292a49a0bdd7436f9d " + } + }, + "likes": 85, + "downloads": 90988, + "tags": [ + "shortcut", + "voice", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1009.png", + "latest_source_url": "https://raw.githubusercontent.com/Neodymium7/BetterDiscordStuff/ae69e6331e892398939d37958c627a07f96ea0f7/VoiceActivity/VoiceActivity.plugin.js", + "initial_release_date": "2022-04-05T16:46:25.863418Z", + "latest_release_date": "2026-01-25T09:50:54.215708Z", + "guild": null + }, + "copier.plugin.js": { + "id": 176, + "name": "Copier", + "file_name": "Copier.plugin.js", + "type": "plugin", + "description": "Allows you to copy certain stuff with custom options.", + "version": "1.6.3", + "author": { + "github_id": "46447572", + "github_name": "Strencher", + "display_name": "Strencher", + "discord_name": "strencher", + "discord_avatar_hash": "7237ff8a1459d2cff1578e39884892b1", + "discord_snowflake": "415849376598982656", + "guild": { + "name": "Stern Community", + "snowflake": "458997239738793984", + "invite_link": "https://discord.gg/gvA2ree", + "avatar_hash": "a_1eb44ed607d75dca952eed9095e71f35 " + } + }, + "likes": 94, + "downloads": 90524, + "tags": [ + "channels", + "shortcut", + "enhancement", + "servers", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/356.png", + "latest_source_url": "https://raw.githubusercontent.com/Strencher/BetterDiscordStuff/9a7f0cfd9b5462e483bb5ecb17462fdb21c21ba4/Copier/Copier.plugin.js", + "initial_release_date": "2021-04-26T18:46:33.188568Z", + "latest_release_date": "2026-01-26T20:05:57.847979Z", + "guild": { + "name": "Stern Community", + "snowflake": "458997239738793984", + "invite_link": "https://discord.gg/gvA2ree", + "avatar_hash": "a_1eb44ed607d75dca952eed9095e71f35 " + } + }, + "novum.theme.css": { + "id": 576, + "name": "Novum", + "file_name": "Novum.theme.css", + "type": "theme", + "description": "Red galaxy theme enhanced with couple of addons and small features.", + "version": "5.9.0", + "author": { + "github_id": "40776950", + "github_name": "przemec", + "display_name": "przemec", + "discord_name": "przemec", + "discord_avatar_hash": "f723a1cc0c92e301bf6de6c729331c69", + "discord_snowflake": "214676791955161091", + "guild": null + }, + "likes": 87, + "downloads": 123318, + "tags": [ + "transparent", + "customizable", + "fiction", + "space", + "red" + ], + "thumbnail_url": "/resources/thumbnails/885.png", + "latest_source_url": "https://raw.githubusercontent.com/przemec/Novum/d777b562fef2405263b053a8c2d2b3428469f9cf/Novum.theme.css", + "initial_release_date": "2022-01-18T11:57:04.304628Z", + "latest_release_date": "2025-12-23T22:01:31.107732Z", + "guild": null + }, + "discordfreeemojis.plugin.js": { + "id": 1331, + "name": "FreeEmojis", + "file_name": "DiscordFreeEmojis.plugin.js", + "type": "plugin", + "description": "Link emojis if you don't have nitro! Type them out or use the emoji picker!", + "version": "1.11.2", + "author": { + "github_id": "20029624", + "github_name": "EpicGazel", + "display_name": "gazel", + "discord_name": "gazel", + "discord_avatar_hash": "8c782d7869cec835aa95671b08d91e05", + "discord_snowflake": "119706600230354944", + "guild": null + }, + "likes": 12, + "downloads": 40486, + "tags": [ + "emotes", + "chat" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/EpicGazel/DiscordFreeEmojis/a55bf8c837ca4b9997f83df1a2f74d7de8a9a09f/DiscordFreeEmojis.plugin.js", + "initial_release_date": "2025-05-13T02:09:45.355161Z", + "latest_release_date": "2025-12-16T22:37:44.359321Z", + "guild": null + }, + "glass wave.theme.css": { + "id": 929, + "name": "Glass Wave", + "file_name": "Glass Wave.theme.css", + "type": "theme", + "description": "Discord Glass Wave transparent theme.", + "version": "1.2", + "author": { + "github_id": "53162615", + "github_name": "Elisniper", + "display_name": "Elisniper ", + "discord_name": "elisniper", + "discord_avatar_hash": "b3f43637c4975ba7a0a746858ee3a262", + "discord_snowflake": "253480609224065025", + "guild": null + }, + "likes": 22, + "downloads": 39929, + "tags": [ + "transparent", + "customizable", + "dark", + "aqua" + ], + "thumbnail_url": "/resources/thumbnails/1280.png", + "latest_source_url": "https://raw.githubusercontent.com/Elisniper/Glass-Wave/58bce62db14c379deeacb9f2df93c5a5509999b7/release/Glass Wave.theme.css", + "initial_release_date": "2023-04-08T23:20:35.056838Z", + "latest_release_date": "2023-10-29T01:10:42.511473Z", + "guild": null + }, + "synthesis.theme.css": { + "id": 770, + "name": "Synthesis", + "file_name": "Synthesis.theme.css", + "type": "theme", + "description": "A vibrant neon synthwave theme with customisable background and tweaks.", + "version": "1.8", + "author": { + "github_id": "29710355", + "github_name": "Saltssaumure", + "display_name": "Saltssaumure", + "discord_name": "saltssaumure", + "discord_avatar_hash": "aceb2016f8abe3be7e74739f3a3c862c", + "discord_snowflake": "134142022092062720", + "guild": { + "name": "SUPERHOT Official", + "snowflake": "385450774949396480", + "invite_link": "https://discord.gg/9eAwJF8", + "avatar_hash": "a_421544d89dcae9c3abfd599acbcebee6 " + } + }, + "likes": 77, + "downloads": 87758, + "tags": [ + "transparent", + "customizable", + "orange", + "purple", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/1121.png", + "latest_source_url": "https://raw.githubusercontent.com/Saltssaumure/synthesis-discord-theme/abb3c00f351069be3ed3617f114c2d65a19e9d9f/Synthesis.theme.css", + "initial_release_date": "2022-08-31T14:19:08.309383Z", + "latest_release_date": "2024-06-19T04:42:15.173101Z", + "guild": null + }, + "jumptotop.plugin.js": { + "id": 1067, + "name": "JumpToTop", + "file_name": "JumpToTop.plugin.js", + "type": "plugin", + "description": "Adds a button to the channel header allowing you to jump to first message in a channel.", + "version": "1.0.4", + "author": { + "github_id": "92663142", + "github_name": "Huderon", + "display_name": "Huderon", + "discord_name": "huderon", + "discord_avatar_hash": "bbc46b6d7a3a29cf37c54f7f628e76ef", + "discord_snowflake": "310741793668857859", + "guild": { + "name": "Addon Support", + "snowflake": "1335096998826606605", + "invite_link": "https://discord.gg/NUH7cYDZ5A", + "avatar_hash": "7de124c75441605bfc76d3ce3ee82f7b " + } + }, + "likes": 9, + "downloads": 7217, + "tags": [ + "channels", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1783.png", + "latest_source_url": "https://raw.githubusercontent.com/Huderon/BetterDiscordPlugins/fe614ea60ac377a02d6d8962e8bad705425c6fff/JumpToTop/JumpToTop.plugin.js", + "initial_release_date": "2024-01-07T17:39:12.072726Z", + "latest_release_date": "2025-01-31T00:22:54.610427Z", + "guild": null + }, + "translucence.theme.css": { + "id": 156, + "name": "Translucence", + "file_name": "Translucence.theme.css", + "type": "theme", + "description": "A translucent/frosted glass Discord theme", + "version": "1.0.7", + "author": { + "github_id": "4013216", + "github_name": "CapnKitten", + "display_name": "CapnKitten", + "discord_name": "capnkitten", + "discord_avatar_hash": "dfacf8af0f7d729e6cb8fde41bb0d9a8", + "discord_snowflake": "124276233478471680", + "guild": { + "name": "CapnKitten", + "snowflake": "403760306611814410", + "invite_link": "https://discord.gg/jzJkA6Z", + "avatar_hash": "a_0284b0297857e340d8d6a036ae4557e7 " + } + }, + "likes": 458, + "downloads": 767073, + "tags": [ + "transparent", + "customizable", + "dark" + ], + "thumbnail_url": "/resources/thumbnails/608.png", + "latest_source_url": "https://raw.githubusercontent.com/CapnKitten/Translucence/8d26641cba9fe88a49f7a1da77823040395e8359/Translucence.theme.css", + "initial_release_date": "2021-04-21T18:41:23.445052Z", + "latest_release_date": "2025-10-27T04:25:12.603056Z", + "guild": { + "name": "CapnKitten", + "snowflake": "403760306611814410", + "invite_link": "https://discord.gg/jzJkA6Z", + "avatar_hash": "a_0284b0297857e340d8d6a036ae4557e7 " + } + }, + "newbnha.theme.css": { + "id": 641, + "name": "New BNHA Theme", + "file_name": "NewBNHA.theme.css", + "type": "theme", + "description": "Newer BNHA theme for my return", + "version": "7", + "author": { + "github_id": "30361475", + "github_name": "ShadowDevilsAvenged", + "display_name": "ShadowDevilsAvenged", + "discord_name": "shadowdevilsavenged", + "discord_avatar_hash": "59346cd10940d7cae650a5e2336b316a", + "discord_snowflake": "858800346884997142", + "guild": { + "name": "Shadow's Safe Theme Collection", + "snowflake": "862776993152892939", + "invite_link": "https://discord.gg/6gseVdez6A", + "avatar_hash": "9c81716265f90ddb92c06af468a0094d " + } + }, + "likes": 120, + "downloads": 123275, + "tags": [ + "transparent", + "anime" + ], + "thumbnail_url": "/resources/thumbnails/958.png", + "latest_source_url": "https://raw.githubusercontent.com/ShadowDevilsAvenged/ShadowDevilsAvenged/84c3dd45b7bfe98ee6482b2abfa467c1b81580df/My_Theme_Collection/NewBNHA.theme.css", + "initial_release_date": "2022-03-06T14:38:00.121355Z", + "latest_release_date": "2025-10-01T21:30:41.843001Z", + "guild": null + }, + "gametimetracker.plugin.js": { + "id": 1175, + "name": "GameTimeTracker", + "file_name": "GameTimeTracker.plugin.js", + "type": "plugin", + "description": "Track time spent in games", + "version": "1.2.2", + "author": { + "github_id": "17981128", + "github_name": "Yentis", + "display_name": "Yentis", + "discord_name": "yentis", + "discord_avatar_hash": "32c13fc21db9beedce27f57eaa76033e", + "discord_snowflake": "68834122860077056", + "guild": null + }, + "likes": 1, + "downloads": 5547, + "tags": [ + "game", + "activity", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1528.png", + "latest_source_url": "https://raw.githubusercontent.com/Yentis/betterdiscord-game-time-tracker/5d4ff43d76ad06c896cfea5236fe0c9f30d5e904/GameTimeTracker.plugin.js", + "initial_release_date": "2024-12-12T11:19:49.707635Z", + "latest_release_date": "2026-01-26T22:48:23.404089Z", + "guild": null + }, + "hideiconbadge.plugin.js": { + "id": 189, + "name": "HideIconBadge", + "file_name": "HideIconBadge.plugin.js", + "type": "plugin", + "description": "Hides the badge that appears on the taskbar icon.", + "version": "0.0.6", + "author": { + "github_id": "6865942", + "github_name": "zerebos", + "display_name": "Zerebos", + "discord_name": "zerebos", + "discord_avatar_hash": "e204ec2e923c4e4dfbf98c1eeaaa04f6", + "discord_snowflake": "249746236008169473", + "guild": null + }, + "likes": 13, + "downloads": 14809, + "tags": [ + "edit", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/401.png", + "latest_source_url": "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/9ddd6f5852b3b24dd22fe1504911d9c8d1706bf6/Plugins/HideIconBadge/HideIconBadge.plugin.js", + "initial_release_date": "2021-05-02T01:59:49.182404Z", + "latest_release_date": "2024-12-14T06:45:50.41067Z", + "guild": null + }, + "fullscreentoggle.plugin.js": { + "id": 1263, + "name": "FullscreenToggle", + "file_name": "FullscreenToggle.plugin.js", + "type": "plugin", + "description": "A BetterDiscord plugin to easily make Discord fullscreen and customize its fullscreen behavior", + "version": "2.1.2", + "author": { + "github_id": "43104632", + "github_name": "programmer2514", + "display_name": "programmer2514", + "discord_name": "programmer2514", + "discord_avatar_hash": "b8a0b2af5aaf90549f620f36b8a57cc1", + "discord_snowflake": "563652755814875146", + "guild": null + }, + "likes": 4, + "downloads": 2999, + "tags": [ + "shortcut", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1594.png", + "latest_source_url": "https://raw.githubusercontent.com/programmer2514/BetterDiscord-FullscreenToggle/31355d49ef7a1c78636de2cfb9fffdaeaf0f72c2/FullscreenToggle.plugin.js", + "initial_release_date": "2025-03-19T02:26:33.942834Z", + "latest_release_date": "2025-12-15T21:34:46.62118Z", + "guild": null + }, + "dtm-16.theme.css": { + "id": 161, + "name": "DTM-16", + "file_name": "dtm-16.theme.css", + "type": "theme", + "description": "Formerly called Oldcord - a theme that tries to bring back the 2016 look of Discord.", + "version": "7", + "author": { + "github_id": "51988432", + "github_name": "XYZenix", + "display_name": "11pixels", + "discord_name": "11pixels", + "discord_avatar_hash": "4f66aa9a960aa629d134439dfc7c70e8", + "discord_snowflake": "211270674482724864", + "guild": { + "name": "Omega Nexus", + "snowflake": "581110034977783838", + "invite_link": "https://discord.gg/u3p3KmqEJu", + "avatar_hash": "d12b3bed3a420e7739916b6e9bbdd8d8 " + } + }, + "likes": 67, + "downloads": 94489, + "tags": [ + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/851.png", + "latest_source_url": "https://raw.githubusercontent.com/XYZenix/DTM-16/76c586165b3ab694ac5403e0755e749c7a06b023/dtm-16.theme.css", + "initial_release_date": "2021-04-21T19:41:03.338233Z", + "latest_release_date": "2025-03-25T19:21:54.908235Z", + "guild": null + }, + "frostedglass.theme.css": { + "id": 40, + "name": "Frosted Glass", + "file_name": "FrostedGlass.theme.css", + "type": "theme", + "description": "Display your picture of choice with adjustable blur and brightness. Dark theme is required.", + "version": "2.0.0", + "author": { + "github_id": "20338746", + "github_name": "Gibbu", + "display_name": "Gibbu", + "discord_name": "gibbu", + "discord_avatar_hash": "a_5e798741ca272ab6f141cde6e50b3bd0", + "discord_snowflake": "174868361040232448", + "guild": { + "name": "Gibbu's Support Server", + "snowflake": "546243058992283650", + "invite_link": "https://discord.gg/ZHthyCw", + "avatar_hash": "2c60e7071c4e6f3bf412f42fa9365d35 " + } + }, + "likes": 687, + "downloads": 945826, + "tags": [ + "customizable", + "nature" + ], + "thumbnail_url": "/resources/thumbnails/212.jpg", + "latest_source_url": "https://raw.githubusercontent.com/DiscordStyles/FrostedGlass/c87ce3498268bdb3757f20858365c1f4f505cec3/dist/FrostedGlass.theme.css", + "initial_release_date": "2021-02-23T23:47:51.373892Z", + "latest_release_date": "2021-10-18T15:03:21.492913Z", + "guild": null + }, + "minimalcord.theme.css": { + "id": 125, + "name": "MinimalCord", + "file_name": "MinimalCord.theme.css", + "type": "theme", + "description": "Changes Discord enough to give it a fresh feel while also making it darker. Supports both Light and Dark themes.", + "version": "2.0.0", + "author": { + "github_id": "20338746", + "github_name": "Gibbu", + "display_name": "Gibbu", + "discord_name": "gibbu", + "discord_avatar_hash": "a_5e798741ca272ab6f141cde6e50b3bd0", + "discord_snowflake": "174868361040232448", + "guild": { + "name": "Gibbu's Support Server", + "snowflake": "546243058992283650", + "invite_link": "https://discord.gg/ZHthyCw", + "avatar_hash": "2c60e7071c4e6f3bf412f42fa9365d35 " + } + }, + "likes": 65, + "downloads": 107603, + "tags": [ + "customizable", + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/1130.png", + "latest_source_url": "https://raw.githubusercontent.com/DiscordStyles/MinimalCord/c522850757dc861e4e8e17d151a47bb9ea448dab/dist/MinimalCord.theme.css", + "initial_release_date": "2021-03-26T04:17:42.09521Z", + "latest_release_date": "2021-09-23T00:47:35.559536Z", + "guild": null + }, + "kaleidoscope.theme.css": { + "id": 257, + "name": "kaleidoscope", + "file_name": "kaleidoscope.theme.css", + "type": "theme", + "description": "A vibrant, colourful dark theme inspired by the 2077 theme for VSCode.", + "version": "2.4.2", + "author": { + "github_id": "62459597", + "github_name": "spinfish", + "display_name": "Alex", + "discord_name": "legume#0173", + "discord_avatar_hash": "f7c62fb86cedb984101407b2d4de0df0", + "discord_snowflake": "574870314928832533", + "guild": null + }, + "likes": 136, + "downloads": 144314, + "tags": [ + "dark" + ], + "thumbnail_url": "/resources/thumbnails/473.png", + "latest_source_url": "https://raw.githubusercontent.com/spinfish/kaleidoscope/2f219f5d95911b955c1de8d066225ad97baebed7/source/support/kaleidoscope.theme.css", + "initial_release_date": "2021-05-20T09:12:11.011079Z", + "latest_release_date": "2021-08-19T22:08:19.048023Z", + "guild": null + }, + "gameactivitytoggle.plugin.js": { + "id": 120, + "name": "GameActivityToggle", + "file_name": "GameActivityToggle.plugin.js", + "type": "plugin", + "description": "Adds a Quick-Toggle Game Activity Button", + "version": "1.3.9", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 800, + "downloads": 1462338, + "tags": [ + "game", + "activity", + "shortcut", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/306.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/1f2cff9651c8333cf7e04623f29a45b297b078ee/Plugins/GameActivityToggle/GameActivityToggle.plugin.js", + "initial_release_date": "2021-03-20T13:44:43.833082Z", + "latest_release_date": "2026-02-08T18:22:52.731738Z", + "guild": null + }, + "autoscroll.plugin.js": { + "id": 802, + "name": "AutoScroll", + "file_name": "AutoScroll.plugin.js", + "type": "plugin", + "description": "Autoscroll with the mouse wheel button on GNU/Linux and macOS!", + "version": "0.3.0", + "author": { + "github_id": "15032780", + "github_name": "hackermare", + "display_name": "programmerpony", + "discord_name": "programmerpony#6871", + "discord_avatar_hash": "ccdf426bc77cc85440e27c4501d78e9c", + "discord_snowflake": "300294176501923841", + "guild": null + }, + "likes": 17, + "downloads": 9296, + "tags": [ + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1144.png", + "latest_source_url": "https://raw.githubusercontent.com/hackermare/BD-AutoScroll/f9e225ecd49375749ba689a969da2b1a1c5e71af/AutoScroll.plugin.js", + "initial_release_date": "2022-10-17T21:33:37.441636Z", + "latest_release_date": "2025-03-04T21:50:34.183384Z", + "guild": null + }, + "nomosaic.plugin.js": { + "id": 1090, + "name": "NoMosaic", + "file_name": "NoMosaic.plugin.js", + "type": "plugin", + "description": "Removes the new mosaic image layout on Discord", + "version": "1.2.8", + "author": { + "github_id": "45918062", + "github_name": "KingGamingYT", + "display_name": "KingGamingYT", + "discord_name": "kinggamingyt", + "discord_avatar_hash": "7a6a74d142c1bc7604e40e2239411d2d", + "discord_snowflake": "247153658385399818", + "guild": null + }, + "likes": 4, + "downloads": 2344, + "tags": [ + "channels", + "enhancement", + "chat", + "organization", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1433.png", + "latest_source_url": "https://raw.githubusercontent.com/KingGamingYT/discord-no-mosaic/19798449fa7f8053eb670216b1655642f2a996c2/NoMosaic.plugin.js", + "initial_release_date": "2024-04-21T01:38:17.503052Z", + "latest_release_date": "2026-01-24T06:44:27.08488Z", + "guild": null + }, + "mixpack.theme.css": { + "id": 695, + "name": "MixPack", + "file_name": "MixPack.theme.css", + "type": "theme", + "description": "A pack that combines things I've created and liked the look of.", + "version": "1.0", + "author": { + "github_id": "37601080", + "github_name": "Mixter213", + "display_name": "Mixter213", + "discord_name": "mixter213", + "discord_avatar_hash": "63c18337dfb79c51a61eea1672b30918", + "discord_snowflake": "214478470972047360", + "guild": null + }, + "likes": 54, + "downloads": 75503, + "tags": [ + "dark" + ], + "thumbnail_url": "/resources/thumbnails/1023.png", + "latest_source_url": "https://raw.githubusercontent.com/Mixter213/Discord/239b85bce411de74c5bb4ed18071e091f3ec99ec/Theme/MixPack.theme.css", + "initial_release_date": "2022-04-25T13:08:52.402592Z", + "latest_release_date": "2022-04-25T13:08:52.956687Z", + "guild": null + }, + "alanwalker.theme.css": { + "id": 508, + "name": "Alan Walker Theme", + "file_name": "alanWalker.theme.css", + "type": "theme", + "description": "dark theme with Alan Walker's logo as background,\r\nis compatible with bd's transparency settings", + "version": "1.3.7.1", + "author": { + "github_id": "47672899", + "github_name": "lolpowerluke", + "display_name": "lolpowerluke", + "discord_name": "lolpowerluke", + "discord_avatar_hash": "08f7a98fd769de626b8dfbb5b6c490e5", + "discord_snowflake": "398131626695196672", + "guild": null + }, + "likes": 12, + "downloads": 33194, + "tags": [ + "transparent", + "customizable", + "dark", + "black" + ], + "thumbnail_url": "/resources/thumbnails/1483.png", + "latest_source_url": "https://raw.githubusercontent.com/lolpowerluke/bd-themes/60042afdd047df87bf5bb2304fead3dea35ca8ab/AlanWalkerTheme/alanWalker.theme.css", + "initial_release_date": "2021-10-07T20:10:34.976261Z", + "latest_release_date": "2026-02-12T11:17:04.698462Z", + "guild": null + }, + "bypassblockedorignored.plugin.js": { + "id": 1262, + "name": "BypassBlockedOrIgnored", + "file_name": "BypassBlockedOrIgnored.plugin.js", + "type": "plugin", + "description": "Bypass the blocked or ignored user modal if is present in voice channels", + "version": "1.0.11", + "author": { + "github_id": "61830443", + "github_name": "nicola02nb", + "display_name": "nicola02nb", + "discord_name": "nicola02nb", + "discord_avatar_hash": "b0e2a870015d31c91f2d8895d80b2113", + "discord_snowflake": "257900031351193600", + "guild": { + "name": "nicola02nb Dev Support", + "snowflake": "1329222382279327825", + "invite_link": "https://discord.gg/hFuY8DfDGK", + "avatar_hash": null + } + }, + "likes": 3, + "downloads": 3014, + "tags": [ + "enhancement", + "utility" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/nicola02nb/BetterDiscord-Stuff/5fd47918777cd75cd3d0df6ca4d0d00c1d18e4cf/Plugins/BypassBlockedOrIgnored/BypassBlockedOrIgnored.plugin.js", + "initial_release_date": "2025-03-07T17:31:14.000572Z", + "latest_release_date": "2025-09-18T15:34:58.875671Z", + "guild": null + }, + "favoritemedia.plugin.js": { + "id": 331, + "name": "FavoriteMedia", + "file_name": "FavoriteMedia.plugin.js", + "type": "plugin", + "description": "Allows to favorite GIFs, images, videos and audios.", + "version": "1.13.23", + "author": { + "github_id": "58090137", + "github_name": "Dastan21", + "display_name": "Dastan", + "discord_name": "dastan21", + "discord_avatar_hash": "e2c09e4a7c84c1eb1909de17362f82ac", + "discord_snowflake": "310450863845933057", + "guild": null + }, + "likes": 209, + "downloads": 145022, + "tags": [ + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1224.png", + "latest_source_url": "https://raw.githubusercontent.com/Dastan21/BDAddons/fed84663a7e7336622d754c9a89f8ae7af8b528f/plugins/FavoriteMedia/FavoriteMedia.plugin.js", + "initial_release_date": "2021-06-21T10:16:01.971859Z", + "latest_release_date": "2026-01-31T23:07:04.913146Z", + "guild": null + }, + "system24.theme.css": { + "id": 1143, + "name": "system24", + "file_name": "system24.theme.css", + "type": "theme", + "description": "A tui-style discord theme.", + "version": "2.0.0", + "author": { + "github_id": "34758569", + "github_name": "refact0r", + "display_name": "refact0r", + "discord_name": "refact0r", + "discord_avatar_hash": "3b8b9b4e2adc28e02f55fcffe0bbe8e5", + "discord_snowflake": "508863359777505290", + "guild": null + }, + "likes": 75, + "downloads": 58812, + "tags": [ + "customizable", + "dark" + ], + "thumbnail_url": "/resources/thumbnails/1488.png", + "latest_source_url": "https://raw.githubusercontent.com/refact0r/system24/e8391e9c6112d8beedf7394c77e7d59f053fb7e1/theme/system24.theme.css", + "initial_release_date": "2024-08-30T19:01:28.610739Z", + "latest_release_date": "2025-12-19T06:05:27.37425Z", + "guild": null + }, + "chatbuttonsbegone.plugin.js": { + "id": 1463, + "name": "ChatButtonsBegone", + "file_name": "ChatButtonsBegone.plugin.js", + "type": "plugin", + "description": "Remove annoying stuff from your Discord clients.", + "version": "3.5.0", + "author": { + "github_id": "54820766", + "github_name": "LancersBucket", + "display_name": "thelastbucket", + "discord_name": "thelastbucket", + "discord_avatar_hash": "1413bd691f883f553215c7feb52cafe6", + "discord_snowflake": "355477882082033664", + "guild": null + }, + "likes": 3, + "downloads": 735, + "tags": [ + "enhancement" + ], + "thumbnail_url": "/resources/thumbnails/1802.png", + "latest_source_url": "https://raw.githubusercontent.com/LancersBucket/ChatButtonsBegone/bb915e9d37ac1f3a0e881961d6906bea81f0b6e8/ChatButtonsBegone.plugin.js", + "initial_release_date": "2026-01-15T20:17:38.783606Z", + "latest_release_date": "2026-02-18T14:19:27.804116Z", + "guild": null + }, + "rolementionicons.plugin.js": { + "id": 599, + "name": "RoleMentionIcons", + "file_name": "RoleMentionIcons.plugin.js", + "type": "plugin", + "description": "Displays icons next to role mentions.", + "version": "1.4.3", + "author": { + "github_id": "68879269", + "github_name": "Neodymium7", + "display_name": "Neodymium", + "discord_name": "neodymium_", + "discord_avatar_hash": "9c50bfb13c623c676697300b65741f71", + "discord_snowflake": "340614112331694081", + "guild": { + "name": "Neodymium's Support Server", + "snowflake": "965006167917068400", + "invite_link": "https://discord.gg/fRbsqH87Av", + "avatar_hash": "940c1cba79a578292a49a0bdd7436f9d " + } + }, + "likes": 20, + "downloads": 22860, + "tags": [ + "enhancement", + "roles" + ], + "thumbnail_url": "/resources/thumbnails/993.png", + "latest_source_url": "https://raw.githubusercontent.com/Neodymium7/BetterDiscordStuff/2b70e2e774d5dd0f9f3a3cf762f264225108e312/RoleMentionIcons/RoleMentionIcons.plugin.js", + "initial_release_date": "2022-01-29T21:55:19.485737Z", + "latest_release_date": "2025-08-01T01:35:17.035348Z", + "guild": { + "name": "Neodymium's Support Server", + "snowflake": "965006167917068400", + "invite_link": "https://discord.gg/fRbsqH87Av", + "avatar_hash": "940c1cba79a578292a49a0bdd7436f9d " + } + }, + "collapsibleui.plugin.js": { + "id": 366, + "name": "CollapsibleUI", + "file_name": "CollapsibleUI.plugin.js", + "type": "plugin", + "description": "A feature-rich BetterDiscord plugin that reworks the Discord UI to be significantly more modular.", + "version": "12.3.4", + "author": { + "github_id": "43104632", + "github_name": "programmer2514", + "display_name": "programmer2514", + "discord_name": "programmer2514", + "discord_avatar_hash": "b8a0b2af5aaf90549f620f36b8a57cc1", + "discord_snowflake": "563652755814875146", + "guild": null + }, + "likes": 136, + "downloads": 117445, + "tags": [ + "enhancement", + "organization", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/658.png", + "latest_source_url": "https://raw.githubusercontent.com/programmer2514/BetterDiscord-CollapsibleUI/5dc03ddcc632fb20dc383ff951dc8aa22ced28e8/CollapsibleUI.plugin.js", + "initial_release_date": "2021-07-09T15:44:02.927627Z", + "latest_release_date": "2026-02-17T21:17:29.925702Z", + "guild": null + }, + "bettermentions.plugin.js": { + "id": 1464, + "name": "BetterMentions", + "file_name": "BetterMentions.plugin.js", + "type": "plugin", + "description": "Adds profile pictures to mentions and enables click-to-profile on text editor mentions!", + "version": "1.0.1", + "author": { + "github_id": "48053193", + "github_name": "DaddyBoard", + "display_name": "DaddyBoard", + "discord_name": "daddyboard", + "discord_avatar_hash": "b17e321406b8b40e9a55cbe9889c31d2", + "discord_snowflake": "241334335884492810", + "guild": { + "name": "DaddyBoard's BD Plugins", + "snowflake": "1298432536761991270", + "invite_link": "https://discord.gg/ggNWGDV7e2", + "avatar_hash": "0ebf86191cc8d0ad44ec716406843990 " + } + }, + "likes": 0, + "downloads": 975, + "tags": [ + "text", + "enhancement", + "servers", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/1803.png", + "latest_source_url": "https://raw.githubusercontent.com/DaddyBoard/BD-Plugins/aec53f8a2c31ebfa677fbced72268c9b81c8696b/BetterMentions/BetterMentions.plugin.js", + "initial_release_date": "2026-01-16T03:05:49.116867Z", + "latest_release_date": "2026-01-24T00:15:11.545252Z", + "guild": null + }, + "personalpins.plugin.js": { + "id": 91, + "name": "PersonalPins", + "file_name": "PersonalPins.plugin.js", + "type": "plugin", + "description": "Allows you to locally pin Messages", + "version": "2.3.3", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 106, + "downloads": 53336, + "tags": [ + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/278.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/92f52d9f66172df2c4f0d4394de843332689b861/Plugins/PersonalPins/PersonalPins.plugin.js", + "initial_release_date": "2021-03-06T17:48:42.644664Z", + "latest_release_date": "2026-02-08T18:59:01.549548Z", + "guild": null + }, + "pesterchum.theme.css": { + "id": 1030, + "name": "Pesterchum", + "file_name": "Pesterchum.theme.css", + "type": "theme", + "description": "Emulates the Pesterchum chat client from Homestuck.", + "version": "1.7", + "author": { + "github_id": "29710355", + "github_name": "Saltssaumure", + "display_name": "Saltssaumure", + "discord_name": "saltssaumure", + "discord_avatar_hash": "aceb2016f8abe3be7e74739f3a3c862c", + "discord_snowflake": "134142022092062720", + "guild": { + "name": "SUPERHOT Official", + "snowflake": "385450774949396480", + "invite_link": "https://discord.gg/9eAwJF8", + "avatar_hash": "a_421544d89dcae9c3abfd599acbcebee6 " + } + }, + "likes": 20, + "downloads": 14473, + "tags": [ + "flat", + "customizable", + "fiction", + "yellow", + "high-contrast" + ], + "thumbnail_url": "/resources/thumbnails/1374.png", + "latest_source_url": "https://raw.githubusercontent.com/MiniDiscordThemes/Pesterchum/f3dd9dc84ab65faf03ca372cbdb3c6c4176cb47d/Pesterchum.theme.css", + "initial_release_date": "2023-10-10T01:37:29.775869Z", + "latest_release_date": "2024-06-19T17:56:06.315676Z", + "guild": null + }, + "ddex4.theme.css": { + "id": 1232, + "name": "DDEX4", + "file_name": "ddex4.theme.css", + "type": "theme", + "description": "Discord's scrapped Design Experiment 4, made real.", + "version": "3.1", + "author": { + "github_id": "74104258", + "github_name": "Blade04208", + "display_name": "the.rabbit.disabler", + "discord_name": "the.rabbit.disabler", + "discord_avatar_hash": "4755cf80054087c8d963086dc0f8518c", + "discord_snowflake": "1124341362955919371", + "guild": null + }, + "likes": 4, + "downloads": 7400, + "tags": [ + "customizable", + "dark" + ], + "thumbnail_url": "/resources/thumbnails/1574.png", + "latest_source_url": "https://raw.githubusercontent.com/Blade04208/ddex4/b71827347a3cdd28d55b6f1d07de2fdebd699420/ddex4.theme.css", + "initial_release_date": "2025-02-06T21:08:39.045809Z", + "latest_release_date": "2025-12-19T14:34:03.728851Z", + "guild": null + }, + "permissionsviewer.plugin.js": { + "id": 29, + "name": "PermissionsViewer", + "file_name": "PermissionsViewer.plugin.js", + "type": "plugin", + "description": "Allows you to view a users permissions.", + "version": "0.3.2", + "author": { + "github_id": "6865942", + "github_name": "zerebos", + "display_name": "Zerebos", + "discord_name": "zerebos", + "discord_avatar_hash": "e204ec2e923c4e4dfbf98c1eeaaa04f6", + "discord_snowflake": "249746236008169473", + "guild": null + }, + "likes": 288, + "downloads": 631844, + "tags": [ + "enhancement", + "servers", + "security", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/186.png", + "latest_source_url": "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/6f5e5e5a3b0bbbf92bcf1a560c0dc9650b9291d9/Plugins/PermissionsViewer/PermissionsViewer.plugin.js", + "initial_release_date": "2021-02-22T19:38:41.687097Z", + "latest_release_date": "2026-02-02T08:15:42.336631Z", + "guild": null + }, + "betterfolders.plugin.js": { + "id": 181, + "name": "BetterFolders", + "file_name": "BetterFolders.plugin.js", + "type": "plugin", + "description": "Adds new functionality to server folders. Custom Folder Icons. Close other folders on open.", + "version": "3.8.1", + "author": { + "github_id": "19844016", + "github_name": "Zerthox", + "display_name": "Zerthox", + "discord_name": "zerthox", + "discord_avatar_hash": "3966dfed9ef64656359792e34af73305", + "discord_snowflake": "144881947557101568", + "guild": null + }, + "likes": 175, + "downloads": 176286, + "tags": [ + "enhancement", + "servers" + ], + "thumbnail_url": "/resources/thumbnails/396.png", + "latest_source_url": "https://raw.githubusercontent.com/Zerthox/BetterDiscord-Plugins/7c5f6e4ad7cada88974885daf2bfaedae2658a73/dist/bd/BetterFolders.plugin.js", + "initial_release_date": "2021-05-01T13:35:09.005808Z", + "latest_release_date": "2026-02-14T15:49:14.742314Z", + "guild": null + }, + "removenicknames.plugin.js": { + "id": 96, + "name": "RemoveNicknames", + "file_name": "RemoveNicknames.plugin.js", + "type": "plugin", + "description": "Replaces Nicknames with Accountnames", + "version": "1.4.5", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 22, + "downloads": 26469, + "tags": [ + "enhancement", + "members" + ], + "thumbnail_url": "/resources/thumbnails/283.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/RemoveNicknames/RemoveNicknames.plugin.js", + "initial_release_date": "2021-03-06T17:55:03.799101Z", + "latest_release_date": "2025-12-07T11:27:58.954899Z", + "guild": null + }, + "factoftheday.plugin.js": { + "id": 1312, + "name": "FactOfTheDay", + "file_name": "FactOfTheDay.plugin.js", + "type": "plugin", + "description": "Gives you a (useless) random fact, or qoute of the day each time you login to discord.", + "version": "2.0.0", + "author": { + "github_id": "22408517", + "github_name": "SrS2225a", + "display_name": "Nyx#8614", + "discord_name": ".fenriris", + "discord_avatar_hash": "a_bc121fc48960989bafcbbb385e85a2a4", + "discord_snowflake": "270848136006729728", + "guild": null + }, + "likes": 1, + "downloads": 1895, + "tags": [ + "game", + "fun", + "notifications" + ], + "thumbnail_url": "/resources/thumbnails/1642.png", + "latest_source_url": "https://raw.githubusercontent.com/SrS2225a/BetterDiscord/2ebf9561c8a308585d0eeaec1cf33af7b36df8ef/plugins/FactOfTheDay/FactOfTheDay.plugin.js", + "initial_release_date": "2025-04-28T21:41:21.738958Z", + "latest_release_date": "2025-06-19T01:09:30.601805Z", + "guild": null + }, + "messagepeek.plugin.js": { + "id": 1163, + "name": "MessagePeek", + "file_name": "MessagePeek.plugin.js", + "type": "plugin", + "description": "See the last message in a Channel like on mobile", + "version": "1.2.4", + "author": { + "github_id": "50876016", + "github_name": "domi-btnr", + "display_name": "domi.btnr", + "discord_name": "domi.btnr", + "discord_avatar_hash": "661304b01ea9a6593b93c6c4880070f3", + "discord_snowflake": "354191516979429376", + "guild": { + "name": "¯\\_(ツ)_/¯", + "snowflake": "838690163160514601", + "invite_link": "https://discord.gg/gp2ExK5vc7", + "avatar_hash": "216b8f7141608a933da9c668adb4edee " + } + }, + "likes": 8, + "downloads": 23155, + "tags": [ + "channels", + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1515.png", + "latest_source_url": "https://raw.githubusercontent.com/domi-btnr/BetterDiscordStuff/cee07a958387e89cc379c8cafcb951c62085cfb2/MessagePeek/MessagePeek.plugin.js", + "initial_release_date": "2024-11-24T22:04:44.940198Z", + "latest_release_date": "2026-02-08T05:15:22.818572Z", + "guild": null + }, + "friendnotifications.plugin.js": { + "id": 86, + "name": "FriendNotifications", + "file_name": "FriendNotifications.plugin.js", + "type": "plugin", + "description": "Shows a Notification when a Friend or a User, you choose to observe, changes their Status", + "version": "2.1.4", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 279, + "downloads": 436796, + "tags": [ + "friends" + ], + "thumbnail_url": "/resources/thumbnails/270.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/000751b198abefae0e9a71ba8863653190cdd204/Plugins/FriendNotifications/FriendNotifications.plugin.js", + "initial_release_date": "2021-03-06T17:39:08.505912Z", + "latest_release_date": "2026-02-20T10:00:28.595176Z", + "guild": null + }, + "exponent.theme.css": { + "id": 823, + "name": "Exponent", + "file_name": "Exponent.theme.css", + "type": "theme", + "description": "A Windows XP style theme built for BetterDiscord.", + "version": "1.9", + "author": { + "github_id": "29710355", + "github_name": "Saltssaumure", + "display_name": "Saltssaumure", + "discord_name": "saltssaumure", + "discord_avatar_hash": "aceb2016f8abe3be7e74739f3a3c862c", + "discord_snowflake": "134142022092062720", + "guild": { + "name": "SUPERHOT Official", + "snowflake": "385450774949396480", + "invite_link": "https://discord.gg/9eAwJF8", + "avatar_hash": "a_421544d89dcae9c3abfd599acbcebee6 " + } + }, + "likes": 253, + "downloads": 138621, + "tags": [ + "layout", + "nature", + "dark", + "light", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/1165.png", + "latest_source_url": "https://raw.githubusercontent.com/Saltssaumure/xp-discord-theme/6e9af94d5cc8816a7a6e9fceb2dd8437307cdbc4/Exponent.theme.css", + "initial_release_date": "2022-11-05T04:45:05.311709Z", + "latest_release_date": "2024-06-26T23:29:33.629215Z", + "guild": null + }, + "colorindicator.plugin.js": { + "id": 1058, + "name": "ColorIndicator", + "file_name": "ColorIndicator.plugin.js", + "type": "plugin", + "description": "Highlights color codes in chats", + "version": "1.0.5", + "author": { + "github_id": "19613657", + "github_name": "BinaryQuantumSoul", + "display_name": "quantumsoul", + "discord_name": "quantumsoul", + "discord_avatar_hash": "e3df9db97b7f3d99f88d50fe8624afcb", + "discord_snowflake": "177440665402081281", + "guild": null + }, + "likes": 5, + "downloads": 5773, + "tags": [ + "text", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1402.png", + "latest_source_url": "https://raw.githubusercontent.com/BinaryQuantumSoul/discord-better-messages/a2022c8879b06bb80e0378ba0b6d9323557bd13d/plugins/ColorIndicator/ColorIndicator.plugin.js", + "initial_release_date": "2023-12-29T23:20:15.585396Z", + "latest_release_date": "2026-01-22T23:53:47.567471Z", + "guild": null + }, + "chatfilter.plugin.js": { + "id": 66, + "name": "ChatFilter", + "file_name": "ChatFilter.plugin.js", + "type": "plugin", + "description": "Allows you to censor Words or block complete Messages/Statuses", + "version": "3.6.2", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 45, + "downloads": 92516, + "tags": [ + "text", + "enhancement", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/241.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/ChatFilter/ChatFilter.plugin.js", + "initial_release_date": "2021-03-06T09:47:00.725011Z", + "latest_release_date": "2025-12-07T11:27:54.113351Z", + "guild": null + }, + "hidechaticons.plugin.js": { + "id": 356, + "name": "Hide Chat Icons", + "file_name": "HideChatIcons.plugin.js", + "type": "plugin", + "description": "Hides the chat icons behind a button (also has a hover option)", + "version": "1.3.6", + "author": { + "github_id": "8385001", + "github_name": "Farcrada", + "display_name": "Farcrada", + "discord_name": "Farcrada#1879", + "discord_avatar_hash": "9e4cdff0341d13746d1c9772c951ce2e", + "discord_snowflake": "131212461499088896", + "guild": null + }, + "likes": 58, + "downloads": 57850, + "tags": [ + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/592.gif", + "latest_source_url": "https://raw.githubusercontent.com/Farcrada/DiscordPlugins/c1250d3990ed3b9f1bf3518aa68050402929c52b/Hide-Chat-Icons/HideChatIcons.plugin.js", + "initial_release_date": "2021-07-04T20:08:29.700501Z", + "latest_release_date": "2024-09-20T12:54:53.357468Z", + "guild": null + }, + "livetyping.plugin.js": { + "id": 1311, + "name": "TypingIndicator", + "file_name": "LiveTyping.plugin.js", + "type": "plugin", + "description": "Typing status per user on servers, channels or threads.", + "version": "2.0.0", + "author": { + "github_id": "90235641", + "github_name": "zrodevkaan", + "display_name": "Arven", + "discord_name": "zrodevkaan", + "discord_avatar_hash": "c088651f69e5f7f9291dcedba65ce8d8", + "discord_snowflake": "917630027477159986", + "guild": { + "name": "Froggy Support", + "snowflake": "1142561913453166794", + "invite_link": "https://discord.gg/t3zMgv7Nvb", + "avatar_hash": "4a4284a4e8e19853afcba9451e2d5fbb " + } + }, + "likes": 6, + "downloads": 5598, + "tags": [ + "friends", + "members", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1641.png", + "latest_source_url": "https://raw.githubusercontent.com/zrodevkaan/BDPlugins/c724a7d46b6d3616b124bec2c5897aa69bc51e7d/Plugins/LiveTyping/LiveTyping.plugin.js", + "initial_release_date": "2025-04-27T23:15:15.971051Z", + "latest_release_date": "2026-01-24T00:48:08.68846Z", + "guild": null + }, + "replacetimestamps.plugin.js": { + "id": 699, + "name": "ReplaceTimestamps", + "file_name": "ReplaceTimestamps.plugin.js", + "type": "plugin", + "description": "Replaces plaintext times and dates into Discord's timestamps", + "version": "1.4.3", + "author": { + "github_id": "50876016", + "github_name": "domi-btnr", + "display_name": "domi.btnr", + "discord_name": "domi.btnr", + "discord_avatar_hash": "661304b01ea9a6593b93c6c4880070f3", + "discord_snowflake": "354191516979429376", + "guild": { + "name": "¯\\_(ツ)_/¯", + "snowflake": "838690163160514601", + "invite_link": "https://discord.gg/gp2ExK5vc7", + "avatar_hash": "216b8f7141608a933da9c668adb4edee " + } + }, + "likes": 8, + "downloads": 7102, + "tags": [ + "text", + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1468.png", + "latest_source_url": "https://raw.githubusercontent.com/domi-btnr/BetterDiscordStuff/cee07a958387e89cc379c8cafcb951c62085cfb2/ReplaceTimestamps/ReplaceTimestamps.plugin.js", + "initial_release_date": "2022-05-03T22:29:00.668428Z", + "latest_release_date": "2026-02-08T05:15:19.193288Z", + "guild": null + }, + "editservers.plugin.js": { + "id": 75, + "name": "EditServers", + "file_name": "EditServers.plugin.js", + "type": "plugin", + "description": "Allows you to locally edit Servers", + "version": "2.4.8", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 50, + "downloads": 62993, + "tags": [ + "edit", + "servers" + ], + "thumbnail_url": "/resources/thumbnails/250.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/EditServers/EditServers.plugin.js", + "initial_release_date": "2021-03-06T09:58:05.341158Z", + "latest_release_date": "2025-12-07T11:27:08.387162Z", + "guild": null + }, + "keywordping.plugin.js": { + "id": 1452, + "name": "KeywordPing", + "file_name": "KeywordPing.plugin.js", + "type": "plugin", + "description": "Get notified when messages match your keywords.", + "version": "2.5.0", + "author": { + "github_id": "81967520", + "github_name": "Snusene", + "display_name": "Snues", + "discord_name": ".snues", + "discord_avatar_hash": "48a2f024dc5fe2a29a470c424ab26e8f", + "discord_snowflake": "98862725609816064", + "guild": null + }, + "likes": 4, + "downloads": 529, + "tags": [ + "notifications", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1786.png", + "latest_source_url": "https://raw.githubusercontent.com/Snusene/BetterDiscordPlugins/b9a42cb308f4332282680ff9bc5707a5d364f193/KeywordPing/KeywordPing.plugin.js", + "initial_release_date": "2026-01-04T10:08:00.433841Z", + "latest_release_date": "2026-02-07T12:40:10.554675Z", + "guild": null + }, + "old-bnha-theme.theme.css": { + "id": 714, + "name": "Old BNHA Theme Returns", + "file_name": "Old-BNHA-Theme.theme.css", + "type": "theme", + "description": "Say Hello to the BNHA theme once again,back at it again", + "version": "7", + "author": { + "github_id": "30361475", + "github_name": "ShadowDevilsAvenged", + "display_name": "ShadowDevilsAvenged", + "discord_name": "shadowdevilsavenged", + "discord_avatar_hash": "59346cd10940d7cae650a5e2336b316a", + "discord_snowflake": "858800346884997142", + "guild": { + "name": "Shadow's Safe Theme Collection", + "snowflake": "862776993152892939", + "invite_link": "https://discord.gg/6gseVdez6A", + "avatar_hash": "9c81716265f90ddb92c06af468a0094d " + } + }, + "likes": 39, + "downloads": 35001, + "tags": [ + "transparent", + "anime", + "green" + ], + "thumbnail_url": "/resources/thumbnails/1034.png", + "latest_source_url": "https://raw.githubusercontent.com/ShadowDevilsAvenged/ShadowDevilsAvenged/84c3dd45b7bfe98ee6482b2abfa467c1b81580df/My_Theme_Collection/Old-BNHA-Theme.theme.css", + "initial_release_date": "2022-05-23T13:35:50.02157Z", + "latest_release_date": "2025-10-01T21:30:36.568442Z", + "guild": null + }, + "pyrite.theme.css": { + "id": 166, + "name": "Pyrite", + "file_name": "pyrite.theme.css", + "type": "theme", + "description": "Pyrite for BetterDiscord", + "version": "0.2.8", + "author": { + "github_id": "13402990", + "github_name": "LeafyLuigi", + "display_name": "LeafyLuigi", + "discord_name": "Nao Tomori#1166", + "discord_avatar_hash": "3e675ddc82e2ac841236f888dcf4b932", + "discord_snowflake": "309976820109803520", + "guild": null + }, + "likes": 206, + "downloads": 406625, + "tags": [ + "transparent", + "layout", + "customizable" + ], + "thumbnail_url": "/resources/thumbnails/1035.png", + "latest_source_url": "https://raw.githubusercontent.com/LeafyLuigi/discord-themes/99823b01b6cf6bd3a1b9afc3a5312cafecc5cd62/pyrite/pyrite.theme.css", + "initial_release_date": "2021-04-24T04:00:28.280426Z", + "latest_release_date": "2025-05-06T12:49:11.987081Z", + "guild": { + "name": "Pyrite Theme Server", + "snowflake": "746748217663946844", + "invite_link": "https://discord.gg/7P99YTBRUu", + "avatar_hash": "317586bbc3a69f34d52f0c91b3a3a9eb " + } + }, + "darkneon.theme.css": { + "id": 142, + "name": "Dark Neon", + "file_name": "DarkNeon.theme.css", + "type": "theme", + "description": "A dark theme with customizable bright neon colors!", + "version": "3.0.0", + "author": { + "github_id": "23524203", + "github_name": "B4T3S", + "display_name": "Bates", + "discord_name": "bates", + "discord_avatar_hash": "488b2260c70a0e17ef8d284cfab365bf", + "discord_snowflake": "137259132305539072", + "guild": null + }, + "likes": 282, + "downloads": 462222, + "tags": [ + "dark", + "black", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/879.png", + "latest_source_url": "https://raw.githubusercontent.com/B4T3S/DiscordDarkNeon/ae8485b6bbf995481ce569ed8d8c7dc928150901/DarkNeon.theme.css", + "initial_release_date": "2021-04-17T22:07:38.978163Z", + "latest_release_date": "2025-04-14T09:24:43.446564Z", + "guild": { + "name": "Bates's Dev server", + "snowflake": "931137677695266856", + "invite_link": "https://discord.gg/6qd3SkP6Ch", + "avatar_hash": "70b67c1534c45cb020c0fe328a244f9c " + } + }, + "open-in-mpv.plugin.js": { + "id": 1078, + "name": "open in mpv", + "file_name": "open-in-mpv.plugin.js", + "type": "plugin", + "description": "Use the context menu to open a video in mpv.", + "version": "2.4.0", + "author": { + "github_id": "50302352", + "github_name": "binarynoise", + "display_name": "tv2000", + "discord_name": "tv2000", + "discord_avatar_hash": "93689b56ffb5af10693cf9dc292e2e87", + "discord_snowflake": "528850043227340801", + "guild": null + }, + "likes": 1, + "downloads": 783, + "tags": [ + "shortcut", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1419.png", + "latest_source_url": "https://raw.githubusercontent.com/binarynoise/open-in-mpv/df97a2ed2ebe989ad53f687f7de1cf74046cc15d/open-in-mpv.plugin.js", + "initial_release_date": "2024-02-20T17:54:04.774311Z", + "latest_release_date": "2026-02-04T00:24:08.902829Z", + "guild": null + }, + "minimalimprovementborderless.theme.css": { + "id": 637, + "name": "MinimalImprovement (Without Borders)", + "file_name": "MinimalImprovementBorderless.theme.css", + "type": "theme", + "description": "Dark mode, made geometric and sleek. Now without borders.", + "version": "3.14.0", + "author": { + "github_id": "20595808", + "github_name": "Juicysteak117", + "display_name": "Juicysteak117", + "discord_name": "juicysteak117", + "discord_avatar_hash": "a92a9944bde7b02c4985cd66b7a57c1e", + "discord_snowflake": "112685077707665408", + "guild": null + }, + "likes": 6, + "downloads": 8994, + "tags": [ + "flat", + "dark", + "black" + ], + "thumbnail_url": "/resources/thumbnails/952.png", + "latest_source_url": "https://raw.githubusercontent.com/Juicysteak117/MinimalImprovement/143a006d440cadd18cfb25ba0e982a9aadaab172/MinimalImprovementBorderless.theme.css", + "initial_release_date": "2022-03-05T05:03:07.880599Z", + "latest_release_date": "2023-08-31T20:46:00.257849Z", + "guild": null + }, + "roleexplorer.plugin.js": { + "id": 1279, + "name": "RoleExplorer", + "file_name": "RoleExplorer.plugin.js", + "type": "plugin", + "description": "View the members of roles in a popout. Remake of RoleMembers by Zerebos.", + "version": "1.0.8", + "author": { + "github_id": "48053193", + "github_name": "DaddyBoard", + "display_name": "DaddyBoard", + "discord_name": "daddyboard", + "discord_avatar_hash": "b17e321406b8b40e9a55cbe9889c31d2", + "discord_snowflake": "241334335884492810", + "guild": { + "name": "DaddyBoard's BD Plugins", + "snowflake": "1298432536761991270", + "invite_link": "https://discord.gg/ggNWGDV7e2", + "avatar_hash": "0ebf86191cc8d0ad44ec716406843990 " + } + }, + "likes": 8, + "downloads": 5581, + "tags": [ + "servers", + "members", + "utility", + "search", + "roles" + ], + "thumbnail_url": "/resources/thumbnails/1613.gif", + "latest_source_url": "https://raw.githubusercontent.com/DaddyBoard/BD-Plugins/aec53f8a2c31ebfa677fbced72268c9b81c8696b/RoleExplorer/RoleExplorer.plugin.js", + "initial_release_date": "2025-04-09T23:15:38.679975Z", + "latest_release_date": "2026-01-24T00:15:09.661251Z", + "guild": null + }, + "mentionfix.plugin.js": { + "id": 1324, + "name": "MentionFix", + "file_name": "MentionFix.plugin.js", + "type": "plugin", + "description": "Hate the `@unknown-user` when mentioning someone you've never met? Yeah this fixes that. :>", + "version": "2.0.1", + "author": { + "github_id": "90235641", + "github_name": "zrodevkaan", + "display_name": "Arven", + "discord_name": "zrodevkaan", + "discord_avatar_hash": "c088651f69e5f7f9291dcedba65ce8d8", + "discord_snowflake": "917630027477159986", + "guild": { + "name": "Froggy Support", + "snowflake": "1142561913453166794", + "invite_link": "https://discord.gg/t3zMgv7Nvb", + "avatar_hash": "4a4284a4e8e19853afcba9451e2d5fbb " + } + }, + "likes": 0, + "downloads": 3016, + "tags": [ + "shortcut", + "enhancement", + "members" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/zrodevkaan/BDPlugins/00850cfa1f44413d61c768ac9dc4a058c97df5b1/Plugins/MentionFix/MentionFix.plugin.js", + "initial_release_date": "2025-05-06T21:46:49.566587Z", + "latest_release_date": "2026-01-28T03:32:41.159363Z", + "guild": null + }, + "tritone.theme.css": { + "id": 1008, + "name": "Tritone", + "file_name": "Tritone.theme.css", + "type": "theme", + "description": "A customisable tri-tone theme inspired by IEYTD.", + "version": "2.5", + "author": { + "github_id": "29710355", + "github_name": "Saltssaumure", + "display_name": "Saltssaumure", + "discord_name": "saltssaumure", + "discord_avatar_hash": "aceb2016f8abe3be7e74739f3a3c862c", + "discord_snowflake": "134142022092062720", + "guild": { + "name": "SUPERHOT Official", + "snowflake": "385450774949396480", + "invite_link": "https://discord.gg/9eAwJF8", + "avatar_hash": "a_421544d89dcae9c3abfd599acbcebee6 " + } + }, + "likes": 25, + "downloads": 24493, + "tags": [ + "customizable", + "game", + "red", + "abstract", + "high-contrast" + ], + "thumbnail_url": "/resources/thumbnails/1354.png", + "latest_source_url": "https://raw.githubusercontent.com/Saltssaumure/ieytd-discord-theme/f9487410c07d9a1b7aa292383021406e8d0989d8/Tritone.theme.css", + "initial_release_date": "2023-08-13T18:41:41.369103Z", + "latest_release_date": "2024-06-19T05:36:54.684075Z", + "guild": null + }, + "autosilentmessage.plugin.js": { + "id": 1005, + "name": "AutoSilentMessage", + "file_name": "AutoSilentMessage.plugin.js", + "type": "plugin", + "description": "Make every message you send a silent message.", + "version": "1.0.1", + "author": { + "github_id": "117419247", + "github_name": "BiscuitCleaner", + "display_name": "uwucutesingle", + "discord_name": "glorytotheprc", + "discord_avatar_hash": "1f1bacef0ac26d09cfdd3844ad4fbebc", + "discord_snowflake": "869418754348580885", + "guild": null + }, + "likes": 5, + "downloads": 4537, + "tags": [ + "text", + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1351.png", + "latest_source_url": "https://raw.githubusercontent.com/BiscuitCleaner/BetterDiscordPlugins/c011b402b927bcd9d091eda4d43bd263c5207e50/AutoSilentMessage.plugin.js", + "initial_release_date": "2023-08-10T16:06:10.77223Z", + "latest_release_date": "2024-07-03T05:23:52.236914Z", + "guild": null + }, + "github-dark.theme.css": { + "id": 359, + "name": "GitHub Dark", + "file_name": "GitHub-Dark.theme.css", + "type": "theme", + "description": "A theme based off of the GitHub Dark theme.", + "version": "1.0.0", + "author": { + "github_id": "74567441", + "github_name": "moistp1ckle", + "display_name": "Sango", + "discord_name": "Sango#0090", + "discord_avatar_hash": "6300bffff78ddc770101e0cc3adf5490", + "discord_snowflake": "581286236350709760", + "guild": null + }, + "likes": 135, + "downloads": 241113, + "tags": [ + "dark", + "black", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/597.png", + "latest_source_url": "https://raw.githubusercontent.com/moistp1ckle/GitHub_Dark/320e4264ee2ed37e2db9d92c7d2877e6683be807/GitHub-Dark.theme.css", + "initial_release_date": "2021-07-06T02:02:12.666372Z", + "latest_release_date": "2021-07-10T08:35:20.900304Z", + "guild": null + }, + "betterformattingredux.plugin.js": { + "id": 193, + "name": "BetterFormattingRedux", + "file_name": "BetterFormattingRedux.plugin.js", + "type": "plugin", + "description": "Lets you format your messages with buttons and adds more formatting options.", + "version": "2.3.15", + "author": { + "github_id": "6865942", + "github_name": "zerebos", + "display_name": "Zerebos", + "discord_name": "zerebos", + "discord_avatar_hash": "e204ec2e923c4e4dfbf98c1eeaaa04f6", + "discord_snowflake": "249746236008169473", + "guild": null + }, + "likes": 244, + "downloads": 238692, + "tags": [ + "fun", + "shortcut", + "text", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/405.gif", + "latest_source_url": "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/9e5c9e8e9ae73eb6b6d96112a6890c7da52c59d7/Plugins/BetterFormattingRedux/BetterFormattingRedux.plugin.js", + "initial_release_date": "2021-05-02T02:11:04.773916Z", + "latest_release_date": "2025-08-04T20:34:36.429661Z", + "guild": null + }, + "neobrutal.theme.css": { + "id": 1098, + "name": "Neobrutal", + "file_name": "Neobrutal.theme.css", + "type": "theme", + "description": "Neobrutalism brought to Discord. Supports light and dark mode, see GitHub for customisation options.", + "version": "1.1", + "author": { + "github_id": "29710355", + "github_name": "Saltssaumure", + "display_name": "Saltssaumure", + "discord_name": "saltssaumure", + "discord_avatar_hash": "aceb2016f8abe3be7e74739f3a3c862c", + "discord_snowflake": "134142022092062720", + "guild": { + "name": "SUPERHOT Official", + "snowflake": "385450774949396480", + "invite_link": "https://discord.gg/9eAwJF8", + "avatar_hash": "a_421544d89dcae9c3abfd599acbcebee6 " + } + }, + "likes": 2, + "downloads": 14019, + "tags": [ + "flat", + "customizable", + "dark", + "light", + "high-contrast" + ], + "thumbnail_url": "/resources/thumbnails/1439.png", + "latest_source_url": "https://raw.githubusercontent.com/Saltssaumure/neobrutal-discord-theme/cca5b9ed4139e3885fde2a38eeef83d5e65a14fe/Neobrutal.theme.css", + "initial_release_date": "2024-05-13T03:48:53.105145Z", + "latest_release_date": "2024-06-19T07:12:17.5178Z", + "guild": null + }, + "surcord.theme.css": { + "id": 922, + "name": "surCord", + "file_name": "surCord.theme.css", + "type": "theme", + "description": "A Discord theme inspired by Apple's Human Interface Guidelines.", + "version": "06.2025", + "author": { + "github_id": "76500838", + "github_name": "SlippingGitty", + "display_name": "vozy 🎀", + "discord_name": "vozy 🎀#7673", + "discord_avatar_hash": "656f79665c57b42d917c4c3af689f013", + "discord_snowflake": "359175647257690113", + "guild": null + }, + "likes": 27, + "downloads": 32391, + "tags": [ + "flat", + "customizable", + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/1271.png", + "latest_source_url": "https://raw.githubusercontent.com/SlippingGittys-Discord-Themes/surCord/cd8b5a2ba681d0878abdd01c5aec4082765d927e/surCord.theme.css", + "initial_release_date": "2023-03-25T21:29:08.476126Z", + "latest_release_date": "2025-06-22T14:51:07.116479Z", + "guild": null + }, + "donottrack.plugin.js": { + "id": 186, + "name": "DoNotTrack", + "file_name": "DoNotTrack.plugin.js", + "type": "plugin", + "description": "Stops Discord from tracking everything you do like Sentry and Analytics.", + "version": "0.1.0", + "author": { + "github_id": "6865942", + "github_name": "zerebos", + "display_name": "Zerebos", + "discord_name": "zerebos", + "discord_avatar_hash": "e204ec2e923c4e4dfbf98c1eeaaa04f6", + "discord_snowflake": "249746236008169473", + "guild": null + }, + "likes": 184, + "downloads": 383432, + "tags": [ + "activity", + "status", + "enhancement", + "security" + ], + "thumbnail_url": "/resources/thumbnails/1569.png", + "latest_source_url": "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/6d839d0ab65371819b081218bc43b09d7d6e762d/Plugins/DoNotTrack/DoNotTrack.plugin.js", + "initial_release_date": "2021-05-02T01:56:54.507574Z", + "latest_release_date": "2024-12-15T22:01:53.062684Z", + "guild": null + }, + "moredoubleclicks.plugin.js": { + "id": 1457, + "name": "MoreDoubleClicks", + "file_name": "MoreDoubleClicks.plugin.js", + "type": "plugin", + "description": "Enables double-click actions across more areas, with modifier keys for different behaviors like double-click to reply, react, edit, or delete messages.", + "version": "3.0.0", + "author": { + "github_id": "90235641", + "github_name": "zrodevkaan", + "display_name": "Arven", + "discord_name": "zrodevkaan", + "discord_avatar_hash": "c088651f69e5f7f9291dcedba65ce8d8", + "discord_snowflake": "917630027477159986", + "guild": { + "name": "Froggy Support", + "snowflake": "1142561913453166794", + "invite_link": "https://discord.gg/t3zMgv7Nvb", + "avatar_hash": "4a4284a4e8e19853afcba9451e2d5fbb " + } + }, + "likes": 1, + "downloads": 514, + "tags": [ + "edit", + "shortcut", + "text", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1794.png", + "latest_source_url": "https://raw.githubusercontent.com/zrodevkaan/BDPlugins/c724a7d46b6d3616b124bec2c5897aa69bc51e7d/Plugins/MoreDoubleClicks/MoreDoubleClicks.plugin.js", + "initial_release_date": "2026-01-12T00:45:31.347484Z", + "latest_release_date": "2026-01-24T00:48:14.280488Z", + "guild": null + }, + "discordrecolor.theme.css": { + "id": 47, + "name": "DiscordRecolor", + "file_name": "DiscordRecolor.theme.css", + "type": "theme", + "description": "Allows you to customize Discord's native Color Scheme", + "version": "1.0.0", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 128, + "downloads": 149109, + "tags": [ + "customizable", + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/495.gif", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/67e8c7ce5cab4bf5b693dc5b71d97d6d8f360c4a/Themes/DiscordRecolor/DiscordRecolor.theme.css", + "initial_release_date": "2021-02-25T10:14:05.917827Z", + "latest_release_date": "2025-06-29T10:42:38.728121Z", + "guild": null + }, + "voiceevents.plugin.js": { + "id": 182, + "name": "VoiceEvents", + "file_name": "VoiceEvents.plugin.js", + "type": "plugin", + "description": "Adds TTS Event Notifications to your selected Voice Channel. TeamSpeak feeling.", + "version": "2.8.3", + "author": { + "github_id": "19844016", + "github_name": "Zerthox", + "display_name": "Zerthox", + "discord_name": "zerthox", + "discord_avatar_hash": "3966dfed9ef64656359792e34af73305", + "discord_snowflake": "144881947557101568", + "guild": null + }, + "likes": 15, + "downloads": 13431, + "tags": [ + "notifications", + "channels", + "voice" + ], + "thumbnail_url": "/resources/thumbnails/397.png", + "latest_source_url": "https://raw.githubusercontent.com/Zerthox/BetterDiscord-Plugins/fb071c0b1b510ba5bef34dc43ca6d6940f3146a9/dist/bd/VoiceEvents.plugin.js", + "initial_release_date": "2021-05-01T13:36:48.845885Z", + "latest_release_date": "2026-02-14T15:50:23.643618Z", + "guild": null + }, + "ezlight.theme.css": { + "id": 18, + "name": "EzLight", + "file_name": "EzLight.theme.css", + "type": "theme", + "description": "Burns your eyes but you'll get used to it fast.", + "version": "1.0.3", + "author": { + "github_id": "18194808", + "github_name": "Inve1951", + "display_name": "square", + "discord_name": "square3880", + "discord_avatar_hash": "a14a62c0248cff5dffce2f3e57f25a78", + "discord_snowflake": "219363409097916416", + "guild": null + }, + "likes": 11, + "downloads": 42142, + "tags": [ + "flat", + "light" + ], + "thumbnail_url": "/resources/thumbnails/167.png", + "latest_source_url": "https://raw.githubusercontent.com/Inve1951/BetterDiscordStuff/742a6888b2380696bc5a3ae5bf715e99a25e95d4/themes/EzLight.theme.css", + "initial_release_date": "2021-02-22T19:15:41.764491Z", + "latest_release_date": "2021-08-21T12:11:38.026167Z", + "guild": null + }, + "usernotes.plugin.js": { + "id": 110, + "name": "UserNotes", + "file_name": "UserNotes.plugin.js", + "type": "plugin", + "description": "Allows you to write User Notes locally", + "version": "1.1.2", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 33, + "downloads": 11278, + "tags": [ + "organization", + "members" + ], + "thumbnail_url": "/resources/thumbnails/299.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/UserNotes/UserNotes.plugin.js", + "initial_release_date": "2021-03-06T18:05:43.994013Z", + "latest_release_date": "2025-12-07T11:26:39.945285Z", + "guild": null + }, + "calltimecounter.plugin.js": { + "id": 228, + "name": "CallTimeCounter", + "file_name": "CallTimeCounter.plugin.js", + "type": "plugin", + "description": "Shows how much time you are in a voice chat.", + "version": "1.0.1", + "author": { + "github_id": "45918062", + "github_name": "KingGamingYT", + "display_name": "KingGamingYT", + "discord_name": "kinggamingyt", + "discord_avatar_hash": "7a6a74d142c1bc7604e40e2239411d2d", + "discord_snowflake": "247153658385399818", + "guild": null + }, + "likes": 1489, + "downloads": 2199434, + "tags": [ + "voice", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1804.png", + "latest_source_url": "https://raw.githubusercontent.com/KingGamingYT/CallTimeCounter/06655d077cede0fc6f4511bb1dd349eafeb3051a/CallTimeCounter.plugin.js", + "initial_release_date": "2021-05-12T18:51:56.319625Z", + "latest_release_date": "2026-01-24T06:45:27.86431Z", + "guild": null + }, + "lastfmrichpresence.plugin.js": { + "id": 850, + "name": "LastFMRichPresence", + "file_name": "LastFMRichPresence.plugin.js", + "type": "plugin", + "description": "Last.fm rich presence to show what you're listening to. Finally not just Spotify!", + "version": "1.0.9", + "author": { + "github_id": "26517362", + "github_name": "dimdenGD", + "display_name": "dimden", + "discord_name": "dimden#9999", + "discord_avatar_hash": "a010c0ef6f1fdadbc22bd61112518218", + "discord_snowflake": "705153934758772746", + "guild": { + "name": "dimden.plex 🌙", + "snowflake": "503244758966337546", + "invite_link": "https://discord.gg/5jm5P3SJF3", + "avatar_hash": "0708ac3b22a1b117770983e54687f3a6 " + } + }, + "likes": 19, + "downloads": 23512, + "tags": [ + "fun", + "activity", + "status" + ], + "thumbnail_url": "/resources/thumbnails/1192.png", + "latest_source_url": "https://raw.githubusercontent.com/dimdenGD/LastFMRichPresence/baaa33a8ee93fade23778faa8adc1deedeed461a/LastFMRichPresence.plugin.js", + "initial_release_date": "2022-12-19T16:11:55.475182Z", + "latest_release_date": "2025-12-01T21:44:37.823747Z", + "guild": null + }, + "wildberry.theme.css": { + "id": 52, + "name": "Wildberry", + "file_name": "Wildberry.theme.css", + "type": "theme", + "description": "Inspired by a Pop Tart flavour enjoy a berry inspired color storm for discord. Over 75k+ downloads!", + "version": "1.0.0", + "author": { + "github_id": "60603110", + "github_name": "Daggy1234", + "display_name": "Daggy1234", + "discord_name": "Daggy1234#4120", + "discord_avatar_hash": "6f2a16eeb9c990980905fa11c56858d5", + "discord_snowflake": "491174779278065689", + "guild": null + }, + "likes": 375, + "downloads": 260140, + "tags": [ + "customizable", + "dark", + "red", + "purple", + "abstract" + ], + "thumbnail_url": "/resources/thumbnails/224.png", + "latest_source_url": "https://raw.githubusercontent.com/DagCord/Wildberry/c4f538773a6ccf021c8246496ffc1d347d24cdea/BetterDiscord/Wildberry.theme.css", + "initial_release_date": "2021-02-25T15:45:01.419259Z", + "latest_release_date": "2021-02-25T15:45:01.419259Z", + "guild": null + }, + "holdyourtongue.plugin.js": { + "id": 1453, + "name": "HoldYourTongue", + "file_name": "HoldYourTongue.plugin.js", + "type": "plugin", + "description": "Stop yourself from saying things in chat! Configurable keywords and stop message dialog", + "version": "2.0.0", + "author": { + "github_id": "90235641", + "github_name": "zrodevkaan", + "display_name": "Arven", + "discord_name": "zrodevkaan", + "discord_avatar_hash": "c088651f69e5f7f9291dcedba65ce8d8", + "discord_snowflake": "917630027477159986", + "guild": { + "name": "Froggy Support", + "snowflake": "1142561913453166794", + "invite_link": "https://discord.gg/t3zMgv7Nvb", + "avatar_hash": "4a4284a4e8e19853afcba9451e2d5fbb " + } + }, + "likes": 1, + "downloads": 264, + "tags": [ + "text", + "chat", + "security", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1787.png", + "latest_source_url": "https://raw.githubusercontent.com/zrodevkaan/BDPlugins/c724a7d46b6d3616b124bec2c5897aa69bc51e7d/Plugins/HoldYourTongue/HoldYourTongue.plugin.js", + "initial_release_date": "2026-01-05T05:23:34.508181Z", + "latest_release_date": "2026-01-24T00:48:05.102258Z", + "guild": null + }, + "prioritydm.plugin.js": { + "id": 1460, + "name": "PriorityDM", + "file_name": "PriorityDM.plugin.js", + "type": "plugin", + "description": "Bypass Do Not Disturb for DMs from specific people. Right click a user to add them.", + "version": "1.0.6", + "author": { + "github_id": "81967520", + "github_name": "Snusene", + "display_name": "Snues", + "discord_name": ".snues", + "discord_avatar_hash": "48a2f024dc5fe2a29a470c424ab26e8f", + "discord_snowflake": "98862725609816064", + "guild": null + }, + "likes": 5, + "downloads": 595, + "tags": [ + "notifications", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1797.png", + "latest_source_url": "https://raw.githubusercontent.com/Snusene/BetterDiscordPlugins/74c2e0177093af0bd9042df9a6a748b4d9cf44db/PriorityDM/PriorityDM.plugin.js", + "initial_release_date": "2026-01-12T22:16:19.805229Z", + "latest_release_date": "2026-02-07T12:30:28.808968Z", + "guild": null + }, + "clicktochat.plugin.js": { + "id": 382, + "name": "ClickToChat", + "file_name": "ClickToChat.plugin.js", + "type": "plugin", + "description": "Click to open direct message", + "version": "1.1.5", + "author": { + "github_id": "53181695", + "github_name": "hobbica98", + "display_name": "hobbica", + "discord_name": "hobbica#8423", + "discord_avatar_hash": "dcb21334fe1abeeee32d4505d4473ab4", + "discord_snowflake": "83806103388815360", + "guild": null + }, + "likes": 21, + "downloads": 11176, + "tags": [ + "shortcut", + "chat", + "organization" + ], + "thumbnail_url": "/resources/thumbnails/664.png", + "latest_source_url": "https://raw.githubusercontent.com/hobbica98/ClickToChat-BetterDiscord-Plugin/b994a63b5d37721a1224e0345444da59e69d97ee/ClickToChat.plugin.js", + "initial_release_date": "2021-07-14T13:26:23.719848Z", + "latest_release_date": "2025-05-23T12:33:43.572315Z", + "guild": null + }, + "moon-rabbits-dream-about-virtual.theme.css": { + "id": 483, + "name": "Moon Rabbits Dream About Virtual", + "file_name": "Moon-Rabbits-Dream-About-Virtual.theme.css", + "type": "theme", + "description": "Based in Tsukino Mito Album cover\r\n| Lastest version is 4!!!\r\n| support server: https://discord.gg/PsNtzGeHuW", + "version": "4", + "author": { + "github_id": "79029257", + "github_name": "zuzumi-f", + "display_name": "zuzumi", + "discord_name": "zuzumiarchivo", + "discord_avatar_hash": "ff307784d53358a6a1ed4f8e4321b9e9", + "discord_snowflake": "403725623161257984", + "guild": { + "name": "zuzumi's support server", + "snowflake": "931920891946868796", + "invite_link": "https://discord.gg/PsNtzGeHuW", + "avatar_hash": "19c8b8c36d7a4e0f3991bbfbeae92ca8 " + } + }, + "likes": 425, + "downloads": 207372, + "tags": [ + "customizable", + "anime" + ], + "thumbnail_url": "/resources/thumbnails/1471.png", + "latest_source_url": "https://raw.githubusercontent.com/zuzumi-f/Moon-Rabbits-Dream-About-Virtual/dca0908c01f4912bc9337f586e8dc71fd81b3c4a/Moon-Rabbits-Dream-About-Virtual.theme.css", + "initial_release_date": "2021-09-11T00:55:19.036258Z", + "latest_release_date": "2024-06-27T16:56:07.385452Z", + "guild": null + }, + "serverfolders.plugin.js": { + "id": 101, + "name": "ServerFolders", + "file_name": "ServerFolders.plugin.js", + "type": "plugin", + "description": "Changes Discord's Folders, Servers open in a new Container, also adds extra Features to more easily organize, customize and manage your Folders", + "version": "7.4.7", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 118, + "downloads": 103390, + "tags": [ + "enhancement", + "servers", + "organization" + ], + "thumbnail_url": "/resources/thumbnails/288.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/8e5b069baefd1e5de2fce990f09a932712a45fd4/Plugins/ServerFolders/ServerFolders.plugin.js", + "initial_release_date": "2021-03-06T17:58:53.484472Z", + "latest_release_date": "2026-01-31T16:54:26.237643Z", + "guild": null + }, + "unicodeemojis.plugin.js": { + "id": 1063, + "name": "UnicodeEmojis", + "file_name": "UnicodeEmojis.plugin.js", + "type": "plugin", + "description": "Replaces discord emojis that you send with their unicode equivalent", + "version": "1.1.6", + "author": { + "github_id": "76746384", + "github_name": "TheLazySquid", + "display_name": "TheLazySquid", + "discord_name": "thelazysquid", + "discord_avatar_hash": "e04c7e5372cbf77ecf7bd2784775aa3c", + "discord_snowflake": "619261917352951815", + "guild": { + "name": "TheLazySquid's Stuff", + "snowflake": "1432531781780897814", + "invite_link": "https://discord.gg/fKdAaFYbD5", + "avatar_hash": "4264dfaa6aefa8ac415b4680731dc25b " + } + }, + "likes": 1, + "downloads": 3885, + "tags": [ + "edit", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/1405.png", + "latest_source_url": "https://raw.githubusercontent.com/TheLazySquid/BetterDiscordPlugins/4dac5cb7bfc292366c5f8d288e180dffe15c2400/plugins/UnicodeEmojis/UnicodeEmojis.plugin.js", + "initial_release_date": "2024-01-05T16:08:35.544966Z", + "latest_release_date": "2026-02-10T13:00:31.091496Z", + "guild": null + }, + "betterchannellist.plugin.js": { + "id": 1044, + "name": "BetterChannelList", + "file_name": "BetterChannelList.plugin.js", + "type": "plugin", + "description": "2 in 1: Shows the most recent message for each channel and brings channel list redesign from the new mobile UI.", + "version": "1.2.14", + "author": { + "github_id": "52377180", + "github_name": "arg0NNY", + "display_name": "arg0NNY", + "discord_name": "arg0nny", + "discord_avatar_hash": "87d3e880f144cc8756954fce94fb3548", + "discord_snowflake": "224538553944637440", + "guild": { + "name": "arg0NNY's Lounge", + "snowflake": "947007182552064050", + "invite_link": "https://discord.gg/M8DBtcZjXD", + "avatar_hash": "840d87c006a42a594deb620339b8d178 " + } + }, + "likes": 21, + "downloads": 16489, + "tags": [ + "channels", + "enhancement" + ], + "thumbnail_url": "/resources/thumbnails/1390.gif", + "latest_source_url": "https://raw.githubusercontent.com/arg0NNY/DiscordPlugins/e719f7adbc60a12a4427fce96724203cb6b7b691/BetterChannelList/BetterChannelList.plugin.js", + "initial_release_date": "2023-11-05T08:01:33.789198Z", + "latest_release_date": "2026-01-26T18:49:52.796725Z", + "guild": null + }, + "enhancecodeblocks.plugin.js": { + "id": 878, + "name": "enhancecodeblocks", + "file_name": "EnhanceCodeBlocks.plugin.js", + "type": "plugin", + "description": "Enhances Discords Codeblocks & Text File Attachments", + "version": "1.0.26", + "author": { + "github_id": "71196819", + "github_name": "doggybootsy", + "display_name": "DoggyBootsy", + "discord_name": "doggybootsy", + "discord_avatar_hash": "51e45b02bb0acf0449a87f3f1e079fc8", + "discord_snowflake": "515780151791976453", + "guild": null + }, + "likes": 9, + "downloads": 5880, + "tags": [ + "text", + "enhancement", + "chat", + "utility", + "developers" + ], + "thumbnail_url": "/resources/thumbnails/1226.png", + "latest_source_url": "https://raw.githubusercontent.com/doggybootsy/enhancecodeblocks/dd436bbc108e8ae76bf556e8c262bb19b8bd15d8/dist/EnhanceCodeBlocks.plugin.js", + "initial_release_date": "2023-02-10T20:23:31.022502Z", + "latest_release_date": "2025-02-12T01:00:24.84002Z", + "guild": null + }, + "betterchatnames.plugin.js": { + "id": 679, + "name": "BetterChatNames", + "file_name": "BetterChatNames.plugin.js", + "type": "plugin", + "description": "Improves chat names by automatically capitalising them, and removing dashes + underscores", + "version": "1.9.0", + "author": { + "github_id": "80194912", + "github_name": "Break-Ben", + "display_name": "Break", + "discord_name": "break_ben", + "discord_avatar_hash": "2a199e01b7a94e5cef7f638ce257eee5", + "discord_snowflake": "231675734693773325", + "guild": null + }, + "likes": 46, + "downloads": 53643, + "tags": [ + "channels", + "text", + "enhancement", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/1006.png", + "latest_source_url": "https://raw.githubusercontent.com/Break-Ben/BetterDiscordAddons/1cf4c85ee3a43a03d882cb90ff33a83a2d198877/BetterChatNames/BetterChatNames.plugin.js", + "initial_release_date": "2022-04-12T13:49:34.424848Z", + "latest_release_date": "2026-02-19T07:17:03.575021Z", + "guild": null + }, + "noctis-viola.theme.css": { + "id": 320, + "name": "Noctis Viola", + "file_name": "noctis-viola.theme.css", + "type": "theme", + "description": "A Better Discord theme based on the Noctis Viola VSCode theme.", + "version": "0.6.1", + "author": { + "github_id": "11217065", + "github_name": "ChaseIngebritson", + "display_name": "Goslopo", + "discord_name": "GOmonkeymanGO#7025", + "discord_avatar_hash": "ac4b8920f2d20f0e7cf8286cfc785b65", + "discord_snowflake": "132935909640241153", + "guild": null + }, + "likes": 88, + "downloads": 69470, + "tags": [ + "flat", + "dark", + "purple" + ], + "thumbnail_url": "/resources/thumbnails/549.png", + "latest_source_url": "https://raw.githubusercontent.com/ChaseIngebritson/noctis-viola-discord-theme/e75113fd20476786042826de7cd408af65d011aa/dist/noctis-viola.theme.css", + "initial_release_date": "2021-06-12T20:11:21.519196Z", + "latest_release_date": "2022-03-26T03:05:26.396766Z", + "guild": null + }, + "transcribevoicenotes.plugin.js": { + "id": 1458, + "name": "TranscribeVoiceNotes", + "file_name": "TranscribeVoiceNotes.plugin.js", + "type": "plugin", + "description": "Transcribes voice notes in Discord using STT (speech-to-text). Requires your own API key (OpenAI or compatible API).", + "version": "0.1.4", + "author": { + "github_id": "3945220", + "github_name": "jh0ker", + "display_name": "jh0ker", + "discord_name": "jh0ker", + "discord_avatar_hash": "27d06eda741fce40f2efe1f519d3b222", + "discord_snowflake": "325250926795554816", + "guild": null + }, + "likes": 3, + "downloads": 729, + "tags": [ + "voice", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1801.png", + "latest_source_url": "https://raw.githubusercontent.com/jh0ker/better-discord-transcribe-voice-notes/bbbefdd230041585c7dff3929f8383b619e5ce40/dist/TranscribeVoiceNotes.plugin.js", + "initial_release_date": "2026-01-12T01:12:06.770517Z", + "latest_release_date": "2026-01-13T02:02:55.267266Z", + "guild": null + }, + "revealallspoilers.plugin.js": { + "id": 97, + "name": "RevealAllSpoilers", + "file_name": "RevealAllSpoilers.plugin.js", + "type": "plugin", + "description": "Allows you to reveal all Spoilers within a Message/Status by holding the Ctrl Key and clicking a Spoiler", + "version": "1.1.2", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 44, + "downloads": 51487, + "tags": [ + "shortcut", + "text", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/603.gif", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/RevealAllSpoilers/RevealAllSpoilers.plugin.js", + "initial_release_date": "2021-03-06T17:55:57.751395Z", + "latest_release_date": "2025-12-07T11:28:23.403669Z", + "guild": null + }, + "timedlightdarkmode.plugin.js": { + "id": 108, + "name": "TimedLightDarkMode", + "file_name": "TimedLightDarkMode.plugin.js", + "type": "plugin", + "description": "Adds a Time Slider to the Appearance Settings", + "version": "1.2.1", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 19, + "downloads": 11516, + "tags": [ + "enhancement" + ], + "thumbnail_url": "/resources/thumbnails/295.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/TimedLightDarkMode/TimedLightDarkMode.plugin.js", + "initial_release_date": "2021-03-06T18:04:29.998659Z", + "latest_release_date": "2025-12-07T11:26:28.471802Z", + "guild": null + }, + "0bdfdb.plugin.js": { + "id": 59, + "name": "BDFDB", + "file_name": "0BDFDB.plugin.js", + "type": "plugin", + "description": "Required Library for DevilBro's Plugins", + "version": "4.4.6", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 255, + "downloads": 400438, + "tags": [ + "library" + ], + "thumbnail_url": "/resources/thumbnails/234.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/4e992befefe77cdbbcbe25c629beaee803e92188/Library/0BDFDB.plugin.js", + "initial_release_date": "2021-03-06T09:38:58.199352Z", + "latest_release_date": "2026-02-20T19:45:50.716757Z", + "guild": null + }, + "editroles.plugin.js": { + "id": 127, + "name": "EditRoles", + "file_name": "EditRoles.plugin.js", + "type": "plugin", + "description": "Allows you to locally edit Roles", + "version": "1.2.7", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 45, + "downloads": 82921, + "tags": [ + "edit", + "members", + "roles" + ], + "thumbnail_url": "/resources/thumbnails/314.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/8c4178a4202e18dcb36649d5ce7290065e2d6dbf/Plugins/EditRoles/EditRoles.plugin.js", + "initial_release_date": "2021-04-02T14:32:23.220841Z", + "latest_release_date": "2026-01-31T16:25:49.859678Z", + "guild": null + }, + "discord-mica.theme.css": { + "id": 1152, + "name": "Discord Mica", + "file_name": "discord-mica.theme.css", + "type": "theme", + "description": "Discord with mica :)", + "version": "1.0", + "author": { + "github_id": "44529370", + "github_name": "PL7963", + "display_name": "coolkie", + "discord_name": "coolkie", + "discord_avatar_hash": "22035234b04cc7dddfd2ec8dfda123fe", + "discord_snowflake": "247181419313496074", + "guild": null + }, + "likes": 24, + "downloads": 57996, + "tags": [ + "transparent" + ], + "thumbnail_url": "/resources/thumbnails/1501.png", + "latest_source_url": "https://raw.githubusercontent.com/PL7963/Discord-Mica/5a4b26304e430d269923a131c0a3c682872a227c/discord-mica.theme.css", + "initial_release_date": "2024-10-20T09:20:20.564803Z", + "latest_release_date": "2025-07-31T11:52:03.915069Z", + "guild": null + }, + "alwaysexpandroles.plugin.js": { + "id": 1456, + "name": "AlwaysExpandRoles", + "file_name": "AlwaysExpandRoles.plugin.js", + "type": "plugin", + "description": "Always expands the role list in profile popouts (optionally keeps them expanded) and can optionally hide the collapse button.", + "version": "1.0.2", + "author": { + "github_id": "79576222", + "github_name": "SBvn-dev", + "display_name": "samanticate", + "discord_name": "samanticate", + "discord_avatar_hash": "e7595d15ddd89c0379194b1382463fed", + "discord_snowflake": "505752339173605397", + "guild": null + }, + "likes": 0, + "downloads": 347, + "tags": [ + "status", + "servers", + "members" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/SBvn-dev/BetterDiscord/3922a055eeca4a0f9315729e7033ded5364e52f5/AlwaysExpandRoles.plugin.js", + "initial_release_date": "2026-01-10T22:37:58.979673Z", + "latest_release_date": "2026-01-24T06:12:28.842087Z", + "guild": null + }, + "pingnotification.plugin.js": { + "id": 1141, + "name": "PingNotification", + "file_name": "PingNotification.plugin.js", + "type": "plugin", + "description": "Show in-app notifications for anything you would hear a ping for.", + "version": "9.3.0", + "author": { + "github_id": "48053193", + "github_name": "DaddyBoard", + "display_name": "DaddyBoard", + "discord_name": "daddyboard", + "discord_avatar_hash": "b17e321406b8b40e9a55cbe9889c31d2", + "discord_snowflake": "241334335884492810", + "guild": { + "name": "DaddyBoard's BD Plugins", + "snowflake": "1298432536761991270", + "invite_link": "https://discord.gg/ggNWGDV7e2", + "avatar_hash": "0ebf86191cc8d0ad44ec716406843990 " + } + }, + "likes": 15, + "downloads": 8939, + "tags": [ + "activity", + "notifications", + "text", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1755.png", + "latest_source_url": "https://raw.githubusercontent.com/DaddyBoard/BD-Plugins/c2f138da3f558e371b7f431e43411dc96926fce3/PingNotification/PingNotification.plugin.js", + "initial_release_date": "2024-08-29T23:15:06.750659Z", + "latest_release_date": "2026-02-14T00:49:22.189485Z", + "guild": null + }, + "newoldprofiles.plugin.js": { + "id": 1398, + "name": "NewOldProfiles", + "file_name": "NewOldProfiles.plugin.js", + "type": "plugin", + "description": "A full, largely accurate restoration of Discord's profile layout used from 2018 to 2021. Features modern additions such as banners, theme colors, and guild tags.", + "version": "1.1.1", + "author": { + "github_id": "45918062", + "github_name": "KingGamingYT", + "display_name": "KingGamingYT", + "discord_name": "kinggamingyt", + "discord_avatar_hash": "7a6a74d142c1bc7604e40e2239411d2d", + "discord_snowflake": "247153658385399818", + "guild": null + }, + "likes": 1, + "downloads": 600, + "tags": [ + "activity", + "status", + "enhancement", + "organization", + "members" + ], + "thumbnail_url": "/resources/thumbnails/1726.png", + "latest_source_url": "https://raw.githubusercontent.com/KingGamingYT/NewOldProfiles/d6f15b65d5c6de865219be772632e3a0722e6781/dist/NewOldProfiles.plugin.js", + "initial_release_date": "2025-09-01T05:24:59.835616Z", + "latest_release_date": "2026-02-15T06:51:08.664245Z", + "guild": null + }, + "newakamegakill.theme.css": { + "id": 659, + "name": "New Akame Ga Kill! Theme", + "file_name": "NewAkameGaKill.theme.css", + "type": "theme", + "description": "Newer Akame Ga Kill! theme for my return", + "version": "7.5", + "author": { + "github_id": "30361475", + "github_name": "ShadowDevilsAvenged", + "display_name": "ShadowDevilsAvenged", + "discord_name": "shadowdevilsavenged", + "discord_avatar_hash": "59346cd10940d7cae650a5e2336b316a", + "discord_snowflake": "858800346884997142", + "guild": { + "name": "Shadow's Safe Theme Collection", + "snowflake": "862776993152892939", + "invite_link": "https://discord.gg/6gseVdez6A", + "avatar_hash": "9c81716265f90ddb92c06af468a0094d " + } + }, + "likes": 330, + "downloads": 412905, + "tags": [ + "transparent", + "anime", + "red" + ], + "thumbnail_url": "/resources/thumbnails/980.png", + "latest_source_url": "https://raw.githubusercontent.com/ShadowDevilsAvenged/ShadowDevilsAvenged/84c3dd45b7bfe98ee6482b2abfa467c1b81580df/My_Theme_Collection/NewAkameGaKill.theme.css", + "initial_release_date": "2022-03-19T11:02:02.305383Z", + "latest_release_date": "2025-10-01T21:30:35.009475Z", + "guild": null + }, + "settingsmodal.theme.css": { + "id": 153, + "name": "SettingsModal", + "file_name": "SettingsModal.theme.css", + "type": "theme", + "description": "Turns Settings Windows like User-/Channel-/Serversettings and Boost Overview into Modals", + "version": "1.0.1", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 69, + "downloads": 45345, + "tags": [ + "layout", + "customizable", + "other" + ], + "thumbnail_url": "/resources/thumbnails/335.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/87d5c052677ea8fa8acff2bf5d1ad2184ef9ea6d/Themes/SettingsModal/SettingsModal.theme.css", + "initial_release_date": "2021-04-21T16:01:08.920705Z", + "latest_release_date": "2023-11-18T15:24:01.358354Z", + "guild": null + }, + "midnight.theme.css": { + "id": 507, + "name": "midnight", + "file_name": "midnight.theme.css", + "type": "theme", + "description": "a dark, customizable discord theme.", + "version": "2.1.1", + "author": { + "github_id": "34758569", + "github_name": "refact0r", + "display_name": "refact0r", + "discord_name": "refact0r", + "discord_avatar_hash": "3b8b9b4e2adc28e02f55fcffe0bbe8e5", + "discord_snowflake": "508863359777505290", + "guild": null + }, + "likes": 367, + "downloads": 354130, + "tags": [ + "flat", + "customizable", + "dark", + "black" + ], + "thumbnail_url": "/resources/thumbnails/1384.png", + "latest_source_url": "https://raw.githubusercontent.com/refact0r/midnight-discord/d16c0dd0b403bc0508a22b7ba2048a669a9887ce/themes/midnight.theme.css", + "initial_release_date": "2021-10-06T21:45:46.751951Z", + "latest_release_date": "2025-12-19T07:30:18.91426Z", + "guild": null + }, + "bubblethemev2.theme.css": { + "id": 664, + "name": "Bubble Theme v2", + "file_name": "BubbleThemev2.theme.css", + "type": "theme", + "description": "Bubble Theme v2. Return request by Reeses Benis Butter Cups", + "version": "2.9", + "author": { + "github_id": "30361475", + "github_name": "ShadowDevilsAvenged", + "display_name": "ShadowDevilsAvenged", + "discord_name": "shadowdevilsavenged", + "discord_avatar_hash": "59346cd10940d7cae650a5e2336b316a", + "discord_snowflake": "858800346884997142", + "guild": { + "name": "Shadow's Safe Theme Collection", + "snowflake": "862776993152892939", + "invite_link": "https://discord.gg/6gseVdez6A", + "avatar_hash": "9c81716265f90ddb92c06af468a0094d " + } + }, + "likes": 47, + "downloads": 58721, + "tags": [ + "transparent", + "abstract" + ], + "thumbnail_url": "/resources/thumbnails/984.png", + "latest_source_url": "https://raw.githubusercontent.com/ShadowDevilsAvenged/ShadowDevilsAvenged/398be14a65264534ebaf564f8fa34cff9a71985d/My_Theme_Collection/BubbleThemev2.theme.css", + "initial_release_date": "2022-03-27T04:52:58.250575Z", + "latest_release_date": "2023-01-01T05:56:06.042799Z", + "guild": { + "name": "Shadow's Safe Theme Collection", + "snowflake": "862776993152892939", + "invite_link": "https://discord.gg/6gseVdez6A", + "avatar_hash": "9c81716265f90ddb92c06af468a0094d " + } + }, + "chillax.theme.css": { + "id": 736, + "name": "Chillax", + "file_name": "chillax.theme.css", + "type": "theme", + "description": "stress doesn't really goes with your fit, try out chillax you might not regret it.", + "version": "2.1.2", + "author": { + "github_id": "84565593", + "github_name": "warrayquipsome", + "display_name": "wq_qt", + "discord_name": "wq_qt", + "discord_avatar_hash": "9f40cb6ada38c4902ade657395dbd617", + "discord_snowflake": "709377715320651838", + "guild": { + "name": "wq's support server", + "snowflake": "881453188509753384", + "invite_link": "https://discord.gg/DrfX6286kF", + "avatar_hash": "d91b7619db28b45eef53b74be1f8c370 " + } + }, + "likes": 155, + "downloads": 177739, + "tags": [ + "customizable", + "dark", + "light", + "red", + "animated" + ], + "thumbnail_url": "/resources/thumbnails/1393.png", + "latest_source_url": "https://raw.githubusercontent.com/warrayquipsome/Chillax/dce4fa9562588af790d6acfb4d495b1b9ae07669/chillax.theme.css", + "initial_release_date": "2022-07-01T09:49:27.869853Z", + "latest_release_date": "2026-01-24T03:29:22.723241Z", + "guild": null + }, + "messagescanai.plugin.js": { + "id": 1130, + "name": "MessageScanAI", + "file_name": "MessageScanAI.plugin.js", + "type": "plugin", + "description": "Adds a button to scan messages for phishing/scams with AI", + "version": "2.1.3", + "author": { + "github_id": "43104632", + "github_name": "programmer2514", + "display_name": "programmer2514", + "discord_name": "programmer2514", + "discord_avatar_hash": "b8a0b2af5aaf90549f620f36b8a57cc1", + "discord_snowflake": "563652755814875146", + "guild": null + }, + "likes": 7, + "downloads": 8517, + "tags": [ + "enhancement", + "chat", + "security", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1593.png", + "latest_source_url": "https://raw.githubusercontent.com/programmer2514/BetterDiscord-MessageScanAI/500f565a3db1768ad64b5c561db4593a3335959e/MessageScanAI.plugin.js", + "initial_release_date": "2024-07-22T04:08:43.516068Z", + "latest_release_date": "2025-12-15T21:24:03.117169Z", + "guild": null + }, + "lastmessagedate.plugin.js": { + "id": 85, + "name": "LastMessageDate", + "file_name": "LastMessageDate.plugin.js", + "type": "plugin", + "description": "Displays the Last Message Date of a Member for the current Server/DM in the UserPopout and UserModal", + "version": "1.5.6", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 156, + "downloads": 224826, + "tags": [ + "enhancement", + "members" + ], + "thumbnail_url": "/resources/thumbnails/509.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/10731bb6ebb27d4f6f554f9698a0ad29de3f9209/Plugins/LastMessageDate/LastMessageDate.plugin.js", + "initial_release_date": "2021-03-06T10:09:26.681869Z", + "latest_release_date": "2026-01-25T13:44:53.190472Z", + "guild": null + }, + "channelspreview.plugin.js": { + "id": 592, + "name": "ChannelsPreview", + "file_name": "ChannelsPreview.plugin.js", + "type": "plugin", + "description": "Allows you to view recent messages in channels without switching to them.", + "version": "2.1.12", + "author": { + "github_id": "52377180", + "github_name": "arg0NNY", + "display_name": "arg0NNY", + "discord_name": "arg0nny", + "discord_avatar_hash": "87d3e880f144cc8756954fce94fb3548", + "discord_snowflake": "224538553944637440", + "guild": { + "name": "arg0NNY's Lounge", + "snowflake": "947007182552064050", + "invite_link": "https://discord.gg/M8DBtcZjXD", + "avatar_hash": "840d87c006a42a594deb620339b8d178 " + } + }, + "likes": 63, + "downloads": 63592, + "tags": [ + "channels", + "text", + "enhancement", + "servers", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/1473.gif", + "latest_source_url": "https://raw.githubusercontent.com/arg0NNY/DiscordPlugins/e719f7adbc60a12a4427fce96724203cb6b7b691/ChannelsPreview/ChannelsPreview.plugin.js", + "initial_release_date": "2022-01-27T17:28:53.164842Z", + "latest_release_date": "2026-01-26T18:49:49.928857Z", + "guild": null + }, + "membercount.plugin.js": { + "id": 1440, + "name": "MemberCount", + "file_name": "MemberCount.plugin.js", + "type": "plugin", + "description": "Displays a server's member-count at the top of the member-list, can be styled with the `#MemberCount` selector.", + "version": "3.0.14", + "author": { + "github_id": "24483230", + "github_name": "Arashiryuu", + "display_name": "Arashiryuu", + "discord_name": "arashiryuu", + "discord_avatar_hash": "f3c87e2bcc03430376d848115ed8797e", + "discord_snowflake": "238108500109033472", + "guild": null + }, + "likes": 2, + "downloads": 1247, + "tags": [ + "enhancement", + "members" + ], + "thumbnail_url": "/resources/thumbnails/1774.png", + "latest_source_url": "https://raw.githubusercontent.com/Arashiryuu/crap/2eba3387c5a3280018a567261148fbc617c63d80/BdApi/MemberCount/MemberCount.plugin.js", + "initial_release_date": "2025-12-13T04:08:43.023958Z", + "latest_release_date": "2026-01-24T01:59:31.318672Z", + "guild": null + }, + "removeblockedusers.plugin.js": { + "id": 95, + "name": "RemoveBlockedUsers", + "file_name": "RemoveBlockedUsers.plugin.js", + "type": "plugin", + "description": "Removes blocked/ignored Messages/Users", + "version": "1.8.1", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 119, + "downloads": 155413, + "tags": [ + "enhancement", + "chat", + "members" + ], + "thumbnail_url": "/resources/thumbnails/282.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/RemoveBlockedUsers/RemoveBlockedUsers.plugin.js", + "initial_release_date": "2021-03-06T17:54:10.165739Z", + "latest_release_date": "2025-12-07T11:29:00.175759Z", + "guild": null + }, + "editimageuploads.plugin.js": { + "id": 1434, + "name": "EditImageUploads", + "file_name": "EditImageUploads.plugin.js", + "type": "plugin", + "description": "Adds an option to edit images before sending.", + "version": "0.1.1", + "author": { + "github_id": "91275265", + "github_name": "Naru-kami", + "display_name": "Narukami", + "discord_name": "narukami5414", + "discord_avatar_hash": "abe87c21aff218d8e24f7c615815b675", + "discord_snowflake": "245262066745737216", + "guild": null + }, + "likes": 3, + "downloads": 1165, + "tags": [ + "edit", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1793.png", + "latest_source_url": "https://raw.githubusercontent.com/Naru-kami/EditImageUploads/9e25a58d372bd6d90caec04df4c3605259a61b38/EditImageUploads.plugin.js", + "initial_release_date": "2025-11-23T21:28:00.95184Z", + "latest_release_date": "2026-02-06T11:23:22.34517Z", + "guild": null + }, + "osx.theme.css": { + "id": 553, + "name": "OSX", + "file_name": "OSX.theme.css", + "type": "theme", + "description": "Theme that brings some of the best and most memorable parts of MacOS and OSX into a theme.", + "version": "1.0.1", + "author": { + "github_id": "87679354", + "github_name": "TheCommieAxolotl", + "display_name": "The Commie Axolotl", + "discord_name": "thecommieaxolotl", + "discord_avatar_hash": "a_8370207d9881cf097303de86ee1dc550", + "discord_snowflake": "538487970408300544", + "guild": { + "name": "Axolotl Support", + "snowflake": "901774051318591508", + "invite_link": "https://discord.gg/5BSWtSM3XU", + "avatar_hash": "2cf170b95a4701313b141531a892affa " + } + }, + "likes": 15, + "downloads": 35096, + "tags": [ + "layout", + "customizable" + ], + "thumbnail_url": "/resources/thumbnails/855.png", + "latest_source_url": "https://raw.githubusercontent.com/TheCommieAxolotl/BetterDiscord-Stuff/c34774e1d37123473abe188720e79a8e78c9ab99/OSX/OSX.theme.css", + "initial_release_date": "2021-12-22T05:06:31.239153Z", + "latest_release_date": "2022-05-03T23:49:57.107689Z", + "guild": { + "name": "Axolotl Support", + "snowflake": "901774051318591508", + "invite_link": "https://discord.gg/5BSWtSM3XU", + "avatar_hash": "2cf170b95a4701313b141531a892affa " + } + }, + "materialistic.theme.css": { + "id": 172, + "name": "Materialistic", + "file_name": "Materialistic.theme.css", + "type": "theme", + "description": "A clean material design theme for discord with a simple accent color\r\n", + "version": "1.0.0", + "author": { + "github_id": "82894010", + "github_name": "booglesmcgee", + "display_name": "BigMeanie", + "discord_name": "BigMeanie#3739", + "discord_avatar_hash": "392927ab2471a808a741984f919b073c", + "discord_snowflake": "684156670783651855", + "guild": null + }, + "likes": 32, + "downloads": 60132, + "tags": [ + "customizable", + "dark", + "purple" + ], + "thumbnail_url": "/resources/thumbnails/408.png", + "latest_source_url": "https://raw.githubusercontent.com/booglesmcgee/booglesmcgee.github.io/8a871fc63a7cff6f8816f69e04ea5e490b30c435/Materialistic-Discord/Materialistic.theme.css", + "initial_release_date": "2021-04-25T00:20:31.674104Z", + "latest_release_date": "2021-05-07T21:39:23.113739Z", + "guild": null + }, + "toggleyourstuff.plugin.js": { + "id": 30, + "name": "Toggle Your Stuff", + "file_name": "toggleYourStuff.plugin.js", + "type": "plugin", + "description": "Let's you set shortcuts to enable/disable individual (or multiple) themes and/or plugins.", + "version": "1.2.3", + "author": { + "github_id": "18194808", + "github_name": "Inve1951", + "display_name": "square", + "discord_name": "square3880", + "discord_avatar_hash": "a14a62c0248cff5dffce2f3e57f25a78", + "discord_snowflake": "219363409097916416", + "guild": null + }, + "likes": 21, + "downloads": 10011, + "tags": [ + "shortcut", + "utility" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/Inve1951/BetterDiscordStuff/2cc6493e1cd400aa1fd50f8e662cba8055395753/plugins/toggleYourStuff.plugin.js", + "initial_release_date": "2021-02-22T20:00:16.380877Z", + "latest_release_date": "2025-12-16T16:12:51.628185Z", + "guild": null + }, + "nox.theme.css": { + "id": 3, + "name": "Nox", + "file_name": "Nox.theme.css", + "type": "theme", + "description": "A theme for Discord *loosely* based on Google's Material Design Guidelines.", + "version": "2.0.0", + "author": { + "github_id": "43654580", + "github_name": "epic1online", + "display_name": "epic1online", + "discord_name": "epic1online", + "discord_avatar_hash": "9fcb1b14409a616f13a4196eba77bbc2", + "discord_snowflake": "328669550101004288", + "guild": null + }, + "likes": 30, + "downloads": 34075, + "tags": [ + "flat", + "layout", + "dark", + "black" + ], + "thumbnail_url": "/resources/thumbnails/149.png", + "latest_source_url": "https://raw.githubusercontent.com/zerebos/Nox/6e16e5be2a388d6785aa1843d16e5ed409dc0331/release/Nox.theme.css", + "initial_release_date": "2021-02-22T18:38:27.703775Z", + "latest_release_date": "2024-08-29T20:38:17.607572Z", + "guild": null + }, + "uncompressedimages.plugin.js": { + "id": 936, + "name": "Uncompressed Images", + "file_name": "UncompressedImages.plugin.js", + "type": "plugin", + "description": "Basically, make images look better.\r\n\r\n\r\nNot basically,\r\nDiscord's solution to previewing images is awful so by changing 'media.discordapp.net' links to 'cdn.discordapp.com' links, we will no longer have blurry images (especially with JPEG, WebP and other lossy formats).", + "version": "4.00", + "author": { + "github_id": "94736474", + "github_name": "Knewest", + "display_name": "Knew", + "discord_name": "knewest", + "discord_avatar_hash": "17d3bae6e4dc88b8dc82d13924db2ff7", + "discord_snowflake": "332116671294734336", + "guild": null + }, + "likes": 46, + "downloads": 74324, + "tags": [ + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1284.png", + "latest_source_url": "https://raw.githubusercontent.com/Knewest/uncompressed-discord-images/f18cf0f673fc8a06a117d47c2510d77c1cd49635/UncompressedImages.plugin.js", + "initial_release_date": "2023-04-15T18:47:15.005711Z", + "latest_release_date": "2025-12-09T07:15:26.000772Z", + "guild": null + }, + "discordia.theme.css": { + "id": 1221, + "name": "Lande della Discordia", + "file_name": "discordia.theme.css", + "type": "theme", + "description": "Lande della Discordia implements a custom purple theme to enhance your Discord experience.", + "version": "25.1", + "author": { + "github_id": "79840904", + "github_name": "ungiglio", + "display_name": "ungiglio", + "discord_name": "ungiglio", + "discord_avatar_hash": "06dc28e107de2d9683d2b4d3532cf7eb", + "discord_snowflake": "769144538107215872", + "guild": { + "name": "ungiglio's GitHub lab", + "snowflake": "1283064177199550536", + "invite_link": "https://discord.gg/DDaRdZwB4h", + "avatar_hash": "5acf99d9fe0bd389ad3b997ee7826740 " + } + }, + "likes": 33, + "downloads": 38738, + "tags": [ + "transparent", + "customizable", + "purple" + ], + "thumbnail_url": "/resources/thumbnails/1605.png", + "latest_source_url": "https://raw.githubusercontent.com/ungiglio/DiscordDiscordia/2bb4257878fb905d1345e5e86d5b6e86df4eab8f/discordia.theme.css", + "initial_release_date": "2025-01-26T22:01:55.435112Z", + "latest_release_date": "2025-12-19T12:12:57.66166Z", + "guild": null + }, + "vriskatypingquirk.plugin.js": { + "id": 1385, + "name": "Vriska'sTypingQuirk", + "file_name": "VriskaTypingQuirk.plugin.js", + "type": "plugin", + "description": "Vriska's typing quirk. With 8 soundalikes! Now with URL protection.", + "version": "1.3", + "author": { + "github_id": "223217317", + "github_name": "poff-null", + "display_name": "poff_null", + "discord_name": "poff_null", + "discord_avatar_hash": "1290b2ccecfbebb050c820e26c1f0b31", + "discord_snowflake": "1196991508549750784", + "guild": null + }, + "likes": 3, + "downloads": 247, + "tags": [ + "fun", + "edit", + "text", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1718.png", + "latest_source_url": "https://raw.githubusercontent.com/poff-null/discord-plugins/80e362d9133630e051953d43d001a2fca5a49c5f/VriskaTypingQuirk.plugin.js", + "initial_release_date": "2025-07-31T13:55:26.861065Z", + "latest_release_date": "2025-11-17T10:25:30.761276Z", + "guild": null + }, + "gamezen.plugin.js": { + "id": 1126, + "name": "GameZen", + "file_name": "GameZen.plugin.js", + "type": "plugin", + "description": "Automatically activates Do Not Disturb mode when a game is launched.", + "version": "1.1.2", + "author": { + "github_id": "77923554", + "github_name": "TheoEwzZer", + "display_name": "Theo EwzZer", + "discord_name": "theoewzzer", + "discord_avatar_hash": "7b7618620428c9cf060c224cd07f5bff", + "discord_snowflake": "384009727253807105", + "guild": null + }, + "likes": 2, + "downloads": 1115, + "tags": [ + "game", + "activity", + "status" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/TheoEwzZer/GameZen/236b45fde996f58107142d22ffc564fe4842ca5f/GameZen.plugin.js", + "initial_release_date": "2024-07-02T16:00:10.666116Z", + "latest_release_date": "2025-12-11T16:57:56.044483Z", + "guild": null + }, + "unsuppressembeds.plugin.js": { + "id": 1233, + "name": "UnsuppressEmbeds", + "file_name": "UnsuppressEmbeds.plugin.js", + "type": "plugin", + "description": "Allows you to unsuppress embeds in messages", + "version": "1.0.0", + "author": { + "github_id": "50876016", + "github_name": "domi-btnr", + "display_name": "domi.btnr", + "discord_name": "domi.btnr", + "discord_avatar_hash": "661304b01ea9a6593b93c6c4880070f3", + "discord_snowflake": "354191516979429376", + "guild": { + "name": "¯\\_(ツ)_/¯", + "snowflake": "838690163160514601", + "invite_link": "https://discord.gg/gp2ExK5vc7", + "avatar_hash": "216b8f7141608a933da9c668adb4edee " + } + }, + "likes": 2, + "downloads": 1596, + "tags": [ + "text", + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1575.gif", + "latest_source_url": "https://raw.githubusercontent.com/domi-btnr/BetterDiscordStuff/4a2ef69f721a493e23206e887a4a26dec38e96c2/UnsuppressEmbeds/UnsuppressEmbeds.plugin.js", + "initial_release_date": "2025-02-08T01:11:23.647048Z", + "latest_release_date": "2025-02-08T01:32:08.792201Z", + "guild": null + }, + "ggo_kirito.theme.css": { + "id": 56, + "name": "GGO Kirito", + "file_name": "GGO_Kirito.theme.css", + "type": "theme", + "description": "A theme with Kirito and Deathgun fighting in GGO (Gun Gale Online)", + "version": "1.4.X", + "author": { + "github_id": "28992080", + "github_name": "VaporousCreeper", + "display_name": "VaporousCreeper", + "discord_name": "vaporouscreeper", + "discord_avatar_hash": "a_b43bc638751fb96c7e6965e032251cca", + "discord_snowflake": "267228264580382721", + "guild": null + }, + "likes": 1145, + "downloads": 1035464, + "tags": [ + "customizable", + "dark", + "anime", + "red" + ], + "thumbnail_url": "/resources/thumbnails/233.jpg", + "latest_source_url": "https://raw.githubusercontent.com/VaporousCreeper/BetterDiscord-ThemesAndPlugins/6d5c56ec225a04c6e29f5cc11fd415650008c9c7/Themes/GGO_Kirito/GGO_Kirito.theme.css", + "initial_release_date": "2021-03-05T23:57:51.336779Z", + "latest_release_date": "2025-09-14T09:45:21.99968Z", + "guild": null + }, + "audiooptions.plugin.js": { + "id": 1346, + "name": "AudioOptions", + "file_name": "AudioOptions.plugin.js", + "type": "plugin", + "description": "Adds a options button next to voice messages.", + "version": "2.0.0", + "author": { + "github_id": "90235641", + "github_name": "zrodevkaan", + "display_name": "Arven", + "discord_name": "zrodevkaan", + "discord_avatar_hash": "c088651f69e5f7f9291dcedba65ce8d8", + "discord_snowflake": "917630027477159986", + "guild": { + "name": "Froggy Support", + "snowflake": "1142561913453166794", + "invite_link": "https://discord.gg/t3zMgv7Nvb", + "avatar_hash": "4a4284a4e8e19853afcba9451e2d5fbb " + } + }, + "likes": 5, + "downloads": 4363, + "tags": [ + "shortcut", + "voice", + "enhancement" + ], + "thumbnail_url": "/resources/thumbnails/1676.png", + "latest_source_url": "https://raw.githubusercontent.com/zrodevkaan/BDPlugins/c724a7d46b6d3616b124bec2c5897aa69bc51e7d/Plugins/AudioOptions/AudioOptions.plugin.js", + "initial_release_date": "2025-06-03T02:16:25.721833Z", + "latest_release_date": "2026-01-24T00:48:12.216268Z", + "guild": null + }, + "discordplus.theme.css": { + "id": 209, + "name": "Discord+", + "file_name": "DiscordPlus.theme.css", + "type": "theme", + "description": "A sleek, customizable Discord theme, inspired by Material", + "version": "3.4.1", + "author": { + "github_id": "38794586", + "github_name": "PlusInsta", + "display_name": "Insta", + "discord_name": "plusinsta", + "discord_avatar_hash": "6d50035b07bda63ce59f04ab1f0517c6", + "discord_snowflake": "309931975102300160", + "guild": null + }, + "likes": 1217, + "downloads": 1384907, + "tags": [ + "transparent", + "customizable", + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/548.png", + "latest_source_url": "https://raw.githubusercontent.com/PlusInsta/discord-plus/56c5a26008339cfd82f2a69648d717a556d5758b/DiscordPlus.theme.css", + "initial_release_date": "2021-05-07T17:24:24.560792Z", + "latest_release_date": "2025-11-17T10:50:00.80485Z", + "guild": { + "name": "+Server", + "snowflake": "408433554477809665", + "invite_link": "https://discord.gg/2Jwh2nS", + "avatar_hash": "a_d401148c7ab24fc9257aceb8cda92745 " + } + }, + "spectra.theme.css": { + "id": 296, + "name": "Spectra", + "file_name": "spectra.theme.css", + "type": "theme", + "description": "A dark theme letting the light shine through! Spectra is a visibility focused theme that is still light on your eyes.", + "version": "1.1", + "author": { + "github_id": "44953835", + "github_name": "PixelMelt", + "display_name": "Pix", + "discord_name": "pixelmelt", + "discord_avatar_hash": "5f4bed651c9c2e3b88c0a5cfa648b437", + "discord_snowflake": "583750578094735360", + "guild": null + }, + "likes": 35, + "downloads": 37074, + "tags": [ + "nature", + "dark", + "yellow" + ], + "thumbnail_url": "/resources/thumbnails/607.png", + "latest_source_url": "https://raw.githubusercontent.com/PixelMelt/Spectra/b399b39a83f4f2fc60558dbc19ae1640c06519c3/spectra.theme.css", + "initial_release_date": "2021-06-01T17:29:09.020136Z", + "latest_release_date": "2021-07-14T20:28:19.504722Z", + "guild": null + }, + "editchannels.plugin.js": { + "id": 74, + "name": "EditChannels", + "file_name": "EditChannels.plugin.js", + "type": "plugin", + "description": "Allows you to locally edit Channels", + "version": "4.6.4", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 36, + "downloads": 38075, + "tags": [ + "edit", + "channels" + ], + "thumbnail_url": "/resources/thumbnails/249.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/a0bc89713a2a92af1a1f42311c3442d56269cc4d/Plugins/EditChannels/EditChannels.plugin.js", + "initial_release_date": "2021-03-06T09:57:00.586962Z", + "latest_release_date": "2026-01-23T05:40:55.375309Z", + "guild": null + }, + "operagx.theme.css": { + "id": 894, + "name": "OperaGX Theme", + "file_name": "OperaGX.theme.css", + "type": "theme", + "description": "A highly customizable theme based off of the OperaGX browser.\r\nTo easily customize the theme use https://bdeditor.dev/theme/operagxtheme", + "version": "1.2", + "author": { + "github_id": "54047268", + "github_name": "L-Ratio", + "display_name": "HawkRatio", + "discord_name": "hawkratio", + "discord_avatar_hash": "ad96ff1536f3c46fc3e9955b2b5c0afc", + "discord_snowflake": "502701912370577418", + "guild": { + "name": "Hawk's support server", + "snowflake": "1082457658885406770", + "invite_link": "https://discord.gg/Xn7UUQUHbN", + "avatar_hash": "4d893f595cd3af648f927454702b33bb " + } + }, + "likes": 60, + "downloads": 58138, + "tags": [ + "flat", + "transparent", + "customizable" + ], + "thumbnail_url": "/resources/thumbnails/1564.png", + "latest_source_url": "https://raw.githubusercontent.com/L-Ratio/OperaGXTheme/6f4804eae872cfff1c779974fd730fc01d4cbf0c/release/OperaGX.theme.css", + "initial_release_date": "2023-02-19T20:31:58.382645Z", + "latest_release_date": "2023-03-26T08:28:05.888643Z", + "guild": null + }, + "dtm-08.theme.css": { + "id": 205, + "name": "DTM-08", + "file_name": "dtm-08.theme.css", + "type": "theme", + "description": "A skeuomorphic / glossy theme for Discord", + "version": "2.0.2", + "author": { + "github_id": "51988432", + "github_name": "XYZenix", + "display_name": "11pixels", + "discord_name": "11pixels", + "discord_avatar_hash": "4f66aa9a960aa629d134439dfc7c70e8", + "discord_snowflake": "211270674482724864", + "guild": { + "name": "Omega Nexus", + "snowflake": "581110034977783838", + "invite_link": "https://discord.gg/u3p3KmqEJu", + "avatar_hash": "d12b3bed3a420e7739916b6e9bbdd8d8 " + } + }, + "likes": 67, + "downloads": 48284, + "tags": [ + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/850.png", + "latest_source_url": "https://raw.githubusercontent.com/XYZenix/DTM-08/2fcf10fbdd544f6ef4ded65c3f0c45f1d52afdd9/dtm-08.theme.css", + "initial_release_date": "2021-05-06T12:27:37.177182Z", + "latest_release_date": "2024-06-23T18:09:11.069679Z", + "guild": null + }, + "completetimestamps.plugin.js": { + "id": 67, + "name": "CompleteTimestamps", + "file_name": "CompleteTimestamps.plugin.js", + "type": "plugin", + "description": "Replaces Timestamps with your own custom Timestamps", + "version": "1.7.4", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 101, + "downloads": 102213, + "tags": [ + "enhancement", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/242.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/7312137d997813c83eb09aee4faaadd537b39999/Plugins/CompleteTimestamps/CompleteTimestamps.plugin.js", + "initial_release_date": "2021-03-06T09:48:03.120554Z", + "latest_release_date": "2025-12-20T07:44:06.244619Z", + "guild": null + }, + "amoled-cord.theme.css": { + "id": 144, + "name": "AMOLED-Cord", + "file_name": "amoled-cord.theme.css", + "type": "theme", + "description": "A theme that's nice on your eyes.. lights out baby!", + "version": "5.0.11", + "author": { + "github_id": "39076891", + "github_name": "LuckFire", + "display_name": "LuckFire", + "discord_name": "luckfire", + "discord_avatar_hash": "a_af5727f24fa11a7c07d3421049c72b45", + "discord_snowflake": "399416615742996480", + "guild": { + "name": "fish support server", + "snowflake": "976700353624039444", + "invite_link": "https://discord.gg/vYdXbEzqDs", + "avatar_hash": "9b955b4af4377b8d822f71645af53b08 " + } + }, + "likes": 277, + "downloads": 464930, + "tags": [ + "dark" + ], + "thumbnail_url": "/resources/thumbnails/326.png", + "latest_source_url": "https://raw.githubusercontent.com/LuckFire/amoled-cord/343808e7d5297223e43868b3955da4cbbd01ceef/clients/amoled-cord.theme.css", + "initial_release_date": "2021-04-18T00:58:38.786642Z", + "latest_release_date": "2026-02-17T19:16:07.973915Z", + "guild": null + }, + "nier-light-source.theme.css": { + "id": 39, + "name": "NieR: Automata - YoRHa Menu UI", + "file_name": "NieR-Light-Source.theme.css", + "type": "theme", + "description": "Better Discord theme based around NieR: Automata's Menu UI. Dark Mode available on my git page!", + "version": "1.4", + "author": { + "github_id": "8460982", + "github_name": "AccraZed", + "display_name": "accrazed", + "discord_name": "accrazed", + "discord_avatar_hash": "e3e5c3a3db1eab64ef9fd6c7ce36f2ab", + "discord_snowflake": "153562159161278473", + "guild": null + }, + "likes": 238, + "downloads": 150288, + "tags": [ + "dark", + "light", + "game" + ], + "thumbnail_url": "/resources/thumbnails/1807.png", + "latest_source_url": "https://raw.githubusercontent.com/AccraZed/YoRHA-UI-BetterDiscord/49d5eeee90307fbab5bac301010fcad5e6705be9/NieR-Light-Source.theme.css", + "initial_release_date": "2021-02-23T21:03:20.941511Z", + "latest_release_date": "2022-12-22T06:38:25.414692Z", + "guild": null + }, + "inmyvoice.plugin.js": { + "id": 598, + "name": "InMyVoice", + "file_name": "InMyVoice.plugin.js", + "type": "plugin", + "description": "Shows if a person in the text chat is also in a voice chat that you're in.", + "version": "1.2.2", + "author": { + "github_id": "52377180", + "github_name": "arg0NNY", + "display_name": "arg0NNY", + "discord_name": "arg0nny", + "discord_avatar_hash": "87d3e880f144cc8756954fce94fb3548", + "discord_snowflake": "224538553944637440", + "guild": { + "name": "arg0NNY's Lounge", + "snowflake": "947007182552064050", + "invite_link": "https://discord.gg/M8DBtcZjXD", + "avatar_hash": "840d87c006a42a594deb620339b8d178 " + } + }, + "likes": 40, + "downloads": 41293, + "tags": [ + "voice", + "enhancement", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/912.png", + "latest_source_url": "https://raw.githubusercontent.com/arg0NNY/DiscordPlugins/828ac1cb014351681ca56552a054265de47bd412/InMyVoice/InMyVoice.plugin.js", + "initial_release_date": "2022-01-29T16:39:19.33172Z", + "latest_release_date": "2026-01-25T19:02:58.669853Z", + "guild": { + "name": "arg0NNY's Lounge", + "snowflake": "947007182552064050", + "invite_link": "https://discord.gg/M8DBtcZjXD", + "avatar_hash": "840d87c006a42a594deb620339b8d178 " + } + }, + "morequickreacts.plugin.js": { + "id": 1424, + "name": "MoreQuickReacts", + "file_name": "MoreQuickReacts.plugin.js", + "type": "plugin", + "description": "Increases the number of quick reactions available when hovering over a message", + "version": "1.0.5", + "author": { + "github_id": "76746384", + "github_name": "TheLazySquid", + "display_name": "TheLazySquid", + "discord_name": "thelazysquid", + "discord_avatar_hash": "e04c7e5372cbf77ecf7bd2784775aa3c", + "discord_snowflake": "619261917352951815", + "guild": { + "name": "TheLazySquid's Stuff", + "snowflake": "1432531781780897814", + "invite_link": "https://discord.gg/fKdAaFYbD5", + "avatar_hash": "4264dfaa6aefa8ac415b4680731dc25b " + } + }, + "likes": 0, + "downloads": 1348, + "tags": [ + "shortcut", + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1751.png", + "latest_source_url": "https://raw.githubusercontent.com/TheLazySquid/BetterDiscordPlugins/f64a98babc45938436ba262978ea178840a37be3/plugins/MoreQuickReacts/MoreQuickReacts.plugin.js", + "initial_release_date": "2025-10-18T14:05:04.496243Z", + "latest_release_date": "2026-02-09T23:02:21.540494Z", + "guild": null + }, + "rightclickjoin.plugin.js": { + "id": 312, + "name": "Right Click to Join", + "file_name": "RightClickJoin.plugin.js", + "type": "plugin", + "description": "Right click a user to join a voice channel they are in.", + "version": "1.7.0", + "author": { + "github_id": "8385001", + "github_name": "Farcrada", + "display_name": "Farcrada", + "discord_name": "Farcrada#1879", + "discord_avatar_hash": "9e4cdff0341d13746d1c9772c951ce2e", + "discord_snowflake": "131212461499088896", + "guild": null + }, + "likes": 28, + "downloads": 34917, + "tags": [ + "voice", + "enhancement", + "chat", + "organization", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/539.png", + "latest_source_url": "https://raw.githubusercontent.com/Farcrada/DiscordPlugins/bf6c27a89f260a416ae1ef7aa8299cf928a63ed5/Right-Click-Join/RightClickJoin.plugin.js", + "initial_release_date": "2021-06-06T22:57:53.819949Z", + "latest_release_date": "2023-09-04T21:11:40.119896Z", + "guild": null + }, + "zippreview.plugin.js": { + "id": 1185, + "name": "ZipPreview", + "file_name": "ZipPreview.plugin.js", + "type": "plugin", + "description": "Lets you see inside zip files, and download individual files, without ever downloading/extracting the zip", + "version": "0.6.3", + "author": { + "github_id": "76746384", + "github_name": "TheLazySquid", + "display_name": "TheLazySquid", + "discord_name": "thelazysquid", + "discord_avatar_hash": "e04c7e5372cbf77ecf7bd2784775aa3c", + "discord_snowflake": "619261917352951815", + "guild": { + "name": "TheLazySquid's Stuff", + "snowflake": "1432531781780897814", + "invite_link": "https://discord.gg/fKdAaFYbD5", + "avatar_hash": "4264dfaa6aefa8ac415b4680731dc25b " + } + }, + "likes": 4, + "downloads": 8489, + "tags": [ + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1536.png", + "latest_source_url": "https://raw.githubusercontent.com/TheLazySquid/BetterDiscordPlugins/99f81a9a7b31ccd914c2e1a7118eda5bbf83e32b/plugins/ZipPreview/ZipPreview.plugin.js", + "initial_release_date": "2024-12-20T20:50:00.605125Z", + "latest_release_date": "2026-02-13T17:30:20.814826Z", + "guild": null + }, + "fluent.theme.css": { + "id": 662, + "name": "Fluent", + "file_name": "Fluent.theme.css", + "type": "theme", + "description": "Brings the look of Windows 11 to Discord.", + "version": "1.0.10", + "author": { + "github_id": "20338746", + "github_name": "Gibbu", + "display_name": "Gibbu", + "discord_name": "gibbu", + "discord_avatar_hash": "a_5e798741ca272ab6f141cde6e50b3bd0", + "discord_snowflake": "174868361040232448", + "guild": { + "name": "Gibbu's Support Server", + "snowflake": "546243058992283650", + "invite_link": "https://discord.gg/ZHthyCw", + "avatar_hash": "2c60e7071c4e6f3bf412f42fa9365d35 " + } + }, + "likes": 52, + "downloads": 53162, + "tags": [ + "flat", + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/1318.png", + "latest_source_url": "https://raw.githubusercontent.com/DiscordStyles/Fluent/ea0306b27d8a72bccfdecbf43c8d2076d0e62180/Fluent.theme.css", + "initial_release_date": "2022-03-22T08:58:35.678823Z", + "latest_release_date": "2024-07-04T03:41:45.070042Z", + "guild": null + }, + "betteranimations.plugin.js": { + "id": 608, + "name": "BetterAnimations", + "file_name": "BetterAnimations.plugin.js", + "type": "plugin", + "description": "🌊 Discord Animations Client Mod & Framework", + "version": "2.1.10", + "author": { + "github_id": "52377180", + "github_name": "arg0NNY", + "display_name": "arg0NNY", + "discord_name": "arg0nny", + "discord_avatar_hash": "87d3e880f144cc8756954fce94fb3548", + "discord_snowflake": "224538553944637440", + "guild": { + "name": "arg0NNY's Lounge", + "snowflake": "947007182552064050", + "invite_link": "https://discord.gg/M8DBtcZjXD", + "avatar_hash": "840d87c006a42a594deb620339b8d178 " + } + }, + "likes": 340, + "downloads": 634642, + "tags": [ + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1732.gif", + "latest_source_url": "https://raw.githubusercontent.com/arg0NNY/DiscordPlugins/75b0af4e15f8cb41677a9ce0451a195ee9eeac28/BetterAnimations/BetterAnimations.plugin.js", + "initial_release_date": "2022-02-05T09:00:35.239038Z", + "latest_release_date": "2026-02-18T21:43:55.215743Z", + "guild": null + }, + "roundmoled.theme.css": { + "id": 342, + "name": "Roundmoled", + "file_name": "roundmoled.theme.css", + "type": "theme", + "description": "Simple but COOL Discord theme inspired by the AMOLED mode from Mobile, mixed with new rounded borders to the main sections of the user interface for a cleaner look.", + "version": "1.1", + "author": { + "github_id": "86564702", + "github_name": "tiredmala", + "display_name": "malasangre", + "discord_name": "malasangre#4444", + "discord_avatar_hash": "a_cc51f50155bb44fcfd6786b7d6370237", + "discord_snowflake": "699552486360612954", + "guild": null + }, + "likes": 114, + "downloads": 133702, + "tags": [ + "layout", + "customizable", + "dark", + "black", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/656.png", + "latest_source_url": "https://raw.githubusercontent.com/tiredmala/Roundmoled/7975ad63bbbbb047b9c4000adc76b0b719d89eea/BetterDiscord/roundmoled.theme.css", + "initial_release_date": "2021-06-29T14:49:53.944062Z", + "latest_release_date": "2021-07-17T01:52:54.114071Z", + "guild": null + }, + "latex.plugin.js": { + "id": 1048, + "name": "LaTeX Renderer", + "file_name": "LaTeX.plugin.js", + "type": "plugin", + "description": "Renders LaTeX equations using MathJax", + "version": "1.0.5", + "author": { + "github_id": "19613657", + "github_name": "BinaryQuantumSoul", + "display_name": "quantumsoul", + "discord_name": "quantumsoul", + "discord_avatar_hash": "e3df9db97b7f3d99f88d50fe8624afcb", + "discord_snowflake": "177440665402081281", + "guild": null + }, + "likes": 1, + "downloads": 3151, + "tags": [ + "text", + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1391.png", + "latest_source_url": "https://raw.githubusercontent.com/BinaryQuantumSoul/discord-latex/a2051554763d1bbfde1d540def8a9efb965dadcf/dist/LaTeX.plugin.js", + "initial_release_date": "2023-11-12T22:18:47.510954Z", + "latest_release_date": "2024-08-15T07:08:06.357323Z", + "guild": null + }, + "discord16.theme.css": { + "id": 1271, + "name": "Discord 1.6", + "file_name": "discord16.theme.css", + "type": "theme", + "description": "CS 1.6 + Old Steam Inspired Theme for Discord", + "version": "0.0.3", + "author": { + "github_id": "84600518", + "github_name": "dom1torii", + "display_name": ".domitori", + "discord_name": ".domitori", + "discord_avatar_hash": "d5d632d02b1c77af859aa2a81145ea5b", + "discord_snowflake": "295541656978063360", + "guild": null + }, + "likes": 13, + "downloads": 15544, + "tags": [ + "customizable", + "game", + "green", + "other" + ], + "thumbnail_url": "/resources/thumbnails/1599.png", + "latest_source_url": "https://raw.githubusercontent.com/dom1torii/discord16/532a51b09070180e6e9c38fea74f3a6329b30067/src/discord16.theme.css", + "initial_release_date": "2025-03-28T22:20:24.243691Z", + "latest_release_date": "2025-05-08T23:42:19.386265Z", + "guild": null + }, + "servercounter.plugin.js": { + "id": 99, + "name": "ServerCounter", + "file_name": "ServerCounter.plugin.js", + "type": "plugin", + "description": "Adds a Server Counter to the Server List", + "version": "1.1.1", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 187, + "downloads": 253085, + "tags": [ + "servers", + "organization" + ], + "thumbnail_url": "/resources/thumbnails/286.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/ServerCounter/ServerCounter.plugin.js", + "initial_release_date": "2021-03-06T17:57:24.398473Z", + "latest_release_date": "2025-12-07T11:29:33.765452Z", + "guild": null + }, + "material-discord.theme.css": { + "id": 41, + "name": "MaterialDiscord", + "file_name": "Material-Discord.theme.css", + "type": "theme", + "description": "A theme based on Google's Material Design", + "version": "3.0.7", + "author": { + "github_id": "4013216", + "github_name": "CapnKitten", + "display_name": "CapnKitten", + "discord_name": "capnkitten", + "discord_avatar_hash": "dfacf8af0f7d729e6cb8fde41bb0d9a8", + "discord_snowflake": "124276233478471680", + "guild": { + "name": "CapnKitten", + "snowflake": "403760306611814410", + "invite_link": "https://discord.gg/jzJkA6Z", + "avatar_hash": "a_0284b0297857e340d8d6a036ae4557e7 " + } + }, + "likes": 210, + "downloads": 183827, + "tags": [ + "flat", + "customizable", + "dark", + "game" + ], + "thumbnail_url": "/resources/thumbnails/1273.png", + "latest_source_url": "https://raw.githubusercontent.com/CapnKitten/Material-Discord/d29c7875a7c96054d4687b15742ad6ce16bfb3dc/Material-Discord.theme.css", + "initial_release_date": "2021-02-24T08:35:37.392007Z", + "latest_release_date": "2026-02-04T05:36:58.673194Z", + "guild": null + }, + "blurplerecolor.theme.css": { + "id": 46, + "name": "BlurpleRecolor", + "file_name": "BlurpleRecolor.theme.css", + "type": "theme", + "description": "Replaces Discord's native Blurple and Boost-Pink with your own Color", + "version": "1.0.0", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 25, + "downloads": 26424, + "tags": [ + "customizable" + ], + "thumbnail_url": "/resources/thumbnails/496.gif", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/d25feb904b1beb2cb3a0e2db32d0d43386113d64/Themes/BlurpleRecolor/BlurpleRecolor.theme.css", + "initial_release_date": "2021-02-25T10:09:56.124569Z", + "latest_release_date": "2023-11-18T12:58:48.667185Z", + "guild": null + }, + "tokyo-night.theme.css": { + "id": 439, + "name": "Tokyo Night", + "file_name": "tokyo-night.theme.css", + "type": "theme", + "description": "Discord Port of Visual Studio Code Theme", + "version": "2.0.2", + "author": { + "github_id": "69681505", + "github_name": "Dyzean", + "display_name": "Ashtrath", + "discord_name": "ashtrath", + "discord_avatar_hash": "f618af65c5e562f1b830958384940751", + "discord_snowflake": "354831939099688962", + "guild": null + }, + "likes": 125, + "downloads": 235373, + "tags": [ + "customizable", + "dark", + "light", + "other", + "white" + ], + "thumbnail_url": "/resources/thumbnails/690.png", + "latest_source_url": "https://raw.githubusercontent.com/Dyzean/Tokyo-Night/93e60a52711900a4a4f9cba2faaf6afd705c643c/tokyo-night.theme.css", + "initial_release_date": "2021-08-10T05:59:41.804976Z", + "latest_release_date": "2022-09-26T18:01:43.512208Z", + "guild": null + }, + "extendedtypingsounds.plugin.js": { + "id": 1210, + "name": "Extended Typing Sounds", + "file_name": "ExtendedTypingSounds.plugin.js", + "type": "plugin", + "description": "Play different click sounds when you press a key. Supports multiple keyboard sounds", + "version": "1.0.3", + "author": { + "github_id": "15162905", + "github_name": "Slluxx", + "display_name": "Slluxx", + "discord_name": "slluxx", + "discord_avatar_hash": "707f898c7c2b5e65ad0f9a4c4fa73500", + "discord_snowflake": "346059939502096396", + "guild": null + }, + "likes": 4, + "downloads": 7002, + "tags": [ + "fun", + "text", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/1567.jpeg", + "latest_source_url": "https://raw.githubusercontent.com/Slluxx/BetterDiscord-Extended-Typing-Sounds/8bcbb34eb7925f8771bb1feec3837563dc9fb197/ExtendedTypingSounds.plugin.js", + "initial_release_date": "2025-01-05T23:28:44.503856Z", + "latest_release_date": "2025-06-01T14:45:19.608499Z", + "guild": null + }, + "nord.theme.css": { + "id": 53, + "name": "Nord", + "file_name": "Nord.theme.css", + "type": "theme", + "description": "A theme for discord inspired by the deep blues of the Atlantic", + "version": "1.0.0", + "author": { + "github_id": "60603110", + "github_name": "Daggy1234", + "display_name": "Daggy1234", + "discord_name": "Daggy1234#4120", + "discord_avatar_hash": "6f2a16eeb9c990980905fa11c56858d5", + "discord_snowflake": "491174779278065689", + "guild": null + }, + "likes": 47, + "downloads": 56429, + "tags": [ + "dark", + "black", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/225.png", + "latest_source_url": "https://raw.githubusercontent.com/DagCord/Nord/086c6dc2acdf04bdd764256a8edd609f1ff34567/BetterDiscord/Nord.theme.css", + "initial_release_date": "2021-02-25T15:46:51.045407Z", + "latest_release_date": "2021-02-25T15:46:51.045407Z", + "guild": null + }, + "radialstatus.theme.css": { + "id": 32, + "name": "RadialStatus", + "file_name": "RadialStatus.theme.css", + "type": "theme", + "description": "Changes the status icons to wrap around the avatar. Shape can be changed inside the theme file.", + "version": "2.0.0", + "author": { + "github_id": "20338746", + "github_name": "Gibbu", + "display_name": "Gibbu", + "discord_name": "gibbu", + "discord_avatar_hash": "a_5e798741ca272ab6f141cde6e50b3bd0", + "discord_snowflake": "174868361040232448", + "guild": { + "name": "Gibbu's Support Server", + "snowflake": "546243058992283650", + "invite_link": "https://discord.gg/ZHthyCw", + "avatar_hash": "2c60e7071c4e6f3bf412f42fa9365d35 " + } + }, + "likes": 372, + "downloads": 459871, + "tags": [ + "other" + ], + "thumbnail_url": "/resources/thumbnails/420.jpg", + "latest_source_url": "https://raw.githubusercontent.com/DiscordStyles/RadialStatus/e361ae760dd8b88f6c187efa11a83e9aea3afd83/RadialStatus.theme.css", + "initial_release_date": "2021-02-23T02:30:36.828937Z", + "latest_release_date": "2025-04-03T23:04:06.355354Z", + "guild": { + "name": "Gibbu's Support Server", + "snowflake": "546243058992283650", + "invite_link": "https://discord.gg/ZHthyCw", + "avatar_hash": "2c60e7071c4e6f3bf412f42fa9365d35 " + } + }, + "pios.theme.css": { + "id": 572, + "name": "piOS", + "file_name": "piOS.theme.css", + "type": "theme", + "description": "SUPERHOT piOS inspired theme. With customisable colours, supports cosy and compact mode.", + "version": "9.7", + "author": { + "github_id": "29710355", + "github_name": "Saltssaumure", + "display_name": "Saltssaumure", + "discord_name": "saltssaumure", + "discord_avatar_hash": "aceb2016f8abe3be7e74739f3a3c862c", + "discord_snowflake": "134142022092062720", + "guild": { + "name": "SUPERHOT Official", + "snowflake": "385450774949396480", + "invite_link": "https://discord.gg/9eAwJF8", + "avatar_hash": "a_421544d89dcae9c3abfd599acbcebee6 " + } + }, + "likes": 102, + "downloads": 67898, + "tags": [ + "customizable", + "dark", + "game", + "black", + "high-contrast" + ], + "thumbnail_url": "/resources/thumbnails/877.png", + "latest_source_url": "https://raw.githubusercontent.com/Saltssaumure/pios-discord-theme/1f46fec1b1cbe55c36e789ae4dbc5346cf98670b/piOS.theme.css", + "initial_release_date": "2022-01-12T06:28:07.457838Z", + "latest_release_date": "2024-05-30T04:42:34.137665Z", + "guild": null + }, + "ninex.theme.css": { + "id": 933, + "name": "NineX", + "file_name": "NineX.theme.css", + "type": "theme", + "description": "A Windows 9x style Discord theme.", + "version": "1.7", + "author": { + "github_id": "29710355", + "github_name": "Saltssaumure", + "display_name": "Saltssaumure", + "discord_name": "saltssaumure", + "discord_avatar_hash": "aceb2016f8abe3be7e74739f3a3c862c", + "discord_snowflake": "134142022092062720", + "guild": { + "name": "SUPERHOT Official", + "snowflake": "385450774949396480", + "invite_link": "https://discord.gg/9eAwJF8", + "avatar_hash": "a_421544d89dcae9c3abfd599acbcebee6 " + } + }, + "likes": 104, + "downloads": 76132, + "tags": [ + "customizable", + "dark", + "light", + "other" + ], + "thumbnail_url": "/resources/thumbnails/1283.png", + "latest_source_url": "https://raw.githubusercontent.com/Saltssaumure/w9x-discord-theme/287375731ec93cdadbcfa4728a55b25e1b35c7b9/NineX.theme.css", + "initial_release_date": "2023-04-14T00:25:42.23964Z", + "latest_release_date": "2024-06-19T06:53:36.459976Z", + "guild": null + }, + "adifferentsearch.plugin.js": { + "id": 1240, + "name": "ADifferentSearch", + "file_name": "ADifferentSearch.plugin.js", + "type": "plugin", + "description": "Change the search engine used in the `Search With` feature.", + "version": "1.2.4", + "author": { + "github_id": "46033729", + "github_name": "AceLikesGhosts", + "display_name": "ace.", + "discord_name": "ace.", + "discord_avatar_hash": "fb11fd66eaaa8a8ad2601af010aebbdb", + "discord_snowflake": "327639826075484162", + "guild": null + }, + "likes": 2, + "downloads": 1070, + "tags": [ + "text", + "chat", + "utility" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/AceLikesGhosts/bd-plugins/ddff250f1e5b84b236a1bdfc06622fbd56c1f74e/dist/ADifferentSearch/ADifferentSearch.plugin.js", + "initial_release_date": "2025-02-14T05:03:14.78128Z", + "latest_release_date": "2026-01-23T22:47:10.890295Z", + "guild": null + }, + "doubleclicktoedit.plugin.js": { + "id": 354, + "name": "Double Click To Edit", + "file_name": "DoubleClickToEdit.plugin.js", + "type": "plugin", + "description": "Double click a message you wrote to quickly edit it.", + "version": "9.4.10", + "author": { + "github_id": "8385001", + "github_name": "Farcrada", + "display_name": "Farcrada", + "discord_name": "Farcrada#1879", + "discord_avatar_hash": "9e4cdff0341d13746d1c9772c951ce2e", + "discord_snowflake": "131212461499088896", + "guild": null + }, + "likes": 495, + "downloads": 861958, + "tags": [ + "shortcut", + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/590.gif", + "latest_source_url": "https://raw.githubusercontent.com/Farcrada/DiscordPlugins/97504f31e4aff8365e91f0d559721b12a8d78d57/Double-click-to-edit/DoubleClickToEdit.plugin.js", + "initial_release_date": "2021-07-03T19:05:20.541889Z", + "latest_release_date": "2025-08-08T12:52:09.211741Z", + "guild": null + }, + "autoswitchstatus.plugin.js": { + "id": 1459, + "name": "AutoSwitchStatus", + "file_name": "AutoSwitchStatus.plugin.js", + "type": "plugin", + "description": "Automatically switches your discord status when you are muted, connected to a server or when disconnected from a server.", + "version": "1.9.4", + "author": { + "github_id": "61830443", + "github_name": "nicola02nb", + "display_name": "nicola02nb", + "discord_name": "nicola02nb", + "discord_avatar_hash": "b0e2a870015d31c91f2d8895d80b2113", + "discord_snowflake": "257900031351193600", + "guild": { + "name": "nicola02nb Dev Support", + "snowflake": "1329222382279327825", + "invite_link": "https://discord.gg/hFuY8DfDGK", + "avatar_hash": null + } + }, + "likes": 1, + "downloads": 527, + "tags": [ + "status", + "notifications", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1796.gif", + "latest_source_url": "https://raw.githubusercontent.com/nicola02nb/BetterDiscord-Stuff/1373dadd6a9910e1956ba9dad400cf56d583066e/Plugins/AutoSwitchStatus/AutoSwitchStatus.plugin.js", + "initial_release_date": "2026-01-12T17:48:01.274378Z", + "latest_release_date": "2026-01-24T11:26:45.218231Z", + "guild": null + }, + "typinguserspopouts.plugin.js": { + "id": 593, + "name": "TypingUsersPopouts", + "file_name": "TypingUsersPopouts.plugin.js", + "type": "plugin", + "description": "Opens the user's popout when clicking on a name in the typing area.", + "version": "1.4.11", + "author": { + "github_id": "68879269", + "github_name": "Neodymium7", + "display_name": "Neodymium", + "discord_name": "neodymium_", + "discord_avatar_hash": "9c50bfb13c623c676697300b65741f71", + "discord_snowflake": "340614112331694081", + "guild": { + "name": "Neodymium's Support Server", + "snowflake": "965006167917068400", + "invite_link": "https://discord.gg/fRbsqH87Av", + "avatar_hash": "940c1cba79a578292a49a0bdd7436f9d " + } + }, + "likes": 15, + "downloads": 8769, + "tags": [ + "shortcut", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/955.png", + "latest_source_url": "https://raw.githubusercontent.com/Neodymium7/BetterDiscordStuff/ae69e6331e892398939d37958c627a07f96ea0f7/TypingUsersPopouts/TypingUsersPopouts.plugin.js", + "initial_release_date": "2022-01-28T02:38:52.757854Z", + "latest_release_date": "2026-01-25T09:50:51.41291Z", + "guild": { + "name": "Neodymium's Support Server", + "snowflake": "965006167917068400", + "invite_link": "https://discord.gg/fRbsqH87Av", + "avatar_hash": "940c1cba79a578292a49a0bdd7436f9d " + } + }, + "fvui.theme.css": { + "id": 1033, + "name": "FVUI", + "file_name": "FVUI.theme.css", + "type": "theme", + "description": "Blur is the New Black", + "version": "2.0.0", + "author": { + "github_id": "81682869", + "github_name": "FeoreV", + "display_name": "FeoreV", + "discord_name": "feorev", + "discord_avatar_hash": "580ce6bfe722b0adee8770a9310b3538", + "discord_snowflake": "489785940546551831", + "guild": null + }, + "likes": 64, + "downloads": 58084, + "tags": [ + "transparent", + "customizable", + "dark", + "light", + "animated" + ], + "thumbnail_url": "/resources/thumbnails/1377.png", + "latest_source_url": "https://raw.githubusercontent.com/FeoreV/Themes/58988c87e4ea10b078cac6b768f8d657910b2357/Discord/Release/FVUI.theme.css", + "initial_release_date": "2023-10-15T19:55:05.791636Z", + "latest_release_date": "2024-04-05T10:03:35.909045Z", + "guild": null + }, + "membercounter.plugin.js": { + "id": 1040, + "name": "MemberCounter", + "file_name": "MemberCounter.plugin.js", + "type": "plugin", + "description": "Displays the Member Count of a Server at the top of the Member List, can be configured to show Total Members, Online Members, Offline Members, and a DM Counter.", + "version": "2.31", + "author": { + "github_id": "23367120", + "github_name": "SyndiShanX", + "display_name": "SyndiShanX", + "discord_name": "syndishanx", + "discord_avatar_hash": "d1931fac0a94b380d60dce2c9b3ec3e1", + "discord_snowflake": "178394162381455360", + "guild": null + }, + "likes": 22, + "downloads": 23285, + "tags": [ + "enhancement", + "servers", + "members", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1422.png", + "latest_source_url": "https://raw.githubusercontent.com/SyndiShanX/Better-Discord-Plugins/1aefde59c06bab5c47a7f027b6f4fc484b6d7d55/MemberCounter/MemberCounter.plugin.js", + "initial_release_date": "2023-11-04T18:34:27.898181Z", + "latest_release_date": "2026-02-16T20:18:40.90923Z", + "guild": null + }, + "nocturnal.theme.css": { + "id": 256, + "name": "Nocturnal", + "file_name": "nocturnal.theme.css", + "type": "theme", + "description": "For when default discord just isn't dark enough, a theme for those who live a night", + "version": "3.0.1", + "author": { + "github_id": "59365159", + "github_name": "xcruxiex", + "display_name": "cruxie", + "discord_name": "cruxie", + "discord_avatar_hash": "d53200d43a5951bd19ffb0b255c48b41", + "discord_snowflake": "332394843743584256", + "guild": null + }, + "likes": 178, + "downloads": 293520, + "tags": [ + "flat", + "customizable", + "dark", + "green", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/471.png", + "latest_source_url": "https://raw.githubusercontent.com/xcruxiex/themes/dca5284cc5a152e1a50487426bdee390707d700e/themes-files/nocturnal.theme.css", + "initial_release_date": "2021-05-20T00:38:21.240231Z", + "latest_release_date": "2026-02-19T10:28:07.970221Z", + "guild": null + }, + "nordic.theme.css": { + "id": 33, + "name": "Nordic", + "file_name": "nordic.theme.css", + "type": "theme", + "description": "An full themed discord with Nord palette (https://www.nordtheme.com/)", + "version": "4.13.0", + "author": { + "github_id": "1344792", + "github_name": "orblazer", + "display_name": "orblazer", + "discord_name": "orblazer", + "discord_avatar_hash": "6df65f24b472f18ee77ea21fb5546b0a", + "discord_snowflake": "179681974879911946", + "guild": null + }, + "likes": 34, + "downloads": 33686, + "tags": [ + "customizable", + "dark", + "light", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/1248.png", + "latest_source_url": "https://raw.githubusercontent.com/orblazer/discord-nordic/8503e3679978a95c7f5a429a8e065ecb47677376/nordic.theme.css", + "initial_release_date": "2021-02-23T18:44:04.659357Z", + "latest_release_date": "2026-01-24T15:30:27.06977Z", + "guild": null + }, + "amethyst.theme.css": { + "id": 422, + "name": "Amethyst", + "file_name": "amethyst.theme.css", + "type": "theme", + "description": "A calm, deep purple theme for those seeking serenity.", + "version": "1.7.0", + "author": { + "github_id": "62459597", + "github_name": "spinfish", + "display_name": "Alex", + "discord_name": "legume#0173", + "discord_avatar_hash": "f7c62fb86cedb984101407b2d4de0df0", + "discord_snowflake": "574870314928832533", + "guild": null + }, + "likes": 116, + "downloads": 117600, + "tags": [ + "dark", + "purple" + ], + "thumbnail_url": "/resources/thumbnails/676.png", + "latest_source_url": "https://raw.githubusercontent.com/spinfish/amethyst/9386ffda4343380841e7ed028c8b0e433b380423/support/amethyst.theme.css", + "initial_release_date": "2021-08-03T06:55:22.428258Z", + "latest_release_date": "2022-01-19T02:20:38.963378Z", + "guild": null + }, + "newremtheme.theme.css": { + "id": 653, + "name": "New Rem Theme", + "file_name": "NewRemTheme.theme.css", + "type": "theme", + "description": "Newer Rem theme for my return", + "version": "7.2", + "author": { + "github_id": "30361475", + "github_name": "ShadowDevilsAvenged", + "display_name": "ShadowDevilsAvenged", + "discord_name": "shadowdevilsavenged", + "discord_avatar_hash": "59346cd10940d7cae650a5e2336b316a", + "discord_snowflake": "858800346884997142", + "guild": { + "name": "Shadow's Safe Theme Collection", + "snowflake": "862776993152892939", + "invite_link": "https://discord.gg/6gseVdez6A", + "avatar_hash": "9c81716265f90ddb92c06af468a0094d " + } + }, + "likes": 268, + "downloads": 328820, + "tags": [ + "transparent", + "anime", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/974.png", + "latest_source_url": "https://raw.githubusercontent.com/ShadowDevilsAvenged/ShadowDevilsAvenged/84c3dd45b7bfe98ee6482b2abfa467c1b81580df/My_Theme_Collection/NewRemTheme.theme.css", + "initial_release_date": "2022-03-14T03:21:12.226022Z", + "latest_release_date": "2025-10-01T21:30:43.586984Z", + "guild": { + "name": "Shadow's Safe Theme Collection", + "snowflake": "862776993152892939", + "invite_link": "https://discord.gg/6gseVdez6A", + "avatar_hash": "9c81716265f90ddb92c06af468a0094d " + } + }, + "mentionfilter.plugin.js": { + "id": 1157, + "name": "MentionFilter", + "file_name": "MentionFilter.plugin.js", + "type": "plugin", + "description": "Provides a filter for suppressing mentions.", + "version": "1.0.4", + "author": { + "github_id": "92663142", + "github_name": "Huderon", + "display_name": "Huderon", + "discord_name": "huderon", + "discord_avatar_hash": "bbc46b6d7a3a29cf37c54f7f628e76ef", + "discord_snowflake": "310741793668857859", + "guild": { + "name": "Addon Support", + "snowflake": "1335096998826606605", + "invite_link": "https://discord.gg/NUH7cYDZ5A", + "avatar_hash": "7de124c75441605bfc76d3ce3ee82f7b " + } + }, + "likes": 1, + "downloads": 797, + "tags": [ + "notifications", + "utility" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/Huderon/BetterDiscordPlugins/f62e6f9b7b71f87b58ac0f8193e1f108d95e0cbc/MentionFilter/MentionFilter.plugin.js", + "initial_release_date": "2024-10-29T13:07:56.599777Z", + "latest_release_date": "2026-01-25T05:57:00.586748Z", + "guild": null + }, + "clearvision-v7-betterdiscord.theme.css": { + "id": 23, + "name": "ClearVision", + "file_name": "ClearVision-v7-BetterDiscord.theme.css", + "type": "theme", + "description": "Highly customizable theme for BetterDiscord (and Powercord).", + "version": "7.0.1", + "author": { + "github_id": "58749666", + "github_name": "NyxIsBad", + "display_name": "NyxIsBad", + "discord_name": "night.flower", + "discord_avatar_hash": "f38719d28f5faac2b8a2232bc5d904c4", + "discord_snowflake": "393900343135830016", + "guild": null + }, + "likes": 1832, + "downloads": 2552872, + "tags": [ + "transparent", + "customizable", + "dark", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/176.png", + "latest_source_url": "https://raw.githubusercontent.com/ClearVision/ClearVision-v7/81822515a91db4b821e2573f635e80fec3e763a0/ClearVision-v7-BetterDiscord.theme.css", + "initial_release_date": "2021-02-22T19:22:59.764097Z", + "latest_release_date": "2025-12-11T01:46:54.490253Z", + "guild": { + "name": "ClearVision", + "snowflake": "212324635356692500", + "invite_link": "https://discord.gg/673e2sqBzd", + "avatar_hash": "cb6c1ea206b52f9355595c4d8a5ade2f " + } + }, + "statuseverywherev2.plugin.js": { + "id": 1277, + "name": "StatusEverywhereV2", + "file_name": "StatusEverywhereV2.plugin.js", + "type": "plugin", + "description": "Show status everywhere (chat avatars and voice chat avatars)", + "version": "1.0.9", + "author": { + "github_id": "48053193", + "github_name": "DaddyBoard", + "display_name": "DaddyBoard", + "discord_name": "daddyboard", + "discord_avatar_hash": "b17e321406b8b40e9a55cbe9889c31d2", + "discord_snowflake": "241334335884492810", + "guild": { + "name": "DaddyBoard's BD Plugins", + "snowflake": "1298432536761991270", + "invite_link": "https://discord.gg/ggNWGDV7e2", + "avatar_hash": "0ebf86191cc8d0ad44ec716406843990 " + } + }, + "likes": 3, + "downloads": 3803, + "tags": [ + "status", + "voice", + "enhancement", + "chat", + "members" + ], + "thumbnail_url": "/resources/thumbnails/1606.png", + "latest_source_url": "https://raw.githubusercontent.com/DaddyBoard/BD-Plugins/c60e843a935224e5d940c5b021a9f40999212144/StatusEverywhereV2/StatusEverywhereV2.plugin.js", + "initial_release_date": "2025-03-31T21:29:29.902389Z", + "latest_release_date": "2026-02-18T22:07:54.080508Z", + "guild": null + }, + "previewmessage.plugin.js": { + "id": 970, + "name": "PreviewMessage", + "file_name": "PreviewMessage.plugin.js", + "type": "plugin", + "description": "Allows you to preview a message before you send it.", + "version": "1.0.3", + "author": { + "github_id": "87679354", + "github_name": "TheCommieAxolotl", + "display_name": "The Commie Axolotl", + "discord_name": "thecommieaxolotl", + "discord_avatar_hash": "a_8370207d9881cf097303de86ee1dc550", + "discord_snowflake": "538487970408300544", + "guild": { + "name": "Axolotl Support", + "snowflake": "901774051318591508", + "invite_link": "https://discord.gg/5BSWtSM3XU", + "avatar_hash": "2cf170b95a4701313b141531a892affa " + } + }, + "likes": 22, + "downloads": 14997, + "tags": [ + "shortcut", + "text", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1319.png", + "latest_source_url": "https://raw.githubusercontent.com/TheCommieAxolotl/BetterDiscord-Stuff/38b22daa8db787b24a60730e51cfd0f757949704/PreviewMessage/PreviewMessage.plugin.js", + "initial_release_date": "2023-06-26T23:47:51.073987Z", + "latest_release_date": "2024-09-14T22:00:39.466272Z", + "guild": null + }, + "dateviewer.plugin.js": { + "id": 819, + "name": "DateViewer", + "file_name": "DateViewer.plugin.js", + "type": "plugin", + "description": "Displays the current date, weekday, and time, at the bottom of the member-list.", + "version": "1.0.22", + "author": { + "github_id": "24483230", + "github_name": "Arashiryuu", + "display_name": "Arashiryuu", + "discord_name": "arashiryuu", + "discord_avatar_hash": "f3c87e2bcc03430376d848115ed8797e", + "discord_snowflake": "238108500109033472", + "guild": null + }, + "likes": 14, + "downloads": 15251, + "tags": [ + "enhancement" + ], + "thumbnail_url": "/resources/thumbnails/1775.png", + "latest_source_url": "https://raw.githubusercontent.com/Arashiryuu/crap/9134fb19028d73984b4911c8a5f08e4248131e75/BdApi/DateViewer/DateViewer.plugin.js", + "initial_release_date": "2022-10-30T20:33:42.572685Z", + "latest_release_date": "2026-01-24T02:34:32.714745Z", + "guild": null + }, + "oldcord.theme.css": { + "id": 1274, + "name": "OldCord", + "file_name": "OldCord.theme.css", + "type": "theme", + "description": "Restores discord's 2020 UI", + "version": "2.1", + "author": { + "github_id": "51537072", + "github_name": "milbits", + "display_name": "milbit", + "discord_name": "milbit", + "discord_avatar_hash": "d3916af7793509dabde4efd9f97ee93b", + "discord_snowflake": "525379333951324190", + "guild": null + }, + "likes": 6, + "downloads": 14908, + "tags": [ + "layout" + ], + "thumbnail_url": "/resources/thumbnails/1617.png", + "latest_source_url": "https://raw.githubusercontent.com/milbits/oldcord/cb656082b31267f3e8adbdef5839b53d8075e55d/OldCord.theme.css", + "initial_release_date": "2025-03-31T07:39:54.148681Z", + "latest_release_date": "2025-09-16T06:29:40.074585Z", + "guild": null + }, + "materialdesign.theme.css": { + "id": 734, + "name": "MaterialDesign", + "file_name": "MaterialDesign.theme.css", + "type": "theme", + "description": "A clean theme inspired by Google's Material Design Guidelines.", + "version": "1.0.0", + "author": { + "github_id": "87679354", + "github_name": "TheCommieAxolotl", + "display_name": "The Commie Axolotl", + "discord_name": "thecommieaxolotl", + "discord_avatar_hash": "a_8370207d9881cf097303de86ee1dc550", + "discord_snowflake": "538487970408300544", + "guild": { + "name": "Axolotl Support", + "snowflake": "901774051318591508", + "invite_link": "https://discord.gg/5BSWtSM3XU", + "avatar_hash": "2cf170b95a4701313b141531a892affa " + } + }, + "likes": 51, + "downloads": 45252, + "tags": [ + "flat", + "layout", + "customizable", + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/1070.png", + "latest_source_url": "https://raw.githubusercontent.com/TheCommieAxolotl/BetterDiscord-Stuff/2ac5b8a215e35a4c66777b469da4ce858be49092/MaterialDesign/MaterialDesign.theme.css", + "initial_release_date": "2022-06-29T09:31:27.406391Z", + "latest_release_date": "2022-12-23T06:10:50.611052Z", + "guild": null + }, + "pindms.plugin.js": { + "id": 92, + "name": "PinDMs", + "file_name": "PinDMs.plugin.js", + "type": "plugin", + "description": "Allows you to pin DMs, making them appear at the top of your DMs/ServerList", + "version": "2.0.7", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 208, + "downloads": 240231, + "tags": [ + "channels", + "organization", + "members" + ], + "thumbnail_url": "/resources/thumbnails/279.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/PinDMs/PinDMs.plugin.js", + "initial_release_date": "2021-03-06T17:49:58.270081Z", + "latest_release_date": "2025-12-07T11:27:02.208239Z", + "guild": null + }, + "ultra.theme.css": { + "id": 541, + "name": "Ultra", + "file_name": "Ultra.theme.css", + "type": "theme", + "description": "A Smooth, Black theme", + "version": "1.0.7", + "author": { + "github_id": "87679354", + "github_name": "TheCommieAxolotl", + "display_name": "The Commie Axolotl", + "discord_name": "thecommieaxolotl", + "discord_avatar_hash": "a_8370207d9881cf097303de86ee1dc550", + "discord_snowflake": "538487970408300544", + "guild": { + "name": "Axolotl Support", + "snowflake": "901774051318591508", + "invite_link": "https://discord.gg/5BSWtSM3XU", + "avatar_hash": "2cf170b95a4701313b141531a892affa " + } + }, + "likes": 224, + "downloads": 316222, + "tags": [ + "flat", + "customizable", + "dark" + ], + "thumbnail_url": "/resources/thumbnails/849.png", + "latest_source_url": "https://raw.githubusercontent.com/TheCommieAxolotl/BetterDiscord-Stuff/0cef4643ae6a0ea9cc4914c41d9a42c51295e532/Ultra/Ultra.theme.css", + "initial_release_date": "2021-12-05T10:08:57.975401Z", + "latest_release_date": "2023-01-04T07:03:27.497321Z", + "guild": { + "name": "Axolotl Support", + "snowflake": "901774051318591508", + "invite_link": "https://discord.gg/5BSWtSM3XU", + "avatar_hash": "2cf170b95a4701313b141531a892affa " + } + }, + "notificationwhitelist.plugin.js": { + "id": 971, + "name": "NotificationWhitelist", + "file_name": "NotificationWhitelist.plugin.js", + "type": "plugin", + "description": "Allows servers and channels to be added to a notification whitelist", + "version": "1.2.0", + "author": { + "github_id": "24487592", + "github_name": "deathbyprograms", + "display_name": "DeathByPrograms", + "discord_name": "deathbyprograms", + "discord_avatar_hash": "54d49657d2ecf1c3a74431d900d9f060", + "discord_snowflake": "234086939102281728", + "guild": null + }, + "likes": 3, + "downloads": 3195, + "tags": [ + "notifications", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1320.jpg", + "latest_source_url": "https://raw.githubusercontent.com/deathbyprograms/BetterDiscordAddons/41e0911b8c317c589abc702b34d9d5b4af8a0b22/dist/NotificationWhitelist/NotificationWhitelist.plugin.js", + "initial_release_date": "2023-07-03T08:48:27.088157Z", + "latest_release_date": "2025-02-09T01:31:21.337395Z", + "guild": null + }, + "autostartrichpresence.plugin.js": { + "id": 1301, + "name": "AutoStartRichPresence", + "file_name": "AutoStartRichPresence.plugin.js", + "type": "plugin", + "description": "Auto starts Rich Presence with configurable settings.", + "version": "2.0.20", + "author": { + "github_id": "47940064", + "github_name": "Miniontoby", + "display_name": "miniontoby", + "discord_name": "miniontoby", + "discord_avatar_hash": "eb9561b3008587dc0c1a4b60c8f76e87", + "discord_snowflake": "849180136828960799", + "guild": { + "name": "Official Miniontoby Server ©", + "snowflake": "850001912366891078", + "invite_link": "https://discord.gg/hxxJbASsRt", + "avatar_hash": "62a80dd26298bee344ec72d5e1209c8b " + } + }, + "likes": 5, + "downloads": 10995, + "tags": [ + "activity", + "status" + ], + "thumbnail_url": "/resources/thumbnails/1647.png", + "latest_source_url": "https://raw.githubusercontent.com/Miniontoby/MinionBDStuff/ddd6d251bd76ec2865072226b0bcecda709aedc7/Plugins/AutoStartRichPresence/AutoStartRichPresence.plugin.js", + "initial_release_date": "2025-04-24T21:22:43.189516Z", + "latest_release_date": "2026-01-23T23:38:54.285341Z", + "guild": null + }, + "autoidleonafk.plugin.js": { + "id": 577, + "name": "AutoIdleOnAFK", + "file_name": "AutoIdleOnAFK.plugin.js", + "type": "plugin", + "description": "Automatically updates your discord status to 'idle' when you haven't opened your discord client for more than 5 minutes and then back to 'online' when you're active on Discord window.\r\n", + "version": "0.5.1", + "author": { + "github_id": "39442192", + "github_name": "RoguedBear", + "display_name": "RoguedBear", + "discord_name": "bounceprime", + "discord_avatar_hash": "6043ec1ed73cce05de184f6a58116b41", + "discord_snowflake": "712318895062515809", + "guild": null + }, + "likes": 63, + "downloads": 78205, + "tags": [ + "status", + "shortcut", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/886.gif", + "latest_source_url": "https://raw.githubusercontent.com/RoguedBear/BetterDiscordPlugin-AutoIdleOnAFK/1cefea4b8637576462f1a2d027572f858afb0083/release/AutoIdleOnAFK.plugin.js", + "initial_release_date": "2022-01-18T15:40:49.289575Z", + "latest_release_date": "2025-09-20T16:44:20.540226Z", + "guild": null + }, + "displayserversaschannels.plugin.js": { + "id": 73, + "name": "DisplayServersAsChannels", + "file_name": "DisplayServersAsChannels.plugin.js", + "type": "plugin", + "description": "Displays Servers in a similar way as Channels", + "version": "2.0.2", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 48, + "downloads": 41308, + "tags": [ + "channels", + "servers" + ], + "thumbnail_url": "/resources/thumbnails/248.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/1eb8ca2dfa4cdd5a8268e4b07f32dab22b94b588/Plugins/DisplayServersAsChannels/DisplayServersAsChannels.plugin.js", + "initial_release_date": "2021-03-06T09:55:52.447012Z", + "latest_release_date": "2026-01-25T16:04:29.20985Z", + "guild": null + }, + "showspectators.plugin.js": { + "id": 1236, + "name": "ShowSpectators", + "file_name": "ShowSpectators.plugin.js", + "type": "plugin", + "description": "Shows you who's spectating your stream under the screenshare panel", + "version": "1.0.4", + "author": { + "github_id": "50876016", + "github_name": "domi-btnr", + "display_name": "domi.btnr", + "discord_name": "domi.btnr", + "discord_avatar_hash": "661304b01ea9a6593b93c6c4880070f3", + "discord_snowflake": "354191516979429376", + "guild": { + "name": "¯\\_(ツ)_/¯", + "snowflake": "838690163160514601", + "invite_link": "https://discord.gg/gp2ExK5vc7", + "avatar_hash": "216b8f7141608a933da9c668adb4edee " + } + }, + "likes": 6, + "downloads": 22671, + "tags": [ + "enhancement", + "members", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1578.png", + "latest_source_url": "https://raw.githubusercontent.com/domi-btnr/BetterDiscordStuff/cee07a958387e89cc379c8cafcb951c62085cfb2/ShowSpectators/ShowSpectators.plugin.js", + "initial_release_date": "2025-02-10T07:39:05.65797Z", + "latest_release_date": "2026-02-08T05:15:21.098126Z", + "guild": null + }, + "rosynight.theme.css": { + "id": 1138, + "name": "RosyNight", + "file_name": "RosyNight.theme.css", + "type": "theme", + "description": "A sleek and elegant theme blending deep blacks with soft rosy hues for a stunning night-time aesthetic.", + "version": "RN.1", + "author": { + "github_id": "73029696", + "github_name": "DevEvil99", + "display_name": "DevEvil", + "discord_name": "devevil", + "discord_avatar_hash": "fff54ac325af38e0752324d8665510f3", + "discord_snowflake": "468132563714703390", + "guild": { + "name": "༺⟅𝗗𝗲𝘃𝗘𝘃𝗶𝗹 𝗖𝗼𝗺𝗺𝘂𝗻𝗶𝘁𝘆⟆༻", + "snowflake": "763094597454397490", + "invite_link": "https://discord.gg/jsQ9UP7kCA", + "avatar_hash": "a82d42e51a8711f98798033bf4ace472 " + } + }, + "likes": 15, + "downloads": 16581, + "tags": [ + "customizable", + "dark", + "red", + "black" + ], + "thumbnail_url": "/resources/thumbnails/1612.png", + "latest_source_url": "https://raw.githubusercontent.com/DevEvil99/RosyNight-Discord-Theme/3a6cd3b48e327cf3cc2e3c95e5a73a48249298be/RosyNight.theme.css", + "initial_release_date": "2024-08-27T21:48:09.042791Z", + "latest_release_date": "2025-03-26T15:31:32.927619Z", + "guild": null + }, + "notificationsounds.plugin.js": { + "id": 88, + "name": "NotificationSounds", + "file_name": "NotificationSounds.plugin.js", + "type": "plugin", + "description": "Allows you to replace the native Sounds with custom Sounds", + "version": "4.0.7", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 308, + "downloads": 489387, + "tags": [ + "edit", + "notifications" + ], + "thumbnail_url": "/resources/thumbnails/275.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/92f52d9f66172df2c4f0d4394de843332689b861/Plugins/NotificationSounds/NotificationSounds.plugin.js", + "initial_release_date": "2021-03-06T17:45:34.835051Z", + "latest_release_date": "2026-02-08T18:59:15.409277Z", + "guild": null + }, + "hidedisabledemojis.plugin.js": { + "id": 188, + "name": "HideDisabledEmojis", + "file_name": "HideDisabledEmojis.plugin.js", + "type": "plugin", + "description": "Hides disabled emojis from the emoji picker. Useful for non-nitro users.", + "version": "0.1.0", + "author": { + "github_id": "6865942", + "github_name": "zerebos", + "display_name": "Zerebos", + "discord_name": "zerebos", + "discord_avatar_hash": "e204ec2e923c4e4dfbf98c1eeaaa04f6", + "discord_snowflake": "249746236008169473", + "guild": null + }, + "likes": 400, + "downloads": 740738, + "tags": [ + "edit", + "text", + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/400.jpg", + "latest_source_url": "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/c2fdb36b97e4aee083130381a20ace050dd16621/Plugins/HideDisabledEmojis/HideDisabledEmojis.plugin.js", + "initial_release_date": "2021-05-02T01:58:43.244163Z", + "latest_release_date": "2024-12-17T01:18:57.029509Z", + "guild": null + }, + "sleekcord.theme.css": { + "id": 1407, + "name": "Sleekcord", + "file_name": "SleekCord.theme.css", + "type": "theme", + "description": "A pitch black theme that's sleek and modern in every way.", + "version": "1", + "author": { + "github_id": "110022489", + "github_name": "TNT4ME", + "display_name": "tnt4me", + "discord_name": "tnt4me", + "discord_avatar_hash": "b8efeb84e2c0eae6bfb3919b0b2dbdf4", + "discord_snowflake": "790463558625918999", + "guild": null + }, + "likes": 0, + "downloads": 3451, + "tags": [ + "flat", + "customizable", + "dark", + "purple", + "animated" + ], + "thumbnail_url": "/resources/thumbnails/1735.png", + "latest_source_url": "https://raw.githubusercontent.com/TNT4ME/SleekCord/4cb266498962cf6f67bcd4dcd5adf3070934b786/SleekCord.theme.css", + "initial_release_date": "2025-09-13T20:53:24.913511Z", + "latest_release_date": "2026-01-26T11:31:28.744408Z", + "guild": null + }, + "showconnections.plugin.js": { + "id": 291, + "name": "ShowConnections", + "file_name": "ShowConnections.plugin.js", + "type": "plugin", + "description": "Shows the connected Accounts of a User in the UserPopout", + "version": "1.3.2", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 271, + "downloads": 401368, + "tags": [ + "activity", + "enhancement", + "members" + ], + "thumbnail_url": "/resources/thumbnails/512.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/10731bb6ebb27d4f6f554f9698a0ad29de3f9209/Plugins/ShowConnections/ShowConnections.plugin.js", + "initial_release_date": "2021-05-31T13:04:52.109016Z", + "latest_release_date": "2026-01-25T13:44:58.363425Z", + "guild": null + }, + "neutron.theme.css": { + "id": 226, + "name": "Neutron", + "file_name": "neutron.theme.css", + "type": "theme", + "description": "A highly customizable Vibrant Purple Space Theme, now sugar free!", + "version": "9.0", + "author": { + "github_id": "59365159", + "github_name": "xcruxiex", + "display_name": "cruxie", + "discord_name": "cruxie", + "discord_avatar_hash": "d53200d43a5951bd19ffb0b255c48b41", + "discord_snowflake": "332394843743584256", + "guild": null + }, + "likes": 185, + "downloads": 287789, + "tags": [ + "transparent", + "customizable", + "dark", + "purple", + "abstract" + ], + "thumbnail_url": "/resources/thumbnails/1766.png", + "latest_source_url": "https://raw.githubusercontent.com/xcruxiex/themes/60f99e1273ac4208f02f28833bf3fb317469f2bd/themes-files/neutron.theme.css", + "initial_release_date": "2021-05-12T18:33:19.590701Z", + "latest_release_date": "2026-02-14T21:05:44.492825Z", + "guild": null + }, + "emojireplace.theme.css": { + "id": 48, + "name": "EmojiReplace", + "file_name": "EmojiReplace.theme.css", + "type": "theme", + "description": "Replaces Discord's Emojis with Emojis of a different Provider (Apple, Facebook...)", + "version": "1.0.0", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 337, + "downloads": 355841, + "tags": [ + "customizable", + "other" + ], + "thumbnail_url": "/resources/thumbnails/1294.gif", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/3b33c83ee23bd5b8eb384c99fb15a6f6d20873d6/Themes/EmojiReplace/EmojiReplace.theme.css", + "initial_release_date": "2021-02-25T10:15:59.102548Z", + "latest_release_date": "2024-03-15T13:48:36.432595Z", + "guild": null + }, + "snowfall.plugin.js": { + "id": 843, + "name": "Snowfall", + "file_name": "Snowfall.plugin.js", + "type": "plugin", + "description": "Let It Snow...", + "version": "1.1.2", + "author": { + "github_id": "18194808", + "github_name": "Inve1951", + "display_name": "square", + "discord_name": "square3880", + "discord_avatar_hash": "a14a62c0248cff5dffce2f3e57f25a78", + "discord_snowflake": "219363409097916416", + "guild": null + }, + "likes": 3, + "downloads": 10540, + "tags": [ + "fun" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/Inve1951/BetterDiscordStuff/71dfee1db3fdf9431468329a172c6972f2a882eb/plugins/Snowfall.plugin.js", + "initial_release_date": "2022-12-02T15:18:36.991484Z", + "latest_release_date": "2025-12-01T21:43:34.1208Z", + "guild": null + }, + "removedconnectionalerts.plugin.js": { + "id": 859, + "name": "RemovedConnectionAlerts", + "file_name": "RemovedConnectionAlerts.plugin.js", + "type": "plugin", + "description": "Keep track which friends and servers remove you (original concept by Metalloriff)", + "version": "0.9.5", + "author": { + "github_id": "9921070", + "github_name": "iyu46", + "display_name": "iris!#2888", + "discord_name": "selia.", + "discord_avatar_hash": "86f8c378ffecdd8b73d76c4b9e606b33", + "discord_snowflake": "102528230413578240", + "guild": null + }, + "likes": 23, + "downloads": 23472, + "tags": [ + "notifications", + "servers", + "friends" + ], + "thumbnail_url": "/resources/thumbnails/1383.png", + "latest_source_url": "https://raw.githubusercontent.com/iyu46/RemovedConnectionAlerts/a6081c130661c1433d56785314af6f6f6972a1d3/RemovedConnectionAlerts.plugin.js", + "initial_release_date": "2023-01-02T01:52:03.745228Z", + "latest_release_date": "2026-01-24T04:46:46.189532Z", + "guild": null + }, + "betterfriendlist.plugin.js": { + "id": 61, + "name": "BetterFriendList", + "file_name": "BetterFriendList.plugin.js", + "type": "plugin", + "description": "Adds extra Controls to the Friends Page, for example sort by Name/Status, Search and Amount Numbers, new Tabs", + "version": "1.6.8", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 292, + "downloads": 403653, + "tags": [ + "enhancement", + "organization", + "friends" + ], + "thumbnail_url": "/resources/thumbnails/451.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/54a30eda34f08a507a39f589807ba007c0691001/Plugins/BetterFriendList/BetterFriendList.plugin.js", + "initial_release_date": "2021-03-06T09:41:24.009245Z", + "latest_release_date": "2026-01-24T08:59:12.828641Z", + "guild": null + }, + "translator.plugin.js": { + "id": 81, + "name": "Translator", + "file_name": "Translator.plugin.js", + "type": "plugin", + "description": "Allows you to translate Messages and your outgoing Message within Discord", + "version": "2.7.9", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 951, + "downloads": 1661404, + "tags": [ + "text", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/255.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/a035793f28338dae7fdf72b9430520b92fc0ca3e/Plugins/Translator/Translator.plugin.js", + "initial_release_date": "2021-03-06T10:04:15.04675Z", + "latest_release_date": "2026-02-14T07:05:15.868229Z", + "guild": null + }, + "friendcodes.plugin.js": { + "id": 1124, + "name": "FriendCodes", + "file_name": "FriendCodes.plugin.js", + "type": "plugin", + "description": "Generate FriendCodes to easily add friends", + "version": "1.2.3", + "author": { + "github_id": "50876016", + "github_name": "domi-btnr", + "display_name": "domi.btnr", + "discord_name": "domi.btnr", + "discord_avatar_hash": "661304b01ea9a6593b93c6c4880070f3", + "discord_snowflake": "354191516979429376", + "guild": { + "name": "¯\\_(ツ)_/¯", + "snowflake": "838690163160514601", + "invite_link": "https://discord.gg/gp2ExK5vc7", + "avatar_hash": "216b8f7141608a933da9c668adb4edee " + } + }, + "likes": 4, + "downloads": 1689, + "tags": [ + "enhancement", + "friends", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1509.png", + "latest_source_url": "https://raw.githubusercontent.com/domi-btnr/BetterDiscordStuff/faa0f461681365f35ffbe315a233b86df1c3813a/FriendCodes/FriendCodes.plugin.js", + "initial_release_date": "2024-06-28T01:19:24.55805Z", + "latest_release_date": "2026-01-24T12:05:28.893098Z", + "guild": null + }, + "betterroledot.plugin.js": { + "id": 954, + "name": "BetterRoleDot", + "file_name": "BetterRoleDot.plugin.js", + "type": "plugin", + "description": "Allows you to use role dots while still having coloured role names! Also makes the role dot copy the role colour on click", + "version": "1.0.0", + "author": { + "github_id": "45497981", + "github_name": "Vendicated", + "display_name": "vee 💢", + "discord_name": "vending.machine", + "discord_avatar_hash": "a_40f8befd7c6fec85bdc3fc9fcdaefea8", + "discord_snowflake": "343383572805058560", + "guild": null + }, + "likes": 4, + "downloads": 7146, + "tags": [ + "enhancement", + "roles" + ], + "thumbnail_url": "/resources/thumbnails/1303.png", + "latest_source_url": "https://raw.githubusercontent.com/Vendicated/BetterDiscordPlugins/6d718060a816742333e3804f434bd80f197f0b21/Plugins/BetterRoleDot/BetterRoleDot.plugin.js", + "initial_release_date": "2023-05-23T15:24:14.663127Z", + "latest_release_date": "2023-05-23T15:24:15.294575Z", + "guild": null + }, + "ocean.theme.css": { + "id": 468, + "name": "Ocean", + "file_name": "Ocean.theme.css", + "type": "theme", + "description": "Highly customized and customizable theme for Discord", + "version": "3.1", + "author": { + "github_id": "73029696", + "github_name": "DevEvil99", + "display_name": "DevEvil", + "discord_name": "devevil", + "discord_avatar_hash": "fff54ac325af38e0752324d8665510f3", + "discord_snowflake": "468132563714703390", + "guild": { + "name": "༺⟅𝗗𝗲𝘃𝗘𝘃𝗶𝗹 𝗖𝗼𝗺𝗺𝘂𝗻𝗶𝘁𝘆⟆༻", + "snowflake": "763094597454397490", + "invite_link": "https://discord.gg/jsQ9UP7kCA", + "avatar_hash": "a82d42e51a8711f98798033bf4ace472 " + } + }, + "likes": 95, + "downloads": 254339, + "tags": [ + "transparent", + "customizable", + "blue", + "aqua" + ], + "thumbnail_url": "/resources/thumbnails/1610.png", + "latest_source_url": "https://raw.githubusercontent.com/DevEvil99/Ocean-Discord-Theme/5e4624a67fe92afdec9101524847c5212975cf95/Ocean.theme.css", + "initial_release_date": "2021-08-29T12:33:35.167502Z", + "latest_release_date": "2025-01-27T15:01:48.159114Z", + "guild": null + }, + "guildprofile.plugin.js": { + "id": 1243, + "name": "GuildProfile", + "file_name": "GuildProfile.plugin.js", + "type": "plugin", + "description": "Gives every server a profile popout of a guild spanning to Mutual friends, blocked and even emojis!", + "version": "2.0.0", + "author": { + "github_id": "90235641", + "github_name": "zrodevkaan", + "display_name": "Arven", + "discord_name": "zrodevkaan", + "discord_avatar_hash": "c088651f69e5f7f9291dcedba65ce8d8", + "discord_snowflake": "917630027477159986", + "guild": { + "name": "Froggy Support", + "snowflake": "1142561913453166794", + "invite_link": "https://discord.gg/t3zMgv7Nvb", + "avatar_hash": "4a4284a4e8e19853afcba9451e2d5fbb " + } + }, + "likes": 2, + "downloads": 4732, + "tags": [ + "fun", + "enhancement", + "servers", + "utility", + "search" + ], + "thumbnail_url": "/resources/thumbnails/1639.png", + "latest_source_url": "https://raw.githubusercontent.com/zrodevkaan/BDPlugins/c724a7d46b6d3616b124bec2c5897aa69bc51e7d/Plugins/GuildProfile/GuildProfile.plugin.js", + "initial_release_date": "2025-02-18T02:26:34.06869Z", + "latest_release_date": "2026-01-24T00:47:53.83923Z", + "guild": null + }, + "skeuocord.theme.css": { + "id": 204, + "name": "SkeuoCord", + "file_name": "SkeuoCord.theme.css", + "type": "theme", + "description": "A complete skeuomorphic overhaul for Discord.", + "version": "Auto Update", + "author": { + "github_id": "60007477", + "github_name": "Marda33", + "display_name": "JustCursedRedLuigi", + "discord_name": "JustCursedRedLuigi#3625", + "discord_avatar_hash": "2684bf9bbf53f6c5b61d4738b59dd4ee", + "discord_snowflake": "257528678420840449", + "guild": null + }, + "likes": 217, + "downloads": 111550, + "tags": [ + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/994.gif", + "latest_source_url": "https://raw.githubusercontent.com/Marda33/SkeuoCord/cd26d2767d2104f4eb472233ba90cf5c9f0e134c/SkeuoCord.theme.css", + "initial_release_date": "2021-05-05T07:46:42.68878Z", + "latest_release_date": "2025-07-14T21:02:19.416648Z", + "guild": null + }, + "spellcheck.plugin.js": { + "id": 104, + "name": "SpellCheck", + "file_name": "SpellCheck.plugin.js", + "type": "plugin", + "description": "Adds a Spell Check to all Message Inputs. Select a Word and Right Click it to add it to your Dictionary", + "version": "1.7.3", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 169, + "downloads": 231272, + "tags": [ + "text", + "enhancement" + ], + "thumbnail_url": "/resources/thumbnails/291.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/92f52d9f66172df2c4f0d4394de843332689b861/Plugins/SpellCheck/SpellCheck.plugin.js", + "initial_release_date": "2021-03-06T18:00:54.381397Z", + "latest_release_date": "2026-02-08T18:59:11.210626Z", + "guild": null + }, + "uirefreshrefresh.theme.css": { + "id": 1270, + "name": "UI Refresh Refresh", + "file_name": "UIRefreshRefresh.theme.css", + "type": "theme", + "description": "Enjoy Discord's UI refresh without the visual inconsistencies.", + "version": "1.6.0", + "author": { + "github_id": "43104632", + "github_name": "programmer2514", + "display_name": "programmer2514", + "discord_name": "programmer2514", + "discord_avatar_hash": "b8a0b2af5aaf90549f620f36b8a57cc1", + "discord_snowflake": "563652755814875146", + "guild": null + }, + "likes": 5, + "downloads": 8211, + "tags": [ + "layout" + ], + "thumbnail_url": "/resources/thumbnails/1598.png", + "latest_source_url": "https://raw.githubusercontent.com/programmer2514/BetterDiscord-UIRefreshRefresh/56c25d2efa3e500a718f02587c172d679fd36f55/UIRefreshRefresh.theme.css", + "initial_release_date": "2025-03-27T05:51:25.554378Z", + "latest_release_date": "2026-01-15T23:23:26.663478Z", + "guild": null + }, + "discordeffects.plugin.js": { + "id": 1436, + "name": "DiscordEffects", + "file_name": "DiscordEffects.plugin.js", + "type": "plugin", + "description": "Adds the ability to put effects on your discord.", + "version": "2.2.0", + "author": { + "github_id": "153164151", + "github_name": "Deleox", + "display_name": "Deleox", + "discord_name": "deleox001", + "discord_avatar_hash": "5e1d13ea37ed1c635a1683dc36bd90d1", + "discord_snowflake": "1156430974008184962", + "guild": null + }, + "likes": 2, + "downloads": 2226, + "tags": [ + "fun" + ], + "thumbnail_url": "/resources/thumbnails/1768.png", + "latest_source_url": "https://raw.githubusercontent.com/Deleox/BDPlugins/55a2402b53043d49d9163133cca71aa48ce04bdb/DiscordEffects/DiscordEffects.plugin.js", + "initial_release_date": "2025-11-28T19:27:24.916095Z", + "latest_release_date": "2026-02-17T19:05:42.700538Z", + "guild": null + }, + "filenamerandomization.plugin.js": { + "id": 1077, + "name": "FileNameRandomization", + "file_name": "FileNameRandomization.plugin.js", + "type": "plugin", + "description": "Randomizes uploaded file names for enhanced privacy and organization. Users can opt for a unique random string, a Unix timestamp, or a custom format.", + "version": "2.0.0", + "author": { + "github_id": "90235641", + "github_name": "zrodevkaan", + "display_name": "Arven", + "discord_name": "zrodevkaan", + "discord_avatar_hash": "c088651f69e5f7f9291dcedba65ce8d8", + "discord_snowflake": "917630027477159986", + "guild": { + "name": "Froggy Support", + "snowflake": "1142561913453166794", + "invite_link": "https://discord.gg/t3zMgv7Nvb", + "avatar_hash": "4a4284a4e8e19853afcba9451e2d5fbb " + } + }, + "likes": 1, + "downloads": 2708, + "tags": [ + "enhancement" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/zrodevkaan/BDPlugins/c724a7d46b6d3616b124bec2c5897aa69bc51e7d/Plugins/FileNameRandomization/FileNameRandomization.plugin.js", + "initial_release_date": "2024-02-19T20:08:00.857533Z", + "latest_release_date": "2026-01-24T00:47:57.509802Z", + "guild": null + }, + "hideembedlink.plugin.js": { + "id": 171, + "name": "HideEmbedLink", + "file_name": "HideEmbedLink.plugin.js", + "type": "plugin", + "description": "Removes links for embed messages. Adds a button to force show links of a message.", + "version": "2.2.6", + "author": { + "github_id": "58090137", + "github_name": "Dastan21", + "display_name": "Dastan", + "discord_name": "dastan21", + "discord_avatar_hash": "e2c09e4a7c84c1eb1909de17362f82ac", + "discord_snowflake": "310450863845933057", + "guild": null + }, + "likes": 17, + "downloads": 17589, + "tags": [ + "text", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/352.png", + "latest_source_url": "https://raw.githubusercontent.com/Dastan21/BDAddons/f0ef1371542771ba45f70c5ba62a553a4f872235/plugins/HideEmbedLink/HideEmbedLink.plugin.js", + "initial_release_date": "2021-04-24T23:44:10.822418Z", + "latest_release_date": "2026-02-19T10:06:15.135663Z", + "guild": null + }, + "stickeremojipreview.plugin.js": { + "id": 807, + "name": "StickerEmojiPreview", + "file_name": "StickerEmojiPreview.plugin.js", + "type": "plugin", + "description": "Adds a zoomed preview to those tiny Stickers and Emojis", + "version": "1.3.3", + "author": { + "github_id": "114232893", + "github_name": "Skamt", + "display_name": "Skamt", + "discord_name": "skamt.", + "discord_avatar_hash": "4530f48f71ac21738a7ce40cb10ff48c", + "discord_snowflake": "1030617301818552320", + "guild": null + }, + "likes": 10, + "downloads": 17330, + "tags": [ + "emotes", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1147.png", + "latest_source_url": "https://raw.githubusercontent.com/Skamt/BDAddons/c0ac383b283a92d9d7b501c9cecf2834812ba2d6/StickerEmojiPreview/StickerEmojiPreview.plugin.js", + "initial_release_date": "2022-10-18T22:51:25.840926Z", + "latest_release_date": "2025-12-04T12:53:27.461952Z", + "guild": null + }, + "minimalimprovement.theme.css": { + "id": 636, + "name": "MinimalImprovement (With Borders)", + "file_name": "MinimalImprovement.theme.css", + "type": "theme", + "description": "Dark mode, made geometric and sleek. Contains borders to emphasize distinct spaces.", + "version": "3.14.0", + "author": { + "github_id": "20595808", + "github_name": "Juicysteak117", + "display_name": "Juicysteak117", + "discord_name": "juicysteak117", + "discord_avatar_hash": "a92a9944bde7b02c4985cd66b7a57c1e", + "discord_snowflake": "112685077707665408", + "guild": null + }, + "likes": 7, + "downloads": 8421, + "tags": [ + "flat", + "dark", + "black" + ], + "thumbnail_url": "/resources/thumbnails/951.png", + "latest_source_url": "https://raw.githubusercontent.com/Juicysteak117/MinimalImprovement/1e7f3c87a21b924e1c8afc9f2629ff15a4ba5a4f/MinimalImprovement.theme.css", + "initial_release_date": "2022-03-05T05:02:01.626273Z", + "latest_release_date": "2023-08-31T20:46:02.09325Z", + "guild": null + }, + "spotifycontrols.plugin.js": { + "id": 105, + "name": "SpotifyControls", + "file_name": "SpotifyControls.plugin.js", + "type": "plugin", + "description": "Adds a Control Panel while listening to Spotify on a connected Account", + "version": "1.5.3", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 978, + "downloads": 1511791, + "tags": [ + "activity", + "shortcut", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/292.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/b5141de4161130b252fa276a4e538f10f1a2ef41/Plugins/SpotifyControls/SpotifyControls.plugin.js", + "initial_release_date": "2021-03-06T18:01:43.653057Z", + "latest_release_date": "2026-01-25T14:30:42.412902Z", + "guild": null + }, + "fluent-discord.theme.css": { + "id": 643, + "name": "Fluent Discord", + "file_name": "Fluent-Discord.theme.css", + "type": "theme", + "description": "Fluent theme for Discord", + "version": "1.12.1", + "author": { + "github_id": "20344790", + "github_name": "TakosThings", + "display_name": "Tako", + "discord_name": "hot_takoyaki", + "discord_avatar_hash": "396899ca01cf15f306c30c0b6caba685", + "discord_snowflake": "124017508662378500", + "guild": null + }, + "likes": 53, + "downloads": 52769, + "tags": [ + "flat", + "dark" + ], + "thumbnail_url": "/resources/thumbnails/959.png", + "latest_source_url": "https://raw.githubusercontent.com/TakosThings/Fluent-Discord/d7fa5fb5677c35dd767c4f21dcefcb73753c668e/Fluent-Discord.theme.css", + "initial_release_date": "2022-03-06T15:27:15.14971Z", + "latest_release_date": "2025-04-19T07:33:18.418445Z", + "guild": { + "name": "Fluent Discord", + "snowflake": "861849676089393173", + "invite_link": "https://discord.gg/ZYrCacRuez", + "avatar_hash": "9a5fb801f7821472f22dd373b5905da9 " + } + }, + "youtube_nation.theme.css": { + "id": 866, + "name": "Youtube Nation", + "file_name": "Youtube_Nation.theme.css", + "type": "theme", + "description": "The Storage of All Videos", + "version": "4.4", + "author": { + "github_id": "30361475", + "github_name": "ShadowDevilsAvenged", + "display_name": "ShadowDevilsAvenged", + "discord_name": "shadowdevilsavenged", + "discord_avatar_hash": "59346cd10940d7cae650a5e2336b316a", + "discord_snowflake": "858800346884997142", + "guild": { + "name": "Shadow's Safe Theme Collection", + "snowflake": "862776993152892939", + "invite_link": "https://discord.gg/6gseVdez6A", + "avatar_hash": "9c81716265f90ddb92c06af468a0094d " + } + }, + "likes": 11, + "downloads": 17078, + "tags": [ + "flat", + "transparent" + ], + "thumbnail_url": "/resources/thumbnails/1210.png", + "latest_source_url": "https://raw.githubusercontent.com/ShadowDevilsAvenged/ShadowDevilsAvenged/84c3dd45b7bfe98ee6482b2abfa467c1b81580df/My_Theme_Collection/Youtube_Nation.theme.css", + "initial_release_date": "2023-01-15T22:59:09.330201Z", + "latest_release_date": "2025-10-01T21:30:39.895251Z", + "guild": null + }, + "backgroundmanager.plugin.js": { + "id": 1115, + "name": "BackgroundManager", + "file_name": "BackgroundManager.plugin.js", + "type": "plugin", + "description": "Enhances themes supporting background images with features (local folder, slideshow, transitions).", + "version": "1.2.19", + "author": { + "github_id": "91275265", + "github_name": "Naru-kami", + "display_name": "Narukami", + "discord_name": "narukami5414", + "discord_avatar_hash": "abe87c21aff218d8e24f7c615815b675", + "discord_snowflake": "245262066745737216", + "guild": null + }, + "likes": 10, + "downloads": 27235, + "tags": [ + "fun", + "shortcut", + "enhancement" + ], + "thumbnail_url": "/resources/thumbnails/1460.gif", + "latest_source_url": "https://raw.githubusercontent.com/Naru-kami/BackgroundManager-plugin/6084a357d1eff75e5f26d2d51dc59043512dfcdc/BackgroundManager.plugin.js", + "initial_release_date": "2024-06-15T10:28:59.501193Z", + "latest_release_date": "2025-12-16T22:50:15.592773Z", + "guild": null + }, + "quickmention.plugin.js": { + "id": 93, + "name": "QuickMention", + "file_name": "QuickMention.plugin.js", + "type": "plugin", + "description": "Adds a Mention Button to the Message Options Bar", + "version": "1.0.9", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 244, + "downloads": 328375, + "tags": [ + "notifications", + "shortcut", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/280.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/QuickMention/QuickMention.plugin.js", + "initial_release_date": "2021-03-06T17:50:51.854173Z", + "latest_release_date": "2025-12-07T11:28:54.32883Z", + "guild": null + }, + "oldtitlebar.plugin.js": { + "id": 89, + "name": "OldTitlebar", + "file_name": "OldTitleBar.plugin.js", + "type": "plugin", + "description": "Allows you to switch to Discord's old Titlebar", + "version": "1.9.2", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 36, + "downloads": 39111, + "tags": [ + "organization" + ], + "thumbnail_url": "/resources/thumbnails/276.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/1a474537571b0a8bfa90d4cbffdb8a1810d0960d/Plugins/OldTitleBar/OldTitleBar.plugin.js", + "initial_release_date": "2021-03-06T17:46:53.426913Z", + "latest_release_date": "2026-02-05T13:57:37.045442Z", + "guild": null + }, + "discorddark.theme.css": { + "id": 252, + "name": "Discord Dark", + "file_name": "discorddark.theme.css", + "type": "theme", + "description": "A Dark Theme for Discord, Inspired by Github Dark", + "version": "1.59", + "author": { + "github_id": "23367120", + "github_name": "SyndiShanX", + "display_name": "SyndiShanX", + "discord_name": "syndishanx", + "discord_avatar_hash": "d1931fac0a94b380d60dce2c9b3ec3e1", + "discord_snowflake": "178394162381455360", + "guild": null + }, + "likes": 75, + "downloads": 128339, + "tags": [ + "transparent", + "customizable", + "dark", + "black", + "other" + ], + "thumbnail_url": "/resources/thumbnails/1083.png", + "latest_source_url": "https://raw.githubusercontent.com/SyndiShanX/Discord-Dark/ed84f9627161a79f167202bb7f05e340d81a8b00/discorddark.theme.css", + "initial_release_date": "2021-05-18T17:32:08.950245Z", + "latest_release_date": "2026-01-25T17:35:50.741136Z", + "guild": null + }, + "voicemessages.plugin.js": { + "id": 1199, + "name": "VoiceMessages", + "file_name": "VoiceMessages.plugin.js", + "type": "plugin", + "description": "Allows you to send voice messages like on mobile. To do so, click the upload button and click Send Voice Message.", + "version": "0.1.12", + "author": { + "github_id": "54255074", + "github_name": "riolubruh", + "display_name": "Riolubruh", + "discord_name": "riolubruh", + "discord_avatar_hash": "012aba2bcdd74d7667fa7abed83ae4a5", + "discord_snowflake": "359063827091816448", + "guild": { + "name": "Riolubruh's Public Server", + "snowflake": "1228506998128246835", + "invite_link": "https://discord.gg/HfFxUbgsBc", + "avatar_hash": "c1d1c565377f2963fd54da54dec5f2a3 " + } + }, + "likes": 12, + "downloads": 28249, + "tags": [ + "enhancement", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/1547.png", + "latest_source_url": "https://raw.githubusercontent.com/riolubruh/VoiceMessages/8b1da926a288d82d9c385a2ccababd9c644de6db/VoiceMessages.plugin.js", + "initial_release_date": "2024-12-23T00:12:18.793844Z", + "latest_release_date": "2026-02-06T19:16:57.059428Z", + "guild": null + }, + "neptune.theme.css": { + "id": 581, + "name": "Neptune", + "file_name": "Neptune.theme.css", + "type": "theme", + "description": "A dark, blue and customizable theme", + "version": "4", + "author": { + "github_id": "73029696", + "github_name": "DevEvil99", + "display_name": "DevEvil", + "discord_name": "devevil", + "discord_avatar_hash": "fff54ac325af38e0752324d8665510f3", + "discord_snowflake": "468132563714703390", + "guild": { + "name": "༺⟅𝗗𝗲𝘃𝗘𝘃𝗶𝗹 𝗖𝗼𝗺𝗺𝘂𝗻𝗶𝘁𝘆⟆༻", + "snowflake": "763094597454397490", + "invite_link": "https://discord.gg/jsQ9UP7kCA", + "avatar_hash": "a82d42e51a8711f98798033bf4ace472 " + } + }, + "likes": 40, + "downloads": 48610, + "tags": [ + "customizable", + "dark", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/1609.png", + "latest_source_url": "https://raw.githubusercontent.com/DevEvil99/Neptune-Discord-Theme/de1277722fbebdb24818450de1a370ccc32a7b73/Neptune.theme.css", + "initial_release_date": "2022-01-21T16:26:23.934488Z", + "latest_release_date": "2025-01-27T15:01:42.464176Z", + "guild": null + }, + "toproleeverywhere.plugin.js": { + "id": 109, + "name": "TopRoleEverywhere", + "file_name": "TopRoleEverywhere.plugin.js", + "type": "plugin", + "description": "Adds the highest Role of a User as a Tag", + "version": "3.2.3", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 55, + "downloads": 69551, + "tags": [ + "enhancement", + "chat", + "roles" + ], + "thumbnail_url": "/resources/thumbnails/296.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/TopRoleEverywhere/TopRoleEverywhere.plugin.js", + "initial_release_date": "2021-03-06T18:05:08.957411Z", + "latest_release_date": "2025-12-07T11:27:42.924561Z", + "guild": null + }, + "discordnight.theme.css": { + "id": 155, + "name": "DiscordNight", + "file_name": "DiscordNight.theme.css", + "type": "theme", + "description": "A dark and compact theme", + "version": "0.9.5.8", + "author": { + "github_id": "1519297", + "github_name": "KillYoy", + "display_name": "KillYoy", + "discord_name": "KillYoy#0295", + "discord_avatar_hash": "d63597cc4f64b5312524372fed01b06b", + "discord_snowflake": "98468214824001536", + "guild": null + }, + "likes": 245, + "downloads": 580955, + "tags": [ + "flat", + "customizable", + "dark", + "black" + ], + "thumbnail_url": "/resources/thumbnails/338.png", + "latest_source_url": "https://raw.githubusercontent.com/KillYoy/DiscordNight/97181506274285c3aa03d0d5e3742dd3a014a6fa/DiscordNight.theme.css", + "initial_release_date": "2021-04-21T17:54:00.477684Z", + "latest_release_date": "2024-04-01T01:43:32.104129Z", + "guild": null + }, + "azurite.theme.css": { + "id": 722, + "name": "Azurite", + "file_name": "Azurite.theme.css", + "type": "theme", + "description": "Bring a new look to your Discord with Azurite!", + "version": "4", + "author": { + "github_id": "73029696", + "github_name": "DevEvil99", + "display_name": "DevEvil", + "discord_name": "devevil", + "discord_avatar_hash": "fff54ac325af38e0752324d8665510f3", + "discord_snowflake": "468132563714703390", + "guild": { + "name": "༺⟅𝗗𝗲𝘃𝗘𝘃𝗶𝗹 𝗖𝗼𝗺𝗺𝘂𝗻𝗶𝘁𝘆⟆༻", + "snowflake": "763094597454397490", + "invite_link": "https://discord.gg/jsQ9UP7kCA", + "avatar_hash": "a82d42e51a8711f98798033bf4ace472 " + } + }, + "likes": 62, + "downloads": 66503, + "tags": [ + "customizable", + "dark", + "green" + ], + "thumbnail_url": "/resources/thumbnails/1607.png", + "latest_source_url": "https://raw.githubusercontent.com/DevEvil99/Azurite-Discord-Theme/aaf6bdd8c601dba33fd19bf50746b2331fd9890f/Azurite.theme.css", + "initial_release_date": "2022-06-07T20:07:09.194743Z", + "latest_release_date": "2025-05-22T20:11:27.968767Z", + "guild": null + }, + "usertags.plugin.js": { + "id": 1343, + "name": "UserTags", + "file_name": "UserTags.plugin.js", + "type": "plugin", + "description": "Allows you to add custom tags to users and search by them.", + "version": "1.0.0", + "author": { + "github_id": "22408517", + "github_name": "SrS2225a", + "display_name": "Nyx#8614", + "discord_name": ".fenriris", + "discord_avatar_hash": "a_bc121fc48960989bafcbbb385e85a2a4", + "discord_snowflake": "270848136006729728", + "guild": null + }, + "likes": 1, + "downloads": 1312, + "tags": [ + "shortcut", + "organization", + "members", + "utility", + "search" + ], + "thumbnail_url": "/resources/thumbnails/1675.png", + "latest_source_url": "https://raw.githubusercontent.com/SrS2225a/BetterDiscord/415a84b535ae33f5ac706f647cc54a90bd2db24f/plugins/UserTags/UserTags.plugin.js", + "initial_release_date": "2025-05-30T02:38:53.058287Z", + "latest_release_date": "2025-05-30T03:08:24.607837Z", + "guild": null + }, + "inserttimestamps.plugin.js": { + "id": 953, + "name": "InsertTimestamps", + "file_name": "InsertTimestamps.plugin.js", + "type": "plugin", + "description": "Allows you to insert timestamp markdown with a convenient chat bar button", + "version": "1.0.13", + "author": { + "github_id": "45497981", + "github_name": "Vendicated", + "display_name": "vee 💢", + "discord_name": "vending.machine", + "discord_avatar_hash": "a_40f8befd7c6fec85bdc3fc9fcdaefea8", + "discord_snowflake": "343383572805058560", + "guild": null + }, + "likes": 16, + "downloads": 12132, + "tags": [ + "text", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1302.png", + "latest_source_url": "https://raw.githubusercontent.com/Vendicated/BetterDiscordPlugins/09bd7603c379b695102110ac838e59b4a1105ff4/Plugins/InsertTimestamps/InsertTimestamps.plugin.js", + "initial_release_date": "2023-05-23T15:22:52.029347Z", + "latest_release_date": "2025-12-19T06:16:08.084719Z", + "guild": null + }, + "channeltabs.plugin.js": { + "id": 195, + "name": "ChannelTabs", + "file_name": "ChannelTabs.plugin.js", + "type": "plugin", + "description": "Adds tabs and bookmarks to Discord, just like in a normal web browser.", + "version": "2.8.9", + "author": { + "github_id": "6759716", + "github_name": "samfundev", + "display_name": "samfundev", + "discord_name": "samfundev", + "discord_avatar_hash": "6f11cad8e89d3d867828d8bcdcd150fd", + "discord_snowflake": "76052829285916672", + "guild": null + }, + "likes": 175, + "downloads": 193866, + "tags": [ + "shortcut", + "enhancement", + "organization" + ], + "thumbnail_url": "/resources/thumbnails/1382.gif", + "latest_source_url": "https://raw.githubusercontent.com/samfundev/BetterDiscordStuff/b2485111e095270d51265d1b65e7dc26c8251562/Plugins/ChannelTabs/ChannelTabs.plugin.js", + "initial_release_date": "2021-05-02T13:02:04.06993Z", + "latest_release_date": "2026-02-06T17:13:23.533715Z", + "guild": null + }, + "spotifyenhance.plugin.js": { + "id": 1097, + "name": "SpotifyEnhance", + "file_name": "SpotifyEnhance.plugin.js", + "type": "plugin", + "description": "All in one better spotify-discord experience.", + "version": "1.1.12", + "author": { + "github_id": "114232893", + "github_name": "Skamt", + "display_name": "Skamt", + "discord_name": "skamt.", + "discord_avatar_hash": "4530f48f71ac21738a7ce40cb10ff48c", + "discord_snowflake": "1030617301818552320", + "guild": null + }, + "likes": 19, + "downloads": 40693, + "tags": [ + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1438.png", + "latest_source_url": "https://raw.githubusercontent.com/Skamt/BDAddons/4ae8d77fe93def05a64d9a1a8b363d5a436c631c/SpotifyEnhance/SpotifyEnhance.plugin.js", + "initial_release_date": "2024-05-08T14:32:13.587903Z", + "latest_release_date": "2026-02-05T21:30:32.343901Z", + "guild": null + }, + "collapseembeds.plugin.js": { + "id": 827, + "name": "CollapseEmbeds", + "file_name": "CollapseEmbeds.plugin.js", + "type": "plugin", + "description": "Collapse embeds & attachments.", + "version": "2.1.5", + "author": { + "github_id": "19844016", + "github_name": "Zerthox", + "display_name": "Zerthox", + "discord_name": "zerthox", + "discord_avatar_hash": "3966dfed9ef64656359792e34af73305", + "discord_snowflake": "144881947557101568", + "guild": null + }, + "likes": 15, + "downloads": 8124, + "tags": [ + "text", + "enhancement", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/1170.png", + "latest_source_url": "https://raw.githubusercontent.com/Zerthox/BetterDiscord-Plugins/7c5f6e4ad7cada88974885daf2bfaedae2658a73/dist/bd/CollapseEmbeds.plugin.js", + "initial_release_date": "2022-11-09T17:47:58.271359Z", + "latest_release_date": "2026-02-14T15:49:18.740024Z", + "guild": null + }, + "bettermediaplayer.plugin.js": { + "id": 377, + "name": "BetterMediaPlayer", + "file_name": "BetterMediaPlayer.plugin.js", + "type": "plugin", + "description": "Adds more functionality to the media player in discord.", + "version": "1.2.22", + "author": { + "github_id": "43104779", + "github_name": "unknown81311", + "display_name": "unknown81311", + "discord_name": "unknown81311", + "discord_avatar_hash": "a_201499df3e7ad9745eefe2c6f220c5b5", + "discord_snowflake": "359174224809689089", + "guild": { + "name": "Dr.Discord", + "snowflake": "864267123694370836", + "invite_link": "https://discord.gg/yYJA3qQE5F", + "avatar_hash": "fdd9ac64197768137c2ab18651987635 " + } + }, + "likes": 106, + "downloads": 136744, + "tags": [ + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1568.png", + "latest_source_url": "https://raw.githubusercontent.com/unknown81311/BetterMediaPlayer/8081c989e1982453202e07b7eabbf61d6c0914fa/BetterMediaPlayer.plugin.js", + "initial_release_date": "2021-07-13T00:21:54.36422Z", + "latest_release_date": "2026-01-23T21:05:25.26123Z", + "guild": null + }, + "scrolltolatest.plugin.js": { + "id": 1451, + "name": "ScrollToLatest", + "file_name": "ScrollToLatest.plugin.js", + "type": "plugin", + "description": "Auto marks channels as read when you enter them so that it scrolls to the last message.", + "version": "1.0.2", + "author": { + "github_id": "81967520", + "github_name": "Snusene", + "display_name": "Snues", + "discord_name": ".snues", + "discord_avatar_hash": "48a2f024dc5fe2a29a470c424ab26e8f", + "discord_snowflake": "98862725609816064", + "guild": null + }, + "likes": 2, + "downloads": 450, + "tags": [ + "enhancement", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/1785.png", + "latest_source_url": "https://raw.githubusercontent.com/Snusene/BetterDiscordPlugins/10906d67308a09626f862aaa8d8388e6865ed2a6/ScrollToLatest/ScrollToLatest.plugin.js", + "initial_release_date": "2026-01-04T07:47:58.304566Z", + "latest_release_date": "2026-01-23T22:05:43.278904Z", + "guild": null + }, + "simpleusermenu.plugin.js": { + "id": 1347, + "name": "SimpleUserMenu", + "file_name": "SimpleUserMenu.plugin.js", + "type": "plugin", + "description": "Simplifies the user panel menu, giving it only the essentials and features it had pre-2024.", + "version": "1.1.0", + "author": { + "github_id": "45918062", + "github_name": "KingGamingYT", + "display_name": "KingGamingYT", + "discord_name": "kinggamingyt", + "discord_avatar_hash": "7a6a74d142c1bc7604e40e2239411d2d", + "discord_snowflake": "247153658385399818", + "guild": null + }, + "likes": 2, + "downloads": 1308, + "tags": [ + "shortcut", + "enhancement", + "organization" + ], + "thumbnail_url": "/resources/thumbnails/1677.png", + "latest_source_url": "https://raw.githubusercontent.com/KingGamingYT/SimpleUserMenu/392d06bc605560d0e3ef9a0e18e96999c810c826/SimpleUserMenu.plugin.js", + "initial_release_date": "2025-06-03T07:17:48.809128Z", + "latest_release_date": "2026-01-28T01:01:28.311495Z", + "guild": null + }, + "sendandforget.plugin.js": { + "id": 1382, + "name": "SendAndForget", + "file_name": "SendAndForget.plugin.js", + "type": "plugin", + "description": "Don't follow forwarded messages after sending them. Port of [Vendicated/Vencord#3558]()", + "version": "1.0.2", + "author": { + "github_id": "71196819", + "github_name": "doggybootsy", + "display_name": "DoggyBootsy", + "discord_name": "doggybootsy", + "discord_avatar_hash": "51e45b02bb0acf0449a87f3f1e079fc8", + "discord_snowflake": "515780151791976453", + "guild": null + }, + "likes": 0, + "downloads": 824, + "tags": [ + "shortcut", + "enhancement", + "utility" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/doggybootsy/BDPlugins/d24d705b3d33653b9a521d5421a1cb8fbf802b7c/SendAndForget/SendAndForget.plugin.js", + "initial_release_date": "2025-07-25T04:09:33.247573Z", + "latest_release_date": "2025-08-30T01:10:35.223652Z", + "guild": null + }, + "slate.theme.css": { + "id": 1, + "name": "Slate", + "file_name": "Slate.theme.css", + "type": "theme", + "description": "An optimized, consistent, and functional theme for Discord based on GitHub's design language.", + "version": "1.1", + "author": { + "github_id": "42101043", + "github_name": "Tropix126", + "display_name": "Tropical", + "discord_name": "tropicaal", + "discord_avatar_hash": "b282ebc62b5cadc7c590303892493771", + "discord_snowflake": "254362351170617345", + "guild": null + }, + "likes": 123, + "downloads": 194023, + "tags": [ + "flat", + "layout", + "customizable", + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/147.gif", + "latest_source_url": "https://raw.githubusercontent.com/DiscordStyles/Slate/f8488f744c6461fced2d2215458ead3a4d01b587/dist/Slate.theme.css", + "initial_release_date": "2021-02-22T18:19:05.569069Z", + "latest_release_date": "2021-11-03T09:04:25.048981Z", + "guild": null + }, + "editusers.plugin.js": { + "id": 76, + "name": "EditUsers", + "file_name": "EditUsers.plugin.js", + "type": "plugin", + "description": "Allows you to locally edit Users", + "version": "5.1.0", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 179, + "downloads": 356469, + "tags": [ + "edit", + "members" + ], + "thumbnail_url": "/resources/thumbnails/251.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/1a474537571b0a8bfa90d4cbffdb8a1810d0960d/Plugins/EditUsers/EditUsers.plugin.js", + "initial_release_date": "2021-03-06T09:59:17.578711Z", + "latest_release_date": "2026-02-05T13:57:43.450649Z", + "guild": null + }, + "notifywhenmuted.plugin.js": { + "id": 1206, + "name": "NotifyWhenMuted", + "file_name": "NotifyWhenMuted.plugin.js", + "type": "plugin", + "description": "Plays a sound when user tries to speak while muted", + "version": "1.4.18", + "author": { + "github_id": "61830443", + "github_name": "nicola02nb", + "display_name": "nicola02nb", + "discord_name": "nicola02nb", + "discord_avatar_hash": "b0e2a870015d31c91f2d8895d80b2113", + "discord_snowflake": "257900031351193600", + "guild": { + "name": "nicola02nb Dev Support", + "snowflake": "1329222382279327825", + "invite_link": "https://discord.gg/hFuY8DfDGK", + "avatar_hash": null + } + }, + "likes": 0, + "downloads": 4472, + "tags": [ + "notifications", + "voice", + "utility" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/nicola02nb/BetterDiscord-Stuff/689fd824ee80fc1ce2615e58ade2b09be0cc2fc6/Plugins/NotifyWhenMuted/NotifyWhenMuted.plugin.js", + "initial_release_date": "2025-01-02T12:40:46.27311Z", + "latest_release_date": "2026-02-09T14:41:21.70258Z", + "guild": null + }, + "gifcaptioner.plugin.js": { + "id": 1074, + "name": "GifCaptioner", + "file_name": "GifCaptioner.plugin.js", + "type": "plugin", + "description": "Allows you to add a caption to discord gifs", + "version": "2.1.4", + "author": { + "github_id": "76746384", + "github_name": "TheLazySquid", + "display_name": "TheLazySquid", + "discord_name": "thelazysquid", + "discord_avatar_hash": "e04c7e5372cbf77ecf7bd2784775aa3c", + "discord_snowflake": "619261917352951815", + "guild": { + "name": "TheLazySquid's Stuff", + "snowflake": "1432531781780897814", + "invite_link": "https://discord.gg/fKdAaFYbD5", + "avatar_hash": "4264dfaa6aefa8ac415b4680731dc25b " + } + }, + "likes": 12, + "downloads": 18398, + "tags": [ + "fun", + "text", + "friends" + ], + "thumbnail_url": "/resources/thumbnails/1417.png", + "latest_source_url": "https://raw.githubusercontent.com/TheLazySquid/BetterDiscordPlugins/99f81a9a7b31ccd914c2e1a7118eda5bbf83e32b/plugins/GifCaptioner/GifCaptioner.plugin.js", + "initial_release_date": "2024-02-18T22:39:27.410092Z", + "latest_release_date": "2026-02-13T17:30:16.913821Z", + "guild": null + }, + "opensteamlinksinapp.plugin.js": { + "id": 106, + "name": "OpenSteamLinksInApp", + "file_name": "OpenSteamLinksInApp.plugin.js", + "type": "plugin", + "description": "Opens Steam Links in Steam instead of your Browser", + "version": "1.1.8", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 61, + "downloads": 43330, + "tags": [ + "game", + "activity", + "shortcut" + ], + "thumbnail_url": "/resources/thumbnails/293.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/OpenSteamLinksInApp/OpenSteamLinksInApp.plugin.js", + "initial_release_date": "2021-03-06T18:02:29.847586Z", + "latest_release_date": "2025-12-07T11:27:17.871795Z", + "guild": null + }, + "shine.theme.css": { + "id": 1392, + "name": "Shine", + "file_name": "shine.theme.css", + "type": "theme", + "description": "Glossy, glassy theme based on a twitter design.", + "version": "1.2", + "author": { + "github_id": "74104258", + "github_name": "Blade04208", + "display_name": "the.rabbit.disabler", + "discord_name": "the.rabbit.disabler", + "discord_avatar_hash": "4755cf80054087c8d963086dc0f8518c", + "discord_snowflake": "1124341362955919371", + "guild": null + }, + "likes": 0, + "downloads": 2234, + "tags": [ + "customizable", + "dark", + "purple" + ], + "thumbnail_url": "/resources/thumbnails/1722.png", + "latest_source_url": "https://raw.githubusercontent.com/Blade04208/shine/6cbcb89bb32bd0904608ce205e49e1a418311569/shine.theme.css", + "initial_release_date": "2025-08-19T11:49:16.637069Z", + "latest_release_date": "2025-12-19T14:43:29.104977Z", + "guild": null + }, + "customstatuspresets.plugin.js": { + "id": 71, + "name": "CustomStatusPresets", + "file_name": "CustomStatusPresets.plugin.js", + "type": "plugin", + "description": "Allows you to save Custom Statuses as Quick Select", + "version": "1.3.6", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 186, + "downloads": 175004, + "tags": [ + "activity", + "status", + "shortcut" + ], + "thumbnail_url": "/resources/thumbnails/246.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/b17d9ee3edae5cb96be696b47705cfe1826f6579/Plugins/CustomStatusPresets/CustomStatusPresets.plugin.js", + "initial_release_date": "2021-03-06T09:52:42.227691Z", + "latest_release_date": "2026-02-08T18:53:13.8413Z", + "guild": null + }, + "old-akamegakill-theme.theme.css": { + "id": 713, + "name": "Old-Akame_Ga_Kill-Theme", + "file_name": "Old-AkameGaKill-Theme.theme.css", + "type": "theme", + "description": "Akame from Akame Ga Kill. This theme is back at it again", + "version": "7", + "author": { + "github_id": "30361475", + "github_name": "ShadowDevilsAvenged", + "display_name": "ShadowDevilsAvenged", + "discord_name": "shadowdevilsavenged", + "discord_avatar_hash": "59346cd10940d7cae650a5e2336b316a", + "discord_snowflake": "858800346884997142", + "guild": { + "name": "Shadow's Safe Theme Collection", + "snowflake": "862776993152892939", + "invite_link": "https://discord.gg/6gseVdez6A", + "avatar_hash": "9c81716265f90ddb92c06af468a0094d " + } + }, + "likes": 221, + "downloads": 260315, + "tags": [ + "transparent", + "anime", + "red" + ], + "thumbnail_url": "/resources/thumbnails/1033.png", + "latest_source_url": "https://raw.githubusercontent.com/ShadowDevilsAvenged/ShadowDevilsAvenged/84c3dd45b7bfe98ee6482b2abfa467c1b81580df/My_Theme_Collection/Old-AkameGaKill-Theme.theme.css", + "initial_release_date": "2022-05-23T13:34:50.252973Z", + "latest_release_date": "2025-10-01T21:30:38.32533Z", + "guild": null + }, + "auroragsi.plugin.js": { + "id": 1023, + "name": "AuroraGSI", + "file_name": "AuroraGSI.plugin.js", + "type": "plugin", + "description": "Sends information to Aurora about users connecting to/disconnecting from, mute/deafen status https://www.project-aurora.com/", + "version": "2.6.7", + "author": { + "github_id": "11393706", + "github_name": "Aytackydln", + "display_name": "Martian#8833", + "discord_name": "sexymartian", + "discord_avatar_hash": "c1194801c13617724001ce58c0380558", + "discord_snowflake": "384087218957844482", + "guild": null + }, + "likes": 1, + "downloads": 7384, + "tags": [ + "activity", + "status", + "notifications" + ], + "thumbnail_url": "/resources/thumbnails/1370.png", + "latest_source_url": "https://raw.githubusercontent.com/Aurora-RGB/Discord-GSI/200a59c7ea141f4968aa0697a99f56f24bbf0ef6/AuroraGSI.plugin.js", + "initial_release_date": "2023-08-30T14:42:25.357417Z", + "latest_release_date": "2026-01-30T12:46:54.558541Z", + "guild": null + }, + "googlesearchreplace.plugin.js": { + "id": 80, + "name": "GoogleSearchReplace", + "file_name": "GoogleSearchReplace.plugin.js", + "type": "plugin", + "description": "Replaces the default Google Text Search with a custom Search Engine", + "version": "1.4.1", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 34, + "downloads": 32582, + "tags": [ + "shortcut", + "text", + "search" + ], + "thumbnail_url": "/resources/thumbnails/254.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/GoogleSearchReplace/GoogleSearchReplace.plugin.js", + "initial_release_date": "2021-03-06T10:03:23.090538Z", + "latest_release_date": "2025-12-07T11:27:32.824887Z", + "guild": null + }, + "morerolecolors.plugin.js": { + "id": 1172, + "name": "MoreRoleColors", + "file_name": "MoreRoleColors.plugin.js", + "type": "plugin", + "description": "Adds role colors to usernames across Discord - including messages, voice channels, typing indicators, mentions, account area, text editor, audit log, role headers, user profiles, and tags", + "version": "2.0.10", + "author": { + "github_id": "48053193", + "github_name": "DaddyBoard", + "display_name": "DaddyBoard", + "discord_name": "daddyboard", + "discord_avatar_hash": "b17e321406b8b40e9a55cbe9889c31d2", + "discord_snowflake": "241334335884492810", + "guild": { + "name": "DaddyBoard's BD Plugins", + "snowflake": "1298432536761991270", + "invite_link": "https://discord.gg/ggNWGDV7e2", + "avatar_hash": "0ebf86191cc8d0ad44ec716406843990 " + } + }, + "likes": 21, + "downloads": 53515, + "tags": [ + "channels", + "voice", + "enhancement", + "servers", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/1524.png", + "latest_source_url": "https://raw.githubusercontent.com/DaddyBoard/BD-Plugins/744f902526e009fbea565bfba0ed14b7ab3d20a9/MoreRoleColors/MoreRoleColors.plugin.js", + "initial_release_date": "2024-12-04T10:47:20.908915Z", + "latest_release_date": "2026-01-29T00:18:30.204396Z", + "guild": null + }, + "stafftag.plugin.js": { + "id": 162, + "name": "StaffTag", + "file_name": "StaffTag.plugin.js", + "type": "plugin", + "description": "Adds a Crown/Tag to Server Owners (or Admins/Management)", + "version": "1.7.2", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 238, + "downloads": 324345, + "tags": [ + "members", + "roles" + ], + "thumbnail_url": "/resources/thumbnails/340.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/10731bb6ebb27d4f6f554f9698a0ad29de3f9209/Plugins/StaffTag/StaffTag.plugin.js", + "initial_release_date": "2021-04-23T10:12:01.602413Z", + "latest_release_date": "2026-01-25T13:45:03.767797Z", + "guild": null + }, + "emotereplacer.plugin.js": { + "id": 132, + "name": "EmoteReplacer", + "file_name": "EmoteReplacer.plugin.js", + "type": "plugin", + "description": "Check for known emote names and replace them with an embedded image of the emote. Also supports modifiers similar to BetterDiscord's emotes.", + "version": "2.2.6", + "author": { + "github_id": "17981128", + "github_name": "Yentis", + "display_name": "Yentis", + "discord_name": "yentis", + "discord_avatar_hash": "32c13fc21db9beedce27f57eaa76033e", + "discord_snowflake": "68834122860077056", + "guild": null + }, + "likes": 40, + "downloads": 48696, + "tags": [ + "emotes", + "text", + "enhancement" + ], + "thumbnail_url": "/resources/thumbnails/380.png", + "latest_source_url": "https://raw.githubusercontent.com/Yentis/betterdiscord-emotereplacer/c39e4239633a72ad44cd243663df7d920e392a37/EmoteReplacer.plugin.js", + "initial_release_date": "2021-04-06T10:25:13.889698Z", + "latest_release_date": "2026-01-26T23:08:54.577749Z", + "guild": null + }, + "alternativevistaautoupdate.theme.css": { + "id": 1183, + "name": "Alternative Vista", + "file_name": "AlternativeVistaAutoUpdate.theme.css", + "type": "theme", + "description": "Cutting edge 2015 theme. Inspired by skeuomorphism, windows aero and the old Steam UI. I brought over some styling from SkeuoCord and DTM-08", + "version": "Auto Update", + "author": { + "github_id": "62991761", + "github_name": "deadfrekk", + "display_name": "cavw", + "discord_name": "cavw", + "discord_avatar_hash": "a753066a32f3170b8049703572d8c3c6", + "discord_snowflake": "215569781322022915", + "guild": null + }, + "likes": 10, + "downloads": 23516, + "tags": [ + "dark", + "game", + "other" + ], + "thumbnail_url": "/resources/thumbnails/1534.png", + "latest_source_url": "https://raw.githubusercontent.com/deadfrekk/AlternativeVista/fac1b9487e0a9c5d394e5e441f9c75535afb66bf/themes/vista/AlternativeVistaAutoUpdate.theme.css", + "initial_release_date": "2024-12-17T10:50:15.954077Z", + "latest_release_date": "2024-12-17T10:50:16.656317Z", + "guild": null + }, + "showbadgesinchat.plugin.js": { + "id": 60, + "name": "ShowBadgesInChat", + "file_name": "ShowBadgesInChat.plugin.js", + "type": "plugin", + "description": "Displays Badges (Nitro, Hypesquad, etc...) in the Chat/MemberList/DMList", + "version": "2.1.5", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 224, + "downloads": 512223, + "tags": [ + "enhancement", + "members" + ], + "thumbnail_url": "/resources/thumbnails/419.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/ShowBadgesInChat/ShowBadgesInChat.plugin.js", + "initial_release_date": "2021-03-06T09:40:24.400796Z", + "latest_release_date": "2025-12-07T11:29:50.092319Z", + "guild": null + }, + "spotify-discord.theme.css": { + "id": 112, + "name": "Spotify Discord", + "file_name": "Spotify-Discord.theme.css", + "type": "theme", + "description": "Combine Discord and Spotify into one", + "version": "2.0.0", + "author": { + "github_id": "4013216", + "github_name": "CapnKitten", + "display_name": "CapnKitten", + "discord_name": "capnkitten", + "discord_avatar_hash": "dfacf8af0f7d729e6cb8fde41bb0d9a8", + "discord_snowflake": "124276233478471680", + "guild": { + "name": "CapnKitten", + "snowflake": "403760306611814410", + "invite_link": "https://discord.gg/jzJkA6Z", + "avatar_hash": "a_0284b0297857e340d8d6a036ae4557e7 " + } + }, + "likes": 444, + "downloads": 484973, + "tags": [ + "flat", + "dark" + ], + "thumbnail_url": "/resources/thumbnails/1378.png", + "latest_source_url": "https://raw.githubusercontent.com/CapnKitten/Spotify-Discord/9984ccda043f3fc9b715991eadd81db62c153026/Spotify-Discord.theme.css", + "initial_release_date": "2021-03-09T08:12:38.908314Z", + "latest_release_date": "2024-04-15T04:05:23.581706Z", + "guild": null + }, + "discord11.theme.css": { + "id": 613, + "name": "Discord 11", + "file_name": "Discord11.theme.css", + "type": "theme", + "description": "Based in Windows 11's UI\r\n| Lastest version is 4!!!\r\n| Support server: https://discord.gg/PsNtzGeHuW", + "version": "4", + "author": { + "github_id": "79029257", + "github_name": "zuzumi-f", + "display_name": "zuzumi", + "discord_name": "zuzumiarchivo", + "discord_avatar_hash": "ff307784d53358a6a1ed4f8e4321b9e9", + "discord_snowflake": "403725623161257984", + "guild": { + "name": "zuzumi's support server", + "snowflake": "931920891946868796", + "invite_link": "https://discord.gg/PsNtzGeHuW", + "avatar_hash": "19c8b8c36d7a4e0f3991bbfbeae92ca8 " + } + }, + "likes": 48, + "downloads": 88669, + "tags": [ + "transparent", + "customizable", + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/1472.png", + "latest_source_url": "https://raw.githubusercontent.com/zuzumi-f/Discord-11/8425eaa47b60a684705f3fcf2ff48004e25c2d21/Discord11.theme.css", + "initial_release_date": "2022-02-07T14:04:42.00065Z", + "latest_release_date": "2024-06-27T17:24:30.849354Z", + "guild": null + }, + "chataliases.plugin.js": { + "id": 65, + "name": "ChatAliases", + "file_name": "ChatAliases.plugin.js", + "type": "plugin", + "description": "Allows you to configure your own Aliases/Commands", + "version": "2.5.1", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 30, + "downloads": 10948, + "tags": [ + "shortcut", + "text", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/240.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/ChatAliases/ChatAliases.plugin.js", + "initial_release_date": "2021-03-06T09:45:56.16039Z", + "latest_release_date": "2025-12-07T11:26:34.833193Z", + "guild": null + }, + "horizontalserverlist.theme.css": { + "id": 124, + "name": "Horizontal Server List", + "file_name": "HorizontalServerList.theme.css", + "type": "theme", + "description": "Moves the server list from the left to the top of Discord.", + "version": "3.0.0", + "author": { + "github_id": "20338746", + "github_name": "Gibbu", + "display_name": "Gibbu", + "discord_name": "gibbu", + "discord_avatar_hash": "a_5e798741ca272ab6f141cde6e50b3bd0", + "discord_snowflake": "174868361040232448", + "guild": { + "name": "Gibbu's Support Server", + "snowflake": "546243058992283650", + "invite_link": "https://discord.gg/ZHthyCw", + "avatar_hash": "2c60e7071c4e6f3bf412f42fa9365d35 " + } + }, + "likes": 169, + "downloads": 224967, + "tags": [ + "other" + ], + "thumbnail_url": "/resources/thumbnails/646.png", + "latest_source_url": "https://raw.githubusercontent.com/DiscordStyles/HorizontalServerList/ddc563a7e2dd218c60b6a1ae460e08f6a5ec7bbf/HorizontalServerList.theme.css", + "initial_release_date": "2021-03-26T04:16:30.796776Z", + "latest_release_date": "2025-04-03T23:02:18.985353Z", + "guild": null + }, + "betterguildtooltip.plugin.js": { + "id": 798, + "name": "BetterGuildTooltip", + "file_name": "BetterGuildTooltip.plugin.js", + "type": "plugin", + "description": "Displays an online and total member count in the guild tooltip.", + "version": "1.2.3", + "author": { + "github_id": "52377180", + "github_name": "arg0NNY", + "display_name": "arg0NNY", + "discord_name": "arg0nny", + "discord_avatar_hash": "87d3e880f144cc8756954fce94fb3548", + "discord_snowflake": "224538553944637440", + "guild": { + "name": "arg0NNY's Lounge", + "snowflake": "947007182552064050", + "invite_link": "https://discord.gg/M8DBtcZjXD", + "avatar_hash": "840d87c006a42a594deb620339b8d178 " + } + }, + "likes": 19, + "downloads": 13749, + "tags": [ + "enhancement", + "servers", + "members" + ], + "thumbnail_url": "/resources/thumbnails/1140.png", + "latest_source_url": "https://raw.githubusercontent.com/arg0NNY/DiscordPlugins/e719f7adbc60a12a4427fce96724203cb6b7b691/BetterGuildTooltip/BetterGuildTooltip.plugin.js", + "initial_release_date": "2022-10-16T10:00:54.41974Z", + "latest_release_date": "2026-01-26T18:49:47.919799Z", + "guild": null + }, + "splitlargemessages.plugin.js": { + "id": 98, + "name": "SplitLargeMessages", + "file_name": "SplitLargeMessages.plugin.js", + "type": "plugin", + "description": "Allows you to enter larger Messages, which will automatically split into several smaller Messages", + "version": "1.8.7", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 550, + "downloads": 861038, + "tags": [ + "shortcut", + "enhancement", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/285.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/SplitLargeMessages/SplitLargeMessages.plugin.js", + "initial_release_date": "2021-03-06T17:56:45.9135Z", + "latest_release_date": "2025-12-07T11:28:38.557566Z", + "guild": null + }, + "reminder.plugin.js": { + "id": 1132, + "name": "Reminder", + "file_name": "Reminder.plugin.js", + "type": "plugin", + "description": "A BetterDiscord plugin that lets you create, view, and manage custom reminders and schedules with notification support.", + "version": "1.6.0", + "author": { + "github_id": "73029696", + "github_name": "DevEvil99", + "display_name": "DevEvil", + "discord_name": "devevil", + "discord_avatar_hash": "fff54ac325af38e0752324d8665510f3", + "discord_snowflake": "468132563714703390", + "guild": { + "name": "༺⟅𝗗𝗲𝘃𝗘𝘃𝗶𝗹 𝗖𝗼𝗺𝗺𝘂𝗻𝗶𝘁𝘆⟆༻", + "snowflake": "763094597454397490", + "invite_link": "https://discord.gg/jsQ9UP7kCA", + "avatar_hash": "a82d42e51a8711f98798033bf4ace472 " + } + }, + "likes": 7, + "downloads": 2723, + "tags": [ + "notifications", + "shortcut", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1820.png", + "latest_source_url": "https://raw.githubusercontent.com/DevEvil99/Reminder-BetterDiscord-Plugin/e1381a00d8a241ef834532e9b853b8d96bf849b2/Reminder.plugin.js", + "initial_release_date": "2024-08-04T17:52:46.394465Z", + "latest_release_date": "2026-02-14T14:38:33.764598Z", + "guild": null + }, + "spoticord.theme.css": { + "id": 601, + "name": "SpotiCord", + "file_name": "SpotiCord.theme.css", + "type": "theme", + "description": "Light-weight Spotify theme for Discord", + "version": "AutoUpdating", + "author": { + "github_id": "74999267", + "github_name": "Slddev", + "display_name": "Sappy", + "discord_name": "slddev", + "discord_avatar_hash": "53d12d72c909b9508c2e41c040f87fe4", + "discord_snowflake": "741262207391629343", + "guild": null + }, + "likes": 44, + "downloads": 68866, + "tags": [ + "flat", + "customizable", + "dark", + "green" + ], + "thumbnail_url": "/resources/thumbnails/1306.png", + "latest_source_url": "https://raw.githubusercontent.com/Slddev/SpotiCord/661ae15f4b835664291f4b9764886d06ea8f67a6/SpotiCord.theme.css", + "initial_release_date": "2022-01-30T12:30:18.244984Z", + "latest_release_date": "2024-07-08T13:44:42.907708Z", + "guild": null + }, + "hidemutedcategories.plugin.js": { + "id": 82, + "name": "HideMutedCategories", + "file_name": "HideMutedCategories.plugin.js", + "type": "plugin", + "description": "Hides muted Categories, if muted Channels are hidden", + "version": "1.1.4", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 25, + "downloads": 31394, + "tags": [ + "channels", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/256.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/HideMutedCategories/HideMutedCategories.plugin.js", + "initial_release_date": "2021-03-06T10:06:08.863337Z", + "latest_release_date": "2025-12-07T11:26:45.836585Z", + "guild": null + }, + "notanotheranimetheme.theme.css": { + "id": 286, + "name": "NotAnotherAnimeTheme", + "file_name": "NotAnotherAnimeTheme.theme.css", + "type": "theme", + "description": "An easily customizable and automatically updating theme", + "version": "3.2", + "author": { + "github_id": "10362744", + "github_name": "puckzxz", + "display_name": "puckzxz", + "discord_name": "puckzxz#2080", + "discord_avatar_hash": "89baef980894b68c7b3312426d277619", + "discord_snowflake": "135554522616561664", + "guild": null + }, + "likes": 869, + "downloads": 1393685, + "tags": [ + "transparent", + "customizable", + "dark", + "anime" + ], + "thumbnail_url": "/resources/thumbnails/504.jpg", + "latest_source_url": "https://raw.githubusercontent.com/puckzxz/NotAnotherAnimeTheme/50d1dca2f31425a0b3e722469a473a9a71e7f87a/NotAnotherAnimeTheme.theme.css", + "initial_release_date": "2021-05-29T01:19:04.716805Z", + "latest_release_date": "2022-08-22T05:02:05.372692Z", + "guild": { + "name": "NotAnotherAnimeTheme", + "snowflake": "412794678791110664", + "invite_link": "https://discord.gg/FdZhbjY", + "avatar_hash": "1a06e3842dec1b0a1f81bef400b648f3 " + } + }, + "fallout4terminal.theme.css": { + "id": 147, + "name": "Fallout 4 Terminal", + "file_name": "Fallout4Terminal.theme.css", + "type": "theme", + "description": "A theme inspired by Fallout 4's terminals!", + "version": "4.0.0", + "author": { + "github_id": "23524203", + "github_name": "B4T3S", + "display_name": "Bates", + "discord_name": "bates", + "discord_avatar_hash": "488b2260c70a0e17ef8d284cfab365bf", + "discord_snowflake": "137259132305539072", + "guild": null + }, + "likes": 804, + "downloads": 693137, + "tags": [ + "game", + "green", + "black" + ], + "thumbnail_url": "/resources/thumbnails/878.png", + "latest_source_url": "https://raw.githubusercontent.com/B4T3S/Fallout4TerminalTheme/7a8f7cdd6c6c2663f6c97547dbdc31ca8d2a4e85/Fallout4Terminal.theme.css", + "initial_release_date": "2021-04-19T03:33:54.487484Z", + "latest_release_date": "2025-04-14T10:22:01.11176Z", + "guild": null + }, + "allcalltimecounter.plugin.js": { + "id": 1066, + "name": "AllCallTimeCounter", + "file_name": "AllCallTimeCounter.plugin.js", + "type": "plugin", + "description": "Simply, add a call timer to all users in all server voice channels.", + "version": "1.1.3", + "author": { + "github_id": "49804267", + "github_name": "Max-Herbold", + "display_name": "Max__", + "discord_name": "._.mack", + "discord_avatar_hash": null, + "discord_snowflake": "1189527130611138663", + "guild": null + }, + "likes": 23, + "downloads": 52806, + "tags": [ + "voice", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1416.png", + "latest_source_url": "https://raw.githubusercontent.com/Max-Herbold/AllCallTimersDiscordPlugin/71cd81df986ee0c50c592d6766792d9e8c44532b/AllCallTimeCounter.plugin.js", + "initial_release_date": "2024-01-06T02:15:44.065794Z", + "latest_release_date": "2026-01-25T09:22:34.168479Z", + "guild": null + }, + "dark+.theme.css": { + "id": 412, + "name": "Dark+", + "file_name": "Dark+.theme.css", + "type": "theme", + "description": "Highly customized dark and purple theme ", + "version": "Dark+6", + "author": { + "github_id": "73029696", + "github_name": "DevEvil99", + "display_name": "DevEvil", + "discord_name": "devevil", + "discord_avatar_hash": "fff54ac325af38e0752324d8665510f3", + "discord_snowflake": "468132563714703390", + "guild": { + "name": "༺⟅𝗗𝗲𝘃𝗘𝘃𝗶𝗹 𝗖𝗼𝗺𝗺𝘂𝗻𝗶𝘁𝘆⟆༻", + "snowflake": "763094597454397490", + "invite_link": "https://discord.gg/jsQ9UP7kCA", + "avatar_hash": "a82d42e51a8711f98798033bf4ace472 " + } + }, + "likes": 448, + "downloads": 850644, + "tags": [ + "customizable", + "dark", + "purple" + ], + "thumbnail_url": "/resources/thumbnails/663.jpg", + "latest_source_url": "https://raw.githubusercontent.com/DevEvil99/DarkPlus-Discord-Theme/92105070e81adac511be555fdc4af08c605876b7/Dark+.theme.css", + "initial_release_date": "2021-07-30T09:51:10.809688Z", + "latest_release_date": "2025-05-22T19:41:29.217314Z", + "guild": { + "name": "༺⟅𝗗𝗲𝘃𝗘𝘃𝗶𝗹 𝗖𝗼𝗺𝗺𝘂𝗻𝗶𝘁𝘆⟆༻", + "snowflake": "763094597454397490", + "invite_link": "https://discord.gg/jsQ9UP7kCA", + "avatar_hash": "a82d42e51a8711f98798033bf4ace472 " + } + }, + "virtual-boy.theme.css": { + "id": 973, + "name": "Virtual Boy", + "file_name": "virtual-boy.theme.css", + "type": "theme", + "description": "A theme based on a failed Nintendo console with the same name.", + "version": "1.5", + "author": { + "github_id": "87764384", + "github_name": "Riddim-GLiTCH", + "display_name": "Riddim_GLiTCH", + "discord_name": "riddim_glitch", + "discord_avatar_hash": "4a713e88cae8803a394521052af1e56c", + "discord_snowflake": "801089753038061669", + "guild": null + }, + "likes": 59, + "downloads": 69737, + "tags": [ + "customizable", + "black" + ], + "thumbnail_url": "/resources/thumbnails/1321.gif", + "latest_source_url": "https://raw.githubusercontent.com/Riddim-GLiTCH/Virtual-Boy/e64f327132e7c3c4ae447895456bb5f398146f66/virtual-boy.theme.css", + "initial_release_date": "2023-07-04T10:03:07.667336Z", + "latest_release_date": "2024-01-29T12:20:57.204476Z", + "guild": null + }, + "dynamicchatbuttons.plugin.js": { + "id": 1425, + "name": "DynamicChatButtons", + "file_name": "DynamicChatButtons.plugin.js", + "type": "plugin", + "description": "Customize which chat buttons are visible in Discord by right clicking the chat area.", + "version": "1.0.0", + "author": { + "github_id": "90235641", + "github_name": "zrodevkaan", + "display_name": "Arven", + "discord_name": "zrodevkaan", + "discord_avatar_hash": "c088651f69e5f7f9291dcedba65ce8d8", + "discord_snowflake": "917630027477159986", + "guild": { + "name": "Froggy Support", + "snowflake": "1142561913453166794", + "invite_link": "https://discord.gg/t3zMgv7Nvb", + "avatar_hash": "4a4284a4e8e19853afcba9451e2d5fbb " + } + }, + "likes": 1, + "downloads": 302, + "tags": [ + "enhancement", + "utility" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/zrodevkaan/BDPlugins/bfd90992eaad7d322dc45f231eec43fa1bc6e4ed/Plugins/DynamicChatButtons/DynamicChatButtons.plugin.js", + "initial_release_date": "2025-10-22T10:11:47.722274Z", + "latest_release_date": "2026-01-28T12:06:31.559434Z", + "guild": null + }, + "avatarsettingsbutton.plugin.js": { + "id": 579, + "name": "AvatarSettingsButton", + "file_name": "AvatarSettingsButton.plugin.js", + "type": "plugin", + "description": "Moves the User Settings button to left clicking on the user avatar, with the status picker and context menu still available on configurable actions. ", + "version": "2.3.3", + "author": { + "github_id": "68879269", + "github_name": "Neodymium7", + "display_name": "Neodymium", + "discord_name": "neodymium_", + "discord_avatar_hash": "9c50bfb13c623c676697300b65741f71", + "discord_snowflake": "340614112331694081", + "guild": { + "name": "Neodymium's Support Server", + "snowflake": "965006167917068400", + "invite_link": "https://discord.gg/fRbsqH87Av", + "avatar_hash": "940c1cba79a578292a49a0bdd7436f9d " + } + }, + "likes": 15, + "downloads": 10723, + "tags": [ + "organization", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/970.gif", + "latest_source_url": "https://raw.githubusercontent.com/Neodymium7/BetterDiscordStuff/3773785aa9fa3a3fb5a6a9f9ed9499ec2aae0e74/AvatarSettingsButton/AvatarSettingsButton.plugin.js", + "initial_release_date": "2022-01-19T21:50:38.515847Z", + "latest_release_date": "2026-02-08T08:36:06.629262Z", + "guild": null + }, + "shortcutscreensharescreen.plugin.js": { + "id": 1260, + "name": "ShortcutScreenshareScreen", + "file_name": "ShortcutScreenshareScreen.plugin.js", + "type": "plugin", + "description": "Screenshare screen from keyboard shortcut when no game is running", + "version": "2.0.0", + "author": { + "github_id": "61830443", + "github_name": "nicola02nb", + "display_name": "nicola02nb", + "discord_name": "nicola02nb", + "discord_avatar_hash": "b0e2a870015d31c91f2d8895d80b2113", + "discord_snowflake": "257900031351193600", + "guild": { + "name": "nicola02nb Dev Support", + "snowflake": "1329222382279327825", + "invite_link": "https://discord.gg/hFuY8DfDGK", + "avatar_hash": null + } + }, + "likes": 0, + "downloads": 1300, + "tags": [ + "game", + "shortcut", + "enhancement", + "utility" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/nicola02nb/BetterDiscord-Stuff/a38b7fb8143d1b9f2c2abbe1a839d603011276ff/Plugins/ShortcutScreenshareScreen/ShortcutScreenshareScreen.plugin.js", + "initial_release_date": "2025-02-28T15:39:48.805753Z", + "latest_release_date": "2026-02-04T20:33:17.010662Z", + "guild": null + }, + "newkemonofriends.theme.css": { + "id": 642, + "name": "New Kemono Friends Theme", + "file_name": "NewKemonoFriends.theme.css", + "type": "theme", + "description": "Newer Kemono Friends theme for my return", + "version": "7.3", + "author": { + "github_id": "30361475", + "github_name": "ShadowDevilsAvenged", + "display_name": "ShadowDevilsAvenged", + "discord_name": "shadowdevilsavenged", + "discord_avatar_hash": "59346cd10940d7cae650a5e2336b316a", + "discord_snowflake": "858800346884997142", + "guild": { + "name": "Shadow's Safe Theme Collection", + "snowflake": "862776993152892939", + "invite_link": "https://discord.gg/6gseVdez6A", + "avatar_hash": "9c81716265f90ddb92c06af468a0094d " + } + }, + "likes": 113, + "downloads": 169791, + "tags": [ + "transparent", + "anime" + ], + "thumbnail_url": "/resources/thumbnails/979.png", + "latest_source_url": "https://raw.githubusercontent.com/ShadowDevilsAvenged/ShadowDevilsAvenged/84c3dd45b7bfe98ee6482b2abfa467c1b81580df/My_Theme_Collection/NewKemonoFriends.theme.css", + "initial_release_date": "2022-03-06T14:39:12.705816Z", + "latest_release_date": "2025-10-01T21:30:32.084748Z", + "guild": { + "name": "Shadow's Safe Theme Collection", + "snowflake": "862776993152892939", + "invite_link": "https://discord.gg/6gseVdez6A", + "avatar_hash": "9c81716265f90ddb92c06af468a0094d " + } + }, + "emojistatistics.plugin.js": { + "id": 77, + "name": "EmojiStatistics", + "file_name": "EmojiStatistics.plugin.js", + "type": "plugin", + "description": "Shows you an Overview of Emojis and Emoji Servers", + "version": "3.0.4", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 30, + "downloads": 24730, + "tags": [ + "emotes", + "servers", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/510.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/EmojiStatistics/EmojiStatistics.plugin.js", + "initial_release_date": "2021-03-06T10:00:31.65255Z", + "latest_release_date": "2025-12-07T11:28:12.498925Z", + "guild": null + }, + "blurnsfw.plugin.js": { + "id": 28, + "name": "BlurNSFW", + "file_name": "BlurNSFW.plugin.js", + "type": "plugin", + "description": "Blurs all pictures and videos on a per-channel basis.", + "version": "1.0.5", + "author": { + "github_id": "6865942", + "github_name": "zerebos", + "display_name": "Zerebos", + "discord_name": "zerebos", + "discord_avatar_hash": "e204ec2e923c4e4dfbf98c1eeaaa04f6", + "discord_snowflake": "249746236008169473", + "guild": null + }, + "likes": 115, + "downloads": 141925, + "tags": [ + "fun", + "edit", + "enhancement", + "servers", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/184.gif", + "latest_source_url": "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/c1488d0c8d143fb43cb70175f92062e184737a47/Plugins/BlurNSFW/BlurNSFW.plugin.js", + "initial_release_date": "2021-02-22T19:35:05.525714Z", + "latest_release_date": "2024-12-15T09:47:58.899866Z", + "guild": null + }, + "autodndongame.plugin.js": { + "id": 1290, + "name": "AutoDNDOnGame", + "file_name": "AutoDNDOnGame.plugin.js", + "type": "plugin", + "description": "Automatically set your status to Do Not Disturb when you launch a game", + "version": "1.2.0", + "author": { + "github_id": "81591350", + "github_name": "xenoncolt", + "display_name": "xenoncolt", + "discord_name": "xenoncolt", + "discord_avatar_hash": "1f98859bde57806930ec670dc7a17f7b", + "discord_snowflake": "709210314230726776", + "guild": { + "name": "Useless Place", + "snowflake": "1224023444211892234", + "invite_link": "https://discord.gg/xr6NpHfCFz", + "avatar_hash": "39f85ef500e97bc1efe07405b0f2a870 " + } + }, + "likes": 1, + "downloads": 1075, + "tags": [ + "game", + "activity", + "status" + ], + "thumbnail_url": "/resources/thumbnails/1622.gif", + "latest_source_url": "https://raw.githubusercontent.com/xenoncolt/AutoDNDOnGame/76d276c8fe6e419a79878b907a4c4f2da7933a7f/AutoDNDOnGame.plugin.js", + "initial_release_date": "2025-04-24T04:29:52.184028Z", + "latest_release_date": "2025-12-05T09:46:06.965939Z", + "guild": null + }, + "imagefolder.plugin.js": { + "id": 1070, + "name": "ImageFolder", + "file_name": "ImageFolder.plugin.js", + "type": "plugin", + "description": "A BetterDiscord plugin that allows you to save and send images from a folder for easy access", + "version": "1.5.5", + "author": { + "github_id": "76746384", + "github_name": "TheLazySquid", + "display_name": "TheLazySquid", + "discord_name": "thelazysquid", + "discord_avatar_hash": "e04c7e5372cbf77ecf7bd2784775aa3c", + "discord_snowflake": "619261917352951815", + "guild": { + "name": "TheLazySquid's Stuff", + "snowflake": "1432531781780897814", + "invite_link": "https://discord.gg/fKdAaFYbD5", + "avatar_hash": "4264dfaa6aefa8ac415b4680731dc25b " + } + }, + "likes": 11, + "downloads": 7346, + "tags": [ + "shortcut", + "text", + "enhancement" + ], + "thumbnail_url": "/resources/thumbnails/1412.png", + "latest_source_url": "https://raw.githubusercontent.com/TheLazySquid/BetterDiscordPlugins/99f81a9a7b31ccd914c2e1a7118eda5bbf83e32b/plugins/ImageFolder/ImageFolder.plugin.js", + "initial_release_date": "2024-01-26T01:15:18.035711Z", + "latest_release_date": "2026-02-13T17:30:18.907568Z", + "guild": null + }, + "material blue discord.theme.css": { + "id": 746, + "name": "Material Blue Discord", + "file_name": "Material Blue Discord.theme.css", + "type": "theme", + "description": "Material Blue Discord Theme.", + "version": "2.1.0", + "author": { + "github_id": "53162615", + "github_name": "Elisniper", + "display_name": "Elisniper ", + "discord_name": "elisniper", + "discord_avatar_hash": "b3f43637c4975ba7a0a746858ee3a262", + "discord_snowflake": "253480609224065025", + "guild": null + }, + "likes": 4, + "downloads": 11241, + "tags": [ + "flat", + "dark", + "black", + "blue", + "abstract" + ], + "thumbnail_url": "/resources/thumbnails/1161.png", + "latest_source_url": "https://raw.githubusercontent.com/Elisniper/MBD/36881aacae8b35cc08ec634b9d6e3b766a56a42f/release/Material Blue Discord.theme.css", + "initial_release_date": "2022-07-09T08:58:25.830411Z", + "latest_release_date": "2023-07-01T07:09:03.060102Z", + "guild": null + }, + "hidechannels.plugin.js": { + "id": 353, + "name": "Hide Channels", + "file_name": "HideChannels.plugin.js", + "type": "plugin", + "description": "Hide channel list from view. This also works for DM list.", + "version": "2.2.14", + "author": { + "github_id": "8385001", + "github_name": "Farcrada", + "display_name": "Farcrada", + "discord_name": "Farcrada#1879", + "discord_avatar_hash": "9e4cdff0341d13746d1c9772c951ce2e", + "discord_snowflake": "131212461499088896", + "guild": null + }, + "likes": 188, + "downloads": 286182, + "tags": [ + "channels", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/589.gif", + "latest_source_url": "https://raw.githubusercontent.com/Farcrada/DiscordPlugins/8b3b2f766a645de5db9152df0c6c2830aacf092f/Hide-Channels/HideChannels.plugin.js", + "initial_release_date": "2021-07-03T17:36:55.904795Z", + "latest_release_date": "2025-09-27T17:45:29.287255Z", + "guild": null + }, + "serverconfig.plugin.js": { + "id": 489, + "name": "ServerConfig", + "file_name": "ServerConfig.plugin.js", + "type": "plugin", + "description": "Apply custom server settings when joining a new server", + "version": "1.1.2", + "author": { + "github_id": "7073622", + "github_name": "Ekibunnel", + "display_name": "Ekibunnel", + "discord_name": "ekibunnel", + "discord_avatar_hash": "4f6c5ff3436f001786d0a433ec286322", + "discord_snowflake": "130328880098705409", + "guild": { + "name": "Ekibunnel", + "snowflake": "886648259538485258", + "invite_link": "https://discord.gg/PEsMUjatGu", + "avatar_hash": "1787844e999902ee9173a9825d4aada8 " + } + }, + "likes": 19, + "downloads": 10895, + "tags": [ + "notifications", + "enhancement", + "servers" + ], + "thumbnail_url": "/resources/thumbnails/768.png", + "latest_source_url": "https://raw.githubusercontent.com/Ekibunnel/BetterDiscordAddons/f632842fca487a7e6fd200a868b0519b3a1a8356/Plugins/ServerConfig/ServerConfig.plugin.js", + "initial_release_date": "2021-09-17T22:42:32.773833Z", + "latest_release_date": "2026-01-24T14:05:58.656047Z", + "guild": { + "name": "Ekibunnel", + "snowflake": "886648259538485258", + "invite_link": "https://discord.gg/PEsMUjatGu", + "avatar_hash": "1787844e999902ee9173a9825d4aada8 " + } + }, + "softx.theme.css": { + "id": 515, + "name": "SoftX", + "file_name": "SoftX.theme.css", + "type": "theme", + "description": "A soft and comfy feel for Discord.", + "version": "2.0.0", + "author": { + "github_id": "20338746", + "github_name": "Gibbu", + "display_name": "Gibbu", + "discord_name": "gibbu", + "discord_avatar_hash": "a_5e798741ca272ab6f141cde6e50b3bd0", + "discord_snowflake": "174868361040232448", + "guild": { + "name": "Gibbu's Support Server", + "snowflake": "546243058992283650", + "invite_link": "https://discord.gg/ZHthyCw", + "avatar_hash": "2c60e7071c4e6f3bf412f42fa9365d35 " + } + }, + "likes": 355, + "downloads": 435405, + "tags": [ + "dark", + "abstract" + ], + "thumbnail_url": "/resources/thumbnails/795.png", + "latest_source_url": "https://raw.githubusercontent.com/DiscordStyles/SoftX/e10600ca55392f77741c099aa729b12ccba570b5/SoftX.theme.css", + "initial_release_date": "2021-10-18T10:24:47.674923Z", + "latest_release_date": "2025-05-13T00:46:27.234752Z", + "guild": { + "name": "Gibbu's Support Server", + "snowflake": "546243058992283650", + "invite_link": "https://discord.gg/ZHthyCw", + "avatar_hash": "2c60e7071c4e6f3bf412f42fa9365d35 " + } + }, + "openinapp.plugin.js": { + "id": 1068, + "name": "OpenInApp", + "file_name": "OpenInApp.plugin.js", + "type": "plugin", + "description": "Adds support to open URLs in their related app and not the browser.", + "version": "1.0.1", + "author": { + "github_id": "72281779", + "github_name": "fluzzeon", + "display_name": "Fluzz", + "discord_name": "f1uzz", + "discord_avatar_hash": "8ebe3926d41c5fde217bc99cb391c8b4", + "discord_snowflake": "224207524411211777", + "guild": null + }, + "likes": 8, + "downloads": 11971, + "tags": [ + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1409.jpg", + "latest_source_url": "https://raw.githubusercontent.com/fluzzeon/betterdiscord-OpenInApp/2b1f7c2c05af8abb16529dc9c248a224a15492c3/OpenInApp.plugin.js", + "initial_release_date": "2024-01-07T18:39:10.390473Z", + "latest_release_date": "2024-01-07T18:39:10.982511Z", + "guild": null + }, + "summarizer.plugin.js": { + "id": 1298, + "name": "Summarizer", + "file_name": "Summarizer.plugin.js", + "type": "plugin", + "description": "**Summarize articles and YouTube videos.** Right-click on any link and select `Summarize` to extract key insights.", + "version": "0.6.2", + "author": { + "github_id": "65749353", + "github_name": "JanitorialMess", + "display_name": "SuperTouch", + "discord_name": "supertouch", + "discord_avatar_hash": "0d3962322b0581a66e8fe8e0a0e5ae63", + "discord_snowflake": "221720773826576384", + "guild": null + }, + "likes": 2, + "downloads": 337, + "tags": [ + "text", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1630.png", + "latest_source_url": "https://raw.githubusercontent.com/JanitorialMess/Summarizer/55b2c08a0dc6be834eaa02437f1dc1ae92a3e57f/dist/Summarizer.plugin.js", + "initial_release_date": "2025-04-24T18:24:03.056444Z", + "latest_release_date": "2026-01-25T08:57:19.330571Z", + "guild": null + }, + "embedmoreimages.plugin.js": { + "id": 1000, + "name": "Embed More Images", + "file_name": "EmbedMoreImages.plugin.js", + "type": "plugin", + "description": "Embed formats you usually can't.\r\n\r\nExpanded description:\r\nEmbed file formats that Discord can but won't support for whatever reason. \r\n\r\nThis plugin currently adds embed support for sequenced WebP, APNG, AVIF, BitMaP, ICO, CUR, and additional JPEG 1 file extensions (like *.jfif).", + "version": "1.14", + "author": { + "github_id": "94736474", + "github_name": "Knewest", + "display_name": "Knew", + "discord_name": "knewest", + "discord_avatar_hash": "17d3bae6e4dc88b8dc82d13924db2ff7", + "discord_snowflake": "332116671294734336", + "guild": null + }, + "likes": 16, + "downloads": 18408, + "tags": [ + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1345.png", + "latest_source_url": "https://raw.githubusercontent.com/Knewest/embed-more-images/08bc91484bbcd8763ce2707b24b4fc7b84534c18/EmbedMoreImages.plugin.js", + "initial_release_date": "2023-07-28T20:37:46.747263Z", + "latest_release_date": "2026-01-29T12:45:16.219987Z", + "guild": null + }, + "imageutilities.plugin.js": { + "id": 83, + "name": "ImageUtilities", + "file_name": "ImageUtilities.plugin.js", + "type": "plugin", + "description": "Adds several Utilities for Images/Videos (Gallery, Download, Reverse Search, Zoom, Copy, etc.)", + "version": "5.6.6", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 499, + "downloads": 798110, + "tags": [ + "utility" + ], + "thumbnail_url": "/resources/thumbnails/271.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/4ac659dc5993e86ba960777322cd0c0345a1273e/Plugins/ImageUtilities/ImageUtilities.plugin.js", + "initial_release_date": "2021-03-06T10:07:22.026062Z", + "latest_release_date": "2026-02-05T14:02:45.942743Z", + "guild": null + }, + "bettersearchpage.plugin.js": { + "id": 63, + "name": "BetterSearchPage", + "file_name": "BetterSearchPage.plugin.js", + "type": "plugin", + "description": "Makes the Controls in the Search Results Page sticky", + "version": "1.2.9", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 152, + "downloads": 114891, + "tags": [ + "enhancement", + "search" + ], + "thumbnail_url": "/resources/thumbnails/1186.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/dade850216f0539ea0728655fba5168a65a71196/Plugins/BetterSearchPage/BetterSearchPage.plugin.js", + "initial_release_date": "2021-03-06T09:43:48.910585Z", + "latest_release_date": "2026-01-19T17:35:45.48948Z", + "guild": null + }, + "linkconverter.plugin.js": { + "id": 1423, + "name": "LinkConverter", + "file_name": "LinkConverter.plugin.js", + "type": "plugin", + "description": "Converts all links into a configurable embed link", + "version": "2.0.1", + "author": { + "github_id": "90235641", + "github_name": "zrodevkaan", + "display_name": "Arven", + "discord_name": "zrodevkaan", + "discord_avatar_hash": "c088651f69e5f7f9291dcedba65ce8d8", + "discord_snowflake": "917630027477159986", + "guild": { + "name": "Froggy Support", + "snowflake": "1142561913453166794", + "invite_link": "https://discord.gg/t3zMgv7Nvb", + "avatar_hash": "4a4284a4e8e19853afcba9451e2d5fbb " + } + }, + "likes": 1, + "downloads": 506, + "tags": [ + "edit", + "text", + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/zrodevkaan/BDPlugins/bfa2851e298c05e88923b572d7045e87b4bc87ee/Plugins/LinkConverter/LinkConverter.plugin.js", + "initial_release_date": "2025-10-15T15:34:09.968034Z", + "latest_release_date": "2026-02-20T23:40:01.504489Z", + "guild": null + }, + "darkmatter.theme.css": { + "id": 174, + "name": "Dark Matter", + "file_name": "DarkMatter.theme.css", + "type": "theme", + "description": "A cold, dark & frosty theme.", + "version": "3.0.0", + "author": { + "github_id": "42101043", + "github_name": "Tropix126", + "display_name": "Tropical", + "discord_name": "tropicaal", + "discord_avatar_hash": "b282ebc62b5cadc7c590303892493771", + "discord_snowflake": "254362351170617345", + "guild": null + }, + "likes": 1113, + "downloads": 1923843, + "tags": [ + "transparent", + "customizable", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/387.png", + "latest_source_url": "https://raw.githubusercontent.com/DiscordStyles/DarkMatter/3ac5b187799ad90f59d4201b0b91725a92674e43/DarkMatter.theme.css", + "initial_release_date": "2021-04-25T23:05:55.029522Z", + "latest_release_date": "2021-05-07T20:49:52.77722Z", + "guild": { + "name": "Black Box", + "snowflake": "449175561529589761", + "invite_link": "https://discord.gg/TeRQEPb", + "avatar_hash": "a_927da1c2b111767f3df61e31d1949a96 " + } + }, + "basicbackground.theme.css": { + "id": 45, + "name": "BasicBackground", + "file_name": "BasicBackground.theme.css", + "type": "theme", + "description": "Allows you to use a Background Image without greatly altering the basic Layout of Discord", + "version": "1.0.5", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 580, + "downloads": 675150, + "tags": [ + "transparent", + "customizable" + ], + "thumbnail_url": "/resources/thumbnails/498.gif", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/bca7c871d2374f0b21e836d60dbf096d65df53aa/Themes/BasicBackground/BasicBackground.theme.css", + "initial_release_date": "2021-02-25T10:07:16.547286Z", + "latest_release_date": "2025-04-15T16:20:47.351611Z", + "guild": null + }, + "sendstickersaslinks.plugin.js": { + "id": 882, + "name": "SendStickersAsLinks", + "file_name": "SendStickersAsLinks.plugin.js", + "type": "plugin", + "description": "Enables you to send custom Stickers as links, (custom stickers as in the ones that are added by servers, not official discord stickers).", + "version": "2.3.4", + "author": { + "github_id": "114232893", + "github_name": "Skamt", + "display_name": "Skamt", + "discord_name": "skamt.", + "discord_avatar_hash": "4530f48f71ac21738a7ce40cb10ff48c", + "discord_snowflake": "1030617301818552320", + "guild": null + }, + "likes": 20, + "downloads": 46999, + "tags": [ + "fun", + "emotes", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1307.gif", + "latest_source_url": "https://raw.githubusercontent.com/Skamt/BDAddons/c0ac383b283a92d9d7b501c9cecf2834812ba2d6/SendStickersAsLinks/SendStickersAsLinks.plugin.js", + "initial_release_date": "2023-02-15T14:55:37.337947Z", + "latest_release_date": "2025-12-04T12:53:32.761278Z", + "guild": null + }, + "invisibletyping.plugin.js": { + "id": 295, + "name": "InvisibleTyping", + "file_name": "InvisibleTyping.plugin.js", + "type": "plugin", + "description": "Enhanced version of silent typing.", + "version": "1.4.5", + "author": { + "github_id": "46447572", + "github_name": "Strencher", + "display_name": "Strencher", + "discord_name": "strencher", + "discord_avatar_hash": "7237ff8a1459d2cff1578e39884892b1", + "discord_snowflake": "415849376598982656", + "guild": { + "name": "Stern Community", + "snowflake": "458997239738793984", + "invite_link": "https://discord.gg/gvA2ree", + "avatar_hash": "a_1eb44ed607d75dca952eed9095e71f35 " + } + }, + "likes": 563, + "downloads": 1097083, + "tags": [ + "channels", + "text", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/516.png", + "latest_source_url": "https://raw.githubusercontent.com/Strencher/BetterDiscordStuff/c01cbe4d1d6c6cb7c260cd183a4b7e93530f264c/InvisibleTyping/InvisibleTyping.plugin.js", + "initial_release_date": "2021-05-31T16:12:22.445554Z", + "latest_release_date": "2026-01-24T01:09:53.278627Z", + "guild": { + "name": "Stern Community", + "snowflake": "458997239738793984", + "invite_link": "https://discord.gg/gvA2ree", + "avatar_hash": "a_1eb44ed607d75dca952eed9095e71f35 " + } + }, + "peekmessagelinks.plugin.js": { + "id": 1241, + "name": "PeekMessageLinks", + "file_name": "PeekMessageLinks.plugin.js", + "type": "plugin", + "description": "Peek into message links without leaving your current channel", + "version": "1.2.6", + "author": { + "github_id": "48053193", + "github_name": "DaddyBoard", + "display_name": "DaddyBoard", + "discord_name": "daddyboard", + "discord_avatar_hash": "b17e321406b8b40e9a55cbe9889c31d2", + "discord_snowflake": "241334335884492810", + "guild": { + "name": "DaddyBoard's BD Plugins", + "snowflake": "1298432536761991270", + "invite_link": "https://discord.gg/ggNWGDV7e2", + "avatar_hash": "0ebf86191cc8d0ad44ec716406843990 " + } + }, + "likes": 7, + "downloads": 4813, + "tags": [ + "shortcut", + "text", + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1580.gif", + "latest_source_url": "https://raw.githubusercontent.com/DaddyBoard/BD-Plugins/c2f138da3f558e371b7f431e43411dc96926fce3/PeekMessageLinks/PeekMessageLinks.plugin.js", + "initial_release_date": "2025-02-16T17:06:07.839227Z", + "latest_release_date": "2026-02-14T00:49:24.497336Z", + "guild": null + }, + "clickablementions.plugin.js": { + "id": 352, + "name": "ClickableMentions", + "file_name": "ClickableMentions.plugin.js", + "type": "plugin", + "description": "Allows you to open a User Popout by clicking a Mention in your Message Input", + "version": "1.0.8", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 33, + "downloads": 14333, + "tags": [ + "members", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/588.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/e5bd286711cb3d3855238fd71235b36ddcad53d8/Plugins/ClickableMentions/ClickableMentions.plugin.js", + "initial_release_date": "2021-07-03T14:33:25.255328Z", + "latest_release_date": "2026-01-24T09:23:51.892578Z", + "guild": null + }, + "voicehub.plugin.js": { + "id": 1244, + "name": "VoiceHub", + "file_name": "VoiceHub.plugin.js", + "type": "plugin", + "description": "Allows you to see all VCs at once to see what friends/groups are available!", + "version": "2.0.0", + "author": { + "github_id": "90235641", + "github_name": "zrodevkaan", + "display_name": "Arven", + "discord_name": "zrodevkaan", + "discord_avatar_hash": "c088651f69e5f7f9291dcedba65ce8d8", + "discord_snowflake": "917630027477159986", + "guild": { + "name": "Froggy Support", + "snowflake": "1142561913453166794", + "invite_link": "https://discord.gg/t3zMgv7Nvb", + "avatar_hash": "4a4284a4e8e19853afcba9451e2d5fbb " + } + }, + "likes": 0, + "downloads": 6811, + "tags": [ + "channels", + "voice", + "enhancement", + "friends", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1581.png", + "latest_source_url": "https://raw.githubusercontent.com/zrodevkaan/BDPlugins/00850cfa1f44413d61c768ac9dc4a058c97df5b1/Plugins/VoiceHub/VoiceHub.plugin.js", + "initial_release_date": "2025-02-18T02:33:10.554445Z", + "latest_release_date": "2026-01-28T03:32:39.004973Z", + "guild": null + }, + "startuppage.plugin.js": { + "id": 1303, + "name": "StartUpPage", + "file_name": "StartUpPage.plugin.js", + "type": "plugin", + "description": "StartUpPage lets you choose which page Discord opens to on startup.", + "version": "1.3", + "author": { + "github_id": "73029696", + "github_name": "DevEvil99", + "display_name": "DevEvil", + "discord_name": "devevil", + "discord_avatar_hash": "fff54ac325af38e0752324d8665510f3", + "discord_snowflake": "468132563714703390", + "guild": { + "name": "༺⟅𝗗𝗲𝘃𝗘𝘃𝗶𝗹 𝗖𝗼𝗺𝗺𝘂𝗻𝗶𝘁𝘆⟆༻", + "snowflake": "763094597454397490", + "invite_link": "https://discord.gg/jsQ9UP7kCA", + "avatar_hash": "a82d42e51a8711f98798033bf4ace472 " + } + }, + "likes": 1, + "downloads": 1144, + "tags": [ + "channels", + "servers", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1666.png", + "latest_source_url": "https://raw.githubusercontent.com/DevEvil99/StartUpPage-BetterDiscord-Plugin/36b79bce055fd3d60f81af1346a18b63a4809c10/StartUpPage.plugin.js", + "initial_release_date": "2025-04-25T01:57:44.250291Z", + "latest_release_date": "2026-02-07T14:45:56.166687Z", + "guild": null + }, + "cyberpunk2077.theme.css": { + "id": 752, + "name": "Cyberpunk 2077", + "file_name": "cyberpunk2077.theme.css", + "type": "theme", + "description": "Theme for Better Discord inspired by Cyberpunk 2077 user interface", + "version": "1.0.15", + "author": { + "github_id": "55627799", + "github_name": "PatrykBielanin", + "display_name": "bielak", + "discord_name": "bielak", + "discord_avatar_hash": "e5b7540e449110f623904f4a2ef92f8f", + "discord_snowflake": "477402916966236172", + "guild": null + }, + "likes": 255, + "downloads": 241517, + "tags": [ + "dark", + "game", + "red", + "black", + "high-contrast" + ], + "thumbnail_url": "/resources/thumbnails/1086.png", + "latest_source_url": "https://raw.githubusercontent.com/PatrykBielanin/discord-cyberpunk2077-theme/bb6a7411cc6a42f288db6f037bd9c0fb966cc9e2/cyberpunk2077.theme.css", + "initial_release_date": "2022-07-20T07:23:25.510076Z", + "latest_release_date": "2025-03-27T19:11:32.202538Z", + "guild": null + }, + "serverhider.plugin.js": { + "id": 102, + "name": "ServerHider", + "file_name": "ServerHider.plugin.js", + "type": "plugin", + "description": "Allows you to hide certain Servers in your Server List", + "version": "6.3.4", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 43, + "downloads": 59411, + "tags": [ + "servers", + "organization" + ], + "thumbnail_url": "/resources/thumbnails/289.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/ServerHider/ServerHider.plugin.js", + "initial_release_date": "2021-03-06T17:59:31.435394Z", + "latest_release_date": "2025-12-07T11:30:30.250116Z", + "guild": null + }, + "aplatformindicators.plugin.js": { + "id": 158, + "name": "PlatformIndicators", + "file_name": "APlatformIndicators.plugin.js", + "type": "plugin", + "description": "Adds indicators for every platform that the user is using. Source code availble on the repo in the 'src' folder.", + "version": "1.6.0", + "author": { + "github_id": "46447572", + "github_name": "Strencher", + "display_name": "Strencher", + "discord_name": "strencher", + "discord_avatar_hash": "7237ff8a1459d2cff1578e39884892b1", + "discord_snowflake": "415849376598982656", + "guild": { + "name": "Stern Community", + "snowflake": "458997239738793984", + "invite_link": "https://discord.gg/gvA2ree", + "avatar_hash": "a_1eb44ed607d75dca952eed9095e71f35 " + } + }, + "likes": 209, + "downloads": 293069, + "tags": [ + "status", + "shortcut", + "servers", + "chat" + ], + "thumbnail_url": null, + "latest_source_url": "https://raw.githubusercontent.com/Strencher/BetterDiscordStuff/576f6e123505b96ea7fd406df43e25a4e3a8fef9/PlatformIndicators/APlatformIndicators.plugin.js", + "initial_release_date": "2021-04-21T19:06:24.143165Z", + "latest_release_date": "2026-02-16T11:40:56.326078Z", + "guild": { + "name": "Stern Community", + "snowflake": "458997239738793984", + "invite_link": "https://discord.gg/gvA2ree", + "avatar_hash": "a_1eb44ed607d75dca952eed9095e71f35 " + } + }, + "dtm-18.theme.css": { + "id": 915, + "name": "DTM-18", + "file_name": "dtm-18.theme.css", + "type": "theme", + "description": "Aims to recreate the 2018 Discord style.", + "version": "2", + "author": { + "github_id": "111528101", + "github_name": "davart154", + "display_name": "dav#8444", + "discord_name": "dav#8444", + "discord_avatar_hash": "f7ad4673e8887cbde895738948b70a1d", + "discord_snowflake": "490604571824226306", + "guild": null + }, + "likes": 17, + "downloads": 27266, + "tags": [ + "flat", + "transparent", + "dark", + "light" + ], + "thumbnail_url": "/resources/thumbnails/1265.png", + "latest_source_url": "https://raw.githubusercontent.com/davart154/DTM-18/8dd25c8cd22807e2651c3d1bc57d0e0b1bd2cb60/dtm-18.theme.css", + "initial_release_date": "2023-03-17T17:21:43.642676Z", + "latest_release_date": "2024-09-17T02:02:19.813922Z", + "guild": null + }, + "writeuppercase.plugin.js": { + "id": 111, + "name": "WriteUppercase", + "file_name": "WriteUpperCase.plugin.js", + "type": "plugin", + "description": "Changes the first Letter of each Sentence in Message Inputs to Uppercase", + "version": "1.4.5", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 112, + "downloads": 167296, + "tags": [ + "text", + "enhancement", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/298.gif", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/WriteUpperCase/WriteUpperCase.plugin.js", + "initial_release_date": "2021-03-06T18:06:32.529041Z", + "latest_release_date": "2025-12-07T11:28:17.058987Z", + "guild": null + }, + "dyslexia.theme.css": { + "id": 16, + "name": "Dyslexia", + "file_name": "dyslexia.theme.css", + "type": "theme", + "description": "A simple discord theme which makes the app more accommodating to people with dyslexia.", + "version": "1.0", + "author": { + "github_id": "42101043", + "github_name": "Tropix126", + "display_name": "Tropical", + "discord_name": "tropicaal", + "discord_avatar_hash": "b282ebc62b5cadc7c590303892493771", + "discord_snowflake": "254362351170617345", + "guild": null + }, + "likes": 82, + "downloads": 55529, + "tags": [ + "other" + ], + "thumbnail_url": "/resources/thumbnails/165.png", + "latest_source_url": "https://raw.githubusercontent.com/DiscordStyles/discord-dyslexia/8a453af6997b1a807857511e9f554ee60d15cf49/dyslexia.theme.css", + "initial_release_date": "2021-02-22T19:11:34.256227Z", + "latest_release_date": "2021-02-22T19:11:34.256227Z", + "guild": { + "name": "Black Box", + "snowflake": "449175561529589761", + "invite_link": "https://discord.gg/TeRQEPb", + "avatar_hash": "a_927da1c2b111767f3df61e31d1949a96 " + } + }, + "betternsfwtag.plugin.js": { + "id": 62, + "name": "BetterNsfwTag", + "file_name": "BetterNsfwTag.plugin.js", + "type": "plugin", + "description": "Adds a more noticeable Tag to NSFW Channels", + "version": "1.3.3", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 178, + "downloads": 251615, + "tags": [ + "channels" + ], + "thumbnail_url": "/resources/thumbnails/237.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/c561c50fadf8ce7e7862e6a090e48196f6069b9b/Plugins/BetterNsfwTag/BetterNsfwTag.plugin.js", + "initial_release_date": "2021-03-06T09:42:44.933025Z", + "latest_release_date": "2025-12-07T11:29:28.888694Z", + "guild": null + }, + "tabbys.plugin.js": { + "id": 1418, + "name": "Tabbys", + "file_name": "Tabbys.plugin.js", + "type": "plugin", + "description": "Adds Browser like tabs/bookmarks for channels", + "version": "1.0.8", + "author": { + "github_id": "114232893", + "github_name": "Skamt", + "display_name": "Skamt", + "discord_name": "skamt.", + "discord_avatar_hash": "4530f48f71ac21738a7ce40cb10ff48c", + "discord_snowflake": "1030617301818552320", + "guild": null + }, + "likes": 2, + "downloads": 811, + "tags": [ + "enhancement", + "organization", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1770.png", + "latest_source_url": "https://raw.githubusercontent.com/Skamt/BDAddons/02b3643b7acd9637d61a5f1326d834ce1916fa0a/Tabbys/Tabbys.plugin.js", + "initial_release_date": "2025-10-08T14:28:07.205512Z", + "latest_release_date": "2026-02-05T21:16:25.530117Z", + "guild": null + }, + "lazyloadchannels.plugin.js": { + "id": 881, + "name": "LazyLoadChannels", + "file_name": "LazyLoadChannels.plugin.js", + "type": "plugin", + "description": "Lets you choose whether to load a channel", + "version": "1.2.16", + "author": { + "github_id": "114232893", + "github_name": "Skamt", + "display_name": "Skamt", + "discord_name": "skamt.", + "discord_avatar_hash": "4530f48f71ac21738a7ce40cb10ff48c", + "discord_snowflake": "1030617301818552320", + "guild": null + }, + "likes": 7, + "downloads": 3969, + "tags": [ + "channels", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1229.png", + "latest_source_url": "https://raw.githubusercontent.com/Skamt/BDAddons/bd95ff71cc8a2ee1cc3fc4779aa31f3354d11052/LazyLoadChannels/LazyLoadChannels.plugin.js", + "initial_release_date": "2023-02-15T14:54:30.372192Z", + "latest_release_date": "2026-01-24T14:53:47.350889Z", + "guild": null + }, + "simplify.theme.css": { + "id": 385, + "name": "Simplify", + "file_name": "simplify.theme.css", + "type": "theme", + "description": "a simple clean looking theme with blue colors.", + "version": "1.0.0", + "author": { + "github_id": "69681505", + "github_name": "Dyzean", + "display_name": "Ashtrath", + "discord_name": "ashtrath", + "discord_avatar_hash": "f618af65c5e562f1b830958384940751", + "discord_snowflake": "354831939099688962", + "guild": null + }, + "likes": 46, + "downloads": 51496, + "tags": [ + "customizable", + "dark", + "blue" + ], + "thumbnail_url": "/resources/thumbnails/648.png", + "latest_source_url": "https://raw.githubusercontent.com/Dyzean/Simplify/a1c9857c00084d4ed5b33166ef92721b5f7c3cfb/simplify.theme.css", + "initial_release_date": "2021-07-16T09:20:55.058599Z", + "latest_release_date": "2021-07-16T09:20:55.058599Z", + "guild": null + }, + "eris.theme.css": { + "id": 371, + "name": "Eris", + "file_name": "Eris.theme.css", + "type": "theme", + "description": "A darker, cooler, classier look for Discord.", + "version": "1.4", + "author": { + "github_id": "86799620", + "github_name": "RockESV", + "display_name": "Rock#5429", + "discord_name": "Rock#5429", + "discord_avatar_hash": "392887c184ac5236653e7bd94b58a650", + "discord_snowflake": "196643951787114496", + "guild": null + }, + "likes": 21, + "downloads": 24923, + "tags": [ + "flat", + "dark" + ], + "thumbnail_url": "/resources/thumbnails/610.png", + "latest_source_url": "https://raw.githubusercontent.com/RockESV/Eris/95b6e0d18e754322c61693f84c5bad21170cd119/Eris.theme.css", + "initial_release_date": "2021-07-10T18:49:37.878588Z", + "latest_release_date": "2024-02-09T00:06:54.49412Z", + "guild": null + }, + "viewprofilepicture.plugin.js": { + "id": 805, + "name": "ViewProfilePicture", + "file_name": "ViewProfilePicture.plugin.js", + "type": "plugin", + "description": "Adds a button to the user popout and profile that allows you to view the Avatar and banner.", + "version": "1.3.7", + "author": { + "github_id": "114232893", + "github_name": "Skamt", + "display_name": "Skamt", + "discord_name": "skamt.", + "discord_avatar_hash": "4530f48f71ac21738a7ce40cb10ff48c", + "discord_snowflake": "1030617301818552320", + "guild": null + }, + "likes": 74, + "downloads": 96450, + "tags": [ + "shortcut", + "enhancement", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1346.png", + "latest_source_url": "https://raw.githubusercontent.com/Skamt/BDAddons/4ae8d77fe93def05a64d9a1a8b363d5a436c631c/ViewProfilePicture/ViewProfilePicture.plugin.js", + "initial_release_date": "2022-10-18T22:41:26.264322Z", + "latest_release_date": "2026-02-05T21:30:28.879623Z", + "guild": null + }, + "videocompressor.plugin.js": { + "id": 1410, + "name": "VideoCompressor", + "file_name": "VideoCompressor.plugin.js", + "type": "plugin", + "description": "Compress videos that are too large to upload normally", + "version": "0.2.6", + "author": { + "github_id": "76746384", + "github_name": "TheLazySquid", + "display_name": "TheLazySquid", + "discord_name": "thelazysquid", + "discord_avatar_hash": "e04c7e5372cbf77ecf7bd2784775aa3c", + "discord_snowflake": "619261917352951815", + "guild": { + "name": "TheLazySquid's Stuff", + "snowflake": "1432531781780897814", + "invite_link": "https://discord.gg/fKdAaFYbD5", + "avatar_hash": "4264dfaa6aefa8ac415b4680731dc25b " + } + }, + "likes": 4, + "downloads": 1750, + "tags": [ + "edit", + "enhancement", + "chat", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1739.png", + "latest_source_url": "https://raw.githubusercontent.com/TheLazySquid/BetterDiscordPlugins/99f81a9a7b31ccd914c2e1a7118eda5bbf83e32b/plugins/VideoCompressor/VideoCompressor.plugin.js", + "initial_release_date": "2025-09-14T21:29:03.091637Z", + "latest_release_date": "2026-02-13T17:30:14.511346Z", + "guild": null + }, + "glass_local.theme.css": { + "id": 308, + "name": "Vibrant Glass (31/10/2023)", + "file_name": "glass_local.theme.css", + "type": "theme", + "description": "Customizable simple glass theme, idk what else I can say.\r\nIt is still WIP, so if you have any issues or see anything weird, make a PR/issue in github.", + "version": "1.1", + "author": { + "github_id": "25254770", + "github_name": "Kayno0o", + "display_name": "Kaynooo", + "discord_name": "kaynooo", + "discord_avatar_hash": "a_6a0423a492e5eebdb47549bfcc5a4a00", + "discord_snowflake": "258334777046859776", + "guild": null + }, + "likes": 86, + "downloads": 184405, + "tags": [ + "flat", + "transparent", + "customizable", + "dark", + "abstract" + ], + "thumbnail_url": "/resources/thumbnails/537.png", + "latest_source_url": "https://raw.githubusercontent.com/Kayno0o/vibrant-glass-theme/c7937bc6830d46adc1f5f79899599df439d27303/glass_local.theme.css", + "initial_release_date": "2021-06-06T14:33:56.762951Z", + "latest_release_date": "2023-10-25T11:43:36.171587Z", + "guild": null + }, + "serverdetails.plugin.js": { + "id": 100, + "name": "ServerDetails", + "file_name": "ServerDetails.plugin.js", + "type": "plugin", + "description": "Shows Server Details in the Server List Tooltip", + "version": "1.3.4", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 300, + "downloads": 524132, + "tags": [ + "status", + "servers", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/287.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/e2bcd60f180890d18505e746287474da32df2486/Plugins/ServerDetails/ServerDetails.plugin.js", + "initial_release_date": "2021-03-06T17:58:11.519937Z", + "latest_release_date": "2025-12-18T19:27:38.990448Z", + "guild": null + }, + "onlinefriendcount.plugin.js": { + "id": 183, + "name": "OnlineFriendCount", + "file_name": "OnlineFriendCount.plugin.js", + "type": "plugin", + "description": "Adds the old online friend count back to server list. Because nostalgia.", + "version": "3.3.1", + "author": { + "github_id": "19844016", + "github_name": "Zerthox", + "display_name": "Zerthox", + "discord_name": "zerthox", + "discord_avatar_hash": "3966dfed9ef64656359792e34af73305", + "discord_snowflake": "144881947557101568", + "guild": null + }, + "likes": 181, + "downloads": 310264, + "tags": [ + "enhancement", + "friends" + ], + "thumbnail_url": "/resources/thumbnails/392.png", + "latest_source_url": "https://raw.githubusercontent.com/Zerthox/BetterDiscord-Plugins/157a9ad91345183404d5078de41b6179b313d499/dist/bd/OnlineFriendCount.plugin.js", + "initial_release_date": "2021-05-01T13:37:50.339128Z", + "latest_release_date": "2025-10-13T16:00:51.934338Z", + "guild": null + }, + "showping.plugin.js": { + "id": 1225, + "name": "ShowPing", + "file_name": "ShowPing.plugin.js", + "type": "plugin", + "description": "Displays your live ping. For Bugs or Feature Requests open an issue on my Github.", + "version": "2.6.12", + "author": { + "github_id": "61830443", + "github_name": "nicola02nb", + "display_name": "nicola02nb", + "discord_name": "nicola02nb", + "discord_avatar_hash": "b0e2a870015d31c91f2d8895d80b2113", + "discord_snowflake": "257900031351193600", + "guild": { + "name": "nicola02nb Dev Support", + "snowflake": "1329222382279327825", + "invite_link": "https://discord.gg/hFuY8DfDGK", + "avatar_hash": null + } + }, + "likes": 3, + "downloads": 4474, + "tags": [ + "voice", + "enhancement" + ], + "thumbnail_url": "/resources/thumbnails/1566.png", + "latest_source_url": "https://raw.githubusercontent.com/nicola02nb/BetterDiscord-Stuff/20fb3e93a1abbac3d667f8169c4b68c85813e3c9/Plugins/ShowPing/ShowPing.plugin.js", + "initial_release_date": "2025-01-31T16:53:42.362132Z", + "latest_release_date": "2026-01-28T18:16:54.478171Z", + "guild": null + }, + "friends-since.plugin.js": { + "id": 1080, + "name": "FriendsSince", + "file_name": "friends-since.plugin.js", + "type": "plugin", + "description": "Shows the date of when you and a friend became friends", + "version": "1.0.14", + "author": { + "github_id": "71196819", + "github_name": "doggybootsy", + "display_name": "DoggyBootsy", + "discord_name": "doggybootsy", + "discord_avatar_hash": "51e45b02bb0acf0449a87f3f1e079fc8", + "discord_snowflake": "515780151791976453", + "guild": null + }, + "likes": 13, + "downloads": 26150, + "tags": [ + "shortcut", + "enhancement", + "friends", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1423.png", + "latest_source_url": "https://raw.githubusercontent.com/doggybootsy/BDPlugins/627cbc4a3a756ebd8af339c86da16a6a38a72052/friends-since/friends-since.plugin.js", + "initial_release_date": "2024-02-22T01:33:36.534993Z", + "latest_release_date": "2026-02-06T06:17:38.355781Z", + "guild": null + }, + "readallnotificationsbutton.plugin.js": { + "id": 94, + "name": "ReadAllNotificationsButton", + "file_name": "ReadAllNotificationsButton.plugin.js", + "type": "plugin", + "description": "Adds a Clear Button to the Server List and the Mentions Popout", + "version": "1.8.5", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 709, + "downloads": 1374279, + "tags": [ + "notifications", + "shortcut" + ], + "thumbnail_url": "/resources/thumbnails/281.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/80de11f2cc8b769f80ac3aceb41ec3262a8839ac/Plugins/ReadAllNotificationsButton/ReadAllNotificationsButton.plugin.js", + "initial_release_date": "2021-03-06T17:53:14.399855Z", + "latest_release_date": "2025-12-21T10:53:14.13505Z", + "guild": null + }, + "t1.theme.css": { + "id": 218, + "name": "T1", + "file_name": "T1.theme.css", + "type": "theme", + "description": "Image background + Horizontal server bar", + "version": "0.10.0", + "author": { + "github_id": "29176216", + "github_name": "Eight-P", + "display_name": "Eight_P", + "discord_name": "eight_p", + "discord_avatar_hash": "5d84722af31b161ab78a56d56fd75798", + "discord_snowflake": "120202910586896385", + "guild": null + }, + "likes": 103, + "downloads": 199173, + "tags": [ + "flat", + "layout", + "customizable", + "dark" + ], + "thumbnail_url": "/resources/thumbnails/1249.png", + "latest_source_url": "https://raw.githubusercontent.com/Eight-P/BD.8P/61dd6e8d30fd2ea6f441d8faee87283383909a6a/Themes/T1/T1.theme.css", + "initial_release_date": "2021-05-08T23:17:40.012224Z", + "latest_release_date": "2025-06-21T15:47:00.724239Z", + "guild": null + }, + "bettertts.plugin.js": { + "id": 1188, + "name": "BetterTTS", + "file_name": "BetterTTS.plugin.js", + "type": "plugin", + "description": "A plugin that allows you to play a custom TTS when a message is received.", + "version": "2.17.6", + "author": { + "github_id": "61830443", + "github_name": "nicola02nb", + "display_name": "nicola02nb", + "discord_name": "nicola02nb", + "discord_avatar_hash": "b0e2a870015d31c91f2d8895d80b2113", + "discord_snowflake": "257900031351193600", + "guild": { + "name": "nicola02nb Dev Support", + "snowflake": "1329222382279327825", + "invite_link": "https://discord.gg/hFuY8DfDGK", + "avatar_hash": null + } + }, + "likes": 4, + "downloads": 3162, + "tags": [ + "notifications", + "channels", + "text", + "enhancement" + ], + "thumbnail_url": "/resources/thumbnails/1546.png", + "latest_source_url": "https://raw.githubusercontent.com/nicola02nb/BetterDiscord-Stuff/5fca87ae85ba1979b151f794504dbc25dd51b1d7/Plugins/BetterTTS/BetterTTS.plugin.js", + "initial_release_date": "2024-12-20T22:41:00.285739Z", + "latest_release_date": "2026-01-30T15:36:17.041419Z", + "guild": null + }, + "servercolumns.theme.css": { + "id": 49, + "name": "ServerColumns", + "file_name": "ServerColumns.theme.css", + "type": "theme", + "description": "Changes the Server List to a grid-like Structure that allows any Amount of Columns", + "version": "1.0.6", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 73, + "downloads": 87547, + "tags": [ + "layout", + "customizable" + ], + "thumbnail_url": "/resources/thumbnails/232.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/42c63f152ee95fcc937de51b04c5f4cb240f795b/Themes/ServerColumns/ServerColumns.theme.css", + "initial_release_date": "2021-02-25T10:18:52.310172Z", + "latest_release_date": "2025-12-26T09:42:25.714237Z", + "guild": null + }, + "activityfilter.plugin.js": { + "id": 1191, + "name": "ActivityFilter", + "file_name": "ActivityFilter.plugin.js", + "type": "plugin", + "description": "Customize which activities, games, or apps are displayed in your Discord status with advanced filtering and visibility options.", + "version": "1.1.1", + "author": { + "github_id": "107440461", + "github_name": "sewsho", + "display_name": "sewsho", + "discord_name": "sewsho", + "discord_avatar_hash": "f7fb72b7ec28180be1467dc3618a167c", + "discord_snowflake": "1019660983758766130", + "guild": null + }, + "likes": 4, + "downloads": 6447, + "tags": [ + "activity", + "status", + "utility" + ], + "thumbnail_url": "/resources/thumbnails/1542.png", + "latest_source_url": "https://raw.githubusercontent.com/sewsho/BetterDiscordAddons/845b36f3ec7d583998b9a8f8b18328b155f2ba34/Plugins/ActivityFilter/ActivityFilter.plugin.js", + "initial_release_date": "2024-12-21T01:30:36.314854Z", + "latest_release_date": "2024-12-29T19:05:28.052093Z", + "guild": null + }, + "passcodelock.plugin.js": { + "id": 671, + "name": "PasscodeLock", + "file_name": "PasscodeLock.plugin.js", + "type": "plugin", + "description": "Protect your Discord with a passcode.", + "version": "1.5.7", + "author": { + "github_id": "52377180", + "github_name": "arg0NNY", + "display_name": "arg0NNY", + "discord_name": "arg0nny", + "discord_avatar_hash": "87d3e880f144cc8756954fce94fb3548", + "discord_snowflake": "224538553944637440", + "guild": { + "name": "arg0NNY's Lounge", + "snowflake": "947007182552064050", + "invite_link": "https://discord.gg/M8DBtcZjXD", + "avatar_hash": "840d87c006a42a594deb620339b8d178 " + } + }, + "likes": 130, + "downloads": 151905, + "tags": [ + "security" + ], + "thumbnail_url": "/resources/thumbnails/996.gif", + "latest_source_url": "https://raw.githubusercontent.com/arg0NNY/DiscordPlugins/828ac1cb014351681ca56552a054265de47bd412/PasscodeLock/PasscodeLock.plugin.js", + "initial_release_date": "2022-04-06T17:35:10.448077Z", + "latest_release_date": "2026-01-25T19:03:00.142607Z", + "guild": { + "name": "arg0NNY's Lounge", + "snowflake": "947007182552064050", + "invite_link": "https://discord.gg/M8DBtcZjXD", + "avatar_hash": "840d87c006a42a594deb620339b8d178 " + } + }, + "quiet.theme.css": { + "id": 580, + "name": "Quiet", + "file_name": "Quiet.theme.css", + "type": "theme", + "description": "Dark and Quiet. Easy on the eyes. Very Flat.\r\nUpdated since 16/04.2025 for new layout change", + "version": "2.0", + "author": { + "github_id": "628576", + "github_name": "squee666", + "display_name": "Izy ", + "discord_name": ".izy_", + "discord_avatar_hash": "4ad4a8c9468fddc48679b46f7d67759f", + "discord_snowflake": "107511169366888448", + "guild": { + "name": "Black Box", + "snowflake": "449175561529589761", + "invite_link": "https://discord.gg/TeRQEPb", + "avatar_hash": "a_927da1c2b111767f3df61e31d1949a96 " + } + }, + "likes": 66, + "downloads": 94311, + "tags": [ + "flat", + "customizable", + "dark", + "red" + ], + "thumbnail_url": "/resources/thumbnails/1615.png", + "latest_source_url": "https://raw.githubusercontent.com/squee666/Discord-Themes/20eb3a4e5e0e7342dcf1d434c4ad14021e377540/Themes/Quiet.theme.css", + "initial_release_date": "2022-01-20T17:51:16.966762Z", + "latest_release_date": "2025-04-20T23:47:36.303438Z", + "guild": null + }, + "wizardui.theme.css": { + "id": 1014, + "name": "WizardUI", + "file_name": "WizardUI.theme.css", + "type": "theme", + "description": "An amazing fully customizable theme with many addons, made with 💖 by NEBULYS.", + "version": "1.91", + "author": { + "github_id": "31554073", + "github_name": "GoldenLys", + "display_name": "Nebulys", + "discord_name": "nebulys", + "discord_avatar_hash": "0e3baa0ee432a8ac69890698355a0937", + "discord_snowflake": "201474545289396224", + "guild": { + "name": "Wizard Tower", + "snowflake": "572926470788218891", + "invite_link": "https://discord.gg/SBuYeHh", + "avatar_hash": "cd929cffc703120ced494e689740a600 " + } + }, + "likes": 57, + "downloads": 82727, + "tags": [ + "customizable", + "space", + "dark", + "animated" + ], + "thumbnail_url": "/resources/thumbnails/1360.jpg", + "latest_source_url": "https://raw.githubusercontent.com/GoldenLys/WizardUI/dee209eaff437b7ab4d40a92321d54968ba1c4bc/WizardUI.theme.css", + "initial_release_date": "2023-08-15T10:09:22.582418Z", + "latest_release_date": "2026-01-29T13:56:21.127713Z", + "guild": null + }, + "roundmoledv2.theme.css": { + "id": 1125, + "name": "RoundmoledV2", + "file_name": "roundmoledV2.theme.css", + "type": "theme", + "description": "Simple amoled black + rounded corners theme - Updated version", + "version": "1.3.1", + "author": { + "github_id": "77923554", + "github_name": "TheoEwzZer", + "display_name": "Theo EwzZer", + "discord_name": "theoewzzer", + "discord_avatar_hash": "7b7618620428c9cf060c224cd07f5bff", + "discord_snowflake": "384009727253807105", + "guild": null + }, + "likes": 14, + "downloads": 22428, + "tags": [ + "customizable", + "dark", + "black" + ], + "thumbnail_url": "/resources/thumbnails/1496.png", + "latest_source_url": "https://raw.githubusercontent.com/TheoEwzZer/RoundmoledV2/f6d0c56c118f3e8158ee1a44bf96341bc5f8004d/roundmoledV2.theme.css", + "initial_release_date": "2024-07-02T14:52:20.68398Z", + "latest_release_date": "2025-02-21T15:13:09.224133Z", + "guild": null + }, + "charcounter.plugin.js": { + "id": 64, + "name": "CharCounter", + "file_name": "CharCounter.plugin.js", + "type": "plugin", + "description": "Adds a Character Counter to most Inputs", + "version": "1.7.1", + "author": { + "github_id": "23700969", + "github_name": "mwittrien", + "display_name": "DevilBro", + "discord_name": "devilbrother", + "discord_avatar_hash": "980bb73b58e703af70711635e0cee84f", + "discord_snowflake": "278543574059057154", + "guild": { + "name": "DevilBro's Haus", + "snowflake": "410787888507256842", + "invite_link": "https://discord.gg/Jx3TjNS", + "avatar_hash": "3ed65aef2806f01ceb4022674b50ea1a " + } + }, + "likes": 218, + "downloads": 247878, + "tags": [ + "text", + "chat" + ], + "thumbnail_url": "/resources/thumbnails/239.png", + "latest_source_url": "https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/23c24d91e7449c27b497784761ff1e82df273a59/Plugins/CharCounter/CharCounter.plugin.js", + "initial_release_date": "2021-03-06T09:44:53.196884Z", + "latest_release_date": "2025-12-17T05:33:51.920613Z", + "guild": null + } + }, + "version": "v3" +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/data/stable/custom.css b/dotfiles/.config/BetterDiscord/data/stable/custom.css new file mode 100644 index 0000000..3592eca --- /dev/null +++ b/dotfiles/.config/BetterDiscord/data/stable/custom.css @@ -0,0 +1,5 @@ +body { + --custom-guild-sidebar-width: 500px !important; + --user-area-width: 450px !important; + --custom-member-list-width: 550px !important; +} diff --git a/dotfiles/.config/BetterDiscord/data/stable/emotes.json b/dotfiles/.config/BetterDiscord/data/stable/emotes.json new file mode 100644 index 0000000..88c519a --- /dev/null +++ b/dotfiles/.config/BetterDiscord/data/stable/emotes.json @@ -0,0 +1,15 @@ +{ + "general": { + "download": true, + "emoteMenu": true, + "hideEmojiMenu": false, + "modifiers": true, + "animateOnHover": false + }, + "categories": { + "twitchglobal": true, + "twitchsubscriber": false, + "frankerfacez": true, + "bttv": true + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/data/stable/misc.json b/dotfiles/.config/BetterDiscord/data/stable/misc.json new file mode 100644 index 0000000..39622d9 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/data/stable/misc.json @@ -0,0 +1,13 @@ +{ + "version": "1.13.8", + "drawerStates": { + "settings": { + "addons": true, + "store": true, + "customcss": true, + "editor": true, + "window": true, + "developer": true + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/data/stable/plugins.json b/dotfiles/.config/BetterDiscord/data/stable/plugins.json new file mode 100644 index 0000000..a1046ef --- /dev/null +++ b/dotfiles/.config/BetterDiscord/data/stable/plugins.json @@ -0,0 +1,45 @@ +{ + "PluginRepo": false, + "BDFDB": true, + "GameActivityToggle": true, + "ReadAllNotificationsButton": true, + "ThemeRepo": false, + "ZeresPluginLibrary": true, + "AuroraGSI": false, + "BetterFormattingRedux": true, + "BetterRoleColors": false, + "ShowConnections": true, + "ThemeSettings": false, + "VoiceEvents": false, + "WhoReacted": true, + "serverthemes": false, + "betterdiscord-google-fonts": true, + "PronounDB": false, + "VoiceActivity": true, + "InMyVoice": true, + "VoiceChatNotifications": true, + "AvatarHover": true, + "BetterChatNames": true, + "HideServersChannelsRedux": false, + "ShowBadgesInChat": true, + "removeTrackingURL": false, + "BetterGuildTooltip": true, + "Translator": true, + "ChannelsBadges": false, + "DisplayUsernames": true, + "enhancecodeblocks": false, + "StickerEmojiPreview": true, + "WiderUserArea": true, + "Animations": false, + "ServerDetails": true, + "ActivityIcons": false, + "PermissionsViewer": true, + "ChannelPermissions": false, + "BetterChannelList": false, + "ChannelTabs": false, + "NotificationSounds": true, + "MoreRoleColors": true, + "BetterAnimations": false, + "DoNotTrack": true, + "ViewProfilePicture": true +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/data/stable/settings.json b/dotfiles/.config/BetterDiscord/data/stable/settings.json new file mode 100644 index 0000000..8907038 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/data/stable/settings.json @@ -0,0 +1,54 @@ +{ + "general": { + "voiceDisconnect": false, + "showToasts": true, + "mediaKeys": true, + "bdContextMenu": true, + "themeAttributes": true, + "notificationPosition": "top-right", + "notificationEnabled": true + }, + "addons": { + "addonErrors": true, + "editAction": "detached", + "checkForUpdates": true, + "updateInterval": 4 + }, + "store": { + "bdAddonStore": true, + "alwaysEnable": false, + "addonEmbeds": true + }, + "customcss": { + "customcss": true, + "liveUpdate": true, + "openAction": "settings" + }, + "editor": { + "theme": "system", + "lineNumbers": true, + "minimap": true, + "hover": true, + "quickSuggestions": true, + "insertSpaces": false, + "tabSize": 4, + "fontSize": 14, + "renderWhitespace": "selection" + }, + "window": { + "transparency": true, + "removeMinimumSize": false, + "frame": true, + "inAppTrafficLights": false + }, + "developer": { + "debugLogs": false, + "devTools": true, + "debuggerHotkey": false, + "reactDevTools": false, + "inspectElement": false, + "devToolsWarning": false, + "recovery": true, + "canary": false + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/data/stable/themes.json b/dotfiles/.config/BetterDiscord/data/stable/themes.json new file mode 100644 index 0000000..310c28c --- /dev/null +++ b/dotfiles/.config/BetterDiscord/data/stable/themes.json @@ -0,0 +1,5 @@ +{ + "Arindy": false, + "FVUI": false, + "Translucence": true +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/data/stable/webpack.json b/dotfiles/.config/BetterDiscord/data/stable/webpack.json new file mode 100644 index 0000000..56b5aa7 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/data/stable/webpack.json @@ -0,0 +1,136 @@ +{ + "core-SimpleMarkdownWrapper": "46054", + "core-React": "64700", + "core-Dispatcher": "73153", + "core-ReactSpring": "278420", + "core-AccessibilityContext": "158954", + "core-Tooltip": "397927", + "core-contextmenu-Actions": "442433", + "core-commandmanager-appIcons": "664929", + "core-settings-search": "368631", + "0PluginLibrary:1254924683": "64700", + "0PluginLibrary:341512209": "64700", + "0PluginLibrary:3695157183": "350535", + "0PluginLibrary:1032993886": "117178", + "0PluginLibrary:2508050256": "750506", + "0PluginLibrary:3026836349": "158954", + "0PluginLibrary:4136795093": "267102", + "0PluginLibrary:3050996723": "652215", + "0PluginLibrary:951786541": "158954", + "0PluginLibrary:83147845": "64700", + "0PluginLibrary:3075157097": "544028", + "0PluginLibrary:377304194": "558179", + "0PluginLibrary:2879910472": "46054", + "BetterChatNames:1201": "967198", + "BetterChatNames:1233": "309010", + "BetterChatNames:1319": "166444", + "BetterChatNames:1349": "742589", + "BetterChatNames:1385": "415409", + "BetterChatNames:1412": "232042", + "BetterFormattingRedux:22226": "843472", + "BetterFormattingRedux:22258": "111314", + "BetterGuildTooltip:1392": "73153", + "BetterGuildTooltip:1479": "311907", + "BetterGuildTooltip:1803": "311907", + "BetterGuildTooltip:1878": "485366", + "BetterGuildTooltip:2014": "308528", + "BetterGuildTooltip:2109": "647668", + "0PluginLibrary:3508192550": "635071", + "DoNotTrack:6603": "594061", + "DoNotTrack:6630": "406935", + "DoNotTrack:6659": "954571", + "DoNotTrack:6693": "837921", + "EnhanceCodeBlocks:3413228107": "278420", + "EnhanceCodeBlocks:1829278509": "855522", + "EnhanceCodeBlocks:1929428925": "28728", + "EnhanceCodeBlocks:1078947860": "459127", + "EnhanceCodeBlocks:1946862927": "102741", + "EnhanceCodeBlocks:1082876702": "397927", + "EnhanceCodeBlocks:1859630996": "158954", + "EnhanceCodeBlocks:2928343028": "397927", + "EnhanceCodeBlocks:3078982459": "453903", + "EnhanceCodeBlocks:830998530": "103552", + "InMyVoice:1637": "679740", + "InMyVoice:1672": "931228", + "InMyVoice:1864": "607567", + "InMyVoice:1895": "635071", + "InMyVoice:1919": "709066", + "InMyVoice:1958": "311907", + "MoreRoleColors:453": "481947", + "MoreRoleColors:611": "147192", + "MoreRoleColors:628": "288539", + "MoreRoleColors:772": "311907", + "MoreRoleColors:21476720": "613306", + "MoreRoleColors:24485674": "485947", + "MoreRoleColors:26065620": "320819", + "MoreRoleColors:27696461": "709066", + "PermissionsViewer:12806": "652215", + "PermissionsViewer:12833": "820883", + "PermissionsViewer:12870": "837921", + "PermissionsViewer:12897": "985018", + "PermissionsViewer:3028674": "773669", + "PermissionsViewer:14663700": "997168", + "PermissionsViewer:14698296": "707616", + "PermissionsViewer:14725204": "949296", + "PermissionsViewer:15024075": "183959", + "PronounDB:2502": "311907", + "PronounDB:2547": "311907", + "StickerEmojiPreview:4460131": "151271", + "StickerEmojiPreview:5010": "265690", + "StickerEmojiPreview:5144": "499867", + "StickerEmojiPreview:4461902": "338464", + "StickerEmojiPreview:6543603": "265872", + "StickerEmojiPreview:10469": "158954", + "ViewProfilePicture:10993": "265690", + "ViewProfilePicture:11127": "499867", + "ViewProfilePicture:12451": "192308", + "ViewProfilePicture:12492": "935462", + "ViewProfilePicture:13508": "397927", + "ViewProfilePicture:15403": "9578", + "ViewProfilePicture:15425": "608214", + "ViewProfilePicture:15619": "158954", + "ViewProfilePicture:15639": "454290", + "ViewProfilePicture:17226": "310784", + "ViewProfilePicture:18000": "827734", + "ViewProfilePicture:19610": "654107", + "ViewProfilePicture:19993": "710016", + "ViewProfilePicture:20235": "158954", + "ViewProfilePicture:22447": "946356", + "ViewProfilePicture:23582": "158954", + "ViewProfilePicture:26590": "158954", + "VoiceActivity:5346226": "311907", + "VoiceActivity:5347121": "110574", + "VoiceActivity:5408780": "701363", + "VoiceActivity:5347431": "174279", + "VoiceActivity:5347552": "652215", + "VoiceActivity:525973814": "820659", + "VoiceActivity:5347837": "714114", + "VoiceActivity:434714749": "158954", + "VoiceActivity:434714778": "83107", + "VoiceActivity:434714908": "158954", + "VoiceActivity:434715029": "158954", + "VoiceActivity:434715159": "158954", + "VoiceActivity:434715274": "158954", + "VoiceActivity:5357596": "10862", + "VoiceActivity:5357907": "976860", + "core-SimpleMarkdown": "791332", + "0BDFDB:3340963209": "287809", + "BetterChatNames:1262": "976860", + "0BDFDB:3538865044": "287809", + "core-changelog-anchorClasses": "68380", + "core-commandmanager-iconClasses": "382780", + "core-commandmanager-builtInSeparatorClasses": "127461", + "core-mdinstallcss-codeBlockStyles": "830412", + "core-themeattributes-MessageComponent": "371741", + "core-themeattributes-messageHook": "672341", + "core-themeattributes-VoiceUserComponent": "481947", + "core-themeattributes-ChatAvatar": "643204", + "core-themeattributes-AvatarImg": "97808", + "BetterChatNames:1263": "967198", + "BetterChatNames:1295": "309010", + "BetterChatNames:1324": "976860", + "BetterChatNames:1381": "166444", + "BetterChatNames:1411": "742589", + "BetterChatNames:1447": "415409", + "BetterChatNames:1474": "232042" +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/0BDFDB.config.json b/dotfiles/.config/BetterDiscord/plugins/0BDFDB.config.json new file mode 100755 index 0000000..579c7d9 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/0BDFDB.config.json @@ -0,0 +1,31 @@ +{ + "all": { + "changeLogs": { + "BDFDB": "4.4.6", + "GameActivityToggle": "1.3.9", + "NotificationSounds": "4.0.7", + "PluginRepo": "2.6.0", + "ReadAllNotificationsButton": "1.8.5", + "ServerDetails": "1.3.4", + "ShowBadgesInChat": "2.1.5", + "ShowConnections": "1.3.2", + "ThemeRepo": "2.6.0", + "ThemeSettings": "9.9.9", + "Translator": "2.7.9" + }, + "choices": { + "toastPosition": "right" + }, + "general": { + "shareData": true, + "showToasts": true, + "checkForUpdates": false, + "showSupportBadges": true, + "useChromium": false + }, + "hashes": { + "0BDFDB.data.json": "b7e1d2d9f648471712355e67b6df0df0208fe27b", + "0BDFDB.raw.css": "edada1edc762888eebc5aa44e5b8c1baf2e60104" + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/0BDFDB.data.json b/dotfiles/.config/BetterDiscord/plugins/0BDFDB.data.json new file mode 100644 index 0000000..b7e1d2d --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/0BDFDB.data.json @@ -0,0 +1,5482 @@ +{ + "PluginNameMap": {}, + "PluginUrlMap": { + "BDFDB": "https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js", + "CopyRawMessage": "https://mwittrien.github.io/BetterDiscordAddons/_DEAD/CopyRawMessage.plugin.js", + "CreationDate": "https://mwittrien.github.io/BetterDiscordAddons/_DEAD/CreationDate.plugin.js", + "ForceImagePreviews": "https://mwittrien.github.io/BetterDiscordAddons/_DEAD/ForceImagePreviews.plugin.js", + "JoinedAtDate": "https://mwittrien.github.io/BetterDiscordAddons/_DEAD/JoinedAtDate.plugin.js", + "ShowHiddenChannels": "https://mwittrien.github.io/BetterDiscordAddons/_DEAD/ShowHiddenChannels.plugin.js", + "ThemeSettings": "https://mwittrien.github.io/BetterDiscordAddons/_DEAD/ThemeSettings.plugin.js" + }, + "LibraryRequires": ["electron", "fs", "path", "process", "request"], + "DiscordObjects": { + "Channel": {"props": ["getRecipientId", "isManaged", "getGuildId"]}, + "Guild": {"strings": ["publicUpdatesChannelId", "nsfwLevel", "instanceof Date"]}, + "Invite": {"props": ["getExpiresAt", "isExpired"]}, + "Message": {"props": ["getReaction", "isEdited", "getChannelId"]}, + "Messages": {"props": ["jumpToMessage", "hasAfterCached", "forEach"]}, + "Timestamp": {"props": ["add", "dayOfYear", "hasAlignedHourOffset"]}, + "User": {"props": ["addGuildAvatarHash", "isLocalBot"]} + }, + "CustomDiscordConstants": { + "MAX_GUILD_FOLDER_NAME_LENGTH": 32, + "MAX_MESSAGE_LENGTH": 2000, + "MAX_MESSAGE_LENGTH_PREMIUM": 4000, + "MAX_MESSAGES_PER_CHANNEL": 50, + "MAX_VIDEO_WIDTH": 3840, + "MAX_VIDEO_HEIGHT": 2160, + "ME": "@me", + "MenuItemColors": { + "BRAND": "brand", + "DANGER": "danger", + "DEFAULT": "default", + "PREMIUM": "premium", + "PREMIUMGRADIENT": "premiumgradient", + "SUCCESS": "success" + }, + "SEARCH_PAGE_SIZE": 25, + "ToastIcons": { + "info": "INFO", + "danger": "CLOSE_CIRCLE", + "success": "CHECKMARK_CIRCLE", + "warning": "WARNING" + }, + "ToastPositions": { + "center": "toastscenter", + "left": "toastsleft", + "right": "toastsright" + }, + "UserBadges": { + "active_developer": "6bdc42827a38498929a4920da12695d9", + "automod": "f2459b691ac7453ed6039bbcfaccbfcd", + "bot_commands": "6f9e37f9029ff57aef81db857890005e", + "bug_hunter_lvl1": "2717692c7dca7289b35297368a940dd0", + "bug_hunter_lvl2": "848f79194d4be5ff5f81505cbd0ce1e6", + "certified_moderator": "fee1624003e2fee35cb398e125dc479b", + "guild_booster_lvl1": "51040c70d4f20a921ad6674ff86fc95c", + "guild_booster_lvl2": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "guild_booster_lvl3": "72bed924410c304dbe3d00a6e593ff59", + "guild_booster_lvl4": "df199d2050d3ed4ebf84d64ae83989f8", + "guild_booster_lvl5": "996b3e870e8a22ce519b3a50e6bdd52f", + "guild_booster_lvl6": "991c9f39ee33d7537d9f408c3e53141e", + "guild_booster_lvl7": "cb3ae83c15e970e8f3d410bc62cb8b99", + "guild_booster_lvl8": "7142225d31238f6387d9f09efaa02759", + "guild_booster_lvl9": "ec92202290b48d0879b7413d2dde3bab", + "hypesquad": "bf01d1073931f921909045f3a39fd264", + "hypesquad_house_1": "8a88d63823d8a71cd5e390baa45efa02", + "hypesquad_house_2": "011940fd013da3f7fb926e4a1cd2e618", + "hypesquad_house_3": "3aa41de486fa12454c3761e8e223442e", + "legacy_username": "6de6d34650760ba5551a79732e98ed60", + "partner": "3f9748e53446a137a052f3454e2de41e", + "premium": "2ba85e8026a8614b640c2837bcdfe21b", + "premium_early_supporter": "7060786766c9c840eb3019e725d2b358", + "premium_tenure_3_month_v2": "4514fab914bdbfb4ad2fa23df76121a6", + "premium_tenure_6_month_v2": "2895086c18d5531d499862e41d1155a6", + "premium_tenure_12_month_v2": "0334688279c8359120922938dcb1d6f8", + "premium_tenure_24_month_v2": "0d61871f72bb9a33a7ae568c1fb4f20a", + "premium_tenure_36_month_v2": "11e2d339068b55d3a506cff34d3780f3", + "premium_tenure_60_month_v2": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "quest_completed": "7d9ae358c8c5e118768335dbe68b4fb8", + "staff": "5e74e9b61934fc1f67c65515d1f7e60d", + "verified_developer": "6df5892e0f35b051f8b61eace34f4967" + }, + "UserPremiumLevels": {"1": 1, "2": 2, "3": 3, "4": 6, "5": 9, "6": 12, "7": 15, "8": 18, "9": 24} + }, + "DiscordConstants": { + "ActivityTypes": ["CUSTOM_STATUS", "STREAMING", "WATCHING"], + "AnalyticsSections": ["FRIENDS_LIST", "PROFILE_MODAL", "ACCOUNT_PANEL"], + "AutocompleterResultTypes": ["VOICE_CHANNEL", "TEXT_CHANNEL", "USER", "LINK"], + "ChannelTextAreaTypes": ["CREATE_FORUM_POST", "PROFILE_BIO_INPUT", "FORUM_CHANNEL_GUIDELINES"], + "ChannelTypes": ["GUILD_STORE", "GUILD_TEXT", "GUILD_FORUM"], + "Colors": {"props": ["RawColors"], "value": "RawColors"}, + "ColorsCSS": {"props": ["themes", "colors", "space"], "value": "colors"}, + "ComponentActions": ["INSERT_TEXT", "PREPEND_TEXT", "SHAKE_APP"], + "EmojiSprites": ["DiversityPerRow", "PickerCount", "PickerPerRow"], + "Endpoints": ["MESSAGES", "CHANNEL", "GUILD", "SEARCH_CHANNEL"], + "FriendsSections": ["ADD_FRIEND", "SUGGESTIONS", "ONLINE"], + "ImageReadyStates": ["ERROR", "LOADING", "READY"], + "InboxTabs": ["MENTIONS", "UNREADS"], + "KeyboardDeviceTypes": ["GAMEPAD_BUTTON", "KEYBOARD_KEY", "MOUSE_BUTTON"], + "LinuxKeyToCode": ["meta", "track back"], + "MacosKeyToCode": ["right command", "command"], + "MessageStates": ["SEND_FAILED", "SENDING", "SENT"], + "MessageTypes": ["THREAD_CREATED", "CHAT_INPUT_COMMAND"], + "MessageTypeGroups": ["USER_MESSAGE", "UNDELETABLE"], + "Permissions": ["MOVE_MEMBERS", "ADD_REACTIONS", "USE_EXTERNAL_APPS", "VIEW_CREATOR_MONETIZATION_ANALYTICS"], + "ProfileTypes": ["POPOUT", "MODAL", "SIDEBAR"], + "ReadStateTypes": ["GUILD_EVENT", "NOTIFICATION_CENTER", "CHANNEL"], + "RelationshipTypes": ["FRIEND", "PENDING_INCOMING", "SUGGESTION"], + "Routes": ["CHANNEL", "APP", "QUESTS"], + "UserFlags": ["BOT_HTTP_INTERACTIONS", "PARTNER", "SPAMMER"], + "UserNotificationSettings": ["ONLY_MENTIONS", "NO_MESSAGES", "ALL_MESSAGES"], + "UserSettingsActionTypes": ["SLOW_USER_ACTION", "DAILY"], + "UserSettingsSections": ["CHANGE_LOG", "DEVELOPER_OPTIONS"], + "WindowsKeyToCode": ["fast forward", "play", "print screen"] + }, + "LibraryModules": { + "AckUtils": {"strings": ["type:\"CHANNEL_ACK\",channelId", "type:\"BULK_ACK\",channels:"], "exported": false, "value": "exports", "map": { + "ack": ["type:\"CHANNEL_ACK\""], + "bulkAck": ["type:\"BULK_ACK\""] + }}, + "ActivityUtils": {"props": ["sendActivityInvite", "updateActivity"]}, + "AnalyticsUtils": {"props": ["isThrottled", "track"]}, + "AnimationUtils": {"props": ["spring", "decay"]}, + "APIEncodeUtils": {"props": ["stringify", "parse", "encode"]}, + "APIUtils": {"strings": ["window.GLOBAL_ENV.API_ENDPOINT+", "GLOBAL_ENV.API_VERSION"], "exported": false, "value": "exports", "map": { + "getAPIBaseURL": ["window.GLOBAL_ENV.API_ENDPOINT+"] + }}, + "AppearanceSettingsUtils": {"props": ["updateLocale", "updateTheme"]}, + "ApplicationAssetUtils": {"strings": ["icon.customPNG", "smallImage:", "application_id"], "exported": false, "value": "exports", "map": { + "getAssetImage": ["icon.customPNG", "smallImage:"] + }}, + "AppUtils": {"props": ["clipboard", "os"]}, + "ArrayUtils": {"props": ["isArrayLike", "zipObject"]}, + "AvatarUtils": {"strings": ["avatarPlaceholderSrc:", "default.getUser"], "exported": false, "value": "exports", "map": { + "getAvatarProps": ["avatarPlaceholderSrc:", "default.getUser"] + }}, + "CategoryCollapseUtils": {"strings": [".dispatch({type:\"CATEGORY_EXPAND\"", ".dispatch({type:\"CATEGORY_COLLAPSE\""], "exported": false, "value": "exports", "map": { + "categoryCollapse": [".dispatch({type:\"CATEGORY_COLLAPSE\""], + "categoryExpand": [".dispatch({type:\"CATEGORY_EXPAND\""] + }}, + "ChannelUtils": {"props": ["selectChannel", "selectPrivateChannel"]}, + "ChatRestrictionUtils": {"strings": ["dispatch({type:\"MESSAGE_LENGTH_UPSELL\"", "Message Too Long Alert"], "exported": false, "value": "exports", "map": { + "applyChatRestrictions": ["Message Too Long Alert"] + }}, + "ClipboardUtils": {"strings": ["clipboard.writeText", "Clipboard API not supported"], "exported": false, "value": "exports", "map": { + "copy": ["clipboard.writeText", "Clipboard API not supported"] + }}, + "ConnectionProviderUtils": {"props": ["get", "isSupported", "filter"]}, + "ConnectionUtils": {"props": ["setShowActivity", "setVisibility"]}, + "ContextMenuUtils": {"strings": ["renderLazy:", ".enableSpellCheck", "type:\"CONTEXT_MENU_CLOSE\""], "exported": false, "value": "exports", "map": { + "closeContextMenu": ["type:\"CONTEXT_MENU_CLOSE\""], + "openContextMenu": ["renderLazy:", ".enableSpellCheck"] + }}, + "CurrentUserStore": {"props": ["getCurrentUser"]}, + "CustomStatusStore": {"strings": ["DONT_CLEAR", "getTime", "clear_after:null"], "exported": false, "value": "exports", "map": { + "update": ["DONT_CLEAR", "getTime", "clear_after:null"] + }}, + "CustomStatusUtils": {"strings": ["findActivity", ".CUSTOM_STATUS", "default.getId()==="], "exported": false, "value": "exports", "map": { + "get": ["findActivity", ".CUSTOM_STATUS", "default.getId()==="] + }}, + "DesktopNotificationUtils": {"props": ["showNotification", "requestPermission"]}, + "DispatchApiUtils": {"props": ["dispatch", "isDispatching"]}, + "DispatchUtils": {"strings": ["ComponentDispatchUtils"], "exported": false, "value": "exports", "map": { + "ComponentDispatch": ["\"_maxListeners\":100"], + "ComponentDispatcher": ["this.emitter.setMaxListeners"] + }}, + "EmojiUtils": {"props": ["translateInlineEmojiToSurrogates", "translateSurrogatesToInlineEmoji"]}, + "EmojiStateUtils": {"props": ["getURL", "isEmojiDisabled"]}, + "Env": {"props": ["env"]}, + "FolderSettingsUtils": {"strings": [".create", ".folderColor", "clientThemeSettings:"], "exported": false, "value": "exports", "map": { + "saveClientTheme": ["clientThemeSettings:"], + "saveGuildFolders": [".create", ".folderColor"] + }}, + "GuildNotificationsUtils": {"props": ["updateChannelOverrideSettings", "updateGuildNotificationSettings"]}, + "GuildUtils": {"props": ["selectGuild", "transitionToGuildSync"]}, + "HistoryUtils": {"strings": ["transitionTo - Transitioning to", ",channelId:"], "exported": false, "value": "exports", "map": { + "transitionTo": ["transitionTo - Transitioning to"] + }}, + "HTTPUtils": {"props": ["get", "patch", "del"]}, + "IconUtils": {"props": ["getGuildIconURL", "getGuildBannerURL"]}, + "InviteUtils": {"props": ["acceptInvite", "createInvite"]}, + "InternalReactUtils": {"props": ["jsx", "jsxs", "Fragment"]}, + "KeyCodeUtils": {"strings": [".KEYBOARD_MODIFIER_KEY", "Unrecognized DeviceType", "[\"\"+"], "exported": false, "value": "exports", "map": { + "codeToKey": [".keyCode,", "return null"], + "keyToCode": [".keyCode:null", ".KEYBOARD_KEY,"] + }}, + "KeyEvents": {"props": ["aliases", "code", "codes"]}, + "LanguageUtils": {"props": ["getLanguages", "getLocale"]}, + "LanguageIntlUtils": {"props": ["formatToPlainString", "formatToParts"]}, + "LoginUtils": {"props": ["login", "logout"]}, + "MediaEngineUtils": {"props": ["setOutputDevice", "setInputDevice"]}, + "MemberDisplayUtils": {"strings": [".getGuildMemberProfile", ".getUser("], "exported": false, "value": "exports", "map": { + "getDisplayProfile": [".getGuildMemberProfile"], + "getUserProfile": [".default,", "()=>"] + }}, + "MentionUtils": {"strings": [",mentionEveryone:", ",suppressEveryone:", ".getGuildId()"], "exported": false, "value": "exports", "map": { + "isMentioned": [".getGuildId()"], + "isRawMessageMentioned": ["rawMessage", ",suppressEveryone:"] + }}, + "MessageAuthorUtils": {"strings": [".channel_id);return", "Result cannot be null because the message is not null"], "exported": false, "value": "exports", "map": { + "getAuthor": ["Result cannot be null because the message is not null"], + "getMessageAuthor": [".channel_id);return"] + }}, + "MessageComponentUtils": {"strings": [".UNBLOCK_TO_JUMP_BODY", ".altKey"]}, + "MessageManageUtils": {"strings": ["showMentionToggle", "message-actions"], "exported": false, "value": "exports", "map": { + "copyLink": ["MESSAGE_LINK_COPIED"], + "replyToMessage": ["showMentionToggle"] + }}, + "MessageParser": {"props": ["parseEmbedTitle", "defaultRules"]}, + "MessagePinUtils": {"props": ["pinMessage", "unpinMessage"]}, + "MessageToolbarUtils": {"props": ["useMessageMenu"]}, + "MessageUtils": {"props": ["receiveMessage", "editMessage"]}, + "ModalUtils": {"strings": [".modalKey", "onCloseCallback", "Layer:"], "exported": false, "value": "exports", "map": { + "openModal": ["backdropStyle:", "modalKey:", "Layer:"], + "closeAll": ["[e];if(null!=t)for(let n of t)"] + }}, + "NitroUtils": {"props": ["canUseIncreasedMessageLength", "canUsePremiumGuildMemberProfile"]}, + "NotificationSettingsUtils": {"props": ["setDesktopType", "setTTSType"]}, + "PlatformUtils": {"props": ["isAndroid", "isWindows"]}, + "PermissionRoleUtils": {"strings": ["permission:", ".getMember", "excludeGuildPermissions"], "exported": false, "value": "exports", "map": { + "can": ["excludeGuildPermissions", "permission:"] + }}, + "PreferencesContext": {"strings": ["reducedMotion", "forcedColors", "alwaysShowLinkDecorations", "createContext"], "exported": false, "value": "exports", "map": { + "AccessibilityPreferencesContext": ["[object Object]"] + }}, + "PrivateChannelUtils": {"props": ["addRecipient", "openPrivateChannel"]}, + "QuerySearchUtils": {"props": ["queryGuilds", "queryGroupDMs"]}, + "RecentMentionUtils": {"props": ["deleteRecentMention", "fetchRecentMentions"]}, + "RelationshipUtils": {"props": ["addRelationship", "removeRelationship"]}, + "RoleIconUtils": {"strings": [".subscription_listing_id", ".ROLE_ICONS", "customIconSrc:"], "exported": false, "value": "exports", "map": { + "canGuildUseRoleIcons": [".subscription_listing_id", ".ROLE_ICONS"], + "getRoleIconData": ["customIconSrc:"] + }}, + "RTCConnectionUtils": {"props": ["getChannelId", "getGuildId", "getRTCConnectionId"]}, + "SearchPageUtils": {"strings": ["SEARCH_RESULT_PAGE_CHANGED", "newPageIndex", "refreshSearch"], "exported": false, "value": "exports", "map": { + "changePage": ["SEARCH_RESULT_PAGE_CHANGED"] + }}, + "SimpleMarkdownParser": {"props": ["parseBlock", "parseInline", "defaultOutput"]}, + "SlateRichUtils": {"strings": ["{type:\"line\",children", "textValue:", "richValue:", "map"], "exported": false, "value": "exports", "map": { + "toRichValue": ["{type:\"line\",children", "map"] + }}, + "SlateTextUtils": {"strings": ["preventEmojiSurrogates", "ignoreTrailingEmptyNodes", "allowBlock"], "exported": false, "value": "exports", "map": { + "toTextValue": ["ignoreTrailingEmptyNodes"] + }}, + "SoundParser": {"strings": ["discodo", "ddr-down", "mute"]}, + "SoundUtils": {"strings": [".disableSounds)", ".getSoundpack()", "Unable to find sound for pack name"], "exported": false, "value": "exports", "map": { + "createSound": [";return new "], + "playSound": ["playWithListener"] + }}, + "SpotifyUtils": {"strings": [".CONNECTION_ACCESS_TOKEN", "SPOTIFY_PLAYER_PAUSE"], "exported": false, "value": "exports", "map": { + "getAccessToken": [".CONNECTION_ACCESS_TOKEN", "SPOTIFY"], + "pause": ["SPOTIFY_PLAYER_PAUSE"], + "play": ["SPOTIFY_PLAYER_PLAY"] + }}, + "StoreChangeUtils": {"props": ["get", "set", "clear", "remove"]}, + "TimeoutUtils": {"strings": ["_timeout.isStarted()"]}, + "TimestampUtils": {"props": ["fromTimestamp", "extractTimestamp"]}, + "UploadUtils": {"props": ["upload", "instantBatchUpload"]}, + "URLParser": {"props": ["parse", "resolveObject"]}, + "UserBadgeUtils": {"strings": ["getBadges", "STREAMER_MODE_ENABLED", "description"], "exported": false, "value": "exports", "map": { + "getBadges": ["getBadges", "STREAMER_MODE_ENABLED"] + }}, + "UserBannerUtils": {"strings": ["displayProfile:", "bannerSrc:", "new Image"], "exported": false, "value": "exports", "map": { + "getBanner": ["displayProfile:", "bannerSrc:", "new Image"] + }}, + "UserNameFontUtils": {"strings": ["useDisplayNameStylesFont", ".fontId"], "exported": false, "value": "exports", "map": { + "getClass": ["useDisplayNameStylesFont", ".fontId"] + }}, + "UserNameUtils": {"props": ["getNickname", "getName", "useName"]}, + "UserPopoutUtils": {"strings": ["Cannot moderate user in DM or group DM", "onCloseContext"]}, + "UserProfileUtils": {"props": ["fetchProfile", "getUser"]}, + "UserProfileUtils": {"strings": ["dispatch({type:\"USER_UPDATE\"", ".USER(", "user cannot"], "exported": false, "value": "exports", "map": { + "fetchProfile": ["fetchProfile error", "USER_PROFILE_FETCH_FAILURE"], + "getUser": ["dispatch({type:\"USER_UPDATE\"", ".USER("] + }}, + "UserSettingsProtoUtils": {"strings": [".updateAsync(\"guilds\"", "INFREQUENT_USER_ACTION"]}, + "UserSettingsUtils": {"props": ["open", "updateAccount"]}, + "Utilities": {"props": ["flatMap", "cloneDeep"]}, + "WindowUtils": {"props": ["minimize", "maximize", "close"]} + }, + "NativeSubComponents": { + "Button": {"props": ["Colors", "Link", "Looks"]}, + "Clickable": {"strings": [".ENTER", "renderNonInteractive", ".handleKeyPress"]}, + "FormTitle": {"strings": [".errorSeparator", "defaultMargin", "required"]}, + "KeybindRecorder": {"strings": [".RECORDING", ".DEFAULT", "toggleRecordMode"]}, + "PopoutContainer": {"strings": ["Unsupported animation config"]}, + "RadioGroup": {"strings": ["itemInfoClassName", "hasSelection", ".radioItemIconClassName"], "funcStrings": ["description"]}, + "SearchBar": {"strings": ["onClear", "query", "inputRef"]}, + "SearchableSelect": {"strings": ["\"SingleSelect\""]}, + "Slider": {"strings": [".stickToMarkers", "sortedMarkers"]}, + "TabBar": {"props": ["Item", "Header", "Panel"]}, + "Table": {"props": ["SortDirection", "defaultProps"]}, + "TextArea": {"strings": ["onKeyDown", "defaultDirty"]}, + "TextInput": {"strings": ["onFocus", "defaultValue", "editable", "errorMessage"]}, + "TooltipContainer": {"strings": ["Tooltip cannot find DOM node", "shouldShowTooltip", "clickable"]} + }, + "LibraryComponents": { + "Anchor": {"strings": ["useDefaultUnderlineStyles", "rel=\"noreferrer noopener\""]}, + "Animations": {"props": ["Controller", "Spring", "animated"]}, + "AppReferencePositionLayer": {"strings": ["\"App\"", "layerContext:"], "funcStrings": ["{children:(", "{ref:"]}, + "AvatarConstants": {"strings": ["\"SIZE_20\"", "\"SIZE_32\"", "HIGH"], "exported": false, "value": "exports", "map": { + "AvatarSizes": ["\"SIZE_20\"", "\"SIZE_32\""] + }}, + "Avatars": {"strings": [".UNKNOWN", "foreignObject", "fromIsMobile"], "exported": false, "value": "exports", "map": { + "AnimatedAvatar": ["{\"compare\":null}"], + "Avatar": ["statusColor", ".UNKNOWN"] + }}, + "Badges": {"strings": ["disableColor", "BADGE", "ROUND"], "exported": false, "value": "exports", "map": { + "BadgeShapes": ["baseShapeRoundRight", "baseShapeRoundLeft"], + "IconBadge": ["icon:"], + "NumberBadge": ["count:"], + "PremiumBadge": ["text:"], + "TextBadge": ["color", "text:"] + }}, + "Checkmark": {"strings": ["M21.7 5.3a1 1 0 0 1 0 1.4l-12 12a1 1 0 0 1-1.4 0l-6-6a1 1 0 1 1 1.4-1.4L9 16.58l11.3-11.3a1 1 0 0 1 1.4 0Z"]}, + "Emoji": {"strings": ["emojiName", "shouldAnimate", "jumboable"], "funcStrings": ["shouldAnimate"]}, + "EmojiButton": {"strings": ["renderButtonContents", "\"emojiButton\"", "spritePremiumColored"], "funcStrings": ["EMOJI_BUTTON"]}, + "EmojiPicker": {"strings": ["emojiSize", "inspectedExpressionPosition", ",emojiPaddingHorizontal:"], "funcStrings": ["compare"]}, + "Flex": {"props": ["Wrap", "Direction", "Child", "Gutter"]}, + "FlowerStar": {"strings": ["style:", "flowerStarClassName", "allowFullSizedIcon"]}, + "FocusRingScope": {"strings": ["FocusRing", "ringTarget was not"]}, + "FormDivider": {"strings": ["let{className:", "gap:", ",style:", "marginTop:", "marginBottom:"]}, + "FormText": {"strings": ["DESCRIPTION",".Text",".DEFAULT"], "exported": false, "value": "exports", "map": { + "Text": [".DEFAULT", "className"], + "Types": ["\"DESCRIPTION\"", "\"DEFAULT\""] + }}, + "GuildBadge": {"strings": ["foregroundDarkColor","GUILD_HEADER"]}, + "GuildIcon": {"strings": ["SMOL", "badgeStrokeColor"], "funcStrings": ["render()"]}, + "GuildItem": {"strings": ["guildNode:", "lowerBadge:", "isUnavailable"]}, + "GuildTooltipMutedText": {"strings": ["muteConfig:", "color:\"text-muted\""]}, + "Heading": {"strings": ["data-excessive", "tag"], "funcStrings": ["tag"]}, + "HeaderBarComponents": {"strings": ["isAuthenticated", ".HEADER_BAR"]}, + "Image": {"strings": ["innerRef", "zoomable", "LOADING"]}, + "ImageModal": {"strings": ["MODAL_CAROUSEL_NEXT", "shouldHideMediaOptions"]}, + "ImageModalOuter": {"strings": ["MEDIA_VIEWER", "numMediaItem", "LIGHTBOX"]}, + "LayerContainerComponents": {"strings": ["layerContainerElement", "LayerProvider"], "exported": false, "value": "exports", "map": { + "createLayer": [".createContext"], + "LayerContainer": ["missing parent"] + }}, + "LazyImage": {"protos": ["loadImage", "getRatio", "getSrc"]}, + "Mask": {"props": "Masks"}, + "Menu": {"strings": ["\"flexible\"", "onFocus:", "menuItemProps"]}, + "MessageGroup": {"strings": ["animateAvatar:", "getMessageByReference"]}, + "MessagesPopoutComponents": {"strings": ["canCloseAllMessages", "loadingMore"], "exported": false, "value": "exports", "map": { + "EmptyState": ["backgroundImage"], + "Header": ["TEXT", "title"], + "Popout": ["renderHeader", "renderEmptyState"] + }}, + "ModalComponents": {"strings": ["HORIZONTAL_REVERSE", "withCircleBackground", "headerIdIsManaged"], "exported": false, "value": "exports", "map": { + "ModalCloseButton": ["withCircleBackground", "BLANK"], + "ModalHeader": ["headerIdIsManaged", "Direction.HORIZONTAL"], + "ModalRoot": ["fullscreenOnMobile", "ENTERING"], + "ModalSize": ["\"DYNAMIC\"", "\"LARGE\""] + }}, + "Paginator": {"strings": ["totalPageCount", "selectedPage", "TRANSPARENT"]}, + "PanelButton": {"strings": ["Masks.PANEL_BUTTON"], "funcStrings": ["{}"]}, + "PopoutFocusLock": {"strings": ["impressionName", "useImperativeHandle"]}, + "PrivateChannelItems": {"strings": ["getRecipientId", "interactiveClassName", "avatar:"], "exported": false, "value": "exports", "map": { + "DirectMessage": ["getUser", "getRecipientId"] + }}, + "ScrollerBase": {"strings": ["paddingFix", "getScrollerState"]}, + "SearchResultsPagination": {"strings": ["maxVisiblePages", "pageSize", "floor"]}, + "SpinnerComponents": {"strings": ["WANDERING_CUBES", "wanderingCubes", ".spinningCircleInner"], "exported": false, "value": "exports", "map": { + "Spinner": [".spinningCircleInner"], + "Types": ["WANDERING_CUBES", "wanderingCubes"] + }}, + "StatusComponents": {"strings": ["OFFLINE", "mask", "isMobile", "status"], "exported": false, "value": "exports", "map": { + "Status": ["status", "mask"], + "Types": ["\"OFFLINE\"", "\"ONLINE\""] + }}, + "Text": {"strings": ["lineClamp:", "fontScaling"], "funcStrings": ["fontScaling"]}, + "Timeout": {"protos": ["start", "stop", "isStarted"]}, + "UserBadges": {"strings": ["QUEST_CONTENT_VIEWED", "\"PRESS_BADGE\"", "badgeClassName"]}, + "UserPopout": {"strings": ["t.isNonUserBot()", "onHide", "user:"]}, + "UserPopoutItem": {"strings": ["userId", "renderSubmenu"]}, + "UserSummaryItem": {"strings": ["popoutUserId:", "dimEmptyUsers"]}, + "VideoForwardRef": {"strings": ["HTMLSourceElement", "autoPlay"], "funcStrings": ["externalRef"]} + }, + "ContextMenuTypes": { + "AudioDeviceContextMenu": "audio-device-context", + "ChannelContextMenu": "channel-context", + "DeveloperContextMenu": "dev-context", + "GroupDMContextMenu": "gdm-context", + "GuildContextMenu": "guild-context", + "GuilDiscoveryContextMenu": "guild-discovery-context-menu", + "GuildEntryContextMenu": "guild-entry-context", + "GuildHeaderContextMenu": "guild-header-popout", + "ImageContextMenu": "image-context", + "MessageContextMenu": "message", + "TextAreaContextMenu": "textarea-context", + "TextContextMenu": "text-context", + "ThreadContextMenu": "thread-context", + "UserContextMenu": "user-context", + "UserSettingsCogContextMenu": "user-settings-cog" + }, + "PatchModules": { + "Account": {"strings": ["handleOpenSettingsContextMenu", "shouldShowSpeakingWhileMutedTooltip", "renderNameZone"]}, + "AccountButtons": {"strings": ["handleOpenSettingsContextMenu", "shouldShowSpeakingWhileMutedTooltip", "shouldShowOutputDeviceChangedTooltip"]}, + "AccountPopout": {"strings": ["UserProfileAccountPopout", "ACCOUNT_POPOUT"]}, + "AccountName": {"strings": ["accountProfilePopoutWrapper", ".avatarWrapper", "renderNameTag"]}, + "AnalyticsContext": {"props": ["ObjectTypes", "Objects", "Pages", "Sections"]}, + "AuditLogEntry": {"protos": ["renderRoleUpdate", "renderChangeDetails", "getActionTypeColor", "renderPermissionUpdate"]}, + "AuthWrapper": {"protos": ["renderDefault", "renderMobile", "mobileReplaceWith"]}, + "Autocomplete": {"strings": ["autocompleteInner", "innerClassName:", "autocomplete"]}, + "AutocompleteChannelResult": {"strings": ["channel:", "category:", ".type===", ".GUILD_CATEGORY?", ".default", "className"], "noSearch": true}, + "AutocompleteRoleResult": {"strings": [".roleStyle", "\"username\"", "hideDescription"], "noSearch": true}, + "AutocompleteRowContentPrimary": {"strings": [".jsx)(\"div\"", "autocompleteRowContentPrimary"]}, + "AutocompleteRowContentSecondary": {"strings": [".jsx)(", "autocompleteRowContentSecondary"]}, + "AutocompleteRowIcon": {"strings": [".jsx)(\"div\"", "autocompleteRowIcon"]}, + "AutocompleteUserResult": {"strings": ["hidePersonalInformation:", ".descriptionDiscriminator", "\"#\"", "Sizes.SIZE_24"], "noSearch": true}, + "BlobMask": {"strings": ["\"BlobMask\"", "badgeMaskSize"]}, + "BlobMaskInner": {"strings": ["lower_badge_masks", "upper_badge_masks"], "noSearch": true}, + "BlockedMessageGroup": {"strings": ["collapsed-message-item", "isBeforeGroup", "collapsedReason"]}, + "ChannelCall": {"strings": ["maybeLeaveFullScreen", "handleFullscreenParticipant", "CHANNEL_CALL_POPOUT"]}, + "ChannelCallHeader": {"strings": ["focusedApplication:", "focusedParticipant:", ".getSelectedParticipant", "appContext"]}, + "ChannelCallGrid": {"strings": ["totalNumberOfParticipants:", "keyExtractor:", "channel_user_limit:"]}, + "ChannelCallVideoParticipants": {"strings": [".tileSizer", "participantTileWidth:", ".COVER"]}, + "ChannelEmptyMessages": {"strings": [".IS_JOIN_REQUEST_INTERVIEW_CHANNEL", "showingBanner:"]}, + "ChannelFloatingSidebar": {"strings": ["messageRequestSidebarWidth", "floatingLayer:", "chatLayerWrapper"]}, + "ChannelItem": {"strings": ["hasActiveThreads", "hasUsersInVoiceChannel", "UNREAD_IMPORTANT"]}, + "ChannelItemIcon": {"strings": ["withGuildIcon:", "locked:", ".iconContainerWithGuildIcon"], "noSearch": true}, + "ChannelMembers": {"strings": ["MEMBER_LIST_VIEWED", "getDimensions", "member-"], "noSearch": true}, + "ChannelMembersThread": {"strings": ["MEMBER_LIST_VIEWED", "hiddenMembers", "PRIVATE_THREAD"], "noSearch": true}, + "ChannelPins": {"strings": ["PINNED_MESSAGES", "renderEmptyState", "listName"]}, + "ChannelReply": {"strings": [".messageReference", ".REPLY_MENTION_OFF", ".mentionIcon"]}, + "ChannelSidebar": {"strings": ["ACCOUNT_PANEL", "RTC_CONNECTION_PANEL", "ACCOUNT_A11Y_LABEL"]}, + "ChannelsList": {"strings": ["this.jumpToVoiceChannels", "guildChannels:", "renderBottomUnread"], "noSearch": true}, + "ChannelTextAreaButtons": {"strings": [".button", "\"emoji\"", "activeCommandOption"]}, + "ChannelTextAreaContainer": {"strings": ["uploadPromptCharacterCount", "onSelectSticker", "handleAutocompleteVisibilityChange"], "noSearch": true}, + "ChannelTextAreaCounter": {"strings": ["iconOnly", ".CHARACTER_COUNT"]}, + "ChannelTextAreaEditor": {"strings": ["onHideAutocomplete", "onMaybeShowAutocomplete", "uploadPromptCharacterCount", "_focusBlurQueue"]}, + "ChannelTextAreaForm": {"strings": ["renderApplicationCommandIcon", "handleTextareaChange", "handleSendMessage"]}, + "ChannelThreadItem": {"strings": [".typeThread", ".modeUnread", ".GUILD_CHANNEL_LIST"]}, + "CircleIconButton": {"strings": ["icon:", "lowerBadgeSize", "tooltip:"]}, + "Clickable": {"strings": [".ENTER", "renderNonInteractive", ".handleKeyPress"]}, + "CloseButton": {"strings": [".closeButton", ".closeIcon", "offset:{"]}, + "ConnectedLazyImageZoomable": {"strings": ["isWindowFocused:", ",appContext:"]}, + "CustomStatusModalWithPreview": {"strings": ["showRemainingCharacterCount", "optionClassName"]}, + "DefaultChannelEmptyMessage": {"strings": [".CHANNEL_WELCOME", ".titleName", "handlePersonalize"]}, + "DirectMessage": {"strings": ["getRecipientId", "Controller", "handleContextMenu", ".DM"]}, + "DirectMessageAddPopout": {"strings": [".COPY_INSTANT_INVITE", ".handleInviteUsers=", ".selectedUsers", ".mobileToolsContainer"]}, + "DirectMessageAddPopoutRow": {"strings": [".weightMedium", ".friendSelected", ".friendWrapper"]}, + "DiscordTag": {"strings": ["hidePersonalInformation", "isVerifiedBot", "botType"]}, + "Embed": {"strings": ["shouldShowStaticPlaceholder", "renderEmbedContent", "iconProxyURL"]}, + "EmojiPicker": {"strings": ["emojiSize", ",emojiPaddingHorizontal:"]}, + "EmojiPickerHeader": {"strings": ["defaultSearch", "headerClassName", "onBurstReaction"]}, + "EmojiPickerListRow": {"strings": ["emojiSize", "surrogateCodePoint", "showEmojiFavoriteTooltip"]}, + "FocusRingScope": {"strings": ["FocusRing", "ringTarget was not"]}, + "FolderHeader": {"strings": ["folderNode:", "disableWrapper", "FOLDER_SIZE"]}, + "FolderContextMenu": {"strings": ["GUILD_ACTIONS_MENU_LABEL", "navId:\"guild", "folderId:"], "noSearch": true}, + "FolderIcon": {"strings": ["folderNode:", "hovered:", "color:\"currentColor\""], "noSearch": true}, + "FolderIconWrapper": {"strings": ["folderNode:", "treeitem", "isMentionLow"]}, + "FolderItem": {"strings": ["folderNode:", "--custom-folder-color"]}, + "FolderItemWrapper": {"strings": ["folderNode:", "isFolderExpanded", "defaultFolderName:"]}, + "FolderSettingsModal": {"strings": [".handleNameChange", ".handleSubmit", "folderName:"], "noSearch": true}, + "GuildBadge": {"strings": ["foregroundDarkColor", "getBadgeCategory"]}, + "GuildChannelListContextMenu": {"strings": ["GUILD_ACTIONS_MENU_LABEL", "\"guild-context\",\"aria-label\""], "noSearch": true}, + "GuildContextMenu": {"strings": ["GUILD_ACTIONS_MENU_LABEL", "navId:\"guild", "GuildContextMenu"], "noSearch": true}, + "GuildFavorites": {"strings": [".favoriteIcon", "favoriteServerMuted", "liveStage"]}, + "GuildHeader": {"strings": ["bannerVisible:", "disableBannerAnimation:", "onContextMenu"]}, + "GuildIcon": {"strings": [",animate:", ",size:", "{active:!0"], "noSearch": true}, + "GuildIconWrapper": {"strings": ["badgeStrokeColor:", "textScale:"]}, + "GuildInvitationRow": {"strings": [".getSelectedInviteMetadata", ".getSuggestedProps", "inviteKey:"], "noSearch": true}, + "GuildItem": {"strings": ["spacing:12", "disabled:", "guild:"]}, + "GuildItemWrapper": {"strings": ["guildNode:", "lowerBadge:", "isUnavailable"]}, + "GuildItemIcon": {"strings": ["\"always\"", ".acronym", "width:48,height:48"], "noSearch": true}, + "GuildItemWrapper": {"strings": [".pauseBackground", ".isUnavailable", "guildJoinRequestStatus"]}, + "GuildsBar": {"strings": ["heightBeforeGuilds", "disableAppDownload", "handleJumpToGuild"], "noSearch": true}, + "GuildsBarHeader": {"strings": ["GUILD_MEMBER_VERIFICATION_FOR_HUB", "isCurrentUserGuest", "hideDms"], "noSearch": true}, + "GuildsBarTree": {"strings": ["guildDiscoveryButton", "disableAppDownload", "getGuildsTree"], "noSearch": true}, + "GuildSidebar": {"strings": ["\"guildsnav\"", "unreadMentionsIndicatorBottom"]}, + "HeaderBar": {"strings": ["onDoubleClick", "scrollable", "childrenBottom"]}, + "HeaderBarChannelName": {"strings": ["onDoubleClick", ".setName", "maxLen:"]}, + "HeaderBarContainer": {"strings": ["isAuthenticated", ".HEADER_BAR"]}, + "HeaderBarDiscovery": {"strings": ["variant", "useMemo(()=>\"overlay\""]}, + "HeaderBarRecipient": {"strings": ["getRecipientId", ".avatar", "getName"]}, + "HeaderBarTitle": {"strings": [".titleWrapper", ",onContextMenu", "forceLevel:"]}, + "HeaderBarTitleIcon": {"strings": ["HEADER_BAR_BADGE", ".iconBadge", "iconClassName:"]}, + "HomeButtonDefault": {"strings": [".BUTTON_HOME", "showProgressBadge"], "noSearch": true}, + "I18nLoader": {"strings": ["default.locale", ".loading", "__OVERLAY__"]}, + "IconBadge": {"strings": [".icon}", "ROUND,disableColor", ".iconBadge,"]}, + "ImageModal": {"strings": ["MODAL_CAROUSEL_NEXT", "shouldHideMediaOptions"], "noSearch": true}, + "ImageModalBar": {"strings": [".topBar", "item", "closeButton"]}, + "ImageModalButtons": {"strings": [".actionButtons", "\"IMAGE\"", "FOCUS_SENSITIVE"]}, + "InboxHeader": {"strings": [".UNREADS", ".headerTitle", ".controls"]}, + "IncomingCallModal": {"strings": ["mainChannelInfo", "previewCamera", "RingingType.INCOMING"]}, + "InviteGuildName": {"strings": ["{guild:",".guildName",".guildNameWrapper", ".name", "jsx)(\"span\","], "noSearch": true}, + "KeybindRecorder": {"strings": [".RECORDING", ".DEFAULT", "toggleRecordMode"], "noSearch": true}, + "LayerProvider": {"strings": ["layerContainerElement", "getContextValue"]}, + "LayersProvider": {"strings": ["._currentlyTransitioningKeys", "._keysToEnter", "._keyChildMapping"]}, + "LazyImage": {"protos": ["loadImage", "getRatio", "getSrc"]}, + "LazyImageZoomable": {"strings": ["zoomThumbnailPlaceholder", "onCloseImage"], "noSearch": true}, + "ListItemTooltip": {"strings": ["asContainer", "forceOpen", "disableWrapper"]}, + "MemberListItem": {"strings": ["MemberListItem", "colorRoleName"]}, + "MemberRole": {"strings": ["onRemoveRole", "currentColor", "role:"]}, + "Mention": {"strings": ["wrapper", "roleColors", "gradient"]}, + "Menu": {"strings": ["\"empty\"", "getItemProps", "isUsingKeyboardNavigation"]}, + "Message": {"strings": ["childrenMessageContent", "childrenRepliedMessage", "zalgo", "BaseMessage"]}, + "MessageAccessories": {"protos": ["renderGiftCodes", "renderEmbeds", "renderActivityInvite"]}, + "MessageButtons": {"strings": [".innerClassName", "message", "SEND_FAILED?"]}, + "MessageContent": {"strings": [".SEND_FAILED", "editedTimestamp", "MessageContent"]}, + "MessageContextMenu": {"strings": ["MESSAGE_ACTIONS_MENU_LABEL", "navId:\"message", "getGuildId"]}, + "MessageGroup": {"strings": [".ephemeral", "animateAvatar:", "getMessageByReference"]}, + "MessageHeader": {"strings": ["showTimestampOnHover", "usernameClassName", "isVisibleOnlyOnHover:"], "noSearch": true}, + "MessageReply": {"strings": [".isFirstMessageInForumPost", "referencedUsernameProfile:", "referencedMessage"]}, + "MessageReplyHeader": {"strings": ["pollBadgeReplied", "referencedMessage"]}, + "MessageReplyHeader": {"strings": ["pollBadgeReplied", "referencedMessage"]}, + "Messages": {"strings": ["messageGroupSpacing", "groupSpacing", "jumpToPresent"]}, + "MessageSearchResultContextMenu": {"strings": ["MESSAGE_ACTIONS_MENU_LABEL", "navId:\"message"], "nonStrings": ["getGuildId"]}, + "MessageTimestamp": {"strings": ["asContainer:", "timeFormatted:"], "noSearch": true}, + "MessageToolbar": {"strings": ["useMessageUtilitiesProps", "hasDeveloperMode", "emojiPicker:"], "noSearch": true}, + "MessageUsername": {"strings": ["\"username\"", "colorString", "compact"]}, + "Modal": {"strings": ["actionBarInputLayout", "listProps", "notice:"]}, + "ModalActions": {"strings": ["actionBarTrailingFullWidth", "autoFocus", "primary"]}, + "ModalContent": {"strings": ["let{controls:", "listProps", "children:"]}, + "ModalHeader": {"strings": ["headerCentered", "headerTitle", "color"]}, + "ModalFooter": {"strings": ["\"footer\"", "children:", ".section)"]}, + "ModalRoot": {"strings": ["rootWithShadow", "ImpressionTypes.MODAL", "transitionState:"]}, + "NameContainer": {"strings": ["\"listitem\"", "wrappedName", "innerClassName"], "noSearch": true}, + "NameContainerAvatar": {"strings": ["shouldAnimateStatus", "avatarDecoration", "MEMBER_USER"], "noSearch": true}, + "NameContainerDecorators": {"strings": ["lostPermissionTooltipText", "premiumSince", "isOwner"], "noSearch": true}, + "NameContainerInner": {"strings": ["primaryGuild", ".clanTag", "hideClanTag"], "noSearch": true}, + "NameTag": {"strings": ["invertColor:", "usernameClass", "discriminatorClass"]}, + "NavItem": {"strings": ["pathname", "modules.guildbar", "fontSize:"]}, + "Note": {"strings": [".noteRef", "handleBlur", ".SPACE"]}, + "NowPlayingHeader": {"strings": ["ACTIVITY_FEED_NOW_PLAYING_HEADER", "partiedMembers"]}, + "NowPlayingItem": {"strings": [".wrapper", "padded:"]}, + "PanelButton": {"strings": ["Masks.PANEL_BUTTON"]}, + "ParticipantsForSelectedParticipant": {"strings": ["ACTIVITY", "maxVisibleUsers", "participantType"]}, + "PeopleList": {"strings": [".SECTION_NO_RESULTS", ".section", "SUGGESTIONS"]}, + "PeopleListItem": {"strings": ["height:new", "isFocused", "onOtherHover"]}, + "PeopleListItemBlocked": {"strings": [".listItemContents", "UNKNOWN", ".BLOCKED"]}, + "PeopleListItemFriend": {"strings": [".getMutablePrivateChannels", ".handleOpenPrivateChannel", "isActiveRow"]}, + "PeopleListItemPending": {"strings": ["PENDING_INCOMING", ".DENY", "addRelationship"]}, + "PeopleListSectionedLazy": {"strings": ["rows", "id:\"people-list\""]}, + "PeopleListSectionedNonLazy": {"strings": ["rows", "id:\"people\""]}, + "PictureInPictureVideo": {"strings": [".topControls", ".bottomControl", "screenMessage", "renderBottomLeftControls:"]}, + "PrivateChannel": {"strings": ["isMultiUserDM", "location:\"PrivateChannel\"", "nameplate"], "noSearch": true}, + "PrivateChannelRecipients": {"strings": [".membersWrap", "recipients"]}, + "PrivateChannelsList": {"strings": ["privateChannelIds", "sections:"], "noSearch": true}, + "QuickMessage": {"strings": ["QUICK_DM_USER", ".NORMAL"]}, + "QuickSwitchChannelResult": {"strings": ["getAccessibilityLabel", ".contentUnread", "renderVoiceStates"]}, + "QuickSwitcher": {"protos": ["getRowId", "renderSection", "renderResults", "renderProtip", "search"]}, + "QuickSwitchGroupDMResult": {"strings": ["getAccessibilityLabel", ".contentUnread", "dmIconContainer"]}, + "QuickSwitchGuildResult": {"strings": ["getAccessibilityLabel", ".contentUnread", "guildIconContainer"]}, + "QuickSwitchUserResult": {"strings": ["getAccessibilityLabel", ".contentUnread", "getDisplayNickname"]}, + "Reactions": {"strings": ["\"reaction\"", "showImmediate:", "emojiSize:"], "noSearch": true}, + "ReactionsModal": {"strings": [".BURST", ".reactions", ".emoji.name", "selectedReaction"], "noSearch": true}, + "ReactionsModalUser": {"strings": ["currentUser cannot be undefined", "reactorDefault"]}, + "ReactionsModalUsers": {"strings": ["hasMore", "emoji", "spinnerMore"], "noSearch": true}, + "RecentMentions": {"strings": ["canCloseAllMessages", "RECENT_MENTIONS", "badgeState:"]}, + "RecentsChannelHeader": {"strings": [".subtextContainer", ".channelNameSpan", ".channelNameHeader", "gotoChannel:"]}, + "RichChannelMention": {"strings": ["iconType:", "children:\"#\"", "\"locked\""], "noSearch": true}, + "RichRoleMention": {"strings": ["ROLE_MENTION", "roleId:", "getRole"]}, + "RichUserMention": {"strings": ["hidePersonalInformation", "getUser", "asContainer"], "noSearch": true}, + "RTCConnection": {"strings": [".NOISE_CANCELLATION_POPOUT", ".voicePanelIntroductionButton"]}, + "RTCConnectionVoiceUsers": {"strings": ["PlusSmallIcon", "selfVideo", "voiceStates:"]}, + "SearchBar": {"strings": ["this.inputRef", ".containerRef", ".handleOnChange"]}, + "SearchPopout": {"strings": [".handleHintClick", ".selectedIndex", "token.start"]}, + "SearchPopoutOption": {"strings": [".plusIcon", ".option,", ".filter"]}, + "SearchResult": {"strings": ["onJump:", "message:", "handleMessageClick"]}, + "SearchResults": {"strings": [".WARNING", "isSearching", "paginationTotalCount"], "noSearch": true}, + "SearchResultsHeader": {"strings": ["onPopoverRequestClose", "onSearchModeChange"]}, + "SearchResultsInner": {"strings": ["totalResults", "resultsBlocked", "\"search-results\""], "noSearch": true}, + "SearchResultsPagination": {"strings": ["maxVisiblePages", "pageSize", "floor"]}, + "SettingsView": {"strings": ["renderSettingsSectionTabBarItem", "PROFILE_CUSTOMIZATION", "badgeCount"]}, + "Shakeable": {"protos": ["shake", "getDefaultAnimProps", "stop"]}, + "Spoiler": {"strings": [".removeObscurity", "onReveal:"]}, + "SpoilerWarning": {"strings": [".spoilerWarning", ".SPOILER"]}, + "StandardSidebarView": {"strings": ["standardSidebarView", "sidebarTheme:", "mobileSidebarHeader"]}, + "SystemMessageThreadCreated": {"strings": ["threadOnClick:", ".SYSTEM_MESSAGE_THREAD_CREATED", "viewThreadsOnClick:"]}, + "SystemMessageWrapper": {"strings": ["unknown message type", "\"SystemMessage\""]}, + "TabBar": {"props": ["Item", "Header", "Panel"]}, + "TextChannelEmptyMessage": {"strings": ["MANAGE_CHANNELS", ".BEGINNING_CHANNEL_DESCRIPTION", "topicHook:"]}, + "TextInput": {"strings": ["onFocus","defaultValue", "editable"], "noSearch": true}, + "ThreadCard": {"strings": ["threadId:", ".container", ".threadName"]}, + "ThreadCardDescription": {"strings": [".bullet", ".lastMessageId"]}, + "ThreadEmptyMessage": {"strings": [",{channel:", ".name", "{channelId:", ".iconWrapper", ".icon}"]}, + "ThreadEmptyMessageAuthor": {"strings": [".threadCreatorName", ".unknownCreatorName", ".getUser"]}, + "ThreadMessageAccessories": {"strings": ["threadMetadata", "castMessageIdAsChannelId", "isSystemMessage"]}, + "ThreadMessageAccessoryMessage": {"strings": [".threadMessageAccessoryPlaceholder", ".threadMessageAccessoryContent", "formatInline:"]}, + "ThreadSidebar": {"strings": ["baseChannelId:", ".SIDEBAR", ".THREAD_HEADER_BAR"]}, + "TitleBar": {"strings": ["PlatformTypes.WINDOWS", "trailing", "onDoubleClick"]}, + "TooltipContainer": {"protos": ["renderTooltip", "setDomElement", "shouldShowTooltip", "toggleShow"]}, + "TooltipContainerWithShortcut": {"strings": ["asContainer", "shouldShow", "keyboardShortcut"]}, + "TransitionGroup": {"protos": ["performAppear", "performEnter", "performLeave"]}, + "TypingUsers": {"strings": ["Easing.quad", ".emphasizeSlowmodeCooldown", ".sequence("]}, + "UnavailableGuildsButton": {"strings": [".errorInner", ".guildsError"]}, + "UnreadDMs": {"strings": ["getMutablePrivateChannels", "selectedVoiceGuildId", "selectedVoiceChannelId:"]}, + "UserBadges": {"strings": ["QUEST_CONTENT_VIEWED", "\"PRESS_BADGE\"", "badgeClassName"], "noSearch": true}, + "UserBanner": {"strings": [".gifTag", "pendingBanner"], "noSearch": true}, + "UserBannerMask": {"strings": [".bannerSVGWrapper", "overrideBannerWidth", "foreignObject"]}, + "UserGenericContextMenu": {"strings": ["USER_ACTIONS_MENU_LABEL", "navId:\"user", ".USER_GENERIC_MENU"], "noSearch": true}, + "UserHeaderAvatar": {"strings": ["onOpenProfile", "PRESS_VIEW_PROFILE", "avatarDecorationSrc"], "noSearch": true}, + "UserHeaderStatus": {"strings": ["PROFILE_CUSTOM_STATUS", "placeholderText"], "noSearch": true}, + "UserHeaderUsername": {"strings": ["pronouns", "nicknameIcons", "discriminatorClass"], "noSearch": true}, + "UserInfo": {"strings": ["hasUniqueUsername", "isMobile:"]}, + "UserMemberContextMenu": {"strings": ["USER_ACTIONS_MENU_LABEL", "navId:\"user", ".GUILD_CHANNEL_USER_MENU"], "noSearch": true}, + "UserMention": {"strings": ["inlinePreview", "\"@\"", "renderPopout:"]}, + "UserPanelHeader": {"strings": ["PRESS_VIEW_PROFILE", "DM_PANEL"], "noSearch": true}, + "UserPopout": {"strings": ["{user:", "userId:", "\"Unexpected missing user\")", "getUser"], "noSearch": true}, + "UserPopoutHeader": {"strings": ["PRESS_VIEW_PROFILE", "PROFILE_POPOUT"], "noSearch": true}, + "UserPopoutStatusBubble": {"strings": ["CustomStatusBubble", "action:\"HOVER_CUSTOM_STATUS\""], "noSearch": true}, + "UserPopoutStatusBubbleEmpty": {"strings": ["trackUserProfileAction", "action:\"PRESS_ADD_CUSTOM_STATUS\""], "noSearch": true}, + "UserProfile": {"strings": [".Provider", "themeType:", "secondaryColor"]}, + "UserProfileHeader": {"strings": ["\"header\"", "user", "PROFILE_MODAL"], "noSearch": true}, + "UserProfileInfoSection": {"strings": ["trackUserProfileAction:", "\"PRESS_APP_CONNECTION\"", "onUpdate"], "noSearch": true}, + "UserProfileMutualGuilds": {"strings": ["mutualGuilds", ".listScroller", ".emptyText"]}, + "UserSummaryItem": {"protos": ["renderMoreUsers", "renderUsers", "renderIcon"]}, + "UserSettingsAppearance": {"strings": ["handleMessageDisplayModeChange", "APPEARANCE_MESSAGE_DISPLAY_COMPACT", "handleFontSizeChange"], "noSearch": true}, + "UserThemeContainer": {"strings": ["profileUi", "\"VIEW\"", ".Provider"], "noSearch": true}, + "VideoBackground": {"strings": ["backgroundSrc:", "pulseSpeakingIndicator:", ".avatarWrapper"]}, + "VoiceUser": {"strings": ["speaking", "collapsed", "userNameClassName"]}, + "VoiceUsers": {"strings": ["hidePreview", "previewIsOpen", ".MOVE_MEMBERS"]} + }, + "ModuleUtilsConfig": { + "QueuedComponents": [], + "ContextMenuTypes": [], + "ContextMenuTypesMap": {}, + "ContextMenuSubItemsMap": {}, + "PatchTypes": [], + "InstanceFunctions": [], + "PatchMap": {}, + "Finder": {}, + "LoadedInComponents": {} + }, + "SvgIcons": { + "ACTIVITY": { + "defaultProps": { + "width": 16, + "height": 16 + }, + "icon": "" + }, + "ACTIVITY_DISABLED": { + "defaultProps": { + "width": 16, + "height": 16, + "foreground": 16 + }, + "icon": "" + }, + "ARROW_DOWN": { + "defaultProps": { + "width": 18, + "height": 18 + }, + "icon": "" + }, + "ARROW_UP": { + "defaultProps": { + "width": 18, + "height": 18 + }, + "icon": "" + }, + "BOOST": { + "defaultProps": { + "width": 24, + "height": 24, + "foreground": "" + }, + "icon": "" + }, + "CALENDAR": { + "icon": "" + }, + "CHANGELOG": { + "icon": "" + }, + "CHECKBOX": { + "defaultProps": { + "background": "", + "foreground": "" + }, + "icon": "" + }, + "CHECKBOX_EMPTY": { + "defaultProps": { + "foreground": "" + }, + "icon": "" + }, + "CHECKMARK": { + "defaultProps": { + "width": 18, + "height": 18 + }, + "icon": "" + }, + "CHECKMARK_CIRCLE": { + "icon": "" + }, + "CLOCK": { + "icon": "" + }, + "CLOSE": { + "defaultProps": { + "width": 12, + "height": 12 + }, + "icon": "" + }, + "CLOSE_CIRCLE": { + "icon": "" + }, + "COG": { + "icon": "" + }, + "CROWN": { + "icon": "" + }, + "DOWNLOAD": { + "defaultProps": { + "width": 16, + "height": 16 + }, + "icon": "" + }, + "DROPPER": { + "defaultProps": { + "width": 14, + "height": 14 + }, + "icon": "" + }, + "EYE": { + "icon": "" + }, + "FAVORITE": { + "icon": "" + }, + "FAVORITE_FILLED": { + "icon": "" + }, + "FOLDER": { + "icon": "" + }, + "GAMEPAD": { + "icon": "" + }, + "GAMEPAD_DISABLED": { + "icon": "" + }, + "GITHUB": { + "icon": "" + }, + "GLOBE": { + "defaultProps": { + "width": 20, + "height": 20 + }, + "icon": "" + }, + "GRADIENT": { + "defaultProps": { + "width": 36, + "height": 36 + }, + "icon": "" + }, + "HEART": { + "defaultProps": { + "width": 16, + "height": 16 + }, + "icon": "" + }, + "INFO": { + "icon": "" + }, + "LEFT_CARET": { + "icon": "" + }, + "LEFT_DOUBLE_CARET": { + "icon": "" + }, + "LOCK_CLOSED": { + "icon": "" + }, + "LOCK_OPEN": { + "icon": "" + }, + "METAMASK": { + "icon": "" + }, + "MORE": { + "icon": "" + }, + "NOVA_AT": { + "icon": "" + }, + "NOVA_PIN": { + "icon": "" + }, + "NOVA_TRASH": { + "icon": "" + }, + "NUMPAD": { + "icon": "" + }, + "OPEN_EXTERNAL": { + "icon": "" + }, + "OVERLAY": { + "icon": "" + }, + "OVERLAY_DISABLED": { + "icon": "" + }, + "PATREON": { + "icon": "" + }, + "PAYPAL": { + "icon": "" + }, + "PENCIL": { + "defaultProps": { + "width": 16, + "height": 16 + }, + "icon": "" + }, + "PHANTOM": { + "icon": "" + }, + "PIN": { + "defaultProps": { + "width": 16, + "height": 16 + }, + "icon": "" + }, + "PODIUM": { + "icon": "" + }, + "QUESTIONMARK": { + "icon": "" + }, + "QUESTIONMARK_ACTIVITY": { + "defaultProps": { + "width": 40, + "height": 40 + }, + "icon": "" + }, + "QUOTE": { + "icon": "" + }, + "RAW_TEXT": { + "icon": "" + }, + "REMOVE": { + "icon": "" + }, + "RIGHT_CARET": { + "icon": "" + }, + "RIGHT_DOUBLE_CARET": { + "icon": "" + }, + "SEARCH": { + "defaultProps": { + "width": 18, + "height": 18 + }, + "icon": "" + }, + "SPEAKER": { + "icon": "" + }, + "STREAM": { + "icon": "" + }, + "TRASH": { + "icon": "" + }, + "WARNING": { + "icon": "" + }, + "ZOOM": { + "icon": "" + } + }, + "CustomClassModules": { + "BDFDB": { + "BDFDBundefined": "BDFDB_undefined", + "avatarDisabled": "disabled_6G33EE", + "avatarBadge": "badge_e9c564", + "avatarBadgeAvatar": "avatar_e9c564", + "avatarBadgeDev": "dev_e9c564", + "avatarBadgeHas": "hasBadge_e9c564", + "avatarBadgeSupporter": "supporter_e9c564", + "avatarBadgeSupporterTier1": "tier1_e9c564", + "avatarBadgeSupporterTier2": "tier2_e9c564", + "avatarBadgeSupporterTier3": "tier3_e9c564", + "avatarBadgeSupporterTier4": "tier4_e9c564", + "cardDisabled": "cardDisabled_255783", + "cardHorizontal": "horizontal_255783", + "cardInner": "inner_255783", + "cardWrapper": "card_255783", + "changeLogButton": "changeLogButton_b6d233", + "charCounter": "counter_89de42", + "changeLogModal": "changeLogModal_bad42d", + "channelHeaderDivider": "divider_348c72", + "collapseContainer": "container_54728c", + "collapseContainerCollapsed": "collapsed_54728c", + "collapseContainerHeader": "header_54728c", + "collapseContainerInner": "inner_54728c", + "collapseContainerMini": "container_54728c containerMini_54728c", + "collapseContainerTitle": "title_54728c", + "colorPicker": "colorPicker_15820d", + "colorPickerAlpha": "alpha_15820d", + "colorPickerAlphaCheckered": "alpha-checkered", + "colorPickerAlphaCursor": "alpha_cursor", + "colorPickerAlphaHorizontal": "alpha-horizontal", + "colorPickerGradient": "gradient_15820d", + "colorPickerGradientCheckered": "gradient-checkered", + "colorPickerGradientCursor": "gradient_cursor", + "colorPickerGradientCursorEdge": "gradient-cursor-edge", + "colorPickerGradientCursorSelected": "gradient-cursor-selected", + "colorPickerGradientHorizontal": "gradient-horizontal", + "colorPickerGradientButton": "gradientButton_15820d", + "colorPickerGradientButtonEnabled": "enabled_15820d", + "colorPickerSwatches": "swatches_15820d", + "colorPickerSwatchesDisabled": "disabled_15820d", + "colorPickerSwatchSelected": "selected_15820d", + "colorPickerSwatchSingle": "single_15820d", + "colorPickerSwatchSingleWrapper": "swatch_15820d", + "confirmModal": "confirmModal_0047c2", + "cursorDefault": "cursorDefault_2ea504", + "cursorPointer": "cursorPointer_2ea504", + "dateInputButton": "dateInputButton_5aa63f", + "dateInputButtonSelected": "selected_5aa63f", + "dateInputControls": "dateInputControls_5aa63f", + "dateInputField": "dateInputField_5aa63f", + "dateInputInner": "dateInputInner_5aa63f", + "dateInputPreview": "dateInputPreview_5aa63f", + "dateInputPreviewPrefix": "dateInputPreviewPrefix_5aa63f", + "dateInputPreviewSuffix": "dateInputPreviewSuffix_5aa63f", + "dateInputWrapper": "dateInputWrapper_5aa63f", + "dividerDefault": "dividerDefault_a85fe2", + "favButtonContainer": "favbutton_9d91f0", + "guild": "guild_9e0140", + "guildBadgeLowerLeft": "lowerLeftBadge_820890", + "guildBadgeUpperLeft": "upperLeftBadge_820890", + "guildsLabel": "label_caf933", + "guildSummaryClickableIcon": "clickableIcon_3df50b", + "guildSummaryContainer": "container_3df50b", + "guildSummaryEmptyGuild": "emptyGuild_3df50b", + "guildSummaryIcon": "icon_3df50b", + "guildSummaryIconContainer": "iconContainer_3df50b", + "guildSummaryIconContainerMasked": "iconContainerMasked_3df50b iconContainer_3df50b", + "guildSummaryMoreGuilds": "moreGuilds_3df50b", + "guildSummarySvgIcon": "icon_3df50b", + "guildVoiceList": "guildVoiceList_85a81a", + "hoverCardButton": "button_916131", + "hotkeyResetButton": "resetButton_df7f12", + "hotkeyWrapper": "recorder_df7f12", + "imageModal": "imageModal_10e0a6", + "inputMini": "mini_c655a8", + "inputNumberButton": "button_afe673", + "inputNumberButtonDown": "down_afe673 button_afe673", + "inputNumberButtonUp": "up_afe673 button_afe673", + "inputNumberButtons": "buttons_afe673", + "inputNumberWrapper": "numberInputWrapper_afe673", + "inputNumberWrapperDefault": "numberInputWrapperDefault_afe673 numberInputWrapper_afe673", + "inputNumberWrapperMini": "numberInputWrapperMini_afe673 numberInputWrapper_afe673", + "inputSuccess": "success_dd8655", + "layerContainerZIndexDisabled": "zIndexDisabled_3da2cb", + "listInput": "listInput_1a306d", + "listInputDelete": "delete_1a306d", + "listInputItem": "item_1a306d", + "listInputItems": "items_1a306d", + "listRow": "listRow_1a306d", + "loadingIcon": "loadingIcon_783e4b", + "loadingIconWrapper": "loadingIconWrapper_783e4b", + "overflowEllipsis": "ellipsis_21b794", + "pagination": "pagination_9d3f6f", + "paginationBottom": "bottom_9d3f6f", + "paginationList": "list_9d3f6f", + "paginationListAlphabet": "alphabet_9d3f6f", + "paginationListAlphabetChar": "alphabetChar_9d3f6f", + "paginationListAlphabetCharDisabled": "disabled_9d3f6f", + "paginationListContent": "listContent_9d3f6f", + "paginationListMini": "miniList_9d3f6f", + "paginationMini": "mini_9d3f6f", + "paginationTop": "top_9d3f6f", + "popoutArrow": "popoutArrow_36d982", + "popoutArrowBottom": "popoutArrowBottom_36d982", + "popoutArrowTop": "popoutArrowTop_36d982", + "popoutWrapper": "popout_36d982", + "quickSelectWrapper": "quickSelectWrapper_b394cd", + "marginLeft4": "marginLeft4_c3c148", + "marginLeft8": "marginLeft8_c3c148", + "menuColorCustom": "colorCustom_07d237", + "menuItemHint": "hint_07d237", + "messageToolbarExtrasTooltip": "tooltip_8f4sa2", + "messagesPopoutChannelSeparator": "channelSeparator_c60233", + "modalHeaderShade": "shade_e62d17", + "modalHeaderHasSibling": "hasSiblings_e62d17", + "modalNoScroller": "noScroller_e62d17", + "modalSidebar": "sidebar_e62d17", + "modalTabContent": "tabContent_e62d17", + "modalTabContentOpen": "open_e62d17", + "modalSubInner": "inner_e62d17", + "modalTextContent": "text_e62d17", + "modalWrapper": "modal_e62d17", + "multiInput": "multiInput_ec841d", + "multiInputField": "multiInputField_ec841d", + "multiInputFirst": "multiInputFirst_ec841d", + "multiInputLast": "multiInputLast_ec841d", + "multiInputWrapper": "multiInputWrapper_ec841d", + "noticeClosing": "closing_f3337b", + "noticeSuccess": "colorSuccess_f3337b", + "noticeText": "text_f3337b", + "noticeUpdate": "updateNotice_f3337b", + "noticeUpdateButtonAll": "all_f3337b", + "noticeUpdateButtonReload": "reload_f3337b", + "noticeUpdateEntries": "entries_f3337b", + "noticeUpdateEntry": "entry_f3337b", + "noticeUpdateText": "notice-message", + "noticeUpdateSeparator": "separator_f3337b", + "noticeWrapper": "noticeWrapper_f3337b", + "searchBarWrapper": "searchBarWrapper_ddc25b", + "selectWrapper": "selectWrapper_9d6c62", + "settingsGuild": "guild_bd9487", + "settingsGuildDisabled": "disabled_bd9487", + "settingsPanel": "settingsPanel_3aa079", + "settingsPanelList": "settingsList_3aa079", + "settingsPanelListWrapper": "wrapper_3aa079", + "settingsPanelListWrapperMini": "mini_3aa079", + "settingsRow": "settingsRow_c94a44", + "settingsRowDisabled": "disabled_c94a44", + "settingsRowTitleMini": "titleMini_c94a44", + "settingsTableCard": "settingsTableCard_e12760", + "settingsTableCardConfigs": "settingsTableCardConfigs_e12760", + "settingsTableCardLabel": "settingsTableCardLabel_e12760", + "settingsTableHeaderOptions": "headerOptions_e12760", + "settingsTableHeaders": "settingsTableHeaders_e12760", + "settingsTableHeaderVertical": "headerVertical_e12760", + "settingsTableList": "settingsTableList_e12760", + "sidebar": "sidebar_cb295e", + "sidebarContent": "content_cb295e", + "sidebarList": "list_cb295e", + "sliderBubble": "bubble_8baf61", + "switchMini": "mini_27a090", + "svgIcon": "icon_9cab3e", + "svgIconWrapper": "iconWrapper_9cab3e", + "tabBarContainer": "container_450376", + "tabBarContainerBottom": "bottom_450376", + "table": "table_e9cd98", + "tableBodyCell": "bodyCell_e9cd98", + "tableHeaderCell": "headerCell_e9cd98", + "tableHeaderCellSorted": "headerCellSorted_e9cd98", + "textScroller": "textScroller_72a89f", + "themedPopout": "themedPopout_e2c0d0", + "tooltipCustom": "tooltipCustom_dc56aa", + "tooltipNote": "note_dc56aa", + "tooltipRowExtra": "extraRow_dc56aa" + }, + "BD": { + "bdModal": "bd-modal", + "bdModalContent": "bd-modal-content", + "bdMinimalMode": "bd-minimal" + }, + "BetterFriendList": { + "mutualGuilds": "mutualGuilds_d61638", + "nameCell": "nameCell_d61638", + "title": "title_d61638" + }, + "BetterNsfwTag": { + "nsfwTag": "nsfwTag_efc92a" + }, + "BetterSearchPage": { + "pagination": "pagination_4b5900" + }, + "ChatFilter": { + "blocked": "blocked_5616f4", + "blockedStamp": "blockedStamp_5616f4", + "censored": "censored_5616f4", + "censoredStamp": "censoredStamp_5616f4" + }, + "CharCounter": { + "charCounter": "charCounter_c47c43", + "chatCounter": "chatCounter_c47c43", + "counterAdded": "charCounterAdded_c47c43", + "customStatusCounter": "customStatusCounter_c47c43", + "editCounter": "editCounter_c47c43", + "popoutNoteCounter": "popoutNoteCounter_c47c43", + "profileNoteCounter": "profileNoteCounter_c47c43", + "threadCreationCounter": "threadCreationCounter_c47c43", + "userProfileCounter": "userProfileCounter_c47c43" + }, + "CustomStatusPresets": { + "customStatusItem": "customStatusItem_e78cca", + "deleteButton": "deleteButton_e78cca", + "deleteIcon": "deleteIcon_e78cca", + "dragPreview": "dragPreview_e78cca", + "sortableCard": "sortableCard_e78cca", + "sortDivider": "sortDivider_e78cca", + "status": "status_e78cca" + }, + "DisplayServersAsChannels": { + "badge": "badge_71509e", + "colored": "colored_71509e", + "muted": "muted_71509e", + "name": "name_71509e", + "styled": "styledGuildsAsChannels_71509e" + }, + "EmojiStatistics": { + "amountCell": "amountCell_7741f8", + "iconCell": "iconCell_7741f8", + "nameCell": "nameCell_7741f8", + "statisticsButton": "statisticsButton_7741f8" + }, + "FriendNotifications": { + "friendsOnline": "friendsOnline_aa9717", + "friendsOnlineWrap": "friendsOnlineWrap_aa9717", + "logAvatar": "avatar_aa9717", + "logContent": "content_aa9717", + "logTime": "time_aa9717", + "timeLogModal": "timeLogModal_aa9717", + "typeLabel": "label_aa9717" + }, + "GameActivityToggle": { + "added": "gameActivityToggleAdded_fd3fb5", + "button": "gameActivityToggleButton_fd3fb5" + }, + "ImageUtilities": { + "details": "details_ac0584", + "detailsAdded": "detailsAdded_ac0584", + "detailsLabel": "label_ac0584", + "detailsWrapper": "detailsWrapper_ac0584", + "gallery": "gallery_Jac0584", + "imageDetails": "imageDetails_ac0584", + "imageDetailsAdded": "imageDetailsAdded_ac0584", + "lens": "zoomLens_ac0584", + "lensBackdrop": "lensBackdrop_ac0584", + "next": "next_ac0584", + "operations": "operations_ac0584", + "previous": "previous_ac0584", + "sibling": "sibling_ac0584", + "switchIcon": "switchIcon_ac0584", + "viewer": "viewer_ac0584" + }, + "LastMessageDate": { + "icon": "icon_dba1d2" + }, + "OldTitleBar": { + "extraButtons": "buttons_8227b0", + "oldTitleBarEnabled": "oldTitleBarEnabled_8227b0", + "settingsToolbar": "settingsToolbar_8227b0", + "toolbar": "toolbar_8227b0" + }, + "PersonalPins": { + "messageTag": "messageTag_226317", + "messageTagAdd": "messageTagAdd_226317", + "messageTagAddActive": "active_226317", + "messageTagDelete": "messageTagDelete_226317", + "messageTagName": "messageTagName_226317" + }, + "PinDMs": { + "dragPreview": "dragPreview_c32045", + "dmChannelPinned": "pinned_c32045", + "dmChannelPlaceholder": "placeholder_c32045", + "pinnedChannelsHeaderAmount": "headerAmount_c32045", + "pinnedChannelsHeaderArrow": "pinnedChannelsHeaderArrow_c32045", + "pinnedChannelsHeaderCollapsed": "collapsed_c32045", + "pinnedChannelsHeaderColored": "colored_c32045", + "pinnedChannelsHeaderContainer": "pinnedChannelsHeaderContainer_c32045", + "recentPinned": "pinned_c32045", + "recentPlaceholder": "placeholder_c32045", + "unpinButton": "unpinButton_c32045", + "unpinIcon": "unpinIcon_c32045" + }, + "ReadAllNotificationsButton": { + "button": "button_aa90f4", + "frame": "frame_aa90f4", + "innerFrame": "innerFrame_aa90f4" + }, + "ServerCounter": { + "serverCount": "serverCount_1dcf05", + "serverCountWrap": "serverCountWrap_1dcf05" + }, + "ServerDetails": { + "details": "details_78f589", + "icon": "icon_78f589", + "tooltip": "detailsTooltip_78f589" + }, + "ServerFolders": { + "dragPreview": "dragPreview_c32045", + "folderContent": "content_ac0584", + "folderContentClosed": "closed_ac0584", + "folderContentIsOpen": "folderContentIsOpen_ac0584", + "guildPlaceholder": "placeholder_c32045", + "hasSidebar": "hasSidebar_ac0584", + "iconSwatch": "iconSwatch_ac0584", + "iconSwatchInner": "iconInner_ac0584", + "iconSwatchPreview": "preview_ac0584", + "iconSwatchNoPreview": "noPreview_ac0584", + "iconSwatchSelected": "selected_ac0584" + }, + "SpellCheck": { + "error": "error_29aaa7", + "overlay": "spellCheckOverlay_29aaa7" + }, + "ShowBadgesInChat": { + "badges": "badges_a12ad2", + "badgesChat": "badgesChat_a12ad2", + "badgesDMs": "badgesDMs_a12ad2", + "badgesMembers": "badgesMembers_a12ad2", + "badgesSettings": "badgesSettings_a12ad2", + "indicator": "indicator_a12ad2" + }, + "ShowConnections": { + "connection": "connection_c56430", + "connectionIcon": "connectionIcon_c56430", + "connections": "connections_c56430", + "connectionsWrapper": "connectionsWrapper_c56430", + "verifiedBadge": "verifiedBadge_c56430" + }, + "SpotifyControls": { + "activityButton": "activityButton_791eb8", + "bar": "bar_791eb8", + "barGabber": "grabber_791eb8", + "barFill": "barFill_791eb8", + "barText": "barText_791eb8", + "buttonActive": "active_791eb8", + "container": "container_791eb8", + "containerInner": "inner_791eb8", + "containerMaximized": "maximized_791eb8", + "containerPaused": "paused_791eb8", + "containerWithTimeline": "withTimeline_791eb8", + "cover": "cover_791eb8", + "coverMaximizer": "maximizer_791eb8", + "coverWrapper": "coverWrapper_791eb8", + "details": "details_791eb8", + "interpret": "interpret_791eb8", + "settingsIcon": "icon_791eb8", + "settingsLabel": "label_791eb8", + "song": "song_791eb8", + "timeline": "timeline_791eb8", + "volumeSlider": "volumeSlider_791eb8" + }, + "StaffTag": { + "adminIcon": "admin_975d7e", + "forumCreatorIcon": "forumCreator_975d7e", + "groupOwnerIcon": "groupOwner_975d7e", + "managementIcon": "management_975d7e", + "ownerIcon": "owner_975d7e", + "threadCreatorIcon": "threadCreator_975d7e" + }, + "TimedLightDarkMode": { + "dateGrabber": "dateGrabber_53ee4a", + "timerGrabber": "timerGrabber_53ee4a", + "timerSettings": "timerSettings_53ee4a" + }, + "TopRolesEverywhere": { + "badgeStyle": "badgeStyle_78e9f1", + "chatTag": "chatTag_78e9f1", + "memberTag": "memberTag_78e9f1", + "roleStyle": "roleStyle_78e9f1", + "tag": "tag_78e9f1", + "voiceTag": "voiceTag_78e9f1" + }, + "Translator": { + "configButton": "configButton_68ddea", + "translateButton": "translateButton_68ddea", + "translated": "translated_68ddea", + "translating": "translating_68ddea" + }, + "WriteUpperCase": { + "enabled": "enabled_e9b3d0", + "quickToggleButton": "quickToggleButton_e9b3d0" + }, + "NotFound": { + "_": "", + "emoji": "emoji", + "carouselModal": "carouselModal_d3a6f0", + "enableForcedColors": "enable-forced-colors", + "highlight": "highlight", + "highlight": "highlight", + "hueCursor": "hue_cursor", + "hueHorizontal": "hue-horizontal", + "hueVertical": "hue-vertical", + "mention": "mention", + "mentionInteractive": "interactive", + "mentionWrapper": "wrapper_f61d60", + "messagesLoadingWrapper": "wrapper_d852db", + "nameContainerNameContainer": "container__13cf1 header__13cf1", + "saturationBlack": "saturation-black", + "saturationColor": "saturation-color", + "saturationCursor": "saturation-cursor", + "saturationWhite": "saturation-white", + "searchResultsPagination": "container_b82a71", + "stopAnimations": "stop-animations", + "subtext": "subtext__339d0", + "themeCustomBackground": "custom-theme-background", + "themeDark": "theme-dark", + "themeLight": "theme-light", + "themeUndefined": "theme-undefined", + "visualRefresh": "visual-refresh" + }, + "Toast": { + "avatar": "avatar_67c5da", + "bar": "bar_67c5da", + "barInner": "barInner_67c5da", + "bg": "bg_67c5da", + "brand": "colorBrand_67c5da", + "center": "center_67c5da", + "closable": "closable_67c5da", + "closeIcon": "closeIcon_67c5da", + "closing": "closing_67c5da", + "custom": "colorCustom_67c5da", + "customBar": "customBar_67c5da", + "danger": "colorDanger_67c5da", + "default": "colorDefault_67c5da", + "icon": "icon_67c5da", + "info": "colorInfo_67c5da", + "inner": "inner_67c5da", + "left": "left_67c5da", + "opening": "opening_67c5da", + "right": "right_67c5da", + "success": "colorSuccess_67c5da", + "text": "text_67c5da", + "toast": "toast_67c5da", + "toasts": "toasts_67c5da", + "warning": "colorWarning_67c5da" + } + }, + "LazyloadedClassModules": { + "ChannelSettings": "_0511a", + "ColorPickerInner": "_889ee", + "CustomStatusModalWithPreview": "dbc4b7", + "ForumPage": "f369db", + "GuildInviteModal": "_67dba", + "GuildNotificationsModal": "_856b5", + "HotKeyRecorder": "_2636e", + "HotKeyRecorderBase": "f89b2c", + "HoverCardRemoveButton": "e18686", + "ImageModalButtons": "cc1819", + "ModalMiniContent": "_487be", + "NoteTextarea": "_9daae", + "PollModal": "_01c8c", + "SelectFilterPopout": "_5906b", + "SettingsTabBar": "f8303a", + "SettingsWindow": "_23e6b", + "UserProfile": "_24502", + "UserProfileListScroller": "_9d78f", + "UserProfileSection": "bf424d", + "UserProfileUsernameSection": "_05e81" + }, + "DiscordClassModules": { + "AccountDetails": {"props": ["plated", "nameTag", "panelTitleContainer"]}, + "AccountDetailsButtons": {"props": ["button", "enabled", "disabled"]}, + "AccountDetailsButtonsStyle": {"props": ["redIcon", "strikethrough"]}, + "AccountDetailsQuest": {"props": ["mask", "wrapper", "wrapperInvisible"]}, + "Anchor": {"props": ["anchor", "anchorUnderlineOnHover"]}, + "AnimationContainer": {"props": ["animatorLeft", "didRender"]}, + "Animations": {"props": ["focusSensitive", "fadeIn", "base"]}, + "AppBase": {"props": ["container", "base", "panels"]}, + "AppInner": {"props": ["app", "layers"]}, + "AppMount": {"props": ["appMount"]}, + "AppOuter": {"props": ["app", "mobileApp"]}, + "Attachment": {"props": ["audio", "video", "metadataDownload"]}, + "AttachmentCover": {"props": ["cover", "icon", "iconWrapper"]}, + "AuditLog": {"props": ["auditLog", "divider"]}, + "AuthBox": {"props": ["authBox", "authBoxExpanded"]}, + "AuthBoxBackgroundCharacter": {"props": ["characterBackground"]}, + "AuthBoxBackgroundSplash": {"props": ["splashBackground"]}, + "Autocomplete": {"props": ["autocomplete", "autocompleteRowIcon"]}, + "AutocompleteAliases": {"props": ["autocomplete", "autocompleteAttached"]}, + "Avatar": {"props": ["avatar", "mask", "wrapper"]}, + "AvatarIcon": {"props": ["iconActiveLarge", "iconActiveMedium"]}, + "Backdrop": {"props": ["backdrop", "withLayer"]}, + "Badge": {"props": ["numberBadge", "textBadge", "iconBadge"]}, + "BotTag": {"props": ["botTagRegular", "botTagInvert"]}, + "Button": {"props": ["colorBrand", "lookBlank"]}, + "ButtonRevamp": {"props": ["expressive", "buttonChildren"]}, + "CallCurrent": {"props": ["wrapper", "fullScreen"]}, + "CallIncoming": {"props": ["wrapper", "mainChannelInfo"]}, + "CallScreenshare": {"props": ["videoControls", "topControls", "gradientTop"]}, + "Card": {"props": ["card", "cardBrand"]}, + "Category": {"props": ["wrapper", "children", "addButtonIcon"]}, + "ChangeLog": {"props": ["added", "fixed", "improved", "progress"]}, + "Channel": {"props": ["wrapper", "numberBadge", "modeSelected"]}, + "ChannelContainer": {"props": ["actionIcon", "containerDefault"]}, + "ChannelLimit": {"props": ["users", "total", "wrapper"]}, + "ChannelSettings": {"props": ["topicContainer", "topicCharacterCount"]}, + "ChannelTextArea": {"props": ["textArea", "buttons"]}, + "ChannelTextAreaAttachButton": {"props": ["attachButton", "attachWrapper"]}, + "ChannelTextAreaButton": {"props": ["buttonWrapper", "active"]}, + "ChannelTextAreaCharCounter": {"props": ["characterCount", "flairContainer"]}, + "ChannelTextAreaSlate": {"props": ["slateContainer", "placeholder"]}, + "ChatThreadSidebar": {"props": ["container", "floating", "resizeHandle"]}, + "ChatWindow": {"props": ["chat", "channelTextArea", "chatContent"]}, + "Checkbox": {"props": ["checkboxWrapper", "round"]}, + "ColorPicker": {"props": ["customColorPicker", "colorPickerDropper"]}, + "ColorPickerInner": {"props": ["hue", "saturation"]}, + "Combobox": {"props": ["combobox", "itemLabel"]}, + "CustomStatus": {"props": ["customStatusSoloEmoji", "customStatusText", "customStatus"]}, + "CustomStatusIcon": {"props": ["textRuler", "emoji", "icon"]}, + "CustomStatusModalWithPreview": {"props": ["profilePreview", "header", "formGroup"]}, + "DmAddPopout": {"props": ["popout", "searchBarComponent"]}, + "DmAddPopoutItems": {"props": ["friendSelected", "friendWrapper"]}, + "DefaultChannelWelcome": {"props": ["titleName", "card", "cardWrapper"]}, + "Embed": {"props": ["embedFull", "embedAuthorIcon"]}, + "EmbedActions": {"props": ["iconPlay", "iconWrapperActive"]}, + "Emoji": {"props": ["emoji"], "length": 1}, + "EmojiButton": {"props": ["emojiButton", "sprite"]}, + "EmojiPicker": {"props": ["emojiPicker", "inspector"]}, + "EmojiPickerCustomEmoji": {"props": ["image", "imageLoading"]}, + "EmojiPickerDiversitySelector": {"props": ["diversityEmojiItemImage", "diversitySelectorOptions"]}, + "EmojiPickerItem": {"props": ["emojiSpriteImage"]}, + "EmojiPickerInspector": {"props": ["inspector", "graphicPrimary"]}, + "EmojiPickerInspectorEmoji": {"props": ["emoji", "glyphEmoji"]}, + "ErrorScreen": {"props": ["wrapper", "flexWrapper", "note"]}, + "ExpressionPicker": {"props": ["contentWrapper", "navButton", "navList"]}, + "FavButton": {"props": ["gifFavoriteButton", "selected", "icon"]}, + "File": {"props": ["cancelButton", "fileNameLink"]}, + "FileDownloadButton": {"props": ["downloadButton", "attachmentName"]}, + "Flex": {"props": ["flex", "flexGutterLarge"]}, + "FlexProperties": {"props": ["flexHorizontal", "flexJustifyStart"]}, + "FlowerStar": {"props": ["flowerStarContainer", "flowerStar"]}, + "FormText": {"props": ["description", "modeDefault"]}, + "ForumPage": {"props": ["newPostsButton", "list", "searchIcon"]}, + "GifFavoriteButton": {"props": ["gifFavoriteButton", "showPulse"]}, + "GoLiveDetails": {"props": ["panel", "gameWrapper"]}, + "Guild": {"props": ["wrapper", "lowerBadge", "svg"]}, + "GuildChannels": {"props": ["positionedContainer", "unreadBar"]}, + "GuildChannelsWrapper": {"props": ["hubContainer", "container"]}, + "GuildDiscovery": {"props": ["pageWrapper", "guildList"]}, + "GuildDm": {"props": ["pill"], "length": 1}, + "GuildEdges": {"props": ["wrapper", "target", "centerTarget"]}, + "GuildFavorites": {"props": ["favoriteIcon", "ring"]}, + "GuildFolder": {"props": ["folderGroup", "isExpanded"]}, + "GuildHeader": {"props": ["header", "name", "bannerImage"]}, + "GuildHeaderButton": {"props": ["button", "open"]}, + "GuildInviteModal": {"props": ["inviteRow", "modal"]}, + "GuildItem": {"props": ["listItem", "isCurrentUserConnected"]}, + "GuildIcon": {"props": ["acronym", "selected", "wrapper"]}, + "GuildInvite": {"props": ["wrapper", "guildIconJoined"]}, + "GuildNotificationsModal": {"props": ["guildName", "checkboxContainer"]}, + "GuildsButton": {"props": ["circleIconButton", "circleIcon"], "length": 6, "smaller": true}, + "GuildsDragPlaceholder": {"props": ["dragInner", "placeholderMask"], "length": 6, "smaller": true}, + "GuildSeparator": {"props": ["guildSeparator"], "length": 6, "smaller": true}, + "GuildsError": {"props": ["guildsError"]}, + "GuildServer": {"props": ["blobContainer", "pill"]}, + "GuildsFooter": {"props": ["gradient", "footer"]}, + "GuildsListItem": {"props": ["listItemWrapper", "listItemTooltipContent"], "length": 4, "smaller": true}, + "GuildsWrapper": {"props": ["scroller", "unreadMentionsBar", "wrapper"]}, + "HeaderBar": {"props": ["container", "children", "toolbar"]}, + "HeaderBarDiscovery": {"props": ["headerBar", "icon", "backdrop"]}, + "HeaderBarDiscoverySearch": {"props": ["searchFloating", "search"]}, + "HeaderBarExtras": {"props": ["updateIconForeground", "search"]}, + "HeaderBarSearch": {"props": ["search", "searchBar", "open"]}, + "HeaderBarTopic": {"props": ["topic", "expandable", "content"]}, + "Heading": {"props": ["eyebrow", "code"]}, + "HotKeyRecorder": {"props": ["editIcon", "recording"]}, + "HotKeyRecorderBase": {"props": ["addKeybindButton", "containerDisabled"]}, + "HoverCard": {"props": ["card"], "length": 1}, + "HoverCardRemoveButton": {"props": ["button", "filled", "default"]}, + "IconDirection": {"props": ["directionDown", "directionUp"]}, + "ImageAssets": {"props": ["pngImage", "lottieCanvas"]}, + "ImageModalButtons": {"props": ["actionButtons", "actionButtonWrapper"]}, + "ImageModalMedia": {"props": ["media", "dimensionlessImage"]}, + "ImageModalMediaWrapper": {"props": ["mediaArea"]}, + "ImageMosaic": {"props": ["lazyImg", "oneByOneGridSingle"]}, + "ImageWrapper": {"props": ["clickable", "imageWrapperBackground"]}, + "Input": {"props": ["inputWrapper", "focused", "editable"]}, + "Item": {"props": ["item", "side", "header"]}, + "ItemLayerContainer": {"props": ["layer", "layerContainer"]}, + "Layers": {"props": ["layer", "layers"]}, + "LiveTag": {"props": ["liveLarge", "live"]}, + "LoadingScreen": {"props": ["container", "problemsText", "problems"]}, + "LottieIcon": {"props": ["lottieIcon", "lottieIconColors"]}, + "Margins": {"props": ["marginBottom4", "marginCenterHorz"]}, + "Menu": {"props": ["menu", "colorPremiumGradient", "item"]}, + "MenuReactButton": {"props": ["wrapper", "icon", "focused", "button"]}, + "MenuSlider": {"props": ["slider", "sliderContainer"], "length": 4, "smaller": true}, + "Member": {"props": ["member", "ownerIcon"]}, + "MemberName": {"props": ["roleDotRight", "nameContainer"]}, + "MemberSince": {"props": ["memberSince", "discordIcon"]}, + "MembersWrapper": {"props": ["membersWrap", "membersGroup"]}, + "Message": {"props": ["message", "mentioned"]}, + "MessageAccessory": {"props": ["confirmText", "gifFavoriteButton"]}, + "MessageAttachment": {"props": ["removeMosaicItemButton", "inline"]}, + "MessageBlocked": {"props": ["blockedMessageText", "expanded"]}, + "MessageBody": {"props": ["markupRtl", "edited"]}, + "MessageDivider": {"props": ["isUnread", "divider"]}, + "MessageElements": {"props": ["jumpToPresentBar", "summariesBetaTag"]}, + "MessageFile": {"props": ["cancelButton", "filenameLinkWrapper"]}, + "MessageLocalBot": {"props": ["ephemeralMessage", "icon"]}, + "MessageMarkup": {"props": ["markup"]}, + "MessagePopout": {"props": ["message", "spacing"]}, + "MessageOperations": {"props": ["operations"], "length": 1}, + "MessageReactions": {"props": ["reactions", "reactionMe"]}, + "MessageReactionsModal": {"props": ["reactors", "reactionSelected"]}, + "MessageReply": {"props": ["container", "text", "closeButton"]}, + "Messages": {"props": ["messages", "divider", "jumpButton"]}, + "MessagesPopout": {"props": ["messagesPopoutWrap", "actionButtons"]}, + "MessagesPopoutButtons": {"props": ["button", "secondary", "sm"]}, + "MessagesPopoutExtras": {"props": ["control", "statusMessageContainer"]}, + "MessagesPopoutHeader": {"props": ["threadIcon", "title", "divider"]}, + "MessagesPopoutInfo": {"props": ["channelName", "guildName", "dmIcon"]}, + "MessagesPopoutTabBar": {"props": ["header", "headerTabs", "tab", "controls"]}, + "MessagesLoading": {"props": ["attachment", "blob", "cozy"]}, + "MessagesWelcome": {"props": ["emptyChannelIcon", "description", "header"]}, + "MessagesWelcomeButton": {"props": ["button", "buttonIcon"], "length": 2}, + "MessagesWelcomeThread": {"props": ["iconWrapper", "threadCreatorName"]}, + "MessagesWrap": {"props": ["messagesWrapper", "emptyForum"]}, + "MessageSystem": {"props": ["container", "actionAnchor"]}, + "MessageSystemAccessories": {"props": ["name", "spine", "cta"]}, + "MessageToolbar": {"props": ["container", "isHeader"]}, + "MessageToolbarExtras": {"props": ["hoverBarButton", "icon", "popover"]}, + "MessageToolbarItems": {"props": ["wrapper", "button", "separator"]}, + "Modal": {"props": ["root", "small", "medium"]}, + "ModalCarouselNav": {"props": ["navPrev", "navNext", "slide"]}, + "ModalDivider": {"props": ["divider"], "length": 1}, + "ModalLayer": {"props": ["layer", "hidden"]}, + "ModalLayerBackdrop": {"props": ["backdrop", "withLayer"]}, + "ModalMiniContent": {"props": ["modal", "content"], "length": 2}, + "ModalNew": {"props": ["container", "actionBar", "headerMain"]}, + "ModalSub": {"props": ["modal", "sizeLarge"]}, + "NameContainer": {"props": ["nameAndDecorators", "name"]}, + "NameContainerState": {"props": ["interactive", "selected", "muted"]}, + "NameTag": {"props": ["bot", "nameTag"]}, + "NitroStore": {"props": ["applicationStore", "marketingHeader"]}, + "NoteTextarea": {"props": ["textarea"], "length": 1}, + "Notice": {"props": ["notice", "colorStreamerMode"]}, + "NoticePlatform": {"props": ["iconAndroid", "textLinkSmall"]}, + "PageImage": {"props": ["title", "image", "wrapper", "text"], "length": 5, "smaller": true}, + "Pagination": {"props": ["activeButton", "pageControl"]}, + "PeopleItem": {"props": ["peopleListItem", "active"]}, + "PeopleItemButton": {"props": ["actionAccept", "actionButton", "highlight"]}, + "PeopleItemInfo": {"props": ["listItemContents", "actions"], "length": 2}, + "PeopleItemUser": {"props": ["userInfo", "discordTag"]}, + "PeopleList": {"props": ["peopleList", "emptyStateContainer"]}, + "Peoples": {"props": ["peopleColumn", "tabBar"]}, + "PeoplesNowPlayingMember": {"props": ["memberItem", "unknown"]}, + "PictureInPicture": {"props": ["pictureInPicture", "pictureInPictureWindow"]}, + "PillWrapper": {"props": ["item", "wrapper"]}, + "PollModal": {"props": ["voters", "emoji", "close"]}, + "PopoutActivity": {"props": ["ellipsis", "activityActivityFeed"]}, + "PrivateChannel": {"props": ["channel", "closeButton"]}, + "PrivateChannelList": {"props": ["privateChannels", "searchBar"]}, + "PrivateChannelListScroller": {"props": ["privateChannelsHeaderContainer", "headerText"]}, + "QuickMessage": {"props": ["input"], "length": 1}, + "QuickSelect": {"props": ["quickSelectArrow", "quickSelectPopout"]}, + "QuickSwitch": {"props": ["result", "guildIconContainer"]}, + "QuickSwitchWrap": {"props": ["container", "miscContainer"]}, + "RadioGroup": {"props": ["radioBar", "item"]}, + "Reactions": {"props": ["reactionBtn", "reaction"]}, + "RecentMentions": {"props": ["recentMentionsPopout"]}, + "RecentMentionsHeader": {"props": ["channelName", "channelHeader", "dmIcon"]}, + "Role": {"props": ["roleName", "roleRemoveIcon"]}, + "RoleCircle": {"props": ["roleCircle", "dot"]}, + "RoleIcon": {"props": ["clickable", "roleIcon"], "length": 4, "smaller": true}, + "Roles": {"props": ["rolePill", "roles", "rolePillBorder"]}, + "Scrollbar": {"props": ["scrollbar", "scrollbarGhost"]}, + "Scroller": {"props": ["disableScrollAnchor", "none", "fade"]}, + "SearchBar": {"props": ["container", "code", "input"]}, + "SearchBarIcon": {"props": ["clear", "icon", "pointer"]}, + "SearchPopout": {"props": ["answerPill", "groupHeader"]}, + "SearchPopoutDatepicker": {"props": ["datePicker", "inputField"]}, + "SearchPopoutWrap": {"props": ["container", "queryContainer"]}, + "SearchResults": {"props": ["noResults", "searchResultsWrap"]}, + "SearchResultsElements": {"props": ["totalResults", "searchHeaderTabList"]}, + "SearchResultsGroup": {"props": ["searchResultGroup", "channelNameContainer"]}, + "SearchResultsMessage": {"props": ["message", "searchResult"]}, + "Select": {"props": ["searchable", "option", "selectedIcon"]}, + "SelectFilterPopout": {"props": ["selectFilterPopout", "avatar", "row"]}, + "SettingsCloseButton": {"props": ["closeButton", "keybind"]}, + "SettingsItems": {"props": ["required", "helperTextContainer"]}, + "SettingsTabBar": {"props": ["tab", "tabBar", "tabBarPanel"]}, + "SettingsWindow": {"props": ["contentRegion", "standardSidebarView"]}, + "Slider": {"props": ["slider", "grabber"]}, + "Spinner": {"props": ["spinner", "chasingDots"]}, + "Spoiler": {"props": ["spoilerContainer", "hidden"]}, + "Stack": {"props": ["stack"], "length": 1}, + "Switch": {"props": ["container", "slider", "input"]}, + "Table": {"props": ["stickyHeader", "sortIcon"]}, + "Text": {"props": ["defaultColor", "defaultMarginh1"]}, + "Text2": {"props": ["defaultColor", "selectable", "lineClamp1"]}, + "TextColor": {"props": ["colorStandard", "colorMuted", "colorError"]}, + "TextStyle": {"props": ["strikethrough", "underline", "bold"]}, + "ThreadCard": {"props": ["threadName", "container", "facepile"]}, + "Tip": {"props": ["pro", "inline"]}, + "TitleBar": {"props": ["typeMacOS", "wordmarkWindows"]}, + "TitleBarNew": {"props": ["bar", "trailing", "title"]}, + "Tooltip": {"props": ["tooltip", "tooltipTop"]}, + "TooltipGuild": {"props": ["rowIcon", "rowGuildName"]}, + "Typing": {"props": ["typingDots", "typing"]}, + "TypingCoowldown": {"props": ["cooldownWrapper", "slowModeIcon"]}, + "UnreadBar": {"props": ["active", "bar", "unread"]}, + "UploadModal": {"props": ["uploadModal", "bgScale"]}, + "UserBadges": {"props": ["badge", "container"], "length": 5, "smaller": true}, + "UserBanner": {"props": ["popoutBanner", "popoutBannerPremium"]}, + "UserHeaderUsername": {"props": ["clickableUsername", "nickname", "bot"]}, + "UserPopoutBody": {"props": ["body", "menus"]}, + "UserPopoutHeader": {"props": ["avatarHint", "profileBadges", "avatarWrapperNormal"]}, + "UserPopoutSection": {"props": ["section", "lastSection"]}, + "UserPopoutStatusBubble": {"props": ["hoisted", "addStatusIcon"]}, + "UserProfile": {"props": ["root", "friendRequestBanner"]}, + "UserProfileHeader": {"props": ["additionalActionsIcon", "header", "relationshipButtons"]}, + "UserProfileList": {"props": ["row", "noIcon", "avatar"]}, + "UserProfileListGuild": {"props": ["guildAvatar", "guildAvatarWithoutIcon"]}, + "UserProfileListScroller": {"props": ["listScroller", "emptyText"]}, + "UserProfileSection": {"props": ["section", "heading"], "length": 2}, + "UserProfileUsernameSection": {"props": ["customStatusSoloEmoji", "nameTag", "discriminator"]}, + "UserSummaryItem": {"props": ["avatarContainerMasked", "container"]}, + "UserTheme": {"props": ["overlay", "outer", "inner"]}, + "VoiceChannel": {"props": ["usernameSpeaking", "voiceUser"]}, + "VoiceChannelLimit": {"props": ["total", "users", "wrapper"]}, + "VoiceChannelList": {"props": ["list", "collapsed"]}, + "VoiceDetails": {"props": ["container", "fauxDisabled", "viewAsRolesWarning"]}, + "VoiceDetailsPing": {"props": ["rtcConnectionQualityBad", "rtcConnectionQualityFine"]} + }, + "DiscordClasses": { + "_bdmodal": ["BD", "bdModal"], + "_bdmodalcontent": ["BD", "bdModalContent"], + "_betterfriendlistmutualguilds": ["BetterFriendList", "mutualGuilds"], + "_betterfriendlistnamecell": ["BetterFriendList", "nameCell"], + "_betterfriendlisttitle": ["BetterFriendList", "title"], + "_betternsfwtagtag": ["BetterNsfwTag", "nsfwTag"], + "_bettersearchpagepagination": ["BetterSearchPage", "pagination"], + "_chatfilterblocked": ["ChatFilter", "blocked"], + "_chatfilterblockedstamp": ["ChatFilter", "blockedStamp"], + "_chatfiltercensored": ["ChatFilter", "censored"], + "_chatfiltercensoredstamp": ["ChatFilter", "censoredStamp"], + "_charcountercounter": ["CharCounter", "charCounter"], + "_charcounterchatcounter": ["CharCounter", "chatCounter"], + "_charcountercounteradded": ["CharCounter", "counterAdded"], + "_charcountercustomstatuscounter": ["CharCounter", "customStatusCounter"], + "_charcountereditcounter": ["CharCounter", "editCounter"], + "_charcounterpopoutnotecounter": ["CharCounter", "popoutNoteCounter"], + "_charcounterprofilenotecounter": ["CharCounter", "profileNoteCounter"], + "_charcounterthreadcreationcounter": ["CharCounter", "threadCreationCounter"], + "_charcounteruserprofilecounter": ["CharCounter", "userProfileCounter"], + "_customstatuspresetscustomstatusitem": ["CustomStatusPresets", "customStatusItem"], + "_customstatuspresetsdeletebutton": ["CustomStatusPresets", "deleteButton"], + "_customstatuspresetsdeleteicon": ["CustomStatusPresets", "deleteIcon"], + "_customstatuspresetsdragpreview": ["CustomStatusPresets", "dragPreview"], + "_customstatuspresetssortablecard": ["CustomStatusPresets", "sortableCard"], + "_customstatuspresetssortdivider": ["CustomStatusPresets", "sortDivider"], + "_customstatuspresetsstatus": ["CustomStatusPresets", "status"], + "_displayserversaschannelsbadge": ["DisplayServersAsChannels", "badge"], + "_displayserversaschannelscolored": ["DisplayServersAsChannels", "colored"], + "_displayserversaschannelsmuted": ["DisplayServersAsChannels", "muted"], + "_displayserversaschannelsname": ["DisplayServersAsChannels", "name"], + "_displayserversaschannelsstyled": ["DisplayServersAsChannels", "styled"], + "_emojistatisticsstatisticsbutton": ["EmojiStatistics", "statisticsButton"], + "_emojistatisticsamountcell": ["EmojiStatistics", "amountCell"], + "_emojistatisticsiconcell": ["EmojiStatistics", "iconCell"], + "_emojistatisticsnamecell": ["EmojiStatistics", "nameCell"], + "_friendnotificationslogavatar": ["FriendNotifications", "logAvatar"], + "_friendnotificationslogcontent": ["FriendNotifications", "logContent"], + "_friendnotificationslogtime": ["FriendNotifications", "logTime"], + "_friendnotificationsfriendsonline": ["FriendNotifications", "friendsOnline"], + "_friendnotificationsfriendsonlinewrap": ["FriendNotifications", "friendsOnlineWrap"], + "_friendnotificationstimelogmodal": ["FriendNotifications", "timeLogModal"], + "_friendnotificationstypelabel": ["FriendNotifications", "typeLabel"], + "_gameactivitytoggleadded": ["GameActivityToggle", "added"], + "_gameactivitytogglebutton": ["GameActivityToggle", "button"], + "_imageutilitiesdetails": ["ImageUtilities", "details"], + "_imageutilitiesdetailsadded": ["ImageUtilities", "detailsAdded"], + "_imageutilitiesdetailslabel": ["ImageUtilities", "detailsLabel"], + "_imageutilitiesdetailswrapper": ["ImageUtilities", "detailsWrapper"], + "_imageutilitiesgallery": ["ImageUtilities", "gallery"], + "_imageutilitiesimagedetails": ["ImageUtilities", "imageDetails"], + "_imageutilitiesimagedetailsadded": ["ImageUtilities", "imageDetailsAdded"], + "_imageutilitieslense": ["ImageUtilities", "lens"], + "_imageutilitieslensebackdrop": ["ImageUtilities", "lensBackdrop"], + "_imageutilitiesnext": ["ImageUtilities", "next"], + "_imageutilitiesoperations": ["ImageUtilities", "operations"], + "_imageutilitiesprevious": ["ImageUtilities", "previous"], + "_imageutilitiessibling": ["ImageUtilities", "sibling"], + "_imageutilitiesswitchicon": ["ImageUtilities", "switchIcon"], + "_imageutilitiesviewer": ["ImageUtilities", "viewer"], + "_lastmessagedateicon": ["LastMessageDate", "icon"], + "_oldtitlebarenabled": ["OldTitleBar", "oldTitleBarEnabled"], + "_oldtitlebarextrabuttons": ["OldTitleBar", "extraButtons"], + "_oldtitlebarsettingstoolbar": ["OldTitleBar", "settingsToolbar"], + "_oldtitlebartoolbar": ["OldTitleBar", "toolbar"], + "_personalpinsmessagetag": ["PersonalPins", "messageTag"], + "_personalpinsmessagetagadd": ["PersonalPins", "messageTagAdd"], + "_personalpinsmessagetagaddactive": ["PersonalPins", "messageTagAddActive"], + "_personalpinsmessagetagdelete": ["PersonalPins", "messageTagDelete"], + "_personalpinsmessagetagname": ["PersonalPins", "messageTagName"], + "_pindmsdragpreview": ["PinDMs", "dragPreview"], + "_pindmsdmchannelpinned": ["PinDMs", "dmChannelPinned"], + "_pindmsdmchannelplaceholder": ["PinDMs", "dmChannelPlaceholder"], + "_pindmspinnedchannelsheaderamount": ["PinDMs", "pinnedChannelsHeaderAmount"], + "_pindmspinnedchannelsheaderarrow": ["PinDMs", "pinnedChannelsHeaderArrow"], + "_pindmspinnedchannelsheadercollapsed": ["PinDMs", "pinnedChannelsHeaderCollapsed"], + "_pindmspinnedchannelsheadercolored": ["PinDMs", "pinnedChannelsHeaderColored"], + "_pindmspinnedchannelsheadercontainer": ["PinDMs", "pinnedChannelsHeaderContainer"], + "_pindmsrecentpinned": ["PinDMs", "recentPinned"], + "_pindmsrecentplaceholder": ["PinDMs", "recentPlaceholder"], + "_pindmsunpinbutton": ["PinDMs", "unpinButton"], + "_pindmsunpinicon": ["PinDMs", "unpinIcon"], + "_readallnotificationsbuttonbutton": ["ReadAllNotificationsButton", "button"], + "_readallnotificationsbuttonframe": ["ReadAllNotificationsButton", "frame"], + "_readallnotificationsbuttoninner": ["ReadAllNotificationsButton", "innerFrame"], + "_repochangelogbutton": ["BDFDB", "changeLogButton"], + "_servercounterservercount": ["ServerCounter", "serverCount"], + "_servercounterservercountwrap": ["ServerCounter", "serverCountWrap"], + "_serverdetailsdetails": ["ServerDetails", "details"], + "_serverdetailsicon": ["ServerDetails", "icon"], + "_serverdetailstooltip": ["ServerDetails", "tooltip"], + "_serverfoldersdragpreview": ["ServerFolders", "dragPreview"], + "_serverfoldersfoldercontent": ["ServerFolders", "folderContent"], + "_serverfoldersfoldercontentclosed": ["ServerFolders", "folderContentClosed"], + "_serverfoldersfoldercontentisopen": ["ServerFolders", "folderContentIsOpen"], + "_serverfoldersguildplaceholder": ["ServerFolders", "guildPlaceholder"], + "_serverfoldershassidebar": ["ServerFolders", "hasSidebar"], + "_serverfoldersiconswatch": ["ServerFolders", "iconSwatch"], + "_serverfoldersiconswatchinner": ["ServerFolders", "iconSwatchInner"], + "_serverfoldersiconswatchpreview": ["ServerFolders", "iconSwatchPreview"], + "_serverfoldersiconswatchnopreview": ["ServerFolders", "iconSwatchNoPreview"], + "_serverfoldersiconswatchselected": ["ServerFolders", "iconSwatchSelected"], + "_showbadgesinchatbadges": ["ShowBadgesInChat", "badges"], + "_showbadgesinchatbadgeschat": ["ShowBadgesInChat", "badgesChat"], + "_showbadgesinchatbadgesdmslist": ["ShowBadgesInChat", "badgesDMs"], + "_showbadgesinchatbadgesmemberlist": ["ShowBadgesInChat", "badgesMembers"], + "_showbadgesinchatbadgessettings": ["ShowBadgesInChat", "badgesSettings"], + "_showbadgesinchatindicator": ["ShowBadgesInChat", "indicator"], + "_showconnectionsconnection": ["ShowConnections", "connection"], + "_showconnectionsconnections": ["ShowConnections", "connections"], + "_showconnectionsconnectionswrapper": ["ShowConnections", "connectionsWrapper"], + "_showconnectionsicon": ["ShowConnections", "connectionIcon"], + "_showconnectionsverifiedbadge": ["ShowConnections", "verifiedBadge"], + "_spellcheckerror": ["SpellCheck", "error"], + "_spellcheckoverlay": ["SpellCheck", "overlay"], + "_spotifycontrolsactivitybutton": ["SpotifyControls", "activityButton"], + "_spotifycontrolsbar": ["SpotifyControls", "bar"], + "_spotifycontrolsbarfill": ["SpotifyControls", "barFill"], + "_spotifycontrolsbargrabber": ["SpotifyControls", "barGabber"], + "_spotifycontrolsbartext": ["SpotifyControls", "barText"], + "_spotifycontrolsbuttonactive": ["SpotifyControls", "buttonActive"], + "_spotifycontrolscontainer": ["SpotifyControls", "container"], + "_spotifycontrolscontainerinner": ["SpotifyControls", "containerInner"], + "_spotifycontrolscontainermaximized": ["SpotifyControls", "containerMaximized"], + "_spotifycontrolscontainerpaused": ["SpotifyControls", "containerPaused"], + "_spotifycontrolscontainerwithtimeline": ["SpotifyControls", "containerWithTimeline"], + "_spotifycontrolscover": ["SpotifyControls", "cover"], + "_spotifycontrolscovermaximizer": ["SpotifyControls", "coverMaximizer"], + "_spotifycontrolscoverwrapper": ["SpotifyControls", "coverWrapper"], + "_spotifycontrolsdetails": ["SpotifyControls", "details"], + "_spotifycontrolsinterpret": ["SpotifyControls", "interpret"], + "_spotifycontrolssettingsicon": ["SpotifyControls", "settingsIcon"], + "_spotifycontrolssettingslabel": ["SpotifyControls", "settingsLabel"], + "_spotifycontrolssong": ["SpotifyControls", "song"], + "_spotifycontrolstimeline": ["SpotifyControls", "timeline"], + "_spotifycontrolsvolumeslider": ["SpotifyControls", "volumeSlider"], + "_stafftagadminicon": ["StaffTag", "adminIcon"], + "_stafftagforumcreatoricon": ["StaffTag", "forumCreatorIcon"], + "_stafftaggroupownericon": ["StaffTag", "groupOwnerIcon"], + "_stafftagmanagementicon": ["StaffTag", "managementIcon"], + "_stafftagownericon": ["StaffTag", "ownerIcon"], + "_stafftagthreadcreatoricon": ["StaffTag", "threadCreatorIcon"], + "_timedlightdarkmodedategrabber": ["TimedLightDarkMode", "dateGrabber"], + "_timedlightdarkmodetimergrabber": ["TimedLightDarkMode", "timerGrabber"], + "_timedlightdarkmodetimersettings": ["TimedLightDarkMode", "timerSettings"], + "_toproleseverywherebadgestyle": ["TopRolesEverywhere", "badgeStyle"], + "_toproleseverywherechattag": ["TopRolesEverywhere", "chatTag"], + "_toproleseverywheremembertag": ["TopRolesEverywhere", "memberTag"], + "_toproleseverywhererolestyle": ["TopRolesEverywhere", "roleStyle"], + "_toproleseverywheretag": ["TopRolesEverywhere", "tag"], + "_toproleseverywherevoicetag": ["TopRolesEverywhere", "voiceTag"], + "_translatorconfigbutton": ["Translator", "configButton"], + "_translatortranslatebutton": ["Translator", "translateButton"], + "_translatortranslated": ["Translator", "translated"], + "_translatortranslating": ["Translator", "translating"], + "_writeuppercasequicktogglebutton": ["WriteUpperCase", "quickToggleButton"], + "_writeuppercasequicktogglebuttonenabled": ["WriteUpperCase", "enabled"], + "accountinfo": ["AccountDetails", "container"], + "accountinfoavatar": ["AccountDetails", "avatar"], + "accountinfobutton": ["AccountDetailsButtons", "button"], + "accountinfobuttondisabled": ["AccountDetailsButtons", "disabled"], + "accountinfobuttonenabled": ["AccountDetailsButtons", "enabled"], + "accountinfobuttonredicon": ["AccountDetailsButtonsStyle", "redIcon"], + "accountinfobuttons": ["AccountDetails", "buttons"], + "accountinfobuttonstrikethrough": ["AccountDetailsButtonsStyle", "strikethrough"], + "accountinfodetails": ["AccountDetails", "panelTitleContainer"], + "accountinfonametag": ["AccountDetails", "nameTag"], + "accountinfoquestmask": ["AccountDetailsQuest", "mask"], + "aliasautocomplete": ["AutocompleteAliases", "autocomplete"], + "anchor": ["Anchor", "anchor"], + "anchorunderlineonhover": ["Anchor", "anchorUnderlineOnHover"], + "animationcontainerbottom": ["AnimationContainer", "animatorBottom"], + "animationcontainercenter": ["AnimationContainer", "animatorCenter"], + "animationcontainerfade": ["AnimationContainer", "fade"], + "animationcontainerleft": ["AnimationContainer", "animatorLeft"], + "animationcontainerright": ["AnimationContainer", "animatorRight"], + "animationcontainertop": ["AnimationContainer", "animatorTop"], + "animationcontainerrender": ["AnimationContainer", "didRender"], + "animationcontainerscale": ["AnimationContainer", "scale"], + "animationcontainertranslate": ["AnimationContainer", "translate"], + "animationsbase": ["Animations", "base"], + "animationsfadeout": ["Animations", "fadeOut"], + "animationsfadeout": ["Animations", "fadeOut"], + "animationsfocussensitive": ["Animations", "focusSensitive"], + "animationshidden": ["Animations", "hidden"], + "app": ["AppOuter", "app"], + "appbase": ["AppBase", "base"], + "appcontainer": ["AppBase", "container"], + "appinner": ["AppInner", "app"], + "appmount": ["AppMount", "appMount"], + "applayers": ["AppInner", "layers"], + "attachment": ["Attachment", "wrapper"], + "attachmentcontrolshidden": ["Attachment", "wrapperControlsHidden"], + "attachmentcover": ["AttachmentCover", "cover"], + "attachmentcovericon": ["AttachmentCover", "icon"], + "attachmentvideo": ["Attachment", "video"], + "attachmentvideocontrols": ["Attachment", "videoControls"], + "auditlog": ["AuditLog", "auditLog"], + "auditlogoverflowellipsis": ["AuditLog", "overflowEllipsis"], + "auditlogtimestamp": ["AuditLog", "timestamp"], + "auditloguserhook": ["AuditLog", "userHook"], + "authbox": ["AuthBox", "authBox"], + "autocomplete": ["Autocomplete", "autocomplete"], + "autocompletecontenttitle": ["Autocomplete", "contentTitle"], + "autocompletedescriptiondiscriminator": ["Autocomplete", "descriptionDiscriminator"], + "autocompleteemoji": ["Autocomplete", "emojiImage"], + "autocompleteicon": ["Autocomplete", "icon"], + "autocompleteiconforeground": ["Autocomplete", "iconForeground"], + "autocompleteinner": ["Autocomplete", "autocompleteInner"], + "autocompleterow": ["Autocomplete", "autocompleteRow"], + "autocompleterowcontent": ["Autocomplete", "autocompleteRowContent"], + "autocompleterowcontentprimary": ["Autocomplete", "autocompleteRowContentPrimary"], + "autocompleterowcontentsecondary": ["Autocomplete", "autocompleteRowContentSecondary"], + "autocompleterowhorizontal": ["Autocomplete", "autocompleteRowHorizontal"], + "autocompleterowicon": ["Autocomplete", "autocompleteRowIcon"], + "autocompleterowsubheading": ["Autocomplete", "autocompleteRowSubheading"], + "autocompleterowvertical": ["Autocomplete", "autocompleteRowVertical"], + "avatar": ["Avatar", "avatar"], + "avatarcursordefault": ["Avatar", "cursorDefault"], + "avatardisabled": ["BDFDB", "avatarDisabled"], + "avataricon": ["AvatarIcon", "icon"], + "avatariconactivelarge": ["AvatarIcon", "iconActiveLarge"], + "avatariconactivemedium": ["AvatarIcon", "iconActiveMedium"], + "avatariconactivemini": ["AvatarIcon", "iconActiveMini"], + "avatariconactivesmall": ["AvatarIcon", "iconActiveSmall"], + "avatariconactivexlarge": ["AvatarIcon", "iconActiveXLarge"], + "avatariconinactive": ["AvatarIcon", "iconInactive"], + "avatariconsizelarge": ["AvatarIcon", "iconSizeLarge"], + "avatariconsizemedium": ["AvatarIcon", "iconSizeMedium"], + "avatariconsizemini": ["AvatarIcon", "iconSizeMini"], + "avatariconsizesmol": ["AvatarIcon", "iconSizeSmol"], + "avatariconsizesmall": ["AvatarIcon", "iconSizeSmall"], + "avatariconsizexlarge": ["AvatarIcon", "iconSizeXLarge"], + "avatarmask": ["Avatar", "mask"], + "avatarnoicon": ["AvatarIcon", "noIcon"], + "avatarpointer": ["Avatar", "pointer"], + "avatarpointerevents": ["Avatar", "pointerEvents"], + "avatarstack": ["Avatar", "avatarStack"], + "avatarsvg": ["Avatar", "svg"], + "avatarwrapper": ["Avatar", "wrapper"], + "backdrop": ["Backdrop", "backdrop"], + "backdropwithlayer": ["Backdrop", "withLayer"], + "badgebase": ["Badge", "base"], + "badgeicon": ["Badge", "icon"], + "badgeiconbadge": ["Badge", "iconBadge"], + "badgenumberbadge": ["Badge", "numberBadge"], + "badgeshaperound": ["Badge", "baseShapeRound"], + "badgeshaperoundleft": ["Badge", "baseShapeRoundLeft"], + "badgeshaperoundright": ["Badge", "baseShapeRoundRight"], + "badgetextbadge": ["Badge", "textBadge"], + "bdfdbbadge": ["BDFDB", "avatarBadge"], + "bdfdbbadgeavatar": ["BDFDB", "avatarBadgeAvatar"], + "bdfdbdev": ["BDFDB", "avatarBadgeDev"], + "bdfdbhasbadge": ["BDFDB", "avatarBadgeHas"], + "bdfdbsupporter": ["BDFDB", "avatarBadgeSupporter"], + "bdfdbsupportert1": ["BDFDB", "avatarBadgeSupporterTier1"], + "bdfdbsupportert2": ["BDFDB", "avatarBadgeSupporterTier2"], + "bdfdbsupportert3": ["BDFDB", "avatarBadgeSupporterTier3"], + "bdfdbsupportert4": ["BDFDB", "avatarBadgeSupporterTier4"], + "bold": ["TextStyle", "bold"], + "bottag": ["BotTag", "botTag"], + "bottaginvert": ["BotTag", "botTagInvert"], + "bottagmember": ["Member", "botTag"], + "bottagnametag": ["NameTag", "bot"], + "bottagpx": ["BotTag", "px"], + "bottagregular": ["BotTag", "botTagRegular"], + "bottagrem": ["BotTag", "rem"], + "bottagtext": ["BotTag", "botText"], + "bottagverified": ["BotTag", "botTagVerified"], + "button": ["Button", "button"], + "buttoncolorbrand": ["Button", "colorBrand"], + "buttoncolorgreen": ["Button", "colorGreen"], + "buttoncolorlink": ["Button", "colorLink"], + "buttoncolorprimary": ["Button", "colorPrimary"], + "buttoncolorred": ["Button", "colorRed"], + "buttoncolortransparent": ["Button", "colorTransparent"], + "buttoncolorwhite": ["Button", "colorWhite"], + "buttoncontents": ["Button", "contents"], + "buttondisabledoverlay": ["Button", "disabledButtonOverlay"], + "buttondisabledwrapper": ["Button", "disabledButtonWrapper"], + "buttonfullwidth": ["Button", "fullWidth"], + "buttongrow": ["Button", "grow"], + "buttonlookblank": ["Button", "lookBlank"], + "buttonlookfilled": ["Button", "lookFilled"], + "buttonlooklink": ["Button", "lookLink"], + "buttonlookoutlined": ["Button", "lookOutlined"], + "buttonrevamp": ["ButtonRevamp", "button"], + "buttonrevampchildren": ["ButtonRevamp", "buttonChildren"], + "buttonrevampchildrenwrapper": ["ButtonRevamp", "buttonChildrenWrapper"], + "buttonrevamphastext": ["ButtonRevamp", "hasText"], + "buttonrevampicon": ["ButtonRevamp", "icon"], + "buttonrevampoverlayprimary": ["ButtonRevamp", "overlay-primary"], + "buttonrevampoverlaysecondary": ["ButtonRevamp", "overlay-secondary"], + "buttonrevampmd": ["ButtonRevamp", "md"], + "buttonrevampsm": ["ButtonRevamp", "sm"], + "buttonrevampxs": ["ButtonRevamp", "xs"], + "buttonsizeicon": ["Button", "sizeIcon"], + "buttonsizelarge": ["Button", "sizeLarge"], + "buttonsizemax": ["Button", "sizeMax"], + "buttonsizemedium": ["Button", "sizeMedium"], + "buttonsizemin": ["Button", "sizeMin"], + "buttonsizesmall": ["Button", "sizeSmall"], + "buttonspinner": ["Button", "spinner"], + "buttonspinneritem": ["Button", "spinnerItem"], + "buttonsubmitting": ["Button", "submitting"], + "callcurrentcontainer": ["CallCurrent", "wrapper"], + "callincomingicon": ["CallIncoming", "icon"], + "callincomingroot": ["CallIncoming", "root"], + "callincomingtitle": ["CallIncoming", "title"], + "callincomingwrapper": ["CallIncoming", "wrapper"], + "callscreensharebottomcontrols": ["CallScreenshare", "bottomControls"], + "callscreensharetopcontrols": ["CallScreenshare", "topControls"], + "callscreensharevideocontrols": ["CallScreenshare", "videoControls"], + "card": ["Card", "card"], + "cardbrand": ["Card", "cardBrand"], + "carddanger": ["Card", "cardDanger"], + "cardeditable": ["Card", "editable"], + "cardprimary": ["Card", "cardPrimary"], + "cardsuccess": ["Card", "cardSuccess"], + "cardwarning": ["Card", "cardWarning"], + "categoryaddbutton": ["Category", "addButton"], + "categoryaddbuttonicon": ["Category", "addButtonIcon"], + "categorychildren": ["Category", "children"], + "categoryclickable": ["Category", "clickable"], + "categorycollapsed": ["Category", "collapsed"], + "categorycontainerdefault": ["Category", "containerDefault"], + "categoryforcevisible": ["Category", "forceVisible"], + "categoryicon": ["Category", "icon"], + "categoryiconvisibility": ["Category", "iconVisibility"], + "categorymaincontent": ["Category", "mainContent"], + "categorymuted": ["Category", "muted"], + "categoryname": ["Category", "name"], + "categorywrapper": ["Category", "wrapper"], + "changelogadded": ["ChangeLog", "added"], + "changelogcontainer": ["ChangeLog", "container"], + "changelogfixed": ["ChangeLog", "fixed"], + "changelogfooter": ["ChangeLog", "footer"], + "changelogimproved": ["ChangeLog", "improved"], + "changelogprogress": ["ChangeLog", "progress"], + "changelogsociallink": ["ChangeLog", "socialLink"], + "changelogtitle": ["ChangeLog", "title"], + "channelactionicon": ["ChannelContainer", "actionIcon"], + "channelchildicon": ["ChannelContainer", "iconItem"], + "channelchildiconbase": ["ChannelContainer", "iconBase"], + "channelchildren": ["Channel", "children"], + "channelcontainerdefault": ["ChannelContainer", "containerDefault"], + "channelcontent": ["Channel", "link"], + "channelheaderavatar": ["ChatWindow", "avatar"], + "channelheaderchildren": ["HeaderBar", "children"], + "channelheadercursorpointer": ["ChatWindow", "cursorPointer"], + "channelheaderdiscovery": ["HeaderBarDiscovery", "headerBar"], + "channelheaderdiscoverysearch": ["HeaderBarDiscoverySearch", "searchBar"], + "channelheaderdiscoverysearchfloating": ["HeaderBarDiscoverySearch", "searchFloating"], + "channelheaderdivider": ["BDFDB", "channelHeaderDivider"], + "channelheaderheaderbar": ["HeaderBar", "container"], + "channelheaderheaderbarthemed": ["HeaderBar", "themed"], + "channelheaderheaderbartitle": ["HeaderBar", "title"], + "channelheadericon": ["HeaderBar", "icon"], + "channelheadericonbadge": ["HeaderBar", "iconBadge"], + "channelheadericonclickable": ["HeaderBar", "clickable"], + "channelheadericonselected": ["HeaderBar", "selected"], + "channelheadericonwrapper": ["HeaderBar", "iconWrapper"], + "channelheadertitle": ["ChatWindow", "title"], + "channelheadersearch": ["HeaderBarExtras", "search"], + "channelheadersearchbar": ["HeaderBarSearch", "searchBar"], + "channelheadersearchicon": ["HeaderBarSearch", "icon"], + "channelheadersearchinner": ["HeaderBarSearch", "search"], + "channelheadertoolbar": ["HeaderBar", "toolbar"], + "channelheadertopic": ["HeaderBarTopic", "topic"], + "channelheadertopicexpandable": ["HeaderBarTopic", "expandable"], + "channelicon": ["Channel", "icon"], + "channeliconcontainer": ["Channel", "iconContainer"], + "channeliconitem": ["ChannelContainer", "iconItem"], + "channeliconvisibility": ["ChannelContainer", "iconVisibility"], + "channelinfo": ["ChannelContainer", "channelInfo"], + "channelmodeconnected": ["Channel", "modeConnected"], + "channelmodelocked": ["Channel", "modeLocked"], + "channelmodemuted": ["Channel", "modeMuted"], + "channelmodeselected": ["Channel", "modeSelected"], + "channelname": ["Channel", "name"], + "channelpanel": ["AppBase", "activityPanel"], + "channelpanels": ["AppBase", "panels"], + "channels": ["AppBase", "sidebar"], + "channelselected": ["ChannelContainer", "selected"], + "channelsettingstopiccontainer": ["ChannelSettings", "topicContainer"], + "channelsettingstopiccounter": ["ChannelSettings", "topicCharacterCount"], + "channelshidden": ["AppBase", "hidden"], + "channelslist": ["AppBase", "sidebarList"], + "channelsscroller": ["GuildChannels", "scroller"], + "channelsunreadbar": ["GuildChannels", "unreadBar"], + "channelsunreadbarcontainer": ["GuildChannels", "positionedContainer"], + "channelsunreadbarbottom": ["GuildChannels", "unreadBottom"], + "channelsunreadbarunread": ["GuildChannels", "unread"], + "channelsunreadbartop": ["GuildChannels", "unreadTop"], + "channelunread": ["Channel", "unread"], + "channeluserlimit": ["ChannelLimit", "wrapper"], + "channeluserlimittotal": ["ChannelLimit", "total"], + "channeluserlimitusers": ["ChannelLimit", "users"], + "channelwrapper": ["Channel", "wrapper"], + "charcounter": ["BDFDB", "charCounter"], + "chat": ["ChatWindow", "chat"], + "chatbase": ["AppBase", "base"], + "chatcontent": ["ChatWindow", "chatContent"], + "chatform": ["ChatWindow", "form"], + "chatinner": ["ChatWindow", "content"], + "chatspacer": ["AppBase", "content"], + "chatthreadsidebar": ["ChatThreadSidebar", "container"], + "chatthreadsidebaropen": ["ChatWindow", "threadSidebarOpen"], + "checkbox": ["Checkbox", "checkbox"], + "checkboxbox": ["Checkbox", "box"], + "checkboxchecked": ["Checkbox", "checked"], + "checkboxcontainer": ["GuildNotificationsModal", "checkboxContainer"], + "checkboxinput": ["Checkbox", "input"], + "checkboxinputdefault": ["Checkbox", "inputDefault"], + "checkboxinputdisabled": ["Checkbox", "inputDisabled"], + "checkboxinputreadonly": ["Checkbox", "inputReadonly"], + "checkboxlabel": ["Checkbox", "label"], + "checkboxlabelclickable": ["Checkbox", "labelClickable"], + "checkboxlabeldisabled": ["Checkbox", "labelDisabled"], + "checkboxlabelforward": ["Checkbox", "labelForward"], + "checkboxlabelreversed": ["Checkbox", "labelReversed"], + "checkboxround": ["Checkbox", "round"], + "checkboxwrapper": ["Checkbox", "checkboxWrapper"], + "checkboxwrapperdisabled": ["Checkbox", "checkboxWrapperDisabled"], + "collapsecontainer": ["BDFDB", "collapseContainer"], + "collapsecontainercollapsed": ["BDFDB", "collapseContainerCollapsed"], + "collapsecontainerheader": ["BDFDB", "collapseContainerHeader"], + "collapsecontainerinner": ["BDFDB", "collapseContainerInner"], + "collapsecontainermini": ["BDFDB", "collapseContainerMini"], + "collapsecontainertitle": ["BDFDB", "collapseContainerTitle"], + "colorpicker": ["ColorPicker", "customColorPicker"], + "colorpickeralpha": ["BDFDB", "colorPickerAlpha"], + "colorpickeralphacheckered": ["BDFDB", "colorPickerAlphaCheckered"], + "colorpickeralphacursor": ["BDFDB", "colorPickerAlphaCursor"], + "colorpickeralphahorizontal": ["BDFDB", "colorPickerAlphaHorizontal"], + "colorpickergradient": ["BDFDB", "colorPickerGradient"], + "colorpickergradientbutton": ["BDFDB", "colorPickerGradientButton"], + "colorpickergradientbuttonenabled": ["BDFDB", "colorPickerGradientButtonEnabled"], + "colorpickergradientcheckered": ["BDFDB", "colorPickerGradientCheckered"], + "colorpickergradientcursor": ["BDFDB", "colorPickerGradientCursor"], + "colorpickergradientcursoredge": ["BDFDB", "colorPickerGradientCursorEdge"], + "colorpickergradientcursorselected": ["BDFDB", "colorPickerGradientCursorSelected"], + "colorpickergradienthorizontal": ["BDFDB", "colorPickerGradientHorizontal"], + "colorpickerhue": ["ColorPickerInner", "hue"], + "colorpickerhuecursor": ["NotFound", "hueCursor"], + "colorpickerhuehorizontal": ["NotFound", "hueHorizontal"], + "colorpickerhuevertical": ["NotFound", "hueVertical"], + "colorpickerinner": ["ColorPickerInner", "wrapper"], + "colorpickerrow": ["ColorPicker", "colorPickerRow"], + "colorpickersaturation": ["ColorPickerInner", "saturation"], + "colorpickersaturationblack": ["NotFound", "saturationBlack"], + "colorpickersaturationcolor": ["NotFound", "saturationColor"], + "colorpickersaturationcursor": ["NotFound", "saturationCursor"], + "colorpickersaturationwhite": ["NotFound", "saturationWhite"], + "colorpickersuggestedcolor": ["ColorPicker", "suggestedColor"], + "colorpickersuggestedcolors": ["ColorPicker", "suggestedColors"], + "colorpickerswatch": ["ColorPicker", "colorPickerSwatch"], + "colorpickerswatches": ["BDFDB", "colorPickerSwatches"], + "colorpickerswatchescontainer": ["ColorPicker", "container"], + "colorpickerswatchesdisabled": ["BDFDB", "colorPickerSwatchesDisabled"], + "colorpickerswatchcustom": ["ColorPicker", "custom"], + "colorpickerswatchcustomcontainer": ["ColorPicker", "customContainer"], + "colorpickerswatchdefault": ["ColorPicker", "default"], + "colorpickerswatchdefaultcontainer": ["ColorPicker", "defaultContainer"], + "colorpickerswatchdisabled": ["ColorPicker", "disabled"], + "colorpickerswatchdropper": ["ColorPicker", "colorPickerDropper"], + "colorpickerswatchnocolor": ["ColorPicker", "noColor"], + "colorpickerswatchselected": ["BDFDB", "colorPickerSwatchSelected"], + "colorpickerswatchsingle": ["BDFDB", "colorPickerSwatchSingle"], + "colorpickerswatchsinglewrapper": ["BDFDB", "colorPickerSwatchSingleWrapper"], + "colorpickerwrapper": ["BDFDB", "colorPicker"], + "colorprimary": ["TextColor", "colorHeaderPrimary"], + "colorred": ["TextColor", "colorStatusRed"], + "colorsecondary": ["TextColor", "colorHeaderSecondary"], + "colorselectable": ["TextColor", "selectable"], + "colorstandard": ["TextColor", "colorStandard"], + "coloryellow": ["TextColor", "colorStatusYellow"], + "combobox": ["Combobox", "combobox"], + "comboboxitem": ["Combobox", "item"], + "comboboxitemlabel": ["Combobox", "itemLabel"], + "cursordefault": ["BDFDB", "cursorDefault"], + "cursorpointer": ["BDFDB", "cursorPointer"], + "customstatusemoji": ["CustomStatusIcon", "emoji"], + "customstatusicon": ["CustomStatusIcon", "icon"], + "customstatusmodalprofilepreview": ["CustomStatusModalWithPreview", "profilePreview"], + "dateinputbutton": ["BDFDB", "dateInputButton"], + "dateinputbuttonselected": ["BDFDB", "dateInputButtonSelected"], + "dateinputcontrols": ["BDFDB", "dateInputControls"], + "dateinputfield": ["BDFDB", "dateInputField"], + "dateinputinner": ["BDFDB", "dateInputInner"], + "dateinputpreview": ["BDFDB", "dateInputPreview"], + "dateinputpreviewprefix": ["BDFDB", "dateInputPreviewPrefix"], + "dateinputpreviewsuffix": ["BDFDB", "dateInputPreviewSuffix"], + "dateinputwrapper": ["BDFDB", "dateInputWrapper"], + "defaultcolor": ["Text", "defaultColor"], + "description": ["FormText", "description"], + "directiondown": ["IconDirection", "directionDown"], + "directionleft": ["IconDirection", "directionLeft"], + "directionright": ["IconDirection", "directionRight"], + "directionup": ["IconDirection", "directionUp"], + "directiontransition": ["IconDirection", "transition"], + "divider": ["ModalDivider", "divider"], + "dividerdefault": ["BDFDB", "dividerDefault"], + "dmaddpopout": ["DmAddPopout", "popout"], + "dmaddpopoutdiscordtag": ["DmAddPopoutItems", "discordTag"], + "dmaddpopoutfriend": ["DmAddPopoutItems", "friend"], + "dmaddpopoutfriendwrapper": ["DmAddPopoutItems", "friendWrapper"], + "dmaddpopoutnickname": ["DmAddPopoutItems", "nickname"], + "dmchannel": ["PrivateChannel", "channel"], + "dmchannelactivity": ["PrivateChannel", "activity"], + "dmchannelactivityemoji": ["PrivateChannel", "activityEmoji"], + "dmchannelactivitytext": ["PrivateChannel", "activityText"], + "dmchannelclose": ["PrivateChannel", "closeButton"], + "dmchannelheadercontainer": ["PrivateChannelListScroller", "privateChannelsHeaderContainer"], + "dmchannelheadertext": ["PrivateChannelListScroller", "headerText"], + "dmchannels": ["PrivateChannelList", "privateChannels"], + "dmchannelsempty": ["PrivateChannelListScroller", "empty"], + "dmchannelsscroller": ["PrivateChannelListScroller", "scroller"], + "dmpill": ["GuildDm", "pill"], + "defaultchannelwelcome": ["DefaultChannelWelcome", "container"], + "defaultchannelwelcometitle": ["DefaultChannelWelcome", "titleName"], + "ellipsis": ["PopoutActivity", "ellipsis"], + "embed": ["Embed", "embed"], + "embedauthor": ["Embed", "embedAuthor"], + "embedauthoricon": ["Embed", "embedAuthorIcon"], + "embedauthorname": ["Embed", "embedAuthorName"], + "embedauthornamelink": ["Embed", "embedAuthorNameLink"], + "embedcentercontent": ["Embed", "centerContent"], + "embeddescription": ["Embed", "embedDescription"], + "embedfield": ["Embed", "embedField"], + "embedfieldname": ["Embed", "embedFieldName"], + "embedfields": ["Embed", "embedFields"], + "embedfieldvalue": ["Embed", "embedFieldValue"], + "embedfooter": ["Embed", "embedFooter"], + "embedfootericon": ["Embed", "embedFooterIcon"], + "embedfooterseparator": ["Embed", "embedFooterSeparator"], + "embedfootertext": ["Embed", "embedFooterText"], + "embedfull": ["Embed", "embedFull"], + "embedgrid": ["Embed", "grid"], + "embedgridcontainer": ["Embed", "gridContainer"], + "embedhasthumbnail": ["Embed", "hasThumbnail"], + "embediframe": ["Embed", "embedIframe"], + "embedimage": ["Embed", "embedImage"], + "embedinlinemedia": ["Embed", "inlineMediaEmbed"], + "embedlink": ["Embed", "embedLink"], + "embedmargin": ["Embed", "embedMargin"], + "embedmedia": ["Embed", "embedMedia"], + "embedprovider": ["Embed", "embedProvider"], + "embedspoilerattachment": ["Embed", "spoilerAttachment"], + "embedspoilerembed": ["Embed", "spoilerEmbed"], + "embedspotify": ["Embed", "embedSpotify"], + "embedthumbnail": ["Embed", "embedThumbnail"], + "embedtitle": ["Embed", "embedTitle"], + "embedtitlelink": ["Embed", "embedTitleLink"], + "embedvideo": ["Embed", "embedVideo"], + "embedvideoactions": ["Embed", "embedVideoActions"], + "embedvideoimagecomponent": ["Embed", "embedVideoImageComponent"], + "embedvideoimagecomponentinner": ["Embed", "embedVideoImageComponentInner"], + "emoji": ["Emoji", "emoji"], + "emojiold": ["NotFound", "emoji"], + "emojibutton": ["EmojiButton", "emojiButton"], + "emojibuttonhovered": ["EmojiButton", "emojiButtonHovered"], + "emojibuttonnormal": ["EmojiButton", "emojiButtonNormal"], + "emojibuttonsprite": ["EmojiButton", "sprite"], + "emojibuttonspritegreyscale": ["EmojiButton", "spriteGreyscale"], + "emojiinput": ["CustomStatusModalWithPreview", "input"], + "emojiinputbutton": ["CustomStatusModalWithPreview", "emojiButton"], + "emojiinputbuttoncontainer": ["CustomStatusModalWithPreview", "emojiButtonContainer"], + "emojiinputbuttonemoji": ["CustomStatusModalWithPreview", "emoji"], + "emojiinputclearbutton": ["CustomStatusModalWithPreview", "clearButton"], + "emojiinputclearicon": ["CustomStatusModalWithPreview", "clearIcon"], + "emojiinputcontainer": ["CustomStatusModalWithPreview", "inputContainer"], + "emojipickerbutton": ["Reactions", "reactionBtn"], + "emojipicker": ["EmojiPicker", "emojiPicker"], + "emojipickercustomemoji": ["EmojiPickerCustomEmoji", "image"], + "emojipickerdiversityemojiitem": ["EmojiPickerDiversitySelector", "diversityEmojiItem"], + "emojipickerdiversityemojiitemimage": ["EmojiPickerDiversitySelector", "diversityEmojiItemImage"], + "emojipickerdiversityselector": ["EmojiPicker", "diversitySelector"], + "emojipickerdiversityselectoroptions": ["EmojiPickerDiversitySelector", "diversitySelectorOptions"], + "emojipickerdiversityselectorwrapper": ["EmojiPicker", "diversitySelector"], + "emojipickeremojispriteimage": ["EmojiPickerItem", "emojiSpriteImage"], + "emojipickerheader": ["EmojiPicker", "header"], + "emojipickerinspector": ["EmojiPickerInspector", "inspector"], + "emojipickerinspectoremoji": ["EmojiPickerInspectorEmoji", "emoji"], + "errorscreen": ["ErrorScreen", "wrapper"], + "expressionpicker": ["ExpressionPicker", "contentWrapper"], + "expressionpickernav": ["ExpressionPicker", "nav"], + "expressionpickernavbutton": ["ExpressionPicker", "navButton"], + "expressionpickernavbuttonactive": ["ExpressionPicker", "navButtonActive"], + "expressionpickernavitem": ["ExpressionPicker", "navItem"], + "expressionpickernavlist": ["ExpressionPicker", "navList"], + "eyebrow": ["Heading", "eyebrow"], + "favbutton": ["FavButton", "gifFavoriteButton"], + "favbuttoncontainer": ["BDFDB", "favButtonContainer"], + "favbuttonicon": ["FavButton", "icon"], + "favbuttonselected": ["FavButton", "selected"], + "fileattachment": ["File", "attachment"], + "fileattachmentinner": ["File", "attachmentInner"], + "filecancelbutton": ["File", "cancelButton"], + "filedownloadbutton": ["FileDownloadButton", "downloadButton"], + "filename": ["File", "filename"], + "filenamelink": ["File", "fileNameLink"], + "filenamelinkwrapper": ["File", "filenameLinkWrapper"], + "filenamewrapper": ["File", "filenameWrapper"], + "flex": ["Flex", "flex"], + "flexaligncenter": ["FlexProperties", "flexAlignCenter"], + "flexalignend": ["FlexProperties", "flexAlignEnd"], + "flexalignstart": ["FlexProperties", "flexAlignStart"], + "flexalignstretch": ["FlexProperties", "flexAlignStretch"], + "flexchild": ["Flex", "flexChild"], + "flexhorizontal": ["FlexProperties", "flexHorizontal"], + "flexhorizontalreverse": ["FlexProperties", "flexHorizontalReverse"], + "flexjustifycenter": ["FlexProperties", "flexJustifyCenter"], + "flexjustifyend": ["FlexProperties", "flexJustifyEnd"], + "flexjustifystart": ["FlexProperties", "flexJustifyStart"], + "flexnowrap": ["FlexProperties", "flexNoWrap"], + "flexvertical": ["FlexProperties", "flexVertical"], + "flexwrap": ["FlexProperties", "flexWrap"], + "flexwrapreverse": ["FlexProperties", "flexWrapReverse"], + "flowerstar": ["FlowerStar", "flowerStar"], + "flowerstarchild": ["FlowerStar", "childContainer"], + "flowerstarcontainer": ["FlowerStar", "flowerStarContainer"], + "formtext": ["FormText", "formText"], + "forumpagelist": ["ForumPage", "list"], + "giffavoritebutton": ["MessageAccessory", "gifFavoriteButton"], + "giffavoritecolor": ["GifFavoriteButton", "gifFavoriteButton"], + "giffavoriteicon": ["GifFavoriteButton", "icon"], + "goliveactions": ["GoLiveDetails", "actions"], + "golivebody": ["GoLiveDetails", "body"], + "goliveclickablegamewrapper": ["GoLiveDetails", "clickableGameWrapper"], + "golivegameicon": ["GoLiveDetails", "gameIcon"], + "golivegamewrapper": ["GoLiveDetails", "gameWrapper"], + "goliveinfo": ["GoLiveDetails", "info"], + "golivepanel": ["GoLiveDetails", "panel"], + "guild": ["BDFDB", "guild"], + "guildbuttoninner": ["GuildsButton", "circleIconButton"], + "guildbuttonicon": ["GuildsButton", "circleIcon"], + "guildbuttonpill": ["GuildsButton", "pill"], + "guildbuttonselected": ["GuildsButton", "selected"], + "guildchannels": ["GuildChannelsWrapper", "container"], + "guildcontainer": ["GuildServer", "blobContainer"], + "guilddiscovery": ["GuildDiscovery", "pageWrapper"], + "guildfavoritesicon": ["GuildFavorites", "favoriteIcon"], + "guildfavoritesring": ["GuildFavorites", "ring"], + "guildfolder": ["GuildFolder", "folderHeader"], + "guildfolderbutton": ["GuildFolder", "folderButton"], + "guildfolderbuttoninner": ["GuildFolder", "folderButtonInner"], + "guildfolderbuttoncontent": ["GuildFolder", "folderButtonContent"], + "guildfolderexpandedbackground": ["GuildFolder", "folderGroupBackground"], + "guildfolderexpandedbackgroundhover": ["GuildFolder", "isHover"], + "guildfolderguildicon": ["GuildFolder", "folderPreviewGuildIcon"], + "guildfoldericon": ["GuildFolder", "folderIcon"], + "guildfoldericonwrapper": ["GuildFolder", "folderIconWrapper"], + "guildfolderisexpanded": ["GuildFolder", "isExpanded"], + "guildfolderwrapper": ["GuildFolder", "folderGroup"], + "guildheader": ["GuildHeader", "container"], + "guildheaderbanneranimatedhoverlayer": ["GuildHeader", "animatedBannerHoverLayer"], + "guildheaderbannerimage": ["GuildHeader", "bannerImage"], + "guildheaderbannerimagecontainer": ["GuildHeader", "animatedContainer"], + "guildheaderbannervisible": ["GuildHeader", "bannerVisible"], + "guildheaderbutton": ["GuildHeaderButton", "button"], + "guildheaderbuttonopen": ["GuildHeaderButton", "open"], + "guildheaderclickable": ["GuildHeader", "clickable"], + "guildheaderhasbanner": ["GuildHeader", "hasBanner"], + "guildheaderheader": ["GuildHeader", "header"], + "guildheadername": ["GuildHeader", "name"], + "guildicon": ["GuildIcon", "icon"], + "guildiconacronym": ["GuildIcon", "acronym"], + "guildiconbadge": ["GuildItem", "iconBadge"], + "guildiconchildwrapper": ["GuildIcon", "childWrapper"], + "guildiconchildwrappernohoverbg": ["GuildIcon", "childWrapperNoHoverBg"], + "guildiconselected": ["GuildIcon", "selected"], + "guildiconwrapper": ["GuildIcon", "wrapper"], + "guildinner": ["Guild", "wrapper"], + "guildlistitem": ["GuildsListItem", "listItemWrapper"], + "guildlistitemtooltip": ["GuildsListItem", "listItemTooltipContent"], + "guildlowerbadge": ["Guild", "lowerBadge"], + "guildlowerleftbadge": ["BDFDB", "guildBadgeLowerLeft"], + "guildouter": ["GuildItem", "listItem"], + "guildpill": ["GuildServer", "pill"], + "guildpillitem": ["PillWrapper", "item"], + "guildpillwrapper": ["PillWrapper", "wrapper"], + "guildplaceholder": ["GuildsDragPlaceholder", "dragInner"], + "guildplaceholdermask": ["GuildsDragPlaceholder", "placeholderMask"], + "guilds": ["AppBase", "guilds"], + "guildseparator": ["GuildSeparator", "guildSeparator"], + "guildserror": ["GuildsError", "guildsError"], + "guildsfooter": ["GuildsFooter", "footer"], + "guildslabel": ["BDFDB", "guildsLabel"], + "guildsscroller": ["GuildsWrapper", "scroller"], + "guildstree": ["GuildsWrapper", "tree"], + "guildsummaryclickableicon": ["BDFDB", "guildSummaryClickableIcon"], + "guildsummarycontainer": ["BDFDB", "guildSummaryContainer"], + "guildsummaryemptyguild": ["BDFDB", "guildSummaryEmptyGuild"], + "guildsummaryicon": ["BDFDB", "guildSummaryIcon"], + "guildsummaryiconcontainer": ["BDFDB", "guildSummaryIconContainer"], + "guildsummaryiconcontainermasked": ["BDFDB", "guildSummaryIconContainerMasked"], + "guildsummarymoreguilds": ["BDFDB", "guildSummaryMoreGuilds"], + "guildsummarysvgicon": ["BDFDB", "guildSummarySvgIcon"], + "guildsvg": ["Guild", "svg"], + "guildswrapper": ["GuildsWrapper", "wrapper"], + "guildswrapperhidden": ["GuildsWrapper", "hidden"], + "guildswrapperitems": ["GuildsWrapper", "itemsContainer"], + "guildswrapperunreadmentionsbar": ["GuildsWrapper", "unreadMentionsBar"], + "guildswrapperunreadmentionsbarbottom": ["GuildsWrapper", "unreadMentionsIndicatorBottom"], + "guildswrapperunreadmentionsbartop": ["GuildsWrapper", "unreadMentionsIndicatorTop"], + "guildtarget": ["GuildEdges", "target"], + "guildtargetcenter": ["GuildEdges", "centerTarget"], + "guildtargetwrapper": ["GuildEdges", "wrapper"], + "guildtutorialcontainer": ["GuildItem", "tutorialContainer"], + "guildupperbadge": ["Guild", "upperBadge"], + "guildupperleftbadge": ["BDFDB", "guildBadgeUpperLeft"], + "guildvoicelist": ["BDFDB", "guildVoiceList"], + "h1": ["Text", "h1"], + "h1defaultmargin": ["Text", "defaultMarginh1"], + "h2": ["Text", "h2"], + "h2defaultmargin": ["Text", "defaultMarginh2"], + "h3": ["Text", "h3"], + "h3defaultmargin": ["Text", "defaultMarginh3"], + "h4": ["Text", "h4"], + "h4defaultmargin": ["Text", "defaultMarginh4"], + "h5": ["Text", "h5"], + "h5defaultmargin": ["Text", "defaultMarginh5"], + "headertitle": ["Text", "title"], + "highlight": ["NotFound", "highlight"], + "hotkey": ["HotKeyRecorderBase", "container"], + "hotkeyadd": ["HotKeyRecorder", "addKeybindButton"], + "hotkeybutton": ["HotKeyRecorderBase", "button"], + "hotkeycontainer": ["HotKeyRecorder", "recorderContainer"], + "hotkeycontainerdisabled": ["HotKeyRecorder", "containerDisabled"], + "hotkeydisabled": ["HotKeyRecorderBase", "container"], + "hotkeyediticon": ["HotKeyRecorder", "editIcon"], + "hotkeyhasvalue": ["HotKeyRecorder", "hasValue"], + "hotkeyinput": ["HotKeyRecorderBase", "input"], + "hotkeyinputbase": ["HotKeyRecorderBase", "base"], + "hotkeyinner": ["HotKeyRecorder", "recorderLayout"], + "hotkeykeybindinput": ["HotKeyRecorder", "keybindInput"], + "hotkeylayout": ["HotKeyRecorderBase", "layout"], + "hotkeyrecording": ["HotKeyRecorder", "recording"], + "hotkeyresetbutton": ["BDFDB", "hotkeyResetButton"], + "hotkeytext": ["HotKeyRecorder", "text"], + "hotkeywrapper": ["BDFDB", "hotkeyWrapper"], + "hovercard": ["HoverCard", "card"], + "hovercardbutton": ["BDFDB", "hoverCardButton"], + "hovercarddisabled": ["BDFDB", "cardDisabled"], + "hovercardhorizontal": ["BDFDB", "cardHorizontal"], + "hovercardinner": ["BDFDB", "cardInner"], + "hovercardremovebutton": ["HoverCardRemoveButton", "button"], + "hovercardremovebuttondefault": ["HoverCardRemoveButton", "default"], + "hovercardremovebuttonfilled": ["HoverCardRemoveButton", "filled"], + "hovercardwrapper": ["BDFDB", "cardWrapper"], + "icon": ["EmbedActions", "icon"], + "iconactionswrapper": ["EmbedActions", "wrapper"], + "iconexternal": ["EmbedActions", "iconExternal"], + "iconexternalmargins": ["EmbedActions", "iconExternalMargins"], + "iconplay": ["EmbedActions", "iconPlay"], + "iconwrapper": ["EmbedActions", "iconWrapper"], + "iconwrapperactive": ["EmbedActions", "iconWrapperActive"], + "imageaccessory": ["ImageWrapper", "imageAccessory"], + "imagealttext": ["MessageElements", "altText"], + "imagealttextcontainer": ["MessageElements", "mediaMosaicAltTextContainer"], + "imageclickable": ["ImageWrapper", "clickable"], + "imageerror": ["ImageWrapper", "imageError"], + "imageoriginallink": ["ImageWrapper", "originalLink"], + "imagemodal": ["BDFDB", "imageModal"], + "imagemodalactionbutton": ["ImageModalButtons", "actionButton"], + "imagemodalactionbuttoninner": ["ImageModalButtons", "actionButtonInner"], + "imagemodalactionbuttonwrapper": ["ImageModalButtons", "actionButtonWrapper"], + "imagemodalactionbuttons": ["ImageModalButtons", "actionButtons"], + "imagemodalactionbuttonselected": ["ImageModalButtons", "selected"], + "imagemodalimagemedia": ["ImageModalMedia", "media"], + "imagemodalimagemediawrapper": ["ImageModalMediaWrapper", "mediaArea"], + "imagemodalnavbutton": ["ModalCarouselNav", "nav"], + "imagemodalnavbuttonprev": ["ModalCarouselNav", "navPrev"], + "imagemodalnavbuttonnext": ["ModalCarouselNav", "navNext"], + "imagemosaicattachmentscontainer": ["ImageMosaic", "visualMediaItemContainer"], + "imagemosaiconebyonegridsingle": ["ImageMosaic", "oneByOneGridSingle"], + "imagesticker": ["ImageAssets", "stickerAsset"], + "imagewrapper": ["ImageWrapper", "imageWrapper"], + "imagewrapperbackground": ["ImageWrapper", "imageWrapperBackground"], + "imagezoom": ["ImageWrapper", "imageZoom"], + "itemlayer": ["ItemLayerContainer", "layer"], + "itemlayercontainer": ["ItemLayerContainer", "layerContainer"], + "itemlayercontainerclicktrap": ["ItemLayerContainer", "clickTrapContainer"], + "itemlayercontainerzindexdisabled": ["BDFDB", "layerContainerZIndexDisabled"], + "itemlayerdisabledpointerevents": ["ItemLayerContainer", "disabledPointerEvents"], + "input": ["Input", "input"], + "inputdisabled": ["Input", "disabled"], + "inputeditable": ["Input", "editable"], + "inputerror": ["Input", "error"], + "inputfocused": ["Input", "focused"], + "inputlist": ["BDFDB", "listInput"], + "inputlistdelete": ["BDFDB", "listInputDelete"], + "inputlistitem": ["BDFDB", "listInputItem"], + "inputlistitems": ["BDFDB", "listInputItems"], + "inputmini": ["BDFDB", "inputMini"], + "inputmulti": ["BDFDB", "multiInput"], + "inputmultifield": ["BDFDB", "multiInputField"], + "inputmultifirst": ["BDFDB", "multiInputFirst"], + "inputmultilast": ["BDFDB", "multiInputLast"], + "inputmultiwrapper": ["BDFDB", "multiInputWrapper"], + "inputnumberbutton": ["BDFDB", "inputNumberButton"], + "inputnumberbuttondown": ["BDFDB", "inputNumberButtonDown"], + "inputnumberbuttonup": ["BDFDB", "inputNumberButtonUp"], + "inputnumberbuttons": ["BDFDB", "inputNumberButtons"], + "inputnumberwrapper": ["BDFDB", "inputNumberWrapper"], + "inputnumberwrapperdefault": ["BDFDB", "inputNumberWrapperDefault"], + "inputnumberwrappermini": ["BDFDB", "inputNumberWrapperMini"], + "inputsuccess": ["BDFDB", "inputSuccess"], + "inputwrapper": ["Input", "inputWrapper"], + "invite": ["GuildInvite", "wrapper"], + "invitebutton": ["GuildInvite", "button"], + "invitebuttonfornonmember": ["GuildInvite", "buttonForNonMember"], + "invitebuttonresolving": ["GuildInvite", "invite-button-resolving"], + "invitebuttonsize": ["GuildInvite", "buttonSize"], + "invitechannel": ["GuildInvite", "channel"], + "invitechannelicon": ["GuildInvite", "channelIcon"], + "invitechannelname": ["GuildInvite", "channelName"], + "invitecontent": ["GuildInvite", "content"], + "invitecount": ["GuildInvite", "count"], + "invitedestination": ["GuildInvite", "inviteDestination"], + "invitedestinationexpired": ["GuildInvite", "inviteDestinationExpired"], + "invitedestinationjoined": ["GuildInvite", "inviteDestinationJoined"], + "inviteguildbadge": ["GuildInvite", "guildBadge"], + "inviteguilddetail": ["GuildInvite", "guildDetail"], + "inviteguildicon": ["GuildInvite", "guildIcon"], + "inviteguildiconexpired": ["GuildInvite", "guildIconExpired"], + "inviteguildiconimage": ["GuildInvite", "guildIconImage"], + "inviteguildiconimagejoined": ["GuildInvite", "guildIconImageJoined"], + "inviteguildiconjoined": ["GuildInvite", "guildIconJoined"], + "inviteguildinfo": ["GuildInvite", "guildInfo"], + "inviteguildname": ["GuildInvite", "guildName"], + "inviteguildnamewrapper": ["GuildInvite", "guildNameWrapper"], + "inviteheader": ["GuildInvite", "header"], + "invitemodal": ["GuildInviteModal", "modal"], + "invitemodalinviterow": ["GuildInviteModal", "inviteRow"], + "invitemodalinviterowname": ["GuildInviteModal", "inviteRowName"], + "invitemodalwrapper": ["GuildInviteModal", "wrapper"], + "invitesplash": ["GuildInvite", "inviteSplash"], + "invitesplashimage": ["GuildInvite", "inviteSplashImage"], + "invitesplashimageloaded": ["GuildInvite", "inviteSplashImageLoaded"], + "inviteresolving": ["GuildInvite", "resolving"], + "inviteresolvingbackground": ["GuildInvite", "resolvingBackground"], + "invitestatus": ["GuildInvite", "status"], + "invitestatuscounts": ["GuildInvite", "statusCounts"], + "invitestatusoffline": ["GuildInvite", "statusOffline"], + "invitestatusonline": ["GuildInvite", "statusOnline"], + "italics": ["TextStyle", "italics"], + "layer": ["Layers", "layer"], + "layerbase": ["Layers", "baseLayer"], + "layers": ["Layers", "layers"], + "layersbg": ["Layers", "bg"], + "linedefaultcolor": ["Text2", "defaultColor"], + "listavatar": ["UserProfileList", "avatar"], + "listguildavatar": ["UserProfileListGuild", "guildAvatar"], + "listguildavatarwithouticon": ["UserProfileListGuild", "guildAvatarWithoutIcon"], + "listname": ["UserProfileList", "name"], + "listrow": ["UserProfileList", "row"], + "listrowcontent": ["UserProfileList", "details"], + "listrowwrapper": ["BDFDB", "listRow"], + "listscroller": ["UserProfileListScroller", "listScroller"], + "livetag": ["LiveTag", "live"], + "livetaggrey": ["LiveTag", "grey"], + "livetaglarge": ["LiveTag", "liveLarge"], + "livetagsmall": ["LiveTag", "liveSmall"], + "loadingicon": ["BDFDB", "loadingIcon"], + "loadingiconwrapper": ["BDFDB", "loadingIconWrapper"], + "loadingscreen": ["LoadingScreen", "container"], + "lottieicon": ["LottieIcon", "lottieIcon"], + "marginbottom4": ["Margins", "marginBottom4"], + "marginbottom8": ["Margins", "marginBottom8"], + "marginbottom20": ["Margins", "marginBottom20"], + "marginbottom40": ["Margins", "marginBottom40"], + "margincenterhorz": ["Margins", "marginCenterHorz"], + "marginleft4": ["BDFDB", "marginLeft4"], + "marginleft8": ["BDFDB", "marginLeft8"], + "marginreset": ["Margins", "marginReset"], + "margintop4": ["Margins", "marginTop4"], + "margintop8": ["Margins", "marginTop8"], + "margintop20": ["Margins", "marginTop20"], + "margintop40": ["Margins", "marginTop40"], + "margintop60": ["Margins", "marginTop60"], + "member": ["Member", "member"], + "memberactivity": ["Member", "activity"], + "membericon": ["Member", "icon"], + "memberinner": ["Member", "memberInner"], + "membername": ["MemberName", "name"], + "membernamecontainer": ["MemberName", "nameContainer"], + "membernameouter": ["MemberName", "container"], + "membernameuser": ["MemberName", "username"], + "memberoffline": ["Member", "offline"], + "memberownericon": ["Member", "ownerIcon"], + "memberpremiumicon": ["Member", "premiumIcon"], + "members": ["MembersWrapper", "members"], + "membersince": ["MemberSince", "memberSince"], + "membersgroup": ["MembersWrapper", "membersGroup"], + "memberswrap": ["MembersWrapper", "membersWrap"], + "memberusername": ["Member", "username"], + "mention": ["NotFound", "mention"], + "mentioninteractive": ["NotFound", "mentionInteractive"], + "mentionwrapper": ["NotFound", "mentionWrapper"], + "menu": ["Menu", "menu"], + "menucaret": ["Menu", "caret"], + "menucheck": ["Menu", "check"], + "menucheckbox": ["Menu", "checkbox"], + "menucolorbrand": ["Menu", "colorBrand"], + "menucolorcustom": ["BDFDB", "menuColorCustom"], + "menucolordanger": ["Menu", "colorDanger"], + "menucolordefault": ["Menu", "colorDefault"], + "menucolorpremium": ["Menu", "colorPremium"], + "menucolorpremiumgradient": ["Menu", "colorPremiumGradient"], + "menucolorsuccess": ["Menu", "colorSuccess"], + "menucustomitem": ["Menu", "customItem"], + "menudisabled": ["Menu", "disabled"], + "menufocused": ["Menu", "focused"], + "menuhideinteraction": ["Menu", "hideInteraction"], + "menuhint": ["BDFDB", "menuItemHint"], + "menuicon": ["Menu", "icon"], + "menuiconcontainer": ["Menu", "iconContainer"], + "menuiconcontainerleft": ["Menu", "iconContainerLeft"], + "menuimage": ["Menu", "image"], + "menuimagecontainer": ["Menu", "imageContainer"], + "menuitem": ["Menu", "item"], + "menulabel": ["Menu", "label"], + "menulabelcontainer": ["Menu", "labelContainer"], + "menureactbuttonfocused": ["MenuReactButton", "focused"], + "menureactbuttonicon": ["MenuReactButton", "icon"], + "menureactbuttons": ["MenuReactButton", "wrapper"], + "menuscroller": ["Menu", "scroller"], + "menuseparator": ["Menu", "separator"], + "menuslider": ["MenuSlider", "slider"], + "menuslidercontainer": ["MenuSlider", "sliderContainer"], + "menusubmenu": ["Menu", "submenu"], + "menusubtext": ["Menu", "subtext"], + "message": ["Message", "message"], + "messageaccessory": ["MessageAccessory", "container"], + "messageattachment": ["MessageAttachment", "mosaicItem"], + "messageattachmentremovebutton": ["MessageAttachment", "removeMosaicItemButton"], + "messageavatar": ["MessageBody", "avatar"], + "messageavatarclickable": ["MessageBody", "clickable"], + "messagebackgroundflash": ["Message", "backgroundFlash"], + "messagebarbase": ["MessageElements", "barBase"], + "messagebarbuttonalt": ["MessageElements", "barButtonAlt"], + "messagebarbuttonbase": ["MessageElements", "barButtonBase"], + "messagebarbuttonicon": ["MessageElements", "barButtonIcon"], + "messagebarbuttonmain": ["MessageElements", "barButtonMain"], + "messagebarjumptopresentbar": ["MessageElements", "jumpToPresentBar"], + "messagebarnewmessagesbar": ["MessageElements", "newMessagesBar"], + "messagebarspan": ["MessageElements", "span"], + "messagebarspinner": ["MessageElements", "spinner"], + "messagebarspinneritem": ["MessageElements", "spinnerItem"], + "messagebeforegroup": ["Message", "beforeGroup"], + "messageblockedaction": ["MessageBlocked", "blockedAction"], + "messageblockedexpanded": ["MessageBlocked", "expanded"], + "messageblockedicon": ["MessageBlocked", "blockedIcon"], + "messageblockedsystemmessage": ["MessageBlocked", "blockedSystemMessage"], + "messageblockedtext": ["MessageBlocked", "blockedMessageText"], + "messageblockquotecontainer": ["MessageMarkup", "blockquoteContainer"], + "messageblockquotedivider": ["MessageMarkup", "blockquoteDivider"], + "messagebottag": ["MessageBody", "botTag"], + "messagebottagcompact": ["MessageBody", "botTagCompact"], + "messagebottagcozy": ["MessageBody", "botTagCozy"], + "messagebuttoncontainer": ["MessageBody", "buttonContainer"], + "messagebuttons": ["Message", "buttons"], + "messagechanneltextarea": ["Message", "channelTextArea"], + "messagecompact": ["MessageBody", "compact"], + "messagecontents": ["MessageBody", "contents"], + "messagecozy": ["MessageBody", "cozy"], + "messagecozymessage": ["Message", "cozyMessage"], + "messagedisableinteraction": ["Message", "disableInteraction"], + "messagedivider": ["Message", "divider"], + "messagedividerhascontent": ["Message", "hasContent"], + "messageedited": ["MessageBody", "edited"], + "messagegroupstart": ["Message", "groupStart"], + "messageheader": ["MessageBody", "header"], + "messageheadertext": ["MessageBody", "headerText"], + "messagelistitem": ["Message", "messageListItem"], + "messagelocalbot": ["Message", "ephemeral"], + "messagelocalboticon": ["MessageLocalBot", "icon"], + "messagelocalbotoperations": ["MessageLocalBot", "ephemeralMessage"], + "messagemarkup": ["MessageMarkup", "markup"], + "messagemarkupcompact": ["MessageBody", "compact"], + "messagemarkupcontent": ["MessageBody", "messageContent"], + "messagemarkupcozy": ["MessageBody", "cozy"], + "messagemarkupisfailed": ["MessageBody", "isFailed"], + "messagemarkupissending": ["MessageBody", "isSending"], + "messagemarkuprtl": ["MessageBody", "markupRtl"], + "messagemarkuptimestamp": ["MessageMarkup", "timestamp"], + "messagemarkuptimestamptooltip": ["MessageMarkup", "timestampTooltip"], + "messagementioned": ["Message", "mentioned"], + "messagepopout": ["MessagePopout", "message"], + "messageoperations": ["MessageOperations", "operations"], + "messagereaction": ["MessageReactions", "reaction"], + "messagereactionme": ["MessageReactions", "reactionMe"], + "messagereactions": ["MessageReactions", "reactions"], + "messagereactionsmodalemoji": ["MessageReactionsModal", "emoji"], + "messagereactionsmodalname": ["MessageReactionsModal", "name"], + "messagereactionsmodalnickname": ["MessageReactionsModal", "nickname"], + "messagereactionsmodalreactor": ["MessageReactionsModal", "reactor"], + "messagereactionsmodalusername": ["MessageReactionsModal", "username"], + "messagerepliedmessage": ["MessageBody", "repliedMessage"], + "messagerepliedmessagecontent": ["MessageBody", "repliedTextContent"], + "messagerepliedmessagecontentclickable": ["MessageBody", "clickable"], + "messagerepliedmessagepreview": ["MessageBody", "repliedTextPreview"], + "messagereply": ["MessageReply", "container"], + "messagereplyname": ["MessageReply", "name"], + "messagereplytext": ["MessageReply", "text"], + "messageroleicon": ["MessageBody", "roleIcon"], + "messageselected": ["Message", "selected"], + "messages": ["Messages", "messages"], + "messagesdivider": ["Messages", "divider"], + "messagesloadingavatar": ["MessagesLoading", "avatar"], + "messagesloadingcompact": ["MessagesLoading", "compact"], + "messagesloadingcozy": ["MessagesLoading", "cozy"], + "messagesloadingmessage": ["MessagesLoading", "wrapper"], + "messagesloadingwrapper": ["NotFound", "messagesLoadingWrapper"], + "messagespopout": ["MessagesPopout", "messagesPopout"], + "messagespopoutactionbuttons": ["MessagesPopout", "actionButtons"], + "messagespopoutbody": ["MessagesPopout", "body"], + "messagespopoutbutton": ["MessagesPopoutButtons", "button"], + "messagespopoutbuttonchildren": ["MessagesPopoutButtons", "buttonChildren"], + "messagespopoutbuttonchildrenwrapper": ["MessagesPopoutButtons", "buttonChildrenWrapper"], + "messagespopoutbuttonsecondary": ["MessagesPopoutButtons", "secondary"], + "messagespopoutbuttonsm": ["MessagesPopoutButtons", "sm"], + "messagespopoutbuttonxs": ["MessagesPopoutButtons", "xs"], + "messagespopoutchannelname": ["MessagesPopoutInfo", "channelName"], + "messagespopoutchannelseparator": ["BDFDB", "messagesPopoutChannelSeparator"], + "messagespopoutcontrols": ["MessagesPopoutTabBar", "controls"], + "messagespopoutemptyplaceholder": ["MessagesPopout", "emptyPlaceholder"], + "messagespopoutfooter": ["MessagesPopout", "footer"], + "messagespopoutguildname": ["MessagesPopoutInfo", "guildName"], + "messagespopoutgroupcozy": ["MessagesPopout", "messageGroupCozy"], + "messagespopoutgroupwrapper": ["MessagesPopout", "messageGroupWrapper"], + "messagespopouthasmore": ["MessagesPopout", "hasMore"], + "messagespopoutheader": ["MessagesPopout", "header"], + "messagespopoutheaderdivider": ["MessagesPopoutHeader", "divider"], + "messagespopoutimage": ["MessagesPopout", "image"], + "messagespopoutloadingmore": ["MessagesPopout", "loadingMore"], + "messagespopoutloadingplaceholder": ["MessagesPopout", "loadingPlaceholder"], + "messagespopoutscrollingfooterwrap": ["MessagesPopout", "scrollingFooterWrap"], + "messagespopoutsearchbox": ["MessagesPopoutExtras", "control"], + "messagespopouttabbar": ["MessagesPopoutTabBar", "headerTabs"], + "messagespopouttabbarheader": ["MessagesPopoutTabBar", "header"], + "messagespopouttabbarinner": ["MessagesPopoutTabBar", "tabBar"], + "messagespopouttabbartab": ["MessagesPopoutTabBar", "tab"], + "messagespopouttitle": ["MessagesPopoutHeader", "title"], + "messagespopoutwrap": ["MessagesPopout", "messagesPopoutWrap"], + "messagesscroller": ["MessagesWrap", "scroller"], + "messagesscrollercontent": ["MessagesWrap", "scrollerContent"], + "messagesscrollerinner": ["MessagesWrap", "scrollerInner"], + "messageswelcome": ["MessagesWelcome", "container"], + "messageswelcomebutton": ["MessagesWelcomeButton", "button"], + "messageswelcomebuttonicon": ["MessagesWelcomeButton", "buttonIcon"], + "messageswelcomedescription": ["MessagesWelcome", "description"], + "messageswelcomeemptychannelicon": ["MessagesWelcome", "emptyChannelIcon"], + "messageswelcomeheader": ["MessagesWelcome", "header"], + "messageswelcomelocked": ["MessagesWelcome", "locked"], + "messageswelcomethreadcreator": ["MessagesWelcomeThread", "threadCreatorName"], + "messageswrapper": ["MessagesWrap", "messagesWrapper"], + "messagesystem": ["Message", "systemMessage"], + "messagesystemcontainer": ["MessageSystem", "container"], + "messagesystemcontent": ["MessageSystem", "content"], + "messagesystemicon": ["MessageSystem", "icon"], + "messagesystemiconcontainer": ["MessageSystem", "iconContainer"], + "messagesystemiconsize": ["MessageSystem", "iconSize"], + "messagesystemname": ["MessageSystemAccessories", "name"], + "messagethreadaccessory": ["MessageBody", "threadMessageAccessory"], + "messagethreadaccessoryavatar": ["MessageBody", "threadMessageAccessoryAvatar"], + "messagethreadaccessorypreview": ["MessageBody", "threadMessageAccessoryPreview"], + "messagetimedivider": ["MessageDivider", "divider"], + "messagetimedividercontent": ["MessageDivider", "content"], + "messagetimedividerhascontent": ["MessageDivider", "hasContent"], + "messagetimedividerisunread": ["MessageDivider", "isUnread"], + "messagetimedividerunreadpill": ["MessageDivider", "unreadPill"], + "messagetimedividerunreadpillcap": ["MessageDivider", "unreadPillCap"], + "messagetimedividerunreadpillcapstroke": ["MessageDivider", "unreadPillCapStroke"], + "messagetimestampasiancompact": ["MessageBody", "asianCompactTimeStamp"], + "messagetimestamp": ["MessageBody", "timestamp"], + "messagetimestampalt": ["MessageBody", "alt"], + "messagetimestampinline": ["MessageBody", "timestampInline"], + "messagetimestamplatin12compact": ["MessageBody", "latin12CompactTimeStamp"], + "messagetimestamplatin24compact": ["MessageBody", "latin24CompactTimeStamp"], + "messagetimestampseparator": ["MessageBody", "separator"], + "messagetimestamptooltip": ["MessageBody", "timestampTooltip"], + "messagetimestampvisibleonhover": ["MessageBody", "timestampVisibleOnHover"], + "messagetoolbar": ["MessageToolbar", "container"], + "messagetoolbarbutton": ["MessageToolbarItems", "button"], + "messagetoolbarbuttoncontent": ["MessageToolbarExtras", "buttonContent"], + "messagetoolbarbuttondisabled": ["MessageToolbarItems", "disabled"], + "messagetoolbarbuttonselected": ["MessageToolbarItems", "selected"], + "messagetoolbarhoverbutton": ["MessageToolbarExtras", "hoverBarButton"], + "messagetoolbaricon": ["MessageToolbarExtras", "icon"], + "messagetoolbarinner": ["MessageToolbarItems", "wrapper"], + "messagetoolbarisheader": ["MessageToolbar", "isHeader"], + "messagetoolbarseparator": ["MessageToolbarExtras", "separator"], + "messagetoolbartooltip": ["BDFDB", "messageToolbarExtrasTooltip"], + "messageuploadcancel": ["MessageFile", "cancelButton"], + "messageusername": ["MessageBody", "username"], + "messagewrapper": ["MessageBody", "wrapper"], + "messagezalgo": ["MessageBody", "zalgo"], + "modal": ["Modal", "root"], + "modalbodyinner": ["ModalNew", "bodyInner"], + "modalcarouselmodal": ["NotFound", "carouselModal"], + "modalclose": ["Modal", "close"], + "modalchangelogmodal": ["BDFDB", "changeLogModal"], + "modalconfirmmodal": ["BDFDB", "confirmModal"], + "modalcontainer": ["ModalNew", "container"], + "modalcontent": ["Modal", "content"], + "modalfooter": ["Modal", "footer"], + "modalheader": ["Modal", "header"], + "modalheaderhassibling": ["BDFDB", "modalHeaderHasSibling"], + "modalheadershade": ["BDFDB", "modalHeaderShade"], + "modallarge": ["Modal", "large"], + "modallayer": ["ModalLayer", "layer"], + "modallayerbackdrop": ["ModalLayerBackdrop", "backdrop"], + "modallayerbackdropwithlayer": ["ModalLayerBackdrop", "withLayer"], + "modallayerhidden": ["ModalLayer", "hidden"], + "modalmedium": ["Modal", "medium"], + "modalmini": ["ModalMiniContent", "modal"], + "modalminicontent": ["ModalMiniContent", "content"], + "modalminitext": ["HeaderBarTopic", "content"], + "modalnoscroller": ["BDFDB", "modalNoScroller"], + "modalseparator": ["Modal", "separator"], + "modalsidebar": ["BDFDB", "modalSidebar"], + "modalsmall": ["Modal", "small"], + "modalsub": ["ModalSub", "modal"], + "modalsublarge": ["ModalSub", "sizeLarge"], + "modalsubmedium": ["ModalSub", "sizeMedium"], + "modalsubsmall": ["ModalSub", "sizeSmall"], + "modalsubinner": ["BDFDB", "modalSubInner"], + "modaltabcontent": ["BDFDB", "modalTabContent"], + "modaltabcontentopen": ["BDFDB", "modalTabContentOpen"], + "modaltextcontent": ["BDFDB", "modalTextContent"], + "modalwrapper": ["BDFDB", "modalWrapper"], + "modedefault": ["FormText", "modeDefault"], + "modedisabled": ["FormText", "modeDisabled"], + "modeselectable": ["FormText", "modeSelectable"], + "namecontainer": ["NameContainer", "nameContainer"], + "namecontaineravatar": ["NameContainer", "avatar"], + "namecontainercontent": ["NameContainer", "content"], + "namecontainerinteractive": ["NameContainerState", "interactive"], + "namecontainerlayout": ["NameContainer", "layout"], + "namecontainermuted": ["NameContainerState", "muted"], + "namecontainername": ["NameContainer", "name"], + "namecontainernamecontainer": ["NotFound", "nameContainerNameContainer"], + "namecontainernamewrapper": ["NameContainer", "nameAndDecorators"], + "namecontainerselected": ["NameContainerState", "selected"], + "namecontainersubtext": ["NameContainer", "subText"], + "nametag": ["NameTag", "nameTag"], + "nitrostore": ["NitroStore", "applicationStore"], + "nochannel": ["ChatWindow", "noChat"], + "notice": ["Notice", "notice"], + "noticebrand": ["Notice", "colorBrand"], + "noticebutton": ["Notice", "button"], + "noticebuttonminor": ["Notice", "buttonMinor"], + "noticeclosing": ["BDFDB", "noticeClosing"], + "noticecustom": ["Notice", "colorCustom"], + "noticedanger": ["Notice", "colorDanger"], + "noticedefault": ["Notice", "colorDefault"], + "noticedismiss": ["Notice", "closeButton"], + "noticedismissicon": ["Notice", "closeIcon"], + "noticeicon": ["NoticePlatform", "icon"], + "noticeiconandroid": ["NoticePlatform", "iconAndroid"], + "noticeiconapple": ["NoticePlatform", "iconApple"], + "noticeiconwindows": ["NoticePlatform", "iconWindows"], + "noticeinfo": ["Notice", "colorInfo"], + "noticeneutral": ["Notice", "colorNeutral"], + "noticeplatformicon": ["NoticePlatform", "platformIcon"], + "noticeplaystation": ["Notice", "colorPlayStation"], + "noticepremium": ["Notice", "colorPremium"], + "noticepremiumaction": ["NoticePlatform", "premiumAction"], + "noticepremiumicon": ["NoticePlatform", "premiumIcon"], + "noticepremiumlogo": ["NoticePlatform", "premiumLogo"], + "noticepremiumtext": ["NoticePlatform", "premiumText"], + "noticepremiumtier0": ["Notice", "colorPremiumTier0"], + "noticepremiumtier1": ["Notice", "colorPremiumTier1"], + "noticepremiumtier2": ["Notice", "colorPremiumTier2"], + "noticespotify": ["Notice", "colorSpotify"], + "noticestreamer": ["Notice", "colorStreamerMode"], + "noticesuccess": ["BDFDB", "noticeSuccess"], + "noticetext": ["BDFDB", "noticeText"], + "noticetextlink": ["NoticePlatform", "textLink"], + "noticeupdate": ["BDFDB", "noticeUpdate"], + "noticeupdatebuttonall": ["BDFDB", "noticeUpdateButtonAll"], + "noticeupdatebuttonreload": ["BDFDB", "noticeUpdateButtonReload"], + "noticeupdateentries": ["BDFDB", "noticeUpdateEntries"], + "noticeupdateentry": ["BDFDB", "noticeUpdateEntry"], + "noticeupdatetext": ["BDFDB", "noticeUpdateText"], + "noticeupdateseparator": ["BDFDB", "noticeUpdateSeparator"], + "noticewarning": ["Notice", "colorWarning"], + "noticewrapper": ["BDFDB", "noticeWrapper"], + "overflowellipsis": ["BDFDB", "overflowEllipsis"], + "pageimage": ["PageImage", "image"], + "pageimagetext": ["PageImage", "text"], + "pageimagewrapper": ["PageImage", "wrapper"], + "pagination": ["BDFDB", "pagination"], + "paginationbottom": ["BDFDB", "paginationBottom"], + "paginationbutton": ["Pagination", "pageButton"], + "paginationbuttonactive": ["Pagination", "activeButton"], + "paginationbuttonend": ["Pagination", "endButton"], + "paginationbuttonendinner": ["Pagination", "endButtonInner"], + "paginationbuttonround": ["Pagination", "roundButton"], + "paginationcontainer": ["Pagination", "pageControlContainer"], + "paginationcontrol": ["Pagination", "pageControl"], + "paginationgap": ["Pagination", "gap"], + "paginationicon": ["Pagination", "iconCaret"], + "paginationlist": ["BDFDB", "paginationList"], + "paginationlistalphabet": ["BDFDB", "paginationListAlphabet"], + "paginationlistalphabetchar": ["BDFDB", "paginationListAlphabetChar"], + "paginationlistalphabetchardisabled": ["BDFDB", "paginationListAlphabetCharDisabled"], + "paginationlistcontent": ["BDFDB", "paginationListContent"], + "paginationlistmini": ["BDFDB", "paginationListMini"], + "paginationmini": ["BDFDB", "paginationMini"], + "paginationtop": ["BDFDB", "paginationTop"], + "peoples": ["Peoples", "container"], + "peoplesactions": ["PeopleItemInfo", "actions"], + "peoplesactionbutton": ["PeopleItemButton", "actionButton"], + "peoplesactionbuttonhighlight": ["PeopleItemButton", "highlight"], + "peoplesavatar": ["PeopleItemUser", "avatar"], + "peoplesbadge": ["Peoples", "badge"], + "peoplescontents": ["PeopleItemInfo", "listItemContents"], + "peoplesdiscordtag": ["PeopleItemUser", "discordTag"], + "peoplesdiscriminator": ["PeopleItemUser", "discriminator"], + "peoplesitem": ["PeopleItem", "peopleListItem"], + "peopleslist": ["PeopleList", "peopleList"], + "peopleslistempty": ["PeopleList", "emptyStateContainer"], + "peopleslistsearchbar": ["PeopleList", "searchBar"], + "peoplesnowplayingcolumn": ["Peoples", "nowPlayingColumn"], + "peoplesnowplayingmember": ["PeoplesNowPlayingMember", "memberItem"], + "peoplespeoplecolumn": ["Peoples", "peopleColumn"], + "peoplestabbar": ["Peoples", "tabBar"], + "peoplestabbaritem": ["Peoples", "item"], + "peoplessubtext": ["PeopleItemUser", "subtext"], + "peoplestext": ["PeopleItemUser", "text"], + "peoplesuser": ["PeopleItemUser", "userInfo"], + "peoplesuserhovered": ["PeopleItemUser", "hovered"], + "peoplesusername": ["PeopleItemUser", "username"], + "pictureinpicture": ["PictureInPicture", "pictureInPicture"], + "pictureinpicturewindow": ["PictureInPicture", "pictureInPictureWindow"], + "pollmodalemoji": ["PollModal", "emoji"], + "popoutarrow": ["BDFDB", "popoutArrow"], + "popoutarrowbottom": ["BDFDB", "popoutArrowBottom"], + "popoutarrowtop": ["BDFDB", "popoutArrowTop"], + "popoutthemedpopout": ["BDFDB", "themedPopout"], + "popoutwrapper": ["BDFDB", "popoutWrapper"], + "quickmessage": ["QuickMessage", "input"], + "quickselect": ["QuickSelect", "quickSelect"], + "quickselectarrow": ["QuickSelect", "quickSelectArrow"], + "quickselectclick": ["QuickSelect", "quickSelectClick"], + "quickselectlabel": ["QuickSelect", "quickSelectLabel"], + "quickselectvalue": ["QuickSelect", "quickSelectValue"], + "quickselectwrapper": ["BDFDB", "quickSelectWrapper"], + "quickswitcher": ["QuickSwitchWrap", "quickswitcher"], + "quickswitchresult": ["QuickSwitch", "result"], + "quickswitchresultguildicon": ["QuickSwitch", "guildIcon"], + "quickswitchresulticon": ["QuickSwitch", "icon"], + "quickswitchresulticoncontainer": ["QuickSwitch", "iconContainer"], + "quickswitchresultmatch": ["QuickSwitch", "match"], + "quickswitchresultmisccontainer": ["QuickSwitchWrap", "miscContainer"], + "quickswitchresultname": ["QuickSwitch", "name"], + "quickswitchresultnote": ["QuickSwitch", "note"], + "quickswitchresultusername": ["QuickSwitch", "username"], + "radiogroup": ["RadioGroup", "item"], + "radiogroupinner": ["RadioGroup", "radioBar"], + "recentmentionschannelname": ["RecentMentionsHeader", "channelName"], + "recentmentionschannelnameheader": ["RecentMentionsHeader", "channelNameHeader"], + "recentmentionschannelnamespan": ["RecentMentionsHeader", "channelNameSpan"], + "recentmentionsclosebutton": ["RecentMentions", "closeButton"], + "recentmentionsdmicon": ["RecentMentionsHeader", "dmIcon"], + "recentmentionsguildicon": ["RecentMentionsHeader", "guildIcon"], + "recentmentionsguildname": ["RecentMentionsHeader", "guildName"], + "recentmentionsjumpbutton": ["RecentMentions", "jumpButton"], + "recentmentionspopout": ["RecentMentions", "recentMentionsPopout"], + "roleicon": ["RoleIcon", "roleIcon"], + "scrollbar": ["Scrollbar", "scrollbar"], + "scrollbardefault": ["Scrollbar", "scrollbarDefault"], + "scrollbarghost": ["Scrollbar", "scrollbarGhost"], + "scrollbarghosthairline": ["Scrollbar", "scrollbarGhostHairline"], + "scroller": ["Scroller", "scrollerBase"], + "scrollerauto": ["Scroller", "auto"], + "scrollercontent": ["Scroller", "content"], + "scrollercustomtheme": ["Scroller", "customTheme"], + "scrollerdisablescrollanchor": ["Scroller", "disableScrollAnchor"], + "scrollerfade": ["Scroller", "fade"], + "scrollernone": ["Scroller", "none"], + "scrollerscrolling": ["Scroller", "scrolling"], + "scrollerthin": ["Scroller", "thin"], + "searchbar": ["SearchBar", "container"], + "searchbarclear": ["SearchBarIcon", "clear"], + "searchbaricon": ["SearchBarIcon", "icon"], + "searchbariconlayout": ["SearchBarIcon", "iconLayout"], + "searchbariconwrap": ["SearchBarIcon", "iconContainer"], + "searchbarinner": ["SearchBar", "inner"], + "searchbarinput": ["SearchBar", "input"], + "searchbarvisible": ["SearchBarIcon", "visible"], + "searchbarwrapper": ["BDFDB", "searchBarWrapper"], + "searchpopout": ["SearchPopoutWrap", "container"], + "searchpopoutdatepicker": ["SearchPopoutDatepicker", "datePicker"], + "searchpopoutdisplayavatar": ["SearchPopout", "displayAvatar"], + "searchpopoutdisplayusername": ["SearchPopout", "displayUsername"], + "searchpopoutdisplayednick": ["SearchPopout", "displayedNick"], + "searchpopoutnontext": ["SearchPopout", "nonText"], + "searchpopoutoption": ["SearchPopout", "item"], + "searchpopoutresultchannel": ["SearchPopout", "channelName"], + "searchpopoutresultchannelicon": ["SearchPopout", "channelNameIcon"], + "searchresultschannelname": ["SearchResultsGroup", "channelNameText"], + "searchresultsblocked": ["SearchResultsGroup", "resultsBlocked"], + "searchresultsgroup": ["SearchResultsGroup", "searchResultGroup"], + "searchresultsmessage": ["SearchResultsMessage", "message"], + "searchresultspagination": ["NotFound", "searchResultsPagination"], + "searchresultsresult": ["SearchResultsMessage", "searchResult"], + "searchresultsscroller": ["SearchResults", "scroller"], + "searchresultswrap": ["SearchResults", "searchResultsWrap"], + "select": ["Select", "select"], + "selectfilterpopout": ["SelectFilterPopout", "selectFilterPopout"], + "selectfilterpopoutavatar": ["SelectFilterPopout", "avatar"], + "selectoption": ["Select", "option"], + "selectouter": ["Select", "wrapper"], + "selectsearchinput": ["Select", "searchInput"], + "selectselectedicon": ["Select", "selectedIcon"], + "selectwrapper": ["BDFDB", "selectWrapper"], + "settingsclosebutton": ["SettingsCloseButton", "closeButton"], + "settingsclosebuttoncontainer": ["SettingsCloseButton", "container"], + "settingsguild": ["BDFDB", "settingsGuild"], + "settingsguilddisabled": ["BDFDB", "settingsGuildDisabled"], + "settingsheader": ["Item", "header"], + "settingsitem": ["Item", "item"], + "settingsitemselected": ["Item", "selected"], + "settingsitemthemed": ["Item", "themed"], + "settingspanel": ["BDFDB", "settingsPanel"], + "settingspanellist": ["BDFDB", "settingsPanelList"], + "settingspanellistwrapper": ["BDFDB", "settingsPanelListWrapper"], + "settingspanellistwrappermini": ["BDFDB", "settingsPanelListWrapperMini"], + "settingsrow": ["SettingsItems", "container"], + "settingsrowcontainer": ["BDFDB", "settingsRow"], + "settingsrowcontrol": ["SettingsItems", "control"], + "settingsrowdisabled": ["BDFDB", "settingsRowDisabled"], + "settingsrowlabel": ["SettingsItems", "labelContainer"], + "settingsrownote": ["SettingsItems", "description"], + "settingsrowtitle": ["SettingsItems", "label"], + "settingsrowtitlemini": ["BDFDB", "settingsRowTitleMini"], + "settingsseparator": ["Item", "separator"], + "settingstableheader": ["GuildNotificationsModal", "header"], + "settingstableheadername": ["GuildNotificationsModal", "headerName"], + "settingstableheaderoption": ["GuildNotificationsModal", "headerOption"], + "settingstableheaderoptions": ["BDFDB", "settingsTableHeaderOptions"], + "settingstableheaders": ["BDFDB", "settingsTableHeaders"], + "settingstableheadervertical": ["BDFDB", "settingsTableHeaderVertical"], + "settingstablecard": ["BDFDB", "settingsTableCard"], + "settingstablecardconfigs": ["BDFDB", "settingsTableCardConfigs"], + "settingstablecardlabel": ["BDFDB", "settingsTableCardLabel"], + "settingstablelist": ["BDFDB", "settingsTableList"], + "settingswindowcontentcolumn": ["SettingsWindow", "contentColumn"], + "settingswindowcontentcolumndefault": ["SettingsWindow", "contentColumnDefault"], + "settingswindowcontentregion": ["SettingsWindow", "contentRegion"], + "settingswindowcontentregionscroller": ["SettingsWindow", "contentRegionScroller"], + "settingswindowsidebar": ["SettingsWindow", "sidebar"], + "settingswindowsidebarregion": ["SettingsWindow", "sidebarRegion"], + "settingswindowsidebarregionscroller": ["SettingsWindow", "sidebarRegionScroller"], + "settingswindowstandardsidebarview": ["SettingsWindow", "standardSidebarView"], + "settingswindowtoolscontainer": ["SettingsWindow", "toolsContainer"], + "sidebar": ["BDFDB", "sidebar"], + "sidebarcontent": ["BDFDB", "sidebarContent"], + "sidebarlist": ["BDFDB", "sidebarList"], + "slider": ["Slider", "slider"], + "sliderbar": ["Slider", "bar"], + "sliderbarfill": ["Slider", "barFill"], + "sliderbubble": ["BDFDB", "sliderBubble"], + "sliderdisabled": ["Slider", "disabled"], + "slidergrabber": ["Slider", "grabber"], + "sliderhasmarks": ["Slider", "hasMarks"], + "slidermark": ["Slider", "mark"], + "slidermarkabove": ["Slider", "markAbove"], + "slidermarkbelow": ["Slider", "markBelow"], + "slidermarkdash": ["Slider", "markDash"], + "slidermarkdashsimple": ["Slider", "markDashSimple"], + "slidermarkvalue": ["Slider", "markValue"], + "slidermini": ["Slider", "mini"], + "slidertrack": ["Slider", "track"], + "spinner": ["Spinner", "spinner"], + "spoilercontainer": ["Spoiler", "spoilerContainer"], + "spoilerhidden": ["Spoiler", "hidden"], + "spoilertext": ["Spoiler", "spoilerContent"], + "spoilerwarning": ["Spoiler", "spoilerWarning"], + "stack": ["Stack", "stack"], + "strikethrough": ["TextStyle", "strikethrough"], + "stopanimations": ["NotFound", "stopAnimations"], + "subtext": ["NotFound", "subtext"], + "svgicon": ["BDFDB", "svgIcon"], + "svgiconwrapper": ["BDFDB", "svgIconWrapper"], + "switch": ["Switch", "container"], + "switchchecked": ["Switch", "checked"], + "switchinner": ["Switch", "input"], + "switchmini": ["BDFDB", "switchMini"], + "switchslider": ["Switch", "slider"], + "tabbar": ["SettingsTabBar", "tabBar"], + "tabbarcontainer": ["BDFDB", "tabBarContainer"], + "tabbarcontainerbottom": ["BDFDB", "tabBarContainerBottom"], + "tabbaritem": ["SettingsTabBar", "tab"], + "tabbarside": ["Item", "side"], + "tabbartop": ["Item", "top"], + "tabbartoppill": ["Item", "topPill"], + "table": ["BDFDB", "table"], + "tablebodycell": ["BDFDB", "tableBodyCell"], + "tableheadercell": ["BDFDB", "tableHeaderCell"], + "tableheadercellclickable": ["Table", "clickable"], + "tableheadercellcontent": ["Table", "headerCellContent"], + "tableheadercellsorted": ["BDFDB", "tableHeaderCellSorted"], + "tableheadercellwrapper": ["Table", "headerCell"], + "tableheadersorticon": ["Table", "sortIcon"], + "tablerow": ["Table", "row"], + "tablestickyheader": ["Table", "stickyHeader"], + "tabularnumbers": ["Text2", "tabularNumbers"], + "textarea": ["ChannelTextArea", "textArea"], + "textareaaccessorybar": ["ChannelTextArea", "accessoryBar"], + "textareaattachbutton": ["ChannelTextAreaAttachButton", "attachButton"], + "textareaattachbuttoninner": ["ChannelTextAreaAttachButton", "attachButtonInner"], + "textareaattachbuttonplus": ["ChannelTextAreaAttachButton", "attachButtonPlus"], + "textareaattachwrapper": ["ChannelTextAreaAttachButton", "attachWrapper"], + "textareabutton": ["ChannelTextAreaButton", "button"], + "textareabuttonactive": ["ChannelTextAreaButton", "active"], + "textareabuttonpulse": ["ChannelTextAreaButton", "pulseButton"], + "textareabuttonwrapper": ["ChannelTextAreaButton", "buttonWrapper"], + "textareacharcounter": ["ChannelTextAreaCharCounter", "characterCount"], + "textareacharcounterinner": ["ChannelTextAreaCharCounter", "flairContainer"], + "textareacharcounterupsell": ["ChannelTextAreaCharCounter", "upsell"], + "textareadisabled": ["ChannelTextArea", "textAreaDisabled"], + "textareafontsize12padding": ["ChannelTextArea", "fontSize12Padding"], + "textareafontsize14padding": ["ChannelTextArea", "fontSize14Padding"], + "textareafontsize15padding": ["ChannelTextArea", "fontSize15Padding"], + "textareafontsize16padding": ["ChannelTextArea", "fontSize16Padding"], + "textareafontsize18padding": ["ChannelTextArea", "fontSize18Padding"], + "textareafontsize20padding": ["ChannelTextArea", "fontSize20Padding"], + "textareafontsize24padding": ["ChannelTextArea", "fontSize24Padding"], + "textareaicon": ["ChannelTextAreaButton", "icon"], + "textareaiconpulse": ["ChannelTextAreaButton", "pulseIcon"], + "textareainner": ["ChannelTextArea", "inner"], + "textareainnerdisabled": ["ChannelTextArea", "innerDisabled"], + "textareapickerbutton": ["ChannelTextArea", "button"], + "textareapickerbuttoncontainer": ["ChannelTextArea", "buttonContainer"], + "textareapickerbuttons": ["ChannelTextArea", "buttons"], + "textareascrollablecontainer": ["ChannelTextArea", "scrollableContainer"], + "textareaslate": ["ChannelTextArea", "textAreaSlate"], + "textareauploadinput": ["ChannelTextAreaAttachButton", "uploadInput"], + "textareawrapall": ["ChannelTextArea", "channelTextArea"], + "textareawrapchat": ["ChatWindow", "channelTextArea"], + "textareawrapdisabled": ["ChannelTextArea", "channelTextAreaDisabled"], + "textrow": ["PopoutActivity", "textRow"], + "textscroller": ["BDFDB", "textScroller"], + "themecustombackground": ["NotFound", "themeCustomBackground"], + "themedark": ["NotFound", "themeDark"], + "themeenableforcedcolors": ["NotFound", "enableForcedColors"], + "themelight": ["NotFound", "themeLight"], + "themeundefined": ["NotFound", "themeUndefined"], + "threadcard": ["ThreadCard", "container"], + "threadcardauthor": ["ThreadCard", "authorName"], + "threadcardavatar": ["ThreadCard", "avatar"], + "threadcardname": ["ThreadCard", "threadName"], + "threadcardstartedby": ["ThreadCard", "startedByName"], + "tip": ["Tip", "tip"], + "tipblock": ["Tip", "block"], + "tippro": ["Tip", "pro"], + "tipinline": ["Tip", "inline"], + "titlebar": ["TitleBar", "titleBar"], + "titlebarthick": ["TitleBarNew", "bar"], + "titlebarthickbutton": ["TitleBarNew", "button"], + "titlebarmac": ["TitleBar", "typeMacOS"], + "titlebarmacbutton": ["TitleBar", "macButton"], + "titlebarmacbuttonclose": ["TitleBar", "macButtonClose"], + "titlebarmacbuttonmin": ["TitleBar", "macButtonMinimize"], + "titlebarmacbuttonmax": ["TitleBar", "macButtonMaximize"], + "titlebarmacbuttons": ["TitleBar", "macButtons"], + "titlebarmacwithframe": ["TitleBar", "typeMacOSWithFrame"], + "titlebarwinbutton": ["TitleBar", "winButton"], + "titlebarwinbuttonclose": ["TitleBar", "winButtonClose"], + "titlebarwinbuttonminmax": ["TitleBar", "winButtonMinMax"], + "titlebarwindows": ["TitleBar", "typeWindows"], + "titlebarwithframe": ["TitleBar", "withFrame"], + "titlebarwordmarkmac": ["TitleBar", "wordmarkMacOS"], + "titlebarwordmarkwindows": ["TitleBar", "wordmarkWindows"], + "toast": ["Toast", "toast"], + "toastavatar": ["Toast", "avatar"], + "toastbar": ["Toast", "bar"], + "toastbarinner": ["Toast", "barInner"], + "toastbg": ["Toast", "bg"], + "toastbrand": ["Toast", "brand"], + "toastclosable": ["Toast", "closable"], + "toastcloseicon": ["Toast", "closeIcon"], + "toastclosing": ["Toast", "closing"], + "toastcustom": ["Toast", "custom"], + "toastcustombar": ["Toast", "customBar"], + "toastdanger": ["Toast", "danger"], + "toastdefault": ["Toast", "default"], + "toasticon": ["Toast", "icon"], + "toastinfo": ["Toast", "info"], + "toastinner": ["Toast", "inner"], + "toastopening": ["Toast", "opening"], + "toasts": ["Toast", "toasts"], + "toastscenter": ["Toast", "center"], + "toastsleft": ["Toast", "left"], + "toastsright": ["Toast", "right"], + "toastsuccess": ["Toast", "success"], + "toasttext": ["Toast", "text"], + "toastwarning": ["Toast", "warning"], + "tooltip": ["Tooltip", "tooltip"], + "tooltipactivityicon": ["TooltipGuild", "activityIcon"], + "tooltipbottom": ["Tooltip", "tooltipBottom"], + "tooltipbrand": ["Tooltip", "tooltipBrand"], + "tooltipcontent": ["Tooltip", "tooltipContent"], + "tooltipcontentallowoverflow": ["Tooltip", "tooltipContentAllowOverflow"], + "tooltipcustom": ["BDFDB", "tooltipCustom"], + "tooltipgreen": ["Tooltip", "tooltipGreen"], + "tooltipgrey": ["Tooltip", "tooltipGrey"], + "tooltipguildnametext": ["TooltipGuild", "guildNameText"], + "tooltipguildnametextlimitedsize": ["TooltipGuild", "guildNameTextLimitedSize"], + "tooltipleft": ["Tooltip", "tooltipLeft"], + "tooltipmutetext": ["TooltipGuild", "muteText"], + "tooltipmutetextwithactivity": ["TooltipGuild", "muteTextWithActivity"], + "tooltipnote": ["BDFDB", "tooltipNote"], + "tooltippointer": ["Tooltip", "tooltipPointer"], + "tooltippointerbg": ["Tooltip", "tooltipPointerBg"], + "tooltipprimary": ["Tooltip", "tooltipPrimary"], + "tooltipred": ["Tooltip", "tooltipRed"], + "tooltipright": ["Tooltip", "tooltipRight"], + "tooltiprow": ["TooltipGuild", "row"], + "tooltiprowextra": ["BDFDB", "tooltipRowExtra"], + "tooltiprowguildname": ["TooltipGuild", "rowGuildName"], + "tooltiprowicon": ["TooltipGuild", "rowIcon"], + "tooltiprowiconv2": ["TooltipGuild", "rowIconV2"], + "tooltiptop": ["Tooltip", "tooltipTop"], + "tooltipyellow": ["Tooltip", "tooltipYellow"], + "typing": ["Typing", "typing"], + "typingcooldownwrapper": ["TypingCoowldown", "cooldownWrapper"], + "typingtext": ["Typing", "text"], + "underline": ["TextStyle", "underline"], + "unreadbar": ["UnreadBar", "bar"], + "unreadbaractive": ["UnreadBar", "active"], + "unreadbarcontainer": ["UnreadBar", "container"], + "unreadbaricon": ["UnreadBar", "icon"], + "unreadbarmention": ["UnreadBar", "mention"], + "unreadbartext": ["UnreadBar", "text"], + "unreadbarunread": ["UnreadBar", "unread"], + "uploadmodal": ["UploadModal", "uploadModal"], + "userbadge": ["UserBadges", "badge"], + "userbadges": ["UserBadges", "container"], + "userbanner": ["UserBanner", "banner"], + "userbannerpopout": ["UserBanner", "popoutBanner"], + "userbannerpopoutpremium": ["UserBanner", "popoutBannerPremium"], + "userbannermodal": ["UserBanner", "modalBanner"], + "userbannermodalpremium": ["UserBanner", "modalBannerPremium"], + "userheaderbottag": ["UserHeaderUsername", "bot"], + "userheaderclickableusername": ["UserHeaderUsername", "clickableUsername"], + "userheadernickname": ["UserHeaderUsername", "nickname"], + "userheadernicknamewithstyle": ["UserHeaderUsername", "nicknameWithDisplayNameStyles"], + "userheadertag": ["UserHeaderUsername", "userTag"], + "userpopoutavatarclickable": ["UserPopoutHeader", "clickable"], + "userpopoutavatarhint": ["UserPopoutHeader", "avatarHint"], + "userpopoutavatarhintinner": ["UserPopoutHeader", "avatarHintInner"], + "userpopoutavatarpositionnormal": ["UserPopoutHeader", "avatarPositionNormal"], + "userpopoutavatarwrappernonuserbot": ["UserPopoutHeader", "avatarWrapperNonUserBot"], + "userpopoutavatarwrapper": ["UserPopoutHeader", "avatarWrapper"], + "userpopoutavatarwrappernormal": ["UserPopoutHeader", "avatarWrapperNormal"], + "userpopoutcustomstatus": ["CustomStatus", "customStatus"], + "userpopoutcustomstatusemoji": ["CustomStatus", "customStatusEmoji"], + "userpopoutcustomstatussoloemoji": ["CustomStatus", "customStatusSoloEmoji"], + "userpopoutcustomstatustext": ["CustomStatus", "customStatusText"], + "userpopoutinner": ["UserTheme", "inner"], + "userpopoutmenuitem": ["UserPopoutBody", "menuItem"], + "userpopoutmenuitemcontent": ["UserPopoutBody", "menuItemContent"], + "userpopoutmenuitemicon": ["UserPopoutBody", "menuItemIcon"], + "userpopoutmenuiteminner": ["UserPopoutBody", "menuItemInner"], + "userpopoutmenuitemlabel": ["UserPopoutBody", "menuItemLabel"], + "userpopoutmenuitemlabeltext": ["UserPopoutBody", "menuItemLabelText"], + "userpopoutmenuoverlay": ["UserPopoutBody", "menuOverlay"], + "userpopoutmenus": ["UserPopoutBody", "menus"], + "userpopoutouter": ["UserTheme", "outer"], + "userpopoutoverlay": ["UserTheme", "overlay"], + "userpopoutsection": ["UserPopoutSection", "section"], + "userpopoutsectionlast": ["UserPopoutSection", "lastSection"], + "userpopoutstatusbubble": ["UserPopoutStatusBubble", "outer"], + "userpopoutstatusbubblecontainer": ["UserPopoutStatusBubble", "container"], + "userpopoutstatusbubbleeditable": ["UserPopoutStatusBubble", "editable"], + "userprofilebody": ["UserProfile", "body"], + "userprofilemodal": ["UserProfile", "root"], + "userprofilenametag": ["UserProfileUsernameSection", "nameTag"], + "userprofilesection": ["UserProfileSection", "section"], + "userprofilesectionheading": ["UserProfileSection", "heading"], + "userprofileusername": ["UserProfileUsernameSection", "username"], + "username": ["NameTag", "username"], + "usernotetextarea": ["NoteTextarea", "textarea"], + "userrole": ["Role", "role"], + "userrolecircle": ["RoleCircle", "roleCircle"], + "userrolelist": ["Roles", "roles"], + "userrolename": ["Role", "roleName"], + "userroleremovebutton": ["Role", "roleRemoveButton"], + "userroles": ["Role", "root"], + "usersummaryavatar": ["UserSummaryItem", "avatar"], + "usersummaryavatarcontainer": ["UserSummaryItem", "avatarContainer"], + "usersummaryavatarcontainermasked": ["UserSummaryItem", "avatarContainerMasked"], + "usersummaryclickableavatar": ["UserSummaryItem", "clickableAvatar"], + "usersummarycontainer": ["UserSummaryItem", "container"], + "usersummaryemptyuser": ["UserSummaryItem", "emptyUser"], + "usersummaryicon": ["UserSummaryItem", "icon"], + "usersummarymoreUsers": ["UserSummaryItem", "moreUsers"], + "visualrefresh": ["NotFound", "visualRefresh"], + "voiceavatar": ["VoiceChannel", "avatar"], + "voiceavatarcontainer": ["VoiceChannel", "avatarContainer"], + "voiceavatarlarge": ["VoiceChannel", "avatarLarge"], + "voiceavatarsmall": ["VoiceChannel", "avatarSmall"], + "voiceclickable": ["VoiceChannel", "clickable"], + "voicecontent": ["VoiceChannel", "content"], + "voicedetails": ["VoiceDetails", "container"], + "voicedetailsactionbuttons": ["VoiceDetails", "actionButtons"], + "voicedetailsbutton": ["VoiceDetails", "button"], + "voicedetailsbuttonactive": ["VoiceDetails", "buttonActive"], + "voicedetailsbuttoncolor": ["VoiceDetails", "buttonColor"], + "voicedetailsbuttonicon": ["VoiceDetails", "buttonIcon"], + "voicedetailsbuttonwithtext": ["VoiceDetails", "withText"], + "voicedetailschannel": ["VoiceDetails", "channel"], + "voicedetailsinner": ["VoiceDetails", "inner"], + "voicedetailslabelwrapper": ["VoiceDetailsPing", "labelWrapper"], + "voicedetailsping": ["VoiceDetailsPing", "ping"], + "voicedetailspingforeground": ["VoiceDetailsPing", "pingForeground"], + "voicedetailsqualityaverage": ["VoiceDetailsPing", "rtcConnectionQualityAverage"], + "voicedetailsqualitybad": ["VoiceDetailsPing", "rtcConnectionQualityBad"], + "voicedetailsqualityfine": ["VoiceDetailsPing", "rtcConnectionQualityFine"], + "voicedetailsstatus": ["VoiceDetailsPing", "rtcConnectionStatus"], + "voicedetailsstatusconnected": ["VoiceDetailsPing", "rtcConnectionStatusConnected"], + "voicedetailsstatusconnecting": ["VoiceDetailsPing", "rtcConnectionStatusConnecting"], + "voicedetailsstatuserror": ["VoiceDetailsPing", "rtcConnectionStatusError"], + "voiceflipped": ["VoiceChannel", "flipped"], + "voiceicon": ["VoiceChannel", "icon"], + "voiceicons": ["VoiceChannel", "icons"], + "voiceiconspacing": ["VoiceChannel", "iconSpacing"], + "voicelimit": ["VoiceChannelLimit", "wrapper"], + "voicelimittotal": ["VoiceChannelLimit", "total"], + "voicelimitusers": ["VoiceChannelLimit", "users"], + "voicelist": ["VoiceChannel", "list"], + "voicelist2": ["VoiceChannelList", "list"], + "voicelistcollapsed": ["VoiceChannel", "listCollapse"], + "voicelistcollapsed2": ["VoiceChannelList", "collapsed"], + "voicelistdefault": ["VoiceChannel", "listDefault"], + "voicename": ["VoiceChannel", "username"], + "voicenamefont": ["VoiceChannel", "usernameFont"], + "voicenamespeaking": ["VoiceChannel", "usernameSpeaking"], + "voiceselected": ["VoiceChannel", "selected"], + "voiceuser": ["VoiceChannel", "voiceUser"], + "voiceuserlarge": ["VoiceChannel", "userLarge"], + "voiceusersmall": ["VoiceChannel", "userSmall"] + }, + "myId": "278543574059057154", + "myGuildId": "410787888507256842", + "mySolana": "8R96iUjP61Z6p6YHrcLX5QccZ93yk3rE6rtKBoupNExz", + "myEthereum": "0xaD7C90Ed43c0021d855970B9Ed6282A1922eECEb", + "authorIdAttribute": "data-author-id", + "authorFriendAttribute": "data-is-author-friend", + "authorSelfAttribute": "data-is-author-self", + "userIdAttribute": "data-user-id", + "userBackgroundsProperties": { + "background": "--user-background", + "orientation": "--user-popout-position" + }, + "userBackgroundsUrl": "https://discord-custom-covers.github.io/usrbg/dist/usrbg.json", + "Languages": { + "$discord": {"name":"Discord (English)", "id":"en", "ownlang":"English"}, + "af": {"name":"Afrikaans", "id":"af", "ownlang":"Afrikaans"}, + "sq": {"name":"Albanian", "id":"sq", "ownlang":"Shqip"}, + "am": {"name":"Amharic", "id":"am", "ownlang":"አማርኛ"}, + "ar": {"name":"Arabic", "id":"ar", "ownlang":"اللغة العربية"}, + "hy": {"name":"Armenian", "id":"hy", "ownlang":"Հայերեն"}, + "az": {"name":"Azerbaijani", "id":"az", "ownlang":"آذربایجان دیلی"}, + "ba": {"name":"Bashkir", "id":"ba", "ownlang":"Башҡорт"}, + "eu": {"name":"Basque", "id":"eu", "ownlang":"Euskara"}, + "be": {"name":"Belarusian", "id":"be", "ownlang":"Беларуская"}, + "bn": {"name":"Bengali", "id":"bn", "ownlang":"বাংলা"}, + "bs": {"name":"Bosnian", "id":"bs", "ownlang":"Босански"}, + "bg": {"name":"Bulgarian", "id":"bg", "ownlang":"български"}, + "my": {"name":"Burmese", "id":"my", "ownlang":"မြန်မာစာ"}, + "ca": {"name":"Catalan", "id":"ca", "ownlang":"Català"}, + "ceb": {"name":"Cebuano", "id":"ceb", "ownlang":"Bisaya"}, + "ny": {"name":"Chichewa", "id":"ny", "ownlang":"Nyanja"}, + "zh": {"name":"Chinese", "id":"zh", "ownlang":"中文"}, + "zh-CN": {"name":"Chinese (China)", "id":"zh-CN", "ownlang":"中文"}, + "zh-HK": {"name":"Chinese (Hong Kong)", "id":"zh-HK", "ownlang":"香港中文"}, + "zh-TW": {"name":"Chinese (Taiwan)", "id":"zh-TW", "ownlang":"繁體中文"}, + "co": {"name":"Corsican", "id":"co", "ownlang":"Corsu"}, + "hr": {"name":"Croatian", "id":"hr", "ownlang":"Hrvatski"}, + "cs": {"name":"Czech", "id":"cs", "ownlang":"Čeština"}, + "da": {"name":"Danish", "id":"da", "ownlang":"Dansk"}, + "nl": {"name":"Dutch", "id":"nl", "ownlang":"Nederlands"}, + "en": {"name":"English", "id":"en", "ownlang":"English"}, + "en-GB": {"name":"English (UK)", "id":"en-GB", "ownlang":"English (UK)"}, + "en-US": {"name":"English (US)", "id":"en-US", "ownlang":"English (US)"}, + "eo": {"name":"Esperanto", "id":"eo", "ownlang":"Esperanto"}, + "et": {"name":"Estonian", "id":"et", "ownlang":"Eesti"}, + "fil": {"name":"Filipino", "id":"fil", "ownlang":"Wikang Filipino"}, + "tl": {"name":"Filipino (Tagalog)", "id":"tl", "ownlang":"Wikang Tagalog"}, + "fi": {"name":"Finnish", "id":"fi", "ownlang":"Suomi"}, + "fr": {"name":"French", "id":"fr", "ownlang":"Français"}, + "fr-CA": {"name":"French (Canadian)", "id":"fr-CA", "ownlang":"Français Canadien"}, + "fy": {"name":"Frisian", "id":"fy", "ownlang":"Frysk"}, + "gl": {"name":"Galician", "id":"gl", "ownlang":"Galego"}, + "ka": {"name":"Georgian", "id":"ka", "ownlang":"ქართული"}, + "de": {"name":"German", "id":"de", "ownlang":"Deutsch"}, + "de-AT": {"name":"German (Austria)", "id":"de-AT", "ownlang":"Österreichisch Deutsch"}, + "de-CH": {"name":"German (Switzerland)", "id":"de-CH", "ownlang":"Schweizerdeutsch"}, + "el": {"name":"Greek", "id":"el", "ownlang":"Ελληνικά"}, + "gu": {"name":"Gujarati", "id":"gu", "ownlang":"ગુજરાતી"}, + "ht": {"name":"Haitian Creole", "id":"ht", "ownlang":"Kreyòl Ayisyen"}, + "ha": {"name":"Hausa", "id":"ha", "ownlang":"حَوْسَ"}, + "haw": {"name":"Hawaiian", "id":"haw", "ownlang":"ʻŌlelo Hawaiʻi"}, + "he": {"name":"Hebrew", "id":"he", "ownlang":"עברית"}, + "iw": {"name":"Hebrew (Israel)", "id":"iw", "ownlang":"עברית"}, + "hi": {"name":"Hindi", "id":"hi", "ownlang":"हिन्दी"}, + "hmn": {"name":"Hmong", "id":"hmn", "ownlang":"lol Hmongb"}, + "hu": {"name":"Hungarian", "id":"hu", "ownlang":"Magyar"}, + "is": {"name":"Icelandic", "id":"is", "ownlang":"Íslenska"}, + "ig": {"name":"Igbo", "id":"ig", "ownlang":"Asụsụ Igbo"}, + "id": {"name":"Indonesian", "id":"id", "ownlang":"Bahasa Indonesia"}, + "ga": {"name":"Irish", "id":"ga", "ownlang":"Gaeilge"}, + "it": {"name":"Italian", "id":"it", "ownlang":"Italiano"}, + "ja": {"name":"Japanese", "id":"ja", "ownlang":"日本語"}, + "jv": {"name":"Javanese", "id":"jv", "ownlang":"ꦧꦱꦗꦮ"}, + "jw": {"name":"Javanese (Javanese)", "id":"jw", "ownlang":"ꦧꦱꦗꦮ"}, + "kn": {"name":"Kannada", "id":"kn", "ownlang":"ಕನ್ನಡ"}, + "kk": {"name":"Kazakh", "id":"kk", "ownlang":"Қазақ Tілі"}, + "km": {"name":"Khmer", "id":"km", "ownlang":"ភាសាខ្មែរ"}, + "rw": {"name":"Kinyarwanda", "id":"rw", "ownlang":"Ikinyarwanda"}, + "ko": {"name":"Korean", "id":"ko", "ownlang":"한국어"}, + "ku": {"name":"Kurdish", "id":"ku", "ownlang":"کوردی"}, + "ky": {"name":"Kyrgyz", "id":"ky", "ownlang":"кыргызча"}, + "lo": {"name":"Lao", "id":"lo", "ownlang":"ພາສາລາວ"}, + "la": {"name":"Latin", "id":"la", "ownlang":"Latina"}, + "lv": {"name":"Latvian", "id":"lv", "ownlang":"Latviešu"}, + "lt": {"name":"Lithuanian", "id":"lt", "ownlang":"Lietuvių"}, + "lb": {"name":"Luxembourgish", "id":"lb", "ownlang":"Lëtzebuergesch"}, + "mk": {"name":"Macedonian", "id":"mk", "ownlang":"Mакедонски"}, + "mg": {"name":"Malagasy", "id":"mg", "ownlang":"Malagasy"}, + "ms": {"name":"Malay", "id":"ms", "ownlang":"بهاس ملايو"}, + "ml": {"name":"Malayalam", "id":"ml", "ownlang":"മലയാളം"}, + "mt": {"name":"Maltese", "id":"mt", "ownlang":"Malti"}, + "mi": {"name":"Maori", "id":"mi", "ownlang":"te Reo Māori"}, + "mr": {"name":"Marathi", "id":"mr", "ownlang":"मराठी"}, + "mhr": {"name":"Mari", "id":"mhr", "ownlang":"марий йылме"}, + "mn": {"name":"Mongolian", "id":"mn", "ownlang":"Монгол Хэл"}, + "my": {"name":"Myanmar (Burmese)", "id":"my", "ownlang":"မြန်မာл Хэл"}, + "ne": {"name":"Nepali", "id":"ne", "ownlang":"नेपाली"}, + "no": {"name":"Norwegian", "id":"no", "ownlang":"Norsk"}, + "or": {"name":"Odia", "id":"or", "ownlang":"ଓଡ଼ିଆ"}, + "pap": {"name":"Papiamento", "id":"pap", "ownlang":"Papiamentu"}, + "ps": {"name":"Pashto", "id":"ps", "ownlang":"پښتو"}, + "fa": {"name":"Persian", "id":"fa", "ownlang":"فارسی"}, + "pl": {"name":"Polish", "id":"pl", "ownlang":"Polski"}, + "pt": {"name":"Portuguese", "id":"pt", "ownlang":"Português"}, + "pt-BR": {"name":"Portuguese (Brazil)", "id":"pt-BR", "ownlang":"Português do Brasil"}, + "pt-PT": {"name":"Portuguese (Portugal)", "id":"pt-PT", "ownlang":"Português do Portugal"}, + "pa": {"name":"Punjabi", "id":"pa", "ownlang":"पंजाबी"}, + "ro": {"name":"Romanian", "id":"ro", "ownlang":"Română"}, + "ru": {"name":"Russian", "id":"ru", "ownlang":"Pусский"}, + "sm": {"name":"Samoan", "id":"sm", "ownlang":"Gagana Sāmoa"}, + "gd": {"name":"Scottish Gaelic", "id":"gd", "ownlang":"Gàidhlig"}, + "sr": {"name":"Serbian", "id":"sr", "ownlang":"Српски"}, + "st": {"name":"Sesotho", "id":"st", "ownlang":"Sesotho"}, + "sn": {"name":"Shona", "id":"sn", "ownlang":"Shona"}, + "sd": {"name":"Sindhi", "id":"sd", "ownlang":"سنڌي"}, + "si": {"name":"Sinhala", "id":"si", "ownlang":"සිංහල"}, + "sk": {"name":"Slovak", "id":"sk", "ownlang":"Slovenčina"}, + "sl": {"name":"Slovenian", "id":"sl", "ownlang":"Slovenščina"}, + "so": {"name":"Somali", "id":"so", "ownlang":"Soomaali"}, + "es": {"name":"Spanish", "id":"es", "ownlang":"Español"}, + "es-419": {"name":"Spanish (Latin America)", "id":"es-419", "ownlang":"Español latinoamericano"}, + "su": {"name":"Sundanese", "id":"su", "ownlang":"Basa Sunda"}, + "sw": {"name":"Swahili", "id":"sw", "ownlang":"Kiswahili"}, + "sv": {"name":"Swedish", "id":"sv", "ownlang":"Svenska"}, + "tg": {"name":"Tajik", "id":"tg", "ownlang":"тоҷикӣ"}, + "ta": {"name":"Tamil", "id":"ta", "ownlang":"தமிழ்"}, + "tt": {"name":"Tatar", "id":"tt", "ownlang":"татарча"}, + "te": {"name":"Telugu", "id":"te", "ownlang":"తెలుగు"}, + "th": {"name":"Thai", "id":"th", "ownlang":"ภาษาไทย"}, + "tr": {"name":"Turkish", "id":"tr", "ownlang":"Türkçe"}, + "tk": {"name":"Turkmen", "id":"tk", "ownlang":"Türkmençe"}, + "udm": {"name":"Udmurt", "id":"udm", "ownlang":"удмурт кыл"}, + "uk": {"name":"Ukrainian", "id":"uk", "ownlang":"Yкраїнський"}, + "ur": {"name":"Urdu", "id":"ur", "ownlang":"اُردُو"}, + "ug": {"name":"Uyghur", "id":"ug", "ownlang":"ئۇيغۇر تىلى"}, + "uz": {"name":"Uzbek", "id":"uz", "ownlang":"اوزبیک"}, + "vi": {"name":"Vietnamese", "id":"vi", "ownlang":"Tiếng Việt Nam"}, + "cy": {"name":"Welsh", "id":"cy", "ownlang":"Cymraeg"}, + "xh": {"name":"Xhosa", "id":"xh", "ownlang":"Xhosa"}, + "yi": {"name":"Yiddish", "id":"yi", "ownlang":"ייִדיש ייִדיש‬"}, + "yo": {"name":"Yoruba", "id":"yo", "ownlang":"Èdè Yorùbá"}, + "zu": {"name":"Zulu", "id":"zu", "ownlang":"Zulu"} + }, + "LibraryStrings": { + "bg": { + "add_to": "Добавяне към {{var0}}", + "ascending": "Възходящ", + "center": "Центрирано", + "changelog_added": "Нови функции", + "changelog_fixed": "Отстраняване на неизправности", + "changelog_improved": "Подобрения", + "changelog_progress": "Напредък", + "check_for_updates": "Провери за актуализации", + "clipboard_success": "Копирано {{var0}} в клипборда", + "confirm": "Сигурен ли си?", + "copy": "Копиране {{var0}}", + "delete_fail": "{{var0}} не може да бъде изтрито", + "delete_success": "{{var0}} изтрито успешно", + "descending": "Низходящ", + "developer": "Разработчик", + "donate_message": "Подкрепете ме за повече актуализации!", + "download": "Изтегли", + "download_fail": "{{var0}} не може да бъде изтеглен", + "download_success": "{{var0}} изтеглено успешно", + "file_navigator_text": "Преглед на файл", + "first": "Първо", + "from": "От", + "gradient": "Градиент", + "guildbanner": "Банер", + "guildicon": "Икона", + "installed": "Инсталирани", + "last": "Последно", + "left": "Наляво", + "loading": "Зарежда се {{var0}}", + "location": "Местоположение", + "order": "Последователност", + "outdated": "Остарял", + "please_wait": "Приятно чакане", + "right": "Нали", + "save_fail": "{{var0}} не може да бъде запазен", + "save_success": "{{var0}} запазено успешно", + "send": "Изпрати {{var0}}", + "server": "Сървър", + "settings_checkForUpdates_description": "Проверете за актуализации на приставки директно в GitHub", + "settings_checkForUpdates_note": "Това ще заобиколи прегледа на безопасността на приставките на BetterDiscord!", + "settings_shareData_description": "Синхронизира конфигурациите на приставките между акаунти в Discord", + "settings_showSupportBadges_description": "Показва малки значки за потребители, които поддържат моя Patreon", + "settings_showToasts_description": "Показва тостове за стартиране и спиране на приставката", + "settings_showToasts_note": "Деактивирайте общата настройка '{{var0}}' на BD, преди да ги деактивирате", + "settings_toastPosition_description": "Позиция за тост по подразбиране", + "settings_toastPosition_note": "Променя само позицията на тостове, създадени от моите приставки", + "settings_useChromium_description": "Отворете връзки в Discord вместо вашия браузър", + "sort_by": "Сортиране по", + "status": "Състояние", + "time": "Време", + "timezone": "Часова зона", + "to": "Да се", + "toast_plugin_loaded": "{{var0}} е зареден", + "toast_plugin_started": "{{var0}} започна", + "toast_plugin_stopped": "{{var0}} спря", + "toast_plugin_translated": "преведено на {{var0}}", + "toast_plugin_unloaded": "{{var0}} е разтоварен", + "toast_plugin_update_failed": "Актуализацията за {{var0}} не може да бъде изтеглена", + "toast_plugin_updated": "{{var0}} {{var1}} е заменено с {{var2}} {{var3}}", + "update_check_complete": "Проверката за актуализация на приставката завърши", + "update_check_complete_outdated": "Проверката за актуализация на приставката завърши - {{var0}} остаряла!", + "update_check_info": "Проверете приставки, които поддържат проверката на актуализацията", + "update_notice_click": "Щракнете за актуализация!", + "update_notice_reload": "Презаредете, за да завършите актуализацията", + "update_notice_update": "Следните приставки трябва да бъдат актуализирани: ", + "updated": "Актуализирано" + }, + "cs": { + "add_to": "Přidat do {{var0}}", + "ascending": "Vzestupně", + "center": "Na střed", + "changelog_added": "Nové vlastnosti", + "changelog_fixed": "Oprava chyb", + "changelog_improved": "Vylepšení", + "changelog_progress": "Pokrok", + "check_for_updates": "Kontrola aktualizací", + "clipboard_success": "{{var0}} zkopírováno do schránky", + "confirm": "Jsi si jistá?", + "copy": "Kopírovat {{var0}}", + "delete_fail": "Aplikaci {{var0}} nelze smazat", + "delete_success": "{{var0}} úspěšně smazán", + "descending": "Klesající", + "developer": "Vývojář", + "donate_message": "Podpořte mě, abyste získali další aktualizace!", + "download": "Stažení", + "download_fail": "Aplikaci {{var0}} nelze stáhnout", + "download_success": "Aplikace {{var0}} byla úspěšně stažena", + "file_navigator_text": "Procházet soubor", + "first": "První", + "from": "Z", + "gradient": "Spád", + "guildbanner": "Ikona", + "guildicon": "Prapor", + "installed": "Nainstalováno", + "last": "Poslední", + "left": "Vlevo, odjet", + "loading": "Načítání {{var0}}", + "location": "Umístění", + "order": "Objednat", + "outdated": "Zastaralý", + "please_wait": "Prosím, čekejte", + "right": "Že jo", + "save_fail": "{{var0}} nelze uložit", + "save_success": "{{var0}} úspěšně uložen", + "send": "Odeslat {{var0}}", + "server": "Server", + "settings_checkForUpdates_description": "Zkontrolujte aktualizace pluginů přímo na GitHubu", + "settings_checkForUpdates_note": "Obejdete tím BetterDiscord Plugin Safety Review!", + "settings_shareData_description": "Synchronizuje konfigurace zásuvných modulů mezi účty Discord", + "settings_showSupportBadges_description": "Zobrazuje malé odznaky pro uživatele, kteří podporují můj Patreon", + "settings_showToasts_description": "Zobrazuje spuštění a zastavení pluginu", + "settings_showToasts_note": "Před deaktivací deaktivujte obecné nastavení BD {{var0}}", + "settings_toastPosition_description": "Výchozí pozice toastu", + "settings_toastPosition_note": "Změní pouze pozici toastů vytvořených mými pluginy", + "settings_useChromium_description": "Otevřete odkazy v programu Discord namísto v prohlížeči", + "sort_by": "Seřazeno podle", + "status": "Postavení", + "time": "Čas", + "timezone": "Časové pásmo", + "to": "Na", + "toast_plugin_loaded": "{{var0}} byl načten", + "toast_plugin_started": "Byla spuštěna aplikace {{var0}}", + "toast_plugin_stopped": "Aplikace {{var0}} byla zastavena", + "toast_plugin_translated": "přeloženo do jazyka {{var0}}", + "toast_plugin_unloaded": "Aplikace {{var0}} byla uvolněna", + "toast_plugin_update_failed": "Aktualizaci pro {{var0}} nelze stáhnout", + "toast_plugin_updated": "{{var0}} {{var1}} byl nahrazen {{var2}} {{var3}}", + "update_check_complete": "Kontrola aktualizace pluginu dokončena", + "update_check_complete_outdated": "Kontrola aktualizace pluginu dokončena - {{var0}} zastaralá!", + "update_check_info": "Zkontrolujte doplňky, které podporují kontrolu aktualizace", + "update_notice_click": "Klikněte pro aktualizaci!", + "update_notice_reload": "Aktualizaci dokončete znovu", + "update_notice_update": "Je třeba aktualizovat následující doplňky:", + "updated": "Aktualizováno" + }, + "da": { + "add_to": "Føj til {{var0}}", + "ascending": "Stigende", + "center": "Centreret", + "changelog_added": "Nye funktioner", + "changelog_fixed": "Fejlfinding", + "changelog_improved": "Forbedringer", + "changelog_progress": "Fremskridt", + "check_for_updates": "Søg efter opdateringer", + "clipboard_success": "Kopieret {{var0}} til udklipsholder", + "confirm": "Er du sikker?", + "copy": "Kopiér {{var0}}", + "delete_fail": "{{var0}} kan ikke slettes", + "delete_success": "{{var0}} blev slettet", + "descending": "Aftagende", + "developer": "Udvikler", + "donate_message": "Støt mig for flere opdateringer!", + "download": "Hent", + "download_fail": "{{var0}} kan ikke downloades", + "download_success": "{{var0}} downloadet med succes", + "file_navigator_text": "Gennemse fil", + "first": "Først", + "from": "Fra", + "gradient": "Gradient", + "guildbanner": "Banner", + "guildicon": "Ikon", + "installed": "Installeret", + "last": "Sidst", + "left": "Venstre", + "loading": "Indlæser {{var0}}", + "location": "Beliggenhed", + "order": "Sekvens", + "outdated": "Forældet", + "please_wait": "Vent venligst", + "right": "Ret", + "save_fail": "{{var0}} kan ikke gemmes", + "save_success": "{{var0}} blev gemt", + "send": "Send {{var0}}", + "server": "Server", + "settings_checkForUpdates_description": "Tjek for plugin-opdateringer direkte på GitHub", + "settings_checkForUpdates_note": "Dette vil omgå BetterDiscord's Plugin Safety Review!", + "settings_shareData_description": "Synkroniserer plugin-konfigurationerne mellem Discord-konti", + "settings_showSupportBadges_description": "Viser små badges til brugere, der understøtter min Patreon", + "settings_showToasts_description": "Viser plugin start og stop toasts", + "settings_showToasts_note": "Deaktiver den generelle indstilling '{{var0}}' for BD'er, inden du deaktiverer dem", + "settings_toastPosition_description": "Standard skålposition", + "settings_toastPosition_note": "Ændrer kun placeringen af ​​skåle oprettet af mine plugins", + "settings_useChromium_description": "Åbn Links i Discord i stedet for din browser", + "sort_by": "Sorter efter", + "status": "Status", + "time": "Tid", + "timezone": "Tidszone", + "to": "Til", + "toast_plugin_loaded": "{{var0}} er indlæst", + "toast_plugin_started": "{{var0}} er startet", + "toast_plugin_stopped": "{{var0}} er stoppet", + "toast_plugin_translated": "oversat til {{var0}}", + "toast_plugin_unloaded": "{{var0}} er blevet aflæst", + "toast_plugin_update_failed": "Opdatering til {{var0}} kan ikke downloades", + "toast_plugin_updated": "{{var0}} {{var1}} er blevet erstattet af {{var2}} {{var3}}", + "update_check_complete": "Kontrollen af plugin-opdatering afsluttet", + "update_check_complete_outdated": "Kontrollen af ​​plugin-opdatering afsluttet - {{var0}} forældet!", + "update_check_info": "Kontroller plugins, der understøtter opdateringskontrollen", + "update_notice_click": "Klik for at opdatere!", + "update_notice_reload": "Genindlæs for at fuldføre opdateringen", + "update_notice_update": "Følgende plugins skal opdateres: ", + "updated": "Opdateret" + }, + "de": { + "add_to": "Zu {{var0}} hinzufügen", + "ascending": "Aufsteigend", + "center": "Zentriert", + "changelog_added": "Neue Features", + "changelog_fixed": "Fehlerbehebung", + "changelog_improved": "Verbesserungen", + "changelog_progress": "Fortschritt", + "check_for_updates": "Suche nach Updates", + "clipboard_success": "{{var0}} in die Zwischenablage kopiert", + "confirm": "Bist du sicher?", + "copy": "{{var0}} kopieren", + "delete_fail": "{{var0}} kann nicht gelöscht werden", + "delete_success": "{{var0}} erfolgreich gelöscht", + "descending": "Absteigend", + "developer": "Entwickler", + "donate_message": "Unterstütze mich, um weitere Updates zu erhalten!", + "download": "Herunterladen", + "download_fail": "{{var0}} kann nicht heruntergeladen werden", + "download_success": "{{var0}} erfolgreich heruntergeladen", + "file_navigator_text": "Datei durchsuchen", + "first": "Zuerst", + "from": "Von", + "gradient": "Gradient", + "guildbanner": "Banner", + "guildicon": "Symbol", + "installed": "Installiert", + "last": "Zuletzt", + "left": "Links", + "loading": "Lädt {{var0}}", + "location": "Ort", + "order": "Reihenfolge", + "outdated": "Veraltet", + "please_wait": "Bitte warten", + "right": "Rechts", + "save_fail": "{{var0}} kann nicht gespeichert werden", + "save_success": "{{var0}} erfolgreich gespeichert", + "send": "{{var0}} senden", + "server": "Server", + "settings_checkForUpdates_description": "Sucht direkt auf GitHub nach Plugin-Updates", + "settings_checkForUpdates_note": "Dadurch wird die Plugin-Sicherheitsüberprüfung von BetterDiscord umgangen!", + "settings_shareData_description": "Synchronisiert die Plugin-Konfigurationen zwischen Discord-Konten", + "settings_showSupportBadges_description": "Zeigt kleine Abzeichen für Benutzer, die meinen Patreon unterstützen", + "settings_showToasts_description": "Zeigt Plugin Start und Stopp Toasts", + "settings_showToasts_note": "Deaktiviere die allgemeine BD Einstellung '{{var0}}', bevor du diese deaktivierst", + "settings_toastPosition_description": "Standard Toast Position", + "settings_toastPosition_note": "Ändert nur die Position von Toasts, die von meinen Plugins erstellt wurden", + "settings_useChromium_description": "Öffne Links in Discord anstelle deines Browsers", + "sort_by": "Sortieren nach", + "status": "Status", + "time": "Zeit", + "timezone": "Zeitzone", + "to": "Zu", + "toast_plugin_loaded": "{{var0}} wurde geladen", + "toast_plugin_started": "{{var0}} wurde gestartet", + "toast_plugin_stopped": "{{var0}} wurde gestoppt", + "toast_plugin_translated": "übersetzt nach {{var0}}", + "toast_plugin_unloaded": "{{var0}} wurde entladen", + "toast_plugin_update_failed": "Update für {{var0}} kann nicht heruntergeladen werden", + "toast_plugin_updated": "{{var0}} {{var1}} wurde durch {{var2}} {{var3}} ersetzt", + "update_check_complete": "Plugin Update Check abgeschlossen", + "update_check_complete_outdated": "Plugin Update Check abgeschlossen - {{var0}} veraltet!", + "update_check_info": "Check Plugins, die den Update Check unterstützen", + "update_notice_click": "Zum Aktualisieren klicken!", + "update_notice_reload": "Neu laden, um die Aktualisierung abzuschließen", + "update_notice_update": "Die folgenden Plugins müssen aktualisiert werden: ", + "updated": "Aktualisiert" + }, + "el": { + "add_to": "Προσθήκη στο {{var0}}", + "ascending": "Αύξουσα", + "center": "Στο κέντρο", + "changelog_added": "Νέα χαρακτηριστικά", + "changelog_fixed": "Διορθώσεις σφαλμάτων", + "changelog_improved": "Βελτιώσεις", + "changelog_progress": "Πρόοδος", + "check_for_updates": "Ελεγχος για ενημερώσεις", + "clipboard_success": "Αντιγραφή του {{var0}} στο Πρόχειρο", + "confirm": "Σίγουρα;", + "copy": "Αντιγραφή {{var0}}", + "delete_fail": "Δεν είναι δυνατή η διαγραφή του {{var0}}", + "delete_success": "Το {{var0}} διαγράφηκε με επιτυχία", + "descending": "Φθίνουσα", + "developer": "Προγραμματιστής", + "donate_message": "Υποστηρίξτε με για περισσότερες ενημερώσεις!", + "download": "Λήψη", + "download_fail": "Αδυνατή η λήψη του {{var0}}", + "download_success": "Η λήψη του {{var0}} ολοκληρώθηκε με επιτυχία", + "file_navigator_text": "Αναζήτηση αρχείου", + "first": "Πρώτα", + "from": "Από", + "gradient": "Βαθμίδα", + "guildbanner": "Διαφημιστικό", + "guildicon": "Εικονίδιο", + "installed": "Εγκατεστημένο", + "last": "Τελευταίος", + "left": "Αριστερά", + "loading": "Φόρτωση {{var0}}", + "location": "Τοποθεσία", + "order": "Ταξινόμηση", + "outdated": "Παλαιό", + "please_wait": "Αναμείνατε...", + "right": "Δεξιά", + "save_fail": "Αδυνατή η αποθήκευση του {{var0}}", + "save_success": "Το {{var0}} αποθηκεύτηκε με επιτυχία", + "send": "Αποστολή {{var0}}", + "server": "Διακομιστής", + "settings_checkForUpdates_description": "Ελέγξτε για ενημερώσεις προσθηκών απευθείας στο GitHub", + "settings_checkForUpdates_note": "Aυτό θα παρακάμψει την αναθεώρηση ασφάλειας προσθηκών της BetterDiscord!", + "settings_shareData_description": "Συγχρ. ρύθμισης παραμέτρων Προσθέτου μεταξύ λογαριασμών Discord", + "settings_showSupportBadges_description": "Εμφανίζει μικρά παράσημα για χρήστες που υποστηρίζουν το Patreon μου", + "settings_showToasts_description": "Εμφάνιση επισημάνσεων έναρξης και τερματισμού Προσθέτου", + "settings_showToasts_note": "Απενεργοποιεί τη γενική ρύθμιση «{{var0}}» του BD πριν απενεργοποιηθεί", + "settings_toastPosition_description": "Προεπιλεγμένη Θέση Επεξήγησης", + "settings_toastPosition_note": "Αλλάζει μόνο τη Θέση των Επεξηγήσεων που δημιουργήθηκαν από τις προσθήκες μου", + "settings_useChromium_description": "Ανοίξτε τους Συνδέσμους στο Discord αντί για τον φυλλομετρητή σας", + "sort_by": "Ταξινόμηση κατά", + "status": "Κατάσταση", + "time": "Χρόνος", + "timezone": "Ζώνη ώρας", + "to": "Προς το", + "toast_plugin_loaded": "Το {{var0}} έχει φορτωθεί", + "toast_plugin_started": "Το {{var0}} έχει ξεκινήσει", + "toast_plugin_stopped": "Το {{var0}} έχει τερματιστεί", + "toast_plugin_translated": "μεταφράστηκε σε {{var0}}", + "toast_plugin_unloaded": "Το {{var0}} έχει εκφορτωθεί", + "toast_plugin_update_failed": "Δεν είναι δυνατή η λήψη της ενημέρωσης για το {{var0}}", + "toast_plugin_updated": "Το {{var0}} {{var1}} αντικαταστάθηκε από το {{var2}} {{var3}}", + "update_check_complete": "Ο Έλεγχος Ενημέρωσης Πρόσθετου ολοκληρώθηκε", + "update_check_complete_outdated": "Ο Έλεγχος Ενημέρωσης Πρόσθετου ολοκληρώθηκε - {{var0}} δεν είναι ενημερωμένο!", + "update_check_info": "Έλεγχος Προσθέτων με υποστήριξη ενημέρωσης", + "update_notice_click": "Πατήστε για ενημέρωση!", + "update_notice_reload": "Επαναφορτώστε για ολοκλήρωση της ενημέρωσης", + "update_notice_update": "Τα ακόλουθα Πρόσθετα πρέπει να ενημερωθούν: ", + "updated": "Ενημερωμένο" + }, + "es": { + "add_to": "Agregar a {{var0}}", + "ascending": "Ascendente", + "center": "Centrado", + "changelog_added": "Nuevas características", + "changelog_fixed": "Solución de problemas", + "changelog_improved": "Mejoras", + "changelog_progress": "Progreso", + "check_for_updates": "Buscar actualizaciones", + "clipboard_success": "Copiado {{var0}} al Portapapeles", + "confirm": "¿Estás seguro?", + "copy": "Copiar {{var0}}", + "delete_fail": "{{var0}} no se puede eliminar", + "delete_success": "{{var0}} eliminado correctamente", + "descending": "Descendiendo", + "developer": "Desarrollador", + "donate_message": "¡Apóyanme para obtener más actualizaciones!", + "download": "Descargar", + "download_fail": "{{var0}} no se puede descargar", + "download_success": "{{var0}} descargado correctamente", + "file_navigator_text": "Buscar Archivo", + "first": "Primero", + "from": "Desde", + "gradient": "Degradado", + "guildbanner": "Bandera", + "guildicon": "Icono", + "installed": "Instalado", + "last": "Último", + "left": "Izquierda", + "loading": "Cargando {{var0}}", + "location": "Localización", + "order": "Secuencia", + "outdated": "Anticuado", + "please_wait": "Por favor espera", + "right": "Correcto", + "save_fail": "{{var0}} no se puede guardar", + "save_success": "{{var0}} guardado correctamente", + "send": "Enviar {{var0}}", + "server": "Servidor", + "settings_checkForUpdates_description": "Busque actualizaciones de complementos directamente en GitHub", + "settings_checkForUpdates_note": "Esto omitirá la revisión de seguridad de complementos de BetterDiscord!", + "settings_shareData_description": "Sincroniza las configuraciones del complemento entre las cuentas de Discord", + "settings_showSupportBadges_description": "Muestra pequeñas insignias para los usuarios que apoyan mi Patreon", + "settings_showToasts_description": "Muestra el inicio y la parada del complemento.", + "settings_showToasts_note": "Desactive la configuración general '{{var0}}' de los BD antes de desactivarlos", + "settings_toastPosition_description": "Posición de tostada predeterminada", + "settings_toastPosition_note": "Solo cambia la posición de los brindis creados por mis complementos", + "settings_useChromium_description": "Abra enlaces en Discord en lugar de su navegador", + "sort_by": "Ordenar por", + "status": "Estado", + "time": "Hora", + "timezone": "Zona horaria", + "to": "A", + "toast_plugin_loaded": "Se cargó {{var0}}", + "toast_plugin_started": "{{var0}} ha comenzado", + "toast_plugin_stopped": "{{var0}} se detuvo", + "toast_plugin_translated": "traducido a {{var0}}", + "toast_plugin_unloaded": "{{var0}} se ha descargado", + "toast_plugin_update_failed": "No se puede descargar la actualización para {{var0}}", + "toast_plugin_updated": "{{var0}} {{var1}} ha sido reemplazado por {{var2}} {{var3}}", + "update_check_complete": "Comprobación de actualización del complemento completada", + "update_check_complete_outdated": "Comprobación de actualización del complemento completada: {{var0}} desactualizada.", + "update_check_info": "Verifique los complementos que admiten la verificación de actualizaciones", + "update_notice_click": "¡Haz clic para actualizar!", + "update_notice_reload": "Recargar para completar la actualización", + "update_notice_update": "Los siguientes complementos deben actualizarse: ", + "updated": "Actualizado" + }, + "es-419": { + "add_to": "Añadir a {{var0}}", + "ascending": "Ascendente", + "center": "Centrado", + "changelog_added": "Nuevas características", + "changelog_fixed": "Corrección de errores", + "changelog_improved": "Mejoras", + "changelog_progress": "Progreso", + "check_for_updates": "Buscar actualizaciones", + "clipboard_success": "Copiado {{var0}} al portapapeles", + "confirm": "¿Está seguro?", + "copy": "Copiar {{var0}}", + "delete_fail": "{{var0}} no se puede eliminar", + "delete_success": "{{var0}} eliminado correctamente", + "descending": "Descendente", + "developer": "Revelador", + "donate_message": "¡Apóyame para recibir más actualizaciones!", + "download": "Descargar", + "download_fail": "{{var0}} no se puede descargar", + "download_success": "{{var0}} descargado exitosamente", + "file_navigator_text": "Examinar archivo", + "first": "Primero", + "from": "De", + "gradient": "Gradiente", + "guildbanner": "Bandera", + "guildicon": "Icono", + "installed": "Instalado", + "last": "Último", + "left": "Izquierda", + "loading": "Cargando {{var0}}", + "location": "Ubicación", + "order": "Orden", + "outdated": "Anticuado", + "please_wait": "Espere por favor", + "right": "Bien", + "save_fail": "{{var0}} no se puede guardar", + "save_success": "{{var0}} guardado exitosamente", + "send": "Enviar {{var0}}", + "server": "Servidor", + "settings_checkForUpdates_description": "Busque actualizaciones de complementos directamente en GitHub", + "settings_checkForUpdates_note": "Esto omitirá la revisión de seguridad de complementos de BetterDiscord!", + "settings_shareData_description": "Sincroniza las configuraciones del complemento entre cuentas de Discord", + "settings_showSupportBadges_description": "Muestra pequeñas insignias para los usuarios que apoyan mi Patreon.", + "settings_showToasts_description": "Muestra el inicio y la parada del complemento Tostadas.", + "settings_showToasts_note": "Deshabilite la configuración general '{{var0}}' de BD antes de deshabilitar esto", + "settings_toastPosition_description": "Posición de tostado predeterminada", + "settings_toastPosition_note": "Solo cambia la posición de los brindis creados por mis complementos", + "settings_useChromium_description": "Abra enlaces en Discord en lugar de en su navegador", + "sort_by": "Ordenar por", + "status": "Estado", + "time": "Tiempo", + "timezone": "Zona horaria", + "to": "A", + "toast_plugin_loaded": "{{var0}} ha sido cargado", + "toast_plugin_started": "{{var0}} ha sido iniciado", + "toast_plugin_stopped": "{{var0}} ha sido detenido", + "toast_plugin_translated": "traducido a {{var0}}", + "toast_plugin_unloaded": "{{var0}} ha sido descargado", + "toast_plugin_update_failed": "No se puede descargar la actualización para {{var0}}", + "toast_plugin_updated": "{{var0}} {{var1}} ha sido reemplazado por {{var2}} {{var3}}", + "update_check_complete": "Comprobación de actualización del complemento completada", + "update_check_complete_outdated": "Comprobación de actualización del complemento completada: ¡{{var0}} desactualizado!", + "update_check_info": "Verifique los complementos que admiten la verificación de actualización", + "update_notice_click": "¡Haz clic para actualizar!", + "update_notice_reload": "Vuelva a cargar para completar la actualización", + "update_notice_update": "Es necesario actualizar los siguientes complementos: ", + "updated": "Actualizado" + }, + "fi": { + "add_to": "Lisää {{var0}}", + "ascending": "Nouseva", + "center": "Keskitetty", + "changelog_added": "Uudet ominaisuudet", + "changelog_fixed": "Ongelmien karttoittaminen", + "changelog_improved": "Parannuksia", + "changelog_progress": "Edistystä", + "check_for_updates": "Tarkista päivitykset", + "clipboard_success": "Kopioitu {{var0}} leikepöydälle", + "confirm": "Oletko varma?", + "copy": "Kopioi {{var0}}", + "delete_fail": "{{var0}} ei voi poistaa", + "delete_success": "{{var0}} poistettu", + "descending": "Laskeva", + "developer": "Kehittäjä", + "donate_message": "Tue minua lisää päivityksiä varten!", + "download": "Ladata", + "download_fail": "{{var0}} ei voi ladata", + "download_success": "{{var0}} ladattu onnistuneesti", + "file_navigator_text": "Selaa tiedostoa", + "first": "Ensimmäinen", + "from": "Alkaen", + "gradient": "Kaltevuus", + "guildbanner": "Banneri", + "guildicon": "Kuvake", + "installed": "Asennettu", + "last": "Kestää", + "left": "Vasen", + "loading": "Ladataan {{var0}}", + "location": "Sijainti", + "order": "Järjestys", + "outdated": "Vanhentunut", + "please_wait": "Odota", + "right": "Aivan", + "save_fail": "Kohdetta {{var0}} ei voi tallentaa", + "save_success": "{{var0}} tallennettu onnistuneesti", + "send": "Lähetä {{var0}}", + "server": "Palvelin", + "settings_checkForUpdates_description": "Tarkista laajennuspäivitykset suoraan GitHubista", + "settings_checkForUpdates_note": "Tämä ohittaa BetterDiscordin laajennusten turvallisuustarkistuksen!", + "settings_shareData_description": "Synkronoi laajennusasetukset Discord-tilien välillä", + "settings_showSupportBadges_description": "Näyttää pienet merkit käyttäjille, jotka tukevat Patreoniani", + "settings_showToasts_description": "Näyttää laajennuksen aloitus- ja lopetusleivokset", + "settings_showToasts_note": "Poista BD-levyjen yleinen asetus {{var0}} ennen niiden poistamista käytöstä", + "settings_toastPosition_description": "Oletus paahtoleipää", + "settings_toastPosition_note": "Muuttaa vain laajennukset luomien paahtoleivien sijaintia", + "settings_useChromium_description": "Avaa Linkit ristiriidassa selaimen sijaan", + "sort_by": "Järjestä", + "status": "Tila", + "time": "Aika", + "timezone": "Aikavyöhyke", + "to": "Vastaanottaja", + "toast_plugin_loaded": "{{var0}} on ladattu", + "toast_plugin_started": "{{var0}} on alkanut", + "toast_plugin_stopped": "{{var0}} on pysähtynyt", + "toast_plugin_translated": "käännetty kielelle {{var0}}", + "toast_plugin_unloaded": "{{var0}} on purettu", + "toast_plugin_update_failed": "Verkkotunnuksen {{var0}} päivitystä ei voi ladata", + "toast_plugin_updated": "{{var0}} {{var1}} on korvattu sanalla {{var2}} {{var3}}", + "update_check_complete": "Laajennuksen päivityksen tarkistus valmis", + "update_check_complete_outdated": "Laajennuksen päivityksen tarkistus valmis - {{var0}} vanhentunut!", + "update_check_info": "Tarkista päivitystarkistusta tukevat laajennukset", + "update_notice_click": "Napsauta päivittääksesi!", + "update_notice_reload": "Lataa päivitys loppuun", + "update_notice_update": "Seuraavat laajennukset on päivitettävä: ", + "updated": "Päivitetty" + }, + "fr": { + "add_to": "Ajouter à {{var0}}", + "ascending": "Ascendant", + "center": "Centré", + "changelog_added": "Nouvelles fonctionnalités", + "changelog_fixed": "Dépannage", + "changelog_improved": "Améliorations", + "changelog_progress": "Le progrès", + "check_for_updates": "Rechercher des mises à jour", + "clipboard_success": "{{var0}} copié dans le presse-papiers", + "confirm": "Êtes-vous sûr?", + "copy": "Copiez {{var0}}", + "delete_fail": "{{var0}} ne peut pas être supprimé", + "delete_success": "{{var0}} supprimé avec succès", + "descending": "Descendant", + "developer": "Développeur", + "donate_message": "Soutenez-moi pour plus de mises à jour!", + "download": "Télécharger", + "download_fail": "{{var0}} ne peut pas être téléchargé", + "download_success": "{{var0}} téléchargé avec succès", + "file_navigator_text": "Parcourir le fichier", + "first": "Première", + "from": "De", + "gradient": "Pente", + "guildbanner": "Bannière", + "guildicon": "Icône", + "installed": "Installée", + "last": "Dernier", + "left": "Gauche", + "loading": "Chargement de {{var0}}", + "location": "Emplacement", + "order": "Séquence", + "outdated": "Dépassé", + "please_wait": "S'il vous plaît, attendez", + "right": "Droite", + "save_fail": "{{var0}} ne peut pas être enregistré", + "save_success": "{{var0}} a bien été enregistré", + "send": "Envoyer {{var0}}", + "server": "Serveur", + "settings_checkForUpdates_description": "Recherchez les mises à jour des plugins directement sur GitHub", + "settings_checkForUpdates_note": "Cela contournera l'examen de la sécurité des plugins de BetterDiscord!", + "settings_shareData_description": "Synchronise les configurations de plugin entre les comptes Discord", + "settings_showSupportBadges_description": "Affiche de petits badges pour les utilisateurs qui soutiennent mon Patreon", + "settings_showToasts_description": "Affiche les toasts de démarrage et d'arrêt du plugin", + "settings_showToasts_note": "Désactivez le paramètre général '{{var0}}' des BD avant de les désactiver", + "settings_toastPosition_description": "Position de toast par défaut", + "settings_toastPosition_note": "Modifie uniquement la position des toasts créés par mes plugins", + "settings_useChromium_description": "Ouvrez les liens dans Discord au lieu de votre navigateur", + "sort_by": "Trier par", + "status": "Statut", + "time": "Temps", + "timezone": "Fuseau horaire", + "to": "À", + "toast_plugin_loaded": "{{var0}} a été chargé", + "toast_plugin_started": "{{var0}} a commencé", + "toast_plugin_stopped": "{{var0}} s'est arrêté", + "toast_plugin_translated": "traduit en {{var0}}", + "toast_plugin_unloaded": "{{var0}} a été déchargé", + "toast_plugin_update_failed": "La mise à jour pour {{var0}} ne peut pas être téléchargée", + "toast_plugin_updated": "{{var0}} {{var1}} a été remplacé par {{var2}} {{var3}}", + "update_check_complete": "Vérification de la mise à jour du plugin terminée", + "update_check_complete_outdated": "Vérification de la mise à jour du plugin terminée - {{var0}} obsolète!", + "update_check_info": "Vérifiez les plugins prenant en charge la vérification des mises à jour", + "update_notice_click": "Cliquez pour mettre à jour!", + "update_notice_reload": "Recharger pour terminer la mise à jour", + "update_notice_update": "Les plugins suivants doivent être mis à jour: ", + "updated": "Mis à jour" + }, + "hi": { + "add_to": "{{var0}} में जोड़ें", + "ascending": "आरोही", + "center": "केंद्रित", + "changelog_added": "नए विशेषताएँ", + "changelog_fixed": "कंप्यूटर प्रोग्राम या प्रणाली में बग को दूर करना", + "changelog_improved": "सुधार", + "changelog_progress": "प्रगति", + "check_for_updates": "अद्यतन के लिए जाँच", + "clipboard_success": "{{var0}} को क्लिपबोर्ड पर कॉपी किया गया", + "confirm": "क्या आपको यकीन है?", + "copy": "कॉपी {{var0}}", + "delete_fail": "{{var0}} को हटाया नहीं जा सकता", + "delete_success": "{{var0}} सफलतापूर्वक हटाया गया", + "descending": "अवरोही", + "developer": "डेवलपर", + "donate_message": "आगे के अपडेट प्राप्त करने के लिए मेरा समर्थन करें!", + "download": "डाउनलोड", + "download_fail": "{{var0}} डाउनलोड नहीं किया जा सकता", + "download_success": "{{var0}} सफलतापूर्वक डाउनलोड किया गया", + "file_navigator_text": "फाइल खोजो", + "first": "प्रथम", + "from": "से", + "gradient": "ढाल", + "guildbanner": "बैनर", + "guildicon": "आइकन", + "installed": "स्थापित", + "last": "पिछले", + "left": "बाएं", + "loading": "लोड हो रहा है {{var0}}", + "location": "स्थान", + "order": "गण", + "outdated": "रगड़ा हुआ", + "please_wait": "कृपया प्रतीक्षा करें", + "right": "सही", + "save_fail": "{{var0}} सहेजा नहीं जा सकता", + "save_success": "{{var0}} सफलतापूर्वक सहेजा गया", + "send": "भेजें {{var0}}", + "server": "सर्वर", + "settings_checkForUpdates_description": "सीधे GitHub पर प्लगइन अपडेट की जाँच करें", + "settings_checkForUpdates_note": "यह BetterDiscord के प्लगइन सुरक्षा समीक्षा को बायपास कर देगा!", + "settings_shareData_description": "डिस्कॉर्ड खातों के बीच प्लगइन कॉन्फिग को सिंक्रोनाइज़ करता है", + "settings_showSupportBadges_description": "मेरे Patreon का समर्थन करने वाले उपयोगकर्ताओं के लिए छोटे बैज दिखाता है", + "settings_showToasts_description": "प्लगिन को दिखाता है टोस्ट शुरू और बंद करो", + "settings_showToasts_note": "इसे अक्षम करने से पहले BDs सामान्य '{{var0}}' सेटिंग अक्षम करें", + "settings_toastPosition_description": "डिफ़ॉल्ट टोस्ट स्थिति", + "settings_toastPosition_note": "केवल मेरे प्लगइन्स द्वारा बनाए गए टोस्ट की स्थिति बदलता है", + "settings_useChromium_description": "अपने ब्राउज़र के बजाय डिस्कॉर्ड में लिंक खोलें", + "sort_by": "इसके अनुसार क्रमबद्ध करें", + "status": "स्थिति", + "time": "समय", + "timezone": "समय क्षेत्र", + "to": "सेवा", + "toast_plugin_loaded": "{{var0}} लोड कर दिया गया है", + "toast_plugin_started": "{{var0}} शुरू कर दिया गया है", + "toast_plugin_stopped": "{{var0}} रोक दिया गया है", + "toast_plugin_translated": "{{var0}} में अनुवादित", + "toast_plugin_unloaded": "{{var0}} अनलोड कर दिया गया है", + "toast_plugin_update_failed": "{{var0}} के लिए अपडेट डाउनलोड नहीं किया जा सकता", + "toast_plugin_updated": "{{var0}} {{var1}} को {{var2}} {{var3}} से बदल दिया गया है", + "update_check_complete": "प्लगइन अद्यतन जाँच पूर्ण", + "update_check_complete_outdated": "प्लगिन अपडेट जांच पूरी हुई - {{var0}} पुरानी!", + "update_check_info": "चेक प्लगइन्स जो अपडेट चेक का समर्थन करते हैं", + "update_notice_click": "अपडेट करने के लिए क्लिक करें!", + "update_notice_reload": "अद्यतन पूरा करने के लिए पुनः लोड करें", + "update_notice_update": "निम्नलिखित प्लगइन्स को अद्यतन करने की आवश्यकता है:", + "updated": "अद्यतन" + }, + "hr": { + "add_to": "Dodaj u {{var0}}", + "ascending": "Uzlazni", + "center": "Centrirano", + "changelog_added": "Nove značajke", + "changelog_fixed": "Rješavanje problema", + "changelog_improved": "Poboljšanja", + "changelog_progress": "Napredak", + "check_for_updates": "Provjerite ima li ažuriranja", + "clipboard_success": "Kopirano {{var0}} u međuspremnik", + "confirm": "Jesi li siguran?", + "copy": "Kopiraj {{var0}}", + "delete_fail": "{{var0}} nije moguće izbrisati", + "delete_success": "{{var0}} uspješno je izbrisano", + "descending": "Silazni", + "developer": "Programer", + "donate_message": "Podržite me za još novosti!", + "download": "Preuzimanje datoteka", + "download_fail": "{{var0}} nije moguće preuzeti", + "download_success": "{{var0}} uspješno preuzeto", + "file_navigator_text": "Pregledaj datoteku", + "first": "Prvi", + "from": "Iz", + "gradient": "Gradijent", + "guildbanner": "Natpis", + "guildicon": "Ikona", + "installed": "Instaliran", + "last": "Posljednji", + "left": "Lijevo", + "loading": "Učitavanje {{var0}}", + "location": "Mjesto", + "order": "Slijed", + "outdated": "Zastario", + "please_wait": "Drago mi je čekati", + "right": "Pravo", + "save_fail": "{{var0}} nije moguće spremiti", + "save_success": "{{var0}} uspješno spremljeno", + "send": "Pošalji {{var0}}", + "server": "Poslužitelju", + "settings_checkForUpdates_description": "Provjerite ažuriranja dodataka izravno na GitHubu", + "settings_checkForUpdates_note": "Ovo će zaobići BetterDiscordov pregled sigurnosti dodataka!", + "settings_shareData_description": "Sinkronizira konfiguracije dodataka između Discord računa", + "settings_showSupportBadges_description": "Prikazuje male značke za korisnike koji podržavaju moj Patreon", + "settings_showToasts_description": "Prikazuje tost za pokretanje i zaustavljanje dodatka", + "settings_showToasts_note": "Onemogućite opću postavku '{{var0}}' BD-ova prije nego što ih onemogućite", + "settings_toastPosition_description": "Zadani položaj tosta", + "settings_toastPosition_note": "Mijenja samo položaj zdravica koje su stvorili moji dodatke", + "settings_useChromium_description": "Otvorite veze u Discordu umjesto u pregledniku", + "sort_by": "Poredati po", + "status": "Status", + "time": "Vrijeme", + "timezone": "Vremenska zona", + "to": "Do", + "toast_plugin_loaded": "Učitana je {{var0}}", + "toast_plugin_started": "{{var0}} je započeo", + "toast_plugin_stopped": "{{var0}} je zaustavljen", + "toast_plugin_translated": "prevedeno na {{var0}}", + "toast_plugin_unloaded": "{{var0}} je istovaren", + "toast_plugin_update_failed": "Ažuriranje za {{var0}} nije moguće preuzeti", + "toast_plugin_updated": "{{var0}} {{var1}} zamijenjen je s {{var2}} {{var3}}", + "update_check_complete": "Provjera ažuriranja dodatka dovršena", + "update_check_complete_outdated": "Provjera ažuriranja dodatka dovršena - {{var0}} zastarjelo!", + "update_check_info": "Provjerite dodatke koji podržavaju provjeru ažuriranja", + "update_notice_click": "Kliknite za ažuriranje!", + "update_notice_reload": "Ponovo učitajte da biste dovršili ažuriranje", + "update_notice_update": "Treba ažurirati sljedeće dodatke: ", + "updated": "Ažurirano" + }, + "hu": { + "add_to": "Hozzáadás a következőhöz: {{var0}}", + "ascending": "Növekvő", + "center": "Középre", + "changelog_added": "Új funkciók", + "changelog_fixed": "Hibaelhárítás", + "changelog_improved": "Fejlesztések", + "changelog_progress": "Előrehalad", + "check_for_updates": "Frissítések keresése", + "clipboard_success": "{{var0}} a vágólapra másolva", + "confirm": "Biztos vagy ebben?", + "copy": "Másolás {{var0}}", + "delete_fail": "A {{var0}} nem törölhető", + "delete_success": "{{var0}} sikeresen törölve", + "descending": "Csökkenő", + "developer": "Fejlesztő", + "donate_message": "Támogasson további frissítésekért!", + "download": "Letöltés", + "download_fail": "A {{var0}} nem tölthető le", + "download_success": "A {{var0}} letöltése sikeresen megtörtént", + "file_navigator_text": "Tallózás a fájlban", + "first": "Első", + "from": "Erről", + "gradient": "Gradiens", + "guildbanner": "Banner", + "guildicon": "Ikon", + "installed": "Telepítve", + "last": "Utolsó", + "left": "Bal", + "loading": "{{var0}} betöltése", + "location": "Elhelyezkedés", + "order": "Sorrend", + "outdated": "Elavult", + "please_wait": "Várj", + "right": "Jobb", + "save_fail": "A {{var0}} nem menthető", + "save_success": "{{var0}} sikeresen mentve", + "send": "{{var0}} küldése", + "server": "Szerver", + "settings_checkForUpdates_description": "Ellenőrizze a beépülő modulok frissítéseit közvetlenül a GitHubon", + "settings_checkForUpdates_note": "Ez megkerüli a BetterDiscord bővítménybiztonsági felülvizsgálatát!", + "settings_shareData_description": "Szinkronizálja a beépülő modulok beállításait a Discord fiókok között", + "settings_showSupportBadges_description": "Apró jelvényeket mutat azoknak a felhasználóknak, akik támogatják a Patreon-t", + "settings_showToasts_description": "Mutatja a bővítmény start és stop pirítósokat", + "settings_showToasts_note": "Mielőtt letiltaná őket, tiltsa le a BD-k '{{var0}} ” általános beállítását", + "settings_toastPosition_description": "Alapértelmezett pirítós pozíció", + "settings_toastPosition_note": "Csak a bővítményeket által létrehozott pirítósok pozícióját változtatja meg", + "settings_useChromium_description": "Nyissa meg a Linkeket a diszkordban a böngészője helyett", + "sort_by": "Sorrend", + "status": "Állapot", + "time": "Idő", + "timezone": "Időzóna", + "to": "Erre", + "toast_plugin_loaded": "{{var0}} betöltve", + "toast_plugin_started": "A {{var0}} elindult", + "toast_plugin_stopped": "A {{var0}} leállt", + "toast_plugin_translated": "lefordítva {{var0}} nyelvre", + "toast_plugin_unloaded": "{{var0}} kirakva", + "toast_plugin_update_failed": "A {{var0}} frissítése nem tölthető le", + "toast_plugin_updated": "A {{var0}} {{var1}} helyébe a következő lépett: {{var2}} {{var3}}", + "update_check_complete": "A bővítmény modul frissítésének ellenőrzése befejeződött", + "update_check_complete_outdated": "A bővítmény modul frissítésének ellenőrzése befejeződött - {{var0}} elavult!", + "update_check_info": "Ellenőrizze a Frissítés ellenőrzését támogató bővítményeket modulokat", + "update_notice_click": "Kattintson a frissítéshez!", + "update_notice_reload": "Töltse be újra a frissítés befejezéséhez", + "update_notice_update": "A következő bővítményeket frissíteni kell: ", + "updated": "Frissítve" + }, + "it": { + "add_to": "Aggiungi a {{var0}}", + "ascending": "Ascendente", + "center": "Centrato", + "changelog_added": "Nuove caratteristiche", + "changelog_fixed": "Risoluzione dei problemi", + "changelog_improved": "Miglioramenti", + "changelog_progress": "Progresso", + "check_for_updates": "Controlla gli aggiornamenti", + "clipboard_success": "{{var0}} copiato negli Appunti", + "confirm": "Sei sicuro?", + "copy": "Copia {{var0}}", + "delete_fail": "{{var0}} non può essere eliminato", + "delete_success": "{{var0}} eliminato correttamente", + "descending": "Discendente", + "developer": "Sviluppatore", + "donate_message": "Supportami per ulteriori aggiornamenti!", + "download": "Scarica", + "download_fail": "{{var0}} non può essere scaricato", + "download_success": "{{var0}} scaricato correttamente", + "file_navigator_text": "Sfoglia file", + "first": "Primo", + "from": "A partire dal", + "gradient": "Pendenza", + "guildbanner": "Banner", + "guildicon": "Icona", + "installed": "Installato", + "last": "Scorso", + "left": "Sinistra", + "loading": "Caricamento di {{var0}}", + "location": "Posizione", + "order": "Sequenza", + "outdated": "Obsoleto", + "please_wait": "Attendere prego", + "right": "Destra", + "save_fail": "{{var0}} non può essere salvato", + "save_success": "{{var0}} salvato correttamente", + "send": "Invia {{var0}}", + "server": "Server", + "settings_checkForUpdates_description": "Controlla gli aggiornamenti dei plugin direttamente su GitHub", + "settings_checkForUpdates_note": "Questo ignorerà la revisione della sicurezza dei plugin di BetterDiscord!", + "settings_shareData_description": "Sincronizza le configurazioni dei plug-in tra gli account Discord", + "settings_showSupportBadges_description": "Mostra piccoli badge per gli utenti che supportano il mio Patreon", + "settings_showToasts_description": "Mostra l'avvio e l'arresto del plugin", + "settings_showToasts_note": "Disabilita l'impostazione generale '{{var0}}' dei BD prima di disabilitarli", + "settings_toastPosition_description": "Posizione predefinita del toast", + "settings_toastPosition_note": "Cambia solo la posizione dei toast creati dai miei plugins", + "settings_useChromium_description": "Apri link in Discord invece che nel tuo browser", + "sort_by": "Ordina per", + "status": "Stato", + "time": "Tempo", + "timezone": "Fuso orario", + "to": "Per", + "toast_plugin_loaded": "{{var0}} è stato caricato", + "toast_plugin_started": "{{var0}} è iniziato", + "toast_plugin_stopped": "{{var0}} si è fermato", + "toast_plugin_translated": "tradotto in {{var0}}", + "toast_plugin_unloaded": "{{var0}} è stato scaricato", + "toast_plugin_update_failed": "Impossibile scaricare l'aggiornamento per {{var0}}", + "toast_plugin_updated": "{{var0}} {{var1}} è stato sostituito da {{var2}} {{var3}}", + "update_check_complete": "Controllo dell'aggiornamento del plugin completato", + "update_check_complete_outdated": "Controllo dell'aggiornamento del plugin completato - {{var0}} non aggiornato!", + "update_check_info": "Controlla i plugins che supportano il controllo degli aggiornamenti", + "update_notice_click": "Fare clic per aggiornare!", + "update_notice_reload": "Ricarica per completare l'aggiornamento", + "update_notice_update": "I seguenti plugins devono essere aggiornati: ", + "updated": "Aggiornato" + }, + "ja": { + "add_to": "{{var0}} に追加", + "ascending": "昇順", + "center": "中央揃え", + "changelog_added": "新機能", + "changelog_fixed": "修正点", + "changelog_improved": "改善点", + "changelog_progress": "進行中", + "check_for_updates": "アップデートを確認", + "clipboard_success": "{{var0}}をクリップボードにコピーしました", + "confirm": "本気ですか?", + "copy": "{{var0}}をコピーします", + "delete_fail": "{{var0}}の削除に失敗しました", + "delete_success": "{{var0}}が正常に削除されました", + "descending": "降順", + "developer": "開発者", + "donate_message": "もっといっぱいアップデートするために私をサポートしてください!", + "download": "ダウンロード", + "download_fail": "{{var0}} のダウンロードに失敗しました", + "download_success": "{{var0}} が正常にダウンロードされました", + "file_navigator_text": "ファイルの参照", + "first": "最初", + "from": "から", + "gradient": "変化の割合", + "guildbanner": "バナー", + "guildicon": "アイコン", + "installed": "インストール済み", + "last": "最後", + "left": "左", + "loading": "{{var0} を読み込んでいます}", + "location": "位置", + "order": "順序", + "outdated": "古いバージョン", + "please_wait": "お待ちください", + "right": "右", + "save_fail": "{{var0}}は保存できませんでした", + "save_success": "{{var0}}が正常に保存されました", + "send": "{{var0}}を送信します", + "server": "サーバー", + "settings_checkForUpdates_description": "GitHub で直接プラグインの更新を確認します。", + "settings_checkForUpdates_note": "これにより、BetterDiscord のプラグインの安全性レビューが回避されます。", + "settings_shareData_description": "プラグイン構成の同期", + "settings_showSupportBadges_description": "私へ寄付いただいたユーザーに小さなバッジを表示します", + "settings_showToasts_description": "プラグインの開始と停止のトースト通知を表示します", + "settings_showToasts_note": "先にBD の一般設定の '{{var0}}' を無効にしてください", + "settings_toastPosition_description": "デフォルトのトースト通知の位置", + "settings_toastPosition_note": "プラグインによるトーストの位置のみを変更します", + "settings_useChromium_description": "ブラウザの代わりにDiscord内でリンクを開く", + "sort_by": "並べ替え", + "status": "状態", + "time": "時間", + "timezone": "タイムゾーン", + "to": "に", + "toast_plugin_loaded": "{{var0}} が読み込まれました", + "toast_plugin_started": "{{var0}} を開始しました", + "toast_plugin_stopped": "{{var0}} を停止しました", + "toast_plugin_translated": "{{var0}} に変換されました", + "toast_plugin_unloaded": "{{var0}} をアンロードしました", + "toast_plugin_update_failed": "{{var0}} の更新に失敗しました", + "toast_plugin_updated": "{{var0}} {{var1}} は {{var2}} {{var3}} に置き換えられました", + "update_check_complete": "プラグインの更新チェックが完了しました", + "update_check_complete_outdated": "プラグインの更新チェックが完了しました- {{var0}} が古くなっています!", + "update_check_info": "アップデート機能を持つプラグインをチェックします", + "update_notice_click": "クリックして更新!", + "update_notice_reload": "リロードして更新を完了します", + "update_notice_update": "プラグインを更新する必要があります。", + "updated": "更新が完了しました" + }, + "ko": { + "add_to": "{{var0}}에 추가", + "ascending": "오름차순", + "center": "중앙", + "changelog_added": "새로운 기능", + "changelog_fixed": "문제 해결", + "changelog_improved": "개선됨", + "changelog_progress": "진행중", + "check_for_updates": "업데이트 확인", + "clipboard_success": "{{var0}}을(를) 클립보드에 복사했습니다.", + "confirm": "확실한가요?", + "copy": "{{var0}} 복사", + "delete_fail": "{{var0}}을(를) 삭제할 수 없습니다.", + "delete_success": "{{var0}}이(가) 성공적으로 삭제되었습니다.", + "descending": "내림차순", + "developer": "개발자", + "donate_message": "더 많은 업데이트를 위해 저를 후원해주세요!", + "download": "다운로드", + "download_fail": "{{var0}}을(를) 다운로드 할 수 없습니다.", + "download_success": "{{var0}}을(를) 다운로드 완료했습니다", + "file_navigator_text": "파일 찾아보기", + "first": "먼저", + "from": "에서", + "gradient": "그라데이션", + "guildbanner": "배너", + "guildicon": "상", + "installed": "설치됨", + "last": "마지막", + "left": "좌측", + "loading": "로드 중 {{var0}}", + "location": "위치", + "order": "순서", + "outdated": "구버전", + "please_wait": "잠시만 기다려주세요", + "right": "우측", + "save_fail": "{{var0}}을(를) 저장할 수 없습니다.", + "save_success": "{{var0}}이(가) 성공적으로 저장되었습니다.", + "send": "{{var0}} 보내기", + "server": "서버", + "settings_checkForUpdates_description": "GitHub에서 직접 플러그인 업데이트를 확인하세요.", + "settings_checkForUpdates_note": "이는 BetterDiscord의 플러그인 안전성 검토를 우회합니다.", + "settings_shareData_description": "Discord 계정 간에 플러그인 구성을 동기화합니다.", + "settings_showSupportBadges_description": "개발자의 Patreon으로 후원해주는 사용자에게 작은 배지를 표시합니다.", + "settings_showToasts_description": "플러그인 시작 및 중지 알림 표시", + "settings_showToasts_note": "BD를 비활성화하기 전에 BD를 일반 설정 '{{var0}}'을(를) 비활성화하세요.", + "settings_toastPosition_description": "기본 토스트창(Toast) 위치", + "settings_toastPosition_note": "위 옵션은 내 플러그인에 의해 생성된 토스트창에만 해당됩니다.", + "settings_useChromium_description": "브라우저 대신 Discord 에서 링크 열기", + "sort_by": "정렬 기준", + "status": "상태", + "time": "시각", + "timezone": "시간대", + "to": "에", + "toast_plugin_loaded": "{{var0}}이(가) 로드되었습니다.", + "toast_plugin_started": "{{var0}}이(가) 시작되었습니다.", + "toast_plugin_stopped": "{{var0}}이(가) 중지되었습니다.", + "toast_plugin_translated": "{{var0}} 로 번역됨", + "toast_plugin_unloaded": "{{var0}}이(가) 언로드되었습니다.", + "toast_plugin_update_failed": "{{var0}} 에 대한 업데이트를 다운로드 할 수 없습니다.", + "toast_plugin_updated": "{{var0}} {{var1}}이(가) {{var2}} {{var3}}으(로) 교체되었습니다.", + "update_check_complete": "플러그인 업데이트 확인 완료", + "update_check_complete_outdated": "플러그인 업데이트 확인 완료 - {{var0}}이(가) 구버전입니다!", + "update_check_info": "업데이트 기능을 지원하는 모든 플러그인 업데이트", + "update_notice_click": "업데이트하려면 클릭하세요!", + "update_notice_reload": "업데이트를 완료하려면 새로고침하세요.", + "update_notice_update": "다음 플러그인을 업데이트해야합니다: ", + "updated": "업데이트됨" + }, + "lt": { + "add_to": "Pridėti prie {{var0}}", + "ascending": "Kylanti", + "center": "Centruotas", + "changelog_added": "Naujos savybės", + "changelog_fixed": "Problemų sprendimas", + "changelog_improved": "Patobulinimai", + "changelog_progress": "Progresas", + "check_for_updates": "Tikrinti, ar yra atnaujinimų", + "clipboard_success": "{{var0}} nukopijuotas į mainų sritį", + "confirm": "Ar tu tuo tikras?", + "copy": "Kopijuoti {{var0}}", + "delete_fail": "{{var0}} negalima ištrinti", + "delete_success": "{{var0}} sėkmingai ištrinta", + "descending": "Mažėjantis", + "developer": "Programuotojas", + "donate_message": "Palaikykite mane, kad gautumėte daugiau naujinių!", + "download": "Parsisiųsti", + "download_fail": "{{var0}} negalima atsisiųsti", + "download_success": "{{var0}} sėkmingai atsisiųsta", + "file_navigator_text": "Naršyti failą", + "first": "Pirmas", + "from": "Nuo", + "gradient": "Gradientas", + "guildbanner": "Reklamjuostė", + "guildicon": "Piktograma", + "installed": "Įdiegta", + "last": "Paskutinis", + "left": "Kairėje", + "loading": "Įkeliama {{var0}}", + "location": "Vieta", + "order": "Seka", + "outdated": "Pasenęs", + "please_wait": "Maloniai palauk", + "right": "Teisingai", + "save_fail": "{{var0}} negalima išsaugoti", + "save_success": "{{var0}} išsaugota sėkmingai", + "send": "Siųsti {{var0}}", + "server": "Serverio", + "settings_checkForUpdates_description": "Tiesiogiai „GitHub“ patikrinkite, ar nėra papildinių naujinimų", + "settings_checkForUpdates_note": "Tai apeis „BetterDiscord“ papildinių saugos apžvalgą!", + "settings_shareData_description": "Sinchronizuoja įskiepio konfigūracijas tarp „Discord“ paskyrų", + "settings_showSupportBadges_description": "Rodo mažus ženklelius vartotojams, palaikantiems mano Patreon", + "settings_showToasts_description": "Parodo įskiepių paleidimo ir sustabdymo tostus", + "settings_showToasts_note": "Prieš išjungdami, išjunkite bendrą BD nustatymą '{{var0}}'", + "settings_toastPosition_description": "Numatytoji skrudintos duonos padėtis", + "settings_toastPosition_note": "Keičia tik mano įskiepiai sukurtų tostų poziciją", + "settings_useChromium_description": "Vietoj savo naršyklės atidarykite nuorodas nesantaikoje", + "sort_by": "Rūšiuoti pagal", + "status": "Būsena", + "time": "Laikas", + "timezone": "Laiko zona", + "to": "Į", + "toast_plugin_loaded": "{{var0}} buvo įkelta", + "toast_plugin_started": "{{var0}} prasidėjo", + "toast_plugin_stopped": "{{var0}} sustabdyta", + "toast_plugin_translated": "išversta į {{var0}}", + "toast_plugin_unloaded": "{{var0}} buvo iškrautas", + "toast_plugin_update_failed": "Negalima atsisiųsti {{var0}} naujinio", + "toast_plugin_updated": "{{var0}} {{var1}} pakeista į {{var2}} {{var3}}", + "update_check_complete": "Įskiepių atnaujinimo patikrinimas baigtas", + "update_check_complete_outdated": "Įskiepių atnaujinimo patikrinimas baigtas - {{var0}} pasenęs!", + "update_check_info": "Patikrinkite įskiepiai, kurie palaiko naujinimo patikrą", + "update_notice_click": "Spustelėkite norėdami atnaujinti!", + "update_notice_reload": "Įkelkite iš naujo, kad užbaigtumėte atnaujinimą", + "update_notice_update": "Reikia atnaujinti šiuos įskiepiai: ", + "updated": "Atnaujinta" + }, + "nl": { + "add_to": "Toevoegen aan {{var0}}", + "ascending": "Oplopend", + "center": "Gecentreerd", + "changelog_added": "Nieuwe features", + "changelog_fixed": "Probleemoplossen", + "changelog_improved": "Verbeteringen", + "changelog_progress": "Vooruitgang", + "check_for_updates": "Controleer op updates", + "clipboard_success": "{{var0}} gekopieerd naar klembord", + "confirm": "Weet je zeker dat?", + "copy": "Kopieer {{var0}}", + "delete_fail": "{{var0}} kan niet worden verwijderd", + "delete_success": "{{var0}} is verwijderd", + "descending": "Aflopend", + "developer": "Ontwikkelaar", + "donate_message": "Steun mij voor meer updates!", + "download": "Downloaden", + "download_fail": "{{var0}} kan niet worden gedownload", + "download_success": "{{var0}} succesvol gedownload", + "file_navigator_text": "Bestand zoeken", + "first": "Eerste", + "from": "Van", + "gradient": "Verloop", + "guildbanner": "Banner", + "guildicon": "Icoon", + "installed": "Geïnstalleerd", + "last": "Laatste", + "left": "Links", + "loading": "Laden {{var0}}", + "location": "Plaats", + "order": "Volgorde", + "outdated": "Verouderd", + "please_wait": "Wacht even", + "right": "Rechtsaf", + "save_fail": "{{var0}} kan niet worden opgeslagen", + "save_success": "{{var0}} succesvol opgeslagen", + "send": "Stuur {{var0}}", + "server": "Server", + "settings_checkForUpdates_description": "Controleer rechtstreeks op GitHub op plug-inupdates", + "settings_checkForUpdates_note": "Hiermee omzeil je de Plugin Safety Review van BetterDiscord!", + "settings_shareData_description": "Synchroniseert de plug-inconfiguraties tussen Discord-accounts", + "settings_showSupportBadges_description": "Toont kleine badges voor gebruikers die mijn Patreon ondersteunen", + "settings_showToasts_description": "Toont plugin start en stop toasts", + "settings_showToasts_note": "Schakel de algemene instelling '{{var0}}' van BD's uit voordat u ze uitschakelt", + "settings_toastPosition_description": "Standaard toastpositie", + "settings_toastPosition_note": "Verandert alleen de positie van toast gemaakt door mijn plugins", + "settings_useChromium_description": "Open links in Discord in plaats van uw browser", + "sort_by": "Sorteer op", + "status": "Toestand", + "time": "Tijd", + "timezone": "Tijdzone", + "to": "Naar", + "toast_plugin_loaded": "{{var0}} is geladen", + "toast_plugin_started": "{{var0}} is gestart", + "toast_plugin_stopped": "{{var0}} is gestopt", + "toast_plugin_translated": "vertaald naar {{var0}}", + "toast_plugin_unloaded": "{{var0}} is verwijderd", + "toast_plugin_update_failed": "Update voor {{var0}} kan niet worden gedownload", + "toast_plugin_updated": "{{var0}} {{var1}} is vervangen door {{var2}} {{var3}}", + "update_check_complete": "Controle op update van plugin voltooid", + "update_check_complete_outdated": "Controle van update van plugin voltooid - {{var0}} verouderd!", + "update_check_info": "Controleer plugins die de updatecontrole ondersteunen", + "update_notice_click": "Klik om te updaten!", + "update_notice_reload": "Laad opnieuw om de update te voltooien", + "update_notice_update": "De volgende plugins moeten worden bijgewerkt: ", + "updated": "Bijgewerkt" + }, + "no": { + "add_to": "Legg til i {{var0}}", + "ascending": "Stigende", + "center": "Sentrert", + "changelog_added": "Nye funksjoner", + "changelog_fixed": "Feilsøking", + "changelog_improved": "Forbedringer", + "changelog_progress": "Framgang", + "check_for_updates": "Se etter oppdateringer", + "clipboard_success": "Kopierte {{var0}} til utklippstavlen", + "confirm": "Er du sikker?", + "copy": "Kopier {{var0}}", + "delete_fail": "{{var0}} kan ikke slettes", + "delete_success": "{{var0}} ble slettet", + "descending": "Fallende", + "developer": "Utvikler", + "donate_message": "Støtt meg for flere oppdateringer!", + "download": "Nedlasting", + "download_fail": "{{var0}} kan ikke lastes ned", + "download_success": "{{var0}} lastet ned", + "file_navigator_text": "Bla gjennom filen", + "first": "Først", + "from": "Fra", + "gradient": "Gradient", + "guildbanner": "Banner", + "guildicon": "Ikon", + "installed": "Installert", + "last": "Siste", + "left": "Venstre", + "loading": "Laster inn {{var0}}", + "location": "Plassering", + "order": "Sekvens", + "outdated": "Utdatert", + "please_wait": "Vennligst vent", + "right": "Ikke sant", + "save_fail": "{{var0}} kan ikke lagres", + "save_success": "{{var0}} lagret", + "send": "Send {{var0}}", + "server": "Server", + "settings_checkForUpdates_description": "Se etter plugin-oppdateringer direkte på GitHub", + "settings_checkForUpdates_note": "Dette vil omgå BetterDiscords Plugin Safety Review!", + "settings_shareData_description": "Synkroniserer plugin-konfigurasjonene mellom Discord-kontoer", + "settings_showSupportBadges_description": "Viser små merker for brukere som støtter min Patreon", + "settings_showToasts_description": "Viser plugin start og stopp toasts", + "settings_showToasts_note": "Deaktiver den generelle innstillingen '{{var0}}' for BD-er før du deaktiverer dem", + "settings_toastPosition_description": "Standard toastposisjon", + "settings_toastPosition_note": "Endrer bare posisjonen for ristet brød opprettet av plugins mine", + "settings_useChromium_description": "Åpne koblinger i Discord i stedet for nettleseren din", + "sort_by": "Sorter etter", + "status": "Status", + "time": "Tid", + "timezone": "Tidssone", + "to": "Til", + "toast_plugin_loaded": "{{var0}} er lastet inn", + "toast_plugin_started": "{{var0}} har startet", + "toast_plugin_stopped": "{{var0}} har stoppet", + "toast_plugin_translated": "oversatt til {{var0}}", + "toast_plugin_unloaded": "{{var0}} er lastet ut", + "toast_plugin_update_failed": "Oppdatering for {{var0}} kan ikke lastes ned", + "toast_plugin_updated": "{{var0}} {{var1}} er erstattet av {{var2}} {{var3}}", + "update_check_complete": "Sjekk plugin-oppdatering fullført", + "update_check_complete_outdated": "Sjekk plugin-oppdatering fullført - {{var0}} utdatert!", + "update_check_info": "Sjekk plugins som støtter oppdateringskontrollen", + "update_notice_click": "Klikk for å oppdatere!", + "update_notice_reload": "Last inn for å fullføre oppdateringen", + "update_notice_update": "Følgende plugins må oppdateres: ", + "updated": "Oppdatert" + }, + "pl": { + "add_to": "Dodaj do {{var0}}", + "ascending": "Rosnąco", + "center": "Środek", + "changelog_added": "Dodano", + "changelog_fixed": "Naprawiono", + "changelog_improved": "Ulepszono", + "changelog_progress": "Postęp", + "check_for_updates": "Sprawdź aktualizacje", + "clipboard_success": "Skopiowano {{var0}} do schowka", + "confirm": "Na pewno?", + "copy": "Kopiuj {{var0}}", + "delete_fail": "Nie można usunąć {{var0}}", + "delete_success": "Pomyślnie usunięto {{var0}}", + "descending": "Malejąco", + "developer": "Deweloper", + "donate_message": "Wesprzyj mnie, aby uzyskać więcej aktualizacji!", + "download": "Pobieranie", + "download_fail": "Nie można pobrać {{var0}}", + "download_success": "Pomyślnie pobrano {{var0}}", + "file_navigator_text": "Przeglądaj plik", + "first": "Pierwszy", + "from": "Od", + "gradient": "Gradient", + "guildbanner": "Transparent", + "guildicon": "Ikona", + "installed": "Zainstalowany", + "last": "Ostatni", + "left": "Lewo", + "loading": "Ładowanie {{var0}}", + "location": "Lokalizacja", + "order": "Kolejność", + "outdated": "Przestarzały", + "please_wait": "Proszę czekać", + "right": "Prawo", + "save_fail": "Nie można zapisać {{var0}}", + "save_success": "Pomyślnie zapisano {{var0}}", + "send": "Wyślij {{var0}}", + "server": "Serwer", + "settings_checkForUpdates_description": "Sprawdź aktualizacje wtyczek bezpośrednio w GitHub", + "settings_checkForUpdates_note": "Spowoduje to pominięcie przeglądu bezpieczeństwa wtyczek BetterDiscord!", + "settings_shareData_description": "Synchronizuje konfiguracje wtyczek między kontami Discord", + "settings_showSupportBadges_description": "Pokazuje małe odznaki dla wspierających mojego Patreona", + "settings_showToasts_description": "Pokazuje toasty uruchamiające i zatrzymujące wtyczki", + "settings_showToasts_note": "Wyłącz ogólne ustawienie '{{var0}} ” dysków BD przed ich wyłączeniem", + "settings_toastPosition_description": "Domyślna pozycja toastów", + "settings_toastPosition_note": "Zmienia tylko pozycję toastów utworzonych przez moje wtyczki", + "settings_useChromium_description": "Otwieraj linki w Discordzie zamiast w przeglądarce", + "sort_by": "Sortuj według", + "status": "Status", + "time": "Czas", + "timezone": "Strefa czasowa", + "to": "Do", + "toast_plugin_loaded": "Załadowano {{var0}}", + "toast_plugin_started": "Rozpoczęto {{var0}}", + "toast_plugin_stopped": "{{var0}} został zatrzymany", + "toast_plugin_translated": "przetłumaczone na {{var0}}", + "toast_plugin_unloaded": "Usunięto {{var0}}", + "toast_plugin_update_failed": "Nie można pobrać aktualizacji dla {{var0}}", + "toast_plugin_updated": "{{var0}} {{var1}} został zastąpiony przez {{var2}} {{var3}}", + "update_check_complete": "Sprawdzanie aktualizacji wtyczki zakończone", + "update_check_complete_outdated": "Sprawdzanie aktualizacji wtyczki zakończone - {{var0}} nieaktualne!", + "update_check_info": "Sprawdź wtyczki obsługujące sprawdzanie aktualizacji", + "update_notice_click": "Kliknij, aby zaktualizować!", + "update_notice_reload": "Załaduj ponownie, aby zakończyć aktualizację", + "update_notice_update": "Należy zaktualizować następujące wtyczki: ", + "updated": "Zaktualizowano" + }, + "pt-BR": { + "add_to": "Adicionar a {{var0}}", + "ascending": "Ascendente", + "center": "Centrado", + "changelog_added": "Novas características", + "changelog_fixed": "Solução de problemas", + "changelog_improved": "Melhorias", + "changelog_progress": "Progresso", + "check_for_updates": "Verifique se há atualizações", + "clipboard_success": "Copiado {{var0}} para a área de transferência", + "confirm": "Você tem certeza?", + "copy": "Copiar {{var0}}", + "delete_fail": "{{var0}} não pode ser excluído", + "delete_success": "{{var0}} excluído com sucesso", + "descending": "Descendente", + "developer": "Desenvolvedor", + "donate_message": "Apoie-me para mais atualizações!", + "download": "Baixar", + "download_fail": "{{var0}} não pode ser baixado", + "download_success": "{{var0}} baixado com sucesso", + "file_navigator_text": "Procurar arquivo", + "first": "Primeiro", + "from": "De", + "gradient": "Gradiente", + "guildbanner": "Bandeira", + "guildicon": "Ícone", + "installed": "Instalado", + "last": "Último", + "left": "Esquerda", + "loading": "Carregando {{var0}}", + "location": "Localização", + "order": "Seqüência", + "outdated": "Desatualizado", + "please_wait": "Por favor espere", + "right": "Direito", + "save_fail": "{{var0}} não pode ser salvo", + "save_success": "{{var0}} salvo com sucesso", + "send": "Enviar {{var0}}", + "server": "Servidor", + "settings_checkForUpdates_description": "Verifique se há atualizações de plug-ins diretamente no GitHub", + "settings_checkForUpdates_note": "Isso ignorará a revisão de segurança de plug-ins da BetterDiscord!", + "settings_shareData_description": "Sincroniza as configurações de plug-in entre contas do Discord", + "settings_showSupportBadges_description": "Mostra pequenos emblemas para usuários que apóiam meu Patreon", + "settings_showToasts_description": "Mostra o início e o fim do plugin do brinde", + "settings_showToasts_note": "Desative a configuração geral '{{var0}}' de BDs antes de desativá-los", + "settings_toastPosition_description": "Posição padrão do brinde", + "settings_toastPosition_note": "Apenas altera a posição dos brindes criados pelos meus plugins", + "settings_useChromium_description": "Abra links no Discord em vez do seu navegador", + "sort_by": "Ordenar por", + "status": "Status", + "time": "Tempo", + "timezone": "Fuso horário", + "to": "Para", + "toast_plugin_loaded": "{{var0}} foi carregado", + "toast_plugin_started": "{{var0}} começou", + "toast_plugin_stopped": "{{var0}} parou", + "toast_plugin_translated": "traduzido para {{var0}}", + "toast_plugin_unloaded": "{{var0}} foi descarregado", + "toast_plugin_update_failed": "A atualização para {{var0}} não pode ser baixada", + "toast_plugin_updated": "{{var0}} {{var1}} foi substituído por {{var2}} {{var3}}", + "update_check_complete": "Verificação de atualização do plugin concluída", + "update_check_complete_outdated": "Verificação de atualização do plugin concluída - {{var0}} desatualizado!", + "update_check_info": "Verifique os plugins que suportam a verificação de atualização", + "update_notice_click": "Clique para atualizar!", + "update_notice_reload": "Recarregue para completar a atualização", + "update_notice_update": "Os seguintes plugins precisam ser atualizados: ", + "updated": "Atualizada" + }, + "ro": { + "add_to": "Adăugați la {{var0}}", + "ascending": "Ascendent", + "center": "Centrat", + "changelog_added": "Functii noi", + "changelog_fixed": "Depanare", + "changelog_improved": "Îmbunătățiri", + "changelog_progress": "Progres", + "check_for_updates": "Verifică pentru actualizări", + "clipboard_success": "{{var0}} a fost copiat în Clipboard", + "confirm": "Esti sigur?", + "copy": "Copiați {{var0}}", + "delete_fail": "{{var0}} nu poate fi șters", + "delete_success": "{{var0}} șters cu succes", + "descending": "Descendentă", + "developer": "Dezvoltator", + "donate_message": "Sprijină-mă pentru mai multe actualizări!", + "download": "Descarca", + "download_fail": "{{var0}} nu poate fi descărcat", + "download_success": "{{var0}} descărcat cu succes", + "file_navigator_text": "Răsfoiți fișierul", + "first": "Primul", + "from": "Din", + "gradient": "Gradient", + "guildbanner": "Banner", + "guildicon": "Pictogramă", + "installed": "Instalat", + "last": "Ultimul", + "left": "Stânga", + "loading": "Se încarcă {{var0}}", + "location": "Locație", + "order": "Secvenţă", + "outdated": "Învechit", + "please_wait": "Așteptați plăcut", + "right": "Dreapta", + "save_fail": "{{var0}} nu poate fi salvat", + "save_success": "{{var0}} salvat cu succes", + "send": "Trimite {{var0}}", + "server": "Server", + "settings_checkForUpdates_description": "Verificați actualizările pluginurilor direct pe GitHub", + "settings_checkForUpdates_note": "Aceasta va ocoli revizuirea de siguranță a pluginurilor BetterDiscord!", + "settings_shareData_description": "Sincronizează configurațiile pluginurilor între conturile Discord", + "settings_showSupportBadges_description": "Afișează insigne mici pentru utilizatorii care acceptă Patreon", + "settings_showToasts_description": "Afișează pornirea și oprirea toastelor pluginului", + "settings_showToasts_note": "Dezactivați setarea generală '{{var0}} ” a BD-urilor înainte de a le dezactiva", + "settings_toastPosition_description": "Poziție implicită Toast", + "settings_toastPosition_note": "Modifică doar poziția toastelor create de pluginuri mele", + "settings_useChromium_description": "Deschideți linkuri în discordie în loc de browser", + "sort_by": "Filtrează după", + "status": "Stare", + "time": "Timp", + "timezone": "Fus orar", + "to": "La", + "toast_plugin_loaded": "{{var0}} a fost încărcat", + "toast_plugin_started": "{{var0}} a început", + "toast_plugin_stopped": "{{var0}} s-a oprit", + "toast_plugin_translated": "tradus în {{var0}}", + "toast_plugin_unloaded": "{{var0}} a fost descărcat", + "toast_plugin_update_failed": "Actualizarea pentru {{var0}} nu poate fi descărcată", + "toast_plugin_updated": "{{var0}} {{var1}} a fost înlocuit cu {{var2}} {{var3}}", + "update_check_complete": "Verificarea actualizării pluginului s-a finalizat", + "update_check_complete_outdated": "Verificarea actualizării pluginului s-a finalizat - {{var0}} nu este actualizată!", + "update_check_info": "Verificați pluginuri care acceptă verificarea actualizării", + "update_notice_click": "Faceți clic pentru a actualiza!", + "update_notice_reload": "Reîncărcați pentru a finaliza actualizarea", + "update_notice_update": "Următoarele pluginuri trebuie actualizate: ", + "updated": "La curent" + }, + "ru": { + "add_to": "Добавить в {{var0}}", + "ascending": "Возрастанию", + "center": "По центру", + "changelog_added": "Новые возможности", + "changelog_fixed": "Исправления ошибок", + "changelog_improved": "Улучшения", + "changelog_progress": "Прогресс", + "check_for_updates": "Проверить наличие обновлений", + "clipboard_success": "Скопировано {{var0}} в буфер обмена", + "confirm": "Подтвердить?", + "copy": "Копировать {{var0}}", + "delete_fail": "Не удалось удалить: {{var0}}", + "delete_success": "Успешно удалено: {{var0}}", + "descending": "Убыванию", + "developer": "Разработчик", + "donate_message": "Поддержите меня, чтобы получать дальнейшие обновления!", + "download": "Скачать", + "download_fail": "Не удалось скачать: {{var0}}", + "download_success": "Успешно скачено: {{var0}}", + "file_navigator_text": "Посмотреть файл", + "first": "Первый", + "from": "Из", + "gradient": "Градиент", + "guildbanner": "Баннер", + "guildicon": "Икона", + "installed": "Установлено", + "last": "Последний", + "left": "Слева", + "loading": "Загрузка {{var0}}", + "location": "Расположение", + "order": "Порядок", + "outdated": "Устаревшее", + "please_wait": "Подождите", + "right": "Справа", + "save_fail": "Не удалось сохранить: {{var0}}", + "save_success": "Успешно сохранено: {{var0}}", + "send": "Отправить {{var0}}", + "server": "Сервер", + "settings_checkForUpdates_description": "Проверьте наличие обновлений плагинов непосредственно на GitHub", + "settings_checkForUpdates_note": "Это обойдет проверку безопасности плагинов BetterDiscord!", + "settings_shareData_description": "Синхронизирует конфигурации плагинов между учетными записями Discord", + "settings_showSupportBadges_description": "Показывать маленькие значки у пользователей, которые поддерживают мой Patreon", + "settings_showToasts_description": "Показывать всплывающие уведомления запуска и остановки плагинов", + "settings_showToasts_note": "Отключите общий параметр BD '{{var0}}' перед отключением этого параметра", + "settings_toastPosition_description": "Местоположение всплывающего уведомления по умолчанию", + "settings_toastPosition_note": "Изменяет только положение всплывающих уведомлений, созданных моими плагинами", + "settings_useChromium_description": "Открывайте ссылки через Discord вместо браузера", + "sort_by": "Сортировать по", + "status": "Состояние", + "time": "Время", + "timezone": "Часовой пояс", + "to": "В", + "toast_plugin_loaded": "{{var0}} загружен", + "toast_plugin_started": "{{var0}} запущен", + "toast_plugin_stopped": "{{var0}} остановлен", + "toast_plugin_translated": "переведён на {{var0}}", + "toast_plugin_unloaded": "{{var0}} выгружен", + "toast_plugin_update_failed": "Обновление для {{var0}} не может быть загружено", + "toast_plugin_updated": "{{var0}} {{var1}} заменён на {{var2}} {{var3}}", + "update_check_complete": "Проверка плагинов на обновления завершена", + "update_check_complete_outdated": "Проверка плагинов на обновления завершена - {{var0}} устарел!", + "update_check_info": "Проверьте поддерживаемые плагины на обновления", + "update_notice_click": "Нажмите, чтобы обновить!", + "update_notice_reload": "Перезагрузите, чтобы завершить обновление", + "update_notice_update": "Необходимо обновить следующие плагины: ", + "updated": "Обновлено" + }, + "sv": { + "add_to": "Lägg till i {{var0}}", + "ascending": "Stigande", + "center": "Centrerad", + "changelog_added": "Nya egenskaper", + "changelog_fixed": "Felsökning", + "changelog_improved": "Förbättringar", + "changelog_progress": "Framsteg", + "check_for_updates": "Sök efter uppdateringar", + "clipboard_success": "Kopierade {{var0}} till Urklipp", + "confirm": "Är du säker?", + "copy": "Kopiera {{var0}}", + "delete_fail": "{{var0}} kan inte raderas", + "delete_success": "{{var0}} har tagits bort", + "descending": "Nedåtgående", + "developer": "Utvecklaren", + "donate_message": "Stöd mig för fler uppdateringar!", + "download": "Ladda ner", + "download_fail": "{{var0}} kan inte laddas ner", + "download_success": "{{var0}} laddades ner", + "file_navigator_text": "Bläddra i filen", + "first": "Först", + "from": "Från", + "gradient": "Lutning", + "guildbanner": "Baner", + "guildicon": "Ikon", + "installed": "Installerat", + "last": "Sista", + "left": "Vänster", + "loading": "Laddar {{var0}}", + "location": "Plats", + "order": "Sekvens", + "outdated": "Föråldrad", + "please_wait": "Vänligen vänta", + "right": "Höger", + "save_fail": "{{var0}} kan inte sparas", + "save_success": "{{var0}} har sparats", + "send": "Skicka {{var0}}", + "server": "Server", + "settings_checkForUpdates_description": "Sök efter pluginuppdateringar direkt på GitHub", + "settings_checkForUpdates_note": "Detta kommer att kringgå BetterDiscords Plugin Safety Review!", + "settings_shareData_description": "Synkroniserar plugin-konfigurationerna mellan Discord-konton", + "settings_showSupportBadges_description": "Visar små märken för användare som stöder min Patreon", + "settings_showToasts_description": "Visar plugin start och stopp toasts", + "settings_showToasts_note": "Inaktivera den allmänna inställningen '{{var0}}' för BD-skivor innan du inaktiverar dem", + "settings_toastPosition_description": "Standard toastposition", + "settings_toastPosition_note": "Ändrar bara positionen för rostat bröd som skapats av mina plugins", + "settings_useChromium_description": "Öppna Länkar i Discord istället för din webbläsare", + "sort_by": "Sortera efter", + "status": "Status", + "time": "Tid", + "timezone": "Tidszon", + "to": "Till", + "toast_plugin_loaded": "{{var0}} har laddats", + "toast_plugin_started": "{{var0}} har startat", + "toast_plugin_stopped": "{{var0}} har slutat", + "toast_plugin_translated": "översatt till {{var0}}", + "toast_plugin_unloaded": "{{var0}} har lossats", + "toast_plugin_update_failed": "Uppdatering för {{var0}} kan inte laddas ner", + "toast_plugin_updated": "{{var0}} {{var1}} har ersatts med {{var2}} {{var3}}", + "update_check_complete": "Kontrollen av plugin-uppdateringen slutförd", + "update_check_complete_outdated": "Kontrollen av plugin-uppdateringen slutförd - {{var0}} inaktuell!", + "update_check_info": "Kontrollera plugins som stöder uppdateringskontrollen", + "update_notice_click": "Klicka för att uppdatera!", + "update_notice_reload": "Ladda om för att slutföra uppdateringen", + "update_notice_update": "Följande plugins måste uppdateras: ", + "updated": "Uppdaterad" + }, + "th": { + "add_to": "เพิ่มใน{{var0}}", + "ascending": "จากน้อยไปมาก", + "center": "อยู่กึ่งกลาง", + "changelog_added": "คุณสมบัติใหม่", + "changelog_fixed": "การแก้ไขปัญหา", + "changelog_improved": "การปรับปรุง", + "changelog_progress": "ความคืบหน้า", + "check_for_updates": "ตรวจสอบสำหรับการอัพเดต", + "clipboard_success": "คัดลอก {{var0}} ไปยังคลิปบอร์ดแล้ว", + "confirm": "คุณแน่ใจไหม?", + "copy": "คัดลอก {{var0}}", + "delete_fail": "ไม่สามารถลบ{{var0}}ได้", + "delete_success": "ลบ{{var0}}เรียบร้อยแล้ว", + "descending": "จากมากไปน้อย", + "developer": "ผู้พัฒนา", + "donate_message": "สนับสนุนฉันสำหรับการอัปเดตเพิ่มเติม!", + "download": "ดาวน์โหลด", + "download_fail": "ไม่สามารถดาวน์โหลด{{var0}}", + "download_success": "ดาวน์โหลด{{var0}}สำเร็จ", + "file_navigator_text": "เรียกดูไฟล์", + "first": "อันดับแรก", + "from": "จาก", + "gradient": "ไล่ระดับสี", + "guildbanner": "แบนเนอร์", + "guildicon": "ไอคอน", + "installed": "ติดตั้งแล้ว", + "last": "ล่าสุด", + "left": "ซ้าย", + "loading": "กำลังโหลด{{var0}}", + "location": "สถานที่", + "order": "ลำดับ", + "outdated": "เก่า", + "please_wait": "โปรดรอ", + "right": "ขวา", + "save_fail": "ไม่สามารถบันทึก{{var0}}ได้", + "save_success": "บันทึก{{var0}}เรียบร้อยแล้ว", + "send": "ส่ง {{var0}}", + "server": "เซิร์ฟเวอร์", + "settings_checkForUpdates_description": "ตรวจสอบการอัปเดตปลั๊กอินโดยตรงบน GitHub ซึ่งจะข้ามการตรวจสอบปลั๊กอิน Safety ของ BD!", + "settings_checkForUpdates_note": "", + "settings_shareData_description": "ประสานการกำหนดค่าปลั๊กอินระหว่างบัญชีที่ไม่ลงรอยกัน", + "settings_showSupportBadges_description": "แสดงป้ายขนาดเล็กสำหรับผู้ใช้ที่สนับสนุน Patreon ของฉัน", + "settings_showToasts_description": "แสดงปลั๊กอินเริ่มและหยุดขนมปังปิ้ง", + "settings_showToasts_note": "ปิดการใช้งานการตั้งค่าทั่วไป '{{var0}}' ของ BD ก่อนปิดใช้งาน", + "settings_toastPosition_description": "ตำแหน่งขนมปังเริ่มต้น", + "settings_toastPosition_note": "เปลี่ยนเฉพาะตำแหน่งของขนมปังที่สร้างโดยปลั๊กอินของฉัน", + "settings_useChromium_description": "เปิดลิงค์ใน Discord แทนเบราว์เซอร์ของคุณ", + "sort_by": "จัดเรียงตาม", + "status": "สถานะ", + "time": "เวลา", + "timezone": "เขตเวลา", + "to": "ถึง", + "toast_plugin_loaded": "โหลด {{var0}} แล้ว", + "toast_plugin_started": "{{var0}} เริ่มแล้ว", + "toast_plugin_stopped": "{{var0}} หยุดทำงาน", + "toast_plugin_translated": "แปลเป็น {{var0}}", + "toast_plugin_unloaded": "ยกเลิกการโหลด {{var0}} แล้ว", + "toast_plugin_update_failed": "ไม่สามารถดาวน์โหลดการอัปเดตสำหรับ {{var0}}", + "toast_plugin_updated": "{{var0}} {{var1}} ถูกแทนที่ด้วย {{var2}} {{var3}}", + "update_check_complete": "การตรวจสอบการอัปเดตปลั๊กอินเสร็จสมบูรณ์", + "update_check_complete_outdated": "การตรวจสอบการอัปเดตปลั๊กอินเสร็จสมบูรณ์ - {{var0}} ล้าสมัย!", + "update_check_info": "ตรวจสอบปลั๊กอินที่รองรับการตรวจสอบการอัปเดต", + "update_notice_click": "คลิกเพื่ออัพเดท!", + "update_notice_reload": "โหลดซ้ำเพื่ออัปเดตให้เสร็จสมบูรณ์", + "update_notice_update": "จำเป็นต้องอัปเดตปลั๊กอินต่อไปนี้: ", + "updated": "อัปเดตแล้ว" + }, + "tr": { + "add_to": "{{var0}} ekle", + "ascending": "Artan", + "center": "Ortalanmış", + "changelog_added": "Yeni özellikler", + "changelog_fixed": "Sorun giderme", + "changelog_improved": "İyileştirmeler", + "changelog_progress": "Ilerleme", + "check_for_updates": "Güncellemeleri kontrol et", + "clipboard_success": "{{var0}} Panoya kopyalandı", + "confirm": "Emin misiniz?", + "copy": "{{var0}} kopyala", + "delete_fail": "{{var0}} silinemez", + "delete_success": "{{var0}} başarıyla silindi", + "descending": "Azalan", + "developer": "Geliştirici", + "donate_message": "Daha fazla güncelleme için beni destekleyin!", + "download": "İndir", + "download_fail": "{{var0}} indirilemez", + "download_success": "{{var0}} başarıyla indirildi", + "file_navigator_text": "Dosyaya Gözat", + "first": "İlk", + "from": "Nereden", + "gradient": "Gradyan", + "guildbanner": "Afiş", + "guildicon": "Simge", + "installed": "Kurulmuş", + "last": "Son", + "left": "Ayrıldı", + "loading": "Yükleniyor {{var0}}", + "location": "Yer", + "order": "Sıra", + "outdated": "Modası geçmiş", + "please_wait": "Lütfen bekle", + "right": "Sağ", + "save_fail": "{{var0}} kaydedilemez", + "save_success": "{{var0}} başarıyla kaydedildi", + "send": "{{var0}} gönder", + "server": "Sunucu", + "settings_checkForUpdates_description": "Eklenti Güncellemelerini doğrudan GitHub'da kontrol edin", + "settings_checkForUpdates_note": "Bu BetterDiscord'un Eklenti Güvenliği İncelemesini atlayacaktır!", + "settings_shareData_description": "Eklenti Yapılandırmalarını Discord Hesapları arasında senkronize eder", + "settings_showSupportBadges_description": "Patreon'umu destekleyen kullanıcılar için küçük rozetler gösterir", + "settings_showToasts_description": "Eklenti başlangıç ​​ve bitiş tostlarını gösterir", + "settings_showToasts_note": "Devre dışı bırakmadan önce BD'lerin genel ayarını '{{var0}}' devre dışı bırakın", + "settings_toastPosition_description": "Varsayılan Tost Konumu", + "settings_toastPosition_note": "Yalnızca Eklentileri tarafından oluşturulan Toastların Konumunu değiştirir", + "settings_useChromium_description": "Tarayıcınız yerine Discord'da Bağlantıları Açın", + "sort_by": "Göre sırala", + "status": "Durum", + "time": "Zaman", + "timezone": "Saat dilimi", + "to": "İçin", + "toast_plugin_loaded": "{{var0}} yüklendi", + "toast_plugin_started": "{{var0}} başladı", + "toast_plugin_stopped": "{{var0}} durdu", + "toast_plugin_translated": "{{var0}} diline çevrildi", + "toast_plugin_unloaded": "{{var0}} kaldırıldı", + "toast_plugin_update_failed": "{{var0}} için güncelleme indirilemiyor", + "toast_plugin_updated": "{{var0}} {{var1}}, {{var2}} {{var3}} ile değiştirildi", + "update_check_complete": "Eklenti güncelleme kontrolü tamamlandı", + "update_check_complete_outdated": "Eklenti güncelleme kontrolü tamamlandı - {{var0}} güncel değil!", + "update_check_info": "Güncelleme Kontrolünü destekleyen Eklentileri kontrol edin", + "update_notice_click": "Güncellemek için tıklayın!", + "update_notice_reload": "Güncellemeyi tamamlamak için yeniden yükleyin", + "update_notice_update": "Aşağıdaki Eklentileri güncellenmesi gerekiyor: ", + "updated": "Güncellenmiş" + }, + "uk": { + "add_to": "Додати до {{var0}}", + "ascending": "Висхідний", + "center": "По центру", + "changelog_added": "Нові можливості", + "changelog_fixed": "Вирішення проблем", + "changelog_improved": "Покращення", + "changelog_progress": "Прогрес", + "check_for_updates": "Перевірити наявність оновлень", + "clipboard_success": "Скопійовано {{var0}} в буфер обміну", + "confirm": "Ти впевнений?", + "copy": "Копіювати {{var0}}", + "delete_fail": "{{var0}} не можна видалити", + "delete_success": "{{var0}} успішно видалено", + "descending": "За спаданням", + "developer": "Розробник", + "donate_message": "Підтримайте мене, щоб отримати більше оновлень!", + "download": "Завантажити", + "download_fail": "{{var0}} не можна завантажити", + "download_success": "{{var0}} завантажено успішно", + "file_navigator_text": "Переглянути файл", + "first": "Спочатку", + "from": "Від", + "gradient": "Градієнт", + "guildbanner": "Банер", + "guildicon": "Піктограма", + "installed": "Встановлено", + "last": "Останній", + "left": "Ліворуч", + "loading": "Завантаження {{var0}}", + "location": "Розташування", + "order": "Послідовність", + "outdated": "Застарілий", + "please_wait": "Задоволення почекайте", + "right": "Правильно", + "save_fail": "{{var0}} не можна зберегти", + "save_success": "{{var0}} успішно збережено", + "send": "Надіслати {{var0}}", + "server": "Сервер", + "settings_checkForUpdates_description": "Перевірте наявність оновлень плагінів безпосередньо на GitHub", + "settings_checkForUpdates_note": "Це обійде перевірку безпеки плагінів BetterDiscord!", + "settings_shareData_description": "Синхронізує конфігурації плагінів між обліковими записами Discord", + "settings_showSupportBadges_description": "Показує невеликі значки для користувачів, які підтримують мій Patreon", + "settings_showToasts_description": "Показує тости запуску та зупинки плагіна", + "settings_showToasts_note": "Вимкніть загальне налаштування '{{var0}}' BD, перш ніж їх вимикати", + "settings_toastPosition_description": "Позиція тостів за замовчуванням", + "settings_toastPosition_note": "Змінює лише позицію тостів, створених моїми плагіни", + "settings_useChromium_description": "Відкрийте посилання в Discord замість браузера", + "sort_by": "Сортувати за", + "status": "Статус", + "time": "Час", + "timezone": "Часовий пояс", + "to": "До", + "toast_plugin_loaded": "{{var0}} завантажено", + "toast_plugin_started": "{{var0}} розпочато", + "toast_plugin_stopped": "{{var0}} зупинено", + "toast_plugin_translated": "перекладено на {{var0}}", + "toast_plugin_unloaded": "{{var0}} вивантажено", + "toast_plugin_update_failed": "Не вдається завантажити оновлення для {{var0}}", + "toast_plugin_updated": "{{var0}} {{var1}} замінено на {{var2}} {{var3}}", + "update_check_complete": "Завершено перевірку оновлення плагіна", + "update_check_complete_outdated": "Завершено перевірку оновлення плагіна - {{var0}} застаріло!", + "update_check_info": "Перевірте плагіни, які підтримують перевірку оновлення", + "update_notice_click": "Натисніть, щоб оновити!", + "update_notice_reload": "Перезавантажте, щоб завершити оновлення", + "update_notice_update": "Потрібно оновити такі плагіни: ", + "updated": "Оновлено" + }, + "vi": { + "add_to": "Thêm vào {{var0}}", + "ascending": "Tăng dần", + "center": "Căn giữa", + "changelog_added": "Các tính năng mới", + "changelog_fixed": "Xử lý sự cố", + "changelog_improved": "Cải tiến", + "changelog_progress": "Phát triển", + "check_for_updates": "Kiểm tra cập nhật", + "clipboard_success": "Đã sao chép {{var0}} vào Clipboard", + "confirm": "Bạn có chắc không?", + "copy": "Sao chép {{var0}}", + "delete_fail": "{{var0}} không thể bị xóa", + "delete_success": "Đã xóa {{var0}} thành công", + "descending": "Giảm dần", + "developer": "Người phát triển", + "donate_message": "Hỗ trợ tôi để cập nhật thêm!", + "download": "Tải xuống", + "download_fail": "Không thể tải xuống {{var0}}", + "download_success": "Đã tải xuống {{var0}} thành công", + "file_navigator_text": "Chọn thư mục", + "first": "Đầu tiên", + "from": "Từ", + "gradient": "Dốc", + "guildbanner": "Ảnh bìa", + "guildicon": "Biểu tượng", + "installed": "Cài đặt", + "last": "Cuối cùng", + "left": "Trái", + "loading": "Đang tải {{var0}}", + "location": "Vị trí", + "order": "Sự nối tiếp", + "outdated": "Lỗi thời", + "please_wait": "Hân hạnh chờ đợi", + "right": "Đúng", + "save_fail": "{{var0}} không thể lưu được", + "save_success": "{{var0}} đã được lưu thành công", + "send": "Gửi cho {{var0}}", + "server": "Người phục vụ", + "settings_checkForUpdates_description": "Kiểm tra cập nhật plugin trực tiếp trên GitHub", + "settings_checkForUpdates_note": "Việc này sẽ bỏ qua Đánh giá an toàn plugin của BetterDiscord!", + "settings_shareData_description": "Đồng bộ hóa cấu hình plugin giữa các tài khoản Discord", + "settings_showSupportBadges_description": "Hiển thị các huy hiệu nhỏ cho những người dùng ủng hộ Patreon của tôi", + "settings_showToasts_description": "Hiển thị plugin bắt đầu và dừng nâng cốc", + "settings_showToasts_note": "Tắt cài đặt chung '{{var0}}' của BD trước khi tắt chúng", + "settings_toastPosition_description": "Vị trí bánh mì nướng mặc định", + "settings_toastPosition_note": "Chỉ thay đổi Vị trí của Bánh nướng được tạo bởi plugins của tôi", + "settings_useChromium_description": "Mở Liên kết trong Discord thay vì Trình duyệt của bạn", + "sort_by": "Sắp xếp theo", + "status": "Trạng thái", + "time": "Thời gian", + "timezone": "Múi giờ", + "to": "Đến", + "toast_plugin_loaded": "{{var0}} đã được tải", + "toast_plugin_started": "{{var0}} đã bắt đầu", + "toast_plugin_stopped": "{{var0}} đã dừng", + "toast_plugin_translated": "đã dịch sang {{var0}}", + "toast_plugin_unloaded": "{{var0}} đã được tải xuống", + "toast_plugin_update_failed": "Không thể tải xuống bản cập nhật cho {{var0}}", + "toast_plugin_updated": "{{var0}} {{var1}} đã được thay thế bằng {{var2}} {{var3}}", + "update_check_complete": "Đã hoàn tất kiểm tra cập nhật plugin", + "update_check_complete_outdated": "Đã hoàn tất kiểm tra cập nhật plugin - {{var0}} đã lỗi thời!", + "update_check_info": "Kiểm tra các plugins hỗ trợ Kiểm tra cập nhật", + "update_notice_click": "Bấm để cập nhật!", + "update_notice_reload": "Tải lại để hoàn tất cập nhật", + "update_notice_update": "Các plugins sau cần được cập nhật: ", + "updated": "Đã cập nhật" + }, + "zh-CN": { + "add_to": "添加到 {{var0}}", + "ascending": "升序", + "center": "居中", + "changelog_added": "新增功能", + "changelog_fixed": "问题修复", + "changelog_improved": "改进之处", + "changelog_progress": "进展", + "check_for_updates": "检查更新", + "clipboard_success": "已将 {{var0}} 复制到剪贴板", + "confirm": "确定吗?", + "copy": "复制 {{var0}}", + "delete_fail": "无法删除 {{var0}}", + "delete_success": "成功删除 {{var0}}", + "descending": "降序", + "developer": "开发者", + "donate_message": "支持我继续更新!", + "download": "下载", + "download_fail": "无法下载 {{var0}}", + "download_success": "{{var0}} 下载成功", + "file_navigator_text": "浏览文件", + "first": "首个", + "from": "从", + "gradient": "渐变", + "guildbanner": "横幅", + "guildicon": "图标", + "installed": "已安装", + "last": "最后", + "left": "左侧", + "loading": "正在加载 {{var0}}", + "location": "位置", + "order": "旧的", + "outdated": "已过时", + "please_wait": "请稍候", + "right": "右侧", + "save_fail": "无法保存 {{var0}}", + "save_success": "成功保存 {{var0}}", + "send": "发送 {{var0}}", + "server": "服务器", + "settings_checkForUpdates_description": "直接在 GitHub 上检查插件更新", + "settings_checkForUpdates_note": "这将绕过 BetterDiscord 的插件安全审查!", + "settings_shareData_description": "在Discord账号之间同步插件配置", + "settings_showSupportBadges_description": "为在Patreon支持我的用户显示小徽章", + "settings_showToasts_description": "显示插件启动和停止通知", + "settings_showToasts_note": "禁用此功能前请先关闭BD的 \"{{var0}}\" 设置", + "settings_toastPosition_description": "默认通知位置", + "settings_toastPosition_note": "仅更改由我的插件创建的通知位置", + "settings_useChromium_description": "在Discord(而非浏览器)中打开链接", + "sort_by": "排序方式", + "status": "状态", + "time": "时间", + "timezone": "时区", + "to": "到", + "toast_plugin_loaded": "{{var0}} 已加载", + "toast_plugin_started": "{{var0}} 已启动", + "toast_plugin_stopped": "{{var0}} 已停止", + "toast_plugin_translated": "已转换为 {{var0}}", + "toast_plugin_unloaded": "{{var0}} 已卸载", + "toast_plugin_update_failed": "无法下载 {{var0}} 的更新", + "toast_plugin_updated": "{{var0}} {{var1}} 已更新至 {{var2}} {{var3}}", + "update_check_complete": "插件更新检查完成", + "update_check_complete_outdated": "插件更新检查完成 - {{var0}}需要更新!", + "update_check_info": "正在检查支持更新检查的插件", + "update_notice_click": "点击更新!", + "update_notice_reload": "重新加载以完成更新", + "update_notice_update": "以下插件需要更新:", + "updated": "已更新" + }, + "zh-TW": { + "add_to": "添加至 {{var0}}", + "ascending": "升序", + "center": "置中", + "changelog_added": "新的功能", + "changelog_fixed": "故障排除", + "changelog_improved": "改進之處", + "changelog_progress": "進展", + "check_for_updates": "檢查更新", + "clipboard_success": "已將 {{var0}} 複製到剪貼板", + "confirm": "你確定嗎?", + "copy": "複製 {{var0}}", + "delete_fail": "{{var0}} 無法刪除", + "delete_success": "{{var0}} 成功刪除", + "descending": "降序", + "developer": "開發商", + "donate_message": "支持我更多更新!", + "download": "下載", + "download_fail": "{{var0}} 無法下載", + "download_success": "{{var0}} 已成功下載", + "file_navigator_text": "瀏覽文件", + "first": "第一", + "from": "從", + "gradient": "梯度", + "guildbanner": "橫幅", + "guildicon": "圖標", + "installed": "已安裝", + "last": "最後", + "left": "左邊", + "loading": "正在加載 {{var0}}", + "location": "地點", + "order": "舊的", + "outdated": "過時的", + "please_wait": "請稍等", + "right": "右邊", + "save_fail": "{{var0}} 無法保存", + "save_success": "{{var0}} 保存成功", + "send": "發送 {{var0}}", + "server": "伺服器", + "settings_checkForUpdates_description": "直接在 GitHub 上檢查插件更新", + "settings_checkForUpdates_note": "這將繞過 BetterDiscord 的插件安全性審查!", + "settings_shareData_description": "在 Discord 帳戶之間同步插件配置", + "settings_showSupportBadges_description": "為在 Patreon 支持我的用戶顯示小徽章", + "settings_showToasts_description": "顯示插件啟動和停止通知框", + "settings_showToasts_note": "在禁用此功能之前先關閉 BD 的 '{{var0}}' 設置", + "settings_toastPosition_description": "預設通知框位置", + "settings_toastPosition_note": "僅更改由我的插件創建的通知框位置", + "settings_useChromium_description": "在 Discord (而不是瀏覽器)中打開連結", + "sort_by": "排序方式", + "status": "狀態", + "time": "時間", + "timezone": "時區", + "to": "至", + "toast_plugin_loaded": "{{var0}} 已加載", + "toast_plugin_started": "{{var0}} 已啟動", + "toast_plugin_stopped": "{{var0}} 已停止", + "toast_plugin_translated": "轉換為 {{var0}}", + "toast_plugin_unloaded": "{{var0}} 已被卸載", + "toast_plugin_update_failed": "無法下載 {{var0}} 的更新", + "toast_plugin_updated": "{{var0}} {{var1}} 已替換為 {{var2}} {{var3}}", + "update_check_complete": "插件更新檢查完成", + "update_check_complete_outdated": "插件更新檢查完成 - {{var0}} 已過期!", + "update_check_info": "檢查支持更新檢查的插件", + "update_notice_click": "點擊更新!", + "update_notice_reload": "重新加載以完成更新", + "update_notice_update": "以下插件需要更新: ", + "updated": "更新" + }, + "default": { + "add_to": "Add to {{var0}}", + "ascending": "Ascending", + "center": "Centered", + "changelog_added": "New Features", + "changelog_fixed": "Bug Fixes", + "changelog_improved": "Improvements", + "changelog_progress": "Progress", + "check_for_updates": "Check for Updates", + "clipboard_success": "Copied {{var0}} to Clipboard", + "confirm": "Are you sure?", + "copy": "Copy {{var0}}", + "delete_fail": "{{var0}} cannot be deleted", + "delete_success": "{{var0}} deleted successfully", + "descending": "Descending", + "developer": "Developer", + "donate_message": "Support me to receive further Updates!", + "download": "Download", + "download_fail": "{{var0}} cannot be downloaded", + "download_success": "{{var0}} downloaded successfully", + "file_navigator_text": "Browse File", + "first": "First", + "from": "From", + "gradient": "Gradient", + "guildbanner": "Banner", + "guildicon": "Icon", + "installed": "Installed", + "last": "Last", + "left": "Left", + "loading": "Loading {{var0}}", + "location": "Location", + "order": "Order", + "outdated": "Outdated", + "please_wait": "Please wait", + "right": "Right", + "save_fail": "{{var0}} cannot be saved", + "save_success": "{{var0}} saved successfully", + "send": "Send {{var0}}", + "server": "Server", + "settings_checkForUpdates_description": "Check for Plugin Updates directly on GitHub", + "settings_checkForUpdates_note": "This will bypass BetterDiscord's Plugin Safety Review!", + "settings_shareData_description": "Synchronizes the Plugin Configs between Discord Accounts", + "settings_showSupportBadges_description": "Shows small Badges for Users who support my Patreon", + "settings_showToasts_description": "Shows Plugin start and stop Toasts", + "settings_showToasts_note": "Disable BDs general '{{var0}}' setting before disabling this", + "settings_toastPosition_description": "Default Toast Position", + "settings_toastPosition_note": "Only changes Position of Toasts created by my Plugins", + "settings_useChromium_description": "Open Links in Discord instead of your Browser", + "sort_by": "Sort by", + "status": "Status", + "time": "Time", + "timezone": "Timezone", + "to": "To", + "toast_plugin_loaded": "{{var0}} has been loaded", + "toast_plugin_started": "{{var0}} has been started", + "toast_plugin_stopped": "{{var0}} has been stopped", + "toast_plugin_translated": "translated to {{var0}}", + "toast_plugin_unloaded": "{{var0}} has been unloaded", + "toast_plugin_update_failed": "Update for {{var0}} cannot be downloaded", + "toast_plugin_updated": "{{var0}} {{var1}} has been replaced by {{var2}} {{var3}}", + "update_check_complete": "Plugin Update Check completed", + "update_check_complete_outdated": "Plugin Update Check completed - {{var0}} outdated!", + "update_check_info": "Check Plugins that support the Update Check", + "update_notice_click": "Click to update!", + "update_notice_reload": "Reload to complete the Update", + "update_notice_update": "The following Plugins need to be updated: ", + "updated": "Updated" + } + }, + "BDFDB_Patrons": { + "1113460780113854525": {"active": true, "tier": "t2", "text": "", "color": "", "id": "pinguwu (hugo)"}, + "864982372751245313": {"active": true, "tier": "t2", "text": "", "color": "", "id": "windowsuss (fynn)"}, + "148234730523852800": {"active": true, "tier": "t2", "text": "", "color": "", "id": "inbroso (MaEp)"}, + "105509397211406336": {"active": true, "tier": "t2", "text": "", "color": "", "id": "samtino (SgJeff)"}, + "323494393828999168": {"active": true, "tier": "t2", "text": "", "color": "", "id": "polak (SzKu)"}, + "507464069100601363": {"active": true, "tier": "t2", "text": "", "color": "", "id": "cracky (MiPo)"}, + "620397524494057513": {"active": true, "tier": "t2", "text": "", "color": "", "id": "FUSL"}, + "798499176220327966": {"active": true, "tier": "t2", "text": "", "color": "", "id": "void (JaUt)"} + }, + "BDFDB_Patron_Tiers": { + "t1": { + "text": "" + }, + "t2": { + "text": "BDFDB Patron" + }, + "t3": { + "text": "BDFDB Patron+" + }, + "t4": { + "text": "BDFDB Special Supporter" + } + }, + "LanguageStringHashes": {"DISCORD":["VSgOVg", "/7xJCA"]} +} diff --git a/dotfiles/.config/BetterDiscord/plugins/0BDFDB.plugin.js b/dotfiles/.config/BetterDiscord/plugins/0BDFDB.plugin.js new file mode 100755 index 0000000..1b8a056 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/0BDFDB.plugin.js @@ -0,0 +1,8878 @@ +/** + * @name BDFDB + * @author DevilBro + * @authorId 278543574059057154 + * @version 4.4.6 + * @description Required Library for DevilBro's Plugins + * @invite Jx3TjNS + * @donate https://www.paypal.me/MircoWittrien + * @patreon https://www.patreon.com/MircoWittrien + * @website https://mwittrien.github.io/ + * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Library/ + * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js + */ + +module.exports = (_ => { + if (window.BDFDB_Global && window.BDFDB_Global.PluginUtils && typeof window.BDFDB_Global.PluginUtils.cleanUp == "function") window.BDFDB_Global.PluginUtils.cleanUp(window.BDFDB_Global); + + const fs = require("fs"), path = require("path"); + + var BDFDB, Internal; + var LibraryRequires = {}; + var DiscordObjects = {}, DiscordConstants = {}; + var LibraryStores = {}, LibraryModules = {}; + var LibraryComponents = {}, NativeSubComponents = {}, CustomComponents = {}; + var PluginStores = {}; + + BDFDB = { + started: true, + changeLog: {} + }; + + return class BDFDB_Frame { + constructor (meta) {for (let key in meta) { + if (!this[key]) this[key] = meta[key]; + if (!BDFDB[key]) BDFDB[key] = meta[key]; + }} + getName () {return this.name;} + getAuthor () {return this.author;} + getVersion () {return this.version;} + getDescription () {return this.description;} + + load () { + const BdApi = window.BdApi; + + const Cache = {data: {}, modules: {}}; + + var changeLogs = {}; + + Internal = Object.assign({}, BDFDB, { + patchPriority: 0, + forceSyncData: true, + settings: {}, + defaults: { + general: { + shareData: { + value: true, + onChange: _ => Cache.data = {} + }, + showToasts: { + value: true, + isDisabled: data => data.nativeValue, + hasNote: data => data.disabled && data.value + }, + checkForUpdates: { + value: false, + hasNote: true + }, + showSupportBadges: { + value: false + }, + useChromium: { + value: false, + isHidden: data => !Internal.LibraryRequires.electron || !Internal.LibraryRequires.electron.remote, + getValue: data => !data.disabled + } + }, + choices: { + toastPosition: { + value: "right", + items: "ToastPositions" + } + } + }, + }); + for (let key in Internal.defaults) Internal.settings[key] = {}; + + PluginStores = { + loaded: {}, + delayed: { + loads: [], + starts: [] + }, + updateData: { + plugins: {}, + timeouts: [], + downloaded: [], + interval: null + }, + modulePatches: {} + }; + + const Plugin = function (changeLog) { + return class Plugin { + constructor (meta) {for (let key in meta) if (!this[key]) this[key] = meta[key];} + getName () {return this.name;} + getAuthor () {return this.author;} + getVersion () {return this.version;} + getDescription () {return this.description;} + load () { + this.changeLog = changeLog; + this.loaded = true; + this.defaults = {}; + this.labels = {}; + if (window.BDFDB_Global.loading) { + if (!PluginStores.delayed.loads.includes(this)) PluginStores.delayed.loads.push(this); + } + else BDFDB.TimeUtils.suppress(_ => { + PluginStores.loaded[this.name] = this; + BDFDB.PluginUtils.load(this); + if (typeof this.onLoad == "function") this.onLoad(); + }, "Failed to load Plugin!", this)(); + } + start () { + if (!this.loaded) this.load(); + if (window.BDFDB_Global.loading) { + if (!PluginStores.delayed.starts.includes(this)) PluginStores.delayed.starts.push(this); + } + else { + if (this.started) return; + this.started = true; + BDFDB.TimeUtils.suppress(_ => { + BDFDB.PluginUtils.init(this); + if (typeof this.onStart == "function") this.onStart(); + }, "Failed to start Plugin!", this)(); + delete this.stopping; + } + } + stop () { + if (window.BDFDB_Global.loading) { + if (PluginStores.delayed.starts.includes(this)) PluginStores.delayed.starts.splice(PluginStores.delayed.starts.indexOf(this), 1); + } + else { + if (this.stopping) return; + this.stopping = true; + BDFDB.TimeUtils.timeout(_ => {delete this.stopping;}); + + BDFDB.TimeUtils.suppress(_ => { + if (typeof this.onStop == "function") this.onStop(); + BDFDB.PluginUtils.clear(this); + }, "Failed to stop Plugin!", this)(); + + delete this.started; + } + } + }; + }; + + const requestFunction = function (...args) { + let url = typeof args[0] == "string" && args[0]; + if (!url) return; + let callback = typeof args[1] == "function" && args[1] || typeof args[2] == "function" && args[2]; + if (typeof callback != "function") return; + if (url.indexOf("data:") == 0) callback(null, { + aborted: false, + complete: true, + end: undefined, + headers: {"content-type": url.slice(5).split(";")[0]}, + method: null, + rawHeaders: [], + statusCode: 200, + statusMessage: "OK", + url: "" + }, url); + else { + let config = args[1] && typeof args[1] == "object" ? args[1] : {}; + let timeout = 600000; + if (!isNaN(parseInt(config.timeout)) && config.timeout > 0) timeout = config.timeout; + if (config.form && typeof config.form == "object") { + let query = Object.entries(config.form).map(n => n[0] + "=" + n[1]).join("&"); + if (query) url += `?${query}`; + } + if (config.method) config.method = config.method.toUpperCase(); + let killed = false, timeoutObj = BDFDB.TimeUtils.timeout(_ => { + killed = true; + BDFDB.TimeUtils.clear(timeoutObj); + callback(new Error(`Request Timeout after ${timeout}ms`), { + aborted: false, + complete: true, + end: undefined, + headers: {}, + method: null, + rawHeaders: [], + statusCode: 408, + statusMessage: "OK", + url: "" + }, null); + }, timeout); + let response = null, isFallback = false; + return (config.bdVersion && BdApi && BdApi.Net && BdApi.Net.fetch ? BdApi.Net.fetch : fetch)(url, config).catch(error => { + BDFDB.TimeUtils.clear(timeoutObj); + if (!config.bdVersion) return requestFunction(url, Object.assign({}, config, {bdVersion: true}), callback); + else callback(new Error(error), { + aborted: false, + complete: true, + end: undefined, + headers: {}, + method: null, + rawHeaders: [], + statusCode: 408, + statusMessage: "OK", + url: "" + }, null); + }).then(r => { + response = r; + if (!response) return; + response.statusCode = response.status; + if (response.headers) response.headers["content-type"] = response.headers.get("content-type"); + BDFDB.TimeUtils.clear(timeoutObj); + return config.toBase64 ? response.blob() : config.toBuffer ? response.arrayBuffer() : response.text(); + }).then(result => { + if (!killed && response) { + if (!config.toBase64 || response.status != 200) callback(response.status != 200 ? new Error(response.statusText || "Fetch Failed") : null, response, result); + else { + let reader = new FileReader(); + reader.onload = _ => callback(null, response, reader.result); + reader.readAsDataURL(result); + } + } + }); + } + }; + + BDFDB.LogUtils = {}; + Internal.console = function (type, config = {}) { + if (!console[type]) return; + let name, version; + if (typeof config.name == "string" && config.name) { + name = config.name; + version = typeof config.version == "string" ? config.version : ""; + } + else { + name = BDFDB.name; + version = BDFDB.version; + } + console[type](...[[name && `%c[${name}]`, version && `%c(v${version})`].filter(n => n).join(" "), name && "color: #3a71c1; font-weight: 700;", version && "color: #666; font-weight: 600; font-size: 11px;", [config.strings].flat(10).filter(n => n).join(" ").trim()].filter(n => n)); + }; + BDFDB.LogUtils.log = function (strings, config = {}) { + Internal.console("log", Object.assign({}, config, {name: typeof config == "string" ? config : config.name, strings})); + }; + BDFDB.LogUtils.warn = function (strings, config = {}) { + Internal.console("warn", Object.assign({}, config, {name: typeof config == "string" ? config : config.name, strings})); + }; + BDFDB.LogUtils.error = function (strings, config = {}) { + Internal.console("error", Object.assign({}, config, {name: typeof config == "string" ? config : config.name, strings: ["Fatal Error:", strings]})); + }; + + BDFDB.TimeUtils = {}; + BDFDB.TimeUtils.interval = function (callback, delay, ...args) { + if (typeof callback != "function" || typeof delay != "number" || delay < 1) return; + else { + let count = 0, interval = setInterval(_ => BDFDB.TimeUtils.suppress(callback, "Interval")(...[interval, count++, args].flat()), delay); + return interval; + } + }; + BDFDB.TimeUtils.timeout = function (callback, delay, ...args) { + delay = parseFloat(delay); + if (typeof callback != "function") return; + if (isNaN(delay) || typeof delay != "number" || delay < 1) { + let immediate = setImmediate(_ => BDFDB.TimeUtils.suppress(callback, "Immediate")(...[immediate, args].flat())); + return immediate; + } + else { + let start, paused = true, timeout = { + pause: _ => { + if (paused) return; + paused = true; + BDFDB.TimeUtils.clear(timeout.timer); + delay -= performance.now() - start; + }, + resume: _ => { + if (!paused) return; + paused = false; + start = performance.now(); + timeout.timer = setTimeout(_ => BDFDB.TimeUtils.suppress(callback, "Timeout")(...[timeout, args].flat()), delay) + } + }; + timeout.resume(); + return timeout; + } + }; + BDFDB.TimeUtils.clear = function (...timeObjects) { + for (let t of timeObjects.flat(10).filter(n => n)) { + t = t.timer != undefined ? t.timer : t; + if (typeof t == "number") { + clearInterval(t); + clearTimeout(t); + } + else if (typeof t == "object") clearImmediate(t); + } + }; + BDFDB.TimeUtils.suppress = function (callback, strings, config) {return function (...args) { + try {return callback(...args);} + catch (err) {(!config || !config.ignoreErrors) && BDFDB.LogUtils.error([strings, err], config);} + }}; + + BDFDB.LogUtils.log("Loading Library"); + + BDFDB.sameProto = function (a, b) { + if (a != null && typeof a == "object") return a.constructor && a.constructor.prototype && typeof a.constructor.prototype.isPrototypeOf == "function" && a.constructor.prototype.isPrototypeOf(b); + else return typeof a == typeof b; + }; + BDFDB.equals = function (mainA, mainB, sorted) { + let i = -1; + if (sorted === undefined || typeof sorted !== "boolean") sorted = false; + return equal(mainA, mainB); + function equal(a, b) { + i++; + let result = true; + if (i > 1000) result = null; + else { + if (typeof a !== typeof b) result = false; + else if (typeof a == "function") result = a.toString() == b.toString(); + else if (typeof a === "undefined") result = true; + else if (typeof a === "symbol") result = true; + else if (typeof a === "boolean") result = a == b; + else if (typeof a === "string") result = a == b; + else if (typeof a === "number") { + if (isNaN(a) || isNaN(b)) result = isNaN(a) == isNaN(b); + else result = a == b; + } + else if (!a && !b) result = true; + else if (!a || !b) result = false; + else if (typeof a === "object") { + let keysA = Object.getOwnPropertyNames(a); + let keysB = Object.getOwnPropertyNames(b); + if (keysA.length !== keysB.length) result = false; + else for (let j = 0; result === true && j < keysA.length; j++) { + if (sorted) result = equal(a[keysA[j]], b[keysB[j]]); + else result = equal(a[keysA[j]], b[keysA[j]]); + } + } + } + i--; + return result; + } + }; + + BDFDB.ObjectUtils = {}; + BDFDB.ObjectUtils.is = function (obj) { + return obj && !Array.isArray(obj) && !Set.prototype.isPrototypeOf(obj) && (typeof obj == "function" || typeof obj == "object"); + }; + BDFDB.ObjectUtils.get = function (nodeOrObj, valuePath) { + if (!nodeOrObj || !valuePath) return null; + let obj = Node.prototype.isPrototypeOf(nodeOrObj) ? BDFDB.ReactUtils.getInstance(nodeOrObj) : nodeOrObj; + if (!BDFDB.ObjectUtils.is(obj)) return null; + let found = obj; + for (const value of valuePath.split(".").filter(n => n)) { + if (!found) return null; + found = found[value]; + } + return found; + }; + BDFDB.ObjectUtils.invert = function (obj) { + let newObj = {}; + if (BDFDB.ObjectUtils.is(obj)) for (let entry of Object.entries(obj)) newObj[entry[1]] = entry[0]; + return newObj; + }; + BDFDB.ObjectUtils.extract = function (obj, ...keys) { + let newObj = {}; + if (BDFDB.ObjectUtils.is(obj)) for (let key of keys.flat(10).filter(n => n)) if (obj[key] != null) newObj[key] = obj[key]; + return newObj; + }; + BDFDB.ObjectUtils.exclude = function (obj, ...keys) { + let newObj = Object.assign({}, obj); + BDFDB.ObjectUtils.delete(newObj, ...keys) + return newObj; + }; + BDFDB.ObjectUtils.delete = function (obj, ...keys) { + if (BDFDB.ObjectUtils.is(obj)) for (let key of keys.flat(10).filter(n => n)) delete obj[key]; + }; + BDFDB.ObjectUtils.sort = function (obj, sort, except) { + if (!BDFDB.ObjectUtils.is(obj)) return {}; + let newObj = {}; + if (sort === undefined || !sort) for (let key of Object.keys(obj).sort()) newObj[key] = obj[key]; + else { + let values = []; + for (let key in obj) values.push(obj[key]); + values = BDFDB.ArrayUtils.keySort(values, sort, except); + for (let value of values) for (let key in obj) if (BDFDB.equals(value, obj[key])) { + newObj[key] = value; + break; + } + } + return newObj; + }; + BDFDB.ObjectUtils.filter = function (obj, filter, byKey = false) { + if (!BDFDB.ObjectUtils.is(obj)) return {}; + if (typeof filter != "function") return obj; + return Object.keys(obj).filter(key => filter(byKey ? key : obj[key])).reduce((newObj, key) => (newObj[key] = obj[key], newObj), {}); + }; + BDFDB.ObjectUtils.map = function (obj, mapFunc) { + if (!BDFDB.ObjectUtils.is(obj)) return {}; + if (typeof mapFunc != "string" && typeof mapFunc != "function") return obj; + let newObj = {}; + for (let key in obj) if (BDFDB.ObjectUtils.is(obj[key])) newObj[key] = typeof mapFunc == "string" ? obj[key][mapFunc] : mapFunc(obj[key], key); + return newObj; + }; + BDFDB.ObjectUtils.toArray = function (obj) { + if (!BDFDB.ObjectUtils.is(obj)) return []; + return Object.entries(obj).map(n => n[1]); + }; + BDFDB.ObjectUtils.deepAssign = function (obj, ...objs) { + if (!objs.length) return obj; + let nextObj = objs.shift(); + if (BDFDB.ObjectUtils.is(obj) && BDFDB.ObjectUtils.is(nextObj)) { + for (let key in nextObj) { + if (BDFDB.ObjectUtils.is(nextObj[key])) { + if (!obj[key]) Object.assign(obj, {[key]:{}}); + BDFDB.ObjectUtils.deepAssign(obj[key], nextObj[key]); + } + else Object.assign(obj, {[key]:nextObj[key]}); + } + } + return BDFDB.ObjectUtils.deepAssign(obj, ...objs); + }; + BDFDB.ObjectUtils.isEmpty = function (obj) { + return !BDFDB.ObjectUtils.is(obj) || Object.getOwnPropertyNames(obj).length == 0; + }; + BDFDB.ObjectUtils.copy = function (obj) { + if (!BDFDB.ObjectUtils.is(obj)) return obj; + let copy = {}; + for (let key of Object.getOwnPropertyNames(obj)) copy[key] = obj[key]; + for (let key of Reflect.ownKeys(obj.constructor.prototype)) if (!copy[key] && obj[key] !== undefined) copy[key] = obj[key]; + return copy; + }; + + BDFDB.ArrayUtils = {}; + BDFDB.ArrayUtils.is = function (array) { + return array && Array.isArray(array); + }; + BDFDB.ArrayUtils.sum = function (array) { + return Array.isArray(array) ? array.reduce((total, num) => total + Math.round(num), 0) : 0; + }; + BDFDB.ArrayUtils.keySort = function (array, key, except) { + if (!BDFDB.ArrayUtils.is(array)) return []; + if (key == null) return array; + if (except === undefined) except = null; + return array.sort((x, y) => { + let xValue = x[key], yValue = y[key]; + if (xValue !== except) return xValue < yValue ? -1 : xValue > yValue ? 1 : 0; + }); + }; + BDFDB.ArrayUtils.numSort = function (array) { + return array.sort((x, y) => (x < y ? -1 : x > y ? 1 : 0)); + }; + BDFDB.ArrayUtils.includes = function (array, ...values) { + if (!BDFDB.ArrayUtils.is(array)) return null; + if (!array.length) return false; + let all = values.pop(); + if (typeof all != "boolean") { + values.push(all); + all = true; + } + if (!values.length) return false; + let contained = undefined; + for (let v of values) { + if (contained === undefined) contained = all; + if (all && !array.includes(v)) contained = false; + if (!all && array.includes(v)) contained = true; + } + return contained; + }; + BDFDB.ArrayUtils.remove = function (array, value, all = false) { + if (!BDFDB.ArrayUtils.is(array)) return []; + if (!array.includes(value)) return array; + if (!all) array.splice(array.indexOf(value), 1); + else while (array.indexOf(value) > -1) array.splice(array.indexOf(value), 1); + return array; + }; + BDFDB.ArrayUtils.removeCopies = function (array) { + if (!BDFDB.ArrayUtils.is(array)) return []; + return [...new Set(array)]; + }; + BDFDB.ArrayUtils.getAllIndexes = function (array, value) { + if (!BDFDB.ArrayUtils.is(array) && typeof array != "string") return []; + var indexes = [], index = -1; + while ((index = array.indexOf(value, index + 1)) !== -1) indexes.push(index); + return indexes; + }; + + BDFDB.BDUtils = {}; + BDFDB.BDUtils.getPluginsFolder = function () { + if (BdApi && BdApi.Plugins && BdApi.Plugins.folder && typeof BdApi.Plugins.folder == "string") return BdApi.Plugins.folder; + else if (Internal.LibraryRequires.process.env.BETTERDISCORD_DATA_PATH) return Internal.LibraryRequires.path.resolve(Internal.LibraryRequires.process.env.BETTERDISCORD_DATA_PATH, "plugins/"); + else if (Internal.LibraryRequires.process.env.injDir) return Internal.LibraryRequires.path.resolve(Internal.LibraryRequires.process.env.injDir, "plugins/"); + else switch (Internal.LibraryRequires.process.platform) { + case "win32": + return Internal.LibraryRequires.path.resolve(Internal.LibraryRequires.process.env.appdata, "BetterDiscord/plugins/"); + case "darwin": + return Internal.LibraryRequires.path.resolve(Internal.LibraryRequires.process.env.HOME, "Library/Preferences/BetterDiscord/plugins/"); + default: + if (Internal.LibraryRequires.process.env.XDG_CONFIG_HOME) return Internal.LibraryRequires.path.resolve(Internal.LibraryRequires.process.env.XDG_CONFIG_HOME, "BetterDiscord/plugins/"); + else if (Internal.LibraryRequires.process.env.HOME) return Internal.LibraryRequires.path.resolve(Internal.LibraryRequires.process.env.HOME, ".config/BetterDiscord/plugins/"); + else return ""; + } + }; + BDFDB.BDUtils.getThemesFolder = function () { + if (BdApi && BdApi.Themes && BdApi.Themes.folder && typeof BdApi.Themes.folder == "string") return BdApi.Themes.folder; + else if (Internal.LibraryRequires.process.env.BETTERDISCORD_DATA_PATH) return Internal.LibraryRequires.path.resolve(Internal.LibraryRequires.process.env.BETTERDISCORD_DATA_PATH, "themes/"); + else if (Internal.LibraryRequires.process.env.injDir) return Internal.LibraryRequires.path.resolve(Internal.LibraryRequires.process.env.injDir, "plugins/"); + else switch (Internal.LibraryRequires.process.platform) { + case "win32": + return Internal.LibraryRequires.path.resolve(Internal.LibraryRequires.process.env.appdata, "BetterDiscord/themes/"); + case "darwin": + return Internal.LibraryRequires.path.resolve(Internal.LibraryRequires.process.env.HOME, "Library/Preferences/BetterDiscord/themes/"); + default: + if (Internal.LibraryRequires.process.env.XDG_CONFIG_HOME) return Internal.LibraryRequires.path.resolve(Internal.LibraryRequires.process.env.XDG_CONFIG_HOME, "BetterDiscord/themes/"); + else if (Internal.LibraryRequires.process.env.HOME) return Internal.LibraryRequires.path.resolve(Internal.LibraryRequires.process.env.HOME, ".config/BetterDiscord/themes/"); + else return ""; + } + }; + BDFDB.BDUtils.isPluginEnabled = function (pluginName) { + if (BdApi && BdApi.Plugins && typeof BdApi.Plugins.isEnabled == "function") return BdApi.Plugins.isEnabled(pluginName); + }; + BDFDB.BDUtils.reloadPlugin = function (pluginName) { + if (BdApi && BdApi.Plugins && typeof BdApi.Plugins.reload == "function") BdApi.Plugins.reload(pluginName); + }; + BDFDB.BDUtils.enablePlugin = function (pluginName) { + if (BdApi && BdApi.Plugins && typeof BdApi.Plugins.enable == "function") BdApi.Plugins.enable(pluginName); + }; + BDFDB.BDUtils.disablePlugin = function (pluginName) { + if (BdApi && BdApi.Plugins && typeof BdApi.Plugins.disable == "function") BdApi.Plugins.disable(pluginName); + }; + BDFDB.BDUtils.getPlugin = function (pluginName, hasToBeEnabled = false, overHead = false) { + if (BdApi && !hasToBeEnabled || BDFDB.BDUtils.isPluginEnabled(pluginName) && BdApi.Plugins && typeof BdApi.Plugins.get == "function") { + let plugin = BdApi.Plugins.get(pluginName); + if (!plugin) return null; + if (overHead) return plugin.filename && plugin.exports && plugin.instance ? plugin : {filename: Internal.LibraryRequires.fs.existsSync(Internal.LibraryRequires.path.join(BDFDB.BDUtils.getPluginsFolder(), `${pluginName}.plugin.js`)) ? `${pluginName}.plugin.js` : null, id: pluginName, name: pluginName, plugin: plugin}; + else return plugin.filename && plugin.exports && plugin.instance ? plugin.instance : plugin; + } + return null; + }; + BDFDB.BDUtils.isThemeEnabled = function (themeName) { + if (BdApi && BdApi.Themes && typeof BdApi.Themes.isEnabled == "function") return BdApi.Themes.isEnabled(themeName); + }; + BDFDB.BDUtils.enableTheme = function (themeName) { + if (BdApi && BdApi.Themes && typeof BdApi.Themes.enable == "function") BdApi.Themes.enable(themeName); + }; + BDFDB.BDUtils.disableTheme = function (themeName) { + if (BdApi && BdApi.Themes && typeof BdApi.Themes.disable == "function") BdApi.Themes.disable(themeName); + }; + BDFDB.BDUtils.getTheme = function (themeName, hasToBeEnabled = false) { + if (BdApi && !hasToBeEnabled || BDFDB.BDUtils.isThemeEnabled(themeName) && BdApi.Themes && typeof BdApi.Themes.get == "function") return BdApi.Themes.get(themeName); + return null; + }; + BDFDB.BDUtils.settingsIds = { + automaticLoading: "settings.addons.autoReload", + coloredText: "settings.appearance.coloredText", + normalizedClasses: "settings.general.classNormalizer", + showToasts: "settings.general.showToasts" + }; + BDFDB.BDUtils.toggleSettings = function (key, state) { + if (BdApi && typeof key == "string") { + let path = key.split("."); + let currentState = BDFDB.BDUtils.getSettings(key); + if (state === true) { + if (currentState === false && typeof BdApi.enableSetting == "function") BdApi.enableSetting(...path); + } + else if (state === false) { + if (currentState === true && typeof BdApi.disableSetting == "function") BdApi.disableSetting(...path); + } + else if (currentState === true || currentState === false) BDFDB.BDUtils.toggleSettings(key, !currentState); + } + }; + BDFDB.BDUtils.getSettings = function (key) { + if (!BdApi) return {}; + if (typeof key == "string") return typeof BdApi.isSettingEnabled == "function" && BdApi.isSettingEnabled(...key.split(".")); + else return BDFDB.ArrayUtils.is(BdApi.settings) ? BdApi.settings.map(n => n.settings.map(m => m.settings.map(l => ({id: [n.id, m.id, l.id].join("."), value: l.value})))).flat(10).reduce((newObj, setting) => (newObj[setting.id] = setting.value, newObj), {}) : {}; + }; + BDFDB.BDUtils.getSettingsProperty = function (property, key) { + if (!BdApi || !BDFDB.ArrayUtils.is(BdApi.settings)) return key ? "" : {}; + else { + let settingsMap = BdApi.settings.map(n => n.settings.map(m => m.settings.map(l => ({id: [n.id, m.id, l.id].join("."), value: l[property]})))).flat(10).reduce((newObj, setting) => (newObj[setting.id] = setting.value, newObj), {}); + return key ? (settingsMap[key] != null ? settingsMap[key] : "") : ""; + } + }; + + const cssFileName = "0BDFDB.raw.css", dataFileName = "0BDFDB.data.json"; + const cssFilePath = path.join(BDFDB.BDUtils.getPluginsFolder(), cssFileName), dataFilePath = path.join(BDFDB.BDUtils.getPluginsFolder(), dataFileName); + BDFDB.PluginUtils = {}; + BDFDB.PluginUtils.buildPlugin = function (changeLog) { + return [Plugin(changeLog), BDFDB]; + }; + BDFDB.PluginUtils.load = function (plugin) { + if (!PluginStores.updateData.timeouts.includes(plugin.name)) { + PluginStores.updateData.timeouts.push(plugin.name); + const url = Internal.getPluginURL(plugin); + + PluginStores.updateData.plugins[url] = {name: plugin.name, raw: url, version: plugin.version}; + + BDFDB.PluginUtils.checkUpdate(plugin.name, url); + + if (plugin.changeLog && !BDFDB.ObjectUtils.isEmpty(plugin.changeLog) && typeof plugin.getSettingsPanel != "function") plugin.getSettingsPanel = _ => BDFDB.PluginUtils.createSettingsPanel(plugin, { + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.MessagesPopoutComponents.EmptyState, { + msg: "No Settings available for this Plugin", + image: BDFDB.DiscordUtils.getTheme() == BDFDB.disCN.themelight ? "/assets/9b0d90147f7fab54f00dd193fe7f85cd.svg" : "/assets/308e587f3a68412f137f7317206e92c2.svg" + }) + }); + + if (!PluginStores.updateData.interval) PluginStores.updateData.interval = BDFDB.TimeUtils.interval(_ => { + BDFDB.PluginUtils.checkAllUpdates(); + }, 1000*60*60*4); + + BDFDB.TimeUtils.timeout(_ => BDFDB.ArrayUtils.remove(PluginStores.updateData.timeouts, plugin.name, true), 30000); + } + }; + BDFDB.PluginUtils.init = function (plugin) { + BDFDB.PluginUtils.load(plugin); + + plugin.settings = BDFDB.DataUtils.get(plugin); + + BDFDB.LogUtils.log(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_started", ""), plugin); + if (Internal.settings.general.showToasts && !BDFDB.BDUtils.getSettings(BDFDB.BDUtils.settingsIds.showToasts)) BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_started", `${plugin.name} v${plugin.version}`), { + disableInteractions: true, + barColor: BDFDB.DiscordConstants.ColorsCSS.STATUS_POSITIVE + }); + + if (plugin.css) BDFDB.DOMUtils.appendLocalStyle(plugin.name, plugin.css); + + BDFDB.PatchUtils.unpatch(plugin); + Internal.addModulePatches(plugin); + Internal.addContextPatches(plugin); + + BDFDB.PluginUtils.translate(plugin); + + BDFDB.PluginUtils.checkChangeLog(plugin); + }; + BDFDB.PluginUtils.clear = function (plugin) { + BDFDB.LogUtils.log(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_stopped", ""), plugin); + if (Internal.settings.general.showToasts && !BDFDB.BDUtils.getSettings(BDFDB.BDUtils.settingsIds.showToasts)) BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_stopped", `${plugin.name} v${plugin.version}`), { + disableInteractions: true, + barColor: BDFDB.DiscordConstants.ColorsCSS.STATUS_DANGER + }); + + const url = Internal.getPluginURL(plugin); + + BDFDB.PluginUtils.cleanUp(plugin); + + for (const modal of document.querySelectorAll(`.${plugin.name}-modal, .${plugin.name.toLowerCase()}-modal, .${plugin.name}-settingsmodal, .${plugin.name.toLowerCase()}-settingsmodal`)) { + const closeButton = modal.querySelector(BDFDB.dotCN.modalclose); + if (closeButton) closeButton.click(); + } + + delete Cache.data[plugin.name] + delete PluginStores.updateData.plugins[url]; + }; + BDFDB.PluginUtils.translate = function (plugin) { + if (typeof plugin.setLabelsByLanguage == "function" || typeof plugin.changeLanguageStrings == "function") { + const translate = _ => { + if (typeof plugin.setLabelsByLanguage == "function") plugin.labels = plugin.setLabelsByLanguage(); + if (typeof plugin.changeLanguageStrings == "function") plugin.changeLanguageStrings(); + }; + if (BDFDB.DiscordUtils.getLanguage()) translate(); + else BDFDB.TimeUtils.interval(interval => { + if (BDFDB.DiscordUtils.getLanguage()) { + BDFDB.TimeUtils.clear(interval); + translate(); + } + }, 100); + } + }; + BDFDB.PluginUtils.cleanUp = function (plugin) { + BDFDB.TimeUtils.suppress(_ => { + if (!BDFDB.ObjectUtils.is(plugin)) return; + if (plugin == window.BDFDB_Global) { + plugin = BDFDB; + let updateNotice = BDFDB.dotCN && document.querySelector(BDFDB.dotCN.noticeupdate); + if (updateNotice) updateNotice.close(); + BDFDB.TimeUtils.clear(PluginStores && PluginStores.updateData && PluginStores.updateData.interval); + delete window.BDFDB_Global.loaded; + if (PluginStores) BDFDB.TimeUtils.interval((interval, count) => { + if (count > 60 || window.BDFDB_Global.loaded) BDFDB.TimeUtils.clear(interval); + if (window.BDFDB_Global.loaded) for (let pluginName in BDFDB.ObjectUtils.sort(PluginStores.loaded)) BDFDB.TimeUtils.timeout(_ => { + if (PluginStores.loaded[pluginName].started) BDFDB.BDUtils.reloadPlugin(pluginName); + }); + }, 1000); + } + if (BDFDB.DOMUtils && BDFDB.DOMUtils.removeLocalStyle) BDFDB.DOMUtils.removeLocalStyle(plugin.name); + if (BDFDB.ListenerUtils && BDFDB.ListenerUtils.remove) BDFDB.ListenerUtils.remove(plugin); + if (BDFDB.ListenerUtils && BDFDB.ListenerUtils.removeGlobal) BDFDB.ListenerUtils.removeGlobal(plugin); + if (BDFDB.StoreChangeUtils && BDFDB.StoreChangeUtils.remove) BDFDB.StoreChangeUtils.remove(plugin); + if (BDFDB.PatchUtils && BDFDB.PatchUtils.unpatch) BDFDB.PatchUtils.unpatch(plugin); + + for (const patchType in PluginStores.modulePatches) { + for (const type in PluginStores.modulePatches[patchType]) { + for (const priority in PluginStores.modulePatches[patchType][type]) BDFDB.ArrayUtils.remove(PluginStores.modulePatches[patchType][type][priority], plugin, true); + if (!PluginStores.modulePatches[patchType][type].flat(10).length) delete PluginStores.modulePatches[patchType][type]; + } + if (BDFDB.ObjectUtils.isEmpty(PluginStores.modulePatches[patchType])) delete PluginStores.modulePatches[patchType]; + } + }, "Failed to clean up Plugin!", plugin)(); + }; + BDFDB.PluginUtils.checkUpdate = function (pluginName, url) { + if (pluginName && url && Internal.settings.general.checkForUpdates && PluginStores.updateData.plugins[url]) return new Promise(callback => { + requestFunction(url, {timeout: 60000}, (error, response, body) => { + if (error || !PluginStores.updateData.plugins[url]) return callback(null); + let newName = (body.match(/"name"\s*:\s*"([^"]+)"/) || [])[1] || pluginName; + let newVersion = (body.match(/@version ([0-9]+\.[0-9]+\.[0-9]+)|['"]([0-9]+\.[0-9]+\.[0-9]+)['"]/i) || []).filter(n => n)[1]; + if (!newVersion) return callback(null); + if (BDFDB.NumberUtils.compareVersions(newVersion, PluginStores.updateData.plugins[url].version)) { + if (PluginStores.updateData.plugins[url]) PluginStores.updateData.plugins[url].outdated = true; + BDFDB.PluginUtils.showUpdateNotice(pluginName, url); + return callback(1); + } + else { + BDFDB.PluginUtils.removeUpdateNotice(pluginName); + return callback(0); + } + }); + }); + return new Promise(callback => callback(null)); + }; + BDFDB.PluginUtils.checkAllUpdates = function () { + return new Promise(callback => { + let finished = 0, amount = 0; + for (let url in PluginStores.updateData.plugins) { + let plugin = PluginStores.updateData.plugins[url]; + if (plugin) BDFDB.PluginUtils.checkUpdate(plugin.name, plugin.raw).then(state => { + finished++; + if (state == 1) amount++; + if (finished >= Object.keys(PluginStores.updateData.plugins).length) callback(amount); + }); + } + }); + }; + BDFDB.PluginUtils.hasUpdateCheck = function (url) { + if (!url || typeof url != "string") return false; + let updateStore = Object.assign({}, window.PluginUpdates && window.PluginUpdates.plugins, PluginStores.updateData.plugins); + if (updateStore[url]) return true; + else { + let temp = url.replace("//raw.githubusercontent.com", "//").split("/"); + let gitName = temp.splice(3, 1); + temp.splice(4, 1); + temp.splice(2, 1, gitName + ".github.io"); + let pagesUrl = temp.join("/"); + return !!updateStore[pagesUrl]; + } + }; + BDFDB.PluginUtils.showUpdateNotice = function (pluginName, url) { + if (!pluginName || !url) return; + let updateNotice = document.querySelector(BDFDB.dotCN.noticeupdate); + if (!updateNotice) { + let vanishObserver = new MutationObserver(changes => { + if (!document.contains(updateNotice)) { + if (updateNotice && updateNotice.querySelector(BDFDB.dotCN.noticeupdateentry)) { + let layers = document.querySelector(BDFDB.dotCN.layers) || document.querySelector(BDFDB.dotCN.appmount); + if (layers) layers.parentElement.insertBefore(updateNotice, layers); + } + else vanishObserver.disconnect(); + } + else if (document.contains(updateNotice) && !updateNotice.querySelector(BDFDB.dotCNC.noticeupdateentry + BDFDB.dotCN.noticebutton)) vanishObserver.disconnect(); + }); + vanishObserver.observe(document.body, {childList: true, subtree: true}); + updateNotice = BDFDB.NotificationUtils.notice(`${BDFDB.LanguageUtils.LibraryStrings.update_notice_update}    
`, { + type: "info", + className: BDFDB.disCN.noticeupdate, + html: true, + customIcon: ``, + buttons: [{ + className: BDFDB.disCN.noticeupdatebuttonall, + contents: BDFDB.LanguageUtils.LanguageStrings.ALL, + onClick: _ => {for (let notice of updateNotice.querySelectorAll(BDFDB.dotCN.noticeupdateentry)) notice.click();} + }], + onClose: _ => vanishObserver.disconnect() + }); + updateNotice.style.setProperty("position", "relative", "important"); + updateNotice.style.setProperty("visibility", "visible", "important"); + updateNotice.style.setProperty("opacity", "1", "important"); + updateNotice.style.setProperty("z-index", "100000", "important"); + let reloadButton = updateNotice.querySelector(BDFDB.dotCN.noticeupdatebuttonreload); + if (reloadButton) BDFDB.DOMUtils.hide(reloadButton); + } + if (updateNotice) { + let updateNoticeList = updateNotice.querySelector(BDFDB.dotCN.noticeupdateentries); + if (updateNoticeList && !updateNoticeList.querySelector(`#${pluginName}-notice`)) { + if (updateNoticeList.childElementCount) updateNoticeList.appendChild(BDFDB.DOMUtils.create(`
,
`)); + let updateEntry = BDFDB.DOMUtils.create(`
${pluginName}
`); + updateEntry.addEventListener("click", _ => { + if (!updateEntry.wasClicked) { + updateEntry.wasClicked = true; + BDFDB.PluginUtils.downloadUpdate(pluginName, url); + } + }); + updateNoticeList.appendChild(updateEntry); + if (!updateNoticeList.hasTooltip) { + updateNoticeList.hasTooltip = true; + updateNotice.tooltip = BDFDB.TooltipUtils.create(updateNoticeList, BDFDB.LanguageUtils.LibraryStrings.update_notice_click, { + type: "bottom", + zIndex: 100001, + delay: 500, + onHide: _ => {updateNoticeList.hasTooltip = false;} + }); + } + } + } + }; + BDFDB.PluginUtils.removeUpdateNotice = function (pluginName, updateNotice = document.querySelector(BDFDB.dotCN.noticeupdate)) { + if (!pluginName || !updateNotice) return; + let updateNoticeList = updateNotice.querySelector(BDFDB.dotCN.noticeupdateentries); + if (updateNoticeList) { + let noticeEntry = updateNoticeList.querySelector(`#${pluginName}-notice`); + if (noticeEntry) { + let nextSibling = noticeEntry.nextSibling; + let prevSibling = noticeEntry.prevSibling; + if (nextSibling && BDFDB.DOMUtils.containsClass(nextSibling, BDFDB.disCN.noticeupdateseparator)) nextSibling.remove(); + else if (prevSibling && BDFDB.DOMUtils.containsClass(prevSibling, BDFDB.disCN.noticeupdateseparator)) prevSibling.remove(); + noticeEntry.remove(); + } + if (!updateNoticeList.childElementCount) { + let reloadButton = updateNotice.querySelector(BDFDB.dotCN.noticeupdatebuttonreload); + if (reloadButton) { + updateNotice.querySelector(BDFDB.dotCN.noticetext).innerText = BDFDB.LanguageUtils.LibraryStrings.update_notice_reload; + BDFDB.DOMUtils.show(reloadButton); + } + else updateNotice.querySelector(BDFDB.dotCN.noticedismiss).click(); + } + } + }; + BDFDB.PluginUtils.downloadUpdate = function (pluginName, url) { + if (pluginName && url) requestFunction(url, {timeout: 60000}, (error, response, body) => { + if (error) { + BDFDB.PluginUtils.removeUpdateNotice(pluginName); + BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_update_failed", pluginName), { + type: "danger", + disableInteractions: true + }); + } + else { + let wasEnabled = BDFDB.BDUtils.isPluginEnabled(pluginName); + let newName = (body.match(/@name ([^"^\n^\t^\t]+)|['"]([^"^\n^\t^\t]+)['"]/i) || []).filter(n => n)[1] || pluginName; + let newVersion = (body.match(/@version ([0-9]+\.[0-9]+\.[0-9]+)|['"]([0-9]+\.[0-9]+\.[0-9]+)['"]/i) || []).filter(n => n)[1]; + let oldVersion = PluginStores.updateData.plugins[url].version; + let fileName = pluginName == "BDFDB" ? "0BDFDB" : pluginName; + let newFileName = newName == "BDFDB" ? "0BDFDB" : newName; + Internal.LibraryRequires.fs.writeFile(Internal.LibraryRequires.path.join(BDFDB.BDUtils.getPluginsFolder(), newFileName + ".plugin.js"), body, _ => { + if (PluginStores.updateData.plugins[url]) PluginStores.updateData.plugins[url].version = newVersion; + if (fileName != newFileName) { + Internal.LibraryRequires.fs.unlink(Internal.LibraryRequires.path.join(BDFDB.BDUtils.getPluginsFolder(), fileName + ".plugin.js"), _ => {}); + let configPath = Internal.LibraryRequires.path.join(BDFDB.BDUtils.getPluginsFolder(), fileName + ".config.json"); + Internal.LibraryRequires.fs.exists(configPath, exists => { + if (exists) Internal.LibraryRequires.fs.rename(configPath, Internal.LibraryRequires.path.join(BDFDB.BDUtils.getPluginsFolder(), newFileName + ".config.json"), _ => {}); + }); + BDFDB.TimeUtils.timeout(_ => {if (wasEnabled && !BDFDB.BDUtils.isPluginEnabled(newName)) BDFDB.BDUtils.enablePlugin(newName);}, 3000); + } + BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_updated", pluginName, "v" + oldVersion, newName, "v" + newVersion), { + disableInteractions: true + }); + let updateNotice = document.querySelector(BDFDB.dotCN.noticeupdate); + if (updateNotice) { + if (updateNotice.querySelector(BDFDB.dotCN.noticebutton) && !PluginStores.updateData.downloaded.includes(pluginName)) { + PluginStores.updateData.downloaded.push(pluginName); + } + BDFDB.PluginUtils.removeUpdateNotice(pluginName, updateNotice); + } + }); + } + }); + }; + BDFDB.PluginUtils.checkChangeLog = function (plugin) { + if (!BDFDB.ObjectUtils.is(plugin) || !BDFDB.ObjectUtils.is(plugin.changeLog) || plugin.changeLog.info) return; + if (!changeLogs[plugin.name] || BDFDB.NumberUtils.compareVersions(plugin.version, changeLogs[plugin.name])) { + changeLogs[plugin.name] = plugin.version; + BDFDB.DataUtils.save(changeLogs, BDFDB, "changeLogs"); + BDFDB.PluginUtils.openChangeLog(plugin); + } + }; + BDFDB.PluginUtils.openChangeLog = function (plugin) { + if (!BDFDB.ObjectUtils.is(plugin) || !BDFDB.ObjectUtils.is(plugin.changeLog)) return; + let changeLogEntries = [], headers = { + added: "New Features", + fixed: "Bug Fixes", + improved: "Improvements", + progress: "Progress" + }; + for (let type in plugin.changeLog) { + type = type.toLowerCase(); + if (InternalData.DiscordClasses["changelog" + type]) changeLogEntries.push([ + BDFDB.ReactUtils.createElement("h1", { + className: BDFDB.disCNS["changelog" + type] + BDFDB.disCN.margintop20, + style: {"margin-top": !changeLogEntries.length ? 0 : null}, + children: BDFDB.LanguageUtils && BDFDB.LanguageUtils.LibraryStrings && BDFDB.LanguageUtils.LibraryStrings["changelog_" + type] || headers[type] + }), + BDFDB.ReactUtils.createElement("ul", { + children: Object.keys(plugin.changeLog[type]).map(key => BDFDB.ReactUtils.createElement("li", { + children: [ + BDFDB.ReactUtils.createElement("strong", {children: key}), + plugin.changeLog[type][key] ? `: ${plugin.changeLog[type][key]}.` : "" + ] + })) + }) + ]); + } + if (changeLogEntries.length) BDFDB.ModalUtils.open(plugin, { + header: `${plugin.name} ${BDFDB.LanguageUtils.LanguageStrings.CHANGE_LOG}`, + subHeader: `Version ${plugin.version}`, + className: BDFDB.disCN.modalchangelogmodal, + contentClassName: BDFDB.disCNS.changelogcontainer + BDFDB.disCN.modalminicontent, + footerDirection: Internal.LibraryComponents.Flex.Direction.HORIZONTAL, + children: changeLogEntries.flat(10).filter(n => n), + footerChildren: (plugin == BDFDB || plugin == this || PluginStores.loaded[plugin.name] && PluginStores.loaded[plugin.name] == plugin && plugin.author == "DevilBro") && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.changelogfooter, + children: [{ + href: "https://www.paypal.me/MircoWittrien", + name: "PayPal", + icon: "PAYPAL" + }, { + href: "https://www.patreon.com/MircoWittrien", + name: "Patreon", + icon: "PATREON" + }, { + name: BDFDB.LanguageUtils.LibraryStringsFormat("send", "Solana"), + icon: "PHANTOM", + onClick: _ => { + BDFDB.LibraryModules.WindowUtils.copy(InternalData.mySolana); + BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("clipboard_success", "Phantom Wallet Key"), { + type: "success" + }); + } + }, { + name: BDFDB.LanguageUtils.LibraryStringsFormat("send", "Ethereum"), + icon: "METAMASK", + onClick: _ => { + BDFDB.LibraryModules.WindowUtils.copy(InternalData.myEthereum); + BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("clipboard_success", "MetaMask Wallet Key"), { + type: "success" + }); + } + }].map(data => BDFDB.ReactUtils.createElement(data.href ? Internal.LibraryComponents.Anchor : Internal.LibraryComponents.Clickable, { + className: BDFDB.disCN.changelogsociallink, + href: data.href || "", + onClick: !data.onClick ? (_ => {}) : data.onClick, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TooltipContainer, { + text: data.name, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: Internal.LibraryComponents.SvgIcon.Names[data.icon], + width: 16, + height: 16 + }) + }) + })).concat(BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextElement, { + size: Internal.LibraryComponents.TextElement.Sizes.SIZE_12, + children: BDFDB.LanguageUtils.LibraryStrings.donate_message + })) + }) + }); + }; + BDFDB.PluginUtils.addLoadingIcon = function (icon) { + if (!Node.prototype.isPrototypeOf(icon)) return; + let app = document.querySelector(BDFDB.dotCN.app); + if (!app) return; + BDFDB.DOMUtils.addClass(icon, BDFDB.disCN.loadingicon); + let loadingIconWrapper = document.querySelector(BDFDB.dotCN.app + ">" + BDFDB.dotCN.loadingiconwrapper); + if (!loadingIconWrapper) { + loadingIconWrapper = BDFDB.DOMUtils.create(`
`); + app.appendChild(loadingIconWrapper); + let killObserver = new MutationObserver(changes => { + if (!loadingIconWrapper.firstElementChild) { + killObserver.disconnect(); + BDFDB.DOMUtils.remove(loadingIconWrapper); + } + }); + killObserver.observe(loadingIconWrapper, {childList: true}); + } + loadingIconWrapper.appendChild(icon); + }; + BDFDB.PluginUtils.createSettingsPanel = function (addon, props) { + if (!window.BDFDB_Global.loaded) return BdApi.React.createElement("div", { + style: {"color": BDFDB.DiscordConstants.ColorsCSS.HEADER_SECONDARY, "white-space": "pre-wrap"}, + children: [ + "Could not initiate BDFDB Library Plugin! Can not create Settings Panel!\n\nTry deleting the ", + BdApi.React.createElement("strong", {children: dataFileName}), + " File in your ", + BdApi.React.createElement("strong", {children: BDFDB.BDUtils.getPluginsFolder()}), + "\nDirectory and reload Discord afterwards!" + ] + }); + addon = addon == BDFDB && Internal || addon; + if (!BDFDB.ObjectUtils.is(addon)) return; + let settingsProps = props; + if (settingsProps && !BDFDB.ObjectUtils.is(settingsProps) && (BDFDB.ReactUtils.isValidElement(settingsProps) || BDFDB.ArrayUtils.is(settingsProps))) settingsProps = { + children: settingsProps + }; + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SettingsPanel, Object.assign({ + addon: addon, + collapseStates: settingsProps && settingsProps.collapseStates + }, settingsProps)); + }; + BDFDB.PluginUtils.refreshSettingsPanel = function (plugin, settingsPanel, ...args) { + if (BDFDB.ObjectUtils.is(plugin)) { + if (settingsPanel && settingsPanel.props && BDFDB.ObjectUtils.is(settingsPanel.props._instance)) { + settingsPanel.props._instance.props = Object.assign({}, settingsPanel.props._instance.props, ...args); + BDFDB.ReactUtils.forceUpdate(settingsPanel.props._instance); + } + else if (typeof plugin.getSettingsPanel == "function" && Node.prototype.isPrototypeOf(settingsPanel) && settingsPanel.parentElement) { + settingsPanel.parentElement.appendChild(plugin.getSettingsPanel(...args)); + settingsPanel.remove(); + } + } + }; + + window.BDFDB_Global = Object.assign({ + started: true, + loading: true, + PluginUtils: { + buildPlugin: BDFDB.PluginUtils.buildPlugin, + cleanUp: BDFDB.PluginUtils.cleanUp + } + }, window.BDFDB_Global); + + Internal.writeConfig = function (plugin, path, config) { + let allData = {}; + try {allData = JSON.parse(fs.readFileSync(path));} + catch (err) {allData = {};} + try {fs.writeFileSync(path, JSON.stringify(Object.assign({}, allData, {[Internal.shouldSyncConfig(plugin) ? "all" : BDFDB.UserUtils.me.id]: config}), null, " "));} + catch (err) {} + }; + Internal.readConfig = function (plugin, path) { + let sync = Internal.shouldSyncConfig(plugin); + try { + let config = JSON.parse(fs.readFileSync(path)); + if (config && Object.keys(config).some(n => !(n == "all" || parseInt(n)))) { + config = {[Internal.shouldSyncConfig(plugin) ? "all" : BDFDB.UserUtils.me.id]: config}; + try {fs.writeFileSync(path, JSON.stringify(config, null, " "));} + catch (err) {} + } + return config && config[sync ? "all" : BDFDB.UserUtils.me.id] || {}; + } + catch (err) {return {};} + }; + Internal.shouldSyncConfig = function (plugin) { + return plugin.neverSyncData !== undefined ? !plugin.neverSyncData : (plugin.forceSyncData || Internal.settings.general.shareData); + }; + + BDFDB.DataUtils = {}; + BDFDB.DataUtils.save = function (data, plugin, key, id) { + plugin = plugin == BDFDB && Internal || plugin; + let pluginName = typeof plugin === "string" ? plugin : plugin.name; + let fileName = pluginName == "BDFDB" ? "0BDFDB" : pluginName; + let configPath = path.join(BDFDB.BDUtils.getPluginsFolder(), fileName + ".config.json"); + + let config = Cache.data[pluginName] !== undefined ? Cache.data[pluginName] : (Internal.readConfig(plugin, configPath) || {}); + + if (key === undefined) config = BDFDB.ObjectUtils.is(data) ? BDFDB.ObjectUtils.sort(data) : data; + else { + if (id === undefined) config[key] = BDFDB.ObjectUtils.is(data) ? BDFDB.ObjectUtils.sort(data) : data; + else { + if (!BDFDB.ObjectUtils.is(config[key])) config[key] = {}; + config[key][id] = BDFDB.ObjectUtils.is(data) ? BDFDB.ObjectUtils.sort(data) : data; + } + } + + let configIsObject = BDFDB.ObjectUtils.is(config); + if (key !== undefined && configIsObject && BDFDB.ObjectUtils.is(config[key]) && BDFDB.ObjectUtils.isEmpty(config[key])) delete config[key]; + if (BDFDB.ObjectUtils.isEmpty(config)) { + delete Cache.data[pluginName]; + if (fs.existsSync(configPath)) fs.unlinkSync(configPath); + } + else { + if (configIsObject) config = BDFDB.ObjectUtils.sort(config); + Cache.data[pluginName] = configIsObject ? BDFDB.ObjectUtils.deepAssign({}, config) : config; + Internal.writeConfig(plugin, configPath, config); + } + }; + + BDFDB.DataUtils.load = function (plugin, key, id) { + plugin = plugin == BDFDB && Internal || plugin; + let pluginName = typeof plugin === "string" ? plugin : plugin.name; + let fileName = pluginName == "BDFDB" ? "0BDFDB" : pluginName; + let configPath = path.join(BDFDB.BDUtils.getPluginsFolder(), fileName + ".config.json"); + + let config = Cache.data[pluginName] !== undefined ? Cache.data[pluginName] : (Internal.readConfig(plugin, configPath) || {}); + let configIsObject = BDFDB.ObjectUtils.is(config); + Cache.data[pluginName] = configIsObject ? BDFDB.ObjectUtils.deepAssign({}, config) : config; + + if (key === undefined) return config; + else { + let keyData = configIsObject ? (BDFDB.ObjectUtils.is(config[key]) || config[key] === undefined ? BDFDB.ObjectUtils.deepAssign({}, config[key]) : config[key]) : null; + if (id === undefined) return keyData; + else return !BDFDB.ObjectUtils.is(keyData) || keyData[id] === undefined ? null : keyData[id]; + } + }; + BDFDB.DataUtils.remove = function (plugin, key, id) { + plugin = plugin == BDFDB && Internal || plugin; + let pluginName = typeof plugin === "string" ? plugin : plugin.name; + let fileName = pluginName == "BDFDB" ? "0BDFDB" : pluginName; + let configPath = path.join(BDFDB.BDUtils.getPluginsFolder(), fileName + ".config.json"); + + let config = Cache.data[pluginName] !== undefined ? Cache.data[pluginName] : (Internal.readConfig(plugin, configPath) || {}); + let configIsObject = BDFDB.ObjectUtils.is(config); + + if (key === undefined || !configIsObject) config = {}; + else { + if (id === undefined) delete config[key]; + else if (BDFDB.ObjectUtils.is(config[key])) delete config[key][id]; + } + + if (BDFDB.ObjectUtils.is(config[key]) && BDFDB.ObjectUtils.isEmpty(config[key])) delete config[key]; + if (BDFDB.ObjectUtils.isEmpty(config)) { + delete Cache.data[pluginName]; + if (fs.existsSync(configPath)) fs.unlinkSync(configPath); + } + else { + if (configIsObject) config = BDFDB.ObjectUtils.sort(config); + Cache.data[pluginName] = configIsObject ? BDFDB.ObjectUtils.deepAssign({}, config) : config; + Internal.writeConfig(plugin, configPath, config); + } + }; + BDFDB.DataUtils.get = function (plugin, key, id) { + plugin = plugin == BDFDB && Internal || plugin; + plugin = typeof plugin == "string" ? BDFDB.BDUtils.getPlugin(plugin) : plugin; + const defaults = plugin && plugin.defaults; + if (!BDFDB.ObjectUtils.is(defaults) || key && !BDFDB.ObjectUtils.is(defaults[key])) return id === undefined ? {} : null; + let oldC = BDFDB.DataUtils.load(plugin), newC = {}, update = false; + const checkLayer = (i, j) => { + let isObj = BDFDB.ObjectUtils.is(defaults[i][j].value); + if (!newC[i]) newC[i] = {}; + if (oldC[i] == null || oldC[i][j] == null || isObj && (!BDFDB.ObjectUtils.is(oldC[i][j]) || Object.keys(defaults[i][j].value).some(n => defaults[i][j].value[n] != null && !BDFDB.sameProto(defaults[i][j].value[n], oldC[i][j][n])))) { + newC[i][j] = isObj ? BDFDB.ObjectUtils.deepAssign({}, defaults[i][j].value) : defaults[i][j].value; + update = true; + } + else newC[i][j] = oldC[i][j]; + }; + if (key) {for (let j in defaults[key]) checkLayer(key, j);} + else {for (let i in defaults) if (BDFDB.ObjectUtils.is(defaults[i])) for (let j in defaults[i]) checkLayer(i, j);} + if (update) BDFDB.DataUtils.save(Object.assign({}, oldC, newC), plugin); + + if (key === undefined) return newC; + else if (id === undefined) return newC[key] === undefined ? {} : newC[key]; + else return newC[key] === undefined || newC[key][id] === undefined ? null : newC[key][id]; + }; + let InternalData, libHashes = {}, oldLibHashes = BDFDB.DataUtils.load(BDFDB, "hashes"), libraryCSS; + + const getBackup = (fileName, path) => { + return {backup: fs.existsSync(path) && (fs.readFileSync(path) || "").toString(), hashIsSame: libHashes[fileName] && oldLibHashes[fileName] && libHashes[fileName] == oldLibHashes[fileName]}; + }; + const requestLibraryHashes = tryAgain => { + requestFunction("https://api.github.com/repos/mwittrien/BetterDiscordAddons/contents/Library/_res/", {timeout: 60000}, (e, r, b) => { + if ((e || !b || r.statusCode != 200) && tryAgain) return BDFDB.TimeUtils.timeout(_ => requestLibraryHashes(), 10000); + else { + try { + b = JSON.parse(b); + libHashes[cssFileName] = (b.find(n => n && n.name == cssFileName) || {}).sha; + libHashes[dataFileName] = (b.find(n => n && n.name == dataFileName) || {}).sha; + BDFDB.DataUtils.save(libHashes, BDFDB, "hashes"); + } + catch (err) {} + requestLibraryData(true); + } + }); + }; + const requestLibraryData = tryAgain => { + const parseCSS = css => { + libraryCSS = css; + + const backupObj = getBackup(dataFileName, dataFilePath); + const UserStore = BdApi.Webpack && BdApi.Webpack.getModule && BdApi.Webpack.Filters && BdApi.Webpack.Filters.byKeys && BdApi.Webpack.getModule(BdApi.Webpack.Filters.byKeys("getCurrentUser")); + if (backupObj.backup && backupObj.hashIsSame || UserStore && UserStore.getCurrentUser().id == "278543574059057154") parseData(backupObj.backup); + else requestFunction(`https://mwittrien.github.io/BetterDiscordAddons/Library/_res/${dataFileName}`, {timeout: 60000}, (e, r, b) => { + if ((e || !b || r.statusCode != 200) && tryAgain) return BDFDB.TimeUtils.timeout(_ => requestLibraryData(), 10000); + if (!e && b && r.statusCode == 200) { + if (backupObj.backup && backupObj.backup.replace(/\s/g, "") == b.replace(/\s/g, "")) { + libHashes[dataFileName] = oldLibHashes[dataFileName]; + BDFDB.DataUtils.save(libHashes, BDFDB, "hashes"); + } + parseData(b, true); + } + else parseData(fs.existsSync(dataFilePath) && (fs.readFileSync(dataFilePath) || "").toString()); + }); + }; + const parseData = (dataString, fetched) => { + try {InternalData = JSON.parse(dataString);} + catch (err) { + if (fetched) { + try { + dataString = fs.existsSync(dataFilePath) && (fs.readFileSync(dataFilePath) || "").toString(); + InternalData = JSON.parse(dataString); + } + catch (err2) {BDFDB.LogUtils.error(["Failed to initiate Library!", "Failed Fetch!", dataString ? "Corrupt Backup." : "No Backup.", , err2]);} + } + else BDFDB.LogUtils.error(["Failed to initiate Library!", dataString ? "Corrupt Backup." : "No Backup.", err]); + } + if (fetched && dataString) fs.writeFile(dataFilePath, dataString, _ => {}); + + Internal.getWebModuleReq = function () { + if (!Internal.getWebModuleReq.req) { + const id = "BDFDB-WebModules_" + Math.floor(Math.random() * 10000000000000000); + let req; + webpackChunkdiscord_app.push([[id], {}, r => {if ("b" in r) req = r;}]); + delete req.m[id]; + delete req.c[id]; + Internal.getWebModuleReq.req = req; + } + return Internal.getWebModuleReq.req; + }; + + if (InternalData) loadLibrary(); + else BdApi.UI.alert("Error", "Could not initiate BDFDB Library Plugin. Check your Internet Connection and make sure GitHub isn't blocked by your Network or try disabling your VPN/Proxy."); + }; + + const backupObj = getBackup(cssFileName, cssFilePath); + if (backupObj.backup && backupObj.hashIsSame) parseCSS(backupObj.backup); + else requestFunction(`https://mwittrien.github.io/BetterDiscordAddons/Library/_res/${cssFileName}`, {timeout: 60000}, (e, r, b) => { + if ((e || !b || r.statusCode != 200) && tryAgain) return BDFDB.TimeUtils.timeout(_ => requestLibraryData(), 10000); + if (!e && b && r.statusCode == 200) { + if (backupObj.backup && backupObj.backup.replace(/\s/g, "") == b.replace(/\s/g, "")) { + libHashes[cssFileName] = oldLibHashes[cssFileName]; + BDFDB.DataUtils.save(libHashes, BDFDB, "hashes"); + } + fs.writeFile(cssFilePath, b, _ => {}); + parseCSS(b); + } + else parseCSS(fs.existsSync(cssFilePath) && (fs.readFileSync(cssFilePath) || "").toString()); + }); + }; + const loadLibrary = _ => { + Internal.getPluginURL = function (plugin) { + plugin = plugin == BDFDB && Internal || plugin; + if (BDFDB.ObjectUtils.is(plugin)) { + if (InternalData.PluginUrlMap && InternalData.PluginUrlMap[plugin.name]) return InternalData.PluginUrlMap[plugin.name]; + else if (plugin.updateUrl) return plugin.updateUrl; + else { + let name = InternalData.PluginNameMap && InternalData.PluginNameMap[plugin.name] || plugin.name; + return `https://mwittrien.github.io/BetterDiscordAddons/Plugins/${name}/${name}.plugin.js`; + } + } + else return ""; + }; + + Internal.findModule = function (type, cacheString, filter, config = {}) { + if (!BDFDB.ObjectUtils.is(Cache.modules[type])) Cache.modules[type] = {module: {}, export: {}}; + let defaultExport = typeof config.defaultExport != "boolean" ? true : config.defaultExport; + if (!config.all && defaultExport && Cache.modules[type].export[cacheString]) return Cache.modules[type].export[cacheString]; + else if (!config.all && !defaultExport && Cache.modules[type].module[cacheString]) return Cache.modules[type].module[cacheString]; + else { + let m = BDFDB.ModuleUtils.find(filter, config); + if (m) { + if (!config.all) { + if (defaultExport) Cache.modules[type].export[cacheString] = m; + else Cache.modules[type].module[cacheString] = m; + } + return m; + } + else if (!config.noWarnings) BDFDB.LogUtils.warn(`${cacheString} [${type}] not found in WebModules`); + } + }; + Internal.checkModuleStrings = function (module, strings, config = {}) { + const check = (s1, s2) => { + s1 = config.ignoreCase ? s1.toString().toLowerCase() : s1.toString(); + return config.hasNot ? s1.indexOf(s2) == -1 : s1.indexOf(s2) > -1; + }; + return [strings].flat(10).filter(n => typeof n == "string").map(config.ignoreCase ? (n => n.toLowerCase()) : (n => n)).every(string => module && ((typeof module == "function" || typeof module == "string") && (check(module, string) || typeof module.__originalFunction == "function" && check(module.__originalFunction, string)) || typeof module.type == "function" && check(module.type, string) || (typeof module == "function" || typeof module == "object") && module.prototype && Object.keys(module.prototype).filter(n => n.indexOf("render") == 0).some(n => check(module.prototype[n], string)))); + }; + Internal.checkModuleProps = function (module, properties, config = {}) { + return [properties].flat(10).filter(n => typeof n == "string").every(prop => { + const value = module[prop]; + return config.hasNot ? value === undefined : (value !== undefined && !(typeof value == "string" && !value)); + }); + }; + Internal.checkModuleProtos = function (module, protoProps, config = {}) { + return module.prototype && [protoProps].flat(10).filter(n => typeof n == "string").every(prop => { + const value = module.prototype[prop]; + return config.hasNot ? value === undefined : (value !== undefined && !(typeof value == "string" && !value)); + }); + }; + Internal.getModuleString = function (module) { + const id = (BDFDB.ModuleUtils.find(m => m == module && m, {defaultExport: false}) || {}).id; + if (!id) return ""; + const req = Internal.getWebModuleReq(); + return (req.m[id] || "").toString(); + }; + + BDFDB.ModuleUtils = {}; + BDFDB.ModuleUtils.lazyLoadModuleImports = function (moduleString) { + return new Promise(callback => { + if (typeof moduleString !== "string") moduleString = Internal.getModuleString(moduleString); + if (!moduleString || typeof moduleString !== "string") { + BDFDB.LogUtils.error("Trying to lazy load Imports but Module is not a String"); + return callback(null); + } + let run = true, imports = [], menuIndexes = []; + while (run) { + const [matchString, promiseMatch, menuRequest] = moduleString.match(/await .+?(\(.+?\))\.then\(.{1}\.bind\((.+?)\)\)/) ?? []; + if (!promiseMatch) run = false; + else { + imports = imports.concat(promiseMatch.match(/\d+/g)?.map(e => Number(e))); + menuIndexes.push(menuRequest.match(/\d+/)?.[0]); + moduleString = moduleString.replace(matchString, ""); + } + } + if (!imports.length || !menuIndexes.length) { + BDFDB.LogUtils.error("Trying to lazy load Imports but could not find Indexes"); + return callback(null); + } + const req = Internal.getWebModuleReq(); + Promise.all(BDFDB.ArrayUtils.removeCopies(imports).map(i => req.e(i))).then(_ => Promise.all(BDFDB.ArrayUtils.removeCopies(menuIndexes).map(i => req(i)))).then(callback); + }); + }; + + BDFDB.ModuleUtils.find = function (filter, config = {}) { + let defaultExport = typeof config.defaultExport != "boolean" ? true : config.defaultExport; + let onlySearchUnloaded = typeof config.onlySearchUnloaded != "boolean" ? false : config.onlySearchUnloaded; + let all = typeof config.all != "boolean" ? false : config.all; + const req = Internal.getWebModuleReq(); + const found = []; + const isSearchable = (m, checkObject) => { + return m && (checkObject && typeof m == "object" || typeof m == "function") && !m.constructor.toLocaleString().startsWith("function DOMTokenList()") && typeof m.toLocaleString == "function" && m.toLocaleString().indexOf("IntlMessagesProxy") == -1; + }; + if (!onlySearchUnloaded) for (let i in req.c) if (req.c.hasOwnProperty(i) && req.c[i].exports != window) { + let m = req.c[i].exports, r = null; + if (isSearchable(m, true)) { + if (!!(r = filter(m))) { + if (all) found.push(defaultExport ? r : req.c[i]); + else return defaultExport ? r : req.c[i]; + } + else if (Object.keys(m).length < 400) for (let key of Object.keys(m)) try { + if (m[key] && isSearchable(m[key], true) && !!(r = filter(m[key]))) { + if (all) found.push(defaultExport ? r : req.c[i]); + else return defaultExport ? r : req.c[i]; + } + } catch (err) {} + } + if (config.moduleName && isSearchable(m, true) && isSearchable(m[config.moduleName], true)) { + if (!!(r = filter(m[config.moduleName]))) { + if (all) found.push(defaultExport ? r : req.c[i]); + else return defaultExport ? r : req.c[i]; + } + else if (m[config.moduleName].type && isSearchable(m[config.moduleName].type, true) && !!(r = filter(m[config.moduleName].type))) { + if (all) found.push(defaultExport ? r : req.c[i]); + else return defaultExport ? r : req.c[i]; + } + } + if (m && m.__esModule && isSearchable(m.default, true)) { + if (!!(r = filter(m.default))) { + if (all) found.push(defaultExport ? r : req.c[i]); + else return defaultExport ? r : req.c[i]; + } + else if (isSearchable(m.default.type, true) && !!(r = filter(m.default.type))) { + if (all) found.push(defaultExport ? r : req.c[i]); + else return defaultExport ? r : req.c[i]; + } + } + } + for (let i in req.m) if (req.m.hasOwnProperty(i)) { + let m = req.m[i]; + if (m && isSearchable(m)) { + if (req.c[i] && isSearchable(req.c[i].exports, true) && (!config.exportsFilter || config.exportsFilter(req.c[i].exports)) && !onlySearchUnloaded && filter(m)) { + if (all) found.push(defaultExport ? req.c[i].exports : req.c[i]); + else return defaultExport ? req.c[i].exports : req.c[i]; + } + if (!req.c[i] && onlySearchUnloaded && filter(m)) { + const resolved = {}, resolved2 = {}; + m(resolved, resolved2, req); + const trueResolved = resolved2 && BDFDB.ObjectUtils.isEmpty(resolved2) ? resolved : resolved2; + if (all) found.push(defaultExport ? trueResolved.exports : trueResolved); + else return defaultExport ? trueResolved.exports : trueResolved; + } + } + } + if (all) return found; + }; + BDFDB.ModuleUtils.findByProperties = function (...properties) { + properties = properties.flat(10); + let config = properties.pop(); + if (typeof config == "string") { + properties.push(config); + config = {}; + } + return Internal.findModule("props", JSON.stringify(properties), m => Internal.checkModuleProps(m, properties) && m, config); + }; + BDFDB.ModuleUtils.findByName = function (name, config = {}) { + return Internal.findModule("name", JSON.stringify(name), m => m.displayName === name && m || m.render && m.render.displayName === name && m || m.constructor && m.constructor.displayName === name && m || m[name] && m[name].displayName === name && m[name] || typeof m.getName == "function" && m.getName() == name && m, config); + }; + BDFDB.ModuleUtils.findByString = function (...strings) { + strings = strings.flat(10); + let config = strings.pop(); + if (typeof config == "string") { + strings.push(config); + config = {}; + } + return Internal.findModule("string", JSON.stringify(strings), m => Internal.checkModuleStrings(m, strings) && m, config); + }; + BDFDB.ModuleUtils.findByPrototypes = function (...protoProps) { + protoProps = protoProps.flat(10); + let config = protoProps.pop(); + if (typeof config == "string") { + protoProps.push(config); + config = {}; + } + return Internal.findModule("proto", JSON.stringify(protoProps), m => Internal.checkModuleProtos(m, protoProps) && m, config); + }; + BDFDB.ModuleUtils.findStringObject = function (props, config = {}) { + let nonProps = [config.nonProps].flat(10).filter(n => n); + let firstReturn = BDFDB.ModuleUtils.find(m => { + let amount = Object.keys(m).length; + return (!config.length || (config.smaller ? amount < config.length : amount == config.length)) && [props].flat(10).every(prop => typeof m[prop] == "string") && (!nonProps.length || nonProps.every(prop => typeof m[prop] == "undefined")) && m; + }, {all: config.all, defaultExport: config.defaultExport}); + if (!config.all && firstReturn) return firstReturn; + let secondReturn = BDFDB.ModuleUtils.find(m => { + if (typeof m != "function") return false; + let stringified = m.toString().replace(/\s/g, ""); + if (stringified.indexOf(".exports={") == -1 || !(/function\([A-z],[A-z],[A-z]\)\{[A-z]\.[A-z]\([A-z]\.exports=\{/.test(stringified) || /function\([A-z],[A-z],[A-z]\)\{[A-z]\.exports=\{/.test(stringified) || /function\([A-z]\)\{[A-z]\.exports=\{/.test(stringified))) return false; + let amount = stringified.split(":\"").length - 1; + return (!config.length || (config.smaller ? amount < config.length : amount == config.length)) && [props].flat(10).every(string => stringified.indexOf(`${string}:`) > -1) && (!nonProps.length || nonProps.every(string => stringified.indexOf(`${string}:`) == -1)) && m; + }, {onlySearchUnloaded: true, all: config.all, defaultExport: config.defaultExport}); + if (!config.all) return secondReturn; + return BDFDB.ArrayUtils.removeCopies([firstReturn].concat(secondReturn).flat(10)); + }; + + const DiscordConstantsObject = BDFDB.ModuleUtils.find(m => {try { + if (!m || !Object.keys(m)) return; + if ([["CUSTOM_STATUS", "STREAMING", "WATCHING"], ["GUILD_TEXT", "GUILD_FORUM", "GUILD_STORE"], ["THREAD_CREATED", "CHAT_INPUT_COMMAND"]].every(array => Object.keys(m).some(k => { + if (!m[k]) return false; + let keys = Object.keys(m[k]); + if (keys && array.every(n => keys.indexOf(n) > -1)) return true; + }))) return m; + } catch (err) {}}); + if (InternalData.CustomDiscordConstants) DiscordConstants = Object.assign(DiscordConstants, InternalData.CustomDiscordConstants); + if (DiscordConstantsObject) DiscordConstants = Object.assign(DiscordConstants, DiscordConstantsObject); + Internal.DiscordConstants = new Proxy(DiscordConstants, { + get: function (_, item) { + if (DiscordConstants[item]) return DiscordConstants[item]; + if (!InternalData.DiscordConstants[item]) { + BDFDB.LogUtils.warn([item, "Object not found in DiscordConstants"]); + return {}; + } + if (InternalData.DiscordConstants[item].strings) DiscordConstants[item] = BDFDB.ModuleUtils.findByString(InternalData.DiscordConstants[item].strings); + else if (InternalData.DiscordConstants[item].props) DiscordConstants[item] = BDFDB.ModuleUtils.findByProperties(InternalData.DiscordConstants[item].props); + else { + if (DiscordConstantsObject) DiscordConstants[item] = DiscordConstantsObject[Object.keys(DiscordConstantsObject).find(n => { + let keys = Object.keys(DiscordConstantsObject[n]); + if (keys && keys.length < 10000 && InternalData.DiscordConstants[item].every && InternalData.DiscordConstants[item].every(k => keys.indexOf(k) > -1)) return true; + })]; + if (!DiscordConstants[item]) DiscordConstants[item] = BDFDB.ModuleUtils.findByProperties(InternalData.DiscordConstants[item]); + } + if (DiscordConstants[item] && InternalData.DiscordConstants[item].value) DiscordConstants[item] = DiscordConstants[item][InternalData.DiscordConstants[item].value] || DiscordConstants[item]; + return DiscordConstants[item] ? DiscordConstants[item] : {}; + } + }); + const ColorsCSS = Internal.DiscordConstants.ColorsCSS || {}; + Internal.DiscordConstants.ColorsCSS = new Proxy(ColorsCSS, { + get: function (_, item) { + const color = ColorsCSS[item] || ColorsCSS[item.toLowerCase()] || ColorsCSS[item.toUpperCase()]; + return color && color.css || color || ""; + } + }); + const DiscordColors = Internal.DiscordConstants.Colors || {}; + Internal.DiscordConstants.Colors = new Proxy(DiscordColors, { + get: function (_, item) { + const color = DiscordColors[item] || DiscordColors[item.toLowerCase()] || DiscordColors[item.toUpperCase()]; + if (color) return color && color.hex || color || ""; + else { + const item2 = item + "_500"; + const color2 = DiscordColors[item2] || DiscordColors[item2.toLowerCase()] || DiscordColors[item2.toUpperCase()]; + if (color2) return color2 && color2.hex || color2 || ""; + else { + const item3 = item.replace(/-/g, "_"); + const color3 = DiscordColors[item3] || DiscordColors[item3.toLowerCase()] || DiscordColors[item3.toUpperCase()]; + if (color3) return color3 && color3.hex || color3 || ""; + else { + const item4 = item.replace(/_/g, "-"); + const color4 = DiscordColors[item4] || DiscordColors[item4.toLowerCase()] || DiscordColors[item4.toUpperCase()]; + return color4 && color4.hex || color4 || ""; + } + } + } + } + }); + BDFDB.DiscordConstants = Internal.DiscordConstants; + + Internal.DiscordObjects = new Proxy(DiscordObjects, { + get: function (_, item) { + if (DiscordObjects[item]) return DiscordObjects[item]; + if (!InternalData.DiscordObjects[item]) return (function () {}); + let defaultExport = InternalData.DiscordObjects[item].exported == undefined ? true : InternalData.DiscordObjects[item].exported; + if (InternalData.DiscordObjects[item].props) DiscordObjects[item] = BDFDB.ModuleUtils.findByPrototypes(InternalData.DiscordObjects[item].props, {defaultExport}); + else if (InternalData.DiscordObjects[item].strings) DiscordObjects[item] = BDFDB.ModuleUtils.findByString(InternalData.DiscordObjects[item].strings, {defaultExport}); + return DiscordObjects[item] ? DiscordObjects[item] : (function () {}); + } + }); + BDFDB.DiscordObjects = Internal.DiscordObjects; + + Internal.LibraryRequires = new Proxy(LibraryRequires, { + get: function (_, item) { + if (item == "request") return requestFunction; + if (LibraryRequires[item]) return LibraryRequires[item]; + if (InternalData.LibraryRequires.indexOf(item) == -1) return (function () {}); + try {LibraryRequires[item] = require(item);} + catch (err) {} + return LibraryRequires[item] ? LibraryRequires[item] : (function () {}); + } + }); + BDFDB.LibraryRequires = Internal.LibraryRequires; + + Internal.LibraryStores = new Proxy(LibraryStores, { + get: function (_, item) { + if (LibraryStores[item]) return LibraryStores[item]; + LibraryStores[item] = BDFDB.ModuleUtils.find(m => m && m.constructor && typeof m.constructor.displayName == "string" && m.constructor.displayName == item && m); + if (!LibraryStores[item]) BDFDB.LogUtils.warn([item, "could not be found in Webmodule Stores"]); + return LibraryStores[item] ? LibraryStores[item] : null; + } + }); + BDFDB.LibraryStores = Internal.LibraryStores; + + BDFDB.StoreChangeUtils = {}; + BDFDB.StoreChangeUtils.add = function (plugin, store, callback) { + plugin = plugin == BDFDB && Internal || plugin; + if (!BDFDB.ObjectUtils.is(plugin) || !BDFDB.ObjectUtils.is(store) || typeof store.addChangeListener != "function" || typeof callback != "function") return; + BDFDB.StoreChangeUtils.remove(plugin, store, callback); + if (!BDFDB.ArrayUtils.is(plugin.changeListeners)) plugin.changeListeners = []; + plugin.changeListeners.push({store, callback}); + store.addChangeListener(callback); + }; + BDFDB.StoreChangeUtils.remove = function (plugin, store, callback) { + plugin = plugin == BDFDB && Internal || plugin; + if (!BDFDB.ObjectUtils.is(plugin) || !BDFDB.ArrayUtils.is(plugin.changeListeners)) return; + if (!store) { + while (plugin.changeListeners.length) { + let listener = plugin.changeListeners.pop(); + listener.store.removeChangeListener(listener.callback); + } + } + else if (BDFDB.ObjectUtils.is(store) && typeof store.addChangeListener == "function") { + if (!callback) { + for (let listener of plugin.changeListeners) { + let removedListeners = []; + if (listener.store == store) { + listener.store.removeChangeListener(listener.callback); + removedListeners.push(listener); + } + if (removedListeners.length) plugin.changeListeners = plugin.changeListeners.filter(listener => !removedListeners.includes(listener)); + } + } + else if (typeof callback == "function") { + store.removeChangeListener(callback); + plugin.changeListeners = plugin.changeListeners.filter(listener => listener.store == store && listener.callback == callback); + } + } + }; + + var pressedKeys = [], mousePosition; + BDFDB.ListenerUtils = {}; + BDFDB.ListenerUtils.isPressed = function (key) { + return pressedKeys.includes(key); + }; + BDFDB.ListenerUtils.getPosition = function (key) { + return mousePosition; + }; + BDFDB.ListenerUtils.add = function (plugin, ele, actions, selectorOrCallback, callbackOrNothing) { + plugin = plugin == BDFDB && Internal || plugin; + if (!BDFDB.ObjectUtils.is(plugin) || (!Node.prototype.isPrototypeOf(ele) && ele !== window) || !actions) return; + let callbackIs4th = typeof selectorOrCallback == "function"; + let selector = callbackIs4th ? undefined : selectorOrCallback; + let callback = callbackIs4th ? selectorOrCallback : callbackOrNothing; + if (typeof callback != "function") return; + BDFDB.ListenerUtils.remove(plugin, ele, actions, selector); + for (let action of actions.split(" ")) { + action = action.split("."); + let eventName = action.shift().toLowerCase(); + if (!eventName) return; + let origEventName = eventName; + eventName = eventName == "mouseenter" || eventName == "mouseleave" ? "mouseover" : eventName; + let namespace = (action.join(".") || "") + plugin.name; + if (!BDFDB.ArrayUtils.is(plugin.eventListeners)) plugin.eventListeners = []; + let eventCallback = null; + if (selector) { + if (origEventName == "mouseenter" || origEventName == "mouseleave") eventCallback = e => { + if (e.composedPath) for (let child of e.composedPath()) if (typeof child.matches == "function" && child.matches(selector) && !child[namespace + "BDFDB" + origEventName]) { + child[namespace + "BDFDB" + origEventName] = true; + if (origEventName == "mouseenter") callback(BDFDB.ListenerUtils.copyEvent(e, child)); + let mouseOut = e2 => { + if (e2.target.contains(child) || e2.target == child || !child.contains(e2.target)) { + if (origEventName == "mouseleave") callback(BDFDB.ListenerUtils.copyEvent(e, child)); + delete child[namespace + "BDFDB" + origEventName]; + document.removeEventListener("mouseout", mouseOut); + } + }; + document.addEventListener("mouseout", mouseOut); + break; + } + }; + else eventCallback = e => { + if (e.composedPath) for (let child of e.composedPath()) if (typeof child.matches == "function" && child.matches(selector)) { + callback(BDFDB.ListenerUtils.copyEvent(e, child)); + break; + } + }; + } + else eventCallback = e => callback(BDFDB.ListenerUtils.copyEvent(e, ele)); + + let observer; + if (Node.prototype.isPrototypeOf(ele)) { + observer = new MutationObserver(changes => changes.forEach(change => { + const nodes = Array.from(change.removedNodes); + if (nodes.indexOf(ele) > -1 || nodes.some(n => n.contains(ele))) BDFDB.ListenerUtils.remove(plugin, ele, actions, selector); + })); + observer.observe(document.body, {subtree: true, childList: true}); + } + + plugin.eventListeners.push({ele, eventName, origEventName, namespace, selector, eventCallback, observer}); + ele.addEventListener(eventName, eventCallback, true); + } + }; + BDFDB.ListenerUtils.remove = function (plugin, ele, actions = "", selector) { + plugin = plugin == BDFDB && Internal || plugin; + if (!BDFDB.ObjectUtils.is(plugin) || !BDFDB.ArrayUtils.is(plugin.eventListeners)) return; + if (!ele) { + while (plugin.eventListeners.length) { + let listener = plugin.eventListeners.pop(); + listener.ele.removeEventListener(listener.eventName, listener.eventCallback, true); + if (listener.observer) listener.observer.disconnect(); + } + } + else if (Node.prototype.isPrototypeOf(ele) || ele === window) { + for (let action of actions.split(" ")) { + action = action.split("."); + let eventName = action.shift().toLowerCase(); + let namespace = (action.join(".") || "") + plugin.name; + for (let listener of plugin.eventListeners) { + let removedListeners = []; + if (listener.ele == ele && (!eventName || listener.origEventName == eventName) && listener.namespace == namespace && (selector === undefined || listener.selector == selector)) { + listener.ele.removeEventListener(listener.eventName, listener.eventCallback, true); + if (listener.observer) listener.observer.disconnect(); + removedListeners.push(listener); + } + if (removedListeners.length) plugin.eventListeners = plugin.eventListeners.filter(listener => !removedListeners.includes(listener)); + } + } + } + }; + const leftSideMap = { + 16: 160, + 17: 170, + 18: 164 + }; + BDFDB.ListenerUtils.addGlobal = function (plugin, id, keybind, action) { + plugin = plugin == BDFDB && Internal || plugin; + if (!BDFDB.ObjectUtils.is(plugin) || !id || !BDFDB.ArrayUtils.is(keybind) || typeof action != "function") return; + if (!BDFDB.ObjectUtils.is(plugin.globalKeybinds)) plugin.globalKeybinds = {}; + BDFDB.ListenerUtils.removeGlobal(plugin, id); + plugin.globalKeybinds[id] = BDFDB.NumberUtils.generateId(Object.entries(plugin.globalKeybinds).map(n => n[1])); + BDFDB.LibraryModules.WindowUtils.inputEventRegister(plugin.globalKeybinds[id], keybind.map(n => [0, n]), action, {blurred: true, focused: true, keydown: false, keyup: true}); + if (Object.keys(leftSideMap).some(key => keybind.indexOf(parseInt(key)) > -1)) { + const alternativeId = id + "___ALTERNATIVE"; + plugin.globalKeybinds[alternativeId] = BDFDB.NumberUtils.generateId(Object.entries(plugin.globalKeybinds).map(n => n[1])); + BDFDB.LibraryModules.WindowUtils.inputEventRegister(plugin.globalKeybinds[alternativeId], keybind.map(n => [0, leftSideMap[n] ?? n]), action, {blurred: true, focused: true, keydown: false, keyup: true}); + } + return (_ => BDFDB.ListenerUtils.removeGlobal(plugin, id)); + }; + BDFDB.ListenerUtils.removeGlobal = function (plugin, id) { + if (!BDFDB.ObjectUtils.is(plugin) || !plugin.globalKeybinds) return; + if (!id) { + for (let cachedId in plugin.globalKeybinds) BDFDB.LibraryModules.WindowUtils.inputEventUnregister(plugin.globalKeybinds[cachedId]); + plugin.globalKeybinds = {}; + } + else { + if (plugin.globalKeybinds[id]) BDFDB.LibraryModules.WindowUtils.inputEventUnregister(plugin.globalKeybinds[id]); + delete plugin.globalKeybinds[id]; + const alternativeId = id + "___ALTERNATIVE"; + if (plugin.globalKeybinds[alternativeId]) BDFDB.LibraryModules.WindowUtils.inputEventUnregister(plugin.globalKeybinds[alternativeId]); + delete plugin.globalKeybinds[alternativeId]; + } + }; + BDFDB.ListenerUtils.multiAdd = function (node, actions, callback) { + if (!Node.prototype.isPrototypeOf(node) || !actions || typeof callback != "function") return; + for (let action of actions.trim().split(" ").filter(n => n)) node.addEventListener(action, callback, true); + }; + BDFDB.ListenerUtils.multiRemove = function (node, actions, callback) { + if (!Node.prototype.isPrototypeOf(node) || !actions || typeof callback != "function") return; + for (let action of actions.trim().split(" ").filter(n => n)) node.removeEventListener(action, callback, true); + }; + BDFDB.ListenerUtils.addToChildren = function (node, actions, selector, callback) { + if (!Node.prototype.isPrototypeOf(node) || !actions || !selector || !selector.trim() || typeof callback != "function") return; + for (let action of actions.trim().split(" ").filter(n => n)) { + let eventCallback = callback; + if (action == "mouseenter" || action == "mouseleave") eventCallback = e => {if (e.target.matches(selector)) callback(e);}; + node.querySelectorAll(selector.trim()).forEach(child => child.addEventListener(action, eventCallback, true)); + } + }; + BDFDB.ListenerUtils.copyEvent = function (e, ele) { + if (!e || !e.constructor || !e.type) return e; + let eCopy = new e.constructor(e.type, e); + Object.defineProperty(eCopy, "originalEvent", {value: e}); + Object.defineProperty(eCopy, "composedPath", {value: e.composedPath}); + Object.defineProperty(eCopy, "which", {value: e.which}); + Object.defineProperty(eCopy, "keyCode", {value: e.keyCode}); + Object.defineProperty(eCopy, "relatedTarget", {value: e.relatedTarget}); + Object.defineProperty(eCopy, "srcElement", {value: e.srcElement}); + Object.defineProperty(eCopy, "target", {value: e.target}); + Object.defineProperty(eCopy, "toElement", {value: e.toElement}); + if (ele) Object.defineProperty(eCopy, "currentTarget", {value: ele}); + return eCopy; + }; + BDFDB.ListenerUtils.stopEvent = function (e) { + if (BDFDB.ObjectUtils.is(e)) { + if (typeof e.preventDefault == "function") e.preventDefault(); + if (typeof e.stopPropagation == "function") e.stopPropagation(); + if (typeof e.stopImmediatePropagation == "function") e.stopImmediatePropagation(); + if (BDFDB.ObjectUtils.is(e.originalEvent)) { + if (typeof e.originalEvent.preventDefault == "function") e.originalEvent.preventDefault(); + if (typeof e.originalEvent.stopPropagation == "function") e.originalEvent.stopPropagation(); + if (typeof e.originalEvent.stopImmediatePropagation == "function") e.originalEvent.stopImmediatePropagation(); + } + } + }; + + var Toasts = [], NotificationBars = []; + var ToastQueues = {}, DesktopNotificationQueue = {queue: [], running: false}; + for (let key in Internal.DiscordConstants.ToastPositions) ToastQueues[Internal.DiscordConstants.ToastPositions[key]] = {queue: [], full: false}; + + BDFDB.NotificationUtils = {}; + BDFDB.NotificationUtils.toast = function (children, config = {}) { + if (!children) return; + let app = document.querySelector(BDFDB.dotCN.appmount) || document.body; + if (!app) return; + let position = config.position && Internal.DiscordConstants.ToastPositions[config.position] || Internal.settings.choices.toastPosition && Internal.DiscordConstants.ToastPositions[Internal.settings.choices.toastPosition] || Internal.DiscordConstants.ToastPositions.right; + let queue = ToastQueues[position] || {}; + + const runQueue = _ => { + if (queue.full) return; + let data = queue.queue.shift(); + if (!data) return; + + let id = BDFDB.NumberUtils.generateId(Toasts); + let toasts = document.querySelector(BDFDB.dotCN.toasts + BDFDB.dotCN[position]); + if (!toasts) { + toasts = BDFDB.DOMUtils.create(`
`); + app.appendChild(toasts); + } + + if (data.config.id) data.toast.id = data.config.id.split(" ").join(""); + if (data.config.className) BDFDB.DOMUtils.addClass(data.toast, data.config.className); + if (data.config.css) BDFDB.DOMUtils.appendLocalStyle("BDFDBcustomToast" + id, data.config.css); + if (data.config.style) data.toast.style = Object.assign({}, data.toast.style, data.config.style); + + let backgroundColor, fontColor, barColor; + + let type = data.config.type && BDFDB.disCN["toast" + data.config.type]; + if (!type) { + barColor = BDFDB.ColorUtils.getType(data.config.barColor) ? BDFDB.ColorUtils.convert(data.config.barColor, "HEX") : data.config.barColor; + let comp = BDFDB.ColorUtils.convert(data.config.color, "RGBCOMP"); + if (comp) { + backgroundColor = BDFDB.ColorUtils.convert(comp, "HEX"); + fontColor = comp[0] > 180 && comp[1] > 180 && comp[2] > 180 ? "#000" : "#FFF"; + BDFDB.DOMUtils.addClass(data.toast, BDFDB.disCN.toastcustom); + } + else BDFDB.DOMUtils.addClass(data.toast, BDFDB.disCN.toastdefault); + } + else BDFDB.DOMUtils.addClass(data.toast, type); + + let loadingInterval; + let disableInteractions = data.config.disableInteractions && typeof data.config.onClick != "function"; + let timeout = typeof data.config.timeout == "number" && !disableInteractions ? data.config.timeout : 3000; + timeout = (timeout > 0 ? timeout : 600000) + 300; + if (data.config.ellipsis && typeof data.children == "string") loadingInterval = BDFDB.TimeUtils.interval(_ => data.toast.update(data.children.endsWith(".....") ? data.children.slice(0, -5) : data.children + "."), 500); + + let closeTimeout = BDFDB.TimeUtils.timeout(_ => data.toast.close(), timeout); + data.toast.close = _ => { + BDFDB.TimeUtils.clear(closeTimeout); + if (document.contains(data.toast)) { + BDFDB.DOMUtils.addClass(data.toast, BDFDB.disCN.toastclosing); + data.toast.style.setProperty("pointer-events", "none", "important"); + BDFDB.TimeUtils.timeout(_ => { + if (typeof data.config.onClose == "function") data.config.onClose(); + BDFDB.TimeUtils.clear(loadingInterval); + BDFDB.ArrayUtils.remove(Toasts, id); + BDFDB.DOMUtils.removeLocalStyle("BDFDBcustomToast" + id); + data.toast.remove(); + if (!toasts.querySelectorAll(BDFDB.dotCN.toast).length) toasts.remove(); + }, 300); + } + queue.full = false; + runQueue(); + }; + + if (disableInteractions) data.toast.style.setProperty("pointer-events", "none", "important"); + else { + BDFDB.DOMUtils.addClass(data.toast, BDFDB.disCN.toastclosable); + data.toast.addEventListener("click", event => { + if (typeof data.config.onClick == "function" && !BDFDB.DOMUtils.getParent(BDFDB.dotCN.toastcloseicon, event.target)) data.config.onClick(); + data.toast.close(); + }); + if (typeof closeTimeout.pause == "function") { + let paused = false; + data.toast.addEventListener("mouseenter", _ => { + if (paused) return; + paused = true; + closeTimeout.pause(); + }); + data.toast.addEventListener("mouseleave", _ => { + if (!paused) return; + paused = false; + closeTimeout.resume(); + }); + } + } + + toasts.appendChild(data.toast); + BDFDB.TimeUtils.timeout(_ => BDFDB.DOMUtils.removeClass(data.toast, BDFDB.disCN.toastopening)); + + let icon = data.config.avatar ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Avatars.Avatar, { + src: data.config.avatar, + size: Internal.LibraryComponents.AvatarConstants.AvatarSizes.SIZE_24 + }) : ((data.config.icon || data.config.type && Internal.DiscordConstants.ToastIcons[data.config.type]) ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: data.config.type && Internal.DiscordConstants.ToastIcons[data.config.type] && Internal.LibraryComponents.SvgIcon.Names[Internal.DiscordConstants.ToastIcons[data.config.type]], + iconSVG: data.config.icon, + width: 18, + height: 18, + nativeClass: true + }) : null); + + BDFDB.ReactUtils.render(BDFDB.ReactUtils.createElement(class BDFDB_Toast extends Internal.LibraryModules.React.Component { + componentDidMount() { + data.toast.update = newChildren => { + if (!newChildren) return; + data.children = newChildren; + BDFDB.ReactUtils.forceUpdate(this); + }; + } + render() { + return BDFDB.ReactUtils.createElement(Internal.LibraryModules.React.Fragment, { + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.toastbg, + style: {backgroundColor: backgroundColor} + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.toastinner, + style: {color: fontColor}, + children: [ + icon && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(data.config.avatar && BDFDB.disCN.toastavatar, BDFDB.disCN.toasticon, data.config.iconClassName), + children: icon + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.toasttext, data.config.textClassName), + children: data.children + }), + !disableInteractions && BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.toastcloseicon, + name: Internal.LibraryComponents.SvgIcon.Names.CLOSE, + width: 16, + height: 16 + }) + ].filter(n => n) + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.toastbar, barColor && BDFDB.disCN.toastcustombar), + style: { + backgroundColor: barColor, + animation: `toast-bar ${timeout}ms normal linear` + } + }) + ] + }); + } + }, {}), data.toast); + + queue.full = (BDFDB.ArrayUtils.sum(Array.from(toasts.childNodes).map(c => { + let height = BDFDB.DOMUtils.getRects(c).height; + return height > 50 ? height : 50; + })) - 100) > BDFDB.DOMUtils.getRects(app).height; + + if (typeof data.config.onShow == "function") data.config.onShow(); + }; + + let toast = BDFDB.DOMUtils.create(`
`); + toast.update = _ => {}; + queue.queue.push({children, config, toast}); + runQueue(); + return toast; + }; + BDFDB.NotificationUtils.desktop = function (content, config = {}) { + if (!content) return; + + const queue = _ => { + DesktopNotificationQueue.queue.push({content, config}); + runQueue(); + }; + const runQueue = _ => { + if (DesktopNotificationQueue.running) return; + let data = DesktopNotificationQueue.queue.shift(); + if (!data) return; + + DesktopNotificationQueue.running = true; + let muted = data.config.silent; + data.config.silent = data.config.silent || data.config.sound ? true : false; + let audio = new Audio(); + if (!muted && data.config.sound) { + audio.src = data.config.sound; + audio.play(); + } + let notification = new Notification(data.content, data.config); + + let disableInteractions = data.config.disableInteractions && typeof data.config.onClick != "function"; + if (disableInteractions) notification.onclick = _ => {}; + else notification.onclick = _ => { + if (typeof data.config.onClick == "function") data.config.onClick(); + notification.close(); + }; + + notification.onclose = _ => { + audio.pause(); + DesktopNotificationQueue.running = false; + BDFDB.TimeUtils.timeout(runQueue, 1000); + } + }; + + if (!("Notification" in window)) {BDFDB.NotificationUtils.toast(content, config);} + else if (Notification.permission === "granted") queue(); + else if (Notification.permission !== "denied") Notification.requestPermission(function (response) {if (response === "granted") queue();}); + }; + BDFDB.NotificationUtils.notice = function (text, config = {}) { + if (!text) return; + let layers = document.querySelector(BDFDB.dotCN.layers) || document.querySelector(BDFDB.dotCN.appmount); + if (!layers) return; + let notice = BDFDB.DOMUtils.create(`
`); + layers.parentElement.insertBefore(notice, layers); + let noticeText = notice.querySelector(BDFDB.dotCN.noticetext); + if (config.platform) for (let platform of config.platform.split(" ")) if (InternalData.DiscordClasses["noticeicon" + platform]) { + let icon = BDFDB.DOMUtils.create(``); + BDFDB.DOMUtils.addClass(icon, BDFDB.disCN.noticeplatformicon); + BDFDB.DOMUtils.removeClass(icon, BDFDB.disCN.noticeicon); + notice.insertBefore(icon, noticeText); + } + if (config.customIcon) { + let icon = document.createElement("i"), iconInner = BDFDB.DOMUtils.create(config.customIcon); + if (iconInner.nodeType == Node.TEXT_NODE) icon.style.setProperty("background", `url(${config.customIcon}) center/cover no-repeat`); + else { + icon = iconInner; + if ((icon.tagName || "").toUpperCase() == "SVG") { + icon.removeAttribute("width"); + icon.setAttribute("height", "100%"); + } + } + BDFDB.DOMUtils.addClass(icon, BDFDB.disCN.noticeplatformicon); + BDFDB.DOMUtils.removeClass(icon, BDFDB.disCN.noticeicon); + notice.insertBefore(icon, noticeText); + } + if (BDFDB.ArrayUtils.is(config.buttons)) for (let data of config.buttons) { + let contents = typeof data.contents == "string" && data.contents; + if (contents) { + let button = BDFDB.DOMUtils.create(``); + button.addEventListener("click", event => { + if (data.close) notice.close(); + if (typeof data.onClick == "function") data.onClick(event, notice); + }); + if (typeof data.onMouseEnter == "function") button.addEventListener("mouseenter", event => data.onMouseEnter(event, notice)); + if (typeof data.onMouseLeave == "function") button.addEventListener("mouseleave", event => data.onMouseLeave(event, notice)); + notice.appendChild(button); + } + } + if (config.id) notice.id = config.id.split(" ").join(""); + if (config.className) BDFDB.DOMUtils.addClass(notice, config.className); + if (config.textClassName) BDFDB.DOMUtils.addClass(noticeText, config.textClassName); + if (config.css) BDFDB.DOMUtils.appendLocalStyle("BDFDBcustomNotificationBar" + id, config.css); + if (config.style) notice.style = config.style; + if (config.html) noticeText.innerHTML = text; + else { + let link = document.createElement("a"); + let newText = []; + for (let word of text.split(" ")) { + let encodedWord = BDFDB.StringUtils.htmlEscape(word); + link.href = word; + newText.push(link.host && link.host !== window.location.host ? `` : encodedWord); + } + noticeText.innerHTML = newText.join(" "); + } + let type = null; + if (config.type && !document.querySelector(BDFDB.dotCNS.chatbase + BDFDB.dotCN.noticestreamer)) { + if (type = BDFDB.disCN["notice" + config.type]) BDFDB.DOMUtils.addClass(notice, type); + if (config.type == "premium") { + let noticeButton = notice.querySelector(BDFDB.dotCN.noticebutton); + if (noticeButton) BDFDB.DOMUtils.addClass(noticeButton, BDFDB.disCN.noticepremiumaction); + BDFDB.DOMUtils.addClass(noticeText, BDFDB.disCN.noticepremiumtext); + notice.insertBefore(BDFDB.DOMUtils.create(``), noticeText); + } + } + if (!type) { + let comp = BDFDB.ColorUtils.convert(config.color, "RGBCOMP"); + if (comp) { + let fontColor = comp[0] > 180 && comp[1] > 180 && comp[2] > 180 ? "#000" : "#FFF"; + let backgroundColor = BDFDB.ColorUtils.convert(comp, "HEX"); + notice.style.setProperty("--custom-notice-background", backgroundColor); + notice.style.setProperty("--custom-notice-text", fontColor); + notice.style.setProperty("--custom-notice-button-hover", fontColor); + BDFDB.DOMUtils.addClass(notice, BDFDB.disCN.noticecustom); + } + else BDFDB.DOMUtils.addClass(notice, BDFDB.disCN.noticedefault); + } + notice.close = _ => { + BDFDB.DOMUtils.addClass(notice, BDFDB.disCN.noticeclosing); + if (notice.tooltip && typeof notice.tooltip.removeTooltip == "function") notice.tooltip.removeTooltip(); + BDFDB.TimeUtils.timeout(_ => { + if (typeof config.onClose == "function") config.onClose(); + BDFDB.DOMUtils.remove(notice); + }, 500); + }; + let dismiss = notice.querySelector(BDFDB.dotCN.noticedismiss); + if (dismiss) dismiss.addEventListener("click", notice.close); + return notice; + }; + BDFDB.NotificationUtils.alert = function (header, body) { + if (typeof header == "string" && typeof header == "string" && BdApi && typeof BdApi.UI.alert == "function") BdApi.UI.alert(header, body); + }; + + var Tooltips = []; + BDFDB.TooltipUtils = {}; + BDFDB.TooltipUtils.create = function (anker, text, config = {}) { + if (!text && !config.guild) return null; + const itemLayerContainer = document.querySelector(`${BDFDB.dotCN.app} ~ ${BDFDB.dotCN.itemlayercontainer}:has(${BDFDB.dotCN.itemlayercontainerclicktrap})`) || document.querySelector(`${BDFDB.dotCN.app} ~ ${BDFDB.dotCN.itemlayercontainer}`) || document.querySelector(BDFDB.dotCN.itemlayercontainer); + if (!itemLayerContainer || !Node.prototype.isPrototypeOf(anker) || !document.contains(anker)) return null; + const id = BDFDB.NumberUtils.generateId(Tooltips); + const wrapper = BDFDB.DOMUtils.create(`
`); + itemLayerContainer.appendChild(wrapper); + + const itemLayer = wrapper.firstElementChild; + const tooltip = wrapper.querySelector(BDFDB.dotCN.tooltip); + const tooltipContent = wrapper.querySelector(BDFDB.dotCN.tooltipcontent); + const tooltipPointers = Array.from(wrapper.querySelectorAll(BDFDB.dotCN.tooltippointer)); + + if (config.id) tooltip.id = config.id.split(" ").join(""); + + if (typeof config.type != "string" || !BDFDB.disCN["tooltip" + config.type.toLowerCase()]) config.type = "top"; + let type = config.type.toLowerCase(); + BDFDB.DOMUtils.addClass(tooltip, BDFDB.disCN["tooltip" + type], config.className); + BDFDB.DOMUtils.addClass(tooltipContent, config.contentClassName); + + let fontColorIsGradient = false, customBackgroundColor = false, style = ""; + if (config.style) style += config.style; + if (config.fontColor) { + fontColorIsGradient = BDFDB.ObjectUtils.is(config.fontColor); + if (!fontColorIsGradient) style = (style ? (style + " ") : "") + `color: ${BDFDB.ColorUtils.convert(config.fontColor, "RGBA")} !important;` + } + if (config.backgroundColor) { + customBackgroundColor = true; + let backgroundColorIsGradient = BDFDB.ObjectUtils.is(config.backgroundColor); + let backgroundColor = !backgroundColorIsGradient ? BDFDB.ColorUtils.convert(config.backgroundColor, "RGBA") : BDFDB.ColorUtils.createGradient(config.backgroundColor); + let borderColor = backgroundColorIsGradient ? BDFDB.ColorUtils.convert(config.backgroundColor[type == "left" ? 100 : 0], "RGBA") : backgroundColor; + style = (style ? (style + " ") : "") + `background: ${backgroundColor} !important; --tooltip-pointer-bg: ${borderColor} !important;`; + } + if (style) tooltip.style = style; + const zIndexed = config.zIndex && typeof config.zIndex == "number"; + if (zIndexed) { + itemLayer.style.setProperty("z-index", config.zIndex, "important"); + tooltip.style.setProperty("z-index", config.zIndex, "important"); + tooltipContent.style.setProperty("z-index", config.zIndex, "important"); + BDFDB.DOMUtils.addClass(itemLayerContainer, BDFDB.disCN.itemlayercontainerzindexdisabled); + } + if (typeof config.width == "number" && config.width > 196) { + tooltip.style.setProperty("width", `${config.width}px`, "important"); + tooltip.style.setProperty("max-width", `${config.width}px`, "important"); + } + if (typeof config.maxWidth == "number" && config.maxWidth > 196) { + tooltip.style.setProperty("max-width", `${config.maxWidth}px`, "important"); + } + if (customBackgroundColor) BDFDB.DOMUtils.addClass(tooltip, BDFDB.disCN.tooltipcustom); + else if (config.color && BDFDB.disCN["tooltip" + config.color.toLowerCase()]) BDFDB.DOMUtils.addClass(tooltip, BDFDB.disCN["tooltip" + config.color.toLowerCase()]); + else BDFDB.DOMUtils.addClass(tooltip, BDFDB.disCN.tooltipprimary); + + if (config.list || BDFDB.ObjectUtils.is(config.guild)) BDFDB.DOMUtils.addClass(tooltipContent, BDFDB.disCN.guildlistitemtooltip); + + const removeTooltip = _ => { + document.removeEventListener("wheel", wheel); + document.removeEventListener("mousemove", mouseMove); + document.removeEventListener("mouseleave", mouseLeave); + BDFDB.DOMUtils.remove(wrapper); + BDFDB.ArrayUtils.remove(Tooltips, id); + observer.disconnect(); + if (zIndexed) BDFDB.DOMUtils.removeClass(itemLayerContainer, BDFDB.disCN.itemlayercontainerzindexdisabled); + if (typeof config.onHide == "function") config.onHide(wrapper, anker); + }; + const setText = newText => { + if (BDFDB.ObjectUtils.is(config.guild)) { + let isMuted = Internal.LibraryStores.UserGuildSettingsStore.isMuted(config.guild.id); + let muteConfig = Internal.LibraryStores.UserGuildSettingsStore.getMuteConfig(config.guild.id); + + let children = [typeof newText == "function" ? newText() : newText].flat(10).filter(n => typeof n == "string" || BDFDB.ReactUtils.isValidElement(n)); + BDFDB.ReactUtils.render(BDFDB.ReactUtils.createElement(Internal.LibraryModules.React.Fragment, { + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.tooltiprow, BDFDB.disCN.tooltiprowguildname), + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.GuildBadge, { + guild: config.guild, + size: 16, + className: BDFDB.disCN.tooltiprowiconv2 + }), + BDFDB.ReactUtils.createElement("span", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.tooltipguildnametext), + children: fontColorIsGradient ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextGradientElement, { + gradient: BDFDB.ColorUtils.createGradient(config.fontColor), + children: config.guild.name + }) : config.guild.name + }), + ] + }), + children.length && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.tooltiprow, BDFDB.disCN.tooltiprowextra), + children: children + }), + config.note && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.tooltiprow, BDFDB.disCN.tooltiprowextra, BDFDB.disCN.tooltipnote), + children: config.note + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.GuildVoiceList, {guild: config.guild}), + isMuted && muteConfig && (muteConfig.end_time == null ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextElement, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.tooltipmutetext), + size: Internal.LibraryComponents.TextElement.Sizes.SIZE_12, + color: Internal.LibraryComponents.TextElement.Colors.MUTED, + children: BDFDB.LanguageUtils.LanguageStrings.MUTED + }) : BDFDB.ReactUtils.createElement(Internal.LibraryComponents.GuildTooltipMutedText, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.tooltipmutetext), + muteConfig: muteConfig + })) + ].filter(n => n) + }), tooltipContent); + } + else { + let children = [typeof newText == "function" ? newText() : newText].flat(10).filter(n => typeof n == "string" || BDFDB.ReactUtils.isValidElement(n)); + children.length && BDFDB.ReactUtils.render(BDFDB.ReactUtils.createElement(Internal.LibraryModules.React.Fragment, { + children: [ + fontColorIsGradient ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextGradientElement, { + gradient: BDFDB.ColorUtils.createGradient(config.fontColor), + children: children + }) : children, + config.note && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.tooltiprow, BDFDB.disCN.tooltiprowextra, BDFDB.disCN.tooltipnote), + children: config.note + }) + ] + }), tooltipContent); + } + }; + const update = newText => { + if (newText) setText(newText); + let left, top; + const tRects = BDFDB.DOMUtils.getRects(anker); + const iRects = BDFDB.DOMUtils.getRects(itemLayer); + const aRects = BDFDB.DOMUtils.getRects(document.querySelector(BDFDB.dotCN.appmount)); + const positionOffsets = {height: 10, width: 10}; + const offset = typeof config.offset == "number" ? config.offset : 0; + switch (type) { + case "top": + top = tRects.top - iRects.height - positionOffsets.height + 2 - offset; + left = tRects.left + (tRects.width - iRects.width) / 2; + break; + case "bottom": + top = tRects.top + tRects.height + positionOffsets.height - 2 + offset; + left = tRects.left + (tRects.width - iRects.width) / 2; + break; + case "left": + top = tRects.top + (tRects.height - iRects.height) / 2; + left = tRects.left - iRects.width - positionOffsets.width + 2 - offset; + break; + case "right": + top = tRects.top + (tRects.height - iRects.height) / 2; + left = tRects.left + tRects.width + positionOffsets.width - 2 + offset; + break; + } + + itemLayer.style.setProperty("top", `${top}px`, "important"); + itemLayer.style.setProperty("left", `${left}px`, "important"); + + for (let pointer of tooltipPointers) pointer.style.removeProperty("margin-left"); + for (let pointer of tooltipPointers) pointer.style.removeProperty("margin-top"); + if (type == "top" || type == "bottom") { + if (left < 0) { + itemLayer.style.setProperty("left", "5px", "important"); + for (let pointer of tooltipPointers) pointer.style.setProperty("margin-left", `${left - 10}px`, "important"); + } + else { + const rightMargin = aRects.width - (left + iRects.width); + if (rightMargin < 0) { + itemLayer.style.setProperty("left", `${aRects.width - iRects.width - 5}px`, "important"); + for (let pointer of tooltipPointers) pointer.style.setProperty("margin-left", `${-1*rightMargin}px`, "important"); + } + } + } + else if (type == "left" || type == "right") { + if (top < 0) { + const bRects = BDFDB.DOMUtils.getRects(document.querySelector(BDFDB.dotCN.titlebar)); + const barCorrection = (bRects.width || 0) >= Math.round(75 * window.outerWidth / aRects.width) ? (bRects.height + 5) : 0; + itemLayer.style.setProperty("top", `${5 + barCorrection}px`, "important"); + for (let pointer of tooltipPointers) pointer.style.setProperty("margin-top", `${top - 10 - barCorrection}px`, "important"); + } + else { + const bottomMargin = aRects.height - (top + iRects.height); + if (bottomMargin < 0) { + itemLayer.style.setProperty("top", `${aRects.height - iRects.height - 5}px`, "important"); + for (let pointer of tooltipPointers) pointer.style.setProperty("margin-top", `${-1*bottomMargin}px`, "important"); + } + } + } + }; + + const wheel = e => { + const tRects1 = BDFDB.DOMUtils.getRects(anker); + BDFDB.TimeUtils.clear(wheel.timeout); + wheel.timeout = BDFDB.TimeUtils.timeout(_ => { + const tRects2 = BDFDB.DOMUtils.getRects(anker); + if (tRects1.x != tRects2.x || tRects1.y != tRects2.y) removeTooltip(); + }, 500); + }; + const mouseMove = e => { + const parent = e.target.parentElement.querySelector(":hover"); + if (parent && anker != parent && !anker.contains(parent)) removeTooltip(); + }; + const mouseLeave = e => removeTooltip(); + if (!config.perssist) { + document.addEventListener("wheel", wheel); + document.addEventListener("mousemove", mouseMove); + document.addEventListener("mouseleave", mouseLeave); + } + + const observer = new MutationObserver(changes => changes.forEach(change => { + const nodes = Array.from(change.removedNodes); + if (nodes.indexOf(wrapper) > -1 || nodes.indexOf(anker) > -1 || nodes.some(n => n.contains(anker))) removeTooltip(); + })); + observer.observe(document.body, {subtree: true, childList: true}); + + tooltip.removeTooltip = wrapper.removeTooltip = removeTooltip; + tooltip.setText = wrapper.setText = setText; + tooltip.update = wrapper.update = update; + setText(text); + update(); + + if (config.delay) { + BDFDB.DOMUtils.toggle(wrapper); + BDFDB.TimeUtils.timeout(_ => { + BDFDB.DOMUtils.toggle(wrapper); + if (typeof config.onShow == "function") config.onShow(wrapper, anker); + }, config.delay); + } + else { + if (typeof config.onShow == "function") config.onShow(wrapper, anker); + } + return wrapper; + }; + + Internal.addModulePatches = function (plugin) { + plugin = plugin == BDFDB && Internal || plugin; + if (!plugin || !plugin.modulePatches) return; + let patchPriority = !isNaN(plugin.patchPriority) ? plugin.patchPriority : 5; + patchPriority = patchPriority < 1 ? (plugin == Internal ? 0 : 1) : (patchPriority > 9 ? (plugin == Internal ? 10 : 9) : Math.round(patchPriority)); + for (let patchType in plugin.modulePatches) { + if (!PluginStores.modulePatches[patchType]) PluginStores.modulePatches[patchType] = {}; + for (let type of plugin.modulePatches[patchType]) { + if (InternalData.PatchModules[type]) { + let found = false; + if (!InternalData.PatchModules[type].noSearch && (patchType == "before" || patchType == "after")) { + let exports = (BDFDB.ModuleUtils.find(m => Internal.isCorrectModule(m, type) && m, {defaultExport: false, moduleName: type}) || {}).exports; + if (exports && !exports.default) for (let key of Object.keys(exports)) if (typeof exports[key] == "function" && !(exports[key].prototype && exports[key].prototype.render) && Internal.isCorrectModule(exports[key], type, false) && exports[key].toString().length < 50000) { + found = true; + BDFDB.PatchUtils.patch(plugin, exports, key, {[patchType]: e => Internal.initiatePatch(plugin, type, { + arguments: e.methodArguments, + instance: e.instance, + returnvalue: e.returnValue, + component: exports[key], + name: type, + methodname: "render", + patchtypes: [patchType] + })}, {name: type}); + break; + } + } + if (!found) { + if (!PluginStores.modulePatches[patchType][type]) PluginStores.modulePatches[patchType][type] = []; + if (!PluginStores.modulePatches[patchType][type][patchPriority]) PluginStores.modulePatches[patchType][type][patchPriority] = []; + PluginStores.modulePatches[patchType][type][patchPriority].push(plugin); + if (PluginStores.modulePatches[patchType][type][patchPriority].length > 1) PluginStores.modulePatches[patchType][type][patchPriority] = BDFDB.ArrayUtils.keySort(PluginStores.modulePatches[patchType][type][patchPriority], "name"); + } + } + else BDFDB.LogUtils.warn(`[${type}] not found in PatchModules InternalData`, plugin); + } + } + }; + Internal.addContextPatches = function (plugin) { + if (!InternalData.ContextMenuTypes || !BdApi || !BdApi.ContextMenu || typeof BdApi.ContextMenu.patch != "function") return; + plugin = plugin == BDFDB && Internal || plugin; + if (!plugin) return; + for (let type in InternalData.ContextMenuTypes) if (typeof plugin[`on${type}`] == "function") { + if (!BDFDB.ArrayUtils.is(plugin.patchCancels)) plugin.patchCancels = []; + plugin.patchCancels.push(BdApi.ContextMenu.patch(InternalData.ContextMenuTypes[type], (returnValue, props) => typeof plugin[`on${type}`] == "function" && plugin[`on${type}`]({returnvalue: returnValue, instance: {props}}))); + } + }; + Internal.initiatePatch = function (plugin, type, e) { + plugin = plugin == BDFDB && Internal || plugin; + if (BDFDB.ObjectUtils.is(plugin) && !plugin.stopping && e.instance) { + type = BDFDB.StringUtils.upperCaseFirstChar(type).replace(/[^A-z0-9]|_/g, ""); + if (typeof plugin[`process${type}`] == "function") { + if (typeof e.methodname == "string" && ["componentDidMount", "componentDidUpdate", "componentWillUnmount"].indexOf(e.methodname) > -1) { + e.node = BDFDB.ReactUtils.findDOMNode(e.instance); + if (e.node) { + let tempReturn = plugin[`process${type}`](e); + return tempReturn !== undefined ? tempReturn : e.returnvalue; + } + else BDFDB.TimeUtils.timeout(_ => { + e.node = BDFDB.ReactUtils.findDOMNode(e.instance); + if (e.node) plugin[`process${type}`](e); + }); + } + else if (e.returnvalue !== undefined || e.patchtypes.includes("before")) { + let tempReturn = plugin[`process${type}`](e); + return tempReturn !== undefined ? tempReturn : e.returnvalue; + } + } + } + }; + + const PatchTypes = ["before", "instead", "after"]; + BDFDB.PatchUtils = {}; + BDFDB.PatchUtils.isPatched = function (plugin, module, methodName) { + plugin = plugin == BDFDB && Internal || plugin; + if (!plugin || !methodName || (!BDFDB.ObjectUtils.is(module) && !BDFDB.ArrayUtils.is(module)) || !module[methodName] || !module[methodName].BDFDB_Patches) return false; + const pluginId = (typeof plugin === "string" ? plugin : plugin.name).toLowerCase(); + return pluginId && BDFDB.ObjectUtils.toArray(module[methodName].BDFDB_Patches).some(patchObj => BDFDB.ObjectUtils.toArray(patchObj.plugins).some(priorityObj => Object.keys(priorityObj).includes(pluginId))); + }; + BDFDB.PatchUtils.patch = function (plugin, module, methodNames, patchMethods, config = {}) { + if (!BdApi || !BdApi.Patcher) return; + plugin = plugin == BDFDB && Internal || plugin; + if (!plugin || (!BDFDB.ObjectUtils.is(module) && !BDFDB.ArrayUtils.is(module)) || !methodNames || !BDFDB.ObjectUtils.is(patchMethods)) return null; + patchMethods = BDFDB.ObjectUtils.filter(patchMethods, type => PatchTypes.includes(type) && typeof BdApi.Patcher[type] == "function" && typeof patchMethods[type] == "function", true); + if (BDFDB.ObjectUtils.isEmpty(patchMethods)) return null; + + const pluginName = (typeof plugin === "string" ? plugin : plugin.name) || ""; + const pluginVersion = typeof plugin === "string" ? "" : plugin.version; + const pluginId = pluginName.toLowerCase(); + + let patchPriority = !isNaN(config.priority) ? config.priority : (BDFDB.ObjectUtils.is(plugin) && !isNaN(plugin.patchPriority) ? plugin.patchPriority : 5); + patchPriority = patchPriority < 1 ? (plugin == Internal ? 0 : 1) : (patchPriority > 9 ? (plugin == Internal ? 10 : 9) : Math.round(patchPriority)); + + methodNames = [methodNames].flat(10).filter(n => n); + let cancel = _ => BDFDB.PatchUtils.unpatch(plugin, module, methodNames); + + for (let methodName of methodNames) if (module[methodName] == null || typeof module[methodName] == "function") { + if (!module[methodName]) module[methodName] = _ => {return null}; + let patches = module[methodName].BDFDB_Patches || {}; + for (let type in patchMethods) { + const internalData = (Object.entries(InternalData.LibraryModules).find(n => n && n[0] && LibraryModules[n[0]] == module && n[1] && n[1]._originalModule && n[1]._mappedItems[methodName]) || [])[1]; + const name = internalData && internalData[0] || config.name || (module.constructor ? (module.constructor.displayName || module.constructor.name) : "module"); + try { + if (!patches[type]) { + const originalMethod = module[methodName].__originalFunction || module[methodName]; + const mainCancel = BdApi.Patcher[type](Internal.name, internalData && internalData._originalModule || module, internalData && internalData._mappedItems[methodName] || methodName, function(...args) { + let callInsteadAfterwards = false, stopInsteadCall = false; + const data = { + component: module, + methodArguments: args[1], + returnValue: args[2], + originalMethod: originalMethod, + originalMethodName: methodName + }; + if (type == "instead") { + data.callOriginalMethod = _ => data.returnValue = data.originalMethod.apply(this && this !== window ? this : {}, data.methodArguments); + data.callOriginalMethodAfterwards = _ => (callInsteadAfterwards = true, data.returnValue); + data.stopOriginalMethodCall = _ => stopInsteadCall = true; + } + if (args[0] != module) data.instance = args[0] || {props: args[1][0]}; + if (module == LibraryModules.MessageToolbarUtils) data.instance = {props: args[1][0]}; + for (let priority in patches[type].plugins) for (let id in BDFDB.ObjectUtils.sort(patches[type].plugins[priority])) { + let tempReturn = BDFDB.TimeUtils.suppress(patches[type].plugins[priority][id], `"${type}" callback of ${methodName} in ${name}`, {name: patches[type].plugins[priority][id].pluginName, version: patches[type].plugins[priority][id].pluginVersion, ignoreErrors: config.ignoreErrors})(data); + if (type != "before" && tempReturn !== undefined) data.returnValue = tempReturn; + } + if (type == "instead" && callInsteadAfterwards && !stopInsteadCall) BDFDB.TimeUtils.suppress(data.callOriginalMethod, `originalMethod of ${methodName} in ${name}`, {name: "Discord", ignoreErrors: config.ignoreErrors})(); + + if (type != "before") return (methodName == "render" || methodName == "type") && data.returnValue === undefined ? null : data.returnValue; + }); + module[methodName].BDFDB_Patches = patches; + patches[type] = {plugins: {}, cancel: _ => { + if (!config.noCache) BDFDB.ArrayUtils.remove(Internal.patchCancels, patches[type].cancel, true); + delete patches[type]; + if (!config.noCache && BDFDB.ObjectUtils.isEmpty(patches)) delete module[methodName].BDFDB_Patches; + mainCancel(); + }}; + if (!config.noCache) { + if (!BDFDB.ArrayUtils.is(Internal.patchCancels)) Internal.patchCancels = []; + Internal.patchCancels.push(patches[type].cancel); + } + } + if (!patches[type].plugins[patchPriority]) patches[type].plugins[patchPriority] = {}; + patches[type].plugins[patchPriority][pluginId] = (...args) => { + if (config.once || !plugin.started) cancel(); + return patchMethods[type](...args); + }; + patches[type].plugins[patchPriority][pluginId].pluginName = pluginName; + patches[type].plugins[patchPriority][pluginId].pluginVersion = pluginVersion; + } catch (err) {BDFDB.LogUtils.error(["Could not patch Component!", `"${type}" Patch of ${methodName} in ${name}`, err], plugin);} + } + } + if (BDFDB.ObjectUtils.is(plugin) && !config.once && !config.noCache) { + if (!BDFDB.ArrayUtils.is(plugin.patchCancels)) plugin.patchCancels = []; + plugin.patchCancels.push(cancel); + } + return cancel; + }; + BDFDB.PatchUtils.unpatch = function (plugin, module, methodNames) { + plugin = plugin == BDFDB && Internal || plugin; + if (!module || !methodNames) { + if (BDFDB.ObjectUtils.is(plugin) && BDFDB.ArrayUtils.is(plugin.patchCancels)) while (plugin.patchCancels.length) (plugin.patchCancels.pop())(); + } + else { + const pluginId = !plugin ? null : (typeof plugin === "string" ? plugin : plugin.name).toLowerCase(); + for (let methodName of [methodNames].flat(10).filter(n => n)) if (module[methodName] && module[methodName].BDFDB_Patches) { + let patches = module[methodName].BDFDB_Patches; + for (let type in patches) { + if (pluginId) for (let priority in patches[type].plugins) { + delete patches[type].plugins[priority][pluginId]; + if (BDFDB.ObjectUtils.isEmpty(patches[type].plugins[priority])) delete patches[type].plugins[priority]; + } + else patches[type].plugins = {}; + if (BDFDB.ObjectUtils.isEmpty(patches[type].plugins)) patches[type].cancel(); + } + } + } + }; + Internal.forceUpdate = function (pluginDataObjs, instance, type) { + pluginDataObjs = [pluginDataObjs].flat(10).filter(n => n); + if (pluginDataObjs.length && instance && type) { + let forceRender = false; + for (let pluginData of pluginDataObjs) { + let plugin = pluginData.plugin == BDFDB && Internal || pluginData.plugin, methodNames = []; + for (let patchType in plugin.modulePatches) { + if (plugin.modulePatches[patchType].indexOf(type) > -1) methodNames.push(patchType); + } + methodNames = BDFDB.ArrayUtils.removeCopies(methodNames).flat(10).filter(n => n); + if (methodNames.indexOf("componentDidMount") > -1) Internal.initiatePatch(plugin, type, { + arguments: [], + instance: instance, + returnvalue: undefined, + component: undefined, + methodname: "componentDidMount", + patchtypes: pluginData.patchTypes[type] + }); + if (methodNames.indexOf("before") > -1 || methodNames.indexOf("after") > -1) forceRender = true; + else if (!forceRender && methodNames.includes("componentDidUpdate") > -1) Internal.initiatePatch(plugin, type, { + arguments: [], + instance: instance, + returnvalue: undefined, + component: undefined, + methodname: "componentDidUpdate", + patchtypes: pluginData.patchTypes[type] + }); + } + if (forceRender) BDFDB.ReactUtils.forceUpdate(instance); + } + }; + BDFDB.PatchUtils.forceAllUpdates = function (plugins, selectedTypes) { + plugins = [plugins].flat(10).map(n => n == BDFDB && Internal || n).filter(n => BDFDB.ObjectUtils.is(n.modulePatches)); + if (!plugins.length) return; + const app = document.querySelector(BDFDB.dotCN.app); + if (!app) return; + selectedTypes = [selectedTypes].flat(10).filter(n => n); + let toBeUpdatedModulesObj = {}; + let patchTypes = {}; + for (let plugin of plugins) { + toBeUpdatedModulesObj[plugin.name] = []; + patchTypes[plugin.name] = {}; + for (let patchType in plugin.modulePatches) for (let type of plugin.modulePatches[patchType]) if (!selectedTypes.length || selectedTypes.includes(type)) { + toBeUpdatedModulesObj[plugin.name].push(type); + if (!patchTypes[plugin.name][type]) patchTypes[plugin.name][type] = []; + patchTypes[plugin.name][type].push(patchType); + } + } + let toBeUpdatedModules = BDFDB.ArrayUtils.removeCopies(BDFDB.ObjectUtils.toArray(toBeUpdatedModulesObj).flat(10)); + if (!BDFDB.ArrayUtils.sum(toBeUpdatedModules.map(n => n.length))) return; + try { + const appInsDown = BDFDB.ReactUtils.findOwner(app, {name: toBeUpdatedModules, all: true, unlimited: true, group: true}); + const appInsUp = BDFDB.ReactUtils.findOwner(app, {name: toBeUpdatedModules, all: true, unlimited: true, group: true, up: true}); + for (let type in appInsDown) { + let filteredPlugins = plugins.filter(n => toBeUpdatedModulesObj[n.name].includes(type)).map(n => ({plugin: n, patchTypes: patchTypes[n.name]})); + for (let ins of appInsDown[type]) Internal.forceUpdate(filteredPlugins, ins, type); + } + for (let type in appInsUp) { + let filteredPlugins = plugins.filter(n => toBeUpdatedModulesObj[n.name].includes(type)).map(n => ({plugin: n, patchTypes: patchTypes[n.name]})); + for (let ins of appInsUp[type]) Internal.forceUpdate(filteredPlugins, ins, type); + } + } + catch (err) {for (let plugin of plugins) BDFDB.LogUtils.error(["Could not force update Components!", err], plugin);} + }; + + Internal.isCorrectModule = function (module, type, useCache = false) { + if (!InternalData.PatchModules || !InternalData.PatchModules[type]) return false; + else if (useCache && Cache && Cache.modules && Cache.modules.patch && Cache.modules.patch[type] == module) return true; + else { + let foundModule = null; + if (InternalData.PatchModules[type].strings) foundModule = Internal.checkModuleStrings(module._originalFunction || module, InternalData.PatchModules[type].strings) ? module : null; + if (InternalData.PatchModules[type].props) foundModule = Internal.checkModuleProps(module, InternalData.PatchModules[type].props) ? module : null; + if (InternalData.PatchModules[type].protos) foundModule = Internal.checkModuleProtos(module, InternalData.PatchModules[type].protos) ? module : null; + if (foundModule) { + if (InternalData.PatchModules[type].nonStrings) foundModule = Internal.checkModuleStrings(module._originalFunction || module, InternalData.PatchModules[type].nonStrings, {hasNot: true}) ? module : null; + if (InternalData.PatchModules[type].nonProps) foundModule = Internal.checkModuleProps(module, InternalData.PatchModules[type].nonProps, {hasNot: true}) ? module : null; + if (InternalData.PatchModules[type].nonProtos) foundModule = Internal.checkModuleProtos(module, InternalData.PatchModules[type].nonProtos, {hasNot: true}) ? module : null; + } + if (foundModule) { + if (useCache) { + if (!Cache.modules.patch) Cache.modules.patch = {}; + Cache.modules.patch[type] = foundModule; + } + return true; + } + } + return false; + }; + Internal.isCorrectModuleButDontPatch = function (type) { + if (type == "MessageToolbar" && document.querySelector(BDFDB.dotCN.emojipicker)) return true; + return false; + }; + Internal.findModuleViaData = (moduleStorage, dataStorage, item) => { + if (!dataStorage[item]) return; + let defaultExport = typeof dataStorage[item].exported != "boolean" ? true : dataStorage[item].exported; + if (dataStorage[item].props) moduleStorage[item] = BDFDB.ModuleUtils.findByProperties(dataStorage[item].props, {defaultExport: defaultExport, moduleName: item}); + else if (dataStorage[item].protos) moduleStorage[item] = BDFDB.ModuleUtils.findByPrototypes(dataStorage[item].protos, {defaultExport: defaultExport, moduleName: item}); + else if (dataStorage[item].name) moduleStorage[item] = BDFDB.ModuleUtils.findByName(dataStorage[item].name, {defaultExport: defaultExport, moduleName: item}); + else if (dataStorage[item].strings) { + if (dataStorage[item].nonStrings) { + moduleStorage[item] = Internal.findModule("strings + nonStrings", JSON.stringify([dataStorage[item].strings, dataStorage[item].nonStrings].flat(10)), m => Internal.checkModuleStrings(m, dataStorage[item].strings) && Internal.checkModuleStrings(m, dataStorage[item].nonStrings, {hasNot: true}) && m, {defaultExport: defaultExport, moduleName: item}); + } + else moduleStorage[item] = BDFDB.ModuleUtils.findByString(dataStorage[item].strings, {defaultExport: defaultExport, moduleName: item}); + } + if (dataStorage[item].value) moduleStorage[item] = (moduleStorage[item] || {})[dataStorage[item].value]; + if (dataStorage[item].assign) moduleStorage[item] = Object.assign({}, moduleStorage[item]); + if (!moduleStorage[item]) return; + if (moduleStorage[item][item]) moduleStorage[item] = moduleStorage[item][item]; + if (dataStorage[item].funcStrings) moduleStorage[item] = (Object.entries(moduleStorage[item]).find(n => { + if (!n || !n[1]) return; + let funcString = typeof n[1] == "function" ? n[1].toString() : (_ => {try {return JSON.stringify(n[1])}catch(err){return n[1].toString()}})(); + let renderFuncString = typeof n[1].render == "function" && n[1].render.toString() || ""; + return (funcString || renderFuncString) && [dataStorage[item].funcStrings].flat(10).filter(s => s && typeof s == "string").every(string => funcString && funcString.indexOf(string) > -1 || renderFuncString && renderFuncString.indexOf(string) > -1); + }) || [])[1]; + if (dataStorage[item].map) { + dataStorage[item]._originalModule = moduleStorage[item]; + dataStorage[item]._mappedItems = {}; + moduleStorage[item] = new Proxy(Object.assign({}, dataStorage[item]._originalModule, dataStorage[item].map), { + get: function (_, item2) { + if (dataStorage[item]._originalModule[item2]) return dataStorage[item]._originalModule[item2]; + if (dataStorage[item]._mappedItems[item2]) return dataStorage[item]._originalModule[dataStorage[item]._mappedItems[item2]]; + if (!dataStorage[item].map[item2]) return dataStorage[item]._originalModule[item2]; + let foundFunc = dataStorage[item].map[item2] && dataStorage[item].map[item2].length == 1 && dataStorage[item]._originalModule[dataStorage[item].map[item2][0]] ? [dataStorage[item].map[item2][0], dataStorage[item]._originalModule[dataStorage[item].map[item2][0]]] : Object.entries(dataStorage[item]._originalModule).find(n => { + if (!n || !n[1]) return; + let funcString = typeof n[1] == "function" ? n[1].toString() : (_ => {try {return JSON.stringify(n[1])}catch(err){return n[1].toString()}})(); + let renderFuncString = typeof n[1].render == "function" && n[1].render.toString() || ""; + return [dataStorage[item].map[item2]].flat(10).filter(s => s && typeof s == "string").every(string => funcString && funcString.replace(/[\n\t\r]/g, "").indexOf(string) > -1 || renderFuncString && renderFuncString.replace(/[\n\t\r]/g, "").indexOf(string) > -1); + }); + if (foundFunc) { + dataStorage[item]._mappedItems[item2] = foundFunc[0]; + return foundFunc[1]; + } + return "div"; + } + }); + } + }; + const hasDiscordStringHash = m => { + return m && [InternalData.LanguageStringHashes.DISCORD].flat(10).some(hash => m[hash] && typeof m[hash] != "function"); + }; + const LanguageStores = BDFDB.ModuleUtils.find(m => (hasDiscordStringHash(m) || hasDiscordStringHash(m.default)) && m, {all: true, defaultExport: false}); + + LibraryModules.EngLanguageStore = (LanguageStores.find(n => n && n.exports && hasDiscordStringHash(n.exports.default)) || {}).exports || {}; + LibraryModules.EngLanguageStore = LibraryModules.EngLanguageStore.default || LibraryModules.EngLanguageStore; + + LibraryModules.LanguageStore = (LanguageStores.find(n => n && hasDiscordStringHash(n.exports)) || LanguageStores.find(n => n && n.exports && hasDiscordStringHash(n.exports.default)) || {}).exports || {}; + LibraryModules.LanguageStore = LibraryModules.LanguageStore.default || LibraryModules.LanguageStore; + + LibraryModules.React = BDFDB.ModuleUtils.findByProperties("createElement", "cloneElement"); + LibraryModules.ReactDOM = BDFDB.ModuleUtils.findByProperties("render", "findDOMNode", {noWarnings: true}) || BDFDB.ModuleUtils.findByProperties("createRoot"); + LibraryModules.ReactPortal = BDFDB.ModuleUtils.findByProperties("flushSync", "createPortal"); + + Internal.LibraryModules = new Proxy(LibraryModules, { + get: function (_, item) { + if (LibraryModules[item]) return LibraryModules[item]; + if (!InternalData.LibraryModules[item]) return null; + + if (item == "HTTPUtils") { + const APIUtils = Internal.LibraryModules.APIUtils; + if (APIUtils && InternalData.LibraryModules.HTTPUtils.props) LibraryModules[item] = (Object.entries(APIUtils).find(entry => InternalData.LibraryModules.HTTPUtils.props.every(prop => entry[1][prop] !== undefined)) || [])[1]; + } + else if (item == "MessageToolbarUtils") return LibraryModules.MessageToolbarUtils; + else Internal.findModuleViaData(LibraryModules, InternalData.LibraryModules, item); + + return LibraryModules[item] ? LibraryModules[item] : null; + } + }); + BDFDB.LibraryModules = Internal.LibraryModules; + + LibraryModules.MessageToolbarUtils = {}; + LibraryModules.MessageToolbarUtils.useMessageMenu = (props, returnValue) => {return returnValue;}; + + if (Internal.LibraryModules.KeyCodeUtils && Internal.LibraryModules.PlatformUtils) { + let originalModule = LibraryModules.KeyCodeUtils; + LibraryModules.KeyCodeUtils = new Proxy(originalModule, { + get: function (_, item) { + if (item == "getString") return getString; + else if (item == "_originalModule") return originalModule; + else if (originalModule[item]) return originalModule[item]; + else return null; + } + }); + + let codeMap = BDFDB.ObjectUtils.invert(Internal.LibraryModules.PlatformUtils.isLinux && Internal.LibraryModules.PlatformUtils.isLinux() ? Internal.DiscordConstants.LinuxKeyToCode : Internal.LibraryModules.PlatformUtils.isMac && Internal.LibraryModules.PlatformUtils.isMac() ? Internal.DiscordConstants.MacosKeyToCode : Internal.LibraryModules.PlatformUtils && Internal.LibraryModules.PlatformUtils.isWindows && Internal.LibraryModules.PlatformUtils.isWindows() ? Internal.DiscordConstants.WindowsKeyToCode : {}); + let keyMap = [["META", "⌘"], ["RIGHT META", "RIGHT ⌘"], ["SHIFT", "⇧"], ["RIGHT SHIFT", "RIGHT ⇧"], ["ALT", "⌥"], ["RIGHT ALT", "RIGHT ⌥"], ["CTRL", "⌃"], ["RIGHT CTRL", "RIGHT ⌃"], ["ENTER", "↵"], ["BACKSPACE", "⌫"], ["DEL", "⌦"], ["ESC", "⎋"], ["PAGEUP", "⇞"], ["PAGEDOWN", "⇟"], ["UP", "↑"], ["DOWN", "↓"], ["LEFT", "←"], ["RIGHT", "→"], ["HOME", "↖"], ["END", "↘"], ["TAB", "⇥"], ["SPACE", "␣"]]; + let mapKeys = key => { + let upperCaseKey = key.toUpperCase(); + for (let [name, mappedKey] of mapKeys) if (name === upperCaseKey) return mappedKey; + return key; + }; + const getString = function (keyArray, upperCase = true) { + let strings = [keyArray].flat(10).filter(n => n).map(keyCode => { + let code = Internal.LibraryModules.KeyCodeUtils.keyToCode((Object.entries(Internal.LibraryModules.KeyEvents.codes).find(n => n[1] == keyCode && Internal.LibraryModules.KeyCodeUtils.keyToCode(n[0], null)) || [])[0], null) || keyCode; + return codeMap[code] || "UNK".concat(code) + }).filter(n => n != null); + if (!upperCase) return strings.join("+"); + else { + strings = window.navigator.appVersion.indexOf("Mac OS X") != -1 ? strings.map(mapKeys) : strings; + return strings.join(" + ").toUpperCase(); + } + }; + } + + const MyReact = {}; + MyReact.childrenToArray = function (parent) { + if (parent && parent.props && parent.props.children && !BDFDB.ArrayUtils.is(parent.props.children)) { + const child = parent.props.children; + parent.props.children = []; + parent.props.children.push(child); + } + return parent.props.children; + } + MyReact.createElement = function (component, props = {}, errorWrap = false, ignoreErrors = false) { + if (component && component.defaultProps) for (let key in component.defaultProps) if (props[key] == null) props[key] = component.defaultProps[key]; + try { + let child = Internal.LibraryModules.React.createElement(component || "div", props) || null; + if (errorWrap) return Internal.LibraryModules.React.createElement(Internal.ErrorBoundary, {key: child && child.key || ""}, child) || null; + else return child; + } + catch (err) {!ignoreErrors && BDFDB.LogUtils.error(["Could not create React Element!", err]);} + return null; + }; + MyReact.objectToReact = function (obj) { + if (!obj) return null; + else if (typeof obj == "string") return obj; + else if (BDFDB.ObjectUtils.is(obj)) return BDFDB.ReactUtils.createElement(obj.type && typeof obj.type == "function" || typeof obj.type == "string" ? obj.type : (obj.props && obj.props.href && "a" || "div"), !obj.props ? {} : Object.assign({}, obj.props, { + children: obj.props.children ? MyReact.objectToReact(obj.props.children) : null + })); + else if (BDFDB.ArrayUtils.is(obj)) return obj.map(n => MyReact.objectToReact(n)); + else return null; + }; + MyReact.markdownParse = function (str) { + if (!Internal.LibraryModules.SimpleMarkdownParser) return null; + if (!MyReact.markdownParse.parser || !MyReact.markdownParse.render) { + MyReact.markdownParse.parser = Internal.LibraryModules.SimpleMarkdownParser.parserFor(Internal.LibraryModules.SimpleMarkdownParser.defaultRules); + MyReact.markdownParse.render = Internal.LibraryModules.SimpleMarkdownParser.reactFor(Internal.LibraryModules.SimpleMarkdownParser.ruleOutput(Internal.LibraryModules.SimpleMarkdownParser.defaultRules, "react")); + } + return MyReact.render && MyReact.parser ? MyReact.render(MyReact.parser(str, {inline: true})) : null; + }; + MyReact.elementToReact = function (node, ref) { + if (BDFDB.ReactUtils.isValidElement(node)) return node; + else if (!Node.prototype.isPrototypeOf(node)) return null; + else if (node.nodeType == Node.TEXT_NODE) return node.nodeValue; + let attributes = {}, importantStyles = []; + if (typeof ref == "function") attributes.ref = ref; + if (node.attributes) { + for (let attr of node.attributes) attributes[attr.name] = attr.value; + if (node.attributes.style) attributes.style = BDFDB.ObjectUtils.filter(node.style, n => node.style[n] && isNaN(parseInt(n)), true); + } + attributes.children = []; + if (node.style && node.style.cssText) for (let propStr of node.style.cssText.split(";")) if (propStr.endsWith("!important")) { + let key = propStr.split(":")[0]; + let camelprop = key.replace(/-([a-z]?)/g, (m, g) => g.toUpperCase()); + if (attributes.style[camelprop] != null) importantStyles.push(key); + } + for (let child of node.childNodes) attributes.children.push(MyReact.elementToReact(child)); + attributes.className = BDFDB.DOMUtils.formatClassName(attributes.className, attributes.class); + delete attributes.class; + return BDFDB.ReactUtils.forceStyle(BDFDB.ReactUtils.createElement(node.tagName, attributes), importantStyles); + }; + MyReact.forceStyle = function (reactEle, styles) { + if (!BDFDB.ReactUtils.isValidElement(reactEle)) return null; + if (!BDFDB.ObjectUtils.is(reactEle.props.style) || !BDFDB.ArrayUtils.is(styles) || !styles.length) return reactEle; + let ref = reactEle.ref; + reactEle.ref = instance => { + if (typeof ref == "function") ref(instance); + let node = BDFDB.ReactUtils.findDOMNode(instance); + if (Node.prototype.isPrototypeOf(node)) for (let key of styles) { + let propValue = reactEle.props.style[key.replace(/-([a-z]?)/g, (m, g) => g.toUpperCase())]; + if (propValue != null) node.style.setProperty(key, propValue, "important"); + } + }; + return reactEle; + }; + MyReact.findDOMNode = function (instance, onlyChildren) { + if (Node.prototype.isPrototypeOf(instance)) return instance; + if (!instance || !instance.updater) return null; + let node = Internal.LibraryModules.ReactDOM.findDOMNode && Internal.LibraryModules.ReactDOM.findDOMNode(instance); + for (let path of ["child.stateNode", "child.ref.current", !onlyChildren && "return.stateNode", !onlyChildren && "return.return.stateNode"]) if (!node && path) { + node = BDFDB.ObjectUtils.get(instance[BDFDB.ReactUtils.instanceKey] || instance, path); + node = Node.prototype.isPrototypeOf(node) ? node : null; + } + if (!node) { + if (!onlyChildren) node = BDFDB.ReactUtils.findValue(instance[BDFDB.ReactUtils.instanceKey] || instance, "containerInfo", {up: true}); + else { + let child = (instance[BDFDB.ReactUtils.instanceKey] || instance); + while (child && !node) { + if (child && Node.prototype.isPrototypeOf(child.stateNode)) node = child.stateNode; + else child = child.child; + } + } + } + return Node.prototype.isPrototypeOf(node) ? node : null; + }; + MyReact.findParent = function (nodeOrInstance, config) { + if (!nodeOrInstance || !BDFDB.ObjectUtils.is(config) || !config.name && !config.type && !config.key && !config.props && !config.filter) return [null, -1]; + let instance = Node.prototype.isPrototypeOf(nodeOrInstance) ? BDFDB.ReactUtils.getInstance(nodeOrInstance) : nodeOrInstance; + if (!BDFDB.ObjectUtils.is(instance) && !BDFDB.ArrayUtils.is(instance) || instance.props && typeof instance.props.children == "function") return [null, -1]; + + config.name = config.name && [config.name].flat().filter(n => n); + config.type = config.type && [config.type].flat().filter(n => n); + config.key = config.key && [config.key].flat().filter(n => n); + config.props = config.props && [config.props].flat().filter(n => n); + config.filter = typeof config.filter == "function" && config.filter; + + let parent, firstArray; + parent = firstArray = instance; + while (!BDFDB.ArrayUtils.is(firstArray) && firstArray.props && firstArray.props.children) firstArray = firstArray.props.children; + if (!BDFDB.ArrayUtils.is(firstArray)) { + if (parent && parent.props) { + parent.props.children = [parent.props.children]; + firstArray = parent.props.children; + } + else firstArray = []; + } + return getParent(instance); + + function getParent (children) { + let result = [firstArray, -1]; + if (!children) return result; + if (!BDFDB.ArrayUtils.is(children)) { + if (check(children)) result = found(children); + else { + if (children.props && children.props.children) { + parent = children; + result = getParent(children.props.children); + } + if (!(result && result[1] > -1) && children.props && children.props.child) { + parent = children; + result = getParent(children.props.child); + } + } + } + else { + for (let i = 0; result[1] == -1 && i < children.length; i++) if (children[i]) { + if (BDFDB.ArrayUtils.is(children[i])) { + parent = children; + result = getParent(children[i]); + } + else if (check(children[i])) { + parent = children; + result = found(children[i]); + } + else { + if (children[i].props && children[i].props.children) { + parent = children[i]; + result = getParent(children[i].props.children); + } + if (!(result && result[1] > -1) && children[i].props && children[i].props.child) { + parent = children[i]; + result = getParent(children[i].props.child); + } + } + } + } + return result; + } + function found (child) { + if (BDFDB.ArrayUtils.is(parent)) return [parent, parent.indexOf(child)]; + else { + parent.props.children = []; + parent.props.children.push(child); + return [parent.props.children, 0]; + } + } + function check (instance) { + if (!instance || instance == parent) return false; + let props = instance.stateNode ? instance.stateNode.props : instance.props; + if (config.name && instance.type && config.name.some(name => instance.type.displayName == name || instance.type.name == name || Internal.isCorrectModule(instance.type.render || instance.type.type || instance.type, name))) return true; + if (config.type && config.type.some(type => instance.type == type)) return true; + if (config.key && config.key.some(key => instance.key == key)) return true; + if (config.props && props && config.props[config.someProps ? "some" : "every"](prop => BDFDB.ArrayUtils.is(prop) ? (BDFDB.ArrayUtils.is(prop[1]) ? prop[1].some(checkValue => propCheck(props, prop[0], checkValue)) : propCheck(props, prop[0], prop[1])) : props[prop] !== undefined)) return true; + if (config.filter && config.filter(instance)) return true; + return false; + } + function propCheck (props, key, value) { + return key != null && props[key] != null && value != null && (key == "className" ? (" " + props[key] + " ").indexOf(" " + value + " ") > -1 : BDFDB.equals(props[key], value)); + } + }; + MyReact.findChild = function (nodeOrInstance, config) { + if (!nodeOrInstance || !BDFDB.ObjectUtils.is(config) || !config.name && !config.type && !config.key && !config.props && !config.filter) return config.all ? [] : null; + let instance = Node.prototype.isPrototypeOf(nodeOrInstance) ? BDFDB.ReactUtils.getInstance(nodeOrInstance) : nodeOrInstance; + if (!BDFDB.ObjectUtils.is(instance) && !BDFDB.ArrayUtils.is(instance)) return null; + + config.name = config.name && [config.name].flat().filter(n => n); + config.type = config.type && [config.type].flat().filter(n => n); + config.key = config.key && [config.key].flat().filter(n => n); + config.props = config.props && [config.props].flat().filter(n => n); + config.filter = typeof config.filter == "function" && config.filter; + + let depth = -1; + let start = performance.now(); + let maxDepth = config.unlimited ? 999999999 : (config.depth === undefined ? 30 : config.depth); + let maxTime = config.unlimited ? 999999999 : (config.time === undefined ? 150 : config.time); + + let foundChildren = []; + let singleChild = getChild(instance); + if (config.all) { + for (let i in foundChildren) delete foundChildren[i].BDFDBreactSearch; + return foundChildren; + } + else return singleChild; + + function getChild (children) { + let result = null; + if (!children || depth >= maxDepth || performance.now() - start >= maxTime) return result; + if (!BDFDB.ArrayUtils.is(children)) { + if (check(children)) { + if (config.all === undefined || !config.all) result = children; + else if (config.all) { + if (!children.BDFDBreactSearch) { + children.BDFDBreactSearch = true; + foundChildren.push(children); + } + } + } + else { + if (children.props && children.props.children) { + depth++; + result = getChild(children.props.children); + depth--; + } + if (!result && children.props && children.props.child) { + depth++; + result = getChild(children.props.child); + depth--; + } + } + } + else { + for (let child of children) if (child) { + if (BDFDB.ArrayUtils.is(child)) result = getChild(child); + else if (check(child)) { + if (config.all === undefined || !config.all) result = child; + else if (config.all) { + if (!child.BDFDBreactSearch) { + child.BDFDBreactSearch = true; + foundChildren.push(child); + } + } + } + else { + if (child.props && child.props.children) { + depth++; + result = getChild(child.props.children); + depth--; + } + if (!result && child.props && child.props.child) { + depth++; + result = getChild(child.props.child); + depth--; + } + } + if (result) break; + } + } + return result; + } + function check (instance) { + if (!instance || instance == parent) return false; + let props = instance.stateNode ? instance.stateNode.props : instance.props; + if (config.name && instance.type && config.name.some(name => instance.type.displayName == name || instance.type.name == name || Internal.isCorrectModule(instance.type.render || instance.type.type || instance.type, name))) return true; + if (config.type && config.type.some(type => instance.type == type)) return true; + if (config.key && config.key.some(key => instance.key == key)) return true; + if (config.props && props && config.props[config.someProps ? "some" : "every"](prop => BDFDB.ArrayUtils.is(prop) ? (BDFDB.ArrayUtils.is(prop[1]) ? prop[1].some(checkValue => propCheck(props, prop[0], checkValue)) : propCheck(props, prop[0], prop[1])) : props[prop] !== undefined)) return true; + if (config.filter && config.filter(instance)) return true; + return false; + } + function propCheck (props, key, value) { + return key != null && props[key] != null && value != null && (key == "className" ? (" " + props[key] + " ").indexOf(" " + value + " ") > -1 : BDFDB.equals(props[key], value)); + } + }; + MyReact.findOwner = function (nodeOrInstance, config) { + if (!BDFDB.ObjectUtils.is(config)) return null; + if (!nodeOrInstance || !config.name && !config.type && !config.key && !config.props && !config.filter) return config.all ? (config.group ? {} : []) : null; + let instance = Node.prototype.isPrototypeOf(nodeOrInstance) ? BDFDB.ReactUtils.getInstance(nodeOrInstance) : nodeOrInstance; + if (!BDFDB.ObjectUtils.is(instance)) return config.all ? (config.group ? {} : []) : null; + + config.name = config.name && [config.name].flat().filter(n => n); + config.type = config.type && [config.type].flat().filter(n => n); + config.key = config.key && [config.key].flat().filter(n => n); + config.props = config.props && [config.props].flat().filter(n => n); + config.filter = typeof config.filter == "function" && config.filter; + + let depth = -1; + let start = performance.now(); + let maxDepth = config.unlimited ? 999999999 : (config.depth === undefined ? 30 : config.depth); + let maxTime = config.unlimited ? 999999999 : (config.time === undefined ? 150 : config.time); + let whitelist = config.up ? { + return: true, + sibling: true, + default: true + } : { + child: true, + sibling: true, + default: true + }; + whitelist[BDFDB.ReactUtils.instanceKey] = true; + + let foundInstances = config.group ? {} : []; + let singleInstance = getOwner(instance); + if (config.all) { + for (let i in foundInstances) { + if (config.group) for (let j in foundInstances[i]) delete foundInstances[i][j].BDFDBreactSearch; + else delete foundInstances[i].BDFDBreactSearch; + } + return foundInstances; + } + else return singleInstance; + + function getOwner (instance) { + depth++; + let result = undefined; + if (instance && !Node.prototype.isPrototypeOf(instance) && !BDFDB.ReactUtils.getInstance(instance) && depth < maxDepth && performance.now() - start < maxTime) { + let props = instance.stateNode ? instance.stateNode.props : instance.props; + let foundName = ""; + if (instance.stateNode && !Node.prototype.isPrototypeOf(instance.stateNode) && ( + config.name && instance.type && config.name.some(name => {if (instance.type.displayName == name || instance.type.name == name || Internal.isCorrectModule(instance.type.render || instance.type.type || instance.type, name)) { + foundName = name; return true; + }}) || + config.type && instance.type && config.type.some(type => instance.type == type) || + config.key && instance.key && config.key.some(key => instance.key == key) || + config.props && props && config.props.every(prop => BDFDB.ArrayUtils.is(prop) ? (BDFDB.ArrayUtils.is(prop[1]) ? prop[1].some(checkValue => BDFDB.equals(props[prop[0]], checkValue)) : BDFDB.equals(props[prop[0]], prop[1])) : props[prop] !== undefined) || + config.filter && config.filter(instance) + )) { + if (config.all === undefined || !config.all) result = instance.stateNode; + else if (config.all) { + if (!instance.stateNode.BDFDBreactSearch) { + instance.stateNode.BDFDBreactSearch = true; + if (config.group) { + if (foundName) { + if (!BDFDB.ArrayUtils.is(foundInstances[foundName])) foundInstances[foundName] = []; + foundInstances[foundName].push(instance.stateNode); + } + } + else foundInstances.push(instance.stateNode); + } + } + } + if (result === undefined) { + let keys = Object.getOwnPropertyNames(instance); + for (let i = 0; result === undefined && i < keys.length; i++) { + let key = keys[i]; + if (key && whitelist[key] && (typeof instance[key] === "object" || typeof instance[key] == "function")) result = getOwner(instance[key]); + } + } + } + depth--; + return result; + } + }; + MyReact.findValue = function (nodeOrInstance, searchKey, config = {}) { + if (!BDFDB.ObjectUtils.is(config)) return null; + if (!nodeOrInstance || typeof searchKey != "string") return config.all ? [] : null; + let instance = Node.prototype.isPrototypeOf(nodeOrInstance) ? BDFDB.ReactUtils.getInstance(nodeOrInstance) : nodeOrInstance; + if (!BDFDB.ObjectUtils.is(instance)) return config.all ? [] : null; + instance = instance[BDFDB.ReactUtils.instanceKey] || instance; + + let depth = -1; + let start = performance.now(); + let maxDepth = config.unlimited ? 999999999 : (config.depth === undefined ? 30 : config.depth); + let maxTime = config.unlimited ? 999999999 : (config.time === undefined ? 150 : config.time); + let whitelist = { + props: true, + state: true, + stateNode: true, + updater: true, + prototype: true, + type: true, + children: config.up ? false : true, + memoizedProps: true, + memoizedState: true, + child: config.up ? false : true, + return: config.up ? true : false, + sibling: config.up ? false : true + }; + let whitelistKeys = Object.keys(whitelist); + let blacklist = { + contextSection: true + }; + if (BDFDB.ObjectUtils.is(config.whitelist)) Object.assign(whitelist, config.whiteList); + if (BDFDB.ObjectUtils.is(config.blacklist)) Object.assign(blacklist, config.blacklist); + return getKey(instance); + + function getKey(instance) { + depth++; + let result = undefined; + if (instance && !Node.prototype.isPrototypeOf(instance) && !BDFDB.ReactUtils.getInstance(instance) && depth < maxDepth && performance.now() - start < maxTime) { + let keys = Object.keys(instance).sort((x, y) => whitelistKeys.indexOf(x) < whitelistKeys.indexOf(y) ? -1 : 1); + for (let i = 0; result === undefined && i < keys.length; i++) { + let key = keys[i]; + if (key && !blacklist[key]) { + let value = instance[key]; + if (searchKey === key && (config.notNull === undefined || value !== null) && (config.value === undefined || BDFDB.equals(config.value, value))) result = value; + else if ((typeof value === "object" || typeof value == "function") && (whitelist[key] || key[0] == "." || !isNaN(key[0]))) result = getKey(value); + } + } + } + depth--; + return result; + } + }; + MyReact.forceUpdate = function (...instances) { + for (let ins of instances.flat(10).filter(n => n)) if (ins.updater) ins.forceUpdate(); + }; + MyReact.getInstance = function (node) { + if (!BDFDB.ObjectUtils.is(node)) return null; + return node[Object.keys(node).find(key => key.startsWith("__reactInternalInstance") || key.startsWith("__reactFiber"))]; + }; + MyReact.render = function (component, node, ignoreErrors = false) { + if (!BDFDB.ReactUtils.isValidElement(component) || !Node.prototype.isPrototypeOf(node)) return; + try { + let root; + if (Internal.LibraryModules.ReactDOM.render) Internal.LibraryModules.ReactDOM.render(component, node); + else { + root = BDFDB.ReactUtils.createRoot(node); + BDFDB.ReactUtils.flushSync(_ => root.render(component)); + } + let observer = new MutationObserver(changes => changes.forEach(change => { + let nodes = Array.from(change.removedNodes); + if (nodes.indexOf(node) > -1 || nodes.some(n => n.contains(node))) { + observer.disconnect(); + BDFDB.ReactUtils.unmountComponentAtNode(node); + } + })); + observer.observe(document.body, {subtree: true, childList: true}); + if (root) node.root = root; + } + catch (err) { + !ignoreErrors && BDFDB.LogUtils.error(["Could not render React Element!", err]); + } + }; + MyReact.hookCall = function (callback, args, ignoreErrors = false) { + if (typeof callback != "function") return null; + let returnValue = null, tempNode = document.createElement("div"); + BDFDB.ReactUtils.render(BDFDB.ReactUtils.createElement(_ => { + returnValue = callback(args); + return null; + }, {}, false, ignoreErrors), tempNode, ignoreErrors); + BDFDB.ReactUtils.unmountComponentAtNode(tempNode); + return returnValue; + }; + MyReact.unmountComponentAtNode = function (node) { + node && node.root && node.root.unmount ? node.root.unmount() : (Internal.LibraryModules.ReactDOM.unmountComponentAtNode && Internal.LibraryModules.ReactDOM.unmountComponentAtNode(node)); + }; + BDFDB.ReactUtils = new Proxy({}, { + get: function (_, item) { + if (MyReact[item]) return MyReact[item]; + else if (LibraryModules.React[item]) return LibraryModules.React[item]; + else if (LibraryModules.ReactDOM[item]) return LibraryModules.ReactDOM[item]; + else if (LibraryModules.ReactPortal[item]) return LibraryModules.ReactPortal[item]; + else return null; + } + }); + + BDFDB.MessageUtils = {}; + BDFDB.MessageUtils.isSystemMessage = function (message) { + return message && !Internal.DiscordConstants.MessageTypeGroups.USER_MESSAGE.has(message.type) && (message.type !== Internal.DiscordConstants.MessageTypes.CHAT_INPUT_COMMAND || message.interaction == null); + }; + BDFDB.MessageUtils.rerenderAll = function (instant) { + BDFDB.TimeUtils.clear(BDFDB.MessageUtils.rerenderAll.timeout); + BDFDB.MessageUtils.rerenderAll.timeout = BDFDB.TimeUtils.timeout(_ => { + let channelId = Internal.LibraryStores.SelectedChannelStore.getChannelId(); + if (channelId) { + if (BDFDB.DMUtils.isDMChannel(channelId)) BDFDB.DMUtils.markAsRead(channelId); + else BDFDB.ChannelUtils.markAsRead(channelId); + } + let LayerProviderIns = BDFDB.ReactUtils.findOwner(document.querySelector(BDFDB.dotCN.chatcontent), {name: "LayerProvider", unlimited: true, up: true}); + let LayerProviderPrototype = BDFDB.ObjectUtils.get(LayerProviderIns, `${BDFDB.ReactUtils.instanceKey}.type.prototype`); + if (LayerProviderIns && LayerProviderPrototype) { + BDFDB.PatchUtils.patch({name: "BDFDB MessageUtils"}, LayerProviderPrototype, "render", {after: e => { + e.returnValue.props.children = typeof e.returnValue.props.children == "function" ? (_ => {return null;}) : []; + BDFDB.ReactUtils.forceUpdate(LayerProviderIns); + let messagesScroller = document.querySelector(BDFDB.dotCN.messagesscroller); + let scrollTop = messagesScroller && messagesScroller.scrollTop; + if (scrollTop) requestAnimationFrame(() => messagesScroller.scrollTo({top: scrollTop})); + }}, {once: true}); + BDFDB.ReactUtils.forceUpdate(LayerProviderIns); + } + }, instant ? 0 : 1000); + }; + BDFDB.MessageUtils.openMenu = function (message, e = mousePosition, slim = false) { + if (!message) return; + let channel = Internal.LibraryStores.ChannelStore.getChannel(message.channel_id); + if (!channel) return; + e = BDFDB.ListenerUtils.copyEvent(e.nativeEvent || e, (e.nativeEvent || e).currentTarget); + let type = slim ? "MessageSearchResultContextMenu" : "MessageContextMenu"; + let moduleFindData = InternalData.PatchModules[type] && InternalData.PatchModules[type].strings; + if (!moduleFindData) return; + let menu = BDFDB.ModuleUtils.findByString(moduleFindData, {noWarnings: true}); + if (menu) Internal.LibraryModules.ContextMenuUtils.openContextMenu(e, e2 => BDFDB.ReactUtils.createElement(menu.default || menu, Object.assign({}, e2, {message, channel}))); + else BDFDB.ModuleUtils.lazyLoadModuleImports(BDFDB.ModuleUtils.findByString(slim ? InternalData.PatchModules.SearchResult && InternalData.PatchModules.SearchResult.strings : InternalData.LibraryModules.MessageComponentUtils && InternalData.LibraryModules.MessageComponentUtils.strings)).then(_ => { + menu = BDFDB.ModuleUtils.findByString(moduleFindData); + if (menu) Internal.LibraryModules.ContextMenuUtils.openContextMenu(e, e2 => BDFDB.ReactUtils.createElement(menu.default || menu, Object.assign({}, e2, {message, channel}))); + }); + }; + + BDFDB.UserUtils = {}; + BDFDB.UserUtils.is = function (user) { + return user && user instanceof Internal.DiscordObjects.User; + }; + const myDataUser = Internal.LibraryStores.UserStore && Internal.LibraryStores.UserStore.getCurrentUser && Internal.LibraryStores.UserStore.getCurrentUser(); + if (myDataUser && BDFDB.UserUtils._id != myDataUser.id) BDFDB.UserUtils._id = myDataUser.id; + BDFDB.UserUtils.me = new Proxy(myDataUser || {}, { + get: function (list, item) { + const user = Internal.LibraryStores.UserStore && Internal.LibraryStores.UserStore.getCurrentUser && Internal.LibraryStores.UserStore.getCurrentUser(); + if (user && BDFDB.UserUtils._id != user.id) { + Cache.data = {}; + BDFDB.UserUtils._id = user.id; + } + return user ? user[item] : null; + } + }); + BDFDB.UserUtils.getStatus = function (id = BDFDB.UserUtils.me.id) { + id = typeof id == "number" ? id.toFixed() : id; + let activity = BDFDB.UserUtils.getActivity(id); + return activity && activity.type == Internal.DiscordConstants.ActivityTypes.STREAMING ? "streaming" : Internal.LibraryStores.PresenceStore.getStatus(id); + }; + BDFDB.UserUtils.getStatusColor = function (status, useColor) { + if (!Internal.DiscordConstants.Colors || !Internal.DiscordConstants.ColorsCSS) return null; + status = typeof status == "string" ? status.toLowerCase() : null; + let color = ""; + switch (status) { + case "online": color = (useColor ? Internal.DiscordConstants.Colors.GREEN_360 : BDFDB.DiscordConstants.ColorsCSS.STATUS_POSITIVE); break; + case "idle": color = (useColor ? Internal.DiscordConstants.Colors.YELLOW_300 : BDFDB.DiscordConstants.ColorsCSS.STATUS_WARNING); break; + case "dnd": color = (useColor ? Internal.DiscordConstants.Colors.RED_400 : BDFDB.DiscordConstants.ColorsCSS.STATUS_DANGER); break; + case "playing": color = (useColor ? Internal.DiscordConstants.Colors.BRAND : "var(--bdfdb-blurple)"); break; + case "listening": color = Internal.DiscordConstants.Colors.SPOTIFY; break; + case "streaming": color = Internal.DiscordConstants.Colors.TWITCH; break; + default: color = Internal.DiscordConstants.Colors.PRIMARY_400; break; + } + return (color || Internal.DiscordConstants.Colors.GREEN_360).replace(/calc\(.+\s*\*\s*([0-9\.\%]+)\)/g, "$1"); + }; + BDFDB.UserUtils.getActivity = function (id = BDFDB.UserUtils.me.id) { + for (let activity of Internal.LibraryStores.PresenceStore.getActivities(id)) if (activity.type != Internal.DiscordConstants.ActivityTypes.CUSTOM_STATUS) return activity; + return null; + }; + BDFDB.UserUtils.getCustomStatus = function (id = BDFDB.UserUtils.me.id) { + for (let activity of Internal.LibraryStores.PresenceStore.getActivities(id)) if (activity.type == Internal.DiscordConstants.ActivityTypes.CUSTOM_STATUS) return activity; + return null; + }; + BDFDB.UserUtils.getAvatar = function (id = BDFDB.UserUtils.me.id) { + let user = Internal.LibraryStores.UserStore.getUser(id); + if (!user) return window.location.origin + "/assets/1f0bfc0865d324c2587920a7d80c609b.png"; + else return ((user.avatar ? "" : window.location.origin) + Internal.LibraryModules.IconUtils.getUserAvatarURL(user)).split("?")[0]; + }; + BDFDB.UserUtils.getBanner = function (id = BDFDB.UserUtils.me.id, guildId = Internal.LibraryStores.SelectedGuildStore.getGuildId(), canAnimate = false) { + let displayProfile = Internal.LibraryModules.MemberDisplayUtils.getDisplayProfile(id, guildId); + return ((!guildId ? Internal.LibraryModules.IconUtils.getUserBannerURL({banner: displayProfile && displayProfile.banner, id, canAnimate}) : Internal.LibraryModules.IconUtils.getGuildMemberBannerURL({banner: displayProfile && displayProfile.banner, id, guildId, canAnimate})) || "").split("?")[0]; + }; + BDFDB.UserUtils.can = function (permission, id = BDFDB.UserUtils.me.id, channelId = Internal.LibraryStores.SelectedChannelStore.getChannelId()) { + if (!Internal.DiscordConstants.Permissions[permission]) BDFDB.LogUtils.warn([permission, "not found in Permissions"]); + else { + let channel = Internal.LibraryStores.ChannelStore.getChannel(channelId); + if (channel) return Internal.LibraryModules.PermissionRoleUtils.can({permission: Internal.DiscordConstants.Permissions[permission], user: id, context: channel}); + } + return false; + }; + BDFDB.UserUtils.openMenu = function (user, guildId, channelId, e = mousePosition) { + if (!user) return; + if (guildId && !channelId) channelId = (BDFDB.LibraryStores.GuildChannelStore.getDefaultChannel(guildId) || {}).id; + e = BDFDB.ListenerUtils.copyEvent(e.nativeEvent || e, (e.nativeEvent || e).currentTarget); + let type = channelId ? "UserMemberContextMenu" : "UserGenericContextMenu"; + let moduleFindData = InternalData.PatchModules[type] && InternalData.PatchModules[type].strings; + if (!moduleFindData) return; + let menu = BDFDB.ModuleUtils.findByString(moduleFindData, {noWarnings: true}); + if (menu) Internal.LibraryModules.ContextMenuUtils.openContextMenu(e, e2 => BDFDB.ReactUtils.createElement(menu.default || menu, Object.assign({}, e2, {user, guildId, channelId}))); + else BDFDB.ModuleUtils.lazyLoadModuleImports(BDFDB.ModuleUtils.findByString(channelId ? InternalData.LibraryModules.UserPopoutUtils && InternalData.LibraryModules.UserPopoutUtils.strings : InternalData.PatchModules.ParticipantsForSelectedParticipant && InternalData.PatchModules.ParticipantsForSelectedParticipant.strings)).then(_ => { + menu = BDFDB.ModuleUtils.findByString(moduleFindData); + if (menu) Internal.LibraryModules.ContextMenuUtils.openContextMenu(e, e2 => BDFDB.ReactUtils.createElement(menu.default || menu, Object.assign({}, e2, {user, guildId, channelId}))); + }); + }; + + BDFDB.GuildUtils = {}; + BDFDB.GuildUtils.is = function (guild) { + if (!BDFDB.ObjectUtils.is(guild)) return false; + let keys = Object.keys(guild); + return guild instanceof Internal.DiscordObjects.Guild || Object.keys(new Internal.DiscordObjects.Guild({})).every(key => keys.indexOf(key) > -1); + }; + BDFDB.GuildUtils.getIcon = function (id, size, canAnimate) { + let guild = Internal.LibraryStores.GuildStore.getGuild(id); + if (!guild || !guild.icon) return ""; + return size ? Internal.LibraryModules.IconUtils.getGuildIconURL({id, size, canAnimate, icon: guild.icon}) : Internal.LibraryModules.IconUtils.getGuildIconURL({id, size, canAnimate, icon: guild.icon}).split("?")[0]; + }; + BDFDB.GuildUtils.getBanner = function (id) { + let guild = Internal.LibraryStores.GuildStore.getGuild(id); + if (!guild || !guild.banner) return ""; + return Internal.LibraryModules.IconUtils.getGuildBannerURL(guild).split("?")[0]; + }; + BDFDB.GuildUtils.getFolder = function (id) { + return Internal.LibraryStores.SortedGuildStore.getGuildFolders().filter(n => n.folderId).find(n => n.guildIds.includes(id)); + }; + BDFDB.GuildUtils.openMenu = function (guild, e = mousePosition) { + if (!guild) return; + e = BDFDB.ListenerUtils.copyEvent(e.nativeEvent || e, (e.nativeEvent || e).currentTarget); + let moduleFindData = InternalData.PatchModules.GuildContextMenu && InternalData.PatchModules.GuildContextMenu.strings; + if (!moduleFindData) return; + let menu = BDFDB.ModuleUtils.findByString(moduleFindData, {noWarnings: true}); + if (menu) Internal.LibraryModules.ContextMenuUtils.openContextMenu(e, e2 => BDFDB.ReactUtils.createElement(menu.default || menu, Object.assign({}, e2, {guild}))); + else BDFDB.ModuleUtils.lazyLoadModuleImports(BDFDB.ModuleUtils.findByString(InternalData.PatchModules.GuildSidebar && InternalData.PatchModules.GuildSidebar.strings)).then(_ => { + menu = BDFDB.ModuleUtils.findByString(moduleFindData); + if (menu) Internal.LibraryModules.ContextMenuUtils.openContextMenu(e, e2 => BDFDB.ReactUtils.createElement(menu.default || menu, Object.assign({}, e2, {guild}))); + }); + }; + BDFDB.GuildUtils.markAsRead = function (guildIds) { + guildIds = [guildIds].flat(10).filter(id => id && typeof id == "string" && Internal.LibraryStores.GuildStore.getGuild(id)); + if (!guildIds) return; + let channels = guildIds.map(id => [BDFDB.ObjectUtils.toArray(Internal.LibraryStores.GuildChannelStore.getChannels(id)), BDFDB.ObjectUtils.toArray(Internal.LibraryStores.ActiveThreadsStore.getThreadsForGuild(id)).map(BDFDB.ObjectUtils.toArray).flat(), Internal.LibraryStores.GuildScheduledEventStore.getGuildScheduledEventsForGuild(id)]).flat(10).map(n => n && (n.channel && n.channel.id || n.id)).flat().filter(n => n); + if (channels.length) BDFDB.ChannelUtils.markAsRead(channels); + let eventChannels = guildIds.map(id => ({ + channelId: id, + readStateType: Internal.DiscordConstants.ReadStateTypes.GUILD_EVENT, + messageId: Internal.LibraryStores.ReadStateStore.lastMessageId(id, Internal.DiscordConstants.ReadStateTypes.GUILD_EVENT) + })).filter(n => n.messageId); + if (eventChannels.length) Internal.LibraryModules.AckUtils.bulkAck(eventChannels); + }; + + BDFDB.FolderUtils = {}; + BDFDB.FolderUtils.getId = function (div) { + if (!Node.prototype.isPrototypeOf(div) || !BDFDB.ReactUtils.getInstance(div)) return; + div = BDFDB.DOMUtils.getParent(BDFDB.dotCN.guildfolderwrapper, div); + if (!div) return; + return BDFDB.ReactUtils.findValue(div, "folderId", {up: true}); + }; + BDFDB.FolderUtils.getDefaultName = function (folderId) { + let folder = Internal.LibraryStores.SortedGuildStore.getGuildFolderById(folderId); + if (!folder) return ""; + let rest = 2 * Internal.DiscordConstants.MAX_GUILD_FOLDER_NAME_LENGTH; + let names = [], allNames = folder.guildIds.map(guildId => (Internal.LibraryStores.GuildStore.getGuild(guildId) || {}).name).filter(n => n); + for (let name of allNames) if (name.length < rest || names.length === 0) { + names.push(name); + rest -= name.length; + } + return names.join(", ") + (names.length < allNames.length ? ", ..." : ""); + }; + + BDFDB.ChannelUtils = {}; + BDFDB.ChannelUtils.is = function (channel) { + if (!BDFDB.ObjectUtils.is(channel)) return false; + let keys = Object.keys(channel); + return channel instanceof Internal.DiscordObjects.Channel || Object.keys(new Internal.DiscordObjects.Channel({})).every(key => keys.indexOf(key) > -1); + }; + BDFDB.ChannelUtils.isTextChannel = function (channelOrId) { + let channel = typeof channelOrId == "string" ? Internal.LibraryStores.ChannelStore.getChannel(channelOrId) : channelOrId; + return BDFDB.ObjectUtils.is(channel) && (channel.type == Internal.DiscordConstants.ChannelTypes.GUILD_TEXT || channel.type == Internal.DiscordConstants.ChannelTypes.GUILD_STORE || channel.type == Internal.DiscordConstants.ChannelTypes.GUILD_ANNOUNCEMENT); + }; + BDFDB.ChannelUtils.isThread = function (channelOrId) { + let channel = typeof channelOrId == "string" ? Internal.LibraryStores.ChannelStore.getChannel(channelOrId) : channelOrId; + return channel && channel.isThread(); + }; + BDFDB.ChannelUtils.isForumPost = function (channelOrId) { + let channel = typeof channelOrId == "string" ? Internal.LibraryStores.ChannelStore.getChannel(channelOrId) : channelOrId; + return channel && channel.parentChannelThreadType && channel.parentChannelThreadType == Internal.DiscordConstants.ChannelTypes.GUILD_FORUM; + }; + BDFDB.ChannelUtils.isEvent = function (channelOrId) { + let channel = typeof channelOrId == "string" ? Internal.LibraryStores.GuildScheduledEventStore.getGuildScheduledEvent(channelOrId) : channelOrId; + return channel && Internal.LibraryStores.GuildScheduledEventStore.getGuildScheduledEvent(channel.id) && true; + }; + BDFDB.ChannelUtils.markAsRead = function (channelIds) { + let unreadChannels = [channelIds].flat(10).filter(id => id && typeof id == "string" && (BDFDB.LibraryStores.ChannelStore.getChannel(id) || {}).type != Internal.DiscordConstants.ChannelTypes.GUILD_CATEGORY && (Internal.LibraryStores.ReadStateStore.hasUnread(id) || Internal.LibraryStores.ReadStateStore.getMentionCount(id) > 0)).map(id => ({ + channelId: id, + readStateType: Internal.DiscordConstants.ReadStateTypes.CHANNEL, + messageId: Internal.LibraryStores.ReadStateStore.lastMessageId(id) + })); + if (unreadChannels.length) Internal.LibraryModules.AckUtils.bulkAck(unreadChannels); + }; + BDFDB.ChannelUtils.rerenderAll = function (instant) { + BDFDB.TimeUtils.clear(BDFDB.ChannelUtils.rerenderAll.timeout); + BDFDB.ChannelUtils.rerenderAll.timeout = BDFDB.TimeUtils.timeout(_ => { + let ChannelsIns = BDFDB.ReactUtils.findOwner(document.querySelector(BDFDB.dotCN.guildchannels), {name: "ChannelsList", unlimited: true}); + if (!ChannelsIns) return; + else { + if (ChannelsIns && ChannelsIns.props && ChannelsIns.props.guildChannels.categories && Object.keys(ChannelsIns.props.guildChannels.categories).length) { + let category = ChannelsIns.props.guildChannels.categories[Object.keys(ChannelsIns.props.guildChannels.categories)[0]]; + category.isCollapsed ? BDFDB.LibraryModules.CategoryCollapseUtils.categoryCollapse(category.id) : BDFDB.LibraryModules.CategoryCollapseUtils.categoryExpand(category.id); + } + else { + let ChannelsPrototype = BDFDB.ObjectUtils.get(ChannelsIns, `${BDFDB.ReactUtils.instanceKey}.type.prototype`); + if (ChannelsIns && ChannelsPrototype) { + BDFDB.PatchUtils.patch({name: "BDFDB ChannelUtils"}, ChannelsPrototype, "render", {after: e => { + e.returnValue.props.children = typeof e.returnValue.props.children == "function" ? (_ => {return null;}) : []; + BDFDB.ReactUtils.forceUpdate(ChannelsIns); + }}, {once: true}); + BDFDB.ReactUtils.forceUpdate(ChannelsIns); + } + } + } + }, instant ? 0 : 1000); + }; + + BDFDB.DMUtils = {}; + BDFDB.DMUtils.isDMChannel = function (id) { + let channel = Internal.LibraryStores.ChannelStore.getChannel(id); + return BDFDB.ObjectUtils.is(channel) && (channel.isDM() || channel.isGroupDM()); + }; + BDFDB.DMUtils.getIcon = function (id) { + let channel = Internal.LibraryStores.ChannelStore.getChannel(id); + if (!channel) return ""; + if (!channel.icon) return channel.isDM() ? BDFDB.UserUtils.getAvatar(channel.recipients[0]) : (channel.isGroupDM() ? window.location.origin + Internal.LibraryModules.IconUtils.getChannelIconURL(channel).split("?")[0] : null); + return Internal.LibraryModules.IconUtils.getChannelIconURL(channel).split("?")[0]; + }; + BDFDB.DMUtils.markAsRead = function (dmIds) { + let unreadDMs = [dmIds].flat(10).filter(id => id && typeof id == "string" && BDFDB.DMUtils.isDMChannel(id) && (Internal.LibraryStores.ReadStateStore.hasUnread(id) || Internal.LibraryStores.ReadStateStore.getMentionCount(id) > 0)); + if (unreadDMs.length) for (let i in unreadDMs) BDFDB.TimeUtils.timeout(_ => Internal.LibraryModules.AckUtils.ack(unreadDMs[i]), i * 1000); + }; + + BDFDB.ColorUtils = {}; + BDFDB.ColorUtils.convert = function (color, conv, type) { + if (typeof color == "string" && color.indexOf("var(--") == 0) return color; + if (BDFDB.ObjectUtils.is(color)) { + let newColor = {}; + for (let pos in color) newColor[pos] = BDFDB.ColorUtils.convert(color[pos], conv, type); + return newColor; + } + else { + if (typeof color == "string") color = color.replace(/calc\(.+\s*\*\s*([0-9\.\%]+)\)/g, "$1"); + conv = conv === undefined || !conv ? conv = "RGBCOMP" : conv.toUpperCase(); + type = type === undefined || !type || !["RGB", "RGBA", "RGBCOMP", "HSL", "HSLA", "HSLCOMP", "HSV", "HSVA", "HSVCOMP", "HEX", "HEXA", "INT"].includes(type.toUpperCase()) ? BDFDB.ColorUtils.getType(color) : type.toUpperCase(); + if (conv == "RGBCOMP") { + switch (type) { + case "RGBCOMP": + var rgbComp = [].concat(color); + if (rgbComp.length == 3) return processRGB(rgbComp); + else if (rgbComp.length == 4) { + let a = processA(rgbComp.pop()); + return processRGB(rgbComp).concat(a); + } + break; + case "RGB": + return processRGB(color.replace(/\s/g, "").slice(4, -1).split(",")); + case "RGBA": + var rgbComp = color.replace(/\s/g, "").slice(5, -1).split(","); + var a = processA(rgbComp.pop()); + return processRGB(rgbComp).concat(a); + case "HSLCOMP": + var hslComp = [].concat(color); + if (hslComp.length == 3) return BDFDB.ColorUtils.convert(`hsl(${processHSX(hslComp).join(",")})`, "RGBCOMP"); + else if (hslComp.length == 4) { + let a = processA(hslComp.pop()); + return BDFDB.ColorUtils.convert(`hsl(${processHSX(hslComp).join(",")})`, "RGBCOMP").concat(a); + } + break; + case "HSL": + var hslComp = processHSX(color.replace(/\s/g, "").slice(4, -1).split(",")); + var r, g, b, m, c, x, p, q; + var h = hslComp[0], s = processPercentage(hslComp[1]), l = processPercentage(hslComp[2]); + var a = s * Math.min(l, 1-l); + var f = (n, k = (n+h / 30) % 12) => parseInt((l - a * Math.max(Math.min(k-3, 9-k, 1), -1)) * 255); + return [f(0), f(8), f(4)]; + case "HSLA": + var hslComp = color.replace(/\s/g, "").slice(5, -1).split(","); + return BDFDB.ColorUtils.convert(`hsl(${hslComp.slice(0, 3).join(",")})`, "RGBCOMP").concat(processA(hslComp.pop())); + case "HSVCOMP": + var hsvComp = [].concat(color); + if (hsvComp.length == 3) return BDFDB.ColorUtils.convert(`hsv(${processHSX(hsvComp).join(",")})`, "RGBCOMP"); + else if (hsvComp.length == 4) { + let a = processA(hsvComp.pop()); + return BDFDB.ColorUtils.convert(`hsv(${processHSX(hsvComp).join(",")})`, "RGBCOMP").concat(a); + } + break; + case "HSV": + var hsvComp = processHSX(color.replace(/\s/g, "").slice(4, -1).split(",")); + var r, g, b, i, f, p, q, t; + var h = hsvComp[0] / 360, s = processPercentage(hsvComp[1]), v = processPercentage(hsvComp[2]); + i = Math.floor(h * 6), f = h * 6 - i, p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; + } + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + case "HSVA": + var hsvComp = color.replace(/\s/g, "").slice(5, -1).split(","); + return BDFDB.ColorUtils.convert(`hsv(${hsvComp.slice(0, 3).join(",")})`, "RGBCOMP").concat(processA(hsvComp.pop())); + case "HEX": + var hex = /^#([a-f\d]{1})([a-f\d]{1})([a-f\d]{1})$|^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color); + return [parseInt(hex[1] + hex[1] || hex[4], 16), parseInt(hex[2] + hex[2] || hex[5], 16), parseInt(hex[3] + hex[3] || hex[6], 16)]; + case "HEXA": + var hex = /^#([a-f\d]{1})([a-f\d]{1})([a-f\d]{1})([a-f\d]{1})$|^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color); + return [parseInt(hex[1] + hex[1] || hex[5], 16), parseInt(hex[2] + hex[2] || hex[6], 16), parseInt(hex[3] + hex[3] || hex[7], 16), Math.floor(BDFDB.NumberUtils.mapRange([0, 255], [0, 100], parseInt(hex[4] + hex[4] || hex[8], 16)))/100]; + case "INT": + color = processINT(color); + return [parseInt(color >> 16 & 255), parseInt(color >> 8 & 255), parseInt(color & 255)]; + default: + return null; + } + } + else { + if (conv && type && (conv.indexOf("HSL") == 0 && type.indexOf("HSL") == 0 || conv.indexOf("HSV") == 0 && type.indexOf("HSV") == 0)) { + let name = type.indexOf("HSL") == 0 ? "HSL" : "HSV"; + if (type == `${name}COMP`) { + let comp = [].concat(color); + switch (conv) { + case `${name}COMP`: + if (comp.length == 3) return processHSX(comp); + else if (comp.length == 4) { + var a = processA(comp.pop()); + return processHSX(comp).concat(a); + } + break; + case name: + return `${name.toLowerCase()}(${processHSX(comp.slice(0, 3)).join(",")})`; + case `${name}A`: + comp = comp.slice(0, 4); + var a = comp.length == 4 ? processA(comp.pop()) : 1; + return `${name.toLowerCase()}a(${processHSX(comp).concat(a).join(",")})`; + } + } + return BDFDB.ColorUtils.convert(color.replace(/\s/g, "").slice(color.toUpperCase().indexOf("A") == 3 ? 5 : 4, -1).split(","), conv, `${name}COMP`); + } + else { + let rgbComp = type == "RGBCOMP" ? [].concat(color) : BDFDB.ColorUtils.convert(color, "RGBCOMP", type); + if (rgbComp) switch (conv) { + case "RGB": + return `rgb(${processRGB(rgbComp.slice(0, 3)).join(",")})`; + case "RGBA": + rgbComp = rgbComp.slice(0, 4); + var a = rgbComp.length == 4 ? processA(rgbComp.pop()) : 1; + return `rgba(${processRGB(rgbComp).concat(a).join(",")})`; + case "HSLCOMP": + var a = rgbComp.length == 4 ? processA(rgbComp.pop()) : null; + var hslComp = processHSX(BDFDB.ColorUtils.convert(rgbComp, "HSL").replace(/\s/g, "").split(",")); + return a != null ? hslComp.concat(a) : hslComp; + case "HSL": + var r = processC(rgbComp[0]) / 255, g = processC(rgbComp[1]) / 255, b = processC(rgbComp[2]) / 255; + var max = Math.max(r, g, b), min = Math.min(r, g, b); + + var h, s, l; + h = s = l = (max + min) / 2; + + if (max === min) return `hsl(${processHSX([0, 0, l * 100]).join(",")})`; + + var dif = max - min; + s = l >= 0.5 ? dif / (2 - (max + min)) : dif / (max + min); + switch (max) { + case r: h = ((g - b) / dif + 0) * 60; break; + case g: h = ((b - r) / dif + 2) * 60; break; + case b: h = ((r - g) / dif + 4) * 60; break; + } + return `hsl(${processHSX([Math.round(h * 360), s * 100, l * 100]).join(",")})`; + case "HSLA": + var a = rgbComp.length == 4 ? processA(rgbComp.pop()) : 1; + return `hsla(${BDFDB.ColorUtils.convert(rgbComp, "HSL").slice(4, -1).split(",").concat(a).join(",")})`; + case "HSVCOMP": + var a = rgbComp.length == 4 ? processA(rgbComp.pop()) : null; + var hsvComp = processHSX(BDFDB.ColorUtils.convert(rgbComp, "HSV").replace(/\s/g, "").split(",")); + return a != null ? hsvComp.concat(a) : hsvComp; + case "HSV": + var r = processC(rgbComp[0]), g = processC(rgbComp[1]), b = processC(rgbComp[2]); + var max = Math.max(r, g, b), min = Math.min(r, g, b), dif = max - min, h, s = max === 0 ? 0 : dif / max, v = max / 255; + switch (max) { + case min: h = 0; break; + case r: h = g - b + dif * (g < b ? 6 : 0); h /= 6 * dif; break; + case g: h = b - r + dif * 2; h /= 6 * dif; break; + case b: h = r - g + dif * 4; h /= 6 * dif; break; + } + return `hsv(${processHSX([Math.round(h * 360), s * 100, v * 100]).join(",")})`; + case "HSVA": + var a = rgbComp.length == 4 ? processA(rgbComp.pop()) : 1; + return `hsva(${BDFDB.ColorUtils.convert(rgbComp, "HSV").slice(4, -1).split(",").concat(a).join(",")})`; + case "HEX": + return ("#" + (0x1000000 + (rgbComp[2] | rgbComp[1] << 8 | rgbComp[0] << 16)).toString(16).slice(1)).toUpperCase(); + case "HEXA": + return ("#" + (0x1000000 + (rgbComp[2] | rgbComp[1] << 8 | rgbComp[0] << 16)).toString(16).slice(1) + (0x100 + Math.round(BDFDB.NumberUtils.mapRange([0, 100], [0, 255], processA(rgbComp[3]) * 100))).toString(16).slice(1)).toUpperCase(); + case "INT": + return processINT(rgbComp[2] | rgbComp[1] << 8 | rgbComp[0] << 16); + default: + return null; + } + } + } + } + return null; + function processC (c) { + if (c == null) return 255; + else { + c = parseInt(c.toString().replace(/[^0-9\-]/g, "")); + return isNaN(c) || c > 255 ? 255 : c < 0 ? 0 : c; + } + }; + function processRGB (comp) { + return [].concat(comp).map(processC); + }; + function processA (a) { + if (a == null) return 1; + else { + a = a.toString(); + a = (a.indexOf("%") > -1 ? 0.01 : 1) * parseFloat(a.replace(/[^0-9\.\-]/g, "")); + return isNaN(a) || a > 1 ? 1 : a < 0 ? 0 : a; + } + }; + function processPercentage (p) { + if (p == null) return 1; + else return p.indexOf("%") > -1 ? parseFloat(p)/100 : p; + }; + function processSL (sl) { + if (sl == null) return "100%"; + else { + sl = parseFloat(sl.toString().replace(/[^0-9\.\-]/g, "")); + return (isNaN(sl) || sl > 100 ? 100 : sl < 0 ? 0 : sl) + "%"; + } + }; + function processHSX (comp) { + comp = [].concat(comp); + let h = parseFloat(comp.shift().toString().replace(/[^0-9\.\-]/g, "")); + h = isNaN(h) || h > 360 ? 360 : h < 0 ? 0 : h; + return [h].concat(comp.map(processSL)); + }; + function processINT (c) { + if (c == null) return 16777215; + else { + c = parseInt(c.toString().replace(/[^0-9]/g, "")); + return isNaN(c) || c > 16777215 ? 16777215 : c < 0 ? 0 : c; + } + }; + }; + BDFDB.ColorUtils.setAlpha = function (color, a, conv) { + if (typeof color == "string" && color.indexOf("var(--") == 0) return color; + if (BDFDB.ObjectUtils.is(color)) { + let newcolor = {}; + for (let pos in color) newcolor[pos] = BDFDB.ColorUtils.setAlpha(color[pos], a, conv); + return newcolor; + } + else { + let rgbComp = BDFDB.ColorUtils.convert(color, "RGBCOMP"); + if (rgbComp) { + a = a.toString(); + a = (a.indexOf("%") > -1 ? 0.01 : 1) * parseFloat(a.replace(/[^0-9\.\-]/g, "")); + a = isNaN(a) || a > 1 ? 1 : a < 0 ? 0 : a; + rgbComp[3] = a; + conv = (conv || BDFDB.ColorUtils.getType(color)).toUpperCase(); + conv = conv == "RGB" || conv == "HSL" || conv == "HEX" ? conv + "A" : conv; + return BDFDB.ColorUtils.convert(rgbComp, conv); + } + } + return null; + }; + BDFDB.ColorUtils.getAlpha = function (color) { + let rgbComp = BDFDB.ColorUtils.convert(color, "RGBCOMP"); + if (rgbComp) { + if (rgbComp.length == 3) return 1; + else if (rgbComp.length == 4) { + let a = rgbComp[3].toString(); + a = (a.indexOf("%") > -1 ? 0.01 : 1) * parseFloat(a.replace(/[^0-9\.\-]/g, "")); + return isNaN(a) || a > 1 ? 1 : a < 0 ? 0 : a; + } + } + return null; + }; + BDFDB.ColorUtils.change = function (color, value, conv) { + if (typeof color == "string" && color.indexOf("var(--") == 0) return color; + value = parseFloat(value); + if (color != null && typeof value == "number" && !isNaN(value)) { + if (BDFDB.ObjectUtils.is(color)) { + let newColor = {}; + for (let pos in color) newColor[pos] = BDFDB.ColorUtils.change(color[pos], value, conv); + return newColor; + } + else { + let rgbComp = BDFDB.ColorUtils.convert(color, "RGBCOMP"); + if (rgbComp) { + let a = BDFDB.ColorUtils.getAlpha(rgbComp); + if (parseInt(value) !== value) { + value = value.toString(); + value = (value.indexOf("%") > -1 ? 0.01 : 1) * parseFloat(value.replace(/[^0-9\.\-]/g, "")); + value = isNaN(value) ? 0 : value; + return BDFDB.ColorUtils.convert([].concat(rgbComp).slice(0, 3).map(c => { + c = Math.round(c * (1 + value)); + return c > 255 ? 255 : c < 0 ? 0 : c; + }).concat(a), conv || BDFDB.ColorUtils.getType(color)); + } + else return BDFDB.ColorUtils.convert([].concat(rgbComp).slice(0, 3).map(c => { + c = Math.round(c + value); + return c > 255 ? 255 : c < 0 ? 0 : c; + }).concat(a), conv || BDFDB.ColorUtils.getType(color)); + } + } + } + return null; + }; + BDFDB.ColorUtils.invert = function (color, conv) { + if (typeof color == "string" && color.indexOf("var(--") == 0) return color; + if (BDFDB.ObjectUtils.is(color)) { + let newColor = {}; + for (let pos in color) newColor[pos] = BDFDB.ColorUtils.invert(color[pos], conv); + return newColor; + } + else { + let comp = BDFDB.ColorUtils.convert(color, "RGBCOMP"); + if (comp) return BDFDB.ColorUtils.convert([255 - comp[0], 255 - comp[1], 255 - comp[2]], conv || BDFDB.ColorUtils.getType(color)); + } + return null; + }; + BDFDB.ColorUtils.compare = function (color1, color2) { + if (color1 && color2) { + color1 = BDFDB.ColorUtils.convert(color1, "RGBA"); + color2 = BDFDB.ColorUtils.convert(color2, "RGBA"); + if (color1 && color2) return BDFDB.equals(color1, color2); + } + return null; + }; + BDFDB.ColorUtils.isBright = function (color, compare = 160) { + if (!BDFDB.ColorUtils.getType(color)) return false; + color = BDFDB.ColorUtils.convert(color, "RGBCOMP"); + if (!color) return false; + return parseInt(compare) < Math.sqrt(0.299 * color[0]**2 + 0.587 * color[1]**2 + 0.144 * color[2]**2); + }; + BDFDB.ColorUtils.getType = function (color) { + if (color !== null) { + if (typeof color === "object" && (color.length == 3 || color.length == 4)) { + if (isRGB(color)) return "RGBCOMP"; + else if (isHSL(color)) return "HSLCOMP"; + } + else if (typeof color === "string") { + color = color.replace(/calc\(.+\s*\*\s*([0-9\.\%]+)\)/g, "$1"); + if (/^#[a-f\d]{3}$|^#[a-f\d]{6}$/i.test(color)) return "HEX"; + else if (/^#[a-f\d]{4}$|^#[a-f\d]{8}$/i.test(color)) return "HEXA"; + else { + color = color.toUpperCase(); + let comp = color.replace(/[^0-9\.\-\,\%]/g, "").split(","); + if (color.indexOf("RGB(") == 0 && comp.length == 3 && isRGB(comp)) return "RGB"; + else if (color.indexOf("RGBA(") == 0 && comp.length == 4 && isRGB(comp)) return "RGBA"; + else if (color.indexOf("HSL(") == 0 && comp.length == 3 && isHSL(comp)) return "HSL"; + else if (color.indexOf("HSLA(") == 0 && comp.length == 4 && isHSL(comp)) return "HSLA"; + else if (color.indexOf("HSV(") == 0 && comp.length == 3 && isHSL(comp)) return "HSV"; + else if (color.indexOf("HSVA(") == 0 && comp.length == 4 && isHSL(comp)) return "HSVA"; + } + } + else if (typeof color === "number" && parseInt(color) == color && color > -1 && color < 16777216) return "INT"; + } + return null; + function isRGB(comp) {return comp.slice(0, 3).every(rgb => rgb.toString().indexOf("%") == -1 && parseFloat(rgb) == parseInt(rgb));}; + function isHSL(comp) {return comp.slice(1, 3).every(hsl => hsl.toString().indexOf("%") == hsl.length - 1);}; + }; + BDFDB.ColorUtils.createGradient = function (colorObj, direction = "to right") { + let gradientString = "linear-gradient(" + direction; + for (let pos of Object.keys(colorObj).sort()) { + let color = BDFDB.ColorUtils.convert(colorObj[pos], "RGBA"); + gradientString += color ? `, ${color} ${pos*100}%` : '' + } + return gradientString += ")"; + }; + + BDFDB.DOMUtils = {}; + BDFDB.DOMUtils.getSelection = function () { + let selection = document.getSelection(); + return selection && selection.anchorNode ? selection.getRangeAt(0).toString() : ""; + }; + BDFDB.DOMUtils.addClass = function (eles, ...classes) { + if (!eles || !classes) return; + for (let ele of [eles].map(n => NodeList.prototype.isPrototypeOf(n) ? Array.from(n) : n).flat(10).filter(n => n)) { + if (Node.prototype.isPrototypeOf(ele)) add(ele); + else if (NodeList.prototype.isPrototypeOf(ele)) for (let e of ele) add(e); + else if (typeof ele == "string") for (let e of ele.split(",")) if (e && (e = e.trim())) for (let n of document.querySelectorAll(e)) add(n); + } + function add(node) { + if (node && node.classList) for (let cla of classes) for (let cl of [cla].flat(10).filter(n => n)) if (typeof cl == "string") for (let c of cl.split(" ")) if (c) node.classList.add(c); + } + }; + BDFDB.DOMUtils.removeClass = function (eles, ...classes) { + if (!eles || !classes) return; + for (let ele of [eles].map(n => NodeList.prototype.isPrototypeOf(n) ? Array.from(n) : n).flat(10).filter(n => n)) { + if (Node.prototype.isPrototypeOf(ele)) remove(ele); + else if (NodeList.prototype.isPrototypeOf(ele)) for (let e of ele) remove(e); + else if (typeof ele == "string") for (let e of ele.split(",")) if (e && (e = e.trim())) for (let n of document.querySelectorAll(e)) remove(n); + } + function remove(node) { + if (node && node.classList) for (let cla of classes) for (let cl of [cla].flat(10).filter(n => n)) if (typeof cl == "string") for (let c of cl.split(" ")) if (c) node.classList.remove(c); + } + }; + BDFDB.DOMUtils.toggleClass = function (eles, ...classes) { + if (!eles || !classes) return; + var force = classes.pop(); + if (typeof force != "boolean") { + classes.push(force); + force = undefined; + } + if (!classes.length) return; + for (let ele of [eles].map(n => NodeList.prototype.isPrototypeOf(n) ? Array.from(n) : n).flat(10).filter(n => n)) { + if (Node.prototype.isPrototypeOf(ele)) toggle(ele); + else if (NodeList.prototype.isPrototypeOf(ele)) for (let e of ele) toggle(e); + else if (typeof ele == "string") for (let e of ele.split(",")) if (e && (e = e.trim())) for (let n of document.querySelectorAll(e)) toggle(n); + } + function toggle(node) { + if (node && node.classList) for (let cla of classes) for (let cl of [cla].flat(10).filter(n => n)) if (typeof cl == "string") for (let c of cl.split(" ")) if (c) node.classList.toggle(c, force); + } + }; + BDFDB.DOMUtils.containsClass = function (eles, ...classes) { + if (!eles || !classes) return; + let all = classes.pop(); + if (typeof all != "boolean") { + classes.push(all); + all = true; + } + if (!classes.length) return; + let contained = undefined; + for (let ele of [eles].map(n => NodeList.prototype.isPrototypeOf(n) ? Array.from(n) : n).flat(10).filter(n => n)) { + if (Node.prototype.isPrototypeOf(ele)) contains(ele); + else if (NodeList.prototype.isPrototypeOf(ele)) for (let e of ele) contains(e); + else if (typeof ele == "string") for (let c of ele.split(",")) if (c && (c = c.trim())) for (let n of document.querySelectorAll(c)) contains(n); + } + return contained; + function contains(node) { + if (node && node.classList) for (let cla of classes) if (typeof cla == "string") for (let c of cla.split(" ")) if (c) { + if (contained === undefined) contained = all; + if (all && !node.classList.contains(c)) contained = false; + if (!all && node.classList.contains(c)) contained = true; + } + } + }; + BDFDB.DOMUtils.formatClassName = function (...classes) { + return BDFDB.ArrayUtils.removeCopies(classes.flat(10).filter(n => n).join(" ").split(" ")).join(" ").trim(); + }; + BDFDB.DOMUtils.removeClassFromDOM = function (...classes) { + for (let c of classes.flat(10).filter(n => n)) if (typeof c == "string") for (let a of c.split(",")) if (a && (a = a.replace(/\.|\s/g, ""))) BDFDB.DOMUtils.removeClass(document.querySelectorAll("." + a), a); + }; + BDFDB.DOMUtils.show = function (...eles) { + BDFDB.DOMUtils.toggle(...eles, true); + }; + BDFDB.DOMUtils.hide = function (...eles) { + BDFDB.DOMUtils.toggle(...eles, false); + }; + BDFDB.DOMUtils.toggle = function (...eles) { + if (!eles) return; + let force = eles.pop(); + if (typeof force != "boolean") { + eles.push(force); + force = undefined; + } + if (!eles.length) return; + for (let ele of eles.flat(10).filter(n => n)) { + if (Node.prototype.isPrototypeOf(ele)) toggle(ele); + else if (NodeList.prototype.isPrototypeOf(ele)) for (let node of ele) toggle(node); + else if (typeof ele == "string") for (let c of ele.split(",")) if (c && (c = c.trim())) for (let node of document.querySelectorAll(c)) toggle(node); + } + function toggle(node) { + if (!node || !Node.prototype.isPrototypeOf(node)) return; + let hide = force === undefined ? !BDFDB.DOMUtils.isHidden(node) : !force; + if (hide) { + let display = node.style.getPropertyValue("display"); + if (display && display != "none") node.BDFDBhideDisplayState = { + display: display, + important: (` ${node.style.cssText} `.split(` display: ${display}`)[1] || "").trim().indexOf("!important") == 0 + }; + node.style.setProperty("display", "none", "important"); + } + else { + if (node.BDFDBhideDisplayState) { + node.style.setProperty("display", node.BDFDBhideDisplayState.display, node.BDFDBhideDisplayState.important ? "important" : ""); + delete node.BDFDBhideDisplayState; + } + else node.style.removeProperty("display"); + } + } + }; + BDFDB.DOMUtils.isHidden = function (node) { + if (Node.prototype.isPrototypeOf(node) && node.nodeType != Node.TEXT_NODE) return getComputedStyle(node, null).getPropertyValue("display") == "none"; + }; + BDFDB.DOMUtils.remove = function (...eles) { + for (let ele of eles.flat(10).filter(n => n)) { + if (Node.prototype.isPrototypeOf(ele)) ele.remove(); + else if (NodeList.prototype.isPrototypeOf(ele)) { + let nodes = Array.from(ele); + while (nodes.length) nodes.shift().remove(); + } + else if (typeof ele == "string") for (let c of ele.split(",")) if (c && (c = c.trim())) { + let nodes = Array.from(document.querySelectorAll(c)); + while (nodes.length) nodes.shift().remove(); + } + } + }; + BDFDB.DOMUtils.create = function (html) { + if (typeof html != "string" || !html.trim()) return null; + let template = document.createElement("template"); + try {template.innerHTML = html.replace(/(?[\t\r\n]+<(?!pre)/g, "><");} + catch (err) {template.innerHTML = html.replace(/>[\t\r\n]+<(?!pre)/g, "><");} + if (template.content.childNodes.length == 1) return template.content.firstElementChild || template.content.firstChild; + else { + let wrapper = document.createElement("span"); + let nodes = Array.from(template.content.childNodes); + while (nodes.length) wrapper.appendChild(nodes.shift()); + return wrapper; + } + }; + BDFDB.DOMUtils.getParent = function (listOrSelector, node) { + let parent = null; + if (Node.prototype.isPrototypeOf(node) && listOrSelector) { + let list = NodeList.prototype.isPrototypeOf(listOrSelector) ? listOrSelector : typeof listOrSelector == "string" ? document.querySelectorAll(listOrSelector) : null; + if (list) for (let listNode of list) if (listNode.contains(node)) { + parent = listNode; + break; + } + } + return parent; + }; + BDFDB.DOMUtils.setText = function (node, stringOrNode) { + if (!node || !Node.prototype.isPrototypeOf(node)) return; + let textnode = node.nodeType == Node.TEXT_NODE ? node : null; + if (!textnode) for (let child of node.childNodes) if (child.nodeType == Node.TEXT_NODE || BDFDB.DOMUtils.containsClass(child, "BDFDB-textnode")) { + textnode = child; + break; + } + if (textnode) { + if (Node.prototype.isPrototypeOf(stringOrNode) && stringOrNode.nodeType != Node.TEXT_NODE) { + BDFDB.DOMUtils.addClass(stringOrNode, "BDFDB-textnode"); + node.replaceChild(stringOrNode, textnode); + } + else if (Node.prototype.isPrototypeOf(textnode) && textnode.nodeType != Node.TEXT_NODE) node.replaceChild(document.createTextNode(stringOrNode), textnode); + else textnode.textContent = stringOrNode; + } + else node.appendChild(Node.prototype.isPrototypeOf(stringOrNode) ? stringOrNode : document.createTextNode(stringOrNode)); + }; + BDFDB.DOMUtils.getText = function (node) { + if (!node || !Node.prototype.isPrototypeOf(node)) return; + for (let child of node.childNodes) if (child.nodeType == Node.TEXT_NODE) return child.textContent; + }; + BDFDB.DOMUtils.getRects = function (node) { + let rects = {}; + if (Node.prototype.isPrototypeOf(node) && node.nodeType != Node.TEXT_NODE) { + let hideNode = node; + while (hideNode) { + let hidden = BDFDB.DOMUtils.isHidden(hideNode); + if (hidden) { + BDFDB.DOMUtils.toggle(hideNode, true); + hideNode.BDFDBgetRectsHidden = true; + } + hideNode = hideNode.parentElement; + } + rects = node.getBoundingClientRect(); + hideNode = node; + while (hideNode) { + if (hideNode.BDFDBgetRectsHidden) { + BDFDB.DOMUtils.toggle(hideNode, false); + delete hideNode.BDFDBgetRectsHidden; + } + hideNode = hideNode.parentElement; + } + } + return rects; + }; + BDFDB.DOMUtils.getHeight = function (node) { + if (Node.prototype.isPrototypeOf(node) && node.nodeType != Node.TEXT_NODE) { + let rects = BDFDB.DOMUtils.getRects(node); + let style = getComputedStyle(node); + return rects.height + parseInt(style.marginTop) + parseInt(style.marginBottom); + } + return 0; + }; + BDFDB.DOMUtils.getInnerHeight = function (node) { + if (Node.prototype.isPrototypeOf(node) && node.nodeType != Node.TEXT_NODE) { + let rects = BDFDB.DOMUtils.getRects(node); + let style = getComputedStyle(node); + return rects.height - parseInt(style.paddingTop) - parseInt(style.paddingBottom); + } + return 0; + }; + BDFDB.DOMUtils.getWidth = function (node) { + if (Node.prototype.isPrototypeOf(node) && node.nodeType != Node.TEXT_NODE) { + let rects = BDFDB.DOMUtils.getRects(node); + let style = getComputedStyle(node); + return rects.width + parseInt(style.marginLeft) + parseInt(style.marginRight); + } + return 0; + }; + BDFDB.DOMUtils.getInnerWidth = function (node) { + if (Node.prototype.isPrototypeOf(node) && node.nodeType != Node.TEXT_NODE) { + let rects = BDFDB.DOMUtils.getRects(node); + let style = getComputedStyle(node); + return rects.width - parseInt(style.paddingLeft) - parseInt(style.paddingRight); + } + return 0; + }; + BDFDB.DOMUtils.appendWebScript = function (url, container) { + if (typeof url != "string") return; + if (!container && !document.body.querySelector("bd-head bd-scripts")) document.body.appendChild(BDFDB.DOMUtils.create(``)); + container = container || document.body.querySelector("bd-head bd-scripts") || document.body; + container = Node.prototype.isPrototypeOf(container) ? container : document.body; + BDFDB.DOMUtils.removeWebScript(url, container); + let script = document.createElement("script"); + script.src = url; + container.appendChild(script); + }; + BDFDB.DOMUtils.removeWebScript = function (url, container) { + if (typeof url != "string") return; + container = container || document.body.querySelector("bd-head bd-scripts") || document.body; + container = Node.prototype.isPrototypeOf(container) ? container : document.body; + BDFDB.DOMUtils.remove(container.querySelectorAll(`script[src="${url}"]`)); + }; + BDFDB.DOMUtils.appendWebStyle = function (url, container) { + if (typeof url != "string") return; + if (!container && !document.body.querySelector("bd-head bd-styles")) document.body.appendChild(BDFDB.DOMUtils.create(``)); + container = container || document.body.querySelector("bd-head bd-styles") || document.body; + container = Node.prototype.isPrototypeOf(container) ? container : document.body; + BDFDB.DOMUtils.removeWebStyle(url, container); + container.appendChild(BDFDB.DOMUtils.create(``)); + }; + BDFDB.DOMUtils.removeWebStyle = function (url, container) { + if (typeof url != "string") return; + container = container || document.body.querySelector("bd-head bd-styles") || document.body; + container = Node.prototype.isPrototypeOf(container) ? container : document.body; + BDFDB.DOMUtils.remove(container.querySelectorAll(`link[href="${url}"]`)); + }; + BDFDB.DOMUtils.appendLocalStyle = function (id, css, container) { + if (typeof id != "string" || typeof css != "string") return; + if (!container && !document.body.querySelector("bd-head bd-styles")) document.body.appendChild(BDFDB.DOMUtils.create(``)); + container = container || document.body.querySelector("bd-head bd-styles") || document.body; + container = Node.prototype.isPrototypeOf(container) ? container : document.body; + BDFDB.DOMUtils.removeLocalStyle(id, container); + container.appendChild(BDFDB.DOMUtils.create(``)); + }; + BDFDB.DOMUtils.removeLocalStyle = function (id, container) { + if (typeof id != "string") return; + container = container || document.body.querySelector("bd-head bd-styles") || document.body; + container = Node.prototype.isPrototypeOf(container) ? container : document.body; + BDFDB.DOMUtils.remove(container.querySelectorAll(`style[id="${id}CSS"]`)); + }; + + BDFDB.ModalUtils = {}; + BDFDB.ModalUtils.open = function (plugin, config) { + if (!BDFDB.ObjectUtils.is(plugin) || !BDFDB.ObjectUtils.is(config)) return; + let modalInstance, modalProps, cancels = [], closeModal = _ => { + if (BDFDB.ObjectUtils.is(modalProps) && typeof modalProps.onClose == "function") modalProps.onClose(); + }; + + let titleChildren = [], headerChildren = [], contentChildren = [], footerChildren = []; + + if (typeof config.text == "string") { + config.contentClassName = BDFDB.DOMUtils.formatClassName(config.contentClassName, BDFDB.disCN.modaltextcontent); + contentChildren.push(BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextElement, { + children: config.text + })); + } + + if (config.children) { + let tabBarItems = [], tabIns = {}; + for (let child of [config.children].flat(10).filter(n => n)) if (Internal.LibraryModules.React.isValidElement(child)) { + if (child.type == Internal.LibraryComponents.ModalComponents.ModalTabContent) { + if (!tabBarItems.length) child.props.open = true; + else delete child.props.open; + let ref = typeof child.props.ref == "function" ? child.props.ref : (_ => {}); + child.props.ref = instance => { + ref(instance); + if (instance) tabIns[child.props.tab] = instance; + }; + tabBarItems.push({value: child.props.tab}); + } + contentChildren.push(child); + } + if (tabBarItems.length) headerChildren.push(BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.tabbarcontainer, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TabBar, { + className: BDFDB.disCN.tabbar, + itemClassName: BDFDB.disCN.tabbaritem, + type: Internal.LibraryComponents.TabBar.Types.TOP, + items: tabBarItems, + onItemSelect: value => {for (let key in tabIns) tabIns[key].setState({open: key == value});} + }), + config.tabBarChildren + ].flat(10).filter(n => n) + })); + } + + if (BDFDB.ArrayUtils.is(config.buttons)) for (let button of config.buttons) { + let contents = typeof button.contents == "string" && button.contents; + if (contents) { + let color = typeof button.color == "string" && Internal.LibraryComponents.Button.Colors[button.color.toUpperCase()]; + let look = typeof button.look == "string" && Internal.LibraryComponents.Button.Looks[button.look.toUpperCase()]; + let click = typeof button.click == "function" ? button.click : (typeof button.onClick == "function" ? button.onClick : _ => {}); + + if (button.cancel) cancels.push(click); + + footerChildren.push(BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Button, BDFDB.ObjectUtils.exclude(Object.assign({}, button, { + look: look || (color ? Internal.LibraryComponents.Button.Looks.FILLED : Internal.LibraryComponents.Button.Looks.LINK), + color: color || Internal.LibraryComponents.Button.Colors.PRIMARY, + onClick: _ => { + if (button.close) closeModal(); + if (!(button.close && button.cancel)) click(modalInstance); + }, + children: contents + }), "click", "close", "cancel", "contents"))); + } + } + + contentChildren = contentChildren.concat(config.contentChildren).filter(n => n && (typeof n == "string" || BDFDB.ReactUtils.isValidElement(n))); + titleChildren = titleChildren.concat(config.titleChildren).filter(n => n && (typeof n == "string" || BDFDB.ReactUtils.isValidElement(n))); + headerChildren = headerChildren.concat(config.headerChildren).filter(n => n && (typeof n == "string" || BDFDB.ReactUtils.isValidElement(n))); + footerChildren = footerChildren.concat(config.footerChildren).filter(n => n && (typeof n == "string" || BDFDB.ReactUtils.isValidElement(n))); + + if (contentChildren.length) { + if (typeof config.onOpen != "function") config.onOpen = _ => {}; + if (typeof config.onClose != "function") config.onClose = _ => {}; + + let name = plugin.name || (typeof plugin.getName == "function" ? plugin.getName() : null); + name = typeof name == "string" ? name : null; + let oldTransitionState = 0; + !Internal.LibraryModules.ModalUtils ? BdApi.UI.alert(BDFDB.ReactUtils.createElement("div", { + style: {"display": "flex", "flex-direction": "column"}, + children: [ + config.header, + typeof config.subHeader == "string" || BDFDB.ReactUtils.isValidElement(config.subHeader) ? config.subHeader : (name || "") + ].filter(n => n).map(n => BDFDB.ReactUtils.createElement("span", {children: n})) + }), config.content || config.children) : Internal.LibraryModules.ModalUtils.openModal(props => { + modalProps = props; + return BDFDB.ReactUtils.createElement(class BDFDB_Modal extends Internal.LibraryModules.React.Component { + render() { + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.ModalComponents.ModalRoot, { + className: BDFDB.DOMUtils.formatClassName(name && `${name}-modal`, BDFDB.disCN.modalwrapper, config.className), + size: typeof config.size == "string" && Internal.LibraryComponents.ModalComponents.ModalSize[config.size.toUpperCase()] || Internal.LibraryComponents.ModalComponents.ModalSize.SMALL, + transitionState: props.transitionState, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.ModalComponents.ModalHeader, { + className: BDFDB.DOMUtils.formatClassName(config.headerClassName, config.shade && BDFDB.disCN.modalheadershade, headerChildren.length && BDFDB.disCN.modalheaderhassibling), + separator: config.headerSeparator || false, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex.Child, { + style: {flex: 1}, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.FormTitle.Title, { + tag: Internal.LibraryComponents.FormTitle.Tags && Internal.LibraryComponents.FormTitle.Tags.H4, + children: config.header + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextElement, { + size: Internal.LibraryComponents.TextElement.Sizes.SIZE_12, + children: typeof config.subHeader == "string" || BDFDB.ReactUtils.isValidElement(config.subHeader) ? config.subHeader : (name || "") + }) + ] + }), + titleChildren, + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.ModalComponents.ModalCloseButton, { + onClick: closeModal + }) + ].flat(10).filter(n => n) + }), + headerChildren.length ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + grow: 0, + shrink: 0, + children: headerChildren + }) : null, + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.ModalComponents.ModalContent, { + className: config.contentClassName, + scroller: config.scroller, + direction: config.direction, + content: config.content, + children: contentChildren + }), + footerChildren.length ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.ModalComponents.ModalFooter, { + className: config.footerClassName, + direction: config.footerDirection, + children: footerChildren + }) : null + ] + }); + } + componentDidMount() { + modalInstance = this; + if (props.transitionState == 1 && props.transitionState > oldTransitionState) config.onOpen(modalInstance); + oldTransitionState = props.transitionState; + } + componentWillUnmount() { + if (props.transitionState == 2) { + for (let cancel of cancels) cancel(modalInstance); + config.onClose(modalInstance); + } + } + }, props, true); + }, { + onCloseRequest: closeModal + }); + } + }; + BDFDB.ModalUtils.confirm = function (plugin, text, callback) { + if (!BDFDB.ObjectUtils.is(plugin) || typeof text != "string") return; + BDFDB.ModalUtils.open(plugin, { + text: text, + header: BDFDB.LanguageUtils.LibraryStrings.confirm, + className: BDFDB.disCN.modalconfirmmodal, + scroller: false, + buttons: [ + {contents: BDFDB.LanguageUtils.LanguageStrings.OKAY, close: true, color: "RED", onClick: callback}, + {contents: BDFDB.LanguageUtils.LanguageStrings.CANCEL, close: true} + ] + }); + }; + + var MappedMenuItems = {}, RealMenuItems = BDFDB.ModuleUtils.find(m => { + if (!m || typeof m != "function") return false; + let string = m.toString(); + return string.indexOf("(){return null}") > -1 && string.indexOf("catch(") == -1 && string.split("){return null}").length > 4; + }); + if (!RealMenuItems) { + RealMenuItems = {}; + BDFDB.LogUtils.error(["could not find Module for MenuItems"]); + } + BDFDB.ContextMenuUtils = {}; + BDFDB.ContextMenuUtils.open = function (plugin, e, children) { + Internal.LibraryModules.ContextMenuUtils.openContextMenu(e || mousePosition, _ => BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Menu, { + navId: "bdfdb-context", + onClose: Internal.LibraryModules.ContextMenuUtils.closeContextMenu, + children: children + }, true)); + }; + BDFDB.ContextMenuUtils.close = function (nodeOrInstance) { + if (!BDFDB.ObjectUtils.is(nodeOrInstance)) return; + let instance = BDFDB.ReactUtils.findOwner(nodeOrInstance, {props: "closeContextMenu", up: true}); + if (BDFDB.ObjectUtils.is(instance) && instance.props && typeof instance.props.closeContextMenu == "function") instance.props.closeContextMenu(); + else Internal.LibraryModules.ContextMenuUtils.closeContextMenu(); + }; + BDFDB.ContextMenuUtils.createItem = function (component, props = {}) { + if (!component) return null; + else { + if (props.render || props.persisting || BDFDB.ObjectUtils.is(props.popoutProps) || (typeof props.color == "string" && !InternalData.DiscordClasses[`menu${props.color.toLowerCase()}`])) component = Internal.MenuItem; + if (BDFDB.ObjectUtils.toArray(RealMenuItems).some(c => c == component)) return BDFDB.ReactUtils.createElement(component, props); + else return BDFDB.ReactUtils.createElement(LibraryComponents.MenuItems.MenuItem, { + id: props.id, + disabled: props.disabled, + customItem: true, + render: menuItemProps => { + if (!props.state) props.state = BDFDB.ObjectUtils.extract(props, "checked", "value"); + return BDFDB.ReactUtils.createElement(Internal.CustomMenuItemWrapper, { + disabled: props.disabled, + childProps: Object.assign({}, props, menuItemProps, {color: props.color}), + children: component + }, true); + } + }); + } + }; + BDFDB.ContextMenuUtils.createItemId = function (...strings) { + return strings.map(s => typeof s == "number" ? s.toString() : s).filter(s => typeof s == "string").map(s => s.toLowerCase().replace(/\s/, "-")).join("-"); + }; + BDFDB.ContextMenuUtils.findItem = function (returnvalue, config) { + if (!returnvalue || !BDFDB.ObjectUtils.is(config) || !config.label && !config.id) return [null, -1]; + config.label = config.label && [config.label].flat().filter(n => n); + config.id = config.id && [config.id].flat().filter(n => n); + let contextMenu = BDFDB.ArrayUtils.is(returnvalue) ? {props: {children: returnvalue}} : BDFDB.ReactUtils.findChild(returnvalue, {props: "navId"}); + if (contextMenu) { + let children = BDFDB.ArrayUtils.is(contextMenu.props.children) ? contextMenu.props.children : [contextMenu.props.children]; + for (let i in children) if (children[i]) { + if (check(children[i])) return [children, parseInt(i)]; + else if (BDFDB.ArrayUtils.is(children[i])) { + for (let j in children[i]) if (check(children[i][j])) return [children[i], parseInt(j)]; + } + else if (children[i].props && children[i].props.children) { + if (BDFDB.ArrayUtils.is(children[i].props.children) && children[i].props.children.length) { + let [possibleChildren, possibleIndex] = BDFDB.ContextMenuUtils.findItem(children[i].props.children, config); + if (possibleIndex > -1) return [possibleChildren, possibleIndex]; + } + else if (check(children[i].props.children)) { + if (config.group) return [children, parseInt(i)]; + else { + children[i].props.children = [children[i].props.children]; + return [children[i].props.children, 0]; + } + } + else if (children[i].props.children.props && children[i].props.children.props.children) { + if (BDFDB.ArrayUtils.is(children[i].props.children.props.children) && children[i].props.children.props.children.length) { + let [possibleChildren, possibleIndex] = BDFDB.ContextMenuUtils.findItem(children[i].props.children.props.children, config); + if (possibleIndex > -1) return [possibleChildren, possibleIndex]; + } + else if (check(children[i].props.children.props.children)) { + if (config.group) return [children, parseInt(i)]; + else { + children[i].props.children.props.children = [children[i].props.children.props.children]; + return [children[i].props.children.props.children, 0]; + } + } + } + } + } + return [children, -1]; + } + return [null, -1]; + + function check (child) { + if (!child) return false; + let props = child.stateNode ? child.stateNode.props : child.props; + if (!props) return false; + return config.id && config.id.some(key => key == "devmode-copy-id" ? typeof props.id == "string" && props.id.startsWith(key) : props.id == key) || config.label && config.label.some(key => props.label == key); + } + }; + + BDFDB.StringUtils = {}; + BDFDB.StringUtils.upperCaseFirstChar = function (string) { + if (typeof string != "string") return ""; + else return "".concat(string.charAt(0).toUpperCase()).concat(string.slice(1)); + }; + BDFDB.StringUtils.charIsUpperCase = function (string) { + if (typeof string != "string") return false; + else string[0].toUpperCase() === string[0] && string[0].toLowerCase() !== string[0]; + }; + BDFDB.StringUtils.getAcronym = function (string) { + if (typeof string != "string") return ""; + return string.replace(/'s /g," ").replace(/\w+/g, n => n[0]).replace(/\s/g, ""); + }; + BDFDB.StringUtils.cssValueToNumber = function (string) { + if (typeof string != "string") return 0; + const value = parseInt(string, 10); + return isNaN(value) ? 0 : value; + }; + BDFDB.StringUtils.htmlEscape = function (string) { + let ele = document.createElement("div"); + ele.innerText = string; + return ele.innerHTML; + }; + BDFDB.StringUtils.regEscape = function (string) { + return typeof string == "string" && string.replace(/([\-\/\\\^\$\*\+\?\.\(\)\|\[\]\{\}])/g, "\\$1"); + }; + BDFDB.StringUtils.insertNRST = function (string) { + return typeof string == "string" && string.replace(/\\r/g, "\r").replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\s/g, " "); + }; + BDFDB.StringUtils.equalCase = function (match, string) { + if (typeof match != "string" || typeof string != "string") return ""; + let first = match.charAt(0); + return first != first.toUpperCase() ? (string.charAt(0).toLowerCase() + string.slice(1)) : first != first.toLowerCase() ? (string.charAt(0).toUpperCase() + string.slice(1)) : string; + }; + BDFDB.StringUtils.extractSelection = function (original, selection) { + if (typeof original != "string") return ""; + if (typeof selection != "string") return original; + let s = [], f = [], wrong = 0, canceled = false, done = false; + for (let i of BDFDB.ArrayUtils.getAllIndexes(original, selection[0])) if (!done) { + while (i <= original.length && !done) { + let subSelection = selection.slice(s.filter(n => n != undefined).length); + if (!subSelection && s.length - 20 <= selection.length) done = true; + else for (let j in subSelection) if (!done && !canceled) { + if (original[i] == subSelection[j]) { + s[i] = subSelection[j]; + f[i] = subSelection[j]; + wrong = 0; + if (i == original.length) done = true; + } + else { + s[i] = null; + f[i] = original[i]; + wrong++; + if (wrong > 4) { + s = [], f = [], wrong = 0, canceled = true; + break; + } + } + break; + } + canceled = false; + i++; + } + } + if (s.filter(n => n).length) { + let reverseS = [].concat(s).reverse(), i = 0, j = 0; + for (let k in s) { + if (s[k] == null) i = parseInt(k) + 1; + else break; + } + for (let k in reverseS) { + if (reverseS[k] == null) j = parseInt(k) + 1; + else break; + } + return f.slice(i, f.length - j).join(""); + } + else return original; + }; + + BDFDB.SlateUtils = {}; + BDFDB.SlateUtils.isRichValue = function (richValue) { + return richValue && typeof richValue == "object" && BDFDB.SlateUtils.toRichValue("").constructor.prototype.isPrototypeOf(richValue); + }; + BDFDB.SlateUtils.toTextValue = function (richValue) { + return BDFDB.SlateUtils.isRichValue(richValue) ? Internal.LibraryModules.SlateTextUtils.toTextValue(richValue) : ""; + }; + BDFDB.SlateUtils.toRichValue = function (string) { + return typeof string == "string" ? Internal.LibraryModules.SlateRichUtils.toRichValue(string) : null; + }; + + BDFDB.NumberUtils = {}; + BDFDB.NumberUtils.formatBytes = function (bytes, sigDigits) { + bytes = parseInt(bytes); + if (isNaN(bytes) || bytes < 0) return "0 Bytes"; + if (bytes == 1) return "1 Byte"; + let size = Math.floor(Math.log(bytes) / Math.log(1024)); + return parseFloat((bytes / Math.pow(1024, size)).toFixed(sigDigits < 1 ? 0 : sigDigits > 20 ? 20 : sigDigits || 2)) + " " + ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"][size]; + }; + BDFDB.NumberUtils.mapRange = function (from, to, value) { + if (parseFloat(value) < parseFloat(from[0])) return parseFloat(to[0]); + else if (parseFloat(value) > parseFloat(from[1])) return parseFloat(to[1]); + else return parseFloat(to[0]) + (parseFloat(value) - parseFloat(from[0])) * (parseFloat(to[1]) - parseFloat(to[0])) / (parseFloat(from[1]) - parseFloat(from[0])); + }; + BDFDB.NumberUtils.generateId = function (array) { + array = BDFDB.ArrayUtils.is(array) ? array : []; + let id = Math.floor(Math.random() * 1000000000); + if (array.includes(id)) return BDFDB.NumberUtils.generateId(array); + else { + array.push(id); + return id; + } + }; + BDFDB.NumberUtils.compareVersions = function (newV, oldV) { + if (!newV || !oldV) return true; + newV = newV.toString().replace(/["'`]/g, "").split(/,|\./g).map(n => parseInt(n)).filter(n => (n || n == 0) && !isNaN(n)); + oldV = oldV.toString().replace(/["'`]/g, "").split(/,|\./g).map(n => parseInt(n)).filter(n => (n || n == 0) && !isNaN(n)); + let length = Math.max(newV.length, oldV.length); + if (!length) return true; + if (newV.length > oldV.length) { + let tempArray = new Array(newV.length - oldV.length); + for (let i = 0; i < tempArray.length; i++) tempArray[i] = 0; + oldV = tempArray.concat(oldV); + } + else if (newV.length < oldV.length) { + let tempArray = new Array(oldV.length - newV.length); + for (let i = 0; i < tempArray.length; i++) tempArray[i] = 0; + newV = tempArray.concat(newV); + } + for (let i = 0; i < length; i++) for (let iOutdated = false, j = 0; j <= i; j++) { + if (j == i && newV[j] < oldV[j]) return false; + if (j < i) iOutdated = newV[j] == oldV[j]; + if ((j == 0 || iOutdated) && j == i && newV[j] > oldV[j]) return true; + } + return false; + }; + BDFDB.NumberUtils.getVersionDifference = function (newV, oldV) { + if (!newV || !oldV) return false; + newV = newV.toString().replace(/["'`]/g, "").split(/,|\./g).map(n => parseInt(n)).filter(n => (n || n == 0) && !isNaN(n)); + oldV = oldV.toString().replace(/["'`]/g, "").split(/,|\./g).map(n => parseInt(n)).filter(n => (n || n == 0) && !isNaN(n)); + let length = Math.max(newV.length, oldV.length); + if (!length) return false; + if (newV.length > oldV.length) { + let tempArray = new Array(newV.length - oldV.length); + for (let i = 0; i < tempArray.length; i++) tempArray[i] = 0; + oldV = tempArray.concat(oldV); + } + else if (newV.length < oldV.length) { + let tempArray = new Array(oldV.length - newV.length); + for (let i = 0; i < tempArray.length; i++) tempArray[i] = 0; + newV = tempArray.concat(newV); + } + let oldValue = 0, newValue = 0; + for (let i in oldV.reverse()) oldValue += (oldV[i] * (10 ** i)); + for (let i in newV.reverse()) newValue += (newV[i] * (10 ** i)); + return (newValue - oldValue) / (10 ** (length-1)); + }; + + BDFDB.DiscordUtils = {}; + BDFDB.DiscordUtils.getSetting = function (category, key) { + if (!category || !key) return; + return BDFDB.LibraryStores.UserSettingsProtoStore && BDFDB.LibraryStores.UserSettingsProtoStore.settings[category] && BDFDB.LibraryStores.UserSettingsProtoStore.settings[category][key] && BDFDB.LibraryStores.UserSettingsProtoStore.settings[category][key].value; + }; + BDFDB.DiscordUtils.setSetting = function (category, key, value) { + if (!category || !key) return; + let store = BDFDB.DiscordUtils.getSettingsStore(); + if (store) store.updateAsync(category, settings => { + if (!settings) return; + if (!settings[key]) settings[key] = {}; + if (BDFDB.ObjectUtils.is(value)) for (let k in value) settings[key][k] = value[k]; + else settings[key].value = value; + }, Internal.DiscordConstants.UserSettingsActionTypes.INFREQUENT_USER_ACTION); + }; + BDFDB.DiscordUtils.getSettingsStore = function () { + return BDFDB.LibraryModules.UserSettingsProtoUtils && (Object.entries(BDFDB.LibraryModules.UserSettingsProtoUtils).find(n => n && n[1] && n[1].updateAsync && n[1].ProtoClass && n[1].ProtoClass.typeName && n[1].ProtoClass.typeName.endsWith(".PreloadedUserSettings")) || [])[1]; + }; + BDFDB.DiscordUtils.openLink = function (url, config = {}) { + if ((config.inBuilt || config.inBuilt === undefined && Internal.settings.general.useChromium) && Internal.LibraryRequires.electron && Internal.LibraryRequires.electron.remote) { + let browserWindow = new Internal.LibraryRequires.electron.remote.BrowserWindow({ + frame: true, + resizeable: true, + show: true, + darkTheme: BDFDB.DiscordUtils.getTheme() == BDFDB.disCN.themedark, + webPreferences: { + nodeIntegration: false, + nodeIntegrationInWorker: false + } + }); + browserWindow.setMenu(null); + browserWindow.loadURL(url); + if (config.minimized) browserWindow.minimize(null); + } + else window.open(url, "_blank"); + }; + window.DiscordNative && window.DiscordNative.app && window.DiscordNative.app.getPath("appData").then(path => {BDFDB.DiscordUtils.getFolder.base = path;}); + BDFDB.DiscordUtils.isPlaformEmbedded = function () { + return Internal.LibraryModules.PlatformUtils && (Object.entries(Internal.LibraryModules.PlatformUtils).find(n => typeof n[1] == "boolean") || [])[1] || false; + }; + BDFDB.DiscordUtils.getFolder = function () { + if (!BDFDB.DiscordUtils.getFolder.base) return ""; + else if (BDFDB.DiscordUtils.getFolder.folder) return BDFDB.DiscordUtils.getFolder.folder; + else { + let folder; + try { + let build = BDFDB.DiscordUtils.getBuild(); + build = "discord" + (build == "stable" ? "" : build); + folder = Internal.LibraryRequires.path.resolve(BDFDB.DiscordUtils.getFolder.base, build, BDFDB.DiscordUtils.getVersion()); + } + catch (err) {folder = BDFDB.DiscordUtils.getFolder.base;} + return BDFDB.DiscordUtils.getFolder.folder = folder; + } + }; + BDFDB.DiscordUtils.getLanguage = function () { + return Internal.LibraryModules.LanguageUtils && (Internal.LibraryModules.LanguageUtils.chosenLocale || Internal.LibraryModules.LanguageUtils._chosenLocale) || Internal.LibraryModules.LanguageIntlUtils && Internal.LibraryModules.LanguageIntlUtils.getSystemLocale && (Internal.LibraryModules.LanguageIntlUtils.getSystemLocale && Internal.LibraryModules.LanguageIntlUtils.getSystemLocale()) || document.querySelector("html[lang]").getAttribute("lang"); + }; + BDFDB.DiscordUtils.getBuild = function () { + if (BDFDB.DiscordUtils.getBuild.build) return BDFDB.DiscordUtils.getBuild.build; + else { + let build; + try {build = window.DiscordNative.app.getReleaseChannel();} + catch (err) { + let version = BDFDB.DiscordUtils.getVersion(); + if (version) { + version = version.split("."); + if (version.length == 3 && !isNaN(version = parseInt(version[2]))) build = version > 300 ? "stable" : version > 200 ? "canary" : "ptb"; + else build = "stable"; + } + else build = "stable"; + } + return BDFDB.DiscordUtils.getBuild.build = build; + } + }; + BDFDB.DiscordUtils.getVersion = function () { + if (BDFDB.DiscordUtils.getVersion.version) return BDFDB.DiscordUtils.getVersion.version; + else { + let version; + try {version = window.DiscordNative.app.getVersion();} + catch (err) {version = "999.999.9999";} + return BDFDB.DiscordUtils.getVersion.version = version; + } + }; + BDFDB.DiscordUtils.getTheme = function () { + return BDFDB.LibraryStores.ThemeStore.theme == "light" ? BDFDB.disCN.themelight : BDFDB.disCN.themedark; + }; + BDFDB.DiscordUtils.getZoomFactor = function () { + let aRects = BDFDB.DOMUtils.getRects(document.querySelector(BDFDB.dotCN.appmount)); + let widthZoom = Math.round(100 * window.outerWidth / aRects.width); + let heightZoom = Math.round(100 * window.outerHeight / aRects.height); + return widthZoom < heightZoom ? widthZoom : heightZoom; + }; + BDFDB.DiscordUtils.getFontScale = function () { + return parseInt(document.firstElementChild.style.fontSize.replace("%", "")); + }; + BDFDB.DiscordUtils.shake = function () { + BDFDB.ReactUtils.findOwner(document.querySelector(BDFDB.dotCN.appcontainer), {name: "Shakeable", unlimited: true, up: true}).shake(); + }; + BDFDB.DiscordUtils.rerenderAll = function (instant) { + BDFDB.TimeUtils.clear(BDFDB.DiscordUtils.rerenderAll.timeout); + BDFDB.DiscordUtils.rerenderAll.timeout = BDFDB.TimeUtils.timeout(_ => { + let LayersProviderIns = BDFDB.ReactUtils.findOwner(document.querySelector(BDFDB.dotCN.layers), {name: "LayersProvider", unlimited: true, up: true}); + let LayersProviderType = LayersProviderIns && BDFDB.ObjectUtils.get(LayersProviderIns, `${BDFDB.ReactUtils.instanceKey}.type`); + if (!LayersProviderType) return; + let parentSelector = "", notices = document.querySelector("#bd-notices"); + if (notices) { + let parentClasses = [] + for (let i = 0, parent = notices.parentElement; i < 3; i++, parent = parent.parentElement) parentClasses.push(parent.className); + parentSelector = parentClasses.reverse().map(n => !n ? "*" : `.${n.split(" ").join(".")}`).join(" > "); + } + BDFDB.PatchUtils.patch({name: "BDFDB DiscordUtils"}, LayersProviderType.prototype, "render", {after: e => { + e.returnValue = BDFDB.ReactUtils.createElement(LayersProviderType, LayersProviderIns.props); + BDFDB.ReactUtils.forceUpdate(LayersProviderIns); + if (parentSelector) BDFDB.TimeUtils.timeout(_ => { + if (!document.contains(notices)) { + let parent = document.querySelector(parentSelector) || document.querySelector(BDFDB.dotCN.app).parentElement; + if (parent) { + parent.insertBefore(notices, parent.firstElementChild); + let ZeresPluginLibrary = notices.querySelector("#outdated-plugins") && BDFDB.BDUtils.getPlugin("ZeresPluginLibrary"); + let updateChecker = ZeresPluginLibrary && ZeresPluginLibrary?.Library?.PluginUpdater?.checkAllPlugins; + if (typeof updateChecker == "function") updateChecker.apply(ZeresPluginLibrary.Library.PluginUpdater); + } + } + }, 1000); + }}, {once: true}); + BDFDB.ReactUtils.forceUpdate(LayersProviderIns); + }, instant ? 0 : 1000); + }; + + const DiscordClassModules = Object.assign({}, InternalData.CustomClassModules); + Internal.DiscordClassModules = new Proxy(DiscordClassModules, { + get: function (_, item) { + if (DiscordClassModules[item]) return DiscordClassModules[item]; + if (!InternalData.DiscordClassModules[item]) return; + DiscordClassModules[item] = BDFDB.ModuleUtils.findStringObject(InternalData.DiscordClassModules[item].props, Object.assign({}, InternalData.DiscordClassModules[item])); + return DiscordClassModules[item] ? DiscordClassModules[item] : undefined; + } + }); + BDFDB.DiscordClassModules = Internal.DiscordClassModules; + for (let item in InternalData.DiscordClassModules) if (!DiscordClassModules[item]) DiscordClassModules[item] = undefined; + + const DiscordClasses = Object.assign({}, InternalData.DiscordClasses); + BDFDB.DiscordClasses = Object.assign({}, DiscordClasses); + Internal.getDiscordClass = function (item, selector) { + let className, fallbackClassName, notFoundAndLazyloaded = false; + className = fallbackClassName = `${Internal.DiscordClassModules.BDFDB.BDFDBundefined}_${Internal.generateClassId()}`; + if (DiscordClasses[item] === undefined) { + BDFDB.LogUtils.warn([item, "not found in DiscordClasses"]); + return className; + } + else if (!BDFDB.ArrayUtils.is(DiscordClasses[item]) || DiscordClasses[item].length != 2) { + BDFDB.LogUtils.warn([item, "is not an Array of Length 2 in DiscordClasses"]); + return className; + } + else if (Internal.DiscordClassModules[DiscordClasses[item][0]] === undefined) { + if (!InternalData.LazyloadedClassModules || !InternalData.LazyloadedClassModules[DiscordClasses[item][0]]) { + BDFDB.LogUtils.warn([DiscordClasses[item][0], "not found in DiscordClassModules"]); + return className; + } + else notFoundAndLazyloaded = true; + } + else if ([DiscordClasses[item][1]].flat().every(prop => Internal.DiscordClassModules[DiscordClasses[item][0]][prop] === undefined && !(JSON.stringify(Internal.DiscordClassModules[DiscordClasses[item][0]]).split(" ").find(n => n.startsWith(`${prop}_`) || n.endsWith(`-${prop}`)) || "").split("\"")[0])) { + if (!InternalData.LazyloadedClassModules || !InternalData.LazyloadedClassModules[DiscordClasses[item][0]]) { + BDFDB.LogUtils.warn([DiscordClasses[item][1], "not found in", DiscordClasses[item][0], "in DiscordClassModules"]); + return className; + } + else notFoundAndLazyloaded = true; + } + if (notFoundAndLazyloaded) { + className = `${DiscordClasses[item][1]}_${InternalData.LazyloadedClassModules[DiscordClasses[item][0]]}`; + DiscordClassModules[DiscordClasses[item][0]] = Object.assign({}, DiscordClassModules[item], {[DiscordClasses[item][1]]: className}); + } + else for (let prop of [DiscordClasses[item][1]].flat()) { + className = Internal.DiscordClassModules[DiscordClasses[item][0]][prop] || (JSON.stringify(Internal.DiscordClassModules[DiscordClasses[item][0]]).split(" ").find(n => n.startsWith(`${prop}_`) || n.endsWith(`-${prop}`)) || "").split("\"")[0]; + if (className) break; + else className = fallbackClassName; + } + if (selector) { + className = className.split(" ").filter(n => n.indexOf("da-") != 0).join(selector ? "." : " "); + className = className || fallbackClassName; + } + return BDFDB.ArrayUtils.removeCopies(className.split(" ")).join(" ") || fallbackClassName; + }; + const generationChars = "0123456789abcdef".split(""); + Internal.generateClassId = function () { + let id = ""; + while (id.length < 6) id += generationChars[Math.floor(Math.random() * generationChars.length)]; + return id; + }; + BDFDB.disCN = new Proxy({}, { + get: function (list, item) { + return Internal.getDiscordClass(item, false).replace("#", ""); + } + }); + BDFDB.disCNS = new Proxy({}, { + get: function (list, item) { + return Internal.getDiscordClass(item, false).replace("#", "") + " "; + } + }); + BDFDB.disCNC = new Proxy({}, { + get: function (list, item) { + return Internal.getDiscordClass(item, false).replace("#", "") + ","; + } + }); + BDFDB.dotCN = new Proxy({}, { + get: function (list, item) { + let className = Internal.getDiscordClass(item, true); + return (className.indexOf("#") == 0 ? "" : ".") + className; + } + }); + BDFDB.dotCNS = new Proxy({}, { + get: function (list, item) { + let className = Internal.getDiscordClass(item, true); + return (className.indexOf("#") == 0 ? "" : ".") + className + " "; + } + }); + BDFDB.dotCNC = new Proxy({}, { + get: function (list, item) { + let className = Internal.getDiscordClass(item, true); + return (className.indexOf("#") == 0 ? "" : ".") + className + ","; + } + }); + BDFDB.notCN = new Proxy({}, { + get: function (list, item) { + return `:not(.${Internal.getDiscordClass(item, true).split(".")[0]})`; + } + }); + BDFDB.notCNS = new Proxy({}, { + get: function (list, item) { + return `:not(.${Internal.getDiscordClass(item, true).split(".")[0]}) `; + } + }); + BDFDB.notCNC = new Proxy({}, { + get: function (list, item) { + return `:not(.${Internal.getDiscordClass(item, true).split(".")[0]}),`; + } + }); + + const LanguageStringsObj = Internal.LibraryModules.LanguageStore && Internal.LibraryModules.LanguageStore.Messages || Internal.LibraryModules.LanguageStore || {}; + const EngLanguageStringsObj = Internal.LibraryModules.EngLanguageStore && Internal.LibraryModules.EngLanguageStore.Messages || Internal.LibraryModules.EngLanguageStore || {}; + const LanguageStringFormatter = Internal.LibraryModules.LanguageIntlUtils && Internal.LibraryModules.LanguageIntlUtils.formatToPlainString; + var LanguageStringFormattersObj = (BDFDB.ModuleUtils.findByString("use strict", "createLoader:", {exportsFilter: m => !m.messagesLoader, all: true}).find(n => n && (n.A || n.Z) && [InternalData.LanguageStringHashes.DISCORD].flat(10).some(hash => LanguageStringFormatter(n.A && n.A[hash] || n.Z && n.Z[hash]))) || {}); + LanguageStringFormattersObj = LanguageStringFormattersObj.A || LanguageStringFormattersObj.Z; + + const LanguageHashes = {}; + for (let hash of Object.keys(EngLanguageStringsObj)) LanguageHashes[(typeof EngLanguageStringsObj[hash] == "string" ? [EngLanguageStringsObj[hash]] : EngLanguageStringsObj[hash]).map(n => typeof n == "string" ? n : "PLACEHOLDER").join(" ").toUpperCase().replace(/[^a-zA-Z0-9 ]/g, "").split(" ").filter(n => n).join(" ").trim().replace(/ /g, "_")] = hash; + + const LibraryStrings = Object.assign({}, InternalData.LibraryStrings); + BDFDB.LanguageUtils = {}; + BDFDB.LanguageUtils.languages = Object.assign({}, InternalData.Languages); + BDFDB.LanguageUtils.getLanguage = function () { + let lang = BDFDB.DiscordUtils.getLanguage() || "en"; + if (lang == "en-GB" || lang == "en-US") lang = "en"; + let langIds = lang.split("-"); + let langId = langIds[0]; + let langId2 = langIds[1] || ""; + lang = langId2 && langId.toUpperCase() !== langId2.toUpperCase() ? langId + "-" + langId2 : langId; + return BDFDB.LanguageUtils.languages[lang] || BDFDB.LanguageUtils.languages[langId] || BDFDB.LanguageUtils.languages.en; + }; + BDFDB.LanguageUtils.getName = function (language) { + if (!language || typeof language.name != "string") return ""; + if (language.name.startsWith("Discord")) return language.name.slice(0, -1) + (language.ownlang && (BDFDB.LanguageUtils.languages[language.id] || {}).name != language.ownlang ? ` / ${language.ownlang}` : "") + ")"; + else return language.name + (language.ownlang && language.name != language.ownlang ? ` / ${language.ownlang}` : ""); + }; + BDFDB.LanguageUtils.LanguageStrings = new Proxy(LanguageHashes, { + get: function (list, item) { + let stringObj = LanguageStringsObj[item] || LanguageStringsObj[LanguageHashes[item]]; + if (!stringObj) BDFDB.LogUtils.warn([item, "not found in BDFDB.LanguageUtils.LanguageStrings"]); + else { + if (stringObj && typeof stringObj == "object" && typeof stringObj.format == "function" || BDFDB.ArrayUtils.is(stringObj)) return BDFDB.LanguageUtils.LanguageStringsFormat(item); + else return (stringObj || "").replace(/[?!:\.]$/, ""); + } + return ""; + } + }); + BDFDB.LanguageUtils.LanguageStringsCheck = new Proxy(LanguageHashes, { + get: function (list, item) { + return !!(LanguageStringsObj[item] || LanguageStringsObj[LanguageHashes[item]]); + } + }); + let parseLanguageStringObj = obj => { + let string = ""; + if (typeof obj == "string") string += obj; + else if (BDFDB.ObjectUtils.is(obj)) { + if (obj.content) string += parseLanguageStringObj(obj.content); + else if (obj.children) string += parseLanguageStringObj(obj.children); + else if (obj.props) string += parseLanguageStringObj(obj.props); + } + else if (BDFDB.ArrayUtils.is(obj)) for (let ele of obj) string += parseLanguageStringObj(ele); + return (string || "").replace(/[?!:\.]$/, ""); + }; + BDFDB.LanguageUtils.LanguageStringsFormat = function (item, ...values) { + if (item) { + let stringObj = LanguageStringsObj[item] || LanguageStringsObj[LanguageHashes[item]]; + if (stringObj && typeof stringObj == "object" && typeof stringObj.format == "function" || BDFDB.ArrayUtils.is(stringObj)) { + let i = 0, returnvalue, formatVars = {}; + let error = "\n"; + while (!returnvalue && i < 10) { + i++; + try {returnvalue = BDFDB.ArrayUtils.is(stringObj) ? LanguageStringFormatter(LanguageStringFormattersObj[LanguageHashes[item]], formatVars) : stringObj.format(formatVars, false);} + catch (err) { + error += "Error 1 " + err + "\n"; + returnvalue = null; + let value = values.shift(); + value = value != null ? (value === 0 ? "0" : value) : "undefined"; + let valueName = (err.toString().split("for variable '")[1] || "").split("'")[0]; + formatVars[valueName] = valueName.endsWith("Hook") ? (_ => value) : value; + if (stringObj.intMessage) { + try {for (let hook of stringObj.intMessage.format(formatVars).match(/\([^\(\)]+\)/gi)) formatVars[hook.replace(/[\(\)]/g, "")] = n => n;} + catch (err2) {error += "Error 2 " + err2 + "\n";} + } + if (stringObj.intlMessage) { + try {for (let hook of stringObj.intlMessage.format(formatVars).match(/\([^\(\)]+\)/gi)) formatVars[hook.replace(/[\(\)]/g, "")] = n => n;} + catch (err3) {error += "Error 3 " + err3 + "\n";} + } + } + } + if (returnvalue) return parseLanguageStringObj(returnvalue); + else { + BDFDB.LogUtils.warn([item, "failed to format string in BDFDB.LanguageUtils.LanguageStrings", error]); + return ""; + } + } + else return BDFDB.LanguageUtils.LanguageStrings[item]; + } + else BDFDB.LogUtils.warn([item, "enter a valid key to format the string in BDFDB.LanguageUtils.LanguageStrings"]); + return ""; + }; + BDFDB.LanguageUtils.LibraryStrings = new Proxy(LibraryStrings.default || {}, { + get: function (list, item) { + let languageId = BDFDB.LanguageUtils.getLanguage().id; + if (LibraryStrings[languageId] && LibraryStrings[languageId][item]) return LibraryStrings[languageId][item]; + else if (LibraryStrings.default[item]) return LibraryStrings.default[item]; + else BDFDB.LogUtils.warn([item, "not found in BDFDB.LanguageUtils.LibraryStrings"]); + return ""; + } + }); + BDFDB.LanguageUtils.LibraryStringsCheck = new Proxy(LibraryStrings.default || {}, { + get: function (list, item) { + return !!LibraryStrings.default[item]; + } + }); + BDFDB.LanguageUtils.LibraryStringsFormat = function (item, ...values) { + if (item) { + let languageId = BDFDB.LanguageUtils.getLanguage().id, string = null; + if (LibraryStrings[languageId] && LibraryStrings[languageId][item]) string = LibraryStrings[languageId][item]; + else if (LibraryStrings.default[item]) string = LibraryStrings.default[item]; + if (string) { + for (let i = 0; i < values.length; i++) if (typeof values[i] == "string" || typeof values[i] == "number") string = string.replace(new RegExp(`{{var${i}}}`, "g"), values[i]); + return string; + } + else BDFDB.LogUtils.warn([item, "not found in BDFDB.LanguageUtils.LibraryStrings"]); + } + else BDFDB.LogUtils.warn([item, "enter a valid key to format the string in BDFDB.LanguageUtils.LibraryStrings"]); + return ""; + }; + BDFDB.TimeUtils.interval(interval => { + if (BDFDB.DiscordUtils.getLanguage()) { + BDFDB.TimeUtils.clear(interval); + let language = BDFDB.LanguageUtils.getLanguage(); + if (language) BDFDB.LanguageUtils.languages.$discord = Object.assign({}, language, {name: `Discord (${language.name})`}); + } + }, 100); + for (let key in BDFDB.LanguageUtils.languages) try { + if (new Date(0).toLocaleString(key, {second: 'numeric'}) != "0") { + BDFDB.LanguageUtils.languages[key].numberMap = {}; + for (let i = 0; i < 10; i++) BDFDB.LanguageUtils.languages[key].numberMap[i] = new Date(i*1000).toLocaleString(key, {second: 'numeric'}); + } + } + catch (err) {} + + const reactInitialized = Internal.LibraryModules.React && Internal.LibraryModules.React.Component; + Internal.setDefaultProps = function (component, defaultProps) { + if (BDFDB.ObjectUtils.is(component)) component.defaultProps = Object.assign({}, component.defaultProps, defaultProps); + }; + let openedItem; + Internal.MenuItem = reactInitialized && class BDFDB_MenuItem extends Internal.LibraryModules.React.Component { + constructor(props) { + super(props); + this.state = {hovered: false}; + } + componentWillUnmount() { + if (openedItem == this.props.id) openedItem = null; + } + render() { + let color = (typeof this.props.color == "string" ? this.props.color : Internal.DiscordConstants.MenuItemColors.DEFAULT).toLowerCase(); + let isCustomColor = false; + if (color) { + if (InternalData.DiscordClasses[`menucolor${color}`]) color = color; + else if (BDFDB.ColorUtils.getType(color)) { + isCustomColor = true; + color = BDFDB.ColorUtils.convert(color, "RGBA"); + } + else color = (Internal.DiscordConstants.MenuItemColors.DEFAULT || "").toLowerCase(); + } + let renderPopout, onClose, hasPopout = BDFDB.ObjectUtils.is(this.props.popoutProps); + if (hasPopout) { + renderPopout = instance => { + openedItem = this.props.id; + return typeof this.props.popoutProps.renderPopout == "function" && this.props.popoutProps.renderPopout(instance); + }; + onClose = instance => { + openedItem = null; + typeof this.props.popoutProps.onClose == "function" && this.props.popoutProps.onClose(instance); + }; + } + let focused = !openedItem ? this.props.isFocused : openedItem == this.props.id; + let themeDark = BDFDB.DiscordUtils.getTheme() == BDFDB.disCN.themedark; + let item = BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Clickable, Object.assign({ + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.menuitem, (this.props.label || this.props.subtext) && BDFDB.disCN.menulabelcontainer, color && (isCustomColor ? BDFDB.disCN.menucolorcustom : BDFDB.disCN[`menucolor${color}`]), this.props.disabled && BDFDB.disCN.menudisabled, focused && BDFDB.disCN.menufocused), + style: { + color: isCustomColor ? ((focused || this.state.hovered) ? (BDFDB.ColorUtils.isBright(color) ? "#000000" : "#ffffff") : color) : (this.state.hovered ? "#ffffff" : null), + background: isCustomColor && (focused || this.state.hovered) && color + }, + onClick: this.props.disabled ? null : e => { + if (!this.props.action) return false; + !this.props.persisting && !hasPopout && this.props.onClose && this.props.onClose(); + this.props.action(e, this); + }, + onMouseEnter: this.props.disabled ? null : e => { + if (typeof this.props.onMouseEnter == "function") this.props.onMouseEnter(e, this); + this.setState({hovered: true}); + }, + onMouseLeave: this.props.disabled ? null : e => { + if (typeof this.props.onMouseLeave == "function") this.props.onMouseLeave(e, this); + this.setState({hovered: false}); + }, + "aria-disabled": this.props.disabled, + children: [ + this.props.icon && this.props.showIconFirst && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.menuiconcontainerleft, + children: BDFDB.ReactUtils.createElement(this.props.icon, { + className: BDFDB.disCN.menuicon + }) + }), + typeof this.props.render == "function" ? this.props.render(this) : this.props.render, + (this.props.label || this.props.subtext) && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.menulabel, + children: [ + typeof this.props.label == "function" ? this.props.label(this) : this.props.label, + this.props.subtext && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.menusubtext, + children: typeof this.props.subtext == "function" ? this.props.subtext(this) : this.props.subtext + }) + ].filter(n => n) + }), + this.props.hint && !this.props.showIconFirst && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.menuiconcontainer, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.MenuHint, { + children: typeof this.props.hint == "function" ? this.props.hint(this) : this.props.hint + }) + }), + this.props.icon && !this.props.showIconFirst && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.menuiconcontainer, + children: BDFDB.ReactUtils.createElement(this.props.icon, { + className: BDFDB.disCN.menuicon + }) + }), + this.props.input && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.menuiconcontainer, + children: this.props.input + }) + ].filter(n => n) + }, this.props.menuItemProps, {isFocused: focused})); + return hasPopout ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.PopoutContainer, Object.assign({}, this.props.popoutProps, { + children: item, + renderPopout: renderPopout, + onClose: onClose + })) : item; + } + }; + Internal.CustomMenuItemWrapper = reactInitialized && class BDFDB_CustomMenuItemWrapper extends Internal.LibraryModules.React.Component { + constructor(props) { + super(props); + this.state = {hovered: false}; + } + render() { + let isItem = this.props.children == Internal.MenuItem; + let item = BDFDB.ReactUtils.createElement(this.props.children, Object.assign({}, this.props.childProps, { + onMouseEnter: isItem ? e => { + if (this.props.childProps && typeof this.props.childProps.onMouseEnter == "function") this.props.childProps.onMouseEnter(e, this); + this.setState({hovered: true}); + } : this.props.childProps && this.props.childProps.onMouseEnter, + onMouseLeave: isItem ? e => { + if (this.props.childProps && typeof this.props.childProps.onMouseLeave == "function") this.props.childProps.onMouseLeave(e, this); + this.setState({hovered: false}); + } : this.props.childProps && this.props.childProps.onMouseLeave, + isFocused: this.state.hovered && !this.props.disabled + })); + return isItem ? item : BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Clickable, { + onMouseEnter: e => this.setState({hovered: true}), + onMouseLeave: e => this.setState({hovered: false}), + children: item + }); + } + }; + Internal.ErrorBoundary = reactInitialized && class BDFDB_ErrorBoundary extends Internal.LibraryModules.React.PureComponent { + constructor(props) { + super(props); + this.state = {hasError: false}; + } + static getDerivedStateFromError(err) { + return {hasError: true}; + } + componentDidCatch(err, info) { + BDFDB.LogUtils.error(["Could not create React Element!", err]); + } + render() { + if (this.state.hasError) return Internal.LibraryModules.React.createElement("span", { + style: { + background: Internal.DiscordConstants.Colors.PRIMARY, + borderRadius: 5, + color: BDFDB.DiscordConstants.ColorsCSS.STATUS_DANGER, + fontSize: 12, + fontWeight: 600, + padding: 6, + textAlign: "center", + verticalAlign: "center" + }, + children: "React Component Error" + }); + return this.props.children; + } + }; + + Internal.NativeSubComponents = new Proxy(NativeSubComponents, { + get: function (_, item) { + if (NativeSubComponents[item]) return NativeSubComponents[item]; + if (!InternalData.NativeSubComponents[item]) return "div"; + + Internal.findModuleViaData(NativeSubComponents, InternalData.NativeSubComponents, item); + + return NativeSubComponents[item] ? NativeSubComponents[item] : "div"; + } + }); + + CustomComponents.AutoFocusCatcher = reactInitialized && class BDFDB_AutoFocusCatcher extends Internal.LibraryModules.React.Component { + render() { + const style = {padding: 0, margin: 0, border: "none", width: 0, maxWidth: 0, height: 0, maxHeight: 0, visibility: "hidden"}; + return BDFDB.ReactUtils.forceStyle(BDFDB.ReactUtils.createElement("input", {style}), Object.keys(style)); + } + }; + + CustomComponents.BadgeAnimationContainer = reactInitialized && class BDFDB_BadgeAnimationContainer extends Internal.LibraryModules.React.Component { + componentDidMount() {BDFDB.ReactUtils.forceUpdate(this);} + componentWillAppear(e) {if (typeof e == "function") e();} + componentWillEnter(e) {if (typeof e == "function") e();} + componentWillLeave(e) {if (typeof e == "function") this.timeoutId = setTimeout(e, 300);} + componentWillUnmount() {BDFDB.TimeUtils.clear(this.timeoutId)} + render() { + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Animations.animated.div, { + className: this.props.className, + style: this.props.animatedStyle, + children: this.props.children + }); + } + }; + + CustomComponents.Badges = {}; + CustomComponents.Badges.getBadgePaddingForValue = function (count) { + switch (count) { + case 1: + case 4: + case 6: + return 1; + default: + return 0; + } + }; + CustomComponents.Badges.IconBadge = reactInitialized && class BDFDB_IconBadge extends Internal.LibraryModules.React.Component { + render() { + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.badgeiconbadge, this.props.shape && Internal.LibraryComponents.Badges.BadgeShapes[this.props.shape] || Internal.LibraryComponents.Badges.BadgeShapes.ROUND), + style: Object.assign({ + backgroundColor: this.props.disableColor ? null : (this.props.color || BDFDB.DiscordConstants.ColorsCSS.STATUS_DANGER) + }, this.props.style), + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.badgeicon, + name: this.props.icon + }) + }); + } + }; + CustomComponents.Badges.NumberBadge = reactInitialized && class BDFDB_NumberBadge extends Internal.LibraryModules.React.Component { + handleClick(e) {if (typeof this.props.onClick == "function") this.props.onClick(e, this);} + handleContextMenu(e) {if (typeof this.props.onContextMenu == "function") this.props.onContextMenu(e, this);} + handleMouseEnter(e) {if (typeof this.props.onMouseEnter == "function") this.props.onMouseEnter(e, this);} + handleMouseLeave(e) {if (typeof this.props.onMouseLeave == "function") this.props.onMouseLeave(e, this);} + getBadgeWidthForValue(e) {return e < 10 ? 16 : e < 100 ? 22 : 30} + getBadgeCountString(e) {return e < 1e3 ? "" + e : Math.min(Math.floor(e/1e3), 9) + "k+"} + render() { + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.badgenumberbadge, Internal.LibraryComponents.Badges && Internal.LibraryComponents.Badges.BadgeShapes && (this.props.shape && Internal.LibraryComponents.Badges.BadgeShapes[this.props.shape] || Internal.LibraryComponents.Badges.BadgeShapes.ROUND)), + style: Object.assign({ + backgroundColor: !this.props.disableColor && (this.props.color || BDFDB.DiscordConstants.ColorsCSS.STATUS_DANGER), + width: this.getBadgeWidthForValue(this.props.count) + }, this.props.style), + onClick: this.handleClick.bind(this), + onContextMenu: this.handleContextMenu.bind(this), + onMouseEnter: this.handleMouseEnter.bind(this), + onMouseLeave: this.handleMouseLeave.bind(this), + children: this.getBadgeCountString(this.props.count) + }); + } + }; + + CustomComponents.BotTag = reactInitialized && class BDFDB_BotTag extends Internal.LibraryModules.React.Component { + handleClick(e) {if (typeof this.props.onClick == "function") this.props.onClick(e, this);} + handleContextMenu(e) {if (typeof this.props.onContextMenu == "function") this.props.onContextMenu(e, this);} + handleMouseEnter(e) {if (typeof this.props.onMouseEnter == "function") this.props.onMouseEnter(e, this);} + handleMouseLeave(e) {if (typeof this.props.onMouseLeave == "function") this.props.onMouseLeave(e, this);} + render() { + return BDFDB.ReactUtils.createElement("span", { + className: BDFDB.DOMUtils.formatClassName(this.props.className, this.props.invertColor ? BDFDB.disCN.bottaginvert : BDFDB.disCN.bottagregular, this.props.useRemSizes ? BDFDB.disCN.bottagrem : BDFDB.disCN.bottagpx), + style: this.props.style, + onClick: this.handleClick.bind(this), + onContextMenu: this.handleContextMenu.bind(this), + onMouseEnter: this.handleMouseEnter.bind(this), + onMouseLeave: this.handleMouseLeave.bind(this), + children: BDFDB.ReactUtils.createElement("span", { + className: BDFDB.disCN.bottagtext, + children: this.props.tag || BDFDB.LanguageUtils.LanguageStrings.BOT + }) + }); + } + }; + + CustomComponents.Button = reactInitialized && class BDFDB_Button extends Internal.LibraryModules.React.Component { + handleClick(e) {if (typeof this.props.onClick == "function") this.props.onClick(e, this);} + handleContextMenu(e) {if (typeof this.props.onContextMenu == "function") this.props.onContextMenu(e, this);} + handleMouseDown(e) {if (typeof this.props.onMouseDown == "function") this.props.onMouseDown(e, this);} + handleMouseUp(e) {if (typeof this.props.onMouseUp == "function") this.props.onMouseUp(e, this);} + handleMouseEnter(e) {if (typeof this.props.onMouseEnter == "function") this.props.onMouseEnter(e, this);} + handleMouseLeave(e) {if (typeof this.props.onMouseLeave == "function") this.props.onMouseLeave(e, this);} + render() { + if (!this.state) this.state = {disabled: this.props.disabled}; + let processingAndListening = (this.state.disabled || this.props.submitting) && (null != this.props.onMouseEnter || null != this.props.onMouseLeave); + let props = BDFDB.ObjectUtils.exclude(this.props, "look", "color", "hover", "size", "fullWidth", "grow", "disabled", "submitting", "type", "style", "wrapperClassName", "className", "innerClassName", "onClick", "onContextMenu", "onMouseDown", "onMouseUp", "onMouseEnter", "onMouseLeave", "children", "rel"); + let button = BDFDB.ReactUtils.createElement("button", Object.assign({}, !this.state.disabled && !this.props.submitting && props, { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.button, this.props.look != null ? this.props.look : Internal.LibraryComponents.Button.Looks.FILLED, this.props.color != null ? this.props.color : Internal.LibraryComponents.Button.Colors.BRAND, this.props.hover, this.props.size != null ? this.props.size : Internal.LibraryComponents.Button.Sizes.MEDIUM, processingAndListening && this.props.wrapperClassName, this.props.fullWidth && BDFDB.disCN.buttonfullwidth, (this.props.grow === undefined || this.props.grow) && BDFDB.disCN.buttongrow, this.props.submitting && BDFDB.disCN.buttonsubmitting), + onClick: (this.state.disabled || this.props.submitting) ? e => {return e.preventDefault();} : this.handleClick.bind(this), + onContextMenu: (this.state.disabled || this.props.submitting) ? e => {return e.preventDefault();} : this.handleContextMenu.bind(this), + onMouseUp: !this.state.disabled && this.handleMouseDown.bind(this), + onMouseDown: !this.state.disabled && this.handleMouseUp.bind(this), + onMouseEnter: this.handleMouseEnter.bind(this), + onMouseLeave: this.handleMouseLeave.bind(this), + type: !this.props.type ? "button" : this.props.type, + disabled: this.state.disabled, + style: this.props.style, + rel: this.props.rel, + children: [ + this.props.submitting && !this.state.disabled ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SpinnerComponents.Spinner, { + type: Internal.LibraryComponents.SpinnerComponents.Types.PULSING_ELLIPSIS, + className: BDFDB.disCN.buttonspinner, + itemClassName: BDFDB.disCN.buttonspinneritem + }) : null, + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.buttoncontents, this.props.innerClassName), + children: this.props.children + }) + ] + })); + return !processingAndListening ? button : BDFDB.ReactUtils.createElement("span", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.buttondisabledwrapper, this.props.wrapperClassName, this.props.size != null ? this.props.size : Internal.LibraryComponents.Button.Sizes.MEDIUM, this.props.fullWidth && BDFDB.disCN.buttonfullwidth, (this.props.grow === undefined || this.props.grow) && BDFDB.disCN.buttongrow), + children: [ + button, + BDFDB.ReactUtils.createElement("span", { + onMouseEnter: this.handleMouseEnter.bind(this), + onMouseLeave: this.handleMouseLeave.bind(this), + className: BDFDB.disCN.buttondisabledoverlay + }) + ] + }); + } + }; + + CustomComponents.Card = reactInitialized && class BDFDB_Card extends Internal.LibraryModules.React.Component { + render() { + return BDFDB.ReactUtils.createElement("div", BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.hovercardwrapper, this.props.horizontal && BDFDB.disCN.hovercardhorizontal, this.props.backdrop && BDFDB.disCN.hovercard, this.props.className), + onMouseEnter: e => {if (typeof this.props.onMouseEnter == "function") this.props.onMouseEnter(e, this);}, + onMouseLeave: e => {if (typeof this.props.onMouseLeave == "function") this.props.onMouseLeave(e, this);}, + onClick: e => {if (typeof this.props.onClick == "function") this.props.onClick(e, this);}, + onContextMenu: e => {if (typeof this.props.onContextMenu == "function") this.props.onContextMenu(e, this);}, + children: [ + !this.props.noRemove ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Clickable, { + "aria-label": BDFDB.LanguageUtils.LanguageStrings.REMOVE, + className: BDFDB.disCNS.hovercardbutton + BDFDB.disCNS.hovercardremovebutton + BDFDB.disCN.hovercardremovebuttondefault, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + nativeClass: true, + name: Internal.LibraryComponents.SvgIcon.Names.REMOVE + }), + onClick: e => { + if (typeof this.props.onRemove == "function") this.props.onRemove(e, this); + BDFDB.ListenerUtils.stopEvent(e); + } + }) : null, + typeof this.props.children == "string" ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextElement, { + className: BDFDB.disCN.hovercardinner, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextScroller, {children: this.props.children}) + }) : this.props.children + ].flat(10).filter(n => n) + }), "backdrop", "horizontal", "noRemove")); + } + }; + Internal.setDefaultProps(CustomComponents.Card, {backdrop: true, noRemove: false}); + + CustomComponents.ChannelTextAreaButton = reactInitialized && class BDFDB_ChannelTextAreaButton extends Internal.LibraryModules.React.Component { + render() { + const inner = BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.textareabuttonwrapper, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: this.props.iconName, + iconSVG: this.props.iconSVG, + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.textareaicon, this.props.iconClassName, this.props.pulse && BDFDB.disCN.textareaiconpulse), + nativeClass: this.props.nativeClass + }) + }); + const button = BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Button, { + look: Internal.LibraryComponents.Button.Looks.BLANK, + size: Internal.LibraryComponents.Button.Sizes.NONE, + "aria-label": this.props.label, + tabIndex: this.props.tabIndex, + className: BDFDB.DOMUtils.formatClassName(this.props.isActive && BDFDB.disCN.textareabuttonactive), + innerClassName: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.textareabutton, this.props.className, this.props.pulse && BDFDB.disCN.textareaattachbuttonplus), + onClick: this.props.onClick, + onContextMenu: this.props.onContextMenu, + onMouseEnter: this.props.onMouseEnter, + onMouseLeave: this.props.onMouseLeave, + children: this.props.tooltip && this.props.tooltip.text ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TooltipContainer, Object.assign({}, this.props.tooltip, {children: inner})) : inner + }); + return (this.props.className || "").indexOf(BDFDB.disCN.textareapickerbutton) > -1 ? BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.textareapickerbuttoncontainer, + children: button + }) : button; + } + }; + Internal.setDefaultProps(CustomComponents.ChannelTextAreaButton, {tabIndex: 0}); + + CustomComponents.CharCounter = reactInitialized && class BDFDB_CharCounter extends Internal.LibraryModules.React.Component { + getCounterString() { + let input = this.refElement || {}, string = ""; + if (BDFDB.DOMUtils.containsClass(this.refElement, BDFDB.disCN.textarea)) { + let instance = BDFDB.ReactUtils.findOwner(input, {name: "ChannelTextAreaEditor", up: true}); + if (instance) string = instance.props.textValue; + else string = input.value || input.textContent || ""; + } + else string = input.value || input.textContent || ""; + if (this.props.max && this.props.showPercentage && (string.length/this.props.max) * 100 < this.props.showPercentage) return ""; + let start = input.selectionStart || 0, end = input.selectionEnd || 0, selectlength = end - start, selection = BDFDB.DOMUtils.getSelection(); + let select = !selectlength && !selection ? 0 : (selectlength || selection.length); + select = !select ? 0 : (select > string.length ? (end || start ? string.length - (string.length - end - start) : string.length) : select); + let children = [ + typeof this.props.renderPrefix == "function" && this.props.renderPrefix(string.length), + `${string.length}${!this.props.max ? "" : "/" + this.props.max}${!select ? "" : " (" + select + ")"}`, + typeof this.props.renderSuffix == "function" && this.props.renderSuffix(string.length) + ].filter(n => n); + if (typeof this.props.onChange == "function") this.props.onChange(this); + return children.length == 1 ? children[0] : BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + align: Internal.LibraryComponents.Flex.Align.CENTER, + children: children + }); + } + updateCounter() { + if (!this.refElement) return; + BDFDB.TimeUtils.clear(this.updateTimeout); + this.updateTimeout = BDFDB.TimeUtils.timeout(this.forceUpdateCounter.bind(this), 100); + } + forceUpdateCounter() { + if (!this.refElement) return; + this.props.children = this.getCounterString(); + BDFDB.ReactUtils.forceUpdate(this); + } + handleSelection() { + if (!this.refElement) return; + let mouseMove = _ => { + BDFDB.TimeUtils.timeout(this.forceUpdateCounter.bind(this), 10); + }; + let mouseUp = _ => { + document.removeEventListener("mousemove", mouseMove); + document.removeEventListener("mouseup", mouseUp); + if (this.refElement.selectionEnd - this.refElement.selectionStart) BDFDB.TimeUtils.timeout(_ => { + document.addEventListener("click", click); + }); + }; + let click = _ => { + BDFDB.TimeUtils.timeout(this.forceUpdateCounter.bind(this), 100); + document.removeEventListener("mousemove", mouseMove); + document.removeEventListener("mouseup", mouseUp); + document.removeEventListener("click", click); + }; + document.addEventListener("mousemove", mouseMove); + document.addEventListener("mouseup", mouseUp); + } + componentDidMount() { + if (this.props.refClass) { + let node = BDFDB.ReactUtils.findDOMNode(this); + if (node && node.parentElement) { + let parent = node.parentElement, loop = 0; + while (!this.refElement && parent && loop < 5) { + this.refElement = parent.querySelector(this.props.refClass); + parent = parent.parentElement; + loop++; + } + if (this.refElement) { + if (!this._updateCounter) this._updateCounter = _ => { + if (!document.contains(node)) BDFDB.ListenerUtils.multiRemove(this.refElement, "keydown click change", this._updateCounter); + else this.updateCounter(); + }; + if (!this._handleSelection) this._handleSelection = _ => { + if (!document.contains(node)) BDFDB.ListenerUtils.multiRemove(this.refElement, "mousedown", this._handleSelection); + else this.handleSelection(); + }; + BDFDB.ListenerUtils.multiRemove(this.refElement, "mousedown", this._handleSelection); + BDFDB.ListenerUtils.multiAdd(this.refElement, "mousedown", this._handleSelection); + if (this.refElement.tagName == "INPUT" || this.refElement.tagName == "TEXTAREA") { + BDFDB.ListenerUtils.multiRemove(this.refElement, "keydown click change", this._updateCounter); + BDFDB.ListenerUtils.multiAdd(this.refElement, "keydown click change", this._updateCounter); + } + else { + if (!this._mutationObserver) this._mutationObserver = new MutationObserver(changes => { + if (!document.contains(node)) this._mutationObserver.disconnect(); + else this.updateCounter(); + }); + else this._mutationObserver.disconnect(); + this._mutationObserver.observe(this.refElement, {childList: true, subtree: true}); + } + this.updateCounter(); + } + else BDFDB.LogUtils.warn(["could not find referenceElement for BDFDB_CharCounter"]); + } + } + else BDFDB.LogUtils.warn(["refClass can not be undefined for BDFDB_CharCounter"]); + } + render() { + let string = this.getCounterString(); + BDFDB.TimeUtils.timeout(_ => string != this.getCounterString() && BDFDB.ReactUtils.forceUpdate(this)); + return BDFDB.ReactUtils.createElement("div", BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.charcounter, this.props.className), + children: string + }), "parsing", "max", "refClass", "renderPrefix", "renderSuffix", "showPercentage")); + } + }; + + CustomComponents.Checkbox = reactInitialized && class BDFDB_Checkbox extends Internal.LibraryModules.React.Component { + render () { + return BDFDB.ReactUtils.createElement(class extends Internal.LibraryModules.React.Component { + handleMouseDown(e) {if (typeof this.props.onMouseDown == "function") this.props.onMouseDown(e, this);} + handleMouseUp(e) {if (typeof this.props.onMouseUp == "function") this.props.onMouseUp(e, this);} + handleMouseEnter(e) {if (typeof this.props.onMouseEnter == "function") this.props.onMouseEnter(e, this);} + handleMouseLeave(e) {if (typeof this.props.onMouseLeave == "function") this.props.onMouseLeave(e, this);} + getInputMode() { + return this.props.disabled ? "disabled" : this.props.readOnly ? "readonly" : "default"; + } + getStyle() { + let style = this.props.style || {}; + if (!this.props.value) return style; + style = Object.assign({}, style); + this.props.color = typeof this.props.getColor == "function" ? this.props.getColor(this.props.value) : this.props.color; + if (Internal.LibraryComponents.Checkbox.Types) switch (this.props.type) { + case Internal.LibraryComponents.Checkbox.Types.DEFAULT: + style.borderColor = this.props.color; + break; + case Internal.LibraryComponents.Checkbox.Types.GHOST: + let color = BDFDB.ColorUtils.setAlpha(this.props.color, 0.15, "RGB"); + style.backgroundColor = color; + style.borderColor = color; + break; + case Internal.LibraryComponents.Checkbox.Types.INVERTED: + style.backgroundColor = this.props.color; + style.borderColor = this.props.color; + } + return style; + } + getColor() { + return this.props.value ? (Internal.LibraryComponents.Checkbox.Types && this.props.type === Internal.LibraryComponents.Checkbox.Types.INVERTED ? Internal.DiscordConstants.Colors.WHITE : this.props.color) : "transparent"; + } + handleChange(e) { + this.props.value = typeof this.props.getValue == "function" ? this.props.getValue(this.props.value, e, this) : !this.props.value; + if (typeof this.props.onChange == "function") this.props.onChange(this.props.value, this); + BDFDB.ReactUtils.forceUpdate(this); + } + render() { + let label = this.props.children ? BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.checkboxlabel, this.props.disabled ? BDFDB.disCN.checkboxlabeldisabled : BDFDB.disCN.checkboxlabelclickable, this.props.reverse ? BDFDB.disCN.checkboxlabelreversed : BDFDB.disCN.checkboxlabelforward), + style: { + lineHeight: this.props.size + "px" + }, + children: this.props.children + }) : null; + return BDFDB.ReactUtils.createElement("label", { + className: BDFDB.DOMUtils.formatClassName(this.props.disabled ? BDFDB.disCN.checkboxwrapperdisabled : BDFDB.disCN.checkboxwrapper, this.props.align, this.props.className), + children: [ + this.props.reverse && label, + !this.props.displayOnly && BDFDB.ReactUtils.createElement(Internal.LibraryComponents.FocusRingScope, { + children: BDFDB.ReactUtils.createElement("input", { + className: BDFDB.disCN["checkboxinput" + this.getInputMode()], + type: "checkbox", + onClick: this.props.disabled || this.props.readOnly ? (_ => {}) : this.handleChange.bind(this), + onContextMenu: this.props.disabled || this.props.readOnly ? (_ => {}) : this.handleChange.bind(this), + onMouseUp: !this.props.disabled && this.handleMouseDown.bind(this), + onMouseDown: !this.props.disabled && this.handleMouseUp.bind(this), + onMouseEnter: !this.props.disabled && this.handleMouseEnter.bind(this), + onMouseLeave: !this.props.disabled && this.handleMouseLeave.bind(this), + checked: this.props.value, + style: { + width: this.props.size, + height: this.props.size + } + }) + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.checkbox, BDFDB.disCN["checkbox" + this.props.shape], this.props.value && BDFDB.disCN.checkboxchecked), + style: Object.assign({ + width: this.props.size, + height: this.props.size, + borderColor: this.props.checkboxColor + }, this.getStyle()), + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Checkmark, { + size: "null", + width: 18, + height: 18, + color: this.getColor(), + "aria-hidden": true + }) + }), + !this.props.reverse && label + ].filter(n => n) + }); + } + }, this.props); + } + }; + if (CustomComponents.Checkbox) { + CustomComponents.Checkbox.Types = { + DEFAULT: "DEFAULT", + GHOST: "GHOST", + INVERTED: "INVERTED" + }; + CustomComponents.Checkbox.Shapes = { + BOX: "box", + ROUND: "round" + }; + Internal.setDefaultProps(CustomComponents.Checkbox, {type: CustomComponents.Checkbox.Types.INVERTED, shape: CustomComponents.Checkbox.Shapes.ROUND}); + } + + CustomComponents.Clickable = reactInitialized && class BDFDB_Clickable extends Internal.LibraryModules.React.Component { + handleClick(e) {if (typeof this.props.onClick == "function") this.props.onClick(e, this);} + handleContextMenu(e) {if (typeof this.props.onContextMenu == "function") this.props.onContextMenu(e, this);} + handleMouseDown(e) {if (typeof this.props.onMouseDown == "function") this.props.onMouseDown(e, this);} + handleMouseUp(e) {if (typeof this.props.onMouseUp == "function") this.props.onMouseUp(e, this);} + handleMouseEnter(e) {if (typeof this.props.onMouseEnter == "function") this.props.onMouseEnter(e, this);} + handleMouseLeave(e) {if (typeof this.props.onMouseLeave == "function") this.props.onMouseLeave(e, this);} + render() { + return BDFDB.ReactUtils.createElement(Internal.NativeSubComponents.Clickable, Object.assign({}, this.props, { + className: BDFDB.DOMUtils.formatClassName(this.props.className, (this.props.className || "").toLowerCase().indexOf("disabled") == -1 && BDFDB.disCN.cursorpointer), + onClick: this.handleClick.bind(this), + onContextMenu: this.handleContextMenu.bind(this), + onMouseUp: this.handleMouseDown.bind(this), + onMouseDown: !this.props.disabled && this.handleMouseUp.bind(this), + onMouseEnter: this.handleMouseEnter.bind(this), + onMouseLeave: this.handleMouseLeave.bind(this) + })); + } + }; + + CustomComponents.CollapseContainer = reactInitialized && class BDFDB_CollapseContainer extends Internal.LibraryModules.React.Component { + render() { + if (!BDFDB.ObjectUtils.is(this.props.collapseStates)) this.props.collapseStates = {}; + this.props.collapsed = this.props.collapsed && (this.props.collapseStates[this.props.title] || this.props.collapseStates[this.props.title] === undefined); + this.props.collapseStates[this.props.title] = this.props.collapsed; + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(this.props.collapsed && BDFDB.disCN.collapsecontainercollapsed, this.props.mini ? BDFDB.disCN.collapsecontainermini : BDFDB.disCN.collapsecontainer, this.props.className), + id: this.props.id, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + className: BDFDB.disCN.collapsecontainerheader, + align: Internal.LibraryComponents.Flex.Align.CENTER, + onClick: e => { + this.props.collapsed = !this.props.collapsed; + this.props.collapseStates[this.props.title] = this.props.collapsed; + if (typeof this.props.onClick == "function") this.props.onClick(this.props.collapsed, this); + BDFDB.ReactUtils.forceUpdate(this); + }, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.FormTitle.Title, { + tag: Internal.LibraryComponents.FormTitle.Tags && Internal.LibraryComponents.FormTitle.Tags.H5, + className: BDFDB.disCN.collapsecontainertitle, + children: this.props.title + }) + }), + !this.props.collapsed ? BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.collapsecontainerinner, + children: this.props.children + }) : null + ] + }); + } + }; + Internal.setDefaultProps(CustomComponents.CollapseContainer, {collapsed: true, mini: true}); + + CustomComponents.ColorPicker = reactInitialized && class BDFDB_ColorPicker extends Internal.LibraryModules.React.Component { + constructor(props) { + super(props); + if (!this.state) this.state = {}; + this.state.isGradient = props.gradient && props.color && BDFDB.ObjectUtils.is(props.color); + this.state.gradientBarEnabled = this.state.isGradient; + this.state.draggingAlphaCursor = false; + this.state.draggingGradientCursor = false; + this.state.selectedGradientCursor = 0; + } + handleColorChange(color) { + let changed = false; + if (color != null) { + changed = !BDFDB.equals(this.state.isGradient ? this.props.color[this.state.selectedGradientCursor] : this.props.color, color); + if (this.state.isGradient) this.props.color[this.state.selectedGradientCursor] = color; + else this.props.color = color; + } + else changed = true; + if (changed) { + if (typeof this.props.onColorChange == "function") this.props.onColorChange(BDFDB.ColorUtils.convert(this.props.color, "RGBCOMP")); + BDFDB.ReactUtils.forceUpdate(this); + } + } + render() { + if (this.state.isGradient) this.props.color = Object.assign({}, this.props.color); + + let colorFormat = this.props.alpha ? "HSVA" : "HSV"; + let hexRegex = this.props.alpha ? /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i : /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; + + let selectedColor = BDFDB.ColorUtils.convert(this.state.isGradient ? this.props.color[this.state.selectedGradientCursor] : this.props.color, colorFormat) || BDFDB.ColorUtils.convert("#000000FF", colorFormat); + let currentGradient = (this.state.isGradient ? Object.entries(this.props.color, colorFormat) : [[0, selectedColor], [1, selectedColor]]); + + let [h, s, v] = BDFDB.ColorUtils.convert(selectedColor, "HSVCOMP"); + let a = BDFDB.ColorUtils.getAlpha(selectedColor); + a = a == null ? 1 : a; + + let hexColor = BDFDB.ColorUtils.convert(selectedColor, this.props.alpha ? "HEXA" : "HEX"); + let hexLength = hexColor.length; + + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.PopoutFocusLock, { + className: BDFDB.disCNS.colorpickerwrapper + BDFDB.disCN.colorpicker, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickerinner, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickersaturation, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickersaturationcolor, + style: {position: "absolute", top: 0, right: 0, bottom: 0, left: 0, cursor: "crosshair", backgroundColor: BDFDB.ColorUtils.convert([h, "100%", "50%"], "RGB")}, + onClick: event => { + let rects = BDFDB.DOMUtils.getRects(BDFDB.DOMUtils.getParent(BDFDB.dotCN.colorpickersaturationcolor, event.target)); + this.handleColorChange(BDFDB.ColorUtils.convert([h, BDFDB.NumberUtils.mapRange([rects.left, rects.left + rects.width], [0, 100], event.clientX) + "%", BDFDB.NumberUtils.mapRange([rects.top, rects.top + rects.height], [100, 0], event.clientY) + "%", a], colorFormat, "HSVCOMP")); + }, + onMouseDown: event => { + let rects = BDFDB.DOMUtils.getRects(BDFDB.DOMUtils.getParent(BDFDB.dotCN.colorpickersaturationcolor, event.target)); + let mouseUp = _ => { + document.removeEventListener("mouseup", mouseUp); + document.removeEventListener("mousemove", mouseMove); + }; + let mouseMove = event2 => { + this.handleColorChange(BDFDB.ColorUtils.convert([h, BDFDB.NumberUtils.mapRange([rects.left, rects.left + rects.width], [0, 100], event2.clientX) + "%", BDFDB.NumberUtils.mapRange([rects.top, rects.top + rects.height], [100, 0], event2.clientY) + "%", a], colorFormat, "HSVCOMP")); + }; + document.addEventListener("mouseup", mouseUp); + document.addEventListener("mousemove", mouseMove); + }, + children: [ + BDFDB.ReactUtils.createElement("style", { + children: `${BDFDB.dotCN.colorpickersaturationwhite} {background: -webkit-linear-gradient(to right, #fff, rgba(255,255,255,0));background: linear-gradient(to right, #fff, rgba(255,255,255,0));}${BDFDB.dotCN.colorpickersaturationblack} {background: -webkit-linear-gradient(to top, #000, rgba(0,0,0,0));background: linear-gradient(to top, #000, rgba(0,0,0,0));}` + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickersaturationwhite, + style: {position: "absolute", top: 0, right: 0, bottom: 0, left: 0}, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickersaturationblack, + style: {position: "absolute", top: 0, right: 0, bottom: 0, left: 0} + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickersaturationcursor, + style: {position: "absolute", cursor: "crosshair", left: s, top: `${BDFDB.NumberUtils.mapRange([0, 100], [100, 0], parseFloat(v))}%`}, + children: BDFDB.ReactUtils.createElement("div", { + style: {width: 4, height: 4, boxShadow: "rgb(255, 255, 255) 0px 0px 0px 1.5px, rgba(0, 0, 0, 0.3) 0px 0px 1px 1px inset, rgba(0, 0, 0, 0.4) 0px 0px 1px 2px", borderRadius: "50%", transform: "translate(-2px, -2px)"} + }) + }) + ] + }) + ] + }) + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickerhue, + children: BDFDB.ReactUtils.createElement("div", { + style: {position: "absolute", top: 0, right: 0, bottom: 0, left: 0}, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickerhuehorizontal, + style: {padding: "0px 2px", position: "relative", height: "100%"}, + onClick: event => { + let rects = BDFDB.DOMUtils.getRects(BDFDB.DOMUtils.getParent(BDFDB.dotCN.colorpickerhuehorizontal, event.target)); + this.handleColorChange(BDFDB.ColorUtils.convert([BDFDB.NumberUtils.mapRange([rects.left, rects.left + rects.width], [0, 360], event.clientX), s, v, a], colorFormat, "HSVCOMP")); + }, + onMouseDown: event => { + let rects = BDFDB.DOMUtils.getRects(BDFDB.DOMUtils.getParent(BDFDB.dotCN.colorpickerhuehorizontal, event.target)); + let mouseUp = _ => { + document.removeEventListener("mouseup", mouseUp); + document.removeEventListener("mousemove", mouseMove); + }; + let mouseMove = event2 => { + this.handleColorChange(BDFDB.ColorUtils.convert([BDFDB.NumberUtils.mapRange([rects.left, rects.left + rects.width], [0, 360], event2.clientX), s, v, a], colorFormat, "HSVCOMP")); + }; + document.addEventListener("mouseup", mouseUp); + document.addEventListener("mousemove", mouseMove); + }, + children: [ + BDFDB.ReactUtils.createElement("style", { + children: `${BDFDB.dotCN.colorpickerhuehorizontal} {background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);background: -webkit-linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);}${BDFDB.dotCN.colorpickerhuevertical} {background: linear-gradient(to top, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);background: -webkit-linear-gradient(to top, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);}` + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickerhuecursor, + style: {position: "absolute", cursor: "ew-resize", left: `${BDFDB.NumberUtils.mapRange([0, 360], [0, 100], h)}%`}, + children: BDFDB.ReactUtils.createElement("div", { + style: {marginTop: 1, width: 4, borderRadius: 1, height: 8, boxShadow: "rgba(0, 0, 0, 0.6) 0px 0px 2px", background: "rgb(255, 255, 255)", transform: "translateX(-2px)"} + }) + }) + ] + }) + }) + }), + this.props.alpha && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickeralpha, + children: [ + BDFDB.ReactUtils.createElement("div", { + style: {position: "absolute", top: 0, right: 0, bottom: 0, left: 0}, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickeralphacheckered, + style: {padding: "0px 2px", position: "relative", height: "100%"} + }) + }), + BDFDB.ReactUtils.createElement("div", { + style: {position: "absolute", top: 0, right: 0, bottom: 0, left: 0}, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickeralphahorizontal, + style: {padding: "0px 2px", position: "relative", height: "100%", background: `linear-gradient(to right, ${BDFDB.ColorUtils.setAlpha([h, s, v], 0, "RGBA")}, ${BDFDB.ColorUtils.setAlpha([h, s, v], 1, "RGBA")}`}, + onClick: event => { + let rects = BDFDB.DOMUtils.getRects(BDFDB.DOMUtils.getParent(BDFDB.dotCN.colorpickeralphahorizontal, event.target)); + this.handleColorChange(BDFDB.ColorUtils.setAlpha([h, s, v], BDFDB.NumberUtils.mapRange([rects.left, rects.left + rects.width], [0, 1], event.clientX), colorFormat)); + }, + onMouseDown: event => { + let rects = BDFDB.DOMUtils.getRects(BDFDB.DOMUtils.getParent(BDFDB.dotCN.colorpickeralphahorizontal, event.target)); + let mouseUp = _ => { + document.removeEventListener("mouseup", mouseUp); + document.removeEventListener("mousemove", mouseMove); + this.state.draggingAlphaCursor = false; + BDFDB.ReactUtils.forceUpdate(this); + }; + let mouseMove = event2 => { + this.state.draggingAlphaCursor = true; + this.handleColorChange(BDFDB.ColorUtils.setAlpha([h, s, v], BDFDB.NumberUtils.mapRange([rects.left, rects.left + rects.width], [0, 1], event2.clientX), colorFormat)); + }; + document.addEventListener("mouseup", mouseUp); + document.addEventListener("mousemove", mouseMove); + }, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickeralphacursor, + style: {position: "absolute", cursor: "ew-resize", left: `${a * 100}%`}, + children: [ + BDFDB.ReactUtils.createElement("div", { + style: {marginTop: 1, width: 4, borderRadius: 1, height: 8, boxShadow: "rgba(0, 0, 0, 0.6) 0px 0px 2px", background: "rgb(255, 255, 255)", transform: "translateX(-2px)"} + }), + this.state.draggingAlphaCursor && BDFDB.ReactUtils.createElement("span", { + className: BDFDB.disCN.sliderbubble, + style: {opacity: 1, visibility: "visible", left: 2}, + children: `${Math.floor(a * 100)}%` + }) + ].filter(n => n) + }) + }) + }) + ] + }), + this.state.gradientBarEnabled && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickergradient, + children: [ + BDFDB.ReactUtils.createElement("div", { + style: {position: "absolute", top: 0, right: 0, bottom: 0, left: 0}, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickergradientcheckered, + style: {padding: "0px 2px", position: "relative", height: "100%"} + }) + }), + BDFDB.ReactUtils.createElement("div", { + style: {position: "absolute", top: 0, right: 0, bottom: 0, left: 0}, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickergradienthorizontal, + style: {padding: "0px 2px", position: "relative", cursor: "copy", height: "100%", background: BDFDB.ColorUtils.createGradient(currentGradient.reduce((colorObj, posAndColor) => (colorObj[posAndColor[0]] = posAndColor[1], colorObj), {}))}, + onClick: event => { + let rects = BDFDB.DOMUtils.getRects(event.target); + let pos = BDFDB.NumberUtils.mapRange([rects.left, rects.left + rects.width], [0.01, 0.99], event.clientX); + if (Object.keys(this.props.color).indexOf(pos) == -1) { + this.props.color[pos] = BDFDB.ColorUtils.convert("#000000FF", colorFormat); + this.state.selectedGradientCursor = pos; + this.handleColorChange(); + } + }, + children: currentGradient.map(posAndColor => BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.colorpickergradientcursor, (posAndColor[0] == 0 || posAndColor[0] == 1) && BDFDB.disCN.colorpickergradientcursoredge, this.state.selectedGradientCursor == posAndColor[0] && BDFDB.disCN.colorpickergradientcursorselected), + style: {position: "absolute", cursor: "pointer", left: `${posAndColor[0] * 100}%`}, + onMouseDown: posAndColor[0] == 0 || posAndColor[0] == 1 ? _ => {} : event => { + event = event.nativeEvent || event; + let mouseMove = event2 => { + if (Math.sqrt((event.pageX - event2.pageX)**2) > 10) { + document.removeEventListener("mousemove", mouseMove); + document.removeEventListener("mouseup", mouseUp); + + this.state.draggingGradientCursor = true; + let cursor = BDFDB.DOMUtils.getParent(BDFDB.dotCN.colorpickergradientcursor, event.target); + let rects = BDFDB.DOMUtils.getRects(cursor.parentElement); + + let releasing = _ => { + document.removeEventListener("mousemove", dragging); + document.removeEventListener("mouseup", releasing); + BDFDB.TimeUtils.timeout(_ => {this.state.draggingGradientCursor = false;}); + }; + let dragging = event3 => { + let pos = BDFDB.NumberUtils.mapRange([rects.left, rects.left + rects.width], [0.01, 0.99], event3.clientX); + if (Object.keys(this.props.color).indexOf(pos) == -1) { + delete this.props.color[posAndColor[0]]; + posAndColor[0] = pos; + this.props.color[pos] = posAndColor[1]; + this.state.selectedGradientCursor = pos; + this.handleColorChange(); + } + }; + document.addEventListener("mousemove", dragging); + document.addEventListener("mouseup", releasing); + } + }; + let mouseUp = _ => { + document.removeEventListener("mousemove", mouseMove); + document.removeEventListener("mouseup", mouseUp); + }; + document.addEventListener("mousemove", mouseMove); + document.addEventListener("mouseup", mouseUp); + }, + onClick: event => { + BDFDB.ListenerUtils.stopEvent(event); + if (!this.state.draggingGradientCursor) { + this.state.selectedGradientCursor = posAndColor[0]; + BDFDB.ReactUtils.forceUpdate(this); + } + }, + onContextMenu: posAndColor[0] == 0 || posAndColor[0] == 1 ? _ => {} : event => { + BDFDB.ListenerUtils.stopEvent(event); + delete this.props.color[posAndColor[0]]; + this.state.selectedGradientCursor = 0; + this.handleColorChange(); + }, + children: BDFDB.ReactUtils.createElement("div", { + style: {background: BDFDB.ColorUtils.convert(posAndColor[1], "RGBA")} + }) + })) + }) + }) + ] + }) + ].filter(n => n) + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextInput, { + className: BDFDB.disCN.margintop8, + maxLength: this.props.alpha ? 9 : 7, + valuePrefix: "#", + value: hexColor, + autoFocus: true, + onChange: value => { + const oldLength = hexLength; + hexLength = (value || "").length; + if (this.props.alpha && (oldLength > 8 || oldLength < 6) && hexLength == 7) value += "FF"; + if (hexRegex.test(value)) this.handleColorChange(value); + }, + inputChildren: this.props.gradient && BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TooltipContainer, { + text: BDFDB.LanguageUtils.LibraryStrings.gradient, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Clickable, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.colorpickergradientbutton, this.state.gradientBarEnabled && BDFDB.disCN.colorpickergradientbuttonenabled), + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + nativeClass: true, + width: 28, + height: 28, + name: Internal.LibraryComponents.SvgIcon.Names.GRADIENT + }), + onClick: _ => { + this.state.gradientBarEnabled = !this.state.gradientBarEnabled; + if (this.state.gradientBarEnabled && !this.state.isGradient) this.props.color = {0: selectedColor, 1: selectedColor}; + else if (!this.state.gradientBarEnabled && this.state.isGradient) this.props.color = selectedColor; + this.state.isGradient = this.props.color && BDFDB.ObjectUtils.is(this.props.color); + this.handleColorChange(); + } + }) + }) + }), + BDFDB.ReactUtils.createElement("div", { + className: "move-corners", + children: [{top: 0, left: 0}, {top: 0, right: 0}, {bottom: 0, right: 0}, {bottom: 0, left: 0}].map(pos => BDFDB.ReactUtils.createElement("div", { + className: "move-corner", + onMouseDown: e => { + if (!this.domElementRef.current) return; + let rects = BDFDB.DOMUtils.getRects(this.domElementRef.current); + let left = rects.left, top = rects.top; + let oldX = e.pageX, oldY = e.pageY; + let mouseUp = _ => { + document.removeEventListener("mouseup", mouseUp); + document.removeEventListener("mousemove", mouseMove); + }; + let mouseMove = e2 => { + left = left - (oldX - e2.pageX), top = top - (oldY - e2.pageY); + oldX = e2.pageX, oldY = e2.pageY; + this.domElementRef.current.style.setProperty("left", `${left}px`, "important"); + this.domElementRef.current.style.setProperty("top", `${top}px`, "important"); + }; + document.addEventListener("mouseup", mouseUp); + document.addEventListener("mousemove", mouseMove); + }, + style: Object.assign({}, pos, {width: 10, height: 10, cursor: "move", position: "absolute"}) + })) + }) + ] + }); + } + }; + + CustomComponents.ColorSwatches = reactInitialized && class BDFDB_ColorSwatches extends Internal.LibraryModules.React.Component { + ColorSwatch(props) { + const swatches = props.swatches; + let useWhite = !BDFDB.ColorUtils.isBright(props.color); + let swatch = BDFDB.ReactUtils.createElement("button", { + type: "button", + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.colorpickerswatch, props.isSingle && BDFDB.disCN.colorpickerswatchsingle, props.isDisabled && BDFDB.disCN.colorpickerswatchdisabled, props.isSelected && BDFDB.disCN.colorpickerswatchselected, props.isCustom && BDFDB.disCN.colorpickerswatchcustom, props.color == null && BDFDB.disCN.colorpickerswatchnocolor), + number: props.number, + disabled: props.isDisabled, + onClick: _ => { + if (!props.isSelected) { + let color = props.isCustom && props.color == null ? (swatches.props.color || swatches.props.defaultCustomColor || "rgba(0, 0, 0, 1)") : props.color; + if (typeof swatches.props.onColorChange == "function") swatches.props.onColorChange(BDFDB.ColorUtils.convert(color, "RGBCOMP")); + swatches.props.color = color; + swatches.props.customColor = props.isCustom ? color : swatches.props.customColor; + swatches.props.customSelected = props.isCustom; + BDFDB.ReactUtils.forceUpdate(swatches); + } + }, + style: Object.assign({}, props.style, { + background: BDFDB.ObjectUtils.is(props.color) ? BDFDB.ColorUtils.createGradient(props.color) : BDFDB.ColorUtils.convert(props.color, "RGBA") + }), + children: [ + props.isCustom || props.isSingle ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.colorpickerswatchdropper, + name: Internal.LibraryComponents.SvgIcon.Names.DROPPER, + width: props.isCustom ? 14 : 10, + height: props.isCustom ? 14 : 10, + color: useWhite ? Internal.DiscordConstants.Colors.WHITE : Internal.DiscordConstants.Colors.BLACK + }) : null, + props.isSelected && !props.isSingle ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: Internal.LibraryComponents.SvgIcon.Names.CHECKMARK, + width: props.isCustom ? 32 : 16, + height: props.isCustom ? 24 : 16, + color: useWhite ? Internal.DiscordConstants.Colors.WHITE : Internal.DiscordConstants.Colors.BLACK + }) : null + ] + }); + if (props.isCustom || props.isSingle || props.color == null) swatch = BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TooltipContainer, { + text: props.isCustom || props.isSingle ? BDFDB.LanguageUtils.LanguageStrings.CUSTOM_COLOR : BDFDB.LanguageUtils.LanguageStrings.DEFAULT, + tooltipConfig: {type: props.isSingle ? "top" : "bottom"}, + children: swatch + }); + if (props.isCustom || props.isSingle) swatch = BDFDB.ReactUtils.createElement(Internal.LibraryComponents.PopoutContainer, { + children: swatch, + wrap: false, + popoutClassName: BDFDB.disCNS.colorpickerwrapper + BDFDB.disCN.colorpicker, + animation: Internal.LibraryComponents.PopoutContainer.Animation.TRANSLATE, + position: Internal.LibraryComponents.PopoutContainer.Positions.BOTTOM, + align: Internal.LibraryComponents.PopoutContainer.Align.CENTER, + open: swatches.props.pickerOpen, + onClick: _ => swatches.props.pickerOpen = true, + onOpen: _ => { + swatches.props.pickerOpen = true; + if (typeof swatches.props.onPickerOpen == "function") swatches.props.onPickerOpen(this); + }, + onClose: _ => { + delete swatches.props.pickerOpen; + if (typeof swatches.props.onPickerClose == "function") swatches.props.onPickerClose(this); + }, + renderPopout: _ => BDFDB.ReactUtils.createElement(Internal.LibraryComponents.ColorPicker, Object.assign({}, swatches.props.pickerConfig, { + color: swatches.props.color, + onColorChange: color => { + if (typeof swatches.props.onColorChange == "function") swatches.props.onColorChange(color); + props.color = color; + swatches.props.color = color; + swatches.props.customColor = color; + swatches.props.customSelected = true; + BDFDB.ReactUtils.forceUpdate(swatches); + } + }), true) + }); + if (props.isCustom) swatch = BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickerswatchcustomcontainer, + children: swatch + }); + return swatch; + } + render() { + this.props.color = BDFDB.ObjectUtils.is(this.props.color) ? this.props.color : BDFDB.ColorUtils.convert(this.props.color, "RGBA"); + this.props.colors = (BDFDB.ArrayUtils.is(this.props.colors) ? this.props.colors : [null, 5433630, 3066993, 1752220, 3447003, 3429595, 8789737, 10181046, 15277667, 15286558, 15158332, 15105570, 15844367, 13094093, 7372936, 6513507, 16777215, 3910932, 2067276, 1146986, 2123412, 2111892, 7148717, 7419530, 11342935, 11345940, 10038562, 11027200, 12745742, 9936031, 6121581, 2894892]).map(c => BDFDB.ColorUtils.convert(c, "RGBA")); + this.props.colorRows = this.props.colors.length ? [this.props.colors.slice(0, parseInt(this.props.colors.length/2)), this.props.colors.slice(parseInt(this.props.colors.length/2))] : []; + this.props.customColor = !this.props.color || !this.props.customSelected && this.props.colors.indexOf(this.props.color) > -1 ? null : this.props.color; + this.props.defaultCustomColor = BDFDB.ObjectUtils.is(this.props.defaultCustomColor) ? this.props.defaultCustomColor : BDFDB.ColorUtils.convert(this.props.defaultCustomColor, "RGBA"); + this.props.customSelected = !!this.props.customColor; + this.props.pickerConfig = BDFDB.ObjectUtils.is(this.props.pickerConfig) ? this.props.pickerConfig : {gradient: true, alpha: true}; + + const isSingle = !this.props.colors.length; + return BDFDB.ReactUtils.createElement("div", { + className: isSingle ? BDFDB.disCN.colorpickerswatchsinglewrapper : BDFDB.DOMUtils.formatClassName(BDFDB.disCN.colorpickerswatches, BDFDB.disCN.colorpickerswatchescontainer, this.props.disabled && BDFDB.disCN.colorpickerswatchesdisabled), + children: [ + BDFDB.ReactUtils.createElement(this.ColorSwatch, { + swatches: this, + color: this.props.customColor, + isSingle: isSingle, + isCustom: !isSingle, + isSelected: this.props.customSelected, + isDisabled: this.props.disabled, + pickerOpen: this.props.pickerOpen, + style: {margin: 0} + }), + !isSingle && BDFDB.ReactUtils.createElement("div", { + children: this.props.colorRows.map(row => BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.colorpickerrow, + children: row.map(color => BDFDB.ReactUtils.createElement(this.ColorSwatch, { + swatches: this, + color: color, + isCustom: false, + isSelected: !this.props.customSelected && color == this.props.color, + isDisabled: this.props.disabled + })) + })) + }) + ] + }); + } + }; + + CustomComponents.DateInput = reactInitialized && class BDFDB_DateInput extends Internal.LibraryModules.React.Component { + renderFormatButton(props) { + const button = BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Clickable, { + className: BDFDB.disCN.dateinputbutton, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: props.svgName, + width: 20, + height: 20 + }) + }); + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.PopoutContainer, { + width: props.popoutWidth || 350, + padding: 10, + animation: Internal.LibraryComponents.PopoutContainer.Animation.SCALE, + position: Internal.LibraryComponents.PopoutContainer.Positions.TOP, + align: Internal.LibraryComponents.PopoutContainer.Align.RIGHT, + onClose: instance => BDFDB.DOMUtils.removeClass(instance.domElementRef.current, BDFDB.disCN.dateinputbuttonselected), + renderPopout: instance => { + BDFDB.DOMUtils.addClass(instance.domElementRef.current, BDFDB.disCN.dateinputbuttonselected); + return props.children || BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + align: Internal.LibraryComponents.Flex.Align.CENTER, + children: [ + props.name && BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SettingsLabel, { + label: props.name + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextInput, { + className: BDFDB.disCN.dateinputfield, + placeholder: props.placeholder, + value: props.getValue(), + onChange: typeof props.onChange == "function" ? props.onChange : null + }), + props.tooltipText && this.renderInfoButton(props.tooltipText) + ].filter(n => n) + }) + }, + children: props.name ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TooltipContainer, { + text: props.name, + children: button + }) : button + }); + } + renderInfoButton(text, style) { + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TooltipContainer, { + text: [text].flat(10).filter(n => n).map(n => BDFDB.ReactUtils.createElement("div", {children: n})), + tooltipConfig: { + type: "bottom", + zIndex: 1009, + maxWidth: 560 + }, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.dateinputbutton, + style: Object.assign({}, style), + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: Internal.LibraryComponents.SvgIcon.Names.QUESTIONMARK, + width: 24, + height: 24 + }) + }) + }); + } + handleChange() { + if (typeof this.props.onChange == "function") this.props.onChange(BDFDB.ObjectUtils.extract(this.props, "formatString", "dateString", "timeString", "timeOffset", "language")); + } + render() { + let input = this, formatter, preview; + const defaultOffset = ((new Date()).getTimezoneOffset() * (-1/60)); + return BDFDB.ReactUtils.createElement("div", BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.dateinputwrapper, this.props.className), + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SettingsLabel, { + label: this.props.label + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.dateinputinner, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.dateinputcontrols, + children: [ + BDFDB.ReactUtils.createElement(class DateInputPreview extends Internal.LibraryModules.React.Component { + componentDidMount() {formatter = this;} + render() { + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextInput, { + className: BDFDB.disCN.dateinputfield, + placeholder: Internal.LibraryComponents.DateInput.getDefaultString(input.props.language), + value: input.props.formatString, + onChange: value => { + input.props.formatString = value; + input.handleChange.apply(input, []); + BDFDB.ReactUtils.forceUpdate(formatter, preview); + } + }); + } + }), + this.renderInfoButton([ + "$date will be replaced with the Date", + "$time will be replaced with the Time", + "$time12 will be replaced with the Time (12h Form)", + "$month will be replaced with the Month Name", + "$monthS will be replaced with the Month Name (Short Form)", + "$day will be replaced with the Weekday Name", + "$dayS will be replaced with the Weekday Name (Short Form)", + "$agoAmount will be replaced with ('Today', 'Yesterday', 'x days/weeks/months ago')", + "$agoWeekday will be replaced with ('Today', 'Yesterday', $day)", + "$agoWeekdayS will be replaced with ('Today', 'Yesterday', $dayS)", + "$agoDays will be replaced with ('Today', 'Yesterday', 'x days ago')", + "$agoDate will be replaced with ('Today', 'Yesterday', $date)" + ], {marginRight: 6}), + this.renderFormatButton({ + name: BDFDB.LanguageUtils.LanguageStrings.DATE, + svgName: Internal.LibraryComponents.SvgIcon.Names.CALENDAR, + placeholder: this.props.dateString, + getValue: _ => this.props.dateString, + tooltipText: [ + "$d will be replaced with the Day", + "$dd will be replaced with the Day (Forced Zeros)", + "$m will be replaced with the Month", + "$mm will be replaced with the Month (Forced Zeros)", + "$yy will be replaced with the Year (2-Digit)", + "$yyyy will be replaced with the Year (4-Digit)", + "$month will be replaced with the Month Name", + "$monthS will be replaced with the Month Name (Short Form)", + ], + onChange: value => { + this.props.dateString = value; + this.handleChange.apply(this, []); + BDFDB.ReactUtils.forceUpdate(formatter, preview); + } + }), + this.renderFormatButton({ + name: BDFDB.LanguageUtils.LibraryStrings.time, + svgName: Internal.LibraryComponents.SvgIcon.Names.CLOCK, + placeholder: this.props.timeString, + getValue: _ => this.props.timeString, + tooltipText: [ + "$h will be replaced with the Hours", + "$hh will be replaced with the Hours (Forced Zeros)", + "$m will be replaced with the Minutes", + "$mm will be replaced with the Minutes (Forced Zeros)", + "$s will be replaced with the Seconds", + "$ss will be replaced with the Seconds (Forced Zeros)", + "$u will be replaced with the Milliseconds", + "$uu will be replaced with the Milliseconds (Forced Zeros)" + ], + onChange: value => { + this.props.timeString = value; + this.handleChange.apply(this, []); + BDFDB.ReactUtils.forceUpdate(formatter, preview); + } + }), + this.renderFormatButton({ + name: BDFDB.LanguageUtils.LibraryStrings.location, + svgName: Internal.LibraryComponents.SvgIcon.Names.GLOBE, + popoutWidth: 550, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.AutoFocusCatcher, {}), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + className: BDFDB.disCN.marginbottom4, + align: Internal.LibraryComponents.Flex.Align.CENTER, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SettingsLabel, { + label: BDFDB.LanguageUtils.LanguageStrings.LANGUAGE + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Select, { + className: BDFDB.disCN.dateinputfield, + value: this.props.language != null ? this.props.language : "$discord", + options: Object.keys(BDFDB.LanguageUtils.languages).map(id => ({ + value: id, + label: BDFDB.LanguageUtils.getName(BDFDB.LanguageUtils.languages[id]) + })), + searchable: true, + optionRenderer: lang => lang.label, + onChange: value => { + this.props.language = value == "$discord" ? undefined : value; + this.handleChange.apply(this, []); + BDFDB.ReactUtils.forceUpdate(formatter, preview); + } + }) + ] + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + align: Internal.LibraryComponents.Flex.Align.CENTER, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SettingsLabel, { + label: BDFDB.LanguageUtils.LibraryStrings.timezone + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Select, { + className: BDFDB.disCN.dateinputfield, + value: this.props.timeOffset != null ? this.props.timeOffset : defaultOffset, + options: [-12.0, -11.0, -10.0, -9.5, -9.0, -8.0, -7.0, -6.0, -5.0, -4.0, -3.5, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 5.75, 6.0, 6.5, 7.0, 8.0, 8.75, 9.0, 9.5, 10.0, 10.5, 11.0, 12.0, 12.75, 13.0, 14.0].map(offset => ({label: offset< 0 ? offset : `+${offset}`, value: offset})), + searchable: true, + onChange: value => { + this.props.timeOffset = value == defaultOffset ? undefined : value; + this.handleChange.apply(this, []); + BDFDB.ReactUtils.forceUpdate(formatter, preview); + } + }) + ] + }) + ] + }) + ] + }), + BDFDB.ReactUtils.createElement(class DateInputPreview extends Internal.LibraryModules.React.Component { + componentDidMount() {preview = this;} + render() { + return !input.props.noPreview && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.dateinputpreview, + children: [ + input.props.prefix && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.dateinputpreviewprefix, + children: typeof input.props.prefix == "function" ? input.props.prefix(input) : input.props.prefix, + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextScroller, { + children: Internal.LibraryComponents.DateInput.format(input.props, new Date((new Date()) - (1000*60*60*24*2))) + }), + input.props.suffix && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.dateinputpreviewsuffix, + children: typeof input.props.suffix == "function" ? input.props.suffix(input) : input.props.suffix, + }) + ].filter(n => n) + }); + } + }) + ] + }) + ] + }), "onChange", "label", "formatString", "dateString", "timeString", "timeOffset", "language", "noPreview", "prefix", "suffix")); + } + }; + if (CustomComponents.DateInput) { + CustomComponents.DateInput.getDefaultString = function (language) { + language = language || BDFDB.LanguageUtils.getLanguage().id; + const date = new Date(); + return date.toLocaleString(language).replace(date.toLocaleDateString(language), "$date").replace(date.toLocaleTimeString(language, {hourCycle: "h12"}), "$time12").replace(date.toLocaleTimeString(language, {hourCycle: "h11"}), "$time12").replace(date.toLocaleTimeString(language, {hourCycle: "h24"}), "$time").replace(date.toLocaleTimeString(language, {hourCycle: "h23"}), "$time"); + }; + CustomComponents.DateInput.parseDate = function (date, offset) { + let timeObj = date; + if (typeof timeObj == "string") { + const language = BDFDB.LanguageUtils.getLanguage().id; + for (let i = 0; i < 12; i++) { + const tempDate = new Date(); + tempDate.setMonth(i); + timeObj = timeObj.replace(tempDate.toLocaleDateString(language, {month:"long"}), tempDate.toLocaleDateString("en", {month:"short"})); + } + timeObj = new Date(timeObj); + } + else if (typeof timeObj == "number") timeObj = new Date(timeObj); + + if (timeObj.toString() == "Invalid Date") timeObj = new Date(parseInt(date)); + if (timeObj.toString() == "Invalid Date" || typeof timeObj.toLocaleDateString != "function") timeObj = new Date(); + offset = offset != null && parseFloat(offset); + if ((offset || offset === 0) && !isNaN(offset)) timeObj = new Date(timeObj.getTime() + ((offset - timeObj.getTimezoneOffset() * (-1/60)) * 60*60*1000)); + return timeObj; + }; + CustomComponents.DateInput.format = function (data, time) { + if (typeof data == "string") data = {formatString: data}; + if (data && typeof data.formatString != "string") data.formatString = ""; + if (!data || typeof data.formatString != "string" || !time) return ""; + + const language = data.language || BDFDB.LanguageUtils.getLanguage().id; + const timeObj = Internal.LibraryComponents.DateInput.parseDate(time, data.timeOffset); + const now = new Date(); + const daysAgo = Math.round((Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()) - Date.UTC(timeObj.getFullYear(), timeObj.getMonth(), timeObj.getDate()))/(1000*60*60*24)); + const date = data.dateString && typeof data.dateString == "string" ? Internal.LibraryComponents.DateInput.formatDate({dateString: data.dateString, language: language}, timeObj) : timeObj.toLocaleDateString(language); + + return (data.formatString || Internal.LibraryComponents.DateInput.getDefaultString(language)) + .replace(/\$date/g, date) + .replace(/\$time12/g, data.timeString && typeof data.timeString == "string" ? Internal.LibraryComponents.DateInput.formatTime({timeString: data.timeString, language: language}, timeObj, true) : timeObj.toLocaleTimeString(language, {hourCycle: "h12"})) + .replace(/\$time/g, data.timeString && typeof data.timeString == "string" ? Internal.LibraryComponents.DateInput.formatTime({timeString: data.timeString, language: language}, timeObj) : timeObj.toLocaleTimeString(language, {hourCycle: "h23"})) + .replace(/\$monthS/g, timeObj.toLocaleDateString(language, {month: "short"})) + .replace(/\$month/g, timeObj.toLocaleDateString(language, {month: "long"})) + .replace(/\$dayS/g, timeObj.toLocaleDateString(language, {weekday: "short"})) + .replace(/\$day/g, timeObj.toLocaleDateString(language, {weekday: "long"})) + .replace(/\$agoAmount/g, daysAgo < 0 || daysAgo > 1 ? Internal.DiscordObjects.Timestamp(timeObj.getTime()).fromNow() : BDFDB.LanguageUtils.LanguageStrings[daysAgo == 1 ? "YESTERDAY" : "TODAY"]) + .replace(/\$agoWeekdayS/g, daysAgo < 0 || daysAgo > 1 ? timeObj.toLocaleDateString(language, {weekday: "short"}) : BDFDB.LanguageUtils.LanguageStrings[daysAgo == 1 ? "YESTERDAY" : "TODAY"]) + .replace(/\$agoWeekday/g, daysAgo < 0 || daysAgo > 1 ? timeObj.toLocaleDateString(language, {weekday: "long"}) : BDFDB.LanguageUtils.LanguageStrings[daysAgo == 1 ? "YESTERDAY" : "TODAY"]) + .replace(/\$agoDays/g, daysAgo < 0 ? "" : daysAgo > 1 ? BDFDB.LanguageUtils.LanguageStringsFormat(`LAST_PLAYED_PLACEHOLDER`, daysAgo) : BDFDB.LanguageUtils.LanguageStrings[daysAgo == 1 ? "YESTERDAY" : "TODAY"]) + .replace(/\$agoDate/g, daysAgo < 0 || daysAgo > 1 ? date : BDFDB.LanguageUtils.LanguageStrings[daysAgo == 1 ? "YESTERDAY" : "TODAY"]) + .replace(/\(\)|\[\]/g, "").replace(/,\s*$|^\s*,/g, "").replace(/ +/g, " ").trim(); + }; + CustomComponents.DateInput.formatDate = function (data, time) { + if (typeof data == "string") data = {dateString: data}; + if (data && typeof data.dateString != "string") return ""; + if (!data || typeof data.dateString != "string" || !data.dateString || !time) return ""; + + const language = data.language || BDFDB.LanguageUtils.getLanguage().id; + const timeObj = Internal.LibraryComponents.DateInput.parseDate(time, data.timeOffset); + + return data.dateString + .replace(/\$monthS/g, timeObj.toLocaleDateString(language, {month: "short"})) + .replace(/\$month/g, timeObj.toLocaleDateString(language, {month: "long"})) + .replace(/\$dd/g, timeObj.toLocaleDateString(language, {day: "2-digit"})) + .replace(/\$d/g, timeObj.toLocaleDateString(language, {day: "numeric"})) + .replace(/\$mm/g, timeObj.toLocaleDateString(language, {month: "2-digit"})) + .replace(/\$m/g, timeObj.toLocaleDateString(language, {month: "numeric"})) + .replace(/\$yyyy/g, timeObj.toLocaleDateString(language, {year: "numeric"})) + .replace(/\$yy/g, timeObj.toLocaleDateString(language, {year: "2-digit"})) + .trim(); + }; + CustomComponents.DateInput.formatTime = function (data, time, hour12) { + if (typeof data == "string") data = {timeString: data}; + if (data && typeof data.timeString != "string") return ""; + if (!data || typeof data.timeString != "string" || !data.timeString || !time) return ""; + + const language = data.language || BDFDB.LanguageUtils.getLanguage().id; + const timeObj = Internal.LibraryComponents.DateInput.parseDate(time, data.timeOffset); + + let hours = timeObj.getHours(); + if (hour12) { + hours = hours == 0 ? 12 : hours; + if (hours > 12) hours -= 12; + } + const minutes = timeObj.getMinutes(); + const seconds = timeObj.getSeconds(); + const milli = timeObj.getMilliseconds(); + + let string = data.timeString + .replace(/\$hh/g, hours < 10 ? `0${hours}` : hours) + .replace(/\$h/g, hours) + .replace(/\$mm/g, minutes < 10 ? `0${minutes}` : minutes) + .replace(/\$m/g, minutes) + .replace(/\$ss/g, seconds < 10 ? `0${seconds}` : seconds) + .replace(/\$s/g, seconds) + .replace(/\$uu/g, milli < 10 ? `00${milli}` : milli < 100 ? `0${milli}` : milli) + .replace(/\$u/g, milli) + .trim(); + + let digits = "\\d"; + if (BDFDB.LanguageUtils.languages[language] && BDFDB.LanguageUtils.languages[language].numberMap) { + digits = Object.entries(BDFDB.LanguageUtils.languages[language].numberMap).map(n => n[1]).join(""); + for (let number in BDFDB.LanguageUtils.languages[language].numberMap) string = string.replace(new RegExp(number, "g"), BDFDB.LanguageUtils.languages[language].numberMap[number]); + } + return hour12 ? timeObj.toLocaleTimeString(language, {hourCycle: "h12"}).replace(new RegExp(`[${digits}]{1,2}[^${digits}][${digits}]{1,2}[^${digits}][${digits}]{1,2}`, "g"), string) : string; + }; + } + + CustomComponents.EmojiPickerButton = reactInitialized && class BDFDB_EmojiPickerButton extends Internal.LibraryModules.React.Component { + handleEmojiChange(emoji) { + if (emoji != null) { + this.props.emoji = emoji.id ? { + id: emoji.id, + name: emoji.name, + animated: emoji.animated + } : { + id: null, + name: emoji.optionallyDiverseSequence, + animated: false + }; + if (typeof this.props.onSelect == "function") this.props.onSelect(this.props.emoji, this); + if (typeof this.close == "function" && !BDFDB.ListenerUtils.isPressed(16)) this.close(); + BDFDB.ReactUtils.forceUpdate(this); + } + } + render() { + let button = this; + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.PopoutContainer, { + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.EmojiButton, { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.emojiinputbutton), + renderButtonContents: this.props.emoji ? _ => BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Emoji, { + className: BDFDB.disCN.emojiinputbuttonemoji, + emojiId: this.props.emoji.id, + emojiName: this.props.emoji.name + }) : null + }), + wrap: false, + animation: Internal.LibraryComponents.PopoutContainer.Animation.NONE, + position: this.props.position || Internal.LibraryComponents.PopoutContainer.Positions.TOP, + align: Internal.LibraryComponents.PopoutContainer.Align.LEFT, + renderPopout: instance => { + this.close = instance.close; + return [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.EmojiPicker, { + closePopout: this.close, + onSelectEmoji: this.handleEmojiChange.bind(this), + allowManagedEmojis: this.props.allowManagedEmojis, + allowManagedEmojisUsage: this.props.allowManagedEmojisUsage + }), + BDFDB.ReactUtils.createElement(class extends Internal.LibraryModules.React.Component { + componentDidMount() {Internal.LibraryComponents.EmojiPickerButton.current = button;} + componentWillUnmount() {delete Internal.LibraryComponents.EmojiPickerButton.current;} + render() {return null;} + }) + ]; + } + }); + } + }; + Internal.setDefaultProps(CustomComponents.EmojiPickerButton, {allowManagedEmojis: false, allowManagedEmojisUsage: false}); + + CustomComponents.EmptyStateImage = reactInitialized && class BDFDB_EmptyStateImage extends Internal.LibraryModules.React.Component { + render() { + let isLightTheme = BDFDB.DiscordUtils.getTheme() == BDFDB.disCN.themelight; + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.pageimagewrapper, this.props.className), + direction: Internal.LibraryComponents.Flex.Direction.VERTICAL, + align: Internal.LibraryComponents.Flex.Align.CENTER, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.pageimage, BDFDB.disCN.margintop20, BDFDB.disCN.marginbottom20, this.props.imageClassName), + style: { + flex: "0 1 auto", + background: `url("${this.props.src || (isLightTheme ? this.props.lightSrc : this.props.darkSrc) || this.props.lightSrc || this.props.darkSrc || (isLightTheme ? "/assets/a72746e7108167af95c8.svg" : "/assets/01864c39871ce619d855.svg")}") center/contain no-repeat`, + width: this.props.width || "415px", + height: this.props.height || "200px" + } + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.pageimagetext, + children: this.props.text || BDFDB.LanguageUtils.LanguageStrings.NO_RESULTS + }) + ] + }); + } + }; + + CustomComponents.FavButton = reactInitialized && class BDFDB_FavButton extends Internal.LibraryModules.React.Component { + handleClick() { + this.props.isFavorite = !this.props.isFavorite; + if (typeof this.props.onClick == "function") this.props.onClick(this.props.isFavorite, this); + BDFDB.ReactUtils.forceUpdate(this); + } + render() { + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Clickable, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.favbuttoncontainer, BDFDB.disCN.favbutton, this.props.isFavorite && BDFDB.disCN.favbuttonselected, this.props.className), + onClick: this.handleClick.bind(this), + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: Internal.LibraryComponents.SvgIcon.Names[this.props.isFavorite ? "FAVORITE_FILLED" : "FAVORITE"], + width: this.props.width || 24, + height: this.props.height || 24, + className: BDFDB.disCN.favbuttonicon + }) + }); + } + }; + + CustomComponents.FileButton = reactInitialized && class BDFDB_FileButton extends Internal.LibraryModules.React.Component { + render() { + let filter = this.props.filter && [this.props.filter].flat(10).filter(n => typeof n == "string") || []; + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Button, BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + onClick: e => {(e.currentTarget || e.target).querySelector("input").click();}, + children: [ + BDFDB.LanguageUtils.LibraryStrings.file_navigator_text, + BDFDB.ReactUtils.createElement("input", { + type: "file", + accept: filter.length && (filter.join("/*,") + "/*"), + style: {display: "none"}, + onChange: e => { + let file = (e.currentTarget || e.target).files[0]; + if (this.refInput && file && (!filter.length || filter.some(n => file.type.indexOf(n) == 0))) { + let reader = new FileReader(); + reader.onload = _ => { + if (file.size > (!isNaN(this.props.maxSize) && this.props.maxSize > 0 ? this.props.maxSize : 5.1*1024*1024)) BDFDB.NotificationUtils.toast("File too large to upload (Max 5MB)", {type: "danger", position: "center"}); + else { + function Uint8ToString(u8a) { + const CHUNK_SZ = 0x8000; + let c = []; + for (let i = 0; i < u8a.length; i += CHUNK_SZ) c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ))); + return c.join(""); + } + this.refInput.handleChange(file.name, `${this.props.mode == "url" ? "url('" : ""}${`data:${file.type};base64,${btoa(Uint8ToString(new Uint8Array(reader.result)))}`}${this.props.mode ? "')" : ""}`); + } + } + reader.readAsArrayBuffer(file); + } + } + }) + ] + }), "filter", "mode")); + } + }; + + CustomComponents.FormItem = reactInitialized && class BDFDB_FormItem extends Internal.LibraryModules.React.Component { + render() { + return BDFDB.ReactUtils.createElement("div", { + className: this.props.className, + style: this.props.style, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + children: [ + this.props.title != null || this.props.error != null ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex.Child, { + wrap: true, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.FormTitle.Title, { + tag: this.props.tag || Internal.LibraryComponents.FormTitle.Tags && Internal.LibraryComponents.FormTitle.Tags.H5, + disabled: this.props.disabled, + required: this.props.required, + error: this.props.error, + className: this.props.titleClassName, + children: this.props.title + }) + }) : null + ].concat([this.props.titleChildren].flat(10)).filter(n => n) + }), + ].concat(this.props.children) + }); + } + }; + + CustomComponents.FormTitle = reactInitialized && class BDFDB_FormTitle extends Internal.LibraryModules.React.Component { + render() { + return BDFDB.ReactUtils.createElement(Internal.NativeSubComponents.FormTitle, this.props); + } + }; + CustomComponents.FormTitle.Title = CustomComponents.FormTitle; + CustomComponents.FormTitle.Tags = new Proxy({}, { + get: function (_, item) { + let tag = item && item.toLowerCase(); + return tag.startsWith("h") && tag.length == 2 ? tag : "h5"; + } + }); + + CustomComponents.GuildSummaryItem = reactInitialized && class BDFDB_GuildSummaryItem extends Internal.LibraryModules.React.Component { + defaultRenderGuild(guild, isLast) { + if (!guild) return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.guildsummaryemptyguild + }); + let icon = BDFDB.ReactUtils.createElement(Internal.LibraryComponents.GuildIcon, { + className: BDFDB.disCN.guildsummaryicon, + guild: guild, + showTooltip: this.props.showTooltip, + tooltipPosition: "top", + size: Internal.LibraryComponents.GuildIcon.Sizes.SMALLER + }); + return this.props.switchOnClick ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Clickable, { + className: BDFDB.disCN.guildsummaryclickableicon, + onClick: _ => Internal.LibraryModules.HistoryUtils.transitionTo(Internal.DiscordConstants.Routes.CHANNEL(guild.id, Internal.LibraryStores.SelectedChannelStore.getChannelId(guild.id))), + key: guild.id, + tabIndex: -1, + children: icon + }) : icon; + } + renderGuilds() { + let elements = []; + let renderGuild = typeof this.props.renderGuild != "function" ? this.defaultRenderGuild : this.props.renderGuild; + let loaded = 0, max = this.props.guilds.length === this.props.max ? this.props.guilds.length : this.props.max - 1; + while (loaded < max && loaded < this.props.guilds.length) { + let isLast = loaded === this.props.guilds.length - 1; + let guild = renderGuild.apply(this, [this.props.guilds[loaded], isLast]); + elements.push(BDFDB.ReactUtils.createElement("div", { + className: isLast ? BDFDB.disCN.guildsummaryiconcontainer : BDFDB.disCN.guildsummaryiconcontainermasked, + children: guild + })); + loaded++; + } + if (loaded < this.props.guilds.length) { + let rest = Math.min(this.props.guilds.length - loaded, 99); + elements.push(BDFDB.ReactUtils.createElement(Internal.LibraryModules.React.Fragment, { + key: "more-guilds", + children: this.props.renderMoreGuilds("+" + rest, rest, this.props.guilds.slice(loaded), this.props) + })); + } + return elements; + } + renderIcon() { + return this.props.renderIcon ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: Internal.LibraryComponents.SvgIcon.Names.WHATISTHIS, + className: BDFDB.disCN.guildsummarysvgicon + }) : null; + } + render() { + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.guildsummarycontainer), + ref: this.props._ref, + children: [ + this.renderIcon.apply(this), + this.renderGuilds.apply(this) + ].flat(10).filter(n => n) + }); + } + }; + Internal.setDefaultProps(CustomComponents.GuildSummaryItem, {max: 10, renderMoreGuilds: (count, amount, restGuilds, props) => { + let icon = BDFDB.ReactUtils.createElement("div", {className: BDFDB.disCN.guildsummarymoreguilds, children: count}); + return props.showTooltip ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TooltipContainer, { + text: restGuilds.map(guild => guild.name).join(", "), + children: icon + }) : icon; + }, renderIcon: false}); + + CustomComponents.GuildVoiceList = reactInitialized && class BDFDB_GuildVoiceList extends Internal.LibraryModules.React.Component { + render() { + let channels = Internal.LibraryStores.GuildChannelStore.getChannels(this.props.guild.id); + let voiceChannels = (channels.VOCAL || []).filter(c => c.channel.type == Internal.DiscordConstants.ChannelTypes.GUILD_VOICE).map(c => c.channel.id); + let stageChannels = (channels.VOCAL || []).filter(c => c.channel.type == Internal.DiscordConstants.ChannelTypes.GUILD_STAGE_VOICE && Internal.LibraryStores.StageInstanceStore.getStageInstanceByChannel(c.channel.id)).map(c => c.channel.id); + let streamOwnerIds = Internal.LibraryStores.ApplicationStreamingStore.getAllApplicationStreams().filter(app => app.guildId === this.props.guild.id).map(app => app.ownerId) || []; + let streamOwners = streamOwnerIds.map(ownerId => Internal.LibraryStores.UserStore.getUser(ownerId)).filter(n => n); + let voiceStates = BDFDB.ObjectUtils.toArray(Internal.LibraryStores.SortedVoiceStateStore.getVoiceStates(this.props.guild.id)).flat(10); + let connectedVoiceUsers = voiceStates.map(n => voiceChannels.includes(n.voiceState.channelId) && n.voiceState.channelId != this.props.guild.afkChannelId && !streamOwnerIds.includes(n.voiceState.userId) && Internal.LibraryStores.UserStore.getUser(n.voiceState.userId)).filter(n => n); + let connectedStageUsers = voiceStates.map(n => stageChannels.includes(n.voiceState.channelId) && n.voiceState.channelId != this.props.guild.afkChannelId && !streamOwnerIds.includes(n.voiceState.userId) && Internal.LibraryStores.UserStore.getUser(n.voiceState.userId)).filter(n => n); + let children = [ + !connectedStageUsers.length ? null : BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.tooltiprow, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: Internal.LibraryComponents.SvgIcon.Names.PODIUM, + className: BDFDB.disCN.tooltipactivityicon + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.UserSummaryItem, { + users: connectedStageUsers, + max: 6 + }) + ] + }), + !connectedVoiceUsers.length ? null : BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.tooltiprow, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: Internal.LibraryComponents.SvgIcon.Names.SPEAKER, + className: BDFDB.disCN.tooltipactivityicon + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.UserSummaryItem, { + users: connectedVoiceUsers, + max: 6 + }) + ] + }), + !streamOwners.length ? null : BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.tooltiprow, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: Internal.LibraryComponents.SvgIcon.Names.STREAM, + className: BDFDB.disCN.tooltipactivityicon + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.UserSummaryItem, { + users: streamOwners, + max: 6 + }) + ] + }) + ].filter(n => n); + return !children.length ? null : BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.guildvoicelist, + children: children + }); + } + }; + + CustomComponents.KeybindRecorder = reactInitialized && class BDFDB_KeybindRecorder extends Internal.LibraryModules.React.Component { + render () { + return BDFDB.ReactUtils.createElement(class extends Internal.LibraryModules.React.Component { + handleChange(arrays) { + this.props.value = arrays.map(platformKey => Internal.LibraryModules.KeyEvents.codes[Internal.LibraryModules.KeyCodeUtils.codeToKey(platformKey)] || platformKey[1]); + if (typeof this.props.onChange == "function") this.props.onChange(this.props.value, this); + } + handleReset() { + this.props.value = []; + if (this.recorder) this.recorder.setState({codes: []}); + if (typeof this.props.onChange == "function") this.props.onChange([], this); + if (typeof this.props.onReset == "function") this.props.onReset(this); + } + componentDidMount() { + if (!this.recorder) this.recorder = BDFDB.ReactUtils.findOwner(this, {name: "KeybindRecorder"}); + } + render() { + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + className: BDFDB.disCN.hotkeywrapper, + direction: Internal.LibraryComponents.Flex.Direction.HORIZONTAL, + align: Internal.LibraryComponents.Flex.Align.CENTER, + children: [ + BDFDB.ReactUtils.createElement(Internal.NativeSubComponents.KeybindRecorder, BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + defaultValue: [this.props.defaultValue || this.props.value].flat(10).filter(n => n).map(keyCode => [Internal.DiscordConstants.KeyboardDeviceTypes.KEYBOARD_KEY, Internal.LibraryModules.KeyCodeUtils.keyToCode((Object.entries(Internal.LibraryModules.KeyEvents.codes).find(n => n[1] == keyCode && Internal.LibraryModules.KeyCodeUtils.keyToCode(n[0], null)) || [])[0], null) || keyCode]), + onChange: this.handleChange.bind(this) + }), "reset", "onReset")), + this.props.reset || this.props.onReset ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TooltipContainer, { + text: BDFDB.LanguageUtils.LanguageStrings.REMOVE_KEYBIND, + tooltipConfig: {type: "top"}, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Clickable, { + className: BDFDB.disCN.hotkeyresetbutton, + onClick: this.handleReset.bind(this), + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + iconSVG: ``, + }) + }) + }) : null + ].filter(n => n) + }); + } + }, this.props); + } + }; + + CustomComponents.ListRow = reactInitialized && class BDFDB_ListRow extends Internal.LibraryModules.React.Component { + render() { + return BDFDB.ReactUtils.createElement("div", BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.listrowwrapper, this.props.className, BDFDB.disCN.listrow), + children: [ + this.props.prefix, + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.listrowcontent, + style: {flex: "1 1 auto"}, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.listname, this.props.labelClassName), + style: {flex: "1 1 auto"}, + children: this.props.label + }), + typeof this.props.note == "string" ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.FormText.Text, { + type: Internal.LibraryComponents.FormText.Types.DESCRIPTION, + children: this.props.note + }) : null + ].filter(n => n) + }), + this.props.suffix + ].filter(n => n) + }), "label", "note", "suffix", "prefix", "labelClassName")); + } + }; + + CustomComponents.MemberRole = reactInitialized && class BDFDB_MemberRole extends Internal.LibraryModules.React.Component { + handleClick(e) {if (typeof this.props.onClick == "function") this.props.onClick(e, this);} + handleContextMenu(e) {if (typeof this.props.onContextMenu == "function") this.props.onContextMenu(e, this);} + render() { + let color = BDFDB.ColorUtils.convert(this.props.role.colorString, "RGB") || Internal.DiscordConstants.Colors.PRIMARY_300; + return BDFDB.ReactUtils.createElement("li", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.userrole, this.props.className), + style: {borderColor: BDFDB.ColorUtils.setAlpha(color, 0.6)}, + onClick: this.handleClick.bind(this), + onContextMenu: this.handleContextMenu.bind(this), + children: [ + !this.props.noCircle ? BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.userroleremovebutton, + children: BDFDB.ReactUtils.createElement("span", { + className: BDFDB.disCN.userrolecircle, + style: {backgroundColor: color} + }) + }) : null, + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.userrolename, + children: this.props.role.name + }) + ].filter(n => n) + }); + } + }; + + CustomComponents.MenuItems = {}; + CustomComponents.MenuItems.MenuCheckboxItem = reactInitialized && class BDFDB_MenuCheckboxItem extends Internal.LibraryModules.React.Component { + handleClick() { + if (this.props.state) { + this.props.state.checked = !this.props.state.checked; + if (typeof this.props.action == "function") this.props.action(this.props.state.checked, this); + } + BDFDB.ReactUtils.forceUpdate(this); + } + render() { + return BDFDB.ReactUtils.createElement(Internal.MenuItem, Object.assign({}, this.props, { + input: this.props.state && this.props.state.checked ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.menuicon, + background: BDFDB.disCN.menucheckbox, + foreground: BDFDB.disCN.menucheck, + name: Internal.LibraryComponents.SvgIcon.Names.CHECKBOX + }) : BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.menuicon, + name: Internal.LibraryComponents.SvgIcon.Names.CHECKBOX_EMPTY + }), + action: this.handleClick.bind(this) + })); + } + }; + + CustomComponents.MenuItems.MenuHint = reactInitialized && class BDFDB_MenuHint extends Internal.LibraryModules.React.Component { + render() { + return !this.props.hint ? null : BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.menuhint, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextScroller, { + children: this.props.hint + }) + }); + } + }; + + CustomComponents.MenuItems.MenuIcon = reactInitialized && class BDFDB_MenuIcon extends Internal.LibraryModules.React.Component { + render() { + let isString = typeof this.props.icon == "string"; + return !this.props.icon ? null : BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.menuicon, + nativeClass: true, + iconSVG: isString ? this.props.icon : null, + name: !isString ? this.props.icon : null + }); + } + }; + + CustomComponents.MenuItems.MenuControlItem = function (props) { + let effectRef = BDFDB.ReactUtils.useRef(null); + let controlRef = BDFDB.ReactUtils.useRef(null); + + BDFDB.ReactUtils.useLayoutEffect((_ => { + if (props.isFocused) { + BDFDB.LibraryStores.AccessibilityStore.keyboardModeEnabled && controlRef.current && controlRef.current.scrollIntoView({ + block: "nearest" + }); + controlRef.current && controlRef.current.focus(); + } + else controlRef.current && controlRef.current.blur && controlRef.current.blur(controlRef.current); + }), [props.isFocused]); + + return BDFDB.ReactUtils.createElement("div", Object.assign({ + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.menuitem, BDFDB.disCN[`menucolor${(props.color && InternalData.DiscordClasses[`menucolor${props.color.toLowerCase()}`] || Internal.DiscordConstants.MenuItemColors.DEFAULT || "").toLowerCase()}`], props.disabled && BDFDB.disCN.menudisabled, props.showDefaultFocus && props.isFocused && BDFDB.disCN.menufocused, !props.showDefaultFocus && BDFDB.disCN.menuhideinteraction), + onClick: BDFDB.ReactUtils.useCallback((_ => { + if (!controlRef.current || !controlRef.current.activate || !controlRef.current.activate.call(controlRef.current)) typeof props.onClose == "function" && props.onClose(); + }), [props.onClose]), + "aria-disabled": props.disabled, + children: [ + props.label && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.menulabelcontainer, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.menulabel, + children: props.label + }) + }), + typeof props.control == "function" && props.control({ + onClose: props.onClose, + disabled: props.disabled, + isFocused: props.isFocused + }, controlRef) + ] + }, props.menuItemProps)); + }; + + CustomComponents.MenuItems.MenuSliderItem = reactInitialized && class BDFDB_MenuSliderItem extends Internal.LibraryModules.React.Component { + handleValueChange(value) { + if (this.props.state) { + this.props.state.value = Math.round(BDFDB.NumberUtils.mapRange([0, 100], [this.props.minValue, this.props.maxValue], value) * Math.pow(10, this.props.digits)) / Math.pow(10, this.props.digits); + if (typeof this.props.onValueChange == "function") this.props.onValueChange(this.props.state.value, this); + } + BDFDB.ReactUtils.forceUpdate(this); + } + handleValueRender(value) { + let newValue = Math.round(BDFDB.NumberUtils.mapRange([0, 100], [this.props.minValue, this.props.maxValue], value) * Math.pow(10, this.props.digits)) / Math.pow(10, this.props.digits); + if (typeof this.props.onValueRender == "function") { + let tempReturn = this.props.onValueRender(newValue, this); + if (tempReturn != undefined) newValue = tempReturn; + } + return newValue; + } + render() { + let value = this.props.state && this.props.state.value || 0; + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.MenuItems.MenuControlItem, BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + label: typeof this.props.renderLabel == "function" ? this.props.renderLabel(Math.round(value * Math.pow(10, this.props.digits)) / Math.pow(10, this.props.digits), this) : this.props.label, + control: (menuItemProps, ref) => { + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.menuslidercontainer, + children: BDFDB.ReactUtils.createElement(Internal.NativeSubComponents.Slider, Object.assign({}, menuItemProps, { + ref: ref, + className: BDFDB.disCN.menuslider, + mini: true, + initialValue: Math.round(BDFDB.NumberUtils.mapRange([this.props.minValue, this.props.maxValue], [0, 100], value) * Math.pow(10, this.props.digits)) / Math.pow(10, this.props.digits), + onValueChange: this.handleValueChange.bind(this), + onValueRender: this.handleValueRender.bind(this) + })) + }); + } + }), "digits", "renderLabel")); + } + }; + Internal.setDefaultProps(CustomComponents.MenuItems.MenuSliderItem, {minValue: 0, maxValue: 100, digits: 0}); + + CustomComponents.ModalComponents = {}; + CustomComponents.ModalComponents.ModalContent = reactInitialized && class BDFDB_ModalContent extends Internal.LibraryModules.React.Component { + render() { + return this.props.scroller ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Scrollers.Thin, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.modalcontent, this.props.className), + ref: this.props.scrollerRef, + children: this.props.children + }) : BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + className: BDFDB.DOMUtils.formatClassName(this.props.content && BDFDB.disCN.modalcontent, BDFDB.disCN.modalnoscroller, this.props.className), + direction: this.props.direction || Internal.LibraryComponents.Flex.Direction.VERTICAL, + align: Internal.LibraryComponents.Flex.Align.STRETCH, + children: this.props.children + }); + } + }; + Internal.setDefaultProps(CustomComponents.ModalComponents.ModalContent, {scroller: true, content: true}); + + CustomComponents.ModalComponents.ModalTabContent = reactInitialized && class BDFDB_ModalTabContent extends Internal.LibraryModules.React.Component { + constructor(props) { + super(props); + this.state = {open: props.open}; + } + render() { + return !this.state.open ? null : BDFDB.ReactUtils.createElement(this.props.scroller ? Internal.LibraryComponents.Scrollers.Thin : "div", Object.assign(BDFDB.ObjectUtils.exclude(this.props, "scroller", "open"), { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.modaltabcontent, this.state.open && BDFDB.disCN.modaltabcontentopen, this.props.className), + children: this.props.children + })); + } + }; + Internal.setDefaultProps(CustomComponents.ModalComponents.ModalTabContent, {tab: "unnamed"}); + + CustomComponents.ModalComponents.ModalFooter = reactInitialized && class BDFDB_ModalFooter extends Internal.LibraryModules.React.Component { + render() { + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.modalfooter, this.props.className), + direction: this.props.direction || Internal.LibraryComponents.Flex.Direction.HORIZONTAL_REVERSE, + align: Internal.LibraryComponents.Flex.Align.STRETCH, + grow: 0, + shrink: 0, + children: this.props.children + }); + } + }; + + CustomComponents.MultiInput = reactInitialized && class BDFDB_MultiInput extends Internal.LibraryModules.React.Component { + constructor(props) { + super(props); + this.state = {focused: false}; + } + render() { + if (this.props.children && this.props.children.props) this.props.children.props.className = BDFDB.DOMUtils.formatClassName(this.props.children.props.className, BDFDB.disCN.inputmultifield); + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.inputwrapper, BDFDB.disCN.inputmultiwrapper), + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.input, BDFDB.disCN.inputmulti, this.state.focused && BDFDB.disCN.inputfocused), + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(this.props.innerClassName, BDFDB.disCN.inputwrapper, BDFDB.disCN.inputmultifirst), + children: this.props.children + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextInput, BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + className: BDFDB.disCN.inputmultilast, + inputClassName: BDFDB.disCN.inputmultifield, + onFocus: e => this.setState({focused: true}), + onBlur: e => this.setState({focused: false}) + }), "children", "innerClassName")) + ] + }) + }); + } + }; + + CustomComponents.ListInput = reactInitialized && class BDFDB_ListInput extends Internal.LibraryModules.React.Component { + render () { + return BDFDB.ReactUtils.createElement(class extends Internal.LibraryModules.React.Component { + handleChange() { + if (typeof this.props.onChange) this.props.onChange(this.props.items, this); + } + render() { + if (!BDFDB.ArrayUtils.is(this.props.items)) this.props.items = []; + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.MultiInput, BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + className: BDFDB.disCN.inputlist, + innerClassName: BDFDB.disCN.inputlistitems, + onKeyDown: e => { + if (e.which == 13 && e.target.value && e.target.value.trim()) { + let value = e.target.value.trim(); + this.props.value = ""; + if (!this.props.items.includes(value)) { + this.props.items.push(value); + BDFDB.ReactUtils.forceUpdate(this); + this.handleChange.apply(this, []); + } + } + }, + children: this.props.items.map(item => BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Badges.TextBadge, { + className: BDFDB.disCN.inputlistitem, + color: "var(--bdfdb-blurple)", + style: {borderRadius: "3px"}, + text: [ + item, + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.inputlistdelete, + name: Internal.LibraryComponents.SvgIcon.Names.CLOSE, + onClick: _ => { + BDFDB.ArrayUtils.remove(this.props.items, item); + BDFDB.ReactUtils.forceUpdate(this); + this.handleChange.apply(this, []); + } + }) + ] + })) + }), "items")); + } + }, this.props); + } + }; + + CustomComponents.PaginatedList = reactInitialized && class BDFDB_PaginatedList extends Internal.LibraryModules.React.Component { + constructor(props) { + super(props); + this.state = { + offset: props.offset + }; + } + handleJump(offset) { + if (offset > -1 && offset < Math.ceil(this.props.items.length/this.props.amount) && this.state.offset != offset) { + this.state.offset = offset; + if (typeof this.props.onJump == "function") this.props.onJump(offset, this); + BDFDB.ReactUtils.forceUpdate(this); + } + } + renderPagination(bottom) { + let maxOffset = Math.ceil(this.props.items.length/this.props.amount) - 1; + return this.props.items.length > this.props.amount && BDFDB.ReactUtils.createElement("nav", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.pagination, bottom ? BDFDB.disCN.paginationbottom : BDFDB.disCN.paginationtop, this.props.mini && BDFDB.disCN.paginationmini), + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Paginator, { + totalCount: this.props.items.length, + currentPage: this.state.offset + 1, + pageSize: this.props.amount, + maxVisiblePages: this.props.maxVisiblePages, + onPageChange: page => {this.handleJump(isNaN(parseInt(page)) ? -1 : page - 1);} + }), + this.props.jump && BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextInput, { + type: "number", + size: Internal.LibraryComponents.TextInput.Sizes.MINI, + value: this.state.offset + 1, + min: 1, + max: maxOffset + 1, + onKeyDown: (event, instance) => {if (event.which == 13) this.handleJump(isNaN(parseInt(instance.props.value)) ? -1 : instance.props.value - 1);} + }), + ].filter(n => n) + }); + } + render() { + let items = [], alphabet = {}; + if (BDFDB.ArrayUtils.is(this.props.items) && this.props.items.length) { + if (!this.props.alphabetKey) items = this.props.items; + else { + let unsortedItems = [].concat(this.props.items); + for (let key of ["0-9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]) { + let numbers = key == "0-9", alphaItems = []; + for (let item of unsortedItems) if (item && item[this.props.alphabetKey] && (numbers && !isNaN(parseInt(item[this.props.alphabetKey][0])) || item[this.props.alphabetKey].toUpperCase().indexOf(key) == 0)) alphaItems.push(item); + for (let sortedItem of alphaItems) BDFDB.ArrayUtils.remove(unsortedItems, sortedItem); + alphabet[key] = {items: BDFDB.ArrayUtils.keySort(alphaItems, this.props.alphabetKey), disabled: !alphaItems.length}; + } + alphabet["?!"] = {items: BDFDB.ArrayUtils.keySort(unsortedItems, this.props.alphabetKey), disabled: !unsortedItems.length}; + for (let key in alphabet) items.push(alphabet[key].items); + items = items.flat(10); + } + } + return typeof this.props.renderItem != "function" || !items.length ? null : BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Scrollers.Thin, { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.paginationlist, this.props.mini && BDFDB.disCN.paginationlistmini), + fade: this.props.fade, + children: [ + this.renderPagination(), + items.length > this.props.amount && this.props.alphabetKey && BDFDB.ReactUtils.createElement("nav", { + className: BDFDB.disCN.paginationlistalphabet, + children: Object.keys(alphabet).map(key => BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Clickable, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.paginationlistalphabetchar, alphabet[key].disabled &&BDFDB.disCN.paginationlistalphabetchardisabled), + onClick: _ => {if (!alphabet[key].disabled) this.handleJump(Math.floor(items.indexOf(alphabet[key].items[0])/this.props.amount));}, + children: key + })) + }), + this.props.header, + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.paginationlistcontent, + children: items.slice(this.state.offset * this.props.amount, (this.state.offset + 1) * this.props.amount).map((data, i) => {return this.props.renderItem(data, i);}).flat(10).filter(n => n) + }), + this.props.copyToBottom && this.renderPagination(true) + ].flat(10).filter(n => n) + }); + } + }; + Internal.setDefaultProps(CustomComponents.PaginatedList, {amount: 50, offset: 0, mini: true, jump: true, maxVisiblePages: 7, copyToBottom: false, fade: true}); + + CustomComponents.Popout = reactInitialized && class BDFDB_Popout extends Internal.LibraryModules.React.Component { + componentDidMount() { + this.props.containerInstance.popout = this; + if (typeof this.props.onOpen == "function") this.props.onOpen(this.props.containerInstance, this); + } + componentWillUnmount() { + delete this.props.containerInstance.popout; + if (typeof this.props.onClose == "function") this.props.onClose(this.props.containerInstance, this); + } + render() { + if (!this.props.wrap) return this.props.children; + let pos = typeof this.props.position == "string" ? this.props.position.toLowerCase() : null; + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.PopoutFocusLock, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.popoutwrapper, this.props.className, this.props.themed && BDFDB.disCN.popoutthemedpopout, this.props.arrow && BDFDB.disCN.popoutarrow, this.props.arrow && (pos == "top" ? BDFDB.disCN.popoutarrowtop : BDFDB.disCN.popoutarrowbottom)), + id: this.props.id, + onClick: e => e.stopPropagation(), + style: BDFDB.ObjectUtils.extract(this.props, "padding", "height", "maxHeight", "minHeight", "width", "maxWidth", "minWidth"), + children: this.props.children + }); + } + }; + Internal.setDefaultProps(CustomComponents.Popout, {themed: true, wrap: true}); + + CustomComponents.PopoutContainer = reactInitialized && class BDFDB_PopoutContainer extends Internal.LibraryModules.React.Component { + constructor(props) { + super(props); + this.state = {open: props.open}; + } + componentDidMount() { + this.toggle = this.toggle.bind(this); + this.onDocumentClicked = this.onDocumentClicked.bind(this); + this.domElementRef = BDFDB.ReactUtils.createRef(); + this.domElementRef.current = BDFDB.ReactUtils.findDOMNode(this, true) || BDFDB.ReactUtils.findValue(this[BDFDB.ReactUtils.instanceKey], "ref", {notNull: true}); + } + onDocumentClicked() { + const node = BDFDB.ReactUtils.findDOMNode(this.popout); + if (!node || !document.contains(node) || node != event.target && document.contains(event.target) && !node.contains(event.target)) this.toggle(false); + } + toggle(forceState) { + this.setState({open: forceState != undefined ? forceState : !this.state.open}); + } + render() { + if (!this.props._rendered) { + this.props._rendered = true; + const child = (BDFDB.ArrayUtils.is(this.props.children) ? this.props.children[0] : this.props.children) || BDFDB.ReactUtils.createElement("div", {style: {height: "100%", width: "100%"}}); + child.props.className = BDFDB.DOMUtils.formatClassName(child.props.className, this.props.className); + const childProps = Object.assign({}, child.props); + child.props.onClick = (e, childThis) => { + if ((this.props.openOnClick || this.props.openOnClick === undefined)) this.toggle(); + if (typeof this.props.onClick == "function") this.props.onClick(e, this); + if (typeof childProps.onClick == "function") childProps.onClick(e, childThis); + if (this.props.killEvent || childProps.killEvent) BDFDB.ListenerUtils.stopEvent(e); + }; + child.props.onContextMenu = (e, childThis) => { + if (this.props.openOnContextMenu) this.toggle(); + if (typeof this.props.onContextMenu == "function") this.props.onContextMenu(e, this); + if (typeof childProps.onContextMenu == "function") childProps.onContextMenu(e, childThis); + if (this.props.killEvent || childProps.killEvent) BDFDB.ListenerUtils.stopEvent(e); + }; + this.props.children = child; + } + return BDFDB.ReactUtils.createElement(Internal.LibraryModules.React.Fragment, { + children: [ + this.props.children, + this.state.open && BDFDB.ReactUtils.createElement(Internal.LibraryComponents.AppReferencePositionLayer, { + onMount: _ => BDFDB.TimeUtils.timeout(_ => document.addEventListener("click", this.onDocumentClicked)), + onUnmount: _ => document.removeEventListener("click", this.onDocumentClicked), + position: this.props.position, + align: this.props.align, + targetRef: this.domElementRef, + children: _ => { + const popout = BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Popout, BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + className: this.props.popoutClassName, + containerInstance: this, + position: this.props.position, + style: this.props.popoutStyle, + onOpen: typeof this.props.onOpen == "function" ? this.props.onOpen.bind(this) : _ => {}, + onClose: typeof this.props.onClose == "function" ? this.props.onClose.bind(this) : _ => {}, + children: typeof this.props.renderPopout == "function" ? this.props.renderPopout(this) : null + }), "popoutStyle", "popoutClassName", "shouldShow", "changing", "renderPopout", "openOnClick", "onClick", "openOnContextMenu", "onContextMenu")); + const animation = Object.entries(Internal.LibraryComponents.PopoutContainer.Animation).find(n => n[1] == this.props.animation); + return !animation || animation[0] == Internal.LibraryComponents.PopoutContainer.Animation.NONE ? popout : BDFDB.ReactUtils.createElement(Internal.LibraryComponents.PopoutCSSAnimator, { + position: this.props.position, + type: Internal.LibraryComponents.PopoutCSSAnimator.Types[animation[0]], + children: popout + }); + } + }) + ] + }); + } + }; + if (CustomComponents.PopoutContainer) { + CustomComponents.PopoutContainer.Align = { + BOTTOM: "bottom", + CENTER: "center", + LEFT: "left", + RIGHT: "right", + TOP: "top" + }; + CustomComponents.PopoutContainer.Positions = { + BOTTOM: "bottom", + CENTER: "center", + LEFT: "left", + RIGHT: "right", + TOP: "top", + WINDOW_CENTER: "window_center" + }; + CustomComponents.PopoutContainer.ObjectProperties = ["Animation"]; + Internal.setDefaultProps(CustomComponents.PopoutContainer, {wrap: true}); + } + + CustomComponents.PopoutCSSAnimator = function (props) { + let positionState = BDFDB.ReactUtils.useState(props.position != null); + let animationState = BDFDB.ReactUtils.useState((_ => new Internal.LibraryComponents.Timeout)); + BDFDB.ReactUtils.useEffect((_ => (_ => animationState[0].stop())), [animationState[0]]); + BDFDB.ReactUtils.useEffect(_ => (props.position && animationState[0].start(10, (_ => positionState[1](true)))), [props.position, animationState[0]]); + const position = typeof props.position == "string" && props.position.replace("window_", ""); + const animation = (Object.entries(Internal.LibraryComponents.PopoutContainer.Animation).find(n => n[1] == props.animation) || ["NONE"])[0].toLowerCase(); + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(InternalData.DiscordClasses[`animationcontainer${position}`] && BDFDB.disCN[`animationcontainer${position}`], InternalData.DiscordClasses[`animationcontainer${animation}`] && BDFDB.disCN[`animationcontainer${animation}`], positionState[0] && BDFDB.disCN.animationcontainerrender), + children: props.children + }) + }; + if (CustomComponents.PopoutCSSAnimator) CustomComponents.PopoutCSSAnimator.Types = { + "1": "TRANSLATE", + "2": "SCALE", + "3": "FADE", + "TRANSLATE": "1", + "SCALE": "2", + "FADE": "3" + }; + + CustomComponents.QuickSelect = reactInitialized && class BDFDB_QuickSelect extends Internal.LibraryModules.React.Component { + render () { + return BDFDB.ReactUtils.createElement(class extends Internal.LibraryModules.React.Component { + handleChange(option) { + this.props.value = option; + if (typeof this.props.onChange == "function") this.props.onChange(option.value || option.key, this); + BDFDB.ReactUtils.forceUpdate(this); + } + render() { + let options = (BDFDB.ArrayUtils.is(this.props.options) ? this.props.options : [{}]).filter(n => n); + let selectedOption = BDFDB.ObjectUtils.is(this.props.value) ? this.props.value : (options[0] || {}); + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.quickselectwrapper), + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + className: BDFDB.disCN.quickselect, + align: Internal.LibraryComponents.Flex.Align.CENTER, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.quickselectlabel, + children: this.props.label + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + align: Internal.LibraryComponents.Flex.Align.CENTER, + className: BDFDB.disCN.quickselectclick, + onClick: event => { + Internal.LibraryModules.ContextMenuUtils.openContextMenu(event, _ => BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Menu, { + navId: "bdfdb-quickselect", + onClose: Internal.LibraryModules.ContextMenuUtils.closeContextMenu, + className: this.props.popoutClassName, + children: BDFDB.ContextMenuUtils.createItem(Internal.LibraryComponents.MenuItems.MenuGroup, { + children: options.map((option, i) => { + let selected = option.value && option.value === selectedOption.value || option.key && option.key === selectedOption.key; + return BDFDB.ContextMenuUtils.createItem(Internal.LibraryComponents.MenuItems.MenuItem, { + label: option.label, + id: BDFDB.ContextMenuUtils.createItemId("option", option.key || option.value || i), + action: selected ? null : event2 => this.handleChange.bind(this)(option) + }); + }) + }) + })); + }, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.quickselectvalue, + children: typeof this.props.renderValue == "function" ? this.props.renderValue(this.props.value) : this.props.value.label + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.quickselectarrow + }) + ] + }) + ] + }) + }); + } + }, this.props); + } + }; + + CustomComponents.RadioGroup = reactInitialized && class BDFDB_RadioGroup extends Internal.LibraryModules.React.Component { + render () { + return BDFDB.ReactUtils.createElement(class extends Internal.LibraryModules.React.Component { + handleChange(value) { + this.props.value = value.value; + if (typeof this.props.onChange == "function") this.props.onChange(value, this); + BDFDB.ReactUtils.forceUpdate(this); + } + render() { + return BDFDB.ReactUtils.createElement(Internal.NativeSubComponents.RadioGroup, Object.assign({}, this.props, { + onChange: this.handleChange.bind(this) + })); + } + }, this.props); + } + }; + + CustomComponents.Scrollers = new Proxy({}, { + get: function (_, item) { + if (item == "AUTO") return Internal.LibraryComponents.ScrollerBase(BDFDB.disCN.scrollerauto, BDFDB.disCN.scrollerfade, BDFDB.disCN.scrollercustomtheme); + else if (item == "Thin") return Internal.LibraryComponents.ScrollerBase(BDFDB.disCN.scrollerthin, BDFDB.disCN.scrollerfade, BDFDB.disCN.scrollercustomtheme); + else if (item == "None") return Internal.LibraryComponents.ScrollerBase(BDFDB.disCN.scrollernone, BDFDB.disCN.scrollerfade, BDFDB.disCN.scrollercustomtheme); + else return "div"; + } + }); + + CustomComponents.SearchBar = reactInitialized && class BDFDB_SearchBar extends Internal.LibraryModules.React.Component { + handleChange(query) { + this.props.query = query; + if (typeof this.props.onChange == "function") this.props.onChange(query, this); + BDFDB.ReactUtils.forceUpdate(this); + } + handleClear() { + this.props.query = ""; + if (this.props.changeOnClear && typeof this.props.onChange == "function") this.props.onChange("", this); + if (typeof this.props.onClear == "function") this.props.onClear(this); + BDFDB.ReactUtils.forceUpdate(this); + } + render() { + let props = Object.assign({}, this.props, { + onChange: this.handleChange.bind(this), + onClear: this.handleClear.bind(this) + }); + if (typeof props.query != "string") props.query = ""; + return BDFDB.ReactUtils.createElement(Internal.NativeSubComponents.SearchBar, props); + } + }; + + CustomComponents.Select = reactInitialized && class BDFDB_Select extends Internal.LibraryModules.React.Component { + render () { + return BDFDB.ReactUtils.createElement(class extends Internal.LibraryModules.React.Component { + handleChange(value) { + this.props.value = value.value || value; + if (typeof this.props.onChange == "function") this.props.onChange(value, this); + BDFDB.ReactUtils.forceUpdate(this); + } + render() { + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.selectwrapper), + children: BDFDB.ReactUtils.createElement(Internal.NativeSubComponents.SearchableSelect, BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + className: this.props.inputClassName, + autoFocus: this.props.autoFocus ? this.props.autoFocus : false, + maxVisibleItems: this.props.maxVisibleItems || 7, + renderOptionLabel: this.props.optionRenderer, + select: this.handleChange.bind(this), + serialize: typeof this.props.serialize == "function" ? this.props.serialize : _ => {}, + isSelected: typeof this.props.isSelected == "function" ? this.props.isSelected : (value => this.props.value == value) + }), "inputClassName", "optionRenderer")) + }); + } + }, this.props); + } + }; + + CustomComponents.SettingsGuildList = reactInitialized && class BDFDB_SettingsGuildList extends Internal.LibraryModules.React.Component { + render() { + this.props.disabled = BDFDB.ArrayUtils.is(this.props.disabled) ? this.props.disabled : []; + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + className: this.props.className, + wrap: Internal.LibraryComponents.Flex.Wrap.WRAP, + children: [this.props.includeDMs && {name: BDFDB.LanguageUtils.LanguageStrings.DIRECT_MESSAGES, acronym: "DMs", id: Internal.DiscordConstants.ME, getIconURL: _ => {}}].concat(Internal.LibraryStores.SortedGuildStore.getFlattenedGuildIds().map(Internal.LibraryStores.GuildStore.getGuild)).filter(n => n).map(guild => BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TooltipContainer, { + text: guild.name, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(this.props.guildClassName, BDFDB.disCN.settingsguild, this.props.disabled.includes(guild.id) && BDFDB.disCN.settingsguilddisabled), + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.GuildIcon, { + guild: guild, + size: this.props.size || Internal.LibraryComponents.GuildIcon.Sizes.MEDIUM + }), + onClick: e => { + let isDisabled = this.props.disabled.includes(guild.id); + if (isDisabled) BDFDB.ArrayUtils.remove(this.props.disabled, guild.id, true); + else this.props.disabled.push(guild.id); + if (typeof this.props.onClick == "function") this.props.onClick(this.props.disabled, this); + BDFDB.ReactUtils.forceUpdate(this); + } + }) + })) + }); + } + }; + + CustomComponents.SettingsPanel = reactInitialized && class BDFDB_SettingsPanel extends Internal.LibraryModules.React.Component { + componentDidMount() { + this.props._instance = this; + let node = BDFDB.ReactUtils.findDOMNode(this); + if (node) this.props._node = node; + } + componentWillUnmount() { + if (BDFDB.ObjectUtils.is(this.props.addon) && typeof this.props.addon.onSettingsClosed == "function") this.props.addon.onSettingsClosed(); + } + render() { + let panelItems = [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.AutoFocusCatcher, {}), + typeof this.props.children == "function" ? (_ => { + return this.props.children(this.props.collapseStates); + })() : this.props.children + ].flat(10).filter(n => n); + return BDFDB.ReactUtils.createElement("div", { + key: this.props.addon && this.props.addon.name && `${this.props.addon.name}-settingsPanel`, + id: this.props.addon && this.props.addon.name && `${this.props.addon.name}-settings`, + className: BDFDB.disCN.settingspanel, + children: [ + this.props.addon.changeLog && !BDFDB.ObjectUtils.isEmpty(this.props.addon.changeLog) && BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TooltipContainer, { + text: BDFDB.LanguageUtils.LanguageStrings.CHANGE_LOG, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Clickable, { + className: BDFDB.disCN._repochangelogbutton, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: Internal.LibraryComponents.SvgIcon.Names.CHANGELOG, + onClick: _ => BDFDB.PluginUtils.openChangeLog(this.props.addon), + width: 24, + height: 24 + }) + }) + }), + panelItems + ] + }); + } + }; + + CustomComponents.SettingsPanelList = reactInitialized && class BDFDB_SettingsPanelInner extends Internal.LibraryModules.React.Component { + render() { + return this.props.children ? BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.settingspanellistwrapper, this.props.mini && BDFDB.disCN.settingspanellistwrappermini), + children: [ + this.props.dividerTop ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.FormDivider, { + className: this.props.mini ? BDFDB.disCN.marginbottom4 : BDFDB.disCN.marginbottom8 + }) : null, + typeof this.props.title == "string" ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.FormTitle.Title, { + className: BDFDB.disCN.marginbottom4, + tag: Internal.LibraryComponents.FormTitle.Tags && Internal.LibraryComponents.FormTitle.Tags.H3, + children: this.props.title + }) : null, + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.settingspanellist, + children: this.props.children + }), + this.props.dividerBottom ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.FormDivider, { + className: this.props.mini ? BDFDB.disCN.margintop4 : BDFDB.disCN.margintop8 + }) : null + ] + }) : null; + } + }; + + CustomComponents.SettingsItem = reactInitialized && class BDFDB_SettingsItem extends Internal.LibraryModules.React.Component { + constructor(props) { + super(props); + if (!this.state) this.state = {}; + this.state.disabled = props.disabled; + } + handleChange(value) { + if (typeof this.props.onChange == "function") this.props.onChange(value, this); + } + render() { + if (typeof this.props.type != "string" || !["BUTTON", "SELECT", "SLIDER", "SWITCH", "TEXTINPUT"].includes(this.props.type.toUpperCase())) return null; + let childComponent = Internal.LibraryComponents[this.props.type]; + if (!childComponent) return null; + if (this.props.mini && childComponent.Sizes) this.props.size = childComponent.Sizes.MINI || childComponent.Sizes.MIN; + let label = this.props.label ? (this.props.tag ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.FormTitle.Title, { + className: BDFDB.DOMUtils.formatClassName(this.props.labelClassName, BDFDB.disCN.marginreset), + tag: this.props.tag, + children: this.props.label + }) : BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextElement, { + size: Internal.LibraryComponents.TextElement.Sizes.SIZE_16, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SettingsLabel, { + className: BDFDB.DOMUtils.formatClassName(this.props.labelClassName), + mini: this.props.mini, + label: this.props.label + }) + })) : null; + let margin = this.props.margin != null ? this.props.margin : (this.props.mini ? 0 : 8); + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.settingsrowcontainer, margin != null && (InternalData.DiscordClasses[`marginbottom${margin}`] && BDFDB.disCN[`marginbottom${margin}`] || margin == 0 && BDFDB.disCN.marginreset)), + id: this.props.id, + children: [ + this.props.dividerTop ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.FormDivider, { + className: this.props.mini ? BDFDB.disCN.marginbottom4 : BDFDB.disCN.marginbottom8 + }) : null, + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.settingsrow, this.state.disabled && BDFDB.disCN.settingsrowdisabled), + "data-layout": "horizontal", + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.settingsrowlabel, + children: [ + label && !this.props.basis ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex.Child, { + grow: 1, + shrink: 1, + wrap: true, + children: label + }) : label, + typeof this.props.note == "string" ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex.Child, { + className: BDFDB.disCN.settingsrownote, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextElement, { + size: Internal.LibraryComponents.TextElement.Sizes.SIZE_12, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextScroller, { + speed: 2, + children: this.props.note + }) + }) + }) : null + ].flat(10).filter(n => n) + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex.Child, { + className: BDFDB.disCNS.settingsrowcontrol + BDFDB.disCN.flexchild, + grow: 0, + shrink: this.props.basis ? 0 : 1, + basis: this.props.basis, + wrap: true, + style: {"flex-direction": "row", "align-items": "center"}, + children: [ + this.props.labelChildren, + BDFDB.ReactUtils.createElement(childComponent, BDFDB.ObjectUtils.exclude(Object.assign(BDFDB.ObjectUtils.exclude(this.props, "className", "id", "type"), this.props.childProps, { + disabled: this.state.disabled, + onChange: this.handleChange.bind(this), + onValueChange: this.handleChange.bind(this) + }), "basis", "margin", "dividerBottom", "dividerTop", "label", "labelClassName", "labelChildren", "tag", "mini", "note", "childProps")) + ].flat(10).filter(n => n) + }) + ] + }), + this.props.dividerBottom ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.FormDivider, { + className: this.props.mini ? BDFDB.disCN.margintop4 : BDFDB.disCN.margintop8 + }) : null + ] + }); + } + }; + + CustomComponents.SettingsLabel = reactInitialized && class BDFDB_SettingsLabel extends Internal.LibraryModules.React.Component { + render() { + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextScroller, { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.settingsrowtitle, this.props.mini && BDFDB.disCN.settingsrowtitlemini, BDFDB.disCN.cursordefault), + speed: 2, + children: this.props.label + }) + } + }; + + CustomComponents.SettingsList = reactInitialized && class BDFDB_SettingsList extends Internal.LibraryModules.React.Component { + componentDidMount() { + this.checkList(); + } + componentDidUpdate() { + this.checkList(); + } + checkList() { + let list = BDFDB.ReactUtils.findDOMNode(this); + if (list && !this.props.configWidth) { + let headers = Array.from(list.querySelectorAll(BDFDB.dotCN.settingstableheader)); + headers.shift(); + if (BDFDB.DOMUtils.getRects(headers[0]).width == 0) BDFDB.TimeUtils.timeout(_ => this.resizeList(headers)); + else this.resizeList(headers); + } + } + resizeList(headers) { + let configWidth = 0, biggestWidth = 0; + if (!configWidth) { + for (let header of headers) { + header.style = ""; + let width = BDFDB.DOMUtils.getRects(header).width; + configWidth = width > configWidth ? width : configWidth; + } + configWidth += 4; + biggestWidth = configWidth; + } + if (headers.length * configWidth > 300) { + this.props.vertical = true; + configWidth = parseInt(290 / headers.length); + } + else if (configWidth < 36) { + configWidth = 36; + biggestWidth = configWidth; + } + this.props.configWidth = configWidth; + this.props.biggestWidth = biggestWidth; + BDFDB.ReactUtils.forceUpdate(this); + } + renderHeaderOption(props) { + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(props.className, BDFDB.disCN.eyebrow, props.clickable && BDFDB.disCN.cursorpointer), + onClick: _ => {if (typeof this.props.onHeaderClick == "function") this.props.onHeaderClick(props.label, this);}, + onContextMenu: _ => {if (typeof this.props.onHeaderContextMenu == "function") this.props.onHeaderContextMenu(props.label, this);}, + children: BDFDB.ReactUtils.createElement("span", { + children: props.label + }) + }); + } + renderItem(props) { + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Card, BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + className: BDFDB.DOMUtils.formatClassName([this.props.cardClassName, props.className].filter(n => n).join(" ").indexOf(BDFDB.disCN.card) == -1 && BDFDB.disCN.cardprimary, BDFDB.disCN.settingstablecard, this.props.cardClassName, props.className), + cardId: props.key, + backdrop: false, + horizontal: true, + style: Object.assign({}, this.props.cardStyle, props.style), + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.settingstablecardlabel, + children: this.props.renderLabel(props, this) + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.settingstablecardconfigs, + style: { + width: props.wrapperWidth || null, + minWidth: props.wrapperWidth || null, + maxWidth: props.wrapperWidth || null + }, + children: this.props.settings.map(setting => BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.checkboxcontainer, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TooltipContainer, { + text: setting.toUpperCase(), + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Checkbox, { + disabled: props.disabled, + cardId: props.key, + settingId: setting, + shape: Internal.LibraryComponents.Checkbox.Shapes && Internal.LibraryComponents.Checkbox.Shapes.ROUND, + type: Internal.LibraryComponents.Checkbox.Types && Internal.LibraryComponents.Checkbox.Types.INVERTED, + color: this.props.checkboxColor, + getColor: this.props.getCheckboxColor, + value: props[setting], + getValue: this.props.getCheckboxValue, + onChange: this.props.onCheckboxChange + }) + }) + })).flat(10).filter(n => n) + }) + ] + }), "title", "data", "settings", "renderLabel", "cardClassName", "cardStyle", "checkboxColor", "getCheckboxColor", "getCheckboxValue", "onCheckboxChange", "configWidth", "biggestWidth", "pagination")); + } + render() { + this.props.settings = BDFDB.ArrayUtils.is(this.props.settings) ? this.props.settings : []; + this.props.renderLabel = typeof this.props.renderLabel == "function" ? this.props.renderLabel : data => data.label; + this.props.data = (BDFDB.ArrayUtils.is(this.props.data) ? this.props.data : [{}]).filter(n => n); + + let wrapperWidth = this.props.configWidth && this.props.configWidth * this.props.settings.length; + let isHeaderClickable = typeof this.props.onHeaderClick == "function" || typeof this.props.onHeaderContextMenu == "function"; + let usePagination = BDFDB.ObjectUtils.is(this.props.pagination); + + let header = BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.settingstableheaders, + style: this.props.vertical && this.props.biggestWidth ? { + marginTop: this.props.biggestWidth - 15 || 0 + } : {}, + children: [ + this.renderHeaderOption({ + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.settingstableheadername, BDFDB.disCN.settingstableheader), + clickable: this.props.title && isHeaderClickable, + label: this.props.title || "" + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.settingstableheaderoptions, + style: { + width: wrapperWidth || null, + minWidth: wrapperWidth || null, + maxWidth: wrapperWidth || null + }, + children: this.props.settings.map(setting => this.renderHeaderOption({ + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.settingstableheaderoption, BDFDB.disCN.settingstableheader, this.props.vertical && BDFDB.disCN.settingstableheadervertical), + clickable: isHeaderClickable, + label: setting + })) + }) + ] + }); + return !this.props.data.length ? null : BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.settingstablelist, this.props.className), + children: [ + !usePagination && header, + !usePagination ? this.props.data.map(data => this.renderItem(Object.assign({}, data, {wrapperWidth}))) : BDFDB.ReactUtils.createElement(Internal.LibraryComponents.PaginatedList, Object.assign({}, this.props.pagination, { + header: header, + items: this.props.data, + renderItem: data => this.renderItem(Object.assign({}, data, {wrapperWidth})), + onJump: (offset, instance) => { + this.props.pagination.offset = offset; + if (typeof this.props.pagination.onJump == "function") this.props.pagination.onJump(offset, this, instance); + } + })) + ].filter(n => n) + }); + } + }; + + CustomComponents.SettingsSaveItem = reactInitialized && class BDFDB_SettingsSaveItem extends Internal.LibraryModules.React.Component { + saveSettings(value) { + if (!BDFDB.ArrayUtils.is(this.props.keys) || !BDFDB.ObjectUtils.is(this.props.plugin)) return; + let keys = this.props.keys.filter(n => n); + let option = keys.shift(); + if (BDFDB.ObjectUtils.is(this.props.plugin) && option) { + let data = BDFDB.DataUtils.load(this.props.plugin, option); + let newC = ""; + for (let key of keys) newC += `{"${key}":`; + value = value != null && value.value != null ? value.value : value; + let isString = typeof value == "string"; + let marker = isString ? `"` : ``; + newC += (marker + (isString ? value.replace(/\\/g, "\\\\") : value) + marker) + "}".repeat(keys.length); + newC = JSON.parse(newC); + newC = BDFDB.ObjectUtils.is(newC) ? BDFDB.ObjectUtils.deepAssign({}, data, newC) : newC; + BDFDB.DataUtils.save(newC, this.props.plugin, option); + if (!this.props.plugin.settings) this.props.plugin.settings = {}; + this.props.plugin.settings[option] = newC; + this.props.plugin.SettingsUpdated = true; + } + if (typeof this.props.onChange == "function") this.props.onChange(value, this); + } + render() { + if (typeof this.props.type != "string" || !["SELECT", "SLIDER", "SWITCH", "TEXTINPUT"].includes(this.props.type.toUpperCase())) return null; + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SettingsItem, BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + onChange: this.saveSettings.bind(this) + }), "keys", "key", "plugin")); + } + }; + + CustomComponents.SidebarList = reactInitialized && class BDFDB_SidebarList extends Internal.LibraryModules.React.Component { + handleItemSelect(item) { + this.props.selectedItem = item; + if (typeof this.props.onItemSelect == "function") this.props.onItemSelect(item, this); + BDFDB.ReactUtils.forceUpdate(this); + } + render() { + let items = (BDFDB.ArrayUtils.is(this.props.items) ? this.props.items : [{}]).filter(n => n); + let selectedItem = this.props.selectedItem || (items[0] || {}).value; + let selectedElements = (items.find(n => n.value == selectedItem) || {}).elements; + let renderElement = typeof this.props.renderElement == "function" ? this.props.renderElement : (_ => {}); + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(this.props.className, BDFDB.disCN.sidebarlist), + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Scrollers.Thin, { + className: BDFDB.DOMUtils.formatClassName(this.props.sidebarClassName, BDFDB.disCN.sidebar), + fade: true, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TabBar, { + itemClassName: this.props.itemClassName, + type: Internal.LibraryComponents.TabBar.Types.SIDE, + items: items, + selectedItem: selectedItem, + renderItem: this.props.renderItem, + onItemSelect: this.handleItemSelect.bind(this) + }) + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Scrollers.Thin, { + className: BDFDB.DOMUtils.formatClassName(this.props.contentClassName, BDFDB.disCN.sidebarcontent), + fade: true, + children: [selectedElements].flat(10).filter(n => n).map(data => renderElement(data)) + }) + ] + }); + } + }; + + CustomComponents.Slider = reactInitialized && class BDFDB_Slider extends Internal.LibraryModules.React.Component { + render () { + return BDFDB.ReactUtils.createElement(class extends Internal.LibraryModules.React.Component { + handleMarkerRender(marker) { + let newMarker = BDFDB.NumberUtils.mapRange([0, 100], this.props.edges, marker); + if (typeof this.props.digits == "number") newMarker = Math.round(newMarker * Math.pow(10, this.props.digits)) / Math.pow(10, this.props.digits); + return newMarker; + } + handleValueChange(value) { + let newValue = value; + if (typeof this.props.digits == "number") newValue = Math.round(newValue * Math.pow(10, this.props.digits)) / Math.pow(10, this.props.digits); + if (newValue < this.props.minValue) newValue = this.props.minValue; + else if (newValue > this.props.maxValue) newValue = this.props.maxValue; + this.props.defaultValue = this.props.value = newValue; + if (typeof this.props.onValueChange == "function") this.props.onValueChange(BDFDB.NumberUtils.mapRange([0, 100], this.props.edges, newValue), this); + BDFDB.ReactUtils.forceUpdate(this); + } + handleValueRender(value) { + let newValue = BDFDB.NumberUtils.mapRange([0, 100], this.props.edges, value); + if (typeof this.props.digits == "number") newValue = Math.round(newValue * Math.pow(10, this.props.digits)) / Math.pow(10, this.props.digits); + if (typeof this.props.onValueRender == "function") { + let tempReturn = this.props.onValueRender(newValue, this); + if (tempReturn != undefined) newValue = tempReturn; + } + return newValue; + } + render() { + let value = this.props.value || this.props.defaultValue || 0; + this.props.minValue = 0; + this.props.maxValue = 100; + if (!BDFDB.ArrayUtils.is(this.props.edges) || this.props.edges.length != 2) this.props.edges = [this.props.min || this.props.minValue, this.props.max || this.props.maxValue]; + let defaultValue = BDFDB.NumberUtils.mapRange([0, 100], this.props.edges, value); + if (typeof this.props.digits == "number") defaultValue = Math.round(defaultValue * Math.pow(10, this.props.digits)) / Math.pow(10, this.props.digits); + return BDFDB.ReactUtils.createElement(Internal.NativeSubComponents.Slider, BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + initialValue: defaultValue, + markers: typeof this.props.markerAmount == "number" ? Array.from(Array(this.props.markerAmount).keys()).map((_, i) => i * (this.props.maxValue - this.props.minValue)/10) : undefined, + onMarkerRender: this.handleMarkerRender.bind(this), + onValueChange: this.handleValueChange.bind(this), + onValueRender: this.handleValueRender.bind(this) + }), "digits", "edges", "max", "min", "markerAmount")); + } + }, this.props); + } + }; + Internal.setDefaultProps(CustomComponents.Slider, {hideBubble: false, digits: 3}); + + CustomComponents.SvgIcon = reactInitialized && class BDFDB_Icon extends Internal.LibraryModules.React.Component { + render() { + if (BDFDB.ObjectUtils.is(this.props.name)) { + if (this.props.className) this.props.nativeClass = true; + this.props.iconSVG = this.props.name.icon; + let props = Object.assign({ + width: 24, + height: 24, + color: "currentColor" + }, this.props.name.defaultProps, this.props); + for (let key in props) this.props.iconSVG = this.props.iconSVG.replace(new RegExp(`%%${key}`, "g"), props[key]); + } + if (this.props.iconSVG) { + let icon = BDFDB.ReactUtils.elementToReact(BDFDB.DOMUtils.create(this.props.iconSVG)); + if (BDFDB.ReactUtils.isValidElement(icon)) { + icon.props.className = BDFDB.DOMUtils.formatClassName(!this.props.nativeClass && BDFDB.disCN.svgicon, icon.props.className, this.props.className); + icon.props.style = Object.assign({}, icon.props.style, this.props.style); + icon.props = Object.assign({}, BDFDB.ObjectUtils.extract(this.props, "onClick", "onContextMenu", "onMouseDown", "onMouseUp", "onMouseEnter", "onMouseLeave"), icon.props); + return icon; + } + } + return null; + } + }; + if (CustomComponents.SvgIcon) CustomComponents.SvgIcon.Names = InternalData.SvgIcons || {}; + + const SwitchIconPaths = { + a: { + TOP: "M5.13231 6.72963L6.7233 5.13864L14.855 13.2704L13.264 14.8614L5.13231 6.72963Z", + BOTTOM: "M13.2704 5.13864L14.8614 6.72963L6.72963 14.8614L5.13864 13.2704L13.2704 5.13864Z" + }, + b: { + TOP: "M6.56666 11.0013L6.56666 8.96683L13.5667 8.96683L13.5667 11.0013L6.56666 11.0013Z", + BOTTOM: "M13.5582 8.96683L13.5582 11.0013L6.56192 11.0013L6.56192 8.96683L13.5582 8.96683Z" + }, + c: { + TOP: "M7.89561 14.8538L6.30462 13.2629L14.3099 5.25755L15.9009 6.84854L7.89561 14.8538Z", + BOTTOM: "M4.08643 11.0903L5.67742 9.49929L9.4485 13.2704L7.85751 14.8614L4.08643 11.0903Z" + } + }; + const SwitchInner = function (props) { + let reducedMotion = BDFDB.ReactUtils.useContext(Internal.LibraryModules.PreferencesContext.AccessibilityPreferencesContext).reducedMotion; + let ref = BDFDB.ReactUtils.useRef(null); + let state = BDFDB.ReactUtils.useState(false); + let animation = Internal.LibraryComponents.Animations.useSpring({ + config: { + mass: 1, + tension: 250 + }, + opacity: props.disabled ? .3 : 1, + state: state[0] ? (props.value ? .7 : .3) : (props.value ? 1 : 0) + }); + let fill = animation.state.to({ + output: [props.uncheckedColor, props.checkedColor] + }); + let mini = props.size == Internal.LibraryComponents.Switch.Sizes.MINI; + + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Animations.animated.div, { + className: BDFDB.DOMUtils.formatClassName(props.className, BDFDB.disCN.switch, props.value && BDFDB.disCN.switchchecked, mini && BDFDB.disCN.switchmini), + onMouseDown: _ => { + return !props.disabled && state[1](true); + }, + onMouseUp: _ => { + return state[1](false); + }, + onMouseLeave: _ => { + return state[1](false); + }, + style: { + opacity: animation.opacity, + backgroundColor: animation.state.to({ + output: [props.uncheckedColor, props.checkedColor] + }) + }, + tabIndex: -1, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Animations.animated.svg, { + className: BDFDB.disCN.switchslider, + viewBox: "0 0 28 20", + preserveAspectRatio: "xMinYMid meet", + style: { + left: animation.state.to({ + range: [0, .3, .7, 1], + output: mini ? [-1, 2, 6, 9] : [-3, 1, 8, 12] + }) + }, + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Animations.animated.rect, { + fill: "white", + x: animation.state.to({ + range: [0, .3, .7, 1], + output: [4, 0, 0, 4] + }), + y: animation.state.to({ + range: [0, .3, .7, 1], + output: [0, 1, 1, 0] + }), + height: animation.state.to({ + range: [0, .3, .7, 1], + output: [20, 18, 18, 20] + }), + width: animation.state.to({ + range: [0, .3, .7, 1], + output: [20, 28, 28, 20] + }), + rx: "10" + }), + BDFDB.ReactUtils.createElement("svg", { + viewBox: "0 0 20 20", + fill: "none", + children: [ + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Animations.animated.path, { + fill: fill, + d: animation.state.to({ + range: [0, .3, .7, 1], + output: reducedMotion.enabled ? [SwitchIconPaths.a.TOP, SwitchIconPaths.a.TOP, SwitchIconPaths.c.TOP, SwitchIconPaths.c.TOP] : [SwitchIconPaths.a.TOP, SwitchIconPaths.b.TOP, SwitchIconPaths.b.TOP, SwitchIconPaths.c.TOP] + }) + }), + BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Animations.animated.path, { + fill: fill, + d: animation.state.to({ + range: [0, .3, .7, 1], + output: reducedMotion.enabled ? [SwitchIconPaths.a.BOTTOM, SwitchIconPaths.a.BOTTOM, SwitchIconPaths.c.BOTTOM, SwitchIconPaths.c.BOTTOM] : [SwitchIconPaths.a.BOTTOM, SwitchIconPaths.b.BOTTOM, SwitchIconPaths.b.BOTTOM, SwitchIconPaths.c.BOTTOM] + }) + }) + ] + }) + ] + }), + BDFDB.ReactUtils.createElement("input", BDFDB.ObjectUtils.exclude(Object.assign({}, props, { + id: props.id, + type: "checkbox", + ref: ref, + className: BDFDB.DOMUtils.formatClassName(props.inputClassName, BDFDB.disCN.switchinner), + tabIndex: props.disabled ? -1 : 0, + onKeyDown: e => { + if (!props.disabled && !e.repeat && (e.key == " " || e.key == "Enter")) state[1](true); + }, + onKeyUp: e => { + if (!props.disabled && !e.repeat) { + state[1](false); + if (e.key == "Enter" && ref.current) ref.current.click(); + } + }, + onChange: e => { + state[1](false); + if (typeof props.onChange == "function") props.onChange((e.currentTarget || e.target).checked, e); + }, + checked: props.value, + disabled: props.disabled + }), "uncheckedColor", "checkedColor", "size", "value")) + ] + }); + }; + CustomComponents.Switch = reactInitialized && class BDFDB_Switch extends Internal.LibraryModules.React.Component { + render () { + return BDFDB.ReactUtils.createElement(class extends Internal.LibraryModules.React.Component { + handleChange() { + this.props.value = !this.props.value; + if (typeof this.props.onChange == "function") this.props.onChange(this.props.value, this); + BDFDB.ReactUtils.forceUpdate(this); + } + render() { + return BDFDB.ReactUtils.createElement(SwitchInner, Object.assign({}, this.props, { + onChange: this.handleChange.bind(this) + })); + } + }, this.props); + } + }; + if (CustomComponents.Switch) { + CustomComponents.Switch.Sizes = { + DEFAULT: "default", + MINI: "mini", + }; + Internal.setDefaultProps(CustomComponents.Switch, { + size: CustomComponents.Switch.Sizes.DEFAULT, + uncheckedColor: Internal.DiscordConstants.Colors.PRIMARY_400, + checkedColor: Internal.DiscordConstants.Colors.BRAND + }); + } + + CustomComponents.TabBar = reactInitialized && class BDFDB_TabBar extends Internal.LibraryModules.React.Component { + handleItemSelect(item) { + this.props.selectedItem = item; + if (typeof this.props.onItemSelect == "function") this.props.onItemSelect(item, this); + BDFDB.ReactUtils.forceUpdate(this); + } + render() { + let items = (BDFDB.ArrayUtils.is(this.props.items) ? this.props.items : [{}]).filter(n => n); + let selectedItem = this.props.selectedItem || (items[0] || {}).value; + let renderItem = typeof this.props.renderItem == "function" ? this.props.renderItem : (data => data.label || data.value); + return BDFDB.ReactUtils.createElement(Internal.NativeSubComponents.TabBar, BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + selectedItem: selectedItem, + onItemSelect: this.handleItemSelect.bind(this), + children: items.map(data => BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TabBar.Item, { + className: BDFDB.DOMUtils.formatClassName(this.props.itemClassName, selectedItem == data.value && this.props.itemSelectedClassName), + color: this.props.color, + look: this.props.look, + itemType: this.props.type, + id: data.value, + children: renderItem(data), + "aria-label": data.label || data.value + })) + }), "itemClassName", "items", "renderItem")); + } + }; + if (CustomComponents.TabBar) { + CustomComponents.TabBar.Types = { + SIDE: "side", + TOP: "top", + TOP_PILL: "top-pill" + }; + CustomComponents.TabBar.Looks = { + GREY: "grey", + BRAND: "brand", + CUSTOM: "custom" + }; + } + + CustomComponents.Table = reactInitialized && class BDFDB_Table extends Internal.LibraryModules.React.Component { + render() { + return BDFDB.ReactUtils.createElement(Internal.NativeSubComponents.Table, Object.assign({}, this.props, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.table, this.props.className), + headerCellClassName: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.tableheadercell, this.props.headerCellClassName), + sortedHeaderCellClassName: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.tableheadercellsorted, this.props.sortedHeaderCellClassName), + bodyCellClassName: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.tablebodycell, this.props.bodyCellClassName), + onSort: (sortKey, sortDirection) => { + this.props.sortDirection = this.props.sortKey != sortKey && sortDirection == Internal.LibraryComponents.Table.SortDirection.ASCENDING && this.props.columns.filter(n => n.key == sortKey)[0].reverse ? Internal.LibraryComponents.Table.SortDirection.DESCENDING : sortDirection; + this.props.sortKey = sortKey; + this.props.data = BDFDB.ArrayUtils.keySort(this.props.data, this.props.sortKey); + if (this.props.sortDirection == Internal.LibraryComponents.Table.SortDirection.DESCENDING) this.props.data.reverse(); + if (typeof this.props.onSort == "function") this.props.onSort(this.props.sortKey, this.props.sortDirection); + BDFDB.ReactUtils.forceUpdate(this); + } + })); + } + }; + + CustomComponents.TextArea = reactInitialized && class BDFDB_TextArea extends Internal.LibraryModules.React.Component { + render () { + return BDFDB.ReactUtils.createElement(class extends Internal.LibraryModules.React.Component { + handleChange(e) { + this.props.value = e; + if (typeof this.props.onChange == "function") this.props.onChange(e, this); + BDFDB.ReactUtils.forceUpdate(this); + } + handleBlur(e) {if (typeof this.props.onBlur == "function") this.props.onBlur(e, this);} + handleFocus(e) {if (typeof this.props.onFocus == "function") this.props.onFocus(e, this);} + render() { + return BDFDB.ReactUtils.createElement(Internal.NativeSubComponents.TextArea, Object.assign({}, this.props, { + onChange: this.handleChange.bind(this), + onBlur: this.handleBlur.bind(this), + onFocus: this.handleFocus.bind(this) + })); + } + }, this.props); + } + }; + + CustomComponents.TextElement = reactInitialized && class BDFDB_TextElement extends Internal.LibraryModules.React.Component { + render() { + let color = this.props.color != undefined ? this.props.color && Internal.DiscordConstants.ColorsCSS[this.props.color] : Internal.DiscordConstants.ColorsCSS[CustomComponents.TextElement.Colors.STANDARD]; + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Text, { + className: this.props.className, + variant: `${this.props.size || CustomComponents.TextElement.Sizes.SIZE_14}/${this.props.weight || "normal"}`, + style: color ? {color: color} : {}, + color: "", + children: this.props.children + }); + } + }; + CustomComponents.TextElement.Colors = { + "CUSTOM": "", + "MUTED": "TEXT_MUTED", + "PRIMARY": "TEXT_PRIMARY", + "STANDARD": "TEXT_DEFAULT", + "STATUS_RED": "STATUS_DANGER" + }; + CustomComponents.TextElement.Sizes = { + "SIZE_10": "text-xxs", + "SIZE_12": "text-xs", + "SIZE_14": "text-sm", + "SIZE_16": "text-md", + "SIZE_20": "text-lg" + }; + + CustomComponents.TextGradientElement = reactInitialized && class BDFDB_TextGradientElement extends Internal.LibraryModules.React.Component { + render() { + if (this.props.gradient && this.props.children) return BDFDB.ReactUtils.createElement("span", { + children: this.props.children, + ref: instance => { + let ele = BDFDB.ReactUtils.findDOMNode(instance); + if (ele) { + ele.style.setProperty("background-image", this.props.gradient, "important"); + ele.style.setProperty("color", "transparent", "important"); + ele.style.setProperty("text-decoration-color", BDFDB.ColorUtils.convert(this.props.gradient[0], "RGBA"), "important"); + ele.style.setProperty("-webkit-background-clip", "text", "important"); + } + } + }); + return this.props.children || null; + } + }; + + CustomComponents.TextInput = reactInitialized && class BDFDB_TextInputInner extends Internal.LibraryModules.React.Component { + handleChange(e, e2) { + let value = e = BDFDB.ObjectUtils.is(e) ? (e.currentTarget || e.target).value : e; + if (this.props.type == "number") value = parseInt(value); + this.props.value = this.props.valuePrefix && !value.startsWith(this.props.valuePrefix) ? (this.props.valuePrefix + value) : value; + this.props.file = e2 = BDFDB.ObjectUtils.is(e2) ? (e2.currentTarget || e2.target).value : e2; + if (typeof this.props.onChange == "function") this.props.onChange(this.props.type == "file" ? this.props.file : this.props.value, this); + BDFDB.ReactUtils.forceUpdate(this); + } + handleInput(e) {if (typeof this.props.onInput == "function") this.props.onInput(BDFDB.ObjectUtils.is(e) ? (e.currentTarget || e.target).value : e, this);} + handleKeyDown(e) {if (typeof this.props.onKeyDown == "function") this.props.onKeyDown(e, this);} + handleBlur(e) {if (typeof this.props.onBlur == "function") this.props.onBlur(e, this);} + handleFocus(e) {if (typeof this.props.onFocus == "function") this.props.onFocus(e, this);} + handleMouseEnter(e) {if (typeof this.props.onMouseEnter == "function") this.props.onMouseEnter(e, this);} + handleMouseLeave(e) {if (typeof this.props.onMouseLeave == "function") this.props.onMouseLeave(e, this);} + handleNumberButton(ins, value) { + BDFDB.TimeUtils.clear(this.pressedTimeout); + this.pressedTimeout = BDFDB.TimeUtils.timeout(_ => { + delete this.props.focused; + BDFDB.ReactUtils.forceUpdate(this); + }, 1000); + this.props.focused = true; + this.handleChange.apply(this, [value]); + this.handleInput.apply(this, [value]); + } + componentDidMount() { + if (this.props.type == "file") { + let navigatorInstance = BDFDB.ReactUtils.findOwner(this, {name: "BDFDB_FileButton"}); + if (navigatorInstance) navigatorInstance.refInput = this; + } + let input = BDFDB.ReactUtils.findDOMNode(this); + if (!input) return; + input = input.querySelector("input") || input; + if (input && !input.patched) { + input.addEventListener("keydown", e => { + this.handleKeyDown.apply(this, [e]); + e.stopImmediatePropagation(); + }); + input.patched = true; + } + } + render() { + let inputChildren = [ + BDFDB.ReactUtils.createElement("input", BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.input, this.props.size, this.props.inputClassName, this.props.focused && BDFDB.disCN.inputfocused, this.props.error || this.props.errorMessage ? BDFDB.disCN.inputerror : (this.props.success && BDFDB.disCN.inputsuccess), this.props.disabled && BDFDB.disCN.inputdisabled, this.props.editable && BDFDB.disCN.inputeditable), + type: this.props.type == "color" || this.props.type == "file" ? "text" : this.props.type, + onChange: this.handleChange.bind(this), + onInput: this.handleInput.bind(this), + onKeyDown: this.handleKeyDown.bind(this), + onBlur: this.handleBlur.bind(this), + onFocus: this.handleFocus.bind(this), + onMouseEnter: this.handleMouseEnter.bind(this), + onMouseLeave: this.handleMouseLeave.bind(this), + maxLength: this.props.type == "file" ? false : this.props.maxLength, + style: this.props.width ? {width: `${this.props.width}px`} : {}, + ref: this.props.inputRef + }), "errorMessage", "focused", "error", "success", "inputClassName", "inputChildren", "valuePrefix", "size", "editable", "inputRef", "style", "mode", "colorPickerOpen", "noAlpha", "filter")), + this.props.inputChildren, + this.props.type == "color" ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex.Child, { + wrap: true, + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.ColorSwatches, { + colors: [], + color: this.props.value && this.props.mode == "comp" ? BDFDB.ColorUtils.convert(this.props.value.split(","), "RGB") : this.props.value, + onColorChange: color => this.handleChange.apply(this, [!color ? "" : (this.props.mode == "comp" ? BDFDB.ColorUtils.convert(color, "RGBCOMP").slice(0, 3).join(",") : BDFDB.ColorUtils.convert(color, this.props.noAlpha ? "RGB" : "RGBA"))]), + pickerOpen: this.props.colorPickerOpen, + onPickerOpen: _ => this.props.colorPickerOpen = true, + onPickerClose: _ => delete this.props.colorPickerOpen, + ref: this.props.controlsRef, + pickerConfig: {gradient: false, alpha: this.props.mode != "comp" && !this.props.noAlpha} + }) + }) : null, + this.props.type == "file" ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.FileButton, { + filter: this.props.filter, + mode: this.props.mode, + ref: this.props.controlsRef + }) : null + ].flat(10).filter(n => n); + + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.inputwrapper, this.props.type == "number" && (this.props.size && Internal.LibraryComponents.TextInput.Sizes[this.props.size.toUpperCase()] && BDFDB.disCN["inputnumberwrapper" + this.props.size.toLowerCase()] || BDFDB.disCN.inputnumberwrapperdefault), this.props.className), + style: this.props.style, + children: [ + this.props.type == "number" ? BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.inputnumberbuttons, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.inputnumberbuttonup, + onClick: e => { + let min = parseInt(this.props.min); + let max = parseInt(this.props.max); + let newV = parseInt(this.props.value) + 1 || min || 0; + if (isNaN(max) || !isNaN(max) && newV <= max) this.handleNumberButton.bind(this)(e._targetInst, isNaN(min) || !isNaN(min) && newV >= min ? newV : min); + } + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.inputnumberbuttondown, + onClick: e => { + let min = parseInt(this.props.min); + let max = parseInt(this.props.max); + let newV = parseInt(this.props.value) - 1 || min || 0; + if (isNaN(min) || !isNaN(min) && newV >= min) this.handleNumberButton.bind(this)(e._targetInst, isNaN(max) || !isNaN(max) && newV <= max ? newV : max); + } + }) + ] + }) : null, + inputChildren.length == 1 ? inputChildren[0] : BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex, { + wrap: Internal.LibraryComponents.Flex.Wrap.NO_WRAP, + align: Internal.LibraryComponents.Flex.Align.CENTER, + children: inputChildren.map((child, i) => i != 0 ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Flex.Child, { + shrink: 0, + children: child + }) : child) + }), + this.props.errorMessage ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TextElement, { + className: BDFDB.disCN.margintop8, + size: Internal.LibraryComponents.TextElement.Sizes.SIZE_12, + color: Internal.LibraryComponents.TextElement.Colors.STATUS_RED, + children: this.props.errorMessage + }) : null + ].filter(n => n) + }); + } + }; + CustomComponents.TextInput.Sizes = { + MINI: BDFDB.disCN.inputmini + }; + + CustomComponents.TextScroller = reactInitialized && class BDFDB_TextScroller extends Internal.LibraryModules.React.Component { + render() { + let scrolling, scroll = _ => {}; + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.textscroller, this.props.className), + style: Object.assign({}, this.props.style, { + position: "relative", + display: "block", + overflow: "hidden" + }), + ref: instance => { + const ele = BDFDB.ReactUtils.findDOMNode(instance); + if (ele && ele.parentElement) { + BDFDB.DOMUtils.hide(ele); + const maxWidth = BDFDB.DOMUtils.getInnerWidth(ele.parentElement); + if (maxWidth > 50) ele.style.setProperty("max-width", `${maxWidth}px`); + BDFDB.DOMUtils.show(ele); + if (!this.props.initiated) BDFDB.TimeUtils.timeout(_ => { + this.props.initiated = true; + if (document.contains(ele.parentElement)) BDFDB.ReactUtils.forceUpdate(this); + }, 3000); + const Animation = new Internal.LibraryModules.AnimationUtils.Value(0); + Animation.interpolate({inputRange: [0, 1], outputRange: [0, (BDFDB.DOMUtils.getRects(ele.firstElementChild).width - BDFDB.DOMUtils.getRects(ele).width) * -1]}).addListener(v => { + ele.firstElementChild.style.setProperty("display", v.value == 0 ? "inline" : "block", "important"); + ele.firstElementChild.style.setProperty("left", `${v.value}px`, "important"); + }); + scroll = p => { + const display = ele.firstElementChild.style.getPropertyValue("display"); + ele.firstElementChild.style.setProperty("display", "inline", "important"); + const innerWidth = BDFDB.DOMUtils.getRects(ele.firstElementChild).width; + const outerWidth = BDFDB.DOMUtils.getRects(ele).width; + ele.firstElementChild.style.setProperty("display", display, "important"); + + let w = p + parseFloat(ele.firstElementChild.style.getPropertyValue("left")) / (innerWidth - outerWidth); + w = isNaN(w) || !isFinite(w) ? p : w; + w *= innerWidth / (outerWidth * 2); + Internal.LibraryModules.AnimationUtils.parallel([Internal.LibraryModules.AnimationUtils.timing(Animation, {toValue: p, duration: Math.sqrt(w**2) * 4000 / (parseInt(this.props.speed) || 1)})]).start(); + }; + } + }, + onClick: e => { + if (typeof this.props.onClick == "function") this.props.onClick(e, this); + }, + onMouseEnter: e => { + let target = e.currentTarget || e.target; + if (BDFDB.DOMUtils.getRects(target).width < BDFDB.DOMUtils.getRects(target.firstElementChild).width || target.firstElementChild.style.getPropertyValue("display") != "inline") { + scrolling = true; + scroll(1); + } + }, + onMouseLeave: e => { + if (scrolling) { + scrolling = false; + scroll(0); + } + }, + children: BDFDB.ReactUtils.createElement("div", { + style: { + left: "0", + position: "relative", + display: "inline", + whiteSpace: "nowrap" + }, + children: this.props.children + }) + }); + } + }; + CustomComponents.TooltipContainer = reactInitialized && class BDFDB_TooltipContainer extends Internal.LibraryModules.React.Component { + updateTooltip(text) { + if (this.tooltip) this.tooltip.update(text); + } + render() { + let child = (typeof this.props.children == "function" ? this.props.children() : (BDFDB.ArrayUtils.is(this.props.children) ? this.props.children[0] : this.props.children)) || BDFDB.ReactUtils.createElement("div", {}); + if (!child || !child.props) return null; + child.props.className = BDFDB.DOMUtils.formatClassName(child.props.className, this.props.className); + let childProps = Object.assign({}, child.props); + let shown = false; + child.props.onMouseEnter = (e, childThis) => { + let target = e.currentTarget || e.target; + if (!shown && !target.__BDFDBtooltipShown && !(this.props.onlyShowOnShift && !e.shiftKey) && !(this.props.onlyShowOnCtrl && !e.ctrlKey)) { + target.__BDFDBtooltipShown = shown = true; + this.tooltip = BDFDB.TooltipUtils.create(target, typeof this.props.text == "function" ? this.props.text(this, e) : this.props.text, Object.assign({ + note: this.props.note, + delay: this.props.delay + }, this.props.tooltipConfig, { + onHide: (tooltip, anker) => { + delete anker.__BDFDBtooltipShown; + shown = false; + if (this.props.tooltipConfig && typeof this.props.tooltipConfig.onHide == "function") this.props.tooltipConfig.onHide(tooltip, anker); + } + })); + if (typeof this.props.onMouseEnter == "function") this.props.onMouseEnter(e, this); + if (typeof childProps.onMouseEnter == "function") childProps.onMouseEnter(e, childThis); + } + }; + child.props.onMouseLeave = (e, childThis) => { + if (typeof this.props.onMouseLeave == "function") this.props.onMouseLeave(e, this); + if (typeof childProps.onMouseLeave == "function") childProps.onMouseLeave(e, childThis); + }; + child.props.onClick = (e, childThis) => { + if (typeof this.props.onClick == "function") this.props.onClick(e, this); + if (typeof childProps.onClick == "function") childProps.onClick(e, childThis); + if (typeof this.props.text == "function") this.updateTooltip(this.props.text(this, e)); + }; + child.props.onContextMenu = (e, childThis) => { + if (typeof this.props.onContextMenu == "function") this.props.onContextMenu(e, this); + if (typeof childProps.onContextMenu == "function") childProps.onContextMenu(e, childThis); + if (typeof this.props.text == "function") this.updateTooltip(this.props.text(this, e)); + }; + return BDFDB.ReactUtils.createElement(Internal.LibraryModules.React.Fragment, { + children: child + }); + } + }; + if (CustomComponents.TooltipContainer) CustomComponents.TooltipContainer.Positions = { + BOTTOM: "bottom", + CENTER: "center", + LEFT: "left", + RIGHT: "right", + TOP: "top", + WINDOW_CENTER: "window_center" + }; + + CustomComponents.UserPopoutContainer = reactInitialized && class BDFDB_UserPopoutContainer extends Internal.LibraryModules.React.Component { + render() { + return BDFDB.ReactUtils.createElement(Internal.LibraryComponents.PopoutContainer, BDFDB.ObjectUtils.exclude(Object.assign({}, this.props, { + wrap: false, + renderPopout: instance => BDFDB.ReactUtils.createElement(Internal.LibraryComponents.UserPopout, { + user: Internal.LibraryStores.UserStore.getUser(this.props.userId), + currentUser: Internal.LibraryStores.UserStore.getUser(BDFDB.UserUtils.me.id), + userId: this.props.userId, + channelId: this.props.channelId, + guildId: this.props.guildId + }), + }), "userId", "channelId", "guildId")); + } + }; + + CustomComponents.UserMention = reactInitialized && class BDFDB_UserMention extends Internal.LibraryModules.React.Component { + render() { + let user = this.props.user || Internal.LibraryStores.UserStore.getUser(this.props.userId); + let channel = Internal.LibraryStores.ChannelStore.getChannel(this.props.channelId); + let guildId = this.props.guildId || channel && channel.guild_id; + let mention = BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Clickable, { + className: this.props.className, + onContextMenu: event => BDFDB.UserUtils.openMenu(user, guildId, channel.id, event), + children: "@" + BDFDB.LibraryModules.UserNameUtils.getName(guildId, this.props.channelId, user) + }); + return this.props.inlinePreview ? mention : BDFDB.ReactUtils.createElement(Internal.LibraryComponents.UserPopoutContainer, Object.assign({}, this.props, { + position: Internal.LibraryComponents.PopoutContainer.Positions.RIGHT, + align: Internal.LibraryComponents.PopoutContainer.Align.BOTTOM, + children: mention + })); + } + }; + + const VideoInner = function (props) { + let ref = BDFDB.ReactUtils.useRef(null); + BDFDB.ReactUtils.useEffect(_ => { + if (ref.current) props.play ? ref.current.play() : ref.current.pause(); + }, [props.play]); + return props.ignoreMaxSize || props.naturalWidth <= Internal.DiscordConstants.MAX_VIDEO_WIDTH && props.naturalHeight <= Internal.DiscordConstants.MAX_VIDEO_HEIGHT || props.naturalWidth <= Internal.DiscordConstants.MAX_VIDEO_HEIGHT && props.naturalHeight <= Internal.DiscordConstants.MAX_VIDEO_WIDTH ? BDFDB.ReactUtils.createElement(Internal.LibraryComponents.VideoForwardRef, { + ref: ref, + className: props.className, + poster: props.poster, + src: props.src, + width: props.width, + height: props.height, + muted: true, + loop: true, + autoPlay: props.play, + playOnHover: props.playOnHover, + preload: "none" + }) : BDFDB.ReactUtils.createElement("img", { + alt: "", + src: props.poster, + width: props.width, + height: props.height + }); + }; + CustomComponents.Video = reactInitialized && class BDFDB_Video extends Internal.LibraryModules.React.Component { + render() { + return BDFDB.ReactUtils.createElement(VideoInner, this.props); + } + }; + + Internal.LibraryComponents = new Proxy(LibraryComponents, { + get: function (_, item) { + if (LibraryComponents[item]) return LibraryComponents[item]; + if (!InternalData.LibraryComponents[item] && !CustomComponents[item]) return "div"; + + Internal.findModuleViaData(LibraryComponents, InternalData.LibraryComponents, item); + + if (CustomComponents[item]) LibraryComponents[item] = LibraryComponents[item] ? Object.assign({}, LibraryComponents[item], CustomComponents[item]) : CustomComponents[item]; + + if (LibraryComponents[item] && typeof LibraryComponents[item] == "object" && LibraryComponents[item].Z && LibraryComponents[item].Z["$$typeof"]) LibraryComponents[item] = LibraryComponents[item].Z; + else if (LibraryComponents[item] && typeof LibraryComponents[item] == "object" && LibraryComponents[item].A && LibraryComponents[item].A["$$typeof"]) LibraryComponents[item] = LibraryComponents[item].A; + + const NativeComponent = LibraryComponents[item] && Internal.NativeSubComponents[item]; + if (NativeComponent && typeof NativeComponent != "string") { + for (let key in NativeComponent) if (key != "displayName" && key != "name" && (typeof NativeComponent[key] != "function" || key.charAt(0) == key.charAt(0).toUpperCase())) { + if (key == "defaultProps") LibraryComponents[item][key] = Object.assign({}, LibraryComponents[item][key], NativeComponent[key]); + else if (!LibraryComponents[item][key]) LibraryComponents[item][key] = NativeComponent[key]; + } + if (LibraryComponents[item].ObjectProperties) for (let key of LibraryComponents[item].ObjectProperties) if (!LibraryComponents[item][key]) LibraryComponents[item][key] = {}; + } + return LibraryComponents[item] ? LibraryComponents[item] : "div"; + } + }); + + const RealFilteredMenuItems = Object.keys(RealMenuItems).filter(type => typeof RealMenuItems[type] == "function" && RealMenuItems[type].toString().replace(/[\n\t\r]/g, "").endsWith("{return null}")); + for (let type of RealFilteredMenuItems) { + let children = BDFDB.ObjectUtils.get(BDFDB.ReactUtils.hookCall(Internal.LibraryComponents.Menu, {hideScroller: true, children: BDFDB.ReactUtils.createElement(RealMenuItems[type], {})}, true), "props.children.props.children.props.children"); + let menuItem = (BDFDB.ArrayUtils.is(children) ? children : []).flat(10).filter(n => n)[0]; + if (menuItem) { + let menuItemsProps = BDFDB.ReactUtils.findValue(menuItem, "menuItemProps"); + if (menuItemsProps && menuItemsProps.id == "undefined-empty") MappedMenuItems.MenuGroup = type; + else if (menuItemsProps && menuItemsProps.role) { + switch (menuItemsProps.role) { + case "menuitemcheckbox": MappedMenuItems.MenuCheckboxItem = type; break; + case "menuitemradio": MappedMenuItems.MenuRadioItem = type; break; + case "menuitem": { + if (Object.keys(menuItem.props).includes("children")) MappedMenuItems.MenuControlItem = type; + else if (Object.keys(menuItem.props).includes("hasSubmenu")) MappedMenuItems.MenuItem = type; + break; + } + } + } + else { + let key = BDFDB.ReactUtils.findValue(menuItem, "key"); + if (typeof key == "string" && key.startsWith("separator")) MappedMenuItems.MenuSeparator = type; + } + } + } + LibraryComponents.MenuItems = new Proxy(RealFilteredMenuItems.reduce((a, v) => ({ ...a, [v]: v}), {}) , { + get: function (_, item) { + if (CustomComponents.MenuItems[item]) return CustomComponents.MenuItems[item]; + if (RealMenuItems[item]) return RealMenuItems[item]; + if (MappedMenuItems[item] && RealMenuItems[MappedMenuItems[item]]) return RealMenuItems[MappedMenuItems[item]]; + return null; + } + }); + + BDFDB.LibraryComponents = Internal.LibraryComponents; + + const keyDownTimeouts = {}; + let unfocusedWindow = false; + BDFDB.ListenerUtils.add(BDFDB, document, "keydown.BDFDBPressedKeys", e => { + if (!pressedKeys.includes(e.which)) { + BDFDB.TimeUtils.clear(keyDownTimeouts[e.which]); + pressedKeys.push(e.which); + keyDownTimeouts[e.which] = BDFDB.TimeUtils.timeout(_ => { + BDFDB.ArrayUtils.remove(pressedKeys, e.which, true); + }, 60000); + } + }); + BDFDB.ListenerUtils.add(BDFDB, document, "keyup.BDFDBPressedKeys", e => { + BDFDB.TimeUtils.clear(keyDownTimeouts[e.which]); + BDFDB.ArrayUtils.remove(pressedKeys, e.which, true); + }); + BDFDB.ListenerUtils.add(BDFDB, window, "focus.BDFDBPressedKeysReset", e => { + if (unfocusedWindow) { + pressedKeys = []; + unfocusedWindow = false; + } + }); + BDFDB.ListenerUtils.add(BDFDB, window, "blur.BDFDBPressedKeysReset", e => { + if (!document.querySelector(":hover")) unfocusedWindow = true; + }); + BDFDB.ListenerUtils.add(BDFDB, document, "mousedown.BDFDBMousePosition", e => { + mousePosition = e; + }); + + Internal.modulePatches = { + before: [ + "EmojiPickerListRow", + "Menu", + "MessageHeader", + "SearchBar" + ], + after: [ + "DiscordTag", + "NameContainerAvatar", + "UserPanelHeader", + "UserProfileHeader" + ], + componentDidMount: [ + "Account", + "AnalyticsContext" + ], + componentDidUpdate: [ + "Account", + "AnalyticsContext" + ] + }; + + const BDFDB_Patrons = Object.assign({}, InternalData.BDFDB_Patrons), BDFDB_Patron_Tiers = Object.assign({}, InternalData.BDFDB_Patron_Tiers); + Internal._processAvatarMount = function (user, avatar, wrapper) { + if (!user) return; + if (Node.prototype.isPrototypeOf(avatar) && (avatar.className || "").indexOf(BDFDB.disCN.bdfdbbadgeavatar) == -1) { + let role = "", note = "", color, link, addBadge = Internal.settings.general.showSupportBadges; + if (BDFDB_Patrons[user.id] && BDFDB_Patrons[user.id].active) { + link = "https://www.patreon.com/MircoWittrien"; + role = BDFDB_Patrons[user.id].text || (BDFDB_Patron_Tiers[BDFDB_Patrons[user.id].tier] || {}).text; + note = BDFDB_Patrons[user.id].text && (BDFDB_Patron_Tiers[BDFDB_Patrons[user.id].tier] || {}).text; + color = BDFDB_Patrons[user.id].color; + avatar.className = BDFDB.DOMUtils.formatClassName(avatar.className, addBadge && BDFDB.disCN.bdfdbhasbadge, BDFDB.disCN.bdfdbbadgeavatar, BDFDB.disCN.bdfdbsupporter, BDFDB.disCN[`bdfdbsupporter${BDFDB_Patrons[user.id].tier}`]); + } + else if (user.id == InternalData.myId) { + addBadge = true; + role = `Theme ${BDFDB.LanguageUtils.LibraryStrings.developer}`; + avatar.className = BDFDB.DOMUtils.formatClassName(avatar.className, addBadge && BDFDB.disCN.bdfdbhasbadge, BDFDB.disCN.bdfdbbadgeavatar, BDFDB.disCN.bdfdbdev); + } + if (addBadge && role && !avatar.querySelector(BDFDB.dotCN.bdfdbbadge)) { + let badge = document.createElement("div"); + badge.className = BDFDB.disCN.bdfdbbadge; + badge.setAttribute("user-id", user.id); + if (link) badge.addEventListener("click", _ => BDFDB.DiscordUtils.openLink(link)); + badge.addEventListener("mouseenter", _ => BDFDB.TooltipUtils.create(badge, role, {position: "top", note: note, backgroundColor: color || ""})); + avatar.appendChild(badge); + } + } + }; + Internal._processAvatarRender = function (user, avatar, className = "") { + if (BDFDB.ReactUtils.isValidElement(avatar) && BDFDB.ObjectUtils.is(user) && (avatar.props.className || "").indexOf(BDFDB.disCN.bdfdbbadgeavatar) == -1) { + let role = "", note = "", color, link, addBadge = Internal.settings.general.showSupportBadges; + if (BDFDB_Patrons[user.id] && BDFDB_Patrons[user.id].active) { + link = "https://www.patreon.com/MircoWittrien"; + role = BDFDB_Patrons[user.id].text || (BDFDB_Patron_Tiers[BDFDB_Patrons[user.id].tier] || {}).text; + note = BDFDB_Patrons[user.id].text && (BDFDB_Patron_Tiers[BDFDB_Patrons[user.id].tier] || {}).text; + color = BDFDB_Patrons[user.id].color; + className = BDFDB.DOMUtils.formatClassName(avatar.props.className, className, addBadge && BDFDB.disCN.bdfdbhasbadge, BDFDB.disCN.bdfdbbadgeavatar, BDFDB.disCN.bdfdbsupporter, BDFDB.disCN[`bdfdbsupporter${BDFDB_Patrons[user.id].tier}`]); + } + else if (user.id == InternalData.myId) { + addBadge = true; + role = `Theme ${BDFDB.LanguageUtils.LibraryStrings.developer}`; + className = BDFDB.DOMUtils.formatClassName(avatar.props.className, className, BDFDB.disCN.bdfdbhasbadge, BDFDB.disCN.bdfdbbadgeavatar, BDFDB.disCN.bdfdbdev); + } + if (role) { + if (avatar.type == "img") avatar = BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Avatars.Avatar, Object.assign({}, avatar.props, { + size: Internal.LibraryComponents.AvatarConstants.AvatarSizes.SIZE_40 + })); + delete avatar.props.className; + let newProps = { + className: className, + children: [avatar] + }; + avatar = BDFDB.ReactUtils.createElement("div", newProps); + if (addBadge) avatar.props.children.push(BDFDB.ReactUtils.createElement(Internal.LibraryComponents.TooltipContainer, { + text: role, + note: note, + tooltipConfig: {backgroundColor: color || ""}, + onClick: link ? (_ => BDFDB.DiscordUtils.openLink(link)) : (_ => {}), + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.bdfdbbadge, + "user-id": user.id + }) + })); + return avatar; + } + } + }; + + Internal.processAccount = function (e) { + Internal._processAvatarMount(e.instance.props.currentUser, e.node.querySelector(BDFDB.dotCN.avatarwrapper), e.node); + }; + Internal.processAnalyticsContext = function (e) { + if (e.instance.props.section != Internal.DiscordConstants.AnalyticsSections.PROFILE_MODAL && e.instance.props.section != Internal.DiscordConstants.AnalyticsSections.PROFILE_POPOUT) return; + const user = BDFDB.ReactUtils.findValue(e.instance, "user"); + if (!user) return; + const avatar = e.instance.props.section != Internal.DiscordConstants.AnalyticsSections.PROFILE_POPOUT && e.node.querySelector(BDFDB.dotCN.avatarwrapper); + const wrapper = e.node.querySelector(BDFDB.dotCNC.userpopoutouter + BDFDB.dotCN.userprofilemodal) || e.node; + if (avatar) Internal._processAvatarMount(user, avatar, wrapper); + }; + Internal.processDiscordTag = function (e) { + if (e.instance && e.instance.props && e.returnvalue && e.instance.props.user) e.returnvalue.props.user = e.instance.props.user; + }; + Internal.processEmojiPickerListRow = function (e) { + if (e.instance.props.emojiDescriptors && Internal.LibraryComponents.EmojiPickerButton.current && Internal.LibraryComponents.EmojiPickerButton.current.props && Internal.LibraryComponents.EmojiPickerButton.current.props.allowManagedEmojisUsage) for (let i in e.instance.props.emojiDescriptors) e.instance.props.emojiDescriptors[i] = Object.assign({}, e.instance.props.emojiDescriptors[i], {isDisabled: false}); + }; + Internal.processNameContainerAvatar = function (e) { + if (e.returnvalue && Array.isArray(e.returnvalue.props.children) && e.returnvalue.props.children[0]) e.returnvalue.props.children[0] = Internal._processAvatarRender(e.instance.props.user, e.returnvalue.props.children[0]) || e.returnvalue.props.children[0]; + }; + Internal.processMenu = function (e) { + if (e.instance.props && (e.instance.props.children || BDFDB.ArrayUtils.is(e.instance.props.children) && e.instance.props.children.length)) { + let patchCancel = BDFDB.PatchUtils.patch(BDFDB, Internal.LibraryModules.ContextMenuUtils, "closeContextMenu", {instead: e => {}}); + BDFDB.TimeUtils.timeout(_ => patchCancel()); + } + if (e.instance.props && e.instance.props.navId == "message-actions" && !e.instance.props.BDFDBpatched) { + e.instance.props.BDFDBpatched = true; + let copyChild = BDFDB.ReactUtils.findChild(e.instance, {filter: n => n && n.props && n.props.id == "copy-link"}); + if (!copyChild) return; + let link = ""; + BDFDB.PatchUtils.patch(BDFDB, BDFDB.LibraryModules.ClipboardUtils, "copy", {instead: e => {link = e.methodArguments[0];}}, {once: true}); + copyChild.props.action(); + if (!link) return; + let guildId = link.split("/channels/")[1].split("/")[0]; + let channelId = link.split(`/channels/${guildId}/`)[1].split("/")[0]; + let messageId = link.split(`/channels/${guildId}/${channelId}/`)[1].split("/")[0]; + if (!guildId || !channelId || !messageId) return; + LibraryModules.MessageToolbarUtils.useMessageMenu({ + guild: Internal.LibraryStores.GuildStore.getGuild(guildId), + channel: Internal.LibraryStores.ChannelStore.getChannel(channelId), + message: Internal.LibraryStores.MessageStore.getMessage(channelId, messageId) + }, e.instance); + } + }; + Internal.processMessageHeader = function (e) { + if (e.instance.props.message && e.instance.props.message.author) { + if (e.instance.props.avatar && e.instance.props.avatar.props) { + let child = BDFDB.ReactUtils.findChild(e.instance.props.avatar, {filter: n => n && n.props && typeof n.props.children == "function"}); + let renderChildren = child.props.children; + if (renderChildren) child.props.children = BDFDB.TimeUtils.suppress((...args) => { + let renderedChildren = renderChildren(...args); + return Internal._processAvatarRender(e.instance.props.message.author, renderedChildren, BDFDB.disCN.messageavatar) || renderedChildren; + }, "Error in Avatar Render of MessageHeader!"); + } + } + }; + Internal.processSearchBar = function (e) { + if (typeof e.instance.props.query != "string") e.instance.props.query = ""; + }; + Internal.processUserPanelHeader = function (e) { + if (!e.instance.props.user) return; + let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, {filter: n => n && n.props && n.props.src && n.props.size}); + if (index > -1) children[index] = Internal._processAvatarRender(e.instance.props.user, children[index]) || children[index]; + }; + Internal.processUserProfileHeader = function (e) { + if (!e.instance.props.user) return; + let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, {filter: n => n && n.props && n.props.src && n.props.size}); + if (index > -1) children[index] = Internal._processAvatarRender(e.instance.props.user, children[index]) || children[index]; + }; + + MyReact.instanceKey = Object.keys(document.querySelector(BDFDB.dotCN.app) || {}).some(n => n.startsWith("__reactInternalInstance")) ? "_reactInternalFiber" : "_reactInternals"; + + BDFDB.PluginUtils.load(BDFDB); + Internal.settings = BDFDB.DataUtils.get(Internal); + changeLogs = BDFDB.DataUtils.load(BDFDB, "changeLogs"); + BDFDB.PluginUtils.checkChangeLog(BDFDB); + + BDFDB.PatchUtils.unpatch(BDFDB); + Internal.addModulePatches(BDFDB); + Internal.addContextPatches(BDFDB); + + const possibleRenderPaths = ["render", "type", "type.render"]; + const createElementPatches = { + before: e => { + if (!e.methodArguments[0] || typeof e.methodArguments[0] == "string") return; + let renderFunction = null; + if (typeof e.methodArguments[0] == "function") renderFunction = e.methodArguments[0]; + else for (const path of possibleRenderPaths) { + const possibleRenderFuncion = BDFDB.ObjectUtils.get(e.methodArguments[0], path); + if (typeof possibleRenderFuncion == "function") { + renderFunction = possibleRenderFuncion; + break; + } + } + if (!renderFunction || typeof renderFunction != "function") return; + if (PluginStores.modulePatches.before) for (const type in PluginStores.modulePatches.before) if (Internal.isCorrectModule(renderFunction, type, true)) { + let hasArgumentChildren = false, children = [...e.methodArguments].slice(2); + if (children.length && e.methodArguments[1].children === undefined) { + hasArgumentChildren = true; + e.methodArguments[1].children = children; + } + for (let plugin of PluginStores.modulePatches.before[type].flat(10)) Internal.initiatePatch(plugin, type, { + arguments: e.methodArguments, + instance: {props: e.methodArguments[1]}, + returnvalue: e.returnValue, + component: e.methodArguments[0], + name: type, + methodname: "render", + patchtypes: ["before"] + }); + if (hasArgumentChildren) { + [].splice.call(e.methodArguments, 2); + for (let child of e.methodArguments[1].children) [].push.call(e.methodArguments, child); + delete e.methodArguments[1].children; + } + break; + } + if (PluginStores.modulePatches.after) { + let patchFunction = "", parentModule = e.methodArguments[0]; + if (parentModule.prototype && typeof parentModule.prototype.render == "function") parentModule = parentModule.prototype, patchFunction = "render"; + else if (typeof parentModule.render == "function") patchFunction = "render"; + else if (typeof parentModule.type == "function") patchFunction = "type"; + else if (parentModule.type && typeof parentModule.type.render == "function") parentModule = parentModule.type, patchFunction = "render"; + if (patchFunction) for (const type in PluginStores.modulePatches.after) if (Internal.isCorrectModule(renderFunction, type, true)) { + for (let plugin of PluginStores.modulePatches.after[type].flat(10)) if (!BDFDB.PatchUtils.isPatched(plugin, parentModule, patchFunction)) { + BDFDB.PatchUtils.patch(plugin, parentModule, patchFunction, {after: e2 => { + Internal.initiatePatch(plugin, type, { + arguments: e2.methodArguments, + instance: e2.instance, + returnvalue: e2.returnValue, + component: e.methodArguments[0], + name: type, + methodname: patchFunction, + patchtypes: ["after"] + }); + }}, {name: type}); + } + break; + } + } + if (e.methodArguments[0].prototype) for (let patchType of ["componentDidMount", "componentDidUpdate", "componentWillUnmount"]) { + if (PluginStores.modulePatches[patchType]) for (const type in PluginStores.modulePatches[patchType]) if (Internal.isCorrectModule(renderFunction, type, true)) { + for (let plugin of PluginStores.modulePatches[patchType][type].flat(10)) if (!BDFDB.PatchUtils.isPatched(plugin, e.methodArguments[0].prototype, patchType)) { + BDFDB.PatchUtils.patch(plugin, e.methodArguments[0].prototype, patchType, {after: e2 => Internal.initiatePatch(plugin, type, { + arguments: e2.methodArguments, + instance: e2.instance, + returnvalue: e2.returnValue, + component: e.methodArguments[0], + name: type, + methodname: patchType, + patchtypes: ["after"] + })}, {name: type}); + } + break; + } + } + }, + after: e => { + if (!e.methodArguments[0] || typeof e.methodArguments[0] != "function" || (e.methodArguments[0].prototype && typeof e.methodArguments[0].prototype.render == "function") || !PluginStores.modulePatches.after) return; + else for (const type in PluginStores.modulePatches.after) if (Internal.isCorrectModule(e.methodArguments[0], type, true) && !Internal.isCorrectModuleButDontPatch(type)) { + for (let plugin of PluginStores.modulePatches.after[type].flat(10)) BDFDB.PatchUtils.patch(plugin, e.returnValue, "type", {after: e2 => Internal.initiatePatch(plugin, type, { + arguments: e2.methodArguments, + instance: e2.instance, + returnvalue: e2.returnValue, + component: e.methodArguments[0], + name: type, + methodname: "type", + patchtypes: ["after"] + })}, {name: type, noCache: true}); + break; + } + } + }; + BDFDB.PatchUtils.patch(BDFDB, LibraryModules.React, "createElement", createElementPatches); + if (Internal.LibraryModules.InternalReactUtils) for (let key in Internal.LibraryModules.InternalReactUtils) if (typeof Internal.LibraryModules.InternalReactUtils[key] == "function" && Internal.LibraryModules.InternalReactUtils[key].toString().indexOf("return{$$typeof:") > -1) BDFDB.PatchUtils.patch(BDFDB, Internal.LibraryModules.InternalReactUtils, key, createElementPatches, {ignoreErrors: true}); + + let languageChangeTimeout; + BDFDB.PatchUtils.patch(BDFDB, Internal.LibraryModules.AppearanceSettingsUtils, "updateLocale", {after: e => { + BDFDB.TimeUtils.clear(languageChangeTimeout); + languageChangeTimeout = BDFDB.TimeUtils.timeout(_ => { + for (let pluginName in PluginStores.loaded) if (PluginStores.loaded[pluginName].started) BDFDB.PluginUtils.translate(PluginStores.loaded[pluginName]); + }, 10000); + }}); + + Internal.onSettingsClosed = function () { + if (Internal.SettingsUpdated) { + delete Internal.SettingsUpdated; + Internal.forceUpdateAll(); + } + }; + + Internal.forceUpdateAll = function () { + BDFDB.MessageUtils.rerenderAll(); + BDFDB.PatchUtils.forceAllUpdates(BDFDB); + }; + + BDFDB.PatchUtils.patch(BDFDB, Internal.LibraryModules.EmojiStateUtils, "getEmojiUnavailableReason", {after: e => { + if (Internal.LibraryComponents.EmojiPickerButton.current && Internal.LibraryComponents.EmojiPickerButton.current.props && Internal.LibraryComponents.EmojiPickerButton.current.props.allowManagedEmojisUsage) return null; + }}); + + Internal.forceUpdateAll(); + + const pluginQueue = window.BDFDB_Global && BDFDB.ArrayUtils.is(window.BDFDB_Global.pluginQueue) ? window.BDFDB_Global.pluginQueue : []; + + if (BDFDB.UserUtils.me.id == InternalData.myId || BDFDB.UserUtils.me.id == "350635509275557888") { + BDFDB.DevUtils = {}; + BDFDB.DevUtils.generateClassId = Internal.generateClassId; + BDFDB.DevUtils.findByIndex = function (index) { + return BDFDB.DevUtils.req.c[index]; + }; + BDFDB.DevUtils.findPropAny = function (...strings) { + window.t = {"$filter":(prop => [...strings].flat(10).filter(n => typeof n == "string").every(string => prop.toLowerCase().indexOf(string.toLowerCase()) > -1))}; + for (let i in BDFDB.DevUtils.req.c) if (BDFDB.DevUtils.req.c.hasOwnProperty(i)) { + let m = BDFDB.DevUtils.req.c[i].exports; + if (m && typeof m == "object") for (let j in m) if (window.t.$filter(j)) window.t[j + "_" + i] = m; + if (m && typeof m == "object" && typeof m.default == "object") for (let j in m.default) if (window.t.$filter(j)) window.t[j + "_default_" + i] = m.default; + } + console.clear(); + console.log(window.t); + }; + BDFDB.DevUtils.findPropFunc = function (...strings) { + window.t = {"$filter":(prop => [...strings].flat(10).filter(n => typeof n == "string").every(string => prop.toLowerCase().indexOf(string.toLowerCase()) > -1))}; + for (let i in BDFDB.DevUtils.req.c) if (BDFDB.DevUtils.req.c.hasOwnProperty(i)) { + let m = BDFDB.DevUtils.req.c[i].exports; + if (m && typeof m == "object") for (let j in m) if (window.t.$filter(j) && typeof m[j] != "string") window.t[j + "_" + i] = m; + if (m && typeof m == "object" && typeof m.default == "object") for (let j in m.default) if (window.t.$filter(j) && typeof m.default[j] != "string") window.t[j + "_default_" + i] = m.default; + } + console.clear(); + console.log(window.t); + }; + BDFDB.DevUtils.findPropStringLib = function (...strings) { + window.t = {"$filter":(prop => [...strings].flat(10).filter(n => typeof n == "string").every(string => prop.toLowerCase().indexOf(string.toLowerCase()) > -1))}; + for (let i in BDFDB.DevUtils.req.c) if (BDFDB.DevUtils.req.c.hasOwnProperty(i)) { + let m = BDFDB.DevUtils.req.c[i].exports; + if (m && typeof m == "object") for (let j in m) if (window.t.$filter(j) && typeof m[j] == "string" && /^[A-z0-9]+\-[A-z0-9_-]{6}$/.test(m[j])) window.t[j + "_" + i] = m; + if (m && typeof m == "object" && typeof m.default == "object") for (let j in m.default) if (window.t.$filter(j) && typeof m.default[j] == "string" && /^[A-z0-9]+\-[A-z0-9_-]{6}$/.test(m.default[j])) window.t[j + "_default_" + i] = m.default; + } + console.clear(); + console.log(window.t); + }; + BDFDB.DevUtils.findNameAny = function (...strings) { + window.t = {"$filter":(m => [...strings].flat(10).filter(n => typeof n == "string").some(string => typeof m.displayName == "string" && m.displayName.toLowerCase().indexOf(string.toLowerCase()) > -1 || m.name == "string" && m.name.toLowerCase().indexOf(string.toLowerCase()) > -1))}; + for (let i in BDFDB.DevUtils.req.c) if (BDFDB.DevUtils.req.c.hasOwnProperty(i)) { + let m = BDFDB.DevUtils.req.c[i].exports; + if (m && (typeof m == "object" || typeof m == "function") && window.t.$filter(m)) window.t[(m.displayName || m.name) + "_" + i] = m; + if (m && (typeof m == "object" || typeof m == "function") && m.default && (typeof m.default == "object" || typeof m.default == "function") && window.t.$filter(m.default)) window.t[(m.default.displayName || m.default.name) + "_" + i] = m.default; + } + console.clear(); + console.log(window.t); + }; + BDFDB.DevUtils.findCodeAny = function (...strings) { + window.t = {"$filter":(m => Internal.checkModuleStrings(m, strings, {ignoreCase: true}))}; + for (let i in BDFDB.DevUtils.req.c) if (BDFDB.DevUtils.req.c.hasOwnProperty(i)) { + let m = BDFDB.DevUtils.req.c[i].exports; + if (m && typeof m == "function" && window.t.$filter(m)) window.t["module_" + i] = {string: m.toString(), func: m}; + if (m && m.__esModule) { + for (let j in m) if (m[j] && typeof m[j] == "function" && window.t.$filter(m[j])) window.t[j + "_module_" + i] = {string: m[j].toString(), func: m[j], module: m}; + if (m.default && (typeof m.default == "object" || typeof m.default == "function")) for (let j in m.default) if (m.default[j] && typeof m.default[j] == "function" && window.t.$filter(m.default[j])) window.t[j + "_module_" + i + "_default"] = {string: m.default[j].toString(), func: m.default[j], module: m}; + } + } + for (let i in BDFDB.DevUtils.req.m) if (typeof BDFDB.DevUtils.req.m[i] == "function" && window.t.$filter(BDFDB.DevUtils.req.m[i])) window.t["function_" + i] = {string: BDFDB.DevUtils.req.m[i].toString(), func: BDFDB.DevUtils.req.m[i]}; + console.clear(); + console.log(window.t); + }; + BDFDB.DevUtils.getAllModules = function () { + window.t = {}; + for (let i in BDFDB.DevUtils.req.c) if (BDFDB.DevUtils.req.c.hasOwnProperty(i)) { + let m = BDFDB.DevUtils.req.c[i].exports; + if (m && typeof m == "object") window.t[i] = m; + } + console.clear(); + console.log(window.t); + }; + BDFDB.DevUtils.getAllStringLibs = function () { + window.t = []; + for (let i in BDFDB.DevUtils.req.c) if (BDFDB.DevUtils.req.c.hasOwnProperty(i)) { + let m = BDFDB.DevUtils.req.c[i].exports; + if (m && typeof m == "object" && !BDFDB.ArrayUtils.is(m) && Object.keys(m).length) { + var string = true, stringlib = false; + for (let j in m) { + if (typeof m[j] != "string") string = false; + if (typeof m[j] == "string" && /^[A-z0-9]+\-[A-z0-9_-]{6}$/.test(m[j])) stringlib = true; + } + if (string && stringlib) window.t.push(m); + } + if (m && typeof m == "object" && m.default && typeof m.default == "object" && !BDFDB.ArrayUtils.is(m.default) && Object.keys(m.default).length) { + var string = true, stringlib = false; + for (let j in m.default) { + if (typeof m.default[j] != "string") string = false; + if (typeof m.default[j] == "string" && /^[A-z0-9]+\-[A-z0-9_-]{6}$/.test(m.default[j])) stringlib = true; + } + if (string && stringlib) window.t.push(m.default); + } + } + console.clear(); + console.log(window.t); + }; + BDFDB.DevUtils.listen = function (strings) { + strings = BDFDB.ArrayUtils.is(strings) ? strings : Array.from(arguments); + BDFDB.DevUtils.listenStop(); + BDFDB.DevUtils.listen.p = BDFDB.PatchUtils.patch("WebpackSearch", BDFDB.ModuleUtils.findByProperties(strings), strings[0], {after: e => { + console.log(e); + }}); + }; + BDFDB.DevUtils.listenStop = function () { + if (typeof BDFDB.DevUtils.listen.p == "function") BDFDB.DevUtils.listen.p(); + }; + BDFDB.DevUtils.generateLanguageStrings = function (strings, config = {}) { + const language = config.language || "en"; + const languages = BDFDB.ArrayUtils.removeCopies(BDFDB.ArrayUtils.is(config.languages) ? config.languages : ["en"].concat((BDFDB.ModuleUtils.findByProperties("getLanguages").getLanguages()).filter(n => n.enabled).map(n => { + if (BDFDB.LanguageUtils.languages[n.code]) return n.code; + else { + const code = n.code.split("-")[0]; + if (BDFDB.LanguageUtils.languages[code]) return code; + } + })).filter(n => n && !n.startsWith("en-") && !n.startsWith("$") && n != language)).sort(); + let translations = {}; + strings = BDFDB.ObjectUtils.sort(strings); + const stringKeys = Object.keys(strings); + translations[language] = BDFDB.ObjectUtils.toArray(strings); + let text = Object.keys(translations[language]).map(k => translations[language][k]).join("\n\n"); + + let fails = 0, next = lang => { + if (!lang) { + let formatTranslation = (l, s, i) => { + l = l == "en" ? "default" : l; + return config.cached && config.cached[l] && config.cached[l][stringKeys[i]] || (translations[language][i][0] == translations[language][i][0].toUpperCase() ? BDFDB.StringUtils.upperCaseFirstChar(s) : s); + }; + let format = config.asObject ? ((l, isNotFirst) => { + return `${isNotFirst ? "," : ""}\n\t\t"${l == "en" ? "default" : l}": {${translations[l].map((s, i) => `\n\t\t\t"${stringKeys[i]}": "${formatTranslation(l, s, i)}"`).join(",")}\n\t\t}`; + }) : ((l, isNotFirst) => { + return `\n\t\t\t\t\t${l == "en" ? "default" : `case "${l}"`}:"\t\t"// ${BDFDB.LanguageUtils.languages[l].name}\n\t\t\t\t\t\treturn {${translations[l].map((s, i) => `\n\t\t\t\t\t\t\t${stringKeys[i]}:${"\t".repeat(10 - ((stringKeys[i].length + 2) / 4))}"${formatTranslation(l, s, i)}"`).join(",")}\n\t\t\t\t\t\t};`; + }); + let result = Object.keys(translations).filter(n => n != "en").sort().map((l, i) => format(l, i)).join(""); + if (translations.en) result += format("en", result ? 1 : 0); + BDFDB.NotificationUtils.toast("Translation copied to clipboard", { + type: "success" + }); + BDFDB.LibraryModules.WindowUtils.copy(result); + } + else { + const callback = translation => { + BDFDB.LogUtils.log(lang); + if (!translation) { + console.warn("No Translation"); + fails++; + if (fails > 10) console.error("Skipped Language"); + else languages.unshift(lang); + } + else { + fails = 0; + translations[lang] = translation.split("\n\n"); + } + next(languages.shift()); + }; + requestFunction(`https://translate.googleapis.com/translate_a/single?client=gtx&sl=${language}&tl=${lang}&dt=t&dj=1&source=input&q=${encodeURIComponent(text)}`, (error, response, result) => { + if (!error && result && response.statusCode == 200) { + try {callback(JSON.parse(result).sentences.map(n => n && n.trans).filter(n => n).join(""));} + catch (err) {callback("");} + } + else { + if (response.statusCode == 429) { + BDFDB.NotificationUtils.toast("Too many Requests", { + type: "danger" + }); + } + else { + BDFDB.NotificationUtils.toast("Failed to translate Text", { + type: "danger" + }); + callback(""); + } + } + }); + } + }; + if (stringKeys.length) next(languages.shift()); + }; + BDFDB.DevUtils.req = Internal.getWebModuleReq(); + } + + if (libraryCSS) BDFDB.DOMUtils.appendLocalStyle("BDFDB", libraryCSS.replace(/[\n\t\r]/g, "").replace(/\[REPLACE_CLASS_([A-z0-9_]+?)\]/g, (a, b) => BDFDB.dotCN[b])); + + BDFDB.LogUtils.log("Finished loading Library"); + + window.BDFDB_Global = Object.assign({ + started: true, + loaded: true, + PluginUtils: { + buildPlugin: BDFDB.PluginUtils.buildPlugin, + cleanUp: BDFDB.PluginUtils.cleanUp + } + }); + + while (PluginStores.delayed.loads.length) PluginStores.delayed.loads.shift().load(); + while (PluginStores.delayed.starts.length) PluginStores.delayed.starts.shift().start(); + while (pluginQueue.length) { + let pluginName = pluginQueue.shift(); + if (pluginName) BDFDB.TimeUtils.timeout(_ => BDFDB.BDUtils.reloadPlugin(pluginName)); + } + }; + requestLibraryHashes(true); + + this.loaded = true; + } + start () { + if (!this.loaded) this.load(); + } + stop () { + + } + + getSettingsPanel (collapseStates = {}) { + let settingsPanel; + let getString = (type, key, property) => { + return BDFDB.LanguageUtils.LibraryStringsCheck[`settings_${key}_${property}`] ? BDFDB.LanguageUtils.LibraryStringsFormat(`settings_${key}_${property}`, BDFDB.BDUtils.getSettingsProperty("name", BDFDB.BDUtils.settingsIds[key]) || BDFDB.StringUtils.upperCaseFirstChar(key.replace(/([A-Z])/g, " $1"))) : Internal.defaults[type][key][property]; + }; + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(BDFDB, { + collapseStates: collapseStates, + children: _ => { + let settingsItems = []; + + for (let key in Internal.settings.choices) settingsItems.push(BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SettingsSaveItem, { + type: "Select", + plugin: Internal, + keys: ["choices", key], + label: getString("choices", key, "description"), + note: getString("choices", key, "note"), + basis: "50%", + value: Internal.settings.choices[key], + options: Object.keys(Internal.DiscordConstants[Internal.defaults.choices[key].items] || {}).map(p => ({ + value: p, + label: BDFDB.LanguageUtils.LibraryStrings[p] || p + })), + searchable: true + })); + for (let key in Internal.settings.general) { + let nativeSetting = BDFDB.BDUtils.settingsIds[key] && BDFDB.BDUtils.getSettings(BDFDB.BDUtils.settingsIds[key]); + let disabled = typeof Internal.defaults.general[key].isDisabled == "function" && Internal.defaults.general[key].isDisabled({ + value: Internal.settings.general[key], + nativeValue: nativeSetting + }); + let hidden = typeof Internal.defaults.general[key].isHidden == "function" && Internal.defaults.general[key].isHidden({ + value: Internal.settings.general[key], + nativeValue: nativeSetting + }); + if (!hidden) settingsItems.push(BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SettingsSaveItem, { + type: "Switch", + plugin: Internal, + disabled: disabled, + keys: ["general", key], + label: getString("general", key, "description"), + note: (typeof Internal.defaults.general[key].hasNote == "function" ? Internal.defaults.general[key].hasNote({ + value: Internal.settings.general[key], + nativeValue: nativeSetting, + disabled: disabled + }) : Internal.defaults.general[key].hasNote) && getString("general", key, "note"), + value: (typeof Internal.defaults.general[key].getValue == "function" ? Internal.defaults.general[key].getValue({ + value: Internal.settings.general[key], + nativeValue: nativeSetting, + disabled: disabled + }) : true) && (Internal.settings.general[key] || nativeSetting), + onChange: typeof Internal.defaults.general[key].onChange == "function" ? Internal.defaults.general[key].onChange : (_ => {}) + })); + } + settingsItems.push(BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SettingsItem, { + type: "Button", + label: BDFDB.LanguageUtils.LibraryStrings.update_check_info, + dividerTop: true, + basis: "20%", + children: BDFDB.LanguageUtils.LibraryStrings.check_for_updates, + labelChildren: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.Clickable, { + children: BDFDB.ReactUtils.createElement(Internal.LibraryComponents.SvgIcon, { + name: Internal.LibraryComponents.SvgIcon.Names.QUESTIONMARK, + width: 20, + height: 20, + onClick: _ => BDFDB.ModalUtils.open(Internal, { + header: "Plugins", + subHeader: "", + contentClassName: BDFDB.disCN.marginbottom20, + text: BDFDB.ObjectUtils.toArray(Object.assign({}, window.PluginUpdates && window.PluginUpdates.plugins, PluginStores.updateData.plugins)).map(p => p.name).filter(n => n).sort().join(", ") + }) + }) + }), + onClick: _ => { + let toast = BDFDB.NotificationUtils.toast(`${BDFDB.LanguageUtils.LanguageStrings.CHECKING_FOR_UPDATES} - ${BDFDB.LanguageUtils.LibraryStrings.please_wait}`, { + type: "info", + timeout: 0, + ellipsis: true + }); + BDFDB.PluginUtils.checkAllUpdates().then(outdated => { + toast.close(); + if (outdated > 0) BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("update_check_complete_outdated", outdated), { + type: "danger" + }); + else BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStrings.update_check_complete, { + type: "success" + }); + }); + } + })); + + return settingsItems; + } + }); + } + } +})(); \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/0BDFDB.raw.css b/dotfiles/.config/BetterDiscord/plugins/0BDFDB.raw.css new file mode 100755 index 0000000..edada1e --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/0BDFDB.raw.css @@ -0,0 +1,1848 @@ +@import url(https://mwittrien.github.io/BetterDiscordAddons/Themes/_res/SupporterBadge.css); + +:root { + --bdfdb-blurple: var(--brand-experiment, hsl(235, calc(var(--saturation-factor, 1) * 85.6%), 64.7%)); +} + +#bd-notices { + grid-area: notice; +} + +#app-mount [REPLACE_CLASS_h5] { + text-transform: unset; +} + +img:not([src]), img[src=""], img[src="null"] { + opacity: 0; +} + +[REPLACE_CLASS_avatarpointerevents][fill="#80848e"][mask="url(#svg-mask-status-online)"] { + mask: url(#svg-mask-status-offline); +} + +[REPLACE_CLASS_appmount] > [REPLACE_CLASS_itemlayercontainer] { + position: fixed; + z-index: 1001; +} + +[REPLACE_CLASS_userpopoutmenuitemicon] { + align-self: center; +} + +[REPLACE_CLASS_itemlayercontainerzindexdisabled] { + position: absolute !important; + z-index: unset !important; +} +[REPLACE_CLASS_itemlayercontainerzindexdisabled] > *:not([REPLACE_CLASS_itemlayercontainerclicktrap]) { + z-index: 1002; +} +[REPLACE_CLASS_itemlayer] ~ [REPLACE_CLASS_itemlayer] [REPLACE_CLASS_menu] { + z-index: 1001; +} +[REPLACE_CLASS_menu] [REPLACE_CLASS_itemlayer] { + z-index: 1002; +} + +[REPLACE_CLASS_loadingiconwrapper] { + position: absolute; + bottom: 0; + right: 0; + z-index: 1000; + animation: loadingwrapper-fade 3s infinite ease; +} +[REPLACE_CLASS_loadingiconwrapper] [REPLACE_CLASS_loadingicon] { + margin: 0 5px; +} +@keyframes loadingwrapper-fade { + from {opacity: 0.1;} + 50% {opacity: 0.9;} + to {opacity: 0.1;} +} + +[REPLACE_CLASS_settingspanellistwrapper] { + margin-bottom: 8px; +} +[REPLACE_CLASS_settingspanellistwrapper][REPLACE_CLASS_settingspanellistwrappermini] { + margin-bottom: 4px; +} +[REPLACE_CLASS_settingspanellist] { + padding-left: 15px; +} +[REPLACE_CLASS_settingspanellistwrapper][REPLACE_CLASS_settingspanellistwrappermini] [REPLACE_CLASS_settingspanellist] { + padding-left: 10px; +} + +[REPLACE_CLASS_settingsrowcontainer][REPLACE_CLASS_marginreset] { + margin-bottom: 0; +} +[REPLACE_CLASS_settingsrowcontainer][REPLACE_CLASS_marginbottom4] { + margin-bottom: 4px; +} +[REPLACE_CLASS_settingsrowcontainer][REPLACE_CLASS_marginbottom8] { + margin-bottom: 8px; +} +[REPLACE_CLASS_settingsrowcontainer][REPLACE_CLASS_marginbottom20] { + margin-bottom: 20px; +} +[REPLACE_CLASS_settingsrowcontainer][REPLACE_CLASS_marginbottom40] { + margin-bottom: 40px; +} +[REPLACE_CLASS_settingsrowcontainer] [REPLACE_CLASS_settingsrow] { + display: flex; + flex-grow: 1; + align-items: center; +} +[REPLACE_CLASS_settingsrowcontainer] [REPLACE_CLASS_settingsrowlabel] { + flex-grow: 1; +} +[REPLACE_CLASS_settingsrowcontainer] [REPLACE_CLASS_settingsrowcontrol] { + margin-left: 8px; +} +[REPLACE_CLASS_settingsrowcontainer] [REPLACE_CLASS_settingsrowtitlemini] { + line-height: 18px; + font-size: 12px; + font-weight: 400; +} +[REPLACE_CLASS_settingspanel] [REPLACE_CLASS_settingsrowtitle] { + color: var(--text-default); +} +[REPLACE_CLASS_h5][REPLACE_CLASS_marginreset] { + flex: 1 1 auto; +} +[REPLACE_CLASS_flexhorizontal] > [REPLACE_CLASS_settingsrow] + [REPLACE_CLASS_settingsrow] { + margin-left: 6px; +} + +[REPLACE_CLASS_settingspanel] [REPLACE_CLASS_slider] { + min-width: 170px; +} +[REPLACE_CLASS_settingspanel] [REPLACE_CLASS_slidermark] { + line-height: 10px; +} + +[REPLACE_CLASS_switch][REPLACE_CLASS_switchmini] { + width: 26px; + height: 16px; +} +[REPLACE_CLASS_switch][REPLACE_CLASS_switchmini] [REPLACE_CLASS_switchslider] { + width: 16px; + height: 14px; + margin: 1px; +} + +[REPLACE_CLASS_accountinfobutton][REPLACE_CLASS_accountinfobuttondisabled] { + opacity: .4; +} + +[REPLACE_CLASS_marginleft4] { + margin-left: 4px; +} +[REPLACE_CLASS_marginleft8] { + margin-left: 8px; +} + +[REPLACE_CLASS_collapsecontainer] { + margin-bottom: 20px; +} +[REPLACE_CLASS_collapsecontainermini] { + margin-bottom: 8px; +} +[REPLACE_CLASS_collapsecontainerheader] { + margin-bottom: 4px; +} +[REPLACE_CLASS_collapsecontainercollapsed] [REPLACE_CLASS_collapsecontainertitle] { + margin-bottom: 0; +} +[REPLACE_CLASS_collapsecontainertitle] { + flex: 1 1 auto; + display: flex; + justify-content: space-between; + align-items: center; + color: var(--text-subtle); + text-transform: uppercase; + cursor: pointer; + order: 1; +} +[REPLACE_CLASS_collapsecontainertitle]:hover { + color: var(--text-strong); +} +[REPLACE_CLASS_collapsecontainertitle]::before { + content: ""; + flex: 1 1 auto; + background-color: currentColor; + height: 2px; + margin: 0 10px 0 15px; + opacity: 0.2; + order: 2; +} +[REPLACE_CLASS_collapsecontainertitle]::after { + content: ""; + -webkit-mask: url('data:image/svg+xml; utf8, ') center/cover no-repeat; + background-color: currentColor; + width: 20px; + height: 20px; + order: 3; + transition: transform .3s ease; + transform: rotate(0); +} +[REPLACE_CLASS_collapsecontainercollapsed] [REPLACE_CLASS_collapsecontainertitle]::after { + transform: rotate(90deg) +} +[REPLACE_CLASS_collapsecontainerinner] { + padding-left: 15px; +} + +[REPLACE_CLASS_settingsguild] { + flex: 0 0 auto; + border-radius: 50%; + border: 3px solid #43b581; + box-sizing: border-box; + cursor: pointer; + margin: 3px; + overflow: hidden; +} +[REPLACE_CLASS_settingsguilddisabled] { + border-color: #747f8d; + filter: grayscale(100%) brightness(50%); +} + +[REPLACE_CLASS_guildslabel] { + color: var(--text-muted); + text-align: center; + text-transform: uppercase; + font-size: 9px; + font-weight: 500; + line-height: 1.3; + width: 70px; + word-wrap: normal; + white-space: nowrap; +} +[REPLACE_CLASS_guildslabel]:hover { + color: var(--text-subtle); +} +[REPLACE_CLASS_guildslabel]:active { + color: var(--text-strong); +} + +[REPLACE_CLASS_searchbarwrapper] { + padding: 10px; +} +[REPLACE_CLASS_popoutwrapper] [REPLACE_CLASS_searchbarwrapper] { + padding: 0 0 5px 0; + border-bottom: 1px solid var(--background-modifier-accent); +} + +[REPLACE_CLASS_channelheaderdivider] { + background-color: var(--border-subtle); + border-radius: 1px; + width: 1px; + height: 24px; + margin: 0 6px; +} + +[REPLACE_CLASS_pagination] { + display: flex; + justify-content: center; + align-items: center; + width: 100%; +} +[REPLACE_CLASS_paginationtop] { + margin-bottom: 10px; +} +[REPLACE_CLASS_paginationbottom] { + margin-top: 10px; +} +[REPLACE_CLASS_pagination] [REPLACE_CLASS_paginationcontainer] { + width: unset; + margin: 0; +} +[REPLACE_CLASS_paginationmini][REPLACE_CLASS_paginationtop] { + margin-bottom: 5px; +} +[REPLACE_CLASS_paginationmini][REPLACE_CLASS_paginationbottom] { + margin-top: 5px; +} +[REPLACE_CLASS_paginationmini] [REPLACE_CLASS_paginationbutton], +[REPLACE_CLASS_paginationmini] [REPLACE_CLASS_paginationgap] { + font-size: 14px; + margin: 4px 2px; +} +[REPLACE_CLASS_paginationmini] [REPLACE_CLASS_paginationgap] { + width: 20px; +} +[REPLACE_CLASS_paginationmini] [REPLACE_CLASS_inputwrapper] { + min-width: 54px; +} + +[REPLACE_CLASS_paginationlist] { + height: 100%; +} +[REPLACE_CLASS_paginationlistalphabet] { + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + line-height: 20px; + margin-bottom: 10px; + color: var(--text-subtle); +} +[REPLACE_CLASS_paginationlistalphabetchar] { + min-width: 12px; + text-align: center; + margin: 0 4px; +} +[REPLACE_CLASS_paginationlistalphabetchar]:not([REPLACE_CLASS_paginationlistalphabetchardisabled]):hover { + color: var(--text-strong); +} +[REPLACE_CLASS_paginationlistalphabetchar][REPLACE_CLASS_paginationlistalphabetchardisabled] { + color: var(--text-muted); +} +[REPLACE_CLASS_paginationlistmini] [REPLACE_CLASS_paginationlistalphabet] { + font-size: 14px; + line-height: 18px; + margin-bottom: 5px; +} +[REPLACE_CLASS_paginationlistmini] [REPLACE_CLASS_paginationlistalphabetchar] { + min-width: 10px; + margin: 0 3px; +} + +[REPLACE_CLASS_overflowellipsis] { + overflow: hidden; + text-overflow: ellipsis; +} + + +[REPLACE_CLASS_userheadernickname] [REPLACE_CLASS_userheaderbottag], +[REPLACE_CLASS_userheadernicknamewithstyle] [REPLACE_CLASS_userheaderbottag] { + display: inline-flex; + margin-top: 4px !important; +} +[REPLACE_CLASS_userprofilemodal] [REPLACE_CLASS_userheaderbottag] { + margin-top: 6px !important; +} + +[REPLACE_CLASS_avatardisabled] { + filter: grayscale(100%) brightness(50%); +} + +[REPLACE_CLASS_messagebottag] { + top: .15rem; +} +[REPLACE_CLASS_messageusername] ~ [REPLACE_CLASS_messagebottagcompact] { + margin-right: 0; + margin-left: .25rem; +} +[REPLACE_CLASS_messagerepliedmessage] [REPLACE_CLASS_messageusername] ~ [REPLACE_CLASS_messagebottagcompact] { + margin-right: .25rem; + margin-left: 0; +} + +[REPLACE_CLASS_messagetoolbarbutton]:has([REPLACE_CLASS_emojiold]), +[REPLACE_CLASS_messagetoolbarbutton]:has([REPLACE_CLASS_emojiold]) + span, +[REPLACE_CLASS_messagetoolbarbutton]:has([REPLACE_CLASS_emojiold]) + span + [REPLACE_CLASS_messagetoolbarseparator] { + order: -1; +} + +[REPLACE_CLASS_messageavatar][REPLACE_CLASS_bdfdbbadgeavatar] > :not([REPLACE_CLASS_bdfdbbadge]), +[REPLACE_CLASS_messageavatar][REPLACE_CLASS_bdfdbbadgeavatar] [REPLACE_CLASS_avatarwrapper] { + width: inherit !important; + height: inherit !important; +} +[REPLACE_CLASS_messageavatar][REPLACE_CLASS_bdfdbbadgeavatar] [REPLACE_CLASS_messageavatar] { + position: static !important; + margin: unset !important; +} + +[REPLACE_CLASS_favbuttoncontainer] { + display: flex; + position: relative; + cursor: pointer; + width: 24px; + height: 24px; +} + +[REPLACE_CLASS_menuiconcontainer]:has([REPLACE_CLASS_menuhint]) { + width: unset; + max-width: 42px; +} +[REPLACE_CLASS_menuhint] { + width: 42px; + max-width: 42px; +} + +[REPLACE_CLASS_cursordefault] { + cursor: default !important; +} +[REPLACE_CLASS_cursorpointer] { + cursor: pointer !important; +} + +[REPLACE_CLASS_slidergrabber]:active [REPLACE_CLASS_sliderbubble], +[REPLACE_CLASS_slidergrabber]:hover [REPLACE_CLASS_sliderbubble] { + visibility: visible; +} +[REPLACE_CLASS_sliderbubble] { + background-color: var(--background-nested-floating); + border-radius: 3px; + top: -32px; + height: 22px; + width: auto; + padding: 0 5px; + white-space: pre; + transform: translateX(-50%); + line-height: 22px; + text-align: center; + font-weight: 600; + font-size: 12px; + color: var(--text-strong); + visibility: hidden; +} +[REPLACE_CLASS_sliderbubble], +[REPLACE_CLASS_sliderbubble]::before { + position: absolute; + left: 50%; + pointer-events: none; +} +[REPLACE_CLASS_sliderbubble]::before { + border: 5px solid transparent; + border-top-color: var(--background-nested-floating); + content: " "; + width: 0; + height: 0; + margin-left: -5px; + top: 100%; +} + +[REPLACE_CLASS_quickselectwrapper] { + margin-left: 12px; +} + +[REPLACE_CLASS_selectwrapper] { + display: flex; + align-items: center; + flex: 1 1 auto; +} +[REPLACE_CLASS_selectwrapper] [REPLACE_CLASS_select] { + flex: 1 1 auto; +} +[REPLACE_CLASS_selectwrapper] [REPLACE_CLASS_selectouter], +[REPLACE_CLASS_selectwrapper] [REPLACE_CLASS_selectsearchinput] { + width: 100%; +} +[REPLACE_CLASS_selectoption] [REPLACE_CLASS_favbuttoncontainer] { + background: transparent; + width: 20px; + height: 20px; + padding: 0; + flex: 0 0 auto; +} +[REPLACE_CLASS_selectoption] [REPLACE_CLASS_giffavoriteicon] { + width: 20px; + height: 20px; +} + +[REPLACE_CLASS_hotkeywrapper] { + min-width: 200px; +} +[REPLACE_CLASS_hotkeywrapper] [REPLACE_CLASS_hotkeycontainer] { + flex: 1 1 auto; +} +[REPLACE_CLASS_hotkeyresetbutton] { + cursor: pointer; + margin-left: 5px; +} +[REPLACE_CLASS_hotkeyresetbutton] [REPLACE_CLASS_svgicon]:hover { + color: #f04747; +} + +[REPLACE_CLASS_hovercardwrapper] { + position: relative; + display: flex; + flex-direction: column; + align-items: center; +} +[REPLACE_CLASS_hovercardhorizontal] { + flex-direction: row; +} +[REPLACE_CLASS_hovercardhorizontal] > [REPLACE_CLASS_flexchild] + [REPLACE_CLASS_flexchild] { + margin-left: 8px; +} +[REPLACE_CLASS_hovercarddisabled] { + opacity: 0.7; + filter: grayscale(0.2); +} +[REPLACE_CLASS_settingspanel] [REPLACE_CLASS_hovercardwrapper] { + width: calc(100% - 32px); +} +[REPLACE_CLASS_hovercardwrapper][REPLACE_CLASS_hovercard] { + padding: 10px 0; +} +[REPLACE_CLASS_hovercardwrapper][REPLACE_CLASS_hovercard] > * { + z-index: 1; +} +[REPLACE_CLASS_hovercardwrapper], [REPLACE_CLASS_hovercardinner] { + min-height: 28px; +} +[REPLACE_CLASS_hovercardinner] { + width: 100%; + padding-right: 5px; + display: flex; + align-items: center; + z-index: 1; +} +[REPLACE_CLASS_hovercardwrapper] [REPLACE_CLASS_hovercardbutton] { + position: absolute; + top: -6px; + right: -6px; + opacity: 0; +} +[REPLACE_CLASS_hovercardwrapper][REPLACE_CLASS_hovercard] [REPLACE_CLASS_hovercardbutton] { + right: -25px; +} +[REPLACE_CLASS_hovercardwrapper]:hover [REPLACE_CLASS_hovercardbutton] { + opacity: 1; +} + +[REPLACE_CLASS_textareabuttonwrapper] { + display: flex; + justify-content: center; + align-items: center; +} + +[REPLACE_CLASS_guildsummarycontainer] { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} +[REPLACE_CLASS_guildsummarysvgicon] { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + color: var(--text-muted); + width: 20px; + height: 20px; + margin-right: 8px; +} +[REPLACE_CLASS_guildsummaryiconcontainer] { + width: 24px; + height: 24px; +} +[REPLACE_CLASS_guildsummaryiconcontainermasked] { + margin-right: -4px; + -webkit-mask: url('data:image/svg+xml; utf8, ') center/cover no-repeat; +} +[REPLACE_CLASS_guildsummaryclickableicon] { + cursor: pointer; +} +[REPLACE_CLASS_guildsummaryicon], +[REPLACE_CLASS_guildsummaryclickableicon], +[REPLACE_CLASS_guildsummaryemptyguild] { + width: 24px; + height: 24px; + border-radius: 50%; +} +[REPLACE_CLASS_guildsummaryemptyguild] { + background: var(--background-accent); +} +[REPLACE_CLASS_guildsummarymoreguilds] { + -webkit-box-sizing: border-box; + box-sizing: border-box; + background-color: var(--background-base-lowest); + font-size: 12px; + line-height: 24px; + font-weight: 500; + text-align: center; + color: var(--text-subtle); + height: 24px; + min-width: 24px; + border-radius: 12px; + padding: 0 8px; +} + +[REPLACE_CLASS_table] { + position: relative; + width: 100%; +} +[REPLACE_CLASS_table] [REPLACE_CLASS_tablerow]:first-child { + padding: 0px 12px 8px 0; + margin-bottom: 5px; + box-sizing: border-box; + background-color: var(--background-base-low); + border-bottom: 1px solid var(--background-modifier-accent); +} +[REPLACE_CLASS_table] [REPLACE_CLASS_tablestickyheader] { + position: sticky; + width: 100%; +} +[REPLACE_CLASS_modalsub] [REPLACE_CLASS_table] [REPLACE_CLASS_tablestickyheader]:first-child { + padding-left: 20px; +} +[REPLACE_CLASS_tableheadercell] { + color: var(--text-subtle); + font-size: 12px; + font-weight: 600; + text-transform: uppercase; +} +[REPLACE_CLASS_tableheadercell], +[REPLACE_CLASS_tablebodycell] { + border-left: 1px solid var(--background-modifier-accent); + box-sizing: border-box; + padding: 0 12px; +} +[REPLACE_CLASS_tableheadercell]:first-child, +[REPLACE_CLASS_tablebodycell]:first-child { + border-left: none; + padding-left: 0; +} +[REPLACE_CLASS_tableheadercellsorted] { + color: var(--text-subtle); +} +[REPLACE_CLASS_table] [REPLACE_CLASS_tablerow] { + position: relative; + display: flex; + margin-bottom: 5px; + align-items: center; + color: var(--text-subtle); +} +[REPLACE_CLASS_tablebodycell] { + font-size: 15px; +} + +[REPLACE_CLASS_settingstableheaders] { + display: flex; + align-items: center; + flex: 1 0 auto; + margin-right: 20px; + margin-left: 10px; +} +[REPLACE_CLASS_settingstableheaderoptions] { + display: flex; + align-items: center; + justify-content: space-between; + flex: 0 0 auto; +} +[REPLACE_CLASS_settingstableheader] { + margin-bottom: 8px; + color: var(--text-subtle) +} +[REPLACE_CLASS_settingstableheadername] { + margin-left: 8px; + flex: 1 1 auto +} +[REPLACE_CLASS_settingstableheaderoption] { + cursor: default; + text-align: center; + width: 75px; + flex: 0 0 auto +} +[REPLACE_CLASS_settingstablelist] [REPLACE_CLASS_settingstableheader] { + min-height: 10px; + text-transform: uppercase; +} +[REPLACE_CLASS_settingstablelist] [REPLACE_CLASS_settingstableheaderoption] { + width: unset; +} +[REPLACE_CLASS_settingstablelist] [REPLACE_CLASS_settingstableheaderoption][REPLACE_CLASS_settingstableheadervertical] { + width: 24px; +} +[REPLACE_CLASS_settingstableheadervertical] { + position: relative; +} +[REPLACE_CLASS_settingstableheadervertical] > span { + position: absolute; + bottom: 50%; + right: calc(50% - 4px); + margin-bottom: -5px; + writing-mode: vertical-rl; +} +[REPLACE_CLASS_settingstablecard] { + height: 60px; + padding: 0 10px; + margin-bottom: 10px; +} +[REPLACE_CLASS_settingstablecardlabel] { + display: flex; + align-items: center; + flex: 1 1 auto; + overflow: hidden; + color: var(--text-strong); +} +[REPLACE_CLASS_settingstablecardconfigs] { + display: flex; + align-items: center; + justify-content: space-between; + flex: 0 0 auto; +} +[REPLACE_CLASS_settingstablecard] [REPLACE_CLASS_settingstablecardlabel] { + padding-right: 10px; +} +[REPLACE_CLASS_settingstablecard] [REPLACE_CLASS_settingstablecardlabel], +[REPLACE_CLASS_settingstablecard] [REPLACE_CLASS_settingstablecardconfigs] { + margin: 0; +} +[REPLACE_CLASS_settingstablelist] [REPLACE_CLASS_checkboxcontainer]::before { + display: none; +} + +[REPLACE_CLASS_popoutwrapper] [REPLACE_CLASS_messagespopouttabbarheader] { + flex: 1 0 auto; + align-items: center; + height: unset; + min-height: 56px; +} +[REPLACE_CLASS_popoutwrapper] [REPLACE_CLASS_messagespopouttabbarheader] [REPLACE_CLASS_messagespopouttabbar] { + align-items: center; + flex: 1 1 auto; + min-height: 32px; +} +[REPLACE_CLASS_messagespopoutchannelseparator] { + display: flex; + align-items: center; + margin-bottom: 2px; +} +[REPLACE_CLASS_messagespopoutchannelseparator] [REPLACE_CLASS_messagespopoutchannelname], +[REPLACE_CLASS_messagespopoutchannelseparator] [REPLACE_CLASS_messagespopoutguildname] { + color: var(--text-subtle); +} +[REPLACE_CLASS_messagespopoutchannelseparator] [REPLACE_CLASS_messagespopoutchannelname]:hover { + color: var(--text-strong); +} +[REPLACE_CLASS_messagespopoutchannelseparator] [REPLACE_CLASS_messagespopoutchannelname]:not(:last-child) { + margin-right: 10px; +} +[REPLACE_CLASS_messagespopoutchannelseparator] [REPLACE_CLASS_messagespopoutchannelname]:not(:last-child)::after { + content: ""; + border-bottom: 1px solid var(--text-muted); + width: 100%; + margin-left: 10px; +} +[REPLACE_CLASS_messagespopoutchannelseparator] [REPLACE_CLASS_messagespopoutguildname] { + flex: 0 0 auto; + font-size: 12px; + width: unset; +} + +[REPLACE_CLASS_charcounter] { + color: var(--channels-default); +} + +[REPLACE_CLASS_inputmini] { + height: 26px; + line-height: 16px; + padding: 4px 7px; +} +[REPLACE_CLASS_inputmulti] { + display: flex; + justify-content: space-between; + align-items: center; +} +[REPLACE_CLASS_inputmultifirst] { + flex-grow: 1; + padding: 0 8px; +} +[REPLACE_CLASS_inputmultilast]::before { + content: ""; + position: absolute; + border: 1px solid var(--text-strong); + width: 1px; + height: 30px; + margin-top: 5px; + opacity: .1; +} +[REPLACE_CLASS_inputmultilast] input { + width: 250px; + padding-left: 16px; +} +[REPLACE_CLASS_inputmultifield] { + border: none !important; +} + +[REPLACE_CLASS_inputlistitems] { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + flex-wrap: wrap; +} +[REPLACE_CLASS_inputlistitem] { + display: flex; + justify-content: center; + align-items: center; +} +[REPLACE_CLASS_inputlistitem]:not(:last-child) { + margin-right: 4px; +} +[REPLACE_CLASS_inputlistdelete] { + cursor: pointer; + margin-left: 6px; +} + +[REPLACE_CLASS_dateinputwrapper] { + display: flex; + align-items: center; + margin-bottom: 8px; +} +[REPLACE_CLASS_dateinputinner] { + flex: 1 1 auto; + color: var(--text-strong); + max-width: calc(100% - 150px); + margin-left: 8px; +} +[REPLACE_CLASS_dateinputcontrols] { + display: flex; + align-items: center; +} +[REPLACE_CLASS_dateinputpreview] { + display: flex; + align-items: center; + color: var(--text-strong); + margin-top: 8px; + font-weight: normal; +} +[REPLACE_CLASS_dateinputpreviewprefix], +[REPLACE_CLASS_dateinputpreviewsuffix] { + font-weight: 600; +} +[REPLACE_CLASS_dateinputpreviewprefix]:not(:empty) { + margin-right: 6px; +} +[REPLACE_CLASS_dateinputpreviewsuffix]:not(:empty) { + margin-left: 6px; +} +[REPLACE_CLASS_dateinputfield] { + flex: 1 1 auto; +} +[REPLACE_CLASS_dateinputbutton] { + margin-left: 8px; +} +[REPLACE_CLASS_dateinputbuttonselected] [REPLACE_CLASS_svgicon], +[REPLACE_CLASS_dateinputbuttonselected] [REPLACE_CLASS_svgicon]:hover { + color: var(--icon-strong); +} +[REPLACE_CLASS_input][REPLACE_CLASS_inputerror] { + border-color: var(--status-danger) !important; +} +[REPLACE_CLASS_input][REPLACE_CLASS_inputsuccess] { + border-color: var(--status-positive) !important; +} +input[REPLACE_CLASS_input][REPLACE_CLASS_inputsuccess], +input[REPLACE_CLASS_input][REPLACE_CLASS_inputerror] { + border-style: solid; + border-width: 1px; +} +[REPLACE_CLASS_inputnumberwrapper] { + position: relative; +} +[REPLACE_CLASS_inputnumberbuttons]:hover + [REPLACE_CLASS_input]:not([REPLACE_CLASS_inputfocused]):not([REPLACE_CLASS_inputerror]):not([REPLACE_CLASS_inputsuccess]):not([REPLACE_CLASS_inputdisabled]):not(:focus) { + border-color: var(--deprecated-text-input-border-hover); +} +[REPLACE_CLASS_inputnumberwrapper] [REPLACE_CLASS_input] { + text-align: right; +} +[REPLACE_CLASS_inputnumberwrapperdefault] [REPLACE_CLASS_input] { + padding-right: 25px; +} +[REPLACE_CLASS_inputnumberwrappermini] [REPLACE_CLASS_input] { + padding-left: 6px; + padding-right: 17px; +} +[REPLACE_CLASS_inputnumberwrapper] [REPLACE_CLASS_input]::-webkit-inner-spin-button, +[REPLACE_CLASS_inputnumberwrapper] [REPLACE_CLASS_input]::-webkit-outer-spin-button{ + -webkit-appearance: none !important; +} +[REPLACE_CLASS_inputnumberbuttons] { + position: absolute; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-around; + height: 110%; + top: -2%; +} +[REPLACE_CLASS_inputnumberwrapperdefault] [REPLACE_CLASS_inputnumberbuttons] { + right: 8px; +} +[REPLACE_CLASS_inputnumberwrappermini] [REPLACE_CLASS_inputnumberbuttons] { + right: 4px; +} +[REPLACE_CLASS_inputnumberbutton] { + cursor: pointer; + border: transparent solid 5px; + border-top-width: 2.5px; + display: inline-block; +} +[REPLACE_CLASS_inputnumberbutton] { + border-bottom-color: var(--icon-subtle); +} +[REPLACE_CLASS_inputnumberbutton]:hover { + border-bottom-color: var(--icon-strong); +} +[REPLACE_CLASS_inputnumberbuttondown] { + transform: rotate(180deg); +} + +[REPLACE_CLASS_guildupperleftbadge] { + top: 0; +} +[REPLACE_CLASS_guildlowerleftbadge] { + bottom: 0; +} +[REPLACE_CLASS_guildlowerleftbadge], [REPLACE_CLASS_guildupperleftbadge] { + pointer-events: none; + position: absolute; + left: 0; +} + +[REPLACE_CLASS_guildswrapper] [style*="--folder-color"] [REPLACE_CLASS_guildfolderexpandedbackground] { + background: var(--folder-color) !important; + opacity: 0.2 !important; +} + + +[REPLACE_CLASS_tooltip] [REPLACE_CLASS_flowerstarchild] > svg { + width: 10px; + height: 10px; +} + +[REPLACE_CLASS_svgiconwrapper] { + display: flex; + justify-content: center; + align-items: center; +} +[REPLACE_CLASS_svgicon] { + color: var(--icon-subtle); +} +[REPLACE_CLASS_svgicon]:hover { + color: var(--icon-default); +} +[REPLACE_CLASS_svgicon]:active { + color: var(--icon-strong); +} + +[REPLACE_CLASS_listrowwrapper] [REPLACE_CLASS_listavatar] { + display: flex; + justify-content: center; + align-items: center; +} + +[REPLACE_CLASS_sidebarlist] { + display: flex; + flex-direction: row; + flex: 1 1 auto; +} +[REPLACE_CLASS_sidebar] { + padding: 8px; + flex: 0 1 auto; +} +[REPLACE_CLASS_sidebarcontent] { + padding: 8px 0; + flex: 1 1 auto; +} + +[REPLACE_CLASS_modalwrapper] { + color: var(--text-subtle); +} +[REPLACE_CLASS_modalwrapper] [REPLACE_CLASS_modalfooter] [REPLACE_CLASS_button] { + margin-left: 8px; +} +[REPLACE_CLASS_modalwrapper] [REPLACE_CLASS_modalclose] [REPLACE_CLASS_buttoncontents] { + display: flex; + justify-content: center; + align-items: center; +} +[REPLACE_CLASS_modalsubinner] { + padding-left: 16px; + padding-right: 8px; +} +[REPLACE_CLASS_modalbodyinner] { + overflow-x: hidden; +} +[REPLACE_CLASS_modaltextcontent] { + margin-bottom: 10px; +} +[REPLACE_CLASS_modalnoscroller] { + overflow: hidden; +} +[REPLACE_CLASS_modalcontent][REPLACE_CLASS_modalnoscroller] { + padding-bottom: 10px; +} +[REPLACE_CLASS_modaltabcontent] { + margin-top: 10px; +} +[REPLACE_CLASS_listscroller] [REPLACE_CLASS_modaltabcontent] { + margin-top: 0; +} +[REPLACE_CLASS_modalheaderhassibling] { + padding-bottom: 10px; +} +[REPLACE_CLASS_modalheadershade], +[REPLACE_CLASS_modalsidebar] { + background: rgba(0, 0, 0, 0.1); +} +[REPLACE_CLASS_themedark] [REPLACE_CLASS_modalheadershade], +[REPLACE_CLASS_themedark] [REPLACE_CLASS_modalsidebar] { + background: rgba(0, 0, 0, 0.2); +} +[REPLACE_CLASS_tabbarcontainer][REPLACE_CLASS_tabbarcontainerbottom] { + border-top: unset; + border-bottom: 1px solid hsla(0,0%,100%,.1); +} +[REPLACE_CLASS_tabbar] { + max-width: calc(100% - 60px); + gap: var(--spacing-32); + border-bottom: 1px solid var(--background-modifier-accent); +} +[REPLACE_CLASS_tabbaritem] { + display: flex; + flex-shrink: 1; + align-items: center; + gap: var(--spacing-8); + padding-bottom: var(--spacing-12); +} +[REPLACE_CLASS_modal] [REPLACE_CLASS_tabbarcontainer], +[REPLACE_CLASS_modalcontainer] [REPLACE_CLASS_tabbarcontainer] { + background: rgba(0, 0, 0, 0.1); + border: none; + border-radius: unset; + box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.05); + padding: 12px 16px 0; + flex: 1 1 auto; +} +[REPLACE_CLASS_modal] [REPLACE_CLASS_tabbar], +[REPLACE_CLASS_modalcontainer] [REPLACE_CLASS_tabbar] { + border: none; +} +[REPLACE_CLASS_modal] [REPLACE_CLASS_tabbaritem], +[REPLACE_CLASS_modalcontainer] [REPLACE_CLASS_tabbaritem] { + height: 29px; +} +[REPLACE_CLASS_themedark] [REPLACE_CLASS_modal] [REPLACE_CLASS_tabbarcontainer], +[REPLACE_CLASS_themedark] [REPLACE_CLASS_modalcontainer] [REPLACE_CLASS_tabbarcontainer] { + background: rgba(0, 0, 0, 0.2); + box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.1); +} +[REPLACE_CLASS_modalchangelogmodal] [REPLACE_CLASS_changelogsociallink] { + margin-right: 12px; +} +[REPLACE_CLASS_changelogfooter] { + align-items: center; +} + +[REPLACE_CLASS_emojiinputcontainer] { + position: relative; +} +[REPLACE_CLASS_emojiinputbuttoncontainer] { + display: flex; + inset-inline-start: 0; + position: absolute; + top: 0; + -webkit-margin-end: 4px; + align-items: center; + height: 100%; + margin-inline-end: 4px; + z-index: 1; +} +[REPLACE_CLASS_emojiinputbuttonemoji] { + width: var(--custom-status-modal-emoji-size, var(--custom-emoji-sprite-size, 22px)); + height: var(--custom-status-modal-emoji-size, var(--custom-emoji-sprite-size, 22px)); +} +[REPLACE_CLASS_emojiinput] { + padding-left: calc(var(--custom-status-modal-emoji-margin, 10px) + var(--custom-status-modal-emoji-size, var(--custom-emoji-sprite-size, 18px)) + var(--custom-status-modal-emoji-margin, 10px)); + padding-right: 36px +} +[REPLACE_CLASS_emojiinputclearbutton] { + position: absolute; + top: 0; + right: 10px; + display: flex; + align-items: center; + height: 100%; + transition: opacity.2s ease-in-out; + line-height: 0; + opacity: .6 +} +[REPLACE_CLASS_emojiinputclearbutton]:hover, +[REPLACE_CLASS_emojiinputclearbutton]:focus { + opacity: 1 +} +[REPLACE_CLASS_emojiinputclearicon] { + width: 16px; + height: 16px; + color: var(--icon-subtle) +} + +[REPLACE_CLASS_modalmini] { + font-size: 13px; + white-space: pre-wrap; + word-wrap: break-word; + width: 490px; + max-height: 800px; + min-height: 0 +} +@media (max-width: 490px) { + [REPLACE_CLASS_modalmini] { + width:100% + } +} +[REPLACE_CLASS_modalminicontent] { + font-size: 16px; + line-height: 20px +} +[REPLACE_CLASS_modalminicontent] [REPLACE_CLASS_emojiold] { + -o-object-fit: contain; + object-fit: contain; + width: 22px; + height: 22px +} +[REPLACE_CLASS_modalminicontent] h1 { + line-height: 20px; + font-size: 16px +} +[REPLACE_CLASS_modalminicontent] h1, +[REPLACE_CLASS_modalminicontent] h2, +[REPLACE_CLASS_modalminicontent] strong { + font-weight: 700 +} +[REPLACE_CLASS_modalminicontent] em, +[REPLACE_CLASS_modalminicontent] i { + font-style: italic +} +[REPLACE_CLASS_modalminicontent] p+p { + margin-top: 10px +} +[REPLACE_CLASS_modalminicontent] ol { + margin: 16px 0 16px 16px +} +[REPLACE_CLASS_modalminicontent] ol li { + list-style-type: decimal; + margin-bottom: 8px; + margin-left: 20px +} +[REPLACE_CLASS_modalminicontent] ul { + margin: 20px 0 8px 20px +} +[REPLACE_CLASS_modalminicontent] ul ul { + margin-top: 8px +} +[REPLACE_CLASS_modalminicontent] ul li { + position: relative; + list-style: none; + margin-bottom: 8px; + -webkit-user-select: text; + -moz-user-select: text; + user-select: text +} +[REPLACE_CLASS_modalminicontent] ul li:last-child { + margin-bottom: 0 +} +[REPLACE_CLASS_modalminicontent] ul li:before { + content: ""; + position: absolute; + top: 10px; + left: -15px; + width: 6px; + height: 6px; + margin-top: -4px; + margin-left: -3px; + border-radius: 50%; + opacity: .3 +} +[REPLACE_CLASS_modalminicontent] ul li li:before { + top: 12px; + height: 2px; + border-radius: 0 +} +[REPLACE_CLASS_modalminicontent] img { + width: 100% +} +[REPLACE_CLASS_modalminicontent] a { + color: hsl(200 calc(1*100%) 49.4%); + transition: .05s; + -webkit-text-decoration: none; + text-decoration: none +} +@supports (color: hsl(0 0% 0% / 0)) and (top: var(--f)) { + [REPLACE_CLASS_modalminicontent] a { + color: hsl(200 calc(var(--saturation-factor, 1)*100%) 49.4%) + } +} +[REPLACE_CLASS_modalminicontent] a:hover { + -webkit-text-decoration: underline; + text-decoration: underline +} +[REPLACE_CLASS_themedark] [REPLACE_CLASS_modalminicontent] ol, +[REPLACE_CLASS_themedark] [REPLACE_CLASS_modalminicontent] p, +[REPLACE_CLASS_themedark] [REPLACE_CLASS_modalminicontent] ul li { + color: hsl(210 calc(1*9.3%) 78.8%) +} +@supports (color: hsl(0 0% 0% / 0)) and (top: var(--f)) { + [REPLACE_CLASS_themedark] [REPLACE_CLASS_modalminicontent] ol, + [REPLACE_CLASS_themedark] [REPLACE_CLASS_modalminicontent] p, + [REPLACE_CLASS_themedark] [REPLACE_CLASS_modalminicontent] ul li { + color: hsl(210 calc(var(--saturation-factor, 1)*9.3%) 78.8%) + } +} +[REPLACE_CLASS_themedark] [REPLACE_CLASS_modalminicontent] ul li:before { + background-color: hsl(216 calc(1*9.8%) 90%) +} +@supports (color: hsl(0 0% 0% / 0)) and (top: var(--f)) { + [REPLACE_CLASS_themedark] [REPLACE_CLASS_modalminicontent] ul li:before { + background-color: hsl(216 calc(var(--saturation-factor, 1)*9.8%) 90%) + } +} +[REPLACE_CLASS_themelight] [REPLACE_CLASS_modalminicontent] ol, +[REPLACE_CLASS_themelight] [REPLACE_CLASS_modalminicontent] p, +[REPLACE_CLASS_themelight] [REPLACE_CLASS_modalminicontent] ul li { + color: hsl(223 calc(1*5.8%) 52.9%) +} +@supports (color: hsl(0 0% 0% / 0)) and (top: var(--f)) { + [REPLACE_CLASS_themelight] [REPLACE_CLASS_modalminicontent] ol, + [REPLACE_CLASS_themelight] [REPLACE_CLASS_modalminicontent] p, + [REPLACE_CLASS_themelight] [REPLACE_CLASS_modalminicontent] ul li { + color: hsl(223 calc(var(--saturation-factor, 1)*5.8%) 52.9%) + } +} +[REPLACE_CLASS_themelight] [REPLACE_CLASS_modalminicontent] ul li:before { + background-color: hsl(223 calc(1*5.8%) 52.9%) +} +@supports (color: hsl(0 0% 0% / 0)) and (top: var(--f)) { + [REPLACE_CLASS_themelight] [REPLACE_CLASS_modalminicontent] ul li:before { + background-color: hsl(223 calc(var(--saturation-factor, 1)*5.8%) 52.9%) + } +} + +[REPLACE_CLASS_colorpickerswatchescontainer] { + display: flex; + flex-wrap: wrap; + margin-top: -10px; + margin-right: -10px +} +[REPLACE_CLASS_colorpickerswatchcustomcontainer], [REPLACE_CLASS_colorpickerswatchdefaultcontainer] { + margin-top: 10px; + margin-right: 10px; + flex: 1; + min-width: 60px; + max-width: 70px +} +[REPLACE_CLASS_colorpicker] { + display: flex; + flex-direction: column; + width: 220px; + padding: 16px; + gap: 16px; + border: 1px solid var(--border-subtle); + background-color: var(--background-base-low); + border-radius: 4px; + box-shadow: var(--elevation-high) +} +[REPLACE_CLASS_colorpickerrow] { + margin-top: 10px; + display: flex; + flex-wrap: wrap; + height: 20px; + overflow: hidden +} +[REPLACE_CLASS_colorpickerswatch] { + background-color: transparent; + position: relative; + box-sizing: border-box; + width: 20px; + height: 20px; + border-radius: 3px; + margin-right: 10px; + display: flex; + justify-content: center; + align-items: center; + border: 1px solid transparent; + cursor: pointer; + padding: 0 +} +[REPLACE_CLASS_colorpickerswatch][REPLACE_CLASS_colorpickerswatchcustom],[REPLACE_CLASS_colorpickerswatch][REPLACE_CLASS_colorpickerswatchdefault] { + margin-right: 0; + width: 100%; + height: 50px; + border-radius: 4px +} +[REPLACE_CLASS_colorpickerswatch][REPLACE_CLASS_colorpickerswatchdisabled] { + opacity: .3; + cursor: not-allowed; + pointer-events: none +} +[REPLACE_CLASS_colorpickerswatch] [REPLACE_CLASS_colorpickerswatchdropper] { + position: absolute; + top: 4px; + right: 4px +} +[REPLACE_CLASS_colorpickersuggestedcolors] { + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 12px +} +[REPLACE_CLASS_colorpickersuggestedcolor] { + width: 32px; + height: 32px; + border-radius: 4px; + cursor: pointer; + border: 1px solid var(--primary-400) +} +[REPLACE_CLASS_themedark] [REPLACE_CLASS_colorpickerswatch][REPLACE_CLASS_colorpickerswatchnocolor] { + border-color: var(--opacity-white-8) +} +[REPLACE_CLASS_themelight] [REPLACE_CLASS_colorpickerswatch][REPLACE_CLASS_colorpickerswatchnocolor] { + border-color: hsl(var(--primary-500-hsl)/.1) +} +[REPLACE_CLASS_themeenableforcedcolors] [REPLACE_CLASS_colorpickerswatch] { + border: 1px solid ButtonText; + forced-color-adjust: none +} +[REPLACE_CLASS_themeenableforcedcolors] [REPLACE_CLASS_colorpickerswatch][REPLACE_CLASS_colorpickerswatchdisabled] { + border-color: GrayText +} +[REPLACE_CLASS_colorpickerinner] { + width: auto +} +[REPLACE_CLASS_colorpickersaturation] { + position: relative; + width: auto; + height: 150px; + cursor: crosshair +} +[REPLACE_CLASS_colorpickersaturation] > div, +[REPLACE_CLASS_colorpickersaturation] > div > div { + border-radius: 3px +} +[REPLACE_CLASS_colorpickersaturation] > div > div > div { + cursor: crosshair!important +} +[REPLACE_CLASS_colorpickerhue] { + height: 8px; + margin: 8px 0; + position: relative; + cursor: crosshair +} +[REPLACE_CLASS_colorpickerhue] > div > div { + border-radius: 3px +} +[REPLACE_CLASS_colorpickerhue] > div > div > div > div { + height: 16px !important; + width: 8px !important; + margin-top: -3px !important; + border-radius: 3px !important +} +[REPLACE_CLASS_colorpickerhue] > div > div > div > div { + cursor: ew-resize +} + +[REPLACE_CLASS_popoutwrapper] { + position: relative; +} +[REPLACE_CLASS_popoutarrow]::before { + content: ""; + display: block; + position: absolute; + border: 8px solid var(--background-base-lower); + border-right-color: transparent; + border-left-color: transparent; + left: calc(50% - 8px); + z-index: 1; +} +[REPLACE_CLASS_popoutarrowtop] { + margin-bottom: 8px; +} +[REPLACE_CLASS_popoutarrowbottom] { + margin-top: 8px; +} +[REPLACE_CLASS_popoutarrowtop]::before { + border-bottom-color: transparent; + bottom: -16px; +} +[REPLACE_CLASS_popoutarrowbottom]::before { + border-top-color: transparent; + top: -16px; +} +[REPLACE_CLASS_popoutthemedpopout] { + background-color: var(--background-base-lower); + border-radius: 5px; + -webkit-box-shadow: var(--elevation-stroke),var(--elevation-high); + box-shadow: var(--elevation-stroke),var(--elevation-high); + box-sizing: border-box; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +[REPLACE_CLASS_flexhorizontal] > [REPLACE_CLASS_flexchild] + [REPLACE_CLASS_flexchild], +[REPLACE_CLASS_flexhorizontal] > [REPLACE_CLASS_flexchild] + [REPLACE_CLASS_button], +[REPLACE_CLASS_flexhorizontal] > [REPLACE_CLASS_input] + [REPLACE_CLASS_button] { + margin-left: 10px; +} + +[REPLACE_CLASS__repochangelogbutton] { + position: fixed; + top: 14px; + right: 14px; +} + +[REPLACE_CLASS_noticewrapper] { + display: flex; + justify-content: center; + align-items: center; + height: 36px !important; + border-radius: 0 !important; + transition: height 0.5s ease !important; + --custom-notice-text: var(--white-500); +} +[REPLACE_CLASS_noticewrapper]:not([class*=" color"]) { + --custom-notice-background: var(--brand-500); + --custom-notice-text: var(--white-500); + --custom-notice-button-hover: var(--brand-500); +} +[REPLACE_CLASS_noticeclosing] { + height: 0 !important; + overflow: hidden !important; +} +[REPLACE_CLASS_noticesuccess] { + --custom-notice-background: var(--status-positive-background); + --custom-notice-text: var(--status-positive-text); + --custom-notice-button-hover: var(--text-positive); +} +[REPLACE_CLASS_noticetext] { + display: inline-flex; + flex: 0 1 auto; + white-space: pre; + overflow: hidden; +} +[REPLACE_CLASS_noticewrapper] [REPLACE_CLASS_noticeplatformicon] { + display: inline-flex; + flex: 0 0 auto; + position: static; + margin: 0 10px 0 0; +} +[REPLACE_CLASS_noticewrapper] svg[REPLACE_CLASS_noticeplatformicon] { + max-height: 28px; + width: unset; +} +[REPLACE_CLASS_noticewrapper] [REPLACE_CLASS_noticebutton] { + display: inline-flex; + flex: 0 0 auto; + position: static; +} + +#pluginNotice .notice-message { + white-space: pre; +} +#pluginNotice #outdatedPlugins { + font-weight: 700; +} +#pluginNotice #outdatedPlugins span { + -webkit-app-region: no-drag; + color: #fff; + cursor: pointer; +} +#pluginNotice #outdatedPlugins span:hover { + text-decoration: underline; +} +[REPLACE_CLASS_noticeupdateentries] { + display: flex; + font-weight: 700; +} +[REPLACE_CLASS_noticeupdateentry] { + cursor: pointer; + -webkit-app-region: no-drag; +} +[REPLACE_CLASS_noticeupdateentry]:hover { + text-decoration: underline; +} +[REPLACE_CLASS_noticeupdateseparator] { + pointer-events: none; +} + +.platform-osx [REPLACE_CLASS_noticewrapper] ~ * [REPLACE_CLASS_guildswrapper] { + margin-top: 0; +} +.platform-osx [REPLACE_CLASS_noticewrapper] ~ * [REPLACE_CLASS_guildsscroller] { + padding-top: 4px; +} + +[REPLACE_CLASS_tooltiprow]:has([REPLACE_CLASS_usersummarycontainer]:empty) { + display: none; +} +[REPLACE_CLASS_tooltipnote] { + color: var(--text-muted); + font-size: 11px; + margin-top: 2px; +} +[REPLACE_CLASS_tooltip][REPLACE_CLASS_tooltipcustom] { + color: #fff; +} +[REPLACE_CLASS_tooltip]:has([REPLACE_CLASS_tooltipcontent]:empty) { + display: none !important; +} +[REPLACE_CLASS_tooltip][REPLACE_CLASS_tooltipcustom] [REPLACE_CLASS_tooltipnote] { + color: #bbb; +} +[REPLACE_CLASS_tooltipcustom] [REPLACE_CLASS_tooltipguildnametext], +[REPLACE_CLASS_tooltipcustom] [REPLACE_CLASS_tooltipmutetext] { + color: inherit; +} +[REPLACE_CLASS_tooltipcustom] [REPLACE_CLASS_tooltipmutetext] { + opacity: .7; +} +[REPLACE_CLASS_guildvoicelist] ~ [REPLACE_CLASS_tooltipmutetext] { + margin-top: 8px; +} +[REPLACE_CLASS_tooltiprowextra]:empty { + display: none; +} +[REPLACE_CLASS_tooltiprowextra]:not(:empty) [REPLACE_CLASS_tooltipmutetext] { + margin-top: 8px; +} + +[REPLACE_CLASS_colorpickerswatches] > div:not([class]), +[REPLACE_CLASS_colorpickerswatches] [REPLACE_CLASS_colorpickerswatch] { + flex: 1 1 auto; +} +[REPLACE_CLASS_colorpickerswatchsinglewrapper] { + position: relative; + z-index: 1; +} +[REPLACE_CLASS_colorpickerswatchsingle] { + height: 30px; + width: 30px; +} +[REPLACE_CLASS_colorpickerswatchesdisabled] { + cursor: no-drop; + filter: grayscale(70%) brightness(50%); +} +[REPLACE_CLASS_colorpickerswatch][REPLACE_CLASS_colorpickerswatchnocolor] { + border-color: var(--text-muted); +} +[REPLACE_CLASS_colorpickerswatch][style*="background: rgb(255, 255, 255)"] { + border-color: var(--text-strong) !important; +} +[REPLACE_CLASS_colorpickerswatch]:not([REPLACE_CLASS_colorpickerswatchnocolor]):not([REPLACE_CLASS_colorpickerswatchdefault]):not([REPLACE_CLASS_colorpickerswatchdisabled]) { + overflow: hidden; +} +[REPLACE_CLASS_colorpickerswatch][REPLACE_CLASS_colorpickerswatchcustom][style*="background"] { + border: none; +} +[REPLACE_CLASS_colorpickerswatch]:not([REPLACE_CLASS_colorpickerswatchdefault])::after { + border-radius: 3px; +} +[REPLACE_CLASS_colorpickerswatch][REPLACE_CLASS_colorpickerswatchcustom]:not([REPLACE_CLASS_colorpickerswatchdefault])::after { + border-radius: 5px; +} +[REPLACE_CLASS_colorpickerswatch]:not([REPLACE_CLASS_colorpickerswatchnocolor]):not([REPLACE_CLASS_colorpickerswatchdefault]):not([REPLACE_CLASS_colorpickerswatchdisabled])::after { + content: ""; + background: url('data:image/svg+xml; utf8, ') center repeat; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: -1; +} + +[REPLACE_CLASS_colorpickeralpha] { + position: relative; + height: 8px; + margin: 16px 0 8px; +} +[REPLACE_CLASS_colorpickergradient] { + position: relative; + height: 8px; + margin: 27px 2px 2px 2px; +} +[REPLACE_CLASS_colorpickeralpha] > div > div > div > div { + height: 16px !important; + width: 8px !important; + margin-top: -3px !important; + border-radius: 3px !important; +} +[REPLACE_CLASS_colorpickersaturation] > div > div > div > div { + box-shadow: var(--text-strong) 0px 0px 0px 1.5px, var(--background-nested-floating) 0px 0px 1px 1px inset, var(--background-nested-floating) 0px 0px 1px 2px !important; +} +[REPLACE_CLASS_colorpickerhue] > div > div > div > div, +[REPLACE_CLASS_colorpickeralpha] > div > div > div > div { + background: var(--text-strong) !important; + box-shadow: var(--background-nested-floating) 0px 0px 2px !important; +} +[REPLACE_CLASS_colorpickeralpha] > div > div, +[REPLACE_CLASS_colorpickergradient] > div > div { + border-radius: 3px; +} +[REPLACE_CLASS_colorpickeralpha] [REPLACE_CLASS_colorpickeralphacheckered], +[REPLACE_CLASS_colorpickergradient] [REPLACE_CLASS_colorpickergradientcheckered], +[REPLACE_CLASS_colorpickergradient] [REPLACE_CLASS_colorpickergradientcursor] > div::after { + background: url('data:image/svg+xml; utf8, ') center repeat; +} +[REPLACE_CLASS_colorpickergradient] [REPLACE_CLASS_colorpickergradientcursor] > div { + height: 8px; + width: 8px; + margin-top: -15px; + border: 1px solid rgb(128, 128, 128); + border-radius: 3px; + transform: translateX(-5px); + transform-style: preserve-3d; +} +[REPLACE_CLASS_colorpickergradient] [REPLACE_CLASS_colorpickergradientcursor] > div::after { + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: -1; + border-radius: 3px; + transform: translateZ(-1px); +} +[REPLACE_CLASS_colorpickergradient] [REPLACE_CLASS_colorpickergradientcursor] > div::before { + content: ""; + position: absolute; + border: 3px solid transparent; + border-top-width: 5px; + border-top-color: rgb(128, 128, 128); + width: 0; + height: 0; + top: 100%; + left: -50%; + transform: translateX(5px); +} +[REPLACE_CLASS_colorpickergradient] [REPLACE_CLASS_colorpickergradientcursor][REPLACE_CLASS_colorpickergradientcursoredge] > div::before { + border-right-width: 0; + border-left-width: 5px; +} +[REPLACE_CLASS_colorpickergradient] [REPLACE_CLASS_colorpickergradientcursor][REPLACE_CLASS_colorpickergradientcursoredge] ~ [REPLACE_CLASS_colorpickergradientcursor][REPLACE_CLASS_colorpickergradientcursoredge] > div::before { + border-right-width: 5px; + border-left-width: 0; +} +[REPLACE_CLASS_colorpickergradient] [REPLACE_CLASS_colorpickergradientcursor][REPLACE_CLASS_colorpickergradientcursorselected] > div { + border-color: var(--text-subtle); + width: 10px; + height: 10px; + margin-top: -17px; + transform: translateX(-6px); +} +[REPLACE_CLASS_colorpickergradient] [REPLACE_CLASS_colorpickergradientcursor][REPLACE_CLASS_colorpickergradientcursorselected] > div::before { + border-top-color: var(--text-subtle); + transform: translateX(7px); +} +[REPLACE_CLASS_colorpickergradientbutton] { + color: var(--icon-subtle); + opacity: 0.6; + margin-left: 6px; + transition: color 200ms ease, opactity 200ms ease; +} +[REPLACE_CLASS_colorpickergradientbutton]:hover { + color: var(--icon-default); + opacity: 1; +} +[REPLACE_CLASS_colorpickergradientbutton][REPLACE_CLASS_colorpickergradientbuttonenabled], +[REPLACE_CLASS_colorpickergradientbutton][REPLACE_CLASS_colorpickergradientbuttonenabled]:hover { + color: var(--icon-strong); + opacity: 1; +} + +[REPLACE_CLASS_modallarge], [REPLACE_CLASS_modalsublarge] { + max-height: 95vh; +} +@media only screen and (max-height: 900px) { + [REPLACE_CLASS_modalmedium], [REPLACE_CLASS_modalsubmedium] { + max-height: 75vh; + } +} + +[REPLACE_CLASS_peoplesnowplayingmember] [REPLACE_CLASS_nametag] { + pointer-events: none; +} + +.platform-win [REPLACE_CLASS_toasts] { + padding-top: 22px; +} +[REPLACE_CLASS_toasts] { + position: absolute; + top: 10px; + right: 10px; + left: 10px; + display: flex; + flex-direction: column; + pointer-events: none; + z-index: 100001; +} +[REPLACE_CLASS_toasts][REPLACE_CLASS_toastsleft] { + bottom: 10px; + justify-content: flex-start; + align-items: flex-start; +} +[REPLACE_CLASS_toasts][REPLACE_CLASS_toastscenter] { + bottom: 80px; + justify-content: flex-end; + align-items: center; +} +[REPLACE_CLASS_toasts][REPLACE_CLASS_toastsright] { + bottom: 10px; + justify-content: flex-start; + align-items: flex-end; +} +[REPLACE_CLASS_toast] { + position: relative; + display: flex; + align-items: center; + backdrop-filter: blur(5px); + border-radius: 3px; + box-shadow: var(--elevation-medium); + margin-top: 10px; + padding: 10px 10px 12px 10px; + overflow: hidden; + max-width: 50vw; + min-height: 24px; + height: unset; + opacity: 1; + transform: scale(1); + transition: all 300ms ease; +} +[REPLACE_CLASS_toastclosable] { + cursor: pointer; + pointer-events: auto; +} +[REPLACE_CLASS_toasts][REPLACE_CLASS_toastsleft] [REPLACE_CLASS_toast] { + min-width: 200px; + transform-origin: left top; +} +[REPLACE_CLASS_toasts][REPLACE_CLASS_toastscenter] [REPLACE_CLASS_toast] { + min-width: 100px; + transform-origin: center bottom; +} +[REPLACE_CLASS_toasts][REPLACE_CLASS_toastsright] [REPLACE_CLASS_toast] { + min-width: 200px; + transform-origin: right top; +} +[REPLACE_CLASS_toastopening], +[REPLACE_CLASS_toastclosing] { + opacity: 0; + transform: scale(0); +} +[REPLACE_CLASS_toastclosing] { + min-height: 0; + height: 0; +} +[REPLACE_CLASS_toastbg] { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + opacity: 0.9; + z-index: 1; +} +[REPLACE_CLASS_toastinner] { + position: relative; + display: flex; + align-items: center; + flex: 1 0 auto; + z-index: 2; +} +[REPLACE_CLASS_toasticon] { + display: flex; + justify-content: center; + align-items: center; + flex: 0 0 auto; + margin-right: 10px; +} +[REPLACE_CLASS_toastavatar] { + width: 24px; + height: 24px; +} +[REPLACE_CLASS_toasttext] { + max-width: calc(50vw - 50px); + flex: 1 0 auto; + font-size: 14px; + font-weight: 500; + white-space: pre-wrap; + word-wrap: break-word; +} +[REPLACE_CLASS_toastcloseicon] { + display: none; + margin-left: 6px; +} +[REPLACE_CLASS_toast]:hover [REPLACE_CLASS_toastcloseicon] { + display: block; +} +[REPLACE_CLASS_toastbar] { + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 4px; + background: black; + opacity: 0.3; + z-index: 3; + transition: all; +} +[REPLACE_CLASS_toast]:hover [REPLACE_CLASS_toastbar] { + animation-play-state: paused !important; +} +[REPLACE_CLASS_toastclosing] [REPLACE_CLASS_toastbar] { + animation: unset !important; + right: 0 !important; + transition: all !important; +} +@keyframes toast-bar { + from {right: 100%;} + to {right: 0;} +} +[REPLACE_CLASS_toastdefault] [REPLACE_CLASS_toastbar] { + background: var(--text-strong); +} +[REPLACE_CLASS_toastbar][REPLACE_CLASS_toastcustombar] { + opacity: 1; +} +[REPLACE_CLASS_toastdefault] [REPLACE_CLASS_toastbg] { + background-color: var(--background-nested-floating); +} +[REPLACE_CLASS_toastdefault] [REPLACE_CLASS_toastinner] { + color: var(--text-strong); +} +[REPLACE_CLASS_toastbrand] [REPLACE_CLASS_toastbg] { + background-color: var(--bdfdb-blurple); +} +[REPLACE_CLASS_toastbrand] [REPLACE_CLASS_toastinner] { + color: #fff; +} +[REPLACE_CLASS_toastdanger] [REPLACE_CLASS_toastbg] { + background-color: var(--status-danger); +} +[REPLACE_CLASS_toastdanger] [REPLACE_CLASS_toastinner] { + color: var(--status-danger-text); +} +[REPLACE_CLASS_toastinfo] [REPLACE_CLASS_toastbg] { + background-color: #4A90E2; +} +[REPLACE_CLASS_toastinfo] [REPLACE_CLASS_toastinner] { + color: #fff; +} +[REPLACE_CLASS_toastsuccess] [REPLACE_CLASS_toastbg] { + background-color: var(--status-positive); +} +[REPLACE_CLASS_toastsuccess] [REPLACE_CLASS_toastinner] { + color: var(--status-positive-text); +} +[REPLACE_CLASS_toastwarning] [REPLACE_CLASS_toastbg] { + background-color: var(--status-warning); +} +[REPLACE_CLASS_toastwarning] [REPLACE_CLASS_toastinner] { + color: var(--status-warning-text); +} + +[REPLACE_CLASS__bdmodalcontent] { + z-index: 1; +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/0PluginLibrary.plugin.js b/dotfiles/.config/BetterDiscord/plugins/0PluginLibrary.plugin.js new file mode 100644 index 0000000..2520495 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/0PluginLibrary.plugin.js @@ -0,0 +1,6229 @@ +/** + * @name ZeresPluginLibrary + * @description Gives other plugins utility functions. + * @version 2.0.23 + * @author Zerebos + * @source https://github.com/rauenzi/BDPluginLibrary + */ + +/*@cc_on +@if (@_jscript) + + // Offer to self-install for clueless users that try to run this directly. + var shell = WScript.CreateObject("WScript.Shell"); + var fs = new ActiveXObject("Scripting.FileSystemObject"); + var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins"); + var pathSelf = WScript.ScriptFullName; + // Put the user at ease by addressing them in the first person + shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30); + if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) { + shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40); + } else if (!fs.FolderExists(pathPlugins)) { + shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10); + } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) { + fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true); + // Show the user where to put plugins in the future + shell.Exec("explorer " + pathPlugins); + shell.Popup("I'm installed!", 0, "Successfully installed", 0x40); + } + WScript.Quit(); + +@else@*/ + +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ "./src/styles/settings.css": +/*!*********************************!*\ + !*** ./src/styles/settings.css ***! + \*********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (".plugin-input-group {\n margin-top: 5px;\n}\n\n.plugin-input-group .button-collapse {\n background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FscXVlXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSItOTUwIDUzMiAxOCAxOCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAtOTUwIDUzMiAxOCAxODsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCgkuc3Qwe2ZpbGw6bm9uZTt9DQoJLnN0MXtmaWxsOm5vbmU7c3Ryb2tlOiNGRkZGRkY7c3Ryb2tlLXdpZHRoOjEuNTtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTS05MzIsNTMydjE4aC0xOHYtMThILTkzMnoiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QxIiBwb2ludHM9Ii05MzYuNiw1MzguOCAtOTQxLDU0My4yIC05NDUuNCw1MzguOCAiLz4NCjwvc3ZnPg0K);\n height: 16px;\n width: 16px;\n display: inline-block;\n vertical-align: bottom;\n transition: transform .3s ease;\n transform: rotate(0);\n}\n\n.plugin-input-group .button-collapse.collapsed {\n transition: transform .3s ease;\n transform: rotate(-90deg);\n}\n\n.plugin-input-group h2 {\n font-size: 14px;\n}\n\n.plugin-input-group .plugin-input-group h2 {\n margin-left: 16px;\n}\n\n.plugin-inputs {\n height: auto;\n overflow: hidden;\n transition: height 300ms cubic-bezier(0.47, 0, 0.745, 0.715);\n}\n\n.plugin-inputs.collapsed {\n height: 0px;\n}\n\n.file-input {\n color: var(--text-normal);\n background-color: var(--input-background);\n width: 100%;\n border-radius: 5px;\n padding: 10px;\n height: 40px;\n box-sizing: border-box;\n overflow: hidden;\n }\n \n .file-input::-webkit-file-upload-button {\n color: white;\n background: #7289DA;\n outline: 0;\n border: 0;\n padding: 12px!important;\n margin-top: -10px;\n margin-left: -10px;\n margin-right: 10px;\n bottom: 0;\n border-radius: 3px 0 0 3px;\n font-size: 14px;\n font-weight: 500;\n font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif;\n cursor: pointer;\n }\n\n.color-input {\n background: none;\n padding: 0;\n border: none;\n}\n\n.color-input:hover {\n opacity: 0.8;\n}\n\n\n.z-select {\n position: relative;\n cursor: pointer;\n color: var(--text-normal);\n font-size: 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n background-color: var(--deprecated-text-input-bg);\n border: 1px solid var(--deprecated-text-input-border);\n border-radius: 3px;\n padding: 8px 8px 8px 12px;\n transition: 150ms ease border-color;\n }\n \n .z-select:hover,\n .z-select.menu-open {\n border-color: var(--background-tertiary);\n }\n \n .z-select.z-select-transparent {\n align-items: flex-start;\n background: none;\n border: none;\n padding: 0;\n }\n \n .z-select-icons {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n .z-select-clear,\n .z-select-arrow {\n margin-left: 5px;\n fill: var(--interactive-normal);\n }\n \n .z-select .z-select-options {\n position: absolute;\n background: var(--background-secondary);\n border-radius: 0 0 3px 3px;\n max-height: 125px;\n min-width: 100%;\n overflow-y: auto;\n box-shadow: rgba(0, 0, 0, 0.3) 0 1px 5px 0;\n border: 1px solid rgba(0, 0, 0, 0.3);\n border-top: 0;\n margin-top: -1px;\n margin-left: -13px;\n z-index: 2;\n top: 100%;\n }\n \n .z-select-transparent .z-select-options {\n border: 1px solid rgba(0, 0, 0, 0.3);\n margin-top: 3px;\n border-radius: 3px;\n }\n \n .z-select .z-select-option {\n padding: 8px 12px;\n cursor: pointer;\n white-space: pre;\n }\n \n .z-select .z-select-option:hover {\n background: rgba(0, 0, 0, 0.1);\n }\n \n .z-select .z-select-option.selected {\n background: rgba(0, 0, 0, 0.2);\n }\n\n\n\n .z-keybind-wrapper {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n \n .z-keybind-wrapper > :first-child {\n flex-grow: 1;\n }\n \n .z-keybind-clear {\n margin-left: 5px;\n fill: var(--interactive-normal);\n cursor: pointer;\n }\n\n.plugin-input-container > .container-31PmuA {\n margin: 10px 0;\n}"); + +/***/ }), + +/***/ "./src/styles/toasts.css": +/*!*******************************!*\ + !*** ./src/styles/toasts.css ***! + \*******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (".toasts {\n position: fixed;\n display: flex;\n top: 0;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n pointer-events: none;\n z-index: 4000;\n}\n\n@keyframes toast-up {\n from {\n transform: translateY(0);\n opacity: 0;\n }\n}\n\n.toast {\n animation: toast-up 300ms ease;\n transform: translateY(-10px);\n background: #36393F;\n padding: 10px;\n border-radius: 5px;\n box-shadow: 0 0 0 1px rgba(32,34,37,.6), 0 2px 10px 0 rgba(0,0,0,.2);\n font-weight: 500;\n color: #fff;\n user-select: text;\n font-size: 14px;\n opacity: 1;\n margin-top: 10px;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n@keyframes toast-down {\n to {\n transform: translateY(0px);\n opacity: 0;\n }\n}\n\n.toast.closing {\n animation: toast-down 200ms ease;\n animation-fill-mode: forwards;\n opacity: 1;\n transform: translateY(-10px);\n}\n\n.toast.toast-info {\n background-color: #4a90e2;\n}\n\n.toast.toast-success {\n background-color: #43b581;\n}\n\n.toast.toast-danger,\n.toast.toast-error {\n background-color: #f04747;\n}\n\n.toast.toast-warning,\n.toast.toast-warn {\n background-color: #FFA600;\n}\n\n.toast-icon {\n margin-right: 5px;\n fill: white;\n border-radius: 50%;\n overflow: hidden;\n height: 20px;\n width: 20px;\n}\n\n.toast-text {\n line-height: 20px;\n}"); + +/***/ }), + +/***/ "./src/styles/updates.css": +/*!********************************!*\ + !*** ./src/styles/updates.css ***! + \********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("#outdated-plugins {\n font-weight: 700;\n}\n#outdated-plugins > span {\n -webkit-app-region: no-drag;\n color: #fff;\n cursor: pointer;\n}\n#outdated-plugins > span:hover {\n text-decoration: underline;\n}"); + +/***/ }), + +/***/ "./src/config.js": +/*!***********************!*\ + !*** ./src/config.js ***! + \***********************/ +/***/ ((module) => { + +// Use non-ES6 so build script can require() +// Options: added, improved, fixed, progress. +module.exports = { + id: "9", + name: "ZeresPluginLibrary", + author: "Zerebos", + version: "2.0.23", + description: "Gives other plugins utility functions.", + source: "https://github.com/rauenzi/BDPluginLibrary", + github_raw: "https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js", + changelog: [ + { + title: "Final Update", + type: "fixed", + items: [ + "This will likely be the final update of the library. It prevents the library from using APIs that are going away soon just in case.", + "I have been working hard to integrate the functionality of the library directly into BetterDiscord itself.", + "The library is now officially considered deprecated and I recommend plugin devs move off of this as soon as they are able to!", + "For any devs with questions or concerns on how to move from this to `BdApi`, feel free to reach out to me or other devs in the programming channel of the BetterDiscord server." + ] + }, + ], + main: "index.js" +}; + + +/***/ }), + +/***/ "./src/modules/colorconverter.js": +/*!***************************************!*\ + !*** ./src/modules/colorconverter.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ ColorConverter) +/* harmony export */ }); +/** + * Helpful utilities for dealing with colors. + * @module ColorConverter + */ + + +const validHexRegex = /#([a-fA-F0-9]{1,2})([a-fA-F0-9]{1,2})([a-fA-F0-9]{1,2})/; + +class ColorConverter { + + static getDarkness(color) { + const [red, green, blue] = this.getRGB(color); + return 1 - (0.299 * red + 0.587 * green + 0.114 * blue) / 255; + } + + static hex2int(color) { + if (color.length === 4) color = `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}`; + return parseInt(color.slice(1), 16); + } + + static hex2rgb(color) { + const [red, green, blue] = this.getRGB(color); + return `rgb(${red}, ${green}, ${blue})`; + } + + static int2hex(color) { + const red = color >> 16 & 255; + const green = color >> 8 & 255; + const blue = color & 255; + return `#${red.toString(16)}${green.toString(16)}${blue.toString(16)}`; + } + + static int2rgba(color, alpha) { + return `rgba(${color >> 16 & 255}, ${color >> 8 & 255}, ${color & 255}, ${alpha})`; + } + + static isValidHex(color) { + return color.match(validHexRegex) != null; + } + + /** + * Will get the red green and blue values of any color string. + * @param {string} color - the color to obtain the red, green and blue values of. Can be in any of these formats: #fff, #ffffff, rgb, rgba + * @returns {Array} - array containing the red, green, and blue values + */ + static getRGB(color) { + let result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color); + if (result) return [parseInt(result[1]), parseInt(result[2]), parseInt(result[3])]; + + result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*\)/.exec(color); + if (result) return [parseFloat(result[1]) * 2.55, parseFloat(result[2]) * 2.55, parseFloat(result[3]) * 2.55]; + + result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color); + if (result) return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]; + + result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color); + if (result) return [parseInt(result[1] + result[1], 16), parseInt(result[2] + result[2], 16), parseInt(result[3] + result[3], 16)]; + } + + /** + * Will get the darken the color by a certain percent + * @param {string} color - Can be in any of these formats: #fff, #ffffff, rgb, rgba + * @param {number} percent - percent to darken the color by (0-100) + * @returns {string} - new color in rgb format + */ + static darkenColor(color, percent) { + const rgb = this.getRGB(color); + if (!rgb) return color; + for (let i = 0; i < rgb.length; i++) rgb[i] = Math.round(Math.max(0, rgb[i] - rgb[i] * (percent / 100))); + return "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; + } + + /** + * Will get the lighten the color by a certain percent + * @param {string} color - Can be in any of these formats: #fff, #ffffff, rgb, rgba + * @param {number} percent - percent to lighten the color by (0-100) + * @returns {string} - new color in rgb format + */ + static lightenColor(color, percent) { + const rgb = this.getRGB(color); + if (!rgb) return color; + for (let i = 0; i < rgb.length; i++) rgb[i] = Math.round(Math.min(255, rgb[i] + rgb[i] * (percent / 100))); + return "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; + } + + /** + * Converts a color to rgba format string + * @param {string} color - Can be in any of these formats: #fff, #ffffff, rgb, rgba + * @param {number} alpha - alpha level for the new color + * @returns {string} - new color in rgb format + */ + static rgbToAlpha(color, alpha) { + const rgb = this.getRGB(color); + if (!rgb) return color; + return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + alpha + ")"; + } + +} + +/***/ }), + +/***/ "./src/modules/discordclasses.js": +/*!***************************************!*\ + !*** ./src/modules/discordclasses.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _discordclassmodules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./discordclassmodules */ "./src/modules/discordclassmodules.js"); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); + + + +const getRaw = function(prop) { + if (!this.hasOwnProperty(prop)) return ""; + return this[prop]; +}; + +const getClass = function(prop) { + if (!this.hasOwnProperty(prop)) return ""; + return this[prop].split(" ")[0]; +}; + +/** + * Proxy for all the class packages, allows us to safely attempt + * to retrieve nested things without error. Also wraps the class in + * {@link module:DOMTools.ClassName} which adds features but can still + * be used in native function. + * + * For a list of all available class namespaces check out {@link module:DiscordClassModules}. + * + * @see module:DiscordClassModules + * @module DiscordClasses + */ +const DiscordModules = new Proxy(_discordclassmodules__WEBPACK_IMPORTED_MODULE_0__["default"], { + get: function(list, item) { + if (item == "getRaw" || item == "getClass") return (module, prop) => DiscordModules[module][item]([prop]); + if (list[item] === undefined) return new Proxy({}, {get: function() {return "";}}); + return new Proxy(list[item], { + get: function(obj, prop) { + if (prop == "getRaw") return getRaw.bind(obj); + if (prop == "getClass") return getClass.bind(obj); + if (!obj.hasOwnProperty(prop)) return ""; + return new _domtools__WEBPACK_IMPORTED_MODULE_1__["default"].ClassName(obj[prop]); + } + }); + } +}); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (DiscordModules); + +/***/ }), + +/***/ "./src/modules/discordclassmodules.js": +/*!********************************************!*\ + !*** ./src/modules/discordclassmodules.js ***! + \********************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js"); +/* harmony import */ var _webpackmodules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./webpackmodules */ "./src/modules/webpackmodules.js"); + + + +/** + * A large list of known and labelled classes in discord. + * Click the source link down below to view more info. Otherwise, if you + * have the library installed or have a plugin using this library, + * do `Object.keys(ZLibrary.DiscordClassModules)` in console for a list of modules. + * + * You can use this directly, however the preferred way of doing this is to use {@link module:DiscordClasses} or {@link module:DiscordSelectors} + * + * @see module:DiscordClasses + * @see module:DiscordSelectors + * @module DiscordClassModules + */ +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_utilities__WEBPACK_IMPORTED_MODULE_0__["default"].memoizeObject({ + get ContextMenu() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("menu", "item");}, + get Scrollers() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("thin", "scrollerBase", "content");}, + get AccountDetails() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("container", "avatar", "hasBuildOverride");}, + get Typing() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("typing", "text");}, + get UserPopout() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("userPopout");}, + get PopoutRoles() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("roleCircle");}, + get UserModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("profileBadge");}, + get Textarea() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("channelTextArea", "textArea");}, + get Popouts() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("popouts", "popout");}, // broken, popouts element has been removed. + get App() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("app", "mobileApp");}, + get Titles() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("defaultMarginh5");}, + get Notices() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("notice", "colorInfo");}, + get Backdrop() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("backdrop");}, + get Modals() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("hideOnFullscreen", "root");}, + get AuditLog() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("userHook");}, + get ChannelList() {return Object.assign({}, _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("containerDefault"), _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("name", "unread"), _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("sidebar", "hasNotice"));}, + get MemberList() {return Object.assign({}, _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("member", "memberInner"), _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("members", "membersWrap"));}, + get TitleWrap() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("titleWrapper");}, + get Titlebar() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("titleBar");}, + get Embeds() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("embed", "embedAuthor");}, + get Layers() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("layers", "layer");}, + get TooltipLayers() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("layerContainer", "layer");}, + get Margins() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => !m.title && m.marginBottom40 && m.marginTop40);}, + get Dividers() { + const single = _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => Object.keys(m).length == 1 && m.divider); + // const single = singles[singles.length - 1] ?? {}; + return Object.assign({}, _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("dividerDefault"), single); + }, + get Changelog() {return Object.assign({}, _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("container", "added"), _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("content", "modal", "size"));}, + get BasicInputs() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("inputDefault", "copyInput");}, + get Messages() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("message", "containerCozy");}, + get Guilds() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("guildsWrapper");}, + get EmojiPicker() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("emojiPicker", "emojiItem");}, + get Reactions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("reaction", "reactionInner");}, + get Checkbox() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("checkbox", "checkboxInner");}, + get Tooltips() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("tooltip", "tooltipBlack");} +})); + + + +/***/ }), + +/***/ "./src/modules/discordmodules.js": +/*!***************************************!*\ + !*** ./src/modules/discordmodules.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js"); +/* harmony import */ var _webpackmodules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./webpackmodules */ "./src/modules/webpackmodules.js"); +/** + * A large list of known and useful webpack modules internal to Discord. + * Click the source link down below to view more info. Otherwise, if you + * have the library installed or have a plugin using this library, + * do `Object.keys(ZLibrary.DiscordModules)` in console for a list of modules. + * @module DiscordModules + */ + + + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_utilities__WEBPACK_IMPORTED_MODULE_0__["default"].memoizeObject({ + get React() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("createElement", "cloneElement");}, + get ReactDOM() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("render", "findDOMNode");}, + get Events() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByPrototypes("setMaxListeners", "emit");}, + + /* Guild Info, Stores, and Utilities */ + get GuildStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getGuild", "getGuildIds");}, + get SortedGuildStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getSortedGuilds");}, + get SelectedGuildStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getLastSelectedGuildId");}, + get GuildSync() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getSyncedGuilds");}, + get GuildInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getAcronym");}, + get GuildChannelsStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getChannels", "getDefaultChannel");}, + get GuildMemberStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getMember");}, + get MemberCountStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getMemberCounts");}, + get GuildEmojiStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getEmojis");}, + get GuildActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("requestMembers");}, + get GuildPermissions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getGuildPermissions");}, + + /* Channel Store & Actions */ + get ChannelStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getChannel", "getDMFromUserId");}, + get SelectedChannelStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getLastSelectedChannelId");}, + get ChannelActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("selectChannel");}, + get PrivateChannelActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("openPrivateChannel");}, + + /* Current User Info, State and Settings */ + get UserInfoStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getSessionId");}, + get UserSettingsStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("guildPositions");}, + get StreamerModeStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("hidePersonalInformation");}, + get UserSettingsUpdater() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("updateRemoteSettings");}, + get OnlineWatcher() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isOnline");}, + get CurrentUserIdle() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isIdle");}, + get RelationshipStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isBlocked", "getFriendIDs");}, + get RelationshipManager() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("addRelationship");}, + get MentionStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getMentions");}, + + /* User Stores and Utils */ + get UserStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getCurrentUser", "getUser");}, + get UserStatusStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getStatus", "getState");}, + get UserTypingStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isTyping");}, + get UserActivityStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getActivity");}, + get UserNameResolver() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getName");}, + get UserNoteStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getNote");}, + get UserNoteActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("updateNote");}, + + /* Emoji Store and Utils */ + get EmojiInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isEmojiDisabled");}, + get EmojiUtils() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getGuildEmoji");}, + get EmojiStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getByCategory", "EMOJI_NAME_RE");}, + + /* Invite Store and Utils */ + get InviteStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getInvites");}, + get InviteResolver() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("resolveInvite");}, + get InviteActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("acceptInvite");}, + + /* Discord Objects & Utils */ + get DiscordConstants() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Permissions", "ActivityTypes", "StatusTypes");}, + get DiscordPermissions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m.ADD_REACTIONS, {searchExports: true});}, + get Permissions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("computePermissions");}, + get ColorConverter() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => Object.values(m).some(v => v?.toString().includes(`"rgba("`)));}, + get ColorShader() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("darken");}, + get TinyColor() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByPrototypes("toRgb");}, + get ClassResolver() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getClass");}, + get ButtonData() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m.BorderColors, {searchExports: true});}, + get NavigationUtils() { + return { + transitionToGuild: _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("transitionToGuildSync")?.transitionToGuildSync, + transitionTo: _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.toString?.().includes(`"transitionTo - Transitioning to "`), {searchExports: true}), + replaceWith: _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.toString?.().includes(`"Replacing route with "`), {searchExports: true}) + }; + }, + get KeybindStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("keyToCode");}, + + /* Discord Messages */ + get MessageStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getMessage", "getMessages");}, + get ReactionsStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getReactions", "_dispatcher");}, + get MessageActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("jumpToMessage", "_sendMessage");}, + get MessageQueue() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("enqueue");}, + get MessageParser() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => Object.keys(m).length && Object.keys(m).every(k => k === "parse" || k === "unparse"));}, + + /* Experiments */ + get ExperimentStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getExperimentOverrides");}, + get ExperimentsManager() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isDeveloper");}, + get CurrentExperiment() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getExperimentId");}, + + /* Streams */ + get StreamStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getAllActiveStreams", "getStreamForUser");}, + get StreamPreviewStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getIsPreviewLoading", "getPreviewURL");}, + + /* Images, Avatars and Utils */ + get ImageResolver() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getUserAvatarURL", "getGuildIconURL");}, + get ImageUtils() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getSizedImageSrc");}, + get AvatarDefaults() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getUserAvatarURL", "DEFAULT_AVATARS");}, + + /* Drag & Drop */ + get DNDSources() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("addTarget");}, + get DNDObjects() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("DragSource");}, + + /* Electron & Other Internals with Utils*/ + get ElectronModule() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("setBadge");}, + get Flux() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Store", "connectStores");}, + get Dispatcher() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("dispatch", "subscribe");}, + get PathUtils() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("hasBasename");}, + get NotificationModule() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("showNotification");}, + get RouterModule() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Router");}, + get APIModule() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getAPIBaseURL");}, + get AnalyticEvents() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("AnalyticEventConfigs");}, + get KeyGenerator() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByRegex(/"binary"/);}, + get Buffers() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Buffer", "kMaxLength");}, + get DeviceStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getDevices");}, + get SoftwareInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("os");}, + get i18n() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Messages", "languages");}, + + /* Media Stuff (Audio/Video) */ + get MediaDeviceInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Codecs", "MediaEngineContextTypes");}, + get MediaInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getOutputVolume");}, + get MediaEngineInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("determineMediaEngine");}, + get VoiceInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getEchoCancellation");}, + get SoundModule() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("playSound");}, + + /* Window, DOM, HTML */ + get WindowInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isFocused", "windowSize");}, + get DOMInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("canUseDOM");}, + + /* Locale/Location and Time */ + get LocaleManager() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m.Messages && Object.keys(m.Messages).length);}, + get Moment() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("parseZone");}, + get LocationManager() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("createLocation");}, + get Timestamps() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("fromTimestamp");}, + + /* Strings and Utils */ + get Strings() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m.Messages && Object.keys(m.Messages).length && m.Messages.COPY_ID);}, + get StringFormats() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("a", "z");}, + get StringUtils() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("toASCII");}, + + /* URLs and Utils */ + get URLParser() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Url", "parse");}, + get ExtraURLs() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getArticleURL");}, + + /* Text Processing */ + get hljs() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("highlight", "highlightBlock");}, + get SimpleMarkdown() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("parseBlock", "parseInline", "defaultOutput");}, + + /* DOM/React Components */ + /* ==================== */ + get LayerManager() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("popLayer", "pushLayer");}, + get UserSettingsWindow() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "updateAccount");}, + get ChannelSettingsWindow() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "updateChannel");}, + get GuildSettingsWindow() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "updateGuild");}, + + /* Modals */ + get ModalActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("openModal", "closeModal");}, + get ModalStack() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("push", "update", "pop", "popWithKey");}, + get UserProfileModals() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("fetchMutualFriends", "setSection");}, + get AlertModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByPrototypes("handleCancel", "handleSubmit");}, + get ConfirmationModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("ConfirmModal").ConfirmModal;}, + get ChangeNicknameModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "changeNickname");}, + get CreateChannelModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "createChannel");}, + get PruneMembersModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "prune");}, + get NotificationSettingsModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "updateNotificationSettings");}, + get PrivacySettingsModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m.open && m.open.toString().includes("PRIVACY_SETTINGS_MODAL"));}, + get Changelog() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule((m => m.defaultProps && m.defaultProps.selectable == false));}, + get ModalRoot() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.toString?.()?.includes("ENTERING") && m?.toString?.()?.includes("headerId"), {searchExports: true});}, + + /* Popouts */ + get PopoutStack() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "close", "closeAll");}, + get PopoutOpener() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("openPopout");}, + get UserPopout() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.type?.toString?.().includes('Unexpected missing user'), {searchExports: true});}, + + /* Context Menus */ + get ContextMenuActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("openContextMenu");}, + get ContextMenuItemsGroup() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByRegex(/itemGroup/);}, + get ContextMenuItem() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByRegex(/\.label\b.*\.hint\b.*\.action\b/);}, + + /* Misc */ + get ExternalLink() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByRegex(/trusted/);}, + get TextElement() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.Sizes?.SIZE_32 && m.Colors);}, + get Anchor() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("Anchor");}, + get Flex() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("Flex");}, + get FlexChild() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Child");}, + get Clickable() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("Clickable");}, + get Titles() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Tags", "Sizes");}, + get HeaderBar() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("HeaderBar");}, + get TabBar() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("TabBar");}, + get Tooltip() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByPrototypes("renderTooltip");}, + get Spinner() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("Spinner");}, + + /* Forms */ + get FormTitle() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("FormTitle");}, + get FormSection() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("FormSection");}, + get FormNotice() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("FormNotice");}, + + /* Scrollers */ + get ScrollerThin() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("ScrollerThin").ScrollerThin;}, + get ScrollerAuto() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("ScrollerAuto").ScrollerAuto;}, + get AdvancedScrollerThin() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("AdvancedScrollerThin").AdvancedScrollerThin;}, + get AdvancedScrollerAuto() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("AdvancedScrollerAuto").AdvancedScrollerAuto;}, + get AdvancedScrollerNone() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("AdvancedScrollerNone").AdvancedScrollerNone;}, + + /* Settings */ + get SettingsWrapper() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.render?.toString?.().includes("required") && m?.render?.toString?.().includes("titleClassName"), {searchExports: true});}, + get SettingsNote() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.Types?.DESCRIPTION, {searchExports: true});}, + get SettingsDivider() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => !m.defaultProps && m.prototype && m.prototype.render && m.prototype.render.toString().includes("default.divider"));}, + + get ColorPicker() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.displayName === "ColorPicker" && m?.defaultProps);}, + get Dropdown() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("SingleSelect").SingleSelect;}, + get Keybind() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByPrototypes("handleComboChange");}, + get RadioGroup() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.Sizes && m?.toString?.().includes("radioItemClassName"), {searchExports: true});}, + get Slider() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.defaultProps?.maxValue == 100 && m?.prototype?.renderMark, {searchExports: true});}, + get SwitchRow() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.toString?.().includes("tooltipNote"), {searchExports: true});}, + get Textbox() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.defaultProps && m?.defaultProps?.type == "text", {searchExports: true});}, +})); + + +/***/ }), + +/***/ "./src/modules/discordselectors.js": +/*!*****************************************!*\ + !*** ./src/modules/discordselectors.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _discordclassmodules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./discordclassmodules */ "./src/modules/discordclassmodules.js"); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); + + + +const getSelectorAll = function(prop) { + if (!this.hasOwnProperty(prop)) return ""; + return `.${this[prop].split(" ").join(".")}`; +}; + +const getSelector = function(prop) { + if (!this.hasOwnProperty(prop)) return ""; + return `.${this[prop].split(" ")[0]}`; +}; + +/** + * Gives us a way to retrieve the internal classes as selectors without + * needing to concatenate strings or use string templates. Wraps the + * selector in {@link module:DOMTools.Selector} which adds features but can + * still be used in native function. + * + * For a list of all available class namespaces check out {@link module:DiscordClassModules}. + * + * @see module:DiscordClassModules + * @module DiscordSelectors + */ +const DiscordSelectors = new Proxy(_discordclassmodules__WEBPACK_IMPORTED_MODULE_0__["default"], { + get: function(list, item) { + if (item == "getSelectorAll" || item == "getSelector") return (module, prop) => DiscordSelectors[module][item]([prop]); + if (list[item] === undefined) return new Proxy({}, {get: function() {return "";}}); + return new Proxy(list[item], { + get: function(obj, prop) { + if (prop == "getSelectorAll") return getSelectorAll.bind(obj); + if (prop == "getSelector") return getSelector.bind(obj); + if (!obj.hasOwnProperty(prop)) return ""; + return new _domtools__WEBPACK_IMPORTED_MODULE_1__["default"].Selector(obj[prop]); + } + }); + } +}); + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (DiscordSelectors); + +/***/ }), + +/***/ "./src/modules/domtools.js": +/*!*********************************!*\ + !*** ./src/modules/domtools.js ***! + \*********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ DOMTools) +/* harmony export */ }); +/* harmony import */ var structs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! structs */ "./src/structs/structs.js"); +/** + * Helpful utilities for dealing with DOM operations. + * + * This module also extends `HTMLElement` to add a set of utility functions, + * the same as the ones available in the module itself, but with the `element` + * parameter bound to `this`. + * @module DOMTools + */ + + + +/** + * @interface + * @name Offset + * @property {number} top - Top offset of the target element. + * @property {number} right - Right offset of the target element. + * @property {number} bottom - Bottom offset of the target element. + * @property {number} left - Left offset of the target element. + * @property {number} height - Outer height of the target element. + * @property {number} width - Outer width of the target element. + */ + + /** + * Function that automatically removes added listener. + * @callback module:DOMTools~CancelListener + */ + +class DOMTools { + + static get Selector() {return structs__WEBPACK_IMPORTED_MODULE_0__.Selector;} + static get ClassName() {return structs__WEBPACK_IMPORTED_MODULE_0__.ClassName;} + static get DOMObserver() {return structs__WEBPACK_IMPORTED_MODULE_0__.DOMObserver;} + + /** + * Default DOMObserver for global usage. + * + * @see DOMObserver + */ + static get observer() { + return this._observer || (this._observer = new structs__WEBPACK_IMPORTED_MODULE_0__.DOMObserver()); + } + + /** Document/window width */ + static get screenWidth() {return Math.max(document.documentElement.clientWidth, window.innerWidth || 0);} + + /** Document/window height */ + static get screenHeight() {return Math.max(document.documentElement.clientHeight, window.innerHeight || 0);} + + static animate({timing = _ => _, update, duration}) { + // https://javascript.info/js-animation + const start = performance.now(); + + requestAnimationFrame(function renderFrame(time) { + // timeFraction goes from 0 to 1 + let timeFraction = (time - start) / duration; + if (timeFraction > 1) timeFraction = 1; + + // calculate the current animation state + const progress = timing(timeFraction); + + update(progress); // draw it + + if (timeFraction < 1) requestAnimationFrame(renderFrame); + }); + } + + /** + * Adds a style to the document. + * @param {string} id - identifier to use as the element id + * @param {string} css - css to add to the document + */ + static addStyle(id, css) { + document.head.append(DOMTools.createElement(``)); + } + + /** + * Removes a style from the document. + * @param {string} id - original identifier used + */ + static removeStyle(id) { + const element = document.getElementById(id); + if (element && element.tagName === "STYLE") element.remove(); + } + + /** + * Adds/requires a remote script to be loaded + * @param {string} id - identifier to use for this script + * @param {string} url - url from which to load the script + * @returns {Promise} promise that resolves when the script is loaded + */ + static addScript(id, url) { + return new Promise(resolve => { + const script = document.createElement("script"); + script.id = id; + script.src = url; + script.type = "text/javascript"; + script.onload = resolve; + document.head.append(script); + }); + } + + /** + * Removes a remote script from the document. + * @param {string} id - original identifier used + */ + static removeScript(id) { + const element = document.getElementById(id); + if (element && element.tagName === "SCRIPT") element.remove(); + } + + /** + * This is my shit version of not having to use `$` from jQuery. Meaning + * that you can pass a selector and it will automatically run {@link module:DOMTools.query}. + * It also means that you can pass a string of html and it will perform and return `parseHTML`. + * @see module:DOMTools.parseHTML + * @see module:DOMTools.query + * @param {string} selector - Selector to query or HTML to parse + * @returns {(DocumentFragment|NodeList|HTMLElement)} - Either the result of `parseHTML` or `query` + */ + static Q(selector) { + const element = this.parseHTML(selector); + const isHTML = element instanceof NodeList ? Array.from(element).some(n => n.nodeType === 1) : element.nodeType === 1; + if (isHTML) return element; + return this.query(selector); + } + + /** + * Essentially a shorthand for `document.querySelector`. If the `baseElement` is not provided + * `document` is used by default. + * @param {string} selector - Selector to query + * @param {Element} [baseElement] - Element to base the query from + * @returns {(Element|null)} - The found element or null if not found + */ + static query(selector, baseElement) { + if (!baseElement) baseElement = document; + return baseElement.querySelector(selector); + } + + /** + * Essentially a shorthand for `document.querySelectorAll`. If the `baseElement` is not provided + * `document` is used by default. + * @param {string} selector - Selector to query + * @param {Element} [baseElement] - Element to base the query from + * @returns {Array} - Array of all found elements + */ + static queryAll(selector, baseElement) { + if (!baseElement) baseElement = document; + return baseElement.querySelectorAll(selector); + } + + /** + * Parses a string of HTML and returns the results. If the second parameter is true, + * the parsed HTML will be returned as a document fragment {@see https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment}. + * This is extremely useful if you have a list of elements at the top level, they can then be appended all at once to another node. + * + * If the second parameter is false, then the return value will be the list of parsed + * nodes and there were multiple top level nodes, otherwise the single node is returned. + * @param {string} html - HTML to be parsed + * @param {boolean} [fragment=false] - Whether or not the return should be the raw `DocumentFragment` + * @returns {(DocumentFragment|NodeList|HTMLElement)} - The result of HTML parsing + */ + static parseHTML(html, fragment = false) { + const template = document.createElement("template"); + template.innerHTML = html; + const node = template.content.cloneNode(true); + if (fragment) return node; + return node.childNodes.length > 1 ? node.childNodes : node.childNodes[0]; + } + + /** Alternate name for {@link module:DOMTools.parseHTML} */ + static createElement(html, fragment = false) {return this.parseHTML(html, fragment);} + + /** + * Takes a string of html and escapes it using the brower's own escaping mechanism. + * @param {String} html - html to be escaped + */ + static escapeHTML(html) { + const textNode = document.createTextNode(""); + const spanElement = document.createElement("span"); + spanElement.append(textNode); + textNode.nodeValue = html; + return spanElement.innerHTML; + } + + /** + * Takes a string and escapes it for use as a DOM id. + * @param {String} id - string to be escaped + */ + static escapeID(id) { + return id.replace(/^[^a-z]+|[^\w-]+/gi, "-"); + } + + /** + * Adds a list of classes from the target element. + * @param {Element} element - Element to edit classes of + * @param {...string} classes - Names of classes to add + * @returns {Element} - `element` to allow for chaining + */ + static addClass(element, ...classes) { + classes = classes.flat().filter(c => c); + for (let c = 0; c < classes.length; c++) classes[c] = classes[c].toString().split(" "); + classes = classes.flat().filter(c => c); + element.classList.add(...classes); + return element; + } + + /** + * Removes a list of classes from the target element. + * @param {Element} element - Element to edit classes of + * @param {...string} classes - Names of classes to remove + * @returns {Element} - `element` to allow for chaining + */ + static removeClass(element, ...classes) { + for (let c = 0; c < classes.length; c++) classes[c] = classes[c].toString().split(" "); + classes = classes.flat().filter(c => c); + element.classList.remove(...classes); + return element; + } + + /** + * When only one argument is present: Toggle class value; + * i.e., if class exists then remove it and return false, if not, then add it and return true. + * When a second argument is present: + * If the second argument evaluates to true, add specified class value, and if it evaluates to false, remove it. + * @param {Element} element - Element to edit classes of + * @param {string} classname - Name of class to toggle + * @param {boolean} [indicator] - Optional indicator for if the class should be toggled + * @returns {Element} - `element` to allow for chaining + */ + static toggleClass(element, classname, indicator) { + classname = classname.toString().split(" ").filter(c => c); + if (typeof(indicator) !== "undefined") classname.forEach(c => element.classList.toggle(c, indicator)); + else classname.forEach(c => element.classList.toggle(c)); + return element; + } + + /** + * Checks if an element has a specific class + * @param {Element} element - Element to edit classes of + * @param {string} classname - Name of class to check + * @returns {boolean} - `true` if the element has the class, `false` otherwise. + */ + static hasClass(element, classname) { + return classname.toString().split(" ").filter(c => c).every(c => element.classList.contains(c)); + } + + /** + * Replaces one class with another + * @param {Element} element - Element to edit classes of + * @param {string} oldName - Name of class to replace + * @param {string} newName - New name for the class + * @returns {Element} - `element` to allow for chaining + */ + static replaceClass(element, oldName, newName) { + element.classList.replace(oldName, newName); + return element; + } + + /** + * Appends `thisNode` to `thatNode` + * @param {Node} thisNode - Node to be appended to another node + * @param {Node} thatNode - Node for `thisNode` to be appended to + * @returns {Node} - `thisNode` to allow for chaining + */ + static appendTo(thisNode, thatNode) { + if (typeof(thatNode) == "string") thatNode = this.query(thatNode); + if (!thatNode) return null; + thatNode.append(thisNode); + return thisNode; + } + + /** + * Prepends `thisNode` to `thatNode` + * @param {Node} thisNode - Node to be prepended to another node + * @param {Node} thatNode - Node for `thisNode` to be prepended to + * @returns {Node} - `thisNode` to allow for chaining + */ + static prependTo(thisNode, thatNode) { + if (typeof(thatNode) == "string") thatNode = this.query(thatNode); + if (!thatNode) return null; + thatNode.prepend(thisNode); + return thisNode; + } + + /** + * Insert after a specific element, similar to jQuery's `thisElement.insertAfter(otherElement)`. + * @param {Node} thisNode - The node to insert + * @param {Node} targetNode - Node to insert after in the tree + * @returns {Node} - `thisNode` to allow for chaining + */ + static insertAfter(thisNode, targetNode) { + targetNode.parentNode.insertBefore(thisNode, targetNode.nextSibling); + return thisNode; + } + + /** + * Insert after a specific element, similar to jQuery's `thisElement.after(newElement)`. + * @param {Node} thisNode - The node to insert + * @param {Node} newNode - Node to insert after in the tree + * @returns {Node} - `thisNode` to allow for chaining + */ + static after(thisNode, newNode) { + thisNode.parentNode.insertBefore(newNode, thisNode.nextSibling); + return thisNode; + } + + /** + * Gets the next sibling element that matches the selector. + * @param {Element} element - Element to get the next sibling of + * @param {string} [selector=""] - Optional selector + * @returns {Element} - The sibling element + */ + static next(element, selector = "") { + return selector ? element.querySelector("+ " + selector) : element.nextElementSibling; + } + + /** + * Gets all subsequent siblings. + * @param {Element} element - Element to get next siblings of + * @returns {NodeList} - The list of siblings + */ + static nextAll(element) { + return element.querySelectorAll("~ *"); + } + + /** + * Gets the subsequent siblings until an element matches the selector. + * @param {Element} element - Element to get the following siblings of + * @param {string} selector - Selector to stop at + * @returns {Array} - The list of siblings + */ + static nextUntil(element, selector) { + const next = []; + while (element.nextElementSibling && !element.nextElementSibling.matches(selector)) next.push(element = element.nextElementSibling); + return next; + } + + /** + * Gets the previous sibling element that matches the selector. + * @param {Element} element - Element to get the previous sibling of + * @param {string} [selector=""] - Optional selector + * @returns {Element} - The sibling element + */ + static previous(element, selector = "") { + const previous = element.previousElementSibling; + if (selector) return previous && previous.matches(selector) ? previous : null; + return previous; + } + + /** + * Gets all preceeding siblings. + * @param {Element} element - Element to get preceeding siblings of + * @returns {NodeList} - The list of siblings + */ + static previousAll(element) { + const previous = []; + while (element.previousElementSibling) previous.push(element = element.previousElementSibling); + return previous; + } + + /** + * Gets the preceeding siblings until an element matches the selector. + * @param {Element} element - Element to get the preceeding siblings of + * @param {string} selector - Selector to stop at + * @returns {Array} - The list of siblings + */ + static previousUntil(element, selector) { + const previous = []; + while (element.previousElementSibling && !element.previousElementSibling.matches(selector)) previous.push(element = element.previousElementSibling); + return previous; + } + + /** + * Find which index in children a certain node is. Similar to jQuery's `$.index()` + * @param {HTMLElement} node - The node to find its index in parent + * @returns {number} Index of the node + */ + static indexInParent(node) { + const children = node.parentNode.childNodes; + let num = 0; + for (let i = 0; i < children.length; i++) { + if (children[i] == node) return num; + if (children[i].nodeType == 1) num++; + } + return -1; + } + + /** Shorthand for {@link module:DOMTools.indexInParent} */ + static index(node) {return this.indexInParent(node);} + + /** + * Gets the parent of the element if it matches the selector, + * otherwise returns null. + * @param {Element} element - Element to get parent of + * @param {string} [selector=""] - Selector to match parent + * @returns {(Element|null)} - The sibling element or null + */ + static parent(element, selector = "") { + return !selector || element.parentElement.matches(selector) ? element.parentElement : null; + } + + /** + * Gets all children of Element that match the selector if provided. + * @param {Element} element - Element to get all children of + * @param {string} selector - Selector to match the children to + * @returns {Array} - The list of children + */ + static findChild(element, selector) { + return element.querySelector(":scope > " + selector); + } + + /** + * Gets all children of Element that match the selector if provided. + * @param {Element} element - Element to get all children of + * @param {string} selector - Selector to match the children to + * @returns {Array} - The list of children + */ + static findChildren(element, selector) { + return element.querySelectorAll(":scope > " + selector); + } + + /** + * Gets all ancestors of Element that match the selector if provided. + * @param {Element} element - Element to get all parents of + * @param {string} [selector=""] - Selector to match the parents to + * @returns {Array} - The list of parents + */ + static parents(element, selector = "") { + const parents = []; + if (selector) while (element.parentElement && element.parentElement.closest(selector)) parents.push(element = element.parentElement.closest(selector)); + else while (element.parentElement) parents.push(element = element.parentElement); + return parents; + } + + /** + * Gets the ancestors until an element matches the selector. + * @param {Element} element - Element to get the ancestors of + * @param {string} selector - Selector to stop at + * @returns {Array} - The list of parents + */ + static parentsUntil(element, selector) { + const parents = []; + while (element.parentElement && !element.parentElement.matches(selector)) parents.push(element = element.parentElement); + return parents; + } + + /** + * Gets all siblings of the element that match the selector. + * @param {Element} element - Element to get all siblings of + * @param {string} [selector="*"] - Selector to match the siblings to + * @returns {Array} - The list of siblings + */ + static siblings(element, selector = "*") { + return Array.from(element.parentElement.children).filter(e => e != element && e.matches(selector)); + } + + /** + * Sets or gets css styles for a specific element. If `value` is provided + * then it sets the style and returns the element to allow for chaining, + * otherwise returns the style. + * @param {Element} element - Element to set the CSS of + * @param {string} attribute - Attribute to get or set + * @param {string} [value] - Value to set for attribute + * @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned. + */ + static css(element, attribute, value) { + if (typeof(value) == "undefined") return global.getComputedStyle(element)[attribute]; + element.style[attribute] = value; + return element; + } + + /** + * Sets or gets the width for a specific element. If `value` is provided + * then it sets the width and returns the element to allow for chaining, + * otherwise returns the width. + * @param {Element} element - Element to set the CSS of + * @param {string} [value] - Width to set + * @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned. + */ + static width(element, value) { + if (typeof(value) == "undefined") return parseInt(getComputedStyle(element).width); + element.style.width = value; + return element; + } + + /** + * Sets or gets the height for a specific element. If `value` is provided + * then it sets the height and returns the element to allow for chaining, + * otherwise returns the height. + * @param {Element} element - Element to set the CSS of + * @param {string} [value] - Height to set + * @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned. + */ + static height(element, value) { + if (typeof(value) == "undefined") return parseInt(getComputedStyle(element).height); + element.style.height = value; + return element; + } + + /** + * Sets the inner text of an element if given a value, otherwise returns it. + * @param {Element} element - Element to set the text of + * @param {string} [text] - Content to set + * @returns {string} - Either the string set by this call or the current text content of the node. + */ + static text(element, text) { + if (typeof(text) == "undefined") return element.textContent; + return element.textContent = text; + } + + /** + * Returns the innerWidth of the element. + * @param {Element} element - Element to retrieve inner width of + * @return {number} - The inner width of the element. + */ + static innerWidth(element) { + return element.clientWidth; + } + + /** + * Returns the innerHeight of the element. + * @param {Element} element - Element to retrieve inner height of + * @return {number} - The inner height of the element. + */ + static innerHeight(element) { + return element.clientHeight; + } + + /** + * Returns the outerWidth of the element. + * @param {Element} element - Element to retrieve outer width of + * @return {number} - The outer width of the element. + */ + static outerWidth(element) { + return element.offsetWidth; + } + + /** + * Returns the outerHeight of the element. + * @param {Element} element - Element to retrieve outer height of + * @return {number} - The outer height of the element. + */ + static outerHeight(element) { + return element.offsetHeight; + } + + /** + * Gets the offset of the element in the page. + * @param {Element} element - Element to get offset of + * @return {Offset} - The offset of the element + */ + static offset(element) { + return element.getBoundingClientRect(); + } + + static get listeners() {return this._listeners || (this._listeners = {});} + + /** + * This is similar to jQuery's `on` function and can *hopefully* be used in the same way. + * + * Rather than attempt to explain, I'll show some example usages. + * + * The following will add a click listener (in the `myPlugin` namespace) to `element`. + * `DOMTools.on(element, "click.myPlugin", () => {console.log("clicked!");});` + * + * The following will add a click listener (in the `myPlugin` namespace) to `element` that only fires when the target is a `.block` element. + * `DOMTools.on(element, "click.myPlugin", ".block", () => {console.log("clicked!");});` + * + * The following will add a click listener (without namespace) to `element`. + * `DOMTools.on(element, "click", () => {console.log("clicked!");});` + * + * The following will add a click listener (without namespace) to `element` that only fires once. + * `const cancel = DOMTools.on(element, "click", () => {console.log("fired!"); cancel();});` + * + * @param {Element} element - Element to add listener to + * @param {string} event - Event to listen to with option namespace (e.g. "event.namespace") + * @param {(string|callable)} delegate - Selector to run on element to listen to + * @param {callable} [callback] - Function to fire on event + * @returns {module:DOMTools~CancelListener} - A function that will undo the listener + */ + static on(element, event, delegate, callback) { + const [type, namespace] = event.split("."); + const hasDelegate = delegate && callback; + if (!callback) callback = delegate; + const eventFunc = !hasDelegate ? callback : function(ev) { + if (ev.target.matches(delegate)) { + callback(ev); + } + }; + + element.addEventListener(type, eventFunc); + const cancel = () => { + element.removeEventListener(type, eventFunc); + }; + if (namespace) { + if (!this.listeners[namespace]) this.listeners[namespace] = []; + const newCancel = () => { + cancel(); + this.listeners[namespace].splice(this.listeners[namespace].findIndex(l => l.event == type && l.element == element), 1); + }; + this.listeners[namespace].push({ + event: type, + element: element, + cancel: newCancel + }); + return newCancel; + } + return cancel; + } + + /** + * Functionality for this method matches {@link module:DOMTools.on} but automatically cancels itself + * and removes the listener upon the first firing of the desired event. + * + * @param {Element} element - Element to add listener to + * @param {string} event - Event to listen to with option namespace (e.g. "event.namespace") + * @param {(string|callable)} delegate - Selector to run on element to listen to + * @param {callable} [callback] - Function to fire on event + * @returns {module:DOMTools~CancelListener} - A function that will undo the listener + */ + static once(element, event, delegate, callback) { + const [type, namespace] = event.split("."); + const hasDelegate = delegate && callback; + if (!callback) callback = delegate; + const eventFunc = !hasDelegate ? function(ev) { + callback(ev); + element.removeEventListener(type, eventFunc); + } : function(ev) { + if (!ev.target.matches(delegate)) return; + callback(ev); + element.removeEventListener(type, eventFunc); + }; + + element.addEventListener(type, eventFunc); + const cancel = () => { + element.removeEventListener(type, eventFunc); + }; + if (namespace) { + if (!this.listeners[namespace]) this.listeners[namespace] = []; + const newCancel = () => { + cancel(); + this.listeners[namespace].splice(this.listeners[namespace].findIndex(l => l.event == type && l.element == element), 1); + }; + this.listeners[namespace].push({ + event: type, + element: element, + cancel: newCancel + }); + return newCancel; + } + return cancel; + } + + static __offAll(event, element) { + const [type, namespace] = event.split("."); + let matchFilter = listener => listener.event == type, defaultFilter = _ => _; + if (element) { + matchFilter = l => l.event == type && l.element == element; + defaultFilter = l => l.element == element; + } + const listeners = this.listeners[namespace] || []; + const list = type ? listeners.filter(matchFilter) : listeners.filter(defaultFilter); + for (let c = 0; c < list.length; c++) list[c].cancel(); + } + + /** + * This is similar to jQuery's `off` function and can *hopefully* be used in the same way. + * + * Rather than attempt to explain, I'll show some example usages. + * + * The following will remove a click listener called `onClick` (in the `myPlugin` namespace) from `element`. + * `DOMTools.off(element, "click.myPlugin", onClick);` + * + * The following will remove a click listener called `onClick` (in the `myPlugin` namespace) from `element` that only fired when the target is a `.block` element. + * `DOMTools.off(element, "click.myPlugin", ".block", onClick);` + * + * The following will remove a click listener (without namespace) from `element`. + * `DOMTools.off(element, "click", onClick);` + * + * The following will remove all listeners in namespace `myPlugin` from `element`. + * `DOMTools.off(element, ".myPlugin");` + * + * The following will remove all click listeners in namespace `myPlugin` from *all elements*. + * `DOMTools.off("click.myPlugin");` + * + * The following will remove all listeners in namespace `myPlugin` from *all elements*. + * `DOMTools.off(".myPlugin");` + * + * @param {(Element|string)} element - Element to remove listener from + * @param {string} [event] - Event to listen to with option namespace (e.g. "event.namespace") + * @param {(string|callable)} [delegate] - Selector to run on element to listen to + * @param {callable} [callback] - Function to fire on event + * @returns {Element} - The original element to allow for chaining + */ + static off(element, event, delegate, callback) { + if (typeof(element) == "string") return this.__offAll(element); + const [type, namespace] = event.split("."); + if (namespace) return this.__offAll(event, element); + + const hasDelegate = delegate && callback; + if (!callback) callback = delegate; + const eventFunc = !hasDelegate ? callback : function(ev) { + if (ev.target.matches(delegate)) { + callback(ev); + } + }; + + element.removeEventListener(type, eventFunc); + return element; + } + + /** + * Adds a listener for when the node is added/removed from the document body. + * The listener is automatically removed upon firing. + * @param {HTMLElement} node - node to wait for + * @param {callable} callback - function to be performed on event + * @param {boolean} onMount - determines if it should fire on Mount or on Unmount + */ + static onMountChange(node, callback, onMount = true) { + const wrappedCallback = () => { + this.observer.unsubscribe(wrappedCallback); + callback(); + }; + this.observer.subscribe(wrappedCallback, mutation => { + const nodes = Array.from(onMount ? mutation.addedNodes : mutation.removedNodes); + const directMatch = nodes.indexOf(node) > -1; + const parentMatch = nodes.some(parent => parent.contains(node)); + return directMatch || parentMatch; + }); + return node; + } + + /** Shorthand for {@link module:DOMTools.onMountChange} with third parameter `true` */ + static onMount(node, callback) {return this.onMountChange(node, callback);} + + /** Shorthand for {@link module:DOMTools.onMountChange} with third parameter `false` */ + static onUnmount(node, callback) {return this.onMountChange(node, callback, false);} + + /** Alias for {@link module:DOMTools.onMount} */ + static onAdded(node, callback) {return this.onMount(node, callback);} + + /** Alias for {@link module:DOMTools.onUnmount} */ + static onRemoved(node, callback) {return this.onUnmount(node, callback, false);} + + /** + * Helper function which combines multiple elements into one parent element + * @param {Array} elements - array of elements to put into a single parent + */ + static wrap(elements) { + const domWrapper = this.parseHTML(`
`); + for (let e = 0; e < elements.length; e++) domWrapper.appendChild(elements[e]); + return domWrapper; + } + + /** + * Resolves the node to an HTMLElement. This is mainly used by library modules. + * @param {(jQuery|Element)} node - node to resolve + */ + static resolveElement(node) { + try { + if (!(node instanceof window.jQuery) && !(node instanceof Element)) return undefined; + return node instanceof window.jQuery ? node[0] : node; + } + catch { + return node; + } + } +} + +/***/ }), + +/***/ "./src/modules/logger.js": +/*!*******************************!*\ + !*** ./src/modules/logger.js ***! + \*******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ LogTypes: () => (/* binding */ LogTypes), +/* harmony export */ "default": () => (/* binding */ Logger) +/* harmony export */ }); +/** + * Simple logger for the lib and plugins. + * + * @module Logger + */ + +/* eslint-disable no-console */ + +/** + * List of logging types. + */ +const LogTypes = { + /** Alias for error */ + err: "error", + error: "error", + /** Alias for debug */ + dbg: "debug", + debug: "debug", + log: "log", + warn: "warn", + info: "info" +}; + +class Logger { + + /** + * Logs an error using a collapsed error group with stacktrace. + * + * @param {string} module - Name of the calling module. + * @param {string} message - Message or error to have logged. + * @param {Error} error - Error object to log with the message. + */ + static stacktrace(module, message, error) { + console.error(`%c[${module}]%c ${message}\n\n%c`, "color: #3a71c1; font-weight: 700;", "color: red; font-weight: 700;", "color: red;", error); + } + + /** + * Logs using error formatting. For logging an actual error object consider {@link module:Logger.stacktrace} + * + * @param {string} module - Name of the calling module. + * @param {string} message - Messages to have logged. + */ + static err(module, ...message) {Logger._log(module, message, "error");} + + /** + * Logs a warning message. + * + * @param {string} module - Name of the calling module. + * @param {...any} message - Messages to have logged. + */ + static warn(module, ...message) {Logger._log(module, message, "warn");} + + /** + * Logs an informational message. + * + * @param {string} module - Name of the calling module. + * @param {...any} message - Messages to have logged. + */ + static info(module, ...message) {Logger._log(module, message, "info");} + + /** + * Logs used for debugging purposes. + * + * @param {string} module - Name of the calling module. + * @param {...any} message - Messages to have logged. + */ + static debug(module, ...message) {Logger._log(module, message, "debug");} + + /** + * Logs used for basic loggin. + * + * @param {string} module - Name of the calling module. + * @param {...any} message - Messages to have logged. + */ + static log(module, ...message) {Logger._log(module, message);} + + /** + * Logs strings using different console levels and a module label. + * + * @param {string} module - Name of the calling module. + * @param {any|Array} message - Messages to have logged. + * @param {module:Logger.LogTypes} type - Type of log to use in console. + */ + static _log(module, message, type = "log") { + type = Logger.parseType(type); + if (!Array.isArray(message)) message = [message]; + console[type](`%c[${module}]%c`, "color: #3a71c1; font-weight: 700;", "", ...message); + } + + static parseType(type) { + return LogTypes.hasOwnProperty(type) ? LogTypes[type] : "log"; + } + +} + +/***/ }), + +/***/ "./src/modules/modules.js": +/*!********************************!*\ + !*** ./src/modules/modules.js ***! + \********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ ColorConverter: () => (/* reexport safe */ _colorconverter__WEBPACK_IMPORTED_MODULE_3__["default"]), +/* harmony export */ DOMTools: () => (/* reexport safe */ _domtools__WEBPACK_IMPORTED_MODULE_4__["default"]), +/* harmony export */ DiscordClassModules: () => (/* reexport safe */ _discordclassmodules__WEBPACK_IMPORTED_MODULE_13__["default"]), +/* harmony export */ DiscordClasses: () => (/* reexport safe */ _discordclasses__WEBPACK_IMPORTED_MODULE_5__["default"]), +/* harmony export */ DiscordModules: () => (/* reexport safe */ _discordmodules__WEBPACK_IMPORTED_MODULE_2__["default"]), +/* harmony export */ DiscordSelectors: () => (/* reexport safe */ _discordselectors__WEBPACK_IMPORTED_MODULE_6__["default"]), +/* harmony export */ Filters: () => (/* reexport safe */ _webpackmodules__WEBPACK_IMPORTED_MODULE_1__.Filters), +/* harmony export */ Logger: () => (/* reexport safe */ _logger__WEBPACK_IMPORTED_MODULE_9__["default"]), +/* harmony export */ Patcher: () => (/* reexport safe */ _patcher__WEBPACK_IMPORTED_MODULE_10__["default"]), +/* harmony export */ PluginUpdater: () => (/* reexport safe */ _pluginupdater__WEBPACK_IMPORTED_MODULE_11__["default"]), +/* harmony export */ PluginUtilities: () => (/* reexport safe */ _pluginutilities__WEBPACK_IMPORTED_MODULE_12__["default"]), +/* harmony export */ ReactComponents: () => (/* reexport safe */ _reactcomponents__WEBPACK_IMPORTED_MODULE_8__["default"]), +/* harmony export */ ReactTools: () => (/* reexport safe */ _reacttools__WEBPACK_IMPORTED_MODULE_7__["default"]), +/* harmony export */ Structs: () => (/* reexport module object */ structs__WEBPACK_IMPORTED_MODULE_14__), +/* harmony export */ Utilities: () => (/* reexport safe */ _utilities__WEBPACK_IMPORTED_MODULE_0__["default"]), +/* harmony export */ WebpackModules: () => (/* reexport safe */ _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"]) +/* harmony export */ }); +/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js"); +/* harmony import */ var _webpackmodules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./webpackmodules */ "./src/modules/webpackmodules.js"); +/* harmony import */ var _discordmodules__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./discordmodules */ "./src/modules/discordmodules.js"); +/* harmony import */ var _colorconverter__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./colorconverter */ "./src/modules/colorconverter.js"); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); +/* harmony import */ var _discordclasses__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./discordclasses */ "./src/modules/discordclasses.js"); +/* harmony import */ var _discordselectors__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./discordselectors */ "./src/modules/discordselectors.js"); +/* harmony import */ var _reacttools__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./reacttools */ "./src/modules/reacttools.js"); +/* harmony import */ var _reactcomponents__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./reactcomponents */ "./src/modules/reactcomponents.js"); +/* harmony import */ var _logger__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./logger */ "./src/modules/logger.js"); +/* harmony import */ var _patcher__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./patcher */ "./src/modules/patcher.js"); +/* harmony import */ var _pluginupdater__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./pluginupdater */ "./src/modules/pluginupdater.js"); +/* harmony import */ var _pluginutilities__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./pluginutilities */ "./src/modules/pluginutilities.js"); +/* harmony import */ var _discordclassmodules__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./discordclassmodules */ "./src/modules/discordclassmodules.js"); +/* harmony import */ var structs__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! structs */ "./src/structs/structs.js"); + + + + + + + + + + + + + + + + + + + + +/***/ }), + +/***/ "./src/modules/patcher.js": +/*!********************************!*\ + !*** ./src/modules/patcher.js ***! + \********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Patcher) +/* harmony export */ }); +/** + * Patcher that can patch other functions allowing you to run code before, after or + * instead of the original function. Can also alter arguments and return values. + * + * This is a modified version of what we have been working on in BDv2. {@link https://github.com/JsSucks/BetterDiscordApp/blob/master/client/src/modules/patcher.js} + * + * @module Patcher + */ + +class Patcher { + + // Use window._patches instead of local variables in case something tries to whack the lib + static get patches() {return [];} + + /** + * Returns all the patches done by a specific caller + * @param {string} name - Name of the patch caller + * @method + */ + static getPatchesByCaller(name) { + return BdApi.Patcher.getPatchesByCaller(name); + } + + /** + * Unpatches all patches passed, or when a string is passed unpatches all + * patches done by that specific caller. + * @param {Array|string} patches - Either an array of patches to unpatch or a caller name + */ + static unpatchAll(patches) { + BdApi.Patcher.unpatchAll(patches); + } + + /** + * Function with no arguments and no return value that may be called to revert changes made by {@link module:Patcher}, restoring (unpatching) original method. + * @callback module:Patcher~unpatch + */ + + /** + * A callback that modifies method logic. This callback is called on each call of the original method and is provided all data about original call. Any of the data can be modified if necessary, but do so wisely. + * + * The third argument for the callback will be `undefined` for `before` patches. `originalFunction` for `instead` patches and `returnValue` for `after` patches. + * + * @callback module:Patcher~patchCallback + * @param {object} thisObject - `this` in the context of the original function. + * @param {args} args - The original arguments of the original function. + * @param {(function|*)} extraValue - For `instead` patches, this is the original function from the module. For `after` patches, this is the return value of the function. + * @return {*} Makes sense only when using an `instead` or `after` patch. If something other than `undefined` is returned, the returned value replaces the value of `returnValue`. If used for `before` the return value is ignored. + */ + + /** + * This method patches onto another function, allowing your code to run beforehand. + * Using this, you are also able to modify the incoming arguments before the original method is run. + * + * @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""` if you don't care. + * @param {object} moduleToPatch - Object with the function to be patched. Can also patch an object's prototype. + * @param {string} functionName - Name of the method to be patched + * @param {module:Patcher~patchCallback} callback - Function to run before the original method + * @return {module:Patcher~unpatch} Function with no arguments and no return value that should be called to cancel (unpatch) this patch. You should save and run it when your plugin is stopped. + */ + static before(caller, moduleToPatch, functionName, callback) {return BdApi.Patcher.before(caller, moduleToPatch, functionName, callback);} + + /** + * This method patches onto another function, allowing your code to run after. + * Using this, you are also able to modify the return value, using the return of your code instead. + * + * @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""` if you don't care. + * @param {object} moduleToPatch - Object with the function to be patched. Can also patch an object's prototype. + * @param {string} functionName - Name of the method to be patched + * @param {module:Patcher~patchCallback} callback - Function to run instead of the original method + * @return {module:Patcher~unpatch} Function with no arguments and no return value that should be called to cancel (unpatch) this patch. You should save and run it when your plugin is stopped. + */ + static after(caller, moduleToPatch, functionName, callback) {return BdApi.Patcher.after(caller, moduleToPatch, functionName, callback);} + + /** + * This method patches onto another function, allowing your code to run instead. + * Using this, you are also able to modify the return value, using the return of your code instead. + * + * @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""` if you don't care. + * @param {object} moduleToPatch - Object with the function to be patched. Can also patch an object's prototype. + * @param {string} functionName - Name of the method to be patched + * @param {module:Patcher~patchCallback} callback - Function to run after the original method + * @return {module:Patcher~unpatch} Function with no arguments and no return value that should be called to cancel (unpatch) this patch. You should save and run it when your plugin is stopped. + */ + static instead(caller, moduleToPatch, functionName, callback) {return BdApi.Patcher.instead(caller, moduleToPatch, functionName, callback);} + +} + +/***/ }), + +/***/ "./src/modules/pluginupdater.js": +/*!**************************************!*\ + !*** ./src/modules/pluginupdater.js ***! + \**************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ PluginUpdater) +/* harmony export */ }); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); +/* harmony import */ var ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ui */ "./src/ui/ui.js"); +/* harmony import */ var _styles_updates_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../styles/updates.css */ "./src/styles/updates.css"); + + + + + +const fileSystem = require("fs"); +const path = require("path"); +const request = require("request"); + +/** + * Comparator that takes the current version and the remote version, + * then compares them returning `true` if there is an update and `false` otherwise. + * @param {string} currentVersion - the current version of the plugin + * @param {string} remoteVersion - the remote version of the plugin + * @returns {boolean} - whether the plugin has an update or not + * @callback module:PluginUpdater~comparator + */ + +const splitRegex = /[^\S\r\n]*?\r?(?:\r\n|\n)[^\S\r\n]*?\*[^\S\r\n]?/; +const escapedAtRegex = /^\\@/; +const HOUR_IN_MILLISECONDS = 1000 * 60 * 60; +const pluginId = name => name + "-update-notice"; +const pending = []; +const banner = {}; + + +/** + * Functions that check for and update existing plugins. + * @module PluginUpdater + * @deprecated It is recommended to go through the approval process instead. + */ +class PluginUpdater { + + static get CSS() {return _styles_updates_css__WEBPACK_IMPORTED_MODULE_2__["default"];} + static get state() {return window.__PLUGIN_UPDATES__;} + static getPlugin(link) {return this.state.plugins[link];} + static setPlugin(name, raw, version, comparator) {this.state.plugins[raw] = {name, raw, version, comparator};} + static clearPending() { + delete banner.close; + delete banner.notice; + pending.splice(0, pending.length); + } + + /** + * Checks for updates for the specified plugin at the specified link. The final + * parameter should link to the raw text of the plugin and will compare semantic + * versions. + * @param {string} pluginName - name of the plugin + * @param {string} currentVersion - current version (semantic versioning only) + * @param {string} updateURL - url to check for update + * @param {module:PluginUpdater~comparator} [comparator] - comparator that determines if there is an update. If not provided uses {@link module:PluginUpdater.defaultComparator}. + */ + static async checkForUpdate(pluginName, currentVersion, addonId, comparator) { + if (!pluginName || !currentVersion || !addonId) return; + let isUrl = false; + try { + // eslint-disable-next-line no-new + new URL(addonId); + isUrl = true; + } + catch { + isUrl = false; + } + let updateLink = `https://betterdiscord.app/gh-redirect?id=${addonId}`; + if (isUrl) updateLink = addonId; + if (typeof(comparator) != "function") comparator = this.defaultComparator; + this.setPlugin(pluginName, updateLink, currentVersion, comparator); + + const hasUpdate = await this.hasUpdate(updateLink); + if (!hasUpdate) return; + pending.push(updateLink); + this.showUpdateNotice(updateLink); + } + + static async checkAllPlugins() { + for (const link in this.state.plugins) { + const hasUpdate = await this.hasUpdate(link); + if (!hasUpdate) return; + pending.push(link); + this.showUpdateNotice(link); + } + } + + /** + * Will check for updates and automatically show or remove the update notice + * bar based on the internal result. Better not to call this directly and to + * instead use {@link module:PluginUpdater.checkForUpdate}. + * @param {string} pluginName - name of the plugin to check + * @param {string} updateLink - link to the raw text version of the plugin + */ + static async hasUpdate(updateLink) { + const doit = (resolve, result) => { + try { + const plugin = this.getPlugin(updateLink); + const meta = this.parseMeta(result); + plugin.remoteVersion = meta.version; + const hasUpdate = plugin.comparator(plugin.version, plugin.remoteVersion); + if (hasUpdate) plugin.remote = result; + resolve(hasUpdate); + } + catch (err) { + resolve(false); + } + }; + return new Promise(resolve => { + request(updateLink, (err, resp, result) => { + if (err) return resolve(false); + + // If a direct url was used + if (resp.statusCode === 200) return doit(resolve, result); + + // If an addon id and redirect was used + if (resp.statusCode === 302) { + request(resp.headers.location, (error, response, body) => { + if (error || response.statusCode !== 200) return resolve(false); + return doit(resolve, body); + }); + } + }); + }); + } + + /** + * @param {string} pluginName - name of the plugin to download + * @param {string} updateLink - link to the raw text version of the plugin + */ + static async updatePlugin(updateLink) { + const plugin = this.getPlugin(updateLink); + + let filename = updateLink.split("/"); + filename = filename[filename.length - 1]; + const file = path.join(BdApi.Plugins.folder, filename); + await new Promise(r => fileSystem.writeFile(file, plugin.remote, r)); + ui__WEBPACK_IMPORTED_MODULE_1__.Toasts.success(`${plugin.name} ${plugin.version} has been replaced by ${plugin.name} ${plugin.remoteVersion}`); + } + + /** + * Will show the update notice top bar seen in Discord. Better not to call + * this directly and to instead use {@link module:PluginUpdater.checkForUpdate}. + * @param {string} pluginName - name of the plugin + * @param {string} updateLink - link to the raw text version of the plugin + */ + static showUpdateNotice(updateLink) { + const plugin = this.getPlugin(updateLink); + const pluginNoticeID = pluginId(plugin.name); + if (document.getElementById(pluginNoticeID)) return; // This plugin already shown + if (!document.getElementById("plugin-update-notice-message")) { + banner.notice = _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].parseHTML(`The following plugins have updates:  `); + banner.close = BdApi.UI.showNotice(banner.notice, { + timeout: 0, + buttons: [{ + label: "Update All", + onClick: async () => { + for (const link of pending) await this.updatePlugin(link); + banner.close(); + } + }] + }); + _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].onRemoved(banner.notice, this.clearPending); + } + + const outdatedPlugins = document.getElementById("outdated-plugins"); + const pluginNoticeElement = _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].parseHTML(`${plugin.name}`); + pluginNoticeElement.addEventListener("click", async () => { + await this.updatePlugin(updateLink); + this.removeUpdateNotice(updateLink); + }); + if (outdatedPlugins.querySelectorAll("span").length) outdatedPlugins.append(_domtools__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(", ")); + outdatedPlugins.append(pluginNoticeElement); + ui__WEBPACK_IMPORTED_MODULE_1__.Tooltip.create(pluginNoticeElement, "Click To Update!", {side: "bottom"}); + } + + /** + * Will remove the plugin from the update notice top bar seen in Discord. + * Better not to call this directly and to instead use {@link module:PluginUpdater.checkForUpdate}. + * @param {string} pluginName - name of the plugin + */ + static removeUpdateNotice(updateLink) { + const plugin = this.getPlugin(updateLink); + if (!document.getElementById("outdated-plugins")) return; + const notice = document.getElementById(pluginId(plugin.name)); + if (notice) { + if (notice.nextElementSibling && notice.nextElementSibling.matches(".separator")) notice.nextElementSibling.remove(); + else if (notice.previousElementSibling && notice.previousElementSibling.matches(".separator")) notice.previousElementSibling.remove(); + notice.remove(); + } + + if (!document.getElementById("outdated-plugins").querySelectorAll("span").length) { + banner?.close(); + } + } + + static parseMeta(fileContent) { + const block = fileContent.split("/**", 2)[1].split("*/", 1)[0]; + const out = {}; + let field = ""; + let accum = ""; + for (const line of block.split(splitRegex)) { + if (line.length === 0) continue; + if (line.charAt(0) === "@" && line.charAt(1) !== " ") { + out[field] = accum; + const l = line.indexOf(" "); + field = line.substring(1, l); + accum = line.substring(l + 1); + } + else { + accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@"); + } + } + out[field] = accum.trim(); + delete out[""]; + out.format = "jsdoc"; + return out; + } + + /** + * The default comparator used as {@link module:PluginUpdater~comparator} for {@link module:PluginUpdater.checkForUpdate}. + * This solely compares remote > local. You do not need to provide this as a comparator if your plugin adheres + * to this style as this will be used as default. + * @param {string} currentVersion + * @param {string} content + */ + static defaultComparator(currentVersion, remoteVersion) { + return remoteVersion > currentVersion; + } +} + +if (typeof(window.__PLUGIN_UPDATES__) === "undefined") window.__PLUGIN_UPDATES__ = {plugins: {}}; +if (window.__PLUGIN_UPDATES__.interval) clearInterval(window.__PLUGIN_UPDATES__.interval); + +window.__PLUGIN_UPDATES__.interval = setInterval(PluginUpdater.checkAllPlugins.bind(PluginUpdater), HOUR_IN_MILLISECONDS * 2); + +// Transition +if (window.PluginUpdates) { + if (window.PluginUpdates.interval) clearInterval(window.PluginUpdates.interval); + Object.assign(window.__PLUGIN_UPDATES__.plugins, window.PluginUpdates.plugins); + delete window.PluginUpdates; +} + +/***/ }), + +/***/ "./src/modules/pluginutilities.js": +/*!****************************************!*\ + !*** ./src/modules/pluginutilities.js ***! + \****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ PluginUtilities) +/* harmony export */ }); +/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js"); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); + + + +/** + * A series of useful functions for BetterDiscord plugins. + * @module PluginUtilities + * @deprecated 1/21/22 Use Alternatives + */ + + + class PluginUtilities { + + /** + * Loads data through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {string} key - which key the data is saved under + * @param {object} defaultData - default data to populate the object with + * @returns {object} the combined saved and default data + * @deprecated 1/21/22 Use Utilities or BdApi directly + */ + static loadData(name, key, defaultData) {return _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].loadData(name, key, defaultData);} + + /** + * Saves data through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {string} key - which key the data should be saved under + * @param {object} data - data to save + * @deprecated 1/21/22 Use Utilities or BdApi directly + */ + static saveData(name, key, data) {return _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].saveData(name, key, data);} + + /** + * Loads settings through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {object} defaultData - default data to populate the object with + * @returns {object} the combined saved and default settings + * @deprecated 1/21/22 Use Utilities or BdApi directly + */ + static loadSettings(name, defaultSettings) {return _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].loadSettings(name, defaultSettings);} + + /** + * Saves settings through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {object} data - settings to save + * @deprecated 1/21/22 Use Utilities or BdApi directly + */ + static saveSettings(name, data) {return _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].saveSettings(name, data);} + + /** + * Get the full path to the BetterDiscord folder. + * @returns {string} full path to the BetterDiscord folder + * @deprecated 1/21/22 Use BdApi + */ + static getBDFolder(subtarget = "") { + const process = require("process"); + const path = require("path"); + if (process.env.injDir) return path.resolve(process.env.injDir, subtarget); + switch (process.platform) { + case "win32": + return path.resolve(process.env.APPDATA, "BetterDiscord/", subtarget); + case "darwin": + return path.resolve(process.env.HOME, "Library/Application Support/", "BetterDiscord/", subtarget); + default: + return path.resolve(process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : process.env.HOME + "/.config", "BetterDiscord/", subtarget); + } + } + + /** + * Get the full path to the plugins folder. + * @returns {string} full path to the plugins folder + * @deprecated 1/21/22 Use BdApi + */ + static getPluginsFolder() {return BdApi.Plugins.folder;} + + /** + * Get the full path to the themes folder. + * @returns {string} full path to the themes folder + * @deprecated 1/21/22 Use BdApi + */ + static getThemesFolder() {return BdApi.Themes.folder;} + + /** + * Adds a callback to a set of listeners for onSwitch. + * @param {callable} callback - basic callback to happen on channel switch + * @deprecated 1/21/22 Use onSwitch + */ + static addOnSwitchListener() {} + + /** + * Removes the listener added by {@link InternalUtilities.addOnSwitchListener}. + * @param {callable} callback - callback to remove from the listener list + * @deprecated 1/21/22 Use onSwitch + */ + static removeOnSwitchListener() {} + + /** + * Adds a style to the document. + * @param {string} id - identifier to use as the element id + * @param {string} css - css to add to the document + * @deprecated 1/21/22 Use DOMTools + */ + static addStyle(id, css) {return _domtools__WEBPACK_IMPORTED_MODULE_1__["default"].addStyle(id, css);} + + /** + * Removes a style from the document. + * @param {string} id - original identifier used + * @deprecated 1/21/22 Use DOMTools + */ + static removeStyle(id) {return _domtools__WEBPACK_IMPORTED_MODULE_1__["default"].removeStyle(id);} + + /** + * Adds/requires a remote script to be loaded + * @param {string} id - identifier to use for this script + * @param {string} url - url from which to load the script + * @returns {Promise} promise that resolves when the script is loaded + * @deprecated 1/21/22 Use DOMTools + */ + static addScript(id, url) {return _domtools__WEBPACK_IMPORTED_MODULE_1__["default"].addScript(id, url);} + + /** + * Removes a remote script from the document. + * @param {string} id - original identifier used + * @deprecated 1/21/22 Use DOMTools + */ + static removeScript(id) {return _domtools__WEBPACK_IMPORTED_MODULE_1__["default"].removeScript(id);} +} + + + + +/***/ }), + +/***/ "./src/modules/reactcomponents.js": +/*!****************************************!*\ + !*** ./src/modules/reactcomponents.js ***! + \****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ ReactComponents) +/* harmony export */ }); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); +/* harmony import */ var _reacttools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./reacttools */ "./src/modules/reacttools.js"); +/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js"); +/** + * BetterDiscord React Component Manipulations + * Original concept and some code by samogot - https://github.com/samogot / https://github.com/samogot/betterdiscord-plugins/tree/master/v2/1Lib%20Discord%20Internals + * + * Copyright (c) 2015-present JsSucks - https://github.com/JsSucks + * All rights reserved. + * https://github.com/JsSucks - https://betterdiscord.net + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. +*/ + + + + + +class ReactComponent { + constructor(id, component, selector, filter) { + this.id = id; + this.component = component; + this.selector = selector; + this.filter = filter; + } + + forceUpdateAll() { + if (!this.selector) return; + for (const e of document.querySelectorAll(this.selector)) { + const stateNode = _utilities__WEBPACK_IMPORTED_MODULE_2__["default"].findInTree(_reacttools__WEBPACK_IMPORTED_MODULE_1__["default"].getReactInstance(e), m => m && m.forceUpdate, {walkable: ["return", "stateNode"]}); + if (!stateNode) continue; + stateNode.forceUpdate(); + } + } +} + +/** + * Methods for obtaining and interacting with react components. + * @module ReactComponents + */ +class ReactComponents { + static get components() {return this._components || (this._components = new Map());} + static get unknownComponents() {return this._unknownComponents || (this._unknownComponents = new Set());} + static get listeners() {return this._listeners || (this._listeners = new Map());} + static get nameSetters() {return this._nameSetters || (this._nameSetters = new Set());} + + static get ReactComponent() {return ReactComponent;} + + static push(component, selector, filter) { + if (typeof(component) !== "function") return null; + const {displayName} = component; + if (!displayName) return this.processUnknown(component); + + const have = this.components.get(displayName); + if (have) { + if (!have.selector) have.selector = selector; + if (!have.filter) have.filter = filter; + return component; + } + + const c = new ReactComponent(displayName, component, selector, filter); + this.components.set(c.id, c); + + const listener = this.listeners.get(displayName); + if (listener) { + for (const l of listener.children) l(c); + this.listeners.delete(listener); + } + + return c; + } + + /** + * Finds a component from the components array or by waiting for it to be mounted. + * @param {String} name The component's name + * @param {Object} selector A selector to look for + * @return {Promise} + */ + static async getComponentByName(name, selector) { + return this.getComponent(name, selector, m => m.displayName == name); + } + + /** + * Finds a component from the components array or by waiting for it to be mounted. + * @param {String} name The component's name + * @param {Object} selector A selector to look for + * @param {Function} filter A function to filter components if a single element is rendered by multiple components + * @return {Promise} + */ + static async getComponent(name, selector, filter) { + const have = this.components.get(name); + if (have) { + if (!have.selector) have.selector = selector; + if (!have.filter) have.filter = filter; + return have; + } + + if (selector) { + const callback = () => { + if (this.components.get(name)) { + _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].observer.unsubscribe(observerSubscription); + return; + } + + const elements = document.querySelectorAll(selector); + if (!elements.length) return; + + let component; + for (const element of elements) { + const componentsFound = _reacttools__WEBPACK_IMPORTED_MODULE_1__["default"].getComponents(element); + component = filter ? componentsFound.find(filter) : componentsFound[0]; + if (component) break; + } + + if (!component && filter) return; + + _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].observer.unsubscribe(observerSubscription); + + if (!component) return; + + if (!component.displayName) component.displayName = name; + + this.push(component, selector, filter); + }; + + const observerSubscription = _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].observer.subscribeToQuerySelector(callback, selector, null, true); + setTimeout(callback, 0); + } + + let listener = this.listeners.get(name); + if (!listener) { + listener = { + id: name, + children: [], + filter + }; + this.listeners.set(name, listener); + } + + + return new Promise(resolve => { + listener.children.push(resolve); + }); + } + + static setName(name, filter) { + const have = this.components.get(name); + if (have) return have; + + for (const component of this.unknownComponents.entries()) { + if (!filter(component)) continue; + component.displayName = name; + this.unknownComponents.delete(component); + return this.push(component); + } + return this.nameSetters.add({name, filter}); + } + + static processUnknown(component) { + const have = this.unknownComponents.has(component); + for (const setter of this.nameSetters.entries()) { + if (setter.filter.filter(component)) { + component.displayName = setter.name; + this.nameSetters.delete(setter); + return this.push(component); + } + } + if (have) return have; + this.unknownComponents.add(component); + return component; + } + + static *recursiveComponents(internalInstance = _reacttools__WEBPACK_IMPORTED_MODULE_1__["default"].rootInstance) { + if (internalInstance.stateNode) yield internalInstance.stateNode; + if (internalInstance.sibling) yield* this.recursiveComponents(internalInstance.sibling); + if (internalInstance.child) yield* this.recursiveComponents(internalInstance.child); + } +} + + +/***/ }), + +/***/ "./src/modules/reacttools.js": +/*!***********************************!*\ + !*** ./src/modules/reacttools.js ***! + \***********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ ReactTools) +/* harmony export */ }); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); +/* harmony import */ var _discordmodules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./discordmodules */ "./src/modules/discordmodules.js"); +/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js"); +/** + * Helpful utilities for dealing with getting react information from DOM objects. + * @module ReactTools + */ + + + + + +class ReactTools { + + static get rootInstance() {return document.getElementById("app-mount")._reactRootContainer._internalRoot.current;} + + /** + * Grabs the react internal instance of a specific node. + * @param {(HTMLElement|jQuery)} node - node to obtain react instance of + * @return {object} the internal react instance + */ + static getReactInstance(node) { + const domNode = _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].resolveElement(node); + if (!(domNode instanceof Element)) return undefined; + return domNode[Object.keys(domNode).find((key) => key.startsWith("__reactInternalInstance") || key.startsWith("__reactFiber"))]; + } + + /** + * Grabs a value from the react internal instance. Allows you to grab + * long depth values safely without accessing no longer valid properties. + * @param {(HTMLElement|jQuery)} node - node to obtain react instance of + * @param {string} path - path to the requested value + * @return {(*|undefined)} the value requested or undefined if not found. + */ + static getReactProperty(node, path) { + return _utilities__WEBPACK_IMPORTED_MODULE_2__["default"].getNestedProp(this.getReactInstance(node), path); + } + + /** + * Grabs a value from the react internal instance. Allows you to grab + * long depth values safely without accessing no longer valid properties. + * @param {(HTMLElement|jQuery)} node - node to obtain react instance of + * @param {object} options - options for the search + * @param {array} [options.include] - list of items to include from the search + * @param {array} [options.exclude=["Popout", "Tooltip", "Scroller", "BackgroundFlash"]] - list of items to exclude from the search + * @param {callable} [options.filter=_=>_] - filter to check the current instance with (should return a boolean) + * @return {(*|null)} the owner instance or undefined if not found. + */ + static getOwnerInstance(node, {include, exclude = ["Popout", "Tooltip", "Scroller", "BackgroundFlash"], filter = _ => _} = {}) { + if (node === undefined) return undefined; + const excluding = include === undefined; + const nameFilter = excluding ? exclude : include; + function getDisplayName(owner) { + const type = owner.type; + if (!type) return null; + return type.displayName || type.name || null; + } + function classFilter(owner) { + const name = getDisplayName(owner); + return (name !== null && !!(nameFilter.includes(name) ^ excluding)); + } + + let curr = this.getReactInstance(node); + for (curr = curr && curr.return; !_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].isNil(curr); curr = curr.return) { + if (_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].isNil(curr)) continue; + const owner = curr.stateNode; + if (!_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].isNil(owner) && !(owner instanceof HTMLElement) && classFilter(curr) && filter(owner)) return owner; + } + + return null; + } + + /** + * Grabs the react internal state node trees of a specific node. + * @param {(HTMLElement|jQuery)} node - node to obtain state nodes of + * @return {Array} list of found state nodes + */ + static getStateNodes(node) { + const instance = this.getReactInstance(node); + const stateNodes = []; + let lastInstance = instance; + while (lastInstance && lastInstance.return) { + if (lastInstance.return.stateNode instanceof HTMLElement) break; + if (lastInstance.return.stateNode) stateNodes.push(lastInstance.return.stateNode); + lastInstance = lastInstance.return; + } + return stateNodes; + } + + /** + * Grabs the react internal component tree of a specific node. + * @param {(HTMLElement|jQuery)} node - node to obtain react components of + * @return {Array} list of found react components + */ + static getComponents(node) { + const instance = this.getReactInstance(node); + const components = []; + let lastInstance = instance; + while (lastInstance && lastInstance.return) { + if (typeof lastInstance.return.type === "string") break; + if (lastInstance.return.type) components.push(lastInstance.return.type); + lastInstance = lastInstance.return; + } + return components; + } + + /** + * Creates and renders a react element that wraps dom elements. + * @param {(HTMLElement|Array)} element - element or array of elements to wrap into a react element + * @returns {object} - rendered react element + */ + static createWrappedElement(element) { + if (Array.isArray(element)) element = _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].wrap(element); + return _discordmodules__WEBPACK_IMPORTED_MODULE_1__["default"].React.createElement(this.wrapElement(element)); + } + + /** + * Creates an unrendered react component that wraps dom elements. + * @param {(HTMLElement|Array)} element - element or array of elements to wrap into a react component + * @returns {object} - unrendered react component + */ + static wrapElement(element) { + if (Array.isArray(element)) element = _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].wrap(element); + return class ReactWrapper extends _discordmodules__WEBPACK_IMPORTED_MODULE_1__["default"].React.Component { + constructor(props) { + super(props); + this.element = element; + } + + componentDidMount() {this.refs.element.appendChild(this.element);} + render() {return _discordmodules__WEBPACK_IMPORTED_MODULE_1__["default"].React.createElement("div", {className: "react-wrapper", ref: "element"});} + }; + } +} + +/***/ }), + +/***/ "./src/modules/utilities.js": +/*!**********************************!*\ + !*** ./src/modules/utilities.js ***! + \**********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Utilities) +/* harmony export */ }); +/* harmony import */ var _logger__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./logger */ "./src/modules/logger.js"); +/** + * Random set of utilities that didn't fit elsewhere. + * @module Utilities + */ + + + +class Utilities { + + /** + * Stably sorts arrays since `.sort()` has issues. + * @param {Array} list - array to sort + * @param {function} comparator - comparator to sort by + */ + static stableSort(list, comparator) { + const entries = Array(list.length); + + // wrap values with initial indices + for (let index = 0; index < list.length; index++) { + entries[index] = [index, list[index]]; + } + + // sort with fallback based on initial indices + entries.sort(function (a, b) { + const comparison = Number(this(a[1], b[1])); + return comparison || a[0] - b[0]; + }.bind(comparator)); + + // re-map original array to stable sorted values + for (let index = 0; index < list.length; index++) { + list[index] = entries[index][1]; + } + } + + /** + * Generates an automatically memoizing version of an object. + * @param {Object} object - object to memoize + * @returns {Proxy} the proxy to the object that memoizes properties + */ + static memoizeObject(object) { + const proxy = new Proxy(object, { + get: function(obj, mod) { + if (!obj.hasOwnProperty(mod)) return undefined; + if (Object.getOwnPropertyDescriptor(obj, mod).get) { + const value = obj[mod]; + delete obj[mod]; + obj[mod] = value; + } + return obj[mod]; + }, + set: function(obj, mod, value) { + if (obj.hasOwnProperty(mod)) return _logger__WEBPACK_IMPORTED_MODULE_0__["default"].err("MemoizedObject", "Trying to overwrite existing property"); + obj[mod] = value; + return obj[mod]; + } + }); + + Object.defineProperty(proxy, "hasOwnProperty", {value: function(prop) { + return this[prop] !== undefined; + }}); + + return proxy; + } + + /** + * Wraps the method in a `try..catch` block. + * @param {callable} method - method to wrap + * @param {string} description - description of method + * @returns {callable} wrapped version of method + */ + static suppressErrors(method, description) { + return (...params) => { + try {return method(...params);} + catch (e) {_logger__WEBPACK_IMPORTED_MODULE_0__["default"].err("Suppression", "Error occurred in " + description, e);} + }; + } + + /** + * This only exists because Samo relied on lodash being there... fuck lodash. + * @param {*} anything - whatever you want + */ + static isNil(anything) { + return anything === null; + } + + /** + * Format template strings with placeholders (`${placeholder}`) into full strings. + * Quick example: `Utilities.formatString("Hello, ${user}", {user: "Zerebos"})` + * would return "Hello, Zerebos". + * @param {string} string - string to format + * @param {object} values - object literal of placeholders to replacements + * @returns {string} the properly formatted string + */ + static formatTString(string, values) { + for (const val in values) { + let replacement = values[val]; + if (Array.isArray(replacement)) replacement = JSON.stringify(replacement); + if (typeof(replacement) === "object" && replacement !== null) replacement = replacement.toString(); + string = string.replace(new RegExp(`\\$\\{${val}\\}`, "g"), replacement); + } + return string; + } + + /** + * Format strings with placeholders (`{{placeholder}}`) into full strings. + * Quick example: `Utilities.formatString("Hello, {{user}}", {user: "Zerebos"})` + * would return "Hello, Zerebos". + * @param {string} string - string to format + * @param {object} values - object literal of placeholders to replacements + * @returns {string} the properly formatted string + */ + static formatString(string, values) { + for (const val in values) { + let replacement = values[val]; + if (Array.isArray(replacement)) replacement = JSON.stringify(replacement); + if (typeof(replacement) === "object" && replacement !== null) replacement = replacement.toString(); + string = string.replace(new RegExp(`{{${val}}}`, "g"), replacement); + } + return string; + } + + /** + * Finds a value, subobject, or array from a tree that matches a specific filter. Great for patching render functions. + * @param {object} tree React tree to look through. Can be a rendered object or an internal instance. + * @param {callable} searchFilter Filter function to check subobjects against. + */ + static findInReactTree(tree, searchFilter) { + return this.findInTree(tree, searchFilter, {walkable: ["props", "children", "child", "sibling"]}); + } + + /** + * Finds a value, subobject, or array from a tree that matches a specific filter. + * @param {object} tree Tree that should be walked + * @param {callable} searchFilter Filter to check against each object and subobject + * @param {object} options Additional options to customize the search + * @param {Array|null} [options.walkable=null] Array of strings to use as keys that are allowed to be walked on. Null value indicates all keys are walkable + * @param {Array} [options.ignore=[]] Array of strings to use as keys to exclude from the search, most helpful when `walkable = null`. + */ + static findInTree(tree, searchFilter, {walkable = null, ignore = []} = {}) { + if (typeof searchFilter === "string") { + if (tree.hasOwnProperty(searchFilter)) return tree[searchFilter]; + } + else if (searchFilter(tree)) { + return tree; + } + + if (typeof tree !== "object" || tree == null) return undefined; + + let tempReturn; + if (Array.isArray(tree)) { + for (const value of tree) { + tempReturn = this.findInTree(value, searchFilter, {walkable, ignore}); + if (typeof tempReturn != "undefined") return tempReturn; + } + } + else { + const toWalk = walkable == null ? Object.keys(tree) : walkable; + for (const key of toWalk) { + if (!tree.hasOwnProperty(key) || ignore.includes(key)) continue; + tempReturn = this.findInTree(tree[key], searchFilter, {walkable, ignore}); + if (typeof tempReturn != "undefined") return tempReturn; + } + } + return tempReturn; + } + + /** + * Gets a nested property (if it exists) safely. Path should be something like `prop.prop2.prop3`. + * Numbers can be used for arrays as well like `prop.prop2.array.0.id`. + * @param {Object} obj - object to get nested property of + * @param {string} path - representation of the property to obtain + */ + static getNestedProp(obj, path) { + return path.split(".").reduce(function(ob, prop) { + return ob && ob[prop]; + }, obj); + } + + /** + * Builds a classname string from any number of arguments. This includes arrays and objects. + * When given an array all values from the array are added to the list. + * When given an object they keys are added as the classnames if the value is truthy. + * Copyright (c) 2018 Jed Watson https://github.com/JedWatson/classnames MIT License + * @param {...Any} argument - anything that should be used to add classnames. + */ + static className() { + const classes = []; + const hasOwn = {}.hasOwnProperty; + + for (let i = 0; i < arguments.length; i++) { + const arg = arguments[i]; + if (!arg) continue; + + const argType = typeof arg; + + if (argType === "string" || argType === "number") { + classes.push(arg); + } + else if (Array.isArray(arg) && arg.length) { + const inner = this.classNames.apply(null, arg); + if (inner) { + classes.push(inner); + } + } + else if (argType === "object") { + for (const key in arg) { + if (hasOwn.call(arg, key) && arg[key]) { + classes.push(key); + } + } + } + } + + return classes.join(" "); + } + + /** + * Safely adds to the prototype of an existing object by checking if the + * property exists on the prototype. + * @param {object} object - Object whose prototype to extend + * @param {string} prop - Name of the prototype property to add + * @param {callable} func - Function to run + */ + static addToPrototype(object, prop, func) { + if (!object.prototype) return; + if (object.prototype[prop]) return; + return object.prototype[prop] = func; + } + + /** + * Deep extends an object with a set of other objects. Objects later in the list + * of `extenders` have priority, that is to say if one sets a key to be a primitive, + * it will be overwritten with the next one with the same key. If it is an object, + * and the keys match, the object is extended. This happens recursively. + * @param {object} extendee - Object to be extended + * @param {...object} extenders - Objects to extend with + * @returns {object} - A reference to `extendee` + */ + static extend(extendee, ...extenders) { + for (let i = 0; i < extenders.length; i++) { + for (const key in extenders[i]) { + if (extenders[i].hasOwnProperty(key)) { + if (Array.isArray(extendee[key]) && Array.isArray(extenders[i][key])) this.extend(extendee[key], extenders[i][key]); + else if (this.isNil(extenders[i][key])) extendee[key] = extenders[i][key]; + else if (typeof extendee[key] === "object" && typeof extenders[i][key] === "object") this.extend(extendee[key], extenders[i][key]); + else if (Array.isArray(extenders[i][key])) extendee[key] = [], this.extend(extendee[key], extenders[i][key]); // eslint-disable-line no-sequences + else if (typeof extenders[i][key] === "object") extendee[key] = {}, this.extend(extendee[key], extenders[i][key]); // eslint-disable-line no-sequences + else extendee[key] = extenders[i][key]; + } + } + } + return extendee; + } + + /* Code below comes from our work on BDv2: + * https://github.com/JsSucks/BetterDiscordApp/blob/master/common/modules/utils.js + */ + + /** + * Clones an object and all it's properties. + * @param {Any} value The value to clone + * @return {Any} The cloned value + */ + static deepclone(value) { + if (this.isNil(value)) return value; + if (typeof value === "object") { + if (Array.isArray(value)) return value.map(i => this.deepclone(i)); + + const clone = Object.assign({}, value); + + for (const key in clone) { + clone[key] = this.deepclone(clone[key]); + } + + return clone; + } + + return value; + } + + /** + * Freezes an object and all it's properties. + * @param {Any} object The object to freeze + * @param {Function} exclude A function to filter object that shouldn't be frozen + */ + static deepfreeze(object, exclude) { + if (exclude && exclude(object)) return; + + if (typeof object === "object" && object !== null) { + const properties = Object.getOwnPropertyNames(object); + + for (const property of properties) { + this.deepfreeze(object[property], exclude); + } + + Object.freeze(object); + } + + return object; + } + + /** + * Removes an item from an array. This differs from Array.prototype.filter as it mutates the original array instead of creating a new one. + * @param {Array} array The array to filter + * @param {Any} item The item to remove from the array + * @return {Array} + */ + static removeFromArray(array, item, filter) { + let index; + while ((index = filter ? array.findIndex(item) : array.indexOf(item)) > -1) array.splice(index, 1); + return array; + } + + /** + * Returns a function, that, as long as it continues to be invoked, will not + * be triggered. The function will be called after it stops being called for + * N milliseconds. + * + * Adapted from the version by David Walsh (https://davidwalsh.name/javascript-debounce-function) + * + * @param {function} executor + * @param {number} delay + */ + static debounce(executor, delay) { + let timeout; + return function(...args) { + const callback = () => { + timeout = null; + Reflect.apply(executor, null, args); + }; + clearTimeout(timeout); + timeout = setTimeout(callback, delay); + }; + } + + /** + * Loads data through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {string} key - which key the data is saved under + * @param {object} defaultData - default data to populate the object with + * @returns {object} the combined saved and default data + */ + static loadData(name, key, defaultData = {}) { + const defaults = this.deepclone(defaultData); + try { + const storedData = BdApi.Data.load(name, key); + if (typeof(defaults) === "object") return this.extend(defaults, storedData); + return this.isNil(storedData) || typeof(storedData) === "undefined" ? defaults : storedData; + } + catch (err) { + _logger__WEBPACK_IMPORTED_MODULE_0__["default"].err(name, "Unable to load data: ", err); + } + return defaults; + } + + /** + * Saves data through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {string} key - which key the data should be saved under + * @param {object} data - data to save + */ + static saveData(name, key, data) { + try {BdApi.Data.save(name, key, data);} + catch (err) {_logger__WEBPACK_IMPORTED_MODULE_0__["default"].err(name, "Unable to save data: ", err);} + } + + /** + * Loads settings through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {object} defaultData - default data to populate the object with + * @returns {object} the combined saved and default settings + */ + static loadSettings(name, defaultSettings) { + return this.loadData(name, "settings", defaultSettings); + } + + /** + * Saves settings through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {object} data - settings to save + */ + static saveSettings(name, data) { + this.saveData(name, "settings", data); + } + +} + +/***/ }), + +/***/ "./src/modules/webpackmodules.js": +/*!***************************************!*\ + !*** ./src/modules/webpackmodules.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ Filters: () => (/* binding */ Filters), +/* harmony export */ "default": () => (/* binding */ WebpackModules) +/* harmony export */ }); +/* harmony import */ var _discordmodules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./discordmodules */ "./src/modules/discordmodules.js"); +/* harmony import */ var _logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./logger */ "./src/modules/logger.js"); +/** + * Random set of utilities that didn't fit elsewhere. + * @module WebpackModules + */ + + + + /** + * Checks if a given module matches a set of parameters. + * @callback module:WebpackModules.Filters~filter + * @param {*} module - module to check + * @returns {boolean} - True if the module matches the filter, false otherwise + */ + +/** + * Filters for use with {@link module:WebpackModules} but may prove useful elsewhere. + */ +class Filters { + /** + * Generates a {@link module:WebpackModules.Filters~filter} that filters by a set of properties. + * @param {Array} props - Array of property names + * @param {module:WebpackModules.Filters~filter} filter - Additional filter + * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties + */ + static byProperties(props, filter = m => m) { + return module => { + const component = filter(module); + if (!component) return false; + for (let p = 0; p < props.length; p++) { + if (module[props[p]] === undefined) return false; + } + return true; + }; + } + + /** + * Generates a {@link module:WebpackModules.Filters~filter} that filters by a set of properties on the object's prototype. + * @param {Array} fields - Array of property names + * @param {module:WebpackModules.Filters~filter} filter - Additional filter + * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties on the object's prototype + */ + static byPrototypeFields(fields, filter = m => m) { + return module => { + const component = filter(module); + if (!component) return false; + if (!component.prototype) return false; + for (let f = 0; f < fields.length; f++) { + if (module.prototype[fields[f]] === undefined) return false; + } + return true; + }; + } + + /** + * Generates a {@link module:WebpackModules.Filters~filter} that filters by a regex. + * @param {RegExp} search - A RegExp to check on the module + * @param {module:WebpackModules.Filters~filter} filter - Additional filter + * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties + */ + static byCode(search, filter = m => m) { + return module => { + const method = filter(module); + if (!method) return false; + let methodString = ""; + try {methodString = method.toString([]);} + catch (err) {methodString = method.toString();} + return methodString.search(search) !== -1; + }; + } + + /** + * Generates a {@link module:WebpackModules.Filters~filter} that filters by strings. + * @param {...String} search - A RegExp to check on the module + * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of strings + */ + static byString(...strings) { + return module => { + let moduleString = ""; + try {moduleString = module.toString([]);} + catch (err) {moduleString = module.toString();} + for (const s of strings) { + if (!moduleString.includes(s)) return false; + } + return true; + }; + } + + /** + * Generates a {@link module:WebpackModules.Filters~filter} that filters by a set of properties. + * @param {string} name - Name the module should have + * @param {module:WebpackModules.Filters~filter} filter - Additional filter + * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties + */ + static byDisplayName(name) { + return module => { + return module && module.displayName === name; + }; + } + + /** + * Generates a combined {@link module:WebpackModules.Filters~filter} from a list of filters. + * @param {...module:WebpackModules.Filters~filter} filters - A list of filters + * @returns {module:WebpackModules.Filters~filter} - Combinatory filter of all arguments + */ + static combine(...filters) { + return module => { + return filters.every(filter => filter(module)); + }; + } +} + +class WebpackModules { + + static find(filter, first = true) {return this.getModule(filter, first);} + static findAll(filter) {return this.getModule(filter, false);} + static findByUniqueProperties(props, first = true) {return first ? this.getByProps(...props) : this.getAllByProps(...props);} + static findByDisplayName(name) {return this.getByDisplayName(name);} + + /** + * Finds a module using a filter function. + * @param {Function} filter A function to use to filter modules + * @param {Boolean|object} first Whether to return only the first matching module or options object matching BD's options + * @return {Any} + */ + static getModule(filter, first = true) { + const options = typeof(first) === "object" ? first : {first}; + return BdApi.Webpack.getModule(filter, options); + } + + static getIndex() { + return null; + } + + static getIndexByModule() { + return null; + } + + /** + * Finds all modules matching a filter function. + * @param {Function} filter A function to use to filter modules + */ + static getModules(filter) {return this.getModule(filter, false);} + + /** + * Finds a module by its name. + * @param {String} name The name of the module + * @param {Function} fallback A function to use to filter modules if not finding a known module + * @return {Any} + */ + static getModuleByName(name, fallback) { + if (_discordmodules__WEBPACK_IMPORTED_MODULE_0__["default"].hasOwnProperty(name)) return _discordmodules__WEBPACK_IMPORTED_MODULE_0__["default"][name]; + if (!fallback) return undefined; + const module = this.getModule(fallback); + return module ? _discordmodules__WEBPACK_IMPORTED_MODULE_0__["default"][name] = module : undefined; + } + + /** + * Finds a module by its display name. + * @param {String} name The display name of the module + * @return {Any} + */ + static getByDisplayName(name) { + return this.getModule(Filters.byDisplayName(name)); + } + + /** + * Finds a module using its code. + * @param {RegEx} regex A regular expression to use to filter modules + * @param {Boolean} first Whether to return the only the first matching module + * @return {Any} + */ + static getByRegex(regex, first = true) { + return this.getModule(Filters.byCode(regex), first); + } + + /** + * Finds a single module using properties on its prototype. + * @param {...string} prototypes Properties to use to filter modules + * @return {Any} + */ + static getByPrototypes(...prototypes) { + return this.getModule(Filters.byPrototypeFields(prototypes), true); + } + + /** + * Finds all modules with a set of properties of its prototype. + * @param {...string} prototypes Properties to use to filter modules + * @return {Any} + */ + static getAllByPrototypes(...prototypes) { + return this.getModule(Filters.byPrototypeFields(prototypes), false); + } + + /** + * Finds a single module using its own properties. + * @param {...string} props Properties to use to filter modules + * @return {Any} + */ + static getByProps(...props) { + return this.getModule(Filters.byProperties(props), true); + } + + /** + * Finds all modules with a set of properties. + * @param {...string} props Properties to use to filter modules + * @return {Any} + */ + static getAllByProps(...props) { + return this.getModule(Filters.byProperties(props), false); + } + + /** + * Finds a single module using a set of strings. + * @param {...String} props Strings to use to filter modules + * @return {Any} + */ + static getByString(...strings) { + return this.getModule(Filters.byString(...strings), true); + } + + /** + * Finds all modules with a set of strings. + * @param {...String} strings Strings to use to filter modules + * @return {Any} + */ + static getAllByString(...strings) { + return this.getModule(Filters.byString(...strings), false); + } + + /** + * Gets a specific module by index of the webpack require cache. + * Best used in combination with getIndex in order to patch a + * specific function. + * + * Note: this gives the **raw** module, meaning the actual module + * is in returnValue.exports. This is done in order to be able + * to patch modules which export a single function directly. + * @param {Number} index Index into the webpack require cache + * @return {Any} + */ + static getByIndex(index) { + return WebpackModules.require.c[index].exports; + } + + /** + * Discord's __webpack_require__ function. + */ + static get require() { + if (this._require) return this._require; + const __nested_webpack_require_8993__ = window.webpackChunkdiscord_app.push([[Symbol()], {}, r=> r]); + window.webpackChunkdiscord_app.pop(); + return this._require = __nested_webpack_require_8993__; + } + + /** + * Returns all loaded modules. + * @return {Array} + */ + static getAllModules() { + return this.require.c; + } + + + + // Webpack Chunk Observing + static get chunkName() {return "webpackChunkdiscord_app";} + + static initialize() { + this.handlePush = this.handlePush.bind(this); + this.listeners = new Set(); + + this.__ORIGINAL_PUSH__ = window[this.chunkName].push; + Object.defineProperty(window[this.chunkName], "push", { + configurable: true, + get: () => this.handlePush, + set: (newPush) => { + this.__ORIGINAL_PUSH__ = newPush; + + Object.defineProperty(window[this.chunkName], "push", { + value: this.handlePush, + configurable: true, + writable: true + }); + } + }); + } + + /** + * Adds a listener for when discord loaded a chunk. Useful for subscribing to lazy loaded modules. + * @param {Function} listener - Function to subscribe for chunks + * @returns {Function} A cancelling function + */ + static addListener(listener) { + this.listeners.add(listener); + return this.removeListener.bind(this, listener); + } + + /** + * Removes a listener for when discord loaded a chunk. + * @param {Function} listener + * @returns {boolean} + */ + static removeListener(listener) {return this.listeners.delete(listener);} + + static handlePush(chunk) { + const [, modules] = chunk; + + for (const moduleId in modules) { + const originalModule = modules[moduleId]; + + modules[moduleId] = (module, exports, require) => { + try { + Reflect.apply(originalModule, null, [module, exports, require]); + + const listeners = [...this.listeners]; + for (let i = 0; i < listeners.length; i++) { + try {listeners[i](exports, originalModule, moduleId);} + catch (error) { + _logger__WEBPACK_IMPORTED_MODULE_1__["default"].err("WebpackModules", "Could not fire callback listener:", error); + } + } + } + catch (error) { + _logger__WEBPACK_IMPORTED_MODULE_1__["default"].stacktrace("WebpackModules", "Error patching chunked module push", error); + } + }; + + Object.assign(modules[moduleId], originalModule, { + toString: () => originalModule.toString() + }); + } + + return Reflect.apply(this.__ORIGINAL_PUSH__, window[this.chunkName], [chunk]); + } + +} + +WebpackModules.initialize(); + +/***/ }), + +/***/ "./src/structs/dom/classname.js": +/*!**************************************!*\ + !*** ./src/structs/dom/classname.js ***! + \**************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _selector__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./selector */ "./src/structs/dom/selector.js"); + + +/** + * Representation of a Class Name + * @memberof module:DOMTools + **/ +class ClassName { + /** + * + * @param {string} name - name of the class to represent + */ + constructor(name) { + this.value = name; + } + + /** + * Concatenates new class names to the current one using spaces. + * @param {string} classNames - list of class names to add to this class name + * @returns {ClassName} returns self to allow chaining + */ + add(...classNames) { + for (let i = 0; i < classNames.length; i++) this.value += " " + classNames[i]; + return this; + } + + /** + * Returns the raw class name, this is how native function get the value. + * @returns {string} raw class name. + */ + toString() { + return this.value; + } + + /** + * Returns the raw class name, this is how native function get the value. + * @returns {string} raw class name. + */ + valueOf() { + return this.value; + } + + /** + * Returns the classname represented as {@link module:DOMTools.Selector}. + * @returns {Selector} selector representation of this class name. + */ + get selector() { + return new _selector__WEBPACK_IMPORTED_MODULE_0__["default"](this.value); + } + + get single() { + return this.value.split(" ")[0]; + } + + get first() { + return this.value.split(" ")[0]; + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ClassName); + +/***/ }), + +/***/ "./src/structs/dom/observer.js": +/*!*************************************!*\ + !*** ./src/structs/dom/observer.js ***! + \*************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/** + * BetterDiscord Client DOM Module + * Copyright (c) 2015-present JsSucks - https://github.com/JsSucks + * All rights reserved. + * https://betterdiscord.net + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. +*/ + + + +/* eslint-disable operator-linebreak */ + +/** + * Representation of a MutationObserver but with helpful utilities. + * @memberof module:DOMTools + **/ +class DOMObserver { + constructor(root, options) { + this.observe = this.observe.bind(this); + this.subscribe = this.subscribe.bind(this); + this.observerCallback = this.observerCallback.bind(this); + + this.active = false; + this.root = root || document.getElementById("app-mount"); + this.options = options || {attributes: true, childList: true, subtree: true}; + + this.observer = new MutationObserver(this.observerCallback); + this.observe(); + } + + observerCallback(mutations) { + for (const sub of Array.from(this.subscriptions)) { + try { + const filteredMutations = sub.filter ? mutations.filter(sub.filter) : mutations; + + if (sub.group) { + if (!filteredMutations.length) continue; + sub.callback.call(sub.bind || sub, filteredMutations); + } + else { + for (const mutation of filteredMutations) sub.callback.call(sub.bind || sub, mutation); + } + } + catch (err) { + modules__WEBPACK_IMPORTED_MODULE_0__.Logger.stacktrace("DOMObserver", "Error in observer callback", err); + } + } + } + + /** + * Starts observing the element. This will be called when attaching a callback. + * You don't need to call this manually. + */ + observe() { + if (this.active) return; + this.observer.observe(this.root, this.options); + this.active = true; + } + + /** + * Disconnects this observer. This stops callbacks being called, but does not unbind them. + * You probably want to use observer.unsubscribeAll instead. + */ + disconnect() { + if (!this.active) return; + this.observer.disconnect(); + this.active = false; + } + + reconnect() { + if (this.active) { + this.disconnect(); + this.observe(); + } + } + + get root() {return this._root;} + set root(root) {this._root = root; this.reconnect();} + + get options() {return this._options;} + set options(options) {this._options = options; this.reconnect();} + + get subscriptions() { + return this._subscriptions || (this._subscriptions = []); + } + + /** + * Subscribes to mutations. + * @param {Function} callback A function to call when on a mutation + * @param {Function} filter A function to call to filter mutations + * @param {Any} bind Something to bind the callback to + * @param {Boolean} group Whether to call the callback with an array of mutations instead of a single mutation + * @return {Object} + */ + subscribe(callback, filter, bind, group) { + const subscription = {callback, filter, bind, group}; + this.subscriptions.push(subscription); + this.observe(); + return subscription; + } + + /** + * Removes a subscription and disconnect if there are none left. + * @param {Object} subscription A subscription object returned by observer.subscribe + */ + unsubscribe(subscription) { + if (!this.subscriptions.includes(subscription)) subscription = this.subscriptions.find(s => s.callback === subscription); + modules__WEBPACK_IMPORTED_MODULE_0__.Utilities.removeFromArray(this.subscriptions, subscription); + if (!this.subscriptions.length) this.disconnect(); + } + + unsubscribeAll() { + this.subscriptions.splice(0, this.subscriptions.length); + this.disconnect(); + } + + /** + * Subscribes to mutations that affect an element matching a selector. + * @param {Function} callback A function to call when on a mutation + * @param {Function} filter A function to call to filter mutations + * @param {Any} bind Something to bind the callback to + * @param {Boolean} group Whether to call the callback with an array of mutations instead of a single mutation + * @return {Object} + */ + subscribeToQuerySelector(callback, selector, bind, group) { + return this.subscribe(callback, mutation => { + return mutation.target.matches(selector) // If the target matches the selector + || Array.from(mutation.addedNodes).concat(Array.from(mutation.removedNodes)) // Or if either an added or removed node + .find(n => n instanceof Element && (n.matches(selector) || n.querySelector(selector))); // match or contain an element matching the selector + }, bind, group); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (DOMObserver); + +/***/ }), + +/***/ "./src/structs/dom/selector.js": +/*!*************************************!*\ + !*** ./src/structs/dom/selector.js ***! + \*************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Representation of a Selector + * @memberof module:DOMTools + **/ +class Selector { + /** + * + * @param {string} classname - class to create selector for + */ + constructor(className) { + this.value = " ." + className.split(" ").join("."); + } + + /** + * Returns the raw selector, this is how native function get the value. + * @returns {string} raw selector. + */ + toString() { + return this.value; + } + + /** + * Returns the raw selector, this is how native function get the value. + * @returns {string} raw selector. + */ + valueOf() { + return this.value; + } + + selector(symbol, other) { + this.value = `${this.toString()} ${symbol} ${other.toString()}`; + return this; + } + + /** + * Adds another selector as a direct child `>` to this one. + * @param {string|DOMTools.Selector} other - Selector to add as child + * @returns {DOMTools.Selector} returns self to allow chaining + */ + child(other) { + return this.selector(">", other); + } + + /** + * Adds another selector as a adjacent sibling `+` to this one. + * @param {string|DOMTools.Selector} other - Selector to add as adjacent sibling + * @returns {DOMTools.Selector} returns self to allow chaining + */ + adjacent(other) { + return this.selector("+", other); + } + + /** + * Adds another selector as a general sibling `~` to this one. + * @param {string|DOMTools.Selector} other - Selector to add as sibling + * @returns {DOMTools.Selector} returns self to allow chaining + */ + sibling(other) { + return this.selector("~", other); + } + + /** + * Adds another selector as a descendent `(space)` to this one. + * @param {string|DOMTools.Selector} other - Selector to add as descendent + * @returns {DOMTools.Selector} returns self to allow chaining + */ + descend(other) { + return this.selector(" ", other); + } + + /** + * Adds another selector to this one via `,`. + * @param {string|DOMTools.Selector} other - Selector to add + * @returns {DOMTools.Selector} returns self to allow chaining + */ + and(other) { + return this.selector(",", other); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Selector); + +/***/ }), + +/***/ "./src/structs/listenable.js": +/*!***********************************!*\ + !*** ./src/structs/listenable.js ***! + \***********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Acts as an interface for anything that should be listenable. + */ +class Listenable { + + constructor() { + this.listeners = []; + } + + /** + * Adds a listener to the current object. + * @param {callable} callback - callback for when the event occurs + * @returns {callable} - a way to cancel the listener without needing to call `removeListener` + */ + addListener(callback) { + if (typeof(callback) !== "function") return; + this.listeners.push(callback); + return () => { + this.listeners.splice(this.listeners.indexOf(callback), 1); + }; + } + + /** + * Removes a listener from the current object. + * @param {callable} callback - callback that was originally registered + */ + removeListener(callback) { + if (typeof(callback) !== "function") return; + this.listeners.splice(this.listeners.indexOf(callback), 1); + } + + /** + * Alerts the listeners that an event occurred. Data passed is optional + * @param {*} [...data] - Any data desired to be passed to listeners + */ + alertListeners(...data) { + for (let l = 0; l < this.listeners.length; l++) this.listeners[l](...data); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Listenable); + +/***/ }), + +/***/ "./src/structs/plugin.js": +/*!*******************************!*\ + !*** ./src/structs/plugin.js ***! + \*******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Plugin), +/* harmony export */ wrapPluginBase: () => (/* binding */ wrapPluginBase) +/* harmony export */ }); +/* harmony import */ var _modules_pluginupdater__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../modules/pluginupdater */ "./src/modules/pluginupdater.js"); +/* harmony import */ var _modules_logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../modules/logger */ "./src/modules/logger.js"); +/* harmony import */ var _modules_reacttools__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../modules/reacttools */ "./src/modules/reacttools.js"); +/* harmony import */ var _ui_modals__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../ui/modals */ "./src/ui/modals.js"); +/* harmony import */ var _modules_utilities__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../modules/utilities */ "./src/modules/utilities.js"); +/* harmony import */ var _modules_discordmodules__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../modules/discordmodules */ "./src/modules/discordmodules.js"); +/* harmony import */ var _ui_settings__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../ui/settings */ "./src/ui/settings/index.js"); + + + + + + + + +class Plugin { + + get name() {return this._config.name ?? this._config.info.name.replace(" ", "");} + get description() {return this._config.description ?? this._config.info.description;} + get version() {return this._config.version ?? this._config.info.version;} + get author() {return this._config.author ?? this._config.info.authors.map(a => a.name).join(", ");} + getName() {return this._config.name ?? this._config.info.name.replace(" ", "");} + getDescription() {return this._config.description ?? this._config.info.description;} + getVersion() {return this._config.version ?? this._config.info.version;} + getAuthor() {return this._config.author ?? this._config.info.authors.map(a => a.name).join(", ");} + get isEnabled() {return this._enabled;} + + get strings() { + if (!this._config.strings) return {}; + const locale = _modules_discordmodules__WEBPACK_IMPORTED_MODULE_5__["default"].LocaleManager?.getLocale().split("-")[0] ?? "en"; + if (this._config.strings.hasOwnProperty(locale)) return this._config.strings[locale]; + if (this._config.strings.hasOwnProperty("en")) return this._config.strings.en; + return this._config.strings; + } + + set strings(strings) { + this._config.strings = strings; + } + + constructor(zplConfig) { + this._config = zplConfig; + this._enabled = false; + + // Build the settings model from the default if it exists + if (typeof(this._config.defaultConfig) !== "undefined") { + this.defaultSettings = {}; + for (let s = 0; s < this._config.defaultConfig.length; s++) { + const current = this._config.defaultConfig[s]; + if (current.type != "category") {this.defaultSettings[current.id] = current.value;} + else { + this.defaultSettings[current.id] = {}; + for (let si = 0; si < current.settings.length; si++) { + const subCurrent = current.settings[si]; + this.defaultSettings[current.id][subCurrent.id] = subCurrent.value; + } + } + } + + // Clone the default settings to the current ones + this.settings = _modules_utilities__WEBPACK_IMPORTED_MODULE_4__["default"].deepclone(this.defaultSettings); + } + + // Load previously stored info to check if changelog is needed then check for update + const currentVersionInfo = _modules_utilities__WEBPACK_IMPORTED_MODULE_4__["default"].loadData(this.name, "currentVersionInfo", {version: this.version, hasShownChangelog: false}); + if (currentVersionInfo.version != this.version || !currentVersionInfo.hasShownChangelog) { + this.showChangelog(); + _modules_utilities__WEBPACK_IMPORTED_MODULE_4__["default"].saveData(this.name, "currentVersionInfo", {version: this.version, hasShownChangelog: true}); + } + + // Do not check updates for self + if (this._config?.id === "9") return; + _modules_pluginupdater__WEBPACK_IMPORTED_MODULE_0__["default"].checkForUpdate(this.name, this.version, this._config.id ?? this._config.github_raw ?? this._config?.info.github_raw); + } + + async start() { + _modules_logger__WEBPACK_IMPORTED_MODULE_1__["default"].info(this.name, `version ${this.version} has started.`); + if (this.defaultSettings) this.settings = this.loadSettings(); + this._enabled = true; + if (typeof(this.onStart) == "function") this.onStart(); + } + + stop() { + _modules_logger__WEBPACK_IMPORTED_MODULE_1__["default"].info(this.name, `version ${this.version} has stopped.`); + this._enabled = false; + if (typeof(this.onStop) == "function") this.onStop(); + } + + showSettingsModal() { + if (typeof(this.getSettingsPanel) != "function") return; + _ui_modals__WEBPACK_IMPORTED_MODULE_3__["default"].showModal(this.name + " Settings", _modules_reacttools__WEBPACK_IMPORTED_MODULE_2__["default"].createWrappedElement(this.getSettingsPanel()), { + cancelText: "", + confirmText: "Done", + size: _ui_modals__WEBPACK_IMPORTED_MODULE_3__["default"].ModalSizes.MEDIUM + }); + } + + showChangelog(footer) { + if (typeof(this._config.changelog) == "undefined") return; + _ui_modals__WEBPACK_IMPORTED_MODULE_3__["default"].showChangelogModal(this.name + " Changelog", this.version, this._config.changelog, footer); + } + + saveSettings(settings) { + _modules_utilities__WEBPACK_IMPORTED_MODULE_4__["default"].saveSettings(this.name, this.settings ? this.settings : settings); + } + + loadSettings(defaultSettings) { + // loadSettings -> loadData -> defaultSettings gets deep cloned + return _modules_utilities__WEBPACK_IMPORTED_MODULE_4__["default"].loadSettings(this.name, this.defaultSettings ? this.defaultSettings : defaultSettings); + } + + buildSetting(data) { + const {name, note, type, value, onChange, id} = data; + let setting = null; + if (type == "color") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.ColorPicker(name, note, value, onChange, {disabled: data.disabled, presetColors: data.presetColors}); + else if (type == "dropdown") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.Dropdown(name, note, value, data.options, onChange); + else if (type == "file") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.FilePicker(name, note, onChange); + else if (type == "keybind") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.Keybind(name, note, value, onChange); + else if (type == "radio") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.RadioGroup(name, note, value, data.options, onChange, {disabled: data.disabled}); + else if (type == "slider") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.Slider(name, note, data.min, data.max, value, onChange, data); + else if (type == "switch") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.Switch(name, note, value, onChange, {disabled: data.disabled}); + else if (type == "textbox") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.Textbox(name, note, value, onChange, {placeholder: data.placeholder || ""}); + if (id) setting.id = id; + return setting; + } + + buildSettingsPanel() { + const config = this._config.defaultConfig; + const buildGroup = (group) => { + const {name, id, collapsible, shown, settings} = group; + + const list = []; + for (let s = 0; s < settings.length; s++) { + const current = Object.assign({}, settings[s]); + current.value = this.settings[id][current.id]; + current.onChange = (value) => { + this.settings[id][current.id] = value; + }; + if (Object.keys(this.strings).length && this.strings.settings && this.strings.settings[id] && this.strings.settings[id][current.id]) { + const settingStrings = this.strings.settings[id][current.id]; + current.name = settingStrings.name; + current.note = settingStrings.note; + } + list.push(this.buildSetting(current)); + } + + let groupName = name; + if (Object.keys(this.strings).length && this.strings.settings && this.strings.settings[id]) { + groupName = this.strings.settings[id].name; + } + + const settingGroup = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.SettingGroup(groupName, {shown, collapsible}).append(...list); + settingGroup.id = id; + return settingGroup; + }; + const list = []; + for (let s = 0; s < config.length; s++) { + const current = Object.assign({}, config[s]); + if (current.type != "category") { + current.value = this.settings[current.id]; + current.onChange = (value) => { + this.settings[current.id] = value; + }; + if (Object.keys(this.strings).length && this.strings.settings && this.strings.settings[current.id]) { + const {name, note} = this.strings.settings[current.id]; + current.name = name; + current.note = note; + } + list.push(this.buildSetting(current)); + } + else { + list.push(buildGroup(current)); + } + } + + return new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.SettingPanel(this.saveSettings.bind(this), ...list); + } +} + +const wrapPluginBase = (conf) => { + return class BoundPlugin extends Plugin { + constructor() { + super(conf); + } + }; +}; + +/***/ }), + +/***/ "./src/structs/screen.js": +/*!*******************************!*\ + !*** ./src/structs/screen.js ***! + \*******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Representation of the screen such as width and height. + * @deprecated 1/21/22 Use DOMTools + */ +class Screen { + /** Document/window width */ + static get width() {return Math.max(document.documentElement.clientWidth, window.innerWidth || 0);} + /** Document/window height */ + static get height() {return Math.max(document.documentElement.clientHeight, window.innerHeight || 0);} +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Screen); + +/***/ }), + +/***/ "./src/structs/structs.js": +/*!********************************!*\ + !*** ./src/structs/structs.js ***! + \********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ ClassName: () => (/* reexport safe */ _dom_classname__WEBPACK_IMPORTED_MODULE_2__["default"]), +/* harmony export */ DOMObserver: () => (/* reexport safe */ _dom_observer__WEBPACK_IMPORTED_MODULE_3__["default"]), +/* harmony export */ Listenable: () => (/* reexport safe */ _listenable__WEBPACK_IMPORTED_MODULE_4__["default"]), +/* harmony export */ Plugin: () => (/* reexport safe */ _plugin__WEBPACK_IMPORTED_MODULE_5__["default"]), +/* harmony export */ Screen: () => (/* reexport safe */ _screen__WEBPACK_IMPORTED_MODULE_0__["default"]), +/* harmony export */ Selector: () => (/* reexport safe */ _dom_selector__WEBPACK_IMPORTED_MODULE_1__["default"]) +/* harmony export */ }); +/* harmony import */ var _screen__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./screen */ "./src/structs/screen.js"); +/* harmony import */ var _dom_selector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./dom/selector */ "./src/structs/dom/selector.js"); +/* harmony import */ var _dom_classname__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./dom/classname */ "./src/structs/dom/classname.js"); +/* harmony import */ var _dom_observer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./dom/observer */ "./src/structs/dom/observer.js"); +/* harmony import */ var _listenable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./listenable */ "./src/structs/listenable.js"); +/* harmony import */ var _plugin__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./plugin */ "./src/structs/plugin.js"); + + + + + + + + +/***/ }), + +/***/ "./src/ui/discordcontextmenu.js": +/*!**************************************!*\ + !*** ./src/ui/discordcontextmenu.js ***! + \**************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ DiscordContextMenu) +/* harmony export */ }); +/* harmony import */ var _modules_webpackmodules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../modules/webpackmodules */ "./src/modules/webpackmodules.js"); +/* harmony import */ var _modules_reacttools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../modules/reacttools */ "./src/modules/reacttools.js"); +/* harmony import */ var _modules_utilities__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../modules/utilities */ "./src/modules/utilities.js"); +/* harmony import */ var _modules_discordclasses__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../modules/discordclasses */ "./src/modules/discordclasses.js"); + + + + + +// d = e.label, +// f = e.icon, +// h = e.imageUrl, +// v = e.hint, +// m = e.subtext, +// g = e.hasSubmenu, +// y = e.disabled, +// E = e.isFocused, +// S = e.menuItemProps, +// T = e.action, +// b = e.onClose, + + +/** + * Fires when the item is clicked. + * @param {MouseEvent} event - The event generated on click + * @callback module:DiscordContextMenu~MenuItemOnClick + */ + +/** + * @interface + * @name module:DiscordContextMenu~MenuItem + * @description + * This is the generic context menu item component. It is very extensible and will adapt + * it's type depending on the props. + * + * Note: The item ID should be unique to this item across the entire menu. If no `id` is + * provided, the system will use the `label`. Plugins should ensure there are no `label` + * conflicts if they do not wish to provide `id`. `label` conflicts (when not using + * unique `id`s) can cause multiple items to be hovered at once. + * + * @param {object} props - props to pass to the react renderer + * @param {string} props.label - label to show on the menu item + * @param {string} [props.id] - specific id used for this item + * @param {string} [props.hint] - hint to show on the right hand side (usually keyboard combo) + * @param {string} [props.subtext] - description to show underneath + * @param {string} [props.image] - link to image to show on the side + * @param {function} [props.icon] - react component to render on the side + * @param {function} [props.render] - render function for custom rendering the menu item + * @param {module:DiscordContextMenu~MenuItemOnClick} [props.action] - function to perform on click + * @param {module:DiscordContextMenu~MenuItemOnClick} [props.onClick] - function to perform on click (alias of `action`) + * @param {function} [props.onClose] - function to run when this is closed + * @param {boolean} [props.danger=false] - should the item show as danger (red) + * @param {boolean} [props.disabled=false] - should the item be disabled/unclickable + * + * @param {object} [props.style] - allows you to add custom styles + * @param {boolean} [props.closeOnClick] - allows you to prevent closing on click + */ + +/** + * @interface + * @name module:DiscordContextMenu~MenuToggleItem + * @extends module:DiscordContextMenu~MenuItem + * @description + * This item is used for creating checkboxes in menus. Properties shown here are additional + * to those of the main MenuItem {@link module:DiscordContextMenu~MenuItem} + * + * + * @param {boolean} [props.checked=false] - should the checkbox be checked + * @param {boolean} [props.active=false] - alias of `checked` + */ + +/** + * @interface + * @name module:DiscordContextMenu~MenuRadioItem + * @extends module:DiscordContextMenu~MenuItem + * @description + * This item is used for creating radio selections in menus. Properties shown here are additional + * to those of the main MenuItem {@link module:DiscordContextMenu~MenuItem} + * + * Note: for the `forceUpdate` option... Without this enabled, you will manually need to + * manage the state for the functional component. If you do not the toggle will appear + * to not update. @see {@link https://reactjs.org/docs/hooks-reference.html#usestate} + * + * @param {boolean} [props.checked=false] - should the checkbox be checked + * @param {boolean} [props.active=false] - alias of `checked` + * @param {boolean} [props.forceUpdate=true] - should the menu be force-updated after click + */ + +/** + * @interface + * @name module:DiscordContextMenu~SubMenuItem + * @extends module:DiscordContextMenu~MenuItem + * @description + * This item is used for creating nested submenus. Properties shown here are additional + * to those of the main MenuItem {@link module:DiscordContextMenu~MenuItem} + * + * @param {Array} [props.render] - array of items to render in the submenu + * @param {Array} [props.items] - alias of `render` + * @param {Array} [props.children] - Already rendered elements + */ + +/** + * @interface + * @name module:DiscordContextMenu~MenuControlItem + * @extends module:DiscordContextMenu~MenuItem + * @description + * This item is used for adding custom controls like sliders to the context menu. + * Properties shown here are additional to those of the main MenuItem {@link module:DiscordContextMenu~MenuItem} + * + * @param {function} [props.control] - control function that renders the component + */ + + +/** + * A utility for building and rendering Discord's own menus. + * @module DiscordContextMenu + */ +class DiscordContextMenu { + + /** + * Builds a single menu item. The only prop shown here is the type, the rest should + * match the actual component being built. View those to see what options exist + * for each, they often have less in common than you might think. See {@link module:DiscordContextMenu.MenuItem} + * for the majority of props commonly available. Check the documentation for the + * rest of the components. + * + * @param {object} props - props used to build the item + * @param {string} [props.type="text"] - type of the item, options: text, submenu, toggle, radio, custom, separator + * @returns {object} the created component + * + * @see {@link module:DiscordContextMenu~MenuItem} + * @see {@link module:DiscordContextMenu~MenuToggleItem} + * @see {@link module:DiscordContextMenu~MenuRadioItem} + * @see {@link module:DiscordContextMenu~SubMenuItem} + * @see {@link module:DiscordContextMenu~MenuControlItem} + * + * @example + * // Creates a single menu item that prints "MENU ITEM" on click + * DiscordContextMenu.buildMenuItem({ + * label: "Menu Item", + * action: () => {console.log("MENU ITEM");} + * }); + * + * @example + * // Creates a single toggle item that starts unchecked + * // and print the new value on every toggle + * DiscordContextMenu.buildMenuItem({ + * type: "toggle", + * label: "Item Toggle", + * checked: false, + * action: (newValue) => {console.log(newValue);} + * }); + */ + static buildMenuItem(props) { + return window.BdApi.ContextMenu.buildItem(props); + } + + /** + * Creates the all the items **and groups** of a context menu recursively. + * There is no hard limit to the number of groups within groups or number + * of items in a menu. + * @param {Array} setup - array of item props used to build items. See {@link module:DiscordContextMenu.buildMenuItem} + * @returns {Array} array of the created component + * + * @example + * // Creates a single item group item with a toggle item + * DiscordContextMenu.buildMenuChildren([{ + * type: "group", + * items: [{ + * type: "toggle", + * label: "Item Toggle", + * active: false, + * action: (newValue) => {console.log(newValue);} + * }] + * }]); + * + * @example + * // Creates two item groups with a single toggle item each + * DiscordContextMenu.buildMenuChildren([{ + * type: "group", + * items: [{ + * type: "toggle", + * label: "Item Toggle", + * active: false, + * action: (newValue) => { + * console.log(newValue); + * } + * }] + * }, { + * type: "group", + * items: [{ + * type: "toggle", + * label: "Item Toggle", + * active: false, + * action: (newValue) => { + * console.log(newValue); + * } + * }] + * }]); + */ + static buildMenuChildren(setup) { + return window.BdApi.ContextMenu.buildMenuChildren(setup); + } + + /** + * Creates the menu *component* including the wrapping `ContextMenu`. + * Calls {@link module:DiscordContextMenu.buildMenuChildren} under the covers. + * Used to call in combination with {@link module:DiscordContextMenu.openContextMenu}. + * @param {Array} setup - array of item props used to build items. See {@link module:DiscordContextMenu.buildMenuChildren} + * @returns {function} the unique context menu component + */ + static buildMenu(setup) { + return window.BdApi.ContextMenu.buildMenu(setup); + } + + /** + * + * @param {MouseEvent} event - The context menu event. This can be emulated, requires target, and all X, Y locations. + * @param {function} menuComponent - Component to render. This can be any react component or output of {@link module:DiscordContextMenu.buildMenu} + * @param {object} config - configuration/props for the context menu + * @param {string} [config.position="right"] - default position for the menu, options: "left", "right" + * @param {string} [config.align="top"] - default alignment for the menu, options: "bottom", "top" + * @param {function} [config.onClose] - function to run when the menu is closed + * @param {boolean} [config.noBlurEvent=false] - No clue + */ + static openContextMenu(event, menuComponent, config) { + return window.BdApi.ContextMenu.open(event, menuComponent, config); + } + + /** + * Attempts to find and return a specific context menu type's module. Useful + * when patching the render of these menus. + * @param {string | Function} nameOrFilter - name of the context menu type + * @returns {Promise} the webpack module the menu was found in + * @deprecated + */ + static getDiscordMenu(nameOrFilter) { + if (typeof(nameOrFilter) !== "function") { + const displayName = nameOrFilter; + nameOrFilter = (m) => m && m.displayName === displayName; + } + + const directMatch = _modules_webpackmodules__WEBPACK_IMPORTED_MODULE_0__["default"].getModule(m => m.default && nameOrFilter(m.default)); + if (directMatch) return Promise.resolve(directMatch); + + return new Promise(resolve => { + const cancel = _modules_webpackmodules__WEBPACK_IMPORTED_MODULE_0__["default"].addListener(module => { + if (!module.default || !nameOrFilter(module.default)) return; + resolve(module); + cancel(); + }); + }); + } + + /** + * Calls `forceUpdate()` on all context menus it can find. Useful for + * after patching a menu. + */ + static forceUpdateMenus() { + const menus = document.querySelectorAll(`.${_modules_discordclasses__WEBPACK_IMPORTED_MODULE_3__["default"].ContextMenu.menu.first}`); + for (const menu of menus) { + const stateNode = _modules_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].findInTree(_modules_reacttools__WEBPACK_IMPORTED_MODULE_1__["default"].getReactInstance(menu), m=>m && m.forceUpdate && m.updatePosition, {walkable: ["return", "stateNode"]}); + if (!stateNode) continue; + stateNode.forceUpdate(); + stateNode.updatePosition(); + } + } +} + +/***/ }), + +/***/ "./src/ui/errorboundary.js": +/*!*********************************!*\ + !*** ./src/ui/errorboundary.js ***! + \*********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ WrapBoundary: () => (/* binding */ WrapBoundary), +/* harmony export */ "default": () => (/* binding */ ErrorBoundary) +/* harmony export */ }); +/* harmony import */ var _modules_discordmodules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../modules/discordmodules */ "./src/modules/discordmodules.js"); + + +const React = _modules_discordmodules__WEBPACK_IMPORTED_MODULE_0__["default"].React; +const ce = React.createElement; + +class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = {hasError: false}; + } + + componentDidCatch() { + this.setState({hasError: true}); + } + + render() { + if (this.state.hasError) return this.props.errorChildren ? this.props.errorChildren : ce("div", {className: "error"}, "Component Error"); + return this.props.children; + } +} + +function WrapBoundary(Original) { + return class ErrorBoundaryWrapper extends React.Component { + render() { + return ce(ErrorBoundary, null, ce(Original, this.props)); + } + }; +} + +/***/ }), + +/***/ "./src/ui/icons.js": +/*!*************************!*\ + !*** ./src/ui/icons.js ***! + \*************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ IconError: () => (/* reexport safe */ _icons_error__WEBPACK_IMPORTED_MODULE_0__["default"]), +/* harmony export */ IconInfo: () => (/* reexport safe */ _icons_info__WEBPACK_IMPORTED_MODULE_1__["default"]), +/* harmony export */ IconSuccess: () => (/* reexport safe */ _icons_success__WEBPACK_IMPORTED_MODULE_2__["default"]), +/* harmony export */ IconWarning: () => (/* reexport safe */ _icons_warning__WEBPACK_IMPORTED_MODULE_3__["default"]) +/* harmony export */ }); +/* harmony import */ var _icons_error__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./icons/error */ "./src/ui/icons/error.js"); +/* harmony import */ var _icons_info__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./icons/info */ "./src/ui/icons/info.js"); +/* harmony import */ var _icons_success__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./icons/success */ "./src/ui/icons/success.js"); +/* harmony import */ var _icons_warning__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./icons/warning */ "./src/ui/icons/warning.js"); + + + + + +/***/ }), + +/***/ "./src/ui/icons/error.js": +/*!*******************************!*\ + !*** ./src/ui/icons/error.js ***! + \*******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* export default binding */ __WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Error Icon + * @param {number} size - Size of the icon. + */ +/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__(size) { + return ` + + `; +} + +/***/ }), + +/***/ "./src/ui/icons/info.js": +/*!******************************!*\ + !*** ./src/ui/icons/info.js ***! + \******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* export default binding */ __WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Info Icon + * @param {number} size - Size of the icon. + */ +/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__(size) { + return ` + + + `; +} + +/***/ }), + +/***/ "./src/ui/icons/success.js": +/*!*********************************!*\ + !*** ./src/ui/icons/success.js ***! + \*********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* export default binding */ __WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Success Icon + * @param {number} size - Size of the icon. + */ +/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__(size) { + return ` + + + `; +} + +/***/ }), + +/***/ "./src/ui/icons/warning.js": +/*!*********************************!*\ + !*** ./src/ui/icons/warning.js ***! + \*********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* export default binding */ __WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Warning Icon + * @param {number} size - Size of the icon. + */ +/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__(size) { + return ` + + + `; +} + +/***/ }), + +/***/ "./src/ui/modals.js": +/*!**************************!*\ + !*** ./src/ui/modals.js ***! + \**************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Modals) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/** + * Allows an easy way to create and show modals. + * @module Modals + */ + + + +const React = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.React; +const ce = React.createElement; +const Markdown = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m.rules); +const MarkdownParser = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getByProps("defaultRules", "parse"); + +class Modals { + + /** Sizes of modals. */ + static get ModalSizes() {return {};} + + /** + * Shows the user profile modal for a given user. + * @param {string} userId - id of the user to show profile for + */ + static showUserProfile(userId) { + return modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.UserProfileModal.open(userId); + } + + /** + * Acts as a wrapper for {@link module:Modals.showModal} where the `children` is a text element. + * @param {string} title - title of the modal + * @param {string} content - text to show inside the modal. Can be markdown. + * @param {object} [options] - see {@link module:Modals.showModal} + * @see module:Modals.showModal + */ + static showConfirmationModal(title, content, options = {}) { + this.showModal(title, ce(Markdown, null, content), options); + } + + /** + * Shows a very simple alert modal that has title, content and an okay button. + * @param {string} title - title of the modal + * @param {string} body - text to show inside the modal + */ + static showAlertModal(title, body) { + this.showConfirmationModal(title, body, {cancelText: null}); + } + + /** + * Shows a generic but very customizable modal. + * @param {string} title - title of the modal + * @param {(ReactElement|Array)} children - a single or array of rendered react elements to act as children + * @param {object} [options] - options to modify the modal + * @param {boolean} [options.danger=false] - whether the main button should be red or not + * @param {string} [options.confirmText=Okay] - text for the confirmation/submit button + * @param {string} [options.cancelText=Cancel] - text for the cancel button + * @param {callable} [options.onConfirm=NOOP] - callback to occur when clicking the submit button + * @param {callable} [options.onCancel=NOOP] - callback to occur when clicking the cancel button + */ + static showModal(title, children, options = {}) { + const {danger = false, confirmText = "Okay", cancelText = "Cancel", onConfirm = () => {}, onCancel = () => {}} = options; + return modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ModalActions.openModal(props => { + return React.createElement(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ConfirmationModal, Object.assign({ + header: title, + confirmButtonColor: danger ? modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ButtonData.Colors.RED : modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ButtonData.Colors.BRAND, + confirmText: confirmText, + cancelText: cancelText, + onConfirm: onConfirm, + onCancel: onCancel + }, props), children); + }); + } + + /** + * @interface + * @name module:Modals~Changelog + * @property {string} title - title of the changelog section + * @property {string} [type=added] - type information of the section. Options: added, improved, fixed, progress. + * @property {Array} items - itemized list of items to show in that section. Can use markdown. + */ + + /** + * Shows a changelog modal based on changelog data. + * @param {string} title - title of the modal + * @param {string} version - subtitle (usually version or date) of the modal + * @param {module:Modals~Changelog} changelog - changelog to show inside the modal + * @param {string} footer - either an html element or text to show in the footer of the modal. Can use markdown. + */ + static showChangelogModal(title, version, changelog, footer) { + const TextElement = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.TextElement; + const ChangelogModalClasses = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => typeof(m) === "object" && Object.keys(m).length === 2 && m.modal && m.content); + if (!TextElement || !ChangelogModalClasses || !modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.FlexChild || !modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ModalRoot || !modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ModalActions) return modules__WEBPACK_IMPORTED_MODULE_0__.Logger.warn("Modals", "Unable to show changelog modal--missing modules"); + const changelogItems = []; + for (let c = 0; c < changelog.length; c++) { + const entry = changelog[c]; + const type = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Changelog[entry.type] ? modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Changelog[entry.type] : modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Changelog.added; + const margin = c == 0 ? modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Changelog.marginTop : ""; + changelogItems.push(ce("h1", {className: `${type} ${margin}`,}, entry.title)); + const list = ce("ul", null, entry.items.map(i => ce("li", null, MarkdownParser.parse(i)))); + changelogItems.push(list); + } + const renderHeader = function() { + return ce(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.FlexChild, {className: modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Modals.header.toString(), grow: 0, shrink: 0, direction: modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.FlexChild.Direction.VERTICAL}, + ce(TextElement, {tag: "h1", size: TextElement.Sizes.SIZE_20, strong: true}, title), + ce(TextElement, {size: TextElement.Sizes.SIZE_12, color: TextElement.Colors.STANDARD, className: modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Changelog.date.toString()}, "Version " + version) + ); + }; + const renderFooter = footer ? function() { + return ce(Markdown, null, footer); + } : null; + + const body = ce("div", { + className: `${modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Modals.content} ${modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Changelog.container} ${ChangelogModalClasses.content} ${modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Scrollers.thin}` + }, changelogItems); + + // return DiscordModules.ModalActions.openModal(props => { + // return ce(WebpackModules.getModule(m => m?.toString()?.includes("confirmText")), Object.assign({ + // className: DiscordClasses.Changelog.container.toString(), + // selectable: true, + // onScroll: _ => _, + // onClose: _ => _, + // renderHeader: renderHeader, + // renderFooter: renderFooter, + // }, props), changelogItems); + // }); + return modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ModalActions.openModal(props => { + return React.createElement(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ModalRoot, Object.assign({ + className: `bd-changelog-modal ${modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Modals.root} ${modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Modals.small} ${ChangelogModalClasses.modal}`, + selectable: true, + onScroll: _ => _, + onClose: _ => _ + }, props), [renderHeader(), body, renderFooter?.()]); + }); + // return Modals.showModal(`${title} v${version}`, [renderHeader(), changelogItems, renderFooter?.()], {cancelText: null}); + } +} + +/***/ }), + +/***/ "./src/ui/popouts.js": +/*!***************************!*\ + !*** ./src/ui/popouts.js ***! + \***************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Popouts) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/** + * Allows an easy way to create and show popouts. + * @module Popouts + */ + + + +const {React, ReactDOM} = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules; +const {useReducer, useEffect, useRef} = React; +const AppLayer = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => Object.values(m).some(m => m?.displayName === "AppLayer")); +const ReferencePositionLayer = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m?.prototype?.calculatePositionStyle, {searchExports: true}); +// const PopoutCSSAnimator = WebpackModules.getByDisplayName("PopoutCSSAnimator"); +const LayerProvider = Object.values(AppLayer).find(m => m.displayName === "AppLayerProvider")?.().props.layerContext.Provider; // eslint-disable-line new-cap +const ComponentDispatch = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m.toString?.().includes("useContext") && m.toString?.().includes(".windowDispatch"), {searchExports: true}); +const ComponentActions = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m.POPOUT_SHOW, {searchExports: true}); +const Popout = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m?.defaultProps && m?.Animation, {searchExports: true}); +const ThemeContext = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m?.toString?.().includes(".DARK") && m?.toString?.().includes("primaryColor") && m?.toString?.().includes("Provider"), {searchExports: true}); +const Hooks = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m.useSyncExternalStore); +const ThemeStore = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m.theme); + +const createStore = state => { + const listeners = new Set(); + + const setState = function (getter = _ => _) { + const partial = getter(state); + if (partial === state) return; + + state = partial; + + [...listeners].forEach(e => e()); + }; + + setState.getState = () => state; + + function storeListener(getter = _ => _) { + const [, forceUpdate] = useReducer(n => !n, true); + + useEffect(() => { + const dispatch = () => {forceUpdate();}; + + listeners.add(dispatch); + + return () => {listeners.delete(dispatch);}; + }); + + return getter(state); + } + + return [ + setState, + storeListener + ]; +}; + +const [setPopouts, usePopouts] = createStore([]); + +// const AnimationTypes = {FADE: 3, SCALE: 2, TRANSLATE: 1}; + +class Popouts { + + // static get AnimationTypes() {return AnimationTypes;} + + static initialize() { + return; + this.dispose(); + this.popouts = 0; + + this.container = Object.assign(document.createElement("div"), { + className: "ZeresPluginLibraryPopoutsRenderer", + style: "display: none;" + }); + + this.layerContainer = Object.assign(document.createElement("div"), { + id: "ZeresPluginLibraryPopouts", + className: modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClassModules.TooltipLayers.layerContainer + }); + + document.body.append(this.container, this.layerContainer); + ReactDOM.render(React.createElement(PopoutsContainer), this.layerContainer); + } + + /** + * Shows the user popout for a user relative to a target element + * @param {HTMLElement} target - Element to show the popout in relation to + * @param {object} user - Discord User object for the user to show + * @param {object} [options] - Options to modify the request + * @param {string} [options.guild="currentGuildId"] - Id of the guild (uses current if not specified) + * @param {string} [options.channel="currentChannelId"] - Id of the channel (uses current if not specified) + * @param {string} [options.position="right"] - Positioning relative to element + * @param {string} [options.align="top"] - Positioning relative to element + */ + static showUserPopout(target, user, options = {}) { + const {position = "right", align = "top", guild = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.SelectedGuildStore.getGuildId(), channel = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.SelectedChannelStore.getChannelId()} = options; + target = modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.resolveElement(target); + // if (target.getBoundingClientRect().right + 250 >= DOMTools.screenWidth && options.autoInvert) position = "left"; + // if (target.getBoundingClientRect().bottom + 400 >= DOMTools.screenHeight && options.autoInvert) align = "bottom"; + // if (target.getBoundingClientRect().top - 400 >= DOMTools.screenHeight && options.autoInvert) align = "top"; + this.openPopout(target, { + position: position, + align: align, + // animation: options.animation || Popouts.AnimationTypes.TRANSLATE, + autoInvert: options.autoInvert, + nudgeAlignIntoViewport: options.nudgeAlignIntoViewport, + spacing: options.spacing, + render: (props) => { + return modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.React.createElement(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.UserPopout, Object.assign({}, props, { + userId: user.id, + guildId: guild, + channelId: channel, + closePopout: () => this.closePopout(props.popoutId) + })); + } + }); + } + + /** + * Shows a react popout relative to a target element + * @param {HTMLElement} target - Element to show the popout in relation to + * @param {object} [options] - Options to modify the request + * @param {string} [options.position="right"] - General position relative to element + * @param {string} [options.align="top"] - Alignment relative to element + * @param {boolean} [options.autoInvert=true] - Try to automatically adjust the position if it overflows the screen + * @param {boolean} [options.nudgeAlignIntoViewport=true] - Try to automatically adjust the alignment if it overflows the screen + * @param {number} [options.spacing=8] - Spacing between target and popout + */ + static openPopout(target, options) { + const id = this.popouts++; + + setPopouts(popouts => popouts.concat({ + id: id, + element: React.createElement(PopoutWrapper, Object.assign({}, Popout.defaultProps, { + reference: {current: target}, + popoutId: id, + key: "popout_" + id, + spacing: 50 + }, options)) + })); + + return id; + } + + static closePopout(id) { + const popout = setPopouts.getState().find(e => e.id === id); + + if (!popout) return null; + + setPopouts(popouts => { + const clone = [...popouts]; + clone.splice(clone.indexOf(popout), 1); + return clone; + }); + } + + static dispose() { + modules__WEBPACK_IMPORTED_MODULE_0__.Patcher.unpatchAll("Popouts"); + const container = document.querySelector(".ZeresPluginLibraryPopoutsRenderer"); + const layerContainer = document.querySelector("#ZeresPluginLibraryPopouts"); + if (container) ReactDOM.unmountComponentAtNode(container); + if (container) container.remove(); + if (layerContainer) layerContainer.remove(); + } +} + +function DiscordProviders({children, container}) { + const theme = Hooks.useSyncExternalStore([ThemeStore], () => ThemeStore.theme); + + return React.createElement(LayerProvider, {value: [container]}, + React.createElement(ThemeContext, {theme}, children) + ); +} + +function PopoutsContainer() { + const popouts = usePopouts(); + + return React.createElement(DiscordProviders, + {container: Popouts.layerContainer}, + popouts.map((popout) => popout.element) + ); +} + +function PopoutWrapper({render, popoutId, ...props}) { + const popoutRef = useRef(); + + useEffect(() => { + if (!popoutRef.current) return; + + const node = ReactDOM.findDOMNode(popoutRef.current); + + const handleClick = ({target}) => { + if (target === node || node.contains(target)) return; + + Popouts.closePopout(popoutId); + }; + + const target = modules__WEBPACK_IMPORTED_MODULE_0__.Utilities.findInTree(node.__reactFiber$, m => m?.stateNode?.updatePosition, {walkable: ["return"]}); + setTimeout(() => target?.stateNode?.updatePosition(), 1); + + document.addEventListener("click", handleClick); + + return () => { + document.removeEventListener("click", handleClick); + }; + }, [popoutRef]); + + // switch (animation) { + // case PopoutCSSAnimator.Types.FADE: + // case PopoutCSSAnimator.Types.SCALE: + // case PopoutCSSAnimator.Types.TRANSLATE: { + // const renderPopout = render; + // render = (renderProps) => { + // return React.createElement(PopoutCSSAnimator, { + // position: renderProps.position, + // type: animation + // }, renderPopout(renderProps)); + // }; + // } + // } + + // eslint-disable-next-line new-cap + const ComponentDispatcher = ComponentDispatch(); + + return React.createElement(ReferencePositionLayer, Object.assign(props, { + ref: popoutRef, + positionKey: "0", + autoInvert: true, + nudgeAlignIntoViewport: true, + id: "popout_" + popoutId, + animation: 2, + onMount() { + ComponentDispatcher.dispatch(ComponentActions.POPOUT_SHOW); + }, + onUnmount() { + ComponentDispatcher.dispatch(ComponentActions.POPOUT_HIDE); + }, + children: (props, ...p) => React.createElement( + "div", + { + style: {transform: "translateZ(0)"}, // for z-index to work properly for sub-popouts + }, + render({popoutId, ...props}, ...p) + ) + })); +} + + + + +/***/ }), + +/***/ "./src/ui/settings/index.js": +/*!**********************************!*\ + !*** ./src/ui/settings/index.js ***! + \**********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ CSS: () => (/* reexport safe */ _styles_settings_css__WEBPACK_IMPORTED_MODULE_0__["default"]), +/* harmony export */ ColorPicker: () => (/* reexport safe */ _types_color__WEBPACK_IMPORTED_MODULE_5__["default"]), +/* harmony export */ Dropdown: () => (/* reexport safe */ _types_dropdown__WEBPACK_IMPORTED_MODULE_9__["default"]), +/* harmony export */ FilePicker: () => (/* reexport safe */ _types_file__WEBPACK_IMPORTED_MODULE_6__["default"]), +/* harmony export */ Keybind: () => (/* reexport safe */ _types_keybind__WEBPACK_IMPORTED_MODULE_10__["default"]), +/* harmony export */ RadioGroup: () => (/* reexport safe */ _types_radiogroup__WEBPACK_IMPORTED_MODULE_11__["default"]), +/* harmony export */ ReactSetting: () => (/* reexport safe */ _settingfield__WEBPACK_IMPORTED_MODULE_1__.ReactSetting), +/* harmony export */ SettingField: () => (/* reexport safe */ _settingfield__WEBPACK_IMPORTED_MODULE_1__["default"]), +/* harmony export */ SettingGroup: () => (/* reexport safe */ _settinggroup__WEBPACK_IMPORTED_MODULE_2__["default"]), +/* harmony export */ SettingPanel: () => (/* reexport safe */ _settingpanel__WEBPACK_IMPORTED_MODULE_3__["default"]), +/* harmony export */ Slider: () => (/* reexport safe */ _types_slider__WEBPACK_IMPORTED_MODULE_7__["default"]), +/* harmony export */ Switch: () => (/* reexport safe */ _types_switch__WEBPACK_IMPORTED_MODULE_8__["default"]), +/* harmony export */ Textbox: () => (/* reexport safe */ _types_textbox__WEBPACK_IMPORTED_MODULE_4__["default"]) +/* harmony export */ }); +/* harmony import */ var _styles_settings_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../styles/settings.css */ "./src/styles/settings.css"); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var _settinggroup__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./settinggroup */ "./src/ui/settings/settinggroup.js"); +/* harmony import */ var _settingpanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./settingpanel */ "./src/ui/settings/settingpanel.js"); +/* harmony import */ var _types_textbox__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./types/textbox */ "./src/ui/settings/types/textbox.js"); +/* harmony import */ var _types_color__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./types/color */ "./src/ui/settings/types/color.js"); +/* harmony import */ var _types_file__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./types/file */ "./src/ui/settings/types/file.js"); +/* harmony import */ var _types_slider__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./types/slider */ "./src/ui/settings/types/slider.js"); +/* harmony import */ var _types_switch__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./types/switch */ "./src/ui/settings/types/switch.js"); +/* harmony import */ var _types_dropdown__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./types/dropdown */ "./src/ui/settings/types/dropdown.js"); +/* harmony import */ var _types_keybind__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./types/keybind */ "./src/ui/settings/types/keybind.js"); +/* harmony import */ var _types_radiogroup__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./types/radiogroup */ "./src/ui/settings/types/radiogroup.js"); +/** + * An object that makes generating settings panel 10x easier. + * @module Settings + */ + + + + + + + + + + + + + + + + +/***/ }), + +/***/ "./src/ui/settings/settingfield.js": +/*!*****************************************!*\ + !*** ./src/ui/settings/settingfield.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ ReactSetting: () => (/* binding */ ReactSetting), +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _structs_listenable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../structs/listenable */ "./src/structs/listenable.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + + +/** + * Setting field to extend to create new settings + * @memberof module:Settings + */ +class SettingField extends _structs_listenable__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {callable} onChange - callback to perform on setting change + * @param {(ReactComponent|HTMLElement)} settingtype - actual setting to render + * @param {object} [props] - object of props to give to the setting and the settingtype + * @param {boolean} [props.noteOnTop=false] - determines if the note should be shown above the element or not. + */ + constructor(name, note, onChange, settingtype, props = {}) { + super(); + this.name = name; + this.note = note; + if (typeof(onChange) == "function") this.addListener(onChange); + this.inputWrapper = modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.parseHTML(`
`); + this.type = typeof(settingtype) == "function" ? settingtype : modules__WEBPACK_IMPORTED_MODULE_1__.ReactTools.wrapElement(settingtype); + this.props = props; + modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.onAdded(this.getElement(), () => {this.onAdded();}); + modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.onRemoved(this.getElement(), () => {this.onRemoved();}); + } + + /** @returns {HTMLElement} - root element for setting */ + getElement() {return this.inputWrapper;} + + /** Fires onchange to listeners */ + onChange() { + this.alertListeners(...arguments); + } + + /** Fired when root node added to DOM */ + onAdded() { + const reactElement = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.ReactDOM.render(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React.createElement(ReactSetting, Object.assign({ + title: this.name, + type: this.type, + note: this.note, + }, this.props)), this.getElement()); + + if (this.props.onChange) reactElement.props.onChange = this.props.onChange(reactElement); + reactElement.forceUpdate(); + } + + /** Fired when root node removed from DOM */ + onRemoved() { + modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.ReactDOM.unmountComponentAtNode(this.getElement()); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SettingField); + + +const TITLE = modules__WEBPACK_IMPORTED_MODULE_1__.WebpackModules.getByProps("title", "dividerDefault")?.title ?? "title_ed1d57"; + +class ReactSetting extends modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React.Component { + get noteElement() { + const className = this.props.noteOnTop ? modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.Margins.marginBottom8 : modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.Margins.marginTop8; + return modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React.createElement(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.SettingsNote, {children: this.props.note, type: "description", className: className.toString()}); + } + + get dividerElement() {return modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React.createElement("div", {className: modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.Dividers.divider.add(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.Margins.marginTop20).toString()});} + + render() { + const ce = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React.createElement; + const SettingElement = ce(this.props.type, this.props); + if (this.props.inline) { + const Flex = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.FlexChild; + return ce(Flex, {direction: Flex.Direction.VERTICAL, className: modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.Margins.marginTop20.toString()}, + ce(Flex, {align: Flex.Align.START}, + ce(Flex.Child, {wrap: !0}, + ce("div", {className: TITLE}, this.props.title) + ), + ce(Flex.Child, {grow: 0, shrink: 0}, SettingElement) + ), + this.noteElement, + this.dividerElement + ); + } + + return ce(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.SettingsWrapper, { + className: modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.Margins.marginTop20.toString(), + title: this.props.title, + children: [ + this.props.noteOnTop ? this.noteElement : SettingElement, + this.props.noteOnTop ? SettingElement : this.noteElement, + this.dividerElement + ] + }); + } +} + + + +/***/ }), + +/***/ "./src/ui/settings/settinggroup.js": +/*!*****************************************!*\ + !*** ./src/ui/settings/settinggroup.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _structs_listenable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../structs/listenable */ "./src/structs/listenable.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./settingfield */ "./src/ui/settings/settingfield.js"); + + + + +/** + * Grouping of controls for easier management in settings panels. + * @memberof module:Settings + */ +class SettingGroup extends _structs_listenable__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} groupName - title for the group of settings + * @param {object} [options] - additional options for the group + * @param {callback} [options.callback] - callback called on settings changed + * @param {boolean} [options.collapsible=true] - determines if the group should be collapsible + * @param {boolean} [options.shown=false] - determines if the group should be expanded by default + */ + constructor(groupName, options = {}) { + super(); + const {collapsible = true, shown = false, callback = () => {}} = options; + this.addListener(callback); + this.onChange = this.onChange.bind(this); + + const collapsed = shown || !collapsible ? "" : "collapsed"; + const group = modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.parseHTML(`
+

+ ${groupName} +

+
+
`); + const label = group.querySelector("h2"); + const controls = group.querySelector(".plugin-inputs"); + + this.group = group; + this.label = label; + this.controls = controls; + + if (!collapsible) return; + label.addEventListener("click", async () => { + const button = label.querySelector(".button-collapse"); + const wasCollapsed = button.classList.contains("collapsed"); + group.parentElement.querySelectorAll(":scope > .plugin-input-group > .collapsible:not(.collapsed)").forEach((element) => { + element.style.setProperty("height", element.scrollHeight + "px"); + element.classList.add("collapsed"); + setImmediate(() => {element.style.setProperty("height", "");}); + }); + group.parentElement.querySelectorAll(":scope > .plugin-input-group > h2 > .button-collapse").forEach(e => e.classList.add("collapsed")); + if (!wasCollapsed) return; + controls.style.setProperty("height", controls.scrollHeight + "px"); + controls.classList.remove("collapsed"); + button.classList.remove("collapsed"); + await new Promise(resolve => setTimeout(resolve, 300)); + controls.style.setProperty("height", ""); + }); + } + + /** @returns {HTMLElement} - root node for the group. */ + getElement() {return this.group;} + + /** + * Adds multiple nodes to this group. + * @param {(...HTMLElement|...jQuery|...module:Settings.SettingField|...module:Settings.SettingGroup)} nodes - list of nodes to add to the group container + * @returns {module:Settings.SettingGroup} - returns self for chaining + */ + append(...nodes) { + for (let i = 0; i < nodes.length; i++) { + if (modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.resolveElement(nodes[i]) instanceof Element) this.controls.append(nodes[i]); + else if (nodes[i] instanceof _settingfield__WEBPACK_IMPORTED_MODULE_2__["default"] || nodes[i] instanceof SettingGroup) this.controls.append(nodes[i].getElement()); + if (nodes[i] instanceof _settingfield__WEBPACK_IMPORTED_MODULE_2__["default"]) { + nodes[i].addListener(((node) => (value) => { + this.onChange(node.id || node.name, value); + })(nodes[i])); + } + else if (nodes[i] instanceof SettingGroup) { + nodes[i].addListener(((node) => (settingId, value) => { + this.onChange(node.id || node.name, settingId, value); + })(nodes[i])); + } + } + return this; + } + + /** + * Appends this node to another + * @param {HTMLElement} node - node to attach the group to. + * @returns {module:Settings.SettingGroup} - returns self for chaining + */ + appendTo(node) { + node.append(this.group); + return this; + } + + /** Fires onchange to listeners */ + onChange() { + this.alertListeners(...arguments); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SettingGroup); + +/***/ }), + +/***/ "./src/ui/settings/settingpanel.js": +/*!*****************************************!*\ + !*** ./src/ui/settings/settingpanel.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _structs_listenable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../structs/listenable */ "./src/structs/listenable.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var _settinggroup__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./settinggroup */ "./src/ui/settings/settinggroup.js"); + + + + + +/** + * Grouping of controls for easier management in settings panels. + * @memberof module:Settings + */ +class SettingPanel extends _structs_listenable__WEBPACK_IMPORTED_MODULE_0__["default"] { + + /** + * Creates a new settings panel + * @param {callable} onChange - callback to fire when settings change + * @param {(...HTMLElement|...jQuery|...module:Settings.SettingField|...module:Settings.SettingGroup)} nodes - list of nodes to add to the panel container + */ + constructor(onChange, ...nodes) { + super(); + this.element = modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.parseHTML(`
`); + if (typeof(onChange) == "function") this.addListener(onChange); + this.onChange = this.onChange.bind(this); + this.append(...nodes); + } + + /** + * Creates a new settings panel + * @param {callable} onChange - callback to fire when settings change + * @param {(...HTMLElement|...jQuery|...module:Settings.SettingField|...module:Settings.SettingGroup)} nodes - list of nodes to add to the panel container + * @returns {HTMLElement} - root node for the panel. + */ + static build(onChange, ...nodes) { + return (new SettingPanel(onChange, ...nodes)).getElement(); + } + + /** @returns {HTMLElement} - root node for the panel. */ + getElement() {return this.element;} + + /** + * Adds multiple nodes to this panel. + * @param {(...HTMLElement|...jQuery|...SettingField|...SettingGroup)} nodes - list of nodes to add to the panel container + * @returns {module:Settings.SettingPanel} - returns self for chaining + */ + append(...nodes) { + for (let i = 0; i < nodes.length; i++) { + if (modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.resolveElement(nodes[i]) instanceof Element) this.element.append(nodes[i]); + else if (nodes[i] instanceof _settingfield__WEBPACK_IMPORTED_MODULE_2__["default"] || nodes[i] instanceof _settinggroup__WEBPACK_IMPORTED_MODULE_3__["default"]) this.element.append(nodes[i].getElement()); + if (nodes[i] instanceof _settingfield__WEBPACK_IMPORTED_MODULE_2__["default"]) { + nodes[i].addListener(((node) => (value) => { + this.onChange(node.id || node.name, value); + })(nodes[i])); + } + else if (nodes[i] instanceof _settinggroup__WEBPACK_IMPORTED_MODULE_3__["default"]) { + nodes[i].addListener(((node) => (settingId, value) => { + this.onChange(node.id || node.name, settingId, value); + })(nodes[i])); + } + } + return this; + } + + /** Fires onchange to listeners */ + onChange() { + this.alertListeners(...arguments); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SettingPanel); + +/***/ }), + +/***/ "./src/ui/settings/types/color.js": +/*!****************************************!*\ + !*** ./src/ui/settings/types/color.js ***! + \****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + + +const presetColors = [1752220, 3066993, 3447003, 10181046, 15277667, 15844367, 15105570, 15158332, 9807270, 6323595, 1146986, 2067276, 2123412, 7419530, 11342935, 12745742, 11027200, 10038562, 9936031, 5533306]; + +/** + * Creates a color picker using Discord's built in color picker + * as a base. Input and output using hex strings. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class ColorPicker extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {string} value - current hex color + * @param {callable} onChange - callback to perform on setting change, callback receives hex string + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.disabled=false] - should the setting be disabled + * @param {string} [options.defaultColor] - default color to show as large option + * @param {Array} [options.colors] - preset colors to show in swatch + */ + constructor(name, note, value, onChange, options = {}) { + const classes = ["color-input"]; + if (options.disabled) classes.push(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.BasicInputs.disabled); + const ReactColorPicker = modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.parseHTML(``); + if (options.disabled) ReactColorPicker.setAttribute("disabled", ""); + if (value) ReactColorPicker.setAttribute("value", value); + ReactColorPicker.addEventListener("change", (event) => { + this.onChange(event.target.value); + }); + super(name, note, onChange, ReactColorPicker, {inline: true}); + } + + /** Default colors for ColorPicker */ + static get presetColors() {return presetColors;} +} + + + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ColorPicker); + +/***/ }), + +/***/ "./src/ui/settings/types/dropdown.js": +/*!*******************************************!*\ + !*** ./src/ui/settings/types/dropdown.js ***! + \*******************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + +/** + * @interface + * @name module:Settings~DropdownItem + * @property {string|ReactElement} label - label to show in the dropdown + * @property {*} value - actual value represented by label (this is passed via onChange) + */ + + const React = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React; + + class CloseButton extends React.Component { + render() { + const size = this.props.size || "14px"; + return React.createElement("svg", {className: this.props.className || "", fill: "currentColor", viewBox: "0 0 24 24", style: {width: size, height: size}, onClick: this.props.onClick}, + React.createElement("path", {d: "M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z"}) + ); + } +} + + class DownArrow extends React.Component { + render() { + const size = this.props.size || "16px"; + return React.createElement("svg", {className: this.props.className || "", fill: "currentColor", viewBox: "0 0 24 24", style: {width: size, height: size}, onClick: this.props.onClick}, + React.createElement("path", {d: "M8.12 9.29L12 13.17l3.88-3.88c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41l-4.59 4.59c-.39.39-1.02.39-1.41 0L6.7 10.7c-.39-.39-.39-1.02 0-1.41.39-.38 1.03-.39 1.42 0z"}) + ); + } +} + +// + +class Select extends React.Component { + constructor(props) { + super(props); + this.state = {open: false, value: this.props.value}; + this.dropdown = React.createRef(); + this.onChange = this.onChange.bind(this); + this.showMenu = this.showMenu.bind(this); + this.hideMenu = this.hideMenu.bind(this); + this.clear = this.clear.bind(this); + } + + showMenu(event) { + event.preventDefault(); + event.stopPropagation(); + + this.setState((state) => ({open: !state.open}), () => { + if (!this.state.open) return; + + document.addEventListener("click", this.hideMenu); + }); + } + + hideMenu() { + this.setState({open: false}, () => { + document.removeEventListener("click", this.hideMenu); + }); + } + + onChange(value) { + this.setState({value}); + if (this.props.onChange) this.props.onChange(value); + } + + get selected() {return this.props.options.find(o => o.value == this.state.value);} + + get options() { + const selected = this.selected; + return React.createElement("div", {className: "z-select-options"}, + this.props.options.map(opt => + React.createElement("div", {className: `z-select-option${selected?.value == opt.value ? " selected" : ""}`, onClick: this.onChange.bind(this, opt.value)}, opt.label) + ) + ); + } + + clear(event) { + event.stopPropagation(); + this.onChange(null); + } + + render() { + const style = this.props.style == "transparent" ? " z-select-transparent" : ""; + const isOpen = this.state.open ? " menu-open" : ""; + return React.createElement("div", {className: `z-select${style}${isOpen}`, ref: this.dropdown, onClick: this.showMenu}, [ + React.createElement("div", {className: "z-select-value"}, this?.selected?.label ?? this.props.placeholder), + React.createElement("div", {className: "z-select-icons"}, + this.props.clearable && this.selected && React.createElement(CloseButton, {className: "z-select-clear", onClick: this.clear}), + React.createElement(DownArrow, {className: "z-select-arrow"}), + ), + this.state.open && this.options + ]); + } +} + +/** + * Creates a dropdown using discord's built in dropdown. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class Dropdown extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {*} defaultValue - currently selected value + * @param {Array} values - array of all options available + * @param {callable} onChange - callback to perform on setting change, callback item value + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.clearable=false] - should be able to empty the field value + * @param {string} [options.placeholder=""] - Placeholder to show when no option is selected, useful when clearable + * @param {boolean} [options.disabled=false] - should the setting be disabled + */ + constructor(name, note, defaultValue, values, onChange, options = {}) { + const {clearable = false, disabled = false, placeholder = ""} = options; + super(name, note, onChange, Select, { + placeholder: placeholder, + clearable: clearable, + disabled: disabled, + options: values, + onChange: dropdown => value => { + dropdown.props.value = value; + dropdown.forceUpdate(); + this.onChange(value); + }, + value: defaultValue + }); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Dropdown); + +/***/ }), + +/***/ "./src/ui/settings/types/file.js": +/*!***************************************!*\ + !*** ./src/ui/settings/types/file.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + +/** + * Creates a file picker using chromium's default. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class FilePicker extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {callable} onChange - callback to perform on setting change, callback receives File object + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.disabled=false] - should the setting be disabled + * @param {Array|string} [options.accept] - what file types should be accepted + * @param {boolean} [options.multiple=false] - should multiple files be accepted + */ + constructor(name, note, onChange, options = {}) { + const classes = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.BasicInputs.inputDefault.add("file-input"); + if (options.disabled) classes.add(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.BasicInputs.disabled); + const ReactFilePicker = modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.parseHTML(``); + if (options.disabled) ReactFilePicker.setAttribute("disabled", ""); + if (options.multiple) ReactFilePicker.setAttribute("multiple", ""); + if (options.accept) ReactFilePicker.setAttribute("accept", Array.isArray(options.accept) ? options.accept.join(",") : options.accept); + ReactFilePicker.addEventListener("change", (event) => { + this.onChange(event.target.files[0]); + }); + super(name, note, onChange, ReactFilePicker); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (FilePicker); + +/***/ }), + +/***/ "./src/ui/settings/types/keybind.js": +/*!******************************************!*\ + !*** ./src/ui/settings/types/keybind.js ***! + \******************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + +const React = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React; + +class CloseButton extends React.Component { + render() { + const size = this.props.size || "16px"; + return React.createElement("svg", {className: this.props.className || "", fill: "currentColor", viewBox: "0 0 24 24", style: {width: size, height: size}, onClick: this.props.onClick}, + React.createElement("path", {d: "M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z"}) + ); + } +} + +const toCombo = modules__WEBPACK_IMPORTED_MODULE_1__.WebpackModules.getModule(m => m?.toString?.()?.includes("numpad plus"), {searchExports: true}) ?? (() => [[0, 0], [0, 0]]); +const toEvent = modules__WEBPACK_IMPORTED_MODULE_1__.WebpackModules.getModule(m => m?.toString?.()?.includes("keyCode") && m?.toString?.()?.includes("BROWSER"), {searchExports: true}) ?? (() => ({})); + +class ClearableKeybind extends React.Component { + constructor(props) { + super(props); + + this.state = {value: this.props.defaultValue}; + this.clear = this.clear.bind(this); + } + + clear() { + this.setState({value: []}); + this.props.onChange([]); + } + + render() { + return React.createElement("div", {className: "z-keybind-wrapper"}, + React.createElement(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.Keybind, { + disabled: this.props.disabled, + defaultValue: this.state.value, + onChange: this.props.onChange + }), + React.createElement(CloseButton, {className: "z-keybind-clear", onClick: this.clear}) + ); + } +} + +/** + * Creates a keybind setting using discord's built in keybind recorder. + * @memberof module:Settings= + * @extends module:Settings.SettingField + */ +class Keybind extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {Array} value - array of key names + * @param {callable} onChange - callback to perform on setting change, callback receives array of keycodes + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.disabled=false] - should the setting be disabled + */ + constructor(label, help, value, onChange, options = {}) { + const {disabled = false} = options; + if (!Array.isArray(value) || value.some(v => typeof(v) !== "string")) value = []; // if non-strings present, not a valid combo + super(label, help, onChange, ClearableKeybind, { + disabled: disabled, + defaultValue: toCombo(value.join("+")) ?? [], + onChange: element => val => { + if (!Array.isArray(val)) return; + element.props.value = val; + this.onChange(toEvent(val)); + } + }); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Keybind); + +/***/ }), + +/***/ "./src/ui/settings/types/radiogroup.js": +/*!*********************************************!*\ + !*** ./src/ui/settings/types/radiogroup.js ***! + \*********************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + +/** + * @interface + * @name module:Settings~RadioItem + * @property {string} name - label to show in the dropdown + * @property {*} value - actual value represented by label (this is passed via onChange) + * @property {string} desc - description/help text to show below name + * @property {string} color - hex string to color the item + */ + +/** + * Creates a radio group using discord's built in radios. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class RadioGroup extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {*} defaultValue - currently selected value + * @param {Array} values - array of all options available + * @param {callable} onChange - callback to perform on setting change, callback item value + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.disabled=false] - should the setting be disabled + */ + constructor(name, note, defaultValue, values, onChange, options = {}) { + super(name, note, onChange, modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.RadioGroup, { + noteOnTop: true, + disabled: !!options.disabled, + options: values, + onChange: reactElement => option => { + reactElement.props.value = option.value; + reactElement.forceUpdate(); + this.onChange(option.value); + }, + value: defaultValue + }); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (RadioGroup); + + + +/***/ }), + +/***/ "./src/ui/settings/types/slider.js": +/*!*****************************************!*\ + !*** ./src/ui/settings/types/slider.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + +/** + * Used to render the marker. + * @param {Number} value - The value to render + * @returns {string} the text to show in the marker + * @callback module:Settings~SliderMarkerValue + */ + +/** + * Used to render the grabber tooltip. + * @param {Number} value - The value to render + * @returns {string} the text to show in the tooltip + * @callback module:Settings~SliderRenderValue + */ + +/** + * Creates a slider/range using discord's built in slider. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class Slider extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {number} min - minimum value allowed + * @param {number} max - maximum value allowed + * @param {number} value - currently selected value + * @param {callable} onChange - callback to fire when setting is changed, callback receives number + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.disabled=false] - should the setting be disabled + * @param {object} [options.fillStyles] - object of css styles to add to active slider + * @param {number} [options.defaultValue] - value highlighted as default + * @param {number} [options.keyboardStep] - step moved when using arrow keys + * @param {Array} [options.markers] - array of vertical markers to show on the slider + * @param {boolean} [options.stickToMarkers] - should the slider be forced to use markers + * @param {boolean} [options.equidistant] - should the markers be scaled to be equidistant + * @param {module:Settings~SliderMarkerValue} [options.onMarkerRender] - function to call to render the value in the marker + * @param {module:Settings~SliderMarkerValue} [options.renderMarker] - alias of `onMarkerRender` + * @param {module:Settings~SliderRenderValue} [options.onValueRender] - function to call to render the value in the tooltip + * @param {module:Settings~SliderRenderValue} [options.renderValue] - alias of `onValueRender` + * @param {string} [options.units] - can be used in place of `onValueRender` will use this string and render Math.round(value) + units + */ + constructor(name, note, min, max, value, onChange, options = {}) { + const props = { + onChange: _ => _, + initialValue: value, + disabled: !!options.disabled, + minValue: min, + maxValue: max, + handleSize: 10 + }; + if (options.fillStyles) props.fillStyles = options.fillStyles; + if (typeof(options.defaultValue) !== "undefined") props.defaultValue = options.defaultValue; + if (options.keyboardStep) props.keyboardStep = options.keyboardStep; + if (options.markers) props.markers = options.markers; + if (options.stickToMarkers) props.stickToMarkers = options.stickToMarkers; + if (typeof(options.equidistant) != "undefined") props.equidistant = options.equidistant; + if (options.units) { + const renderValueLabel = (val) => `${Math.round(val)}${options.units}`; + props.onMarkerRender = renderValueLabel; + props.onValueRender = renderValueLabel; + } + if (options.onMarkerRender || options.renderMarker) props.onMarkerRender = options.onMarkerRender || options.renderMarker; + if (options.onValueRender || options.renderValue) props.onValueRender = options.onValueRender || options.renderValue; + super(name, note, onChange, modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.Slider, Object.assign(props, {onValueChange: v => this.onChange(v)})); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Slider); + +/***/ }), + +/***/ "./src/ui/settings/types/switch.js": +/*!*****************************************!*\ + !*** ./src/ui/settings/types/switch.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + + +const {useCallback, useState, createElement} = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React; + +function SwitchComponent({id, checked: initialValue, disabled, onChange}) { + const [checked, setChecked] = useState(initialValue); + const change = useCallback(() => { + onChange?.(!checked); + setChecked(!checked); + }, [checked, onChange]); + + const enabledClass = disabled ? " bd-switch-disabled" : ""; + const checkedClass = checked ? " bd-switch-checked" : ""; + return createElement("div", {className: `bd-switch` + enabledClass + checkedClass}, + createElement("input", {id: id, type: "checkbox", disabled: disabled, checked: checked, onChange: change}), + createElement("div", {className: "bd-switch-body"}, + createElement("svg", {className: "bd-switch-slider", viewBox: "0 0 28 20", preserveAspectRatio: "xMinYMid meet"}, + createElement("rect", {className: "bd-switch-handle", fill: "white", x: "4", y: "0", height: "20", width: "20", rx: "10"}), + createElement("svg", {className: "bd-switch-symbol", viewBox: "0 0 20 20", fill: "none"}, + createElement("path"), + createElement("path") + ) + ) + ) + ); +} + +/** + * Creates a switch using discord's built in switch. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class Switch extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {boolean} isChecked - should switch be checked + * @param {callable} onChange - callback to perform on setting change, callback receives boolean + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.disabled=false] - should the setting be disabled + */ + constructor(name, note, isChecked, onChange, options = {}) { + const props = { + disabled: !!options.disabled, + checked: !!isChecked, + onChange: () => value => this.onChange(value), + inline: true + }; + + super(name, note, onChange, SwitchComponent, props); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Switch); + + +/***/ }), + +/***/ "./src/ui/settings/types/textbox.js": +/*!******************************************!*\ + !*** ./src/ui/settings/types/textbox.js ***! + \******************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + +/** + * Creates a textbox using discord's built in textbox. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class Textbox extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {string} value - current text in box + * @param {callable} onChange - callback to perform on setting change, callback receives text + * @param {object} [options] - object of options to give to the setting + * @param {string} [options.placeholder=""] - placeholder for when textbox is empty + * @param {boolean} [options.disabled=false] - should the setting be disabled + */ + constructor(name, note, value, onChange, options = {}) { + const {placeholder = "", disabled = false} = options; + super(name, note, onChange, modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.Textbox, { + onChange: textbox => val => { + textbox.props.value = val; + textbox.forceUpdate(); + this.onChange(val); + }, + value: value, + disabled: disabled, + placeholder: placeholder || "" + }); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Textbox); + +/***/ }), + +/***/ "./src/ui/toasts.js": +/*!**************************!*\ + !*** ./src/ui/toasts.js ***! + \**************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Toasts) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/* harmony import */ var ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ui */ "./src/ui/ui.js"); +/* harmony import */ var _styles_toasts_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../styles/toasts.css */ "./src/styles/toasts.css"); +/** + * Toast maker similar to Android. + * + * @module Toasts + */ + + + + + +class Toasts { + + static get CSS() {return _styles_toasts_css__WEBPACK_IMPORTED_MODULE_2__["default"];} + + /** Shorthand for `type = "success"` for {@link module:Toasts.show} */ + static async success(content, options = {}) {return this.show(content, Object.assign(options, {type: "success"}));} + + /** Shorthand for `type = "info"` for {@link module:Toasts.show} */ + static async info(content, options = {}) {return this.show(content, Object.assign(options, {type: "info"}));} + + /** Shorthand for `type = "warning"` for {@link module:Toasts.show} */ + static async warning(content, options = {}) {return this.show(content, Object.assign(options, {type: "warning"}));} + + /** Shorthand for `type = "error"` for {@link module:Toasts.show} */ + static async error(content, options = {}) {return this.show(content, Object.assign(options, {type: "error"}));} + + /** Shorthand for `type = "default"` for {@link module:Toasts.show} */ + static async default(content, options = {}) {return this.show(content, Object.assign(options, {type: "default"}));} + + + /** + * Shows a simple toast, similar to Android, centered over + * the textarea if it exists, and center screen otherwise. + * Vertically it shows towards the bottom like in Android. + * @param {string} content - The string to show in the toast. + * @param {object} options - additional options for the toast + * @param {string} [options.type] - Changes the type of the toast stylistically and semantically. {@link module:Toasts.ToastTypes} + * @param {string} [options.icon] - URL to an optional icon + * @param {number} [options.timeout=3000] - Adjusts the time (in ms) the toast should be shown for before disappearing automatically + * @returns {Promise} - Promise that resolves when the toast is removed from the DOM + */ + static async show(content, options = {}) { + const {type = "", icon = "", timeout = 3000} = options; + this.ensureContainer(); + const toast = modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.parseHTML(this.buildToast(content, this.parseType(type), icon)); + document.querySelector(".toasts").appendChild(toast); + await new Promise(resolve => setTimeout(resolve, timeout)); + toast.classList.add("closing"); + await new Promise(resolve => setTimeout(resolve, 300)); + toast.remove(); + if (!document.querySelectorAll(".toasts .toast").length) document.querySelector(".toasts").remove(); + } + + static buildToast(message, type, icon) { + const hasIcon = type || icon; + const className = `toast ${hasIcon ? "toast-has-icon" : ""} ${type && type != "default" ? `toast-${type}` : ""}`; + if (!icon && type) icon = type; + return modules__WEBPACK_IMPORTED_MODULE_0__.Utilities.formatString(`
{{icon}}
{{message}}
`, { + className: className, + icon: hasIcon ? this.getIcon(icon) : "", + message: message + }); + } + + static getIcon(icon) { + let iconInner = ``; + switch (icon) { + case "success": iconInner = ui__WEBPACK_IMPORTED_MODULE_1__.Icons.IconSuccess(20); break; // eslint-disable-line new-cap + case "warning": iconInner = ui__WEBPACK_IMPORTED_MODULE_1__.Icons.IconWarning(20); break; // eslint-disable-line new-cap + case "info": iconInner = ui__WEBPACK_IMPORTED_MODULE_1__.Icons.IconInfo(20); break; // eslint-disable-line new-cap + case "error": iconInner = ui__WEBPACK_IMPORTED_MODULE_1__.Icons.IconError(20); // eslint-disable-line new-cap + } + return modules__WEBPACK_IMPORTED_MODULE_0__.Utilities.formatString(`
{{icon}}
`, {icon: iconInner}); + } + + static ensureContainer() { + if (document.querySelector(".toasts")) return; + const channelClass = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordSelectors.ChannelList.sidebar; + const container = channelClass ? document.querySelector(`${channelClass} ~ div:not([style])`) : null; + const memberlist = container ? container.querySelector(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordSelectors.MemberList.membersWrap) : null; + const form = container ? container.querySelector("form") : null; + const left = container ? container.getBoundingClientRect().left : 310; + const right = memberlist ? memberlist.getBoundingClientRect().left : 0; + const width = right ? right - container.getBoundingClientRect().left : (container?.offsetWidth ?? document.body.offsetWidth / 2); + const bottom = form ? form.offsetHeight : 80; + const toastWrapper = document.createElement("div"); + toastWrapper.classList.add("toasts"); + toastWrapper.style.setProperty("left", left + "px"); + toastWrapper.style.setProperty("width", width + "px"); + toastWrapper.style.setProperty("bottom", bottom + "px"); + document.querySelector("#app-mount").appendChild(toastWrapper); + } + + static parseType(type) { + return this.ToastTypes.hasOwnProperty(type) ? this.ToastTypes[type] : ""; + } + + /** + * Enumeration of accepted types. + */ + static get ToastTypes() { + return { + "default": "", + "error": "error", + "success": "success", + "warning": "warning", + "info": "info" + }; + } +} + +/***/ }), + +/***/ "./src/ui/tooltip.js": +/*!***************************!*\ + !*** ./src/ui/tooltip.js ***! + \***************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Tooltip) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/** + * Tooltip that automatically show and hide themselves on mouseenter and mouseleave events. + * Will also remove themselves if the node to watch is removed from DOM through + * a MutationObserver. + * + * Note this is not using Discord's internals but normal DOM manipulation and emulates + * Discord's own tooltips as closely as possible. + * + * @module Tooltip + */ + + + +const getClass = function(sideOrColor) { + const upperCase = sideOrColor[0].toUpperCase() + sideOrColor.slice(1); + const tooltipClass = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Tooltips[`tooltip${upperCase}`]; + if (tooltipClass) return tooltipClass.value; + return null; +}; + +const classExists = function(sideOrColor) { + return !!getClass(sideOrColor); +}; + +const toPx = function(value) { + return `${value}px`; +}; + +/*
+
+
+ User Settings +
+
*/ + +class Tooltip { + /** + * + * @constructor + * @param {(HTMLElement|jQuery)} node - DOM node to monitor and show the tooltip on + * @param {string} tip - string to show in the tooltip + * @param {object} options - additional options for the tooltip + * @param {string} [options.style=black] - correlates to the discord styling/colors (black, brand, green, grey, red, yellow) + * @param {string} [options.side=top] - can be any of top, right, bottom, left + * @param {boolean} [options.preventFlip=false] - prevents moving the tooltip to the opposite side if it is too big or goes offscreen + * @param {boolean} [options.isTimestamp=false] - adds the timestampTooltip class (disables text wrapping) + * @param {boolean} [options.disablePointerEvents=false] - disables pointer events + * @param {boolean} [options.disabled=false] - whether the tooltip should be disabled from showing on hover + */ + constructor(node, text, options = {}) { + const {style = "black", side = "top", preventFlip = false, isTimestamp = false, disablePointerEvents = false, disabled = false} = options; + this.node = modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.resolveElement(node); + this.label = text; + this.style = style.toLowerCase(); + this.side = side.toLowerCase(); + this.preventFlip = preventFlip; + this.isTimestamp = isTimestamp; + this.disablePointerEvents = disablePointerEvents; + this.disabled = disabled; + this.active = false; + + if (!classExists(this.side)) return modules__WEBPACK_IMPORTED_MODULE_0__.Logger.err("Tooltip", `Side ${this.side} does not exist.`); + if (!classExists(this.style)) return modules__WEBPACK_IMPORTED_MODULE_0__.Logger.err("Tooltip", `Style ${this.style} does not exist.`); + + this.element = modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.createElement(`
`); + this.tooltipElement = modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.createElement(`
${this.label}
`); + this.labelElement = this.tooltipElement.childNodes[1]; + this.element.append(this.tooltipElement); + + if (this.disablePointerEvents) { + this.element.classList.add(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.TooltipLayers.disabledPointerEvents); + this.tooltipElement.classList.add(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Tooltips.tooltipDisablePointerEvents); + } + if (this.isTimestamp) this.tooltipElement.classList.add(modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getByProps("timestampTooltip").timestampTooltip); + + + this.node.addEventListener("mouseenter", () => { + if (this.disabled) return; + this.show(); + }); + + this.node.addEventListener("mouseleave", () => { + this.hide(); + }); + } + + /** Alias for the constructor */ + static create(node, text, options = {}) {return new Tooltip(node, text, options);} + + /** Container where the tooltip will be appended. */ + get container() {return document.querySelector(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordSelectors.App.app.sibling(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordSelectors.TooltipLayers.layerContainer));} + /** Boolean representing if the tooltip will fit on screen above the element */ + get canShowAbove() {return this.node.getBoundingClientRect().top - this.element.offsetHeight >= 0;} + /** Boolean representing if the tooltip will fit on screen below the element */ + get canShowBelow() {return this.node.getBoundingClientRect().top + this.node.offsetHeight + this.element.offsetHeight <= modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.screenHeight;} + /** Boolean representing if the tooltip will fit on screen to the left of the element */ + get canShowLeft() {return this.node.getBoundingClientRect().left - this.element.offsetWidth >= 0;} + /** Boolean representing if the tooltip will fit on screen to the right of the element */ + get canShowRight() {return this.node.getBoundingClientRect().left + this.node.offsetWidth + this.element.offsetWidth <= modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.screenWidth;} + + /** Hides the tooltip. Automatically called on mouseleave. */ + hide() { + /** Don't rehide if already inactive */ + if (!this.active) return; + this.active = false; + this.element.remove(); + this.tooltipElement.className = this._className; + } + + /** Shows the tooltip. Automatically called on mouseenter. Will attempt to flip if position was wrong. */ + show() { + /** Don't reshow if already active */ + if (this.active) return; + this.active = true; + this.tooltipElement.className = `${modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Tooltips.tooltip} ${getClass(this.style)}`; + if (this.disablePointerEvents) this.tooltipElement.classList.add(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Tooltips.tooltipDisablePointerEvents); + if (this.isTimestamp) this.tooltipElement.classList.add(modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getByProps("timestampTooltip").timestampTooltip); + this.labelElement.textContent = this.label; + this.container.append(this.element); + + if (this.side == "top") { + if (this.canShowAbove || (!this.canShowAbove && this.preventFlip)) this.showAbove(); + else this.showBelow(); + } + + if (this.side == "bottom") { + if (this.canShowBelow || (!this.canShowBelow && this.preventFlip)) this.showBelow(); + else this.showAbove(); + } + + if (this.side == "left") { + if (this.canShowLeft || (!this.canShowLeft && this.preventFlip)) this.showLeft(); + else this.showRight(); + } + + if (this.side == "right") { + if (this.canShowRight || (!this.canShowRight && this.preventFlip)) this.showRight(); + else this.showLeft(); + } + + /** Do not create a new observer each time if one already exists! */ + if (this.observer) return; + /** Use an observer in show otherwise you'll cause unclosable tooltips */ + this.observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + const nodes = Array.from(mutation.removedNodes); + const directMatch = nodes.indexOf(this.node) > -1; + const parentMatch = nodes.some(parent => parent.contains(this.node)); + if (directMatch || parentMatch) { + this.hide(); + this.observer.disconnect(); + } + }); + }); + + this.observer.observe(document.body, {subtree: true, childList: true}); + } + + /** Force showing the tooltip above the node. */ + showAbove() { + this.tooltipElement.classList.add(getClass("top")); + this.element.style.setProperty("top", toPx(this.node.getBoundingClientRect().top - this.element.offsetHeight - 10)); + this.centerHorizontally(); + } + + /** Force showing the tooltip below the node. */ + showBelow() { + this.tooltipElement.classList.add(getClass("bottom")); + this.element.style.setProperty("top", toPx(this.node.getBoundingClientRect().top + this.node.offsetHeight + 10)); + this.centerHorizontally(); + } + + /** Force showing the tooltip to the left of the node. */ + showLeft() { + this.tooltipElement.classList.add(getClass("left")); + this.element.style.setProperty("left", toPx(this.node.getBoundingClientRect().left - this.element.offsetWidth - 10)); + this.centerVertically(); + } + + /** Force showing the tooltip to the right of the node. */ + showRight() { + this.tooltipElement.classList.add(getClass("right")); + this.element.style.setProperty("left", toPx(this.node.getBoundingClientRect().left + this.node.offsetWidth + 10)); + this.centerVertically(); + } + + centerHorizontally() { + const nodecenter = this.node.getBoundingClientRect().left + (this.node.offsetWidth / 2); + this.element.style.setProperty("left", toPx(nodecenter - (this.element.offsetWidth / 2))); + } + + centerVertically() { + const nodecenter = this.node.getBoundingClientRect().top + (this.node.offsetHeight / 2); + this.element.style.setProperty("top", toPx(nodecenter - (this.element.offsetHeight / 2))); + } +} + +/***/ }), + +/***/ "./src/ui/ui.js": +/*!**********************!*\ + !*** ./src/ui/ui.js ***! + \**********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ DiscordContextMenu: () => (/* reexport safe */ _discordcontextmenu__WEBPACK_IMPORTED_MODULE_6__["default"]), +/* harmony export */ ErrorBoundary: () => (/* reexport safe */ _errorboundary__WEBPACK_IMPORTED_MODULE_7__["default"]), +/* harmony export */ Icons: () => (/* reexport module object */ _icons__WEBPACK_IMPORTED_MODULE_1__), +/* harmony export */ Modals: () => (/* reexport safe */ _modals__WEBPACK_IMPORTED_MODULE_5__["default"]), +/* harmony export */ Popouts: () => (/* reexport safe */ _popouts__WEBPACK_IMPORTED_MODULE_4__["default"]), +/* harmony export */ Settings: () => (/* reexport module object */ _settings__WEBPACK_IMPORTED_MODULE_0__), +/* harmony export */ Toasts: () => (/* reexport safe */ _toasts__WEBPACK_IMPORTED_MODULE_3__["default"]), +/* harmony export */ Tooltip: () => (/* reexport safe */ _tooltip__WEBPACK_IMPORTED_MODULE_2__["default"]) +/* harmony export */ }); +/* harmony import */ var _settings__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./settings */ "./src/ui/settings/index.js"); +/* harmony import */ var _icons__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./icons */ "./src/ui/icons.js"); +/* harmony import */ var _tooltip__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tooltip */ "./src/ui/tooltip.js"); +/* harmony import */ var _toasts__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./toasts */ "./src/ui/toasts.js"); +/* harmony import */ var _popouts__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./popouts */ "./src/ui/popouts.js"); +/* harmony import */ var _modals__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./modals */ "./src/ui/modals.js"); +/* harmony import */ var _discordcontextmenu__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./discordcontextmenu */ "./src/ui/discordcontextmenu.js"); +/* harmony import */ var _errorboundary__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./errorboundary */ "./src/ui/errorboundary.js"); + + + + + + + + + + + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ (() => { +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ })(); +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; +// This entry need to be wrapped in an IIFE because it need to be in strict mode. +(() => { +"use strict"; +/*!**********************!*\ + !*** ./src/index.js ***! + \**********************/ +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/* harmony import */ var ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ui */ "./src/ui/ui.js"); +/* harmony import */ var _structs_plugin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./structs/plugin */ "./src/structs/plugin.js"); + + + + +const LibraryConfig = __webpack_require__(/*! ./config */ "./src/config.js"); // Use cjs require to prevent polyfill + +const Library = {}; +Library.DCM = ui__WEBPACK_IMPORTED_MODULE_1__.DiscordContextMenu; +Library.ContextMenu = ui__WEBPACK_IMPORTED_MODULE_1__.DiscordContextMenu; +Library.Tooltip = ui__WEBPACK_IMPORTED_MODULE_1__.Tooltip; +Library.Toasts = ui__WEBPACK_IMPORTED_MODULE_1__.Toasts; +Library.Settings = ui__WEBPACK_IMPORTED_MODULE_1__.Settings; +Library.Popouts = ui__WEBPACK_IMPORTED_MODULE_1__.Popouts; +Library.Modals = ui__WEBPACK_IMPORTED_MODULE_1__.Modals; +for (const mod in modules__WEBPACK_IMPORTED_MODULE_0__) Library[mod] = modules__WEBPACK_IMPORTED_MODULE_0__[mod]; + +Library.Components = {ErrorBoundary: ui__WEBPACK_IMPORTED_MODULE_1__.ErrorBoundary}; + +// export default LibraryPlugin(Library.Structs.Plugin, Library); // eslint-disable-line new-cap + +class PluginLibrary extends _structs_plugin__WEBPACK_IMPORTED_MODULE_2__["default"] { + get Library() {return Library;} + + constructor() { + super(LibraryConfig); + + const wasLibLoaded = !!document.getElementById("ZLibraryCSS"); + const isBDLoading = document.getElementById("bd-loading-icon"); + modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.removeStyle("ZLibraryCSS"); + modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.addStyle("ZLibraryCSS", ui__WEBPACK_IMPORTED_MODULE_1__.Settings.CSS + ui__WEBPACK_IMPORTED_MODULE_1__.Toasts.CSS + modules__WEBPACK_IMPORTED_MODULE_0__.PluginUpdater.CSS); + ui__WEBPACK_IMPORTED_MODULE_1__.Popouts.initialize(); + + /** + * Checking if this is the library first being loaded during init + * This means that subsequent loads will cause dependents to reload + * This also means first load when installing for the first time + * will automatically reload the dependent plugins. This is needed + * for those plugins that prompt to download and install the lib. + */ + + if (!wasLibLoaded && isBDLoading) return; // If the this is the lib's first load AND this is BD's initialization + + /** + * Now we can go ahead and reload any dependent plugins by checking + * for any with instance._config. Both plugins using buildPlugin() + * and plugin skeletons that prompt for download should have this + * instance property. + */ + + // Temporarily disable toasts so people don't get bombarded + const wasEnabled = BdApi?.isSettingEnabled("settings", "general", "showToasts"); + if (wasEnabled) BdApi?.disableSetting("settings", "general", "showToasts"); + this._reloadPlugins(); + if (wasEnabled) BdApi?.enableSetting("settings", "general", "showToasts"); + } + + _reloadPlugins() { + const list = BdApi.Plugins.getAll().reduce((acc, val) => { + if (!val.instance || !val.instance._config) return acc; + const name = val.id || val.instance?.getName(); + if (name === "ZeresPluginLibrary") return acc; + acc.push(name); + return acc; + }, []); + for (let p = 0; p < list.length; p++) BdApi.Plugins.reload(list[p]); + } + + static bindLibrary(name) { + const BoundAPI = { + Logger: { + stacktrace: (message, error) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.stacktrace(name, message, error), + log: (...message) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.log(name, ...message), + error: (...message) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.err(name, ...message), + err: (...message) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.err(name, ...message), + warn: (...message) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.warn(name, ...message), + info: (...message) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.info(name, ...message), + debug: (...message) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.debug(name, ...message) + }, + Patcher: { + getPatchesByCaller: () => {return modules__WEBPACK_IMPORTED_MODULE_0__.Patcher.getPatchesByCaller(name);}, + unpatchAll: () => {return modules__WEBPACK_IMPORTED_MODULE_0__.Patcher.unpatchAll(name);}, + before: (moduleToPatch, functionName, callback, options = {}) => {return modules__WEBPACK_IMPORTED_MODULE_0__.Patcher.before(name, moduleToPatch, functionName, callback, options);}, + instead: (moduleToPatch, functionName, callback, options = {}) => {return modules__WEBPACK_IMPORTED_MODULE_0__.Patcher.instead(name, moduleToPatch, functionName, callback, options);}, + after: (moduleToPatch, functionName, callback, options = {}) => {return modules__WEBPACK_IMPORTED_MODULE_0__.Patcher.after(name, moduleToPatch, functionName, callback, options);} + } + }; + const BoundLib = Object.assign({}, Library); + BoundLib.Logger = BoundAPI.Logger; + BoundLib.Patcher = BoundAPI.Patcher; + return BoundLib; + } + + static buildPlugin(config) { + return [(0,_structs_plugin__WEBPACK_IMPORTED_MODULE_2__.wrapPluginBase)(config), this.bindLibrary(config.name ?? config.info.name)]; // eslint-disable-line new-cap + } +} + +Object.assign(PluginLibrary, Library); +Library.bindLibrary = PluginLibrary.bindLibrary; +Library.buildPlugin = PluginLibrary.buildPlugin; +window.ZLibrary = Library; +window.ZeresPluginLibrary = PluginLibrary; +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (PluginLibrary); + +})(); + +module.exports.ZeresPluginLibrary = __webpack_exports__["default"]; +/******/ })() +; +/*@end@*/ \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ActivityIcons.config.json b/dotfiles/.config/BetterDiscord/plugins/ActivityIcons.config.json new file mode 100644 index 0000000..973afbb --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ActivityIcons.config.json @@ -0,0 +1,14 @@ +{ + "settings": { + "normalActivityIcons": true, + "richPresenceIcons": true, + "platformIcons": true, + "listeningIcons": true, + "watchingIcons": true + }, + "currentVersionInfo": { + "version": "1.4.11", + "hasShownChangelog": true + }, + "changelogVersion": "1.5.3" +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/Animations.config.json b/dotfiles/.config/BetterDiscord/plugins/Animations.config.json new file mode 100644 index 0000000..bd836e3 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/Animations.config.json @@ -0,0 +1,110 @@ +{ + "settings": { + "buttons": { + "custom": { + "enabled": false, + "frames": [ + { + "anim": "", + "start": "" + }, + { + "anim": "", + "start": "" + }, + { + "anim": "", + "start": "" + } + ], + "page": 0 + }, + "delay": 0.1, + "duration": 0.3, + "enabled": true, + "name": "slide-down-right", + "page": 1, + "selectors": "", + "sequence": "fromLast", + "timing": "linear" + }, + "lists": { + "custom": { + "enabled": false, + "frames": [ + { + "anim": "", + "start": "" + }, + { + "anim": "", + "start": "" + }, + { + "anim": "", + "start": "" + } + ], + "page": 2 + }, + "delay": "0.015", + "duration": "0.01", + "enabled": true, + "name": "slide-down-right", + "page": 1, + "selectors": "", + "sequence": "fromFirst", + "timing": "linear" + }, + "messages": { + "custom": { + "enabled": false, + "frames": [ + { + "anim": "", + "start": "" + }, + { + "anim": "", + "start": "" + }, + { + "anim": "", + "start": "" + } + ], + "page": 0 + }, + "delay": 0.055, + "duration": 0.3, + "enabled": true, + "limit": 30, + "name": "slide-down-right", + "page": 2, + "sending": { + "custom": { + "enabled": false, + "frames": [ + { + "anim": "", + "start": "" + }, + { + "anim": "", + "start": "" + }, + { + "anim": "", + "start": "" + } + ], + "page": 0 + }, + "enabled": "onsent", + "name": "slide-down-left", + "page": 1 + }, + "timing": "linear" + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/BetterChannelList.config.json b/dotfiles/.config/BetterDiscord/plugins/BetterChannelList.config.json new file mode 100644 index 0000000..2ebabbb --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/BetterChannelList.config.json @@ -0,0 +1,19 @@ +{ + "currentVersionInfo": { + "version": "1.1.7", + "hasShownChangelog": true + }, + "settings": { + "lastMessage": { + "enabled": true, + "roleColors": true + }, + "redesign": { + "enabled": true, + "iconSize": "small" + }, + "resizer": { + "enabled": true + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/BetterChatNames.plugin.js b/dotfiles/.config/BetterDiscord/plugins/BetterChatNames.plugin.js new file mode 100644 index 0000000..d60aa24 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/BetterChatNames.plugin.js @@ -0,0 +1,237 @@ +/** + * @name BetterChatNames + * @author Break + * @description Improves chat names by automatically capitalising them and/or removing dashes/underscores + * @version 1.9.0 + * @authorLink https://github.com/Break-Ben + * @website https://github.com/Break-Ben/BetterDiscordAddons + * @source https://github.com/Break-Ben/BetterDiscordAddons/tree/main/BetterChatNames + * @updateUrl https://raw.githubusercontent.com/Break-Ben/BetterDiscordAddons/main/BetterChatNames/BetterChatNames.plugin.js + */ + +const defaultSettings = { + capitalise: true, + removeDashes: true, + removeEmojis: false, + patchUnrestricted: true, + capitalOverrides: 'and, or, of, the, for, to, at, a, FAQ, VC, GitHub' +} + +const unrestrictedChannels = { + voice: 2, + thread: 11, + stage: 13 +} +const regex = { + dash: /-|_/g, + capital: /(?<=(^|[^\p{L}'’]))\p{L}/gu, + word: /\b\p{L}+\b/gu, + emoji: /([-_]?\p{Extended_Pictographic}[-_┃]?)|(\uFE0F\u20E3)/gu +} + +let titleObserver +const { Webpack, Patcher, Utils, Data, UI } = new BdApi('BetterChatNames') +const { getModule, getByStrings, getByKeys, getByPrototypeKeys } = Webpack +const { findInTree } = Utils +const searchOptions = { walkable: ['children', 'props'] } // For Utils.findInTree + +const currentServer = getByKeys('getLastSelectedGuildId') +const currentChannel = getByKeys('getLastSelectedChannelId') +const transitionTo = getByStrings('transitionTo - Transitioning to', { searchExports: true }) + +const sidebar = getModule(m => m?.render?.toString()?.includes('.CHANNEL')) +const header = getByStrings('.HEADER_BAR)', { defaultExport: false }) +const placeholder = getByPrototypeKeys('getPlaceholder').prototype +const mention = getByStrings('channelWithIcon', { defaultExport: false }) + +const debounceTimeMs = 1000 + +module.exports = class BetterChatNames { + start() { + this.settings = Object.assign({}, defaultSettings, Data.load('settings')) + this.updateOverrideMap() + this.observeAppTitle() + this.patchNames() + this.refreshChannel() + } + + stop() { + titleObserver.disconnect() + Patcher.unpatchAll() + this.refreshChannel() + } + + getSettingsPanel = () => UI.buildSettingsPanel({ + settings: [ + { + id: 'capitalise', + name: 'Capitalise Words', + type: 'switch', + value: this.settings.capitalise + }, + { + id: 'removeDashes', + name: 'Remove Dashes and Underscores', + type: 'switch', + value: this.settings.removeDashes + }, + { + id: 'removeEmojis', + name: 'Remove Emojis', + type: 'switch', + value: this.settings.removeEmojis + }, + { + id: 'patchUnrestricted', + name: 'Patch Unrestricted Channels', + note: 'Change the names of channels that can already contain capital letters and spaces, such as voice channels, threads, and stages.', + type: 'switch', + value: this.settings.patchUnrestricted + }, + { + id: 'capitalOverrides', + name: 'Capitalisation Overrides', + note: 'A comma-separated list of words with custom capitalisations. For example: "and, FAQ, GitHub, iPhone". Only enabled if "Capitalise Words" is enabled, and can be cleared to be disabled.', + type: 'text', + inline: false, + value: this.settings.capitalOverrides + } + ], + onChange: (_, id, value) => { + if (id === 'capitalOverrides') { + clearTimeout(this.debounceTimer) + this.debounceTimer = setTimeout(() => { + this.settings[id] = value + Data.save('settings', this.settings) + this.updateOverrideMap() + if (this.settings.capitalise) + this.refreshChannel() + }, debounceTimeMs) + return + } + + this.settings[id] = value + Data.save('settings', this.settings) + this.refreshChannel() + } + }) + + observeAppTitle() { + let lastUnpatchedAppTitle + titleObserver = new MutationObserver(() => { + if (document.title !== lastUnpatchedAppTitle) { // Resolves conflicts with EditChannels' MutationObserver + lastUnpatchedAppTitle = document.title + this.patchAppTitle() + } + }) + titleObserver.observe(document.querySelector('title'), { childList: true }) + } + + patchAppTitle() { + const patchedTitle = this.patchText(document.title) + + if (currentServer?.getGuildId() && document.title !== patchedTitle) // If in server and title not already patched + document.title = patchedTitle + } + + patchNames() { + this.patchSidebar() + this.patchHeader() + this.patchChatPlaceholder() + this.patchMention() + } + + patchSidebar() { + Patcher.after(sidebar, 'render', (_, __, data) => { + const channelName = findInTree(data, prop => prop?.name, searchOptions) + const channelType = findInTree(data, prop => prop?.channel, searchOptions).channel.type + + if (!Object.values(unrestrictedChannels).includes(channelType) || this.settings.patchUnrestricted) // If not a voice/stage channel or patchUnrestricted is enabled + channelName.name = this.patchText(channelName.name) + }) + } + + patchHeader() { + Patcher.after(header, 'A', (_, __, data) => { + const rootChannel = findInTree(data, prop => prop?.level, searchOptions) + if (!rootChannel) + return + + if (rootChannel.level === 1) // If not in thread + rootChannel.children.props.children[2] = this.patchText(rootChannel.children.props.children[2]) + else if (rootChannel.level === 2) { // If in thread + rootChannel.children = this.patchText(rootChannel.children) + if (this.settings.patchUnrestricted) { + const threadName = findInTree(data, prop => Array.isArray(prop) && prop[1] === ' ', searchOptions) + threadName[2] = this.patchText(threadName[2]) + } + } + }) + } + + patchChatPlaceholder() { + Patcher.after(placeholder, 'render', (_, __, data) => { + const textArea = findInTree(data, prop => prop.channel, searchOptions) + const isThread = textArea.channel.type === unrestrictedChannels.thread + + if (textArea.channel.guild_id && (!isThread || this.settings.patchUnrestricted) && !textArea.disabled && textArea.type.analyticsName === 'normal') { // If in a server, not in a thread (or patchUnrestricted is enabled), can message and not editing a message + const splitIndex = (isThread ? textArea.placeholder.indexOf('"') : textArea.placeholder.indexOf('#')) + 1 // Indicates where the channel name starts + textArea.placeholder = textArea.placeholder.substring(0, splitIndex) + this.patchText(textArea.placeholder.substr(splitIndex)) + } + }) + } + + patchMention() { + Patcher.after(mention, 'A', (_, __, data) => { + const channelName = findInTree(data, prop => typeof prop.children === 'string', searchOptions) + + if (data.props.className.includes('iconMentionText') || this.settings.patchUnrestricted) // If is a normal chat mention (not a thread) or patchUnrestricted is enabled + channelName.children = this.patchText(channelName.children) + }) + } + + patchText(text) { + if (this.settings.removeEmojis) + text = text.replace(regex.emoji, '') + + if (this.settings.removeDashes) + text = text.replace(regex.dash, ' ') + + if (this.settings.capitalise) { + text = text.replace(regex.capital, letter => letter.toUpperCase()) + + if (this.settings.capitalOverrides) { + let wordCount = 0 + text = text.replace(regex.word, word => { + wordCount++ + const wordLower = word.toLowerCase() + if (!this.overrideMap.has(wordLower)) + return word + + const wordOverride = this.overrideMap.get(wordLower) + if (wordCount == 1 && wordOverride === wordLower) + return word + + return wordOverride + }) + } + } + + return text + } + + refreshChannel() { + const currentServerId = currentServer?.getGuildId() + const currentChannelId = currentChannel?.getChannelId() + + if (currentServerId) { // If not in a DM + transitionTo('/channels/@me') + setImmediate(() => transitionTo(`/channels/${currentServerId}/${currentChannelId}`)) + } + } + + updateOverrideMap() { + // Maps lowercase words to their custom capitalised versions + this.overrideMap = new Map((this.settings.capitalOverrides).split(',').map(w => w.trim()).filter(w => w).map(w => [w.toLowerCase(), w])) + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/BetterFormattingRedux.config.json b/dotfiles/.config/BetterDiscord/plugins/BetterFormattingRedux.config.json new file mode 100755 index 0000000..398dc8b --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/BetterFormattingRedux.config.json @@ -0,0 +1,59 @@ +{ + "currentVersionInfo": { + "version": "2.3.14", + "hasShownChangelog": true + }, + "settings": { + "toolbar": { + "bold": true, + "italic": true, + "underline": true, + "strikethrough": true, + "spoiler": true, + "code": true, + "codeblock": true, + "superscript": true, + "smallcaps": true, + "fullwidth": true, + "upsidedown": true, + "varied": true, + "leet": false, + "thicc": false + }, + "formats": { + "superscript": true, + "smallcaps": true, + "fullwidth": true, + "upsidedown": true, + "varied": true, + "leet": false, + "thicc": false + }, + "wrappers": { + "superscript": "^^", + "smallcaps": "%%", + "fullwidth": "##", + "upsidedown": "&&", + "varied": "==", + "leet": "++", + "thicc": "$$" + }, + "formatting": { + "fullWidthMap": true, + "reorderUpsidedown": true, + "fullwidth": true + }, + "plugin": { + "hoverOpen": true, + "chainFormats": true, + "closeOnSend": true + }, + "style": { + "icons": true, + "rightSide": true, + "toolbarOpacity": 1, + "fontSize": 85 + } + }, + "version": "2.3.15" +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/BetterFormattingRedux.plugin.js b/dotfiles/.config/BetterDiscord/plugins/BetterFormattingRedux.plugin.js new file mode 100755 index 0000000..6ab2962 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/BetterFormattingRedux.plugin.js @@ -0,0 +1,1002 @@ +/** + * @name BetterFormattingRedux + * @description Enables different types of formatting in standard Discord chat. + * @version 2.3.15 + * @author Zerebos + * @authorId 249746236008169473 + * @website https://github.com/zerebos/BetterDiscordAddons/tree/master/Plugins/BetterFormattingRedux + * @source https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/master/Plugins/BetterFormattingRedux/BetterFormattingRedux.plugin.js + */ + +/*@cc_on +@if (@_jscript) + + // Offer to self-install for clueless users that try to run this directly. + var shell = WScript.CreateObject("WScript.Shell"); + var fs = new ActiveXObject("Scripting.FileSystemObject"); + var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins"); + var pathSelf = WScript.ScriptFullName; + // Put the user at ease by addressing them in the first person + shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30); + if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) { + shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40); + } else if (!fs.FolderExists(pathPlugins)) { + shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10); + } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) { + fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true); + // Show the user where to put plugins in the future + shell.Exec("explorer " + pathPlugins); + shell.Popup("I'm installed!", 0, "Successfully installed", 0x40); + } + WScript.Quit(); + +@else@*/ + +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/plugins/BetterFormattingRedux/index.ts +var index_exports = {}; +__export(index_exports, { + default: () => BetterFormattingRedux +}); +module.exports = __toCommonJS(index_exports); + +// src/common/plugin.ts +var Plugin = class { + meta; + manifest; + settings; + defaultSettings; + LocaleManager; + get strings() { + if (!this.manifest.strings) return {}; + const locale = this.LocaleManager?.locale.split("-")[0] ?? "en"; + if (this.manifest.strings.hasOwnProperty(locale)) return this.manifest.strings[locale]; + if (this.manifest.strings.hasOwnProperty("en")) return this.manifest.strings.en; + return this.manifest.strings; + } + constructor(meta, zplConfig) { + this.meta = meta; + this.manifest = zplConfig; + if (typeof this.manifest.config !== "undefined") { + this.defaultSettings = {}; + for (let s = 0; s < this.manifest.config.length; s++) { + const current = this.manifest.config[s]; + if (current.type != "category") { + this.defaultSettings[current.id] = current.value; + } else { + for (let si = 0; si < current.settings.length; si++) { + const subCurrent = current.settings[si]; + this.defaultSettings[subCurrent.id] = subCurrent.value; + } + } + } + this.settings = BdApi.Utils.extend({}, this.defaultSettings); + } + const currentVersionInfo = BdApi.Data.load(this.meta.name, "version"); + if (currentVersionInfo !== this.meta.version) { + this.#showChangelog(); + BdApi.Data.save(this.meta.name, "version", this.meta.version); + } + if (this.manifest.strings) this.LocaleManager = BdApi.Webpack.getByKeys("locale", "initialize"); + if (this.manifest.config && !this.getSettingsPanel) { + this.getSettingsPanel = () => { + this.#updateConfig(); + return BdApi.UI.buildSettingsPanel({ + onChange: (_, id, value) => { + this.settings[id] = value; + this.saveSettings(); + }, + settings: this.manifest.config + }); + }; + } + } + async start() { + BdApi.Logger.info(this.meta.name, `version ${this.meta.version} has started.`); + if (this.defaultSettings) this.settings = this.loadSettings(); + if (typeof this.onStart == "function") this.onStart(); + } + stop() { + BdApi.Logger.info(this.meta.name, `version ${this.meta.version} has stopped.`); + if (typeof this.onStop == "function") this.onStop(); + } + #showChangelog() { + if (typeof this.manifest.changelog == "undefined") return; + const changelog = { + title: this.meta.name + " Changelog", + subtitle: `v${this.meta.version}`, + changes: [] + }; + if (!Array.isArray(this.manifest.changelog)) Object.assign(changelog, this.manifest.changelog); + else changelog.changes = this.manifest.changelog; + BdApi.UI.showChangelogModal(changelog); + } + saveSettings() { + BdApi.Data.save(this.meta.name, "settings", this.settings); + } + loadSettings() { + return BdApi.Utils.extend({}, this.defaultSettings ?? {}, BdApi.Data.load(this.meta.name, "settings")); + } + #updateConfig() { + if (!this.manifest.config) return; + for (const setting of this.manifest.config) { + if (setting.type !== "category") { + setting.value = this.settings[setting.id] ?? setting.value; + } else { + for (const subsetting of setting.settings) { + subsetting.value = this.settings[subsetting.id] ?? subsetting.value; + } + } + } + } + buildSettingsPanel(onChange) { + this.#updateConfig(); + return BdApi.UI.buildSettingsPanel({ + onChange: (groupId, id, value) => { + this.settings[id] = value; + onChange?.(groupId, id, value); + this.saveSettings(); + }, + settings: this.manifest.config + }); + } +}; + +// src/plugins/BetterFormattingRedux/config.ts +var manifest = { + info: { + name: "BetterFormattingRedux", + authors: [{ + name: "Zerebos", + discord_id: "249746236008169473", + github_username: "zerebos", + twitter_username: "IAmZerebos" + }], + version: "2.3.15", + description: "Enables different types of formatting in standard Discord chat.", + github: "https://github.com/zerebos/BetterDiscordAddons/tree/master/Plugins/BetterFormattingRedux", + github_raw: "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/master/Plugins/BetterFormattingRedux/BetterFormattingRedux.plugin.js" + }, + changelog: [ + { + title: "GUI Works Again", + type: "fixed", + items: [ + "All basic formatting buttons are now working again.", + "Settings appear and work as expected.", + "Formatting should happen for messages with images." + ] + } + ], + main: "index.ts", + config: [ + { + type: "category", + id: "toolbar", + name: "Toolbar Buttons", + collapsible: true, + shown: false, + settings: [ + { type: "switch", id: "boldButton", name: "Bold", value: true }, + { type: "switch", id: "italicButton", name: "Italic", value: true }, + { type: "switch", id: "underlineButton", name: "Underline", value: true }, + { type: "switch", id: "strikethroughButton", name: "Strikethrough", value: true }, + { type: "switch", id: "spoilerButton", name: "Spoiler", value: true }, + { type: "switch", id: "codeButton", name: "Code", value: true }, + { type: "switch", id: "codeblockButton", name: "Codeblock", value: true }, + { type: "switch", id: "superscriptButton", name: "Superscript", value: true }, + { type: "switch", id: "smallcapsButton", name: "Smallcaps", value: true }, + { type: "switch", id: "fullwidthButton", name: "Full Width", value: true }, + { type: "switch", id: "upsidedownButton", name: "Upsidedown", value: true }, + { type: "switch", id: "variedButton", name: "Varied Caps", value: true }, + { type: "switch", id: "leetButton", name: "Leet (1337)", value: false }, + { type: "switch", id: "thiccButton", name: "Extra Thicc", value: false }, + { type: "switch", id: "firstcapsButton", name: "First Caps", value: false }, + { type: "switch", id: "uppercaseButton", name: "Uppercase", value: false }, + { type: "switch", id: "lowercaseButton", name: "Lowercase", value: false } + ] + }, + { + type: "category", + id: "formats", + name: "Active Formats", + collapsible: true, + shown: false, + settings: [ + { type: "switch", id: "superscriptFormat", name: "Superscript", value: true }, + { type: "switch", id: "smallcapsFormat", name: "Smallcaps", value: true }, + { type: "switch", id: "fullwidthFormat", name: "Full Width", value: true }, + { type: "switch", id: "upsidedownFormat", name: "Upsidedown", value: true }, + { type: "switch", id: "variedFormat", name: "Varied Caps", value: true }, + { type: "switch", id: "leetFormat", name: "Leet (1337)", value: false }, + { type: "switch", id: "thiccFormat", name: "Extra Thicc", value: false }, + { type: "switch", id: "firstcapsFormat", name: "First Caps", value: false }, + { type: "switch", id: "uppercaseFormat", name: "Uppercase", value: false }, + { type: "switch", id: "lowercaseFormat", name: "Lowercase", value: false } + ] + }, + { + type: "category", + id: "wrappers", + name: "Wrapper Options", + collapsible: true, + shown: false, + settings: [ + { type: "text", id: "superscriptWrapper", name: "Superscript", note: "The wrapper for superscripted text", value: "^^" }, + { type: "text", id: "smallcapsWrapper", name: "Smallcaps", note: "The wrapper to make Smallcaps.", value: "%%" }, + { type: "text", id: "fullwidthWrapper", name: "Full Width", note: "The wrapper for E X P A N D E D T E X T.", value: "##" }, + { type: "text", id: "upsidedownWrapper", name: "Upsidedown", note: "The wrapper to flip the text upsidedown.", value: "&&" }, + { type: "text", id: "variedWrapper", name: "Varied Caps", note: "The wrapper to VaRy the capitalization.", value: "==" }, + { type: "text", id: "leetWrapper", name: "Leet (1337)", note: "The wrapper to talk in 13375p34k.", value: "++" }, + { type: "text", id: "thiccWrapper", name: "Extra Thicc", note: "The wrapper to get \u4E47\u4E42\u4E0B\u5C3A\u5342 \u4E0B\u5344\u5DE5\u531A\u531A.", value: "$$" }, + { type: "text", id: "firstcapsWrapper", name: "First Caps", note: "The wrapper to capitalize the first letter.", value: "--" }, + { type: "text", id: "uppercaseWrapper", name: "Uppercase", note: "The wrapper to convert to uppercase.", value: ">>" }, + { type: "text", id: "lowercaseWrapper", name: "Lowercase", note: "The wrapper to convert to lowercase.", value: "<<" } + ] + }, + { + type: "category", + id: "formatting", + name: "Formatting Options", + collapsible: true, + shown: false, + settings: [ + { + type: "dropdown", + id: "fullWidthMap", + name: "Fullwidth Style", + note: "Which style of fullwidth formatting should be used.", + value: true, + options: [ + { label: "T H I S", value: false }, + { label: "\uFF54\uFF48\uFF49\uFF53", value: true } + ] + }, + { type: "switch", id: "reorderUpsidedown", name: "Reorder Upsidedown Text", note: "Having this enabled reorders the upside down text to make it in-order.", value: true }, + { type: "switch", id: "startCaps", name: "Start VaRiEd Caps With Capital", note: "Enabling this starts a varied text string with a capital.", value: true } + ] + }, + { + type: "category", + id: "plugin", + name: "Functional Options", + collapsible: true, + shown: false, + settings: [ + { + type: "dropdown", + id: "hoverOpen", + name: "Opening Toolbar", + note: "Determines when to show the toolbar.", + value: true, + options: [ + { label: "Click", value: false }, + { label: "Hover", value: true } + ] + }, + { + type: "dropdown", + id: "chainFormats", + name: "Format Chaining", + note: "Swaps priority of wrappers between inner first and outer first. Check the GitHub for more info.", + value: true, + options: [ + { label: "Inner", value: false }, + { label: "Outer", value: true } + ] + }, + { type: "switch", id: "closeOnSend", name: "Close On Send", note: "This option will close the toolbar when a message is sent.", value: true } + ] + }, + { + type: "category", + id: "style", + name: "Style Options", + collapsible: true, + shown: false, + settings: [ + { + type: "dropdown", + id: "useIcons", + name: "Toolbar Style", + note: "Switches between icons and text as the toolbar buttons.", + value: true, + options: [ + { label: "Text", value: false }, + { label: "Icons", value: true } + ] + }, + { + type: "dropdown", + id: "rightSide", + name: "Toolbar Location", + note: "This option enables swapping toolbar location.", + value: true, + options: [ + { label: "Left", value: false }, + { label: "Right", value: true } + ] + }, + { + type: "slider", + id: "toolbarOpacity", + name: "Opacity", + note: "This allows the toolbar to be partially seethrough.", + value: 1, + min: 0, + max: 1 + }, + { + type: "slider", + id: "fontSize", + name: "Font Size", + note: "Adjusts the font size between 0 and 100%.", + value: 85, + min: 0, + max: 100 + } + ] + } + ] +}; +var config_default = manifest; + +// src/plugins/BetterFormattingRedux/toolbar.ts +var toolbar_default = { + bold: { + type: "native-format", + name: "Bold", + displayName: "Bold", + icon: `` + }, + italic: { + type: "native-format", + name: "Italic", + displayName: "Italic", + icon: `` + }, + underline: { + type: "native-format", + name: "Underline", + displayName: "Underline", + icon: `` + }, + strikethrough: { + type: "native-format", + name: "Strikethrough", + displayName: "Strikethrough", + icon: `` + }, + spoiler: { + type: "native-format", + name: "Spoiler", + displayName: "Spoiler", + icon: `` + }, + code: { + type: "native-format", + name: "Code", + displayName: "Code", + icon: `` + }, + codeblock: { + type: "native-format", + name: "Codeblock", + displayName: "|Codeblock|", + icon: `` + }, + superscript: { + type: "bfr-format", + name: "Superscript", + displayName: "\u02E2\u1D58\u1D56\u1D49\u02B3\u02E2\u1D9C\u02B3\u1DA6\u1D56\u1D57", + icon: "" + }, + smallcaps: { + type: "bfr-format", + name: "Smallcaps", + displayName: "S\u1D0D\u1D00\u029F\u029FC\u1D00\u1D18s", + icon: `` + }, + fullwidth: { + type: "bfr-format", + name: "Fullwidth", + displayName: "\uFF26\uFF55\uFF4C\uFF4C\uFF57\uFF49\uFF44\uFF54\uFF48", + icon: "" + }, + upsidedown: { + type: "bfr-format", + name: "Upsidedown", + displayName: "u\u028Dop\u01DDp\u1D09sd\u2229", + icon: `` + }, + varied: { + type: "bfr-format", + name: "Varied", + displayName: "VaRiEd CaPs", + icon: `` + }, + leet: { + type: "bfr-format", + name: "Leet", + displayName: "1337", + icon: "" + }, + thicc: { + type: "bfr-format", + name: "Extra Thicc", + displayName: "\u4E47\u4E42\u4E0B\u5C3A\u5342 \u4E0B\u5344\u5DE5\u531A\u531A", + icon: `` + }, + firstcaps: { + type: "bfr-format", + name: "First Caps", + displayName: "First Caps", + icon: `` + }, + uppercase: { + type: "bfr-format", + name: "Uppercase", + displayName: "UPPERCASE", + icon: `` + }, + lowercase: { + type: "bfr-format", + name: "Lowercase", + displayName: "lowercase", + icon: `` + } +}; + +// src/plugins/BetterFormattingRedux/languages.ts +var languages_default = { + A: { ada: "Ada", awk: "Awk" }, + B: { bash: "Bash" }, + C: { c: "c", clj: "Clojure", coffeescript: "CoffeeScript", cpp: "C++", crystal: "Crystal", csharp: "C#", css: "CSS" }, + D: { d: "D", dart: "Dart", delphi: "Delphi", dockerfile: "Dockerfile" }, + E: { elixir: "Elixir", elm: "Elm", erl: "Erlang" }, + F: { fs: "F#" }, + G: { go: "Go", graphql: "GraphQL", groovy: "Groovy" }, + H: { hs: "Haskell", html: "HTML/XML" }, + J: { java: "Java", js: "JavaScript", json: "JSON", julia: "Julia" }, + K: { kt: "Kotlin" }, + L: { latex: "LaTeX", less: "Less", lisp: "Lisp", lua: "Lua" }, + O: { ml: "OCaml" }, + M: { markdown: "Markdown", matlab: "Matlab", mk: "Makefile" }, + N: { nginx: "Nginx", nim: "Nim" }, + P: { perl: "Perl", php: "PHP", powershell: "Powershell", prolog: "Prolog", py: "Python" }, + R: { pl: "Raku", r: "R", rs: "Rust", ruby: "Ruby" }, + S: { sas: "SAS", scala: "Scala", scheme: "Scheme", scss: "SCSS", sql: "SQL", switf: "Swift" }, + T: { tcl: "Tcl", ts: "TypeScript" }, + V: { vbnet: "VB.NET", vhdl: "VHDL" }, + Z: { zsh: "ZSH" } +}; + +// src/plugins/BetterFormattingRedux/styles.css +var styles_default = `.bf-toolbar { + user-select: none; + white-space: nowrap; + font-size:85%; + display:block; + position: absolute; + color: rgba(255, 255, 255, .5); + width:auto!important; + right:0; + bottom:auto; + border-radius:3px; + height:27px!important; + top:0px; + transform:translate(0,-100%); + opacity:1; + overflow: hidden!important; + pointer-events: none; + padding:10px 30px 15px 5px; + margin: 0 5px 0 0; +} + +.bf-toolbar.bf-visible, +.bf-toolbar.bf-hover:hover{ + pointer-events: initial; +} + +.bf-toolbar::before { + content:""; + display: block; + width:100%; + height:calc(100% - 15px); + position: absolute; + z-index: -1; + background:#424549; + pointer-events: initial; + left:0px; + top:5px; + border-radius:3px; + transform:translate(0,55px); + transition:all 200ms ease; +} + +.theme-light .bf-toolbar::before { + background: #97A0AA; +} + +.bf-toolbar.bf-visible:before, +.bf-toolbar.bf-hover:hover:before { + transform:translate(0,0px); + transition:all 200ms cubic-bezier(0,0,0,1); +} + +.bf-toolbar .format { + display: inline; + padding: 7px 5px; + cursor: pointer; + display : inline-flex; + align-items : center; + transform:translate(0,55px); + transition:all 50ms,transform 200ms ease; + position:relative; + pointer-events: initial; + border-radius:2px; + max-height: 27px; + box-sizing: border-box; + vertical-align: middle; +} + +.bf-toolbar .format > img, +.bf-toolbar .format > svg { + opacity: 0.6; + vertical-align: middle; + max-height: inherit; +} + +.bf-toolbar .format .format-border { + border: 1px solid rgba(255, 255, 255, .5); + border-radius: inherit; +} + +.bf-toolbar .format:hover{ + background:rgba(255,255,255,.1); + color:rgba(255,255,255,.9); +} + +.bf-toolbar .format:active{ + background:rgba(0,0,0,.1)!important; + transition:all 0ms,transform 200ms ease; +} + +.bf-toolbar.bf-visible .format, +.bf-toolbar.bf-hover:hover .format{ + transform:translate(0,0); + transition:all 50ms,transform 200ms cubic-bezier(0,0,0,1); +} + +.bf-toolbar .format.disabled { + display: none; +} + +.bf-toolbar .format.ghost { + color: transparent; + background: rgba(0,0,0,.1); +} + +.bf-toolbar .format.ghost > img, +.bf-toolbar .format.ghost > svg { + opacity: 0; +} + +.theme-light .bf-toolbar:hover .bf-arrow, +.bf-toolbar .bf-arrow { + content:""; + display:block; + background: url('data:image/svg+xml;utf8,'); + height:30px; + width:30px; + right:5px; + position: absolute; + pointer-events: initial; + bottom:0; + background-repeat: no-repeat; + background-position: 50%; + transition:all 200ms ease; + opacity: .3; + cursor:pointer; +} +.theme-light .bf-toolbar .bf-arrow { + background: url('data:image/svg+xml;utf8,'); +} +.bf-toolbar.bf-visible .bf-arrow, +.bf-toolbar.bf-hover:hover .bf-arrow { + transform:translate(0,-14px)rotate(-90deg); + transition:all 200ms cubic-bezier(0,0,0,1); + opacity: .9; +} + +.message-group .bf-toolbar{ + padding: 10px 5px 15px 5px; + animation:slide-up 300ms cubic-bezier(0,0,0,1), opacity 300ms ease +} +.upload-modal .bf-toolbar { + position: relative; + transform: none; + padding: 5px 0; + margin-right: 0; + border-radius: 2px; + text-align: center; + background: #424549; +} +.upload-modal .bf-toolbar::before { + display: none; +} +.upload-modal .bf-toolbar .format:hover{ + background:rgba(255,255,255,.1); +} +.upload-modal .bf-toolbar .format:active{ + background:rgba(0,0,0,.1); +} +.upload-modal .bf-toolbar .format, +.upload-modal .bf-toolbar:before, +.message-group .bf-toolbar .format, +.message-group .bf-toolbar:before{ + transform:translate(0,0); +} +.upload-modal .bf-toolbar .bf-arrow, +.message-group .bf-toolbar .bf-arrow{ + display: none; +} + +.bf-toolbar.bf-left { + left: 0!important; + right: auto!important; + margin-right: 0!important; + margin-left: 5px!important; + padding: 10px 10px 15px 30px!important; +} + +.bf-toolbar.bf-left .bf-arrow { + left: 5px!important; + right: auto!important; +} + +.bf-toolbar.bf-left.bf-hover:hover .bf-arrow,.bf-toolbar.bf-left.bf-visible .bf-arrow { + -webkit-transform: translate(0,-14px) rotate(90deg)!important; + -ms-transform: translate(0,-14px) rotate(90deg)!important; + transform: translate(0,-14px) rotate(90deg)!important; +} +.bf-languages { + display: block; + position: fixed !important; + transform: scale(1,0); + transform-origin: 100% 100%!important; + background: #424549; + border-radius: 3px; + color: rgba(255,255,255,.5); + padding: 3px; +} +.bf-languages.bf-visible { + height: auto; + transition: 200ms cubic-bezier(.2,0,0,1); + transform: scale(1,1); + transform-origin: 100% 100%!important; +} + +.bf-languages div { + display: block; + cursor: pointer; + padding: 5px 7px; + border-radius: 2px; +} + +.bf-languages div:hover { + background: rgba(255,255,255,.1); + color: rgba(255,255,255,.9); +}`; + +// src/plugins/BetterFormattingRedux/toolbar.html +var toolbar_default2 = '
'; + +// src/plugins/BetterFormattingRedux/index.ts +var { ContextMenu, DOM, Patcher, UI, ReactUtils, Webpack, Logger } = BdApi; +var MessageActions = Webpack.getByKeys("jumpToMessage", "_sendMessage"); +var TextareaClasses = Webpack.getByKeys("channelTextArea", "textArea") ?? { textArea: "textArea_bdf0de" }; +var BetterFormattingRedux = class extends Plugin { + customWrappers; + buttonOrder; + discordWrappers = { bold: "**", italic: "*", underline: "__", strikethrough: "~~", code: "`", codeblock: "```", spoiler: "||" }; + isOpen = false; + replaceList = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}"; + smallCapsList = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`\u1D00\u0299\u1D04\u1D05\u1D07\uA730\u0262\u029C\u026A\u1D0A\u1D0B\u029F\u1D0D\u0274\u1D0F\u1D18\u01EB\u0280\uA731\u1D1B\u1D1C\u1D20\u1D21x\u028F\u1D22{|}"; + superscriptList = " !\"#$%&'\u207D\u207E*\u207A,\u207B./\u2070\xB9\xB2\xB3\u2074\u2075\u2076\u2077\u2078\u2079:;<\u207C>?@\u1D2C\u1D2E\u1D9C\u1D30\u1D31\u1DA0\u1D33\u1D34\u1D35\u1D36\u1D37\u1D38\u1D39\u1D3A\u1D3C\u1D3EQ\u1D3F\u02E2\u1D40\u1D41\u03BD\u1D42\u02E3\u02B8\u1DBB[\\]^_`\u1D43\u1D47\u1D9C\u1D48\u1D49\u1DA0\u1D4D\u02B0\u1DA6\u02B2\u1D4F\u02E1\u1D50\u207F\u1D52\u1D56\u146B\u02B3\u02E2\u1D57\u1D58\u1D5B\u02B7\u02E3\u02B8\u1DBB{|}"; + upsideDownList = ` \xA1"#$%\u2118,)(*+'-\u02D9/0\u0196\u218A\u0190\u07C8\u03DB9\u312586:;>=<\xBF@\u2200\u15FA\u0186\u15E1\u018E\u2132\uA4E8HI\u0550\uA4D8\uA4F6WNO\u0500\uA779\uA4E4S\uA4D5\uA4F5\u039BMX\u2144Z]\\[^\u203E,\u0250q\u0254p\u01DD\u025F\u1D77\u0265\u1D09\u027E\u029E\uA781\u026Fuodb\u0279s\u0287n\u028C\u028Dx\u028Ez}|{`; + fullwidthList = "\u3000\uFF01\uFF02\uFF03\uFF04\uFF05\uFF06\uFF07\uFF08\uFF09\uFF0A\uFF0B\uFF0C\uFF0D\uFF0E\uFF0F\uFF10\uFF11\uFF12\uFF13\uFF14\uFF15\uFF16\uFF17\uFF18\uFF19\uFF1A\uFF1B\uFF1C\uFF1D\uFF1E\uFF1F\uFF20\uFF21\uFF22\uFF23\uFF24\uFF25\uFF26\uFF27\uFF28\uFF29\uFF2A\uFF2B\uFF2C\uFF2D\uFF2E\uFF2F\uFF30\uFF31\uFF32\uFF33\uFF34\uFF35\uFF36\uFF37\uFF38\uFF39\uFF3A\uFF3B\uFF3C\uFF3D\uFF3E\uFF3F\uFF40\uFF41\uFF42\uFF43\uFF44\uFF45\uFF46\uFF47\uFF48\uFF49\uFF4A\uFF4B\uFF4C\uFF4D\uFF4E\uFF4F\uFF50\uFF51\uFF52\uFF53\uFF54\uFF55\uFF56\uFF57\uFF58\uFF59\uFF5A\uFF5B\uFF5C\uFF5D"; + leetList = " !\"#$%&'()*+,-./0123456789:;<=>?@48CD3FG#IJK1MN0PQ\u042F57UVWXY2[\\]^_`48cd3fg#ijk1mn0pq\u042F57uvwxy2{|}"; + thiccList = "\u3000!\"#$%&'()*+,-./0123456789:;<=>?@\u5342\u4E43\u531A\u5200\u4E47\u4E0B\u53B6\u5344\u5DE5\u4E01\u957F\u4E5A\u4ECE\u3093\u53E3\u5C38\u353F\u5C3A\u4E02\u4E05\u51F5\u30EA\u5C71\u4E42\u4E2B\u4E59[\\]^_`\u5342\u4E43\u531A\u5200\u4E47\u4E0B\u53B6\u5344\u5DE5\u4E01\u957F\u4E5A\u4ECE\u3093\u53E3\u5C38\u353F\u5C3A\u4E02\u4E05\u51F5\u30EA\u5C71\u4E42\u4E2B\u4E59{|}"; + constructor(meta) { + super(meta, config_default); + this.customWrappers = (this.manifest.config?.find((g) => g.id === "wrappers")).settings.map((s) => s.id); + this.buttonOrder = (this.manifest.config?.find((g) => g.id === "toolbar")).settings.map((s) => s.id); + } + async onStart() { + DOM.addStyle(this.meta.name + "-style", styles_default); + this.setupToolbar(); + if (!MessageActions) return Logger.error(this.meta.name, "Could not find MessageActions module!"); + Patcher.before(this.meta.name, MessageActions, "sendMessage", (_, [, msg]) => { + msg.content = this.format(msg.content); + }); + } + onStop() { + Patcher.unpatchAll(this.meta.name); + document.querySelector(".bf-toolbar")?.remove(); + DOM.removeStyle(this.meta.name + "-style"); + } + observer(e) { + if (!e.addedNodes.length || !(e.addedNodes[0] instanceof Element)) return; + const elem = e.addedNodes[0]; + const textarea = elem.matches(`.${TextareaClasses.textArea}`) ? elem : elem.querySelector(`.${TextareaClasses.textArea}`); + if (textarea) this.addToolbar(textarea); + } + updateStyle() { + this.updateSide(); + this.updateOpacity(); + this.updateFontSize(); + } + updateSide() { + const toolbar = document.querySelector(".bf-toolbar"); + if (!toolbar) return; + if (this.settings.rightSide) toolbar.classList.remove("bf-left"); + else toolbar.classList.add("bf-left"); + } + updateOpacity() { + const toolbar = document.querySelector(".bf-toolbar"); + if (!toolbar) return; + toolbar.style.opacity = this.settings.toolbarOpacity; + } + updateFontSize() { + const toolbar = document.querySelector(".bf-toolbar"); + if (!toolbar) return; + toolbar.style.fontSize = this.settings.fontSize + "%"; + } + openClose() { + this.isOpen = !this.isOpen; + const toolbar = document.querySelector(".bf-toolbar"); + if (!toolbar) return; + toolbar.classList.toggle("bf-visible"); + } + escape(s) { + return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); + } + doFormat(text, wrapper, offset) { + if (text.substring(offset, offset + wrapper.length) != wrapper) return text; + let returnText = text; + const len = text.length; + const begin = text.indexOf(wrapper, offset); + if (text[begin - 1] == "\\") return text; + let end = text.indexOf(wrapper, begin + wrapper.length); + if (end != -1) end += wrapper.length - 1; + if (this.settings.chainFormats) { + for (let w = 0; w < this.customWrappers.length; w++) { + const newText = this.doFormat(returnText, this.settings[this.customWrappers[w]], begin + wrapper.length); + if (returnText != newText) { + returnText = newText; + end = end - this.settings[this.customWrappers[w]].length * 2; + } + } + } + returnText = returnText.replace(new RegExp(`([^]{${begin}})${this.escape(wrapper)}([^]*)${this.escape(wrapper)}([^]{${len - end - 1}})`), (match, before, middle, after) => { + let letterNum = 0; + middle = middle.replace(/./g, (letter) => { + const index = this.replaceList.indexOf(letter); + letterNum += 1; + if (wrapper == this.settings.fullwidthWrapper) { + if (this.settings.fullWidthMap) return index != -1 ? this.fullwidthList[index] : letter; + return index != -1 ? letterNum == middle.length ? letter.toUpperCase() : letter.toUpperCase() + " " : letter; + } else if (wrapper == this.settings.superscriptWrapper) { + return index != -1 ? this.superscriptList[index] : letter; + } else if (wrapper == this.settings.smallcapsWrapper) { + return index != -1 ? this.smallCapsList[index] : letter; + } else if (wrapper == this.settings.upsidedownWrapper) { + return index != -1 ? this.upsideDownList[index] : letter; + } else if (wrapper == this.settings.leetWrapper) { + return index != -1 ? this.leetList[index] : letter; + } else if (wrapper == this.settings.thiccWrapper) { + return index != -1 ? this.thiccList[index] : letter; + } else if (wrapper == this.settings.variedWrapper) { + const compare = this.settings.startCaps ? 1 : 0; + if (letter.toLowerCase() == letter.toUpperCase()) letterNum = letterNum - 1; + return index != -1 ? letterNum % 2 == compare ? letter.toUpperCase() : letter.toLowerCase() : letter; + } else if (wrapper == this.settings.firstcapsWrapper) { + if (letterNum == 1 || middle[letterNum - 2] === " ") return letter.toUpperCase(); + } else if (wrapper == this.settings.uppercaseWrapper) { + return letter.toUpperCase(); + } else if (wrapper == this.settings.lowercaseWrapper) { + return letter.toLowerCase(); + } + return letter; + }); + if (wrapper == this.settings.upsidedownWrapper && this.settings.reorderUpsidedown) return before + middle.split("").reverse().join("") + after; + return before + middle + after; + }); + return returnText; + } + format(string) { + let text = string; + for (let i = 0; i < text.length; i++) { + if (text[i] == "`") { + const next = text.indexOf("`", i + 1); + if (next != -1) i = next; + } else if (text[i] == "@") { + const match = /@.*#[0-9]*/.exec(text.substring(i)); + if (match && match.index == 0) i += match[0].length - 1; + } else { + for (let w = 0; w < this.customWrappers.length; w++) { + if (!this.settings[this.customWrappers[w].replace("Wrapper", "Format")]) continue; + const newText = this.doFormat(text, this.settings[this.customWrappers[w]], i); + if (text != newText) { + text = newText; + i = i - this.settings[this.customWrappers[w]].length * 2; + } + } + } + } + if (this.settings.closeOnSend) document.querySelector(".bf-toolbar")?.classList.remove("bf-visible"); + return text; + } + async wrapSelection(leftWrapper, rightWrapper) { + if (!rightWrapper) rightWrapper = leftWrapper; + if (leftWrapper.startsWith("```")) leftWrapper = leftWrapper + "\n"; + if (rightWrapper.startsWith("```")) rightWrapper = "\n" + rightWrapper; + const textarea = document.querySelector(`.${TextareaClasses.textArea}`); + if (!textarea) return; + if (textarea.tagName === "TEXTAREA") return this.oldWrapSelection(textarea, leftWrapper, rightWrapper); + const slateNode = ReactUtils.getOwnerInstance(textarea); + const slate = slateNode?.ref?.current?.getSlateEditor(); + if (!slate) return; + let offset; + if (slate.selection.anchor.offset <= slate.selection.focus.offset) { + offset = slate.selection.focus.offset + leftWrapper.length; + slate.apply({ type: "insert_text", text: leftWrapper, path: slate.selection.anchor.path, offset: slate.selection.anchor.offset }); + slate.apply({ type: "insert_text", text: rightWrapper, path: slate.selection.focus.path, offset: slate.selection.focus.offset }); + } else { + offset = slate.selection.anchor.offset + leftWrapper.length; + slate.apply({ type: "insert_text", text: rightWrapper, path: slate.selection.anchor.path, offset: slate.selection.anchor.offset }); + slate.apply({ type: "insert_text", text: leftWrapper, path: slate.selection.focus.path, offset: slate.selection.focus.offset }); + } + const newSelection = { + anchor: { path: slate.selection.anchor.path, offset }, + focus: { path: slate.selection.focus.path, offset } + }; + slate.selection = newSelection; + slate.apply({ type: "insert_text", text: "", path: slate.selection.anchor.path, offset }); + slateNode.focus(); + } + oldWrapSelection(textarea, leftWrapper, rightWrapper) { + let text = textarea.value; + const start = textarea.selectionStart; + const len = text.substring(textarea.selectionStart, textarea.selectionEnd).length; + text = leftWrapper + text.substring(textarea.selectionStart, textarea.selectionEnd) + rightWrapper; + textarea.focus(); + document.execCommand("insertText", false, text); + textarea.selectionStart = start + leftWrapper.length; + textarea.selectionEnd = textarea.selectionStart + len; + } + getContextMenu() { + return ContextMenu.buildMenu( + Object.keys(languages_default).map((letter) => { + return { + type: "submenu", + label: letter, + items: Object.keys(languages_default[letter]).map((language) => { + return { + label: languages_default[letter][language], + action: () => { + this.wrapSelection("```" + language, "```"); + } + }; + }) + }; + }) + ); + } + buildToolbar() { + const toolbar = DOM.parseHTML(toolbar_default2); + const sorted = this.buttonOrder; + for (let i = 0; i < sorted.length; i++) { + const key = sorted[i].replace("Button", ""); + const button = DOM.parseHTML("
"); + if (!toolbar_default[key]) continue; + button.classList.add(toolbar_default[key].type); + UI.createTooltip(button, toolbar_default[key].name); + if (!this.settings[key + "Button"]) button.classList.add("disabled"); + if (key === "codeblock") { + const contextMenu = this.getContextMenu(); + button.addEventListener("contextmenu", (e) => { + ContextMenu.open(e, contextMenu, { align: "bottom" }); + }); + } + button.dataset.name = sorted[i].replace("Button", ""); + if (this.settings.useIcons) button.innerHTML = toolbar_default[key].icon; + else button.innerHTML = toolbar_default[key].displayName; + toolbar.append(button); + } + if (!this.settings.useIcons) { + toolbar.addEventListener("mousemove", (e) => { + const target = e.currentTarget; + const pos = e.pageX - (target.parentElement?.getBoundingClientRect()?.left ?? 0); + const width = parseInt(getComputedStyle(target).width); + let diff = -1 * width; + Array.from(target.children).forEach((elem) => { + diff += elem.offsetWidth; + }); + target.scrollLeft = pos / width * diff; + }); + } + return toolbar; + } + setupToolbar() { + document.querySelector(".bf-toolbar")?.remove(); + document.querySelectorAll(`.${TextareaClasses.textArea}`).forEach((elem) => { + this.addToolbar(elem.children[0]); + }); + } + addToolbar(textarea) { + const toolbarElement = this.buildToolbar(); + if (this.settings.hoverOpen == true) toolbarElement.classList.add("bf-hover"); + if (this.isOpen) toolbarElement.classList.add("bf-visible"); + const inner = textarea.parentElement?.parentElement; + if (!inner) return; + inner.parentElement?.insertBefore(toolbarElement, inner.nextSibling); + toolbarElement.addEventListener("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + const button = e.target.closest("div"); + if (!button) return; + if (button.classList.contains("bf-arrow")) { + if (!this.settings.hoverOpen) this.openClose(); + } else if (button.classList.contains("format")) { + if (!button.dataset.name) return; + let wrapper = ""; + if (button.classList.contains("native-format")) wrapper = this.discordWrappers[button.dataset.name]; + else wrapper = this.settings[button.dataset.name + "Wrapper"]; + this.wrapSelection(wrapper); + } + }); + this.updateStyle(); + } + getSettingsPanel() { + return this.buildSettingsPanel(this.updateSettings.bind(this)); + } + updateSettings(group, id, value) { + if (group == "toolbar") this.setupToolbar(); + if (group == "plugin" && id == "hoverOpen") { + const toolbar = document.querySelector(".bf-toolbar"); + if (value) { + toolbar?.classList.remove("bf-visible"); + toolbar?.classList.add("bf-hover"); + } else { + toolbar?.classList.remove("bf-hover"); + } + } + if (group == "style") { + if (id == "icons") this.setupToolbar(); + if (id == "rightSide") this.updateSide(); + if (id == "toolbarOpacity") this.updateOpacity(); + if (id == "fontSize") this.updateFontSize(); + } + } +}; + +/*@end@*/ \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/BetterGuildTooltip.config.json b/dotfiles/.config/BetterDiscord/plugins/BetterGuildTooltip.config.json new file mode 100644 index 0000000..bb23699 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/BetterGuildTooltip.config.json @@ -0,0 +1,6 @@ +{ + "currentVersionInfo": { + "version": "1.2.3", + "hasShownChangelog": true + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/BetterGuildTooltip.plugin.js b/dotfiles/.config/BetterDiscord/plugins/BetterGuildTooltip.plugin.js new file mode 100644 index 0000000..2c9a93d --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/BetterGuildTooltip.plugin.js @@ -0,0 +1,311 @@ +/** + * @name BetterGuildTooltip + * @author arg0NNY + * @authorId 633223783204782090 + * @invite M8DBtcZjXD + * @version 1.2.3 + * @description Displays an online and total member count in the guild tooltip. + * @website https://github.com/arg0NNY/DiscordPlugins/tree/master/BetterGuildTooltip + * @source https://raw.githubusercontent.com/arg0NNY/DiscordPlugins/master/BetterGuildTooltip/BetterGuildTooltip.plugin.js + * @updateUrl https://raw.githubusercontent.com/arg0NNY/DiscordPlugins/master/BetterGuildTooltip/BetterGuildTooltip.plugin.js + */ + +/* ### CONFIG START ### */ +const config = { + info: { + name: 'BetterGuildTooltip', + version: '1.2.3', + description: 'Displays an online and total member count in the guild tooltip.' + }, + changelog: [ + { + type: 'fixed', + title: 'Fixes', + items: [ + 'Updated to work in the latest release of Discord.' + ] + } + ] +} +/* ### CONFIG END ### */ + +const { + Webpack, + UI, + Patcher, + React, + Utils, + Data +} = new BdApi(config.info.name) +const { Filters } = Webpack + +const Dispatcher = Webpack.getModule(Filters.byKeys('dispatch', 'subscribe'), { searchExports: true }) +const GuildMemberCountStore = Webpack.getStore('GuildMemberCountStore') +const GuildChannelStore = Webpack.getStore('GuildChannelStore') +const Flux = Webpack.getByKeys('Store', 'connectStores') + +const ActionTypes = { + CONNECTION_OPEN: 'CONNECTION_OPEN', + GUILD_CREATE: 'GUILD_CREATE', + GUILD_DELETE: 'GUILD_DELETE', + GUILD_MEMBER_LIST_UPDATE: 'GUILD_MEMBER_LIST_UPDATE', + ONLINE_GUILD_MEMBER_COUNT_UPDATE: 'ONLINE_GUILD_MEMBER_COUNT_UPDATE' +} + +const useStateFromStores = Webpack.getModule(Filters.byStrings('useStateFromStores'), { searchExports: true }) + +const Selectors = { + Guild: Webpack.getByKeys('statusOffline', 'guildDetail') +} + +const GuildStore = Webpack.getStore('GuildStore') +const GuildActions = Webpack.getByKeys('preload', 'closePrivateChannel') +const GuildTooltip = [...Webpack.getWithKey(Filters.byStrings('position'), { target: Webpack.getBySource('GuildTooltip') })] + +const memberCounts = new Map() +const onlineMemberCounts = new Map() + +function handleConnectionOpen ({ guilds }) { + for (const guild of guilds) { + memberCounts.set(guild.id, guild.member_count) + } +} + +function handleGuildCreate ({ guild }) { + memberCounts.set(guild.id, guild.member_count) +} + +function handleGuildDelete ({ guild }) { + memberCounts.delete(guild.id) + onlineMemberCounts.delete(guild.id) +} + +function handleGuildMemberListUpdate ({ guildId, memberCount, groups }) { + if (memberCount !== 0) { + memberCounts.set(guildId, memberCount) + } + + onlineMemberCounts.set( + guildId, + groups.reduce((total, group) => { + return group.id !== 'offline' ? total + group.count : total + }, 0) + ) +} + +function handleOnlineGuildMemberCountUpdate ({ guildId, count }) { + onlineMemberCounts.set(guildId, count) +} + +const MemberCountsStore = new class extends Flux.Store { + initialize () { + const nativeMemberCounts = GuildMemberCountStore.getMemberCounts() + for (const guildId in nativeMemberCounts) { + memberCounts.set(guildId, nativeMemberCounts[guildId]) + } + }; + + getMemberCounts (guildId) { + return { + members: memberCounts.get(guildId), + membersOnline: onlineMemberCounts.get(guildId) + } + }; +}(Dispatcher, { + [ActionTypes.CONNECTION_OPEN]: handleConnectionOpen, + [ActionTypes.GUILD_CREATE]: handleGuildCreate, + [ActionTypes.GUILD_DELETE]: handleGuildDelete, + [ActionTypes.GUILD_MEMBER_LIST_UPDATE]: handleGuildMemberListUpdate, + [ActionTypes.ONLINE_GUILD_MEMBER_COUNT_UPDATE]: handleOnlineGuildMemberCountUpdate +}) + +function formatNumber (number) { + return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ') +} + +function GuildTooltipCounters (props) { + MemberCountsStore.initialize() + + const { presenceCount, memberCount } = useStateFromStores([GuildStore], () => + GuildStore.getGuild(props.guild.id) ?? {} + ) + const { members, membersOnline } = useStateFromStores([MemberCountsStore], () => + MemberCountsStore.getMemberCounts(props.guild.id) + ) + + const onlineDisplayed = props.settings.displayOnline && (membersOnline || presenceCount) + const totalDisplayed = props.settings.displayTotal && (memberCount || members) + + return onlineDisplayed || totalDisplayed ? React.createElement( + 'div', + { + className: Selectors.Guild.guildDetail, + style: props.isPopout ? { + fontWeight: '600' + } : { + marginTop: '5px', + marginBottom: '5px' + } + }, + React.createElement( + 'div', + { + className: Selectors.Guild.statusCounts, + style: { + columnGap: 0, + '-webkit-column-gap': 0 + } + }, + [ + ...(onlineDisplayed ? [React.createElement( + 'i', + { + className: Selectors.Guild.statusOnline + } + ), + React.createElement( + 'span', + { + className: Selectors.Guild.count + }, + formatNumber(membersOnline ?? presenceCount) + )] : []), + ...(totalDisplayed ? [React.createElement( + 'i', + { + className: Selectors.Guild.statusOffline + } + ), + React.createElement( + 'span', + { + className: Selectors.Guild.count + }, + formatNumber(memberCount ?? members) + )] : []) + ] + ) + ) : React.createElement('div') +} + +const PRELOAD_DELAY = 200 + +module.exports = class BetterGuildTooltip { + start () { + this.patchGuildTooltip() + + this.preloadInProccess = false + this.preloadNext = null + } + + preloadGuild (guild) { + if (!guild || this.preloadInProccess) return this.preloadNext = guild + + this._preloadGuild(guild) + this.preloadInProccess = true + setTimeout(() => { + this.preloadInProccess = false + this.preloadGuild(this.preloadNext) + this.preloadNext = null + }, PRELOAD_DELAY) + } + + _preloadGuild (guild) { + GuildActions.preload( + guild.id, + GuildChannelStore.getDefaultChannel(guild.id).id + ) + } + + patchGuildTooltip () { + const callback = (index, props = {}) => (self, [{ guild }], value) => { + if (!this.settings.displayOnline && !this.settings.displayTotal) return + if (this.settings.displayOnline && !onlineMemberCounts.has(guild.id)) this.preloadGuild(guild) + value.props.children.splice(index, 0, React.createElement(GuildTooltipCounters, { + guild, + settings: this.settings, + ...props + })) + } + + Patcher.after(...GuildTooltip, (self, _, value) => { + const nodeRef = React.useRef() + + if (!this.settings.displayOnline && !this.settings.displayTotal) return + + const node = Utils.findInTree( + value, + m => Filters.byStrings('isViewingRoles')(m?.type), + { walkable: ['props', 'children', '__unsupportedReactNodeAsText'] } + ) + if (!node || nodeRef.current === node) return + + Patcher.after(node, 'type', callback(1)) + nodeRef.current = node + }) + } + + stop () { + Patcher.unpatchAll() + } + + constructor () { + this.defaultSettings = { + displayOnline: true, + displayTotal: true + } + + this.settings = this.loadSettings(this.defaultSettings) + + this.showChangelogIfNeeded() + } + + loadSettings (defaults = {}) { + return Utils.extend({}, defaults, Data.load('settings')) + } + saveSettings (settings = this.settings) { + return Data.save('settings', settings) + } + + showChangelogIfNeeded () { + const currentVersionInfo = Utils.extend( + { version: config.info.version, hasShownChangelog: false }, + Data.load('currentVersionInfo') + ) + if (currentVersionInfo.version === config.info.version && currentVersionInfo.hasShownChangelog) return + + this.showChangelog() + Data.save('currentVersionInfo', { version: config.info.version, hasShownChangelog: true }) + } + showChangelog () { + return UI.showChangelogModal({ + title: config.info.name, + subtitle: 'Version ' + config.info.version, + changes: config.changelog + }) + } + + getSettingsPanel () { + return UI.buildSettingsPanel({ + onChange: () => this.saveSettings(), + settings: [ + { + type: 'switch', + id: 'displayOnline', + name: 'Display online count', + note: 'Displays an online member count in the guild tooltip.', + value: this.settings.displayOnline, + onChange: e => this.settings.displayOnline = e + }, + { + type: 'switch', + id: 'displayTotal', + name: 'Display total count', + note: 'Displays a total member count in the guild tooltip.', + value: this.settings.displayTotal, + onChange: e => this.settings.displayTotal = e + } + ] + }) + } +} diff --git a/dotfiles/.config/BetterDiscord/plugins/BetterRoleColors.config.json b/dotfiles/.config/BetterDiscord/plugins/BetterRoleColors.config.json new file mode 100755 index 0000000..56fd4bc --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/BetterRoleColors.config.json @@ -0,0 +1,37 @@ +{ + "currentVersionInfo": { + "version": "0.10.4", + "hasShownChangelog": true + }, + "settings": { + "global": { + "important": false + }, + "modules": { + "typing": true, + "voice": true, + "mentions": true, + "chat": false, + "botTags": true, + "memberList": true + }, + "popouts": { + "username": true, + "discriminator": true, + "nickname": true, + "fallback": true + }, + "modals": { + "username": true, + "discriminator": true + }, + "auditLog": { + "username": true, + "discriminator": false + }, + "account": { + "username": true, + "discriminator": false + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ChannelTabs.config.json b/dotfiles/.config/BetterDiscord/plugins/ChannelTabs.config.json new file mode 100644 index 0000000..334f16c --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ChannelTabs.config.json @@ -0,0 +1,6 @@ +{ + "currentVersionInfo": { + "version": "2.6.15", + "hasShownChangelog": true + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ChannelTabs_new_191969177538396161.config.json b/dotfiles/.config/BetterDiscord/plugins/ChannelTabs_new_191969177538396161.config.json new file mode 100644 index 0000000..2b6569f --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ChannelTabs_new_191969177538396161.config.json @@ -0,0 +1,58 @@ +{ + "settings": { + "tabs": [ + { + "name": "#🤡│facereveal", + "url": "/channels/821835978334535740/987679407114178620", + "selected": true, + "currentStatus": "none", + "iconUrl": "https://cdn.discordapp.com/icons/821835978334535740/f03c55415ef5c81b99585e10c1008c3b.webp?size=32", + "channelId": "987679407114178620" + } + ], + "favs": [ + { + "name": "#🧀│allgemein", + "iconUrl": "https://cdn.discordapp.com/icons/821835978334535740/f03c55415ef5c81b99585e10c1008c3b.webp?size=32", + "url": "/channels/821835978334535740/821835978767073292", + "channelId": "821835978767073292", + "groupId": -1 + }, + { + "name": "#schwitzies", + "iconUrl": "https://cdn.discordapp.com/icons/230708419995107330/aa16ef21ab85c9e89ff8ba36a7260e20.webp?size=32", + "url": "/channels/230708419995107330/775452873377972254", + "channelId": "775452873377972254", + "groupId": -1 + } + ], + "favGroups": [], + "showTabBar": true, + "showFavBar": true, + "reopenLastChannel": true, + "showFavUnreadBadges": true, + "showFavMentionBadges": true, + "showFavTypingBadge": true, + "showEmptyFavBadges": false, + "showTabUnreadBadges": true, + "showTabMentionBadges": true, + "showTabTypingBadge": true, + "showEmptyTabBadges": false, + "showActiveTabUnreadBadges": false, + "showActiveTabMentionBadges": false, + "showActiveTabTypingBadge": false, + "showEmptyActiveTabBadges": false, + "compactStyle": true, + "privacyMode": false, + "radialStatusMode": true, + "tabWidthMin": 100, + "showFavGroupUnreadBadges": true, + "showFavGroupMentionBadges": true, + "showFavGroupTypingBadge": true, + "showEmptyFavGroupBadges": false, + "showQuickSettings": true, + "showNavButtons": true, + "alwaysFocusNewTabs": false, + "useStandardNav": true + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ChannelsBadges.config.json b/dotfiles/.config/BetterDiscord/plugins/ChannelsBadges.config.json new file mode 100644 index 0000000..88c5417 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ChannelsBadges.config.json @@ -0,0 +1,35 @@ +{ + "CBsettings": { + "version": "1.1.7", + "voice": { + "voice": true, + "text": true, + "emoji": true, + "voice_color": "#1ABC9C" + }, + "forum": { + "forum": true, + "text": true, + "emoji": true, + "forum_color": "#206694" + }, + "nsfw": { + "nsfw": true, + "text": true, + "emoji": true, + "nsfw_color": "#F23F42" + }, + "rule": { + "rule": true, + "text": true, + "emoji": true, + "rule_color": "#FF9B2B" + }, + "ads": { + "ads": true, + "text": true, + "emoji": true, + "ads_color": "#FF2BC2" + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/DisplayUsernames.config.json b/dotfiles/.config/BetterDiscord/plugins/DisplayUsernames.config.json new file mode 100644 index 0000000..5dac326 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/DisplayUsernames.config.json @@ -0,0 +1,6 @@ +{ + "currentVersionInfo": { + "version": "1.4.2", + "hasShownChangelog": true + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/DisplayUsernames.plugin.js b/dotfiles/.config/BetterDiscord/plugins/DisplayUsernames.plugin.js new file mode 100644 index 0000000..a4f5c50 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/DisplayUsernames.plugin.js @@ -0,0 +1,288 @@ +/** + * @name DisplayUsernames + * @author HG + * @authorId 124667638298181632 + * @authorLink https://youtube.com/HudsonGTV + * @description Displays Discord handle next to display names in chat and adds '@' symbol in profile cards. + * @version 1.4.2 + * @website https://hudsongreen.com/ + * @invite H3bebA97tV + * @donate https://www.paypal.com/donate/?business=REFHYLZAZUWHJ + * @source https://github.com/HudsonGTV/BetterDiscordPlugins/blob/main/DisplayUsernames/DisplayUsernames.plugin.js + */ + +const request = require("request"); +const fs = require("fs"); +const path = require("path"); + +const config = { + info: { + name: "DisplayUsernames", + authors: [ + { + name: "HG", + discord_id: "124667638298181632", + github_username: "HudsonGTV", + twitter_username: "HudsonKazuto" + } + ], + version: "1.4.2", + description: "Displays Discord handle next to display names in chat and adds '`@`' symbol in profile cards.", + github: "https://github.com/HudsonGTV/BetterDiscordPlugins/blob/main/DisplayUsernames/DisplayUsernames.plugin.js", + github_raw: "https://raw.githubusercontent.com/HudsonGTV/BetterDiscordPlugins/main/DisplayUsernames/DisplayUsernames.plugin.js" + }, + changelog: [ + { + title: "Fixes", + type: "fixed", + items: [ + "`[1.4.2]` Fixed bug with usernames appearing in command replies", + "`[1.4.1]` Fixed bug with seperator symbol in replies", + "`[1.4.0]` Fixed username prefix not appearing due to recent Discord revamp.", + "`[1.4.0]` Fixed bug with username handle in chat being black with certain themes." + ] + }, + { + title: "Additions", + type: "added", + items: [ + "`[1.4.2]` Added ability to hide the username in replies.", + "`[1.4.1]` Added ability to change seperator color seperately.", + "`[1.4.0]` Added ability to change username handle color in chat." + ] + }, + /*{ + title: "Improvements", + type: "improved", + items: [ + "`[1.2.0]` Settings now apply without the need to restart Discord (Except handle symbol changes)." + ] + }*/ + ], + defaultConfig: [ + { + type: "textbox", + id: "handlesymbol", + name: "[Needs restart to fully apply] Username Handle Prefix Symbol", + note: "The symbol used as a prefix for usernames (the @ in @username).", + placeholder: "Blank for none; default: @", + value: "@" + }, + { + type: "color", + id: "usernamecolor", + name: "Username Color", + note: "The color used when displaying the username in the chat", + value: "#949BA4" + }, + { + type: "color", + id: "seperatorcolor", + name: "Seperator Color", + note: "The color used for the symbol that either seperates the username from the message timestamp (@username • timestamp) or the suffix in replies (@username: message)", + value: "#949BA4" + }, + { + type: "switch", + id: "usernamechat", + name: "Show Username In Chat", + note: "Display the message author's username next to the message timestamp.", + value: true + }, + { + type: "switch", + id: "showinreplies", + name: "Show Username in Chat Replies", + note: "Shows the username in the replied message text.", + value: true + }, + { + type: "switch", + id: "profilecard", + name: "Show Handle Prefix In Profile Card & Friends List", + note: "Display the username handle prefix in profile cards/popups as well as the friends list.", + value: true + }, + { + type: "switch", + id: "friendslist", + name: "Always Show Friends List Username", + note: "Force Discord to always display usernames next to display names in friends list. Turn off for default Discord behavior (only show on hover).", + value: true + } + ] +}; + +module.exports = !global.ZeresPluginLibrary ? class { + + constructor() { + this._config = config; + } + + load() { + BdApi.showConfirmationModal("Library plugin is needed", + `The library plugin needed for ZeresPluginLibrary is missing. Please click Download Now to install it.`, { + confirmText: "Download", + cancelText: "Cancel", + onConfirm: () => { + request.get("https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", (error, response, body) => { + if (error) + return electron.shell.openExternal("https://betterdiscord.net/ghdl?url=https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js"); + + fs.writeFileSync(path.join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body); + }); + } + }); + } + + start() { } + stop() { } + +} : (([Plugin, Library]) => { + + const { DiscordModules, WebpackModules, Patcher, PluginUtilities } = Library; + const { React } = DiscordModules; + + class plugin extends Plugin { + + constructor() { + super(); + } + + + onStart() { + + // Apply CSS Styles + this.applyStyles(); + + // Bind usernames if enabled + if(this.settings.usernamechat) this.applyUsername(); + + } + + onStop() { + Patcher.unpatchAll(); + this.removeStyles(); + } + + // Manage settings panel + getSettingsPanel() { + + const panel = this.buildSettingsPanel(); + + // Listen for changes in settings + panel.addListener((id, val) => { + switch(id) { + case "usernamechat": + // Check bool val + if(val) + this.applyUsername(); + else + Patcher.unpatchAll(); // Change this if I add more patches to plugin + break; + case "showinreplies": + case "usernamecolor": + case "seperatorcolor": + case "profilecard": + case "friendslist": + // Reload CSS + this.removeStyles(); + this.applyStyles(); + break; + default: + break; + } + }); + + // Display settings panel + return panel.getElement(); + } + + applyStyles() { + // Chat message username styles (required - configured via applyUsername()) + PluginUtilities.addStyle( + "DisplayUsernames-ChatMessage", + ` + /* style username in messages */ + .hg-username-handle { + margin-left: 0.25rem; + font-size: 0.75rem; + color: ${this.settings.usernamecolor}; + } + /* seperator dot */ + .hg-username-handle::after { + margin-left: 0.5rem; + content: "•"; + color: ${this.settings.seperatorcolor}; + } + /* fix timestamp margin (discord likes to change it randomly) */ + .roleDot_c01716, .cozy_f5c119 .headerText_f47574, .compact__54ecc .headerText_f47574 { + margin-right: 0 !important; + } + /* hide username in command replies */ + .repliedMessage_f9f2ca > .hg-username-handle { + display: none; + } + /* but dont hide handle in normal replies if user wants them visible */ + [id*="message-reply-context-"] > .hg-username-handle { + margin-left: 0; + display: ${this.settings.showinreplies ? "inline" : "none"}; + } + /* change seperator in replies */ + [id*="message-reply-context-"] > .hg-username-handle::after { + margin-left: 0; + content: ": "; + } + ` + ); + // Display handle symbol infront of username in profile card/friends list + if(this.settings.profilecard) PluginUtilities.addStyle( + "DisplayUsernames-ProfileCard", + ` + /* display handle symbol infront of username */ + span.userTagUsername_c32acf::before, /* profile cards */ + span.discriminator_f3939d::before /* friends list */ { + color: #777; + content: "${this.settings.handlesymbol}"; + } + ` + ); + // Always display usernames in friends list + if(this.settings.friendslist) PluginUtilities.addStyle( + "DisplayUsernames-FriendsList", + ` + /* always show username in friends list */ + .discriminator_f3939d { + visibility: visible !important; + } + ` + ); + } + + removeStyles() { + PluginUtilities.removeStyle("DisplayUsernames-ChatMessage"); + PluginUtilities.removeStyle("DisplayUsernames-ProfileCard"); + PluginUtilities.removeStyle("DisplayUsernames-FriendsList"); + } + + applyUsername() { + + const [ module, key ] = BdApi.Webpack.getWithKey(BdApi.Webpack.Filters.byStrings("userOverride", "withMentionPrefix"), { searchExports: false }); + + Patcher.after(module, key, (_, args, ret) => { + let author = args[0].message.author; + let discrim = author.discriminator; + // Ensure this is not a webhook or system message + if(discrim != "0000") + ret.props.children.push( + React.createElement("span", { class: "hg-username-handle" }, this.settings.handlesymbol + author.username + (discrim != "0" ? "#" + discrim : "")) + ); + }); + + } + + } + + return plugin; + +})(global.ZeresPluginLibrary.buildPlugin(config)); diff --git a/dotfiles/.config/BetterDiscord/plugins/DoNotTrack.config.json b/dotfiles/.config/BetterDiscord/plugins/DoNotTrack.config.json new file mode 100644 index 0000000..dfba51c --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/DoNotTrack.config.json @@ -0,0 +1,3 @@ +{ + "version": "0.1.0" +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/DoNotTrack.plugin.js b/dotfiles/.config/BetterDiscord/plugins/DoNotTrack.plugin.js new file mode 100644 index 0000000..db6bdd2 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/DoNotTrack.plugin.js @@ -0,0 +1,277 @@ +/** + * @name DoNotTrack + * @description Stops Discord from tracking everything you do like Sentry and Analytics. + * @version 0.1.0 + * @author Zerebos + * @authorId 249746236008169473 + * @website https://github.com/zerebos/BetterDiscordAddons/tree/master/Plugins/DoNotTrack + * @source https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/master/Plugins/DoNotTrack/DoNotTrack.plugin.js + */ + +/*@cc_on +@if (@_jscript) + + // Offer to self-install for clueless users that try to run this directly. + var shell = WScript.CreateObject("WScript.Shell"); + var fs = new ActiveXObject("Scripting.FileSystemObject"); + var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins"); + var pathSelf = WScript.ScriptFullName; + // Put the user at ease by addressing them in the first person + shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30); + if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) { + shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40); + } else if (!fs.FolderExists(pathPlugins)) { + shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10); + } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) { + fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true); + // Show the user where to put plugins in the future + shell.Exec("explorer " + pathPlugins); + shell.Popup("I'm installed!", 0, "Successfully installed", 0x40); + } + WScript.Quit(); + +@else@*/ + +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/plugins/DoNotTrack/index.ts +var DoNotTrack_exports = {}; +__export(DoNotTrack_exports, { + default: () => DoNotTrack +}); +module.exports = __toCommonJS(DoNotTrack_exports); + +// src/common/plugin.ts +var Plugin = class { + meta; + manifest; + settings; + defaultSettings; + LocaleManager; + get strings() { + if (!this.manifest.strings) return {}; + const locale = this.LocaleManager?.getLocale().split("-")[0] ?? "en"; + if (this.manifest.strings.hasOwnProperty(locale)) return this.manifest.strings[locale]; + if (this.manifest.strings.hasOwnProperty("en")) return this.manifest.strings.en; + return this.manifest.strings; + } + constructor(meta, zplConfig) { + this.meta = meta; + this.manifest = zplConfig; + if (typeof this.manifest.config !== "undefined") { + this.defaultSettings = {}; + for (let s = 0; s < this.manifest.config.length; s++) { + const current = this.manifest.config[s]; + if (current.type != "category") { + this.defaultSettings[current.id] = current.value; + } else { + for (let si = 0; si < current.settings.length; si++) { + const subCurrent = current.settings[si]; + this.defaultSettings[subCurrent.id] = subCurrent.value; + } + } + } + this.settings = BdApi.Utils.extend({}, this.defaultSettings); + } + const currentVersionInfo = BdApi.Data.load(this.meta.name, "version"); + if (currentVersionInfo !== this.meta.version) { + this.#showChangelog(); + BdApi.Data.save(this.meta.name, "version", this.meta.version); + } + if (this.manifest.strings) this.LocaleManager = BdApi.Webpack.getModule((m) => m?.Messages && Object.keys(m?.Messages).length > 0); + if (this.manifest.config && !this.getSettingsPanel) { + this.getSettingsPanel = () => { + this.#updateConfig(); + return BdApi.UI.buildSettingsPanel({ + onChange: (_, id, value) => { + this.settings[id] = value; + this.saveSettings(); + }, + settings: this.manifest.config + }); + }; + } + } + async start() { + BdApi.Logger.info(this.meta.name, `version ${this.meta.version} has started.`); + if (this.defaultSettings) this.settings = this.loadSettings(); + if (typeof this.onStart == "function") this.onStart(); + } + stop() { + BdApi.Logger.info(this.meta.name, `version ${this.meta.version} has stopped.`); + if (typeof this.onStop == "function") this.onStop(); + } + #showChangelog() { + if (typeof this.manifest.changelog == "undefined") return; + const changelog = { + title: this.meta.name + " Changelog", + subtitle: `v${this.meta.version}`, + changes: [] + }; + if (!Array.isArray(this.manifest.changelog)) Object.assign(changelog, this.manifest.changelog); + else changelog.changes = this.manifest.changelog; + BdApi.UI.showChangelogModal(changelog); + } + saveSettings() { + BdApi.Data.save(this.meta.name, "settings", this.settings); + } + loadSettings() { + return BdApi.Utils.extend({}, this.defaultSettings ?? {}, BdApi.Data.load(this.meta.name, "settings")); + } + #updateConfig() { + if (!this.manifest.config) return; + for (const setting of this.manifest.config) { + if (setting.type !== "category") { + setting.value = this.settings[setting.id] ?? setting.value; + } else { + for (const subsetting of setting.settings) { + subsetting.value = this.settings[subsetting.id] ?? subsetting.value; + } + } + } + } + buildSettingsPanel(onChange) { + this.#updateConfig(); + return BdApi.UI.buildSettingsPanel({ + onChange: (groupId, id, value) => { + this.settings[id] = value; + onChange?.(groupId, id, value); + this.saveSettings(); + }, + settings: this.manifest.config + }); + } +}; + +// src/plugins/DoNotTrack/config.ts +var manifest = { + info: { + name: "DoNotTrack", + authors: [{ + name: "Zerebos", + discord_id: "249746236008169473", + github_username: "zerebos", + twitter_username: "IAmZerebos" + }], + version: "0.1.0", + description: "Stops Discord from tracking everything you do like Sentry and Analytics.", + github: "https://github.com/zerebos/BetterDiscordAddons/tree/master/Plugins/DoNotTrack", + github_raw: "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/master/Plugins/DoNotTrack/DoNotTrack.plugin.js" + }, + changelog: [ + { + title: "What's New?", + type: "added", + items: [ + "Plugin no longer relies on ZeresPluginLibrary!", + "DoNotTrack should be more resilient to Discord's changes." + ] + }, + { + title: "Fixes", + type: "fixed", + items: [ + "Fixed startup issues.", + "Hopefully fixed issues with the process monitor." + ] + } + ], + main: "index.ts", + config: [ + { + type: "switch", + id: "stopProcessMonitor", + name: "Stop Process Monitor", + note: "This setting stops Discord from monitoring the processes on your PC and prevents your currently played game from showing.", + value: true + } + ] +}; +var config_default = manifest; + +// src/plugins/DoNotTrack/index.ts +var { Patcher, Webpack, UI } = BdApi; +var SettingsManager = Webpack.getModule((m) => m?.updateAsync && m?.type === 1, { searchExports: true }); +var BoolSetting = Webpack.getModule((m) => m?.typeName?.includes("Bool"), { searchExports: true }); +var Analytics = Webpack.getByKeys("AnalyticEventConfigs"); +var NativeModule = Webpack.getByKeys("getDiscordUtils"); +var DoNotTrack = class extends Plugin { + constructor(meta) { + super(meta, config_default); + } + onStart() { + if (Analytics) { + Patcher.instead(this.meta.name, Analytics.default, "track", () => { + }); + } + if (NativeModule) { + Patcher.instead(this.meta.name, NativeModule, "ensureModule", (_, [moduleName], originalFunction) => { + if (moduleName?.includes("discord_rpc")) return; + return originalFunction(moduleName); + }); + } + window?.__SENTRY__?.globalEventProcessors?.splice(0, window?.__SENTRY__?.globalEventProcessors?.length); + window?.__SENTRY__?.logger?.disable(); + const SentryHub = window.DiscordSentry?.getCurrentHub?.(); + if (SentryHub) { + SentryHub.getClient()?.close?.(0); + const scope = SentryHub.getScope(); + scope?.clear?.(); + scope?.setFingerprint?.(null); + SentryHub?.setUser(null); + SentryHub?.setTags({}); + SentryHub?.setExtras({}); + SentryHub?.endSession(); + } + for (const method in console) { + if (!Object.hasOwn(console[method], "__sentry_original__")) continue; + console[method] = console[method].__sentry_original__; + } + if (this.settings.stopProcessMonitor) this.disableProcessMonitor(); + } + onStop() { + Patcher.unpatchAll(this.meta.name); + } + disableProcessMonitor() { + SettingsManager?.updateAsync("status", (settings) => settings.showCurrentGame = BoolSetting?.create({ value: false }), 0); + const DiscordUtils = NativeModule?.getDiscordUtils(); + if (!DiscordUtils) return UI.alert("DoNotTrack", "Unable to disable process monitor!"); + DiscordUtils.setObservedGamesCallback([], () => { + }); + Patcher.instead(this.meta.name, DiscordUtils, "setObservedGamesCallback", () => { + }); + } + enableProcessMonitor() { + SettingsManager?.updateAsync("status", (settings) => settings.showCurrentGame = BoolSetting?.create({ value: true }), 0); + UI.showConfirmationModal("Reload Discord?", "To reenable the process monitor Discord needs to be reloaded.", { + confirmText: "Reload", + cancelText: "Later", + onConfirm: () => window.location.reload() + }); + } + getSettingsPanel() { + return this.buildSettingsPanel((_, id, value) => { + if (id !== "stopProcessMonitor") return; + if (value) return this.disableProcessMonitor(); + return this.enableProcessMonitor(); + }); + } +}; + +/*@end@*/ \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/GameActivityToggle.config.json b/dotfiles/.config/BetterDiscord/plugins/GameActivityToggle.config.json new file mode 100755 index 0000000..7d3e5cd --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/GameActivityToggle.config.json @@ -0,0 +1,18 @@ +{ + "all": { + "cachedState": { + "date": "2026-01-24T10:37:57.110Z", + "value": false + }, + "general": { + "showButton": true, + "showItem": false, + "playEnable": true, + "playDisable": true + }, + "selections": { + "enableSound": "stream_started", + "disableSound": "stream_ended" + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/GameActivityToggle.plugin.js b/dotfiles/.config/BetterDiscord/plugins/GameActivityToggle.plugin.js new file mode 100755 index 0000000..f87a887 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/GameActivityToggle.plugin.js @@ -0,0 +1,497 @@ +/** + * @name GameActivityToggle + * @author DevilBro + * @authorId 278543574059057154 + * @version 1.3.9 + * @description Adds a Quick-Toggle Game Activity Button + * @invite Jx3TjNS + * @donate https://www.paypal.me/MircoWittrien + * @patreon https://www.patreon.com/MircoWittrien + * @website https://mwittrien.github.io/ + * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/GameActivityToggle/ + * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/GameActivityToggle/GameActivityToggle.plugin.js + */ + +module.exports = (_ => { + const changeLog = { + + }; + + return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class { + constructor (meta) {for (let key in meta) this[key] = meta[key];} + getName () {return this.name;} + getAuthor () {return this.author;} + getVersion () {return this.version;} + getDescription () {return `The Library Plugin needed for ${this.name} is missing. Open the Plugin Settings to download it. \n\n${this.description}`;} + + downloadLibrary () { + BdApi.Net.fetch("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js").then(r => { + if (!r || r.status != 200) throw new Error(); + else return r.text(); + }).then(b => { + if (!b) throw new Error(); + else return require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.UI.showToast("Finished downloading BDFDB Library", {type: "success"})); + }).catch(error => { + BdApi.UI.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library"); + }); + } + + load () { + if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []}); + if (!window.BDFDB_Global.downloadModal) { + window.BDFDB_Global.downloadModal = true; + BdApi.UI.showConfirmationModal("Library Missing", `The Library Plugin needed for ${this.name} is missing. Please click "Download Now" to install it.`, { + confirmText: "Download Now", + cancelText: "Cancel", + onCancel: _ => {delete window.BDFDB_Global.downloadModal;}, + onConfirm: _ => { + delete window.BDFDB_Global.downloadModal; + this.downloadLibrary(); + } + }); + } + if (!window.BDFDB_Global.pluginQueue.includes(this.name)) window.BDFDB_Global.pluginQueue.push(this.name); + } + start () {this.load();} + stop () {} + getSettingsPanel () { + let template = document.createElement("template"); + template.innerHTML = `
The Library Plugin needed for ${this.name} is missing.\nPlease click Download Now to install it.
`; + template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary); + return template.content.firstElementChild; + } + } : (([Plugin, BDFDB]) => { + var _this; + var toggleButton, toggleItem; + + const ActivityToggleComponent = class ActivityToggle extends BdApi.React.Component { + componentDidMount() { + toggleButton = this; + } + render() { + const enabled = this.props.forceState != undefined ? this.props.forceState : BDFDB.DiscordUtils.getSetting("status", "showCurrentGame"); + delete this.props.forceState; + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN._gameactivitytogglebutton, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.PanelButton, Object.assign({}, this.props, { + redGlow: !enabled, + tooltipText: enabled ? _this.labels.disable_activity : _this.labels.enable_activity, + icon: iconProps => BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.lottieicon, + style: { + "--__lottieIconColor": enabled ? "currentColor" : BDFDB.DiscordConstants.ColorsCSS.STATUS_DANGER, + "display": "flex", + "width": "20px", + "height": "20px" + }, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, Object.assign({}, iconProps, { + nativeClass: true, + width: 20, + height: 20, + color: "var(--__lottieIconColor)", + name: enabled ? BDFDB.LibraryComponents.SvgIcon.Names.GAMEPAD : BDFDB.LibraryComponents.SvgIcon.Names.GAMEPAD_DISABLED + })) + }), + onClick: _ => { + _this.toggle(); + if (toggleItem) BDFDB.ReactUtils.forceUpdate(toggleItem); + } + })) + }, true); + } + }; + + const ActivityToggleItemComponent = class ActivityToggleItem extends BdApi.React.Component { + componentDidMount() { + toggleItem = this; + } + componentWillUnmount() { + toggleItem = null; + } + render() { + const enabled = this.props.forceState != undefined ? this.props.forceState : BDFDB.DiscordUtils.getSetting("status", "showCurrentGame"); + delete this.props.forceState; + return BDFDB.ReactUtils.createElement("li", { + className: BDFDB.disCN.userpopoutmenuitem, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.userpopoutmenuiteminner, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, { + className: BDFDB.disCN.userpopoutmenuitemcontent, + onClick: _ => { + _this.toggle(); + if (toggleButton) BDFDB.ReactUtils.forceUpdate(toggleButton); + }, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.userpopoutmenuitemicon, + name: BDFDB.LibraryComponents.SvgIcon.Names.GAMEPAD, + nativeClass: true, + width: 16, + height: 16 + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.userpopoutmenuitemlabel, + children: BDFDB.ReactUtils.createElement("div", { + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextElement, { + className: BDFDB.disCN.userpopoutmenuitemlabel, + size: BDFDB.LibraryComponents.TextElement.Sizes.SIZE_14, + children: BDFDB.LanguageUtils.LanguageStrings.GAME_ACTIVITY + }) + }) + }) + ] + }), + enabled ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.menucolordefault, + background: BDFDB.disCN.menucheckbox, + foreground: BDFDB.disCN.menucheck, + name: BDFDB.LibraryComponents.SvgIcon.Names.CHECKBOX, + style: {background: "unset"} + }) : BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.menucolordefault, + name: BDFDB.LibraryComponents.SvgIcon.Names.CHECKBOX_EMPTY, + style: {background: "unset"} + }) + ] + }) + }); + } + }; + + var sounds = [], keybind; + + return class GameActivityToggle extends Plugin { + onLoad () { + _this = this; + + this.defaults = { + general: { + showButton: {value: true, description: "Show Quick Toggle Button"}, + showItem: {value: false, description: "Show Quick Toggle Item"}, + playEnable: {value: true, description: "Play Enable Sound"}, + playDisable: {value: true, description: "Play Disable Sound"} + }, + selections: { + enableSound: {value: "stream_started", description: "Enable Sound"}, + disableSound: {value: "stream_ended", description: "Disable Sound"} + } + }; + + this.modulePatches = { + after: [ + "Account", + "AccountPopout" + ] + }; + + this.css = ` + ${BDFDB.dotCNS._gameactivitytoggleadded + BDFDB.dotCN.accountinfoavatarwrapper} { + flex: 1 !important; + min-width: 0 !important; + } + ${BDFDB.dotCNS._gameactivitytoggleadded + BDFDB.dotCN._gameactivitytogglebutton} { + margin-right: 4px; + } + `; + } + + onStart () { + sounds = [BDFDB.LibraryModules.SoundParser && BDFDB.LibraryModules.SoundParser.keys()].flat(10).filter(n => n).map(s => s.replace("./", "").split(".")[0]).sort(); + + let cachedState = BDFDB.DataUtils.load(this, "cachedState"); + let state = BDFDB.DiscordUtils.getSetting("status", "showCurrentGame"); + if (!cachedState.date || (new Date() - cachedState.date) > 1000*60*60*24*3) { + cachedState.value = state; + cachedState.date = new Date(); + BDFDB.DataUtils.save(cachedState, this, "cachedState"); + } + else if (cachedState.value != null && cachedState.value != state) BDFDB.DiscordUtils.setSetting("status", "showCurrentGame", cachedState.value); + + let SettingsStore = BDFDB.DiscordUtils.getSettingsStore(); + if (SettingsStore) BDFDB.PatchUtils.patch(this, SettingsStore, "updateAsync", {after: e => { + if (e.methodArguments[0] != "status") return; + let newSettings = {value: undefined}; + e.methodArguments[1](newSettings); + if (newSettings.showCurrentGame != undefined) { + if (toggleButton) toggleButton.props.forceState = newSettings.showCurrentGame.value; + BDFDB.ReactUtils.forceUpdate(toggleButton); + if (toggleItem) toggleItem.props.forceState = newSettings.showCurrentGame.value; + BDFDB.ReactUtils.forceUpdate(toggleItem); + BDFDB.DataUtils.save({date: new Date(), value: newSettings.showCurrentGame.value}, this, "cachedState"); + } + }}); + + keybind = BDFDB.DataUtils.load(this, "keybind"); + keybind = BDFDB.ArrayUtils.is(keybind) ? keybind : []; + this.activateKeybind(); + + BDFDB.DiscordUtils.rerenderAll(); + } + + onStop () { + BDFDB.DiscordUtils.rerenderAll(); + } + + getSettingsPanel (collapseStates = {}) { + let settingsPanel; + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, { + collapseStates: collapseStates, + children: _ => { + let settingsItems = []; + + for (let key in this.defaults.general) settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Switch", + plugin: this, + keys: ["general", key], + label: this.defaults.general[key].description, + value: this.settings.general[key] + })); + + for (let key in this.defaults.selections) settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Select", + plugin: this, + keys: ["selections", key], + label: this.defaults.selections[key].description, + basis: "50%", + options: sounds.map(o => ({value: o, label: o.split(/[-_]/g).map(BDFDB.StringUtils.upperCaseFirstChar).join(" ")})), + value: this.settings.selections[key], + onChange: value => BDFDB.LibraryModules.SoundUtils.playSound(value, .4) + })); + + settingsItems.push(BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.settingsrowcontainer, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.settingsrowlabel, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsLabel, { + label: "Global Hotkey" + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + className: BDFDB.disCNS.settingsrowcontrol + BDFDB.disCN.flexchild, + grow: 0, + wrap: true, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.KeybindRecorder, { + value: !keybind ? [] : keybind, + reset: true, + onChange: value => { + keybind = value; + BDFDB.DataUtils.save(keybind, this, "keybind") + this.activateKeybind(); + } + }) + }) + ].flat(10).filter(n => n) + }) + })); + + return settingsItems; + } + }); + } + + processAccount (e) { + if (!this.settings.general.showButton) return; + let insertButton = returnvalue => { + let accountinfo = BDFDB.ReactUtils.findChild(returnvalue, {props: [["className", BDFDB.disCN.accountinfo]]}); + if (!accountinfo) return; + let buttons = BDFDB.ReactUtils.findChild(returnvalue, {props: [["className", BDFDB.disCN.accountinfobuttons]]}); + if (buttons) { + accountinfo.props.className = BDFDB.DOMUtils.formatClassName(accountinfo.props.className, BDFDB.disCN._gameactivitytoggleadded); + buttons.props.children.unshift(BDFDB.ReactUtils.createElement(ActivityToggleComponent, {})); + } + else { + let [children, index] = BDFDB.ReactUtils.findParent(returnvalue, {name: "AccountButtons"}); + if (index > -1) { + accountinfo.props.className = BDFDB.DOMUtils.formatClassName(accountinfo.props.className, BDFDB.disCN._gameactivitytoggleadded); + children.splice(index, 0, BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.accountinfobuttons, + children: BDFDB.ReactUtils.createElement(ActivityToggleComponent, {}) + })); + } + } + }; + if (e.returnvalue.props.children && e.returnvalue.props.children[0] && e.returnvalue.props.children[0].props && typeof e.returnvalue.props.children[0].props.children == "function") { + let childrenRender = e.returnvalue.props.children[0].props.children; + e.returnvalue.props.children[0].props.children = BDFDB.TimeUtils.suppress((...args) => { + let renderedChildren = childrenRender(...args); + insertButton(renderedChildren); + return renderedChildren; + }, "Error in Children Render in Account!", this); + } + else if (typeof e.returnvalue.props.children[0] == "function") { + let childrenRender = e.returnvalue.props.children; + e.returnvalue.props.children = BDFDB.TimeUtils.suppress((...args) => { + let renderedChildren = childrenRender(...args); + insertButton(renderedChildren); + return renderedChildren; + }, "Error in Children Render in Account!", this); + } + else insertButton(e.returnvalue.props.children); + } + + processAccountPopout (e) { + if (!this.settings.general.showItem) return; + let userpopoutMenus = BDFDB.ReactUtils.findChild(e.returnvalue, {props: [["className", BDFDB.disCN.userpopoutmenus]]}); + if (!userpopoutMenus) return; + userpopoutMenus.props.children.splice(1, 0, BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCNS.userpopoutoverlay + BDFDB.disCN.userpopoutmenuoverlay, + children: BDFDB.ReactUtils.createElement("ul", { + children: BDFDB.ReactUtils.createElement(ActivityToggleItemComponent, {}) + }) + })); + } + + activateKeybind () { + if (keybind && keybind.length) BDFDB.ListenerUtils.addGlobal(this, "GAMEACTIVITY_TOGGLE", keybind, _ => this.toggle()); + else BDFDB.ListenerUtils.removeGlobal(this, "GAMEACTIVITY_TOGGLE"); + } + + toggle () { + const shouldEnable = !BDFDB.DiscordUtils.getSetting("status", "showCurrentGame"); + this.settings.general[shouldEnable ? "playEnable" : "playDisable"] && BDFDB.LibraryModules.SoundUtils.playSound(this.settings.selections[shouldEnable ? "enableSound" : "disableSound"], .4); + BDFDB.DiscordUtils.setSetting("status", "showCurrentGame", shouldEnable); + } + + setLabelsByLanguage () { + switch (BDFDB.LanguageUtils.getLanguage().id) { + case "bg": // Bulgarian + return { + disable_activity: "Деактивирайте активността в играта", + enable_activity: "Активирайте Game Activity" + }; + case "da": // Danish + return { + disable_activity: "Deaktiver spilaktivitet", + enable_activity: "Aktivér spilaktivitet" + }; + case "de": // German + return { + disable_activity: "Spieleaktivität deaktivieren", + enable_activity: "Spieleaktivität aktivieren" + }; + case "el": // Greek + return { + disable_activity: "Απενεργοποίηση δραστηριότητας παιχνιδιού", + enable_activity: "Ενεργοποίηση δραστηριότητας παιχνιδιού" + }; + case "es": // Spanish + return { + disable_activity: "Deshabilitar la actividad del juego", + enable_activity: "Habilitar la actividad del juego" + }; + case "fi": // Finnish + return { + disable_activity: "Poista pelitoiminto käytöstä", + enable_activity: "Ota pelitoiminta käyttöön" + }; + case "fr": // French + return { + disable_activity: "Désactiver l'activité de jeu", + enable_activity: "Activer l'activité de jeu" + }; + case "hr": // Croatian + return { + disable_activity: "Onemogući aktivnost igre", + enable_activity: "Omogući aktivnost u igrama" + }; + case "hu": // Hungarian + return { + disable_activity: "Tiltsa le a játéktevékenységet", + enable_activity: "Engedélyezze a játéktevékenységet" + }; + case "it": // Italian + return { + disable_activity: "Disabilita l'attività di gioco", + enable_activity: "Abilita attività di gioco" + }; + case "ja": // Japanese + return { + disable_activity: "ゲームアクティビティを無効にする", + enable_activity: "ゲームアクティビティを有効にする" + }; + case "ko": // Korean + return { + disable_activity: "게임 활동 비활성화", + enable_activity: "게임 활동 활성화" + }; + case "lt": // Lithuanian + return { + disable_activity: "Išjungti žaidimų veiklą", + enable_activity: "Įgalinti žaidimų veiklą" + }; + case "nl": // Dutch + return { + disable_activity: "Schakel spelactiviteit uit", + enable_activity: "Schakel spelactiviteit in" + }; + case "no": // Norwegian + return { + disable_activity: "Deaktiver spillaktivitet", + enable_activity: "Aktiver spillaktivitet" + }; + case "pl": // Polish + return { + disable_activity: "Wyłącz aktywność w grach", + enable_activity: "Włącz aktywność w grach" + }; + case "pt-BR": // Portuguese (Brazil) + return { + disable_activity: "Desativar atividade de jogo", + enable_activity: "Habilitar atividade de jogo" + }; + case "ro": // Romanian + return { + disable_activity: "Dezactivați Activitatea jocului", + enable_activity: "Activați Activitatea jocului" + }; + case "ru": // Russian + return { + disable_activity: "Отключить игровую активность", + enable_activity: "Включить игровую активность" + }; + case "sv": // Swedish + return { + disable_activity: "Inaktivera spelaktivitet", + enable_activity: "Aktivera spelaktivitet" + }; + case "th": // Thai + return { + disable_activity: "ปิดการใช้งานกิจกรรมของเกม", + enable_activity: "เปิดใช้งานกิจกรรมเกม" + }; + case "tr": // Turkish + return { + disable_activity: "Oyun Etkinliğini Devre Dışı Bırak", + enable_activity: "Oyun Etkinliğini Etkinleştir" + }; + case "uk": // Ukrainian + return { + disable_activity: "Вимкнути ігрову активність", + enable_activity: "Увімкнути ігрову активність" + }; + case "vi": // Vietnamese + return { + disable_activity: "Tắt hoạt động trò chơi", + enable_activity: "Bật hoạt động trò chơi" + }; + case "zh-CN": // Chinese (China) + return { + disable_activity: "禁用游戏活动", + enable_activity: "启用游戏活动" + }; + case "zh-TW": // Chinese (Taiwan) + return { + disable_activity: "禁用遊戲活動", + enable_activity: "啟用遊戲活動" + }; + default: // English + return { + disable_activity: "Disable Game Activity", + enable_activity: "Enable Game Activity" + }; + } + } + }; + })(window.BDFDB_Global.PluginUtils.buildPlugin(changeLog)); +})(); diff --git a/dotfiles/.config/BetterDiscord/plugins/GlobalReplies.config.json b/dotfiles/.config/BetterDiscord/plugins/GlobalReplies.config.json new file mode 100755 index 0000000..fbd9c60 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/GlobalReplies.config.json @@ -0,0 +1,6 @@ +{ + "currentVersionInfo": { + "version": "1.0.1", + "hasShownChangelog": true + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/HideServersChannelsRedux.config.json b/dotfiles/.config/BetterDiscord/plugins/HideServersChannelsRedux.config.json new file mode 100644 index 0000000..9958530 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/HideServersChannelsRedux.config.json @@ -0,0 +1,6 @@ +{ + "currentVersionInfo": { + "version": "1.1.13", + "hasShownChangelog": true + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/InMyVoice.config.json b/dotfiles/.config/BetterDiscord/plugins/InMyVoice.config.json new file mode 100644 index 0000000..f3b7b24 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/InMyVoice.config.json @@ -0,0 +1,6 @@ +{ + "currentVersionInfo": { + "version": "1.2.2", + "hasShownChangelog": true + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/InMyVoice.plugin.js b/dotfiles/.config/BetterDiscord/plugins/InMyVoice.plugin.js new file mode 100644 index 0000000..695108c --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/InMyVoice.plugin.js @@ -0,0 +1,191 @@ +/** + * @name InMyVoice + * @author arg0NNY + * @authorLink https://github.com/arg0NNY/DiscordPlugins + * @invite M8DBtcZjXD + * @donate https://donationalerts.com/r/arg0nny + * @version 1.2.2 + * @description Shows if a person in the text chat is also in a voice chat you're in. + * @website https://github.com/arg0NNY/DiscordPlugins/tree/master/InMyVoice + * @source https://github.com/arg0NNY/DiscordPlugins/blob/master/InMyVoice/InMyVoice.plugin.js + * @updateUrl https://raw.githubusercontent.com/arg0NNY/DiscordPlugins/master/InMyVoice/InMyVoice.plugin.js + */ + +/* ### CONFIG START ### */ +const config = { + info: { + name: 'InMyVoice', + version: '1.2.2', + description: 'Shows if a person in the text chat is also in a voice chat you\'re in.' + }, + changelog: [ + { + type: 'fixed', + title: 'Fixes', + items: [ + 'Updated to work in the latest release of Discord.' + ] + } + ] +} +/* ### CONFIG END ### */ + +const { + Webpack, + UI, + React, + Patcher, + Utils, + Data +} = new BdApi(config.info.name) + +const UserStore = Webpack.getStore('UserStore') +const ChannelStore = Webpack.getStore('ChannelStore') +const SelectedChannelStore = Webpack.getStore('SelectedChannelStore') + +const findInReactTree = (tree, searchFilter) => Utils.findInTree(tree, searchFilter, { walkable: ['props', 'children', 'child', 'sibling'] }) + +const Selectors = { + BotTag: { + botTagCozy: Webpack.getByKeys('botTagCozy').botTagCozy, + botTagVerified: Webpack.getByKeys('botTagVerified').botTagVerified + } +} + +const UNIQUE_TAG = 'InMyVoiceTag' + +const VoiceChannelStore = Webpack.getByKeys('getVoiceStatesForChannel') +const MessageHeader = [...Webpack.getWithKey(Webpack.Filters.byStrings('decorations', 'withMentionPrefix'))] +const BotTag = [...Webpack.getWithKey(m => m?.Types?.SYSTEM_DM)] +const useStateFromStores = Webpack.getModule(Webpack.Filters.byStrings('useStateFromStores'), { searchExports: true }) + +function isInMyVoice (user) { + const voiceChannelId = useStateFromStores([SelectedChannelStore], () => SelectedChannelStore.getVoiceChannelId()) + const currentUser = useStateFromStores([UserStore], () => UserStore.getCurrentUser()) + const channel = useStateFromStores([ChannelStore], () => voiceChannelId && ChannelStore.getChannel(voiceChannelId)) + const voiceState = useStateFromStores([VoiceChannelStore], () => channel && VoiceChannelStore.getVoiceStatesForChannel(channel)) + + if (currentUser.id === user.id || !channel) return false + + const values = Object.values(voiceState) + return values.findIndex(x => x.user?.id === user.id) !== -1 +} + +function InVoiceTag ({ user }) { + if (!isInMyVoice(user)) return null + + return React.createElement(BotTag[0][BotTag[1]], { + className: `${Selectors.BotTag.botTagCozy} ${UNIQUE_TAG}`, + useRemSizes: true, + type: 'IN_VOICE' + }) +} + +module.exports = class InMyVoice { + start () { + this.patches() + } + + patches () { + this.patchMessages() + this.patchBotTags() + } + + patchMessages () { + Patcher.before(...MessageHeader, (self, [{ decorations, message }]) => { + if (!decorations || typeof decorations[1] !== 'object' || !'length' in decorations[1]) return + + decorations[1].unshift( + React.createElement(InVoiceTag, { user: message.author }) + ) + }) + } + + patchBotTags () { + Patcher.after(...BotTag, (self, _, value) => { + if (!value?.props?.className?.includes(UNIQUE_TAG)) return + + const TagContainer = findInReactTree(value, e => e.children?.some(c => typeof c?.props?.children === 'string')) + + TagContainer.children.find(c => typeof c?.props?.children === 'string').props.children = this.settings.text.toUpperCase() + TagContainer.children.unshift(this.buildInVoiceIcon()) + }) + } + + buildInVoiceIcon () { + return React.createElement( + 'svg', + { + className: Selectors.BotTag.botTagVerified, + width: 16, + height: 16, + viewBox: '0 0 28 28', + style: { + position: 'relative', + top: '1px', + left: '1px', + marginRight: '1px' + } + }, + React.createElement('path', { + fill: 'currentColor', + d: 'M3 10v4c0 .55.45 1 1 1h3l3.29 3.29c.63.63 1.71.18 1.71-.71V6.41c0-.89-1.08-1.34-1.71-.71L7 9H4c-.55 0-1 .45-1 1zm13.5 2c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 4.45v.2c0 .38.25.71.6.85C17.18 6.53 19 9.06 19 12s-1.82 5.47-4.4 6.5c-.36.14-.6.47-.6.85v.2c0 .63.63 1.07 1.21.85C18.6 19.11 21 15.84 21 12s-2.4-7.11-5.79-8.4c-.58-.23-1.21.22-1.21.85z' + }) + ) + } + + stop () { + Patcher.unpatchAll() + } + + constructor () { + this.defaultSettings = { + text: 'In voice' + } + + this.settings = this.loadSettings(this.defaultSettings) + + this.showChangelogIfNeeded() + } + + loadSettings (defaults = {}) { + return Utils.extend({}, defaults, Data.load('settings')) + } + saveSettings (settings = this.settings) { + return Data.save('settings', settings) + } + + showChangelogIfNeeded () { + const currentVersionInfo = Utils.extend( + { version: config.info.version, hasShownChangelog: false }, + Data.load('currentVersionInfo') + ) + if (currentVersionInfo.version === config.info.version && currentVersionInfo.hasShownChangelog) return + + this.showChangelog() + Data.save('currentVersionInfo', { version: config.info.version, hasShownChangelog: true }) + } + showChangelog () { + return UI.showChangelogModal({ + title: config.info.name, + subtitle: 'Version ' + config.info.version, + changes: config.changelog + }) + } + + getSettingsPanel () { + return UI.buildSettingsPanel({ + onChange: () => this.saveSettings(), + settings: [ + { + type: 'text', + id: 'text', + name: 'Tag Text', + note: 'Sets up tag\'s text near user\'s name.', + value: this.settings.text, + onChange: e => this.settings.text = e + } + ] + }) + } +} diff --git a/dotfiles/.config/BetterDiscord/plugins/MoreRoleColors.config.json b/dotfiles/.config/BetterDiscord/plugins/MoreRoleColors.config.json new file mode 100644 index 0000000..2950582 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/MoreRoleColors.config.json @@ -0,0 +1,3 @@ +{ + "lastVersion": "2.0.10" +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/MoreRoleColors.plugin.js b/dotfiles/.config/BetterDiscord/plugins/MoreRoleColors.plugin.js new file mode 100644 index 0000000..77a19aa --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/MoreRoleColors.plugin.js @@ -0,0 +1,1060 @@ +/** +* @name MoreRoleColors +* @author DaddyBoard +* @version 2.0.10 +* @description Adds role colors to usernames across Discord - including messages, voice channels, typing indicators, mentions, account area, text editor, audit log, role headers, user profiles, and tags +* @source https://github.com/DaddyBoard/BD-Plugins +* @invite ggNWGDV7e2 +*/ + +const { Webpack, React, Patcher, ReactUtils, Utils } = BdApi; +const { getStore, getByStrings, getBySource, getWithKey, Filters, getModule } = Webpack; +const VoiceUser = getBySource("g4", "H", "getAvatarURL"); +const GuildMemberStore = getStore("GuildMemberStore"); +const SelectedGuildStore = getStore("SelectedGuildStore"); +const RelationshipStore = getStore("RelationshipStore"); +const TypingStore = getStore("TypingStore"); +const TypingModule = getBySource('activityInviteEducationActivity') +const [MentionModule, key] = getWithKey(Filters.byStrings('USER_MENTION',"getNickname", "inlinePreview")); +const ChannelStore = getStore("ChannelStore"); +const UserStore = getStore("UserStore"); +const GuildStore = getStore("GuildStore"); +const useStateFromStores = getModule(Webpack.Filters.byStrings("getStateFromStores"), { searchExports: true }); +const GuildRoleStore = getStore("GuildRoleStore"); + +//types for changelog: added, fixed, improved, progress. +const config = { + banner: "", + changelog: [ + { + "title": "2.0.10 - Fixed", + "type": "fixed", + "items": [ + "Fixed account area coloring not updating when switching servers under certain conditions", + "Fixes for discord update" + ] + } + ], + settings: [ + { + "type": "category", + "id": "accountAreaColoring", + "name": "Account Area", + "collapsible": true, + "shown": false, + "settings": [ + { + "type": "switch", + "id": "accountArea", + "name": "Enable Account Area Coloring", + "note": "Colors your username in the account area", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.accountArea ?? true + }, + { + "type": "switch", + "id": "accountAreaGradient", + "name": "Use Gradient Coloring", + "note": "Apply gradient effect to account area colors", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.accountAreaGradient ?? true + } + ] + }, + { + "type": "category", + "id": "auditLogColoring", + "name": "Audit Log", + "collapsible": true, + "shown": false, + "settings": [ + { + "type": "switch", + "id": "auditLog", + "name": "Enable Audit Log Coloring", + "note": "Colors usernames in the audit log", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.auditLog ?? true + }, + { + "type": "switch", + "id": "auditLogGradient", + "name": "Use Gradient Coloring", + "note": "Apply gradient effect to audit log colors", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.auditLogGradient ?? true + } + ] + }, + { + "type": "category", + "id": "mentionsColoring", + "name": "Mentions", + "collapsible": true, + "shown": false, + "settings": [ + { + "type": "switch", + "id": "mentions", + "name": "Enable Mentions Coloring", + "note": "Colors usernames in mentions", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.mentions ?? true + }, + { + "type": "switch", + "id": "mentionsGradient", + "name": "Use Gradient Coloring", + "note": "Apply gradient effect to mention colors", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.mentionsGradient ?? true + } + ] + }, + { + "type": "category", + "id": "messagesColoring", + "name": "Messages", + "collapsible": true, + "shown": false, + "settings": [ + { + "type": "switch", + "id": "messages", + "name": "Enable Messages Coloring", + "note": "Colors users text by their role color", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.messages ?? true + }, + { + "type": "switch", + "id": "messagesGradient", + "name": "Use Gradient Coloring", + "note": "Apply gradient effect to message text colors", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.messagesGradient ?? true + } + ] + }, + { + "type": "category", + "id": "roleHeadersColoring", + "name": "Role Headers", + "collapsible": true, + "shown": false, + "settings": [ + { + "type": "switch", + "id": "roleHeaders", + "name": "Enable Role Headers Coloring", + "note": "Colors usernames in role headers", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.roleHeaders ?? true + }, + { + "type": "switch", + "id": "roleHeadersGradient", + "name": "Use Gradient Coloring", + "note": "Apply gradient effect to role header colors", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.roleHeadersGradient ?? true + } + ] + }, + { + "type": "category", + "id": "serverProfileDisplayNameColoring", + "name": "Server Profile Display Name", + "collapsible": true, + "shown": false, + "settings": [ + { + "type": "switch", + "id": "serverProfileDisplayName", + "name": "Enable Server Profile Display Name Coloring", + "note": "Colors display names in server profiles", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.serverProfileDisplayName ?? true + }, + { + "type": "switch", + "id": "serverProfileDisplayNameGradient", + "name": "Use Gradient Coloring", + "note": "Apply gradient effect to server profile display name colors", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.serverProfileDisplayNameGradient ?? true + } + ] + }, + { + "type": "category", + "id": "tagsColoring", + "name": "Tags", + "collapsible": true, + "shown": false, + "settings": [ + { + "type": "switch", + "id": "Tags", + "name": "Enable Tags Coloring", + "note": "Colors tags to match role colors", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.Tags ?? true + }, + { + "type": "switch", + "id": "TagsGradient", + "name": "Use Gradient Coloring", + "note": "Apply gradient effect to tag colors", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.TagsGradient ?? true + } + ] + }, + { + "type": "category", + "id": "textEditorColoring", + "name": "Text Editor", + "collapsible": true, + "shown": false, + "settings": [ + { + "type": "switch", + "id": "textEditor", + "name": "Enable Text Editor Coloring", + "note": "Colors mentions in the text editor", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.textEditor ?? true + }, + { + "type": "switch", + "id": "textEditorGradient", + "name": "Use Gradient Coloring", + "note": "Apply gradient effect to text editor colors", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.textEditorGradient ?? true + } + ] + }, + { + "type": "category", + "id": "typingUsersColoring", + "name": "Typing Indicator", + "collapsible": true, + "shown": false, + "settings": [ + { + "type": "switch", + "id": "typingUsers", + "name": "Enable Typing Indicator Coloring", + "note": "Colors usernames in typing indicators", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.typingUsers ?? true + }, + { + "type": "switch", + "id": "typingUsersGradient", + "name": "Use Gradient Coloring", + "note": "Apply gradient effect to typing indicator colors", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.typingUsersGradient ?? true + } + ] + }, + { + "type": "category", + "id": "userProfileColoring", + "name": "User Profile", + "collapsible": true, + "shown": false, + "settings": [ + { + "type": "switch", + "id": "userProfile", + "name": "Enable User Profile Coloring", + "note": "Colors usernames in user profiles", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.userProfile ?? true + }, + { + "type": "switch", + "id": "userProfileGradient", + "name": "Use Gradient Coloring", + "note": "Apply gradient effect to user profile colors", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.userProfileGradient ?? true + } + ] + }, + { + "type": "category", + "id": "voiceUsersColoring", + "name": "Voice Users", + "collapsible": true, + "shown": false, + "settings": [ + { + "type": "switch", + "id": "voiceUsers", + "name": "Enable Voice Users Coloring", + "note": "Colors usernames in voice channels", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.voiceUsers ?? true + }, + { + "type": "switch", + "id": "speakingIndicator", + "name": "Enable Speaking Indicator", + "note": "Changes opacity of voice usernames when speaking", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.speakingIndicator ?? false + }, + { + "type": "switch", + "id": "voiceUsersGradient", + "name": "Use Gradient Coloring", + "note": "Apply gradient effect to voice user colors", + "value": BdApi.Data.load('MoreRoleColors', 'settings')?.voiceUsersGradient ?? true + } + ] + } + ] +}; +module.exports = class MoreRoleColors { + constructor(meta) { + this.meta = meta; + this.defaultSettings = { + voiceUsers: true, + voiceUsersGradient: true, + speakingIndicator: false, + typingUsers: true, + typingUsersGradient: true, + mentions: true, + mentionsGradient: true, + accountArea: true, + accountAreaGradient: true, + textEditor: true, + textEditorGradient: true, + auditLog: true, + auditLogGradient: true, + roleHeaders: true, + roleHeadersGradient: true, + messages: false, + messagesGradient: false, + userProfile: true, + userProfileGradient: true, + serverProfileDisplayName: true, + serverProfileDisplayNameGradient: true, + Tags: true + }; + this.settings = this.loadSettings(); + } + + start() { + const lastVersion = BdApi.Data.load('MoreRoleColors', 'lastVersion'); + if (lastVersion !== this.meta.version) { + BdApi.UI.showChangelogModal({ + title: this.meta.name, + subtitle: this.meta.version, + banner: config.banner, + changes: config.changelog + }); + BdApi.Data.save('MoreRoleColors', 'lastVersion', this.meta.version); + } + + if (this.settings.voiceUsers) this.patchVoiceUsers(); + if (this.settings.typingUsers) this.patchTypingUsers(); + if (this.settings.mentions) this.patchMentions(); + if (this.settings.accountArea) this.patchAccountArea(); + if (this.settings.textEditor) this.patchTextEditor(); + if (this.settings.auditLog) this.patchAuditLog(); + if (this.settings.roleHeaders) this.patchRoleHeaders(); + if (this.settings.messages) this.patchMessages(); + if (this.settings.userProfile) this.patchUserProfile(); + if (this.settings.Tags) this.patchTags(); + if (this.settings.serverProfileDisplayName) this.patchServerProfileDisplayName(); + this.forceUpdateComponents(); + } + + loadSettings() { + return { ...this.defaultSettings, ...BdApi.Data.load('MoreRoleColors', 'settings') }; + } + + saveSettings(newSettings) { + this.settings = newSettings; + BdApi.Data.save('MoreRoleColors', 'settings', newSettings); + } + + getSettingsPanel() { + config.settings.forEach(category => { + if (category.settings) { + category.settings.forEach(setting => { + setting.value = this.settings[setting.id]; + }); + } + }); + + return BdApi.UI.buildSettingsPanel({ + settings: config.settings, + onChange: (category, id, value) => { + const newSettings = { ...this.settings, [id]: value }; + this.saveSettings(newSettings); + + if (value) { + switch (id) { + case 'voiceUsers': this.patchVoiceUsers(); break; + case 'typingUsers': this.patchTypingUsers(); break; + case 'mentions': this.patchMentions(); break; + case 'accountArea': this.patchAccountArea(); break; + case 'textEditor': this.patchTextEditor(); break; + case 'auditLog': this.patchAuditLog(); break; + case 'roleHeaders': this.patchRoleHeaders(); break; + case 'messages': this.patchMessages(); break; + case 'userProfile': this.patchUserProfile(); break; + case 'Tags': this.patchTags(); break; + case 'serverProfileDisplayName': this.patchServerProfileDisplayName(); break; + } + } else { + if (id === 'serverProfileDisplayName') { + Patcher.unpatchAll("MoreRoleColors-ServerProfileDisplayName"); + Patcher.unpatchAll("MoreRoleColors-ServerProfileGuildSelector"); + } else { + Patcher.unpatchAll(`MoreRoleColors-${id}`); + } + if (id === 'accountArea' && this._unpatchAccountArea) { + this._unpatchAccountArea(); + } + } + + this.forceUpdateComponents(); + } + }); + } + + stop() { + Patcher.unpatchAll("MoreRoleColors-voiceUsers"); + Patcher.unpatchAll("MoreRoleColors-typingUsers"); + Patcher.unpatchAll("MoreRoleColors-mentions"); + Patcher.unpatchAll("MoreRoleColors-accountArea"); + Patcher.unpatchAll("MoreRoleColors-textEditor"); + Patcher.unpatchAll("MoreRoleColors-auditLog"); + Patcher.unpatchAll("MoreRoleColors-roleHeaders"); + Patcher.unpatchAll("MoreRoleColors-messages"); + Patcher.unpatchAll("MoreRoleColors-ServerProfileDisplayName"); + Patcher.unpatchAll("MoreRoleColors-ServerProfileGuildSelector"); + Patcher.unpatchAll("MoreRoleColors-userProfile"); + if (this._unpatchAccountArea) this._unpatchAccountArea(); + if (this._unpatchUserProfile) this._unpatchUserProfile(); + if (this._unpatchTags) this._unpatchTags(); + this.forceUpdateComponents(); + } + + onSwitch() { + if (this.settings.accountArea) this.patchAccountArea(); + } + + forceUpdateComponents() { + const voiceUsers = Array.from(document.querySelectorAll("[class^=voiceUser_]"), m => BdApi.ReactUtils.getOwnerInstance(m, { filter: m=> !m?.renderInner }).forceUpdate()); + const accountArea = document.querySelectorAll("[class^=avatarWrapper_]"); + const typingUsers = document.querySelectorAll("[class^=channelBottomBarArea_]"); + for (const node of voiceUsers) { + ReactUtils.getOwnerInstance(node)?.forceUpdate(); + } + for (const node of accountArea) { + ReactUtils.getOwnerInstance(node, { filter: m => m.renderNameTag })?.forceUpdate(); + } + for (const node of typingUsers) { + ReactUtils.getOwnerInstance(node, { filter: m => m.typingUsers })?.forceUpdate(); + } + } + + applyRoleStyle(element, colorObject, type) { + if (!type) { + element.style = {color: colorObject.colorString}; + return; + } + if (!GuildStore.getGuild(SelectedGuildStore.getGuildId())?.features?.has?.("ENHANCED_ROLE_COLORS")) { + element.style = {color: colorObject.colorString}; + return; + } + + if (colorObject.colorStrings && colorObject.colorStrings.primaryColor && colorObject.colorStrings.secondaryColor) { + let gradient; + if (colorObject.colorStrings.tertiaryColor) { + gradient = `linear-gradient(to right, ${colorObject.colorStrings.primaryColor} 0%, ${colorObject.colorStrings.secondaryColor} 50%, ${colorObject.colorStrings.tertiaryColor} 100%)`; + } else { + gradient = `linear-gradient(to right, ${colorObject.colorStrings.primaryColor} 0%, ${colorObject.colorStrings.secondaryColor} 100%)`; + } + + element.style = { + color: "unset", + background: `${gradient} text`, + WebkitBackgroundClip: "text", + WebkitTextFillColor: "transparent" + }; + } else { + element.style = { + color: colorObject.colorString, + background: "none", + backgroundClip: "unset", + WebkitBackgroundClip: "unset", + WebkitTextFillColor: "unset" + }; + } + } + + getColorObjectForMember(guildId, member) { + if (member.colorStrings) { + return member; + } + + const roles = Object.values(GuildRoleStore.getRolesSnapshot(guildId)); + const matchingRole = roles.find(role => role.colorString === member.colorString); + + return matchingRole || member; + } + + patchVoiceUsers() { + Patcher.after("MoreRoleColors-voiceUsers", VoiceUser, "Ay", (_, [props], res) => { + if (!res?.props) return; + + const guildId = SelectedGuildStore.getGuildId(); + const member = GuildMemberStore.getMember(guildId, props?.user?.id); + if (!member?.colorString) return; + + const usernameElement = Utils.findInTree(res, x => x?.className?.includes('usernameFont'), { + walkable: ['props', 'children'] + }); + if (!usernameElement) return; + + const colorObject = this.getColorObjectForMember(guildId, member); + this.applyRoleStyle(usernameElement, colorObject, this.settings.voiceUsersGradient); + + const isSpeaking = props?.speaking; + if (this.settings.speakingIndicator && !isSpeaking) { + usernameElement.style.opacity = "0.56"; + } + + usernameElement.style.backfaceVisibility = "hidden"; + }); + } + + patchTypingUsers() { + const cache = new WeakMap(); + const pluginInstance = this; + + Patcher.after("MoreRoleColors-typingUsers", TypingModule, "Ay", (that, args, res) => { + let newType = cache.get(res.type); + + if (!newType) { + const target = res.type; + + newType = function(props) { + const channelId = props.channel?.id; + const typingUsersStore = useStateFromStores([TypingStore], () => + TypingStore.getTypingUsers(channelId) + ); + + const res = target.apply(this, arguments); + + const typingUsers = Object.keys(typingUsersStore) + .filter(e => e != UserStore.getCurrentUser().id) + .filter(e => !RelationshipStore.isBlockedOrIgnored(e)) + .map(e => UserStore.getUser(e)) + .filter(e => e != null); + + const typing = Utils.findInTree(res, (node) => node?.className?.includes("typingDots"), { + walkable: ["props", "children"] + }); + + if (typing && typeof typing?.children?.[1]?.props?.children !== "string") { + const validUserIds = typingUsers.map(u => u.id); + + if (validUserIds.length <= 3) { + let count = 0; + typing.children[1].props.children = typing.children[1].props.children.map((m, i) => { + if (typeof m === "string") return m; + + const member = GuildMemberStore.getMember(props.guildId, validUserIds[count++]); + let elementStyle = {}; + + if (member?.colorString) { + const colorObject = pluginInstance.getColorObjectForMember(props.guildId, member); + const tempElement = { style: {} }; + pluginInstance.applyRoleStyle(tempElement, colorObject, pluginInstance.settings.typingUsersGradient); + elementStyle = tempElement.style; + } + + return React.createElement("strong", { + key: i, + children: m.props.children, + style: elementStyle + }); + }); + } + } + + return res; + } + + cache.set(res.type, newType); + cache.set(newType, newType); + } + + res.type = newType; + }); + } + + patchMentions() { + Patcher.after("MoreRoleColors-mentions", MentionModule, key, (_, [props], res) => { + if (!props?.userId || !res?.props?.children?.props) return res; + + const guildId = (() => { + if (!BdApi.Plugins.isEnabled("PingNotification")) return SelectedGuildStore.getGuildId(); + + let element = document.activeElement; + while (element && !element.classList.contains('ping-notification')) { + element = element.parentElement; + } + + if (element) { + const channelId = element.getAttribute('data-channel-id'); + if (channelId) { + const channel = ChannelStore.getChannel(channelId); + if (channel?.guild_id) return channel.guild_id; + } + } + + if (props.channelId) { + const channel = ChannelStore.getChannel(props.channelId); + if (channel?.guild_id) return channel.guild_id; + if (!channel?.guild_id) return; + } + + return SelectedGuildStore.getGuildId(); + })(); + + if (!guildId) return res; + + const member = GuildMemberStore.getMember(guildId, props.userId); + if (!member?.colorString) return res; + + const colorObject = this.getColorObjectForMember(guildId, member); + + const original = res.props.children.props.children; + res.props.children.props.children = (props, context) => { + const ret = original(props, context); + if (ret?.props) { + ret.props.color = parseInt(member.colorString.slice(1), 16); + + if (this.settings.mentionsGradient && + GuildStore.getGuild(guildId)?.features?.has?.("ENHANCED_ROLE_COLORS") && + colorObject.colorStrings && + colorObject.colorStrings.primaryColor && colorObject.colorStrings.secondaryColor) { + ret.props.roleColors = { + primaryColor: colorObject.colorStrings.primaryColor, + secondaryColor: colorObject.colorStrings.secondaryColor, + tertiaryColor: colorObject.colorStrings.tertiaryColor || null + }; + } + } + + return ret; + }; + + return res; + }); + } + + patchAccountArea() { + const cache = new WeakMap(); + const MAX_RETRIES = 10; + + const patchAccountAreaWithRetry = (attempts = 0) => { + if (attempts >= MAX_RETRIES) { + return; + } + + const accountArea = document.querySelector("[class^=avatarWrapper_]"); + if (!accountArea) { + setTimeout(() => patchAccountAreaWithRetry(attempts + 1), 1000); + return; + } + + const owner = ReactUtils.getOwnerInstance(accountArea, { filter: m => m.renderNameTag }); + if (!owner) { + setTimeout(() => patchAccountAreaWithRetry(attempts + 1), 1000); + return; + } + + const renderNameTag = owner.renderNameTag; + const pluginInstance = this; + owner.renderNameTag = function() { + const res = renderNameTag.call(this); + const type = res.props.children[0].props.children.type; + + if (type.__MoreRoleColors) return res; + + let component = cache.get(type); + if (!component) { + component = new Proxy(type, { + apply: (target, thisArg, argArray) => { + const res = Reflect.apply(target, thisArg, argArray); + const guildId = SelectedGuildStore.getGuildId(); + const member = GuildMemberStore.getMember( + guildId, + this.props?.currentUser?.id || UserStore.getCurrentUser()?.id + ); + + if (member?.colorString) { + const colorObject = pluginInstance.getColorObjectForMember(guildId, member); + const tempElement = { style: {} }; + pluginInstance.applyRoleStyle(tempElement, colorObject, pluginInstance.settings.accountAreaGradient); + res.props.style = tempElement.style; + } + + return res; + }, + get(target, key, receiver) { + if (key === "__MoreRoleColors") return true; + return Reflect.get(target, key, receiver); + } + }); + cache.set(type, component); + } + + res.props.children[0].props.children.type = component; + return res; + }.bind(owner); + + this._unpatchAccountArea = () => { + owner.renderNameTag = renderNameTag; + }; + owner.forceUpdate(); + }; + + patchAccountAreaWithRetry(); + } + + patchTextEditor() { + const [ module, key ] = BdApi.Webpack.getWithKey(BdApi.Webpack.Filters.byStrings(".hidePersonalInformation", "#", "<@", ".discriminator")); + const pluginInstance = this; + BdApi.Patcher.after("MoreRoleColors-textEditor", module, key, (that, [{ id, guildId }], res) => { + const member = GuildMemberStore.getMember(guildId, id); + if (!member?.colorString) return res; + + const colorObject = pluginInstance.getColorObjectForMember(guildId, member); + const color = parseInt(member.colorString.slice(1), 16); + const innerMention = res.props.children?.props?.children; + + if (!innerMention?.props) return res; + + return BdApi.React.cloneElement(res, { + children: BdApi.React.cloneElement(res.props.children, { + children: BdApi.React.cloneElement(innerMention, { + ...innerMention.props, + color, + ...(pluginInstance.settings.textEditorGradient && + GuildStore.getGuild(guildId)?.features?.has?.("ENHANCED_ROLE_COLORS") && + colorObject.colorStrings?.primaryColor && colorObject.colorStrings?.secondaryColor && { + roleColors: { + primaryColor: colorObject.colorStrings.primaryColor, + secondaryColor: colorObject.colorStrings.secondaryColor, + tertiaryColor: colorObject.colorStrings.tertiaryColor || null + } + }) + }) + }) + }); + }); + } + + patchAuditLog() { + let AuditLogUser; + const pluginInstance = this; + + function patchAuditLogUser() { + BdApi.Patcher.after("MoreRoleColors-auditLog", AuditLogUser.prototype, "render", (that, args, res) => { + const member = GuildMemberStore.getMember(SelectedGuildStore.getGuildId(), that.props.user.id); + + if (!member?.colorString) return; + + if (res.props?.children?.[0]?.props) { + const colorObject = pluginInstance.getColorObjectForMember(SelectedGuildStore.getGuildId(), member); + const tempElement = { style: {} }; + pluginInstance.applyRoleStyle(tempElement, colorObject, pluginInstance.settings.auditLogGradient); + res.props.children[0].props.style = tempElement.style; + } + }); + } + + function attemptPatchAuditLogUser() { + if (AuditLogUser) { + patchAuditLogUser(); + return; + } + + const undo = BdApi.Patcher.after("MoreRoleColors-auditLog-temp", BdApi.Webpack.getModule(m => m.displayName === "ForwardRef(FluxContainer(GuildSettingsAuditLogEntry))"), "render", (that, [props], res) => { + undo(); + + const a = res?.type?.prototype?.render?.call({ + props: res.props, + memoizedGetStateFromStores: () => ({ + theme: "dark" + }) + }); + + const { render } = a.type; + a.type = { + ...a.type, + render() { + const ret = render.apply(this, arguments); + + const undo = BdApi.Patcher.after("MoreRoleColors-auditLog-temp2", ret.props, "children", (that, args, res) => { + undo(); + + const node = BdApi.Utils.findInTree(res, (m) => m?.type?.prototype?.render && m.props.user?.id, { + walkable: [ "children", "props" ] + }); + + if (node?.type) { + AuditLogUser = node.type; + patchAuditLogUser(); + } + }); + + return ret; + } + } + + return a; + }); + } + + BdApi.Webpack.waitForModule(m => m.displayName === "ForwardRef(FluxContainer(GuildSettingsAuditLogEntry))").then(() => { + attemptPatchAuditLogUser(); + }); + } + + patchRoleHeaders() { + const roleHeaderModule = BdApi.Webpack.getBySource(/,.{1,3}.kL,.{1,3}.wx\)/) + BdApi.Patcher.after("MoreRoleColors-roleHeaders", roleHeaderModule, "A", (_, [props], res) => { + let roleNameArea = Utils.findInTree(res, (m) => m?.className?.includes('membersGroupName')) + if (roleNameArea) { + const guildId = SelectedGuildStore.getGuildId(); + const roles = Object.values(GuildRoleStore.getRolesSnapshot(guildId)); + + let roleName = roleNameArea.children; + let role = roles.find(r => r.name === roleName); + if (role) { + this.applyRoleStyle(res.props.children[1].props, role, this.settings.roleHeadersGradient); + } else { + roleName = res.props.children[1].props.children[1]; + role = roles.find(r => r.name === roleName); + if (role) { + this.applyRoleStyle(res.props.children[1].props, role, this.settings.roleHeadersGradient); + } + } + } + }); + } + + patchMessages() { + const MessageContentMRC = BdApi.Webpack.getModule((m) => + m?.type?.toString?.()?.includes("BK") && + m?.type?.toString?.()?.includes("w3") + ); + BdApi.Patcher.after("MoreRoleColors-messages", MessageContentMRC, "type", (_, [props], res) => { + if (!props?.message?.author?.id) return res; + + const guildId = SelectedGuildStore.getGuildId(); + if (!guildId) return res; + + const member = GuildMemberStore.getMember(guildId, props.message.author.id); + if (member?.colorString) { + const colorObject = this.getColorObjectForMember(guildId, member); + + const hasCodeBlock = Array.isArray(res?.props?.children?.[0]) && + res.props.children[0].some(child => child?.type === 'pre'); + const isCompact = props?.compact === true; + const useGradient = this.settings.messagesGradient && !(isCompact && hasCodeBlock); + + if (!res.props.style) res.props.style = {}; + const tempElement = { style: {} }; + this.applyRoleStyle(tempElement, colorObject, useGradient); + Object.assign(res.props.style, tempElement.style); + } + + return res; + }); + } + + patchUserProfile() { + const UserProfileModule = BdApi.Webpack.getByStrings("displayProfile", "hasAvatarForGuild", { defaultExport: false }); + const cache = new WeakMap(); + const pluginInstance = this; + + const GuildMemberStore = BdApi.Webpack.getStore("GuildMemberStore"); + + BdApi.Patcher.after("MoreRoleColors-userProfile", UserProfileModule, "A", (_, [props], res) => { + const profileComponent = res.props.children[1]; + + let newType = cache.get(profileComponent.type); + if (!newType) { + newType = new Proxy(profileComponent.type, { + apply: (target, thisArg, args) => { + const res = Reflect.apply(target, thisArg, args); + + const displayProfile = args[0].tags.props.displayProfile; + + const member = GuildMemberStore.getMember(displayProfile?.guildId, displayProfile?.userId); + + if (!res?.props) return res; + + const userObject = BdApi.Utils.findInTree(res,x=>x?.className?.includes('nickname'), {walkable: ['props','children']}) + if (!userObject) return res; + + if (member?.colorString) { + const colorObject = pluginInstance.getColorObjectForMember(displayProfile?.guildId, member); + const tempElement = { style: {} }; + pluginInstance.applyRoleStyle(tempElement, colorObject, pluginInstance.settings.userProfileGradient); + + if (!userObject?.style) { + Object.defineProperty(userObject, "style", { + value: tempElement.style, + writable: true, + enumerable: true, + configurable: true + }); + } else { + Object.assign(userObject.style, tempElement.style); + } + } + + return res; + } + }); + + cache.set(profileComponent.type, newType); + cache.set(newType, newType); + } + + profileComponent.type = newType; + return res; + }); + } + + patchTags() { + const TagModule = BdApi.Webpack.getByStrings(".BOT", ".ORIGINAL_POSTER", { defaultExport: false }); + + function hexToRgb(hex) { + hex = hex.replace(/^#/, ""); + if (hex.length === 3) { + hex = hex.split("").map(x => x + x).join(""); + } + const num = parseInt(hex, 16); + return `rgb(${(num >> 16) & 255}, ${(num >> 8) & 255}, ${num & 255})`; + } + + class TagWrapper extends BdApi.React.Component { + constructor(props) { + super(props); + this.tagRef = BdApi.React.createRef(); + } + + componentDidMount() { + const node = this.tagRef.current; + if (!node) return; + if (!node.parentElement) return; + const username = node.parentElement.querySelector("[class*=username]"); + if (!username) return; + + const style = username.querySelector("[style]") || username; + let backgroundColor = style?.style?.color; + + if (!backgroundColor) { + const computed = window.getComputedStyle(style); + let gradientColor = computed.getPropertyValue("--custom-gradient-color-1")?.trim(); + if (gradientColor) { + if (gradientColor.startsWith("#")) { + backgroundColor = hexToRgb(gradientColor); + } else if (gradientColor.startsWith("rgb")) { + backgroundColor = gradientColor; + } + } + } + + if (!backgroundColor) return; + + node.style.backgroundColor = backgroundColor; + const contrast = this.getContrastingColor(backgroundColor); + + node.style.color = contrast; + + const svgElements = node.querySelectorAll("svg"); + svgElements.forEach((svg) => { + const paths = svg.querySelectorAll("path"); + paths.forEach((path) => { + path.style.fill = contrast; + }); + }); + + const tagTextSpans = node.querySelectorAll("span"); + tagTextSpans.forEach((span) => { + span.style.color = contrast; + }); + } + + getContrastingColor(color) { + let r, g, b; + if (color.startsWith('#')) { + const hex = color.substring(1); + r = parseInt(hex.substring(0, 2), 16); + g = parseInt(hex.substring(2, 4), 16); + b = parseInt(hex.substring(4, 6), 16); + } else if (color.startsWith('rgb')) { + const rgbValues = color.match(/\d+/g); + if (rgbValues && rgbValues.length >= 3) { + r = parseInt(rgbValues[0]); + g = parseInt(rgbValues[1]); + b = parseInt(rgbValues[2]); + } else { + return "#000000"; + } + } else { + return "#000000"; + } + + const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; + return luminance > 0.5 ? "#000000" : "#FFFFFF"; + } + + render() { + if (!this.props.tag) return null; + return BdApi.React.cloneElement(this.props.tag, { ref: this.tagRef }); + } + } + + Patcher.after("MoreRoleColors-Tags", TagModule, "A", (_, args, res) => { + return BdApi.React.createElement(TagWrapper, { tag: res }); + }); + + this._unpatchTags = () => { + Patcher.unpatchAll("MoreRoleColors-Tags"); + }; + } + + patchServerProfileDisplayName() { + BdApi.UI.showToast("server profile disabled. will fix later."); + // const ServerProfileGuildSelector = BdApi.Webpack.getBySource(".getFlattenedGuildIds", ".getGuilds", "A", "Sizes.SMOL", { defaultExport: false }); + // console.log("MRC", ServerProfileGuildSelector); + // let currentProfileGuildId = null; + // const pluginInstance = this; + + // Patcher.after("MoreRoleColors-ServerProfileGuildSelector", ServerProfileGuildSelector, "A", (_, [props], res) => { + // console.log("selector",res) + // currentProfileGuildId = res.props.children.props.guildId; + // return res; + // }); + + // BdApi.Webpack.waitForModule((e, m) => + // BdApi.Webpack.modules[m.id]?.toString?.()?.includes(".isVerifiedBot", "forceUsername:!0") + // ).then(ServerProfileDisplayNameModule => { + // console.log("MRC2", ServerProfileDisplayNameModule); + // Patcher.after("MoreRoleColors-ServerProfileDisplayName", ServerProfileDisplayNameModule.A, "type", (_, [props], res) => { + // console.log("displayname",res); + // const target = res.props.children[0].props.children[0].props; + // const currentUser = UserStore.getCurrentUser(); + // const member = GuildMemberStore.getMember(currentProfileGuildId, currentUser.id); + + // if (member?.colorString) { + // const colorObject = pluginInstance.getColorObjectForMember(currentProfileGuildId, member); + // const tempElement = { style: {} }; + // pluginInstance.applyRoleStyle(tempElement, colorObject, pluginInstance.settings.serverProfileDisplayNameGradient); + // target.style = tempElement.style; + // } + + // return res; + // }); + + // }); + } +} diff --git a/dotfiles/.config/BetterDiscord/plugins/NotificationSounds.config.json b/dotfiles/.config/BetterDiscord/plugins/NotificationSounds.config.json new file mode 100644 index 0000000..e9c5f6d --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/NotificationSounds.config.json @@ -0,0 +1,420 @@ +{ + "all": { + "choices": { + "activity_end": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "activity_launch": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "activity_user_join": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "activity_user_left": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "call_calling": { + "category": "---", + "invisibleMute": false, + "mute": null, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "call_ringing": { + "category": "---", + "invisibleMute": false, + "mute": null, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "call_ringing_snow_halation": { + "category": "---", + "invisibleMute": false, + "mute": null, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "call_ringing_snowsgiving": { + "category": "---", + "invisibleMute": false, + "mute": null, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "clip_error": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "clip_save": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "deafen": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "disconnect": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "highfive_clap": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "highfive_whistle": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "ddr-down": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "ddr-left": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "ddr-right": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "ddr-up": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "reconnect": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "message1": { + "category": "Discord", + "invisibleMute": false, + "mute": true, + "sound": "Message (Current Channel)", + "streamMute": false, + "volume": 100 + }, + "message3": { + "category": "---", + "invisibleMute": false, + "mute": true, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "dm": { + "category": "---", + "invisibleMute": false, + "mute": true, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "groupdm": { + "category": "---", + "invisibleMute": false, + "mute": true, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "mentioned": { + "category": "---", + "invisibleMute": false, + "mute": true, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "everyone": { + "category": "---", + "invisibleMute": false, + "mute": true, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "here": { + "category": "---", + "invisibleMute": false, + "mute": true, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "reply": { + "category": "---", + "invisibleMute": false, + "mute": true, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "role": { + "category": "---", + "invisibleMute": false, + "mute": true, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "mute": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "ptt_start": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "ptt_stop": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "stage_waiting": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "stream_ended": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "stream_started": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "stream_user_joined": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "stream_user_left": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "success": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "undeafen": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "unmute": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "vibing_wumpus": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "user_join": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "user_leave": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "user_moved": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "voice_filter_loopback_off": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "voice_filter_loopback_on": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "voice_filter_off": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "voice_filter_on": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "voice_filter_swap": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "camera_off": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "camera_on": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + }, + "hang_status_select": { + "category": "---", + "invisibleMute": false, + "mute": false, + "sound": "---", + "streamMute": false, + "volume": 100 + } + }, + "toggles": { + "playIfMuted": false + }, + "volumes": { + "globalVolume": 100 + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/NotificationSounds.plugin.js b/dotfiles/.config/BetterDiscord/plugins/NotificationSounds.plugin.js new file mode 100644 index 0000000..df423bc --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/NotificationSounds.plugin.js @@ -0,0 +1,765 @@ +/** + * @name NotificationSounds + * @author DevilBro + * @authorId 278543574059057154 + * @version 4.0.7 + * @description Allows you to replace the native Sounds with custom Sounds + * @invite Jx3TjNS + * @donate https://www.paypal.me/MircoWittrien + * @patreon https://www.patreon.com/MircoWittrien + * @website https://mwittrien.github.io/ + * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/NotificationSounds/ + * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/NotificationSounds/NotificationSounds.plugin.js + */ + +module.exports = (_ => { + const changeLog = { + + }; + + return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class { + constructor (meta) {for (let key in meta) this[key] = meta[key];} + getName () {return this.name;} + getAuthor () {return this.author;} + getVersion () {return this.version;} + getDescription () {return `The Library Plugin needed for ${this.name} is missing. Open the Plugin Settings to download it. \n\n${this.description}`;} + + downloadLibrary () { + BdApi.Net.fetch("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js").then(r => { + if (!r || r.status != 200) throw new Error(); + else return r.text(); + }).then(b => { + if (!b) throw new Error(); + else return require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.UI.showToast("Finished downloading BDFDB Library", {type: "success"})); + }).catch(error => { + BdApi.UI.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library"); + }); + } + + load () { + if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []}); + if (!window.BDFDB_Global.downloadModal) { + window.BDFDB_Global.downloadModal = true; + BdApi.UI.showConfirmationModal("Library Missing", `The Library Plugin needed for ${this.name} is missing. Please click "Download Now" to install it.`, { + confirmText: "Download Now", + cancelText: "Cancel", + onCancel: _ => {delete window.BDFDB_Global.downloadModal;}, + onConfirm: _ => { + delete window.BDFDB_Global.downloadModal; + this.downloadLibrary(); + } + }); + } + if (!window.BDFDB_Global.pluginQueue.includes(this.name)) window.BDFDB_Global.pluginQueue.push(this.name); + } + start () {this.load();} + stop () {} + getSettingsPanel () { + let template = document.createElement("template"); + template.innerHTML = `
The Library Plugin needed for ${this.name} is missing.\nPlease click Download Now to install it.
`; + template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary); + return template.content.firstElementChild; + } + } : (([Plugin, BDFDB]) => { + var audios, choices, firedEvents; + var volumes = {}; + var toggles = {}; + + const removeAllKey = "REMOVE_ALL_BDFDB_DEVILBRO_DO_NOT_COPY"; + const defaultDevice = "default"; + + var currentDevice = defaultDevice, createdAudios = {}; + + let types = {}, soundPacks = []; + + const message1Types = { + dm: {src: "./message3.mp3", name: "Message (Direct Message)"}, + groupdm: {src: "./message3.mp3", name: "Message (Group Message)"}, + mentioned: {src: "./message2.mp3", name: "Message Mentioned"}, + reply: {src: "./message2.mp3", name: "Message Mentioned (reply)"}, + role: {src: "./mention1.mp3", name: "Message Mentioned (role)"}, + everyone: {src: "./mention2.mp3", name: "Message Mentioned (@everyone)"}, + here: {src: "./mention3.mp3", name: "Message Mentioned (@here)"} + }; + + const namePrefixes = { + "user_join": "Voice Channel", + "user_leave": "Voice Channel", + "user_moved": "Voice Channel" + }; + + const nameSynonymes = { + "message3": "Message (Current Channel)", + "reconnect": "Invited To Speak" + }; + + const defaultAudios = { + "---": { + "---": null + }, + "Discord": {} + }; + + const WebAudioSound = class WebAudioSound { + constructor (type) { + this.name = type; + this._src = choices[type] && audios[choices[type].category][choices[type].sound] || types[type] && types[type].src || BDFDB.LibraryModules.SoundParser(`./${type}.mp3`); + this._volume = (choices[type] ? choices[type].volume : 100) / 100; + } + loop () { + this._ensureAudio().then(audio => { + audio.loop = true; + audio.play(); + }); + } + play () { + this._ensureAudio().then(audio => { + audio.loop = false; + audio.play(); + }); + } + playWithListener (duration) { + return new Promise((callback, errorCallback) => { + this._ensureAudio().then(audio => { + if (!duration && duration !== 0) errorCallback(new Error("sound has no duration")); + audio.loop = false; + audio.play(); + setTimeout(_ => callback(true), duration); + }); + }); + } + pause () { + this._audio.then(audio => { + audio.pause(); + }); + } + stop () { + this._destroyAudio(); + } + setTime (time) { + this._audio.then(audio => { + audio.currentTime = time; + }); + } + setLoop (loop) { + this._audio.then(audio => { + audio.loop = loop; + }); + } + _destroyAudio () { + if (this._audio) { + this._audio.then(audio => { + audio.pause(); + audio.src = ""; + }); + this._audio = null; + } + } + _ensureAudio () { + return this._audio = this._audio || new Promise((callback, errorCallback) => { + let audio = new Audio; + audio.src = this._src && this._src.startsWith("data") ? this._src.replace(/ /g, "") : this._src; + audio.onloadeddata = _ => { + audio.volume = Math.min((BDFDB.LibraryStores.MediaEngineStore.getOutputVolume() / 100) * this._volume * (volumes.globalVolume / 100), 1); + BDFDB.DiscordUtils.isPlaformEmbedded() && audio.setSinkId(currentDevice || defaultDevice); + callback(audio); + }; + audio.onerror = _ => errorCallback(new Error("could not play audio")); + audio.onended = _ => this._destroyAudio(); + audio.load(); + }), this._audio; + } + }; + + return class NotificationSounds extends Plugin { + onLoad () { + audios = {}; + choices = {}; + firedEvents = {}; + + this.defaults = { + volumes: { + globalVolume: {value: 100, description: "Global Notification Sounds Volume"} + }, + toggles: { + playIfMuted: {value: false, description: "Play Sounds Of Muted Channels/Servers"} + } + }; + + this.patchPriority = 9; + } + + onStart () { + soundPacks = []; + const soundKeys = BDFDB.LibraryModules.SoundParser.keys(); + for (let key of soundKeys) { + const id = key.replace("./", "").replace(".mp3", ""); + const name = [namePrefixes[id], (nameSynonymes[id] || id).replace("ddr-", "HotKeys_").replace("ptt_", "Push2Talk_").split(/[_-]/)].flat(10).filter(n => n).map(BDFDB.StringUtils.upperCaseFirstChar).join(" ").replace(/1$/g, ""); + const src = BDFDB.LibraryModules.SoundParser(key); + + let soundPackName = id.split("_")[0]; + if (soundPackName != id && soundKeys.filter(n => n.indexOf(`./${soundPackName}`) > -1).length > 8) { + soundPacks.push(soundPackName); + soundPackName = BDFDB.StringUtils.upperCaseFirstChar(soundPackName); + if (!defaultAudios[soundPackName]) defaultAudios[soundPackName] = {}; + defaultAudios[soundPackName][name.replace(new RegExp(`${soundPackName} `, "i"), "").replace(/bootup/i, "Discodo")] = src; + } + else { + defaultAudios.Discord[name] = src; + if (this.isSoundUsedAnywhere(id)) types[id] = { + name: name, + src: src, + mute: id.startsWith("call_") ? null : false, + streamMute: false, + invisibleMute: false + }; + if (id == "message1" || id == "message3") { + types[id].mute = true; + types[id].streamMute = false; + types[id].invisibleMute = false; + if (id == "message1") for (let subType in message1Types) types[subType] = { + name: message1Types[subType].name, + src: BDFDB.LibraryModules.SoundParser(message1Types[subType].src), + mute: true, + streamMute: false, + invisibleMute: false + } + } + } + types = BDFDB.ObjectUtils.sort(types, "name"); + } + for (let pack in defaultAudios) defaultAudios[pack] = BDFDB.ObjectUtils.sort(defaultAudios[pack]); + + if (BDFDB.DiscordUtils.isPlaformEmbedded()) { + let change = _ => { + if (window.navigator.mediaDevices && window.navigator.mediaDevices.enumerateDevices) { + window.navigator.mediaDevices.enumerateDevices().then(enumeratedDevices => { + let id = BDFDB.LibraryStores.MediaEngineStore.getOutputDeviceId(); + let allDevices = BDFDB.LibraryStores.MediaEngineStore.getOutputDevices(); + let filteredDevices = enumeratedDevices.filter(d => d.kind == "audiooutput" && d.deviceId != "communications"); + let deviceIndex = BDFDB.LibraryModules.ArrayUtils(allDevices).sortBy(d => d.index).findIndex(d => d.id == id); + let deviceViaId = allDevices[id]; + let deviceViaIndex = filteredDevices[deviceIndex]; + if (deviceViaId && deviceViaIndex && deviceViaIndex.label != deviceViaId.name) deviceViaIndex = filteredDevices.find(d => d.label == deviceViaId.name); + currentDevice = deviceViaIndex ? deviceViaIndex.deviceId : defaultDevice; + }).catch(_ => { + currentDevice = defaultDevice; + }); + } + }; + BDFDB.StoreChangeUtils.add(this, BDFDB.LibraryStores.MediaEngineStore, change); + change(); + } + + BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.DispatchApiUtils, "dispatch", {after: e => { + if (BDFDB.ObjectUtils.is(e.methodArguments[0]) && (e.methodArguments[0].type == "CALL_DELETE" || e.methodArguments[0].type == "CALL_UPDATE" && !e.methodArguments[0].ringing.includes(BDFDB.UserUtils.me.id))) { + ["call_ringing", "call_calling"].forEach(type => { + if (createdAudios[type]) createdAudios[type].stop(); + }); + } + + }, before: e => { + if (BDFDB.ObjectUtils.is(e.methodArguments[0]) && e.methodArguments[0].type == "MESSAGE_CREATE" && e.methodArguments[0].message) { + const message = e.methodArguments[0].message; + const guildId = message.guild_id || null; + if (message.author.id != BDFDB.UserUtils.me.id && !BDFDB.LibraryStores.RelationshipStore.isBlocked(message.author.id)) { + const channel = BDFDB.LibraryStores.ChannelStore.getChannel(message.channel_id); + + const isCurrent = BDFDB.LibraryStores.SelectedChannelStore.getChannelId() == channel.id; + const isGroupDM = channel.isGroupDM(); + const isThread = BDFDB.ChannelUtils.isThread(channel); + const isCurrentAndFocused = isCurrent && document.hasFocus(); + + if (!toggles.playIfMuted) { + if ((isThread && BDFDB.LibraryStores.JoinedThreadsStore.isMuted(channel.id)) || (!isThread && BDFDB.LibraryStores.UserGuildSettingsStore.isGuildOrCategoryOrChannelMuted(guildId, channel.id))) return; + } + + if (isCurrent && BDFDB.LibraryStores.NotificationSettingsStore.getNotifyMessagesInSelectedChannel()) { + this.fireEvent("message3"); + this.playAudio("message3"); + return; + } + else if (!guildId) { + this.fireEvent(isGroupDM ? "groupdm" : "dm"); + !isCurrentAndFocused && this.playAudio(isGroupDM ? "groupdm" : "dm"); + return; + } + else if (guildId) { + if (BDFDB.LibraryModules.MentionUtils.isRawMessageMentioned({rawMessage: message, userId: BDFDB.UserUtils.me.id})) { + if (message.mentions.length && !this.isSuppressMentionsEnabled(guildId, channel.id)) for (const mention of message.mentions) if (mention.id == BDFDB.UserUtils.me.id) { + if (message.message_reference && !message.interaction) { + this.fireEvent("reply"); + !isCurrentAndFocused && this.playAudio("reply"); + return; + } + if (!message.message_reference) { + this.fireEvent("mentioned"); + !isCurrentAndFocused && this.playAudio("mentioned"); + return; + } + } + if (message.mention_roles.length && !BDFDB.LibraryStores.UserGuildSettingsStore.isSuppressRolesEnabled(guildId, channel.id)) { + const member = BDFDB.LibraryStores.GuildMemberStore.getMember(guildId, BDFDB.UserUtils.me.id); + if (member && member.roles.length) for (const roleId of message.mention_roles) if (member.roles.includes(roleId)) { + this.fireEvent("role"); + !isCurrentAndFocused && this.playAudio("role"); + return; + } + } + if (message.mention_everyone && !BDFDB.LibraryStores.UserGuildSettingsStore.isSuppressEveryoneEnabled(guildId, channel.id)) { + if (message.content.indexOf("@everyone") > -1) { + this.fireEvent("everyone"); + !isCurrentAndFocused && this.playAudio("everyone"); + return; + } + if (message.content.indexOf("@here") > -1) { + this.fireEvent("here"); + !isCurrentAndFocused && this.playAudio("here"); + return; + } + } + } + if (BDFDB.LibraryStores.UserGuildSettingsStore.allowAllMessages(channel) && !(isThread && !BDFDB.LibraryStores.JoinedThreadsStore.hasJoined(channel.id))) { + this.fireEvent("message1"); + !isCurrentAndFocused && this.playAudio("message1"); + return; + } + } + } + } + }}); + + BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.DesktopNotificationUtils, "showNotification", {before: e => { + let soundObjIndex = Array.from(e.methodArguments).findIndex(n => n && n.sound); + if (soundObjIndex && e.methodArguments[soundObjIndex].sound.includes("message")) e.methodArguments[soundObjIndex].sound = null; + }}); + if (BDFDB.LibraryModules.SoundUtils && BDFDB.LibraryModules.SoundUtils.createSound) { + let cancel = BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.SoundUtils, "createSound", {after: e => { + if (e.returnValue && e.returnValue.constructor && e.returnValue.constructor.prototype && typeof e.returnValue.constructor.prototype.play == "function") { + cancel(); + BDFDB.PatchUtils.patch(this, e.returnValue.constructor.prototype, ["play", "loop", "playWithListener"], {instead: e2 => { + let type = e2.instance && e2.instance.name; + if (type && choices[type]) { + e2.stopOriginalMethodCall(); + if (type == "message1" || type == "message3") BDFDB.TimeUtils.timeout(_ => { + let called = false; + for (let subType of [type].concat(Object.keys(message1Types))) if (firedEvents[subType]) { + delete firedEvents[subType]; + called = true; + break; + } + if (!called) return this.playAudio(type, e2.originalMethodName, e2.instance.duration); + }); + else return this.playAudio(type, e2.originalMethodName, e2.instance.duration); + } + else return this.playAudio(type, e2.originalMethodName, e2.instance.duration); + }}); + BDFDB.PatchUtils.patch(this, e.returnValue.constructor.prototype, "stop", {after: e2 => { + let type = e2.instance && e2.instance.name; + if (type && createdAudios[type]) createdAudios[type].stop(); + }}); + } + }}, {noCache: true}); + BDFDB.LibraryModules.SoundUtils.createSound("call_calling"); + } + + this.loadAudios(); + this.loadChoices(); + + this.forceUpdateAll(); + } + + onStop () { + for (let type in createdAudios) if (createdAudios[type]) createdAudios[type].stop(); + } + + getSettingsPanel (collapseStates = {}) { + let successSavedAudio = data => { + BDFDB.NotificationUtils.toast(`Sound ${data.sound} was added to category ${data.category}.`, {type: "success"}); + if (!audios[data.category]) audios[data.category] = {}; + audios[data.category][data.sound] = data.source; + BDFDB.DataUtils.save(audios, this, "audios"); + BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates); + + }; + + let settingsPanel; + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, { + collapseStates: collapseStates, + children: _ => { + let settingsItems = []; + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "Settings", + collapseStates: collapseStates, + children: [ + ...Object.keys(volumes).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Slider", + plugin: this, + keys: ["volumes", key], + basis: "50%", + label: this.defaults.volumes[key].description, + value: volumes[key] + })), + ...Object.keys(toggles).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Switch", + plugin: this, + keys: ["toggles", key], + label: this.defaults.toggles[key].description, + value: toggles[key] + })) + ] + })); + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "Add new Sound", + collapseStates: collapseStates, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.margintop4, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormItem, { + title: "Categoryname", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + className: "input-newsound input-category", + value: "", + placeholder: "Categoryname" + }) + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormItem, { + title: "Soundname", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + className: "input-newsound input-sound", + value: "", + placeholder: "Soundname" + }) + }) + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.margintop4, + align: BDFDB.LibraryComponents.Flex.Align.END, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormItem, { + title: "Source", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + className: "input-newsound input-source", + type: "file", + filter: ["audio", "video"], + value: "", + placeholder: "Source" + }) + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, { + style: {marginBottom: 4}, + onClick: _ => { + for (let input of settingsPanel.props._node.querySelectorAll(".input-newsound " + BDFDB.dotCN.input)) if (!input.value || input.value.length == 0 || input.value.trim().length == 0) return BDFDB.NotificationUtils.toast("Fill out all Fields to add a new Sound", {type: "danger"}); + let category = settingsPanel.props._node.querySelector(".input-category " + BDFDB.dotCN.input).value.trim(); + let sound = settingsPanel.props._node.querySelector(".input-sound " + BDFDB.dotCN.input).value.trim(); + let source = settingsPanel.props._node.querySelector(".input-source " + BDFDB.dotCN.input); + let value = source && (source.getAttribute("file") || source.value).trim(); + if (value.indexOf("http") == 0) BDFDB.LibraryRequires.request(value, (error, response, result) => { + if (response) { + let type = response.headers["content-type"]; + if (type && (type.indexOf("octet-stream") > -1 || type.indexOf("audio") > -1 || type.indexOf("video") > -1)) return successSavedAudio({category, sound, source: value}); + } + BDFDB.NotificationUtils.toast("Use a valid direct link to a video or audio source, they usually end on something like .mp3, .mp4 or .wav", {type: "danger"}); + }); + else if (value.indexOf("data:") == 0) return successSavedAudio({category, sound, source: value}); + }, + children: BDFDB.LanguageUtils.LanguageStrings.SAVE + }) + ] + }) + ] + })); + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "Sound Configuration", + collapseStates: collapseStates, + children: Object.keys(types).map(type => type == "message3" && !BDFDB.LibraryStores.NotificationSettingsStore.getNotifyMessagesInSelectedChannel() ? null : [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.marginbottom8, + align: BDFDB.LibraryComponents.Flex.Align.CENTER, + direction: BDFDB.LibraryComponents.Flex.Direction.HORIZONTAL, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsLabel, { + label: types[type].name + }), + ["mute", "streamMute", "invisibleMute"].some(n => types[type][n] !== null) && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, { + onClick: event => BDFDB.ContextMenuUtils.open(this, event, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, { + children: [ + {key: "mute", label: ["Mute in", BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.StatusComponents.Status, {style: {marginLeft: 6}, size: 12, status: BDFDB.LibraryComponents.StatusComponents.Types.DND})]}, + {key: "invisibleMute", label: ["Mute in", BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.StatusComponents.Status, {style: {marginLeft: 6}, size: 12, status: BDFDB.LibraryComponents.StatusComponents.Types.INVISIBLE})]}, + {key: "streamMute", label: ["Mute while", BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.StatusComponents.Status, {style: {marginLeft: 6}, size: 12, status: BDFDB.LibraryComponents.StatusComponents.Types.STREAMING})]} + ].map(n => types[type][n.key] !== null && BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuCheckboxItem, { + label: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + align: BDFDB.LibraryComponents.Flex.Align.CENTER, + children: n.label + }), + icon: _ => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.MenuItems.MenuHint, { + hint: n.hint + }), + id: BDFDB.ContextMenuUtils.createItemId(this.name, type, n.key), + checked: choices[type][n.key], + action: state => { + choices[type][n.key] = state; + this.saveChoice(type, false); + } + })).filter(n => n) + })), + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + width: 20, + height: 20, + name: BDFDB.LibraryComponents.SvgIcon.Names.COG + }) + }) + ].filter(n => n) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.marginbottom8, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 0, + shrink: 0, + basis: "31%", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormItem, { + title: "Category", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, { + value: choices[type].category, + options: Object.keys(audios).map(name => ({value: name, label: name})), + searchable: true, + onChange: value => { + const categorySounds = audios[value] || {}; + choices[type].category = value; + choices[type].sound = categorySounds[types[type].name] ? types[type].name : Object.keys(categorySounds)[0]; + this.saveChoice(type, true); + BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates); + } + }) + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 0, + shrink: 0, + basis: "31%", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormItem, { + title: "Sound", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, { + value: choices[type].sound, + options: Object.keys(audios[choices[type].category] || {}).map(name => ({value: name, label: name})), + searchable: true, + onChange: value => { + choices[type].sound = value; + this.saveChoice(type, true); + BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates); + } + }) + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 0, + shrink: 0, + basis: "31%", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormItem, { + title: "Volume", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Slider, { + defaultValue: choices[type].volume, + digits: 1, + onValueRender: value => { + return value + "%"; + }, + onValueChange: value => { + choices[type].volume = value; + this.saveChoice(type, true); + } + }) + }) + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormDivider, { + className: BDFDB.disCN.marginbottom8 + }) + ]).flat(10).filter(n => n) + })); + + let removeableCategories = [{value: removeAllKey, label: BDFDB.LanguageUtils.LanguageStrings.ALL}].concat(Object.keys(audios).filter(category => !(defaultAudios[category] && !Object.keys(audios[category] || {}).filter(sound => defaultAudios[category][sound] === undefined).length)).map(name => ({value: name, label: name}))); + let removeableSounds = {}; + for (let category of removeableCategories) removeableSounds[category.value] = [{value: removeAllKey, label: BDFDB.LanguageUtils.LanguageStrings.ALL}].concat(Object.keys(audios[category.value] || {}).filter(sound => !(defaultAudios[category.value] && defaultAudios[category.value][sound] !== undefined)).map(name => ({value: name, label: name}))); + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "Remove Sounds", + collapseStates: collapseStates, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.margintop4, + align: BDFDB.LibraryComponents.Flex.Align.END, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 0, + shrink: 0, + basis: "35%", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormItem, { + title: "Category", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, { + key: "REMOVE_CATEGORY", + value: removeAllKey, + options: removeableCategories, + searchable: true, + onChange: (category, instance) => { + let soundSelectIns = BDFDB.ReactUtils.findOwner(BDFDB.ReactUtils.findOwner(instance, {name: ["BDFDB_Modal", "BDFDB_SettingsPanel"], up: true}), {key: "REMOVE_SOUND"}); + if (soundSelectIns && removeableSounds[category]) { + soundSelectIns.props.options = removeableSounds[category]; + soundSelectIns.props.value = removeAllKey; + BDFDB.ReactUtils.forceUpdate(soundSelectIns); + } + } + }) + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 0, + shrink: 0, + basis: "35%", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormItem, { + title: "Sound", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, { + key: "REMOVE_SOUND", + value: removeAllKey, + options: removeableSounds[removeAllKey], + searchable: true + }) + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 0, + shrink: 1, + basis: "25%", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, { + style: {marginBottom: 4}, + color: BDFDB.LibraryComponents.Button.Colors.RED, + onClick: (event, instance) => { + let wrapperIns = BDFDB.ReactUtils.findOwner(instance, {name: ["BDFDB_Modal", "BDFDB_SettingsPanel"], up: true}); + let categorySelectIns = BDFDB.ReactUtils.findOwner(wrapperIns, {key: "REMOVE_CATEGORY"}); + let soundSelectIns = BDFDB.ReactUtils.findOwner(wrapperIns, {key: "REMOVE_SOUND"}); + if (categorySelectIns && soundSelectIns) { + let soundAmount = 0; + let catAll = categorySelectIns.props.value == removeAllKey; + let soundAll = soundSelectIns.props.value == removeAllKey; + if (catAll) soundAmount = BDFDB.ArrayUtils.sum(Object.keys(audios).map(category => Object.keys(audios[category] || {}).filter(sound => !(defaultAudios[category] && defaultAudios[category][sound] !== undefined)).length)); + else if (soundAll) soundAmount = Object.keys(audios[categorySelectIns.props.value] || {}).filter(sound => !(defaultAudios[categorySelectIns.props.value] && defaultAudios[categorySelectIns.props.value][sound] !== undefined)).length; + else if (audios[categorySelectIns.props.value][soundSelectIns.props.value]) soundAmount = 1; + + if (soundAmount) BDFDB.ModalUtils.confirm(this, `Are you sure you want to delete ${soundAmount} added Sound${soundAmount == 1 ? "" : "s"}?`, _ => { + if (catAll) BDFDB.DataUtils.remove(this, "audios"); + else if (soundAll) BDFDB.DataUtils.remove(this, "audios", categorySelectIns.props.value); + else { + delete audios[categorySelectIns.props.value][soundSelectIns.props.value]; + if (BDFDB.ObjectUtils.isEmpty(audios[categorySelectIns.props.value])) delete audios[categorySelectIns.props.value]; + BDFDB.DataUtils.save(audios, this, "audios"); + } + this.loadAudios(); + this.loadChoices(); + BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates); + }); + else BDFDB.NotificationUtils.toast("No Sounds to delete", {type: "danger"}); + } + }, + children: BDFDB.LanguageUtils.LanguageStrings.DELETE + }) + }) + ] + }) + })); + + return settingsItems; + } + }); + } + + onSettingsClosed () { + if (this.SettingsUpdated) { + delete this.SettingsUpdated; + for (let type in createdAudios) if (createdAudios[type]) createdAudios[type].stop(); + createdAudios = {}; + this.forceUpdateAll(); + } + } + + forceUpdateAll () { + volumes = BDFDB.DataUtils.get(this, "volumes"); + toggles = BDFDB.DataUtils.get(this, "toggles"); + + BDFDB.PatchUtils.forceAllUpdates(this); + BDFDB.DiscordUtils.rerenderAll(); + } + + loadAudios () { + audios = Object.assign({}, BDFDB.DataUtils.load(this, "audios"), defaultAudios); + BDFDB.DataUtils.save(BDFDB.ObjectUtils.exclude(audios, Object.keys(defaultAudios)), this, "audios"); + } + + loadChoices () { + let loadedChoices = BDFDB.DataUtils.load(this, "choices"); + for (let type in types) { + let choice = loadedChoices[type] || {}, soundFound = false; + for (let category in audios) if (choice.category == category) for (let sound in audios[category]) if (choice.sound == sound) { + soundFound = true; + break; + } + if (!soundFound) choice = { + category: "---", + sound: "---", + volume: 100, + mute: types[type].mute, + streamMute: types[type].streamMute, + invisibleMute: types[type].invisibleMute + }; + choices[type] = choice; + this.saveChoice(type, false); + } + } + + saveChoice (type, play) { + if (!choices[type]) return; + BDFDB.DataUtils.save(choices[type], this, "choices", type); + if (play) { + this.SettingsUpdated = true; + this.playAudio(type); + } + } + + playAudio (type, functionCall = "play", duration = 0) { + type = soundPacks.some(soundPack => type.startsWith(soundPack + "_")) ? type.split("_").splice(1).join("_") : type; + if (this.dontPlayAudio(type) || BDFDB.LibraryStores.StreamerModeStore.disableSounds) return; + if (createdAudios[type]) createdAudios[type].stop(); + createdAudios[type] = new WebAudioSound(type); + return createdAudios[type][typeof createdAudios[type][functionCall] == "function" ? functionCall : "play"](duration); + } + + isSuppressMentionsEnabled (guildId, channelId) { + let channelSettings = BDFDB.LibraryStores.UserGuildSettingsStore.getChannelMessageNotifications(guildId, channelId); + return channelSettings && (channelSettings == BDFDB.DiscordConstants.UserNotificationSettings.NO_MESSAGES || channelSettings == BDFDB.DiscordConstants.UserNotificationSettings.NULL && BDFDB.LibraryStores.UserGuildSettingsStore.getMessageNotifications(guildId) == BDFDB.DiscordConstants.UserNotificationSettings.NO_MESSAGES); + } + + dontPlayAudio (type) { + let status = BDFDB.UserUtils.getStatus(); + return choices[type] && (choices[type].mute && status == "dnd" || choices[type].streamMute && status == "streaming" || choices[type].invisibleMute && (status == "offline" || status == "invisible")); + } + + fireEvent (type) { + firedEvents[type] = true; + BDFDB.TimeUtils.timeout(_ => delete firedEvents[type], 3000); + } + + isSoundUsedAnywhere (type) { + return type && type.indexOf("poggermode_") != 0 && type != "human_man" && type != "robot_man" && type != "discodo" && type != "overlayunlock" && type != "call_ringing_beat" && !(type != "message1" && type != "message3" && /\d$/.test(type)); + } + }; + })(window.BDFDB_Global.PluginUtils.buildPlugin(changeLog)); +})(); diff --git a/dotfiles/.config/BetterDiscord/plugins/PermissionsViewer.config.json b/dotfiles/.config/BetterDiscord/plugins/PermissionsViewer.config.json new file mode 100644 index 0000000..f89b6f0 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/PermissionsViewer.config.json @@ -0,0 +1,12 @@ +{ + "currentVersionInfo": { + "version": "0.2.11", + "hasShownChangelog": true + }, + "settings": { + "contextMenus": true, + "popouts": true, + "displayMode": "cozy" + }, + "version": "0.3.2" +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/PermissionsViewer.plugin.js b/dotfiles/.config/BetterDiscord/plugins/PermissionsViewer.plugin.js new file mode 100644 index 0000000..4e6c75f --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/PermissionsViewer.plugin.js @@ -0,0 +1,739 @@ +/** + * @name PermissionsViewer + * @description Allows you to view a user's permissions. Thanks to Noodlebox for the idea! + * @version 0.3.2 + * @author Zerebos + * @authorId 249746236008169473 + * @website https://github.com/zerebos/BetterDiscordAddons/tree/master/Plugins/PermissionsViewer + * @source https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/master/Plugins/PermissionsViewer/PermissionsViewer.plugin.js + */ + +/*@cc_on +@if (@_jscript) + + // Offer to self-install for clueless users that try to run this directly. + var shell = WScript.CreateObject("WScript.Shell"); + var fs = new ActiveXObject("Scripting.FileSystemObject"); + var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins"); + var pathSelf = WScript.ScriptFullName; + // Put the user at ease by addressing them in the first person + shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30); + if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) { + shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40); + } else if (!fs.FolderExists(pathPlugins)) { + shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10); + } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) { + fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true); + // Show the user where to put plugins in the future + shell.Exec("explorer " + pathPlugins); + shell.Popup("I'm installed!", 0, "Successfully installed", 0x40); + } + WScript.Quit(); + +@else@*/ + +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/plugins/PermissionsViewer/index.ts +var index_exports = {}; +__export(index_exports, { + default: () => PermissionsViewer +}); +module.exports = __toCommonJS(index_exports); + +// src/common/plugin.ts +var Plugin = class { + meta; + manifest; + settings; + defaultSettings; + LocaleManager; + get strings() { + if (!this.manifest.strings) return {}; + const locale = this.LocaleManager?.locale.split("-")[0] ?? "en"; + if (this.manifest.strings.hasOwnProperty(locale)) return this.manifest.strings[locale]; + if (this.manifest.strings.hasOwnProperty("en")) return this.manifest.strings.en; + return this.manifest.strings; + } + constructor(meta, zplConfig) { + this.meta = meta; + this.manifest = zplConfig; + if (typeof this.manifest.config !== "undefined") { + this.defaultSettings = {}; + for (let s = 0; s < this.manifest.config.length; s++) { + const current = this.manifest.config[s]; + if (current.type != "category") { + this.defaultSettings[current.id] = current.value; + } else { + for (let si = 0; si < current.settings.length; si++) { + const subCurrent = current.settings[si]; + this.defaultSettings[subCurrent.id] = subCurrent.value; + } + } + } + this.settings = BdApi.Utils.extend({}, this.defaultSettings); + } + const currentVersionInfo = BdApi.Data.load(this.meta.name, "version"); + if (currentVersionInfo !== this.meta.version) { + this.#showChangelog(); + BdApi.Data.save(this.meta.name, "version", this.meta.version); + } + if (this.manifest.strings) this.LocaleManager = BdApi.Webpack.getByKeys("locale", "initialize"); + if (this.manifest.config && !this.getSettingsPanel) { + this.getSettingsPanel = () => { + this.#updateConfig(); + return BdApi.UI.buildSettingsPanel({ + onChange: (_, id, value) => { + this.settings[id] = value; + this.saveSettings(); + }, + settings: this.manifest.config + }); + }; + } + } + async start() { + BdApi.Logger.info(this.meta.name, `version ${this.meta.version} has started.`); + if (this.defaultSettings) this.settings = this.loadSettings(); + if (typeof this.onStart == "function") this.onStart(); + } + stop() { + BdApi.Logger.info(this.meta.name, `version ${this.meta.version} has stopped.`); + if (typeof this.onStop == "function") this.onStop(); + } + #showChangelog() { + if (typeof this.manifest.changelog == "undefined") return; + const changelog = { + title: this.meta.name + " Changelog", + subtitle: `v${this.meta.version}`, + changes: [] + }; + if (!Array.isArray(this.manifest.changelog)) Object.assign(changelog, this.manifest.changelog); + else changelog.changes = this.manifest.changelog; + BdApi.UI.showChangelogModal(changelog); + } + saveSettings() { + BdApi.Data.save(this.meta.name, "settings", this.settings); + } + loadSettings() { + return BdApi.Utils.extend({}, this.defaultSettings ?? {}, BdApi.Data.load(this.meta.name, "settings")); + } + #updateConfig() { + if (!this.manifest.config) return; + for (const setting of this.manifest.config) { + if (setting.type !== "category") { + setting.value = this.settings[setting.id] ?? setting.value; + } else { + for (const subsetting of setting.settings) { + subsetting.value = this.settings[subsetting.id] ?? subsetting.value; + } + } + } + } + buildSettingsPanel(onChange) { + this.#updateConfig(); + return BdApi.UI.buildSettingsPanel({ + onChange: (groupId, id, value) => { + this.settings[id] = value; + onChange?.(groupId, id, value); + this.saveSettings(); + }, + settings: this.manifest.config + }); + } +}; + +// src/common/formatstring.ts +function formatString(stringToFormat, ...replacements) { + for (let v = 0; v < replacements.length; v++) { + const values = replacements[v]; + for (const val in values) { + let replacement = values[val]; + if (Array.isArray(replacement)) replacement = JSON.stringify(replacement); + if (typeof replacement === "object" && replacement !== null) replacement = replacement.toString(); + stringToFormat = stringToFormat.replace(new RegExp(`{{${val}}}`, "g"), replacement.toString()); + } + } + return stringToFormat; +} + +// src/plugins/PermissionsViewer/config.ts +var manifest = { + info: { + name: "PermissionsViewer", + authors: [{ + name: "Zerebos", + discord_id: "249746236008169473", + github_username: "zerebos", + twitter_username: "IAmZerebos" + }], + version: "0.3.2", + description: "Allows you to view a user's permissions. Thanks to Noodlebox for the idea!", + github: "https://github.com/zerebos/BetterDiscordAddons/tree/master/Plugins/PermissionsViewer", + github_raw: "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/master/Plugins/PermissionsViewer/PermissionsViewer.plugin.js" + }, + changelog: [ + { + title: "Fixed Modal", + type: "fixed", + items: [ + "Fixed the permissions modal not having a backdrop for some users." + ] + } + ], + config: [ + { + type: "switch", + id: "contextMenus", + name: "Context Menus", + value: true + }, + { + type: "switch", + id: "popouts", + name: "Popouts", + value: true + }, + { + type: "radio", + id: "displayMode", + name: "Modal Display Mode", + value: "compact", + options: [ + { name: "Cozy", value: "cozy" }, + { name: "Compact", value: "compact" } + ] + } + ], + strings: { + es: { + contextMenuLabel: "Permisos", + popoutLabel: "Permisos", + modal: { + header: "Permisos de {{name}}", + rolesLabel: "Roles", + permissionsLabel: "Permisos", + owner: "@propietario" + }, + settings: { + popouts: { + name: "Mostrar en Popouts", + note: "Mostrar los permisos de usuario en popouts como los roles." + }, + contextMenus: { + name: "Bot\xF3n de men\xFA contextual", + note: "A\xF1adir un bot\xF3n para ver permisos en los men\xFAs contextuales." + } + } + }, + pt: { + contextMenuLabel: "Permiss\xF5es", + popoutLabel: "Permiss\xF5es", + modal: { + header: "Permiss\xF5es de {{name}}", + rolesLabel: "Cargos", + permissionsLabel: "Permiss\xF5es", + owner: "@dono" + }, + settings: { + popouts: { + name: "Mostrar em Popouts", + note: "Mostrar as permiss\xF5es em popouts como os cargos." + }, + contextMenus: { + name: "Bot\xE3o do menu de contexto", + note: "Adicionar um bot\xE3o parar ver permiss\xF5es ao menu de contexto." + } + } + }, + de: { + contextMenuLabel: "Berechtigungen", + popoutLabel: "Berechtigungen", + modal: { + header: "{{name}}s Berechtigungen", + rolesLabel: "Rollen", + permissionsLabel: "Berechtigungen", + owner: "@eigent\xFCmer" + }, + settings: { + popouts: { + name: "In Popouts anzeigen", + note: "Zeigt die Gesamtberechtigungen eines Benutzers in seinem Popup \xE4hnlich den Rollen an." + }, + contextMenus: { + name: "Kontextmen\xFC-Schaltfl\xE4che", + note: "F\xFCgt eine Schaltfl\xE4che hinzu, um die Berechtigungen mithilfe von Kontextmen\xFCs anzuzeigen." + } + } + }, + en: { + contextMenuLabel: "Permissions", + popoutLabel: "Permissions", + modal: { + header: "{{name}}'s Permissions", + rolesLabel: "Roles", + permissionsLabel: "Permissions", + owner: "@owner" + }, + settings: { + popouts: { + name: "Show In Popouts", + note: "Shows a user's total permissions in their popout similar to roles." + }, + contextMenus: { + name: "Context Menu Button", + note: "Adds a button to view the permissions modal to select context menus." + }, + displayMode: { + name: "Modal Display Mode" + } + } + }, + ru: { + contextMenuLabel: "\u041F\u043E\u043B\u043D\u043E\u043C\u043E\u0447\u0438\u044F", + popoutLabel: "\u041F\u043E\u043B\u043D\u043E\u043C\u043E\u0447\u0438\u044F", + modal: { + header: "\u041F\u043E\u043B\u043D\u043E\u043C\u043E\u0447\u0438\u044F {{name}}", + rolesLabel: "\u0420\u043E\u043B\u0438", + permissionsLabel: "\u041F\u043E\u043B\u043D\u043E\u043C\u043E\u0447\u0438\u044F", + owner: "\u0412\u043B\u0430\u0434\u0435\u043B\u0435\u0446" + }, + settings: { + popouts: { + name: "\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0432\u043E \u0432\u0441\u043F\u043B\u044B\u0432\u0430\u044E\u0449\u0438\u0445 \u043E\u043A\u043D\u0430\u0445", + note: "\u041E\u0442\u043E\u0431\u0440\u0430\u0436\u0430\u0435\u0442 \u043F\u043E\u043B\u043D\u043E\u043C\u043E\u0447\u0438\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F \u0432 \u0438\u0445 \u0432\u0441\u043F\u043B\u044B\u0432\u0430\u044E\u0449\u0435\u043C \u043E\u043A\u043D\u0435, \u0430\u043D\u0430\u043B\u043E\u0433\u0438\u0447\u043D\u043E\u043C \u0440\u043E\u043B\u044F\u043C." + }, + contextMenus: { + name: "\u041A\u043D\u043E\u043F\u043A\u0430 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u043D\u043E\u0433\u043E \u043C\u0435\u043D\u044E", + note: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043A\u043D\u043E\u043F\u043A\u0443 \u0434\u043B\u044F \u043E\u0442\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F \u043F\u043E\u043B\u043D\u043E\u043C\u043E\u0447\u0438\u0439 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u043D\u044B\u0445 \u043C\u0435\u043D\u044E." + } + } + }, + nl: { + contextMenuLabel: "Permissies", + popoutLabel: "Permissies", + modal: { + header: "{{name}}'s Permissies", + rolesLabel: "Rollen", + permissionsLabel: "Permissies", + owner: "@eigenaar" + }, + settings: { + popouts: { + name: "Toon in Popouts", + note: "Toont de totale rechten van een gebruiker in zijn pop-out, vergelijkbaar met rollen." + }, + contextMenus: { + name: "Contextmenuknop", + note: "Voegt een knop toe om de machtigingsmodaliteit voor het selecteren van contextmenu's te bekijken." + }, + displayMode: { + name: "Modal weergavemodus" + } + } + } + }, + main: "index.ts" +}; +var config_default = manifest; + +// src/plugins/PermissionsViewer/styles.css +var styles_default = ".perm-user-avatar {\n border-radius: 50%;\n width: 16px;\n height: 16px;\n margin-right: 3px;\n}\n\n.member-perms-header {\n color: var(--header-secondary);\n display: flex;\n justify-content: space-between;\n}\n\n.member-perms {\n display: flex;\n flex-wrap: wrap;\n margin-top: 2px;\n max-height: 160px;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.member-perms .member-perm .perm-circle {\n border-radius: 50%;\n height: 12px;\n margin: 0 8px 0 5px;\n width: 12px;\n}\n\n.member-perms .member-perm .name {\n margin-right: 4px;\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.perm-details-button {\n cursor: pointer;\n height: 12px;\n}\n\n.perm-details {\n display: flex;\n justify-content: flex-end;\n}\n\n.member-perm-details {\n cursor: pointer;\n}\n\n.member-perm-details-button {\n fill: #72767d;\n height: 10px;\n}\n\n/* Modal */\n\n@keyframes permissions-backdrop {\n to { opacity: 0.85; }\n}\n\n@keyframes permissions-modal-wrapper {\n to { transform: scale(1); opacity: 1; }\n}\n\n@keyframes permissions-backdrop-closing {\n to { opacity: 0; }\n}\n\n@keyframes permissions-modal-wrapper-closing {\n to { transform: scale(0.7); opacity: 0; }\n}\n\n#permissions-modal-wrapper {\n z-index: 100;\n}\n\n#permissions-modal-wrapper .callout-backdrop {\n animation: permissions-backdrop 250ms ease;\n animation-fill-mode: forwards;\n opacity: 0;\n background-color: rgb(0, 0, 0);\n transform: translateZ(0px);\n}\n\n#permissions-modal-wrapper.closing .callout-backdrop {\n animation: permissions-backdrop-closing 200ms linear;\n animation-fill-mode: forwards;\n animation-delay: 50ms;\n opacity: 0.85;\n}\n\n#permissions-modal-wrapper.closing .modal-wrapper {\n animation: permissions-modal-wrapper-closing 250ms cubic-bezier(0.19, 1, 0.22, 1);\n animation-fill-mode: forwards;\n opacity: 1;\n transform: scale(1);\n}\n\n#permissions-modal-wrapper .modal-wrapper {\n animation: permissions-modal-wrapper 250ms cubic-bezier(0.175, 0.885, 0.32, 1.275);\n animation-fill-mode: forwards;\n transform: scale(0.7);\n transform-origin: 50% 50%;\n display: flex;\n align-items: center;\n box-sizing: border-box;\n contain: content;\n justify-content: center;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n opacity: 0;\n pointer-events: none;\n position: absolute;\n user-select: none;\n z-index: 1000;\n}\n\n#permissions-modal-wrapper .modal-body {\n background-color: #36393f;\n height: 440px;\n width: auto;\n /*box-shadow: 0 0 0 1px rgba(32,34,37,.6), 0 2px 10px 0 rgba(0,0,0,.2);*/\n flex-direction: row;\n overflow: hidden;\n display: flex;\n flex: 1;\n contain: layout;\n position: relative;\n}\n\n#permissions-modal-wrapper #permissions-modal {\n contain: layout;\n flex-direction: column;\n pointer-events: auto;\n border: 1px solid rgba(28,36,43,.6);\n border-radius: 5px;\n box-shadow: 0 2px 10px 0 rgba(0,0,0,.2);\n overflow: hidden;\n}\n\n#permissions-modal-wrapper .header {\n background-color: #35393e;\n box-shadow: 0 2px 3px 0 rgba(0,0,0,.2);\n padding: 12px 20px;\n z-index: 1;\n color: #fff;\n font-size: 16px;\n font-weight: 700;\n line-height: 19px;\n}\n\n.role-side, .perm-side {\n flex-direction: column;\n padding-left: 6px;\n}\n\n.role-scroller, .perm-scroller {\n contain: layout;\n flex: 1;\n min-height: 1px;\n overflow-y: scroll;\n}\n\n#permissions-modal-wrapper .scroller-title {\n color: #fff;\n padding: 8px 0 4px 4px;\n margin-right: 8px;\n border-bottom: 1px solid rgba(0,0,0,0.3);\n display: none;\n}\n\n#permissions-modal-wrapper .role-side {\n width: auto;\n min-width: 150px;\n background: #2f3136;\n flex: 0 0 auto;\n overflow: hidden;\n display: flex;\n min-height: 1px;\n position: relative;\n}\n\n#permissions-modal-wrapper .role-scroller {\n contain: layout;\n flex: 1;\n min-height: 1px;\n overflow-y: scroll;\n padding-top: 8px;\n}\n\n#permissions-modal-wrapper .role-item {\n display: flex;\n border-radius: 2px;\n padding: 6px;\n margin-bottom: 5px;\n cursor: pointer;\n color: #dcddde;\n}\n\n#permissions-modal-wrapper .role-item:hover {\n background-color: rgba(0,0,0,0.1);\n}\n\n#permissions-modal-wrapper .role-item.selected {\n background-color: rgba(0,0,0,0.2);\n}\n\n#permissions-modal-wrapper .perm-side {\n width: 273px;\n background-color: #36393f;\n flex: 0 0 auto;\n display: flex;\n min-height: 1px;\n position: relative;\n padding-left: 10px;\n}\n\n#permissions-modal-wrapper .perm-item {\n box-shadow: inset 0 -1px 0 rgba(79,84,92,.3);\n box-sizing: border-box;\n height: 44px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex-direction: row;\n justify-content: flex-start;\n align-items: center;\n display: flex;\n}\n\n#permissions-modal-wrapper .perm-item.allowed svg {\n fill: #43B581;\n}\n\n#permissions-modal-wrapper .perm-item.denied svg {\n fill: #F04747;\n}\n\n#permissions-modal-wrapper .perm-name {\n display: inline;\n flex: 1;\n font-size: 16px;\n font-weight: 400;\n overflow: hidden;\n text-overflow: ellipsis;\n user-select: text;\n color: #dcddde;\n margin-left: 10px;\n}\n\n\n.member-perms::-webkit-scrollbar-thumb, .member-perms::-webkit-scrollbar-track,\n#permissions-modal-wrapper *::-webkit-scrollbar-thumb, #permissions-modal-wrapper *::-webkit-scrollbar-track {\n background-clip: padding-box;\n border-radius: 7.5px;\n border-style: solid;\n border-width: 3px;\n visibility: hidden;\n}\n\n.member-perms:hover::-webkit-scrollbar-thumb, .member-perms:hover::-webkit-scrollbar-track,\n#permissions-modal-wrapper *:hover::-webkit-scrollbar-thumb, #permissions-modal-wrapper *:hover::-webkit-scrollbar-track {\n visibility: visible;\n}\n\n.member-perms::-webkit-scrollbar-track,\n#permissions-modal-wrapper *::-webkit-scrollbar-track {\n border-width: initial;\n background-color: transparent;\n border: 2px solid transparent;\n}\n\n.member-perms::-webkit-scrollbar-thumb,\n#permissions-modal-wrapper *::-webkit-scrollbar-thumb {\n border: 2px solid transparent;\n border-radius: 4px;\n cursor: move;\n background-color: rgba(32,34,37,.6);\n}\n\n.member-perms::-webkit-scrollbar,\n#permissions-modal-wrapper *::-webkit-scrollbar {\n height: 8px;\n width: 8px;\n}\n\n\n\n.theme-light #permissions-modal-wrapper #permissions-modal {\n background: #fff;\n}\n\n.theme-light #permissions-modal-wrapper .modal-body {\n background: transparent;\n}\n\n.theme-light #permissions-modal-wrapper .header {\n background: transparent;\n color: #000;\n}\n\n.theme-light #permissions-modal-wrapper .role-side {\n background: rgba(0,0,0,.2);\n}\n\n.theme-light #permissions-modal-wrapper .perm-side {\n background: rgba(0,0,0,.1);\n}\n\n.theme-light #permissions-modal-wrapper .role-item,\n.theme-light #permissions-modal-wrapper .perm-name {\n color: #000;\n}\n\n#permissions-modal-wrapper #permissions-modal {\n width: auto;\n}"; + +// src/plugins/PermissionsViewer/jumbo.css +var jumbo_default = "#permissions-modal-wrapper #permissions-modal {\n height: 840px;\n}\n\n#permissions-modal-wrapper #permissions-modal .perm-side {\n width: 500px;\n}\n\n#permissions-modal .perm-scroller {\n display: flex;\n flex-wrap: wrap;\n align-content: flex-start;\n}\n\n#permissions-modal .perm-item {\n width: 50%;\n}"; + +// src/plugins/PermissionsViewer/list.html +var list_default = '
\n

\n
{{sectionTitle}}
\n \n \n \n \n \n \n

\n
\n
'; + +// src/plugins/PermissionsViewer/item.html +var item_default = '
\n
\n
\n
'; + +// src/plugins/PermissionsViewer/modal.html +var modal_default = '
\n
\n \n
'; + +// src/plugins/PermissionsViewer/modalitem.html +var modalitem_default = '
'; + +// src/plugins/PermissionsViewer/modalbutton.html +var modalbutton_default = '
'; + +// src/plugins/PermissionsViewer/modalbuttonuser.html +var modalbuttonuser_default = `
`; + +// src/plugins/PermissionsViewer/permallowed.svg +var permallowed_default = ''; + +// src/plugins/PermissionsViewer/permdenied.svg +var permdenied_default = ''; + +// src/common/colors.ts +function getRGB(color) { + let result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color); + if (result) return [parseInt(result[1]), parseInt(result[2]), parseInt(result[3])]; + result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*\)/.exec(color); + if (result) return [parseFloat(result[1]) * 2.55, parseFloat(result[2]) * 2.55, parseFloat(result[3]) * 2.55]; + result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color); + if (result) return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]; + result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color); + if (result) return [parseInt(result[1] + result[1], 16), parseInt(result[2] + result[2], 16), parseInt(result[3] + result[3], 16)]; +} +function rgbToAlpha(color, alpha) { + const rgb = getRGB(color); + if (!rgb) return color; + return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + alpha + ")"; +} + +// src/plugins/PermissionsViewer/index.ts +var { ContextMenu, DOM, Utils, Webpack, UI, ReactUtils } = BdApi; +var GuildStore = Webpack.getStore("GuildStore"); +var SelectedGuildStore = Webpack.getStore("SelectedGuildStore"); +var GuildRoleStore = Webpack.getStore("GuildRoleStore"); +var MemberStore = Webpack.getStore("GuildMemberStore"); +var UserStore = Webpack.getStore("UserStore"); +var DiscordPermissions = Webpack.getModule((m) => m.ADD_REACTIONS, { searchExports: true }); +var AvatarDefaults = Webpack.getByKeys("DEFAULT_AVATARS") ?? { DEFAULT_AVATARS: ["/assets/a0180771ce23344c2a95.png", "/assets/ca24969f2fd7a9fb03d5.png", "/assets/974be2a933143742e8b1.png", "/assets/999edf6459b7dacdcadf.png", "/assets/887bc8fac6c9878f058a.png", "/assets/1256b1e634d7274dd430.png"] }; +var ElectronModule = BdApi.Webpack.getByKeys("setBadge"); +var intlModule = BdApi.Webpack.getByKeys("intl"); +var getRoles = (guild) => guild?.roles ?? GuildRoleStore?.getRolesSnapshot(guild?.id); +var getHashString = (hash) => intlModule?.intl.string(hash); +var getPermString = (perm) => intlModule?.intl.string(intlModule.t[PermissionStringMap[perm]]) ?? perm.toString(); +var PermissionStringMap = { + ADD_REACTIONS: "yEoJAg", + ADMINISTRATOR: "dwlcc3", + ATTACH_FILES: "3AS4UF", + BAN_MEMBERS: "2a50fH", + CHANGE_NICKNAME: "ieWVpK", + CONNECT: "S0W8Z2", + CREATE_EVENTS: "qyjZub", + CREATE_GUILD_EXPRESSIONS: "HarVuL", + CREATE_INSTANT_INVITE: "0BNJdX", + CREATE_PRIVATE_THREADS: "QwbTSU", + CREATE_PUBLIC_THREADS: "25rKnZ", + DEAFEN_MEMBERS: "9L47Fh", + EMBED_LINKS: "969dEB", + KICK_MEMBERS: "pBNv6u", + MANAGE_CHANNELS: "9qLtWl", + MANAGE_EVENTS: "HIgA5e", + MANAGE_GUILD_EXPRESSIONS: "bbuXIi", + MANAGE_MESSAGES: "ZGbTc3", + MANAGE_NICKNAMES: "t+Ct5+", + MANAGE_ROLES: "C8d+oK", + MANAGE_GUILD: "QZRcfH", + MANAGE_THREADS: "kEqgr6", + MANAGE_WEBHOOKS: "/ADKmJ", + MENTION_EVERYONE: "Y78KGB", + MODERATE_MEMBERS: "7DgVBg", + MOVE_MEMBERS: "YtjJPT", + MUTE_MEMBERS: "8EI309", + PRIORITY_SPEAKER: "BVK71t", + READ_MESSAGE_HISTORY: "l9ufaW", + REQUEST_TO_SPEAK: "hLbG5O", + SEND_MESSAGES: "T32rkJ", + SEND_MESSAGES_IN_THREADS: "fTE74u", + SEND_POLLS: "UMQ7W1", + SEND_TTS_MESSAGES: "Mg7bkp", + SEND_VOICE_MESSAGES: "WlWSBQ", + SET_VOICE_CHANNEL_STATUS: "VBwkUV", + SPEAK: "8w1tIS", + STREAM: "UPvOiY", + USE_APPLICATION_COMMANDS: "shbR1d", + USE_CLYDE_AI: "8eeEZm", + USE_EMBEDDED_ACTIVITIES: "rLSGen", + USE_EXTERNAL_APPS: "TtA5rK", + USE_EXTERNAL_EMOJIS: "BpBGZW", + USE_EXTERNAL_SOUNDS: "pwaVJy", + USE_EXTERNAL_STICKERS: "ERNhYW", + USE_SOUNDBOARD: "Bco7ND", + USE_VAD: "08zAV1", + VIEW_AUDIT_LOG: "fZgLpK", + VIEW_CHANNEL: "W/A4Qk", + VIEW_CREATOR_MONETIZATION_ANALYTICS: "0lTLTk", + VIEW_GUILD_ANALYTICS: "rQJBEx" +}; +function isOverwriteEmpty(overwrite) { + return !overwrite.allow && !overwrite.deny && overwrite.type == 0; +} +function hasOverwrites(channel) { + const roleIds = Object.keys(channel.permissionOverwrites); + if (roleIds.length === 0) return false; + if (roleIds.length === 1 && isOverwriteEmpty(channel.permissionOverwrites[roleIds[0]])) return false; + return true; +} +var PermissionsViewer = class extends Plugin { + constructor(meta) { + super(meta, config_default); + } + sectionHTML; + itemHTML; + modalHTML; + contextMenuPatches = []; + onStart() { + DOM.addStyle(this.meta.name, styles_default); + const ModalClasses = Webpack.getByKeys("root", "header", "small"); + const PopoutRoleClasses = Webpack.getByKeys("roleCircle"); + const EyebrowClasses = Webpack.getByKeys("defaultColor", "eyebrow"); + const UserPopoutClasses = Object.assign( + { section: "section_ba4d80", heading: "heading_ba4d80", root: "root_c83b44" }, + Webpack.getByKeys("userPopoutOuter"), + EyebrowClasses, + PopoutRoleClasses, + Webpack.getByKeys("root", "expandButton"), + Webpack.getModule((m) => m?.heading && m?.section && Object.keys(m)?.length === 2) + ); + const RoleClasses = Object.assign({}, PopoutRoleClasses, EyebrowClasses, Webpack.getByKeys("role", "roleName", "roleCircle")); + const BackdropClasses = Webpack.getByKeys("backdrop"); + this.sectionHTML = formatString(list_default, RoleClasses, UserPopoutClasses); + this.itemHTML = formatString(item_default, RoleClasses); + this.modalHTML = formatString(modal_default, BackdropClasses?.backdrop ? { backdrop: BackdropClasses.backdrop } : {}, { root: ModalClasses?.root ?? "root_f9a4c9", small: ModalClasses?.small ?? "small_f9a4c9" }); + if (this.settings.popouts) this.bindPopouts(); + if (this.settings.contextMenus) this.bindContextMenus(); + this.setDisplayMode(this.settings.displayMode); + } + onStop() { + DOM.removeStyle(this.meta.name); + this.unbindPopouts(); + this.unbindContextMenus(); + } + setDisplayMode(mode) { + if (mode === "cozy") DOM.addStyle(this.meta.name + "-jumbo", jumbo_default); + else DOM.removeStyle(this.meta.name + "-jumbo"); + } + patchPopouts(e) { + const popoutMount = (props2) => { + if (!props2 || !props2.displayProfile || !props2.user) return; + const popout2 = document.querySelector(`[class*="userPopout_"], [class*="outer_"]`); + if (!popout2 || popout2.querySelector("#permissions-popout")) return; + const user = MemberStore?.getMember(props2.displayProfile.guildId, props2.user.id); + const guild = GuildStore?.getGuild(props2.displayProfile.guildId); + const name = MemberStore?.getNick(props2.displayProfile.guildId, props2.user.id) ?? props2.user.username; + if (!user || !guild || !name) return; + const userRoles = user.roles.slice(0); + userRoles.push(guild.id); + userRoles.reverse(); + let perms = 0n; + const permBlock = DOM.parseHTML(formatString(this.sectionHTML, { sectionTitle: this.strings.popoutLabel })); + const memberPerms = permBlock.querySelector(".member-perms"); + const referenceRoles = getRoles(guild); + if (!referenceRoles) return; + for (let r = 0; r < userRoles.length; r++) { + const role = userRoles[r]; + if (!referenceRoles[role]) continue; + perms = perms | referenceRoles[role].permissions; + for (const perm in DiscordPermissions) { + const permName = getPermString(perm) || perm.split("_").map((n) => n[0].toUpperCase() + n.slice(1).toLowerCase()).join(" "); + const hasPerm = (perms & DiscordPermissions[perm]) == DiscordPermissions[perm]; + if (hasPerm && !memberPerms.querySelector(`[data-name="${permName}"]`)) { + const element2 = DOM.parseHTML(this.itemHTML); + let roleColor = referenceRoles[role].colorString; + element2.querySelector(".name").textContent = permName; + element2.setAttribute("data-name", permName); + if (!roleColor) roleColor = "#B9BBBE"; + element2.querySelector(".perm-circle").style.backgroundColor = rgbToAlpha(roleColor, 1); + memberPerms.prepend(element2); + } + } + } + permBlock.querySelector(".perm-details")?.addEventListener("click", () => { + popoutInstance?.props?.targetRef?.current?.click(); + document.querySelector(`[class*="backdrop__"]`)?.click(); + this.showModal(this.createModalUser(name, user, guild)); + }); + let roleList = popout2.querySelector(`[class*="root_"]`); + if (roleList?.parentElement?.className.includes("section")) roleList = roleList.parentElement; + roleList?.after(permBlock); + const popoutInstance = Utils.findInTree( + ReactUtils.getInternalInstance(popout2), + (m) => m && m.updatePosition, + { walkable: ["stateNode", "return"] } + ); + if (!popoutInstance || !popoutInstance.updatePosition) return; + popoutInstance.updatePosition(); + }; + if (!e.addedNodes.length || !(e.addedNodes[0] instanceof Element)) return; + const element = e.addedNodes[0]; + const popout = element.querySelector(`[class*="userPopout_"], [class*="outer_"]`) ?? element; + if (!popout || !popout.matches(`[class*="userPopout_"], [class*="outer_"]`)) return; + const props = Utils.findInTree(ReactUtils.getInternalInstance(popout), (m) => m && m.user, { walkable: ["memoizedProps", "return"] }); + popoutMount(props); + } + bindPopouts() { + this.observer = this.patchPopouts.bind(this); + } + unbindPopouts() { + this.observer = void 0; + } + async bindContextMenus() { + this.patchChannelContextMenu(); + this.patchGuildContextMenu(); + this.patchUserContextMenu(); + } + unbindContextMenus() { + for (const cancel of this.contextMenuPatches) cancel(); + } + patchGuildContextMenu() { + this.contextMenuPatches.push(ContextMenu.patch("guild-context", (retVal, props) => { + if (!props?.guild) return retVal; + const newItem = ContextMenu.buildItem({ + label: this.strings.contextMenuLabel, + action: () => { + this.showModal(this.createModalGuild(props.guild.name, props.guild)); + } + }); + retVal.props.children?.splice(1, 0, newItem); + })); + } + patchChannelContextMenu() { + this.contextMenuPatches.push(ContextMenu.patch("channel-context", (retVal, props) => { + const newItem = ContextMenu.buildItem({ + label: this.strings.contextMenuLabel, + action: () => { + if (!hasOverwrites(props.channel)) return UI.showToast(`#${props.channel.name} has no permission overrides`, { type: "info" }); + this.showModal(this.createModalChannel(props.channel.name, props.channel, props.guild)); + } + }); + retVal.props.children?.splice(1, 0, newItem); + })); + } + patchUserContextMenu() { + this.contextMenuPatches.push(ContextMenu.patch("user-context", (retVal, props) => { + const guild = GuildStore?.getGuild(props.guildId); + if (!guild) return; + const newItem = ContextMenu.buildItem({ + label: this.strings.contextMenuLabel, + action: () => { + const user = MemberStore?.getMember(props.guildId, props.user.id); + if (!user) return; + const name = user.nick ? user.nick : props.user.username; + this.showModal(this.createModalUser(name, user, guild)); + } + }); + retVal?.props?.children?.[0]?.props?.children?.splice(2, 0, newItem); + })); + } + showModal(modal) { + const popout = document.querySelector(`[class*="userPopoutOuter-"]`); + if (popout) popout.style.display = "none"; + const app = document.querySelector(".app-19_DXt"); + if (app) app.append(modal); + else document.querySelector("#app-mount")?.append(modal); + const closeModal = (event) => { + if (event.key !== "Escape") return; + modal.classList.add("closing"); + setTimeout(() => { + modal.remove(); + }, 300); + }; + document.addEventListener("keydown", closeModal, true); + DOM.onRemoved(modal, () => document.removeEventListener("keydown", closeModal, true)); + } + createModalChannel(name, channel, guild) { + return this.createModal(`#${name}`, channel.permissionOverwrites, getRoles(guild), true); + } + createModalUser(name, user, guild) { + const guildRoles = Object.assign({}, getRoles(guild)); + const userRoles = user.roles.slice(0).filter((r) => typeof guildRoles[r] !== "undefined"); + userRoles.push(guild.id); + userRoles.sort((a, b) => { + return guildRoles[b].position - guildRoles[a].position; + }); + if (user.userId == guild.ownerId) { + const ALL_PERMISSIONS = Object.values(DiscordPermissions).reduce((all, p) => all | p); + userRoles.push(user.userId); + guildRoles[user.userId] = { name: this.strings.modal.owner ?? "", permissions: ALL_PERMISSIONS }; + } + return this.createModal(name, userRoles, guildRoles); + } + createModalGuild(name, guild) { + return this.createModal(name, getRoles(guild)); + } + createModal(title, displayRoles, referenceRoles, isOverride) { + if (!referenceRoles) referenceRoles = displayRoles; + const modal = DOM.parseHTML(formatString(formatString(this.modalHTML, this.strings.modal), { name: Utils.escapeHTML(title) })); + const closeModal = () => { + modal.classList.add("closing"); + setTimeout(() => { + modal.remove(); + }, 300); + }; + modal.querySelector(".callout-backdrop")?.addEventListener("click", closeModal); + for (const r in displayRoles) { + const role = Array.isArray(displayRoles) ? displayRoles[r] : r; + const user = UserStore?.getUser(role) || { getAvatarURL: () => AvatarDefaults.DEFAULT_AVATARS[Math.floor(Math.random() * AvatarDefaults.DEFAULT_AVATARS.length)], username: role }; + const member = MemberStore?.getMember(SelectedGuildStore?.getGuildId() ?? "", role) || { colorString: "" }; + const item = DOM.parseHTML(!isOverride || displayRoles[role].type == 0 ? modalbutton_default : formatString(modalbuttonuser_default, { avatarUrl: user.getAvatarURL(null, 16, true) })); + if (!isOverride || displayRoles[role].type == 0) item.style.color = referenceRoles[role].colorString; + else item.style.color = member.colorString; + if (isOverride) item.querySelector(".role-name").innerHTML = Utils.escapeHTML(displayRoles[role].type == 0 ? referenceRoles[role].name : user.username); + else item.querySelector(".role-name").innerHTML = Utils.escapeHTML(referenceRoles[role].name); + modal.querySelector(".role-scroller").append(item); + item.addEventListener("click", () => { + modal.querySelectorAll(".role-item.selected").forEach((e) => e.classList.remove("selected")); + item.classList.add("selected"); + const allowed = isOverride ? displayRoles[role].allow : referenceRoles[role].permissions; + const denied = isOverride ? displayRoles[role].deny : null; + const permList = modal.querySelector(".perm-scroller"); + permList.innerHTML = ""; + for (const perm in DiscordPermissions) { + const element = DOM.parseHTML(modalitem_default); + const permAllowed = (allowed & DiscordPermissions[perm]) == DiscordPermissions[perm]; + const permDenied = isOverride ? (denied & DiscordPermissions[perm]) == DiscordPermissions[perm] : !permAllowed; + if (!permAllowed && !permDenied) continue; + if (permAllowed) { + element.classList.add("allowed"); + element.prepend(DOM.parseHTML(permallowed_default)); + } + if (permDenied) { + element.classList.add("denied"); + element.prepend(DOM.parseHTML(permdenied_default)); + } + element.querySelector(".perm-name").textContent = getPermString(perm) || perm.split("_").map((n) => n[0].toUpperCase() + n.slice(1).toLowerCase()).join(" "); + permList.append(element); + } + }); + item.addEventListener("contextmenu", (e) => { + ContextMenu.open(e, ContextMenu.buildMenu([ + { + label: getHashString("rCaznZ") ?? "Copy ID", + action: () => { + ElectronModule?.copy(role); + } + } + ])); + }); + } + modal.querySelector(".role-item")?.click(); + return modal; + } + getSettingsPanel() { + return this.buildSettingsPanel((_, id, checked) => { + if (id == "popouts") { + if (checked) this.bindPopouts(); + else this.unbindPopouts(); + } + if (id == "contextMenus") { + if (checked) this.bindContextMenus(); + this.unbindContextMenus(); + } + if (id == "displayMode") this.setDisplayMode(checked); + }); + } +}; + +/*@end@*/ \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/PluginRepo.config.json b/dotfiles/.config/BetterDiscord/plugins/PluginRepo.config.json new file mode 100755 index 0000000..9515ba4 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/PluginRepo.config.json @@ -0,0 +1,16 @@ +{ + "all": { + "cached": "2 7 8 9 11 14 15 17 28 29 30 59 60 61 62 63 64 65 66 67 70 71 73 74 75 76 77 80 81 82 83 85 86 87 88 89 91 92 93 94 95 96 97 98 99 100 101 102 104 105 106 108 109 110 111 120 126 127 131 132 138 139 146 157 158 159 160 162 171 176 179 181 182 183 184 186 188 189 190 193 195 196 197 228 237 240 245 262 274 278 287 291 292 293 295 306 312 314 317 318 323 331 344 350 352 353 354 356 366 368 377 379 381 382 383 390 395 401 420 421 429 442 476 479 489 518 520 523 525 535 539 547 554 577 579 589 592 593 598 599 606 608 611 614 620 638 644 645 670 671 679 686 692 693 694 699 708 760 762 772 798 802 805 806 807 819 827 843 850 856 859 867 878 881 882 883 930 936 938 953 954 970 971 979 989 996 1000 1005 1010 1016 1023 1040 1041 1044 1048 1049 1058 1063 1066 1067 1068 1069 1070 1074 1075 1077 1078 1079 1080 1081 1087 1090 1092 1097 1115 1124 1126 1130 1132 1141 1157 1163 1166 1172 1175 1185 1188 1190 1191 1204 1205 1206 1210", + "filters": { + "updated": true, + "outdated": true, + "downloadable": true + }, + "general": { + "notifyOutdated": true, + "notifyNewEntries": true, + "startDownloaded": false, + "startUpdated": false + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/PronounDB.config.json b/dotfiles/.config/BetterDiscord/plugins/PronounDB.config.json new file mode 100644 index 0000000..e4dc85c --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/PronounDB.config.json @@ -0,0 +1,9 @@ +{ + "currentVersionInfo": { + "version": "1.2.1", + "hasShownChangelog": true + }, + "settings": { + "lastVersion": "1.4.0" + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ReadAllNotificationsButton.config.json b/dotfiles/.config/BetterDiscord/plugins/ReadAllNotificationsButton.config.json new file mode 100755 index 0000000..a07dea5 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ReadAllNotificationsButton.config.json @@ -0,0 +1,49 @@ +{ + "all": { + "batch": { + "dms": false, + "guilds": true, + "muted": true + }, + "blacklist": [ + "775772308261830737", + "230708419995107330", + "594829566330273792", + "230708419995107330", + "594829566330273792", + "710203122106105927", + "775772308261830737", + "413603118832680960", + "707267476391723008", + "230708419995107330", + "594829566330273792", + "710203122106105927", + "775772308261830737", + "413603118832680960", + "707267476391723008", + "340262122782982147", + "738872654928281620", + "230708419995107330", + "594829566330273792", + "710203122106105927", + "775772308261830737", + "413603118832680960", + "340262122782982147", + "707267476391723008", + "738872654928281620", + "703597941809741834", + "821835978334535740" + ], + "general": { + "addClearButton": true, + "confirmClear": false + }, + "settings": { + "addClearButton": true, + "confirmClear": false, + "includeGuilds": true, + "includeMuted": false, + "includeDMs": false + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ReadAllNotificationsButton.plugin.js b/dotfiles/.config/BetterDiscord/plugins/ReadAllNotificationsButton.plugin.js new file mode 100755 index 0000000..2421d63 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ReadAllNotificationsButton.plugin.js @@ -0,0 +1,700 @@ +/** + * @name ReadAllNotificationsButton + * @author DevilBro + * @authorId 278543574059057154 + * @version 1.8.5 + * @description Adds a Clear Button to the Server List and the Mentions Popout + * @invite Jx3TjNS + * @donate https://www.paypal.me/MircoWittrien + * @patreon https://www.patreon.com/MircoWittrien + * @website https://mwittrien.github.io/ + * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/ReadAllNotificationsButton/ + * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/ReadAllNotificationsButton/ReadAllNotificationsButton.plugin.js + */ + +module.exports = (_ => { + const changeLog = { + + }; + + return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class { + constructor (meta) {for (let key in meta) this[key] = meta[key];} + getName () {return this.name;} + getAuthor () {return this.author;} + getVersion () {return this.version;} + getDescription () {return `The Library Plugin needed for ${this.name} is missing. Open the Plugin Settings to download it. \n\n${this.description}`;} + + downloadLibrary () { + BdApi.Net.fetch("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js").then(r => { + if (!r || r.status != 200) throw new Error(); + else return r.text(); + }).then(b => { + if (!b) throw new Error(); + else return require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.UI.showToast("Finished downloading BDFDB Library", {type: "success"})); + }).catch(error => { + BdApi.UI.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library"); + }); + } + + load () { + if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []}); + if (!window.BDFDB_Global.downloadModal) { + window.BDFDB_Global.downloadModal = true; + BdApi.UI.showConfirmationModal("Library Missing", `The Library Plugin needed for ${this.name} is missing. Please click "Download Now" to install it.`, { + confirmText: "Download Now", + cancelText: "Cancel", + onCancel: _ => {delete window.BDFDB_Global.downloadModal;}, + onConfirm: _ => { + delete window.BDFDB_Global.downloadModal; + this.downloadLibrary(); + } + }); + } + if (!window.BDFDB_Global.pluginQueue.includes(this.name)) window.BDFDB_Global.pluginQueue.push(this.name); + } + start () {this.load();} + stop () {} + getSettingsPanel () { + let template = document.createElement("template"); + template.innerHTML = `
The Library Plugin needed for ${this.name} is missing.\nPlease click Download Now to install it.
`; + template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary); + return template.content.firstElementChild; + } + } : (([Plugin, BDFDB]) => { + var _this; + var blacklist, clearing; + + const ReadAllButtonComponent = class ReadAllButton extends BdApi.React.Component { + clearClick() { + if (_this.settings.batch.guilds) this.clearGuilds(_this.settings.batch.muted ? this.getGuilds() : this.getUnread()); + if (_this.settings.batch.dms) BDFDB.DMUtils.markAsRead(this.getPingedDMs()); + } + clearGuilds(guildIds) { + BDFDB.GuildUtils.markAsRead(guildIds.filter(id => id && !blacklist.includes(id))); + } + getGuilds() { + return BDFDB.LibraryStores.SortedGuildStore.getFlattenedGuildIds().map(BDFDB.LibraryStores.GuildStore.getGuild).map(g => g.id).filter(n => n); + } + getUnread() { + return this.getGuilds().filter(id => BDFDB.LibraryStores.GuildReadStateStore.hasUnread(id) || BDFDB.LibraryStores.GuildReadStateStore.getMentionCount(id) > 0); + } + getPinged() { + return this.getGuilds().filter(id => BDFDB.LibraryStores.GuildReadStateStore.getMentionCount(id) > 0); + } + getMuted() { + return this.getGuilds().filter(id => BDFDB.LibraryStores.UserGuildSettingsStore.isGuildOrCategoryOrChannelMuted(id)); + } + getPingedDMs() { + return BDFDB.LibraryStores.ChannelStore.getSortedPrivateChannels().map(c => c.id).filter(id => id && BDFDB.LibraryStores.ReadStateStore.getMentionCount(id) > 0); + } + render() { + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCNS.guildouter + BDFDB.disCN._readallnotificationsbuttonframe, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCNS.guildiconwrapper + BDFDB.disCN._readallnotificationsbuttoninner, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCNS.guildiconchildwrapper + BDFDB.disCNS.guildiconchildwrappernohoverbg + BDFDB.disCN._readallnotificationsbuttonbutton, + children: "read all", + onClick: _ => { + if (!_this.settings.general.confirmClear) this.clearClick(); + else BDFDB.ModalUtils.confirm(_this, _this.labels.modal_confirmnotifications, _ => this.clearClick()); + }, + onContextMenu: event => BDFDB.ContextMenuUtils.open(_this, event, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, { + children: [ + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: _this.labels.context_unreadguilds, + id: BDFDB.ContextMenuUtils.createItemId(_this.name, "mark-unread-read"), + action: _ => this.clearGuilds(this.getUnread()) + }), + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: _this.labels.context_pingedguilds, + id: BDFDB.ContextMenuUtils.createItemId(_this.name, "mark-pinged-read"), + action: _ => this.clearGuilds(this.getPinged()) + }), + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: _this.labels.context_mutedguilds, + id: BDFDB.ContextMenuUtils.createItemId(_this.name, "mark-muted-read"), + action: _ => this.clearGuilds(this.getMuted()) + }), + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: _this.labels.context_guilds, + id: BDFDB.ContextMenuUtils.createItemId(_this.name, "mark-all-read"), + action: _ => this.clearGuilds(this.getGuilds()) + }), + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: _this.labels.context_dms, + id: BDFDB.ContextMenuUtils.createItemId(_this.name, "mark-dms-read"), + action: _ => BDFDB.DMUtils.markAsRead(this.getPingedDMs()) + }) + ] + })) + }) + }) + }); + } + }; + + return class ReadAllNotificationsButton extends Plugin { + onLoad () { + _this = this; + + this.defaults = { + general: { + addClearButton: {value: true, description: "Adds a 'Clear Mentions' button to the recent mentions popout"}, + confirmClear: {value: false, description: "Asks for your confirmation before clearing reads"} + }, + batch: { + guilds: {value: true, description: "unread Servers"}, + muted: {value: false, description: "muted unread Servers"}, + dms: {value: false, description: "unread DMs"} + } + }; + + this.modulePatches = { + after: [ + "UnreadDMs", + "InboxHeader" + ] + }; + + this.css = ` + ${BDFDB.dotCN.messagespopouttabbar} { + flex: 1 0 auto; + } + ${BDFDB.dotCN.messagespopoutcontrols} { + display: flex; + } + ${BDFDB.dotCN.messagespopoutcontrols} > * { + margin-left: 10px; + } + ${BDFDB.dotCN._readallnotificationsbuttonframe} { + --guildbar-avatar-size: 48px; + } + ${BDFDB.dotCN._readallnotificationsbuttonframe}:active { + transform: translateY(1px); + } + #app-mount ${BDFDB.dotCN._readallnotificationsbuttonframe}, + #app-mount ${BDFDB.dotCN._readallnotificationsbuttoninner}, + #app-mount ${BDFDB.dotCN._readallnotificationsbuttonbutton} { + height: 24px; + } + ${BDFDB.dotCN._readallnotificationsbuttonbutton} { + border-radius: 4px; + font-size: 12px; + line-height: 1.3; + white-space: nowrap; + cursor: pointer; + } + `; + } + + onStart () { + let loadedBlacklist = BDFDB.DataUtils.load(this, "blacklist"); + this.saveBlacklist(!BDFDB.ArrayUtils.is(loadedBlacklist) ? [] : loadedBlacklist); + + this.forceUpdateAll(); + } + + onStop () { + this.forceUpdateAll(); + } + + getSettingsPanel (collapseStates = {}) { + let settingsPanel, settingsItems = []; + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "Settings", + collapseStates: collapseStates, + children: Object.keys(this.defaults.general).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Switch", + plugin: this, + keys: ["general", key], + label: this.defaults.general[key].description, + value: this.settings.general[key] + })).concat(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, { + title: "When left clicking the 'read all' Button mark following Elements as read:", + first: false, + last: true, + children: Object.keys(this.defaults.batch).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Switch", + plugin: this, + keys: ["batch", key], + label: this.defaults.batch[key].description, + value: this.settings.batch[key] + })) + })) + })); + + let listInstance = null, batchSetGuilds = value => { + if (!value) { + for (let id of BDFDB.LibraryStores.SortedGuildStore.getFlattenedGuildIds()) blacklist.push(id); + blacklist = BDFDB.ArrayUtils.removeCopies(blacklist); + } + else blacklist = []; + this.saveBlacklist(blacklist); + if (listInstance) { + listInstance.props.disabled = blacklist; + BDFDB.ReactUtils.forceUpdate(listInstance); + } + }; + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "Server Black List", + collapseStates: collapseStates, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsGuildList, { + className: BDFDB.disCN.marginbottom20, + disabled: BDFDB.DataUtils.load(this, "blacklist"), + onClick: disabledGuilds => this.saveBlacklist(disabledGuilds), + ref: instance => {listInstance = instance;} + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Button", + color: BDFDB.LibraryComponents.Button.Colors.GREEN, + label: "Enable for all Servers", + onClick: _ => batchSetGuilds(true), + children: BDFDB.LanguageUtils.LanguageStrings.ENABLE + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Button", + color: BDFDB.LibraryComponents.Button.Colors.PRIMARY, + label: "Disable for all Servers", + onClick: _ => batchSetGuilds(false), + children: BDFDB.LanguageUtils.LanguageStrings.DISABLE + }) + ] + })); + + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, settingsItems); + } + + onSettingsClosed () { + if (this.SettingsUpdated) { + delete this.SettingsUpdated; + this.forceUpdateAll(); + } + } + + forceUpdateAll () { + BDFDB.DiscordUtils.rerenderAll(); + } + + processUnreadDMs (e) { + e.returnvalue = [e.returnvalue].flat(10); + e.returnvalue.push(BDFDB.ReactUtils.createElement(ReadAllButtonComponent, {})); + } + + processInboxHeader (e) { + if (!this.settings.general.addClearButton || e.instance.props.tab != BDFDB.DiscordConstants.InboxTabs.MENTIONS) return; + let mentionedMessages = BDFDB.LibraryStores.RecentMentionsStore.getMentions(); + if (!mentionedMessages || !mentionedMessages.length) return; + let controls = BDFDB.ReactUtils.findChild(e.returnvalue, {props: [["className", BDFDB.disCN.messagespopoutcontrols]]}); + if (controls) controls.props.children = [ + controls.props.children, + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: `${BDFDB.LanguageUtils.LanguageStrings.CLOSE} (${BDFDB.LanguageUtils.LanguageStrings.ALL})`, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, { + className: BDFDB.disCNS.messagespopoutbutton + BDFDB.disCNS.messagespopoutbuttonsm + BDFDB.disCN.messagespopoutbuttonsecondary, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.messagespopoutbuttonchildrenwrapper, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.messagespopoutbuttonchildren, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + nativeClass: true, + name: BDFDB.LibraryComponents.SvgIcon.Names.CLOSE, + width: 16, + height: 16 + }) + }) + }), + onClick: _ => { + let clear = _ => { + if (clearing) return BDFDB.NotificationUtils.toast(`${this.labels.toast_alreadyclearing} - ${BDFDB.LanguageUtils.LibraryStrings.please_wait}`, {type: "danger"}); + let messages = [].concat(mentionedMessages).filter(n => n); + if (messages.length) { + clearing = true; + let toast = BDFDB.NotificationUtils.toast(`${this.labels.toast_clearing} - ${BDFDB.LanguageUtils.LibraryStrings.please_wait}`, {timeout: 0, ellipsis: true}); + for (let i = 0; i < messages.length; i++) BDFDB.TimeUtils.timeout(_ => { + BDFDB.LibraryModules.HTTPUtils.del({ + url: BDFDB.DiscordConstants.Endpoints.MENTIONS_MESSAGE_ID(messages[i].id), + retries: 2, + oldFormErrors: true + }); + if (i == messages.length - 1) { + clearing = false; + toast.close(); + BDFDB.NotificationUtils.toast(this.labels.toastcleared, {type: "success"}); + } + }, i * 1000); + } + }; + if (this.settings.general.confirmClear) BDFDB.ModalUtils.confirm(this, this.labels.modal_confirmmentions, clear); + else clear(); + } + }) + }) + ].flat(10); + } + + saveBlacklist (savedBlacklist) { + blacklist = savedBlacklist; + BDFDB.DataUtils.save(savedBlacklist, this, "blacklist"); + } + + setLabelsByLanguage () { + switch (BDFDB.LanguageUtils.getLanguage().id) { + case "bg": // Bulgarian + return { + context_dms: "Директно съобщение", + context_guilds: "Всички сървъри", + context_mutedguilds: "Приглушени сървъри", + context_pingedguilds: "Pinged сървъри", + context_unreadguilds: "Непрочетени сървъри", + modal_confirmmentions: "Наистина ли искате да изтриете всички непрочетени споменавания?", + modal_confirmnotifications: "Наистина ли искате да изтриете всички непрочетени известия?", + toast_alreadyclearing: "Изтрива някои споменавания вече", + toast_cleared: "Всички последни споменавания бяха изтрити", + toast_clearing: "Изчиства всички скорошни споменавания" + }; + case "da": // Danish + return { + context_dms: "Direkte beskeder", + context_guilds: "Alle servere", + context_mutedguilds: "Dæmpede servere", + context_pingedguilds: "Pingede servere", + context_unreadguilds: "Ulæste servere", + modal_confirmmentions: "Er du sikker på, at du vil slette alle ulæste omtaler?", + modal_confirmnotifications: "Er du sikker på, at du vil slette alle ulæste meddelelser?", + toast_alreadyclearing: "Sletter allerede nogle omtaler", + toast_cleared: "Alle nylige omtaler er blevet slettet", + toast_clearing: "Rydder alle nylige omtaler" + }; + case "de": // German + return { + context_dms: "Direktnachrichten", + context_guilds: "Alle Server", + context_mutedguilds: "Stummgeschaltete Server", + context_pingedguilds: "Gepingte Server", + context_unreadguilds: "Ungelesene Server", + modal_confirmmentions: "Möchten Sie wirklich alle ungelesenen Erwähnungen löschen?", + modal_confirmnotifications: "Möchten Sie wirklich alle ungelesenen Benachrichtigungen löschen?", + toast_alreadyclearing: "Löscht bereits einige Erwähnungen", + toast_cleared: "Alle kürzlich Erwähnungen wurden gelöscht", + toast_clearing: "Löscht alle letzten Erwähnungen" + }; + case "el": // Greek + return { + context_dms: "Αμεσα μηνύματα", + context_guilds: "Όλοι οι διακομιστές", + context_mutedguilds: "Σίγαση διακομιστών", + context_pingedguilds: "Διακομιστές Ping", + context_unreadguilds: "Μη αναγνωσμένοι διακομιστές", + modal_confirmmentions: "Είστε βέβαιοι ότι θέλετε να διαγράψετε όλες τις μη αναγνωσμένες αναφορές;", + modal_confirmnotifications: "Είστε βέβαιοι ότι θέλετε να διαγράψετε όλες τις μη αναγνωσμένες ειδοποιήσεις;", + toast_alreadyclearing: "Διαγράφει ήδη κάποιες αναφορές", + toast_cleared: "Όλες οι πρόσφατες αναφορές έχουν διαγραφεί", + toast_clearing: "Διαγράφει όλες τις πρόσφατες αναφορές" + }; + case "es": // Spanish + return { + context_dms: "Mensajes directos", + context_guilds: "Todos los servidores", + context_mutedguilds: "Servidores silenciados", + context_pingedguilds: "Servidores con ping", + context_unreadguilds: "Servidores no leídos", + modal_confirmmentions: "¿Estás seguro de que deseas eliminar todas las menciones no leídas?", + modal_confirmnotifications: "¿Está seguro de que desea eliminar todas las notificaciones no leídas?", + toast_alreadyclearing: "Elimina algunas menciones ya", + toast_cleared: "Se han eliminado todas las menciones recientes", + toast_clearing: "Borra todas las menciones recientes" + }; + case "fi": // Finnish + return { + context_dms: "Suorat viestit", + context_guilds: "Kaikki palvelimet", + context_mutedguilds: "Mykistetyt palvelimet", + context_pingedguilds: "Pinged-palvelimet", + context_unreadguilds: "Lukemattomat palvelimet", + modal_confirmmentions: "Haluatko varmasti poistaa kaikki lukemattomat maininnat?", + modal_confirmnotifications: "Haluatko varmasti poistaa kaikki lukemattomat ilmoitukset?", + toast_alreadyclearing: "Poistaa jo joitain mainintoja", + toast_cleared: "Kaikki viimeisimmät maininnat on poistettu", + toast_clearing: "Tyhjentää kaikki viimeisimmät maininnat" + }; + case "fr": // French + return { + context_dms: "Messages directs", + context_guilds: "Tous les serveurs", + context_mutedguilds: "Serveurs muets", + context_pingedguilds: "Serveurs ping", + context_unreadguilds: "Serveurs non lus", + modal_confirmmentions: "Voulez-vous vraiment supprimer toutes les mentions non lues?", + modal_confirmnotifications: "Voulez-vous vraiment supprimer toutes les notifications non lues?", + toast_alreadyclearing: "Supprime déjà certaines mentions", + toast_cleared: "Toutes les mentions récentes ont été supprimées", + toast_clearing: "Efface toutes les mentions récentes" + }; + case "hr": // Croatian + return { + context_dms: "Direktna poruka", + context_guilds: "Svi poslužitelji", + context_mutedguilds: "Prigušeni poslužitelji", + context_pingedguilds: "Pingirani poslužitelji", + context_unreadguilds: "Nepročitani poslužitelji", + modal_confirmmentions: "Jeste li sigurni da želite izbrisati sva nepročitana spominjanja?", + modal_confirmnotifications: "Jeste li sigurni da želite izbrisati sve nepročitane obavijesti?", + toast_alreadyclearing: "Briše već spomenute", + toast_cleared: "Sva nedavna spominjanja su izbrisana", + toast_clearing: "Briše sva nedavna spominjanja" + }; + case "hu": // Hungarian + return { + context_dms: "Közvetlen üzenet", + context_guilds: "Minden szerver", + context_mutedguilds: "Némított szerverek", + context_pingedguilds: "Pingelt szerverek", + context_unreadguilds: "Olvasatlan szerverek", + modal_confirmmentions: "Biztosan törli az összes olvasatlan említést?", + modal_confirmnotifications: "Biztosan törli az összes olvasatlan értesítést?", + toast_alreadyclearing: "Néhány említést már töröl", + toast_cleared: "Az összes közelmúltbeli említést törölték", + toast_clearing: "Törli az összes közelmúltbeli említést" + }; + case "it": // Italian + return { + context_dms: "Messaggi diretti", + context_guilds: "Tutti i server", + context_mutedguilds: "Server disattivati", + context_pingedguilds: "Server sottoposti a ping", + context_unreadguilds: "Server non letti", + modal_confirmmentions: "Sei sicuro di voler eliminare tutte le menzioni non lette?", + modal_confirmnotifications: "Sei sicuro di voler eliminare tutte le notifiche non lette?", + toast_alreadyclearing: "Elimina già alcune menzioni", + toast_cleared: "Tutte le menzioni recenti sono state eliminate", + toast_clearing: "Cancella tutte le menzioni recenti" + }; + case "ja": // Japanese + return { + context_dms: "ダイレクトメッセージ", + context_guilds: "すべてのサーバー", + context_mutedguilds: "ミュートされたサーバー", + context_pingedguilds: "pingされたサーバー", + context_unreadguilds: "未読サーバー", + modal_confirmmentions: "未読のメンションをすべて削除してもよろしいですか?", + modal_confirmnotifications: "未読の通知をすべて削除してもよろしいですか?", + toast_alreadyclearing: "すでにいくつかの言及を削除します", + toast_cleared: "最近の言及はすべて削除されました", + toast_clearing: "最近の言及をすべてクリアします" + }; + case "ko": // Korean + return { + context_dms: "쪽지", + context_guilds: "모든 서버", + context_mutedguilds: "음소거 된 서버", + context_pingedguilds: "핑된 서버", + context_unreadguilds: "읽지 않은 서버", + modal_confirmmentions: "읽지 않은 모든 멘션을 삭제 하시겠습니까?", + modal_confirmnotifications: "읽지 않은 모든 알림을 삭제 하시겠습니까?", + toast_alreadyclearing: "이미 일부 멘션을 삭제합니다.", + toast_cleared: "모든 최근 멘션이 삭제되었습니다.", + toast_clearing: "최근 멘션을 모두 지 웁니다." + }; + case "lt": // Lithuanian + return { + context_dms: "Tiesioginiai pranešimai", + context_guilds: "Visi serveriai", + context_mutedguilds: "Nutildyti serveriai", + context_pingedguilds: "„Pinged“ serveriai", + context_unreadguilds: "Neskaityti serveriai", + modal_confirmmentions: "Ar tikrai norite ištrinti visus neperskaitytus paminėjimus?", + modal_confirmnotifications: "Ar tikrai norite ištrinti visus neperskaitytus pranešimus?", + toast_alreadyclearing: "Kai kurie paminėjimai jau ištrinami", + toast_cleared: "Visi naujausi paminėjimai buvo ištrinti", + toast_clearing: "Išvalo visus naujausius paminėjimus" + }; + case "nl": // Dutch + return { + context_dms: "Directe berichten", + context_guilds: "Alle servers", + context_mutedguilds: "Gedempte servers", + context_pingedguilds: "Gepingde servers", + context_unreadguilds: "Ongelezen servers", + modal_confirmmentions: "Weet u zeker dat u alle ongelezen vermeldingen wilt verwijderen?", + modal_confirmnotifications: "Weet u zeker dat u alle ongelezen meldingen wilt verwijderen?", + toast_alreadyclearing: "Verwijdert al enkele vermeldingen", + toast_cleared: "Alle recente vermeldingen zijn verwijderd", + toast_clearing: "Wist alle recente vermeldingen" + }; + case "no": // Norwegian + return { + context_dms: "Direktemeldinger", + context_guilds: "Alle servere", + context_mutedguilds: "Dempede servere", + context_pingedguilds: "Pingede servere", + context_unreadguilds: "Uleste servere", + modal_confirmmentions: "Er du sikker på at du vil slette alle uleste omtaler?", + modal_confirmnotifications: "Er du sikker på at du vil slette alle uleste varsler?", + toast_alreadyclearing: "Sletter allerede noen omtaler", + toast_cleared: "Alle nylige omtaler er slettet", + toast_clearing: "Fjerner alle nylige omtaler" + }; + case "pl": // Polish + return { + context_dms: "Bezpośrednie wiadomości", + context_guilds: "Wszystkie serwery", + context_mutedguilds: "Wyciszone serwery", + context_pingedguilds: "Serwery pingowane", + context_unreadguilds: "Nieprzeczytane serwery", + modal_confirmmentions: "Czy na pewno chcesz usunąć wszystkie nieprzeczytane wzmianki?", + modal_confirmnotifications: "Czy na pewno chcesz usunąć wszystkie nieprzeczytane powiadomienia?", + toast_alreadyclearing: "Usuwa już niektóre wzmianki", + toast_cleared: "Wszystkie ostatnie wzmianki zostały usunięte", + toast_clearing: "Usuwa wszystkie ostatnie wzmianki" + }; + case "pt-BR": // Portuguese (Brazil) + return { + context_dms: "Mensagens diretas", + context_guilds: "Todos os servidores", + context_mutedguilds: "Servidores Silenciados", + context_pingedguilds: "Servidores com ping", + context_unreadguilds: "Servidores não lidos", + modal_confirmmentions: "Tem certeza de que deseja excluir todas as menções não lidas?", + modal_confirmnotifications: "Tem certeza de que deseja excluir todas as notificações não lidas?", + toast_alreadyclearing: "Exclui algumas menções já", + toast_cleared: "Todas as menções recentes foram excluídas", + toast_clearing: "Limpa todas as menções recentes" + }; + case "ro": // Romanian + return { + context_dms: "Mesaje directe", + context_guilds: "Toate serverele", + context_mutedguilds: "Servere mutate", + context_pingedguilds: "Servere pinged", + context_unreadguilds: "Servere necitite", + modal_confirmmentions: "Sigur doriți să ștergeți toate mențiunile necitite?", + modal_confirmnotifications: "Sigur doriți să ștergeți toate notificările necitite?", + toast_alreadyclearing: "Șterge deja câteva mențiuni", + toast_cleared: "Toate mențiunile recente au fost șterse", + toast_clearing: "Șterge toate mențiunile recente" + }; + case "ru": // Russian + return { + context_dms: "Прямые сообщения", + context_guilds: "Все серверы", + context_mutedguilds: "Отключенные серверы", + context_pingedguilds: "Проверенные серверы", + context_unreadguilds: "Непрочитанные серверы", + modal_confirmmentions: "Вы уверены, что хотите удалить все непрочитанные упоминания?", + modal_confirmnotifications: "Вы действительно хотите удалить все непрочитанные уведомления?", + toast_alreadyclearing: "Удаляет уже некоторые упоминания", + toast_cleared: "Все недавние упоминания были удалены", + toast_clearing: "Удаляет все недавние упоминания" + }; + case "sv": // Swedish + return { + context_dms: "Direktmeddelanden", + context_guilds: "Alla servrar", + context_mutedguilds: "Dämpade servrar", + context_pingedguilds: "Pingade servrar", + context_unreadguilds: "Olästa servrar", + modal_confirmmentions: "Är du säker på att du vill ta bort alla olästa omnämnanden?", + modal_confirmnotifications: "Är du säker på att du vill ta bort alla olästa aviseringar?", + toast_alreadyclearing: "Raderar några omnämnanden redan", + toast_cleared: "Alla nya omnämnanden har tagits bort", + toast_clearing: "Rensar alla senaste omnämnanden" + }; + case "th": // Thai + return { + context_dms: "ข้อความโดยตรง", + context_guilds: "เซิร์ฟเวอร์ทั้งหมด", + context_mutedguilds: "เซิร์ฟเวอร์ที่ปิดเสียง", + context_pingedguilds: "เซิร์ฟเวอร์ Pinged", + context_unreadguilds: "เซิร์ฟเวอร์ที่ยังไม่ได้อ่าน", + modal_confirmmentions: "แน่ใจไหมว่าต้องการลบข้อความที่ยังไม่ได้อ่านทั้งหมด", + modal_confirmnotifications: "แน่ใจไหมว่าต้องการลบการแจ้งเตือนที่ยังไม่ได้อ่านทั้งหมด", + toast_alreadyclearing: "ลบการกล่าวถึงบางส่วนแล้ว", + toast_cleared: "ลบการกล่าวถึงล่าสุดทั้งหมดแล้ว", + toast_clearing: "ล้างการพูดถึงล่าสุดทั้งหมด" + }; + case "tr": // Turkish + return { + context_dms: "Direkt Mesajlar", + context_guilds: "Tüm Sunucular", + context_mutedguilds: "Sessiz Sunucular", + context_pingedguilds: "Ping Gönderilen Sunucular", + context_unreadguilds: "Okunmamış Sunucular", + modal_confirmmentions: "Okunmamış tüm bahisleri silmek istediğinizden emin misiniz?", + modal_confirmnotifications: "Okunmamış tüm bildirimleri silmek istediğinizden emin misiniz?", + toast_alreadyclearing: "Zaten bazı bahsetmeleri siler", + toast_cleared: "Son bahsedenlerin tümü silindi", + toast_clearing: "Tüm son bahsedilenleri temizler" + }; + case "uk": // Ukrainian + return { + context_dms: "Прямі повідомлення", + context_guilds: "Усі сервери", + context_mutedguilds: "Приглушені сервери", + context_pingedguilds: "Pinged сервери", + context_unreadguilds: "Непрочитані сервери", + modal_confirmmentions: "Ви впевнені, що хочете видалити всі непрочитані згадки?", + modal_confirmnotifications: "Ви впевнені, що хочете видалити всі непрочитані сповіщення?", + toast_alreadyclearing: "Видаляє деякі згадки вже", + toast_cleared: "Усі останні згадування були видалені", + toast_clearing: "Очищає всі останні згадування" + }; + case "vi": // Vietnamese + return { + context_dms: "Tin nhắn trực tiếp", + context_guilds: "Tất cả máy chủ", + context_mutedguilds: "Máy chủ bị tắt tiếng", + context_pingedguilds: "Máy chủ Pinged", + context_unreadguilds: "Máy chủ chưa đọc", + modal_confirmmentions: "Bạn có chắc chắn muốn xóa tất cả các đề cập chưa đọc không?", + modal_confirmnotifications: "Bạn có chắc chắn muốn xóa tất cả các thông báo chưa đọc không?", + toast_alreadyclearing: "Đã xóa một số đề cập", + toast_cleared: "Tất cả các đề cập gần đây đã bị xóa", + toast_clearing: "Xóa tất cả các đề cập gần đây" + }; + case "zh-CN": // Chinese (China) + return { + context_dms: "直接讯息", + context_guilds: "所有服务器", + context_mutedguilds: "静音服务器", + context_pingedguilds: "绑定服务器", + context_unreadguilds: "未读服务器", + modal_confirmmentions: "您确定要删除所有未读的提及吗?", + modal_confirmnotifications: "您确定要删除所有未读的通知吗?", + toast_alreadyclearing: "已删除一些提及", + toast_cleared: "最近所有提及的内容均已删除", + toast_clearing: "清除所有最近提及的内容" + }; + case "zh-TW": // Chinese (Taiwan) + return { + context_dms: "直接訊息", + context_guilds: "所有服務器", + context_mutedguilds: "靜音服務器", + context_pingedguilds: "綁定服務器", + context_unreadguilds: "未讀服務器", + modal_confirmmentions: "您確定要刪除所有未讀的提及嗎?", + modal_confirmnotifications: "您確定要刪除所有未讀的通知嗎?", + toast_alreadyclearing: "已刪除一些提及", + toast_cleared: "最近所有提及的內容均已刪除", + toast_clearing: "清除所有最近提及的內容" + }; + default: // English + return { + context_dms: "Direct Messages", + context_guilds: "All Servers", + context_mutedguilds: "Muted Servers", + context_pingedguilds: "Pinged Servers", + context_unreadguilds: "Unread Servers", + modal_confirmmentions: "Are you sure you want to delete all unread Mentions?", + modal_confirmnotifications: "Are you sure you want to delete all unread Notifications?", + toast_alreadyclearing: "Already clearing some Mentions", + toast_cleared: "All recent Mentions have been cleared", + toast_clearing: "Clearing all recent Mentions" + }; + } + } + }; + })(window.BDFDB_Global.PluginUtils.buildPlugin(changeLog)); +})(); \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ServerDetails.config.json b/dotfiles/.config/BetterDiscord/plugins/ServerDetails.config.json new file mode 100644 index 0000000..ff778fb --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ServerDetails.config.json @@ -0,0 +1,28 @@ +{ + "all": { + "amounts": { + "tooltipDelay": 0, + "tooltipWidth": 300 + }, + "colors": { + "tooltipColor": "" + }, + "dates": { + "tooltipDates": {} + }, + "general": { + "onlyShowOnShift": false + }, + "items": { + "icon": true, + "owner": true, + "creationDate": true, + "joinDate": true, + "members": true, + "channels": true, + "roles": true, + "boosts": true, + "language": true + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ServerDetails.plugin.js b/dotfiles/.config/BetterDiscord/plugins/ServerDetails.plugin.js new file mode 100644 index 0000000..2f3538e --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ServerDetails.plugin.js @@ -0,0 +1,577 @@ +/** + * @name ServerDetails + * @author DevilBro + * @authorId 278543574059057154 + * @version 1.3.4 + * @description Shows Server Details in the Server List Tooltip + * @invite Jx3TjNS + * @donate https://www.paypal.me/MircoWittrien + * @patreon https://www.patreon.com/MircoWittrien + * @website https://mwittrien.github.io/ + * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/ServerDetails/ + * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/ServerDetails/ServerDetails.plugin.js + */ + +module.exports = (_ => { + const changeLog = { + + }; + + return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class { + constructor (meta) {for (let key in meta) this[key] = meta[key];} + getName () {return this.name;} + getAuthor () {return this.author;} + getVersion () {return this.version;} + getDescription () {return `The Library Plugin needed for ${this.name} is missing. Open the Plugin Settings to download it. \n\n${this.description}`;} + + downloadLibrary () { + BdApi.Net.fetch("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js").then(r => { + if (!r || r.status != 200) throw new Error(); + else return r.text(); + }).then(b => { + if (!b) throw new Error(); + else return require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.UI.showToast("Finished downloading BDFDB Library", {type: "success"})); + }).catch(error => { + BdApi.UI.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library"); + }); + } + + load () { + if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []}); + if (!window.BDFDB_Global.downloadModal) { + window.BDFDB_Global.downloadModal = true; + BdApi.UI.showConfirmationModal("Library Missing", `The Library Plugin needed for ${this.name} is missing. Please click "Download Now" to install it.`, { + confirmText: "Download Now", + cancelText: "Cancel", + onCancel: _ => {delete window.BDFDB_Global.downloadModal;}, + onConfirm: _ => { + delete window.BDFDB_Global.downloadModal; + this.downloadLibrary(); + } + }); + } + if (!window.BDFDB_Global.pluginQueue.includes(this.name)) window.BDFDB_Global.pluginQueue.push(this.name); + } + start () {this.load();} + stop () {} + getSettingsPanel () { + let template = document.createElement("template"); + template.innerHTML = `
The Library Plugin needed for ${this.name} is missing.\nPlease click Download Now to install it.
`; + template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary); + return template.content.firstElementChild; + } + } : (([Plugin, BDFDB]) => { + var _this; + + const GuildDetailsComponent = class GuildDetails extends BdApi.React.Component { + constructor(props) { + super(props); + this.state = {fetchedOwner: false, delayed: false, repositioned: false, shouldReposition: false, forced: false}; + } + componentDidUpdate() { + let tooltip = BDFDB.DOMUtils.getParent(BDFDB.dotCN.tooltip, BDFDB.ReactUtils.findDOMNode(this)); + if (tooltip) BDFDB.DOMUtils.addClass(tooltip, BDFDB.disCN._serverdetailstooltip); + else if (this.props.tooltipContainer && this.props.tooltipContainer.tooltip) BDFDB.DOMUtils.removeClass(this.props.tooltipContainer.tooltip.firstElementChild, BDFDB.disCN._serverdetailstooltip); + if (this.state.shouldReposition || _this.settings.amounts.tooltipDelay && this.state.delayed && !this.state.repositioned) { + this.state.repositioned = true; + this.state.shouldReposition = false; + if (this.props.tooltipContainer && this.props.tooltipContainer.tooltip) this.props.tooltipContainer.tooltip.update(); + } + } + componentDidMount() { + if (!_this.settings.amounts.tooltipDelay && (!_this.settings.general.onlyShowOnShift || _this.settings.general.onlyShowOnShift && this.props.shiftKey)) BDFDB.DOMUtils.addClass(BDFDB.DOMUtils.getParent(BDFDB.dotCN.tooltip, BDFDB.ReactUtils.findDOMNode(this)), BDFDB.disCN._serverdetailstooltip); + } + render() { + if (_this.settings.general.onlyShowOnShift) { + let addListener = expanded => { + let triggered = false, listener = event => { + if (event.which != 16 || triggered) return; + triggered = true; + document.removeEventListener(expanded ? "keyup" : "keydown", listener); + this.props.shiftKey = !expanded; + this.state.forced = !expanded; + this.state.repositioned = false; + this.state.shouldReposition = true; + BDFDB.ReactUtils.forceUpdate(this); + }; + document.addEventListener(expanded ? "keyup" : "keydown", listener); + }; + if (!this.props.shiftKey) { + addListener(false); + return null; + } + else { + addListener(true); + } + } + let owner = BDFDB.LibraryStores.UserStore.getUser(this.props.guild.ownerId); + if (!owner && !this.state.fetchedOwner) { + this.state.fetchedOwner = true; + BDFDB.LibraryModules.UserProfileUtils.getUser(this.props.guild.ownerId).then(_ => BDFDB.ReactUtils.forceUpdate(this)); + } + if (_this.settings.amounts.tooltipDelay && !this.state.delayed && !this.state.shouldReposition) { + BDFDB.TimeUtils.timeout(_ => { + this.state.delayed = true; + BDFDB.ReactUtils.forceUpdate(this); + }, this.state.forced ? 0 : (_this.settings.amounts.tooltipDelay * 1000)); + return null; + } + else { + let src = BDFDB.GuildUtils.getIcon(this.props.guild.id, 4096, this.props.guild.icon && BDFDB.LibraryModules.IconUtils.isAnimatedIconHash(this.props.guild.icon)); + let roles = BDFDB.LibraryStores.GuildRoleStore.getSortedRoles(this.props.guild.id); + return BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + direction: BDFDB.LibraryComponents.Flex.Direction.VERTICAL, + align: BDFDB.LibraryComponents.Flex.Align.CENTER, + children: [ + _this.settings.items.icon && (src ? BDFDB.ReactUtils.createElement("img", { + className: BDFDB.disCN._serverdetailsicon, + src: src + }) : BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN._serverdetailsicon, + children: this.props.guild.acronym + })), + _this.settings.items.owner && BDFDB.ReactUtils.createElement(GuildDetailsRowComponent, { + prefix: BDFDB.LanguageUtils.LanguageStrings.SERVER_OWNER, + string: !owner ? BDFDB.LanguageUtils.LanguageStrings.UNKNOWN_USER : owner.username + }), + _this.settings.items.creationDate && BDFDB.ReactUtils.createElement(GuildDetailsRowComponent, { + prefix: _this.labels.creation_date, + string: BDFDB.LibraryComponents.DateInput.format(_this.settings.dates.tooltipDates, BDFDB.LibraryModules.TimestampUtils.extractTimestamp(this.props.guild.id)) + }), + _this.settings.items.joinDate && BDFDB.ReactUtils.createElement(GuildDetailsRowComponent, { + prefix: _this.labels.join_date, + string: BDFDB.LibraryComponents.DateInput.format(_this.settings.dates.tooltipDates, this.props.guild.joinedAt) + }), + _this.settings.items.members && BDFDB.ReactUtils.createElement(GuildDetailsRowComponent, { + prefix: BDFDB.LanguageUtils.LanguageStrings.MEMBERS, + string: BDFDB.LibraryStores.GuildMemberCountStore.getMemberCount(this.props.guild.id) + }), + _this.settings.items.boosts && BDFDB.ReactUtils.createElement(GuildDetailsRowComponent, { + prefix: _this.labels.boosts, + string: this.props.guild.premiumSubscriberCount + }), + _this.settings.items.channels && BDFDB.ReactUtils.createElement(GuildDetailsRowComponent, { + prefix: BDFDB.LanguageUtils.LanguageStrings.CHANNELS, + string: BDFDB.LibraryStores.GuildChannelStore.getChannels(this.props.guild.id).count + }), + _this.settings.items.roles && roles && roles.length && BDFDB.ReactUtils.createElement(GuildDetailsRowComponent, { + prefix: BDFDB.LanguageUtils.LanguageStrings.ROLES, + string: roles.filter(n => n.id != this.props.guild.id).length + }), + _this.settings.items.language && BDFDB.ReactUtils.createElement(GuildDetailsRowComponent, { + prefix: BDFDB.LanguageUtils.LanguageStrings.LANGUAGE, + string: BDFDB.LanguageUtils.getName(BDFDB.LanguageUtils.languages[this.props.guild.preferredLocale]) || this.props.guild.preferredLocale + }) + ].flat(10).filter(n => n) + }); + } + } + }; + + const GuildDetailsRowComponent = class GuildDetailsRow extends BdApi.React.Component { + render() { + return (this.props.prefix.length + this.props.string.length) > Math.round(34 * (_this.settings.amounts.tooltipWidth/300)) ? [ + BDFDB.ReactUtils.createElement("div", { + children: `${this.props.prefix}:` + }), + BDFDB.ReactUtils.createElement("div", { + children: this.props.string + }) + ] : BDFDB.ReactUtils.createElement("div", { + children: `${BDFDB.StringUtils.upperCaseFirstChar(this.props.prefix)}: ${this.props.string}` + }); + } + }; + + return class ServerDetails extends Plugin { + onLoad () { + _this = this; + + this.defaults = { + general: { + onlyShowOnShift: {value: false, description: "Only show the Details Tooltip, while holding 'Shift'"} + }, + items: { + icon: {value: true, description: "icon"}, + owner: {value: true, description: "SERVER_OWNER"}, + creationDate: {value: true, description: "creation_date"}, + joinDate: {value: true, description: "join_date"}, + members: {value: true, description: "MEMBERS"}, + channels: {value: true, description: "CHANNELS"}, + roles: {value: true, description: "ROLES"}, + boosts: {value: true, description: "boosts"}, + language: {value: true, description: "LANGUAGE"} + }, + dates: { + tooltipDates: {value: {}, description: "Tooltip Dates"} + }, + colors: { + tooltipColor: {value: "", description: "Tooltip Color"} + }, + amounts: { + tooltipDelay: {value: 0, min: 0, max: 10, digits: 1, unit: "s", description: "Tooltip Delay"}, + tooltipWidth: {value: 300, min: 200, max: 600, digits: 0, unit: "px", description: "Tooltip Width"} + } + }; + + this.modulePatches = { + after: [ + "GuildItem" + ] + }; + + this.patchPriority = 9; + + this.css = ` + ${BDFDB.dotCNS._serverdetailstooltip + BDFDB.dotCN.tooltipcontent} { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + max-width: unset; + word-wrap: unset; + } + ${BDFDB.dotCNS._serverdetailstooltip + BDFDB.dotCN._serverdetailsicon} { + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 5px; + border-radius: 10px; + overflow: hidden; + } + ${BDFDB.dotCN._serverdetailstooltip} div${BDFDB.dotCN._serverdetailsicon} { + background-color: var(--background-base-low); + color: var(--text-subtle); + font-size: 40px; + } + `; + } + + onStart () { + this.forceUpdateAll(); + } + + onStop () { + this.forceUpdateAll(); + + BDFDB.DOMUtils.removeLocalStyle(this.name + "TooltipWidth"); + } + + getSettingsPanel (collapseStates = {}) { + let settingsPanel; + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, { + collapseStates: collapseStates, + children: _ => { + let settingsItems = []; + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "General", + collapseStates: collapseStates, + children: Object.keys(this.defaults.general).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Switch", + plugin: this, + keys: ["general", key], + label: this.defaults.general[key].description, + value: this.settings.general[key] + })) + })); + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "Tooltip Items", + collapseStates: collapseStates, + children: Object.keys(this.defaults.items).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Switch", + plugin: this, + keys: ["items", key], + label: this.labels[this.defaults.items[key].description] || BDFDB.LanguageUtils.LanguageStrings[this.defaults.items[key].description], + value: this.settings.items[key] + })) + })); + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "Tooltip Format", + collapseStates: collapseStates, + children: Object.keys(this.defaults.dates).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.DateInput, Object.assign({}, this.settings.dates[key], { + label: this.defaults.dates[key].description, + onChange: valueObj => { + this.SettingsUpdated = true; + this.settings.dates[key] = valueObj; + BDFDB.DataUtils.save(this.settings.dates, this, "dates"); + } + }))).concat(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormDivider, { + className: BDFDB.disCN.marginbottom8 + })).concat(Object.keys(this.defaults.amounts).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Slider", + plugin: this, + keys: ["amounts", key], + label: this.defaults.amounts[key].description, + basis: "70%", + min: this.defaults.amounts[key].min, + max: this.defaults.amounts[key].max, + digits: this.defaults.amounts[key].digits, + markerAmount: 11, + onValueRender: value => value + this.defaults.amounts[key].unit, + childProps: {type: "number"}, + value: this.settings.amounts[key] + }))).concat(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormDivider, { + className: BDFDB.disCN.marginbottom8 + })).concat(Object.keys(this.defaults.colors).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "TextInput", + plugin: this, + keys: ["colors", key], + basis: "70%", + label: this.defaults.colors[key].description, + value: this.settings.colors[key], + childProps: {type: "color"}, + placeholder: this.settings.colors[key] + }))) + })); + + return settingsItems; + } + }); + } + + onSettingsClosed () { + if (this.SettingsUpdated) { + delete this.SettingsUpdated; + this.forceUpdateAll(); + } + } + + forceUpdateAll () { + let iconSize = this.settings.amounts.tooltipWidth - 80; + BDFDB.DOMUtils.appendLocalStyle(this.name + "TooltipWidth", ` + ${BDFDB.dotCN._serverdetailstooltip} { + min-width: ${this.settings.amounts.tooltipWidth}px !important; + width: unset !important; + max-width: unset !important; + } + ${BDFDB.dotCNS._serverdetailstooltip + BDFDB.dotCN._serverdetailsicon} { + width: ${iconSize > 0 ? iconSize : 30}px; + height: ${iconSize > 0 ? iconSize : 30}px; + } + `); + + BDFDB.DiscordUtils.rerenderAll(); + } + + processGuildItem (e) { + if (!e.instance.props.guild || typeof e.instance.props?.children?.props?.className != "string" || e.instance.props?.children?.props?.className.indexOf(BDFDB.disCN.guildcontainer) == -1) return; + if (!BDFDB.GuildUtils.is(e.instance.props.guild)) return; + let tooltipContainer; + e.returnvalue = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, Object.assign({}, e.returnvalue.props, { + ref: instance => {if (instance) tooltipContainer = instance;}, + tooltipConfig: Object.assign({ + backgroundColor: this.settings.colors.tooltipColor + }, e.returnvalue.props.tooltipConfig, { + type: "right", + guild: e.instance.props.guild, + list: true, + offset: 4 + }), + text: (instance, event) => BDFDB.ReactUtils.createElement(GuildDetailsComponent, { + shiftKey: event.shiftKey, + tooltipContainer: tooltipContainer, + guild: e.instance.props.guild + }), + children: typeof e.returnvalue.props.children == "function" ? e.instance.props.children : e.returnvalue.props.children + })); + } + + setLabelsByLanguage () { + switch (BDFDB.LanguageUtils.getLanguage().id) { + case "bg": // Bulgarian + return { + boosts: "Бустери", + creation_date: "Дата на създаване", + icon: "Икона", + join_date: "Дата на присъединяване" + }; + case "da": // Danish + return { + boosts: "Boosts", + creation_date: "Oprettelsesdato", + icon: "Ikon", + join_date: "Deltag i dato" + }; + case "de": // German + return { + boosts: "Boosts", + creation_date: "Erstellungsdatum", + icon: "Symbol", + join_date: "Beitrittsdatum" + }; + case "el": // Greek + return { + boosts: "Ενισχυτές", + creation_date: "Ημερομηνία δημιουργίας", + icon: "Εικονίδιο", + join_date: "Ημερομηνία προσχώρησης" + }; + case "es": // Spanish + return { + boosts: "Impulsores", + creation_date: "Fecha de creación", + icon: "Icono", + join_date: "Fecha de Ingreso" + }; + case "fi": // Finnish + return { + boosts: "Tehostimet", + creation_date: "Luomispäivä", + icon: "Kuvake", + join_date: "Liittymispäivä" + }; + case "fr": // French + return { + boosts: "Boosts", + creation_date: "Date de création", + icon: "Icône", + join_date: "Date d'inscription" + }; + case "hr": // Croatian + return { + boosts: "Pojačala", + creation_date: "Datum stvaranja", + icon: "Ikona", + join_date: "Datum pridruživanja" + }; + case "hu": // Hungarian + return { + boosts: "Emlékeztetők", + creation_date: "Létrehozás dátuma", + icon: "Ikon", + join_date: "Csatlakozás dátuma" + }; + case "it": // Italian + return { + boosts: "Boosts", + creation_date: "Data di creazione", + icon: "Icona", + join_date: "Data di iscrizione" + }; + case "ja": // Japanese + return { + boosts: "ブースター", + creation_date: "作成日", + icon: "アイコン", + join_date: "参加日" + }; + case "ko": // Korean + return { + boosts: "부스터", + creation_date: "제작 일", + icon: "상", + join_date: "가입 날짜" + }; + case "lt": // Lithuanian + return { + boosts: "Stiprintuvai", + creation_date: "Sukūrimo data", + icon: "Piktograma", + join_date: "Įstojimo data" + }; + case "nl": // Dutch + return { + boosts: "Boosts", + creation_date: "Aanmaakdatum", + icon: "Icoon", + join_date: "Toetredingsdatum" + }; + case "no": // Norwegian + return { + boosts: "Boosts", + creation_date: "Opprettelsesdato", + icon: "Ikon", + join_date: "Bli med på dato" + }; + case "pl": // Polish + return { + boosts: "Boosty", + creation_date: "Data utworzenia", + icon: "Ikona", + join_date: "Data dołączenia" + }; + case "pt-BR": // Portuguese (Brazil) + return { + boosts: "Boosts", + creation_date: "Data de criação", + icon: "Ícone", + join_date: "Data de afiliação" + }; + case "ro": // Romanian + return { + boosts: "Amplificatoare", + creation_date: "Data crearii", + icon: "Pictogramă", + join_date: "Data înscrierii" + }; + case "ru": // Russian + return { + boosts: "Бустеры", + creation_date: "Дата создания", + icon: "Икона", + join_date: "Дате вступления" + }; + case "sv": // Swedish + return { + boosts: "Boosts", + creation_date: "Skapelsedagen", + icon: "Ikon", + join_date: "Gå med datum" + }; + case "th": // Thai + return { + boosts: "บูสเตอร์", + creation_date: "วันที่สร้าง", + icon: "ไอคอน", + join_date: "วันที่เข้าร่วม" + }; + case "tr": // Turkish + return { + boosts: "Güçlendiriciler", + creation_date: "Oluşturulma tarihi", + icon: "Simge", + join_date: "Üyelik Tarihi" + }; + case "uk": // Ukrainian + return { + boosts: "Підсилювачі", + creation_date: "Дата створення", + icon: "Піктограма", + join_date: "Дата приєднання" + }; + case "vi": // Vietnamese + return { + boosts: "Bộ tăng tốc", + creation_date: "Ngày thành lập", + icon: "Biểu tượng", + join_date: "Ngày tham gia" + }; + case "zh-CN": // Chinese (China) + return { + boosts: "助推器", + creation_date: "创建日期", + icon: "图标", + join_date: "参加日期" + }; + case "zh-TW": // Chinese (Taiwan) + return { + boosts: "加成數", + creation_date: "創建日期", + icon: "圖示", + join_date: "參加日期" + }; + default: // English + return { + boosts: "Boosts", + creation_date: "Creation Date", + icon: "Icon", + join_date: "Join Date" + }; + } + } + }; + })(window.BDFDB_Global.PluginUtils.buildPlugin(changeLog)); +})(); \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ServerThemes.config.json b/dotfiles/.config/BetterDiscord/plugins/ServerThemes.config.json new file mode 100644 index 0000000..dc0b390 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ServerThemes.config.json @@ -0,0 +1,24 @@ +{ + "themeAssignments": { + "noguild": "Frosted Glass", + "191163742346543104": "Default", + "230708419995107330": "Default", + "301021184051642369": "Default", + "340262122782982147": " Lluna Plena", + "413603118832680960": "Default", + "423075122610700288": "Default", + "501047524963123212": "Default", + "514422760781643786": "Default", + "594829566330273792": "Default", + "607901720806293515": "Default", + "609090770712002575": "Default", + "679071578021167135": "Default", + "703597941809741834": "Default", + "707267476391723008": "Default", + "710203122106105927": "Default", + "734060708219846666": "Default", + "738872654928281620": "Default", + "775772308261830737": "Default", + "834834066008309800": "Default" + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ShowBadgesInChat.config.json b/dotfiles/.config/BetterDiscord/plugins/ShowBadgesInChat.config.json new file mode 100644 index 0000000..ed060bc --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ShowBadgesInChat.config.json @@ -0,0 +1,12613 @@ +{ + "all": { + "badgeCache": { + "1009476157344845884": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 15 May 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Whatsuup89#7284", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770825062661 + }, + "1011303608945487912": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Jaina81#2119", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771339233802 + }, + "1018947933267308715": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Lubero#2872", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770545985464 + }, + "1022952195194359889": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1770801895529 + }, + "1028724625800167494": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Magnus Mattison#5720", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771486330200 + }, + "1033065771871518760": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Deschki#7507", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770384468710 + }, + "1037781954302578708": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as BIENE#9141", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485903922 + }, + "1038916045404110938": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771485208273 + }, + "1047144730024226816": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 10 Nov 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 10 Nov 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1770544373334 + }, + "1051601668325781535": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 9 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770102892960 + }, + "1060697495887425666": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 6 Jan 2023. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as thechainsmokers19#4316", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320914787 + }, + "1064596579698352218": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as DerSammy#1691", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105399789 + }, + "1064848237263081473": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Karbrueggen1992#6013", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102919108 + }, + "1071040464549068840": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as shean#1370", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769381751760 + }, + "1075138784313749594": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sophie03#9536", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770852771872 + }, + "1081675754154229870": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Blyadrunner#4821", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770367936966 + }, + "1086015166753292398": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Rasyel#7826", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105375069 + }, + "108660441793732608": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as ceddy#2081", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770856289476 + }, + "1088848167799365703": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105408667 + }, + "1089172594973999206": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Domenica_R#4704", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485894523 + }, + "109576851327844352": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as Pascal#9635", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770102877623 + }, + "1097152343193178202": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as kanjh0#9465", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770915857777 + }, + "1104110699761909771": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Bowser#8658", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770371438303 + }, + "1114092320753725500": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 8 Jun 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as yamsh#0757", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770295781781 + }, + "1115671447889772635": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771338246572 + }, + "1117019457370476554": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as TireaLetsWatch#8040", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770450681820 + }, + "111915525012983808": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as PDawn#5279", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771343829771 + }, + "1138882290236272794": { + "badges": [], + "date": 1770423387640 + }, + "1141836192300679222": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 31 Dec 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770368754027 + }, + "114773240722751493": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 7 Apr 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 3 Jun 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as BazZTee#3718", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770294627776 + }, + "1158493148314599434": { + "badges": [], + "date": 1770856233256 + }, + "1169220456679735317": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770826774100 + }, + "1181631232782643281": { + "badges": [], + "date": 1771343669102 + }, + "1183504051384615032": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 10 Sept 2025", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1770382190036 + }, + "1188143649448546325": { + "badges": [], + "date": 1770875155355 + }, + "1193653963602542645": { + "badges": [], + "date": 1771485888806 + }, + "1199077029082050590": { + "badges": [], + "date": 1770876615427 + }, + "1200832137541206042": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769108462514 + }, + "1203536580921532486": { + "badges": [], + "date": 1771485601562 + }, + "1203780212836401236": { + "badges": [], + "date": 1770825061193 + }, + "1216347812313436320": { + "badges": [], + "date": 1771485217409 + }, + "1219432911208714302": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770541912210 + }, + "122351855807365121": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Kerelon#4198", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771524182123 + }, + "122481410010382338": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sermones#6429", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770657444266 + }, + "1242184861104472156": { + "badges": [], + "date": 1770877997619 + }, + "1246169994476126362": { + "badges": [], + "date": 1771485219871 + }, + "1267073226152874058": { + "badges": [], + "date": 1771320939721 + }, + "1287821677861933179": { + "badges": [], + "date": 1771338244050 + }, + "1296199183568474124": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 8 Feb 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 8 Feb 2026", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1770877992527 + }, + "129652751931146241": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as danyNyte#5826", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771349484968 + }, + "1299078667023945830": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770825149482 + }, + "130008120004050944": { + "badges": [ + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as 1twoHikaru#1337", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102926208 + }, + "1319322760249606164": { + "badges": [], + "date": 1771321026663 + }, + "1323209816579112991": { + "badges": [], + "date": 1770877987427 + }, + "1328838704332017786": { + "badges": [], + "date": 1769105386880 + }, + "132915367201865728": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Beolfsky#1475", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485902687 + }, + "1333060479081386006": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 16 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 16 Jan 2026", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769350033152 + }, + "133588959744753664": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 29 Nov 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 29 Nov 2020", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as GaWo#3005", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771321008297 + }, + "1339011420511010931": { + "badges": [], + "date": 1770365609116 + }, + "1356635281876586648": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769412233710 + }, + "137251386231488512": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Leola#6114", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770544375979 + }, + "1372826600084734023": { + "badges": [ + { + "id": "premium_tenure_3_month_v2", + "description": "Earned on 21 Aug 2025. 3 months: Silver", + "icon": "4514fab914bdbfb4ad2fa23df76121a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl3", + "description": "Server Boosting since 21 Aug 2025", + "icon": "72bed924410c304dbe3d00a6e593ff59", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1770102920452 + }, + "1373068285540831355": { + "badges": [], + "date": 1770877998973 + }, + "137602863672197129": { + "badges": [ + { + "id": "premium_tenure_72_month_v2", + "description": "Earned on 2 Feb 2020. 6+ years: Opal", + "icon": "5b154df19c53dce2af92c9b61e6be5e2", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 8 Jun 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Kurainu#8560", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771321021461 + }, + "1376120216140120154": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 24 Nov 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 25 Dec 2025", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1771338246197 + }, + "139834955848089601": { + "badges": [ + { + "id": "premium_tenure_72_month_v2", + "description": "Earned on 5 Jun 2019. 6+ years: Opal", + "icon": "5b154df19c53dce2af92c9b61e6be5e2", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 7 Jun 2019", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Dr. Lancelot#0666", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105358011 + }, + "142326534416498688": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Steve#8920", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770883323891 + }, + "143127943684685824": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Terrkas#2870", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105419050 + }, + "1431320996467441678": { + "badges": [], + "date": 1771487382690 + }, + "143140524050743296": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Deen#8570", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294636585 + }, + "146669544482537474": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 19 Nov 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 19 Nov 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as flim#7318", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771344053620 + }, + "149931993663012864": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Kythvin#8821", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485898357 + }, + "152434576252338176": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as CPT.QUOKKA#9733", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770801886133 + }, + "155149108183695360": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1771320932114 + }, + "156897171235405824": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 11 Oct 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 11 Oct 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as DasBroo#7335", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320922088 + }, + "158987571223986176": { + "badges": [], + "date": 1770451961424 + }, + "159019601085202432": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 30 Jul 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 30 Jul 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as TerraMx#3814", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338247802 + }, + "159820769398882304": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 29 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 29 Jan 2026", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as lakses#7276", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770910814980 + }, + "159985870458322944": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1771320938458 + }, + "163738373142347776": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 17 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as CybeX#1403", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102929648 + }, + "164840096460177408": { + "badges": [ + { + "id": "premium_tenure_72_month_v2", + "description": "Earned on 9 Oct 2017. 6+ years: Opal", + "icon": "5b154df19c53dce2af92c9b61e6be5e2", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 27 Jul 2019", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320997826 + }, + "166904483979198464": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 27 Apr 2022", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as bierschiss356#0356", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771330377603 + }, + "169450708440055808": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 25 Oct 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Gugli#8302", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770380332447 + }, + "172031461115953153": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Franigo#9536", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770884645640 + }, + "175347426079670273": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Der_Nerdi#0639", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770825056186 + }, + "178559963931738112": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Dennis#6240", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770386335908 + }, + "178681393142956032": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 24 Mar 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Kitty#3173", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771665252725 + }, + "180026459929182208": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 10 Apr 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 30 Nov 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1770183788602 + }, + "182172983282892800": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as luzin#7026", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771321014895 + }, + "184286284846596096": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as TheSuspect#6760", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770365611166 + }, + "185763439384199168": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Cheesecakes buntes Eierhäschen#1203", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770856812771 + }, + "185871860641169408": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Escanor#0439", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770877983729 + }, + "187179189638660096": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 12 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 12 Jan 2026", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770825063959 + }, + "188646379260870658": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 13 Feb 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 1 Jun 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Akiva#9025", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294855243 + }, + "189100428229607424": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Seroveih#1540", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771666029539 + }, + "189441218181201930": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as AmaiCmoon#1094", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320920309 + }, + "190198409150464001": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1770102891490 + }, + "190928860697591808": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769420895251 + }, + "191553056884654080": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 21 Oct 2022", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Jenneko - Wutwuschel#4786", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485215813 + }, + "191969177538396161": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 2 Nov 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 1 Feb 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Arindy#6666", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771676512077 + }, + "192689091622469632": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as BaerchenBaron#3194", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320961864 + }, + "194938862319960064": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 28 Apr 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 15 Aug 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as AtokirinaFry#8302", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771343666597 + }, + "195482372823711746": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 31 Dec 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Flemmingnator#4055", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338267629 + }, + "196928312344444928": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as MaxikingIV#9188", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105368188 + }, + "197010249088106496": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as GorillaJoe82#1368", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770541922428 + }, + "197018932480180224": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as PhilRIO#7897", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771524188174 + }, + "198840330865344512": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Steveeh#2774", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343812786 + }, + "201416960460718081": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 21 Aug 2017", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as shimpooky#1822", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770801885070 + }, + "202149256285388801": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 8 Oct 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl5", + "description": "Server Boosting since 29 Apr 2025", + "icon": "996b3e870e8a22ce519b3a50e6bdd52f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Bonsailinse#0001", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770804318092 + }, + "202151424061079552": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 28 Nov 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Phranzy#8775", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770245581769 + }, + "202846576262119424": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Cpt.Fischi#4102", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485911477 + }, + "204714119452950533": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Lucien_Wolf#3905", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485887847 + }, + "204940751824486400": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 23 May 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 20 Dec 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Neyt#1996", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771497684179 + }, + "205401239465230336": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 24 Jul 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl5", + "description": "Server Boosting since 23 Feb 2025", + "icon": "996b3e870e8a22ce519b3a50e6bdd52f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as lukiiixD#9009", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105389557 + }, + "205830688790937600": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 3 May 2022", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as BimbMcPewPew#5645", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770393695050 + }, + "206471054749597697": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sarion#3825", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771488536193 + }, + "206753365953216512": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 30 Nov 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Sana#3978", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769420911712 + }, + "206873240323358721": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 11 Dec 2022", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Ancillius#2501", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320920840 + }, + "208205065822011393": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as HunterSenjin#6538", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770102931618 + }, + "208309936080420864": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 31 Oct 2022", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1771485210722 + }, + "209272591532163073": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 1 May 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl5", + "description": "Server Boosting since 1 May 2025", + "icon": "996b3e870e8a22ce519b3a50e6bdd52f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Eriol#2999", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338313214 + }, + "211536672267960320": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 23 Jun 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 3 Oct 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as PhoenixDBlack#8013", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771349470146 + }, + "211823185883168768": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as AquaTixzzZ#5100", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770626693610 + }, + "212984051160580097": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294633874 + }, + "215514429968744449": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 13 Jun 2021", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Kneev#7294", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769420908566 + }, + "215592419738124298": { + "badges": [], + "date": 1771485885148 + }, + "217280055129276416": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 21 Feb 2024", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1770664742327 + }, + "218403127471243266": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 16 May 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 16 May 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Wolfkampf#6641", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320918285 + }, + "219126362802356224": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as PsychoUn4y#5151", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338274638 + }, + "219219128307810304": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Mister_Oktopus#6128", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485886589 + }, + "222036407143628800": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Jeromy#7832", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105361585 + }, + "222082746854408195": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Rüdiger#5787", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769182830820 + }, + "223505557091385345": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 14 Nov 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 14 Nov 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Khrel#9641", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771343659327 + }, + "224251982536376322": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Kettweasl#9427", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769513293801 + }, + "226801021194600448": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Don Promillo#2559", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770852900534 + }, + "229310692870586370": { + "badges": [ + { + "id": "premium_tenure_72_month_v2", + "description": "Earned on 1 Jun 2018. 6+ years: Opal", + "icon": "5b154df19c53dce2af92c9b61e6be5e2", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 11 Feb 2020", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Sl1kz#1337", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320994069 + }, + "229316169448947722": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 26 Sept 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Schun#6318", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770365609823 + }, + "229575900495216640": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 30 Mar 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Hyrunicorn#1337", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769251190105 + }, + "230067705347178498": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 10 Jan 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 17 Jul 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as MisakiDestiny#7724", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320916092 + }, + "230400073685663744": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Potterrr_#2144", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771338250110 + }, + "231517614667464704": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as EinfachNieDa#8101", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770147027082 + }, + "232166301731127297": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 20 Feb 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 20 Feb 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as T3rrA#0001", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771343662422 + }, + "232475934438064138": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Lacuny#5515", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771321023930 + }, + "232509818877771776": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Chrystalkey#7412", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771524190660 + }, + "232746669685473291": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 11 Jun 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 8 Jul 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Algaliarept#1987", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771485205782 + }, + "232951917607649280": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sami#8460", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770653024674 + }, + "233606591549669376": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Miurath#1257", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105370973 + }, + "233681014512156692": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 17 Apr 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 19 Apr 2020", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as GibbyOfficial#1337", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320925700 + }, + "234031488939720704": { + "badges": [ + { + "id": "premium_tenure_72_month_v2", + "description": "Earned on 4 Jul 2018. 6+ years: Opal", + "icon": "5b154df19c53dce2af92c9b61e6be5e2", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 14 May 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as NoPhil#2494", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770541924913 + }, + "234753156410507264": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as catroofdance#8502", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771349470806 + }, + "235088799074484224": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1770801899753 + }, + "235148962103951360": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1771320937133 + }, + "235866440131936256": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Slendy#6480", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770385525379 + }, + "236190394235355138": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770102935269 + }, + "236864327028572170": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 26 Mar 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Crisio#3564", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320995327 + }, + "237220728959795212": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 21 Aug 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 21 Aug 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485515945 + }, + "237524466286985216": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 15 Aug 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 5 Jan 2024", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Lantes | Chris#8061", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485909802 + }, + "237862498047492096": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 31 Dec 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 26 Feb 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Dome#7769", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771338253864 + }, + "238001798907035659": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as thelost404#7631", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105381745 + }, + "238280632264949772": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as GaminGandalf#8007", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770544380216 + }, + "238375883298177034": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as aquilaiovis#0223", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105355875 + }, + "239327241773776896": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as NERDSTAFF#4285", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770857012894 + }, + "241644714976083968": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 17 Apr 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 17 Apr 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Mezreal#0593", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485883971 + }, + "242010117661392906": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 11 Jan 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 29 Jan 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Flogge#0142", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770365602472 + }, + "242435614425874433": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as neon_leitz#0873", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770816615830 + }, + "242524543598592000": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sariel#0318", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769603603768 + }, + "243485119065751552": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 19 Jun 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 9 Jul 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as jelzinovic#2465", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770294729491 + }, + "244507960942788609": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Bernd#7593", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485903420 + }, + "244884362322116608": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as fredlllll#5278", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485597808 + }, + "245478611899908096": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Timbaleek#8077", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105385594 + }, + "245631961790414848": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as zujg#6150", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485890382 + }, + "245645707904548866": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as LuckyIceTea#2543", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771321029131 + }, + "245658596589699072": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 1 May 2022", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Aufersteher#0491", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770801886846 + }, + "246319776912244736": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 27 Apr 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 2 Jul 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771343663782 + }, + "246943540448329729": { + "badges": [ + { + "id": "premium_tenure_3_month_v2", + "description": "Earned on 2 Oct 2025. 3 months: Silver", + "icon": "4514fab914bdbfb4ad2fa23df76121a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl3", + "description": "Server Boosting since 5 Oct 2025", + "icon": "72bed924410c304dbe3d00a6e593ff59", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Lord Micky#7794", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770911139961 + }, + "247383152459382784": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Minosus#5367", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771349472143 + }, + "247782679099998208": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Talimgoron#6983", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770852774361 + }, + "247802503947747328": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 23 May 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 20 Jun 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Alphakater#1587", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485214539 + }, + "248452202031480835": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 31 Aug 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 18 Feb 2024", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1771497681163 + }, + "248506194581258240": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as Artur_Tendragon#0823", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771524180825 + }, + "248841932913115136": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as xangixangix#5313", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771339766450 + }, + "249279143496253442": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Paroiya#1591", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771338247477 + }, + "249320784470867978": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as RaysBow#9959", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770852896605 + }, + "249652622338949120": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 1 Oct 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 1 Oct 2020", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Leikur#2201", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771494113948 + }, + "250352435267239936": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Shaiger#2349", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770892885852 + }, + "250646952193163264": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 5 Jun 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 8 Jun 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as PhotoMarv#5099", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770280058209 + }, + "250974809855361034": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Fidi#6610", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102932083 + }, + "251096357526437888": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Isual#2256", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771349472790 + }, + "251483313745690624": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as KamiMadara#3030", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770365604949 + }, + "254990358297968640": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 4 Jun 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 13 Jul 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Adandran#3386", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320919512 + }, + "257561206712172544": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Anni James#8961", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771497270991 + }, + "257590973880729611": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Feljon#0599", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105373714 + }, + "257661500758425600": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Fain Elebeth#0685", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770294638042 + }, + "258538901352742912": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as MsKuhglocke#9403", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770966226459 + }, + "258661816278843392": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 24 Nov 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 24 Nov 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as passed_call#4173", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770852899141 + }, + "258682659423780864": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 25 Sept 2024", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770541968388 + }, + "258764058742751234": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as Skuly_S4#8532", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771338275975 + }, + "259004372480950273": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Lightning#9098", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770455659844 + }, + "259809300279590922": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771522223133 + }, + "260441347947757568": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 24 Dec 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 5 Feb 2026", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as ElloWeen#1033", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770801888061 + }, + "260817932643205131": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Rotsandii#3347", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485889135 + }, + "261485333139423233": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 30 Jun 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 4 Oct 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as SenpaiMinato💛#9999", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769420907919 + }, + "261567596497731584": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as bluehairedtrash#6675", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770451950592 + }, + "261848938317873153": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Chillian#1126", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771349469096 + }, + "261920599876698124": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 25 Feb 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 19 Jun 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as slimy#0786", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320912588 + }, + "262186435396763648": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 23 Oct 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl5", + "description": "Server Boosting since 22 Mar 2025", + "icon": "996b3e870e8a22ce519b3a50e6bdd52f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as TheVoice#6543", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105384341 + }, + "263293000111620096": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as eulebugman94#8182", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771321003122 + }, + "263685270442868747": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 12 Sept 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as _Clumsy#4000", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338245339 + }, + "263768010563649546": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as TyLee#3602", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771336604676 + }, + "264020441901826049": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 6 Jul 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl5", + "description": "Server Boosting since 12 Mar 2025", + "icon": "996b3e870e8a22ce519b3a50e6bdd52f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as buttercore#0960", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338634680 + }, + "265057001820782593": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Ede_der_Baertige#2484", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320924303 + }, + "265569351200014347": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 4 May 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 7 Oct 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Tera#0343", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770365610378 + }, + "266039942755385364": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 24 May 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl5", + "description": "Server Boosting since 4 Mar 2025", + "icon": "996b3e870e8a22ce519b3a50e6bdd52f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Elanor#3358", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105358376 + }, + "266298766661910528": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Drakonas8#8855", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105354310 + }, + "266655947198431251": { + "badges": [ + { + "id": "premium_tenure_3_month_v2", + "description": "Earned on 26 Oct 2025. 3 months: Silver", + "icon": "4514fab914bdbfb4ad2fa23df76121a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Philly#2186", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770909471301 + }, + "267363596407799808": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Thor8472#6570", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771602901724 + }, + "267687873476689931": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Br_obert#7805", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769353853973 + }, + "268028466497191936": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 16 Nov 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Halcyon#0666", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770675561506 + }, + "268296193069678592": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 27 Feb 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 27 Feb 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as michilinmnnchn#0503", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770365607393 + }, + "268467728887578624": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769420906480 + }, + "268487624010891264": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Liam#6146", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770365606178 + }, + "268495690122919937": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Rovan_Saber#9929", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105411378 + }, + "269974689630257163": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 8 Mar 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl5", + "description": "Server Boosting since 8 Mar 2025", + "icon": "996b3e870e8a22ce519b3a50e6bdd52f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as GroßKaiser Wilhelm II#4312", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771321006946 + }, + "270203060171571201": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 3 Jul 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 25 May 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as PaineDark#1008", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770294631863 + }, + "270956595607765004": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as TomWayne#3316", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769420909895 + }, + "271011501035356170": { + "badges": [ + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 14 Jun 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Xeren ShadowLink#8830", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770852776795 + }, + "271342354840027138": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sir Happy#0251", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769283670182 + }, + "272004734536253440": { + "badges": [ + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as maci97#5161", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770376256074 + }, + "273149968876044289": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 28 Aug 2025", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as GamixPr#7192", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294639351 + }, + "273237238848946177": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 26 Nov 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 14 Dec 2025", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Efeukind#9853", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105401023 + }, + "273493216609959937": { + "badges": [ + { + "id": "premium_tenure_3_month_v2", + "description": "Earned on 15 Oct 2025. 3 months: Silver", + "icon": "4514fab914bdbfb4ad2fa23df76121a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 13 Dec 2025", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as kleenesEumel#1295", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770423882619 + }, + "274858006229811200": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Kacksuppe95#3578", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105362543 + }, + "275318647495458816": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 16 Apr 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "guild_booster_lvl5", + "description": "Server Boosting since 16 Apr 2025", + "icon": "996b3e870e8a22ce519b3a50e6bdd52f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Schmeggins#3498", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770542333755 + }, + "275740664061231105": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as TillSMC#0321", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102878964 + }, + "275764484377804800": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 20 Nov 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "legacy_username", + "description": "Originally known as Cpt.Rakuma#1213", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771495076621 + }, + "276360138833788928": { + "badges": [], + "date": 1769420905270 + }, + "277823685253922818": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 26 Jun 2025", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Arya/Julia#1225", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771487003325 + }, + "279742053833900032": { + "badges": [ + { + "id": "premium_tenure_72_month_v2", + "description": "Earned on 13 Feb 2017. 6+ years: Opal", + "icon": "5b154df19c53dce2af92c9b61e6be5e2", + "link": "https://discord.com/settings/premium" + }, + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 7 Jun 2019", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Trekkie...Who?#4242", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485204526 + }, + "280196119382851584": { + "badges": [ + { + "id": "premium_tenure_72_month_v2", + "description": "Earned on 21 Apr 2018. 6+ years: Opal", + "icon": "5b154df19c53dce2af92c9b61e6be5e2", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 27 Feb 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as 🍌Sarahbanana#0815", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771320918868 + }, + "281431329369882625": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Nehekhara#8443", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105352602 + }, + "281783990124544010": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as happy_pineapple#7006", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769420895240 + }, + "282530290054725632": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 20 Dec 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 28 Dec 2020", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Johann Schnee#1999", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771338270686 + }, + "282889350092554240": { + "badges": [ + { + "id": "premium_tenure_3_month_v2", + "description": "Earned on 14 Sept 2025. 3 months: Silver", + "icon": "4514fab914bdbfb4ad2fa23df76121a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Sidari Yumanara#4815", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770983325202 + }, + "282907859854032896": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 8 Apr 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 5 Jun 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Relicat#9999", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771493875148 + }, + "283699818121920514": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as Klicker Beta#9131", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771339957314 + }, + "285301901090750464": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as DNX#1337", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770294627063 + }, + "285423283187482626": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 20 Jun 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl7", + "description": "Server Boosting since 20 Oct 2024", + "icon": "cb3ae83c15e970e8f3d410bc62cb8b99", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Eva_Amanda#7104", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771487000910 + }, + "285852216076533760": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 5 Dec 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 5 Dec 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294631275 + }, + "286099500957827072": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as SemJon#9736", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771486973587 + }, + "286927806921834497": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as sharkelelele#0932", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771522221906 + }, + "288327626249076738": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Shintoras | Tobi#6571", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769420902660 + }, + "289087542878535681": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as hybaZZor#8787", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770544374597 + }, + "290181670055706625": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 25 Oct 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl6", + "description": "Server Boosting since 2 Feb 2025", + "icon": "991c9f39ee33d7537d9f408c3e53141e", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771338251329 + }, + "290196252925231116": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Alby#5383", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105392104 + }, + "291613327040184320": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Oren#4503", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769420900118 + }, + "291900462373011466": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Blackie15#2698", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105403698 + }, + "293826530168143883": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Horrorxwitch#2843", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770365603720 + }, + "294863687523500032": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 21 Mar 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as MisterInc#4215", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771346392107 + }, + "295575013350834176": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Hanso#4456", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485886025 + }, + "297326690282242049": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 30 Apr 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 30 Apr 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Cr45hCode#1337", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294635146 + }, + "297389640506277899": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 28 Apr 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 13 Jan 2026", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as SirLame#2352", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771343661162 + }, + "297415916122144780": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770826064031 + }, + "298933444518346752": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Meridyn#6606", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485890031 + }, + "299218193694851074": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as DINGmann#4863", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770874154115 + }, + "301055974460882945": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 3 Aug 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 3 Aug 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Doomikami#2895", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770970452328 + }, + "301421313300627457": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Florian#8590", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771321005614 + }, + "302496663216324619": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Luc | Nerdfactory#7045", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343192090 + }, + "302527944469905409": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as RDM#2840", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485888278 + }, + "302822832902242316": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 23 Jan 2023. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 12 Sept 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as _Jarvis/Tatze#0815", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338311370 + }, + "302986247352090626": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 1 Nov 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl7", + "description": "Server Boosting since 5 Nov 2024", + "icon": "cb3ae83c15e970e8f3d410bc62cb8b99", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Purpl3GF#0033", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770825066803 + }, + "303125739295997952": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as DasMomo199877#3932", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343975015 + }, + "303766795423186944": { + "badges": [], + "date": 1771485891314 + }, + "304222103002152961": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Vajhar#5713", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320917618 + }, + "304389225120399360": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as CritiCarl#4086", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770365598664 + }, + "305335223967547392": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 14 Oct 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 14 Oct 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Cilla#6942", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771497679921 + }, + "306659618611003396": { + "badges": [ + { + "id": "premium_tenure_72_month_v2", + "description": "Earned on 19 Aug 2019. 6+ years: Opal", + "icon": "5b154df19c53dce2af92c9b61e6be5e2", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771522220618 + }, + "306834146947235840": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 22 Oct 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl7", + "description": "Server Boosting since 22 Oct 2024", + "icon": "cb3ae83c15e970e8f3d410bc62cb8b99", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as LuciferSirzechs#9425", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770873009797 + }, + "309238652989603840": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as berowinger#0841", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105402371 + }, + "310875154957991940": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 18 Sept 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 27 Apr 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as 𝕮𝖍𝖗𝖎𝖘𝖈𝖗𝖔𝖘𝕷𝖕#6933", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771344518324 + }, + "311534286224490499": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 14 Dec 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Mizki#0266", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771487001263 + }, + "312203925635727361": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Aru#5529", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770818463317 + }, + "313240674843492352": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 17 Jun 2022", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771321018703 + }, + "313259694904967168": { + "badges": [ + { + "id": "premium_tenure_72_month_v2", + "description": "Earned on 27 Jul 2019. 6+ years: Opal", + "icon": "5b154df19c53dce2af92c9b61e6be5e2", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 5 Sept 2019", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Wazari#4096", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769415950874 + }, + "315159214588821506": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Paradogs#8850", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485892685 + }, + "315797957075009536": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Coca_D_Kolo#1480", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770857337267 + }, + "316338427522187267": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 1 Jun 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 1 Jun 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Excowlibur#8251", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771321004407 + }, + "317372924023537664": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Elfchenschmiede#5491", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770120114363 + }, + "318519338636148746": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 18 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Keksvernichter#5419", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771321020083 + }, + "318829050401390597": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 18 Mar 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 18 Mar 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as BluuBerry#9546", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771524174842 + }, + "319844039316733952": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 27 Feb 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as Paetz#5858", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770801896472 + }, + "321313380440735744": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Kalpeq#7146", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105393459 + }, + "321961785387122688": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 27 Dec 2024", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as WarWolf#4754", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770852775566 + }, + "322662670140702722": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as nocibi#4866", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771322160615 + }, + "322829427954417664": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 6 Apr 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 24 May 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Sulayon#2249", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770296003590 + }, + "326784682622713856": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Annamäleon#9970", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770541963221 + }, + "328543983628189696": { + "badges": [], + "date": 1769344549265 + }, + "328958937221300225": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Kavo#4166", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771524185493 + }, + "329686286887682068": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as lametastic#5069", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771580995198 + }, + "330009320508882947": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Synlana#8698", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770247075216 + }, + "330311752883634178": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as Neinfuss#8637", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769531245201 + }, + "330372778584702978": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 6 May 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 10 May 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Hardrockdevil#0069", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770394138720 + }, + "331154960966352896": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 12 Jun 2021", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as Schmackkes#4896", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770894434655 + }, + "332561149251813377": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 1 Jul 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 5 Dec 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Yumiyay#5232", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770636842891 + }, + "334376647580975106": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 23 Dec 2022", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Walhalla#3132", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770432536320 + }, + "334715038050942977": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Mimimose#4805", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770801883826 + }, + "334765741515603970": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 15 Aug 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl3", + "description": "Server Boosting since 13 Aug 2025", + "icon": "72bed924410c304dbe3d00a6e593ff59", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Donatz#9735", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770294637238 + }, + "336195431530889216": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Dragonlight#1982", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771344052299 + }, + "336913917295198209": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as K0riB4te#5912", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771666562234 + }, + "337347113627418654": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Nait#8803", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770818529461 + }, + "338688741553995776": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Panpan#8786", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771488721615 + }, + "338741573175083008": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Avalos#4003", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485898899 + }, + "339038420645052418": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 20 Sept 2025", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Soru2304#8675", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769353850680 + }, + "339428212276723712": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 25 Aug 2024", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as LONELY-Camper#0530", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770810781031 + }, + "339548510729011215": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Rinaii#9266", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770633206629 + }, + "340581651900006420": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as g.riesi#2101", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320959383 + }, + "340782227204079616": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as SixEcho#1826", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343831034 + }, + "341227522941255700": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338257627 + }, + "341285361709744128": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 31 Dec 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 31 Dec 2020", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as tj_1998#9284", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105383016 + }, + "341288703202689026": { + "badges": [], + "date": 1771485907271 + }, + "341678096463822859": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as Trollingcat#4810", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770371791983 + }, + "341693290992762912": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 6 Dec 2024", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Leldalein#9609", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770852865070 + }, + "342137505891745792": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 26 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 1 Feb 2026", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as AMOK#4880", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294628353 + }, + "342234035222872066": { + "badges": [], + "date": 1771485895194 + }, + "342743426795831297": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as wheeler#1481", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102937102 + }, + "342978280036761601": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as Amisaki#7423", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338261390 + }, + "344803477135818753": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338266382 + }, + "347082124165120000": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 12 Dec 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 17 Dec 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771524169169 + }, + "348144505150636032": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 13 Oct 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 13 Oct 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as DontEatYellowSnow||Nobby#6169", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770801891827 + }, + "348534158051770369": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 23 Sept 2025", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as Paddi#1252", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770974678211 + }, + "349913274352926721": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Django9000#4011", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770294629647 + }, + "349924663809409026": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Aliquis#7841", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485359969 + }, + "349996134087000066": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as HeimiDerEine#3440", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294641539 + }, + "350260082979766273": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Jimbei147#9280", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771321016147 + }, + "350311192842993676": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as Devos#0810", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770366961541 + }, + "350383890831245322": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as HANSI WILL KEKSE!!!#3173", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105417761 + }, + "350712240007282690": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 17 Oct 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 23 Apr 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as stfan_#0429", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771497677931 + }, + "351227880153546754": { + "badges": [], + "date": 1771320921550 + }, + "351328658956484610": { + "badges": [ + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "legacy_username", + "description": "Originally known as Alpaca soldier#4116", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771338260166 + }, + "353492449047609345": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Christoph#1531", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485911043 + }, + "353949313489174549": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Hansi130#8026", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771665251488 + }, + "355117564038217730": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as itschotsch#2386", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105415244 + }, + "355799049485680653": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Chopperbob#4277", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770980230432 + }, + "355830069454962688": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Yokai#8419", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769417139094 + }, + "356493629398712320": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as Carmen#4741", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770915958393 + }, + "356789478305824779": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 3 Apr 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl6", + "description": "Server Boosting since 6 Jan 2025", + "icon": "991c9f39ee33d7537d9f408c3e53141e", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as N0BZN™#8778", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102919403 + }, + "357993640993226753": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as alexs#3961", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771580993924 + }, + "358358335340150795": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as McKessington#5154", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485895772 + }, + "358927608311971842": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as MattBroetchen#8661", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770875725983 + }, + "358960532839071744": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Theowin / Stefan#4492", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771488465781 + }, + "359759212869582849": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as BrightOneDaniel#8207", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770294634492 + }, + "361862713623838720": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 22 Nov 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "early_supporter", + "description": "Early Supporter", + "icon": "7060786766c9c840eb3019e725d2b358", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 25 Nov 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Der Strese#1222", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770541921141 + }, + "363620871564296192": { + "badges": [ + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "legacy_username", + "description": "Originally known as julian.#3662", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770451953250 + }, + "364176580387274762": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771343656749 + }, + "364754397982490634": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 9 Jun 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320949969 + }, + "365966619026915328": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 14 Feb 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 20 Jul 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as notNatex#4662", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770379068625 + }, + "367374090656284684": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 16 Dec 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as LetsRayTV#7442", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320935846 + }, + "368481389751828480": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Whistleblower®#3836", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771522215431 + }, + "369162578364203010": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Charybdis / Sebastian#8972", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770801890003 + }, + "369865156781539329": { + "badges": [ + { + "id": "premium_tenure_72_month_v2", + "description": "Earned on 16 Mar 2019. 6+ years: Opal", + "icon": "5b154df19c53dce2af92c9b61e6be5e2", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 18 Dec 2019", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as NoLoneSurvivor#1972", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770971285212 + }, + "370518720923172864": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as CluCas#7379", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770294629044 + }, + "370637121100447756": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770801885579 + }, + "372813035024416769": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Couflax#5827", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105376351 + }, + "375331564222939139": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Tempodeluxe#4388", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771349716280 + }, + "375805687529209857": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1771320942306 + }, + "377906977935720448": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 13 Jul 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl3", + "description": "Server Boosting since 23 Oct 2025", + "icon": "72bed924410c304dbe3d00a6e593ff59", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as MdM#9743", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320930794 + }, + "379634469444911117": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 3 May 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 5 May 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as FFROFRODO#4484", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770865067268 + }, + "381880545543454720": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as ariane#3379", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770417345226 + }, + "382336765547249675": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as die Mieze#2663", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771486000416 + }, + "384049859080093696": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Pogo#3699", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771497670619 + }, + "384356598224781332": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 13 Mar 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 15 Apr 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Layqz#3454", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320934616 + }, + "384446668818350080": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 22 Feb 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 22 Feb 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as JohnnyChicago | Moritz#6113", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770801888738 + }, + "386973639276494850": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as DerFabo#3922", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770389204480 + }, + "386974034975391746": { + "badges": [], + "date": 1771320957608 + }, + "386976767266521090": { + "badges": [], + "date": 1770294630360 + }, + "388001609667575808": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 31 Aug 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 31 Aug 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Kjay9558#2921", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485905930 + }, + "388406528362807307": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Honor_of_Claymore#0627", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771321011005 + }, + "389490322494717974": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Narok Om#2780", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105372240 + }, + "390155356187394059": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as ViLLeX_TFS#8363", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770102935910 + }, + "390163193819037707": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Angelbeats#5045", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105395940 + }, + "391306890040311808": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 9 Jul 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 9 Jul 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771338271994 + }, + "392735954701647876": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Falo29#1070", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771524177011 + }, + "393848308121993217": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 25 May 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as NyawNick#4509", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771524186780 + }, + "396022843516780544": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as XeNNiiiii#1749", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102937763 + }, + "397057388676644865": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 18 Sept 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 18 Sept 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771325397756 + }, + "397844230187384833": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 18 Feb 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 7 Jul 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Thorthemar | Jo#4733", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770366939156 + }, + "398209187831611393": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Noodle#9894", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102933404 + }, + "398978499769270314": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Eddie#8403", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771321009570 + }, + "399629597081206796": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 1 Dec 2025", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Risi#7080", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771341634428 + }, + "400289446899220482": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Feni Hunter#2935", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770877984927 + }, + "400669846213230605": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 24 Nov 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 24 Nov 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as brlndude#0342", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770378456559 + }, + "401101105096032257": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as KaiusMagnus#4940", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771524191939 + }, + "402046361618612227": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as werderaner#5664", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770977763247 + }, + "402528814548254720": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1770801893081 + }, + "402871205453103114": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 14 Sept 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 29 Sept 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770801894307 + }, + "403915105974091797": { + "badges": [ + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "legacy_username", + "description": "Originally known as Kristallkeks#5262", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770877994998 + }, + "404589532743467008": { + "badges": [ + { + "id": "premium_tenure_3_month_v2", + "description": "Earned on 4 Oct 2025. 3 months: Silver", + "icon": "4514fab914bdbfb4ad2fa23df76121a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 13 Jan 2026", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Nobu Nobu#7861", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343988193 + }, + "404695226159202314": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as derth0mas#4155", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770365599928 + }, + "404867593011200000": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as NGinC | noelcalderon#0336", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771522216716 + }, + "407847733983051777": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 1 Dec 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 8 Jul 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Ric#6053", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770825058743 + }, + "409444221465001994": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Fenrir#8606", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485891607 + }, + "409811439050686495": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Kirtash22#8233", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771349473637 + }, + "409820694503882764": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as peacockp_#8322", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770162747768 + }, + "411444066094415874": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Tino#6916", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770294633109 + }, + "411661538936487939": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 2 Dec 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 2 Dec 2020", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Bad Wolf#8756", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102922440 + }, + "412229397697921035": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 30 Aug 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 30 Aug 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Gatto Dolce#6391", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320924599 + }, + "413505054029643786": { + "badges": [], + "date": 1771527151124 + }, + "414492976811606027": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sanara#6024", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771490179073 + }, + "416249097880600576": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Apollonius#7415", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485897012 + }, + "416978285277478913": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Celestine#4856", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485907700 + }, + "417013370475118610": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Light_Up_My_Way#3344", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770209723819 + }, + "418131740217835530": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sumalina#3263", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320943535 + }, + "418832569577242635": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Benni#5227", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485901377 + }, + "418855338062774273": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Xalones#4993", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770890601092 + }, + "420882200481759232": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 23 Dec 2020", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1771320923370 + }, + "423751554445606922": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Futschikato#4655", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485897678 + }, + "424518912953155585": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Socko#1710", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320960230 + }, + "424663065225723904": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Salina#1350", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770652000321 + }, + "425802326771499020": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 27 Sept 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 27 Sept 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Adriaen#1104", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770541964606 + }, + "426054173067575297": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Droggelbecher#9905", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771491564333 + }, + "427555600768172047": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 19 Jun 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 21 Jun 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1770801903530 + }, + "427579266058813451": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 25 Feb 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 27 May 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as bezauBERD#7777", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770102929211 + }, + "430492819392561153": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Conky#2321", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770862737904 + }, + "431197471192907779": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as Schmockolade#0001", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338887587 + }, + "431887977967517696": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Hunter Craven#7504", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770826052739 + }, + "432176394697441310": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 27 Oct 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 27 Oct 2020", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as bumban037#0037", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320963121 + }, + "432182420339425287": { + "badges": [ + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "legacy_username", + "description": "Originally known as Riyce#2498", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770365822608 + }, + "432611250363695114": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as selanee#1745", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770365829834 + }, + "432902039425777695": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 2 Apr 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Uta_Maske#3339", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770825069824 + }, + "433239270329679873": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as DeZeE91#3757", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770233683083 + }, + "436587785454092288": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 7 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as .T02I#1913", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771345130182 + }, + "436895855497314314": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as MarkusT264#4747", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770825488002 + }, + "438257724447522816": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 14 Jun 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "legacy_username", + "description": "Originally known as Shirosobi#8545", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770294626344 + }, + "438421829624004610": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Lars Varghals#6879", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770651999770 + }, + "439183441162928138": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 16 Apr 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 7 Jan 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as der_Kun_#6283", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770404364910 + }, + "439477141923692564": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 9 Apr 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 26 Oct 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Topstar#0144", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320958092 + }, + "440394442600349706": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as wia#7304", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105352253 + }, + "440417877485682698": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 8 Dec 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Mencheres#6262", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770825068151 + }, + "440576663051304983": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 5 Nov 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 9 Feb 2026", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771581018177 + }, + "441616308417855489": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Daniel 22042019#9680", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485207034 + }, + "441659002687062047": { + "badges": [ + { + "id": "premium_tenure_3_month_v2", + "description": "Earned on 17 Nov 2025. 3 months: Silver", + "icon": "4514fab914bdbfb4ad2fa23df76121a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Vivi / Maria#1651", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771524195605 + }, + "443855347480920064": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Feary#4198", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771344157922 + }, + "446353523618217984": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Dennis0230#0046", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770825059960 + }, + "447060856807882755": { + "badges": [], + "date": 1770102896892 + }, + "447790976145162241": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 1 Nov 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 1 Nov 2020", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as HAFENSAENGER#1337", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770295015002 + }, + "449197345469628417": { + "badges": [], + "date": 1770400943908 + }, + "449308577195098122": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 23 Nov 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 21 Dec 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Yumai#8304", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770102899392 + }, + "450240942155497473": { + "badges": [], + "date": 1770379311716 + }, + "451451117503643678": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Ysadora/Ruth#3413", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771490129448 + }, + "452480501345943553": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as JanCamorizzi#1763", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770297873692 + }, + "452498685230710784": { + "badges": [ + { + "id": "premium_tenure_3_month_v2", + "description": "Earned on 18 Sept 2025. 3 months: Silver", + "icon": "4514fab914bdbfb4ad2fa23df76121a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl3", + "description": "Server Boosting since 18 Sept 2025", + "icon": "72bed924410c304dbe3d00a6e593ff59", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Anonym#4490", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320941041 + }, + "454620379424489483": { + "badges": [ + { + "id": "premium_tenure_72_month_v2", + "description": "Earned on 4 Jan 2019. 6+ years: Opal", + "icon": "5b154df19c53dce2af92c9b61e6be5e2", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Steems#5841", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105379189 + }, + "454628294613139469": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Teelina#6100", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770970312070 + }, + "456059598156333086": { + "badges": [], + "date": 1771524179587 + }, + "456938639696330764": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Eclipse#7862", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770877980832 + }, + "457216066880536579": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as worrso#1085", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105394717 + }, + "457525994215964673": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as MrTank/DSRambo#2172", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770852897910 + }, + "458276816071950337": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1770801901008 + }, + "459094901666086925": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sheewa#2869", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771602900384 + }, + "459278010730807300": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Gruber47#2484", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485886280 + }, + "459773099651497985": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as SteveTheSloth#2682", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771497678695 + }, + "460024116473495563": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as kanitoga#0414", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770213754132 + }, + "460444717793148949": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Katschinga#5845", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770801893879 + }, + "462232116076478465": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as The Rat Man#7883", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771524194379 + }, + "462433242436337666": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Dat_Hebben#6693", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770395791079 + }, + "465968842670080010": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sarah Katharina Feenlicht#9019", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485891944 + }, + "468152878326874113": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Billy_is_your_friend#8200", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771497679291 + }, + "469591504349036544": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 17 Dec 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 17 Dec 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as theriseoftombraider#9468", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102934640 + }, + "471476055308500993": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 29 Jul 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 29 Jul 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Akai#4950", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771338361340 + }, + "472100260613718018": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as anka#7782", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769420919139 + }, + "472730563531505675": { + "badges": [ + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 17 Nov 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as WcFrischesiegel#6639", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769416117879 + }, + "475233274327859200": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 11 Jun 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 11 Jun 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Bergsteigermüsli#3470", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320962768 + }, + "476089123241721866": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 4 Mar 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 4 Mar 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770801887479 + }, + "476407651761389569": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 9 Feb 2025. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl3", + "description": "Server Boosting since 27 Aug 2025", + "icon": "72bed924410c304dbe3d00a6e593ff59", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Debgac#0425", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770852766507 + }, + "476716255894568973": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Inseminse#5363", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771321013648 + }, + "477559509221376021": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 22 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as WattVolt | Corvin#6268", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770102880172 + }, + "478864983795695618": { + "badges": [], + "date": 1769415900971 + }, + "478947346080727060": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as chibi_yukiToro#6316", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770891850383 + }, + "479395128331599874": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as achan#8983", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771345414718 + }, + "480685813634564098": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as JoJo#3282", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343655434 + }, + "481059616634044426": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Lykano#7970", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105365632 + }, + "483403756764004352": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 31 May 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 31 May 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Marcel V.#8171", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320913498 + }, + "485142656352256020": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Nosferaja#3296", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343828443 + }, + "485184046956281857": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Schülladeus#3092", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485900147 + }, + "485472262951141387": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Denjin#5967", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770852895407 + }, + "488763085574766592": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as TheMorph#5507", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485912263 + }, + "489524228778098690": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Kassi#8729", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485908552 + }, + "490176597899476992": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Lini3001#3821", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320916965 + }, + "490180402737184788": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 13 May 2025", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Kevitiv#6266", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320929430 + }, + "490816072606285824": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 17 Oct 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Murnum#2391", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105356773 + }, + "491921992673525761": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Koko#0958", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770896563388 + }, + "491945821240033282": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 7 Nov 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 17 Nov 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Bonte#1608", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769353853290 + }, + "492031054614495233": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as DarkTyrael1987#1058", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770233219806 + }, + "494945750942220298": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 17 Feb 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 18 Feb 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Narocken#8066", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343827193 + }, + "495949805978386432": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Lotsch#2893", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770298222785 + }, + "496637408247545867": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 29 Jan 2023. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 3 Apr 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1770877989970 + }, + "496704657423597618": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Claudine de Marne#8523", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485910184 + }, + "497132394789470218": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Guffelinchen#8962", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320913163 + }, + "497318325467086878": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 18 Jan 2021. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 18 Jan 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as iAeglos | iAringlos#9343", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771335951173 + }, + "500972491775148032": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 22 Nov 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 22 Nov 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Czechman#1820", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338256371 + }, + "503261744441851921": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338243682 + }, + "505063643646525462": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 23 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl7", + "description": "Server Boosting since 5 Sept 2024", + "icon": "cb3ae83c15e970e8f3d410bc62cb8b99", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Gina#9065", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294640216 + }, + "506121925299404810": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770825558330 + }, + "506197905288462358": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Tyran#2647", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770367452376 + }, + "507561867732975664": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 3 Sept 2024", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771524173498 + }, + "507888403996475392": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Rases#4467", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771522219272 + }, + "510553306448396299": { + "badges": [ + { + "id": "premium_tenure_72_month_v2", + "description": "Earned on 18 Jan 2019. 6+ years: Opal", + "icon": "5b154df19c53dce2af92c9b61e6be5e2", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 10 Dec 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Fłåúšçhïï#0666", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771524178319 + }, + "513071726196817920": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Böbel der Komplexe#6409", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338265087 + }, + "513363345802395659": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Jan1Kids#1211", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343815513 + }, + "514055419912519701": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Raven77#7868", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105351629 + }, + "514510752619429888": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as --Hugo--#3287", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770299147937 + }, + "516244810495950851": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 28 Dec 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 6 Dec 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as PipeL1n3#3978", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320915238 + }, + "517652348932325377": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 4 Feb 2025", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Das Superheldenland / Igamaro13#8807", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770451958938 + }, + "517718328807260171": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 20 May 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 29 May 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770654806713 + }, + "519594050634907663": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 29 Sept 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 11 Jun 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as xD_tifa_#8682", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770881323852 + }, + "519826357946023956": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 26 Jan 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as Dome007#9260", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771320923067 + }, + "519940317252681728": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as Ossi#1093", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770366448880 + }, + "522546373585928214": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Clubfreak#4999", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770860634395 + }, + "523891778521268224": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Cookie44_#8952", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770451954942 + }, + "525304939325554698": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Pitbull_1989#2774", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770710027725 + }, + "531144470024945666": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Jojomania#4950", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770905878051 + }, + "533357828048683029": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as muemla#1688", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770630488664 + }, + "533770293659959307": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 2 Apr 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 2 Apr 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as xHappyx96#3973", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294766690 + }, + "533938764989333515": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Skyliah_Calidra#6250", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770801902224 + }, + "533948342741237761": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 9 Oct 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as fk1981no#2040", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343670350 + }, + "536152651096653845": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 6 Dec 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 26 Dec 2025", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as xinlu#3170", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320956334 + }, + "536624371968770048": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 10 Feb 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 10 Feb 2026", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as DerWahreGaukler#6085", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338269422 + }, + "536991182035746816": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + }, + { + "id": "automod", + "description": "Uses AutoMod", + "icon": "f2459b691ac7453ed6039bbcfaccbfcd" + } + ], + "date": 1771320951258 + }, + "537624073853730816": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Devil-Shirley#9704", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769416150484 + }, + "539149022523555870": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Mäggi#4729", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485912716 + }, + "541680469452914717": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 23 May 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1771485213278 + }, + "543771180084232202": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 13 May 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770371663058 + }, + "544578772062568458": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Reig#1924", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770968372612 + }, + "544584814729101331": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Anouk#8089", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771524170466 + }, + "545301040841621524": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771602878740 + }, + "547066168603836436": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 18 Aug 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 3 Nov 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105397198 + }, + "547144737858256926": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as agu#8338", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485893213 + }, + "549210147897999360": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as AdvocadoDiaboli#8338", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338345021 + }, + "552400898056388649": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 16 Nov 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 29 Nov 2025", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as LilMareWolf#1945", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769513295037 + }, + "553144882575835138": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 29 Mar 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 4 Aug 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as iYo#1234", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294641875 + }, + "557628352828014614": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1771320946149 + }, + "557634654501470248": { + "badges": [], + "date": 1771485600281 + }, + "558247679847694336": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as soul#7223", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771581064185 + }, + "559336123227439114": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 19 Nov 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 19 Nov 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1771338262671 + }, + "561832046209531914": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as nowaczuk#1597", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770450531172 + }, + "562910009072156682": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Hitsuji81#7476", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343671625 + }, + "563723329136099347": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Demon6#1302", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770451960164 + }, + "563771628274712576": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as asdoo357#2806", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770696669358 + }, + "565534090082779136": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Zombiefire#8981", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770680623771 + }, + "566361701893537793": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as [UTH] Rakras#0379", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485218641 + }, + "568406904846680065": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as brickschmidis#5641", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105404937 + }, + "569867949146374146": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 19 Feb 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 19 Feb 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Baertier#1168", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320917189 + }, + "572935595790434313": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as theLuckyMage#6053", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770635085328 + }, + "574900528866394125": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 19 Apr 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as ginamie#4888", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320955090 + }, + "576430534146654208": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 23 Jan 2025. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl2", + "description": "Server Boosting since 28 Nov 2025", + "icon": "0e4080d1d333bc7ad29ef6528b6f2fb7", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as B0rch4rd#7176", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771343667836 + }, + "579392725003141130": { + "badges": [ + { + "id": "premium_tenure_6_month_v2", + "description": "Earned on 24 Jul 2025. 6 months: Gold", + "icon": "2895086c18d5531d499862e41d1155a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 24 Jul 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1770825057432 + }, + "582566303051546624": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 20 Sept 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 20 Sept 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Ecki#1002", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771321001791 + }, + "585565617600593922": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Crunchy-Smitty#2955", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770295683962 + }, + "588778279029309462": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Bertipro#3709", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771524183365 + }, + "592383097832472576": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 1 Nov 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 13 Dec 2025", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as [ZZ]Altwein#3824", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770801889288 + }, + "592622882593439754": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as M.Rose#2502", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485908912 + }, + "598197632396951573": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as benedikt#1255", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770894668187 + }, + "600352405204500519": { + "badges": [], + "date": 1770801891293 + }, + "607378669719257100": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as BadJokesBrian#4238", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770102927960 + }, + "607917992130510850": { + "date": 1770852864136 + }, + "608038800903372819": { + "badges": [ + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "legacy_username", + "description": "Originally known as xMauricefx#1899", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770370879088 + }, + "608045804913164348": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Nydieth#9843", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770654644003 + }, + "609059230477058048": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Ulrich / Dennis#1195", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485944014 + }, + "611238405526913025": { + "badges": [], + "date": 1770852770642 + }, + "611286506367352904": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as crazysimba#1679", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771581357511 + }, + "612036788659159050": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 22 Mar 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 1 Jun 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Kataa#2117", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771338248887 + }, + "615126745024167937": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Landgurke09#3578", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770825065273 + }, + "615856032685621279": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as saraahb1893#6844", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769350031446 + }, + "618849052070772776": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as viccitxd#5169", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770398457715 + }, + "618964064420233216": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as froschmaedchen#5686", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343654192 + }, + "620705605220302889": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as FortunaFrani#8656", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770877986140 + }, + "621707575565746236": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 20 Apr 2024", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Elli Kääsekuchen#9745", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771498903969 + }, + "623947385038700553": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as SpRaY Benzer#3509", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770383829928 + }, + "624993015580065822": { + "badges": [ + { + "id": "premium_tenure_3_month_v2", + "description": "Earned on 22 Aug 2025. 3 months: Silver", + "icon": "4514fab914bdbfb4ad2fa23df76121a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl3", + "description": "Server Boosting since 22 Aug 2025", + "icon": "72bed924410c304dbe3d00a6e593ff59", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Gryffindor.Black#7270", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770877988752 + }, + "626131350474326052": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769420897622 + }, + "627622604085133342": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Angrybirds5586#8470", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770541961953 + }, + "628207793886855178": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Gizmo#2350", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769420909143 + }, + "628940079913500703": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as argonius_draw#3733", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771344419717 + }, + "630459338535731230": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as TasticS#2202", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294640595 + }, + "630855607548772372": { + "badges": [], + "date": 1771321012321 + }, + "632236245673574400": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 7 Sept 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 18 Sept 2020", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as elricco#1978", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770801892647 + }, + "632565270971547650": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as AwakenVlad#4886", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769344543690 + }, + "632854990263681024": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Atesca100#6612", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485885741 + }, + "637337034121412663": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Schnitzelglator#3792", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105377716 + }, + "640503294451777539": { + "date": 1769531214666 + }, + "640943213934411809": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as Katlikesmilk#9426", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770710502791 + }, + "641987531864277002": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as conni#0373", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105357139 + }, + "642094960518365184": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 29 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 29 Jan 2026", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as redrum.sam#8122", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770852767748 + }, + "642427463703724032": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 27 May 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 27 May 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Mordred#9497", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338255089 + }, + "643479093471608851": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 29 Oct 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 15 Jul 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as PaterJudas#7290", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770367277489 + }, + "643522692305125406": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as NiQo#4132", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770365607894 + }, + "646058570080845824": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 9 Jun 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 10 Jun 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as L0st#6863", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771321022709 + }, + "650648738766913537": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as IroSerKKer#6611", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320926921 + }, + "651501611990122533": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as aise#6900", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105390803 + }, + "652801719411015720": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as MegaGengu#7045", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769415922588 + }, + "653209582923153419": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Stiles#5774", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771327056397 + }, + "654950684877389835": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Mjui#5703", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771497682020 + }, + "655079427687841808": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Fichtemopped#2384", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770365601201 + }, + "656621136808902656": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1770801890527 + }, + "657286999614554117": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Niko R#4209", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771491463916 + }, + "658320644236378124": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as ZicMorley#8414", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770273218640 + }, + "658727892368490497": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 26 Jul 2021", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770298539267 + }, + "664875548358606878": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as NIKI FOX#0001", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770640352729 + }, + "667780727382867968": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Geri Reifferscheid#0900", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771492781236 + }, + "668208956031893525": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as CrimsonDragon_King#1743", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770862954088 + }, + "668517102122172426": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 23 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "legacy_username", + "description": "Originally known as Kwisch#0835", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770441038653 + }, + "673206047099584536": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 26 Mar 2025", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Juki#7664", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320914168 + }, + "674303782393217048": { + "badges": [], + "date": 1770819920309 + }, + "676118320319037450": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Ares_lu#1071", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105398472 + }, + "679067972362567681": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as DejanVH/IainMacF#9412", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771487003781 + }, + "679349789892149248": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as MrFokZ#9348", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770852773130 + }, + "680116512992002078": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Horizon / Vanessa#2965", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485884419 + }, + "683653117228613704": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Vayda#1441", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770293811523 + }, + "687372357953650740": { + "badges": [ + { + "id": "premium_tenure_3_month_v2", + "description": "Earned on 5 Aug 2025. 3 months: Silver", + "icon": "4514fab914bdbfb4ad2fa23df76121a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 5 Aug 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1770294632573 + }, + "687966941062758413": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as elena.dmk#1756", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770622241906 + }, + "688211614645878819": { + "badges": [], + "date": 1771485899625 + }, + "689131383997988933": { + "badges": [], + "date": 1769105380516 + }, + "689376221976461312": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as AriMars#5864", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770627955959 + }, + "689582430620745865": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as MarkusDeChevalier#1969", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485886873 + }, + "689753281953661008": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 12 Jan 2021. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 21 Dec 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as NChiggi#5928", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771497682469 + }, + "689850906879197307": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Jan_#4653", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770975889337 + }, + "689949539968614510": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Kjael Halvarsson#4309", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771497671915 + }, + "689976767129059403": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as alva#3552", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770295510361 + }, + "690840071716339743": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Flossi#1020", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485893923 + }, + "691276664000348210": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Hürügürü#2704", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485887522 + }, + "691323980979765310": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770541957000 + }, + "691335281927913533": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as HerTinyPlanet#8158", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770995756226 + }, + "693497766109904937": { + "badges": [], + "date": 1771338258941 + }, + "694609644810928150": { + "badges": [ + { + "id": "premium_tenure_3_month_v2", + "description": "Earned on 13 Oct 2025. 3 months: Silver", + "icon": "4514fab914bdbfb4ad2fa23df76121a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl1", + "description": "Server Boosting since 2 Feb 2026", + "icon": "51040c70d4f20a921ad6674ff86fc95c", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as MidNight_Fox#1323", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770818978961 + }, + "695553516160417823": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as chaechae#9122", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105412810 + }, + "695610619298185218": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Shifty#2495", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485913515 + }, + "696095391476875424": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 6 Dec 2020. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 6 Dec 2020", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as FlashmobNbg#7894", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770294638806 + }, + "697828958900715530": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as borsti#9451", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485889508 + }, + "697955661924794439": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as 🗡shadowboss3496(moe)🛡#2736", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770022567434 + }, + "699884270587478026": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Eve#4144", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769420901417 + }, + "700058680619433996": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as plipusmaximus#5276", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771581019566 + }, + "700345448287961148": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Kenrik#6350", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105362933 + }, + "700420453239619634": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Nici#6625", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771602903049 + }, + "701708740604198912": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 5 Jul 2024", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as vany588#9715", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343658082 + }, + "701717748748910723": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Satan^^#3593", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769420896514 + }, + "701849787237924957": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as bs#7538", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485902154 + }, + "702437236901150721": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 27 Mar 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 8 Apr 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Junny#8462", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770365608597 + }, + "703990397298540685": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 26 Nov 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 29 Dec 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as 𝕾𝖈𝖎𝖗𝖔 𝕭𝖆𝖓𝖝#5687", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771320952571 + }, + "704703097225871500": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Majowa#2751", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343823455 + }, + "705719978489544714": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as AndyS#4562", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770804362001 + }, + "706101820623290458": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 18 Mar 2024", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as MintyJenny#6385", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771349471447 + }, + "708783942530105345": { + "badges": [ + { + "id": "premium_tenure_60_month_v2", + "description": "Earned on 9 Feb 2021. 5 years: Ruby", + "icon": "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 15 Jun 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as I'mTheSchivi#8218", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771344098526 + }, + "709475676444688495": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 24 Mar 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_3", + "description": "HypeSquad Balance", + "icon": "3aa41de486fa12454c3761e8e223442e" + }, + { + "id": "guild_booster_lvl7", + "description": "Server Boosting since 24 Aug 2024", + "icon": "cb3ae83c15e970e8f3d410bc62cb8b99", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770371048414 + }, + "710143171740565555": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Satinder#0862", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771487000013 + }, + "712075398095044640": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Trinity52#7104", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770892734852 + }, + "712337743765569616": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 6 Jan 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 6 Jan 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771665349399 + }, + "712651420494463009": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 10 Jun 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 25 Aug 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as michael_9x3#7004", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343822204 + }, + "714507508567506954": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as moonbunny83#9090", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770511996868 + }, + "715584649027649610": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as DrHardScop3#7573", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771321000366 + }, + "716390085896962058": { + "badges": [], + "date": 1770801898517 + }, + "716774730308386898": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 8 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770369642789 + }, + "718118222431518820": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Blackdragon#8812", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770381591538 + }, + "718459037678698626": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Twizzler#1031", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320948685 + }, + "718931311926182000": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Reika#9383", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771524189403 + }, + "721427002883833977": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Shinishi#4385", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770451957650 + }, + "722755079178027050": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105407426 + }, + "722814573362544772": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 23 Aug 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 7 Mar 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as snoks#4558", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771521373018 + }, + "722898361417465926": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Robi#8386", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485890717 + }, + "723576278745219092": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as BenneV#6450", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485900864 + }, + "724334890442817536": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Mija#3446", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770379032887 + }, + "727654046466441336": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as Kisumirika#9145", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771497680629 + }, + "727977783132946544": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Catheart_SH#2486", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771525090428 + }, + "728015254851944499": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as CassandraLionGirl#0334", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771345228185 + }, + "728561569671151667": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 20 Nov 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1770877991211 + }, + "732331932221833226": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as janina.tagini#2876", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770368060343 + }, + "73527438759432192": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Tyren#1958", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485945219 + }, + "739222391657463838": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as HexerWitchPro#7799", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770294635838 + }, + "743473490144788490": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770913323860 + }, + "744196119097245818": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as pattenzione#3676", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102924919 + }, + "745369629014097961": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 25 Jan 2024. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 17 Jul 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Kaisaheart#8556", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343819651 + }, + "745718808827658391": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as BatmanvsJoker28#8067", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485596493 + }, + "747208993537261742": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Wenkayni#7819", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102936505 + }, + "749719802322354279": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Bella77#8835", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771524896579 + }, + "752731102883610624": { + "badges": [], + "date": 1770877982484 + }, + "752942235296727080": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 28 Jul 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770102894187 + }, + "756960382421172314": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as Koala4life#0936", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338252558 + }, + "758382694534217768": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as SammydaLuchs#4368", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485221105 + }, + "758965440768245760": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as boaxba#0043", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771489783552 + }, + "760704203014012948": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as C-378#2409", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770417329383 + }, + "762339909905416242": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 3 Jul 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 3 Jul 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as tanzikatz#3316", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771320944833 + }, + "763033775189524502": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Johanna#4594", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320928152 + }, + "764571275724587018": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 19 Jan 2023. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 22 Feb 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as voodja#8008", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770802203607 + }, + "765627881375924244": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Andurias#8185", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485896447 + }, + "766366140833988609": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Jumangy#2366", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771321030330 + }, + "769594757726076959": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as AutumnLeaf#8278", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770710498972 + }, + "770715760820617226": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Benjmini#1178", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771344050944 + }, + "770730940841852968": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Brini | Elevea/Meriya | Thvndar#5378", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485905162 + }, + "771825823152865302": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 13 Mar 2022", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as antonny#0001", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771346643479 + }, + "772163364461346876": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 4 Apr 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 8 Jun 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Gullideckell#7695", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485599031 + }, + "773473325976256513": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Gold#6309", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770636033348 + }, + "773655133392732160": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320999153 + }, + "774593903797469185": { + "badges": [], + "date": 1770368290503 + }, + "779061645741260821": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Volle_Droschke#4806", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770651998883 + }, + "780216973014335510": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Miaterie#7786", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771522213951 + }, + "781542310751109170": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as lelemmi#9877", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771321027912 + }, + "782657370836828200": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as lydilove_#3768", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770365606606 + }, + "784507362899525672": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 27 Jul 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 27 Jul 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Sören.O#1577", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771526599758 + }, + "784915733037580328": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 28 Apr 2024", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as bgkatja#2746", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770541965877 + }, + "786695514455539743": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Maluri#2725", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343824744 + }, + "787162087922860032": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320961466 + }, + "790326764596756521": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as MadMax#4546", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770506030759 + }, + "791802315706728498": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as DonSkoliose#2143", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770387453012 + }, + "792820372969619456": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 20 Oct 2024", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Flying Tiger#4459", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485209512 + }, + "794296910629306381": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 12 Jun 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 20 Jun 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as karrrrrro#7777", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770375521701 + }, + "797552749109575680": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Chaotic_Frog3#0533", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770852769207 + }, + "801795827735592990": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as kathari_tv#6839", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770637673785 + }, + "807877396442251264": { + "badges": [ + { + "id": "premium_tenure_3_month_v2", + "description": "Earned on 10 Oct 2025. 3 months: Silver", + "icon": "4514fab914bdbfb4ad2fa23df76121a6", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Eric Blackwood#9114", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105416555 + }, + "807898986467033160": { + "badges": [], + "date": 1771486904422 + }, + "807976716365463573": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Kroschnaa#8326", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770877996220 + }, + "808151940050976799": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as $Tump!#5895", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769420918203 + }, + "812440667749875733": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Swamo#2351", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485904694 + }, + "815157329342365738": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as FenrylDalmor#0701", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485884678 + }, + "819215214913519646": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Argon_der_10.#1050", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770626219176 + }, + "820386247511769118": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sturmschein#3284", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771338277305 + }, + "821151310698643526": { + "badges": [], + "date": 1770365592342 + }, + "825071256864686170": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as () Vio ()#9268", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771487002548 + }, + "825381645875413012": { + "badges": [], + "date": 1770966381378 + }, + "825405598514151435": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 31 Jan 2025", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Laubi#3180", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771320933361 + }, + "825649953714995211": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 12 May 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1771324004238 + }, + "826135495599587369": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 26 Sept 2021. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 26 Sept 2021", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as MrKarsik#6750", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770294900757 + }, + "826174520158978078": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Jaytwo#1754", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769513291187 + }, + "826496804325031966": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Das_Weinhorn#8326", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343814266 + }, + "829019505496686634": { + "badges": [ + { + "id": "premium_tenure_36_month_v2", + "description": "Earned on 15 Mar 2022. 3 years: Emerald", + "icon": "11e2d339068b55d3a506cff34d3780f3", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 15 Mar 2022", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Chrissi#9538", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771487002125 + }, + "829040059930902619": { + "badges": [], + "date": 1770397301183 + }, + "830493247679758376": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sea of Gold#7425", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771524193156 + }, + "832877177485262850": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Vittel#0157", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105388241 + }, + "832999139444916274": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 21 Oct 2022", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1771485880850 + }, + "835984025520832513": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 14 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Akumano#7097", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343665297 + }, + "837426156210225273": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as nvanha#2909", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770036401587 + }, + "837708487160889355": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as nordlichtkind#8361", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771522218027 + }, + "838366746984972319": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Gentlegoth#8917", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771497685535 + }, + "838428629603844107": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Lizza#2064", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770133160240 + }, + "840871810324496444": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Daruks Schield#6016", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769105420329 + }, + "841298357241774090": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as kmeetsr#6538", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343820973 + }, + "842436712512225300": { + "badges": [], + "date": 1771321025292 + }, + "844980830488887326": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Blackpit1988#4845", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770918432530 + }, + "846463985888657428": { + "badges": [ + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + } + ], + "date": 1771485227778 + }, + "849213252088365077": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Paavook#4162", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771505033485 + }, + "850056046184628274": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Speedy#8943", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105409956 + }, + "851940080310157323": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 27 Jan 2026", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as WitchHannah#3027", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770524104684 + }, + "852595535907258398": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Tabsi12345#5414", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771522224403 + }, + "857323843047784469": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as HeNko#8508", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771344975056 + }, + "858095140312580096": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as tokudapo#3445", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771666571624 + }, + "860113189646761995": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as David Richter#3435", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770865310446 + }, + "860226030442774528": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Katja_on#1681", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770654650181 + }, + "861383811382902784": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as lyn_web#4328", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771322172851 + }, + "862025093973278730": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as LordDivine#6705", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769182979995 + }, + "862401172747714570": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as dennisgomez#3771", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105414044 + }, + "868979987053699112": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 1 Nov 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as nasaxi97#6715", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770371970498 + }, + "86920406476292096": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1770801895152 + }, + "869352952031244339": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Phloxy#1208", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770460098851 + }, + "879109948246728834": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as miracle_252#1611", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1769105369544 + }, + "882201873824743444": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 15 Nov 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as binjason#2815", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771338314173 + }, + "883822837041889360": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 6 Dec 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + } + ], + "date": 1771320947395 + }, + "884081499085873173": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Vampir_Nico_Robin#1114", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770456853774 + }, + "884895592336211991": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as joe_cool91#7925", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771321017449 + }, + "885210705714876446": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Christoph#4521", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343673385 + }, + "886222818050719775": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Caillean#1422", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485906453 + }, + "886959143481638932": { + "badges": [], + "date": 1769105421537 + }, + "892483403981684818": { + "badges": [ + { + "id": "premium_tenure_1_month_v2", + "description": "Earned on 1 Dec 2025. 1 month: Bronze", + "icon": "4f33c4a9c64ce221936bd256c356f91f", + "link": "https://discord.com/settings/premium" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770102924095 + }, + "896168223395942410": { + "badges": [], + "date": 1770541958256 + }, + "896460139345870902": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 29 Sept 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Jess Tiger#1339", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771343818401 + }, + "897608627760795659": { + "badges": [], + "date": 1771320996587 + }, + "899593521210744914": { + "badges": [ + { + "id": "hypesquad_house_2", + "description": "HypeSquad Brilliance", + "icon": "011940fd013da3f7fb926e4a1cd2e618" + }, + { + "id": "legacy_username", + "description": "Originally known as Breakez#6625", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771338263900 + }, + "911659638540349441": { + "badges": [ + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770541959497 + }, + "914584315394924565": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 16 Mar 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl9", + "description": "Server Boosting since 16 Mar 2023", + "icon": "ec92202290b48d0879b7413d2dde3bab", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Pink Smoothie#7820", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1770902016115 + }, + "915140811556003850": { + "badges": [ + { + "id": "hypesquad_house_1", + "description": "HypeSquad Bravery", + "icon": "8a88d63823d8a71cd5e390baa45efa02" + }, + { + "id": "legacy_username", + "description": "Originally known as Designation_N#4643", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + }, + { + "id": "orb_profile_badge", + "description": "Collected the Orb Profile Badge", + "icon": "83d8a1eb09a8d64e59233eec5d4d5c2d" + } + ], + "date": 1771338691988 + }, + "916581422901395486": { + "badges": [], + "date": 1771320960655 + }, + "917449076327473203": { + "badges": [ + { + "id": "premium", + "description": "Subscriber since 16 Apr 2023", + "icon": "2ba85e8026a8614b640c2837bcdfe21b", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as SK Spezial#0083", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485602820 + }, + "928364505547804672": { + "badges": [ + { + "id": "premium_tenure_24_month_v2", + "description": "Earned on 16 Apr 2023. 2 years: Diamond", + "icon": "0d61871f72bb9a33a7ae568c1fb4f20a", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl4", + "description": "Server Boosting since 27 Jun 2025", + "icon": "df199d2050d3ed4ebf84d64ae83989f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Magda#8422", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770102918268 + }, + "935594519389421599": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Sandymental#2725", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770893119895 + }, + "935927355103383632": { + "badges": [], + "date": 1771343825959 + }, + "936763113557094470": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Neko10 Stream#0158", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771338273241 + }, + "936929561302675456": { + "badges": [ + { + "id": "bot_commands", + "description": "Supports Commands", + "icon": "6f9e37f9029ff57aef81db857890005e", + "link": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge" + } + ], + "date": 1771320953868 + }, + "939872721796489276": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Kokser#0400", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1770877993783 + }, + "948286755092701234": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as analogmensch#2844", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485879613 + }, + "959085350414864415": { + "badges": [ + { + "id": "premium_tenure_12_month_v2", + "description": "Earned on 26 Jul 2024. 1 year: Platinum", + "icon": "0334688279c8359120922938dcb1d6f8", + "link": "https://discord.com/settings/premium" + }, + { + "id": "guild_booster_lvl8", + "description": "Server Boosting since 26 Jul 2024", + "icon": "7142225d31238f6387d9f09efaa02759", + "link": "https://discord.com/settings/premium" + }, + { + "id": "legacy_username", + "description": "Originally known as Penpai#5069", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771485212038 + }, + "961702813015113833": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Burninkoala#6957", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1769344546367 + }, + "972236524089401444": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Lord Nightkan#1813", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770905144082 + }, + "974610260289523762": { + "badges": [], + "date": 1771338244945 + }, + "977692580798165042": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Arjuna#8933", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771349470422 + }, + "980103328421777408": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as meko93#9254", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1771485216098 + }, + "980212308762591272": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as Vogel_018#6476", + "icon": "6de6d34650760ba5551a79732e98ed60" + }, + { + "id": "quest_completed", + "description": "Completed a Quest", + "icon": "7d9ae358c8c5e118768335dbe68b4fb8", + "link": "https://discord.com/discovery/quests" + } + ], + "date": 1771341558490 + }, + "996516239558062150": { + "badges": [ + { + "id": "legacy_username", + "description": "Originally known as White Wolf#3555", + "icon": "6de6d34650760ba5551a79732e98ed60" + } + ], + "date": 1770542491302 + } + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ShowBadgesInChat.plugin.js b/dotfiles/.config/BetterDiscord/plugins/ShowBadgesInChat.plugin.js new file mode 100644 index 0000000..a33310c --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ShowBadgesInChat.plugin.js @@ -0,0 +1,411 @@ +/** + * @name ShowBadgesInChat + * @author DevilBro + * @authorId 278543574059057154 + * @version 2.1.5 + * @description Displays Badges (Nitro, Hypesquad, etc...) in the Chat/MemberList/DMList + * @invite Jx3TjNS + * @donate https://www.paypal.me/MircoWittrien + * @patreon https://www.patreon.com/MircoWittrien + * @website https://mwittrien.github.io/ + * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/ShowBadgesInChat/ + * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/ShowBadgesInChat/ShowBadgesInChat.plugin.js + */ + +module.exports = (_ => { + const changeLog = { + + }; + + return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class { + constructor (meta) {for (let key in meta) this[key] = meta[key];} + getName () {return this.name;} + getAuthor () {return this.author;} + getVersion () {return this.version;} + getDescription () {return `The Library Plugin needed for ${this.name} is missing. Open the Plugin Settings to download it. \n\n${this.description}`;} + + downloadLibrary () { + BdApi.Net.fetch("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js").then(r => { + if (!r || r.status != 200) throw new Error(); + else return r.text(); + }).then(b => { + if (!b) throw new Error(); + else return require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.UI.showToast("Finished downloading BDFDB Library", {type: "success"})); + }).catch(error => { + BdApi.UI.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library"); + }); + } + + load () { + if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []}); + if (!window.BDFDB_Global.downloadModal) { + window.BDFDB_Global.downloadModal = true; + BdApi.UI.showConfirmationModal("Library Missing", `The Library Plugin needed for ${this.name} is missing. Please click "Download Now" to install it.`, { + confirmText: "Download Now", + cancelText: "Cancel", + onCancel: _ => {delete window.BDFDB_Global.downloadModal;}, + onConfirm: _ => { + delete window.BDFDB_Global.downloadModal; + this.downloadLibrary(); + } + }); + } + if (!window.BDFDB_Global.pluginQueue.includes(this.name)) window.BDFDB_Global.pluginQueue.push(this.name); + } + start () {this.load();} + stop () {} + getSettingsPanel () { + let template = document.createElement("template"); + template.innerHTML = `
The Library Plugin needed for ${this.name} is missing.\nPlease click Download Now to install it.
`; + template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary); + return template.content.firstElementChild; + } + } : (([Plugin, BDFDB]) => { + var _this; + var badgeConfigs = {}, loadedUsers = {}, queuedInstances = {}, requestQueue = {queue: [], timeout: null, id: null}, cacheTimeout; + + const places = ["chat", "memberList", "dmsList"]; + + const userBadgeFlagNameMap = { + "BOT_HTTP_INTERACTIONS": "bot_commands", + "HYPESQUAD_ONLINE_HOUSE_1": "hypesquad_house_1", + "HYPESQUAD_ONLINE_HOUSE_2": "hypesquad_house_2", + "HYPESQUAD_ONLINE_HOUSE_3": "hypesquad_house_3", + "bot_commands": "BOT_HTTP_INTERACTIONS", + "hypesquad_house_1": "HYPESQUAD_ONLINE_HOUSE_1", + "hypesquad_house_2": "HYPESQUAD_ONLINE_HOUSE_2", + "hypesquad_house_3": "HYPESQUAD_ONLINE_HOUSE_3" + }; + + const badges = {}; + + return class ShowBadgesInChat extends Plugin { + onLoad () { + _this = this; + + this.modulePatches = { + before: [ + "MessageUsername" + ], + after: [ + "NameContainerDecorators", + "PrivateChannel", + "UserBadges" + ] + }; + + for (let key in BDFDB.DiscordConstants.UserBadges) { + let basicKey = key.replace(/_lvl\d+|_tenure_\d+_month_v2/g, ""); + if (!badges[basicKey]) badges[basicKey] = {value: true, keys: []}; + badges[basicKey].keys.push(key); + } + + this.css = ` + ${BDFDB.dotCN._showbadgesinchatbadges} { + display: inline-flex !important; + justify-content: center; + align-items: center; + flex-wrap: nowrap; + position: relative; + margin: 0 0 0 4px; + padding: 0; + user-select: none; + pointer-events: none !important; + } + ${BDFDB.dotCN._showbadgesinchatbadges} > * { + margin: 0; + } + ${BDFDB.dotCNS._showbadgesinchatbadges + BDFDB.dotCN.userbadge} { + display: flex; + justify-content: center; + align-items: center; + } + ${BDFDB.dotCNS._showbadgesinchatbadges + BDFDB.dotCN.userbadge + BDFDB.dotCN._showbadgesinchatindicator}::before { + display: none; + } + ${BDFDB.dotCNS._showbadgesinchatbadgessettings + BDFDB.dotCN.userbadge} { + width: 24px !important; + height: 20px !important; + } + ${BDFDB.dotCN.memberpremiumicon} { + display: none; + } + ${BDFDB.dotCNS._showbadgesinchatbadges + BDFDB.dotCN.memberpremiumicon} { + display: block; + position: static; + margin: 0; + } + ${BDFDB.dotCNS.messageheadertext + BDFDB.dotCN._showbadgesinchatbadgeschat} { + top: 0.2rem; + } + ${BDFDB.dotCNS.messagerepliedmessage + BDFDB.dotCN._showbadgesinchatbadgeschat} { + top: 0; + } + ${BDFDB.dotCN.messageheadertext}:has(${BDFDB.dotCN._showbadgesinchatbadges}) ${BDFDB.dotCN.bottag} { + top: 0.4rem; + } + ${BDFDB.dotCNS.messagecompact + BDFDB.dotCN.messageusername} ~ ${BDFDB.dotCN._showbadgesinchatbadges}, + ${BDFDB.dotCNS.messagerepliedmessage + BDFDB.dotCN.messageusername} ~ ${BDFDB.dotCN._showbadgesinchatbadges} { + text-indent: 0; + } + ${BDFDB.dotCNS.messagerepliedmessage + BDFDB.dotCN.messageusername} ~ ${BDFDB.dotCN._showbadgesinchatbadges} { + margin-left: 0; + margin-right: .25rem; + } + + ${BDFDB.dotCN._showbadgesinchatbadgessettings} { + color: var(--text-strong); + } + ${BDFDB.dotCN._showbadgesinchatbadgessettings} * { + cursor: default; + } + ${BDFDB.dotCN._showbadgesinchatbadgessettings}:last-child { + margin-right: 8px; + } + ${BDFDB.dotCN._showbadgesinchatbadges} .bd-profile-badge { + height: 15px; + } + ${BDFDB.dotCN._showbadgesinchatbadgeschat} .bd-profile-badge { + position: relative; + top: -1px; + } + ${BDFDB.dotCN._showbadgesinchatbadgesmemberlist} .bd-profile-badge { + display: none; + } + `; + } + + onStart () { + queuedInstances = {}, loadedUsers = {}; + requestQueue = {queue: [], timeout: null, id: null}; + + badgeConfigs = BDFDB.DataUtils.load(this, "badgeConfigs"); + for (let key in badges) { + if (!badgeConfigs[key]) badgeConfigs[key] = {}; + for (let key2 of places) if (badgeConfigs[key][key2] == undefined) badgeConfigs[key][key2] = true; + badgeConfigs[key].key = key; + } + + let badgeCache = BDFDB.DataUtils.load(this, "badgeCache"); + if (badgeCache) { + let now = (new Date()).getTime(), month = 1000*60*60*24*30; + for (let id in badgeCache) { + if (now - badgeCache[id].date > month) delete badgeCache[id]; + else loadedUsers[id] = badgeCache[id]; + } + BDFDB.DataUtils.save(badgeCache, this, "badgeCache"); + } + + const processUser = (id, data) => { + loadedUsers[id] = {} + loadedUsers[id].badges = data.badges; + loadedUsers[id].date = (new Date()).getTime(); + + BDFDB.TimeUtils.clear(cacheTimeout); + cacheTimeout = BDFDB.TimeUtils.timeout(_ => BDFDB.DataUtils.save(loadedUsers, this, "badgeCache"), 5000); + + if (requestQueue.id && requestQueue.id == id) { + BDFDB.ReactUtils.forceUpdate(queuedInstances[requestQueue.id]); + delete queuedInstances[requestQueue.id]; + requestQueue.id = null; + BDFDB.TimeUtils.timeout(_ => this.runQueue(), 1000); + } + }; + BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.DispatchApiUtils, "dispatch", {after: e => { + if (BDFDB.ObjectUtils.is(e.methodArguments[0]) && e.methodArguments[0].type == "USER_PROFILE_FETCH_FAILURE" && e.methodArguments[0].userId) { + const user = BDFDB.LibraryStores.UserStore.getUser(e.methodArguments[0].userId); + if (user && !loadedUsers[user.id]) processUser(e.methodArguments[0].userId, {user: user || {}, flags: user ? user.publicFlags : 0}); + } + else if (BDFDB.ObjectUtils.is(e.methodArguments[0]) && e.methodArguments[0].type == "USER_PROFILE_FETCH_SUCCESS") { + let userProfile = e.methodArguments[0].userProfile || e.methodArguments[0]; + if (userProfile && userProfile.user) processUser(userProfile.user.id, userProfile); + } + }}); + + this.forceUpdateAll(); + } + + onStop () { + BDFDB.TimeUtils.clear(requestQueue.timeout); + + this.forceUpdateAll(); + } + + getSettingsPanel (collapseStates = {}) { + let settingsPanel; + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, { + collapseStates: collapseStates, + children: _ => { + let settingsItems = []; + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormTitle.Title, { + className: BDFDB.disCN.marginbottom4, + tag: BDFDB.LibraryComponents.FormTitle.Tags.H3, + children: "Show Badges in" + })); + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsList, { + settings: places, + data: Object.keys(badges).filter(n => n.indexOf("BOT_") != 0).map(key => badgeConfigs[key]), + noRemove: true, + renderLabel: (cardData, instance) => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, { + onClick: _ => { + for (let settingId of places) badgeConfigs[cardData.key][settingId] = true; + BDFDB.DataUtils.save(badgeConfigs, this, "badgeConfigs"); + BDFDB.ReactUtils.forceUpdate(instance); + this.SettingsUpdated = true; + }, + onContextMenu: _ => { + for (let settingId of places) badgeConfigs[cardData.key][settingId] = false; + BDFDB.DataUtils.save(badgeConfigs, this, "badgeConfigs"); + BDFDB.ReactUtils.forceUpdate(instance); + this.SettingsUpdated = true; + }, + children: cardData.key.split("_").map(n => BDFDB.StringUtils.upperCaseFirstChar(n.toLowerCase())).join(" ") + }) + }), + badges[cardData.key] && badges[cardData.key].keys.map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.UserBadges, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN._showbadgesinchatbadges, BDFDB.disCN._showbadgesinchatbadgessettings), + place: "settings", + custom: true, + badges: [{id: key, icon: BDFDB.DiscordConstants.UserBadges[key]}] + })) + ] + }), + onHeaderClick: (settingId, instance) => { + for (let key in badgeConfigs) badgeConfigs[key][settingId] = true; + BDFDB.DataUtils.save(badgeConfigs, this, "badgeConfigs"); + BDFDB.ReactUtils.forceUpdate(instance); + this.SettingsUpdated = true; + }, + onHeaderContextMenu: (settingId, instance) => { + for (let key in badgeConfigs) badgeConfigs[key][settingId] = false; + BDFDB.DataUtils.save(badgeConfigs, this, "badgeConfigs"); + BDFDB.ReactUtils.forceUpdate(instance); + this.SettingsUpdated = true; + }, + onCheckboxChange: (value, instance) => { + badgeConfigs[instance.props.cardId][instance.props.settingId] = value; + BDFDB.DataUtils.save(badgeConfigs, this, "badgeConfigs"); + this.SettingsUpdated = true; + } + })); + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Button", + color: BDFDB.LibraryComponents.Button.Colors.RED, + label: "Reset cached Badge Data", + onClick: _ => BDFDB.ModalUtils.confirm(this, "Are you sure you want to reset the Badge Cache? This will force all Badges to rerender.", _ => { + BDFDB.DataUtils.remove(this, "badgeCache"); + this.SettingsUpdated = true; + }), + children: BDFDB.LanguageUtils.LanguageStrings.RESET + })); + + return settingsItems; + } + }); + } + + onSettingsClosed () { + if (this.SettingsUpdated) { + delete this.SettingsUpdated; + this.forceUpdateAll(); + } + } + + forceUpdateAll () { + BDFDB.PatchUtils.forceAllUpdates(this); + BDFDB.MessageUtils.rerenderAll(); + } + + processMessageUsername (e) { + if (!e.instance.props.message) return; + const author = e.instance.props.userOverride || e.instance.props.message.author; + let index = e.instance.props.compact ? 1 : 0; + if (!BDFDB.ArrayUtils.is(e.instance.props.decorations[index])) e.instance.props.decorations[index] = [e.instance.props.decorations[index]].filter(n => n); + this.injectBadges(e.instance.props.decorations[index], author, (BDFDB.LibraryStores.ChannelStore.getChannel(e.instance.props.message.channel_id) || {}).guild_id, "chat"); + } + + processNameContainerDecorators (e) { + if (!e.instance.props.user) return; + this.injectBadges(e.returnvalue.props.children, e.instance.props.user, BDFDB.LibraryStores.SelectedGuildStore.getGuildId(), "memberList"); + } + + processPrivateChannel (e) { + if (!e.instance.props.user) return; + let wrapper = e.returnvalue && e.returnvalue.props.children && e.returnvalue.props.children.props && typeof e.returnvalue.props.children.props.children == "function" ? e.returnvalue.props.children : e.returnvalue; + if (typeof wrapper.props.children == "function") { + let childrenRender = wrapper.props.children; + wrapper.props.children = BDFDB.TimeUtils.suppress((...args) => { + let children = childrenRender(...args); + this._processPrivateChannel(e.instance, children); + return children; + }, "Error in Children Render of PrivateChannel!", this); + } + else this._processPrivateChannel(e.instance, wrapper); + } + + _processPrivateChannel (instance, returnvalue, a) { + const wrapper = returnvalue.props.decorators ? returnvalue : BDFDB.ReactUtils.findChild(returnvalue, {props: ["decorators"]}) || returnvalue; + if (!wrapper) return; + wrapper.props.decorators = [wrapper.props.decorators].flat(10); + this.injectBadges(wrapper.props.decorators, instance.props.user, null, "dmsList"); + } + + processUserBadges (e) { + if (!e.instance.props.custom) return; + let filter = e.instance.props.place != "settings"; + for (let i in e.returnvalue.props.children) if (e.returnvalue.props.children[i]) { + let keyName = filter && Object.keys(badges).find(n => badges[n].keys.includes(e.returnvalue.props.children[i].key.split("-")[0])); + if (keyName && badgeConfigs[keyName] && !badgeConfigs[keyName][e.instance.props.place]) e.returnvalue.props.children[i] = null; + else if (typeof e.returnvalue.props.children[i].props.children == "function" && e.returnvalue.props.children[i].props.text) { + e.returnvalue.props.children[i] = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, e.returnvalue.props.children[i].props); + } + } + if (!e.returnvalue.props.children.filter(n => n).length) return null; + } + + injectBadges (children, user, guildId, place) { + if (!BDFDB.ArrayUtils.is(children) || !user || user.isNonUserBot()) return; + if (!loadedUsers[user.id] || !loadedUsers[user.id].badges || ((new Date()).getTime() - loadedUsers[user.id].date >= 1000*60*60*24*7)) { + queuedInstances[user.id] = [].concat(queuedInstances[user.id]).filter(n => n); + if (requestQueue.queue.indexOf(user.id) == -1) requestQueue.queue.push(user.id); + this.runQueue(); + } + children.push(BDFDB.ReactUtils.createElement(class extends BDFDB.ReactUtils.Component { + render() { + if (!loadedUsers[user.id] || !loadedUsers[user.id].badges || ((new Date()).getTime() - loadedUsers[user.id].date >= 1000*60*60*24*7)) { + queuedInstances[user.id] = [].concat(queuedInstances[user.id]).filter(n => n); + if (queuedInstances[user.id].indexOf(this) == -1) queuedInstances[user.id].push(this); + return null; + } + else return BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.UserBadges, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN._showbadgesinchatbadges, BDFDB.disCN[`_showbadgesinchatbadges${place.toLowerCase()}`]), + place: place, + custom: true, + badges: loadedUsers[user.id].badges + }); + } + }, {}, true)); + } + + runQueue () { + if (!requestQueue.id) { + let id = requestQueue.queue.shift(); + if (id) { + requestQueue.id = id; + BDFDB.TimeUtils.clear(requestQueue.timeout); + requestQueue.timeout = BDFDB.TimeUtils.timeout(_ => { + requestQueue.id = null; + this.runQueue(); + }, 30000); + + BDFDB.LibraryModules.UserProfileUtils.fetchProfile(id); + } + } + } + }; + })(window.BDFDB_Global.PluginUtils.buildPlugin(changeLog)); +})(); diff --git a/dotfiles/.config/BetterDiscord/plugins/ShowConnections.config.json b/dotfiles/.config/BetterDiscord/plugins/ShowConnections.config.json new file mode 100755 index 0000000..9a96cc7 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ShowConnections.config.json @@ -0,0 +1,43 @@ +{ + "all": { + "connections": { + "amazon-music": true, + "battlenet": true, + "bluesky": true, + "bungie": true, + "crunchyroll": true, + "domain": true, + "ebay": true, + "epicgames": true, + "facebook": true, + "github": true, + "instagram": true, + "leagueoflegends": true, + "mastodon": true, + "meta_quest_or_horizon": true, + "paypal": true, + "playstation": true, + "playstation-stg": true, + "reddit": true, + "riotgames": true, + "roblox": true, + "samsung": true, + "skype": true, + "spotify": true, + "steam": true, + "tiktok": true, + "twitch": true, + "twitter_legacy": true, + "twitter": true, + "xbox": true, + "youtube": true + }, + "general": { + "useColoredIcons": false, + "useColoredTooltips": false, + "showDetails": true, + "showVerifiedBadge": true, + "openWebpage": true + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ShowConnections.plugin.js b/dotfiles/.config/BetterDiscord/plugins/ShowConnections.plugin.js new file mode 100755 index 0000000..79be29c --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ShowConnections.plugin.js @@ -0,0 +1,269 @@ +/** + * @name ShowConnections + * @author DevilBro + * @authorId 278543574059057154 + * @version 1.3.2 + * @description Shows the connected Accounts of a User in the UserPopout + * @invite Jx3TjNS + * @donate https://www.paypal.me/MircoWittrien + * @patreon https://www.patreon.com/MircoWittrien + * @website https://mwittrien.github.io/ + * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/ShowConnections/ + * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/ShowConnections/ShowConnections.plugin.js + */ + +module.exports = (_ => { + const changeLog = { + + }; + + return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class { + constructor (meta) {for (let key in meta) this[key] = meta[key];} + getName () {return this.name;} + getAuthor () {return this.author;} + getVersion () {return this.version;} + getDescription () {return `The Library Plugin needed for ${this.name} is missing. Open the Plugin Settings to download it. \n\n${this.description}`;} + + downloadLibrary () { + BdApi.Net.fetch("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js").then(r => { + if (!r || r.status != 200) throw new Error(); + else return r.text(); + }).then(b => { + if (!b) throw new Error(); + else return require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.UI.showToast("Finished downloading BDFDB Library", {type: "success"})); + }).catch(error => { + BdApi.UI.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library"); + }); + } + + load () { + if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []}); + if (!window.BDFDB_Global.downloadModal) { + window.BDFDB_Global.downloadModal = true; + BdApi.UI.showConfirmationModal("Library Missing", `The Library Plugin needed for ${this.name} is missing. Please click "Download Now" to install it.`, { + confirmText: "Download Now", + cancelText: "Cancel", + onCancel: _ => {delete window.BDFDB_Global.downloadModal;}, + onConfirm: _ => { + delete window.BDFDB_Global.downloadModal; + this.downloadLibrary(); + } + }); + } + if (!window.BDFDB_Global.pluginQueue.includes(this.name)) window.BDFDB_Global.pluginQueue.push(this.name); + } + start () {this.load();} + stop () {} + getSettingsPanel () { + let template = document.createElement("template"); + template.innerHTML = `
The Library Plugin needed for ${this.name} is missing.\nPlease click Download Now to install it.
`; + template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary); + return template.content.firstElementChild; + } + } : (([Plugin, BDFDB]) => { + var _this; + var loadedUsers, requestedUsers, queuedInstances; + + const UserConnectionsComponents = class UserConnections extends BdApi.React.Component { + render() { + if (!loadedUsers[this.props.user.id] && !requestedUsers[this.props.user.id]) { + requestedUsers[this.props.user.id] = true; + queuedInstances[this.props.user.id] = [].concat(queuedInstances[this.props.user.id]).filter(n => n); + BDFDB.LibraryModules.UserProfileUtils.fetchProfile(this.props.user.id); + } + if (!loadedUsers[this.props.user.id]) { + if (queuedInstances[this.props.user.id].indexOf(this) == -1) queuedInstances[this.props.user.id].push(this); + return null; + } + let connections = loadedUsers[this.props.user.id].filter(c => _this.settings.connections[c.type]); + if (!connections.length) return null; + let isLightTheme = (!this.props.theme || this.props.theme == "light") && BDFDB.DiscordUtils.getTheme() == BDFDB.disCN.themelight; + return BDFDB.ReactUtils.createElement("section", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.userprofilesection, BDFDB.disCN._showconnectionsconnectionswrapper), + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN._showconnectionsconnections, + children: connections.map(c => { + let provider = BDFDB.LibraryModules.ConnectionProviderUtils.get(c.type); + let url = _this.settings.general.openWebpage && provider.getPlatformUserUrl && provider.getPlatformUserUrl(c); + let metadata = []; + if (_this.settings.general.showDetails && provider.hasMetadata && c.metadata) { + if (c.metadata.created_at) metadata.push(BDFDB.ReactUtils.createElement("span", {children: BDFDB.LanguageUtils.LanguageStringsFormat("MEMBER_SINCE_PLACEHOLDER", (new Date(c.metadata.created_at)).toLocaleDateString("default", {year: "numeric", month: "long", day: "numeric"}))})); + } + return BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: `${provider.name}: ${c.name}`, + note: metadata && metadata.length ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + direction: BDFDB.LibraryComponents.Flex.Direction.VERTICAL, + children: metadata + }): null, + tooltipConfig: {backgroundColor: _this.settings.general.useColoredTooltips && BDFDB.ColorUtils.change(provider.color, -0.3), color: !_this.settings.general.useColoredTooltips || !provider.color ? "primary" : null}, + children: BDFDB.ReactUtils.createElement(!url ? "div" : BDFDB.LibraryComponents.Anchor, Object.assign(!url ? {} : { + href: url + }, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN._showconnectionsconnection, url && BDFDB.disCN.cursorpointer), + onContextMenu: event => { + BDFDB.ContextMenuUtils.open(_this, event, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, { + children: [ + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: BDFDB.LanguageUtils.LibraryStringsFormat("copy", BDFDB.LanguageUtils.LanguageStrings.USERNAME), + id: BDFDB.ContextMenuUtils.createItemId(_this.name, "copy-name"), + action: _ => BDFDB.LibraryModules.WindowUtils.copy(c.name) + }), + url && BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: BDFDB.LanguageUtils.LibraryStringsFormat("copy", BDFDB.LanguageUtils.LanguageStrings.COPY_LINK), + id: BDFDB.ContextMenuUtils.createItemId(_this.name, "copy-url"), + action: _ => BDFDB.LibraryModules.WindowUtils.copy(url) + }) + ] + })); + }, + children: [ + BDFDB.ReactUtils.createElement("img", { + className: BDFDB.disCN._showconnectionsicon, + alt: BDFDB.LanguageUtils.LanguageStringsFormat("PLACEHOLDER_LOGO", provider.name), + src: provider.icon[this.props.popoutColor ? (BDFDB.ColorUtils.isBright(this.props.popoutColor) ? "lightSVG" : "darkSVG") : (_this.settings.general.useColoredIcons ? (isLightTheme ? "lightSVG" : "darkSVG") : "whiteSVG")] + }), + _this.settings.general.showVerifiedBadge && c.verified && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: BDFDB.LanguageUtils.LanguageStrings.VERIFIED_CONNECTION, + tooltipConfig: {color: "brand", type: "bottom"}, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FlowerStar, { + className: BDFDB.disCN._showconnectionsverifiedbadge, + size: "50%", + color: isLightTheme ? BDFDB.DiscordConstants.Colors.PRIMARY_200 : BDFDB.DiscordConstants.Colors.PRIMARY, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + name: BDFDB.LibraryComponents.SvgIcon.Names.CHECKMARK, + width: "70%", + height: "70%", + color: isLightTheme ? BDFDB.DiscordConstants.Colors.PRIMARY_500 : BDFDB.DiscordConstants.Colors.WHITE + }) + }) + }) + ] + })) + }); + }) + }) + }); + } + }; + + return class ShowConnections extends Plugin { + onLoad () { + _this = this; + loadedUsers = {}; + requestedUsers = {}; + queuedInstances = {}; + + this.patchPriority = 9; + + this.modulePatches = { + after: [ + "UserHeaderUsername" + ] + }; + + this.defaults = { + general: { + useColoredIcons: {value: true, description: "Uses colored Version of the Icons"}, + useColoredTooltips: {value: true, description: "Uses colored Version of the Tooltips"}, + showDetails: {value: true, description: "Shows Details of Connection on hover"}, + showVerifiedBadge: {value: true, description: "Shows the Badge for verified Connections"}, + openWebpage: {value: true, description: "Opens the Connection Page when clicking the Icon"} + }, + connections: {} + }; + + for (let connection of BDFDB.LibraryModules.ConnectionProviderUtils.filter(n => n)) this.defaults.connections[connection.type] = Object.assign({}, connection, {value: true}); + + this.css = ` + ${BDFDB.dotCN._showconnectionsconnectionswrapper} { + order: 999; + } + ${BDFDB.dotCN._showconnectionsconnections} { + display: flex; + flex-wrap: wrap; + } + ${BDFDB.dotCN._showconnectionsconnection} { + position: relative; + width: 28px; + height: 28px; + margin: 4px 10px 6px 0; + } + ${BDFDB.dotCN._showconnectionsicon} { + margin: -15% 0 0 -15%; + width: 130%; + height: 130%; + } + ${BDFDB.dotCN._showconnectionsverifiedbadge} { + position: absolute; + bottom: -10%; + right: -10%; + } + `; + } + + onStart () { + BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.DispatchApiUtils, "dispatch", {after: e => { + if (BDFDB.ObjectUtils.is(e.methodArguments[0]) && e.methodArguments[0].type == "USER_PROFILE_FETCH_SUCCESS") { + let userProfile = e.methodArguments[0].userProfile || e.methodArguments[0]; + if (userProfile.user && userProfile.connected_accounts) { + delete requestedUsers[userProfile.user.id]; + loadedUsers[userProfile.user.id] = userProfile.connected_accounts; + BDFDB.ReactUtils.forceUpdate(queuedInstances[userProfile.user.id]); + delete queuedInstances[userProfile.user.id]; + } + } + }}); + } + + onStop () {} + + getSettingsPanel (collapseStates = {}) { + let settingsPanel; + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, { + collapseStates: collapseStates, + children: _ => { + let settingsItems = []; + + for (let key in this.defaults.general) settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Switch", + plugin: this, + keys: ["general", key], + label: this.defaults.general[key].description, + value: this.settings.general[key] + })); + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, { + title: "Display Connections:", + children: Object.keys(this.defaults.connections).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Switch", + plugin: this, + keys: ["connections", key], + label: this.defaults.connections[key].name, + value: this.settings.connections[key], + labelChildren: [ + BDFDB.ReactUtils.createElement("img", {style: {width: 28, height: 28}, src: this.defaults.connections[key].icon.lightSVG}), + BDFDB.ReactUtils.createElement("img", {style: {width: 28, height: 28}, src: this.defaults.connections[key].icon.whiteSVG}) + ] + })) + })); + + return settingsItems; + } + }); + } + + processUserHeaderUsername (e) { + let themeType = BDFDB.ObjectUtils.get(e.instance, "props.tags.props.themeType"); + if (themeType != "SIDEBAR" && themeType != "POPOUT" || e.instance.props.className) return; + let user = e.instance.props.user || BDFDB.LibraryStores.UserStore.getUser(e.instance.props.userId); + if (!user || user.isNonUserBot()) return; + e.returnvalue = [e.returnvalue].flat(10); + e.returnvalue.push(BDFDB.ReactUtils.createElement(UserConnectionsComponents, { + user: user, + popoutColor: BDFDB.ObjectUtils.get(e.instance, "props.tags.props.displayProfile.themeColors.1"), + theme: e.instance.props.theme + }, true)); + } + }; + })(window.BDFDB_Global.PluginUtils.buildPlugin(changeLog)); +})(); diff --git a/dotfiles/.config/BetterDiscord/plugins/StickerEmojiPreview.config.json b/dotfiles/.config/BetterDiscord/plugins/StickerEmojiPreview.config.json new file mode 100644 index 0000000..df28d8a --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/StickerEmojiPreview.config.json @@ -0,0 +1,6 @@ +{ + "settings": { + "previewState": false, + "previewDefaultState": false + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/StickerEmojiPreview.plugin.js b/dotfiles/.config/BetterDiscord/plugins/StickerEmojiPreview.plugin.js new file mode 100644 index 0000000..a9c4549 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/StickerEmojiPreview.plugin.js @@ -0,0 +1,375 @@ +/** + * @name StickerEmojiPreview + * @description Adds a zoomed preview to those tiny Stickers and Emojis + * @version 1.3.3 + * @author Skamt + * @website https://github.com/Skamt/BDAddons/tree/main/StickerEmojiPreview + * @source https://raw.githubusercontent.com/Skamt/BDAddons/main/StickerEmojiPreview/StickerEmojiPreview.plugin.js + */ + +// config:@Config +var Config_default = { + "info": { + "name": "StickerEmojiPreview", + "version": "1.3.3", + "description": "Adds a zoomed preview to those tiny Stickers and Emojis", + "source": "https://raw.githubusercontent.com/Skamt/BDAddons/main/StickerEmojiPreview/StickerEmojiPreview.plugin.js", + "github": "https://github.com/Skamt/BDAddons/tree/main/StickerEmojiPreview", + "authors": [{ + "name": "Skamt" + }] + }, + "settings": { + "previewState": false, + "previewDefaultState": false + } +}; + +// common/Api.js +var Api = new BdApi(Config_default.info.name); +var DOM = /* @__PURE__ */ (() => Api.DOM)(); +var Data = /* @__PURE__ */ (() => Api.Data)(); +var React = /* @__PURE__ */ (() => Api.React)(); +var Patcher = /* @__PURE__ */ (() => Api.Patcher)(); +var Logger = /* @__PURE__ */ (() => Api.Logger)(); +var Webpack = /* @__PURE__ */ (() => Api.Webpack)(); + +// common/Utils/Logger.js +Logger.patchError = (patchId) => { + console.error(`%c[${Config_default.info.name}] %cCould not find module for %c[${patchId}]`, "color: #3a71c1;font-weight: bold;", "", "color: red;font-weight: bold;"); +}; +var Logger_default = Logger; + +// common/Utils/EventEmitter.js +var EventEmitter_default = class { + constructor() { + this.listeners = {}; + } + isInValid(event, handler) { + return typeof event !== "string" || typeof handler !== "function"; + } + once(event, handler) { + if (this.isInValid(event, handler)) return; + if (!this.listeners[event]) this.listeners[event] = /* @__PURE__ */ new Set(); + const wrapper = () => { + handler(); + this.off(event, wrapper); + }; + this.listeners[event].add(wrapper); + } + on(event, handler) { + if (this.isInValid(event, handler)) return; + if (!this.listeners[event]) this.listeners[event] = /* @__PURE__ */ new Set(); + this.listeners[event].add(handler); + return () => this.off(event, handler); + } + off(event, handler) { + if (this.isInValid(event, handler)) return; + if (!this.listeners[event]) return; + this.listeners[event].delete(handler); + if (this.listeners[event].size !== 0) return; + delete this.listeners[event]; + } + emit(event, ...payload) { + if (!this.listeners[event]) return; + for (const listener of this.listeners[event]) { + try { + listener.apply(null, payload); + } catch (err) { + Logger_default.error(`Could not run listener for ${event}`, err); + } + } + } +}; + +// common/Utils/Plugin.js +var Events = { + START: "START", + STOP: "STOP" +}; +var Plugin_default = new class extends EventEmitter_default { + start() { + this.emit(Events.START); + } + stop() { + this.emit(Events.STOP); + } +}(); + +// common/Utils/StylesLoader.js +var styleLoader = { + _styles: [], + push(styles) { + this._styles.push(styles); + } +}; +Plugin_default.on(Events.START, () => { + DOM.addStyle(styleLoader._styles.join("\n")); +}); +Plugin_default.on(Events.STOP, () => { + DOM.removeStyle(); +}); +var StylesLoader_default = styleLoader; + +// src/StickerEmojiPreview/styles.css +StylesLoader_default.push(`.stickersPreview { + width: 400px; + font-size: 14px; + background: oklab(0.278867 0.00249027 -0.00875303); + border-radius: 5px; + padding: 0.5em; + box-shadow: var(--elevation-high); +} + +.stickersPreview img { + min-width: 100%; + max-width: 100%; +} + +.animated img { + border: 1px dashed #ff8f09; + padding: 1px; + box-sizing: border-box; +} +`); + +// common/React.jsx +var useRef = /* @__PURE__ */ (() => React.useRef)(); +var React_default = /* @__PURE__ */ (() => React)(); + +// common/Webpack.js +var getModule = /* @__PURE__ */ (() => Webpack.getModule)(); +var Filters = /* @__PURE__ */ (() => Webpack.Filters)(); +var getMangled = /* @__PURE__ */ (() => Webpack.getMangled)(); + +function getModuleAndKey(filter, options) { + let module2; + const target = getModule((entry, m) => filter(entry) ? module2 = m : false, options); + module2 = module2?.exports; + if (!module2) return; + const key = Object.keys(module2).find((k) => module2[k] === target); + if (!key) return; + return { module: module2, key }; +} + +// MODULES-AUTO-LOADER:@Patch/CloseExpressionPicker +var CloseExpressionPicker_default = getModuleAndKey(Filters.byStrings("activeView:null,activeViewType:null"), { searchExports: true }) || {}; + +// common/DiscordModules/zustand.js +var { zustand } = getMangled(Filters.bySource("useSyncExternalStoreWithSelector", "useDebugValue", "subscribe"), { + _: Filters.byStrings("subscribe"), + zustand: () => true +}); +var subscribeWithSelector = getModule(Filters.byStrings("getState", "equalityFn", "fireImmediately"), { searchExports: true }); + +function create(initialState) { + const Store = zustand(initialState); + Object.defineProperty(Store, "state", { + configurable: false, + get: () => Store.getState() + }); + return Store; +} + +// common/Utils/index.js +var nop = () => {}; + +// common/Utils/Settings.js +var SettingsStore = create(subscribeWithSelector(() => Object.assign(Config_default.settings, Data.load("settings") || {}))); +((state) => { + const selectors = {}; + const actions = {}; + for (const [key, value] of Object.entries(state)) { + actions[`set${key}`] = (newValue) => SettingsStore.setState({ + [key]: newValue }); + selectors[key] = (state2) => state2[key]; + } + Object.defineProperty(SettingsStore, "selectors", { value: Object.assign(selectors) }); + Object.assign(SettingsStore, actions); +})(SettingsStore.getInitialState()); +SettingsStore.subscribe( + (state) => state, + () => Data.save("settings", SettingsStore.state) +); +Object.assign(SettingsStore, { + useSetting: (key) => { + const val = SettingsStore((state) => state[key]); + return [val, SettingsStore[`set${key}`]]; + } +}); +var Settings_default = SettingsStore; + +// src/StickerEmojiPreview/patches/patchCloseExpressionPicker.js +Plugin_default.on(Events.START, () => { + const { module: module2, key } = CloseExpressionPicker_default; + if (!module2 || !key) return Logger_default.patchError("CloseExpressionPicker"); + const unpatch = Patcher.after(module2, key, (_, args, ret) => { + Settings_default.setpreviewState(Settings_default.state.previewDefaultState); + }); + Plugin_default.once(Events.STOP, unpatch); +}); + +// MODULES-AUTO-LOADER:@Patch/ExpressionPickerInspector +var ExpressionPickerInspector_default = getModuleAndKey(Filters.byStrings("graphicPrimary", "titlePrimary"), { searchExports: false }) || {}; + +// common/DiscordModules/Modules.js +var DiscordPopout = /* @__PURE__ */ (() => getModule((a) => a?.prototype?.render && a.Animation, { searchExports: true }))(); + +// src/StickerEmojiPreview/Constants.js +var PREVIEW_SIZE = 300; +var PREVIEW_UNAVAILABLE = `data:image/svg+xml,`; + +// src/StickerEmojiPreview/components/PreviewComponent.jsx +var PreviewComponent_default = ({ target, previewComponent }) => { + const [show, setShow] = Settings_default.useSetting("previewState"); + const ref = useRef(); + React_default.useEffect(() => { + function keyupHandler(e) { + if (e.key === "Control") { + setShow(!show); + } + } + document.addEventListener("keyup", keyupHandler); + return () => document.removeEventListener("keyup", keyupHandler); + }, [show]); + return /* @__PURE__ */ React_default.createElement( + DiscordPopout, { + renderPopout: () => /* @__PURE__ */ React_default.createElement( + "div", { + className: "stickersPreview", + style: { width: `${PREVIEW_SIZE}px` } + }, + previewComponent + ), + targetElementRef: ref, + shouldShow: show, + position: "left", + align: "bottom", + animation: "1", + spacing: 60 + }, + () => React_default.cloneElement(target, { ref }) + ); +}; + +// common/Components/ErrorBoundary/index.jsx +var ErrorBoundary = class extends React_default.Component { + state = { hasError: false, error: null, info: null }; + componentDidCatch(error, info) { + this.setState({ error, info, hasError: true }); + const errorMessage = ` + ${error?.message || ""}${(info?.componentStack || "").split("\n").slice(0, 20).join("\n")}`; + console.error(`%c[${Config_default?.info?.name || "Unknown Plugin"}] %cthrew an exception at %c[${this.props.id}] +`, "color: #3a71c1;font-weight: bold;", "", "color: red;font-weight: bold;", errorMessage); + } + renderErrorBoundary() { + return /* @__PURE__ */ React_default.createElement("div", { style: { background: "#292c2c", padding: "20px", borderRadius: "10px" } }, /* @__PURE__ */ React_default.createElement("b", { style: { color: "#e0e1e5" } }, "An error has occured while rendering ", /* @__PURE__ */ React_default.createElement("span", { style: { color: "orange" } }, this.props.id))); + } + renderFallback() { + if (React_default.isValidElement(this.props.fallback)) { + if (this.props.passMetaProps) + this.props.fallback.props = { + id: this.props.id, + plugin: Config_default?.info?.name || "Unknown Plugin", + ...this.props.fallback.props + }; + return this.props.fallback; + } + return /* @__PURE__ */ React_default.createElement( + this.props.fallback, { + id: this.props.id, + plugin: Config_default?.info?.name || "Unknown Plugin" + } + ); + } + render() { + if (!this.state.hasError) return this.props.children; + return this.props.fallback ? this.renderFallback() : this.renderErrorBoundary(); + } +}; + +// src/StickerEmojiPreview/patches/patchPickerInspector.jsx +function getMediaInfo({ props, type }) { + if (props.sticker) return [type, props]; + if (props.src) return [type, { src: props.src.replace(/([?&]size=)(\d+)/, `$1${PREVIEW_SIZE}`) || PREVIEW_UNAVAILABLE }]; + return ["img", null]; +} + +function getPreviewComponent(graphicPrimary) { + const [TypeComponent, props] = getMediaInfo(graphicPrimary); + return /* @__PURE__ */ React.createElement( + TypeComponent, { + ...props, + disableAnimation: false, + size: PREVIEW_SIZE + } + ); +} +Plugin_default.on(Events.START, () => { + const { module: module2, key } = ExpressionPickerInspector_default; + if (!module2 || !key) return Logger_default.patchError("ExpressionPickerInspector"); + const unpatch = Patcher.after(module2, key, (_, [{ graphicPrimary, titlePrimary }], ret) => { + if (titlePrimary?.toLowerCase().includes("upload")) return; + return /* @__PURE__ */ React.createElement( + ErrorBoundary, { + id: "PreviewComponent", + plugin: Config_default.info.name, + fallback: ret + }, + /* @__PURE__ */ + React.createElement( + PreviewComponent_default, { + target: ret, + previewComponent: getPreviewComponent(graphicPrimary) + } + ) + ); + }); + Plugin_default.once(Events.STOP, unpatch); +}); + +// MODULES-AUTO-LOADER:@Modules/FormSwitch +var FormSwitch_default = getModule(Filters.byStrings("note", "tooltipNote"), { searchExports: true }); + +// common/Components/Switch/index.jsx +var Switch_default = getModule(Filters.byStrings('"data-toggleable-component":"switch"', 'layout:"horizontal"'), { searchExports: true }) || function SwitchComponentFallback(props) { + return /* @__PURE__ */ React.createElement("div", { style: { color: "#fff" } }, props.children, /* @__PURE__ */ React.createElement( + "input", { + type: "checkbox", + checked: props.value, + onChange: (e) => props.onChange(e.target.checked) + } + )); +}; + +// common/Components/SettingSwtich/index.jsx +function SettingSwtich({ settingKey, note, onChange = nop, description, ...rest }) { + const [val, set] = Settings_default.useSetting(settingKey); + return /* @__PURE__ */ React.createElement( + Switch_default, { + ...rest, + checked: val, + label: description || settingKey, + description: note, + onChange: (e) => { + set(e); + onChange(e); + } + } + ); +} + +// src/StickerEmojiPreview/components/SettingComponent.jsx +function SettingComponent() { + return [{ + settingKey: "previewDefaultState", + description: "Preview open by default.", + onChange() { + Settings_default.setpreviewState(Settings_default.state.previewDefaultState); + } + }].map(SettingSwtich); +} + +// src/StickerEmojiPreview/index.jsx +Plugin_default.getSettingsPanel = () => /* @__PURE__ */ React_default.createElement(SettingComponent, null); +module.exports = () => Plugin_default; diff --git a/dotfiles/.config/BetterDiscord/plugins/ThemeRepo.config.json b/dotfiles/.config/BetterDiscord/plugins/ThemeRepo.config.json new file mode 100755 index 0000000..38d6f31 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ThemeRepo.config.json @@ -0,0 +1,16 @@ +{ + "all": { + "cached": "1 3 16 18 22 23 32 33 39 40 41 45 46 47 48 49 52 53 56 112 124 125 142 144 147 153 155 156 161 166 172 174 177 204 205 209 218 226 252 256 257 286 296 308 320 342 359 371 385 412 422 439 468 483 507 508 515 541 553 572 576 580 581 597 601 607 613 636 637 641 642 643 653 659 662 664 695 712 713 714 722 734 735 736 746 752 753 770 813 823 858 865 866 894 915 922 928 929 933 973 1006 1008 1014 1030 1033 1098 1125 1128 1138 1143 1152 1183 1221", + "filters": { + "updated": true, + "outdated": true, + "downloadable": true + }, + "general": { + "notifyOutdated": true, + "notifyNewEntries": true, + "startDownloaded": false, + "startUpdated": false + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/Translator.config.json b/dotfiles/.config/BetterDiscord/plugins/Translator.config.json new file mode 100644 index 0000000..b95ba77 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/Translator.config.json @@ -0,0 +1,51 @@ +{ + "all": { + "choices": { + "received": { + "input": "auto", + "output": "$discord" + }, + "sent": { + "input": "auto", + "output": "$discord" + } + }, + "engines": { + "translator": "googleapi", + "backup": "----" + }, + "exceptions": { + "wordStart": [ + "!" + ] + }, + "general": { + "addTranslateButton": true, + "addQuickTranslateButton": true, + "usePerChatTranslation": true, + "sendOriginalMessage": false, + "showOriginalMessage": false, + "useSpoilerInOriginal": false + }, + "prefixes": { + "translationPrefixData": [ + { + "prefix": "$fr", + "language": "fr" + }, + { + "prefix": "$de", + "language": "de" + }, + { + "prefix": "$es", + "language": "es" + }, + { + "prefix": "$jp", + "language": "ja" + } + ] + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/Translator.plugin.js b/dotfiles/.config/BetterDiscord/plugins/Translator.plugin.js new file mode 100644 index 0000000..ac56710 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/Translator.plugin.js @@ -0,0 +1,2976 @@ +/** + * @name Translator + * @author DevilBro + * @authorId 278543574059057154 + * @version 2.7.9 + * @description Allows you to translate incoming and your outgoing Messages within Discord + * @invite Jx3TjNS + * @donate https://www.paypal.me/MircoWittrien + * @patreon https://www.patreon.com/MircoWittrien + * @website https://mwittrien.github.io/ + * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/Translator/ + * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/Translator/Translator.plugin.js + */ + +module.exports = (_ => { + const changeLog = { + + }; + + return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class { + constructor (meta) {for (let key in meta) this[key] = meta[key];} + getName () {return this.name;} + getAuthor () {return this.author;} + getVersion () {return this.version;} + getDescription () {return `The Library Plugin needed for ${this.name} is missing. Open the Plugin Settings to download it. \n\n${this.description}`;} + + downloadLibrary () { + BdApi.Net.fetch("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js").then(r => { + if (!r || r.status != 200) throw new Error(); + else return r.text(); + }).then(b => { + if (!b) throw new Error(); + else return require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.UI.showToast("Finished downloading BDFDB Library", {type: "success"})); + }).catch(error => { + BdApi.UI.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library"); + }); + } + + load () { + if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []}); + if (!window.BDFDB_Global.downloadModal) { + window.BDFDB_Global.downloadModal = true; + BdApi.UI.showConfirmationModal("Library Missing", `The Library Plugin needed for ${this.name} is missing. Please click "Download Now" to install it.`, { + confirmText: "Download Now", + cancelText: "Cancel", + onCancel: _ => {delete window.BDFDB_Global.downloadModal;}, + onConfirm: _ => { + delete window.BDFDB_Global.downloadModal; + this.downloadLibrary(); + } + }); + } + if (!window.BDFDB_Global.pluginQueue.includes(this.name)) window.BDFDB_Global.pluginQueue.push(this.name); + } + start () {this.load();} + stop () {} + getSettingsPanel () { + let template = document.createElement("template"); + template.innerHTML = `
The Library Plugin needed for ${this.name} is missing.\nPlease click Download Now to install it.
`; + template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary); + return template.content.firstElementChild; + } + } : (([Plugin, BDFDB]) => { + var _this; + + const translateIconGeneral = ``; + const translateIconMask = ``; + const translateIcon = translateIconGeneral.replace(``, ``).replace(``, ``).replace(` mask="url(#translateIconMask)"`, ``); + const translateIconUntranslate = translateIconGeneral.replace(``, ``).replace(``, translateIconMask); + + const TranslateButtonComponent = class TranslateButton extends BdApi.React.Component { + render() { + const enabled = _this.isTranslationEnabled(this.props.channelId); + return BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ChannelTextAreaButton, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN._translatortranslatebutton, _this.isTranslationEnabled(this.props.channelId) && BDFDB.disCN._translatortranslating, BDFDB.disCN.textareapickerbutton), + isActive: this.props.isActive, + iconSVG: translateIcon, + nativeClass: true, + tooltip: enabled && { + text: (_ => `${BDFDB.LanguageUtils.getName(languages[_this.getLanguageChoice(languageTypes.INPUT, messageTypes.SENT, this.props.channelId)])} ➝ ${BDFDB.LanguageUtils.getName(languages[_this.getLanguageChoice(languageTypes.OUTPUT, messageTypes.SENT, this.props.channelId)])}`), + tooltipConfig: {style: "max-width: 400px"} + }, + onClick: _ => { + this.props.isActive = true; + BDFDB.ReactUtils.forceUpdate(this); + + BDFDB.ModalUtils.open(_this, { + size: "LARGE", + header: BDFDB.LanguageUtils.LanguageStrings.SETTINGS, + subHeader: "", + onClose: _ => { + this.props.isActive = false; + BDFDB.ReactUtils.forceUpdate(this); + }, + children: BDFDB.ReactUtils.createElement(TranslateSettingsComponent, { + guildId: this.props.guildId, + channelId: this.props.channelId + }) + }); + }, + onContextMenu: _ => { + _this.toggleTranslation(this.props.channelId); + BDFDB.ReactUtils.forceUpdate(this); + } + }); + } + }; + + const TranslateSettingsComponent = class TranslateSettings extends BdApi.React.Component { + filterLanguages(direction, place) { + let isOutput = direction == languageTypes.OUTPUT; + return BDFDB.ObjectUtils.toArray(BDFDB.ObjectUtils.map(isOutput ? BDFDB.ObjectUtils.filter(languages, lang => !lang.auto) : languages, (lang, id) => ({ + value: id, + label: BDFDB.LanguageUtils.getName(lang), + backup: this.isOnlyBackup(lang) + }))).filter(isOutput && this.isOnlyBackup(languages[_this.getLanguageChoice(languageTypes.INPUT, place, this.props.channelId)]) ? (n => n.backup) : (n => n)); + } + isOnlyBackup(lang) { + return lang && (lang.auto && translationEngines[_this.settings.engines.translator] && !translationEngines[_this.settings.engines.translator].auto || !lang.auto && !lang.special && translationEngines[_this.settings.engines.translator] && !translationEngines[_this.settings.engines.translator].languages.includes(lang.id)); + } + render() { + return [ + BDFDB.ArrayUtils.is(_this.settings.exceptions.wordStart) && _this.settings.exceptions.wordStart.length && [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.marginbottom8, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsLabel, { + label: _this.labels.exception_text.replace("{{var0}}", _this.settings.exceptions.wordStart.map(n => '"' + n + '"').join(", ")) + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormDivider, { + className: BDFDB.disCN.marginbottom8 + }) + ], + Object.keys(_this.defaults.choices).map(place => { + let isChannelSpecific = channelLanguages[this.props.channelId] && channelLanguages[this.props.channelId][place]; + let isGuildSpecific = !isChannelSpecific && guildLanguages[this.props.guildId] && guildLanguages[this.props.guildId][place]; + return Object.keys(_this.defaults.choices[place].value).map(direction => [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormItem, { + title: _this.labels[`language_choice_${direction.toLowerCase()}_${place.toLowerCase()}`] + ": ", + titleChildren: direction == languageTypes.OUTPUT && [{ + text: _ => isChannelSpecific ? _this.labels.language_selection_channel : isGuildSpecific ? _this.labels.language_selection_server : _this.labels.language_selection_global, + name: isChannelSpecific || isGuildSpecific ? BDFDB.LibraryComponents.SvgIcon.Names.LOCK_CLOSED : BDFDB.LibraryComponents.SvgIcon.Names.LOCK_OPEN, + color: isChannelSpecific ? "var(--status-danger)" : isGuildSpecific ? "var(--status-warning)" : null, + onClick: _ => { + if (channelLanguages[this.props.channelId] && channelLanguages[this.props.channelId][place]) { + isChannelSpecific = false; + delete channelLanguages[this.props.channelId][place]; + if (BDFDB.ObjectUtils.isEmpty(channelLanguages[this.props.channelId])) delete channelLanguages[this.props.channelId]; + } + else if (guildLanguages[this.props.guildId] && guildLanguages[this.props.guildId][place]) { + isGuildSpecific = false; + isChannelSpecific = true; + delete guildLanguages[this.props.guildId][place]; + if (BDFDB.ObjectUtils.isEmpty(guildLanguages[this.props.guildId])) delete guildLanguages[this.props.guildId]; + if (!channelLanguages[this.props.channelId]) channelLanguages[this.props.channelId] = {}; + channelLanguages[this.props.channelId][place] = {}; + for (let l in languageTypes) channelLanguages[this.props.channelId][place][languageTypes[l]] = _this.getLanguageChoice(languageTypes[l], place, null); + } + else { + isGuildSpecific = true; + if (!guildLanguages[this.props.guildId]) guildLanguages[this.props.guildId] = {}; + guildLanguages[this.props.guildId][place] = {}; + for (let l in languageTypes) guildLanguages[this.props.guildId][place][languageTypes[l]] = _this.getLanguageChoice(languageTypes[l], place, null); + } + BDFDB.DataUtils.save(channelLanguages, _this, "channelLanguages"); + BDFDB.DataUtils.save(guildLanguages, _this, "guildLanguages"); + + BDFDB.ReactUtils.forceUpdate(this); + } + }, { + iconSVG: ``, + onClick: _ => { + let input = _this.getLanguageChoice(languageTypes.INPUT, place, this.props.channelId); + let output = _this.getLanguageChoice(languageTypes.OUTPUT, place, this.props.channelId); + input = input == "auto" ? "en" : input; + + _this.saveLanguageChoice(output, languageTypes.INPUT, place, this.props.channelId); + _this.saveLanguageChoice(input, languageTypes.OUTPUT, place, this.props.channelId); + + _this.setLanguages(); + + BDFDB.ReactUtils.forceUpdate(this); + } + }].map(data => { + const icon = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, { + className: BDFDB.disCN._translatorconfigbutton, + onClick: data.onClick, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + width: 24, + height: 24, + color: data.color || "currentColor", + name: data.name, + iconSVG: data.iconSVG + }) + }); + return data.text ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, {tooltipConfig: {type: "bottom"}, text: data.text, children: icon}) : icon; + }), + className: BDFDB.disCN.marginbottom8, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, { + value: _this.getLanguageChoice(direction, place, this.props.channelId), + options: this.filterLanguages(direction, place), + optionRenderer: lang => languages[lang.value] ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + align: BDFDB.LibraryComponents.Flex.Align.CENTER, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 1, + children: lang.label + }), + lang.backup && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: _this.labels.backup_engine_warning, + tooltipConfig: { + color: "red" + }, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + nativeClass: true, + width: 20, + height: 20, + color: "var(--status-danger)", + name: BDFDB.LibraryComponents.SvgIcon.Names.WARNING + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FavButton, { + isFavorite: languages[lang.value].fav == 0, + onClick: value => { + if (value) favorites.push(lang.value); + else BDFDB.ArrayUtils.remove(favorites, lang.value, true); + BDFDB.DataUtils.save(favorites.sort(), _this, "favorites"); + _this.setLanguages(); + } + }) + ] + }) : null, + onChange: value => { + _this.saveLanguageChoice(value, direction, place, this.props.channelId); + BDFDB.ReactUtils.forceUpdate(this); + } + }) + }), + direction == languageTypes.OUTPUT && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormDivider, { + className: BDFDB.disCN.marginbottom8 + }) + ]); + }), + Object.keys(_this.defaults.engines).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormItem, { + title: _this.labels[`${key}_engine`], + className: BDFDB.disCN.marginbottom8, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, { + value: _this.settings.engines[key], + options: (key == "backup" ? ["----"] : []).concat(Object.keys(translationEngines)).filter(key == "backup" ? (n => n != _this.settings.engines.translator) : (n => n)).map(engineKey => ({value: engineKey, label: translationEngines[engineKey] ? translationEngines[engineKey].name : "----"})), + maxVisibleItems: 3, + onChange: value => { + _this.settings.engines[key] = value; + BDFDB.DataUtils.save(_this.settings.engines, _this, "engines"); + _this.setLanguages(); + BDFDB.ReactUtils.forceUpdate(this); + } + }) + })), + Object.keys(_this.defaults.general).filter(key => _this.defaults.general[key].popout).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Switch", + plugin: _this, + keys: ["general", key], + label: _this.labels[`general_${key}`] || _this.defaults.general[key].description, + tag: BDFDB.LibraryComponents.FormTitle.Tags.H5, + value: _this.settings.general[key] + })), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Switch", + label: _this.labels.translate_your_message, + tag: BDFDB.LibraryComponents.FormTitle.Tags.H5, + value: _this.isTranslationEnabled(this.props.channelId), + onChange: value => { + _this.toggleTranslation(this.props.channelId); + BDFDB.ReactUtils.forceUpdate(this); + } + }) + ].flat(10).filter(n => n); + } + }; + + const brailleConverter = { + "0":"⠴", "1":"⠂", "2":"⠆", "3":"⠒", "4":"⠲", "5":"⠢", "6":"⠖", "7":"⠶", "8":"⠦", "9":"⠔", "!":"⠮", "\"":"⠐", "#":"⠼", "$":"⠫", "%":"⠩", "&":"⠯", "'":"⠄", "(":"⠷", ")":"⠾", "*":"⠡", "+":"⠬", ",":"⠠", "-":"⠤", ".":"⠨", "/":"⠌", ":":"⠱", ";":"⠰", "<":"⠣", "=":"⠿", ">":"⠜", "?":"⠹", "@":"⠈", "a":"⠁", "b":"⠃", "c":"⠉", "d":"⠙", "e":"⠑", "f":"⠋", "g":"⠛", "h":"⠓", "i":"⠊", "j":"⠚", "k":"⠅", "l":"⠇", "m":"⠍", "n":"⠝", "o":"⠕", "p":"⠏", "q":"⠟", "r":"⠗", "s":"⠎", "t":"⠞", "u":"⠥", "v":"⠧", "w":"⠺", "x":"⠭", "y":"⠽", "z":"⠵", "[":"⠪", "\\":"⠳", "]":"⠻", "^":"⠘", "⠁":"a", "⠂":"1", "⠃":"b", "⠄":"'", "⠅":"k", "⠆":"2", "⠇":"l", "⠈":"@", "⠉":"c", "⠊":"i", "⠋":"f", "⠌":"/", "⠍":"m", "⠎":"s", "⠏":"p", "⠐":"\"", "⠑":"e", "⠒":"3", "⠓":"h", "⠔":"9", "⠕":"o", "⠖":"6", "⠗":"r", "⠘":"^", "⠙":"d", "⠚":"j", "⠛":"g", "⠜":">", "⠝":"n", "⠞":"t", "⠟":"q", "⠠":", ", "⠡":"*", "⠢":"5", "⠣":"<", "⠤":"-", "⠥":"u", "⠦":"8", "⠧":"v", "⠨":".", "⠩":"%", "⠪":"[", "⠫":"$", "⠬":"+", "⠭":"x", "⠮":"!", "⠯":"&", "⠰":";", "⠱":":", "⠲":"4", "⠳":"\\", "⠴":"0", "⠵":"z", "⠶":"7", "⠷":"(", "⠸":"_", "⠹":"?", "⠺":"w", "⠻":"]", "⠼":"#", "⠽":"y", "⠾":")", "⠿":"=", "_":"⠸" + }; + + const morseConverter = { + "0":"−−−−−", "1":"·−−−−", "2":"··−−−", "3":"···−−", "4":"····−", "5":"·····", "6":"−····", "7":"−−···", "8":"−−−··", "9":"−−−−·", "!":"−·−·−−", "\"":"·−··−·", "$":"···−··−", "&":"·−···", "'":"·−−−−·", "(":"−·−−·", ")":"−·−−·−", "+":"·−·−·", ",":"−−··−−", "-":"−····−", ".":"·−·−·−", "/":"−··−·", ":":"−−−···", ";":"−·−·−·", "=":"−···−", "?":"··−−··", "@":"·−−·−·", "a":"·−", "b":"−···", "c":"−·−·", "d":"−··", "e":"·", "f":"··−·", "g":"−−·", "h":"····", "i":"··", "j":"·−−−", "k":"−·−", "l":"·−··", "m":"−−", "n":"−·", "o":"−−−", "p":"·−−·", "q":"−−·−", "r":"·−·", "s":"···", "t":"−", "u":"··−", "v":"···−", "w":"·−−", "x":"−··−", "y":"−·−−", "z":"−−··", "·":"e", "··":"i", "···":"s", "····":"h", "·····":"5", "····−":"4", "···−":"v", "···−··−":"$", "···−−":"3", "··−":"u", "··−·":"f", "··−−··":"?", "··−−·−":"_", "··−−−":"2", "·−":"a", "·−·":"r", "·−··":"l", "·−···":"&", "·−··−·":"\"", "·−·−·":"+", "·−·−·−":".", "·−−":"w", "·−−·":"p", "·−−·−·":"@", "·−−−":"j", "·−−−−":"1", "·−−−−·":"'", "−":"t", "−·":"n", "−··":"d", "−···":"b", "−····":"6", "−····−":"-", "−···−":"=", "−··−":"x", "−··−·":"/", "−·−":"k", "−·−·":"c", "−·−·−·":";", "−·−·−−":"!", "−·−−":"y", "−·−−·":"(", "−·−−·−":")", "−−":"m", "−−·":"g", "−−··":"z", "−−···":"7", "−−··−−":",", "−−·−":"q", "−−−":"o", "−−−··":"8", "−−−···":":", "−−−−·":"9", "−−−−−":"0", "_":"··−−·−" + }; + + const googleLanguages = ["af","am","ar","az","be","bg","bn","bs","ca","ceb","co","cs","cy","da","de","el","en","eo","es","et","eu","fa","fi","fr","fy","ga","gd","gl","gu","ha","haw","hi","hmn","hr","ht","hu","hy","id","ig","is","it","iw","ja","jw","ka","kk","km","kn","ko","ku","ky","la","lb","lo","lt","lv","mg","mi","mk","ml","mn","mr","ms","mt","my","ne","nl","no","ny","or","pa","pl","ps","pt","ro","ru","rw","sd","si","sk","sl","sm","sn","so","sq","sr","st","su","sv","sw","ta","te","tg","th","tk","tl","tr","tt","ug","uk","ur","uz","vi","xh","yi","yo","zh-CN","zh-TW","zu"]; + const translationEngines = { + googleapi: { + name: "Google", + auto: true, + funcName: "googleApiTranslate", + languages: googleLanguages + }, + microsoft: { + name: "Microsoft", + auto: true, + funcName: "microsoftTranslate", + languages: ["af","am","ar","az","ba","bg","bn","bs","ca","cs","cy","da","de","el","en","es","et","eu","fa","fi","fil","fr","fr-CA","ga","gl","gu","ha","he","hi","hr","ht","hu","hy","id","ig","is","it","ja","ka","kk","km","kn","ko","ku","ky","lo","lt","lv","mg","mi","mk","ml","mr","ms","mt","my","ne","nl","or","pa","pl","ps","pt","pt-PT","ro","ru","rw","sd","si","sk","sl","sm","sn","so","sq","st","sv","sw","ta","te","th","tk","tr","tt","ug","uk","ur","uz","vi","xh","yo","zh-CN","zh-TW","zu"], + parser: { + "zh-CN": "zh-Hans", + "zh-TW": "zh-Hant" + }, + key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + deepl: { + name: "DeepL", + auto: true, + funcName: "deepLTranslate", + languages: ["bg","cs","da","de","en","el","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nl","no","pl","pt","ro","ru","sk","sl","sv","tr","uk","zh"], + premium: true, + key: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:fx" + }, + deepseek: { + name: "DeepSeek", + auto: true, + funcName: "deepSeekTranslate", + languages: googleLanguages, + key: "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + itranslate: { + name: "iTranslate", + auto: true, + funcName: "iTranslateTranslate", + languages: [...new Set(["af","ar","az","be","bg","bn","bs","ca","ceb","cs","cy","da","de","el","en","eo","es","et","eu","fa","fi","fil","fr","ga","gl","gu","ha","he","hi","hmn","hr","ht","hu","hy","id","ig","is","it","ja","jw","ka","kk","km","kn","ko","la","lo","lt","lv","mg","mi","mk","ml","mn","mr","ms","mt","my","ne","nl","no","ny","pa","pl","pt-BR","pt-PT","ro","ru","si","sk","sl","so","sq","sr","st","su","sv","sw","ta","te","tg","th","tr","uk","ur","uz","vi","we","yi","yo","zh-CN","zh-TW","zu"].concat(googleLanguages))].sort(), + key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + yandex: { + name: "Yandex", + auto: true, + funcName: "yandexTranslate", + languages: ["af","am","ar","az","ba","be","bg","bn","bs","ca","ceb","cs","cy","da","de","el","en","eo","es","et","eu","fa","fi","fr","ga","gd","gl","gu","he","hi","hr","ht","hu","hy","id","is","it","ja","jv","ka","kk","km","kn","ko","ky","la","lb","lo","lt","lv","mg","mhr","mi","mk","ml","mn","mr","ms","mt","my","ne","nl","no","pa","pap","pl","pt","ro","ru","si","sk","sl","sq","sr","su","sv","sw","ta","te","tg","th","tl","tr","tt","udm","uk","ur","uz","vi","xh","yi","zh"], + key: "trnsl.x.x.xxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + papago: { + name: "Papago", + auto: true, + funcName: "papagoTranslate", + languages: ["en","es","fr","id","ja","ko","th","vi","zh-CN","zh-TW"], + key: "xxxxxxxxxxxxxxxxxxxx xxxxxxxxxx" + }, + baidu: { + name: "Baidu", + auto: true, + funcName: "baiduTranslate", + languages: ["ar","bg","cs","da","de","el","en","es","et","fi","fr","hu","it","ja","ko","nl","pl","pt","ro","ru","sl","sv","th","vi","zh","zh-CN","zh-TW"], + parser: { + "ar": "ara", + "bg": "bul", + "da": "dan", + "es": "spa", + "et": "est", + "fi": "fin", + "fr": "fra", + "ja": "jp", + "ko": "kor", + "ro": "rom", + "sl": "slo", + "sv": "swe", + "vi": "vie", + "zh": "wyw", + "zh-CN": "zh", + "zh-TW": "cht" + }, + key: "xxxxxxxxxx xxxxxxxxxxxxxxxxxxxx" + } + }; + + var languages = {}; + var favorites = []; + var authKeys = {}; + var channelLanguages = {}, guildLanguages = {}; + var translationEnabledStates = [], isTranslating; + var translatedMessages = {}, oldMessages = {}; + + const defaultLanguages = { + INPUT: "auto", + OUTPUT: "$discord" + }; + const languageTypes = { + INPUT: "input", + OUTPUT: "output" + }; + const messageTypes = { + RECEIVED: "received", + SENT: "sent", + }; + + return class Translator extends Plugin { + onLoad () { + _this = this; + + this.defaults = { + general: { + addTranslateButton: {value: true, popout: false}, + addQuickTranslateButton: {value: true, popout: false}, + usePerChatTranslation: {value: true, popout: false}, + sendOriginalMessage: {value: false, popout: true}, + showOriginalMessage: {value: false, popout: true}, + useSpoilerInOriginal: {value: false, popout: false, description: "Use Spoilers instead of Quotes for the original Message Text"} + }, + choices: {}, + exceptions: { + wordStart: {value: ["!"], max: 3} + }, + prefixes: { + translationPrefixData: {value: [ + {prefix: "$fr", language: "fr"}, + {prefix: "$de", language: "de"}, + {prefix: "$es", language: "es"}, + {prefix: "$jp", language: "ja"} + ]} + }, + engines: { + translator: {value: "googleapi"}, + backup: {value: "----"} + } + }; + for (let m in messageTypes) this.defaults.choices[messageTypes[m]] = {value: Object.keys(languageTypes).reduce((newObj, l) => (newObj[languageTypes[l]] = defaultLanguages[l], newObj), {})}; + + this.modulePatches = { + before: [ + "ChannelTextAreaContainer", + "ChannelTextAreaEditor", + "Embed", + "MessageReply", + "Messages" + ], + after: [ + "ChannelTextAreaButtons", + "Embed", + "MessageButtons", + "MessageContent" + ] + }; + + this.css = ` + ${BDFDB.dotCN._translatortranslatebutton + BDFDB.dotCNS._translatortranslating + BDFDB.dotCN.textareaicon} { + color: var(--status-danger) !important; + } + ${BDFDB.dotCN._translatorconfigbutton} { + margin: 2px 3px 0 6px; + } + `; + } + + onStart () { + BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.MessageUtils, "startEditMessage", {before: e => { + if (e.methodArguments[1] && oldMessages[e.methodArguments[1]] && oldMessages[e.methodArguments[1]].content) e.methodArguments[2] = oldMessages[e.methodArguments[1]].content; + }}); + BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.MessageUtils, "editMessage", {before: e => { + delete translatedMessages[e.methodArguments[1]]; + delete oldMessages[e.methodArguments[1]]; + }}); + BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.MessageToolbarUtils, "useMessageMenu", {after: e => { + if (e.instance.props.message && e.instance.props.channel) { + let translated = !!translatedMessages[e.instance.props.message.id]; + let [children, index] = BDFDB.ContextMenuUtils.findItem(e.returnValue, {id: ["copy-text", "pin", "unpin"]}); + if (index == -1) [children, index] = BDFDB.ContextMenuUtils.findItem(e.returnValue, {id: ["edit", "add-reaction", "add-reaction-1", "quote"]}); + children.splice(index + 1, 0, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: translated ? this.labels.context_messageuntranslateoption : this.labels.context_messagetranslateoption, + disabled: isTranslating, + id: BDFDB.ContextMenuUtils.createItemId(this.name, translated ? "untranslate-message" : "translate-message"), + icon: _ => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.MenuItems.MenuIcon, { + icon: translated ? translateIconUntranslate : translateIcon + }), + action: _ => this.translateMessage(e.instance.props.message, e.instance.props.channel) + })); + } + }}); + this.forceUpdateAll(); + } + + onStop () { + this.forceUpdateAll(); + } + + getSettingsPanel (collapseStates = {}) { + let settingsPanel; + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, { + collapseStates: collapseStates, + children: _ => { + let settingsItems = []; + + for (let key in this.defaults.general) settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Switch", + plugin: this, + keys: ["general", key], + label: this.labels[`general_${key}`] || this.defaults.general[key].description, + value: this.settings.general[key] + })); + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormDivider, { + className: BDFDB.disCNS.dividerdefault + BDFDB.disCN.marginbottom8 + })); + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, { + title: "Own Auth Keys:", + children: Object.keys(translationEngines).filter(key => translationEngines[key].key).map(key => BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.marginbottom8, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.marginbottom8, + align: BDFDB.LibraryComponents.Flex.Align.CENTER, + direction: BDFDB.LibraryComponents.Flex.Direction.HORIZONTAL, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormTitle.Title, { + className: BDFDB.disCN.marginreset, + tag: BDFDB.LibraryComponents.FormTitle.Tags.H5, + children: translationEngines[key].name + }), + translationEngines[key].premium && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Switch", + margin: 0, + grow: 0, + label: "Paid Version", + tag: BDFDB.LibraryComponents.FormTitle.Tags.H5, + value: authKeys[key] && authKeys[key].paid, + onChange: value => { + if (!authKeys[key]) authKeys[key] = {}; + authKeys[key].paid = value; + BDFDB.DataUtils.save(authKeys, this, "authKeys"); + } + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + placeholder: translationEngines[key].key, + value: authKeys[key] && authKeys[key].key, + onChange: value => { + if (!authKeys[key]) authKeys[key] = {}; + authKeys[key].key = (value || "").trim(); + BDFDB.DataUtils.save(authKeys, this, "authKeys"); + } + }), + // region selector for Microsoft translator + key === "microsoft" && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, { + className: BDFDB.disCN.marginbottom8, + value: authKeys[key] && authKeys[key].region || "global", + options: [ + {value: "eastasia", label: "East Asia"}, + {value: "southeastasia", label: "Southeast Asia"}, + {value: "centralus", label: "Central US"}, + {value: "eastus", label: "East US"}, + {value: "eastus2", label: "East US 2"}, + {value: "westus", label: "West US"}, + {value: "northcentralus", label: "North Central US"}, + {value: "southcentralus", label: "South Central US"}, + {value: "northeurope", label: "North Europe"}, + {value: "westeurope", label: "West Europe"}, + {value: "japanwest", label: "Japan West"}, + {value: "japaneast", label: "Japan East"}, + {value: "brazilsouth", label: "Brazil South"}, + {value: "australiaeast", label: "Australia East"}, + {value: "australiasoutheast", label: "Australia Southeast"}, + {value: "southindia", label: "South India"}, + {value: "centralindia", label: "Central India"}, + {value: "westindia", label: "West India"}, + {value: "canadacentral", label: "Canada Central"}, + {value: "canadaeast", label: "Canada East"}, + {value: "uksouth", label: "UK South"}, + {value: "ukwest", label: "UK West"}, + {value: "westcentralus", label: "West Central US"}, + {value: "westus2", label: "West US 2"}, + {value: "koreacentral", label: "Korea Central"}, + {value: "koreasouth", label: "Korea South"}, + {value: "francecentral", label: "France Central"}, + {value: "francesouth", label: "France South"}, + {value: "australiacentral", label: "Australia Central"}, + {value: "australiacentral2", label: "Australia Central 2"}, + {value: "uaecentral", label: "UAE Central"}, + {value: "uaenorth", label: "UAE North"}, + {value: "southafricanorth", label: "South Africa North"}, + {value: "southafricawest", label: "South Africa West"}, + {value: "switzerlandnorth", label: "Switzerland North"}, + {value: "switzerlandwest", label: "Switzerland West"}, + {value: "germanynorth", label: "Germany North"}, + {value: "germanywestcentral", label: "Germany West Central"}, + {value: "norwaywest", label: "Norway West"}, + {value: "norwayeast", label: "Norway East"} + ].sort((a, b) => a.label.localeCompare(b.label)), + onChange: value => { + if (!authKeys[key]) authKeys[key] = {}; + authKeys[key].region = value; + BDFDB.DataUtils.save(authKeys, this, "authKeys"); + } + }) + ] + })) + })); + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormDivider, { + className: BDFDB.disCNS.dividerdefault + BDFDB.disCN.marginbottom8 + })); + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormItem, { + title: this.labels.prefixes_disable_text, + className: BDFDB.disCN.marginbottom8, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ListInput, { + placeholder: "New Exception Prefix (e.g. !)", + maxLength: this.defaults.exceptions.wordStart.max, + items: this.settings.exceptions.wordStart, + onChange: value => { + this.SettingsUpdated = true; + BDFDB.DataUtils.save(value, this, "exceptions", "wordStart"); + } + }) + })); + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormItem, { + title: this.labels.prefixes_enable_text, + className: BDFDB.disCN.marginbottom8, + children: [ + // Create a table-like layout for prefixes and language selection + ...(this.settings.prefixes.translationPrefixData || []).map((entry, index) => { + return BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.marginbottom8, + align: BDFDB.LibraryComponents.Flex.Align.CENTER, + children: [ + // Prefix input + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 0, + shrink: 0, + basis: "30%", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + placeholder: "Prefix (e.g. $fr)", + value: entry.prefix, + onChange: value => { + this.settings.prefixes.translationPrefixData[index].prefix = value; + BDFDB.DataUtils.save(this.settings.prefixes.translationPrefixData, this, "prefixes", "translationPrefixData"); + this.SettingsUpdated = true; + } + }) + }), + + // Language selection dropdown + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 1, + shrink: 0, + basis: "60%", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, { + value: entry.language, + options: Object.keys(languages) + .filter(key => !languages[key].auto && !languages[key].special) + .map(key => ({ + value: key, + label: BDFDB.LanguageUtils.getName(languages[key]) + })) + .sort((a, b) => a.label.localeCompare(b.label)), + onChange: value => { + this.settings.prefixes.translationPrefixData[index].language = value; + BDFDB.DataUtils.save(this.settings.prefixes.translationPrefixData, this, "prefixes", "translationPrefixData"); + this.SettingsUpdated = true; + } + }) + }), + + // Delete button + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 0, + shrink: 0, + basis: "10%", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, { + color: BDFDB.LibraryComponents.Button.Colors.RED, + size: BDFDB.LibraryComponents.Button.Sizes.TINY, + onClick: _ => { + this.settings.prefixes.translationPrefixData.splice(index, 1); + BDFDB.DataUtils.save(this.settings.prefixes.translationPrefixData, this, "prefixes", "translationPrefixData"); + this.SettingsUpdated = true; + BDFDB.ReactUtils.forceUpdate(this); + }, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + name: BDFDB.LibraryComponents.SvgIcon.Names.TRASH, + width: 16, + height: 16 + }) + }) + }) + ] + }); + }), + + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Button", + color: BDFDB.LibraryComponents.Button.Colors.GREEN, + onClick: _ => { + // Initialize if not exists + if (!this.settings.prefixes.translationPrefixData) this.settings.prefixes.translationPrefixData = []; + + // Add new entry with default values + this.settings.prefixes.translationPrefixData.push({ + prefix: "$en", + language: "en" + }); + + BDFDB.DataUtils.save(this.settings.prefixes.translationPrefixData, this, "prefixes", "translationPrefixData"); + this.SettingsUpdated = true; + BDFDB.ReactUtils.forceUpdate(this); + }, + children: "+ Add new prefix" + }) + ] + })); + + return settingsItems.flat(10).filter(n => n); + } + }); + } + + onSettingsClosed () { + if (this.SettingsUpdated) { + delete this.SettingsUpdated; + this.forceUpdateAll(); + } + } + + forceUpdateAll () { + favorites = BDFDB.DataUtils.load(this, "favorites"); + favorites = !BDFDB.ArrayUtils.is(favorites) ? [] : favorites; + + authKeys = BDFDB.DataUtils.load(this, "authKeys"); + channelLanguages = BDFDB.DataUtils.load(this, "channelLanguages"); + guildLanguages = BDFDB.DataUtils.load(this, "guildLanguages"); + + translationEnabledStates = BDFDB.DataUtils.load(this, "translationEnabledStates"); + translationEnabledStates = BDFDB.ArrayUtils.is(translationEnabledStates) ? translationEnabledStates : []; + + this.setLanguages(); + BDFDB.PatchUtils.forceAllUpdates(this); + BDFDB.MessageUtils.rerenderAll(); + } + + onMessageContextMenu (e) { + if (e.instance.props.message && e.instance.props.channel) { + let translated = !!translatedMessages[e.instance.props.message.id]; + let hint = BDFDB.BDUtils.isPluginEnabled("MessageUtilities") ? BDFDB.BDUtils.getPlugin("MessageUtilities").getActiveShortcutString("__Translate_Message") : null; + let [children, index] = BDFDB.ContextMenuUtils.findItem(e.returnvalue, {id: ["copy-text", "pin", "unpin"]}); + if (index == -1) [children, index] = BDFDB.ContextMenuUtils.findItem(e.returnvalue, {id: ["edit", "add-reaction", "add-reaction-1", "quote"]}); + children.splice(index > -1 ? index + 1 : 0, 0, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: translated ? this.labels.context_messageuntranslateoption : this.labels.context_messagetranslateoption, + id: BDFDB.ContextMenuUtils.createItemId(this.name, translated ? "untranslate-message" : "translate-message"), + icon: hint && (_ => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.MenuItems.MenuHint, { + hint: hint + })), + icon: _ => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.MenuItems.MenuIcon, { + icon: translated ? translateIconUntranslate : translateIcon + }), + disabled: !translated && isTranslating, + action: _ => this.translateMessage(e.instance.props.message, e.instance.props.channel) + })); + this.injectSearchItem(e, false); + } + } + + onTextAreaContextMenu (e) { + this.injectSearchItem(e, true); + } + + injectSearchItem (e, ownMessage) { + let text = document.getSelection().toString(); + if (text) { + let translating, foundTranslation, foundInput, foundOutput, copied; + let [children, index] = BDFDB.ContextMenuUtils.findItem(e.returnvalue, {id: ["devmode-copy-id", "search-google"], group: true}); + children.splice(index > -1 ? index + 1 : 0, 0, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, { + children: BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + id: BDFDB.ContextMenuUtils.createItemId(this.name, "search-translation"), + icon: _ => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.MenuItems.MenuIcon, { + icon: translateIcon + }), + disabled: isTranslating, + label: this.labels.context_translator, + persisting: true, + action: event => { + let item = BDFDB.DOMUtils.getParent(BDFDB.dotCN.menuitem, event.target); + if (item) { + let createTooltip = _ => { + BDFDB.TooltipUtils.create(item, !foundTranslation ? this.labels.toast_translating_failed : [ + `${BDFDB.LanguageUtils.LibraryStrings.from} ${foundInput.name}:`, + text, + `${BDFDB.LanguageUtils.LibraryStrings.to} ${foundOutput.name}:`, + foundTranslation + ].map(n => BDFDB.ReactUtils.createElement("div", {children: n})), { + type: "right", + color: foundTranslation ? "primary" : "red", + className: "googletranslate-tooltip" + }); + }; + if (foundTranslation && foundInput && foundOutput) { + if (document.querySelector(".googletranslate-tooltip")) { + if (!copied) { + copied = true; + BDFDB.LibraryModules.WindowUtils.copy(foundTranslation); + BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("clipboard_success", BDFDB.LanguageUtils.LanguageStrings.TEXT), {type: "success"}); + } + else { + BDFDB.ContextMenuUtils.close(e.instance); + BDFDB.DiscordUtils.openLink(this.getGoogleTranslatePageURL(foundInput.id, foundOutput.id, text)); + } + } + else createTooltip(); + } + else if (!translating) { + translating = true; + this.translateText(text, ownMessage ? messageTypes.SENT : messageTypes.RECEIVED, (translation, input, output) => { + if (translation) { + foundTranslation = translation, foundInput = input, foundOutput = output; + createTooltip(); + } + else createTooltip(); + }); + } + } + } + }) + })); + } + } + + processMessageButtons (e) { + if (!this.settings.general.addQuickTranslateButton || !e.instance.props.message || !e.instance.props.channel) return; + let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, {props: [["className", BDFDB.disCN.messagebuttons]]}); + if (index == -1) return; + let translated = !!translatedMessages[e.instance.props.message.id]; + children.unshift(BDFDB.ReactUtils.createElement(class extends BdApi.React.Component { + render() { + return BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + key: translated ? "untranslate-message" : "translate-message", + text: _ => translated ? _this.labels.context_messageuntranslateoption : _this.labels.context_messagetranslateoption, + tooltipConfig: {className: BDFDB.disCN.messagetoolbartooltip}, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCNS.messagetoolbarhoverbutton + BDFDB.disCN.messagetoolbarbutton, + onClick: _ => { + if (!isTranslating) _this.translateMessage(e.instance.props.message, e.instance.props.channel).then(_ => { + translated = !!translatedMessages[e.instance.props.message.id]; + BDFDB.ReactUtils.forceUpdate(this); + }); + }, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCNS.messagetoolbaricon + BDFDB.disCN.messagetoolbarbuttoncontent, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.messagetoolbaricon, + nativeClass: true, + iconSVG: translated ? translateIconUntranslate : translateIcon + }) + }) + }) + }); + } + })); + } + + processChannelTextAreaContainer (e) { + if (e.instance.props.type != BDFDB.DiscordConstants.ChannelTextAreaTypes.NORMAL && e.instance.props.type != BDFDB.DiscordConstants.ChannelTextAreaTypes.SIDEBAR) return; + BDFDB.PatchUtils.patch(this, e.instance.props, "onSubmit", {instead: e2 => { + if (e2.methodArguments[0].value) { + const text = e2.methodArguments[0].value; + + // Check for translation prefixes + const prefixMap = {}; + const prefixData = this.settings.prefixes && this.settings.prefixes.translationPrefixData || []; + for (const entry of prefixData) { + prefixMap[entry.prefix] = entry.language; + } + + let foundPrefix = null; + let targetLanguage = null; + + // Check for prefixes more efficiently + for (const prefix in prefixMap) { + if (text.trim().startsWith(prefix)) { + foundPrefix = prefix; + targetLanguage = prefixMap[prefix]; + break; + } + } + + if (foundPrefix) { + e2.stopOriginalMethodCall(); + // Remove the prefix from the message + const cleanText = text.trim().substring(foundPrefix.length).trim(); + + // Translate with the specific target language + this.translateText(cleanText, messageTypes.SENT, (translation, input, output) => { + // Override the output language with the one from the prefix + output = {id: targetLanguage, name: languages[targetLanguage] ? languages[targetLanguage].name : targetLanguage}; + + translation = !translation ? cleanText : (this.settings.general.sendOriginalMessage ? (translation + (!this.settings.general.useSpoilerInOriginal ? ("\n\n> *" + cleanText.split("\n").join("*\n> *") + "*").replace(/> \*\*\n/g, "> \n") : `\n\n||${cleanText}||`)) : translation); + e2.originalMethod(Object.assign({}, e2.methodArguments[0], {value: translation})); + }, targetLanguage); + + return Promise.resolve({ + shouldClear: true, + shouldRefocus: true + }); + } + else if (this.isTranslationEnabled(e.instance.props.channel.id)) { + e2.stopOriginalMethodCall(); + this.translateText(e2.methodArguments[0].value, messageTypes.SENT, (translation, input, output) => { + translation = !translation ? e2.methodArguments[0].value : (this.settings.general.sendOriginalMessage ? (translation + (!this.settings.general.useSpoilerInOriginal ? ("\n\n> *" + e2.methodArguments[0].value.split("\n").join("*\n> *") + "*").replace(/> \*\*\n/g, "> \n") : `\n\n||${e2.methodArguments[0].value}||`)) : translation); + e2.originalMethod(Object.assign({}, e2.methodArguments[0], {value: translation})); + }); + return Promise.resolve({ + shouldClear: true, + shouldRefocus: true + }); + } + } + return e2.callOriginalMethodAfterwards(); + }}, {noCache: true}); + } + + processChannelTextAreaEditor (e) { + if (this.isTranslationEnabled(e.instance.props.channel.id) && isTranslating) e.instance.props.disabled = true; + } + + processChannelTextAreaButtons (e) { + if (!this.settings.general.addTranslateButton || e.instance.props.disabled || e.instance.props.type != BDFDB.DiscordConstants.ChannelTextAreaTypes.NORMAL && e.instance.props.type != BDFDB.DiscordConstants.ChannelTextAreaTypes.SIDEBAR) return; + if (e.returnvalue) e.returnvalue.props.children.unshift(BDFDB.ReactUtils.createElement(TranslateButtonComponent, { + guildId: e.instance.props.channel.guild_id ? e.instance.props.channel.guild_id : "@me", + channelId: e.instance.props.channel.id + })); + } + + processMessages (e) { + e.instance.props.channelStream = [].concat(e.instance.props.channelStream); + for (let i in e.instance.props.channelStream) { + let message = e.instance.props.channelStream[i].content; + if (message) { + if (BDFDB.ArrayUtils.is(message.attachments)) this.checkMessage(e.instance.props.channelStream[i], message); + else if (BDFDB.ArrayUtils.is(message)) for (let j in message) { + let childMessage = message[j].content; + if (childMessage && BDFDB.ArrayUtils.is(childMessage.attachments)) this.checkMessage(message[j], childMessage); + } + } + } + } + + checkMessage (stream, message) { + let translation = translatedMessages[message.id]; + if (translation) stream.content.content = translation.content; + else if (oldMessages[message.id] && Object.keys(message).some(key => !BDFDB.equals(oldMessages[message.id][key], message[key]))) { + stream.content.content = oldMessages[message.id].content; + delete oldMessages[message.id]; + } + } + + processMessageReply (e) { + if (!e.instance.props.referencedMessage || !e.instance.props.referencedMessage.message || !translatedMessages[e.instance.props.referencedMessage.message.id]) return; + e.instance.props.referencedMessage = Object.assign({}, e.instance.props.referencedMessage); + e.instance.props.referencedMessage.message = new BDFDB.DiscordObjects.Message(e.instance.props.referencedMessage.message); + e.instance.props.referencedMessage.message.content = translatedMessages[e.instance.props.referencedMessage.message.id].content; + } + + processMessageContent (e) { + if (!e.instance.props.message) return; + let translation = translatedMessages[e.instance.props.message.id]; + if (translation && translation.content) e.returnvalue.props.children.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: `${BDFDB.LanguageUtils.getName(translation.input)} ➝ ${BDFDB.LanguageUtils.getName(translation.output)}`, + tooltipConfig: {style: "max-width: 400px"}, + children: BDFDB.ReactUtils.createElement("span", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.messagetimestamp, BDFDB.disCN.messagetimestampinline, BDFDB.disCN._translatortranslated), + children: BDFDB.ReactUtils.createElement("span", { + className: BDFDB.disCN.messageedited, + children: `(${this.labels.translated_watermark})` + }) + }) + })); + } + + processEmbed (e) { + if (!e.instance.props.embed || !e.instance.props.embed.message_id) return; + let translation = translatedMessages[e.instance.props.embed.message_id]; + if (translation && Object.keys(translation.embeds).length) { + if (!e.returnvalue) e.instance.props.embed = Object.assign({}, e.instance.props.embed, { + rawDescription: translation.embeds[e.instance.props.embed.id].description, + rawTitle: translation.embeds[e.instance.props.embed.id].title, + footer: Object.assign({}, e.instance.props.embed.footer || {}, { + text: translation.embeds[e.instance.props.embed.id].footerText || "" + }), + fields: translation.embeds[e.instance.props.embed.id].fields.map(n => ({ rawName: n.name, rawValue: n.value })), + originalDescription: e.instance.props.embed.originalDescription || e.instance.props.embed.rawDescription, + originalTitle: e.instance.props.embed.originalTitle || e.instance.props.embed.rawTitle, + originalFields: e.instance.props.embed.originalFields || e.instance.props.embed.fields, + originalFooter: e.instance.props.embed.originalFooter || Object.assign({}, e.instance.props.embed.footer) + }); + else { + let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, { props: [["className", BDFDB.disCN.embeddescription]] }); + if (index > -1) { + // Ensure children[index].props.children is an array before attempting to push + // Fix for errors on multiple embeds in single messages + if (!Array.isArray(children[index].props.children)) { + children[index].props.children = [children[index].props.children]; + } + + children[index].props.children.push( + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: `${BDFDB.LanguageUtils.getName(translation.input)} ➝ ${BDFDB.LanguageUtils.getName(translation.output)}`, + tooltipConfig: { style: "max-width: 400px" }, + children: BDFDB.ReactUtils.createElement("span", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.messagetimestamp, BDFDB.disCN.messagetimestampinline, BDFDB.disCN._translatortranslated), + children: BDFDB.ReactUtils.createElement("span", { + className: BDFDB.disCN.messageedited, + children: `(${this.labels.translated_watermark})` + }) + }) + }) + ); + } + } + } + else if (!e.returnvalue && e.instance.props.embed.originalDescription) { + e.instance.props.embed = Object.assign({}, e.instance.props.embed, { + rawDescription: e.instance.props.embed.originalDescription, + rawTitle: e.instance.props.embed.originalTitle, + fields: e.instance.props.embed.originalFields, + footer: e.instance.props.embed.originalFooter + }); + delete e.instance.props.embed.originalDescription; + delete e.instance.props.embed.originalTitle; + delete e.instance.props.embed.originalFields; + delete e.instance.props.embed.originalFooter; + } + } + + toggleTranslation (channelId) { + if (!this.isTranslationEnabled(channelId)) translationEnabledStates.push(this.settings.general.usePerChatTranslation ? channelId : "global"); + else BDFDB.ArrayUtils.remove(translationEnabledStates, this.settings.general.usePerChatTranslation ? channelId : "global", true); + BDFDB.DataUtils.save(translationEnabledStates, this, "translationEnabledStates"); + } + + isTranslationEnabled (channelId) { + return translationEnabledStates.includes(this.settings.general.usePerChatTranslation ? channelId : "global"); + } + + setLanguages () { + if (this.settings.engines.translator == this.settings.engines.backup) { + this.settings.engines.backup = Object.keys(translationEngines).filter(n => n != this.settings.engines.translator)[0]; + BDFDB.DataUtils.save(this.settings.engines, this, "engines"); + } + let engine = translationEngines[this.settings.engines.translator] || {}; + let backup = translationEngines[this.settings.engines.backup] || {}; + let languageIds = [].concat(engine.languages, backup.languages).flat(10).filter(n => n); + languages = BDFDB.ObjectUtils.deepAssign( + !engine.auto && !backup.auto ? {} : { + auto: { + auto: true, + name: this.labels.detect_language, + id: "auto" + } + }, + BDFDB.ObjectUtils.filter(BDFDB.LanguageUtils.languages, lang => languageIds.includes(lang.id)), + { + binary: { + special: true, + name: "Binary", + id: "binary" + }, + braille: { + special: true, + name: "Braille 6-dot", + id: "braille" + }, + morse: { + special: true, + name: "Morse", + id: "morse" + }, + hex: { + special: true, + name: "Hexadecimal", + id: "hex" + }, + } + ); + for (let id in languages) languages[id].fav = favorites.includes(id) ? 0 : 1; + languages = BDFDB.ObjectUtils.sort(languages, "fav"); + } + + getLanguageChoice (direction, place, channelId) { + this.setLanguages(); + let choice; + let channel = channelId && BDFDB.LibraryStores.ChannelStore.getChannel(channelId); + let guildId = channel ? (channel.guild_id ? channel.guild_id : "@me") : null; + if (channelLanguages[channelId] && channelLanguages[channelId][place]) choice = channelLanguages[channelId][place][direction]; + else if (guildId && guildLanguages[guildId] && guildLanguages[guildId][place]) choice = guildLanguages[guildId][place][direction]; + else choice = this.settings.choices[place] && this.settings.choices[place][direction]; + choice = languages[choice] ? choice : Object.keys(languages)[0]; + choice = direction == languageTypes.OUTPUT && choice == "auto" ? "en" : choice; + return choice; + } + + saveLanguageChoice (choice, direction, place, channelId) { + let channel = channelId && BDFDB.LibraryStores.ChannelStore.getChannel(channelId); + let guildId = channel ? (channel.guild_id ? channel.guild_id : "@me") : null; + if (channelLanguages[channelId] && channelLanguages[channelId][place]) { + channelLanguages[channelId][place][direction] = choice; + BDFDB.DataUtils.save(channelLanguages, this, "channelLanguages"); + } + else if (guildLanguages[guildId] && guildLanguages[guildId][place]) { + guildLanguages[guildId][place][direction] = choice; + BDFDB.DataUtils.save(guildLanguages, this, "guildLanguages"); + } + else { + this.settings.choices[place][direction] = choice; + BDFDB.DataUtils.save(this.settings.choices, this, "choices"); + } + } + + translateMessage (message, channel) { + return new Promise(callback => { + if (!message) return callback(null); + if (translatedMessages[message.id]) { + delete translatedMessages[message.id]; + BDFDB.MessageUtils.rerenderAll(true); + callback(false); + } + else { + let originalContentData = { + content: message.content || "", + embeds: message.embeds.map(embed => ({ + description: embed.rawDescription || "", + title: embed.rawTitle || "", + footerText: embed.footer ? embed.footer.text : "", + fields: embed.fields ? embed.fields.map(field => ({name: field.rawName, value: field.rawValue})) : [] + })) + }; + let allTextsToTranslate = originalContentData.content; + originalContentData.embeds.forEach(embed => { + allTextsToTranslate += `\n__________________ __________________ __________________\n`; + allTextsToTranslate += embed.title + "\n" + embed.description; + embed.fields.forEach(field => { + allTextsToTranslate += "\n\n" + field.name + "__________________" + field.value; + }); + if (embed.footerText) allTextsToTranslate += "\n" + embed.footerText; + }); + message.embeds.forEach(embed => embed.message_id = message.id); + let embedIds = message.embeds.map(embed => embed.id); + this.translateText(allTextsToTranslate, messageTypes.RECEIVED, (translation, input, output) => { + if (translation) { + let oldStrings = originalContentData.content.split(/\n{0,1}__________________ __________________ __________________\n{0,1}/); + let strings = translation.split(/\n{0,1}__________________ __________________ __________________\n{0,1}/); + let oldContent = this.settings.general.showOriginalMessage && (oldStrings.shift() || "").trim(); + let content = (strings.shift() || "").trim() + (oldContent ? (!this.settings.general.useSpoilerInOriginal ? ("\n\n> *" + oldContent.split("\n").join("*\n> *") + "*").replace(/> \*\*\n/g, "> \n") : `\n\n||${oldContent}||`) : ""); + let embeds = strings.reduce((dict, segment, index) => { + let embedId = embedIds[index]; + let segmentLines = segment.split("\n"); + let title = segmentLines.shift(); + let description = segmentLines.shift(); + let footerText = segmentLines.pop(); + let fieldsSegment = segmentLines.join("\n").split("\n\n"); + let fields = fieldsSegment.map(line => { + let [name, value] = line.split("__________________"); + return {name, value}; + }); + + dict[embedId] = {title, description, fields, footerText}; + return dict; + }, {}); + oldMessages[message.id] = new BDFDB.DiscordObjects.Message(message); + oldMessages[message.id].originalContentData = originalContentData; + translatedMessages[message.id] = { + content: content, + embeds: embeds, + input, + output + }; + BDFDB.MessageUtils.rerenderAll(true); + } + callback(true); + }); + } + }); + } + + translateText (text, place, callback, forcedOutputLanguage = null) { + let toast = null, toastInterval, finished = false, finishTranslation = translation => { + isTranslating = false; + if (toast) toast.close(); + + if (finished) return; + finished = true; + if (translation) translation = this.addExceptions(translation, excepts); + callback(translation == text ? "" : translation, input, output); + }; + let [newText, excepts, translate] = this.removeExceptions(text.trim(), place); + let channelId = BDFDB.LibraryStores.SelectedChannelStore.getChannelId(); + let input = Object.assign({}, languages[this.getLanguageChoice(languageTypes.INPUT, place, channelId)]); + let output = forcedOutputLanguage ? + Object.assign({}, languages[forcedOutputLanguage] || {id: forcedOutputLanguage, name: forcedOutputLanguage}) : + Object.assign({}, languages[this.getLanguageChoice(languageTypes.OUTPUT, place, channelId)]); + + if (translate && input.id != output.id) { + let specialCase = this.checkForSpecialCase(newText, input); + if (specialCase) { + input.name = specialCase.name; + switch (specialCase.id) { + case "binary": newText = this.binary2string(newText); break; + case "braille": newText = this.braille2string(newText); break; + case "morse": newText = this.morse2string(newText); break; + case "hex": newText = this.hex2string(newText); break; + } + } + if (output.special) { + switch (output.id) { + case "binary": newText = this.string2binary(newText); break; + case "braille": newText = this.string2braille(newText); break; + case "morse": newText = this.string2morse(newText); break; + case "hex": newText = this.string2hex(newText); break; + } + finishTranslation(newText); + } + else { + const startTranslating = engine => { + isTranslating = true; + if (toast) toast.close(); + BDFDB.TimeUtils.clear(toastInterval); + + toast = BDFDB.NotificationUtils.toast(`${this.labels.toast_translating} (${translationEngines[engine].name}) - ${BDFDB.LanguageUtils.LibraryStrings.please_wait}`, { + timeout: 0, + ellipsis: true, + position: "center", + onClose: _ => BDFDB.TimeUtils.clear(toastInterval) + }); + toastInterval = BDFDB.TimeUtils.interval((_, count) => { + if (count < 40) return; + finishTranslation(""); + BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed} (${translationEngines[engine].name}) - ${this.labels.toast_translating_tryanother}`, { + type: "danger", + position: "center" + }); + }, 500); + }; + if (this.validTranslator(this.settings.engines.translator, input, output, specialCase)) { + startTranslating(this.settings.engines.translator); + this[translationEngines[this.settings.engines.translator].funcName].apply(this, [{input, output, text: newText, specialCase, engine: translationEngines[this.settings.engines.translator]}, translation => { + if (!translation && this.validTranslator(this.settings.engines.backup, input, output, specialCase)) { + startTranslating(this.settings.engines.backup); + this[translationEngines[this.settings.engines.backup].funcName].apply(this, [{input, output, text: newText, specialCase, engine: translationEngines[this.settings.engines.backup]}, finishTranslation]); + } + else finishTranslation(translation); + }]); + } + else if (this.validTranslator(this.settings.engines.backup, input, output, specialCase)) { + startTranslating(this.settings.engines.backup); + this[translationEngines[this.settings.engines.backup].funcName].apply(this, [{input, output, text: newText, specialCase, engine: translationEngines[this.settings.engines.backup]}, finishTranslation]); + } + else finishTranslation(); + } + } + else finishTranslation(); + } + + validTranslator (key, input, output, specialCase) { + return translationEngines[key] && typeof this[translationEngines[key].funcName] == "function" && (specialCase || input.auto && translationEngines[key].auto || translationEngines[key].languages.includes(input.id) && translationEngines[key].languages.includes(output.id)); + } + + googleApiTranslate (data, callback) { + BDFDB.LibraryRequires.request("https://translate.googleapis.com/translate_a/single", { + form: { + "client": "gtx", + "dt": "t", + "dj": "1", + "source": "input", + "sl": data.input.id, + "tl": data.output.id, + "q": encodeURIComponent(data.text) + } + }, (error, response, body) => { + if (!error && body && response.statusCode == 200) { + try { + body = JSON.parse(body); + if (!data.specialCase && body.src && body.src && languages[body.src]) { + data.input.name = languages[body.src].name; + data.input.ownlang = languages[body.src].ownlang; + } + callback(body.sentences.map(n => n && n.trans).filter(n => n).join("")); + } + catch (err) {callback("");} + } + else { + if (response.statusCode == 429) BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_hourlylimit}`, { + type: "danger", + position: "center" + }); + else BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_serverdown}`, { + type: "danger", + position: "center" + }); + callback(""); + } + }); + } + + microsoftTranslate (data, callback) { + BDFDB.LibraryRequires.request("https://api.cognitive.microsofttranslator.com/translate", { + method: "post", + headers: { + "Content-Type": "application/json", + "Ocp-Apim-Subscription-Key": authKeys.microsoft && authKeys.microsoft.key || "1ea861033a56423f860fd6f5ff33e308", + "Ocp-Apim-Subscription-Region": authKeys.microsoft && authKeys.microsoft.region || "global" + }, + body: JSON.stringify([{"Text": data.text}]), + form: Object.assign({ + "api-version": "3.0", + "to": data.output.id + }, data.input.auto ? {} : {"from": data.input.id}) + }, (error, response, body) => { + if (!error && body && response.statusCode == 200) { + try { + body = JSON.parse(body)[0]; + if (!data.specialCase && body.detectedLanguage && body.detectedLanguage.language && languages[body.detectedLanguage.language.toLowerCase()]) { + data.input.name = languages[body.detectedLanguage.language.toLowerCase()].name; + data.input.ownlang = languages[body.detectedLanguage.language.toLowerCase()].ownlang; + } + callback(body.translations.map(n => n && n.text).filter(n => n).join("")); + } + catch (err) {callback("");} + } + else { + if (response.statusCode == 403 || response.statusCode == 429) BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_dailylimit}`, { + type: "danger", + position: "center" + }); + else if (response.statusCode == 401) BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_keyoutdated}`, { + type: "danger", + position: "center" + }); + else BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_serverdown}`, { + type: "danger", + position: "center" + }); + callback(""); + } + }); + } + + deepLTranslate (data, callback) { + BDFDB.LibraryRequires.request(authKeys.deepl && authKeys.deepl.paid ? "https://api.deepl.com/v2/translate" : "https://api-free.deepl.com/v2/translate", { + method: "post", + headers: { + "Content-Type": "application/json", + "Authorization": `DeepL-Auth-Key ${authKeys.deepl && authKeys.deepl.key || "75cc2f40-fdae-14cd-7242-6a384e2abb9c:fx"}` + }, + body: JSON.stringify(Object.assign({ + "text": [data.text], + "target_lang": data.output.id + }, data.input.auto ? {} : {"source_lang": data.input.id})) + }, (error, response, body) => { + if (!error && body && response.statusCode == 200) { + try { + body = JSON.parse(body); + if (!data.specialCase && body.translations[0] && body.translations[0].detected_source_language && languages[body.translations[0].detected_source_language.toLowerCase()]) { + data.input.name = languages[body.translations[0].detected_source_language.toLowerCase()].name; + data.input.ownlang = languages[body.translations[0].detected_source_language.toLowerCase()].ownlang; + } + callback(body.translations.map(n => n && n.text).filter(n => n).join("")); + } + catch (err) {callback("");} + } + else { + if (response.statusCode == 429 || response.statusCode == 456) BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_dailylimit}`, { + type: "danger", + position: "center" + }); + else if (response.statusCode == 403) BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_keyoutdated}`, { + type: "danger", + position: "center" + }); + else BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_serverdown}`, { + type: "danger", + position: "center" + }); + callback(""); + } + }); + } + + deepSeekTranslate(data, callback) { + const translationPrompt = ` + You are a professional localization expert. Translate the following ${data.input.auto ? "" : data.input.name + " "}content to ${data.output.name} following these rules: + 1. Return ONLY the translation without any explanations + 2. Use natural, fluent language + 3. Maintain consistent terminology for technical/game terms + 4. Preserve the original tone and style + 5. Use concise sentence structures + 6. Handle numbers/units/proper nouns correctly + 7. Use community-approved expressions for game content + 8. Convert [NEWLINE] markers to actual line breaks (don't show them literally) + + Text to translate: + ${data.text.replace(/\n/g, " [NEWLINE] ").replace(/\s+/g, " ")} + `; + + const requestData = { + model: "deepseek-chat", + messages: [{ + role: "system", + content: "You are a senior bilingual localization specialist" + }, { + role: "user", + content: translationPrompt + }], + temperature: 0.2, + top_p: 0.8 + }; + + BDFDB.LibraryRequires.request("https://api.deepseek.com/v1/chat/completions", { + method: "post", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${authKeys.deepseek && authKeys.deepseek.key || ""}` + }, + body: JSON.stringify(requestData) + }, (error, response, body) => { + if (!error && body && response.statusCode == 200) { + try { + body = JSON.parse(body); + let translatedText = body.choices[0].message.content; + translatedText = translatedText.replace(/\[NEWLINE\]/g, '\n'); + callback(translatedText); + } catch (err) { + console.error("DeepSeek translation error:", err); + callback(""); + } + } else { + if (response.statusCode == 401 || response.statusCode == 403) { + BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_keyoutdated}`, { + type: "danger", + position: "center" + }); + } else if (response.statusCode == 429) { + BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_dailylimit}`, { + type: "danger", + position: "center" + }); + } else { + BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_serverdown}`, { + type: "danger", + position: "center" + }); + } + callback(""); + } + }); + } + + iTranslateTranslate (data, callback) { + let translate = _ => { + BDFDB.LibraryRequires.request("https://web-api.itranslateapp.com/v3/texts/translate", { + method: "post", + headers: { + "API-KEY": authKeys.itranslate && authKeys.itranslate.key || data.engine.APIkey + }, + body: JSON.stringify({ + source: { + dialect: data.input.id, + text: data.text + }, + target: { + dialect: data.output.id + } + }) + }, (error, response, body) => { + if (!error && response && response.statusCode == 200) { + try { + body = JSON.parse(body); + if (!data.specialCase && body.source && body.source.dialect && languages[body.source.dialect]) { + data.input.name = languages[body.source.dialect].name; + data.input.ownlang = languages[body.source.dialect].ownlang; + } + callback(body.target.text); + } + catch (err) {callback("");} + } + else { + if (response.statusCode == 429) BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_dailylimit}`, { + type: "danger", + position: "center" + }); + else if (response.statusCode == 403) BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_keyoutdated}`, { + type: "danger", + position: "center" + }); + else BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_serverdown}`, { + type: "danger", + position: "center" + }); + callback(""); + } + }); + }; + if (authKeys.itranslate && authKeys.itranslate.key || data.engine.APIkey) translate(); + else BDFDB.LibraryRequires.request("https://www.itranslate.com/js/webapp/main.js", {gzip: true}, (error, response, body) => { + if (!error && body) { + let APIkey = /var API_KEY = "(.+)"/.exec(body); + if (APIkey) { + data.engine.APIkey = APIkey[1]; + translate(); + } + else callback(""); + } + else callback(""); + }); + } + + yandexTranslate (data, callback) { + BDFDB.LibraryRequires.request("https://translate.yandex.net/api/v1.5/tr/translate", { + form: { + "key": authKeys.yandex && authKeys.yandex.key || "trnsl.1.1.20191206T223907Z.52bd512eca953a5b.1ec123ce4dcab3ae859f312d27cdc8609ab280de", + "text": encodeURIComponent(data.text), + "lang": data.specialCase || data.input.auto ? data.output.id : (data.input.id + "-" + data.output.id), + "options": "1" + } + }, (error, response, body) => { + if (!error && body && response.statusCode == 200) { + try { + body = BDFDB.DOMUtils.create(body); + let translation = body.querySelector("text"); + let detected = body.querySelector("detected"); + if (translation && detected) { + let detectedLang = detected.getAttribute("lang"); + if (!data.specialCase && detectedLang && languages[detectedLang]) { + data.input.name = languages[detectedLang].name; + data.input.ownlang = languages[detectedLang].ownlang; + } + callback(translation.innerText); + } + else callback(""); + } + catch (err) {callback("");} + } + else if (body && body.indexOf('code="408"') > -1) { + BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_monthlylimit}`, { + type: "danger", + position: "center" + }); + callback(""); + } + else { + BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_serverdown}/${this.labels.error_keyoutdated}`, { + type: "danger", + position: "center" + }); + callback(""); + } + }); + } + + papagoTranslate (data, callback) { + const credentials = (authKeys.papago && authKeys.papago.key || "kUNGxtAmTJQFbaFehdjk zC70k3VhpM").split(" "); + const doTranslate = langCode => { + BDFDB.LibraryRequires.request("https://openapi.naver.com/v1/papago/n2mt", { + method: "post", + headers: { + "X-Naver-Client-Id": credentials[0], + "X-Naver-Client-Secret": credentials[1], + "Content-Type": "application/x-www-form-urlencoded" + }, + form: { + source: langCode, + target: data.output.id, + text: data.text + } + }, (error, response, body) => { + if (!error && body && response.statusCode == 200) { + try { + let message = (JSON.parse(body) || {}).message; + let result = message && (message.body || message.result); + if (result && result.translatedText) callback(result.translatedText); + else callback(""); + } + catch (err) {callback("");} + } + else { + if (response.statusCode == 429) BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_hourlylimit}`, { + type: "danger", + position: "center" + }); + else BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_serverdown}/${this.labels.error_keyoutdated}`, { + type: "danger", + position: "center" + }); + callback(""); + } + }); + }; + if (data.input.auto) { + BDFDB.LibraryRequires.request("https://openapi.naver.com/v1/papago/detectLangs", { + method: "post", + headers: { + "X-Naver-Client-Id": credentials[0], + "X-Naver-Client-Secret": credentials[1], + "Content-Type": "application/x-www-form-urlencoded" + }, + form: { + query: data.text, + } + }, (error, response, body) => { + let langCode = "en"; + if (!error && body && response.statusCode == 200) { + try { + langCode = JSON.parse(body)["langCode"]; + } catch (err) { + langCode = "en"; + } + } + data.input.name = languages[langCode].name; + data.input.ownlang = languages[langCode].ownlang; + doTranslate(langCode); + }); + } + else doTranslate(data.input.id); + } + + baiduTranslate (data, callback) { + const credentials = (authKeys.baidu && authKeys.baidu.key || "20221009001380882 TOPnUKz8jJ32AZNOuUhX").split(" "); + const salt = BDFDB.NumberUtils.generateId(); + BDFDB.LibraryRequires.request("https://fanyi-api.baidu.com/api/trans/vip/translate", { + bdVersion: true, + method: "post", + form: { + from: translationEngines.baidu.parser[data.input.id] || data.input.id, + to: translationEngines.baidu.parser[data.output.id] || data.output.id, + q: encodeURIComponent(data.text), + appid: credentials[0], + salt: salt, + sign: this.MD5(credentials[0] + data.text + salt + (credentials[2] || credentials[1])) + } + }, (error, response, result) => { + if (!error && result && response.statusCode == 200) { + try { + result = JSON.parse(result) || {}; + if (!result.error_code) { + let messages = result.trans_result; + if (messages && messages.length > 0 && result.from != result.to) callback(messages.map(message => decodeURIComponent(message.dst)).join("\n")); + else {callback("");} + } + else { + if (result.error_code == 54004) BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_monthlylimit}.`, { + type: "danger", + position: "center" + }); + else BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${result.error_code} : ${result.error_msg}.`, { + type: "danger", + position: "center" + }); + callback(""); + } + } + catch (err) {callback("");} + } + else { + BDFDB.NotificationUtils.toast(`${this.labels.toast_translating_failed}. ${this.labels.toast_translating_tryanother}. ${this.labels.error_serverdown}`, { + type: "danger", + position: "center" + }); + callback(""); + } + }); + } + + MD5 (e) { + function h(a, b) { + var e = a & 2147483648, f = b & 2147483648, c = a & 1073741824, d = b & 1073741824, g = (a & 1073741823) + (b & 1073741823); + return c & d ? g ^ 2147483648 ^ e ^ f : c | d ? g & 1073741824 ? g ^ 3221225472 ^ e ^ f : g ^ 1073741824 ^ e ^ f : g ^ e ^ f + } + function k(a, b, c, d, e, f, g) { + a = h(a, h(h(b & c | ~b & d, e), g)); + return h(a << f | a >>> 32 - f, b); + } + function l(a, b, c, d, e, f, g) { + a = h(a, h(h(b & d | c & ~d, e), g)); + return h(a << f | a >>> 32 - f, b); + } + function m(a, b, d, c, e, f, g) { + a = h(a, h(h(b ^ d ^ c, e), g)); + return h(a << f | a >>> 32 - f, b) + } + function n(a, b, d, c, e, f, g) { + a = h(a, h(h(d ^ (b | ~c), e), g)); + return h(a << f | a >>> 32 - f, b); + } + function p(a) { + var b = "", d = "", c; + for (c = 0; 3 >= c; c++) d = a >>> 8 * c & 255, d = "0" + d.toString(16), b += d.substr(d.length - 2, 2); + return b; + } + + var f = [], q, r, s, t, a, b, c, d; + e = function(a) { + a = a.replace(/\r\n/g, "\n"); + for (var b = "", d = 0; d < a.length; d++) { + var c = a.charCodeAt(d); + 128 > c ? b += String.fromCharCode(c) : (127 < c && 2048 > c ? b += String.fromCharCode(c >> 6 | 192) : (b += String.fromCharCode(c >> 12 | 224), b += String.fromCharCode(c >> 6 & 63 | 128)), b += String.fromCharCode(c & 63 | 128)) + } + return b; + }(e); + f = function(b) { + var a, c = b.length; + a = c + 8; + for (var d = 16 * ((a - a % 64) / 64 + 1), e = Array(d - 1), f = 0, g = 0; g < c;) a = (g - g % 4) / 4, f = g % 4 * 8, e[a] |= b.charCodeAt(g) << f, g++; + a = (g - g % 4) / 4; + e[a] |= 128 << g % 4 * 8; + e[d - 2] = c << 3; + e[d - 1] = c >>> 29; + return e + }(e); + a = 1732584193, b = 4023233417, c = 2562383102, d = 271733878; + for (e = 0; e < f.length; e += 16) q = a, r = b, s = c, t = d, a = k(a, b, c, d, f[e + 0], 7, 3614090360), d = k(d, a, b, c, f[e + 1], 12, 3905402710), c = k(c, d, a, b, f[e + 2], 17, 606105819), b = k(b, c, d, a, f[e + 3], 22, 3250441966), a = k(a, b, c, d, f[e + 4], 7, 4118548399), d = k(d, a, b, c, f[e + 5], 12, 1200080426), c = k(c, d, a, b, f[e + 6], 17, 2821735955), b = k(b, c, d, a, f[e + 7], 22, 4249261313), a = k(a, b, c, d, f[e + 8], 7, 1770035416), d = k(d, a, b, c, f[e + 9], 12, 2336552879), c = k(c, d, a, b, f[e + 10], 17, 4294925233), b = k(b, c, d, a, f[e + 11], 22, 2304563134), a = k(a, b, c, d, f[e + 12], 7, 1804603682), d = k(d, a, b, c, f[e + 13], 12, 4254626195), c = k(c, d, a, b, f[e + 14], 17, 2792965006), b = k(b, c, d, a, f[e + 15], 22, 1236535329), a = l(a, b, c, d, f[e + 1], 5, 4129170786), d = l(d, a, b, c, f[e + 6], 9, 3225465664), c = l(c, d, a, b, f[e + 11], 14, 643717713), b = l(b, c, d, a, f[e + 0], 20, 3921069994), a = l(a, b, c, d, f[e + 5], 5, 3593408605), d = l(d, a, b, c, f[e + 10], 9, 38016083), c = l(c, d, a, b, f[e + 15], 14, 3634488961), b = l(b, c, d, a, f[e + 4], 20, 3889429448), a = l(a, b, c, d, f[e + 9], 5, 568446438), d = l(d, a, b, c, f[e + 14], 9, 3275163606), c = l(c, d, a, b, f[e + 3], 14, 4107603335), b = l(b, c, d, a, f[e + 8], 20, 1163531501), a = l(a, b, c, d, f[e + 13], 5, 2850285829), d = l(d, a, b, c, f[e + 2], 9, 4243563512), c = l(c, d, a, b, f[e + 7], 14, 1735328473), b = l(b, c, d, a, f[e + 12], 20, 2368359562), a = m(a, b, c, d, f[e + 5], 4, 4294588738), d = m(d, a, b, c, f[e + 8], 11, 2272392833), c = m(c, d, a, b, f[e + 11], 16, 1839030562), b = m(b, c, d, a, f[e + 14], 23, 4259657740), a = m(a, b, c, d, f[e + 1], 4, 2763975236), d = m(d, a, b, c, f[e + 4], 11, 1272893353), c = m(c, d, a, b, f[e + 7], 16, 4139469664), b = m(b, c, d, a, f[e + 10], 23, 3200236656), a = m(a, b, c, d, f[e + 13], 4, 681279174), d = m(d, a, b, c, f[e + 0], 11, 3936430074), c = m(c, d, a, b, f[e + 3], 16, 3572445317), b = m(b, c, d, a, f[e + 6], 23, 76029189), a = m(a, b, c, d, f[e + 9], 4, 3654602809), d = m(d, a, b, c, f[e + 12], 11, 3873151461), c = m(c, d, a, b, f[e + 15], 16, 530742520), b = m(b, c, d, a, f[e + 2], 23, 3299628645), a = n(a, b, c, d, f[e + 0], 6, 4096336452), d = n(d, a, b, c, f[e + 7], 10, 1126891415), c = n(c, d, a, b, f[e + 14], 15, 2878612391), b = n(b, c, d, a, f[e + 5], 21, 4237533241), a = n(a, b, c, d, f[e + 12], 6, 1700485571), d = n(d, a, b, c, f[e + 3], 10, 2399980690), c = n(c, d, a, b, f[e + 10], 15, 4293915773), b = n(b, c, d, a, f[e + 1], 21, 2240044497), a = n(a, b, c, d, f[e + 8], 6, 1873313359), d = n(d, a, b, c, f[e + 15], 10, 4264355552), c = n(c, d, a, b, f[e + 6], 15, 2734768916), b = n(b, c, d, a, f[e + 13], 21, 1309151649), a = n(a, b, c, d, f[e + 4], 6, 4149444226), d = n(d, a, b, c, f[e + 11], 10, 3174756917), c = n(c, d, a, b, f[e + 2], 15, 718787259), b = n(b, c, d, a, f[e + 9], 21, 3951481745), a = h(a, q), b = h(b, r), c = h(c, s), d = h(d, t); + return (p(a) + p(b) + p(c) + p(d)).toLowerCase(); + } + + checkForSpecialCase (text, input) { + if (input.special) return input; + else if (input.auto) { + if (/^[0-1]*$/.test(text.replace(/\s/g, ""))) { + return {id: "binary", name: "Binary"}; + } + else if (/^[⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿]*$/.test(text.replace(/\s/g, ""))) { + return {id: "braille", name: "Braille 6-dot"}; + } + else if (/^[/|·−._-]*$/.test(text.replace(/\s/g, ""))) { + return {id: "morse", name: "Morse"}; + } + else if (/^(0x[0-9a-fA-F]{2}\s*)+$/.test(text.replace(/\s/g, ""))) { + return {id: "hex", name: "Hexadecimal"}; + } + } + return null; + } + + + string2binary (string) { + let binary = ""; + for (let character of string) binary += parseInt(character.charCodeAt(0).toString(2)).toPrecision(8).split(".").reverse().join("").toString() + " "; + return binary; + } + + string2braille (string) { + let braille = ""; + for (let character of string) braille += brailleConverter[character.toLowerCase()] ? brailleConverter[character.toLowerCase()] : character; + return braille; + } + + string2morse (string) { + string = string.replace(/ /g, "%%%%%%%%%%"); + let morse = ""; + for (let character of string) morse += (morseConverter[character.toLowerCase()] ? morseConverter[character.toLowerCase()] : character) + " "; + morse = morse.split("\n"); + for (let i in morse) morse[i] = morse[i].trim(); + return morse.join("\n").replace(/% % % % % % % % % % /g, "/ "); + } + string2hex(string) { + let hex = ""; + for (let character of string) { + hex += "0x" + character.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0") + " "; + } + return hex.trim(); + } + binary2string (binary) { + let string = ""; + binary = binary.replace(/\n/g, "00001010").replace(/\r/g, "00001101").replace(/\t/g, "00001001").replace(/\s/g, ""); + if (/^[0-1]*$/.test(binary)) { + let eightDigits = ""; + let counter = 0; + for (let digit of binary) { + eightDigits += digit; + counter++; + if (counter > 7) { + string += String.fromCharCode(parseInt(eightDigits, 2).toString(10)); + eightDigits = ""; + counter = 0; + } + } + } + else BDFDB.NotificationUtils.toast("Invalid binary format. Only use 0s and 1s.", { + type: "danger", + position: "center" + }); + return string; + } + + braille2string (braille) { + let string = ""; + for (let character of braille) string += brailleConverter[character.toLowerCase()] ? brailleConverter[character.toLowerCase()] : character; + return string; + } + + morse2string (morse) { + let string = ""; + for (let word of morse.replace(/[_-]/g, "−").replace(/\./g, "·").replace(/\r|\t/g, "").split(/\/|\||\n/g)) { + for (let characterstr of word.trim().split(" ")) string += morseConverter[characterstr] ? morseConverter[characterstr] : characterstr; + string += " "; + } + return string.trim(); + } + + hex2string(hex) { + let string = ""; + for (let part of hex.trim().split(/\s+/)) { + if (part.startsWith("0x") || part.startsWith("0X")) { + part = part.slice(2); + } + if (part.length === 2 && /^[0-9a-fA-F]{2}$/.test(part)) { + string += String.fromCharCode(parseInt(part, 16)); + } + } + return string; + } + + addExceptions (string, excepts) { + for (let count in excepts) { + let exception = BDFDB.ArrayUtils.is(this.settings.exceptions.wordStart) && this.settings.exceptions.wordStart.some(n => excepts[count].indexOf(n) == 0) ? excepts[count].slice(1) : excepts[count]; + let newString = string.replace(new RegExp(`[{\{]\\s*[{\{]\\s*${count}\\s*[}\}]\\s*[}\}]`), exception); + if (newString == string) string = newString + " " + exception; + else string = newString; + } + return string; + } + + removeExceptions (string, place) { + let emojiRegex = /[\uD83C-\uDBFF\uDC00-\uDFFF]+/; + let excepts = {}, newString = [], count = 0; + if (place == messageTypes.RECEIVED) { + let text = [], i = 0; + string.split("").forEach((chara, index, array) => { + if (chara == "<" && text[i] || emojiRegex.test(chara) && emojiRegex.test(array[index+1])) i++; + text[i] = text[i] ? text[i] + chara : chara; + if (chara == ">" || emojiRegex.test(chara) && emojiRegex.test(array[index-1])) i++; + }); + for (let j in text) { + if (text[j].indexOf("<") == 0 || emojiRegex.test(text[j])) { + newString.push(`{{${count}}}`); + excepts[count] = text[j]; + count++; + } + else newString.push(text[j]); + } + } + else { + let usedExceptions = BDFDB.ArrayUtils.is(this.settings.exceptions.wordStart) ? this.settings.exceptions.wordStart : []; + string.split(" ").forEach(word => { + if (emojiRegex.test(word) || word.indexOf("<@!") == 0 || word.indexOf("<#") == 0 || word.indexOf(":") == 0 || word.indexOf("<:") == 0 || word.indexOf(" word.indexOf(n) == 0 && word.length > 1)) { + newString.push(`{{${count}}}`); + excepts[count] = word; + count++; + } + else newString.push(word); + }); + } + return [newString.join(" "), excepts, newString.length-count != 0]; + } + + getGoogleTranslatePageURL (input, output, text) { + return `https://translate.google.com/#${BDFDB.LanguageUtils.languages[input] ? input : "auto"}/${output}/${encodeURIComponent(text)}`; + } + + setLabelsByLanguage () { + switch (BDFDB.LanguageUtils.getLanguage().id) { + case "bg": // Bulgarian + return { + backup_engine: "Резервен-Преводач", + backup_engine_warning: "Ще използва Резервен-Преводач", + context_messagetranslateoption: "Превод на съобщението", + context_messageuntranslateoption: "Превод на съобщението", + context_translator: "Търсене превод", + detect_language: "Разпознаване на езика", + error_dailylimit: "Дневният лимит на заявките е достигнат.", + error_hourlylimit: "Почасовият лимит на заявките е достигнат.", + error_keyoutdated: "API-ключът е остарял.", + error_monthlylimit: "Месечният лимит на заявките е достигнат.", + error_serverdown: "Сървърът за превод може да е офлайн.", + exception_text: "Думите, започващи с {{var0}}, ще бъдат игнорирани", + general_addQuickTranslateButton: "Добавя бърз бутон за превод в лентата за действия за съобщения", + general_addTranslateButton: "Добавя бутон за превод към текстовото поле на канала", + general_sendOriginalMessage: "Също така изпраща оригиналното съобщение, когато превежда вашето изпратено съобщение", + general_showOriginalMessage: "Също така показва оригиналното съобщение при превод на получено съобщение", + general_usePerChatTranslation: "Активира/деактивира състоянието на бутона за преводач за всеки канал, а не глобално", + language_choice_input_received: "Език на въвеждане в получените съобщения", + language_choice_input_sent: "Език на въвеждане в изпратените от вас съобщения", + language_choice_output_received: "Изходен език в получените съобщения", + language_choice_output_sent: "Изходен език в изпратените ви съобщения", + language_selection_channel: "Изборът на език ще бъде променен специално за този канал", + language_selection_global: "Изборът на език ще бъде променен за всички сървъри", + language_selection_server: "Изборът на език ще бъде променен специално за този сървър", + popout_translateoption: "Превод", + popout_untranslateoption: "Непревод", + prefixes_disable_text: "Префикси, които деактивират превода на съобщението", + prefixes_enable_text: "Префикси, които активират превод със специфичен език (напр. $fr, $de, $jp)", + toast_translating: "Превод", + toast_translating_failed: "Преводът не бе успешен", + toast_translating_tryanother: "Опитайте друг преводач", + translate_your_message: "Преведете вашите съобщения преди изпращане", + translated_watermark: "преведено", + translator_engine: "Преводач" + }; + case "cs": // Czech + return { + backup_engine: "Backup-Překladatel", + backup_engine_warning: "Použije Backup-Překladatel", + context_messagetranslateoption: "Přeložit zprávu", + context_messageuntranslateoption: "Přeložit zprávu", + context_translator: "Hledat Překlad", + detect_language: "Rozpoznat jazyk", + error_dailylimit: "Denní limit požadavků byl dosažen.", + error_hourlylimit: "Bylo dosaženo limitu hodinového požadavku.", + error_keyoutdated: "Klíč API je zastaralý.", + error_monthlylimit: "Byl dosažen limit měsíčních požadavků.", + error_serverdown: "Překladový server může být offline.", + exception_text: "Slova začínající na {{var0}} budou ignorována", + general_addQuickTranslateButton: "Přidá tlačítko rychlého překladu do panelu Actions Message", + general_addTranslateButton: "Přidá tlačítko Přeložit do textové oblasti kanálu", + general_sendOriginalMessage: "Při překladu odeslané zprávy také odešle původní zprávu", + general_showOriginalMessage: "Také zobrazuje původní zprávu při překladu přijaté zprávy", + general_usePerChatTranslation: "Povolí/zakáže stav tlačítka překladače pro kanál, nikoli globálně", + language_choice_input_received: "Vstupní jazyk do přijatých zpráv", + language_choice_input_sent: "Zadejte jazyk do odeslaných zpráv", + language_choice_output_received: "Výstupní jazyk v přijatých zprávách", + language_choice_output_sent: "Jazyk výstupu ve vašich odeslaných zprávách", + language_selection_channel: "Výběr jazyka bude změněn speciálně pro tento kanál", + language_selection_global: "Výběr jazyka se změní pro všechny servery", + language_selection_server: "Výběr jazyka bude změněn speciálně pro tento server", + popout_translateoption: "Přeložit", + popout_untranslateoption: "Nepřeložit", + prefixes_disable_text: "Předpony, které deaktivují překlad zprávy", + prefixes_enable_text: "Předpony, které aktivují překlad s konkrétním jazykem (např. $fr, $de, $jp)", + toast_translating: "Překládání", + toast_translating_failed: "Překlad se nezdařil", + toast_translating_tryanother: "Zkuste jiný překladač", + translate_your_message: "Před odesláním si zprávy přeložte", + translated_watermark: "přeloženo", + translator_engine: "Překladatel" + }; + case "da": // Danish + return { + backup_engine: "Backup-Oversætter", + backup_engine_warning: "Vil bruge Backup-Oversætter", + context_messagetranslateoption: "Oversæt besked", + context_messageuntranslateoption: "Ikke-oversat besked", + context_translator: "Søg oversættelse", + detect_language: "Find sprog", + error_dailylimit: "Daglig anmodningsgrænse nået.", + error_hourlylimit: "Timegrænsen for anmodning er nået.", + error_keyoutdated: "API-nøgle forældet.", + error_monthlylimit: "Månedlig anmodningsgrænse nået.", + error_serverdown: "Oversættelsesserveren er muligvis offline.", + exception_text: "Ord, der begynder med {{var0}}, ignoreres", + general_addQuickTranslateButton: "Tilføjer en hurtig oversættelsesknap i linjen Message Actions", + general_addTranslateButton: "Tilføjer en Oversæt-knap til kanaltekstområdet", + general_sendOriginalMessage: "Sender også den originale besked, når du oversætter din sendte besked", + general_showOriginalMessage: "Viser også den originale besked, når du oversætter modtaget besked", + general_usePerChatTranslation: "Aktiverer/deaktiverer oversætterknappens tilstand pr. kanal og ikke globalt", + language_choice_input_received: "Inputsprog i modtagne beskeder", + language_choice_input_sent: "Indtast sprog i dine sendte beskeder", + language_choice_output_received: "Outputsprog i modtagne beskeder", + language_choice_output_sent: "Outputsprog i dine sendte beskeder", + language_selection_channel: "Valg af sprog vil blive ændret specifikt for denne kanal", + language_selection_global: "Valg af sprog vil blive ændret for alle servere", + language_selection_server: "Sprogvalg vil blive ændret specifikt for denne server", + popout_translateoption: "Oversætte", + popout_untranslateoption: "Untranslate", + prefixes_disable_text: "Præfikser, der deaktiverer oversættelse af meddelelse", + prefixes_enable_text: "Præfikser, der aktiverer oversættelse med specifikt sprog (f.eks. $fr, $de, $jp)", + toast_translating: "Oversætter", + toast_translating_failed: "Kunne ikke oversætte", + toast_translating_tryanother: "Prøv en anden oversætter", + translate_your_message: "Oversæt dine beskeder før afsendelse", + translated_watermark: "oversat", + translator_engine: "Oversætter" + }; + case "de": // German + return { + backup_engine: "Backup-Übersetzer", + backup_engine_warning: "Wird Backup-Übersetzer verwenden", + context_messagetranslateoption: "Nachricht übersetzen", + context_messageuntranslateoption: "Nachricht unübersetzen", + context_translator: "Übersetzung suchen", + detect_language: "Sprache erkennen", + error_dailylimit: "Tägliches Anforderungslimit erreicht.", + error_hourlylimit: "Stündliches Anforderungslimit erreicht.", + error_keyoutdated: "API-Schlüssel veraltet.", + error_monthlylimit: "Monatliches Anforderungslimit erreicht.", + error_serverdown: "Der Übersetzungsserver ist möglicherweise offline.", + exception_text: "Wörter, die mit {{var0}} beginnen, werden ignoriert", + general_addQuickTranslateButton: "Fügt einen Schnellübersetz Schalter zur Nachrichtenaktionsleiste hinzu", + general_addTranslateButton: "Fügt dem Textbereich des Kanals eine Schalter zum Übersetzen hinzu", + general_sendOriginalMessage: "Sendet auch die ursprüngliche Nachricht, wenn die gesendete Nachricht übersetzt wird", + general_showOriginalMessage: "Zeigt auch die ursprüngliche Nachricht an, wenn eine empfangene Nachricht übersetzt wird", + general_usePerChatTranslation: "Aktiviert/deaktiviert die Übersetzung pro Kanal und nicht global", + language_choice_input_received: "Eingabesprache in empfangenen Nachrichten", + language_choice_input_sent: "Eingabesprache in gesendeten Nachrichten", + language_choice_output_received: "Ausgabesprache in empfangenen Nachrichten", + language_choice_output_sent: "Ausgabesprache in gesendeten Nachrichten", + language_selection_channel: "Die Sprachauswahl wird speziell für diesen Kanal geändert", + language_selection_global: "Die Sprachauswahl wird für alle Server geändert", + language_selection_server: "Die Sprachauswahl wird speziell für diesen Server geändert", + popout_translateoption: "Übersetzen", + popout_untranslateoption: "Unübersetzen", + prefixes_disable_text: "Präfixe, die die Übersetzung der Nachricht deaktivieren", + prefixes_enable_text: "Präfixe, die die Übersetzung mit einer bestimmten Sprache aktivieren (z.B. $fr, $de, $jp)", + toast_translating: "Übersetzen", + toast_translating_failed: "Übersetzung fehlgeschlagen", + toast_translating_tryanother: "Versuch einen anderen Übersetzer", + translate_your_message: "Übersetzt Nachrichten vor dem Senden", + translated_watermark: "übersetzt", + translator_engine: "Übersetzer" + }; + case "el": // Greek + return { + backup_engine: "Μεταφράστης-Αντίγραφο ασφαλείας", + backup_engine_warning: "Θα χρησιμοποιηθεί Μεταφράστης-Αντίγραφο ασφαλείας", + context_messagetranslateoption: "Μετάφραση μηνύματος", + context_messageuntranslateoption: "Αναίρεση μετάφρασης μηνύματος", + context_translator: "Αναζήτηση μετάφρασης", + detect_language: "Εντοπισμός γλώσσας", + error_dailylimit: "Συμπληρώθηκε το ημερήσιο όριο αιτημάτων.", + error_hourlylimit: "Συμπληρώθηκε το ωριαίο όριο αιτημάτων.", + error_keyoutdated: "Το κλειδί API δεν είναι ενημερωμένο.", + error_monthlylimit: "Συμπληρώθηκε το μηνιαίο όριο αιτημάτων.", + error_serverdown: "Ο διακομιστής μετάφρασης ενδέχεται να είναι εκτός σύνδεσης.", + exception_text: "Οι λέξεις θα αγνοηθούν που ξεκινούν με {{var0}}", + general_addQuickTranslateButton: "Προσθέτει ένα κουμπί γρήγορης μετάφρασης στη γραμμή ενεργειών μηνυμάτων", + general_addTranslateButton: "Προσθήκη κουμπιού μετάφρασης στην Περιοχή κειμένου του Καναλιού", + general_sendOriginalMessage: "Αποστολή αρχικού Μηνύματος με τη μετάφραση απεσταλμένου μηνύματος", + general_showOriginalMessage: "Εμφάνιση αρχικού Μηνύματος με τη μετάφραση ενός ληφθέντος μηνύματος", + general_usePerChatTranslation: "(Απ)Ενεργοποίηση κατάστασης κουμπιού μεταφραστή ανά κανάλι", + language_choice_input_received: "Γλώσσα εισαγωγής στα ληφθέντα μηνύματα", + language_choice_input_sent: "Γλώσσα εισαγωγής στα απεσταλμένα μηνύματά σας", + language_choice_output_received: "Γλώσσα εξαγωγής στα ληφθέντα μηνύματα", + language_choice_output_sent: "Γλώσσα εξαγωγής στα απεσταλμένα μηνύματά σας", + language_selection_channel: "Η επιλογή γλώσσας θα αλλάξει ειδικά για αυτό το κανάλι", + language_selection_global: "Η Επιλογή Γλώσσας θα αλλάξει για όλους τους Διακομιστές", + language_selection_server: "Η επιλογή γλώσσας θα αλλάξει ειδικά για αυτόν τον διακομιστή", + popout_translateoption: "Μετάφραση", + popout_untranslateoption: "Αναίρεση μετάφρασης", + prefixes_disable_text: "Προθέσεις που απενεργοποιούν την μετάφραση του μηνύματος", + prefixes_enable_text: "Προθέσεις που ενεργοποιούν την μετάφραση με συγκεκριμένη γλώσσα (π.χ. $fr, $de, $jp)", + toast_translating: "Μετάφραση", + toast_translating_failed: "Αποτυχία μετάφρασης", + toast_translating_tryanother: "Δοκιμάστε έναν άλλο Μεταφραστή", + translate_your_message: "Μεταφράστε τα Μηνύματά σας πριν την αποστολή", + translated_watermark: "μεταφρασμένο", + translator_engine: "Μεταφράστης" + }; + case "es": // Spanish + return { + backup_engine: "Backup-Traductor", + backup_engine_warning: "Utilizará Backup-Traductor", + context_messagetranslateoption: "Traducir mensaje", + context_messageuntranslateoption: "Mensaje sin traducir", + context_translator: "Buscar traducción", + detect_language: "Detectar idioma", + error_dailylimit: "Se alcanzó el límite de solicitudes diarias.", + error_hourlylimit: "Se alcanzó el límite de solicitudes por hora.", + error_keyoutdated: "API-Key obsoleta.", + error_monthlylimit: "Se alcanzó el límite de solicitudes mensuales.", + error_serverdown: "El servidor de traducción puede estar fuera de línea.", + exception_text: "Las palabras que comienzan con {{var0}} serán ignoradas", + general_addQuickTranslateButton: "Agrega un botón de traducción rápida en la barra de acciones del mensaje", + general_addTranslateButton: "Agrega un botón de traducción al área de texto del canal", + general_sendOriginalMessage: "También envía el mensaje original al traducir su mensaje enviado", + general_showOriginalMessage: "También muestra el mensaje original al traducir un mensaje recibido", + general_usePerChatTranslation: "Habilita/deshabilita el estado del botón del traductor por canal y no globalmente", + language_choice_input_received: "Idioma de entrada en los mensajes recibidos", + language_choice_input_sent: "Idioma de entrada en sus mensajes enviados", + language_choice_output_received: "Idioma de salida en los mensajes recibidos", + language_choice_output_sent: "Idioma de salida en sus mensajes enviados", + language_selection_channel: "La selección de idioma se cambiará específicamente para este canal", + language_selection_global: "La selección de idioma se cambiará para todos los servidores", + language_selection_server: "La selección de idioma se cambiará específicamente para este servidor", + popout_translateoption: "Traducir", + popout_untranslateoption: "No traducir", + prefixes_disable_text: "Prefijos que desactivan la traducción del mensaje", + prefixes_enable_text: "Prefijos que activan la traducción con un idioma específico (por ejemplo, $fr, $de, $jp)", + toast_translating: "Traductorio", + toast_translating_failed: "No se pudo traducir", + toast_translating_tryanother: "Prueba con otro traductor", + translate_your_message: "Traduce tus mensajes antes de enviarlos", + translated_watermark: "traducido", + translator_engine: "Traductor" + }; + case "es-419": // Spanish (Latin America) + return { + backup_engine: "Traspaso de respaldo", + backup_engine_warning: "Utilizará el traductor de respaldo", + context_messagetranslateoption: "Mensaje de traducir", + context_messageuntranslateoption: "Mensaje no traducido", + context_translator: "Traducción de búsqueda", + detect_language: "Detectar lenguaje", + error_dailylimit: "Límite de solicitud diaria alcanzado.", + error_hourlylimit: "Límite de solicitud por hora alcanzado.", + error_keyoutdated: "Api-key anticuado.", + error_monthlylimit: "Límite de solicitud mensual alcanzado.", + error_serverdown: "El servidor de traducción puede estar fuera de línea.", + exception_text: "Las palabras que comienzan con {{var0}} se ignorarán", + general_addQuickTranslateButton: "Agrega un botón de traducción rápida en la barra de acciones del mensaje", + general_addTranslateButton: "Agrega un botón de traducción al canal TextAREA", + general_sendOriginalMessage: "También envía el mensaje original al traducir su mensaje enviado", + general_showOriginalMessage: "También muestra el mensaje original al traducir un mensaje recibido", + general_usePerChatTranslation: "Habilita/deshabilita el estado del botón de traductor por canal y no a nivel mundial", + language_choice_input_received: "Idioma de entrada en mensajes recibidos", + language_choice_input_sent: "Idioma de entrada en sus mensajes enviados", + language_choice_output_received: "Lenguaje de salida en mensajes recibidos", + language_choice_output_sent: "Lenguaje de salida en sus mensajes enviados", + language_selection_channel: "La selección del idioma se cambiará específicamente para este canal", + language_selection_global: "La selección del idioma se cambiará para todos los servidores", + language_selection_server: "La selección del idioma se cambiará específicamente para este servidor", + popout_translateoption: "Traducir", + popout_untranslateoption: "No traducido", + prefixes_disable_text: "Prefijos que deshabilitan la traducción del mensaje", + prefixes_enable_text: "Prefijos que habilitan la traducción con un lenguaje específico (por ejemplo, $fr, $de, $jp)", + toast_translating: "Traductorio", + toast_translating_failed: "No se pudo traducir", + toast_translating_tryanother: "Prueba otro traductor", + translate_your_message: "Traducir sus mensajes antes de enviar", + translated_watermark: "traducido", + translator_engine: "Traductor" + }; + case "fi": // Finnish + return { + backup_engine: "Backup-Kääntäjä", + backup_engine_warning: "Käyttää Backup-Kääntäjä", + context_messagetranslateoption: "Käännä viesti", + context_messageuntranslateoption: "Käännä viesti", + context_translator: "Hae käännöstä", + detect_language: "Tunnista kieli", + error_dailylimit: "Päivittäinen pyyntöraja saavutettu.", + error_hourlylimit: "Tuntikohtainen pyyntöraja saavutettu.", + error_keyoutdated: "API-avain vanhentunut.", + error_monthlylimit: "Kuukauden pyyntöraja saavutettu.", + error_serverdown: "Käännöspalvelin saattaa olla offline-tilassa.", + exception_text: "{{var0}} alkavat sanat ohitetaan", + general_addQuickTranslateButton: "Lisää nopea käännöspainike Message Action -palkkiin", + general_addTranslateButton: "Lisää käännöspainikkeen kanavan tekstialueeseen", + general_sendOriginalMessage: "Lähettää myös alkuperäisen viestin kääntäessään lähettämääsi viestiä", + general_showOriginalMessage: "Näyttää myös alkuperäisen viestin käännettäessä vastaanotettua viestiä", + general_usePerChatTranslation: "Ottaa käyttöön/poistaa käytöstä kääntäjän painikkeen tilan kanavakohtaisesti, ei maailmanlaajuisesti", + language_choice_input_received: "Syöttökieli vastaanotetuissa viesteissä", + language_choice_input_sent: "Syötä kieli lähettämiisi viesteihin", + language_choice_output_received: "Tulostuskieli vastaanotetuissa viesteissä", + language_choice_output_sent: "Lähetyskieli lähetetyissä viesteissä", + language_selection_channel: "Kielen valintaa muutetaan erityisesti tätä kanavaa varten", + language_selection_global: "Kielen valintaa muutetaan kaikille palvelimille", + language_selection_server: "Kielen valintaa muutetaan erityisesti tätä palvelinta varten", + popout_translateoption: "Kääntää", + popout_untranslateoption: "Käännä", + prefixes_disable_text: "Etuliitteet, jotka poistavat viestin käännöksen käytöstä", + prefixes_enable_text: "Etuliitteet, jotka mahdollistavat käännöksen tietyllä kielellä (esim. $fr, $de, $jp)", + toast_translating: "Kääntäminen", + toast_translating_failed: "Käännös epäonnistui", + toast_translating_tryanother: "Kokeile toista kääntäjää", + translate_your_message: "Käännä viestisi ennen lähettämistä", + translated_watermark: "käännetty", + translator_engine: "Kääntäjä" + }; + case "fr": // French + return { + backup_engine: "Backup-Traducteur", + backup_engine_warning: "Utilisera Backup-Traducteur", + context_messagetranslateoption: "Traduire le message", + context_messageuntranslateoption: "Message non traduit", + context_translator: "Recherche de traduction", + detect_language: "Détecter la langue", + error_dailylimit: "Limite quotidienne de requêtes atteinte.", + error_hourlylimit: "Limite horaire de demandes atteinte.", + error_keyoutdated: "Clé API obsolète.", + error_monthlylimit: "Limite mensuelle de demandes atteinte.", + error_serverdown: "Le serveur de traduction est peut-être hors ligne.", + exception_text: "Les mots commençant par {{var0}} seront ignorés", + general_addQuickTranslateButton: "Ajoute un bouton de traduire rapidement dans la barre des actions du message", + general_addTranslateButton: "Ajoute un bouton de traduction à la zone de texte du canal", + general_sendOriginalMessage: "Envoie également le message d'origine lors de la traduction de votre message envoyé", + general_showOriginalMessage: "Affiche également le message d'origine lors de la traduction d'un message reçu", + general_usePerChatTranslation: "Active/désactive l'état du bouton du traducteur par canal et non globalement", + language_choice_input_received: "Langue d'entrée dans les messages reçus", + language_choice_input_sent: "Langue d'entrée dans vos messages envoyés", + language_choice_output_received: "Langue de sortie dans les messages reçus", + language_choice_output_sent: "Langue de sortie dans vos messages envoyés", + language_selection_channel: "La sélection de la langue sera modifiée spécifiquement pour ce canal", + language_selection_global: "La sélection de la langue sera modifiée pour tous les serveurs", + language_selection_server: "La sélection de la langue sera modifiée spécifiquement pour ce serveur", + popout_translateoption: "Traduire", + popout_untranslateoption: "Non traduit", + prefixes_disable_text: "Préfixes qui désactivent la traduction du message", + prefixes_enable_text: "Préfixes qui activent la traduction avec un langage spécifique (par exemple, $fr, $de, $jp)", + toast_translating: "Traduction en cours", + toast_translating_failed: "Échec de la traduction", + toast_translating_tryanother: "Essayez un autre traducteur", + translate_your_message: "Traduisez vos messages avant de les envoyer", + translated_watermark: "traduit", + translator_engine: "Traducteur" + }; + case "hi": // Hindi + return { + backup_engine: "बैकअप-अनुवादक", + backup_engine_warning: "बैकअप-अनुवादक का उपयोग करेंगे", + context_messagetranslateoption: "संदेश का अनुवाद करें", + context_messageuntranslateoption: "संदेश का अनुवाद न करें", + context_translator: "अनुवाद खोजें", + detect_language: "भाषा की जांच करो", + error_dailylimit: "दैनिक अनुरोध सीमा पूरी हो गई है।", + error_hourlylimit: "घंटे के अनुरोध की सीमा पूरी हो गई है.", + error_keyoutdated: "एपीआई-कुंजी पुरानी हो चुकी है।", + error_monthlylimit: "मासिक अनुरोध सीमा पूरी हो गई है।", + error_serverdown: "अनुवाद सर्वर ऑफ़लाइन हो सकता है।", + exception_text: "{{var0}} से शुरू होने वाले शब्दों पर ध्यान नहीं दिया जाएगा", + general_addQuickTranslateButton: "संदेश कार्रवाई बार में एक त्वरित अनुवाद बटन जोड़ता है", + general_addTranslateButton: "चैनल Textarea में एक अनुवाद बटन जोड़ता है", + general_sendOriginalMessage: "आपके भेजे गए संदेश का अनुवाद करते समय मूल संदेश भी भेजता है", + general_showOriginalMessage: "प्राप्त संदेश का अनुवाद करते समय मूल संदेश भी दिखाता है", + general_usePerChatTranslation: "प्रति चैनल अनुवादक बटन स्थिति को सक्षम/अक्षम करता है और विश्व स्तर पर नहीं", + language_choice_input_received: "प्राप्त संदेशों में इनपुट भाषा", + language_choice_input_sent: "आपके भेजे गए संदेशों में इनपुट भाषा", + language_choice_output_received: "प्राप्त संदेशों में आउटपुट भाषा", + language_choice_output_sent: "आपके भेजे गए संदेशों में आउटपुट भाषा", + language_selection_channel: "इस चैनल के लिए भाषा चयन विशेष रूप से बदला जाएगा", + language_selection_global: "सभी सर्वरों के लिए भाषा चयन बदल दिया जाएगा", + language_selection_server: "इस सर्वर के लिए भाषा चयन विशेष रूप से बदल दिया जाएगा", + popout_translateoption: "अनुवाद करना", + popout_untranslateoption: "अनुवाद न करें", + prefixes_disable_text: "उपसर्ग जो संदेश के अनुवाद को अक्षम करते हैं", + prefixes_enable_text: "उपसर्ग जो विशिष्ट भाषा के साथ अनुवाद को सक्षम करते हैं (जैसे $fr, $de, $jp)", + toast_translating: "अनुवाद", + toast_translating_failed: "अनुवाद करने में विफल", + toast_translating_tryanother: "दूसरे अनुवादक का प्रयास करें", + translate_your_message: "भेजने से पहले अपने संदेशों का अनुवाद करें", + translated_watermark: "अनुवाद", + translator_engine: "अनुवादक" + }; + case "hr": // Croatian + return { + backup_engine: "Rezervni-Prevoditelj", + backup_engine_warning: "Koristit će se Rezervni-Prevoditelj", + context_messagetranslateoption: "Prevedi poruku", + context_messageuntranslateoption: "Prevedi poruku", + context_translator: "Pretraži prijevod", + detect_language: "Prepoznaj jezik", + error_dailylimit: "Dosegnuto je dnevno ograničenje zahtjeva.", + error_hourlylimit: "Dosegnuto je ograničenje zahtjeva po satu.", + error_keyoutdated: "API-ključ zastario.", + error_monthlylimit: "Dosegnuto je mjesečno ograničenje zahtjeva.", + error_serverdown: "Translation Server možda je offline.", + exception_text: "Riječi koje počinju s {{var0}} bit će zanemarene", + general_addQuickTranslateButton: "Dodaje gumb za brzo prevođenje u traku Akcija poruka", + general_addTranslateButton: "Dodaje gumb Prevedi tekstualnom području kanala", + general_sendOriginalMessage: "Također šalje izvornu poruku prilikom prijevoda vaše poslane poruke", + general_showOriginalMessage: "Također prikazuje izvornu poruku prilikom prijevoda primljene poruke", + general_usePerChatTranslation: "Omogućuje/onemogućuje stanje gumba prevoditelja po kanalu, a ne globalno", + language_choice_input_received: "Jezik unosa u primljenim porukama", + language_choice_input_sent: "Jezik unosa u vaše poslane poruke", + language_choice_output_received: "Izlazni jezik u primljenim porukama", + language_choice_output_sent: "Izlazni jezik u vašim poslanim porukama", + language_selection_channel: "Odabir jezika bit će promijenjen posebno za ovaj kanal", + language_selection_global: "Odabir jezika bit će promijenjen za sve poslužitelje", + language_selection_server: "Odabir jezika bit će promijenjen posebno za ovaj poslužitelj", + popout_translateoption: "Prevedi", + popout_untranslateoption: "Neprevedi", + prefixes_disable_text: "Prefiksi koji onemogućuju prijevod poruke", + prefixes_enable_text: "Prefiksi koji omogućuju prijevod određenim jezikom (npr. $fr, $de, $jp)", + toast_translating: "Prevođenje", + toast_translating_failed: "Prijevod nije uspio", + toast_translating_tryanother: "Pokušajte s drugim prevoditeljem", + translate_your_message: "Prevedite svoje poruke prije slanja", + translated_watermark: "prevedeno", + translator_engine: "Prevoditelj" + }; + case "hu": // Hungarian + return { + backup_engine: "Backup-Fordító", + backup_engine_warning: "A Backup-Fordító programot fogja használni", + context_messagetranslateoption: "Üzenet lefordítása", + context_messageuntranslateoption: "Az üzenet lefordítása", + context_translator: "Keresés a fordításban", + detect_language: "Nyelvfelismerés", + error_dailylimit: "Elérte a napi igénylési korlátot.", + error_hourlylimit: "Elérte az óránkénti igénylési korlátot.", + error_keyoutdated: "API-kulcs elavult.", + error_monthlylimit: "Elérte a havi igénylési limitet.", + error_serverdown: "Lehet, hogy a Fordítószerver offline állapotban van.", + exception_text: "A(z) {{var0}} kezdetű szavak figyelmen kívül maradnak", + general_addQuickTranslateButton: "Hozzáad egy gyors lefordítást a Művelet Művelet sávban", + general_addTranslateButton: "Fordítási gombot ad a csatorna szövegterületéhez", + general_sendOriginalMessage: "Az eredeti üzenetet is elküldi az elküldött üzenet fordítása során", + general_showOriginalMessage: "A fogadott üzenet lefordításakor az eredeti üzenetet is megjeleníti", + general_usePerChatTranslation: "Engedélyezi/letiltja a Fordító gomb állapotát csatornánként, nem pedig globálisan", + language_choice_input_received: "Beviteli nyelv a fogadott üzenetekben", + language_choice_input_sent: "Írja be a nyelvet az elküldött üzenetekben", + language_choice_output_received: "Kimeneti nyelv a fogadott üzenetekben", + language_choice_output_sent: "Kimeneti nyelv az elküldött üzenetekben", + language_selection_channel: "A nyelvválasztás kifejezetten ehhez a csatornához fog módosulni", + language_selection_global: "A nyelv kiválasztása minden szerveren módosul", + language_selection_server: "A nyelvválasztás kifejezetten ehhez a szerverhez módosul", + popout_translateoption: "fordít", + popout_untranslateoption: "Fordítás le", + prefixes_disable_text: "Az üzenet fordítását letiltó előtagok", + prefixes_enable_text: "Előtagok, amelyek lehetővé teszik a fordítás meghatározott nyelvvel (például $fr, $de, $jp)", + toast_translating: "Fordítás", + toast_translating_failed: "Nem sikerült lefordítani", + toast_translating_tryanother: "Próbálkozzon másik fordítóval", + translate_your_message: "Küldés előtt fordítsa le az üzeneteit", + translated_watermark: "lefordított", + translator_engine: "Fordító" + }; + case "it": // Italian + return { + backup_engine: "Backup-Traduttore", + backup_engine_warning: "Utilizzerà Backup-Traduttore", + context_messagetranslateoption: "Traduci messaggio", + context_messageuntranslateoption: "Annulla traduzione messaggio", + context_translator: "Cerca traduzione", + detect_language: "Rileva lingua", + error_dailylimit: "Limite di richieste giornaliere raggiunto.", + error_hourlylimit: "Limite di richiesta oraria raggiunto.", + error_keyoutdated: "Chiave API obsoleta.", + error_monthlylimit: "Limite di richieste mensili raggiunto.", + error_serverdown: "Il server di traduzione potrebbe essere offline.", + exception_text: "Le parole che iniziano con {{var0}} verranno ignorate", + general_addQuickTranslateButton: "Aggiunge un pulsante di traduzione rapida nella barra delle azioni del messaggio", + general_addTranslateButton: "Aggiunge un pulsante Traduci all'area di testo del canale", + general_sendOriginalMessage: "Invia anche il messaggio originale durante la traduzione del messaggio inviato", + general_showOriginalMessage: "Mostra anche il messaggio originale durante la traduzione di un messaggio ricevuto", + general_usePerChatTranslation: "Abilita/disabilita lo stato del pulsante Translator per canale e non globalmente", + language_choice_input_received: "Lingua di input nei messaggi ricevuti", + language_choice_input_sent: "Inserisci la lingua nei tuoi messaggi inviati", + language_choice_output_received: "Lingua di output nei messaggi ricevuti", + language_choice_output_sent: "Lingua di output nei messaggi inviati", + language_selection_channel: "La selezione della lingua verrà modificata in modo specifico per questo canale", + language_selection_global: "La selezione della lingua verrà modificata per tutti i server", + language_selection_server: "La selezione della lingua verrà modificata in modo specifico per questo server", + popout_translateoption: "Tradurre", + popout_untranslateoption: "Non tradurre", + prefixes_disable_text: "Parole che iniziano con {{var0}} verranno ignorate", + prefixes_enable_text: "Parole che attivano la traduzione con un linguaggio specifico (ad esempio, $fr, $de, $jp)", + toast_translating: "Tradurre", + toast_translating_failed: "Impossibile tradurre", + toast_translating_tryanother: "Prova un altro traduttore", + translate_your_message: "Traduci i tuoi messaggi prima di inviarli", + translated_watermark: "tradotto", + translator_engine: "Traduttore" + }; + case "ja": // Japanese + return { + backup_engine: "バックアップ翻訳者", + backup_engine_warning: "バックアップ翻訳者 を使用します", + context_messagetranslateoption: "メッセージの翻訳", + context_messageuntranslateoption: "メッセージの翻訳解除", + context_translator: "翻訳を検索", + detect_language: "言語を検出", + error_dailylimit: "1 日のリクエスト上限に達しました。", + error_hourlylimit: "1 時間あたりのリクエスト制限に達しました。", + error_keyoutdated: "API キーが古くなっています。", + error_monthlylimit: "月間リクエスト制限に達しました。", + error_serverdown: "翻訳サーバーがオフラインになっている可能性があります。", + exception_text: "{{var0}} で始まる単語は無視されます", + general_addQuickTranslateButton: "メッセージアクションバーにクイック翻訳ボタンを追加します", + general_addTranslateButton: "チャンネルのテキストエリアに翻訳ボタンを追加します", + general_sendOriginalMessage: "送信したメッセージを翻訳するときに元のメッセージも送信します", + general_showOriginalMessage: "受信したメッセージを翻訳するときに元のメッセージも表示します", + general_usePerChatTranslation: "グローバルではなく、チャネルごとに翻訳者ボタンの状態を有効/無効にします", + language_choice_input_received: "受信メッセージの入力言語", + language_choice_input_sent: "送信メッセージの入力言語", + language_choice_output_received: "受信メッセージの出力言語", + language_choice_output_sent: "送信メッセージの出力言語", + language_selection_channel: "言語の選択は、このチャンネル専用に変更されます", + language_selection_global: "すべてのサーバーの言語選択が変更されます", + language_selection_server: "言語の選択は、このサーバー専用に変更されます", + popout_translateoption: "翻訳する", + popout_untranslateoption: "翻訳しない", + prefixes_disable_text: "メッセージの翻訳を無効にするプレフィックス", + prefixes_enable_text: "特定の言語で翻訳を可能にするプレフィックス(例:$fr, $de, $jp)", + toast_translating: "翻訳", + toast_translating_failed: "翻訳に失敗しました", + toast_translating_tryanother: "別の翻訳者を試す", + translate_your_message: "送信する前にメッセージを翻訳する", + translated_watermark: "翻訳済み", + translator_engine: "翻訳者" + }; + case "ko": // Korean + return { + backup_engine: "백업 번역기", + backup_engine_warning: "백업 번역기를 사용합니다", + context_messagetranslateoption: "메시지 번역", + context_messageuntranslateoption: "메시지 번역 취소", + context_translator: "번역 검색", + detect_language: "언어를 감지", + error_dailylimit: "일일 요청 한도에 도달했습니다.", + error_hourlylimit: "시간당 요청 한도에 도달했습니다.", + error_keyoutdated: "API 키가 오래되었습니다.", + error_monthlylimit: "월간 요청 한도에 도달했습니다.", + error_serverdown: "번역 서버가 오프라인일 수 있습니다.", + exception_text: "{{var0}}로 시작하는 단어는 무시됩니다.", + general_addQuickTranslateButton: "메시지 동작 막대에서 빠른 번역 버튼 추가", + general_addTranslateButton: "채널 텍스트 영역에 번역 버튼 추가", + general_sendOriginalMessage: "또한 보낸 메시지를 번역할 때 원본 메시지를 보냅니다.", + general_showOriginalMessage: "또한 수신된 메시지를 번역할 때 원본 메시지를 표시합니다.", + general_usePerChatTranslation: "전역이 아닌 채널별로 번역기 버튼 상태를 활성화/비활성화합니다.", + language_choice_input_received: "수신된 메시지의 입력 언어", + language_choice_input_sent: "보낸 메시지의 입력 언어", + language_choice_output_received: "수신된 메시지의 출력 언어", + language_choice_output_sent: "보낸 메시지의 출력 언어", + language_selection_channel: "이 채널에 대해 특별히 언어 선택이 변경됩니다.", + language_selection_global: "모든 서버에 대해 언어 선택이 변경됩니다.", + language_selection_server: "이 서버에 대해 특별히 언어 선택이 변경됩니다.", + popout_translateoption: "옮기다", + popout_untranslateoption: "번역 취소", + prefixes_disable_text: "메시지 변환을 비활성화하는 접두사", + prefixes_enable_text: "특정 언어로 변환을 가능하게하는 접두사 (예: $fr, $de, $jp)", + toast_translating: "번역 중", + toast_translating_failed: "번역하지 못했습니다.", + toast_translating_tryanother: "다른 번역기 시도", + translate_your_message: "보내기 전에 메시지 번역", + translated_watermark: "번역", + translator_engine: "역자" + }; + case "lt": // Lithuanian + return { + backup_engine: "Backup-Vertėjas", + backup_engine_warning: "Naudos Backup-Vertėjas", + context_messagetranslateoption: "Versti pranešimą", + context_messageuntranslateoption: "Išversti pranešimą", + context_translator: "Paieškos vertimas", + detect_language: "Aptikti kalbą", + error_dailylimit: "Pasiektas dienos užklausų limitas.", + error_hourlylimit: "Pasiektas valandinių užklausų limitas.", + error_keyoutdated: "API raktas pasenęs.", + error_monthlylimit: "Pasiektas mėnesio užklausų limitas.", + error_serverdown: "Vertimo serveris gali būti neprisijungęs.", + exception_text: "Žodžiai, prasidedantys {{var0}}, bus ignoruojami", + general_addQuickTranslateButton: "Prideda greito vertimo mygtuką pranešimo veiksmų juostoje", + general_addTranslateButton: "Prie kanalo teksto srities pridedamas vertimo mygtukas", + general_sendOriginalMessage: "Taip pat siunčia originalų pranešimą verčiant jūsų išsiųstą žinutę", + general_showOriginalMessage: "Taip pat rodomas pradinis pranešimas, kai verčiamas gautas pranešimas", + general_usePerChatTranslation: "Įjungia / išjungia Vertėjo mygtuko būseną kiekvienam kanalui, o ne visame pasaulyje", + language_choice_input_received: "Įvesties kalba gautuose pranešimuose", + language_choice_input_sent: "Įveskite kalbą siunčiamuose pranešimuose", + language_choice_output_received: "Išvesties kalba gautuose pranešimuose", + language_choice_output_sent: "Išvesties kalba jūsų išsiųstuose pranešimuose", + language_selection_channel: "Kalbos pasirinkimas bus pakeistas specialiai šiam kanalui", + language_selection_global: "Kalbos pasirinkimas bus pakeistas visiems serveriams", + language_selection_server: "Kalbos pasirinkimas bus pakeistas specialiai šiam serveriui", + popout_translateoption: "Išversti", + popout_untranslateoption: "Neišversti", + prefixes_disable_text: "Priešdėliai, kurie išjungia pranešimo vertimą", + prefixes_enable_text: "Priešdėliai, įgalinantys vertimą su konkrečia kalba (pvz., $fr, $de, $jp)", + toast_translating: "Vertimas", + toast_translating_failed: "Nepavyko išversti", + toast_translating_tryanother: "Išbandykite kitą vertėją", + translate_your_message: "Prieš siųsdami išverskite savo pranešimus", + translated_watermark: "išverstas", + translator_engine: "Vertėjas" + }; + case "nl": // Dutch + return { + backup_engine: "Backup-Vertaler", + backup_engine_warning: "Zal Backup-Vertaler gebruiken", + context_messagetranslateoption: "Bericht vertalen", + context_messageuntranslateoption: "Bericht onvertalen", + context_translator: "Zoek vertaling", + detect_language: "Taal detecteren", + error_dailylimit: "Dagelijkse verzoeklimiet bereikt.", + error_hourlylimit: "Verzoeklimiet per uur bereikt.", + error_keyoutdated: "API-sleutel verouderd.", + error_monthlylimit: "Maandelijkse aanvraaglimiet bereikt.", + error_serverdown: "Vertaalserver is mogelijk offline.", + exception_text: "Woorden die beginnen met {{var0}} worden genegeerd", + general_addQuickTranslateButton: "Voegt een snel vertalende knop toe in de Bericht Acties Bar", + general_addTranslateButton: "Voegt een vertaalknop toe aan het kanaaltekstgebied", + general_sendOriginalMessage: "Verzendt ook het originele bericht bij het vertalen van uw verzonden bericht", + general_showOriginalMessage: "Toont ook het originele bericht bij het vertalen van een ontvangen bericht", + general_usePerChatTranslation: "Schakelt de status van de vertaalknop in/uit per kanaal en niet globaal", + language_choice_input_received: "Invoertaal in ontvangen berichten", + language_choice_input_sent: "Invoertaal in uw verzonden berichten", + language_choice_output_received: "Uitvoertaal in ontvangen berichten", + language_choice_output_sent: "Uitvoertaal in uw verzonden berichten", + language_selection_channel: "De taalselectie wordt specifiek voor dit kanaal gewijzigd", + language_selection_global: "Taalkeuze wordt voor alle servers gewijzigd", + language_selection_server: "Taalselectie wordt specifiek voor deze server gewijzigd", + popout_translateoption: "Vertalen", + popout_untranslateoption: "Onvertalen", + prefixes_disable_text: "Voorvoegsels die de vertaling van het bericht uitschakelen", + prefixes_enable_text: "Voorvoegsels die vertaling mogelijk maken met specifieke taal (bijv. $fr, $de, $jp)", + toast_translating: "Vertalen", + toast_translating_failed: "Kan niet vertalen", + toast_translating_tryanother: "Probeer een andere vertaler", + translate_your_message: "Vertaal uw berichten voordat u ze verzendt", + translated_watermark: "vertaald", + translator_engine: "Vertaler" + }; + case "no": // Norwegian + return { + backup_engine: "Backup-Oversetter", + backup_engine_warning: "Vil bruke Backup-Oversetter", + context_messagetranslateoption: "Oversett melding", + context_messageuntranslateoption: "Ikke oversett melding", + context_translator: "Søk i oversettelse", + detect_language: "Oppdage språk", + error_dailylimit: "Daglig forespørselsgrense nådd.", + error_hourlylimit: "Forespørselsgrensen for time nådd.", + error_keyoutdated: "API-nøkkel utdatert.", + error_monthlylimit: "Månedlig forespørselsgrense nådd.", + error_serverdown: "Oversettelsesserveren kan være frakoblet.", + exception_text: "Ord som begynner med {{var0}} vil bli ignorert", + general_addQuickTranslateButton: "Legger til en rask oversettelsesknapp i meldingslinjen", + general_addTranslateButton: "Legger til en oversettknapp til kanaltekstområdet", + general_sendOriginalMessage: "Sender også den originale meldingen når du oversetter den sendte meldingen", + general_showOriginalMessage: "Viser også den originale meldingen når du oversetter en mottatt melding", + general_usePerChatTranslation: "Aktiverer/deaktiverer oversetterknappens tilstand per kanal og ikke globalt", + language_choice_input_received: "Inndataspråk i mottatte meldinger", + language_choice_input_sent: "Inntastingsspråk i sendte meldinger", + language_choice_output_received: "Utdataspråk i mottatte meldinger", + language_choice_output_sent: "Utdataspråk i dine sendte meldinger", + language_selection_channel: "Språkvalg vil bli endret spesifikt for denne kanalen", + language_selection_global: "Språkvalg vil bli endret for alle servere", + language_selection_server: "Språkvalg vil bli endret spesifikt for denne serveren", + popout_translateoption: "Oversette", + popout_untranslateoption: "Ikke oversett", + prefixes_disable_text: "Prefikser som deaktiverer oversettelse av meldingen", + prefixes_enable_text: "Prefikser som muliggjør oversettelse med spesifikt språk (f.eks. $fr, $de, $jp)", + toast_translating: "Oversetter", + toast_translating_failed: "Kunne ikke oversette", + toast_translating_tryanother: "Prøv en annen oversetter", + translate_your_message: "Oversett meldingene dine før sending", + translated_watermark: "oversatt", + translator_engine: "Oversetter" + }; + case "pl": // Polish + return { + backup_engine: "Backup-Tłumacz", + backup_engine_warning: "Użyje Backup-Tłumacz", + context_messagetranslateoption: "Przetłumacz wiadomość", + context_messageuntranslateoption: "Nieprzetłumacz wiadomość", + context_translator: "Wyszukaj tłumaczenie", + detect_language: "Wykryj język", + error_dailylimit: "Osiągnięto dzienny limit żądań.", + error_hourlylimit: "Osiągnięto godzinowy limit żądań.", + error_keyoutdated: "Klucz API jest nieaktualny.", + error_monthlylimit: "Osiągnięto miesięczny limit żądań.", + error_serverdown: "Serwer tłumaczeń może być w trybie offline.", + exception_text: "Słowa zaczynające się od {{var0}} będą ignorowane", + general_addQuickTranslateButton: "Dodaje szybki przycisk Tłumacz na pasku akcji wiadomości", + general_addTranslateButton: "Dodaje przycisk Tłumacz do obszaru tekstowego kanału", + general_sendOriginalMessage: "Wysyła również oryginalną wiadomość podczas tłumaczenia wysłanej wiadomości", + general_showOriginalMessage: "Pokazuje również oryginalną wiadomość podczas tłumaczenia otrzymanej wiadomości", + general_usePerChatTranslation: "Włącza/wyłącza stan przycisku translatora na kanał, a nie globalnie", + language_choice_input_received: "Język wprowadzania w odebranych wiadomościach", + language_choice_input_sent: "Język wprowadzania w wysyłanych wiadomościach", + language_choice_output_received: "Język wyjściowy w odebranych wiadomościach", + language_choice_output_sent: "Język wyjściowy w wysłanych wiadomościach", + language_selection_channel: "Wybór języka zostanie zmieniony specjalnie dla tego kanału", + language_selection_global: "Wybór języka zostanie zmieniony dla wszystkich serwerów", + language_selection_server: "Wybór języka zostanie zmieniony specjalnie dla tego serwera", + popout_translateoption: "Tłumaczyć", + popout_untranslateoption: "Nie przetłumacz", + prefixes_disable_text: "Słowa zaczynające się od {{var0}} będą ignorowane", + prefixes_enable_text: "Słowa, które aktywują tłumaczenie na określony język (np. $fr, $de, $jp)", + toast_translating: "Tłumaczenie", + toast_translating_failed: "Nie udało się przetłumaczyć", + toast_translating_tryanother: "Wypróbuj innego tłumacza", + translate_your_message: "Przetłumacz swoje wiadomości przed wysłaniem", + translated_watermark: "przetłumaczony", + translator_engine: "Tłumacz" + }; + case "pt-BR": // Portuguese (Brazil) + return { + backup_engine: "Backup-Tradutor", + backup_engine_warning: "Usará o Backup-Tradutor", + context_messagetranslateoption: "Traduzir mensagem", + context_messageuntranslateoption: "Mensagem não traduzida", + context_translator: "Tradução de pesquisa", + detect_language: "Detectar idioma", + error_dailylimit: "Limite de solicitações diárias atingido.", + error_hourlylimit: "Limite de solicitação por hora atingido.", + error_keyoutdated: "Chave de API desatualizada.", + error_monthlylimit: "Limite de solicitação mensal atingido.", + error_serverdown: "O servidor de tradução pode estar offline.", + exception_text: "Palavras que começam com {{var0}} serão ignoradas", + general_addQuickTranslateButton: "Adiciona um botão de tradução rápida na barra de ações da mensagem", + general_addTranslateButton: "Adiciona um botão de tradução à área de texto do canal", + general_sendOriginalMessage: "Também envia a Mensagem original ao traduzir sua Mensagem enviada", + general_showOriginalMessage: "Também mostra a Mensagem original ao traduzir uma Mensagem recebida", + general_usePerChatTranslation: "Habilita/desabilita o estado do botão do tradutor por canal e não globalmente", + language_choice_input_received: "Idioma de entrada nas mensagens recebidas", + language_choice_input_sent: "Idioma de entrada em suas mensagens enviadas", + language_choice_output_received: "Idioma de saída nas mensagens recebidas", + language_choice_output_sent: "Idioma de saída em suas mensagens enviadas", + language_selection_channel: "A seleção de idioma será alterada especificamente para este canal", + language_selection_global: "A seleção de idioma será alterada para todos os servidores", + language_selection_server: "A seleção de idioma será alterada especificamente para este servidor", + popout_translateoption: "Traduzir", + popout_untranslateoption: "Não traduzido", + prefixes_disable_text: "Prefixos que desativam a tradução da mensagem", + prefixes_enable_text: "Prefixos que permitem a tradução com linguagem específica (por exemplo, $fr, $de, $jp)", + toast_translating: "Traduzindo", + toast_translating_failed: "Falha ao traduzir", + toast_translating_tryanother: "Tente outro tradutor", + translate_your_message: "Traduza suas mensagens antes de enviar", + translated_watermark: "traduzido", + translator_engine: "Tradutor" + }; + case "ro": // Romanian + return { + backup_engine: "Backup-Traducător", + backup_engine_warning: "Va folosi Backup-Traducător", + context_messagetranslateoption: "Traduceți mesajul", + context_messageuntranslateoption: "Untraduceți mesajul", + context_translator: "Căutare traducere", + detect_language: "Detecteaza limba", + error_dailylimit: "Limita zilnică de solicitare a fost atinsă.", + error_hourlylimit: "Limita orară de solicitare a fost atinsă.", + error_keyoutdated: "API-Key este învechită.", + error_monthlylimit: "Limita lunară de solicitare a fost atinsă.", + error_serverdown: "Serverul de traducere ar putea fi offline.", + exception_text: "Cuvintele care încep cu {{var0}} vor fi ignorate", + general_addQuickTranslateButton: "Adaugă un buton de traducere rapidă în bara de acțiuni de mesaje", + general_addTranslateButton: "Adaugă un buton de traducere în zona de text a canalului", + general_sendOriginalMessage: "De asemenea, trimite mesajul original atunci când traduceți mesajul trimis", + general_showOriginalMessage: "Afișează, de asemenea, mesajul original atunci când traduceți un mesaj primit", + general_usePerChatTranslation: "Activează/dezactivează starea butonului de traducător pe canal și nu la nivel global", + language_choice_input_received: "Limba de intrare în mesajele primite", + language_choice_input_sent: "Introduceți limba în mesajele trimise", + language_choice_output_received: "Limba de ieșire în mesajele primite", + language_choice_output_sent: "Limba de ieșire în mesajele trimise", + language_selection_channel: "Selectarea limbii va fi modificată special pentru acest canal", + language_selection_global: "Selectarea limbii va fi modificată pentru toate serverele", + language_selection_server: "Selectarea limbii va fi modificată special pentru acest Server", + popout_translateoption: "Traduceți", + popout_untranslateoption: "Netradus", + prefixes_disable_text: "Prefixele care dezactivează traducerea mesajului", + prefixes_enable_text: "Prefixele care permit traducerea cu un limbaj specific (de exemplu, $fr, $de, $jp)", + toast_translating: "Traducere", + toast_translating_failed: "Nu s-a putut traduce", + toast_translating_tryanother: "Încercați un alt traducător", + translate_your_message: "Traduceți mesajele înainte de a le trimite", + translated_watermark: "tradus", + translator_engine: "Traducător" + }; + case "ru": // Russian + return { + backup_engine: "Резервный-Переводчик", + backup_engine_warning: "Буду использовать Резервный-Переводчик", + context_messagetranslateoption: "Перевести сообщение", + context_messageuntranslateoption: "Непереведенное сообщение", + context_translator: "Искать перевод", + detect_language: "Определить язык", + error_dailylimit: "Достигнут дневной лимит запросов.", + error_hourlylimit: "Достигнут лимит почасовых запросов.", + error_keyoutdated: "API-ключ устарел.", + error_monthlylimit: "Достигнут месячный лимит запросов.", + error_serverdown: "Сервер переводов может быть отключен.", + exception_text: "Слова, начинающиеся с {{var0}}, будут игнорироваться.", + general_addQuickTranslateButton: "Добавляет кнопку быстрого перевода в панель действий сообщений", + general_addTranslateButton: "Добавляет кнопку перевода в текстовую область канала", + general_sendOriginalMessage: "Также отправляет исходное сообщение при переводе отправленного сообщения.", + general_showOriginalMessage: "Также показывает исходное сообщение при переводе полученного сообщения.", + general_usePerChatTranslation: "Включает/отключает состояние кнопки переводчика для каждого канала, а не глобально", + language_choice_input_received: "Язык ввода в полученных сообщениях", + language_choice_input_sent: "Язык ввода в ваших отправленных сообщениях", + language_choice_output_received: "Язык вывода в полученных сообщениях", + language_choice_output_sent: "Язык вывода в ваших отправленных сообщениях", + language_selection_channel: "Выбор языка будет изменен специально для этого канала.", + language_selection_global: "Выбор языка будет изменен для всех серверов.", + language_selection_server: "Выбор языка будет изменен специально для этого сервера", + popout_translateoption: "Переведите", + popout_untranslateoption: "Неперевести", + prefixes_disable_text: "Префиксы, которые отключают перевод сообщения", + prefixes_enable_text: "Префиксы, которые обеспечивают перевод с конкретным языком (например, $fr, $de, $jp)", + toast_translating: "Идет перевод", + toast_translating_failed: "Не удалось перевести", + toast_translating_tryanother: "Попробуйте другой переводчик", + translate_your_message: "Переводите свои сообщения перед отправкой", + translated_watermark: "переведено", + translator_engine: "Переводчик" + }; + case "sv": // Swedish + return { + backup_engine: "Backup-Översättare", + backup_engine_warning: "Kommer att använda Backup-Översättare", + context_messagetranslateoption: "Översätt meddelande", + context_messageuntranslateoption: "Untranslate meddelande", + context_translator: "Sök översättning", + detect_language: "Upptäck språk", + error_dailylimit: "Daglig förfrågningsgräns nådd.", + error_hourlylimit: "Begäran per timme nådd.", + error_keyoutdated: "API-nyckel föråldrad.", + error_monthlylimit: "Gränsen för månatlig begäran har nåtts.", + error_serverdown: "Översättningsservern kan vara offline.", + exception_text: "Ord som börjar med {{var0}} kommer att ignoreras", + general_addQuickTranslateButton: "Lägger till en snabb translate -knapp i meddelanden om meddelanden om meddelanden om meddelanden", + general_addTranslateButton: "Lägger till en Översätt-knapp i kanaltextområdet", + general_sendOriginalMessage: "Skickar också det ursprungliga meddelandet när du översätter ditt skickade meddelande", + general_showOriginalMessage: "Visar även det ursprungliga meddelandet när ett mottaget meddelande översätts", + general_usePerChatTranslation: "Aktiverar/inaktiverar översättarknappens status per kanal och inte globalt", + language_choice_input_received: "Inmatningsspråk i mottagna meddelanden", + language_choice_input_sent: "Inmatningsspråk i dina skickade meddelanden", + language_choice_output_received: "Utmatningsspråk i mottagna meddelanden", + language_choice_output_sent: "Utmatningsspråk i dina skickade meddelanden", + language_selection_channel: "Språkval kommer att ändras specifikt för denna kanal", + language_selection_global: "Språkval kommer att ändras för alla servrar", + language_selection_server: "Språkval kommer att ändras specifikt för denna server", + popout_translateoption: "Översätt", + popout_untranslateoption: "Untranslate", + prefixes_disable_text: "Prefix som inaktiverar översättning av meddelandet", + prefixes_enable_text: "Prefix som möjliggör översättning med specifikt språk (t.ex. $fr, $de, $jp)", + toast_translating: "Översätter", + toast_translating_failed: "Det gick inte att översätta", + toast_translating_tryanother: "Prova en annan översättare", + translate_your_message: "Översätt dina meddelanden innan du skickar", + translated_watermark: "översatt", + translator_engine: "Översättare" + }; + case "th": // Thai + return { + backup_engine: "สำรอง-นักแปล", + backup_engine_warning: "จะใช้การสำรองข้อมูล-นักแปล", + context_messagetranslateoption: "แปลข้อความ", + context_messageuntranslateoption: "ยกเลิกการแปลข้อความ", + context_translator: "ค้นหาคำแปล", + detect_language: "ตรวจจับภาษา", + error_dailylimit: "ถึงขีดจำกัดคำขอรายวันแล้ว", + error_hourlylimit: "ถึงขีดจำกัดคำขอรายชั่วโมงแล้ว", + error_keyoutdated: "API-Key ล้าสมัยแล้ว", + error_monthlylimit: "ถึงขีดจำกัดคำขอรายเดือนแล้ว", + error_serverdown: "เซิร์ฟเวอร์การแปลอาจออฟไลน์อยู่", + exception_text: "คำที่ขึ้นต้นด้วย {{var0}} จะถูกละเว้น", + general_addQuickTranslateButton: "เพิ่มปุ่มแปลอย่างรวดเร็วในแถบการกระทำข้อความ", + general_addTranslateButton: "เพิ่มปุ่มแปลภาษาไปยัง Textarea ของช่อง", + general_sendOriginalMessage: "ส่งข้อความต้นฉบับเมื่อแปลข้อความที่ส่งของคุณ", + general_showOriginalMessage: "ยังแสดงข้อความต้นฉบับเมื่อแปลข้อความที่ได้รับ", + general_usePerChatTranslation: "เปิด/ปิดสถานะปุ่มนักแปลต่อช่องและไม่ใช่ทั่วโลก", + language_choice_input_received: "ป้อนภาษาในข้อความที่ได้รับ", + language_choice_input_sent: "ป้อนภาษาในข้อความที่คุณส่ง", + language_choice_output_received: "ภาษาเอาต์พุตในข้อความที่ได้รับ", + language_choice_output_sent: "ภาษาที่ส่งออกในข้อความที่ส่งของคุณ", + language_selection_channel: "การเลือกภาษาจะมีการเปลี่ยนแปลงเฉพาะสำหรับช่องนี้", + language_selection_global: "การเลือกภาษาจะมีการเปลี่ยนแปลงสำหรับเซิร์ฟเวอร์ทั้งหมด", + language_selection_server: "การเลือกภาษาจะมีการเปลี่ยนแปลงโดยเฉพาะสำหรับเซิร์ฟเวอร์นี้", + popout_translateoption: "แปลภาษา", + popout_untranslateoption: "ไม่แปล", + prefixes_disable_text: "คำนำหน้าการปิดใช้งานการแปลข้อความ", + prefixes_enable_text: "คำนำหน้าที่เปิดใช้งานการแปลด้วยภาษาที่เฉพาะเจาะจง (เช่น $fr, $de, $jp)", + toast_translating: "กำลังแปล", + toast_translating_failed: "แปลไม่สำเร็จ", + toast_translating_tryanother: "ลองใช้นักแปลคนอื่น", + translate_your_message: "แปลข้อความของคุณก่อนส่ง", + translated_watermark: "แปล", + translator_engine: "นักแปล" + }; + case "tr": // Turkish + return { + backup_engine: "Yedekleme-Çevirmen", + backup_engine_warning: "Yedekleme-Çevirmen kullanacak", + context_messagetranslateoption: "Mesajı Çevir", + context_messageuntranslateoption: "Çeviriyi Kaldır Mesajı", + context_translator: "Çeviri ara", + detect_language: "Dili Algıla", + error_dailylimit: "Günlük İstek Sınırına ulaşıldı.", + error_hourlylimit: "Saatlik İstek Sınırına ulaşıldı.", + error_keyoutdated: "API Anahtarı güncel değil.", + error_monthlylimit: "Aylık İstek Sınırına ulaşıldı.", + error_serverdown: "Çeviri Sunucusu çevrimdışı olabilir.", + exception_text: "{{var0}} ile başlayan kelimeler yok sayılacak", + general_addQuickTranslateButton: "Mesaj Eylemleri çubuğuna hızlı bir çeviri düğmesi ekler", + general_addTranslateButton: "Kanal Metin Alanına Çevir Düğmesi Ekler", + general_sendOriginalMessage: "Gönderilen Mesajınızı çevirirken orijinal Mesajı da gönderir", + general_showOriginalMessage: "Alınan bir Mesajı tercüme ederken orijinal Mesajı da gösterir.", + general_usePerChatTranslation: "Genel olarak değil, Kanal başına Çevirmen Düğmesi Durumunu Etkinleştirir/Devre Dışı Bırakır", + language_choice_input_received: "Alınan Mesajlarda Giriş Dili", + language_choice_input_sent: "Gönderilen Mesajlarınızda Dil Girin", + language_choice_output_received: "Alınan Mesajlarda Çıktı Dili", + language_choice_output_sent: "Gönderilen Mesajlarınızda Çıktı Dili", + language_selection_channel: "Dil Seçimi bu Kanal için özel olarak değiştirilecektir.", + language_selection_global: "Tüm Sunucular için Dil Seçimi değiştirilecek", + language_selection_server: "Dil Seçimi bu Sunucuya özel olarak değiştirilecektir.", + popout_translateoption: "Çevirmek", + popout_untranslateoption: "Çevirmeyi kaldır", + prefixes_disable_text: "Mesajın çevirisini devre dışı bırakan önekler", + prefixes_enable_text: "Belirli bir dille çeviriyi etkinleştiren önekler (örn. $fr, $de, $jp)", + toast_translating: "Çeviri", + toast_translating_failed: "Tercüme edilemedi", + toast_translating_tryanother: "Başka bir Çevirmen deneyin", + translate_your_message: "Göndermeden önce Mesajlarınızı çevirin", + translated_watermark: "tercüme", + translator_engine: "Çevirmen" + }; + case "uk": // Ukrainian + return { + backup_engine: "Резервний-перекладач", + backup_engine_warning: "Використовуватиме Резервний-Перекладач", + context_messagetranslateoption: "Перекласти повідомлення", + context_messageuntranslateoption: "Неперекладене повідомлення", + context_translator: "Пошук перекладу", + detect_language: "Визначити мову", + error_dailylimit: "Денний ліміт запитів досягнуто.", + error_hourlylimit: "Досягнуто погодинного ліміту запитів.", + error_keyoutdated: "API-ключ застарів.", + error_monthlylimit: "Досягнуто місячного ліміту запитів.", + error_serverdown: "Сервер перекладу може бути офлайн.", + exception_text: "Слова, що починаються з {{var0}}, ігноруватимуться", + general_addQuickTranslateButton: "Додає кнопку швидкого перекладу в панелі дій Message", + general_addTranslateButton: "Додає кнопку перекладу до текстової області каналу", + general_sendOriginalMessage: "Також надсилає оригінальне повідомлення під час перекладу вашого надісланого повідомлення", + general_showOriginalMessage: "Також показує оригінальне повідомлення під час перекладу отриманого повідомлення", + general_usePerChatTranslation: "Вмикає/вимикає стан кнопки перекладача для кожного каналу, а не глобально", + language_choice_input_received: "Мова введення в отриманих повідомленнях", + language_choice_input_sent: "Мова введення у ваших надісланих повідомленнях", + language_choice_output_received: "Мова виводу в отриманих повідомленнях", + language_choice_output_sent: "Мова виведення у ваших надісланих повідомленнях", + language_selection_channel: "Вибір мови буде змінено спеціально для цього каналу", + language_selection_global: "Вибір мови буде змінено для всіх серверів", + language_selection_server: "Вибір мови буде змінено спеціально для цього сервера", + popout_translateoption: "Перекласти", + popout_untranslateoption: "Неперекласти", + prefixes_disable_text: "Префікси, що відключають переклад повідомлення", + prefixes_enable_text: "Префікси, що дозволяють перекладати з конкретною мовою (наприклад, $fr, $de, $jp)", + toast_translating: "Переклад", + toast_translating_failed: "Не вдалося перекласти", + toast_translating_tryanother: "Спробуйте іншого перекладача", + translate_your_message: "Перекладіть свої повідомлення перед надсиланням", + translated_watermark: "переклав", + translator_engine: "Перекладач" + }; + case "vi": // Vietnamese + return { + backup_engine: "Backup-Gười phiên dịch", + backup_engine_warning: "Sẽ sử dụng Backup-Gười phiên dịch", + context_messagetranslateoption: "Dịch tin nhắn", + context_messageuntranslateoption: "Thư chưa dịch", + context_translator: "Tìm kiếm bản dịch", + detect_language: "Phát hiện ngôn ngữ", + error_dailylimit: "Đã đạt đến Giới hạn Yêu cầu Hàng ngày.", + error_hourlylimit: "Đã đạt đến Giới hạn Yêu cầu Hàng giờ.", + error_keyoutdated: "API-Key đã lỗi thời.", + error_monthlylimit: "Đã đạt đến Giới hạn Yêu cầu Hàng tháng.", + error_serverdown: "Máy chủ dịch có thể ngoại tuyến.", + exception_text: "Các từ bắt đầu bằng {{var0}} sẽ bị bỏ qua", + general_addQuickTranslateButton: "Thêm nút dịch nhanh vào thanh hành động tin nhắn", + general_addTranslateButton: "Thêm nút dịch vào vùng văn bản của kênh", + general_sendOriginalMessage: "Đồng thời gửi Tin nhắn gốc khi dịch Tin nhắn đã gửi của bạn", + general_showOriginalMessage: "Đồng thời hiển thị Tin nhắn gốc khi dịch một Tin nhắn đã nhận", + general_usePerChatTranslation: "Bật / tắt Trạng thái nút dịch trên mỗi kênh và không bật trên toàn cầu", + language_choice_input_received: "Nhập Ngôn ngữ trong Tin nhắn đã nhận", + language_choice_input_sent: "Nhập Ngôn ngữ trong Tin nhắn đã gửi của bạn", + language_choice_output_received: "Ngôn ngữ đầu ra trong Tin nhắn đã nhận", + language_choice_output_sent: "Ngôn ngữ đầu ra trong Tin nhắn đã gửi của bạn", + language_selection_channel: "Lựa chọn ngôn ngữ sẽ được thay đổi cụ thể cho Kênh này", + language_selection_global: "Lựa chọn ngôn ngữ sẽ được thay đổi cho tất cả các Máy chủ", + language_selection_server: "Lựa chọn ngôn ngữ sẽ được thay đổi cụ thể cho Máy chủ này", + popout_translateoption: "Phiên dịch", + popout_untranslateoption: "Chưa dịch", + prefixes_disable_text: "Tiền tố vô hiệu hóa dịch tin nhắn", + prefixes_enable_text: "Tiền tố cho phép dịch với ngôn ngữ cụ thể (ví dụ: $fr, $de, $jp)", + toast_translating: "Phiên dịch", + toast_translating_failed: "Không dịch được", + toast_translating_tryanother: "Thử một Trình dịch khác", + translate_your_message: "Dịch Tin nhắn của bạn trước khi gửi", + translated_watermark: "đã dịch", + translator_engine: "Người phiên dịch" + }; + case "zh-CN": // Chinese (China) + return { + backup_engine: "备份翻译器", + backup_engine_warning: "将使用备份翻译器", + context_messagetranslateoption: "翻译消息", + context_messageuntranslateoption: "取消翻译消息", + context_translator: "搜索翻译", + detect_language: "检测语言", + error_dailylimit: "已达到每日请求限制。", + error_hourlylimit: "已达到每小时请求限制。", + error_keyoutdated: "API 密钥已过时。", + error_monthlylimit: "已达到每月请求限制。", + error_serverdown: "翻译服务器可能离线。", + exception_text: "以 {{var0}} 开头的单词将被忽略", + general_addQuickTranslateButton: "在消息操作栏中添加一个快速翻译按钮", + general_addTranslateButton: "将翻译按钮添加到频道文本区域", + general_sendOriginalMessage: "翻译您发送的消息时也会发送原始消息", + general_showOriginalMessage: "翻译收到的消息时还显示原始消息", + general_usePerChatTranslation: "启用/禁用每个通道而不是全局的转换器按钮状态", + language_choice_input_received: "收到消息中的输入语言", + language_choice_input_sent: "在您发送的消息中输入语言", + language_choice_output_received: "接收消息中的输出语言", + language_choice_output_sent: "您发送的消息中的输出语言", + language_selection_channel: "将专门为此频道更改语言选择", + language_selection_global: "将更改所有服务器的语言选择", + language_selection_server: "语言选择将专门为此服务器更改", + popout_translateoption: "翻译", + popout_untranslateoption: "取消翻译", + prefixes_disable_text: "禁用消息翻译的前缀", + prefixes_enable_text: "用特定语言启用翻译的前缀(例如 $fr, $de, $jp)", + toast_translating: "正在翻译", + toast_translating_failed: "翻译失败", + toast_translating_tryanother: "尝试其它翻译器", + translate_your_message: "发送前翻译您的消息", + translated_watermark: "已翻译", + translator_engine: "译者" + }; + case "zh-TW": // Chinese (Taiwan) + return { + backup_engine: "備份翻譯器", + backup_engine_warning: "將使用備份翻譯器", + context_messagetranslateoption: "翻譯訊息", + context_messageuntranslateoption: "取消翻譯訊息", + context_translator: "搜尋翻譯", + detect_language: "檢測語言", + error_dailylimit: "已達到每日請求限制。", + error_hourlylimit: "已達到每小時請求限制。", + error_keyoutdated: "API 密鑰已過時。", + error_monthlylimit: "已達到每月請求限制。", + error_serverdown: "翻譯服務器可能離線。", + exception_text: "以 {{var0}} 開頭的單詞將被忽略", + general_addQuickTranslateButton: "在消息操作欄中添加一個快速翻譯按鈕", + general_addTranslateButton: "將翻譯按鈕添加到頻道文本區域", + general_sendOriginalMessage: "翻譯您發送的消息時也會發送原始消息", + general_showOriginalMessage: "翻譯收到的消息時還顯示原始消息", + general_usePerChatTranslation: "啟用/禁用每個通道而不是全局的轉換器按鈕狀態", + language_choice_input_received: "收到消息中的輸入語言", + language_choice_input_sent: "在您發送的消息中輸入語言", + language_choice_output_received: "接收消息中的輸出語言", + language_choice_output_sent: "您發送的消息中的輸出語言", + language_selection_channel: "將專門為此頻道更改語言選擇", + language_selection_global: "將更改所有服務器的語言選擇", + language_selection_server: "語言選擇將專門為此服務器更改", + popout_translateoption: "翻譯", + popout_untranslateoption: "取消翻譯", + prefixes_disable_text: "禁用消息翻译的前缀", + prefixes_enable_text: "用特定语言启用翻译的前缀(例如 $fr, $de, $jp)", + toast_translating: "正在翻譯", + toast_translating_failed: "無法翻譯", + toast_translating_tryanother: "嘗試其它翻譯器", + translate_your_message: "發送前翻譯您的消息", + translated_watermark: "已翻譯", + translator_engine: "譯者" + }; + default: // English + return { + backup_engine: "Backup-Translator", + backup_engine_warning: "Will use Backup-Translator", + context_messagetranslateoption: "Translate Message", + context_messageuntranslateoption: "Untranslate Message", + context_translator: "Search Translation", + detect_language: "Detect Language", + error_dailylimit: "Daily Request Limit reached.", + error_hourlylimit: "Hourly Request Limit reached.", + error_keyoutdated: "API-Key outdated.", + error_monthlylimit: "Monthly Request Limit reached.", + error_serverdown: "Translation Server might be offline.", + exception_text: "Words starting with {{var0}} will be ignored", + general_addQuickTranslateButton: "Adds a Quick Translate Button in the Message Actions Bar", + general_addTranslateButton: "Adds a Translate Button to the Channel Textarea", + general_sendOriginalMessage: "Also sends the original Message when translating your sent Message", + general_showOriginalMessage: "Also shows the original Message when translating a received Message", + general_usePerChatTranslation: "Enables/Disables the Translator Button State per Channel and not globally", + language_choice_input_received: "Input Language in received Messages", + language_choice_input_sent: "Input Language in your sent Messages", + language_choice_output_received: "Output Language in received Messages", + language_choice_output_sent: "Output Language in your sent Messages", + language_selection_channel: "Language Selection will be changed specifically for this Channel", + language_selection_global: "Language Selection will be changed for all Servers", + language_selection_server: "Language Selection will be changed specifically for this Server", + popout_translateoption: "Translate", + popout_untranslateoption: "Untranslate", + prefixes_disable_text: "Prefixes that disable translation of message", + prefixes_enable_text: "Prefixes that enable translation with specific language (e.g. $fr, $de, $jp)", + toast_translating: "Translating", + toast_translating_failed: "Failed to translate", + toast_translating_tryanother: "Try another Translator", + translate_your_message: "Translate your Messages before sending", + translated_watermark: "translated", + translator_engine: "Translator" + }; + } + } + }; + })(window.BDFDB_Global.PluginUtils.buildPlugin(changeLog)); +})(); diff --git a/dotfiles/.config/BetterDiscord/plugins/ViewProfilePicture.config.json b/dotfiles/.config/BetterDiscord/plugins/ViewProfilePicture.config.json new file mode 100644 index 0000000..8ed3446 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ViewProfilePicture.config.json @@ -0,0 +1,6 @@ +{ + "settings": { + "showOnHover": true, + "bannerColor": false + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/ViewProfilePicture.plugin.js b/dotfiles/.config/BetterDiscord/plugins/ViewProfilePicture.plugin.js new file mode 100644 index 0000000..23cc2dc --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ViewProfilePicture.plugin.js @@ -0,0 +1,895 @@ +/** + * @name ViewProfilePicture + * @description Adds a button to the user popout and profile that allows you to view the Avatar and banner. + * @version 1.3.7 + * @author Skamt + * @website https://github.com/Skamt/BDAddons/tree/main/ViewProfilePicture + * @source https://raw.githubusercontent.com/Skamt/BDAddons/main/ViewProfilePicture/ViewProfilePicture.plugin.js + */ + +// config:@Config +var Config_default = { + "info": { + "name": "ViewProfilePicture", + "version": "1.3.7", + "description": "Adds a button to the user popout and profile that allows you to view the Avatar and banner.", + "source": "https://raw.githubusercontent.com/Skamt/BDAddons/main/ViewProfilePicture/ViewProfilePicture.plugin.js", + "github": "https://github.com/Skamt/BDAddons/tree/main/ViewProfilePicture", + "authors": [{ + "name": "Skamt" + }] + }, + "settings": { + "showOnHover": false, + "bannerColor": false + } +}; + +// common/Api.js +var Api = new BdApi(Config_default.info.name); +var UI = /* @__PURE__ */ (() => Api.UI)(); +var DOM = /* @__PURE__ */ (() => Api.DOM)(); +var Data = /* @__PURE__ */ (() => Api.Data)(); +var React = /* @__PURE__ */ (() => Api.React)(); +var Patcher = /* @__PURE__ */ (() => Api.Patcher)(); +var Logger = /* @__PURE__ */ (() => Api.Logger)(); +var Webpack = /* @__PURE__ */ (() => Api.Webpack)(); +var findInTree = /* @__PURE__ */ (() => Api.Utils.findInTree)(); + +// common/Utils/Logger.js +Logger.patchError = (patchId) => { + console.error(`%c[${Config_default.info.name}] %cCould not find module for %c[${patchId}]`, "color: #3a71c1;font-weight: bold;", "", "color: red;font-weight: bold;"); +}; +var Logger_default = Logger; + +// common/Utils/EventEmitter.js +var EventEmitter_default = class { + constructor() { + this.listeners = {}; + } + isInValid(event, handler) { + return typeof event !== "string" || typeof handler !== "function"; + } + once(event, handler) { + if (this.isInValid(event, handler)) return; + if (!this.listeners[event]) this.listeners[event] = /* @__PURE__ */ new Set(); + const wrapper = () => { + handler(); + this.off(event, wrapper); + }; + this.listeners[event].add(wrapper); + } + on(event, handler) { + if (this.isInValid(event, handler)) return; + if (!this.listeners[event]) this.listeners[event] = /* @__PURE__ */ new Set(); + this.listeners[event].add(handler); + return () => this.off(event, handler); + } + off(event, handler) { + if (this.isInValid(event, handler)) return; + if (!this.listeners[event]) return; + this.listeners[event].delete(handler); + if (this.listeners[event].size !== 0) return; + delete this.listeners[event]; + } + emit(event, ...payload) { + if (!this.listeners[event]) return; + for (const listener of this.listeners[event]) { + try { + listener.apply(null, payload); + } catch (err) { + Logger_default.error(`Could not run listener for ${event}`, err); + } + } + } +}; + +// common/Utils/Plugin.js +var Events = { + START: "START", + STOP: "STOP" +}; +var Plugin_default = new class extends EventEmitter_default { + start() { + this.emit(Events.START); + } + stop() { + this.emit(Events.STOP); + } +}(); + +// common/Utils/StylesLoader.js +var styleLoader = { + _styles: [], + push(styles) { + this._styles.push(styles); + } +}; +Plugin_default.on(Events.START, () => { + DOM.addStyle(styleLoader._styles.join("\n")); +}); +Plugin_default.on(Events.STOP, () => { + DOM.removeStyle(); +}); +var StylesLoader_default = styleLoader; + +// src/ViewProfilePicture/styles.css +StylesLoader_default.push(`/* View Profile Button */ +.VPP-Button { + background: rgb(1 0 1 / 54%); + cursor: pointer; + display: flex; + border-radius: 50%; + color: #fff; + width: 32px; + height: 32px; + justify-content: center; + align-items: center; +} + +.VPP-float { + position: absolute; + top: 12px; + right: 12px; + z-index: 987; +} + +.VPP-Button svg { + height: 18px; + width: 18px; +} + +/* Bigger icon on profile */ +.VPP-settings svg, +.VPP-profile svg { + height: 24px; + width: 24px; +} + +.VPP-Button:hover { + background: rgb(1 0 1 / 64%); +} + +/* div replacement if No banner */ +.VPP-NoBanner { + width: 70vw; + height: 50vh; + position: relative; +} + +/* Carousel Modal */ +.VPP-carousel-modal { + background: #0000; + width: 100vw; + height: 100vh; + box-shadow: none !important; +} + +.VPP-carousel { + position: static; + margin: auto; +} + +.VPP-carousel > div[role="button"] { + margin: 0 15px; + background: var(--background-surface-high); + color: var(--interactive-text-default); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.VPP-carousel > div[role="button"]:hover { + background: var(--background-surface-highest); + color: var(--interactive-text-default); +} + +/* Copy color button */ +.VPP-copy-color-container { + position: absolute; + top: 100%; + display: flex; + cursor: pointer; + gap: 5px; +} + +.VPP-copy-color-label, +.VPP-copy-color { + font-size: 14px; + font-weight: 500; + color: #fff; + line-height: 30px; + transition: opacity 0.15s ease; + opacity: 0.5; + text-transform: uppercase; +} + +.VPP-copy-color:hover { + opacity: 1; + text-decoration: underline; +} + +.VPP-separator { + line-height: 30px; + opacity: 0.5; + color: #fff; +} + +.VPP-copy-color-label { + text-transform: capitalize; +} + +.VPP-hover { + opacity: 0; +} + +.VPP-container:hover .VPP-hover { + opacity: 1; +} + +.VPP-colorFormat-options { + display: flex; +} + +.VPP-colorFormat-options > div { + flex: 1; +} +`); + +// common/Utils/index.js +function fit({ width, height, gap = 0.8 }) { + const ratio = Math.min(innerWidth / width, innerHeight / height); + width = Math.round(width * ratio); + height = Math.round(height * ratio); + return { + width, + height, + maxHeight: height * gap, + maxWidth: width * gap + }; +} + +function concateClassNames(...args) { + return args.filter(Boolean).join(" "); +} +var promiseHandler = (promise) => promise.then((data) => [void 0, data]).catch((err) => [err]); + +function copy(data) { + DiscordNative.clipboard.copy(data); +} + +function getNestedProp(obj, path) { + return path.split(".").reduce((ob, prop) => ob?.[prop], obj); +} +var nop = () => {}; + +function getImageDimensions(url) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve({ + width: img.width, + height: img.height + }); + img.onerror = reject; + img.src = url; + }); +} + +// common/React.jsx +var useState = /* @__PURE__ */ (() => React.useState)(); +var useMemo = /* @__PURE__ */ (() => React.useMemo)(); +var React_default = /* @__PURE__ */ (() => React)(); + +// common/Components/ErrorBoundary/index.jsx +var ErrorBoundary = class extends React_default.Component { + state = { hasError: false, error: null, info: null }; + componentDidCatch(error, info) { + this.setState({ error, info, hasError: true }); + const errorMessage = ` + ${error?.message || ""}${(info?.componentStack || "").split("\n").slice(0, 20).join("\n")}`; + console.error(`%c[${Config_default?.info?.name || "Unknown Plugin"}] %cthrew an exception at %c[${this.props.id}] +`, "color: #3a71c1;font-weight: bold;", "", "color: red;font-weight: bold;", errorMessage); + } + renderErrorBoundary() { + return /* @__PURE__ */ React_default.createElement("div", { style: { background: "#292c2c", padding: "20px", borderRadius: "10px" } }, /* @__PURE__ */ React_default.createElement("b", { style: { color: "#e0e1e5" } }, "An error has occured while rendering ", /* @__PURE__ */ React_default.createElement("span", { style: { color: "orange" } }, this.props.id))); + } + renderFallback() { + if (React_default.isValidElement(this.props.fallback)) { + if (this.props.passMetaProps) + this.props.fallback.props = { + id: this.props.id, + plugin: Config_default?.info?.name || "Unknown Plugin", + ...this.props.fallback.props + }; + return this.props.fallback; + } + return /* @__PURE__ */ React_default.createElement( + this.props.fallback, { + id: this.props.id, + plugin: Config_default?.info?.name || "Unknown Plugin" + } + ); + } + render() { + if (!this.state.hasError) return this.props.children; + return this.props.fallback ? this.renderFallback() : this.renderErrorBoundary(); + } +}; + +// common/Components/icons/ErrorIcon/index.jsx +var ErrorIcon_default = (props) => /* @__PURE__ */ React.createElement("div", { ...props }, /* @__PURE__ */ React.createElement( + "svg", { + xmlns: "http://www.w3.org/2000/svg", + viewBox: "0 0 24 24", + fill: "red", + width: "18", + height: "18" + }, + /* @__PURE__ */ + React.createElement( + "path", { + d: "M0 0h24v24H0z", + fill: "none" + } + ), + /* @__PURE__ */ + React.createElement("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" }) +)); + +// common/Webpack.js +var getModule = /* @__PURE__ */ (() => Webpack.getModule)(); +var Filters = /* @__PURE__ */ (() => Webpack.Filters)(); +var getMangled = /* @__PURE__ */ (() => Webpack.getMangled)(); +var getStore = /* @__PURE__ */ (() => Webpack.getStore)(); + +function reactRefMemoFilter(type, ...args) { + const filter = Filters.byStrings(...args); + return (target) => target[type] && filter(target[type]); +} + +// common/DiscordModules/zustand.js +var { zustand } = getMangled(Filters.bySource("useSyncExternalStoreWithSelector", "useDebugValue", "subscribe"), { + _: Filters.byStrings("subscribe"), + zustand: () => true +}); +var subscribeWithSelector = getModule(Filters.byStrings("getState", "equalityFn", "fireImmediately"), { searchExports: true }); + +function create(initialState) { + const Store = zustand(initialState); + Object.defineProperty(Store, "state", { + configurable: false, + get: () => Store.getState() + }); + return Store; +} + +// common/Utils/Settings.js +var SettingsStore = create(subscribeWithSelector(() => Object.assign(Config_default.settings, Data.load("settings") || {}))); +((state) => { + const selectors = {}; + const actions = {}; + for (const [key, value] of Object.entries(state)) { + actions[`set${key}`] = (newValue) => SettingsStore.setState({ + [key]: newValue }); + selectors[key] = (state2) => state2[key]; + } + Object.defineProperty(SettingsStore, "selectors", { value: Object.assign(selectors) }); + Object.assign(SettingsStore, actions); +})(SettingsStore.getInitialState()); +SettingsStore.subscribe( + (state) => state, + () => Data.save("settings", SettingsStore.state) +); +Object.assign(SettingsStore, { + useSetting: (key) => { + const val = SettingsStore((state) => state[key]); + return [val, SettingsStore[`set${key}`]]; + } +}); +var Settings_default = SettingsStore; + +// common/Utils/Modals/styles.css +StylesLoader_default.push(`.transparent-background.transparent-background{ + background: transparent; + border:unset; +}`); + +// common/Utils/Modals/index.jsx +var ModalActions = getModule((a) => a.useModalsStore); +var Modals = /* @__PURE__ */ getMangled( /* @__PURE__ */ Filters.bySource("MODAL_ROOT", "transitionState"), { + ModalRoot: /* @__PURE__ */ Filters.byStrings("transitionState"), + ModalFooter: /* @__PURE__ */ Filters.byStrings(".HORIZONTAL_REVERSE"), + ModalContent: /* @__PURE__ */ Filters.byStrings("scrollbarType", "scrollerRef"), + ModalHeader: /* @__PURE__ */ Filters.byStrings("headerIdIsManaged", "headerId", ".HORIZONTAL"), + Animations: (a) => a.SUBTLE, + Sizes: (a) => a.DYNAMIC, + ModalCloseButton: Filters.byStrings("withCircleBackground") +}); +var openModal = (children, tag, { className, ...modalRootProps } = {}) => { + const id = `${tag ? `${tag}-` : ""}modal`; + return ModalActions.openModal((props) => { + return /* @__PURE__ */ React.createElement( + ErrorBoundary, { + id, + plugin: Config_default.info.name + }, + /* @__PURE__ */ + React.createElement( + Modals.ModalRoot, { + onClick: props.onClose, + transitionState: props.transitionState, + className: concateClassNames("transparent-background", className), + size: Modals.Sizes.DYNAMIC, + ...modalRootProps + }, + React.cloneElement(children, { ...props }) + ) + ); + }); +}; + +// MODULES-AUTO-LOADER:@Modules/Tooltip +var Tooltip_default = getModule(Filters.byPrototypeKeys("renderTooltip"), { searchExports: true }); + +// common/Components/Tooltip/index.jsx +var Tooltip_default2 = ({ note, position, children }) => { + return /* @__PURE__ */ React.createElement( + Tooltip_default, { + text: note, + position: position || "top" + }, + (props) => React.cloneElement(children, { + ...props, + ...children.props + }) + ); +}; + +// common/Components/icons/ImageIcon/index.jsx +function ImageIcon(props) { + return /* @__PURE__ */ React.createElement( + "svg", { + fill: "currentColor", + width: "24", + height: "24", + viewBox: "-50 -50 484 484", + ...props + }, + /* @__PURE__ */ + React.createElement("path", { d: "M341.333,0H42.667C19.093,0,0,19.093,0,42.667v298.667C0,364.907,19.093,384,42.667,384h298.667 C364.907,384,384,364.907,384,341.333V42.667C384,19.093,364.907,0,341.333,0z M42.667,320l74.667-96l53.333,64.107L245.333,192l96,128H42.667z" }) + ); +} + +// common/Utils/ImageModal/styles.css +StylesLoader_default.push(`.downloadLink { + color: white !important; + font-size: 14px; + font-weight: 500; + /* line-height: 18px;*/ + text-decoration: none; + transition: opacity.15s ease; + opacity: 0.5; +} + +.imageModalwrapper { + display: flex; + flex-direction: column; +} + +.imageModalOptions { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 4px; +} +`); + +// MODULES-AUTO-LOADER:@Stores/AccessibilityStore +var AccessibilityStore_default = getStore("AccessibilityStore"); + +// common/Utils/ImageModal/index.jsx +var RenderLinkComponent = getModule((m) => m.type?.toString?.().includes("MASKED_LINK"), { searchExports: false }); +var ImageModal = getModule(reactRefMemoFilter("type", "renderLinkComponent"), { searchExports: true }); + +function h(e, t) { + let n = arguments.length > 2 && void 0 !== arguments[2] && arguments[2]; + true === n || AccessibilityStore_default.useReducedMotion ? e.set(t) : e.start(t); +} +var useSomeScalingHook = getModule(Filters.byStrings("reducedMotion.enabled", "useSpring", "respect-motion-settings"), { searchExports: true }); +var context = getModule((a) => a?._currentValue?.scale, { searchExports: true }); +var ImageComponent = ({ url, ...rest }) => { + const [x, P] = useState(false); + const [M, w] = useSomeScalingHook(() => ({ + scale: AccessibilityStore_default.useReducedMotion ? 1 : 0.9, + x: 0, + y: 0, + config: { + friction: 30, + tension: 300 + } + })); + const contextVal = useMemo( + () => ({ + scale: M.scale, + x: M.x, + y: M.y, + setScale(e, t) { + h(M.scale, e, null == t ? void 0 : t.immediate); + }, + setOffset(e, t, n) { + h(M.x, e, null == n ? void 0 : n.immediate), h(M.y, t, null == n ? void 0 : n.immediate); + }, + zoomed: x, + setZoomed(e) { + P(e), h(M.scale, e ? 2.5 : 1), e || (h(M.x, 0), h(M.y, 0)); + } + }), + [x, M] + ); + return /* @__PURE__ */ React_default.createElement(context.Provider, { value: contextVal }, /* @__PURE__ */ React_default.createElement("div", { className: "imageModalwrapper" }, /* @__PURE__ */ React_default.createElement( + ImageModal, { + maxWidth: rest.maxWidth, + maxHeight: rest.maxHeight, + media: { + ...rest, + type: "IMAGE", + url, + proxyUrl: url + } + } + ), !x && /* @__PURE__ */ React_default.createElement("div", { className: "imageModalOptions" }, /* @__PURE__ */ React_default.createElement( + RenderLinkComponent, { + className: "downloadLink", + href: url + }, + "Open in Browser" + )))); +}; + +// MODULES-AUTO-LOADER:@Modules/Color +var Color_default = getModule(Filters.byKeys("Color", "hex", "hsl"), { searchExports: false }); + +// MODULES-AUTO-LOADER:@Stores/ThemeStore +var ThemeStore_default = getStore("ThemeStore"); + +// common/Utils/Toast.js +function showToast(content, type) { + UI.showToast(`[${Config_default.info.name}] ${content}`, { timeout: 5e3, type }); +} +var Toast_default = { + success(content) { + showToast(content, "success"); + }, + info(content) { + showToast(content, "info"); + }, + warning(content) { + showToast(content, "warning"); + }, + error(content) { + showToast(content, "error"); + } +}; + +// src/ViewProfilePicture/components/ColorModalComponent.jsx +var DesignSystem = getModule((a) => a?.unsafe_rawColors?.PRIMARY_800?.resolve); + +function resolveColor() { + if (!DesignSystem?.unsafe_rawColors?.PRIMARY_800) return "#111214"; + return DesignSystem.unsafe_rawColors?.PRIMARY_800.resolve({ + theme: ThemeStore_default.theme, + saturation: AccessibilityStore_default.saturation + }).hex(); +} + +function copyColor(type, color) { + let c3 = color; + try { + switch (type) { + case "hex": + c3 = Color_default(color).hex(); + break; + case "rgba": + c3 = Color_default(color).css("rgba"); + break; + case "hsla": + c3 = Color_default(color).css("hsla"); + break; + } + } finally { + copy(c3); + Toast_default.success(`${c3} Copied!`); + } +} + +function SimpleColorModal({ color }) { + return /* @__PURE__ */ React_default.createElement( + "div", { + onClick: (e) => e.stopPropagation(), + className: "VPP-NoBanner", + style: { backgroundColor: Color_default(color).css() } + }, + /* @__PURE__ */ + React_default.createElement("div", { className: "VPP-copy-color-container" }, /* @__PURE__ */ React_default.createElement("span", { className: "VPP-copy-color-label" }, "Copy Color:"), ["hex", false, "rgba", false, "hsla"].map( + (name) => name ? /* @__PURE__ */ React_default.createElement( + "a", { + className: "VPP-copy-color", + onClick: (e) => { + e.stopPropagation(); + copyColor(name, color); + } + }, + name + ) : /* @__PURE__ */ React_default.createElement("span", { className: "VPP-separator" }, "|") + )) + ); +} +var palletHook = getModule(Filters.byStrings("toHexString", "toHsl", "palette"), { searchExports: true }) || {}; + +function ColorModal({ displayProfile, user }) { + const color = palletHook(user.getAvatarURL(displayProfile.guildId, 80)); + return /* @__PURE__ */ React_default.createElement(SimpleColorModal, { color: color || resolveColor() }); +} +var ColorModalComponent_default = { + SimpleColorModal, + ColorModal +}; + +// MODULES-AUTO-LOADER:@Modules/ModalCarousel +var ModalCarousel_default = getModule(Filters.byPrototypeKeys("navigateTo", "preloadImage"), { searchExports: false }); + +// src/ViewProfilePicture/components/ModalCarousel.jsx +var ModalCarousel_default2 = class extends ModalCarousel_default { + preloadNextImages() {} +}; + +// MODULES-AUTO-LOADER:@Modules/Spinner +var Spinner_default = getModule((a) => a?.Type?.CHASING_DOTS, { searchExports: true }); + +// src/ViewProfilePicture/components/ViewProfilePictureButtonComponent.jsx +function Banner({ url, src }) { + const [loaded, setLoaded] = React_default.useState(false); + const dimsRef = React_default.useRef(); + React_default.useEffect(() => { + (async () => { + const [err, dims] = await promiseHandler(getImageDimensions(src)); + dimsRef.current = fit(err ? {} : dims); + setLoaded(true); + })(); + }, []); + if (!loaded) return /* @__PURE__ */ React_default.createElement(Spinner_default, { type: Spinner_default.Type.SPINNING_CIRCLE }); + return /* @__PURE__ */ React_default.createElement( + ImageComponent, { + url, + ...dimsRef.current + } + ); +} +var ViewProfilePictureButtonComponent_default = ({ className, user, displayProfile }) => { + const showOnHover = Settings_default(Settings_default.selectors.showOnHover); + const handler = () => { + const avatarURL = user.getAvatarURL(displayProfile.guildId, 4096, true); + const bannerURL = displayProfile.getBannerURL({ canAnimate: true, size: 4096 }); + const color = displayProfile.accentColor || displayProfile.primaryColor; + const items = [ + /* @__PURE__ */ + React_default.createElement( + ImageComponent, { + url: avatarURL, + ...fit({ width: 4096, height: 4096 }) + } + ), + bannerURL && /* @__PURE__ */ React_default.createElement( + Banner, { + url: bannerURL, + src: displayProfile.getBannerURL({ canAnimate: true, size: 20 }) + } + ), + (!bannerURL || Settings_default.getState().bannerColor) && (color ? /* @__PURE__ */ React_default.createElement(ColorModalComponent_default.SimpleColorModal, { color }) : /* @__PURE__ */ React_default.createElement( + ColorModalComponent_default.ColorModal, { + user, + displayProfile + } + )) + ].filter(Boolean).map((item) => ({ component: item })); + openModal( + /* @__PURE__ */ + React_default.createElement( + ModalCarousel_default2, { + startWith: 0, + className: "VPP-carousel", + items + } + ), + "VPP-carousel", { className: "VPP-carousel-modal" } + ); + }; + return /* @__PURE__ */ React_default.createElement(Tooltip_default2, { note: "View profile picture" }, /* @__PURE__ */ React_default.createElement( + "div", { + onClick: handler, + className: concateClassNames(className, showOnHover && "VPP-hover") + }, + /* @__PURE__ */ + React_default.createElement(ImageIcon, null) + )); +}; + +// src/ViewProfilePicture/patches/patchVPPButton.jsx +var UserProfileModalforwardRef = getModule(Filters.byKeys("Overlay", "render")); +var typeFilter = Filters.byStrings("div", "children:"); +Plugin_default.on(Events.START, () => { + if (!UserProfileModalforwardRef) return Logger_default.patchError("patchVPPButton"); + const unpatch = Patcher.after(UserProfileModalforwardRef, "render", (_, [props], ret) => { + const t = getNestedProp(ret, "props.children.props.children.props.children.props.children.1"); + const target = typeFilter(t?.type) && t || findInTree(ret, (a) => a?.type === "header" || a?.props?.className?.includes("profileHeader"), { walkable: ["props", "children"] }); + if (!target) return; + ret.props.className = `${ret.props.className} VPP-container`; + const children = Array.isArray(target.props.children) ? target.props.children : [target.props.children]; + children.unshift( + /* @__PURE__ */ + React.createElement( + ErrorBoundary, { + id: "ViewProfilePictureButtonComponent", + plugin: Config_default.info.name, + fallback: /* @__PURE__ */ React.createElement(ErrorIcon_default, { className: "VPP-Button" }) + }, + /* @__PURE__ */ + React.createElement( + ViewProfilePictureButtonComponent_default, { + className: concateClassNames("VPP-Button", !typeFilter(target?.type) && "VPP-float"), + user: props.user, + displayProfile: props.displayProfile + } + ) + ) + ); + target.props.children = children; + }); + Plugin_default.once(Events.STOP, unpatch); +}); + +// MODULES-AUTO-LOADER:@Modules/FormSwitch +var FormSwitch_default = getModule(Filters.byStrings("note", "tooltipNote"), { searchExports: true }); + +// common/Components/Switch/index.jsx +var Switch_default = getModule(Filters.byStrings('"data-toggleable-component":"switch"', 'layout:"horizontal"'), { searchExports: true }) || function SwitchComponentFallback(props) { + return /* @__PURE__ */ React.createElement("div", { style: { color: "#fff" } }, props.children, /* @__PURE__ */ React.createElement( + "input", { + type: "checkbox", + checked: props.value, + onChange: (e) => props.onChange(e.target.checked) + } + )); +}; + +// common/Components/Divider/styles.css +StylesLoader_default.push(`.divider-horizontal { + border-top: thin solid var(--border-subtle); + align-self: stretch; + margin:var(--divider-gap) var(--divider-gutter) var(--divider-gap) var(--divider-gutter) ; +} + +.divider-vertical { + border-left: thin solid var(--border-subtle); + align-self: stretch; + margin:var(--divider-gutter) var(--divider-gap) var(--divider-gutter) var(--divider-gap); +} +`); + +// common/Utils/css.js +var classNameFactory = (prefix = "", connector = "-") => (...args) => { + const classNames = /* @__PURE__ */ new Set(); + for (const arg of args) { + if (arg && typeof arg === "string") classNames.add(arg); + else if (Array.isArray(arg)) arg.forEach((name) => classNames.add(name)); + else if (arg && typeof arg === "object") Object.entries(arg).forEach(([name, value]) => value && classNames.add(name)); + } + return Array.from(classNames, (name) => `${prefix}${connector}${name}`).join(" "); +}; + +// common/Components/Divider/index.jsx +var c = classNameFactory("divider"); + +function Divider({ gap = 15, gutter = 0, direction = Divider.direction.HORIZONTAL }) { + return /* @__PURE__ */ React_default.createElement( + "div", { + style: { "--divider-gap": `${gap}px`, "--divider-gutter": `${gutter}%` }, + className: c("base", direction) + } + ); +} +Divider.direction = { + HORIZONTAL: "horizontal", + VERTICAL: "vertical" +}; + +// common/Components/SettingSwtich/index.jsx +function SettingSwtich({ settingKey, note, border = false, onChange = nop, description, ...rest }) { + const [val, set] = Settings_default.useSetting(settingKey); + return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement( + Switch_default, { + ...rest, + checked: val, + label: description || settingKey, + description: note, + onChange: (e) => { + set(e); + onChange(e); + } + } + ), border && /* @__PURE__ */ React.createElement(Divider, { gap: 15 })); +} + +// common/Components/FieldSet/styles.css +StylesLoader_default.push(`.fieldset-container { + display: flex; + flex-direction: column; + gap: 16px; +} + +.fieldset-label { + margin-bottom: 12px; +} + +.fieldset-description { + margin-bottom: 12px; +} + +.fieldset-label + .fieldset-description{ + margin-top:-8px; + margin-bottom: 0; +} + +.fieldset-content { + display: flex; + flex-direction: column; + width: 100%; + justify-content: flex-start; +} +`); + +// MODULES-AUTO-LOADER:@Modules/Heading +var Heading_default = getModule((a) => a?.render?.toString().includes("data-excessive-heading-level"), { searchExports: true }); + +// common/Components/FieldSet/index.jsx +var c2 = classNameFactory("fieldset"); + +function FieldSet({ label, description, children, contentGap = 16 }) { + return /* @__PURE__ */ React_default.createElement("fieldset", { className: c2("container") }, label && /* @__PURE__ */ React_default.createElement( + Heading_default, { + className: c2("label"), + tag: "legend", + variant: "text-lg/medium" + }, + label + ), description && /* @__PURE__ */ React_default.createElement( + Heading_default, { + className: c2("description"), + variant: "text-sm/normal", + color: "text-secondary" + }, + description + ), /* @__PURE__ */ React_default.createElement("div", { className: c2("content"), style: { gap: contentGap } }, children)); +} + +// src/ViewProfilePicture/components/SettingComponent.jsx +function SettingComponent() { + return /* @__PURE__ */ React_default.createElement(FieldSet, { contentGap: 8 }, [{ + settingKey: "showOnHover", + note: "By default hide ViewProfilePicture button and show on hover.", + description: "Show on hover" + }, + { + settingKey: "bannerColor", + note: "Always include banner color in carousel, even if a banner is present.", + description: "Include banner color." + } + ].map(SettingSwtich)); +} + +// src/ViewProfilePicture/index.jsx +Plugin_default.getSettingsPanel = () => /* @__PURE__ */ React_default.createElement(SettingComponent, null); +module.exports = () => Plugin_default; diff --git a/dotfiles/.config/BetterDiscord/plugins/VoiceActivity.config.json b/dotfiles/.config/BetterDiscord/plugins/VoiceActivity.config.json new file mode 100644 index 0000000..5f5d36a --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/VoiceActivity.config.json @@ -0,0 +1,16 @@ +{ + "currentVersionInfo": { + "version": "1.8.33", + "hasShownChangelog": true + }, + "settings": { + "showMemberListIcons": true, + "showDMListIcons": true, + "showPeopleListIcons": true, + "currentChannelColor": true, + "ignoreEnabled": false, + "ignoredChannels": [], + "ignoredGuilds": [] + }, + "changelogVersion": "1.12.1" +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/VoiceActivity.plugin.js b/dotfiles/.config/BetterDiscord/plugins/VoiceActivity.plugin.js new file mode 100644 index 0000000..c5cf04f --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/VoiceActivity.plugin.js @@ -0,0 +1,892 @@ +/** + * @name VoiceActivity + * @author Neodymium + * @version 1.12.1 + * @description Shows icons and info in popouts, the member list, and more when someone is in a voice channel. + * @source https://github.com/Neodymium7/BetterDiscordStuff/blob/main/VoiceActivity/VoiceActivity.plugin.js + * @invite fRbsqH87Av + */ + +/*@cc_on +@if (@_jscript) + + // Offer to self-install for clueless users that try to run this directly. + var shell = WScript.CreateObject("WScript.Shell"); + var fs = new ActiveXObject("Scripting.FileSystemObject"); + var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\BetterDiscord\plugins"); + var pathSelf = WScript.ScriptFullName; + // Put the user at ease by addressing them in the first person + shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30); + if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) { + shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40); + } else if (!fs.FolderExists(pathPlugins)) { + shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10); + } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) { + fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true); + // Show the user where to put plugins in the future + shell.Exec("explorer " + pathPlugins); + shell.Popup("I'm installed!", 0, "Successfully installed", 0x40); + } + WScript.Quit(); + +@else@*/ + +'use strict'; + +const betterdiscord = new BdApi("VoiceActivity"); +const react = BdApi.React; + +// styles +let _styles = ""; +function _loadStyle(path, css) { + _styles += "/*" + path + "*/\n" + css + "\n"; +} +function styles() { + return _styles; +} + +// @lib/settings.ts +class SettingsManager { + settings = betterdiscord.Data.load("settings"); + listeners = new Set(); + constructor(defaultSettings) { + if (!this.settings) { + betterdiscord.Data.save("settings", defaultSettings); + this.settings = defaultSettings; + return; + } + if (Object.keys(this.settings) !== Object.keys(defaultSettings)) { + this.settings = { ...defaultSettings, ...this.settings }; + let changed = false; + for (const key in this.settings) { + if (!(key in defaultSettings)) { + delete this.settings[key]; + changed = true; + } + } + if (changed) betterdiscord.Data.save("settings", this.settings); + } + } + addListener(listener) { + this.listeners.add(listener); + return () => { + this.listeners.delete(listener); + }; + } + clearListeners() { + this.listeners.clear(); + } + useSettingsState(...keys) { + let initialState = this.settings; + if (keys.length) initialState = Object.fromEntries(keys.map((key) => [key, initialState[key]])); + const [state, setState] = react.useState(initialState); + react.useEffect(() => { + return this.addListener((key, value) => { + if (!keys.length || keys.includes(key)) setState((state2) => ({ ...state2, [key]: value })); + }); + }, []); + return state; + } + get(key) { + return this.settings[key]; + } + set(key, value) { + this.settings[key] = value; + betterdiscord.Data.save("settings", this.settings); + for (const listener of this.listeners) listener(key, value); + } +} +function buildSettingsPanel(settingsManager, settings) { + for (const setting of settings) { + setting.value = settingsManager.get(setting.id); + } + return betterdiscord.UI.buildSettingsPanel({ + settings, + onChange: (_, id, value) => settingsManager.set(id, value) + }); +} + +// @lib/strings.ts +const LocaleStore = betterdiscord.Webpack.getStore("LocaleStore"); +class StringsManager { + locales; + defaultLocale; + strings; + constructor(locales, defaultLocale) { + this.locales = locales; + this.defaultLocale = defaultLocale; + this.strings = locales[defaultLocale]; + } + setLocale = () => { + this.strings = this.locales[LocaleStore.locale] || this.locales[this.defaultLocale]; + }; + subscribe() { + this.setLocale(); + LocaleStore.addReactChangeListener(this.setLocale); + } + unsubscribe() { + LocaleStore.removeReactChangeListener(this.setLocale); + } + get(key) { + return this.strings[key] || this.locales[this.defaultLocale][key]; + } +} + +// @lib/changelog.ts +function showChangelog(changes, meta) { + if (!changes || changes.length == 0) return; + const changelogVersion = betterdiscord.Data.load("changelogVersion"); + if (meta.version === changelogVersion) return; + betterdiscord.UI.showChangelogModal({ + title: meta.name, + subtitle: meta.version, + changes + }); + betterdiscord.Data.save("changelogVersion", meta.version); +} + +// @lib/utils/webpack.ts +function getClasses(...classes) { + return betterdiscord.Webpack.getModule((m) => betterdiscord.Webpack.Filters.byKeys(...classes)(m) && typeof m[classes[0]] == "string"); +} +function getSelectors(...classes) { + const module = getClasses(...classes); + if (!module) return void 0; + return classes.reduce((obj, className) => { + obj[className] = "." + module[className].replaceAll(" ", "."); + return obj; + }, {}); +} +function getIcon(searchString) { + const filter = (m) => betterdiscord.Webpack.Filters.byStrings(searchString, '"svg"')(m) && typeof m === "function"; + return betterdiscord.Webpack.getModule(filter, { + searchExports: true + }); +} +function expect(object, options) { + if (object) return object; + const fallbackMessage = !options.fatal && options.fallback ? " Using fallback value instead." : ""; + const errorMessage = `Module ${options.name} not found.${fallbackMessage}\n\nContact the plugin developer to inform them of this error.`; + betterdiscord.Logger.error(errorMessage); + options.onError?.(); + if (options.fatal) throw new Error(errorMessage); + return options.fallback; +} +function expectModule(options) { + return expect(betterdiscord.Webpack.getModule(options.filter, options), options); +} +function expectWithKey(options) { + const [module, key] = betterdiscord.Webpack.getWithKey(options.filter, options); + if (module) return [module, key]; + const fallback = expect(module, options); + if (fallback) { + const key2 = "__key"; + return [{ [key2]: fallback }, key2]; + } + return void 0; +} +function expectSelectors(name, classes) { + return expect(getSelectors(...classes), { + name + }); +} +function expectIcon(name, searchString) { + return expect(getIcon(searchString), { + name, + fallback: (_props) => null + }); +} +function byType(type) { + return (e) => typeof e === type; +} + +// manifest.json +const changelog = [ + { + title: "Fixed", + type: "fixed", + items: [ + "Fixed member list icons." + ] + } +]; + +// @discord/stores.ts +const UserStore = betterdiscord.Webpack.getStore("UserStore"); +const VoiceStateStore = betterdiscord.Webpack.getStore("VoiceStateStore"); +const GuildStore = betterdiscord.Webpack.getStore("GuildStore"); +const ChannelStore = betterdiscord.Webpack.getStore("ChannelStore"); +const PermissionStore = betterdiscord.Webpack.getStore("PermissionStore"); +const useStateFromStores = expectModule({ + filter: betterdiscord.Webpack.Filters.byStrings("useStateFromStores"), + name: "Flux", + fallback(stores, callback) { + return callback(); + }, + searchExports: true +}); + +// modules/discordmodules.tsx +function useUserVoiceStateFallback({ userId }) { + const voiceState = useStateFromStores( + [VoiceStateStore], + () => userId && VoiceStateStore.getDiscoverableVoiceStateForUser(userId) + ); + const channel = useStateFromStores([ChannelStore], () => { + if (voiceState?.channelId) return ChannelStore.getChannel(voiceState?.channelId); + }); + const visible = useStateFromStores( + [PermissionStore], + () => channel?.isPrivate() || PermissionStore.can(Permissions.VIEW_CHANNEL, channel) + ); + if (visible) { + return { + voiceState, + voiceChannel: channel + }; + } else return {}; +} +const MemberListItem = expectModule({ + filter: (m) => m.type?.toString?.().includes("MemberListItem"), + name: "MemberListItem", + searchExports: true +}); +const PrivateChannel = expectWithKey({ + filter: betterdiscord.Webpack.Filters.byStrings("PrivateChannel", "getTypingUsers"), + name: "PrivateChannel", + defaultExport: false +}); +const PeopleListItem = expectModule({ + filter: (m) => m?.prototype?.render && betterdiscord.Webpack.Filters.byStrings("this.peopleListItemRef")(m), + name: "PeopleListItem" +}); +const Permissions = expectModule({ + filter: betterdiscord.Webpack.Filters.byKeys("VIEW_CREATOR_MONETIZATION_ANALYTICS"), + searchExports: true, + name: "Permissions", + fallback: { + VIEW_CHANNEL: 1024n + } +}); +const memberSelectors = expectSelectors("Children Class", ["avatar", "children", "layout"]); +const useUserVoiceState = expectModule({ + filter: betterdiscord.Webpack.Filters.byStrings("getDiscoverableVoiceState", "getDiscoverableVoiceStateForUser"), + name: "useUserVoiceState", + fallback: useUserVoiceStateFallback +}); + +// locales.json +const el = { + SETTINGS_PROFILE: "Τομέας Προφίλ", + SETTINGS_PROFILE_NOTE: "Εμφανίζει τον τομέα προφίλ για την τρέχουσα δραστηριότητα φωνής στα αναδυόμενα χρήστη και στην πλευρικές μπάρες προφίλ των Άμεσων Μηνυμάτων.", + SETTINGS_ICONS: "Εικονίδια Λίστας Μελών", + SETTINGS_ICONS_NOTE: "Εμφανίζει εικονίδια στη λίστα μελών όταν κάποιος είναι σε κανάλι φωνής.", + SETTINGS_DM_ICONS: "Εικονίδια Άμεσων Μηνυμάτων", + SETTINGS_DM_ICONS_NOTE: "Εμφανίζει εικονίδια στη λίστα Άμεσων Μηνυμάτων όταν κάποιος είναι σε κανάλι φωνής.", + SETTINGS_PEOPLE_ICONS: "Εικονίδια Λίστας Φίλων", + SETTINGS_PEOPLE_ICONS_NOTE: "Εμφανίζει εικονίδια στη λίστα φίλων όταν κάποιος είναι σε κανάλι φωνής.", + SETTINGS_GUILD_ICONS: "Εικονίδια Συντεχνίας", + SETTINGS_GUILD_ICONS_NOTE: "Εμφανίζει εικονίδια στις συντεχνίες ακόμα και αν δεν συμμετέχετε.", + SETTINGS_COLOR: "Λίστα Μελών - Χρώμα Εικονιδίου Τρέχοντος Καναλιού", + SETTINGS_COLOR_NOTE: "Αλλάζει τα εικονίδια της Λίστας Μελών σε πράσινα όταν ο χρήστης είναι στο δικό σας τρέχον κανάλι φωνής.", + SETTINGS_STATUS: "Λίστα Μελών - Εμφάνιση Εικονιδίων Κατάστασης", + SETTINGS_STATUS_NOTE: "Αλλάζει τα εικονίδια της Λίστας Μελών όταν ο χρήστης είναι σε Σίγαση, Κώφωση ή έχε ενεργοποιημένο Βίντεο.", + SETTINGS_IGNORE: "Αγνόηση", + SETTINGS_IGNORE_NOTE: "Προσθέτει μια επιλογή στο Κανάλι Φωνής και στα μενού περιεχομένου Συντεχνίας για αγνόηση αυτού του καναλιού/συντεχνίας στα Εικονίδια Λϊστας Μελών και στα Αναδυόμενα Χρήστη.", + CONTEXT_IGNORE: "Αγνόηση στη Δραστηριότητα Φωνής", + VOICE_CALL: "Φωνητική Κλήση", + PRIVATE_CALL: "Ιδιωτική Κλήση", + GROUP_CALL: "Ομαδική Κλήση", + LIVE: "Ζωντανά", + HEADER: "Σε ένα Κανάλι Φωνής", + HEADER_VOICE: "Σε μια Κλήση Φωνής", + HEADER_PRIVATE: "Σε μια Ιδιωτική Κλήση", + HEADER_GROUP: "Σε μια Ομαδική Κλήση", + HEADER_STAGE: "Σε ένα Κανάλι Σταδίου", + VIEW: "Προβολή Καναλιού", + VIEW_CALL: "Προβολή Κλήσης", + JOIN: "Συμμετοχή σε Κανάλι", + JOIN_CALL: "Συμμετοχή σε Κλήση", + JOIN_DISABLED: "Ήδη σε Κανάλι", + JOIN_DISABLED_CALL: "Ήδη σε Κλήση", + JOIN_VIDEO: "Συμμετοχή με Βίντεο" +}; +const ru = { + SETTINGS_PROFILE: "Раздел в Профиле", + SETTINGS_PROFILE_NOTE: "Показывает раздел для текущей голосовой активности в профиле, всплывающих окнах и боковых панелях в ЛС.", + SETTINGS_ICONS: "Иконки в Списке Участников", + SETTINGS_ICONS_NOTE: "Показывает иконки в списке участников когда пользователь в голосовом канале.", + SETTINGS_DM_ICONS: "Иконки в ЛС", + SETTINGS_DM_ICONS_NOTE: "Показывает иконки в списке участников ЛС когда пользователь в голосовом канале.", + SETTINGS_PEOPLE_ICONS: "Иконки в Списке Друзей", + SETTINGS_PEOPLE_ICONS_NOTE: "Показывает иконки в списке друзей когда пользователь в голосовом канале.", + SETTINGS_GUILD_ICONS: "Иконки Сервера", + SETTINGS_GUILD_ICONS_NOTE: "Показывает голосовые иконки на серверах даже когда вы не участвуете.", + SETTINGS_COLOR: "Цвет Иконки Голосовой Активности", + SETTINGS_COLOR_NOTE: "Делает иконки в Списке Участников зелёными когда пользователь в голосовом канале вместе с вами.", + SETTINGS_STATUS: "Показывать Иконки Статуса", + SETTINGS_STATUS_NOTE: "Меняет иконки в Списке Участников когда пользователь Выключил Микрофон/Звук или Начал трансляцию.", + SETTINGS_IGNORE: "Ингор", + SETTINGS_IGNORE_NOTE: "Добавляет возможность в контекстных меню Голосовых Каналов и Серверов игнорировать этот канал/сервер в Списке Участников и Профилях Пользователей.", + CONTEXT_IGNORE: "Игнор (Голосовая Активность)", + VOICE_CALL: "Голосовой звонок", + PRIVATE_CALL: "Приватный звонок", + GROUP_CALL: "Групповой звонок", + LIVE: "В ЭФИРЕ", + HEADER: "В Голосовом Канале", + HEADER_VOICE: "В Голосовом Звонке", + HEADER_PRIVATE: "В Приватном Звонке", + HEADER_GROUP: "В Групповом Звонке", + HEADER_STAGE: "В Канале Трибуны", + VIEW: "Просмотреть Канал", + VIEW_CALL: "Просмотреть Звонок", + JOIN: "Присоединиться к Каналу", + JOIN_CALL: "Присоединиться к Звонку", + JOIN_DISABLED: "Уже в Канале", + JOIN_DISABLED_CALL: "Уже в Звонке", + JOIN_VIDEO: "Присоединиться с Видео" +}; +const de = { + SETTINGS_PROFILE: "Profilbereich", + SETTINGS_PROFILE_NOTE: "Zeigt den Profilbereich für die aktuelle Sprachaktivität in Benutzer-Popouts und DM-Profilseitenleisten an.", + SETTINGS_ICONS: "Symbole in der Mitgliederliste", + SETTINGS_ICONS_NOTE: "Zeigt Symbole in der Mitgliederliste an, wenn sich jemand in einem Sprachkanal befindet.", + SETTINGS_DM_ICONS: "DM-Symbole", + SETTINGS_DM_ICONS_NOTE: "Zeigt Symbole in der DM-Liste an, wenn sich jemand in einem Sprachkanal befindet.", + SETTINGS_PEOPLE_ICONS: "Symbole in der Freundesliste", + SETTINGS_PEOPLE_ICONS_NOTE: "Zeigt Symbole in der Freundesliste an, wenn sich jemand in einem Sprachkanal befindet.", + SETTINGS_GUILD_ICONS: "Gildensymbole", + SETTINGS_GUILD_ICONS_NOTE: "Zeigt Symbole für Gilden an, auch wenn du nicht teilnimmst.", + SETTINGS_COLOR: "Mitgliederliste - Farbe des aktuellen Kanalsymbols", + SETTINGS_COLOR_NOTE: "Die Symbole in der Mitgliederliste werden grün, wenn sich der Benutzer in Ihrem aktuellen Sprachkanal befindet.", + SETTINGS_STATUS: "Mitgliederliste - Statussymbole anzeigen", + SETTINGS_STATUS_NOTE: "Ändert die Symbole in der Mitgliederliste, wenn ein Benutzer stummgeschaltet oder mit aktiviertem Video ist.", + SETTINGS_IGNORE: "Ignorieren", + SETTINGS_IGNORE_NOTE: "Fügt eine Option in die Kontextmenüs von Sprachkanälen und Gilden hinzu, um diesen Kanal/diese Gilde in der Mitgliederliste und Benutzer-Popouts zu ignorieren.", + CONTEXT_IGNORE: "Bei Sprachaktivität ignorieren", + VOICE_CALL: "Sprachanruf", + PRIVATE_CALL: "Privatanruf", + GROUP_CALL: "Gruppenanruf", + LIVE: "Live", + HEADER: "In einem Sprachkanal", + HEADER_VOICE: "In einem Sprachanruf", + HEADER_PRIVATE: "In einem privaten Anruf", + HEADER_GROUP: "In einem Gruppenruf", + HEADER_STAGE: "In einem Stage Kanal", + VIEW: "Kanal anzeigen", + VIEW_CALL: "Anruf anzeigen", + JOIN: "Kanal beitreten", + JOIN_CALL: "Anruf beitreten", + JOIN_DISABLED: "Bereits im Kanal", + JOIN_DISABLED_CALL: "Bereits im Aufruf", + JOIN_VIDEO: "Beitreten mit Video" +}; +const fr = { + SETTINGS_PROFILE: "Section de profil", + SETTINGS_PROFILE_NOTE: "Affiche une section de profile pour l'activité vocale actuelle dans les popouts utilisateur et les profiles latéraux de MP.", + SETTINGS_ICONS: "Icônes de la liste de membre", + SETTINGS_ICONS_NOTE: "Affiche des icônes dans la liste de membre quand quelqu'un est dans un salon vocal.", + SETTINGS_DM_ICONS: "Icônes de MP", + SETTINGS_DM_ICONS_NOTE: "Affiche des icônes dans la liste de MP quand quelqu'un est dans un salon vocal.", + SETTINGS_PEOPLE_ICONS: "Icônes de la liste d'amis", + SETTINGS_PEOPLE_ICONS_NOTE: "Affiche des icônes dans la liste d'amis quand quelqu'un est dans un salon vocal.", + SETTINGS_GUILD_ICONS: "Icônes de serveur", + SETTINGS_GUILD_ICONS_NOTE: "Affiche des icônes de vocal sur les serveurs même quand vous ne participez pas.", + SETTINGS_COLOR: "Liste de membre - Couleur d'icône du salon actuel", + SETTINGS_COLOR_NOTE: "Rends les icônes de la liste de membre vertes quand un utilisateur est dans le salon vocal actuel.", + SETTINGS_STATUS: "Liste de membre - Afficher les icônes de status", + SETTINGS_STATUS_NOTE: "Change les icônes de la liste de membre quand un utilisateur est muet, mis en sourdine, ou a la vidéo activée.", + SETTINGS_IGNORE: "Ignorer", + SETTINGS_IGNORE_NOTE: "Ajoute une option dans les menus contextuels des salons vocaux et serveurs pour ignorer le dit salon/serveur dans les icônes de liste de membre et popouts utilisateur.", + CONTEXT_IGNORE: "Ignorer dans Activité Vocale", + VOICE_CALL: "Appel vocal", + PRIVATE_CALL: "Appel privé", + GROUP_CALL: "Appel de groupe", + LIVE: "Live", + HEADER: "Dans un salon vocal", + HEADER_VOICE: "Dans un appel vocal", + HEADER_PRIVATE: "Dans un appel privé", + HEADER_GROUP: "Dans un appel de groupe", + HEADER_STAGE: "Dans un salon de conférence", + VIEW: "Voir le salon", + VIEW_CALL: "Voir l'appel", + JOIN: "Rejoindre le salon", + JOIN_CALL: "Rejoindre l'appel", + JOIN_DISABLED: "Déjà dans le salon", + JOIN_DISABLED_CALL: "Déjà en appel", + JOIN_VIDEO: "Rejoidre avec vidéo", + MEMBER: "Membre", + MEMBERS: "Membres" +}; +const locales = { + "en-US": { + SETTINGS_PROFILE: "Profile Section", + SETTINGS_PROFILE_NOTE: "Shows profile section for current voice activity in user popouts and DM profile sidebars.", + SETTINGS_ICONS: "Member List Icons", + SETTINGS_ICONS_NOTE: "Shows icons on the member list when someone is in a voice channel.", + SETTINGS_DM_ICONS: "DM Icons", + SETTINGS_DM_ICONS_NOTE: "Shows icons on the DM list when someone is in a voice channel.", + SETTINGS_PEOPLE_ICONS: "Friends List Icons", + SETTINGS_PEOPLE_ICONS_NOTE: "Shows icons on the friends list when someone is in a voice channel.", + SETTINGS_GUILD_ICONS: "Server Icons", + SETTINGS_GUILD_ICONS_NOTE: "Shows voice icons on servers even when you're not participating.", + SETTINGS_COLOR: "Member List - Current Channel Icon Color", + SETTINGS_COLOR_NOTE: "Makes the Member List icons green when the user is in your current voice channel.", + SETTINGS_STATUS: "Member List - Show Status Icons", + SETTINGS_STATUS_NOTE: "Changes the Member List icons when a user is Muted, Deafened, or has Video enabled.", + SETTINGS_CURRENT_USER: "Current User Icon", + SETTINGS_CURRENT_USER_NOTE: "Toggles displaying a voice channel icon for the current user.", + SETTINGS_IGNORE: "Ignore", + SETTINGS_IGNORE_NOTE: "Adds an option on Voice Channel and Server context menus to ignore that channel/server in Member List Icons and User Popouts.", + CONTEXT_IGNORE: "Ignore in Voice Activity", + VOICE_CALL: "Voice Call", + PRIVATE_CALL: "Private Call", + GROUP_CALL: "Group Call", + LIVE: "Live", + HEADER: "In a Voice Channel", + HEADER_VOICE: "In a Voice Call", + HEADER_PRIVATE: "In a Private Call", + HEADER_GROUP: "In a Group Call", + HEADER_STAGE: "In a Stage Channel", + VIEW: "View Channel", + VIEW_CALL: "View Call", + JOIN: "Join Channel", + JOIN_CALL: "Join Call", + JOIN_DISABLED: "Already in Channel", + JOIN_DISABLED_CALL: "Already in Call", + JOIN_VIDEO: "Join With Video", + MEMBER: "Member", + MEMBERS: "Members" +}, + el: el, + ru: ru, + de: de, + fr: fr +}; + +// modules/utils.ts +const Settings = new SettingsManager({ + showMemberListIcons: true, + showDMListIcons: true, + showPeopleListIcons: true, + currentChannelColor: true, + showStatusIcons: true, + currentUserIcon: true, + ignoreEnabled: false, + ignoredChannels: [], + ignoredGuilds: [] +}); +const Strings = new StringsManager(locales, "en-US"); +function groupDMName(members) { + if (members.length === 1) { + return UserStore.getUser(members[0]).globalName; + } else if (members.length > 1) { + let name = ""; + for (let i = 0; i < members.length; i++) { + if (i === members.length - 1) name += UserStore.getUser(members[i]).globalName; + else name += UserStore.getUser(members[i]).globalName + ", "; + } + return name; + } + return "Unnamed"; +} + +// styles/voiceicon.module.css +const css = ` +.VoiceActivity-voiceicon-icon { + height: 20px; + width: 20px; + min-width: 20px; + border-radius: 50%; + background-color: var(--background-accent); + cursor: pointer; + svg { + padding: 3px; + color: #fff; + } +} +.VoiceActivity-voiceicon-iconCurrentCall { + background-color: var(--status-positive); +} +.VoiceActivity-voiceicon-iconLive { + height: 16px; + border-radius: 16px; + background-color: var(--red-400); + color: #fff; + font-size: 12px; + line-height: 16px; + font-weight: 600; + font-family: var(--font-display); + text-transform: uppercase; + & > div { + padding: 0 6px; + } +} +.VoiceActivity-voiceicon-tooltip { + .VoiceActivity-voiceicon-header { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .VoiceActivity-voiceicon-subtext { + display: flex; + flex-direction: row; + margin-top: 3px; + & > div { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + .VoiceActivity-voiceicon-tooltipIcon { + min-width: 16px; + margin-right: 3px; + color: var(--interactive-normal); + } +} +.VoiceActivity-voiceicon-iconContainer { + margin-left: auto; +} +.VoiceActivity-voiceicon-peopleListIcon { + margin-right: 16px; +}`; +_loadStyle("voiceicon.module.css", css); +const modules_1af761ba = { + "icon": "VoiceActivity-voiceicon-icon", + "iconCurrentCall": "VoiceActivity-voiceicon-iconCurrentCall", + "iconLive": "VoiceActivity-voiceicon-iconLive", + "tooltip": "VoiceActivity-voiceicon-tooltip", + "header": "VoiceActivity-voiceicon-header", + "subtext": "VoiceActivity-voiceicon-subtext", + "tooltipIcon": "VoiceActivity-voiceicon-tooltipIcon", + "iconContainer": "VoiceActivity-voiceicon-iconContainer", + "peopleListIcon": "VoiceActivity-voiceicon-peopleListIcon" +}; +const iconStyles = modules_1af761ba; + +// @discord/icons.tsx +const Speaker = expectIcon("Speaker", "M12 3a1 1 0 0 0-1-1h-.06a1 1 0 0 0-.74.32L5.92 7H3a1 1"); +const Muted = expectIcon( + "Muted", + "m2.7 22.7 20-20a1 1 0 0 0-1.4-1.4l-20 20a1 1 0 1 0 1.4 1.4ZM10.8 17.32c-.21.21-.1.58.2.62V20H9a1" +); +const ServerMuted = expectIcon( + "ServerMuted", + "M21.76.83a5.02 5.02 0 0 1 .78 7.7 5 5 0 0 1-7.07 0 5.02 5.02 0 0 1 0-7.07" +); +const Deafened = expectIcon( + "Deafened", + "M22.7 2.7a1 1 0 0 0-1.4-1.4l-20 20a1 1 0 1 0 1.4 1.4l20-20ZM17.06" +); +const ServerDeafened = expectIcon( + "ServerDeafened", + "M12.38 1c.38.02.58.45.4.78-.15.3-.3.62-.4.95A.4.4 0 0 1 12 3a9 9 0 0 0-8.95 10h1.87a5" +); +const Video = expectIcon("Video", "M4 4a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h11a3 3"); +const ChannelIcon = expectModule({ + filter: betterdiscord.Webpack.Filters.combine( + betterdiscord.Webpack.Filters.byStrings("isGuildStageVoice", "isGroupDM", "isPrivate"), + (m) => !m.toString?.().includes("intl") + ), + name: "ChannelIcon", + fallback: (_props) => null +}); + +// @discord/modules.ts +const transitionTo = expectModule({ + filter: betterdiscord.Webpack.Filters.combine( + betterdiscord.Webpack.Filters.byStrings("transitionTo -"), + byType("function") + ), + searchExports: true, + name: "transitionTo" +}); + +// components/VoiceIcon.tsx +function VoiceIcon(props) { + const settingsState = Settings.useSettingsState( + "showMemberListIcons", + "showDMListIcons", + "showPeopleListIcons", + "ignoreEnabled", + "ignoredChannels", + "ignoredGuilds", + "currentChannelColor", + "showStatusIcons", + "currentUserIcon" + ); + const currentUser = UserStore.getCurrentUser(); + const { voiceState, voiceChannel: channel } = useUserVoiceState({ userId: props.userId }); + const { voiceState: currentUserVoiceState } = useUserVoiceState({ userId: currentUser?.id }); + if (props.context === "memberlist" && !settingsState.showMemberListIcons) return null; + if (props.context === "dmlist" && !settingsState.showDMListIcons) return null; + if (props.context === "peoplelist" && !settingsState.showPeopleListIcons) return null; + if (props.userId === currentUser?.id && !settingsState.currentUserIcon) return null; + if (!voiceState) return null; + const guild = GuildStore.getGuild(channel.guild_id); + const ignored = settingsState.ignoredChannels.includes(channel.id) || settingsState.ignoredGuilds.includes(guild?.id); + if (settingsState.ignoreEnabled && ignored) return null; + let text; + let subtext; + let channelPath; + let className = iconStyles.icon; + if (settingsState.currentChannelColor && channel.id === currentUserVoiceState?.channelId) + className = `${iconStyles.icon} ${iconStyles.iconCurrentCall}`; + if (voiceState.selfStream) className = iconStyles.iconLive; + if (guild) { + text = guild.name; + subtext = channel.name; + channelPath = `/channels/${guild.id}/${channel.id}`; + } else { + text = channel.name; + subtext = Strings.get("VOICE_CALL"); + channelPath = `/channels/@me/${channel.id}`; + } + switch (channel.type) { + case 1: + text = UserStore.getUser(channel.recipients[0]).globalName; + subtext = Strings.get("PRIVATE_CALL"); + break; + case 3: + text = channel.name || groupDMName(channel.recipients); + subtext = Strings.get("GROUP_CALL"); + break; + } + let Icon = Speaker; + if (settingsState.showStatusIcons) { + if (voiceState.selfVideo) Icon = Video; + else if (voiceState.deaf) Icon = ServerDeafened; + else if (voiceState.selfDeaf) Icon = Deafened; + else if (voiceState.mute) Icon = ServerMuted; + else if (voiceState.selfMute) Icon = Muted; + } + return BdApi.React.createElement( + "div", + { + className, + onClick: (e) => { + e.stopPropagation(); + e.preventDefault(); + if (channelPath) transitionTo?.(channelPath); + } + }, + BdApi.React.createElement( + betterdiscord.Components.Tooltip, + { + text: BdApi.React.createElement("div", { className: iconStyles.tooltip }, BdApi.React.createElement("div", { className: iconStyles.header, style: { fontWeight: "600" } }, text), BdApi.React.createElement("div", { className: iconStyles.subtext }, BdApi.React.createElement( + ChannelIcon, + { + className: iconStyles.tooltipIcon, + size: "16", + width: "16", + height: "16", + color: "currentColor", + channel + } + ), BdApi.React.createElement("div", { style: { fontWeight: "400" } }, subtext))) + }, + (props2) => BdApi.React.createElement("div", { ...props2 }, !voiceState.selfStream ? BdApi.React.createElement(Icon, { size: "14", width: "14", height: "14", color: "currentColor" }) : Strings.get("LIVE")) + ) + ); +} + +// index.tsx +class VoiceActivity { + meta; + contextMenuUnpatches = new Set(); + constructor(meta) { + this.meta = meta; + } + start() { + showChangelog(changelog, this.meta); + betterdiscord.DOM.addStyle( + styles() + `${memberSelectors?.children}:empty { margin-left: 0; } ${memberSelectors?.children} { display: flex; gap: 8px; } ${memberSelectors?.layout} { width: 100%; }` + ); + Strings.subscribe(); + this.patchPeopleListItem(); + this.patchMemberListItem(); + this.patchPrivateChannel(); + this.patchChannelContextMenu(); + this.patchGuildContextMenu(); + betterdiscord.Patcher.instead(VoiceStateStore, "getDiscoverableVoiceState", (_, [guildId, userId]) => { + return VoiceStateStore.getDiscoverableVoiceStateForUser(userId); + }); + } + patchMemberListItem() { + if (!MemberListItem) return; + betterdiscord.Patcher.after(MemberListItem, "type", (_, [props], ret) => { + if (!props.user) return ret; + const children = ret.props.children; + ret.props.children = (childrenProps) => { + const childrenRet = children(childrenProps); + const target = betterdiscord.Utils.findInTree(childrenRet, (x) => x.props?.avatar && x.props?.decorators, { + walkable: ["props", "children"] + }); + const icon = BdApi.React.createElement(VoiceIcon, { userId: props.user.id, context: "memberlist" }); + Array.isArray(target.props.children) ? target.props.children.unshift(icon) : target.props.children = [icon]; + return childrenRet; + }; + }); + } + patchPrivateChannel() { + if (!PrivateChannel) return; + const patchType = (props, ret) => { + if (props.channel.type !== 1) return; + const target = betterdiscord.Utils.findInTree(ret, (e) => typeof e?.props?.children !== "function", { + walkable: ["children", "props"] + })?.props?.children ?? ret; + if (!target) return; + const children = target.props.children; + target.props.children = (childrenProps) => { + const childrenRet = children(childrenProps); + const privateChannel = betterdiscord.Utils.findInTree(childrenRet, (e) => e?.children?.props?.avatar, { + walkable: ["children", "props"] + }); + privateChannel.children = [ + privateChannel.children, + BdApi.React.createElement("div", { className: iconStyles.iconContainer }, BdApi.React.createElement(VoiceIcon, { userId: props.user.id, context: "dmlist" })) + ]; + return childrenRet; + }; + }; + let patchedType; + betterdiscord.Patcher.after(...PrivateChannel, (_, __, containerRet) => { + let target = containerRet.children || containerRet; + if (patchedType) { + target.type = patchedType; + return containerRet; + } + const original = target.type; + patchedType = (props) => { + const ret = original(props); + patchType(props, ret); + return ret; + }; + target.type = patchedType; + }); + } + patchPeopleListItem() { + if (!PeopleListItem) return; + betterdiscord.Patcher.after(PeopleListItem.prototype, "render", (that, _, ret) => { + if (!that.props.user) return; + const children = ret.props.children; + ret.props.children = (childrenProps) => { + const childrenRet = children(childrenProps); + betterdiscord.Utils.findInTree(childrenRet, (i) => Array.isArray(i), { walkable: ["props", "children"] }).splice( + 1, + 0, + BdApi.React.createElement("div", { className: `${iconStyles.iconContainer} ${iconStyles.peopleListIcon}` }, BdApi.React.createElement(VoiceIcon, { userId: that.props.user.id, context: "peoplelist" })) + ); + return childrenRet; + }; + }); + } + patchChannelContextMenu() { + const unpatch = betterdiscord.ContextMenu.patch("channel-context", (ret, props) => { + if (!Settings.get("ignoreEnabled")) return ret; + if (props.channel.type !== 2 && props.channel.type !== 13) return ret; + const { ignoredChannels } = Settings.useSettingsState("ignoredChannels"); + const ignored = ignoredChannels.includes(props.channel.id); + const menuItem = betterdiscord.ContextMenu.buildItem({ + type: "toggle", + label: Strings.get("CONTEXT_IGNORE"), + id: "voiceactivity-ignore", + checked: ignored, + action: () => { + if (ignored) { + const newIgnoredChannels = ignoredChannels.filter((id) => id !== props.channel.id); + Settings.set("ignoredChannels", newIgnoredChannels); + } else { + const newIgnoredChannels = [...ignoredChannels, props.channel.id]; + Settings.set("ignoredChannels", newIgnoredChannels); + } + } + }); + ret.props.children[3].props.children.splice(4, 0, menuItem); + }); + this.contextMenuUnpatches.add(unpatch); + } + patchGuildContextMenu() { + const unpatch = betterdiscord.ContextMenu.patch("guild-context", (ret, props) => { + if (!props.guild) return ret; + if (!Settings.get("ignoreEnabled")) return ret; + const { ignoredGuilds } = Settings.useSettingsState("ignoredGuilds"); + const ignored = ignoredGuilds.includes(props.guild.id); + const menuItem = betterdiscord.ContextMenu.buildItem({ + type: "toggle", + label: Strings.get("CONTEXT_IGNORE"), + id: "voiceactivity-ignore", + checked: ignored, + action: () => { + if (ignored) { + const newIgnoredGuilds = ignoredGuilds.filter((id) => id !== props.guild.id); + Settings.set("ignoredGuilds", newIgnoredGuilds); + } else { + const newIgnoredGuilds = [...ignoredGuilds, props.guild.id]; + Settings.set("ignoredGuilds", newIgnoredGuilds); + } + } + }); + ret.props.children[2].props.children.push(menuItem); + }); + this.contextMenuUnpatches.add(unpatch); + } + stop() { + betterdiscord.DOM.removeStyle(); + betterdiscord.Patcher.unpatchAll(); + this.contextMenuUnpatches.forEach((unpatch) => unpatch()); + this.contextMenuUnpatches.clear(); + Strings.unsubscribe(); + } + getSettingsPanel() { + return buildSettingsPanel(Settings, [ + { + id: "showMemberListIcons", + type: "switch", + name: Strings.get("SETTINGS_ICONS"), + note: Strings.get("SETTINGS_ICONS_NOTE") + }, + { + id: "showDMListIcons", + type: "switch", + name: Strings.get("SETTINGS_DM_ICONS"), + note: Strings.get("SETTINGS_DM_ICONS_NOTE") + }, + { + id: "showPeopleListIcons", + type: "switch", + name: Strings.get("SETTINGS_PEOPLE_ICONS"), + note: Strings.get("SETTINGS_PEOPLE_ICONS_NOTE") + }, + { + id: "currentChannelColor", + type: "switch", + name: Strings.get("SETTINGS_COLOR"), + note: Strings.get("SETTINGS_COLOR_NOTE") + }, + { + id: "showStatusIcons", + type: "switch", + name: Strings.get("SETTINGS_STATUS"), + note: Strings.get("SETTINGS_STATUS_NOTE") + }, + { + id: "currentUserIcon", + type: "switch", + name: Strings.get("SETTINGS_CURRENT_USER"), + note: Strings.get("SETTINGS_CURRENT_USER_NOTE") + }, + { + id: "ignoreEnabled", + type: "switch", + name: Strings.get("SETTINGS_IGNORE"), + note: Strings.get("SETTINGS_IGNORE_NOTE") + } + ]); + } +} + +module.exports = VoiceActivity; + +/*@end@*/ \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/WhoReacted.config.json b/dotfiles/.config/BetterDiscord/plugins/WhoReacted.config.json new file mode 100755 index 0000000..42a170c --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/WhoReacted.config.json @@ -0,0 +1,15 @@ +{ + "currentVersionInfo": { + "version": "1.2.5", + "hasShownChangelog": true + }, + "settings": { + "maxUsersShown": 6, + "avatarSize": 16, + "reactionThreshold": 10, + "userThreshold": 100, + "useHighestUserCount": true, + "showSelf": false, + "showBots": false + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/WiderUserArea.plugin.js b/dotfiles/.config/BetterDiscord/plugins/WiderUserArea.plugin.js new file mode 100644 index 0000000..a8a8fcd --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/WiderUserArea.plugin.js @@ -0,0 +1,288 @@ +/** + * @name WiderUserArea + * @version 0.2.5 + * @description A BetterDiscord plugin that expands your user area into the server list, compatible with most themes + * @author TheLazySquid + * @authorId 619261917352951815 + * @website https://github.com/TheLazySquid/DiscordWiderUserArea + * @source https://github.com/TheLazySquid/DiscordWiderUserArea/blob/main/build/WiderUserArea.plugin.js + */ +module.exports = class { + constructor() { +'use strict'; + +var styles = ":root {\r\n --user-area-bottom: 0;\r\n --user-area-left: 0;\r\n}\r\n\r\nnav[class*=\"guilds_\"] {\r\n height: var(--sidebar-height);\r\n}\r\n\r\n[class*=\"sidebar_\"] {\r\n height: calc(var(--sidebar-height) - var(--notices-height));\r\n}\r\n\r\n/* user area */\r\nsection[class*=\"panels_\"] {\r\n position: fixed;\r\n overflow: hidden;\r\n bottom: var(--user-area-bottom);\r\n left: var(--user-area-left);\r\n width: var(--user-area-width);\r\n}\r\n\r\nsection[class*=\"panels_\"] [class*=\"avatarWrapper_\"] {\r\n flex-grow: 1;\r\n}\r\n\r\n/* prevent the user info popout from clipping */\r\n[class*=\"accountProfilePopoutWrapper\"] {\r\n left: 0;\r\n}"; + +const userAreaSelector = 'section[class*="panels_"]'; +const serverListSelector = 'nav[class*="guilds_"]'; +const containerSelector = `div:has(> ${serverListSelector})`; +const layerSelector = `[class*="baseLayer"]`; +const channelsSelector = '[class*="sidebar_"] > nav'; +const scaleRegex = /scale\((.*)\)/; +const UAButtonsSelector = 'div[class*="avatarWrapper_"] + div'; +const firstServerListSelector = `${serverListSelector}:first-child`; +const secondServerListSelector = `${serverListSelector}:nth-child(2)`; + +function scaleDOMRect(rect, scale, scaleCenterX, scaleCenterY) { + // Calculate the distance of the rect from the scale center + const distX = rect.x - scaleCenterX; + const distY = rect.y - scaleCenterY; + // Scale the distances + const newDistX = distX * scale; + const newDistY = distY * scale; + // Calculate the new position of the rect + const newX = scaleCenterX + newDistX; + const newY = scaleCenterY + newDistY; + // Scale the dimensions of the rect + const newWidth = rect.width * scale; + const newHeight = rect.height * scale; + // Create a new DOMRect with the new dimensions and position + const newRect = new DOMRect(newX, newY, newWidth, newHeight); + return newRect; +} + +const createCallbackHandler = (callbackName) => { + const fullName = callbackName + "Callbacks"; + this[fullName] = []; + this[callbackName] = () => { + for (let i = 0; i < this[fullName].length; i++) { + this[fullName][i].callback(); + } + }; + return (callback, once, id) => { + let object = { callback }; + const delCallback = () => { + this[fullName].splice(this[fullName].indexOf(object), 1); + }; + // if once is true delete it after use + if (once === true) { + object.callback = () => { + callback(); + delCallback(); + }; + } + if (id) { + object.id = id; + for (let i = 0; i < this[fullName].length; i++) { + if (this[fullName][i].id === id) { + this[fullName][i] = object; + return delCallback; + } + } + } + this[fullName].push(object); + return delCallback; + }; +}; +/** + * Takes a callback and fires it when the plugin is started + * @param callback - The callback to be fired + * @param once - If true, the callback will be deleted after use + * @param id - The id of the callback - if it already exists, it will be replaced + * @returns A function to delete the callback + */ +const onStart = createCallbackHandler("start"); +/** + * Takes a callback and fires it when the plugin is stopped + * @param callback - The callback to be fired + * @param once - If true, the callback will be deleted after use + * @param id - The id of the callback - if it already exists, it will be replaced + * @returns A function to delete the callback + */ +const onStop = createCallbackHandler("stop"); +/** + * Takes a callback and fires it when the user navigates + * @param callback - The callback to be fired + * @param once - If true, the callback will be deleted after use + * @param id - The id of the callback - if it already exists, it will be replaced + * @returns A function to delete the callback + */ +const onSwitch = createCallbackHandler("onSwitch"); + +/** + * Watches for an element with a given selector to be added to the DOM + * @param selector The CSS selector to watch + * @param callback The callback to run whenever the matching element is added to the DOM + * @returns A function to stop watching + */ +function watchElement(selector, callback) { + let observer = new MutationObserver((mutations) => { + for (let mutation of mutations) { + if (mutation.addedNodes.length) { + for (let node of mutation.addedNodes) { + if (!(node instanceof HTMLElement)) + continue; + if (node.matches && node.matches(selector)) { + callback(node); + } + if (node.querySelectorAll) { + for (let element of node.querySelectorAll(selector)) { + callback(element); + } + } + } + } + } + }); + let startDispose = onStart(() => { + observer.observe(document.body, { childList: true, subtree: true }); + for (let element of document.querySelectorAll(selector)) { + callback(element); + } + }); + let stopDispose = onStop(() => { + observer.disconnect(); + }); + return () => { + observer.disconnect(); + startDispose(); + stopDispose(); + }; +} + +// @ts-ignore +let baseChannelHeight = 0; +let baseChannelWidth = 0; +let varsSet = new Set(); +const recalcDebounce = BdApi.Utils.debounce(() => { + const userArea = document.querySelector(userAreaSelector); + if (userArea) + userAreaFound(userArea); +}, 150); +watchElement(userAreaSelector, userAreaFound); +let userAreaObserver = new ResizeObserver(entries => { + for (let entry of entries) { + updateVar('--sidebar-height', `${baseChannelHeight - entry.contentRect.height}px`); + } +}); +let channelsRect; +let secondChannelsRect; +let channelsObserver = new ResizeObserver(entries => { + for (let entry of entries) { + channelsRect = entry.contentRect; + // hide everything but the profile picture if the channel list is hidden + let btns = document.querySelector(UAButtonsSelector); + if (entry.contentRect.width === 0) { + if (btns) + btns.style.display = 'none'; + } + else { + if (btns) + btns.style.display = ''; + } + let newWidth = entry.contentRect.right - baseChannelWidth; + if (secondChannelsRect) + newWidth += secondChannelsRect.width; + updateVar('--user-area-width', `${newWidth}px`); + } +}); +watchElement(channelsSelector, (element) => { + channelsObserver.disconnect(); + channelsObserver.observe(element); +}); +let secondServerListObserver = new ResizeObserver((entries) => { + if (!channelsRect) + return; + for (let entry of entries) { + secondChannelsRect = entry.contentRect; + let width = entry.contentRect.width; + updateVar('--user-area-width', `${channelsRect.right - baseChannelWidth + width}px`); + } +}); +watchElement(secondServerListSelector, (element) => { + secondServerListObserver.disconnect(); + secondServerListObserver.observe(element); +}); +let themesObserver = new MutationObserver(async () => { + // wait a bit for it to apply + await new Promise(resolve => setTimeout(resolve, 1000)); + console.log('theme changed'); + // recalculate the user area + const userArea = document.querySelector(userAreaSelector); + if (userArea) + userAreaFound(userArea); +}); +let noticesHeight = 0; +let noticesObserver = new ResizeObserver((entries) => { + for (let entry of entries) { + noticesHeight = entry.contentRect.height; + updateVar('--notices-height', `${entry.contentRect.height}px`); + } +}); +watchElement("#bd-notices", (el) => { + noticesObserver.disconnect(); + noticesObserver.observe(el); +}); +function updateVar(name, value) { + BdApi.DOM.removeStyle(`wua-${name}`); + BdApi.DOM.addStyle(`wua-${name}`, `:root { ${name}: ${value} !important; }`); + varsSet.add(name); +} +onSwitch(() => { + // hacky fix for betteranimations sometimes giving a parent the "perspective" style breaking the fixed positioning + setTimeout(() => { + const userArea = document.querySelector(userAreaSelector); + let parent = userArea?.parentElement; + while (parent) { + if (parent.style.perspective) { + parent.style.perspective = ''; + break; + } + parent = parent.parentElement; + } + userAreaFound(userArea); + }, 5100); +}); +function userAreaFound(element) { + // remove the old style if it exists + BdApi.DOM.removeStyle('wua-styles'); + const layer = document.querySelector(layerSelector); + const container = document.querySelector(containerSelector); + const serverList = document.querySelector(firstServerListSelector); + const channels = document.querySelector(channelsSelector); + const secondServerList = document.querySelector(secondServerListSelector); + const layerScale = 1 / parseFloat(scaleRegex.exec(layer.style.transform)?.[1] ?? '1'); + const layerRect = layer.getBoundingClientRect(); + const centerX = layerRect.left + layerRect.width / 2; + const centerY = layerRect.top + layerRect.height / 2; + // this scaling is neccesary for compatibility with betterAnimations, + // which sometimes scales the layer when in a settings menu, which is where theme switches would be. + const rect = scaleDOMRect(element.getBoundingClientRect(), layerScale, centerX, centerY); + const containerRect = scaleDOMRect(container.getBoundingClientRect(), layerScale, centerX, centerY); + const serverListRect = scaleDOMRect(serverList.getBoundingClientRect(), layerScale, centerX, centerY); + const channelsRect = scaleDOMRect(channels.getBoundingClientRect(), layerScale, centerX, centerY); + // figure out how far from the bottom of the screen the user area is + const bottom = containerRect.bottom - rect.bottom; + baseChannelHeight = channelsRect.height + rect.height; + baseChannelWidth = serverListRect.left - channelsRect.left; + if (secondServerList) { + const secondServerListRect = scaleDOMRect(secondServerList.getBoundingClientRect(), layerScale, centerX, centerY); + baseChannelWidth += secondServerListRect.width; + } + // add the new style + BdApi.DOM.addStyle('wua-styles', styles); + updateVar('--sidebar-height', `${channelsRect.height + noticesHeight}px`); + updateVar('--user-area-width', `${channelsRect.right - serverListRect.left}px`); + updateVar('--user-area-left', `${serverListRect.left}px`); + updateVar('--user-area-bottom', `${bottom}px`); + userAreaObserver.observe(element); +} +onStart(() => { + window.addEventListener('resize', recalcDebounce); + updateVar('--notices-height', '0px'); + themesObserver.observe(document.querySelector("bd-themes"), { childList: true, subtree: true }); +}); +onStop(() => { + userAreaObserver.disconnect(); + channelsObserver.disconnect(); + themesObserver.disconnect(); + secondServerListObserver.disconnect(); + noticesObserver.disconnect(); + BdApi.DOM.removeStyle('wua-styles'); + for (let varName of varsSet) { + BdApi.DOM.removeStyle(`wua-${varName}`); + } + window.removeEventListener('resize', recalcDebounce); +}); + } +} diff --git a/dotfiles/.config/BetterDiscord/plugins/ZeresPluginLibrary.config.json b/dotfiles/.config/BetterDiscord/plugins/ZeresPluginLibrary.config.json new file mode 100644 index 0000000..5dcf22b --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/ZeresPluginLibrary.config.json @@ -0,0 +1,6 @@ +{ + "currentVersionInfo": { + "version": "2.0.23", + "hasShownChangelog": true + } +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/betterdiscord-google-fonts.config.json b/dotfiles/.config/BetterDiscord/plugins/betterdiscord-google-fonts.config.json new file mode 100644 index 0000000..4949dce --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/betterdiscord-google-fonts.config.json @@ -0,0 +1,1425 @@ +{ + "fonts": [ + "Default", + "ABeeZee", + "Abel", + "Abhaya Libre", + "Abril Fatface", + "Aclonica", + "Acme", + "Actor", + "Adamina", + "Advent Pro", + "Aguafina Script", + "Akaya Kanadaka", + "Akaya Telivigala", + "Akronim", + "Akshar", + "Aladin", + "Alata", + "Alatsi", + "Aldrich", + "Alef", + "Alegreya", + "Alegreya SC", + "Alegreya Sans", + "Alegreya Sans SC", + "Aleo", + "Alex Brush", + "Alfa Slab One", + "Alice", + "Alike", + "Alike Angular", + "Allan", + "Allerta", + "Allerta Stencil", + "Allison", + "Allura", + "Almarai", + "Almendra", + "Almendra Display", + "Almendra SC", + "Alumni Sans", + "Alumni Sans Inline One", + "Amarante", + "Amaranth", + "Amatic SC", + "Amethysta", + "Amiko", + "Amiri", + "Amita", + "Anaheim", + "Andada Pro", + "Andika", + "Andika New Basic", + "Anek Bangla", + "Anek Devanagari", + "Anek Gujarati", + "Anek Gurmukhi", + "Anek Kannada", + "Anek Latin", + "Anek Malayalam", + "Anek Odia", + "Anek Tamil", + "Anek Telugu", + "Angkor", + "Annie Use Your Telescope", + "Anonymous Pro", + "Antic", + "Antic Didone", + "Antic Slab", + "Anton", + "Antonio", + "Anybody", + "Arapey", + "Arbutus", + "Arbutus Slab", + "Architects Daughter", + "Archivo", + "Archivo Black", + "Archivo Narrow", + "Are You Serious", + "Aref Ruqaa", + "Arima Madurai", + "Arimo", + "Arizonia", + "Armata", + "Arsenal", + "Artifika", + "Arvo", + "Arya", + "Asap", + "Asap Condensed", + "Asar", + "Asset", + "Assistant", + "Astloch", + "Asul", + "Athiti", + "Atkinson Hyperlegible", + "Atma", + "Atomic Age", + "Aubrey", + "Audiowide", + "Autour One", + "Average", + "Average Sans", + "Averia Gruesa Libre", + "Averia Libre", + "Averia Sans Libre", + "Averia Serif Libre", + "Azeret Mono", + "B612", + "B612 Mono", + "BIZ UDGothic", + "BIZ UDMincho", + "BIZ UDPGothic", + "BIZ UDPMincho", + "Babylonica", + "Bad Script", + "Bahiana", + "Bahianita", + "Bai Jamjuree", + "Bakbak One", + "Ballet", + "Baloo 2", + "Baloo Bhai 2", + "Baloo Bhaijaan 2", + "Baloo Bhaina 2", + "Baloo Chettan 2", + "Baloo Da 2", + "Baloo Paaji 2", + "Baloo Tamma 2", + "Baloo Tammudu 2", + "Baloo Thambi 2", + "Balsamiq Sans", + "Balthazar", + "Bangers", + "Barlow", + "Barlow Condensed", + "Barlow Semi Condensed", + "Barriecito", + "Barrio", + "Basic", + "Baskervville", + "Battambang", + "Baumans", + "Bayon", + "Be Vietnam Pro", + "Beau Rivage", + "Bebas Neue", + "Belgrano", + "Bellefair", + "Belleza", + "Bellota", + "Bellota Text", + "BenchNine", + "Benne", + "Bentham", + "Berkshire Swash", + "Besley", + "Beth Ellen", + "Bevan", + "BhuTuka Expanded One", + "Big Shoulders Display", + "Big Shoulders Inline Display", + "Big Shoulders Inline Text", + "Big Shoulders Stencil Display", + "Big Shoulders Stencil Text", + "Big Shoulders Text", + "Bigelow Rules", + "Bigshot One", + "Bilbo", + "Bilbo Swash Caps", + "BioRhyme", + "BioRhyme Expanded", + "Birthstone", + "Birthstone Bounce", + "Biryani", + "Bitter", + "Black And White Picture", + "Black Han Sans", + "Black Ops One", + "Blaka", + "Blaka Hollow", + "Blinker", + "Bodoni Moda", + "Bokor", + "Bona Nova", + "Bonbon", + "Bonheur Royale", + "Boogaloo", + "Bowlby One", + "Bowlby One SC", + "Brawler", + "Bree Serif", + "Brygada 1918", + "Bubblegum Sans", + "Bubbler One", + "Buda", + "Buenard", + "Bungee", + "Bungee Hairline", + "Bungee Inline", + "Bungee Outline", + "Bungee Shade", + "Butcherman", + "Butterfly Kids", + "Cabin", + "Cabin Condensed", + "Cabin Sketch", + "Caesar Dressing", + "Cagliostro", + "Cairo", + "Caladea", + "Calistoga", + "Calligraffitti", + "Cambay", + "Cambo", + "Candal", + "Cantarell", + "Cantata One", + "Cantora One", + "Capriola", + "Caramel", + "Carattere", + "Cardo", + "Carme", + "Carrois Gothic", + "Carrois Gothic SC", + "Carter One", + "Castoro", + "Catamaran", + "Caudex", + "Caveat", + "Caveat Brush", + "Cedarville Cursive", + "Ceviche One", + "Chakra Petch", + "Changa", + "Changa One", + "Chango", + "Charm", + "Charmonman", + "Chathura", + "Chau Philomene One", + "Chela One", + "Chelsea Market", + "Chenla", + "Cherish", + "Cherry Cream Soda", + "Cherry Swash", + "Chewy", + "Chicle", + "Chilanka", + "Chivo", + "Chonburi", + "Cinzel", + "Cinzel Decorative", + "Clicker Script", + "Coda", + "Coda Caption", + "Codystar", + "Coiny", + "Combo", + "Comfortaa", + "Comforter", + "Comforter Brush", + "Comic Neue", + "Coming Soon", + "Commissioner", + "Concert One", + "Condiment", + "Content", + "Contrail One", + "Convergence", + "Cookie", + "Copse", + "Corben", + "Corinthia", + "Cormorant", + "Cormorant Garamond", + "Cormorant Infant", + "Cormorant SC", + "Cormorant Unicase", + "Cormorant Upright", + "Courgette", + "Courier Prime", + "Cousine", + "Coustard", + "Covered By Your Grace", + "Crafty Girls", + "Creepster", + "Crete Round", + "Crimson Pro", + "Crimson Text", + "Croissant One", + "Crushed", + "Cuprum", + "Cute Font", + "Cutive", + "Cutive Mono", + "DM Mono", + "DM Sans", + "DM Serif Display", + "DM Serif Text", + "Damion", + "Dancing Script", + "Dangrek", + "Darker Grotesque", + "David Libre", + "Dawning of a New Day", + "Days One", + "Dekko", + "Dela Gothic One", + "Delius", + "Delius Swash Caps", + "Delius Unicase", + "Della Respira", + "Denk One", + "Devonshire", + "Dhurjati", + "Didact Gothic", + "Diplomata", + "Diplomata SC", + "Do Hyeon", + "Dokdo", + "Domine", + "Donegal One", + "Dongle", + "Doppio One", + "Dorsa", + "Dosis", + "DotGothic16", + "Dr Sugiyama", + "Duru Sans", + "Dynalight", + "EB Garamond", + "Eagle Lake", + "East Sea Dokdo", + "Eater", + "Economica", + "Eczar", + "El Messiri", + "Electrolize", + "Elsie", + "Elsie Swash Caps", + "Emblema One", + "Emilys Candy", + "Encode Sans", + "Encode Sans Condensed", + "Encode Sans Expanded", + "Encode Sans SC", + "Encode Sans Semi Condensed", + "Encode Sans Semi Expanded", + "Engagement", + "Englebert", + "Enriqueta", + "Ephesis", + "Epilogue", + "Erica One", + "Esteban", + "Estonia", + "Euphoria Script", + "Ewert", + "Exo", + "Exo 2", + "Expletus Sans", + "Explora", + "Fahkwang", + "Familjen Grotesk", + "Fanwood Text", + "Farro", + "Farsan", + "Fascinate", + "Fascinate Inline", + "Faster One", + "Fasthand", + "Fauna One", + "Faustina", + "Federant", + "Federo", + "Felipa", + "Fenix", + "Festive", + "Finger Paint", + "Fira Code", + "Fira Mono", + "Fira Sans", + "Fira Sans Condensed", + "Fira Sans Extra Condensed", + "Fjalla One", + "Fjord One", + "Flamenco", + "Flavors", + "Fleur De Leah", + "Flow Block", + "Flow Circular", + "Flow Rounded", + "Fondamento", + "Fontdiner Swanky", + "Forum", + "Francois One", + "Frank Ruhl Libre", + "Fraunces", + "Freckle Face", + "Fredericka the Great", + "Fredoka", + "Fredoka One", + "Freehand", + "Fresca", + "Frijole", + "Fruktur", + "Fugaz One", + "Fuggles", + "Fuzzy Bubbles", + "GFS Didot", + "GFS Neohellenic", + "Gabriela", + "Gaegu", + "Gafata", + "Galada", + "Galdeano", + "Galindo", + "Gamja Flower", + "Gayathri", + "Gelasio", + "Gemunu Libre", + "Genos", + "Gentium Basic", + "Gentium Book Basic", + "Geo", + "Georama", + "Geostar", + "Geostar Fill", + "Germania One", + "Gideon Roman", + "Gidugu", + "Gilda Display", + "Girassol", + "Give You Glory", + "Glass Antiqua", + "Glegoo", + "Gloria Hallelujah", + "Glory", + "Gluten", + "Goblin One", + "Gochi Hand", + "Goldman", + "Gorditas", + "Gothic A1", + "Gotu", + "Goudy Bookletter 1911", + "Gowun Batang", + "Gowun Dodum", + "Graduate", + "Grand Hotel", + "Grandstander", + "Grape Nuts", + "Gravitas One", + "Great Vibes", + "Grechen Fuemen", + "Grenze", + "Grenze Gotisch", + "Grey Qo", + "Griffy", + "Gruppo", + "Gudea", + "Gugi", + "Gupter", + "Gurajada", + "Gwendolyn", + "Habibi", + "Hachi Maru Pop", + "Hahmlet", + "Halant", + "Hammersmith One", + "Hanalei", + "Hanalei Fill", + "Handlee", + "Hanuman", + "Happy Monkey", + "Harmattan", + "Headland One", + "Heebo", + "Henny Penny", + "Hepta Slab", + "Herr Von Muellerhoff", + "Hi Melody", + "Hina Mincho", + "Hind", + "Hind Guntur", + "Hind Madurai", + "Hind Siliguri", + "Hind Vadodara", + "Holtwood One SC", + "Homemade Apple", + "Homenaje", + "Hubballi", + "Hurricane", + "IBM Plex Mono", + "IBM Plex Sans", + "IBM Plex Sans Arabic", + "IBM Plex Sans Condensed", + "IBM Plex Sans Devanagari", + "IBM Plex Sans Hebrew", + "IBM Plex Sans KR", + "IBM Plex Sans Thai", + "IBM Plex Sans Thai Looped", + "IBM Plex Serif", + "IM Fell DW Pica", + "IM Fell DW Pica SC", + "IM Fell Double Pica", + "IM Fell Double Pica SC", + "IM Fell English", + "IM Fell English SC", + "IM Fell French Canon", + "IM Fell French Canon SC", + "IM Fell Great Primer", + "IM Fell Great Primer SC", + "Ibarra Real Nova", + "Iceberg", + "Iceland", + "Imbue", + "Imperial Script", + "Imprima", + "Inconsolata", + "Inder", + "Indie Flower", + "Ingrid Darling", + "Inika", + "Inknut Antiqua", + "Inria Sans", + "Inria Serif", + "Inspiration", + "Inter", + "Irish Grover", + "Island Moments", + "Istok Web", + "Italiana", + "Italianno", + "Itim", + "Jacques Francois", + "Jacques Francois Shadow", + "Jaldi", + "JetBrains Mono", + "Jim Nightshade", + "Jockey One", + "Jolly Lodger", + "Jomhuria", + "Jomolhari", + "Josefin Sans", + "Josefin Slab", + "Jost", + "Joti One", + "Jua", + "Judson", + "Julee", + "Julius Sans One", + "Junge", + "Jura", + "Just Another Hand", + "Just Me Again Down Here", + "K2D", + "Kadwa", + "Kaisei Decol", + "Kaisei HarunoUmi", + "Kaisei Opti", + "Kaisei Tokumin", + "Kalam", + "Kameron", + "Kanit", + "Kantumruy", + "Karantina", + "Karla", + "Karma", + "Katibeh", + "Kaushan Script", + "Kavivanar", + "Kavoon", + "Kdam Thmor", + "Keania One", + "Kelly Slab", + "Kenia", + "Khand", + "Khmer", + "Khula", + "Kings", + "Kirang Haerang", + "Kite One", + "Kiwi Maru", + "Klee One", + "Knewave", + "KoHo", + "Kodchasan", + "Koh Santepheap", + "Kolker Brush", + "Kosugi", + "Kosugi Maru", + "Kotta One", + "Koulen", + "Kranky", + "Kreon", + "Kristi", + "Krona One", + "Krub", + "Kufam", + "Kulim Park", + "Kumar One", + "Kumar One Outline", + "Kumbh Sans", + "Kurale", + "La Belle Aurore", + "Lacquer", + "Laila", + "Lakki Reddy", + "Lalezar", + "Lancelot", + "Langar", + "Lateef", + "Lato", + "Lavishly Yours", + "League Gothic", + "League Script", + "League Spartan", + "Leckerli One", + "Ledger", + "Lekton", + "Lemon", + "Lemonada", + "Lexend", + "Lexend Deca", + "Lexend Exa", + "Lexend Giga", + "Lexend Mega", + "Lexend Peta", + "Lexend Tera", + "Lexend Zetta", + "Libre Barcode 128", + "Libre Barcode 128 Text", + "Libre Barcode 39", + "Libre Barcode 39 Extended", + "Libre Barcode 39 Extended Text", + "Libre Barcode 39 Text", + "Libre Barcode EAN13 Text", + "Libre Baskerville", + "Libre Bodoni", + "Libre Caslon Display", + "Libre Caslon Text", + "Libre Franklin", + "Licorice", + "Life Savers", + "Lilita One", + "Lily Script One", + "Limelight", + "Linden Hill", + "Literata", + "Liu Jian Mao Cao", + "Livvic", + "Lobster", + "Lobster Two", + "Londrina Outline", + "Londrina Shadow", + "Londrina Sketch", + "Londrina Solid", + "Long Cang", + "Lora", + "Love Light", + "Love Ya Like A Sister", + "Loved by the King", + "Lovers Quarrel", + "Luckiest Guy", + "Lusitana", + "Lustria", + "Luxurious Roman", + "Luxurious Script", + "M PLUS 1", + "M PLUS 1 Code", + "M PLUS 1p", + "M PLUS 2", + "M PLUS Code Latin", + "M PLUS Rounded 1c", + "Ma Shan Zheng", + "Macondo", + "Macondo Swash Caps", + "Mada", + "Magra", + "Maiden Orange", + "Maitree", + "Major Mono Display", + "Mako", + "Mali", + "Mallanna", + "Mandali", + "Manjari", + "Manrope", + "Mansalva", + "Manuale", + "Marcellus", + "Marcellus SC", + "Marck Script", + "Margarine", + "Markazi Text", + "Marko One", + "Marmelad", + "Martel", + "Martel Sans", + "Marvel", + "Mate", + "Mate SC", + "Maven Pro", + "McLaren", + "Mea Culpa", + "Meddon", + "MedievalSharp", + "Medula One", + "Meera Inimai", + "Megrim", + "Meie Script", + "Meow Script", + "Merienda", + "Merienda One", + "Merriweather", + "Merriweather Sans", + "Metal", + "Metal Mania", + "Metamorphous", + "Metrophobic", + "Michroma", + "Milonga", + "Miltonian", + "Miltonian Tattoo", + "Mina", + "Miniver", + "Miriam Libre", + "Mirza", + "Miss Fajardose", + "Mitr", + "Mochiy Pop One", + "Mochiy Pop P One", + "Modak", + "Modern Antiqua", + "Mogra", + "Mohave", + "Molengo", + "Molle", + "Monda", + "Monofett", + "Monoton", + "Monsieur La Doulaise", + "Montaga", + "Montagu Slab", + "MonteCarlo", + "Montez", + "Montserrat", + "Montserrat Alternates", + "Montserrat Subrayada", + "Moo Lah Lah", + "Moon Dance", + "Moul", + "Moulpali", + "Mountains of Christmas", + "Mouse Memoirs", + "Mr Bedfort", + "Mr Dafoe", + "Mr De Haviland", + "Mrs Saint Delafield", + "Mrs Sheppards", + "Ms Madi", + "Mukta", + "Mukta Mahee", + "Mukta Malar", + "Mukta Vaani", + "Mulish", + "Murecho", + "MuseoModerno", + "My Soul", + "Mystery Quest", + "NTR", + "Nanum Brush Script", + "Nanum Gothic", + "Nanum Gothic Coding", + "Nanum Myeongjo", + "Nanum Pen Script", + "Neonderthaw", + "Nerko One", + "Neucha", + "Neuton", + "New Rocker", + "New Tegomin", + "News Cycle", + "Newsreader", + "Niconne", + "Niramit", + "Nixie One", + "Nobile", + "Nokora", + "Norican", + "Nosifer", + "Notable", + "Nothing You Could Do", + "Noticia Text", + "Noto Emoji", + "Noto Kufi Arabic", + "Noto Music", + "Noto Naskh Arabic", + "Noto Nastaliq Urdu", + "Noto Rashi Hebrew", + "Noto Sans", + "Noto Sans Adlam", + "Noto Sans Adlam Unjoined", + "Noto Sans Anatolian Hieroglyphs", + "Noto Sans Arabic", + "Noto Sans Armenian", + "Noto Sans Avestan", + "Noto Sans Balinese", + "Noto Sans Bamum", + "Noto Sans Bassa Vah", + "Noto Sans Batak", + "Noto Sans Bengali", + "Noto Sans Bhaiksuki", + "Noto Sans Brahmi", + "Noto Sans Buginese", + "Noto Sans Buhid", + "Noto Sans Canadian Aboriginal", + "Noto Sans Carian", + "Noto Sans Caucasian Albanian", + "Noto Sans Chakma", + "Noto Sans Cham", + "Noto Sans Cherokee", + "Noto Sans Coptic", + "Noto Sans Cuneiform", + "Noto Sans Cypriot", + "Noto Sans Deseret", + "Noto Sans Devanagari", + "Noto Sans Display", + "Noto Sans Duployan", + "Noto Sans Egyptian Hieroglyphs", + "Noto Sans Elbasan", + "Noto Sans Elymaic", + "Noto Sans Georgian", + "Noto Sans Glagolitic", + "Noto Sans Gothic", + "Noto Sans Grantha", + "Noto Sans Gujarati", + "Noto Sans Gunjala Gondi", + "Noto Sans Gurmukhi", + "Noto Sans HK", + "Noto Sans Hanifi Rohingya", + "Noto Sans Hanunoo", + "Noto Sans Hatran", + "Noto Sans Hebrew", + "Noto Sans Imperial Aramaic", + "Noto Sans Indic Siyaq Numbers", + "Noto Sans Inscriptional Pahlavi", + "Noto Sans Inscriptional Parthian", + "Noto Sans JP", + "Noto Sans Javanese", + "Noto Sans KR", + "Noto Sans Kaithi", + "Noto Sans Kannada", + "Noto Sans Kayah Li", + "Noto Sans Kharoshthi", + "Noto Sans Khmer", + "Noto Sans Khojki", + "Noto Sans Khudawadi", + "Noto Sans Lao", + "Noto Sans Lepcha", + "Noto Sans Limbu", + "Noto Sans Linear A", + "Noto Sans Linear B", + "Noto Sans Lisu", + "Noto Sans Lycian", + "Noto Sans Lydian", + "Noto Sans Mahajani", + "Noto Sans Malayalam", + "Noto Sans Mandaic", + "Noto Sans Manichaean", + "Noto Sans Marchen", + "Noto Sans Masaram Gondi", + "Noto Sans Math", + "Noto Sans Mayan Numerals", + "Noto Sans Medefaidrin", + "Noto Sans Meetei Mayek", + "Noto Sans Meroitic", + "Noto Sans Miao", + "Noto Sans Modi", + "Noto Sans Mongolian", + "Noto Sans Mono", + "Noto Sans Mro", + "Noto Sans Multani", + "Noto Sans Myanmar", + "Noto Sans N Ko", + "Noto Sans Nabataean", + "Noto Sans New Tai Lue", + "Noto Sans Newa", + "Noto Sans Nushu", + "Noto Sans Ogham", + "Noto Sans Ol Chiki", + "Noto Sans Old Hungarian", + "Noto Sans Old Italic", + "Noto Sans Old North Arabian", + "Noto Sans Old Permic", + "Noto Sans Old Persian", + "Noto Sans Old Sogdian", + "Noto Sans Old South Arabian", + "Noto Sans Old Turkic", + "Noto Sans Oriya", + "Noto Sans Osage", + "Noto Sans Osmanya", + "Noto Sans Pahawh Hmong", + "Noto Sans Palmyrene", + "Noto Sans Pau Cin Hau", + "Noto Sans Phags Pa", + "Noto Sans Phoenician", + "Noto Sans Psalter Pahlavi", + "Noto Sans Rejang", + "Noto Sans Runic", + "Noto Sans SC", + "Noto Sans Samaritan", + "Noto Sans Saurashtra", + "Noto Sans Sharada", + "Noto Sans Shavian", + "Noto Sans Siddham", + "Noto Sans Sinhala", + "Noto Sans Sogdian", + "Noto Sans Sora Sompeng", + "Noto Sans Soyombo", + "Noto Sans Sundanese", + "Noto Sans Syloti Nagri", + "Noto Sans Symbols", + "Noto Sans Symbols 2", + "Noto Sans Syriac", + "Noto Sans TC", + "Noto Sans Tagalog", + "Noto Sans Tagbanwa", + "Noto Sans Tai Le", + "Noto Sans Tai Tham", + "Noto Sans Tai Viet", + "Noto Sans Takri", + "Noto Sans Tamil", + "Noto Sans Tamil Supplement", + "Noto Sans Telugu", + "Noto Sans Thaana", + "Noto Sans Thai", + "Noto Sans Thai Looped", + "Noto Sans Tifinagh", + "Noto Sans Tirhuta", + "Noto Sans Ugaritic", + "Noto Sans Vai", + "Noto Sans Wancho", + "Noto Sans Warang Citi", + "Noto Sans Yi", + "Noto Sans Zanabazar Square", + "Noto Serif", + "Noto Serif Ahom", + "Noto Serif Armenian", + "Noto Serif Balinese", + "Noto Serif Bengali", + "Noto Serif Devanagari", + "Noto Serif Display", + "Noto Serif Dogra", + "Noto Serif Ethiopic", + "Noto Serif Georgian", + "Noto Serif Grantha", + "Noto Serif Gujarati", + "Noto Serif Gurmukhi", + "Noto Serif Hebrew", + "Noto Serif JP", + "Noto Serif KR", + "Noto Serif Kannada", + "Noto Serif Khmer", + "Noto Serif Lao", + "Noto Serif Malayalam", + "Noto Serif Myanmar", + "Noto Serif Nyiakeng Puachue Hmong", + "Noto Serif SC", + "Noto Serif Sinhala", + "Noto Serif TC", + "Noto Serif Tamil", + "Noto Serif Tangut", + "Noto Serif Telugu", + "Noto Serif Thai", + "Noto Serif Tibetan", + "Noto Serif Yezidi", + "Noto Traditional Nushu", + "Nova Cut", + "Nova Flat", + "Nova Mono", + "Nova Oval", + "Nova Round", + "Nova Script", + "Nova Slim", + "Nova Square", + "Numans", + "Nunito", + "Nunito Sans", + "Odibee Sans", + "Odor Mean Chey", + "Offside", + "Oi", + "Old Standard TT", + "Oldenburg", + "Ole", + "Oleo Script", + "Oleo Script Swash Caps", + "Oooh Baby", + "Open Sans", + "Oranienbaum", + "Orbitron", + "Oregano", + "Orelega One", + "Orienta", + "Original Surfer", + "Oswald", + "Otomanopee One", + "Outfit", + "Over the Rainbow", + "Overlock", + "Overlock SC", + "Overpass", + "Overpass Mono", + "Ovo", + "Oxanium", + "Oxygen", + "Oxygen Mono", + "PT Mono", + "PT Sans", + "PT Sans Caption", + "PT Sans Narrow", + "PT Serif", + "PT Serif Caption", + "Pacifico", + "Padauk", + "Palanquin", + "Palanquin Dark", + "Palette Mosaic", + "Pangolin", + "Paprika", + "Parisienne", + "Passero One", + "Passion One", + "Passions Conflict", + "Pathway Gothic One", + "Patrick Hand", + "Patrick Hand SC", + "Pattaya", + "Patua One", + "Pavanam", + "Paytone One", + "Peddana", + "Peralta", + "Permanent Marker", + "Petemoss", + "Petit Formal Script", + "Petrona", + "Philosopher", + "Piazzolla", + "Piedra", + "Pinyon Script", + "Pirata One", + "Plaster", + "Play", + "Playball", + "Playfair Display", + "Playfair Display SC", + "Plus Jakarta Sans", + "Podkova", + "Poiret One", + "Poller One", + "Poly", + "Pompiere", + "Pontano Sans", + "Poor Story", + "Poppins", + "Port Lligat Sans", + "Port Lligat Slab", + "Potta One", + "Pragati Narrow", + "Praise", + "Prata", + "Preahvihear", + "Press Start 2P", + "Pridi", + "Princess Sofia", + "Prociono", + "Prompt", + "Prosto One", + "Proza Libre", + "Public Sans", + "Puppies Play", + "Puritan", + "Purple Purse", + "Qahiri", + "Quando", + "Quantico", + "Quattrocento", + "Quattrocento Sans", + "Questrial", + "Quicksand", + "Quintessential", + "Qwigley", + "Qwitcher Grypen", + "Racing Sans One", + "Radio Canada", + "Radley", + "Rajdhani", + "Rakkas", + "Raleway", + "Raleway Dots", + "Ramabhadra", + "Ramaraja", + "Rambla", + "Rammetto One", + "Rampart One", + "Ranchers", + "Rancho", + "Ranga", + "Rasa", + "Rationale", + "Ravi Prakash", + "Readex Pro", + "Recursive", + "Red Hat Display", + "Red Hat Mono", + "Red Hat Text", + "Red Rose", + "Redacted", + "Redacted Script", + "Redressed", + "Reem Kufi", + "Reenie Beanie", + "Reggae One", + "Revalia", + "Rhodium Libre", + "Ribeye", + "Ribeye Marrow", + "Righteous", + "Risque", + "Road Rage", + "Roboto", + "Roboto Condensed", + "Roboto Flex", + "Roboto Mono", + "Roboto Serif", + "Roboto Slab", + "Rochester", + "Rock 3D", + "Rock Salt", + "RocknRoll One", + "Rokkitt", + "Romanesco", + "Ropa Sans", + "Rosario", + "Rosarivo", + "Rouge Script", + "Rowdies", + "Rozha One", + "Rubik", + "Rubik Beastly", + "Rubik Bubbles", + "Rubik Glitch", + "Rubik Microbe", + "Rubik Mono One", + "Rubik Moonrocks", + "Rubik Puddles", + "Rubik Wet Paint", + "Ruda", + "Rufina", + "Ruge Boogie", + "Ruluko", + "Rum Raisin", + "Ruslan Display", + "Russo One", + "Ruthie", + "Rye", + "STIX Two Text", + "Sacramento", + "Sahitya", + "Sail", + "Saira", + "Saira Condensed", + "Saira Extra Condensed", + "Saira Semi Condensed", + "Saira Stencil One", + "Salsa", + "Sanchez", + "Sancreek", + "Sansita", + "Sansita Swashed", + "Sarabun", + "Sarala", + "Sarina", + "Sarpanch", + "Sassy Frass", + "Satisfy", + "Sawarabi Gothic", + "Sawarabi Mincho", + "Scada", + "Scheherazade New", + "Schoolbell", + "Scope One", + "Seaweed Script", + "Secular One", + "Sedgwick Ave", + "Sedgwick Ave Display", + "Sen", + "Send Flowers", + "Sevillana", + "Seymour One", + "Shadows Into Light", + "Shadows Into Light Two", + "Shalimar", + "Shanti", + "Share", + "Share Tech", + "Share Tech Mono", + "Shippori Antique", + "Shippori Antique B1", + "Shippori Mincho", + "Shippori Mincho B1", + "Shizuru", + "Shojumaru", + "Short Stack", + "Shrikhand", + "Siemreap", + "Sigmar One", + "Signika", + "Signika Negative", + "Simonetta", + "Single Day", + "Sintony", + "Sirin Stencil", + "Six Caps", + "Skranji", + "Slabo 13px", + "Slabo 27px", + "Slackey", + "Smokum", + "Smooch", + "Smooch Sans", + "Smythe", + "Sniglet", + "Snippet", + "Snowburst One", + "Sofadi One", + "Sofia", + "Solway", + "Song Myung", + "Sonsie One", + "Sora", + "Sorts Mill Goudy", + "Source Code Pro", + "Source Sans 3", + "Source Sans Pro", + "Source Serif 4", + "Source Serif Pro", + "Space Grotesk", + "Space Mono", + "Special Elite", + "Spectral", + "Spectral SC", + "Spicy Rice", + "Spinnaker", + "Spirax", + "Spline Sans", + "Spline Sans Mono", + "Squada One", + "Square Peg", + "Sree Krushnadevaraya", + "Sriracha", + "Srisakdi", + "Staatliches", + "Stalemate", + "Stalinist One", + "Stardos Stencil", + "Stick", + "Stick No Bills", + "Stint Ultra Condensed", + "Stint Ultra Expanded", + "Stoke", + "Strait", + "Style Script", + "Stylish", + "Sue Ellen Francisco", + "Suez One", + "Sulphur Point", + "Sumana", + "Sunflower", + "Sunshiney", + "Supermercado One", + "Sura", + "Suranna", + "Suravaram", + "Suwannaphum", + "Swanky and Moo Moo", + "Syncopate", + "Syne", + "Syne Mono", + "Syne Tactile", + "Tajawal", + "Tangerine", + "Tapestry", + "Taprom", + "Tauri", + "Taviraj", + "Teko", + "Telex", + "Tenali Ramakrishna", + "Tenor Sans", + "Text Me One", + "Texturina", + "Thasadith", + "The Girl Next Door", + "The Nautigal", + "Tienne", + "Tillana", + "Timmana", + "Tinos", + "Tiro Bangla", + "Tiro Devanagari Hindi", + "Tiro Devanagari Marathi", + "Tiro Devanagari Sanskrit", + "Tiro Gurmukhi", + "Tiro Kannada", + "Tiro Tamil", + "Tiro Telugu", + "Titan One", + "Titillium Web", + "Tomorrow", + "Tourney", + "Trade Winds", + "Train One", + "Trirong", + "Trispace", + "Trocchi", + "Trochut", + "Truculenta", + "Trykker", + "Tulpen One", + "Turret Road", + "Twinkle Star", + "Ubuntu", + "Ubuntu Condensed", + "Ubuntu Mono", + "Uchen", + "Ultra", + "Uncial Antiqua", + "Underdog", + "Unica One", + "UnifrakturCook", + "UnifrakturMaguntia", + "Unkempt", + "Unlock", + "Unna", + "Updock", + "Urbanist", + "VT323", + "Vampiro One", + "Varela", + "Varela Round", + "Varta", + "Vast Shadow", + "Vazirmatn", + "Vesper Libre", + "Viaoda Libre", + "Vibes", + "Vibur", + "Vidaloka", + "Viga", + "Voces", + "Volkhov", + "Vollkorn", + "Vollkorn SC", + "Voltaire", + "Vujahday Script", + "Waiting for the Sunrise", + "Wallpoet", + "Walter Turncoat", + "Warnes", + "Water Brush", + "Waterfall", + "Wellfleet", + "Wendy One", + "Whisper", + "WindSong", + "Wire One", + "Work Sans", + "Xanh Mono", + "Yaldevi", + "Yanone Kaffeesatz", + "Yantramanav", + "Yatra One", + "Yellowtail", + "Yeon Sung", + "Yeseva One", + "Yesteryear", + "Yomogi", + "Yrsa", + "Yuji Boku", + "Yuji Hentaigana Akari", + "Yuji Hentaigana Akebono", + "Yuji Mai", + "Yuji Syuku", + "Yusei Magic", + "ZCOOL KuaiLe", + "ZCOOL QingKe HuangYou", + "ZCOOL XiaoWei", + "Zen Antique", + "Zen Antique Soft", + "Zen Dots", + "Zen Kaku Gothic Antique", + "Zen Kaku Gothic New", + "Zen Kurenaido", + "Zen Loop", + "Zen Maru Gothic", + "Zen Old Mincho", + "Zen Tokyo Zoo", + "Zeyada", + "Zhi Mang Xing", + "Zilla Slab", + "Zilla Slab Highlight" + ], + "lastFetchTime": 1653575402669, + "font": "Fira Code" +} \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/plugins/betterdiscord-google-fonts.plugin.js b/dotfiles/.config/BetterDiscord/plugins/betterdiscord-google-fonts.plugin.js new file mode 100644 index 0000000..88f3c5c --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/betterdiscord-google-fonts.plugin.js @@ -0,0 +1,132 @@ +/** + * @name betterdiscord-google-fonts + * @version 1.0.2 + * @description Allows use of Google Fonts through a simple settings UI + * @author Mike Orozco + * @authorLink https://mikeorozco.dev + * @authorId 142347724392497152 + * @updateUrl https://raw.githubusercontent.com/notmike101/betterdiscord-google-fonts/release/betterdiscord-google-fonts.plugin.js + * @source https://github.com/notmike101/betterdiscord-google-fonts + */ + +// sass:/home/runner/work/betterdiscord-google-fonts/betterdiscord-google-fonts/src/styles/Global.scss +var Global_default = ` +`; + +// _cqfx4ul6y:/home/runner/work/betterdiscord-google-fonts/betterdiscord-google-fonts/src/templates/SettingsPanel.html +var SettingsPanel_default = '
\n
\n
\n \n
\n
\n
\n
{{ CURRENT_FONT }}
\n \n \n \n
\n
\n
\n
\n
\n'; + +// sass:/home/runner/work/betterdiscord-google-fonts/betterdiscord-google-fonts/src/styles/SettingsPanel.scss +var SettingsPanel_default2 = ` +.google-font-settings .bd-setting-item .bd-select { + display: flex; + flex-direction: column; + align-items: flex-start; +} +.google-font-settings .bd-setting-item .bd-select .bd-select-header-custom { + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; +} +.google-font-settings .bd-setting-item .bd-select .bd-select-header-custom .bd-select-arrow { + width: 16px; + height: 16px; +} +.google-font-settings .bd-setting-item .bd-select .bd-select-options { + display: none; + position: initial; + z-index: 0; +}`; + +// src/DiscordPlugin.ts +var BdApi = window.BdApi || {}; +var DiscordPlugin = class { + selectedFont; + fonts; + constructor() { + this.selectedFont = null; + this.fonts = []; + } + getName() { + return "Google Fonts"; + } + async load() { + } + async start() { + BdApi.injectCSS("bd-google-fonts-global-css", Global_default); + BdApi.injectCSS("bd-google-fonts-settingspanel", SettingsPanel_default2); + this.fonts = await this.getAvailableFonts(); + this.selectedFont = BdApi.getData("betterdiscord-google-fonts", "font"); + this.updateDomFont(); + } + stop() { + BdApi.clearCSS("bd-google-fonts-global-css"); + BdApi.clearCSS("bd-google-fonts-custom-font"); + BdApi.clearCSS("bd-google-fonts-settingspanel"); + } + async getAvailableFonts() { + const existingData = BdApi.getData("betterdiscord-google-fonts", "fonts"); + const lastFetchTime = BdApi.getData("betterdiscord-google-fonts", "lastFetchTime"); + const lastFetchExpiration = new Date(lastFetchTime); + lastFetchExpiration.setDate(lastFetchExpiration.getDate() + 1 * 7); + if (!existingData || lastFetchTime < lastFetchExpiration) { + const response = await fetch("https://www.googleapis.com/webfonts/v1/webfonts?key=AIzaSyCfxANDwWkAP3JjzGLv5UOPYcPEwaVAz3k"); + const json = await response.json(); + const fonts = json.items.map((font) => font.family); + fonts.unshift("Default"); + BdApi.setData("betterdiscord-google-fonts", "fonts", fonts); + BdApi.setData("betterdiscord-google-fonts", "lastFetchTime", new Date().getTime()); + return fonts; + } + return existingData; + } + updateDomFont() { + BdApi.clearCSS("bd-google-fonts-custom-font"); + if (this.selectedFont) { + const newStyle = ` + @import url('https://fonts.googleapis.com/css?family=${this.selectedFont}'); + + *:not([class*="hljs"]):not(code){ + font-family: ${this.selectedFont} !important; + } + `; + BdApi.injectCSS("bd-google-fonts-custom-font", newStyle); + } + } + getSettingsPanel() { + const template = document.createElement("template"); + const currentFontString = this.selectedFont ? this.selectedFont : "Default"; + template.innerHTML = SettingsPanel_default.replace("{{ CURRENT_FONT }}", currentFontString); + const settingsPanel = template.content.firstElementChild; + const fontSelection = settingsPanel.querySelector(".bd-select-options"); + settingsPanel.querySelector(".bd-select").addEventListener("click", () => { + const dropdown = settingsPanel.querySelector(".bd-select-options"); + if (dropdown.style.display === "none") { + dropdown.style.display = "block"; + } else { + dropdown.style.display = "none"; + } + }); + this.fonts.forEach((font) => { + const option = document.createElement("div"); + option.classList.add("bd-select-option"); + option.textContent = font; + option.addEventListener("click", (e) => { + const target = e.target; + const selectedFont = target.textContent; + if (selectedFont === "Default") { + this.selectedFont = null; + } else { + this.selectedFont = selectedFont; + } + this.updateDomFont(); + document.querySelector(".google-font-settings .bd-select-value").textContent = selectedFont; + BdApi.setData("betterdiscord-google-fonts", "font", this.selectedFont); + }); + fontSelection.appendChild(option); + }); + return settingsPanel; + } +}; +module.exports = DiscordPlugin; diff --git a/dotfiles/.config/BetterDiscord/plugins/gh-redirect?id=9 b/dotfiles/.config/BetterDiscord/plugins/gh-redirect?id=9 new file mode 100644 index 0000000..9fbd038 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/plugins/gh-redirect?id=9 @@ -0,0 +1,6321 @@ +/** + * @name ZeresPluginLibrary + * @description Gives other plugins utility functions. + * @version 2.0.17 + * @author Zerebos + * @source https://github.com/rauenzi/BDPluginLibrary + */ + +/*@cc_on +@if (@_jscript) + + // Offer to self-install for clueless users that try to run this directly. + var shell = WScript.CreateObject("WScript.Shell"); + var fs = new ActiveXObject("Scripting.FileSystemObject"); + var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins"); + var pathSelf = WScript.ScriptFullName; + // Put the user at ease by addressing them in the first person + shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30); + if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) { + shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40); + } else if (!fs.FolderExists(pathPlugins)) { + shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10); + } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) { + fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true); + // Show the user where to put plugins in the future + shell.Exec("explorer " + pathPlugins); + shell.Popup("I'm installed!", 0, "Successfully installed", 0x40); + } + WScript.Quit(); + +@else@*/ + +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ "./src/styles/settings.css": +/*!*********************************!*\ + !*** ./src/styles/settings.css ***! + \*********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (".plugin-input-group {\r\n margin-top: 5px;\r\n}\r\n\r\n.plugin-input-group .button-collapse {\r\n background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FscXVlXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSItOTUwIDUzMiAxOCAxOCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAtOTUwIDUzMiAxOCAxODsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCgkuc3Qwe2ZpbGw6bm9uZTt9DQoJLnN0MXtmaWxsOm5vbmU7c3Ryb2tlOiNGRkZGRkY7c3Ryb2tlLXdpZHRoOjEuNTtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTS05MzIsNTMydjE4aC0xOHYtMThILTkzMnoiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QxIiBwb2ludHM9Ii05MzYuNiw1MzguOCAtOTQxLDU0My4yIC05NDUuNCw1MzguOCAiLz4NCjwvc3ZnPg0K);\r\n height: 16px;\r\n width: 16px;\r\n display: inline-block;\r\n vertical-align: bottom;\r\n transition: transform .3s ease;\r\n transform: rotate(0);\r\n}\r\n\r\n.plugin-input-group .button-collapse.collapsed {\r\n transition: transform .3s ease;\r\n transform: rotate(-90deg);\r\n}\r\n\r\n.plugin-input-group h2 {\r\n font-size: 14px;\r\n}\r\n\r\n.plugin-input-group .plugin-input-group h2 {\r\n margin-left: 16px;\r\n}\r\n\r\n.plugin-inputs {\r\n height: auto;\r\n overflow: hidden;\r\n transition: height 300ms cubic-bezier(0.47, 0, 0.745, 0.715);\r\n}\r\n\r\n.plugin-inputs.collapsed {\r\n height: 0px;\r\n}\r\n\r\n.file-input {\r\n color: var(--text-normal);\r\n background-color: var(--input-background);\r\n width: 100%;\r\n border-radius: 5px;\r\n padding: 10px;\r\n height: 40px;\r\n box-sizing: border-box;\r\n overflow: hidden;\r\n }\r\n \r\n .file-input::-webkit-file-upload-button {\r\n color: white;\r\n background: #7289DA;\r\n outline: 0;\r\n border: 0;\r\n padding: 12px!important;\r\n margin-top: -10px;\r\n margin-left: -10px;\r\n margin-right: 10px;\r\n bottom: 0;\r\n border-radius: 3px 0 0 3px;\r\n font-size: 14px;\r\n font-weight: 500;\r\n font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif;\r\n cursor: pointer;\r\n }\r\n\r\n.color-input {\r\n background: none;\r\n padding: 0;\r\n border: none;\r\n}\r\n\r\n.color-input:hover {\r\n opacity: 0.8;\r\n}\r\n\r\n\r\n.z-select {\r\n position: relative;\r\n cursor: pointer;\r\n color: var(--text-normal);\r\n font-size: 14px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n background-color: var(--deprecated-text-input-bg);\r\n border: 1px solid var(--deprecated-text-input-border);\r\n border-radius: 3px;\r\n padding: 8px 8px 8px 12px;\r\n transition: 150ms ease border-color;\r\n }\r\n \r\n .z-select:hover,\r\n .z-select.menu-open {\r\n border-color: var(--background-tertiary);\r\n }\r\n \r\n .z-select.z-select-transparent {\r\n align-items: flex-start;\r\n background: none;\r\n border: none;\r\n padding: 0;\r\n }\r\n \r\n .z-select-icons {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n }\r\n \r\n .z-select-clear,\r\n .z-select-arrow {\r\n margin-left: 5px;\r\n fill: var(--interactive-normal);\r\n }\r\n \r\n .z-select .z-select-options {\r\n position: absolute;\r\n background: var(--background-secondary);\r\n border-radius: 0 0 3px 3px;\r\n max-height: 125px;\r\n min-width: 100%;\r\n overflow-y: auto;\r\n box-shadow: rgba(0, 0, 0, 0.3) 0 1px 5px 0;\r\n border: 1px solid rgba(0, 0, 0, 0.3);\r\n border-top: 0;\r\n margin-top: -1px;\r\n margin-left: -13px;\r\n z-index: 2;\r\n top: 100%;\r\n }\r\n \r\n .z-select-transparent .z-select-options {\r\n border: 1px solid rgba(0, 0, 0, 0.3);\r\n margin-top: 3px;\r\n border-radius: 3px;\r\n }\r\n \r\n .z-select .z-select-option {\r\n padding: 8px 12px;\r\n cursor: pointer;\r\n white-space: pre;\r\n }\r\n \r\n .z-select .z-select-option:hover {\r\n background: rgba(0, 0, 0, 0.1);\r\n }\r\n \r\n .z-select .z-select-option.selected {\r\n background: rgba(0, 0, 0, 0.2);\r\n }\r\n\r\n\r\n\r\n .z-keybind-wrapper {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n }\r\n \r\n .z-keybind-wrapper > :first-child {\r\n flex-grow: 1;\r\n }\r\n \r\n .z-keybind-clear {\r\n margin-left: 5px;\r\n fill: var(--interactive-normal);\r\n cursor: pointer;\r\n }\r\n\r\n.plugin-input-container > .container-31PmuA {\r\n margin: 10px 0;\r\n}"); + +/***/ }), + +/***/ "./src/styles/toasts.css": +/*!*******************************!*\ + !*** ./src/styles/toasts.css ***! + \*******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (".toasts {\r\n position: fixed;\r\n display: flex;\r\n top: 0;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: flex-end;\r\n pointer-events: none;\r\n z-index: 4000;\r\n}\r\n\r\n@keyframes toast-up {\r\n from {\r\n transform: translateY(0);\r\n opacity: 0;\r\n }\r\n}\r\n\r\n.toast {\r\n animation: toast-up 300ms ease;\r\n transform: translateY(-10px);\r\n background: #36393F;\r\n padding: 10px;\r\n border-radius: 5px;\r\n box-shadow: 0 0 0 1px rgba(32,34,37,.6), 0 2px 10px 0 rgba(0,0,0,.2);\r\n font-weight: 500;\r\n color: #fff;\r\n user-select: text;\r\n font-size: 14px;\r\n opacity: 1;\r\n margin-top: 10px;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n}\r\n\r\n@keyframes toast-down {\r\n to {\r\n transform: translateY(0px);\r\n opacity: 0;\r\n }\r\n}\r\n\r\n.toast.closing {\r\n animation: toast-down 200ms ease;\r\n animation-fill-mode: forwards;\r\n opacity: 1;\r\n transform: translateY(-10px);\r\n}\r\n\r\n.toast.toast-info {\r\n background-color: #4a90e2;\r\n}\r\n\r\n.toast.toast-success {\r\n background-color: #43b581;\r\n}\r\n\r\n.toast.toast-danger,\r\n.toast.toast-error {\r\n background-color: #f04747;\r\n}\r\n\r\n.toast.toast-warning,\r\n.toast.toast-warn {\r\n background-color: #FFA600;\r\n}\r\n\r\n.toast-icon {\r\n margin-right: 5px;\r\n fill: white;\r\n border-radius: 50%;\r\n overflow: hidden;\r\n height: 20px;\r\n width: 20px;\r\n}\r\n\r\n.toast-text {\r\n line-height: 20px;\r\n}"); + +/***/ }), + +/***/ "./src/styles/updates.css": +/*!********************************!*\ + !*** ./src/styles/updates.css ***! + \********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("#outdated-plugins {\r\n font-weight: 700;\r\n}\r\n#outdated-plugins > span {\r\n -webkit-app-region: no-drag;\r\n color: #fff;\r\n cursor: pointer;\r\n}\r\n#outdated-plugins > span:hover {\r\n text-decoration: underline;\r\n}"); + +/***/ }), + +/***/ "./src/config.js": +/*!***********************!*\ + !*** ./src/config.js ***! + \***********************/ +/***/ ((module) => { + +// Use non-ES6 so build script can require() +// Options: added, improved, fixed, progress. +module.exports = { + id: "9", + name: "ZeresPluginLibrary", + author: "Zerebos", + version: "2.0.17", + description: "Gives other plugins utility functions.", + source: "https://github.com/rauenzi/BDPluginLibrary", + github_raw: "https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js", + changelog: [ + {title: "Changes", type: "progress", items: ["Library no longer tries to update itself. Update it through BetterDiscord's updater.", "The library still checks for updates for other plugins currently."]}, + {title: "Fixed", type: "fixed", items: ["Fixed update looping when updating library.", "Fixed Switch settings not working causing some panels to not render at all.", "Fixed toasts not working in some cases."]}, + ], + main: "index.js" +}; + + +/***/ }), + +/***/ "./src/modules/colorconverter.js": +/*!***************************************!*\ + !*** ./src/modules/colorconverter.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ ColorConverter) +/* harmony export */ }); +/** + * Helpful utilities for dealing with colors. + * @module ColorConverter + */ + + +const validHexRegex = /#([a-fA-F0-9]{1,2})([a-fA-F0-9]{1,2})([a-fA-F0-9]{1,2})/; + +class ColorConverter { + + static getDarkness(color) { + const [red, green, blue] = this.getRGB(color); + return 1 - (0.299 * red + 0.587 * green + 0.114 * blue) / 255; + } + + static hex2int(color) { + if (color.length === 4) color = `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}`; + return parseInt(color.slice(1), 16); + } + + static hex2rgb(color) { + const [red, green, blue] = this.getRGB(color); + return `rgb(${red}, ${green}, ${blue})`; + } + + static int2hex(color) { + const red = color >> 16 & 255; + const green = color >> 8 & 255; + const blue = color & 255; + return `#${red.toString(16)}${green.toString(16)}${blue.toString(16)}`; + } + + static int2rgba(color, alpha) { + return `rgba(${color >> 16 & 255}, ${color >> 8 & 255}, ${color & 255}, ${alpha})`; + } + + static isValidHex(color) { + return color.match(validHexRegex) != null; + } + + /** + * Will get the red green and blue values of any color string. + * @param {string} color - the color to obtain the red, green and blue values of. Can be in any of these formats: #fff, #ffffff, rgb, rgba + * @returns {Array} - array containing the red, green, and blue values + */ + static getRGB(color) { + let result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color); + if (result) return [parseInt(result[1]), parseInt(result[2]), parseInt(result[3])]; + + result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*\)/.exec(color); + if (result) return [parseFloat(result[1]) * 2.55, parseFloat(result[2]) * 2.55, parseFloat(result[3]) * 2.55]; + + result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color); + if (result) return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]; + + result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color); + if (result) return [parseInt(result[1] + result[1], 16), parseInt(result[2] + result[2], 16), parseInt(result[3] + result[3], 16)]; + } + + /** + * Will get the darken the color by a certain percent + * @param {string} color - Can be in any of these formats: #fff, #ffffff, rgb, rgba + * @param {number} percent - percent to darken the color by (0-100) + * @returns {string} - new color in rgb format + */ + static darkenColor(color, percent) { + const rgb = this.getRGB(color); + if (!rgb) return color; + for (let i = 0; i < rgb.length; i++) rgb[i] = Math.round(Math.max(0, rgb[i] - rgb[i] * (percent / 100))); + return "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; + } + + /** + * Will get the lighten the color by a certain percent + * @param {string} color - Can be in any of these formats: #fff, #ffffff, rgb, rgba + * @param {number} percent - percent to lighten the color by (0-100) + * @returns {string} - new color in rgb format + */ + static lightenColor(color, percent) { + const rgb = this.getRGB(color); + if (!rgb) return color; + for (let i = 0; i < rgb.length; i++) rgb[i] = Math.round(Math.min(255, rgb[i] + rgb[i] * (percent / 100))); + return "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; + } + + /** + * Converts a color to rgba format string + * @param {string} color - Can be in any of these formats: #fff, #ffffff, rgb, rgba + * @param {number} alpha - alpha level for the new color + * @returns {string} - new color in rgb format + */ + static rgbToAlpha(color, alpha) { + const rgb = this.getRGB(color); + if (!rgb) return color; + return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + alpha + ")"; + } + +} + +/***/ }), + +/***/ "./src/modules/discordclasses.js": +/*!***************************************!*\ + !*** ./src/modules/discordclasses.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _discordclassmodules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./discordclassmodules */ "./src/modules/discordclassmodules.js"); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); + + + +const getRaw = function(prop) { + if (!this.hasOwnProperty(prop)) return ""; + return this[prop]; +}; + +const getClass = function(prop) { + if (!this.hasOwnProperty(prop)) return ""; + return this[prop].split(" ")[0]; +}; + +/** + * Proxy for all the class packages, allows us to safely attempt + * to retrieve nested things without error. Also wraps the class in + * {@link module:DOMTools.ClassName} which adds features but can still + * be used in native function. + * + * For a list of all available class namespaces check out {@link module:DiscordClassModules}. + * + * @see module:DiscordClassModules + * @module DiscordClasses + */ +const DiscordModules = new Proxy(_discordclassmodules__WEBPACK_IMPORTED_MODULE_0__["default"], { + get: function(list, item) { + if (item == "getRaw" || item == "getClass") return (module, prop) => DiscordModules[module][item]([prop]); + if (list[item] === undefined) return new Proxy({}, {get: function() {return "";}}); + return new Proxy(list[item], { + get: function(obj, prop) { + if (prop == "getRaw") return getRaw.bind(obj); + if (prop == "getClass") return getClass.bind(obj); + if (!obj.hasOwnProperty(prop)) return ""; + return new _domtools__WEBPACK_IMPORTED_MODULE_1__["default"].ClassName(obj[prop]); + } + }); + } +}); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (DiscordModules); + +/***/ }), + +/***/ "./src/modules/discordclassmodules.js": +/*!********************************************!*\ + !*** ./src/modules/discordclassmodules.js ***! + \********************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js"); +/* harmony import */ var _webpackmodules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./webpackmodules */ "./src/modules/webpackmodules.js"); + + + +/** + * A large list of known and labelled classes in discord. + * Click the source link down below to view more info. Otherwise, if you + * have the library installed or have a plugin using this library, + * do `Object.keys(ZLibrary.DiscordClassModules)` in console for a list of modules. + * + * You can use this directly, however the preferred way of doing this is to use {@link module:DiscordClasses} or {@link module:DiscordSelectors} + * + * @see module:DiscordClasses + * @see module:DiscordSelectors + * @module DiscordClassModules + */ +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_utilities__WEBPACK_IMPORTED_MODULE_0__["default"].memoizeObject({ + get ContextMenu() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("menu", "item");}, + get Scrollers() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("thin", "scrollerBase", "content");}, + get AccountDetails() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("container", "avatar", "hasBuildOverride");}, + get Typing() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("typing", "text");}, + get UserPopout() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("userPopout");}, + get PopoutRoles() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("roleCircle");}, + get UserModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("profileBadge");}, + get Textarea() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("channelTextArea", "textArea");}, + get Popouts() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("popouts", "popout");}, // broken, popouts element has been removed. + get App() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("app", "mobileApp");}, + get Titles() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("defaultMarginh5");}, + get Notices() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("notice", "colorInfo");}, + get Backdrop() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("backdrop");}, + get Modals() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("hideOnFullscreen", "root");}, + get AuditLog() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("userHook");}, + get ChannelList() {return Object.assign({}, _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("containerDefault"), _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("name", "unread"), _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("sidebar", "hasNotice"));}, + get MemberList() {return Object.assign({}, _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("member", "memberInner"), _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("members", "membersWrap"));}, + get TitleWrap() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("titleWrapper");}, + get Titlebar() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("titleBar");}, + get Embeds() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("embed", "embedAuthor");}, + get Layers() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("layers", "layer");}, + get TooltipLayers() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("layerContainer", "layer");}, + get Margins() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => !m.title && m.marginBottom40 && m.marginTop40);}, + get Dividers() {return Object.assign({}, _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("dividerDefault"), _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => Object.keys(m).length == 1 && m.divider));}, + get Changelog() {return Object.assign({}, _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("container", "added"), _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("content", "modal", "size"));}, + get BasicInputs() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("inputDefault", "copyInput");}, + get Messages() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("message", "containerCozy");}, + get Guilds() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("guildsWrapper");}, + get EmojiPicker() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("emojiPicker", "emojiItem");}, + get Reactions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("reaction", "reactionInner");}, + get Checkbox() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("checkbox", "checkboxInner");}, + get Tooltips() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("tooltip", "tooltipBlack");} +})); + + + +/***/ }), + +/***/ "./src/modules/discordmodules.js": +/*!***************************************!*\ + !*** ./src/modules/discordmodules.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js"); +/* harmony import */ var _webpackmodules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./webpackmodules */ "./src/modules/webpackmodules.js"); +/** + * A large list of known and useful webpack modules internal to Discord. + * Click the source link down below to view more info. Otherwise, if you + * have the library installed or have a plugin using this library, + * do `Object.keys(ZLibrary.DiscordModules)` in console for a list of modules. + * @module DiscordModules + */ + + + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_utilities__WEBPACK_IMPORTED_MODULE_0__["default"].memoizeObject({ + get React() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("createElement", "cloneElement");}, + get ReactDOM() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("render", "findDOMNode");}, + get Events() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByPrototypes("setMaxListeners", "emit");}, + + /* Guild Info, Stores, and Utilities */ + get GuildStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getGuild", "getGuilds");}, + get SortedGuildStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getSortedGuilds");}, + get SelectedGuildStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getLastSelectedGuildId");}, + get GuildSync() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getSyncedGuilds");}, + get GuildInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getAcronym");}, + get GuildChannelsStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getChannels", "getDefaultChannel");}, + get GuildMemberStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getMember");}, + get MemberCountStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getMemberCounts");}, + get GuildEmojiStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getEmojis");}, + get GuildActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("requestMembers");}, + get GuildPermissions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getGuildPermissions");}, + + /* Channel Store & Actions */ + get ChannelStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getChannel", "getDMFromUserId");}, + get SelectedChannelStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getLastSelectedChannelId");}, + get ChannelActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("selectChannel");}, + get PrivateChannelActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("openPrivateChannel");}, + + /* Current User Info, State and Settings */ + get UserInfoStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getSessionId");}, + get UserSettingsStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("guildPositions");}, + get StreamerModeStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("hidePersonalInformation");}, + get UserSettingsUpdater() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("updateRemoteSettings");}, + get OnlineWatcher() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isOnline");}, + get CurrentUserIdle() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isIdle");}, + get RelationshipStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isBlocked", "getFriendIDs");}, + get RelationshipManager() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("addRelationship");}, + get MentionStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getMentions");}, + + /* User Stores and Utils */ + get UserStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getCurrentUser", "getUser");}, + get UserStatusStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getStatus", "getState");}, + get UserTypingStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isTyping");}, + get UserActivityStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getActivity");}, + get UserNameResolver() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getName");}, + get UserNoteStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getNote");}, + get UserNoteActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("updateNote");}, + + /* Emoji Store and Utils */ + get EmojiInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isEmojiDisabled");}, + get EmojiUtils() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getGuildEmoji");}, + get EmojiStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getByCategory", "EMOJI_NAME_RE");}, + + /* Invite Store and Utils */ + get InviteStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getInvites");}, + get InviteResolver() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("resolveInvite");}, + get InviteActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("acceptInvite");}, + + /* Discord Objects & Utils */ + get DiscordConstants() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Permissions", "ActivityTypes", "StatusTypes");}, + get DiscordPermissions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m.ADD_REACTIONS, {searchExports: true});}, + get Permissions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("computePermissions");}, + get ColorConverter() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => Object.values(m).some(v => v?.toString().includes(`"rgba("`)));}, + get ColorShader() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("darken");}, + get TinyColor() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByPrototypes("toRgb");}, + get ClassResolver() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getClass");}, + get ButtonData() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m.BorderColors, {searchExports: true});}, + get NavigationUtils() { + return { + transitionToGuild: _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("transitionToGuildSync")?.transitionToGuildSync, + transitionTo: _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.toString?.().includes(`"transitionTo - Transitioning to "`), {searchExports: true}), + replaceWith: _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.toString?.().includes(`"Replacing route with "`), {searchExports: true}) + }; + }, + get KeybindStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("keyToCode");}, + + /* Discord Messages */ + get MessageStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getMessage", "getMessages");}, + get ReactionsStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getReactions", "_dispatcher");}, + get MessageActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("jumpToMessage", "_sendMessage");}, + get MessageQueue() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("enqueue");}, + get MessageParser() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => Object.keys(m).length && Object.keys(m).every(k => k === "parse" || k === "unparse"));}, + + /* Experiments */ + get ExperimentStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getExperimentOverrides");}, + get ExperimentsManager() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isDeveloper");}, + get CurrentExperiment() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getExperimentId");}, + + /* Streams */ + get StreamStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getAllActiveStreams", "getStreamForUser");}, + get StreamPreviewStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getIsPreviewLoading", "getPreviewURL");}, + + /* Images, Avatars and Utils */ + get ImageResolver() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getUserAvatarURL", "getGuildIconURL");}, + get ImageUtils() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getSizedImageSrc");}, + get AvatarDefaults() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getUserAvatarURL", "DEFAULT_AVATARS");}, + + /* Drag & Drop */ + get DNDSources() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("addTarget");}, + get DNDObjects() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("DragSource");}, + + /* Electron & Other Internals with Utils*/ + get ElectronModule() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("setBadge");}, + get Flux() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Store", "connectStores");}, + get Dispatcher() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("dispatch", "subscribe");}, + get PathUtils() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("hasBasename");}, + get NotificationModule() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("showNotification");}, + get RouterModule() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Router");}, + get APIModule() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getAPIBaseURL");}, + get AnalyticEvents() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("AnalyticEventConfigs");}, + get KeyGenerator() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByRegex(/"binary"/);}, + get Buffers() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Buffer", "kMaxLength");}, + get DeviceStore() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getDevices");}, + get SoftwareInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("os");}, + get i18n() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Messages", "languages");}, + + /* Media Stuff (Audio/Video) */ + get MediaDeviceInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Codecs", "MediaEngineContextTypes");}, + get MediaInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getOutputVolume");}, + get MediaEngineInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("determineMediaEngine");}, + get VoiceInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getEchoCancellation");}, + get SoundModule() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("playSound");}, + + /* Window, DOM, HTML */ + get WindowInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("isFocused", "windowSize");}, + get DOMInfo() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("canUseDOM");}, + + /* Locale/Location and Time */ + get LocaleManager() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m.Messages && Object.keys(m.Messages).length);}, + get Moment() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("parseZone");}, + get LocationManager() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("createLocation");}, + get Timestamps() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("fromTimestamp");}, + + /* Strings and Utils */ + get Strings() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m.Messages && Object.keys(m.Messages).length && m.Messages.COPY_ID);}, + get StringFormats() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("a", "z");}, + get StringUtils() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("toASCII");}, + + /* URLs and Utils */ + get URLParser() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Url", "parse");}, + get ExtraURLs() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("getArticleURL");}, + + /* Text Processing */ + get hljs() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("highlight", "highlightBlock");}, + get SimpleMarkdown() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("parseBlock", "parseInline", "defaultOutput");}, + + /* DOM/React Components */ + /* ==================== */ + get LayerManager() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("popLayer", "pushLayer");}, + get UserSettingsWindow() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "updateAccount");}, + get ChannelSettingsWindow() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "updateChannel");}, + get GuildSettingsWindow() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "updateGuild");}, + + /* Modals */ + get ModalActions() { + return { + openModal: _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => typeof(m) === "function" && m?.toString().includes("onCloseCallback") && m?.toString().includes("Layer"), {searchExports: true}), + closeModal: _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => typeof(m) === "function" && m?.toString().includes("onCloseCallback()"), {searchExports: true}) + }; + }, + get ModalStack() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("push", "update", "pop", "popWithKey");}, + get UserProfileModals() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("fetchMutualFriends", "setSection");}, + get AlertModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByPrototypes("handleCancel", "handleSubmit");}, + get ConfirmationModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.toString?.()?.includes(".confirmButtonColor"), {searchExports: true});}, + get ChangeNicknameModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "changeNickname");}, + get CreateChannelModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "createChannel");}, + get PruneMembersModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "prune");}, + get NotificationSettingsModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "updateNotificationSettings");}, + get PrivacySettingsModal() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m.open && m.open.toString().includes("PRIVACY_SETTINGS_MODAL"));}, + get Changelog() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule((m => m.defaultProps && m.defaultProps.selectable == false));}, + get ModalRoot() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.toString?.()?.includes("ENTERING") && m?.toString?.()?.includes("headerId"), {searchExports: true});}, + + /* Popouts */ + get PopoutStack() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("open", "close", "closeAll");}, + get PopoutOpener() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("openPopout");}, + get UserPopout() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.type?.toString?.().includes('Unexpected missing user'), {searchExports: true});}, + + /* Context Menus */ + get ContextMenuActions() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("openContextMenu");}, + get ContextMenuItemsGroup() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByRegex(/itemGroup/);}, + get ContextMenuItem() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByRegex(/\.label\b.*\.hint\b.*\.action\b/);}, + + /* Misc */ + get ExternalLink() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByRegex(/trusted/);}, + get TextElement() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.Sizes?.SIZE_32 && m.Colors);}, + get Anchor() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("Anchor");}, + get Flex() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("Flex");}, + get FlexChild() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Child");}, + get Clickable() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("Clickable");}, + get Titles() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("Tags", "Sizes");}, + get HeaderBar() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("HeaderBar");}, + get TabBar() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("TabBar");}, + get Tooltip() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByPrototypes("renderTooltip");}, + get Spinner() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("Spinner");}, + + /* Forms */ + get FormTitle() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("FormTitle");}, + get FormSection() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("FormSection");}, + get FormNotice() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByDisplayName("FormNotice");}, + + /* Scrollers */ + get ScrollerThin() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("ScrollerThin").ScrollerThin;}, + get ScrollerAuto() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("ScrollerAuto").ScrollerAuto;}, + get AdvancedScrollerThin() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("AdvancedScrollerThin").AdvancedScrollerThin;}, + get AdvancedScrollerAuto() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("AdvancedScrollerAuto").AdvancedScrollerAuto;}, + get AdvancedScrollerNone() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("AdvancedScrollerNone").AdvancedScrollerNone;}, + + /* Settings */ + get SettingsWrapper() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.render?.toString?.().includes("required") && m?.render?.toString?.().includes("titleClassName"), {searchExports: true});}, + get SettingsNote() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.Types?.DESCRIPTION, {searchExports: true});}, + get SettingsDivider() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => !m.defaultProps && m.prototype && m.prototype.render && m.prototype.render.toString().includes("default.divider"));}, + + get ColorPicker() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.displayName === "ColorPicker" && m?.defaultProps);}, + get Dropdown() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByProps("SingleSelect").SingleSelect;}, + get Keybind() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getByPrototypes("handleComboChange");}, + get RadioGroup() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.Sizes && m?.toString?.().includes("radioItemClassName"), {searchExports: true});}, + get Slider() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.defaultProps?.maxValue == 100 && m?.prototype?.renderMark, {searchExports: true});}, + get SwitchRow() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.toString?.().includes("tooltipNote"), {searchExports: true});}, + get Textbox() {return _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"].getModule(m => m?.defaultProps && m?.defaultProps?.type == "text", {searchExports: true});}, +})); + + +/***/ }), + +/***/ "./src/modules/discordselectors.js": +/*!*****************************************!*\ + !*** ./src/modules/discordselectors.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _discordclassmodules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./discordclassmodules */ "./src/modules/discordclassmodules.js"); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); + + + +const getSelectorAll = function(prop) { + if (!this.hasOwnProperty(prop)) return ""; + return `.${this[prop].split(" ").join(".")}`; +}; + +const getSelector = function(prop) { + if (!this.hasOwnProperty(prop)) return ""; + return `.${this[prop].split(" ")[0]}`; +}; + +/** + * Gives us a way to retrieve the internal classes as selectors without + * needing to concatenate strings or use string templates. Wraps the + * selector in {@link module:DOMTools.Selector} which adds features but can + * still be used in native function. + * + * For a list of all available class namespaces check out {@link module:DiscordClassModules}. + * + * @see module:DiscordClassModules + * @module DiscordSelectors + */ +const DiscordSelectors = new Proxy(_discordclassmodules__WEBPACK_IMPORTED_MODULE_0__["default"], { + get: function(list, item) { + if (item == "getSelectorAll" || item == "getSelector") return (module, prop) => DiscordSelectors[module][item]([prop]); + if (list[item] === undefined) return new Proxy({}, {get: function() {return "";}}); + return new Proxy(list[item], { + get: function(obj, prop) { + if (prop == "getSelectorAll") return getSelectorAll.bind(obj); + if (prop == "getSelector") return getSelector.bind(obj); + if (!obj.hasOwnProperty(prop)) return ""; + return new _domtools__WEBPACK_IMPORTED_MODULE_1__["default"].Selector(obj[prop]); + } + }); + } +}); + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (DiscordSelectors); + +/***/ }), + +/***/ "./src/modules/domtools.js": +/*!*********************************!*\ + !*** ./src/modules/domtools.js ***! + \*********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ DOMTools) +/* harmony export */ }); +/* harmony import */ var structs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! structs */ "./src/structs/structs.js"); +/** + * Helpful utilities for dealing with DOM operations. + * + * This module also extends `HTMLElement` to add a set of utility functions, + * the same as the ones available in the module itself, but with the `element` + * parameter bound to `this`. + * @module DOMTools + */ + + + +/** + * @interface + * @name Offset + * @property {number} top - Top offset of the target element. + * @property {number} right - Right offset of the target element. + * @property {number} bottom - Bottom offset of the target element. + * @property {number} left - Left offset of the target element. + * @property {number} height - Outer height of the target element. + * @property {number} width - Outer width of the target element. + */ + + /** + * Function that automatically removes added listener. + * @callback module:DOMTools~CancelListener + */ + +class DOMTools { + + static get Selector() {return structs__WEBPACK_IMPORTED_MODULE_0__.Selector;} + static get ClassName() {return structs__WEBPACK_IMPORTED_MODULE_0__.ClassName;} + static get DOMObserver() {return structs__WEBPACK_IMPORTED_MODULE_0__.DOMObserver;} + + /** + * Default DOMObserver for global usage. + * + * @see DOMObserver + */ + static get observer() { + return this._observer || (this._observer = new structs__WEBPACK_IMPORTED_MODULE_0__.DOMObserver()); + } + + /** Document/window width */ + static get screenWidth() {return Math.max(document.documentElement.clientWidth, window.innerWidth || 0);} + + /** Document/window height */ + static get screenHeight() {return Math.max(document.documentElement.clientHeight, window.innerHeight || 0);} + + static animate({timing = _ => _, update, duration}) { + // https://javascript.info/js-animation + const start = performance.now(); + + requestAnimationFrame(function renderFrame(time) { + // timeFraction goes from 0 to 1 + let timeFraction = (time - start) / duration; + if (timeFraction > 1) timeFraction = 1; + + // calculate the current animation state + const progress = timing(timeFraction); + + update(progress); // draw it + + if (timeFraction < 1) requestAnimationFrame(renderFrame); + }); + } + + /** + * Adds a style to the document. + * @param {string} id - identifier to use as the element id + * @param {string} css - css to add to the document + */ + static addStyle(id, css) { + document.head.append(DOMTools.createElement(``)); + } + + /** + * Removes a style from the document. + * @param {string} id - original identifier used + */ + static removeStyle(id) { + const element = document.getElementById(id); + if (element && element.tagName === "STYLE") element.remove(); + } + + /** + * Adds/requires a remote script to be loaded + * @param {string} id - identifier to use for this script + * @param {string} url - url from which to load the script + * @returns {Promise} promise that resolves when the script is loaded + */ + static addScript(id, url) { + return new Promise(resolve => { + const script = document.createElement("script"); + script.id = id; + script.src = url; + script.type = "text/javascript"; + script.onload = resolve; + document.head.append(script); + }); + } + + /** + * Removes a remote script from the document. + * @param {string} id - original identifier used + */ + static removeScript(id) { + const element = document.getElementById(id); + if (element && element.tagName === "SCRIPT") element.remove(); + } + + /** + * This is my shit version of not having to use `$` from jQuery. Meaning + * that you can pass a selector and it will automatically run {@link module:DOMTools.query}. + * It also means that you can pass a string of html and it will perform and return `parseHTML`. + * @see module:DOMTools.parseHTML + * @see module:DOMTools.query + * @param {string} selector - Selector to query or HTML to parse + * @returns {(DocumentFragment|NodeList|HTMLElement)} - Either the result of `parseHTML` or `query` + */ + static Q(selector) { + const element = this.parseHTML(selector); + const isHTML = element instanceof NodeList ? Array.from(element).some(n => n.nodeType === 1) : element.nodeType === 1; + if (isHTML) return element; + return this.query(selector); + } + + /** + * Essentially a shorthand for `document.querySelector`. If the `baseElement` is not provided + * `document` is used by default. + * @param {string} selector - Selector to query + * @param {Element} [baseElement] - Element to base the query from + * @returns {(Element|null)} - The found element or null if not found + */ + static query(selector, baseElement) { + if (!baseElement) baseElement = document; + return baseElement.querySelector(selector); + } + + /** + * Essentially a shorthand for `document.querySelectorAll`. If the `baseElement` is not provided + * `document` is used by default. + * @param {string} selector - Selector to query + * @param {Element} [baseElement] - Element to base the query from + * @returns {Array} - Array of all found elements + */ + static queryAll(selector, baseElement) { + if (!baseElement) baseElement = document; + return baseElement.querySelectorAll(selector); + } + + /** + * Parses a string of HTML and returns the results. If the second parameter is true, + * the parsed HTML will be returned as a document fragment {@see https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment}. + * This is extremely useful if you have a list of elements at the top level, they can then be appended all at once to another node. + * + * If the second parameter is false, then the return value will be the list of parsed + * nodes and there were multiple top level nodes, otherwise the single node is returned. + * @param {string} html - HTML to be parsed + * @param {boolean} [fragment=false] - Whether or not the return should be the raw `DocumentFragment` + * @returns {(DocumentFragment|NodeList|HTMLElement)} - The result of HTML parsing + */ + static parseHTML(html, fragment = false) { + const template = document.createElement("template"); + template.innerHTML = html; + const node = template.content.cloneNode(true); + if (fragment) return node; + return node.childNodes.length > 1 ? node.childNodes : node.childNodes[0]; + } + + /** Alternate name for {@link module:DOMTools.parseHTML} */ + static createElement(html, fragment = false) {return this.parseHTML(html, fragment);} + + /** + * Takes a string of html and escapes it using the brower's own escaping mechanism. + * @param {String} html - html to be escaped + */ + static escapeHTML(html) { + const textNode = document.createTextNode(""); + const spanElement = document.createElement("span"); + spanElement.append(textNode); + textNode.nodeValue = html; + return spanElement.innerHTML; + } + + /** + * Takes a string and escapes it for use as a DOM id. + * @param {String} id - string to be escaped + */ + static escapeID(id) { + return id.replace(/^[^a-z]+|[^\w-]+/gi, "-"); + } + + /** + * Adds a list of classes from the target element. + * @param {Element} element - Element to edit classes of + * @param {...string} classes - Names of classes to add + * @returns {Element} - `element` to allow for chaining + */ + static addClass(element, ...classes) { + classes = classes.flat().filter(c => c); + for (let c = 0; c < classes.length; c++) classes[c] = classes[c].toString().split(" "); + classes = classes.flat().filter(c => c); + element.classList.add(...classes); + return element; + } + + /** + * Removes a list of classes from the target element. + * @param {Element} element - Element to edit classes of + * @param {...string} classes - Names of classes to remove + * @returns {Element} - `element` to allow for chaining + */ + static removeClass(element, ...classes) { + for (let c = 0; c < classes.length; c++) classes[c] = classes[c].toString().split(" "); + classes = classes.flat().filter(c => c); + element.classList.remove(...classes); + return element; + } + + /** + * When only one argument is present: Toggle class value; + * i.e., if class exists then remove it and return false, if not, then add it and return true. + * When a second argument is present: + * If the second argument evaluates to true, add specified class value, and if it evaluates to false, remove it. + * @param {Element} element - Element to edit classes of + * @param {string} classname - Name of class to toggle + * @param {boolean} [indicator] - Optional indicator for if the class should be toggled + * @returns {Element} - `element` to allow for chaining + */ + static toggleClass(element, classname, indicator) { + classname = classname.toString().split(" ").filter(c => c); + if (typeof(indicator) !== "undefined") classname.forEach(c => element.classList.toggle(c, indicator)); + else classname.forEach(c => element.classList.toggle(c)); + return element; + } + + /** + * Checks if an element has a specific class + * @param {Element} element - Element to edit classes of + * @param {string} classname - Name of class to check + * @returns {boolean} - `true` if the element has the class, `false` otherwise. + */ + static hasClass(element, classname) { + return classname.toString().split(" ").filter(c => c).every(c => element.classList.contains(c)); + } + + /** + * Replaces one class with another + * @param {Element} element - Element to edit classes of + * @param {string} oldName - Name of class to replace + * @param {string} newName - New name for the class + * @returns {Element} - `element` to allow for chaining + */ + static replaceClass(element, oldName, newName) { + element.classList.replace(oldName, newName); + return element; + } + + /** + * Appends `thisNode` to `thatNode` + * @param {Node} thisNode - Node to be appended to another node + * @param {Node} thatNode - Node for `thisNode` to be appended to + * @returns {Node} - `thisNode` to allow for chaining + */ + static appendTo(thisNode, thatNode) { + if (typeof(thatNode) == "string") thatNode = this.query(thatNode); + if (!thatNode) return null; + thatNode.append(thisNode); + return thisNode; + } + + /** + * Prepends `thisNode` to `thatNode` + * @param {Node} thisNode - Node to be prepended to another node + * @param {Node} thatNode - Node for `thisNode` to be prepended to + * @returns {Node} - `thisNode` to allow for chaining + */ + static prependTo(thisNode, thatNode) { + if (typeof(thatNode) == "string") thatNode = this.query(thatNode); + if (!thatNode) return null; + thatNode.prepend(thisNode); + return thisNode; + } + + /** + * Insert after a specific element, similar to jQuery's `thisElement.insertAfter(otherElement)`. + * @param {Node} thisNode - The node to insert + * @param {Node} targetNode - Node to insert after in the tree + * @returns {Node} - `thisNode` to allow for chaining + */ + static insertAfter(thisNode, targetNode) { + targetNode.parentNode.insertBefore(thisNode, targetNode.nextSibling); + return thisNode; + } + + /** + * Insert after a specific element, similar to jQuery's `thisElement.after(newElement)`. + * @param {Node} thisNode - The node to insert + * @param {Node} newNode - Node to insert after in the tree + * @returns {Node} - `thisNode` to allow for chaining + */ + static after(thisNode, newNode) { + thisNode.parentNode.insertBefore(newNode, thisNode.nextSibling); + return thisNode; + } + + /** + * Gets the next sibling element that matches the selector. + * @param {Element} element - Element to get the next sibling of + * @param {string} [selector=""] - Optional selector + * @returns {Element} - The sibling element + */ + static next(element, selector = "") { + return selector ? element.querySelector("+ " + selector) : element.nextElementSibling; + } + + /** + * Gets all subsequent siblings. + * @param {Element} element - Element to get next siblings of + * @returns {NodeList} - The list of siblings + */ + static nextAll(element) { + return element.querySelectorAll("~ *"); + } + + /** + * Gets the subsequent siblings until an element matches the selector. + * @param {Element} element - Element to get the following siblings of + * @param {string} selector - Selector to stop at + * @returns {Array} - The list of siblings + */ + static nextUntil(element, selector) { + const next = []; + while (element.nextElementSibling && !element.nextElementSibling.matches(selector)) next.push(element = element.nextElementSibling); + return next; + } + + /** + * Gets the previous sibling element that matches the selector. + * @param {Element} element - Element to get the previous sibling of + * @param {string} [selector=""] - Optional selector + * @returns {Element} - The sibling element + */ + static previous(element, selector = "") { + const previous = element.previousElementSibling; + if (selector) return previous && previous.matches(selector) ? previous : null; + return previous; + } + + /** + * Gets all preceeding siblings. + * @param {Element} element - Element to get preceeding siblings of + * @returns {NodeList} - The list of siblings + */ + static previousAll(element) { + const previous = []; + while (element.previousElementSibling) previous.push(element = element.previousElementSibling); + return previous; + } + + /** + * Gets the preceeding siblings until an element matches the selector. + * @param {Element} element - Element to get the preceeding siblings of + * @param {string} selector - Selector to stop at + * @returns {Array} - The list of siblings + */ + static previousUntil(element, selector) { + const previous = []; + while (element.previousElementSibling && !element.previousElementSibling.matches(selector)) previous.push(element = element.previousElementSibling); + return previous; + } + + /** + * Find which index in children a certain node is. Similar to jQuery's `$.index()` + * @param {HTMLElement} node - The node to find its index in parent + * @returns {number} Index of the node + */ + static indexInParent(node) { + const children = node.parentNode.childNodes; + let num = 0; + for (let i = 0; i < children.length; i++) { + if (children[i] == node) return num; + if (children[i].nodeType == 1) num++; + } + return -1; + } + + /** Shorthand for {@link module:DOMTools.indexInParent} */ + static index(node) {return this.indexInParent(node);} + + /** + * Gets the parent of the element if it matches the selector, + * otherwise returns null. + * @param {Element} element - Element to get parent of + * @param {string} [selector=""] - Selector to match parent + * @returns {(Element|null)} - The sibling element or null + */ + static parent(element, selector = "") { + return !selector || element.parentElement.matches(selector) ? element.parentElement : null; + } + + /** + * Gets all children of Element that match the selector if provided. + * @param {Element} element - Element to get all children of + * @param {string} selector - Selector to match the children to + * @returns {Array} - The list of children + */ + static findChild(element, selector) { + return element.querySelector(":scope > " + selector); + } + + /** + * Gets all children of Element that match the selector if provided. + * @param {Element} element - Element to get all children of + * @param {string} selector - Selector to match the children to + * @returns {Array} - The list of children + */ + static findChildren(element, selector) { + return element.querySelectorAll(":scope > " + selector); + } + + /** + * Gets all ancestors of Element that match the selector if provided. + * @param {Element} element - Element to get all parents of + * @param {string} [selector=""] - Selector to match the parents to + * @returns {Array} - The list of parents + */ + static parents(element, selector = "") { + const parents = []; + if (selector) while (element.parentElement && element.parentElement.closest(selector)) parents.push(element = element.parentElement.closest(selector)); + else while (element.parentElement) parents.push(element = element.parentElement); + return parents; + } + + /** + * Gets the ancestors until an element matches the selector. + * @param {Element} element - Element to get the ancestors of + * @param {string} selector - Selector to stop at + * @returns {Array} - The list of parents + */ + static parentsUntil(element, selector) { + const parents = []; + while (element.parentElement && !element.parentElement.matches(selector)) parents.push(element = element.parentElement); + return parents; + } + + /** + * Gets all siblings of the element that match the selector. + * @param {Element} element - Element to get all siblings of + * @param {string} [selector="*"] - Selector to match the siblings to + * @returns {Array} - The list of siblings + */ + static siblings(element, selector = "*") { + return Array.from(element.parentElement.children).filter(e => e != element && e.matches(selector)); + } + + /** + * Sets or gets css styles for a specific element. If `value` is provided + * then it sets the style and returns the element to allow for chaining, + * otherwise returns the style. + * @param {Element} element - Element to set the CSS of + * @param {string} attribute - Attribute to get or set + * @param {string} [value] - Value to set for attribute + * @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned. + */ + static css(element, attribute, value) { + if (typeof(value) == "undefined") return global.getComputedStyle(element)[attribute]; + element.style[attribute] = value; + return element; + } + + /** + * Sets or gets the width for a specific element. If `value` is provided + * then it sets the width and returns the element to allow for chaining, + * otherwise returns the width. + * @param {Element} element - Element to set the CSS of + * @param {string} [value] - Width to set + * @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned. + */ + static width(element, value) { + if (typeof(value) == "undefined") return parseInt(getComputedStyle(element).width); + element.style.width = value; + return element; + } + + /** + * Sets or gets the height for a specific element. If `value` is provided + * then it sets the height and returns the element to allow for chaining, + * otherwise returns the height. + * @param {Element} element - Element to set the CSS of + * @param {string} [value] - Height to set + * @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned. + */ + static height(element, value) { + if (typeof(value) == "undefined") return parseInt(getComputedStyle(element).height); + element.style.height = value; + return element; + } + + /** + * Sets the inner text of an element if given a value, otherwise returns it. + * @param {Element} element - Element to set the text of + * @param {string} [text] - Content to set + * @returns {string} - Either the string set by this call or the current text content of the node. + */ + static text(element, text) { + if (typeof(text) == "undefined") return element.textContent; + return element.textContent = text; + } + + /** + * Returns the innerWidth of the element. + * @param {Element} element - Element to retrieve inner width of + * @return {number} - The inner width of the element. + */ + static innerWidth(element) { + return element.clientWidth; + } + + /** + * Returns the innerHeight of the element. + * @param {Element} element - Element to retrieve inner height of + * @return {number} - The inner height of the element. + */ + static innerHeight(element) { + return element.clientHeight; + } + + /** + * Returns the outerWidth of the element. + * @param {Element} element - Element to retrieve outer width of + * @return {number} - The outer width of the element. + */ + static outerWidth(element) { + return element.offsetWidth; + } + + /** + * Returns the outerHeight of the element. + * @param {Element} element - Element to retrieve outer height of + * @return {number} - The outer height of the element. + */ + static outerHeight(element) { + return element.offsetHeight; + } + + /** + * Gets the offset of the element in the page. + * @param {Element} element - Element to get offset of + * @return {Offset} - The offset of the element + */ + static offset(element) { + return element.getBoundingClientRect(); + } + + static get listeners() {return this._listeners || (this._listeners = {});} + + /** + * This is similar to jQuery's `on` function and can *hopefully* be used in the same way. + * + * Rather than attempt to explain, I'll show some example usages. + * + * The following will add a click listener (in the `myPlugin` namespace) to `element`. + * `DOMTools.on(element, "click.myPlugin", () => {console.log("clicked!");});` + * + * The following will add a click listener (in the `myPlugin` namespace) to `element` that only fires when the target is a `.block` element. + * `DOMTools.on(element, "click.myPlugin", ".block", () => {console.log("clicked!");});` + * + * The following will add a click listener (without namespace) to `element`. + * `DOMTools.on(element, "click", () => {console.log("clicked!");});` + * + * The following will add a click listener (without namespace) to `element` that only fires once. + * `const cancel = DOMTools.on(element, "click", () => {console.log("fired!"); cancel();});` + * + * @param {Element} element - Element to add listener to + * @param {string} event - Event to listen to with option namespace (e.g. "event.namespace") + * @param {(string|callable)} delegate - Selector to run on element to listen to + * @param {callable} [callback] - Function to fire on event + * @returns {module:DOMTools~CancelListener} - A function that will undo the listener + */ + static on(element, event, delegate, callback) { + const [type, namespace] = event.split("."); + const hasDelegate = delegate && callback; + if (!callback) callback = delegate; + const eventFunc = !hasDelegate ? callback : function(ev) { + if (ev.target.matches(delegate)) { + callback(ev); + } + }; + + element.addEventListener(type, eventFunc); + const cancel = () => { + element.removeEventListener(type, eventFunc); + }; + if (namespace) { + if (!this.listeners[namespace]) this.listeners[namespace] = []; + const newCancel = () => { + cancel(); + this.listeners[namespace].splice(this.listeners[namespace].findIndex(l => l.event == type && l.element == element), 1); + }; + this.listeners[namespace].push({ + event: type, + element: element, + cancel: newCancel + }); + return newCancel; + } + return cancel; + } + + /** + * Functionality for this method matches {@link module:DOMTools.on} but automatically cancels itself + * and removes the listener upon the first firing of the desired event. + * + * @param {Element} element - Element to add listener to + * @param {string} event - Event to listen to with option namespace (e.g. "event.namespace") + * @param {(string|callable)} delegate - Selector to run on element to listen to + * @param {callable} [callback] - Function to fire on event + * @returns {module:DOMTools~CancelListener} - A function that will undo the listener + */ + static once(element, event, delegate, callback) { + const [type, namespace] = event.split("."); + const hasDelegate = delegate && callback; + if (!callback) callback = delegate; + const eventFunc = !hasDelegate ? function(ev) { + callback(ev); + element.removeEventListener(type, eventFunc); + } : function(ev) { + if (!ev.target.matches(delegate)) return; + callback(ev); + element.removeEventListener(type, eventFunc); + }; + + element.addEventListener(type, eventFunc); + const cancel = () => { + element.removeEventListener(type, eventFunc); + }; + if (namespace) { + if (!this.listeners[namespace]) this.listeners[namespace] = []; + const newCancel = () => { + cancel(); + this.listeners[namespace].splice(this.listeners[namespace].findIndex(l => l.event == type && l.element == element), 1); + }; + this.listeners[namespace].push({ + event: type, + element: element, + cancel: newCancel + }); + return newCancel; + } + return cancel; + } + + static __offAll(event, element) { + const [type, namespace] = event.split("."); + let matchFilter = listener => listener.event == type, defaultFilter = _ => _; + if (element) { + matchFilter = l => l.event == type && l.element == element; + defaultFilter = l => l.element == element; + } + const listeners = this.listeners[namespace] || []; + const list = type ? listeners.filter(matchFilter) : listeners.filter(defaultFilter); + for (let c = 0; c < list.length; c++) list[c].cancel(); + } + + /** + * This is similar to jQuery's `off` function and can *hopefully* be used in the same way. + * + * Rather than attempt to explain, I'll show some example usages. + * + * The following will remove a click listener called `onClick` (in the `myPlugin` namespace) from `element`. + * `DOMTools.off(element, "click.myPlugin", onClick);` + * + * The following will remove a click listener called `onClick` (in the `myPlugin` namespace) from `element` that only fired when the target is a `.block` element. + * `DOMTools.off(element, "click.myPlugin", ".block", onClick);` + * + * The following will remove a click listener (without namespace) from `element`. + * `DOMTools.off(element, "click", onClick);` + * + * The following will remove all listeners in namespace `myPlugin` from `element`. + * `DOMTools.off(element, ".myPlugin");` + * + * The following will remove all click listeners in namespace `myPlugin` from *all elements*. + * `DOMTools.off("click.myPlugin");` + * + * The following will remove all listeners in namespace `myPlugin` from *all elements*. + * `DOMTools.off(".myPlugin");` + * + * @param {(Element|string)} element - Element to remove listener from + * @param {string} [event] - Event to listen to with option namespace (e.g. "event.namespace") + * @param {(string|callable)} [delegate] - Selector to run on element to listen to + * @param {callable} [callback] - Function to fire on event + * @returns {Element} - The original element to allow for chaining + */ + static off(element, event, delegate, callback) { + if (typeof(element) == "string") return this.__offAll(element); + const [type, namespace] = event.split("."); + if (namespace) return this.__offAll(event, element); + + const hasDelegate = delegate && callback; + if (!callback) callback = delegate; + const eventFunc = !hasDelegate ? callback : function(ev) { + if (ev.target.matches(delegate)) { + callback(ev); + } + }; + + element.removeEventListener(type, eventFunc); + return element; + } + + /** + * Adds a listener for when the node is added/removed from the document body. + * The listener is automatically removed upon firing. + * @param {HTMLElement} node - node to wait for + * @param {callable} callback - function to be performed on event + * @param {boolean} onMount - determines if it should fire on Mount or on Unmount + */ + static onMountChange(node, callback, onMount = true) { + const wrappedCallback = () => { + this.observer.unsubscribe(wrappedCallback); + callback(); + }; + this.observer.subscribe(wrappedCallback, mutation => { + const nodes = Array.from(onMount ? mutation.addedNodes : mutation.removedNodes); + const directMatch = nodes.indexOf(node) > -1; + const parentMatch = nodes.some(parent => parent.contains(node)); + return directMatch || parentMatch; + }); + return node; + } + + /** Shorthand for {@link module:DOMTools.onMountChange} with third parameter `true` */ + static onMount(node, callback) {return this.onMountChange(node, callback);} + + /** Shorthand for {@link module:DOMTools.onMountChange} with third parameter `false` */ + static onUnmount(node, callback) {return this.onMountChange(node, callback, false);} + + /** Alias for {@link module:DOMTools.onMount} */ + static onAdded(node, callback) {return this.onMount(node, callback);} + + /** Alias for {@link module:DOMTools.onUnmount} */ + static onRemoved(node, callback) {return this.onUnmount(node, callback, false);} + + /** + * Helper function which combines multiple elements into one parent element + * @param {Array} elements - array of elements to put into a single parent + */ + static wrap(elements) { + const domWrapper = this.parseHTML(`
`); + for (let e = 0; e < elements.length; e++) domWrapper.appendChild(elements[e]); + return domWrapper; + } + + /** + * Resolves the node to an HTMLElement. This is mainly used by library modules. + * @param {(jQuery|Element)} node - node to resolve + */ + static resolveElement(node) { + try { + if (!(node instanceof window.jQuery) && !(node instanceof Element)) return undefined; + return node instanceof window.jQuery ? node[0] : node; + } + catch { + return node; + } + } +} + +/***/ }), + +/***/ "./src/modules/logger.js": +/*!*******************************!*\ + !*** ./src/modules/logger.js ***! + \*******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "LogTypes": () => (/* binding */ LogTypes), +/* harmony export */ "default": () => (/* binding */ Logger) +/* harmony export */ }); +/** + * Simple logger for the lib and plugins. + * + * @module Logger + */ + +/* eslint-disable no-console */ + +/** + * List of logging types. + */ +const LogTypes = { + /** Alias for error */ + err: "error", + error: "error", + /** Alias for debug */ + dbg: "debug", + debug: "debug", + log: "log", + warn: "warn", + info: "info" +}; + +class Logger { + + /** + * Logs an error using a collapsed error group with stacktrace. + * + * @param {string} module - Name of the calling module. + * @param {string} message - Message or error to have logged. + * @param {Error} error - Error object to log with the message. + */ + static stacktrace(module, message, error) { + console.error(`%c[${module}]%c ${message}\n\n%c`, "color: #3a71c1; font-weight: 700;", "color: red; font-weight: 700;", "color: red;", error); + } + + /** + * Logs using error formatting. For logging an actual error object consider {@link module:Logger.stacktrace} + * + * @param {string} module - Name of the calling module. + * @param {string} message - Messages to have logged. + */ + static err(module, ...message) {Logger._log(module, message, "error");} + + /** + * Logs a warning message. + * + * @param {string} module - Name of the calling module. + * @param {...any} message - Messages to have logged. + */ + static warn(module, ...message) {Logger._log(module, message, "warn");} + + /** + * Logs an informational message. + * + * @param {string} module - Name of the calling module. + * @param {...any} message - Messages to have logged. + */ + static info(module, ...message) {Logger._log(module, message, "info");} + + /** + * Logs used for debugging purposes. + * + * @param {string} module - Name of the calling module. + * @param {...any} message - Messages to have logged. + */ + static debug(module, ...message) {Logger._log(module, message, "debug");} + + /** + * Logs used for basic loggin. + * + * @param {string} module - Name of the calling module. + * @param {...any} message - Messages to have logged. + */ + static log(module, ...message) {Logger._log(module, message);} + + /** + * Logs strings using different console levels and a module label. + * + * @param {string} module - Name of the calling module. + * @param {any|Array} message - Messages to have logged. + * @param {module:Logger.LogTypes} type - Type of log to use in console. + */ + static _log(module, message, type = "log") { + type = Logger.parseType(type); + if (!Array.isArray(message)) message = [message]; + console[type](`%c[${module}]%c`, "color: #3a71c1; font-weight: 700;", "", ...message); + } + + static parseType(type) { + return LogTypes.hasOwnProperty(type) ? LogTypes[type] : "log"; + } + +} + +/***/ }), + +/***/ "./src/modules/modules.js": +/*!********************************!*\ + !*** ./src/modules/modules.js ***! + \********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "ColorConverter": () => (/* reexport safe */ _colorconverter__WEBPACK_IMPORTED_MODULE_3__["default"]), +/* harmony export */ "DOMTools": () => (/* reexport safe */ _domtools__WEBPACK_IMPORTED_MODULE_4__["default"]), +/* harmony export */ "DiscordClassModules": () => (/* reexport safe */ _discordclassmodules__WEBPACK_IMPORTED_MODULE_13__["default"]), +/* harmony export */ "DiscordClasses": () => (/* reexport safe */ _discordclasses__WEBPACK_IMPORTED_MODULE_5__["default"]), +/* harmony export */ "DiscordModules": () => (/* reexport safe */ _discordmodules__WEBPACK_IMPORTED_MODULE_2__["default"]), +/* harmony export */ "DiscordSelectors": () => (/* reexport safe */ _discordselectors__WEBPACK_IMPORTED_MODULE_6__["default"]), +/* harmony export */ "Filters": () => (/* reexport safe */ _webpackmodules__WEBPACK_IMPORTED_MODULE_1__.Filters), +/* harmony export */ "Logger": () => (/* reexport safe */ _logger__WEBPACK_IMPORTED_MODULE_9__["default"]), +/* harmony export */ "Patcher": () => (/* reexport safe */ _patcher__WEBPACK_IMPORTED_MODULE_10__["default"]), +/* harmony export */ "PluginUpdater": () => (/* reexport safe */ _pluginupdater__WEBPACK_IMPORTED_MODULE_11__["default"]), +/* harmony export */ "PluginUtilities": () => (/* reexport safe */ _pluginutilities__WEBPACK_IMPORTED_MODULE_12__["default"]), +/* harmony export */ "ReactComponents": () => (/* reexport safe */ _reactcomponents__WEBPACK_IMPORTED_MODULE_8__["default"]), +/* harmony export */ "ReactTools": () => (/* reexport safe */ _reacttools__WEBPACK_IMPORTED_MODULE_7__["default"]), +/* harmony export */ "Structs": () => (/* reexport module object */ structs__WEBPACK_IMPORTED_MODULE_14__), +/* harmony export */ "Utilities": () => (/* reexport safe */ _utilities__WEBPACK_IMPORTED_MODULE_0__["default"]), +/* harmony export */ "WebpackModules": () => (/* reexport safe */ _webpackmodules__WEBPACK_IMPORTED_MODULE_1__["default"]) +/* harmony export */ }); +/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js"); +/* harmony import */ var _webpackmodules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./webpackmodules */ "./src/modules/webpackmodules.js"); +/* harmony import */ var _discordmodules__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./discordmodules */ "./src/modules/discordmodules.js"); +/* harmony import */ var _colorconverter__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./colorconverter */ "./src/modules/colorconverter.js"); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); +/* harmony import */ var _discordclasses__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./discordclasses */ "./src/modules/discordclasses.js"); +/* harmony import */ var _discordselectors__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./discordselectors */ "./src/modules/discordselectors.js"); +/* harmony import */ var _reacttools__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./reacttools */ "./src/modules/reacttools.js"); +/* harmony import */ var _reactcomponents__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./reactcomponents */ "./src/modules/reactcomponents.js"); +/* harmony import */ var _logger__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./logger */ "./src/modules/logger.js"); +/* harmony import */ var _patcher__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./patcher */ "./src/modules/patcher.js"); +/* harmony import */ var _pluginupdater__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./pluginupdater */ "./src/modules/pluginupdater.js"); +/* harmony import */ var _pluginutilities__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./pluginutilities */ "./src/modules/pluginutilities.js"); +/* harmony import */ var _discordclassmodules__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./discordclassmodules */ "./src/modules/discordclassmodules.js"); +/* harmony import */ var structs__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! structs */ "./src/structs/structs.js"); + + + + + + + + + + + + + + + + + + + + +/***/ }), + +/***/ "./src/modules/patcher.js": +/*!********************************!*\ + !*** ./src/modules/patcher.js ***! + \********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Patcher) +/* harmony export */ }); +/** + * Patcher that can patch other functions allowing you to run code before, after or + * instead of the original function. Can also alter arguments and return values. + * + * This is a modified version of what we have been working on in BDv2. {@link https://github.com/JsSucks/BetterDiscordApp/blob/master/client/src/modules/patcher.js} + * + * @module Patcher + */ + +class Patcher { + + // Use window._patches instead of local variables in case something tries to whack the lib + static get patches() {return [];} + + /** + * Returns all the patches done by a specific caller + * @param {string} name - Name of the patch caller + * @method + */ + static getPatchesByCaller(name) { + return BdApi.Patcher.getPatchesByCaller(name); + } + + /** + * Unpatches all patches passed, or when a string is passed unpatches all + * patches done by that specific caller. + * @param {Array|string} patches - Either an array of patches to unpatch or a caller name + */ + static unpatchAll(patches) { + BdApi.Patcher.unpatchAll(patches); + } + + /** + * Function with no arguments and no return value that may be called to revert changes made by {@link module:Patcher}, restoring (unpatching) original method. + * @callback module:Patcher~unpatch + */ + + /** + * A callback that modifies method logic. This callback is called on each call of the original method and is provided all data about original call. Any of the data can be modified if necessary, but do so wisely. + * + * The third argument for the callback will be `undefined` for `before` patches. `originalFunction` for `instead` patches and `returnValue` for `after` patches. + * + * @callback module:Patcher~patchCallback + * @param {object} thisObject - `this` in the context of the original function. + * @param {args} args - The original arguments of the original function. + * @param {(function|*)} extraValue - For `instead` patches, this is the original function from the module. For `after` patches, this is the return value of the function. + * @return {*} Makes sense only when using an `instead` or `after` patch. If something other than `undefined` is returned, the returned value replaces the value of `returnValue`. If used for `before` the return value is ignored. + */ + + /** + * This method patches onto another function, allowing your code to run beforehand. + * Using this, you are also able to modify the incoming arguments before the original method is run. + * + * @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""` if you don't care. + * @param {object} moduleToPatch - Object with the function to be patched. Can also patch an object's prototype. + * @param {string} functionName - Name of the method to be patched + * @param {module:Patcher~patchCallback} callback - Function to run before the original method + * @return {module:Patcher~unpatch} Function with no arguments and no return value that should be called to cancel (unpatch) this patch. You should save and run it when your plugin is stopped. + */ + static before(caller, moduleToPatch, functionName, callback) {return BdApi.Patcher.before(caller, moduleToPatch, functionName, callback);} + + /** + * This method patches onto another function, allowing your code to run after. + * Using this, you are also able to modify the return value, using the return of your code instead. + * + * @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""` if you don't care. + * @param {object} moduleToPatch - Object with the function to be patched. Can also patch an object's prototype. + * @param {string} functionName - Name of the method to be patched + * @param {module:Patcher~patchCallback} callback - Function to run instead of the original method + * @return {module:Patcher~unpatch} Function with no arguments and no return value that should be called to cancel (unpatch) this patch. You should save and run it when your plugin is stopped. + */ + static after(caller, moduleToPatch, functionName, callback) {return BdApi.Patcher.after(caller, moduleToPatch, functionName, callback);} + + /** + * This method patches onto another function, allowing your code to run instead. + * Using this, you are also able to modify the return value, using the return of your code instead. + * + * @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""` if you don't care. + * @param {object} moduleToPatch - Object with the function to be patched. Can also patch an object's prototype. + * @param {string} functionName - Name of the method to be patched + * @param {module:Patcher~patchCallback} callback - Function to run after the original method + * @return {module:Patcher~unpatch} Function with no arguments and no return value that should be called to cancel (unpatch) this patch. You should save and run it when your plugin is stopped. + */ + static instead(caller, moduleToPatch, functionName, callback) {return BdApi.Patcher.instead(caller, moduleToPatch, functionName, callback);} + +} + +/***/ }), + +/***/ "./src/modules/pluginupdater.js": +/*!**************************************!*\ + !*** ./src/modules/pluginupdater.js ***! + \**************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ PluginUpdater) +/* harmony export */ }); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); +/* harmony import */ var ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ui */ "./src/ui/ui.js"); +/* harmony import */ var _styles_updates_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../styles/updates.css */ "./src/styles/updates.css"); + + + + + +const fileSystem = require("fs"); +const path = require("path"); +const request = require("request"); + +/** + * Comparator that takes the current version and the remote version, + * then compares them returning `true` if there is an update and `false` otherwise. + * @param {string} currentVersion - the current version of the plugin + * @param {string} remoteVersion - the remote version of the plugin + * @returns {boolean} - whether the plugin has an update or not + * @callback module:PluginUpdater~comparator + */ + +const splitRegex = /[^\S\r\n]*?\r?(?:\r\n|\n)[^\S\r\n]*?\*[^\S\r\n]?/; +const escapedAtRegex = /^\\@/; +const HOUR_IN_MILLISECONDS = 1000 * 60 * 60; +const pluginId = name => name + "-update-notice"; +const pending = []; +const banner = {}; + + +/** + * Functions that check for and update existing plugins. + * @module PluginUpdater + * @deprecated It is recommended to go through the approval process instead. + */ +class PluginUpdater { + + static get CSS() {return _styles_updates_css__WEBPACK_IMPORTED_MODULE_2__["default"];} + static get state() {return window.__PLUGIN_UPDATES__;} + static getPlugin(link) {return this.state.plugins[link];} + static setPlugin(name, raw, version, comparator) {this.state.plugins[raw] = {name, raw, version, comparator};} + static clearPending() { + delete banner.close; + delete banner.notice; + pending.splice(0, pending.length); + } + + /** + * Checks for updates for the specified plugin at the specified link. The final + * parameter should link to the raw text of the plugin and will compare semantic + * versions. + * @param {string} pluginName - name of the plugin + * @param {string} currentVersion - current version (semantic versioning only) + * @param {string} updateURL - url to check for update + * @param {module:PluginUpdater~comparator} [comparator] - comparator that determines if there is an update. If not provided uses {@link module:PluginUpdater.defaultComparator}. + */ + static async checkForUpdate(pluginName, currentVersion, addonId, comparator) { + if (!pluginName || !currentVersion || !addonId) return; + let isUrl = false; + try { + // eslint-disable-next-line no-new + new URL(addonId); + isUrl = true; + } + catch { + isUrl = false; + } + let updateLink = `https://betterdiscord.app/gh-redirect?id=${addonId}`; + if (isUrl) updateLink = addonId; + if (typeof(comparator) != "function") comparator = this.defaultComparator; + this.setPlugin(pluginName, updateLink, currentVersion, comparator); + + const hasUpdate = await this.hasUpdate(updateLink); + if (!hasUpdate) return; + pending.push(updateLink); + this.showUpdateNotice(updateLink); + } + + static async checkAllPlugins() { + for (const link in this.state.plugins) { + const hasUpdate = await this.hasUpdate(link); + if (!hasUpdate) return; + pending.push(link); + this.showUpdateNotice(link); + } + } + + /** + * Will check for updates and automatically show or remove the update notice + * bar based on the internal result. Better not to call this directly and to + * instead use {@link module:PluginUpdater.checkForUpdate}. + * @param {string} pluginName - name of the plugin to check + * @param {string} updateLink - link to the raw text version of the plugin + */ + static async hasUpdate(updateLink) { + const doit = (resolve, result) => { + try { + const plugin = this.getPlugin(updateLink); + const meta = this.parseMeta(result); + plugin.remoteVersion = meta.version; + const hasUpdate = plugin.comparator(plugin.version, plugin.remoteVersion); + if (hasUpdate) plugin.remote = result; + resolve(hasUpdate); + } + catch (err) { + resolve(false); + } + }; + return new Promise(resolve => { + request(updateLink, (err, resp, result) => { + if (err) return resolve(false); + + // If a direct url was used + if (resp.statusCode === 200) return doit(resolve, result); + + // If an addon id and redirect was used + if (resp.statusCode === 302) { + request(resp.headers.location, (error, response, body) => { + if (error || response.statusCode !== 200) return resolve(false); + return doit(resolve, body); + }); + } + }); + }); + } + + /** + * @param {string} pluginName - name of the plugin to download + * @param {string} updateLink - link to the raw text version of the plugin + */ + static async updatePlugin(updateLink) { + const plugin = this.getPlugin(updateLink); + + let filename = updateLink.split("/"); + filename = filename[filename.length - 1]; + const file = path.join(BdApi.Plugins.folder, filename); + await new Promise(r => fileSystem.writeFile(file, plugin.remote, r)); + ui__WEBPACK_IMPORTED_MODULE_1__.Toasts.success(`${plugin.name} ${plugin.version} has been replaced by ${plugin.name} ${plugin.remoteVersion}`); + } + + /** + * Will show the update notice top bar seen in Discord. Better not to call + * this directly and to instead use {@link module:PluginUpdater.checkForUpdate}. + * @param {string} pluginName - name of the plugin + * @param {string} updateLink - link to the raw text version of the plugin + */ + static showUpdateNotice(updateLink) { + const plugin = this.getPlugin(updateLink); + const pluginNoticeID = pluginId(plugin.name); + if (document.getElementById(pluginNoticeID)) return; // This plugin already shown + if (!document.getElementById("plugin-update-notice-message")) { + banner.notice = _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].parseHTML(`The following plugins have updates:  `); + banner.close = BdApi.showNotice(banner.notice, { + timeout: 0, + buttons: [{ + label: "Update All", + onClick: async () => { + for (const link of pending) await this.updatePlugin(link); + banner.close(); + } + }] + }); + _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].onRemoved(banner.notice, this.clearPending); + } + + const outdatedPlugins = document.getElementById("outdated-plugins"); + const pluginNoticeElement = _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].parseHTML(`${plugin.name}`); + pluginNoticeElement.addEventListener("click", async () => { + await this.updatePlugin(updateLink); + this.removeUpdateNotice(updateLink); + }); + if (outdatedPlugins.querySelectorAll("span").length) outdatedPlugins.append(_domtools__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(", ")); + outdatedPlugins.append(pluginNoticeElement); + ui__WEBPACK_IMPORTED_MODULE_1__.Tooltip.create(pluginNoticeElement, "Click To Update!", {side: "bottom"}); + } + + /** + * Will remove the plugin from the update notice top bar seen in Discord. + * Better not to call this directly and to instead use {@link module:PluginUpdater.checkForUpdate}. + * @param {string} pluginName - name of the plugin + */ + static removeUpdateNotice(updateLink) { + const plugin = this.getPlugin(updateLink); + if (!document.getElementById("outdated-plugins")) return; + const notice = document.getElementById(pluginId(plugin.name)); + if (notice) { + if (notice.nextElementSibling && notice.nextElementSibling.matches(".separator")) notice.nextElementSibling.remove(); + else if (notice.previousElementSibling && notice.previousElementSibling.matches(".separator")) notice.previousElementSibling.remove(); + notice.remove(); + } + + if (!document.getElementById("outdated-plugins").querySelectorAll("span").length) { + banner?.close(); + } + } + + static parseMeta(fileContent) { + const block = fileContent.split("/**", 2)[1].split("*/", 1)[0]; + const out = {}; + let field = ""; + let accum = ""; + for (const line of block.split(splitRegex)) { + if (line.length === 0) continue; + if (line.charAt(0) === "@" && line.charAt(1) !== " ") { + out[field] = accum; + const l = line.indexOf(" "); + field = line.substring(1, l); + accum = line.substring(l + 1); + } + else { + accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@"); + } + } + out[field] = accum.trim(); + delete out[""]; + out.format = "jsdoc"; + return out; + } + + /** + * The default comparator used as {@link module:PluginUpdater~comparator} for {@link module:PluginUpdater.checkForUpdate}. + * This solely compares remote > local. You do not need to provide this as a comparator if your plugin adheres + * to this style as this will be used as default. + * @param {string} currentVersion + * @param {string} content + */ + static defaultComparator(currentVersion, remoteVersion) { + return remoteVersion > currentVersion; + } +} + +if (typeof(window.__PLUGIN_UPDATES__) === "undefined") window.__PLUGIN_UPDATES__ = {plugins: {}}; +if (window.__PLUGIN_UPDATES__.interval) clearInterval(window.__PLUGIN_UPDATES__.interval); + +window.__PLUGIN_UPDATES__.interval = setInterval(PluginUpdater.checkAllPlugins.bind(PluginUpdater), HOUR_IN_MILLISECONDS * 2); + +// Transition +if (window.PluginUpdates) { + if (window.PluginUpdates.interval) clearInterval(window.PluginUpdates.interval); + Object.assign(window.__PLUGIN_UPDATES__.plugins, window.PluginUpdates.plugins); + delete window.PluginUpdates; +} + +/***/ }), + +/***/ "./src/modules/pluginutilities.js": +/*!****************************************!*\ + !*** ./src/modules/pluginutilities.js ***! + \****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ PluginUtilities) +/* harmony export */ }); +/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js"); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); + + + +/** + * A series of useful functions for BetterDiscord plugins. + * @module PluginUtilities + * @deprecated 1/21/22 Use Alternatives + */ + + + class PluginUtilities { + + /** + * Loads data through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {string} key - which key the data is saved under + * @param {object} defaultData - default data to populate the object with + * @returns {object} the combined saved and default data + * @deprecated 1/21/22 Use Utilities or BdApi directly + */ + static loadData(name, key, defaultData) {return _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].loadData(name, key, defaultData);} + + /** + * Saves data through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {string} key - which key the data should be saved under + * @param {object} data - data to save + * @deprecated 1/21/22 Use Utilities or BdApi directly + */ + static saveData(name, key, data) {return _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].saveData(name, key, data);} + + /** + * Loads settings through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {object} defaultData - default data to populate the object with + * @returns {object} the combined saved and default settings + * @deprecated 1/21/22 Use Utilities or BdApi directly + */ + static loadSettings(name, defaultSettings) {return _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].loadSettings(name, defaultSettings);} + + /** + * Saves settings through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {object} data - settings to save + * @deprecated 1/21/22 Use Utilities or BdApi directly + */ + static saveSettings(name, data) {return _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].saveSettings(name, data);} + + /** + * Get the full path to the BetterDiscord folder. + * @returns {string} full path to the BetterDiscord folder + * @deprecated 1/21/22 Use BdApi + */ + static getBDFolder(subtarget = "") { + const process = require("process"); + const path = require("path"); + if (process.env.injDir) return path.resolve(process.env.injDir, subtarget); + switch (process.platform) { + case "win32": + return path.resolve(process.env.APPDATA, "BetterDiscord/", subtarget); + case "darwin": + return path.resolve(process.env.HOME, "Library/Application Support/", "BetterDiscord/", subtarget); + default: + return path.resolve(process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : process.env.HOME + "/.config", "BetterDiscord/", subtarget); + } + } + + /** + * Get the full path to the plugins folder. + * @returns {string} full path to the plugins folder + * @deprecated 1/21/22 Use BdApi + */ + static getPluginsFolder() {return BdApi.Plugins.folder;} + + /** + * Get the full path to the themes folder. + * @returns {string} full path to the themes folder + * @deprecated 1/21/22 Use BdApi + */ + static getThemesFolder() {return BdApi.Themes.folder;} + + /** + * Adds a callback to a set of listeners for onSwitch. + * @param {callable} callback - basic callback to happen on channel switch + * @deprecated 1/21/22 Use onSwitch + */ + static addOnSwitchListener() {} + + /** + * Removes the listener added by {@link InternalUtilities.addOnSwitchListener}. + * @param {callable} callback - callback to remove from the listener list + * @deprecated 1/21/22 Use onSwitch + */ + static removeOnSwitchListener() {} + + /** + * Adds a style to the document. + * @param {string} id - identifier to use as the element id + * @param {string} css - css to add to the document + * @deprecated 1/21/22 Use DOMTools + */ + static addStyle(id, css) {return _domtools__WEBPACK_IMPORTED_MODULE_1__["default"].addStyle(id, css);} + + /** + * Removes a style from the document. + * @param {string} id - original identifier used + * @deprecated 1/21/22 Use DOMTools + */ + static removeStyle(id) {return _domtools__WEBPACK_IMPORTED_MODULE_1__["default"].removeStyle(id);} + + /** + * Adds/requires a remote script to be loaded + * @param {string} id - identifier to use for this script + * @param {string} url - url from which to load the script + * @returns {Promise} promise that resolves when the script is loaded + * @deprecated 1/21/22 Use DOMTools + */ + static addScript(id, url) {return _domtools__WEBPACK_IMPORTED_MODULE_1__["default"].addScript(id, url);} + + /** + * Removes a remote script from the document. + * @param {string} id - original identifier used + * @deprecated 1/21/22 Use DOMTools + */ + static removeScript(id) {return _domtools__WEBPACK_IMPORTED_MODULE_1__["default"].removeScript(id);} +} + + + + +/***/ }), + +/***/ "./src/modules/reactcomponents.js": +/*!****************************************!*\ + !*** ./src/modules/reactcomponents.js ***! + \****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ ReactComponents) +/* harmony export */ }); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); +/* harmony import */ var _reacttools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./reacttools */ "./src/modules/reacttools.js"); +/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js"); +/** + * BetterDiscord React Component Manipulations + * Original concept and some code by samogot - https://github.com/samogot / https://github.com/samogot/betterdiscord-plugins/tree/master/v2/1Lib%20Discord%20Internals + * + * Copyright (c) 2015-present JsSucks - https://github.com/JsSucks + * All rights reserved. + * https://github.com/JsSucks - https://betterdiscord.net + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. +*/ + + + + + +class ReactComponent { + constructor(id, component, selector, filter) { + this.id = id; + this.component = component; + this.selector = selector; + this.filter = filter; + } + + forceUpdateAll() { + if (!this.selector) return; + for (const e of document.querySelectorAll(this.selector)) { + const stateNode = _utilities__WEBPACK_IMPORTED_MODULE_2__["default"].findInTree(_reacttools__WEBPACK_IMPORTED_MODULE_1__["default"].getReactInstance(e), m => m && m.forceUpdate, {walkable: ["return", "stateNode"]}); + if (!stateNode) continue; + stateNode.forceUpdate(); + } + } +} + +/** + * Methods for obtaining and interacting with react components. + * @module ReactComponents + */ +class ReactComponents { + static get components() {return this._components || (this._components = new Map());} + static get unknownComponents() {return this._unknownComponents || (this._unknownComponents = new Set());} + static get listeners() {return this._listeners || (this._listeners = new Map());} + static get nameSetters() {return this._nameSetters || (this._nameSetters = new Set());} + + static get ReactComponent() {return ReactComponent;} + + static push(component, selector, filter) { + if (typeof(component) !== "function") return null; + const {displayName} = component; + if (!displayName) return this.processUnknown(component); + + const have = this.components.get(displayName); + if (have) { + if (!have.selector) have.selector = selector; + if (!have.filter) have.filter = filter; + return component; + } + + const c = new ReactComponent(displayName, component, selector, filter); + this.components.set(c.id, c); + + const listener = this.listeners.get(displayName); + if (listener) { + for (const l of listener.children) l(c); + this.listeners.delete(listener); + } + + return c; + } + + /** + * Finds a component from the components array or by waiting for it to be mounted. + * @param {String} name The component's name + * @param {Object} selector A selector to look for + * @return {Promise} + */ + static async getComponentByName(name, selector) { + return this.getComponent(name, selector, m => m.displayName == name); + } + + /** + * Finds a component from the components array or by waiting for it to be mounted. + * @param {String} name The component's name + * @param {Object} selector A selector to look for + * @param {Function} filter A function to filter components if a single element is rendered by multiple components + * @return {Promise} + */ + static async getComponent(name, selector, filter) { + const have = this.components.get(name); + if (have) { + if (!have.selector) have.selector = selector; + if (!have.filter) have.filter = filter; + return have; + } + + if (selector) { + const callback = () => { + if (this.components.get(name)) { + _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].observer.unsubscribe(observerSubscription); + return; + } + + const elements = document.querySelectorAll(selector); + if (!elements.length) return; + + let component; + for (const element of elements) { + const componentsFound = _reacttools__WEBPACK_IMPORTED_MODULE_1__["default"].getComponents(element); + component = filter ? componentsFound.find(filter) : componentsFound[0]; + if (component) break; + } + + if (!component && filter) return; + + _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].observer.unsubscribe(observerSubscription); + + if (!component) return; + + if (!component.displayName) component.displayName = name; + + this.push(component, selector, filter); + }; + + const observerSubscription = _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].observer.subscribeToQuerySelector(callback, selector, null, true); + setTimeout(callback, 0); + } + + let listener = this.listeners.get(name); + if (!listener) { + listener = { + id: name, + children: [], + filter + }; + this.listeners.set(name, listener); + } + + + return new Promise(resolve => { + listener.children.push(resolve); + }); + } + + static setName(name, filter) { + const have = this.components.get(name); + if (have) return have; + + for (const component of this.unknownComponents.entries()) { + if (!filter(component)) continue; + component.displayName = name; + this.unknownComponents.delete(component); + return this.push(component); + } + return this.nameSetters.add({name, filter}); + } + + static processUnknown(component) { + const have = this.unknownComponents.has(component); + for (const setter of this.nameSetters.entries()) { + if (setter.filter.filter(component)) { + component.displayName = setter.name; + this.nameSetters.delete(setter); + return this.push(component); + } + } + if (have) return have; + this.unknownComponents.add(component); + return component; + } + + static *recursiveComponents(internalInstance = _reacttools__WEBPACK_IMPORTED_MODULE_1__["default"].rootInstance) { + if (internalInstance.stateNode) yield internalInstance.stateNode; + if (internalInstance.sibling) yield* this.recursiveComponents(internalInstance.sibling); + if (internalInstance.child) yield* this.recursiveComponents(internalInstance.child); + } +} + + +/***/ }), + +/***/ "./src/modules/reacttools.js": +/*!***********************************!*\ + !*** ./src/modules/reacttools.js ***! + \***********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ ReactTools) +/* harmony export */ }); +/* harmony import */ var _domtools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./domtools */ "./src/modules/domtools.js"); +/* harmony import */ var _discordmodules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./discordmodules */ "./src/modules/discordmodules.js"); +/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js"); +/** + * Helpful utilities for dealing with getting react information from DOM objects. + * @module ReactTools + */ + + + + + +class ReactTools { + + static get rootInstance() {return document.getElementById("app-mount")._reactRootContainer._internalRoot.current;} + + /** + * Grabs the react internal instance of a specific node. + * @param {(HTMLElement|jQuery)} node - node to obtain react instance of + * @return {object} the internal react instance + */ + static getReactInstance(node) { + const domNode = _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].resolveElement(node); + if (!(domNode instanceof Element)) return undefined; + return domNode[Object.keys(domNode).find((key) => key.startsWith("__reactInternalInstance") || key.startsWith("__reactFiber"))]; + } + + /** + * Grabs a value from the react internal instance. Allows you to grab + * long depth values safely without accessing no longer valid properties. + * @param {(HTMLElement|jQuery)} node - node to obtain react instance of + * @param {string} path - path to the requested value + * @return {(*|undefined)} the value requested or undefined if not found. + */ + static getReactProperty(node, path) { + return _utilities__WEBPACK_IMPORTED_MODULE_2__["default"].getNestedProp(this.getReactInstance(node), path); + } + + /** + * Grabs a value from the react internal instance. Allows you to grab + * long depth values safely without accessing no longer valid properties. + * @param {(HTMLElement|jQuery)} node - node to obtain react instance of + * @param {object} options - options for the search + * @param {array} [options.include] - list of items to include from the search + * @param {array} [options.exclude=["Popout", "Tooltip", "Scroller", "BackgroundFlash"]] - list of items to exclude from the search + * @param {callable} [options.filter=_=>_] - filter to check the current instance with (should return a boolean) + * @return {(*|null)} the owner instance or undefined if not found. + */ + static getOwnerInstance(node, {include, exclude = ["Popout", "Tooltip", "Scroller", "BackgroundFlash"], filter = _ => _} = {}) { + if (node === undefined) return undefined; + const excluding = include === undefined; + const nameFilter = excluding ? exclude : include; + function getDisplayName(owner) { + const type = owner.type; + if (!type) return null; + return type.displayName || type.name || null; + } + function classFilter(owner) { + const name = getDisplayName(owner); + return (name !== null && !!(nameFilter.includes(name) ^ excluding)); + } + + let curr = this.getReactInstance(node); + for (curr = curr && curr.return; !_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].isNil(curr); curr = curr.return) { + if (_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].isNil(curr)) continue; + const owner = curr.stateNode; + if (!_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].isNil(owner) && !(owner instanceof HTMLElement) && classFilter(curr) && filter(owner)) return owner; + } + + return null; + } + + /** + * Grabs the react internal state node trees of a specific node. + * @param {(HTMLElement|jQuery)} node - node to obtain state nodes of + * @return {Array} list of found state nodes + */ + static getStateNodes(node) { + const instance = this.getReactInstance(node); + const stateNodes = []; + let lastInstance = instance; + while (lastInstance && lastInstance.return) { + if (lastInstance.return.stateNode instanceof HTMLElement) break; + if (lastInstance.return.stateNode) stateNodes.push(lastInstance.return.stateNode); + lastInstance = lastInstance.return; + } + return stateNodes; + } + + /** + * Grabs the react internal component tree of a specific node. + * @param {(HTMLElement|jQuery)} node - node to obtain react components of + * @return {Array} list of found react components + */ + static getComponents(node) { + const instance = this.getReactInstance(node); + const components = []; + let lastInstance = instance; + while (lastInstance && lastInstance.return) { + if (typeof lastInstance.return.type === "string") break; + if (lastInstance.return.type) components.push(lastInstance.return.type); + lastInstance = lastInstance.return; + } + return components; + } + + /** + * Creates and renders a react element that wraps dom elements. + * @param {(HTMLElement|Array)} element - element or array of elements to wrap into a react element + * @returns {object} - rendered react element + */ + static createWrappedElement(element) { + if (Array.isArray(element)) element = _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].wrap(element); + return _discordmodules__WEBPACK_IMPORTED_MODULE_1__["default"].React.createElement(this.wrapElement(element)); + } + + /** + * Creates an unrendered react component that wraps dom elements. + * @param {(HTMLElement|Array)} element - element or array of elements to wrap into a react component + * @returns {object} - unrendered react component + */ + static wrapElement(element) { + if (Array.isArray(element)) element = _domtools__WEBPACK_IMPORTED_MODULE_0__["default"].wrap(element); + return class ReactWrapper extends _discordmodules__WEBPACK_IMPORTED_MODULE_1__["default"].React.Component { + constructor(props) { + super(props); + this.element = element; + } + + componentDidMount() {this.refs.element.appendChild(this.element);} + render() {return _discordmodules__WEBPACK_IMPORTED_MODULE_1__["default"].React.createElement("div", {className: "react-wrapper", ref: "element"});} + }; + } +} + +/***/ }), + +/***/ "./src/modules/utilities.js": +/*!**********************************!*\ + !*** ./src/modules/utilities.js ***! + \**********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Utilities) +/* harmony export */ }); +/* harmony import */ var _logger__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./logger */ "./src/modules/logger.js"); +/** + * Random set of utilities that didn't fit elsewhere. + * @module Utilities + */ + + + +class Utilities { + + /** + * Stably sorts arrays since `.sort()` has issues. + * @param {Array} list - array to sort + * @param {function} comparator - comparator to sort by + */ + static stableSort(list, comparator) { + const entries = Array(list.length); + + // wrap values with initial indices + for (let index = 0; index < list.length; index++) { + entries[index] = [index, list[index]]; + } + + // sort with fallback based on initial indices + entries.sort(function (a, b) { + const comparison = Number(this(a[1], b[1])); + return comparison || a[0] - b[0]; + }.bind(comparator)); + + // re-map original array to stable sorted values + for (let index = 0; index < list.length; index++) { + list[index] = entries[index][1]; + } + } + + /** + * Generates an automatically memoizing version of an object. + * @param {Object} object - object to memoize + * @returns {Proxy} the proxy to the object that memoizes properties + */ + static memoizeObject(object) { + const proxy = new Proxy(object, { + get: function(obj, mod) { + if (!obj.hasOwnProperty(mod)) return undefined; + if (Object.getOwnPropertyDescriptor(obj, mod).get) { + const value = obj[mod]; + delete obj[mod]; + obj[mod] = value; + } + return obj[mod]; + }, + set: function(obj, mod, value) { + if (obj.hasOwnProperty(mod)) return _logger__WEBPACK_IMPORTED_MODULE_0__["default"].err("MemoizedObject", "Trying to overwrite existing property"); + obj[mod] = value; + return obj[mod]; + } + }); + + Object.defineProperty(proxy, "hasOwnProperty", {value: function(prop) { + return this[prop] !== undefined; + }}); + + return proxy; + } + + /** + * Wraps the method in a `try..catch` block. + * @param {callable} method - method to wrap + * @param {string} description - description of method + * @returns {callable} wrapped version of method + */ + static suppressErrors(method, description) { + return (...params) => { + try {return method(...params);} + catch (e) {_logger__WEBPACK_IMPORTED_MODULE_0__["default"].err("Suppression", "Error occurred in " + description, e);} + }; + } + + /** + * This only exists because Samo relied on lodash being there... fuck lodash. + * @param {*} anything - whatever you want + */ + static isNil(anything) { + return anything === null; + } + + /** + * Format template strings with placeholders (`${placeholder}`) into full strings. + * Quick example: `Utilities.formatString("Hello, ${user}", {user: "Zerebos"})` + * would return "Hello, Zerebos". + * @param {string} string - string to format + * @param {object} values - object literal of placeholders to replacements + * @returns {string} the properly formatted string + */ + static formatTString(string, values) { + for (const val in values) { + let replacement = values[val]; + if (Array.isArray(replacement)) replacement = JSON.stringify(replacement); + if (typeof(replacement) === "object" && replacement !== null) replacement = replacement.toString(); + string = string.replace(new RegExp(`\\$\\{${val}\\}`, "g"), replacement); + } + return string; + } + + /** + * Format strings with placeholders (`{{placeholder}}`) into full strings. + * Quick example: `Utilities.formatString("Hello, {{user}}", {user: "Zerebos"})` + * would return "Hello, Zerebos". + * @param {string} string - string to format + * @param {object} values - object literal of placeholders to replacements + * @returns {string} the properly formatted string + */ + static formatString(string, values) { + for (const val in values) { + let replacement = values[val]; + if (Array.isArray(replacement)) replacement = JSON.stringify(replacement); + if (typeof(replacement) === "object" && replacement !== null) replacement = replacement.toString(); + string = string.replace(new RegExp(`{{${val}}}`, "g"), replacement); + } + return string; + } + + /** + * Finds a value, subobject, or array from a tree that matches a specific filter. Great for patching render functions. + * @param {object} tree React tree to look through. Can be a rendered object or an internal instance. + * @param {callable} searchFilter Filter function to check subobjects against. + */ + static findInReactTree(tree, searchFilter) { + return this.findInTree(tree, searchFilter, {walkable: ["props", "children", "child", "sibling"]}); + } + + /** + * Finds a value, subobject, or array from a tree that matches a specific filter. + * @param {object} tree Tree that should be walked + * @param {callable} searchFilter Filter to check against each object and subobject + * @param {object} options Additional options to customize the search + * @param {Array|null} [options.walkable=null] Array of strings to use as keys that are allowed to be walked on. Null value indicates all keys are walkable + * @param {Array} [options.ignore=[]] Array of strings to use as keys to exclude from the search, most helpful when `walkable = null`. + */ + static findInTree(tree, searchFilter, {walkable = null, ignore = []} = {}) { + if (typeof searchFilter === "string") { + if (tree.hasOwnProperty(searchFilter)) return tree[searchFilter]; + } + else if (searchFilter(tree)) { + return tree; + } + + if (typeof tree !== "object" || tree == null) return undefined; + + let tempReturn; + if (Array.isArray(tree)) { + for (const value of tree) { + tempReturn = this.findInTree(value, searchFilter, {walkable, ignore}); + if (typeof tempReturn != "undefined") return tempReturn; + } + } + else { + const toWalk = walkable == null ? Object.keys(tree) : walkable; + for (const key of toWalk) { + if (!tree.hasOwnProperty(key) || ignore.includes(key)) continue; + tempReturn = this.findInTree(tree[key], searchFilter, {walkable, ignore}); + if (typeof tempReturn != "undefined") return tempReturn; + } + } + return tempReturn; + } + + /** + * Gets a nested property (if it exists) safely. Path should be something like `prop.prop2.prop3`. + * Numbers can be used for arrays as well like `prop.prop2.array.0.id`. + * @param {Object} obj - object to get nested property of + * @param {string} path - representation of the property to obtain + */ + static getNestedProp(obj, path) { + return path.split(".").reduce(function(ob, prop) { + return ob && ob[prop]; + }, obj); + } + + /** + * Builds a classname string from any number of arguments. This includes arrays and objects. + * When given an array all values from the array are added to the list. + * When given an object they keys are added as the classnames if the value is truthy. + * Copyright (c) 2018 Jed Watson https://github.com/JedWatson/classnames MIT License + * @param {...Any} argument - anything that should be used to add classnames. + */ + static className() { + const classes = []; + const hasOwn = {}.hasOwnProperty; + + for (let i = 0; i < arguments.length; i++) { + const arg = arguments[i]; + if (!arg) continue; + + const argType = typeof arg; + + if (argType === "string" || argType === "number") { + classes.push(arg); + } + else if (Array.isArray(arg) && arg.length) { + const inner = this.classNames.apply(null, arg); + if (inner) { + classes.push(inner); + } + } + else if (argType === "object") { + for (const key in arg) { + if (hasOwn.call(arg, key) && arg[key]) { + classes.push(key); + } + } + } + } + + return classes.join(" "); + } + + /** + * Safely adds to the prototype of an existing object by checking if the + * property exists on the prototype. + * @param {object} object - Object whose prototype to extend + * @param {string} prop - Name of the prototype property to add + * @param {callable} func - Function to run + */ + static addToPrototype(object, prop, func) { + if (!object.prototype) return; + if (object.prototype[prop]) return; + return object.prototype[prop] = func; + } + + /** + * Deep extends an object with a set of other objects. Objects later in the list + * of `extenders` have priority, that is to say if one sets a key to be a primitive, + * it will be overwritten with the next one with the same key. If it is an object, + * and the keys match, the object is extended. This happens recursively. + * @param {object} extendee - Object to be extended + * @param {...object} extenders - Objects to extend with + * @returns {object} - A reference to `extendee` + */ + static extend(extendee, ...extenders) { + for (let i = 0; i < extenders.length; i++) { + for (const key in extenders[i]) { + if (extenders[i].hasOwnProperty(key)) { + if (Array.isArray(extendee[key]) && Array.isArray(extenders[i][key])) this.extend(extendee[key], extenders[i][key]); + else if (this.isNil(extenders[i][key])) extendee[key] = extenders[i][key]; + else if (typeof extendee[key] === "object" && typeof extenders[i][key] === "object") this.extend(extendee[key], extenders[i][key]); + else if (Array.isArray(extenders[i][key])) extendee[key] = [], this.extend(extendee[key], extenders[i][key]); // eslint-disable-line no-sequences + else if (typeof extenders[i][key] === "object") extendee[key] = {}, this.extend(extendee[key], extenders[i][key]); // eslint-disable-line no-sequences + else extendee[key] = extenders[i][key]; + } + } + } + return extendee; + } + + /* Code below comes from our work on BDv2: + * https://github.com/JsSucks/BetterDiscordApp/blob/master/common/modules/utils.js + */ + + /** + * Clones an object and all it's properties. + * @param {Any} value The value to clone + * @return {Any} The cloned value + */ + static deepclone(value) { + if (this.isNil(value)) return value; + if (typeof value === "object") { + if (Array.isArray(value)) return value.map(i => this.deepclone(i)); + + const clone = Object.assign({}, value); + + for (const key in clone) { + clone[key] = this.deepclone(clone[key]); + } + + return clone; + } + + return value; + } + + /** + * Freezes an object and all it's properties. + * @param {Any} object The object to freeze + * @param {Function} exclude A function to filter object that shouldn't be frozen + */ + static deepfreeze(object, exclude) { + if (exclude && exclude(object)) return; + + if (typeof object === "object" && object !== null) { + const properties = Object.getOwnPropertyNames(object); + + for (const property of properties) { + this.deepfreeze(object[property], exclude); + } + + Object.freeze(object); + } + + return object; + } + + /** + * Removes an item from an array. This differs from Array.prototype.filter as it mutates the original array instead of creating a new one. + * @param {Array} array The array to filter + * @param {Any} item The item to remove from the array + * @return {Array} + */ + static removeFromArray(array, item, filter) { + let index; + while ((index = filter ? array.findIndex(item) : array.indexOf(item)) > -1) array.splice(index, 1); + return array; + } + + /** + * Returns a function, that, as long as it continues to be invoked, will not + * be triggered. The function will be called after it stops being called for + * N milliseconds. + * + * Adapted from the version by David Walsh (https://davidwalsh.name/javascript-debounce-function) + * + * @param {function} executor + * @param {number} delay + */ + static debounce(executor, delay) { + let timeout; + return function(...args) { + const callback = () => { + timeout = null; + Reflect.apply(executor, null, args); + }; + clearTimeout(timeout); + timeout = setTimeout(callback, delay); + }; + } + + /** + * Loads data through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {string} key - which key the data is saved under + * @param {object} defaultData - default data to populate the object with + * @returns {object} the combined saved and default data + */ + static loadData(name, key, defaultData = {}) { + const defaults = this.deepclone(defaultData); + try { + const storedData = BdApi.getData(name, key); + if (typeof(defaults) === "object") return this.extend(defaults, storedData); + return this.isNil(storedData) || typeof(storedData) === "undefined" ? defaults : storedData; + } + catch (err) { + _logger__WEBPACK_IMPORTED_MODULE_0__["default"].err(name, "Unable to load data: ", err); + } + return defaults; + } + + /** + * Saves data through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {string} key - which key the data should be saved under + * @param {object} data - data to save + */ + static saveData(name, key, data) { + try {BdApi.setData(name, key, data);} + catch (err) {_logger__WEBPACK_IMPORTED_MODULE_0__["default"].err(name, "Unable to save data: ", err);} + } + + /** + * Loads settings through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {object} defaultData - default data to populate the object with + * @returns {object} the combined saved and default settings + */ + static loadSettings(name, defaultSettings) { + return this.loadData(name, "settings", defaultSettings); + } + + /** + * Saves settings through BetterDiscord's API. + * @param {string} name - name for the file (usually plugin name) + * @param {object} data - settings to save + */ + static saveSettings(name, data) { + this.saveData(name, "settings", data); + } + +} + +/***/ }), + +/***/ "./src/modules/webpackmodules.js": +/*!***************************************!*\ + !*** ./src/modules/webpackmodules.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "Filters": () => (/* binding */ Filters), +/* harmony export */ "default": () => (/* binding */ WebpackModules) +/* harmony export */ }); +/* harmony import */ var _discordmodules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./discordmodules */ "./src/modules/discordmodules.js"); +/* harmony import */ var _logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./logger */ "./src/modules/logger.js"); +/** + * Random set of utilities that didn't fit elsewhere. + * @module WebpackModules + */ + + + + /** + * Checks if a given module matches a set of parameters. + * @callback module:WebpackModules.Filters~filter + * @param {*} module - module to check + * @returns {boolean} - True if the module matches the filter, false otherwise + */ + +/** + * Filters for use with {@link module:WebpackModules} but may prove useful elsewhere. + */ +class Filters { + /** + * Generates a {@link module:WebpackModules.Filters~filter} that filters by a set of properties. + * @param {Array} props - Array of property names + * @param {module:WebpackModules.Filters~filter} filter - Additional filter + * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties + */ + static byProperties(props, filter = m => m) { + return module => { + const component = filter(module); + if (!component) return false; + for (let p = 0; p < props.length; p++) { + if (module[props[p]] === undefined) return false; + } + return true; + }; + } + + /** + * Generates a {@link module:WebpackModules.Filters~filter} that filters by a set of properties on the object's prototype. + * @param {Array} fields - Array of property names + * @param {module:WebpackModules.Filters~filter} filter - Additional filter + * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties on the object's prototype + */ + static byPrototypeFields(fields, filter = m => m) { + return module => { + const component = filter(module); + if (!component) return false; + if (!component.prototype) return false; + for (let f = 0; f < fields.length; f++) { + if (module.prototype[fields[f]] === undefined) return false; + } + return true; + }; + } + + /** + * Generates a {@link module:WebpackModules.Filters~filter} that filters by a regex. + * @param {RegExp} search - A RegExp to check on the module + * @param {module:WebpackModules.Filters~filter} filter - Additional filter + * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties + */ + static byCode(search, filter = m => m) { + return module => { + const method = filter(module); + if (!method) return false; + let methodString = ""; + try {methodString = method.toString([]);} + catch (err) {methodString = method.toString();} + return methodString.search(search) !== -1; + }; + } + + /** + * Generates a {@link module:WebpackModules.Filters~filter} that filters by strings. + * @param {...String} search - A RegExp to check on the module + * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of strings + */ + static byString(...strings) { + return module => { + let moduleString = ""; + try {moduleString = module.toString([]);} + catch (err) {moduleString = module.toString();} + for (const s of strings) { + if (!moduleString.includes(s)) return false; + } + return true; + }; + } + + /** + * Generates a {@link module:WebpackModules.Filters~filter} that filters by a set of properties. + * @param {string} name - Name the module should have + * @param {module:WebpackModules.Filters~filter} filter - Additional filter + * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties + */ + static byDisplayName(name) { + return module => { + return module && module.displayName === name; + }; + } + + /** + * Generates a combined {@link module:WebpackModules.Filters~filter} from a list of filters. + * @param {...module:WebpackModules.Filters~filter} filters - A list of filters + * @returns {module:WebpackModules.Filters~filter} - Combinatory filter of all arguments + */ + static combine(...filters) { + return module => { + return filters.every(filter => filter(module)); + }; + } +} + +class WebpackModules { + + static find(filter, first = true) {return this.getModule(filter, first);} + static findAll(filter) {return this.getModule(filter, false);} + static findByUniqueProperties(props, first = true) {return first ? this.getByProps(...props) : this.getAllByProps(...props);} + static findByDisplayName(name) {return this.getByDisplayName(name);} + + /** + * Finds a module using a filter function. + * @param {Function} filter A function to use to filter modules + * @param {Boolean|object} first Whether to return only the first matching module or options object matching BD's options + * @return {Any} + */ + static getModule(filter, first = true) { + const options = typeof(first) === "object" ? first : {first}; + return BdApi.Webpack.getModule(filter, options); + } + + static getIndex() { + return null; + } + + static getIndexByModule() { + return null; + } + + /** + * Finds all modules matching a filter function. + * @param {Function} filter A function to use to filter modules + */ + static getModules(filter) {return this.getModule(filter, false);} + + /** + * Finds a module by its name. + * @param {String} name The name of the module + * @param {Function} fallback A function to use to filter modules if not finding a known module + * @return {Any} + */ + static getModuleByName(name, fallback) { + if (_discordmodules__WEBPACK_IMPORTED_MODULE_0__["default"].hasOwnProperty(name)) return _discordmodules__WEBPACK_IMPORTED_MODULE_0__["default"][name]; + if (!fallback) return undefined; + const module = this.getModule(fallback); + return module ? _discordmodules__WEBPACK_IMPORTED_MODULE_0__["default"][name] = module : undefined; + } + + /** + * Finds a module by its display name. + * @param {String} name The display name of the module + * @return {Any} + */ + static getByDisplayName(name) { + return this.getModule(Filters.byDisplayName(name)); + } + + /** + * Finds a module using its code. + * @param {RegEx} regex A regular expression to use to filter modules + * @param {Boolean} first Whether to return the only the first matching module + * @return {Any} + */ + static getByRegex(regex, first = true) { + return this.getModule(Filters.byCode(regex), first); + } + + /** + * Finds a single module using properties on its prototype. + * @param {...string} prototypes Properties to use to filter modules + * @return {Any} + */ + static getByPrototypes(...prototypes) { + return this.getModule(Filters.byPrototypeFields(prototypes), true); + } + + /** + * Finds all modules with a set of properties of its prototype. + * @param {...string} prototypes Properties to use to filter modules + * @return {Any} + */ + static getAllByPrototypes(...prototypes) { + return this.getModule(Filters.byPrototypeFields(prototypes), false); + } + + /** + * Finds a single module using its own properties. + * @param {...string} props Properties to use to filter modules + * @return {Any} + */ + static getByProps(...props) { + return this.getModule(Filters.byProperties(props), true); + } + + /** + * Finds all modules with a set of properties. + * @param {...string} props Properties to use to filter modules + * @return {Any} + */ + static getAllByProps(...props) { + return this.getModule(Filters.byProperties(props), false); + } + + /** + * Finds a single module using a set of strings. + * @param {...String} props Strings to use to filter modules + * @return {Any} + */ + static getByString(...strings) { + return this.getModule(Filters.byString(...strings), true); + } + + /** + * Finds all modules with a set of strings. + * @param {...String} strings Strings to use to filter modules + * @return {Any} + */ + static getAllByString(...strings) { + return this.getModule(Filters.byString(...strings), false); + } + + /** + * Gets a specific module by index of the webpack require cache. + * Best used in combination with getIndex in order to patch a + * specific function. + * + * Note: this gives the **raw** module, meaning the actual module + * is in returnValue.exports. This is done in order to be able + * to patch modules which export a single function directly. + * @param {Number} index Index into the webpack require cache + * @return {Any} + */ + static getByIndex(index) { + return WebpackModules.require.c[index].exports; + } + + /** + * Discord's __webpack_require__ function. + */ + static get require() { + if (this._require) return this._require; + const __nested_webpack_require_9242__ = window.webpackChunkdiscord_app.push([[Symbol()], {}, r=> r]); + window.webpackChunkdiscord_app.pop(); + return this._require = __nested_webpack_require_9242__; + } + + /** + * Returns all loaded modules. + * @return {Array} + */ + static getAllModules() { + return this.require.c; + } + + + + // Webpack Chunk Observing + static get chunkName() {return "webpackChunkdiscord_app";} + + static initialize() { + this.handlePush = this.handlePush.bind(this); + this.listeners = new Set(); + + this.__ORIGINAL_PUSH__ = window[this.chunkName].push; + Object.defineProperty(window[this.chunkName], "push", { + configurable: true, + get: () => this.handlePush, + set: (newPush) => { + this.__ORIGINAL_PUSH__ = newPush; + + Object.defineProperty(window[this.chunkName], "push", { + value: this.handlePush, + configurable: true, + writable: true + }); + } + }); + } + + /** + * Adds a listener for when discord loaded a chunk. Useful for subscribing to lazy loaded modules. + * @param {Function} listener - Function to subscribe for chunks + * @returns {Function} A cancelling function + */ + static addListener(listener) { + this.listeners.add(listener); + return this.removeListener.bind(this, listener); + } + + /** + * Removes a listener for when discord loaded a chunk. + * @param {Function} listener + * @returns {boolean} + */ + static removeListener(listener) {return this.listeners.delete(listener);} + + static handlePush(chunk) { + const [, modules] = chunk; + + for (const moduleId in modules) { + const originalModule = modules[moduleId]; + + modules[moduleId] = (module, exports, require) => { + try { + Reflect.apply(originalModule, null, [module, exports, require]); + + const listeners = [...this.listeners]; + for (let i = 0; i < listeners.length; i++) { + try {listeners[i](exports, originalModule, moduleId);} + catch (error) { + _logger__WEBPACK_IMPORTED_MODULE_1__["default"].err("WebpackModules", "Could not fire callback listener:", error); + } + } + } + catch (error) { + _logger__WEBPACK_IMPORTED_MODULE_1__["default"].stacktrace("WebpackModules", "Error patching chunked module push", error); + } + }; + + Object.assign(modules[moduleId], originalModule, { + toString: () => originalModule.toString() + }); + } + + return Reflect.apply(this.__ORIGINAL_PUSH__, window[this.chunkName], [chunk]); + } + +} + +WebpackModules.initialize(); + +/***/ }), + +/***/ "./src/structs/dom/classname.js": +/*!**************************************!*\ + !*** ./src/structs/dom/classname.js ***! + \**************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _selector__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./selector */ "./src/structs/dom/selector.js"); + + +/** + * Representation of a Class Name + * @memberof module:DOMTools + **/ +class ClassName { + /** + * + * @param {string} name - name of the class to represent + */ + constructor(name) { + this.value = name; + } + + /** + * Concatenates new class names to the current one using spaces. + * @param {string} classNames - list of class names to add to this class name + * @returns {ClassName} returns self to allow chaining + */ + add(...classNames) { + for (let i = 0; i < classNames.length; i++) this.value += " " + classNames[i]; + return this; + } + + /** + * Returns the raw class name, this is how native function get the value. + * @returns {string} raw class name. + */ + toString() { + return this.value; + } + + /** + * Returns the raw class name, this is how native function get the value. + * @returns {string} raw class name. + */ + valueOf() { + return this.value; + } + + /** + * Returns the classname represented as {@link module:DOMTools.Selector}. + * @returns {Selector} selector representation of this class name. + */ + get selector() { + return new _selector__WEBPACK_IMPORTED_MODULE_0__["default"](this.value); + } + + get single() { + return this.value.split(" ")[0]; + } + + get first() { + return this.value.split(" ")[0]; + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ClassName); + +/***/ }), + +/***/ "./src/structs/dom/observer.js": +/*!*************************************!*\ + !*** ./src/structs/dom/observer.js ***! + \*************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/** + * BetterDiscord Client DOM Module + * Copyright (c) 2015-present JsSucks - https://github.com/JsSucks + * All rights reserved. + * https://betterdiscord.net + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. +*/ + + + +/* eslint-disable operator-linebreak */ + +/** + * Representation of a MutationObserver but with helpful utilities. + * @memberof module:DOMTools + **/ +class DOMObserver { + constructor(root, options) { + this.observe = this.observe.bind(this); + this.subscribe = this.subscribe.bind(this); + this.observerCallback = this.observerCallback.bind(this); + + this.active = false; + this.root = root || document.getElementById("app-mount"); + this.options = options || {attributes: true, childList: true, subtree: true}; + + this.observer = new MutationObserver(this.observerCallback); + this.observe(); + } + + observerCallback(mutations) { + for (const sub of Array.from(this.subscriptions)) { + try { + const filteredMutations = sub.filter ? mutations.filter(sub.filter) : mutations; + + if (sub.group) { + if (!filteredMutations.length) continue; + sub.callback.call(sub.bind || sub, filteredMutations); + } + else { + for (const mutation of filteredMutations) sub.callback.call(sub.bind || sub, mutation); + } + } + catch (err) { + modules__WEBPACK_IMPORTED_MODULE_0__.Logger.stacktrace("DOMObserver", "Error in observer callback", err); + } + } + } + + /** + * Starts observing the element. This will be called when attaching a callback. + * You don't need to call this manually. + */ + observe() { + if (this.active) return; + this.observer.observe(this.root, this.options); + this.active = true; + } + + /** + * Disconnects this observer. This stops callbacks being called, but does not unbind them. + * You probably want to use observer.unsubscribeAll instead. + */ + disconnect() { + if (!this.active) return; + this.observer.disconnect(); + this.active = false; + } + + reconnect() { + if (this.active) { + this.disconnect(); + this.observe(); + } + } + + get root() {return this._root;} + set root(root) {this._root = root; this.reconnect();} + + get options() {return this._options;} + set options(options) {this._options = options; this.reconnect();} + + get subscriptions() { + return this._subscriptions || (this._subscriptions = []); + } + + /** + * Subscribes to mutations. + * @param {Function} callback A function to call when on a mutation + * @param {Function} filter A function to call to filter mutations + * @param {Any} bind Something to bind the callback to + * @param {Boolean} group Whether to call the callback with an array of mutations instead of a single mutation + * @return {Object} + */ + subscribe(callback, filter, bind, group) { + const subscription = {callback, filter, bind, group}; + this.subscriptions.push(subscription); + this.observe(); + return subscription; + } + + /** + * Removes a subscription and disconnect if there are none left. + * @param {Object} subscription A subscription object returned by observer.subscribe + */ + unsubscribe(subscription) { + if (!this.subscriptions.includes(subscription)) subscription = this.subscriptions.find(s => s.callback === subscription); + modules__WEBPACK_IMPORTED_MODULE_0__.Utilities.removeFromArray(this.subscriptions, subscription); + if (!this.subscriptions.length) this.disconnect(); + } + + unsubscribeAll() { + this.subscriptions.splice(0, this.subscriptions.length); + this.disconnect(); + } + + /** + * Subscribes to mutations that affect an element matching a selector. + * @param {Function} callback A function to call when on a mutation + * @param {Function} filter A function to call to filter mutations + * @param {Any} bind Something to bind the callback to + * @param {Boolean} group Whether to call the callback with an array of mutations instead of a single mutation + * @return {Object} + */ + subscribeToQuerySelector(callback, selector, bind, group) { + return this.subscribe(callback, mutation => { + return mutation.target.matches(selector) // If the target matches the selector + || Array.from(mutation.addedNodes).concat(Array.from(mutation.removedNodes)) // Or if either an added or removed node + .find(n => n instanceof Element && (n.matches(selector) || n.querySelector(selector))); // match or contain an element matching the selector + }, bind, group); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (DOMObserver); + +/***/ }), + +/***/ "./src/structs/dom/selector.js": +/*!*************************************!*\ + !*** ./src/structs/dom/selector.js ***! + \*************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Representation of a Selector + * @memberof module:DOMTools + **/ +class Selector { + /** + * + * @param {string} classname - class to create selector for + */ + constructor(className) { + this.value = " ." + className.split(" ").join("."); + } + + /** + * Returns the raw selector, this is how native function get the value. + * @returns {string} raw selector. + */ + toString() { + return this.value; + } + + /** + * Returns the raw selector, this is how native function get the value. + * @returns {string} raw selector. + */ + valueOf() { + return this.value; + } + + selector(symbol, other) { + this.value = `${this.toString()} ${symbol} ${other.toString()}`; + return this; + } + + /** + * Adds another selector as a direct child `>` to this one. + * @param {string|DOMTools.Selector} other - Selector to add as child + * @returns {DOMTools.Selector} returns self to allow chaining + */ + child(other) { + return this.selector(">", other); + } + + /** + * Adds another selector as a adjacent sibling `+` to this one. + * @param {string|DOMTools.Selector} other - Selector to add as adjacent sibling + * @returns {DOMTools.Selector} returns self to allow chaining + */ + adjacent(other) { + return this.selector("+", other); + } + + /** + * Adds another selector as a general sibling `~` to this one. + * @param {string|DOMTools.Selector} other - Selector to add as sibling + * @returns {DOMTools.Selector} returns self to allow chaining + */ + sibling(other) { + return this.selector("~", other); + } + + /** + * Adds another selector as a descendent `(space)` to this one. + * @param {string|DOMTools.Selector} other - Selector to add as descendent + * @returns {DOMTools.Selector} returns self to allow chaining + */ + descend(other) { + return this.selector(" ", other); + } + + /** + * Adds another selector to this one via `,`. + * @param {string|DOMTools.Selector} other - Selector to add + * @returns {DOMTools.Selector} returns self to allow chaining + */ + and(other) { + return this.selector(",", other); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Selector); + +/***/ }), + +/***/ "./src/structs/listenable.js": +/*!***********************************!*\ + !*** ./src/structs/listenable.js ***! + \***********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Acts as an interface for anything that should be listenable. + */ +class Listenable { + + constructor() { + this.listeners = []; + } + + /** + * Adds a listener to the current object. + * @param {callable} callback - callback for when the event occurs + * @returns {callable} - a way to cancel the listener without needing to call `removeListener` + */ + addListener(callback) { + if (typeof(callback) !== "function") return; + this.listeners.push(callback); + return () => { + this.listeners.splice(this.listeners.indexOf(callback), 1); + }; + } + + /** + * Removes a listener from the current object. + * @param {callable} callback - callback that was originally registered + */ + removeListener(callback) { + if (typeof(callback) !== "function") return; + this.listeners.splice(this.listeners.indexOf(callback), 1); + } + + /** + * Alerts the listeners that an event occurred. Data passed is optional + * @param {*} [...data] - Any data desired to be passed to listeners + */ + alertListeners(...data) { + for (let l = 0; l < this.listeners.length; l++) this.listeners[l](...data); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Listenable); + +/***/ }), + +/***/ "./src/structs/plugin.js": +/*!*******************************!*\ + !*** ./src/structs/plugin.js ***! + \*******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Plugin), +/* harmony export */ "wrapPluginBase": () => (/* binding */ wrapPluginBase) +/* harmony export */ }); +/* harmony import */ var _modules_pluginupdater__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../modules/pluginupdater */ "./src/modules/pluginupdater.js"); +/* harmony import */ var _modules_logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../modules/logger */ "./src/modules/logger.js"); +/* harmony import */ var _modules_reacttools__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../modules/reacttools */ "./src/modules/reacttools.js"); +/* harmony import */ var _ui_modals__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../ui/modals */ "./src/ui/modals.js"); +/* harmony import */ var _modules_utilities__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../modules/utilities */ "./src/modules/utilities.js"); +/* harmony import */ var _modules_discordmodules__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../modules/discordmodules */ "./src/modules/discordmodules.js"); +/* harmony import */ var _ui_settings__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../ui/settings */ "./src/ui/settings/index.js"); + + + + + + + + +class Plugin { + + get name() {return this._config.name ?? this._config.info.name.replace(" ", "");} + get description() {return this._config.description ?? this._config.info.description;} + get version() {return this._config.version ?? this._config.info.version;} + get author() {return this._config.author ?? this._config.info.authors.map(a => a.name).join(", ");} + getName() {return this._config.name ?? this._config.info.name.replace(" ", "");} + getDescription() {return this._config.description ?? this._config.info.description;} + getVersion() {return this._config.version ?? this._config.info.version;} + getAuthor() {return this._config.author ?? this._config.info.authors.map(a => a.name).join(", ");} + get isEnabled() {return this._enabled;} + + get strings() { + if (!this._config.strings) return {}; + const locale = _modules_discordmodules__WEBPACK_IMPORTED_MODULE_5__["default"].LocaleManager?.getLocale().split("-")[0] ?? "en"; + if (this._config.strings.hasOwnProperty(locale)) return this._config.strings[locale]; + if (this._config.strings.hasOwnProperty("en")) return this._config.strings.en; + return this._config.strings; + } + + set strings(strings) { + this._config.strings = strings; + } + + constructor(zplConfig) { + this._config = zplConfig; + this._enabled = false; + + // Build the settings model from the default if it exists + if (typeof(this._config.defaultConfig) !== "undefined") { + this.defaultSettings = {}; + for (let s = 0; s < this._config.defaultConfig.length; s++) { + const current = this._config.defaultConfig[s]; + if (current.type != "category") {this.defaultSettings[current.id] = current.value;} + else { + this.defaultSettings[current.id] = {}; + for (let si = 0; si < current.settings.length; si++) { + const subCurrent = current.settings[si]; + this.defaultSettings[current.id][subCurrent.id] = subCurrent.value; + } + } + } + + // Clone the default settings to the current ones + this.settings = _modules_utilities__WEBPACK_IMPORTED_MODULE_4__["default"].deepclone(this.defaultSettings); + } + + // Load previously stored info to check if changelog is needed then check for update + const currentVersionInfo = _modules_utilities__WEBPACK_IMPORTED_MODULE_4__["default"].loadData(this.name, "currentVersionInfo", {version: this.version, hasShownChangelog: false}); + if (currentVersionInfo.version != this.version || !currentVersionInfo.hasShownChangelog) { + this.showChangelog(); + _modules_utilities__WEBPACK_IMPORTED_MODULE_4__["default"].saveData(this.name, "currentVersionInfo", {version: this.version, hasShownChangelog: true}); + } + + // Do not check updates for self + if (this._config?.id === "9") return; + _modules_pluginupdater__WEBPACK_IMPORTED_MODULE_0__["default"].checkForUpdate(this.name, this.version, this._config.id ?? this._config.github_raw ?? this._config?.info.github_raw); + } + + async start() { + _modules_logger__WEBPACK_IMPORTED_MODULE_1__["default"].info(this.name, `version ${this.version} has started.`); + if (this.defaultSettings) this.settings = this.loadSettings(); + this._enabled = true; + if (typeof(this.onStart) == "function") this.onStart(); + } + + stop() { + _modules_logger__WEBPACK_IMPORTED_MODULE_1__["default"].info(this.name, `version ${this.version} has stopped.`); + this._enabled = false; + if (typeof(this.onStop) == "function") this.onStop(); + } + + showSettingsModal() { + if (typeof(this.getSettingsPanel) != "function") return; + _ui_modals__WEBPACK_IMPORTED_MODULE_3__["default"].showModal(this.name + " Settings", _modules_reacttools__WEBPACK_IMPORTED_MODULE_2__["default"].createWrappedElement(this.getSettingsPanel()), { + cancelText: "", + confirmText: "Done", + size: _ui_modals__WEBPACK_IMPORTED_MODULE_3__["default"].ModalSizes.MEDIUM + }); + } + + showChangelog(footer) { + if (typeof(this._config.changelog) == "undefined") return; + _ui_modals__WEBPACK_IMPORTED_MODULE_3__["default"].showChangelogModal(this.name + " Changelog", this.version, this._config.changelog, footer); + } + + saveSettings(settings) { + _modules_utilities__WEBPACK_IMPORTED_MODULE_4__["default"].saveSettings(this.name, this.settings ? this.settings : settings); + } + + loadSettings(defaultSettings) { + // loadSettings -> loadData -> defaultSettings gets deep cloned + return _modules_utilities__WEBPACK_IMPORTED_MODULE_4__["default"].loadSettings(this.name, this.defaultSettings ? this.defaultSettings : defaultSettings); + } + + buildSetting(data) { + const {name, note, type, value, onChange, id} = data; + let setting = null; + if (type == "color") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.ColorPicker(name, note, value, onChange, {disabled: data.disabled, presetColors: data.presetColors}); + else if (type == "dropdown") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.Dropdown(name, note, value, data.options, onChange); + else if (type == "file") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.FilePicker(name, note, onChange); + else if (type == "keybind") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.Keybind(name, note, value, onChange); + else if (type == "radio") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.RadioGroup(name, note, value, data.options, onChange, {disabled: data.disabled}); + else if (type == "slider") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.Slider(name, note, data.min, data.max, value, onChange, data); + else if (type == "switch") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.Switch(name, note, value, onChange, {disabled: data.disabled}); + else if (type == "textbox") setting = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.Textbox(name, note, value, onChange, {placeholder: data.placeholder || ""}); + if (id) setting.id = id; + return setting; + } + + buildSettingsPanel() { + const config = this._config.defaultConfig; + const buildGroup = (group) => { + const {name, id, collapsible, shown, settings} = group; + // this.settings[id] = {}; + + const list = []; + for (let s = 0; s < settings.length; s++) { + const current = Object.assign({}, settings[s]); + current.value = this.settings[id][current.id]; + current.onChange = (value) => { + this.settings[id][current.id] = value; + }; + if (Object.keys(this.strings).length && this.strings.settings && this.strings.settings[id] && this.strings.settings[id][current.id]) { + const {settingName = name, note} = this.strings.settings[id][current.id]; + current.name = settingName; + current.note = note; + } + list.push(this.buildSetting(current)); + } + + const settingGroup = new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.SettingGroup(name, {shown, collapsible}).append(...list); + settingGroup.id = id; + return settingGroup; + }; + const list = []; + for (let s = 0; s < config.length; s++) { + const current = Object.assign({}, config[s]); + if (current.type != "category") { + current.value = this.settings[current.id]; + current.onChange = (value) => { + this.settings[current.id] = value; + }; + if (Object.keys(this.strings).length && this.strings.settings && this.strings.settings[current.id]) { + const {name, note} = this.strings.settings[current.id]; + current.name = name; + current.note = note; + } + list.push(this.buildSetting(current)); + } + else { + list.push(buildGroup(current)); + } + } + + return new _ui_settings__WEBPACK_IMPORTED_MODULE_6__.SettingPanel(this.saveSettings.bind(this), ...list); + } +} + +const wrapPluginBase = (conf) => { + return class BoundPlugin extends Plugin { + constructor() { + super(conf); + } + }; +}; + +/***/ }), + +/***/ "./src/structs/screen.js": +/*!*******************************!*\ + !*** ./src/structs/screen.js ***! + \*******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Representation of the screen such as width and height. + * @deprecated 1/21/22 Use DOMTools + */ +class Screen { + /** Document/window width */ + static get width() {return Math.max(document.documentElement.clientWidth, window.innerWidth || 0);} + /** Document/window height */ + static get height() {return Math.max(document.documentElement.clientHeight, window.innerHeight || 0);} +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Screen); + +/***/ }), + +/***/ "./src/structs/structs.js": +/*!********************************!*\ + !*** ./src/structs/structs.js ***! + \********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "ClassName": () => (/* reexport safe */ _dom_classname__WEBPACK_IMPORTED_MODULE_2__["default"]), +/* harmony export */ "DOMObserver": () => (/* reexport safe */ _dom_observer__WEBPACK_IMPORTED_MODULE_3__["default"]), +/* harmony export */ "Listenable": () => (/* reexport safe */ _listenable__WEBPACK_IMPORTED_MODULE_4__["default"]), +/* harmony export */ "Plugin": () => (/* reexport safe */ _plugin__WEBPACK_IMPORTED_MODULE_5__["default"]), +/* harmony export */ "Screen": () => (/* reexport safe */ _screen__WEBPACK_IMPORTED_MODULE_0__["default"]), +/* harmony export */ "Selector": () => (/* reexport safe */ _dom_selector__WEBPACK_IMPORTED_MODULE_1__["default"]) +/* harmony export */ }); +/* harmony import */ var _screen__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./screen */ "./src/structs/screen.js"); +/* harmony import */ var _dom_selector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./dom/selector */ "./src/structs/dom/selector.js"); +/* harmony import */ var _dom_classname__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./dom/classname */ "./src/structs/dom/classname.js"); +/* harmony import */ var _dom_observer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./dom/observer */ "./src/structs/dom/observer.js"); +/* harmony import */ var _listenable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./listenable */ "./src/structs/listenable.js"); +/* harmony import */ var _plugin__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./plugin */ "./src/structs/plugin.js"); + + + + + + + + +/***/ }), + +/***/ "./src/ui/colorpicker.js": +/*!*******************************!*\ + !*** ./src/ui/colorpicker.js ***! + \*******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ ColorPicker) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + +const React = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.React; + +const Popout = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getByDisplayName("Popout"); +const ColorPickerComponents = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getByProps("CustomColorPicker"); +const Swatch = ColorPickerComponents?.CustomColorButton.prototype.render.call({props: {}}).type; +const Tooltip = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getByPrototypes("renderTooltip"); +const LocaleManager = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.LocaleManager; + +class ColorPicker extends React.Component { + constructor(props) { + super(props); + + this.state = { + value: props.value || 0 + }; + + this.onChange = this.onChange.bind(this); + this.swatchRef = React.createRef(); + } + + get canCustom() {return this.props.acceptsCustom || true;} + + onChange(value) { + this.setState({value: value}, () => { + if (typeof(this.props.onChange) === "function") this.props.onChange(this.state.value); + }); + } + + render() { + const renderPopout = () => { + return React.createElement(ColorPickerComponents.CustomColorPicker, { + value: this.state.value, + onChange: this.onChange, + }); + }; + + return React.createElement(ColorPickerComponents.default, { + value: this.state.value, + onChange: this.onChange, + colors: this.props.colors, + renderDefaultButton: props => React.createElement(Tooltip, { + position: Tooltip.Positions.BOTTOM, + text: LocaleManager.Messages.DEFAULT + }, tooltipProps => React.createElement("div", Object.assign(tooltipProps, { + className: "defaultButtonWrapper", + }), React.createElement(ColorPickerComponents.DefaultColorButton, Object.assign(props, {color: this.props.defaultColor})))), + renderCustomButton: () => React.createElement(Popout, { + renderPopout: renderPopout, + animation: Popout.Animation.TRANSLATE, + align: Popout.Align.CENTER, + position: Popout.Positions.BOTTOM + }, props => React.createElement(Tooltip, { + position: Tooltip.Positions.BOTTOM, + text: LocaleManager.Messages.PICK_A_COLOR + }, tooltipProps => React.createElement("div", Object.assign({}, tooltipProps, props, { + className: "colorPickerButtonWrapper" + }), React.createElement(Swatch, { + isCustom: true, + color: this.state.value, + isSelected: !this.props.colors.includes(this.state.value) && this.props.defaultColor !== this.state.value, + disabled: !this.canCustom + })))) + }); + } +} + +/***/ }), + +/***/ "./src/ui/discordcontextmenu.js": +/*!**************************************!*\ + !*** ./src/ui/discordcontextmenu.js ***! + \**************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ DiscordContextMenu) +/* harmony export */ }); +/* harmony import */ var _modules_webpackmodules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../modules/webpackmodules */ "./src/modules/webpackmodules.js"); +/* harmony import */ var _modules_reacttools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../modules/reacttools */ "./src/modules/reacttools.js"); +/* harmony import */ var _modules_utilities__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../modules/utilities */ "./src/modules/utilities.js"); +/* harmony import */ var _modules_discordclasses__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../modules/discordclasses */ "./src/modules/discordclasses.js"); + + + + + +// d = e.label, +// f = e.icon, +// h = e.imageUrl, +// v = e.hint, +// m = e.subtext, +// g = e.hasSubmenu, +// y = e.disabled, +// E = e.isFocused, +// S = e.menuItemProps, +// T = e.action, +// b = e.onClose, + + +/** + * Fires when the item is clicked. + * @param {MouseEvent} event - The event generated on click + * @callback module:DiscordContextMenu~MenuItemOnClick + */ + +/** + * @interface + * @name module:DiscordContextMenu~MenuItem + * @description + * This is the generic context menu item component. It is very extensible and will adapt + * it's type depending on the props. + * + * Note: The item ID should be unique to this item across the entire menu. If no `id` is + * provided, the system will use the `label`. Plugins should ensure there are no `label` + * conflicts if they do not wish to provide `id`. `label` conflicts (when not using + * unique `id`s) can cause multiple items to be hovered at once. + * + * @param {object} props - props to pass to the react renderer + * @param {string} props.label - label to show on the menu item + * @param {string} [props.id] - specific id used for this item + * @param {string} [props.hint] - hint to show on the right hand side (usually keyboard combo) + * @param {string} [props.subtext] - description to show underneath + * @param {string} [props.image] - link to image to show on the side + * @param {function} [props.icon] - react component to render on the side + * @param {function} [props.render] - render function for custom rendering the menu item + * @param {module:DiscordContextMenu~MenuItemOnClick} [props.action] - function to perform on click + * @param {module:DiscordContextMenu~MenuItemOnClick} [props.onClick] - function to perform on click (alias of `action`) + * @param {function} [props.onClose] - function to run when this is closed + * @param {boolean} [props.danger=false] - should the item show as danger (red) + * @param {boolean} [props.disabled=false] - should the item be disabled/unclickable + * + * @param {object} [props.style] - allows you to add custom styles + * @param {boolean} [props.closeOnClick] - allows you to prevent closing on click + */ + +/** + * @interface + * @name module:DiscordContextMenu~MenuToggleItem + * @extends module:DiscordContextMenu~MenuItem + * @description + * This item is used for creating checkboxes in menus. Properties shown here are additional + * to those of the main MenuItem {@link module:DiscordContextMenu~MenuItem} + * + * + * @param {boolean} [props.checked=false] - should the checkbox be checked + * @param {boolean} [props.active=false] - alias of `checked` + */ + +/** + * @interface + * @name module:DiscordContextMenu~MenuRadioItem + * @extends module:DiscordContextMenu~MenuItem + * @description + * This item is used for creating radio selections in menus. Properties shown here are additional + * to those of the main MenuItem {@link module:DiscordContextMenu~MenuItem} + * + * Note: for the `forceUpdate` option... Without this enabled, you will manually need to + * manage the state for the functional component. If you do not the toggle will appear + * to not update. @see {@link https://reactjs.org/docs/hooks-reference.html#usestate} + * + * @param {boolean} [props.checked=false] - should the checkbox be checked + * @param {boolean} [props.active=false] - alias of `checked` + * @param {boolean} [props.forceUpdate=true] - should the menu be force-updated after click + */ + +/** + * @interface + * @name module:DiscordContextMenu~SubMenuItem + * @extends module:DiscordContextMenu~MenuItem + * @description + * This item is used for creating nested submenus. Properties shown here are additional + * to those of the main MenuItem {@link module:DiscordContextMenu~MenuItem} + * + * @param {Array} [props.render] - array of items to render in the submenu + * @param {Array} [props.items] - alias of `render` + * @param {Array} [props.children] - Already rendered elements + */ + +/** + * @interface + * @name module:DiscordContextMenu~MenuControlItem + * @extends module:DiscordContextMenu~MenuItem + * @description + * This item is used for adding custom controls like sliders to the context menu. + * Properties shown here are additional to those of the main MenuItem {@link module:DiscordContextMenu~MenuItem} + * + * @param {function} [props.control] - control function that renders the component + */ + + +/** + * A utility for building and rendering Discord's own menus. + * @module DiscordContextMenu + */ +class DiscordContextMenu { + + /** + * Builds a single menu item. The only prop shown here is the type, the rest should + * match the actual component being built. View those to see what options exist + * for each, they often have less in common than you might think. See {@link module:DiscordContextMenu.MenuItem} + * for the majority of props commonly available. Check the documentation for the + * rest of the components. + * + * @param {object} props - props used to build the item + * @param {string} [props.type="text"] - type of the item, options: text, submenu, toggle, radio, custom, separator + * @returns {object} the created component + * + * @see {@link module:DiscordContextMenu~MenuItem} + * @see {@link module:DiscordContextMenu~MenuToggleItem} + * @see {@link module:DiscordContextMenu~MenuRadioItem} + * @see {@link module:DiscordContextMenu~SubMenuItem} + * @see {@link module:DiscordContextMenu~MenuControlItem} + * + * @example + * // Creates a single menu item that prints "MENU ITEM" on click + * DiscordContextMenu.buildMenuItem({ + * label: "Menu Item", + * action: () => {console.log("MENU ITEM");} + * }); + * + * @example + * // Creates a single toggle item that starts unchecked + * // and print the new value on every toggle + * DiscordContextMenu.buildMenuItem({ + * type: "toggle", + * label: "Item Toggle", + * checked: false, + * action: (newValue) => {console.log(newValue);} + * }); + */ + static buildMenuItem(props) { + return window.BdApi.ContextMenu.buildItem(props); + } + + /** + * Creates the all the items **and groups** of a context menu recursively. + * There is no hard limit to the number of groups within groups or number + * of items in a menu. + * @param {Array} setup - array of item props used to build items. See {@link module:DiscordContextMenu.buildMenuItem} + * @returns {Array} array of the created component + * + * @example + * // Creates a single item group item with a toggle item + * DiscordContextMenu.buildMenuChildren([{ + * type: "group", + * items: [{ + * type: "toggle", + * label: "Item Toggle", + * active: false, + * action: (newValue) => {console.log(newValue);} + * }] + * }]); + * + * @example + * // Creates two item groups with a single toggle item each + * DiscordContextMenu.buildMenuChildren([{ + * type: "group", + * items: [{ + * type: "toggle", + * label: "Item Toggle", + * active: false, + * action: (newValue) => { + * console.log(newValue); + * } + * }] + * }, { + * type: "group", + * items: [{ + * type: "toggle", + * label: "Item Toggle", + * active: false, + * action: (newValue) => { + * console.log(newValue); + * } + * }] + * }]); + */ + static buildMenuChildren(setup) { + return window.BdApi.ContextMenu.buildMenuChildren(setup); + } + + /** + * Creates the menu *component* including the wrapping `ContextMenu`. + * Calls {@link module:DiscordContextMenu.buildMenuChildren} under the covers. + * Used to call in combination with {@link module:DiscordContextMenu.openContextMenu}. + * @param {Array} setup - array of item props used to build items. See {@link module:DiscordContextMenu.buildMenuChildren} + * @returns {function} the unique context menu component + */ + static buildMenu(setup) { + return window.BdApi.ContextMenu.buildMenu(setup); + } + + /** + * + * @param {MouseEvent} event - The context menu event. This can be emulated, requires target, and all X, Y locations. + * @param {function} menuComponent - Component to render. This can be any react component or output of {@link module:DiscordContextMenu.buildMenu} + * @param {object} config - configuration/props for the context menu + * @param {string} [config.position="right"] - default position for the menu, options: "left", "right" + * @param {string} [config.align="top"] - default alignment for the menu, options: "bottom", "top" + * @param {function} [config.onClose] - function to run when the menu is closed + * @param {boolean} [config.noBlurEvent=false] - No clue + */ + static openContextMenu(event, menuComponent, config) { + return window.BdApi.ContextMenu.open(event, menuComponent, config); + } + + /** + * Attempts to find and return a specific context menu type's module. Useful + * when patching the render of these menus. + * @param {string | Function} nameOrFilter - name of the context menu type + * @returns {Promise} the webpack module the menu was found in + * @deprecated + */ + static getDiscordMenu(nameOrFilter) { + if (typeof(nameOrFilter) !== "function") { + const displayName = nameOrFilter; + nameOrFilter = (m) => m && m.displayName === displayName; + } + + const directMatch = _modules_webpackmodules__WEBPACK_IMPORTED_MODULE_0__["default"].getModule(m => m.default && nameOrFilter(m.default)); + if (directMatch) return Promise.resolve(directMatch); + + return new Promise(resolve => { + const cancel = _modules_webpackmodules__WEBPACK_IMPORTED_MODULE_0__["default"].addListener(module => { + if (!module.default || !nameOrFilter(module.default)) return; + resolve(module); + cancel(); + }); + }); + } + + /** + * Calls `forceUpdate()` on all context menus it can find. Useful for + * after patching a menu. + */ + static forceUpdateMenus() { + const menus = document.querySelectorAll(`.${_modules_discordclasses__WEBPACK_IMPORTED_MODULE_3__["default"].ContextMenu.menu.first}`); + for (const menu of menus) { + const stateNode = _modules_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].findInTree(_modules_reacttools__WEBPACK_IMPORTED_MODULE_1__["default"].getReactInstance(menu), m=>m && m.forceUpdate && m.updatePosition, {walkable: ["return", "stateNode"]}); + if (!stateNode) continue; + stateNode.forceUpdate(); + stateNode.updatePosition(); + } + } +} + +/***/ }), + +/***/ "./src/ui/errorboundary.js": +/*!*********************************!*\ + !*** ./src/ui/errorboundary.js ***! + \*********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "WrapBoundary": () => (/* binding */ WrapBoundary), +/* harmony export */ "default": () => (/* binding */ ErrorBoundary) +/* harmony export */ }); +/* harmony import */ var _modules_discordmodules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../modules/discordmodules */ "./src/modules/discordmodules.js"); + + +const React = _modules_discordmodules__WEBPACK_IMPORTED_MODULE_0__["default"].React; +const ce = React.createElement; + +class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = {hasError: false}; + } + + componentDidCatch() { + this.setState({hasError: true}); + } + + render() { + if (this.state.hasError) return this.props.errorChildren ? this.props.errorChildren : ce("div", {className: "error"}, "Component Error"); + return this.props.children; + } +} + +function WrapBoundary(Original) { + return class ErrorBoundaryWrapper extends React.Component { + render() { + return ce(ErrorBoundary, null, ce(Original, this.props)); + } + }; +} + +/***/ }), + +/***/ "./src/ui/icons.js": +/*!*************************!*\ + !*** ./src/ui/icons.js ***! + \*************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "IconError": () => (/* reexport safe */ _icons_error__WEBPACK_IMPORTED_MODULE_0__["default"]), +/* harmony export */ "IconInfo": () => (/* reexport safe */ _icons_info__WEBPACK_IMPORTED_MODULE_1__["default"]), +/* harmony export */ "IconSuccess": () => (/* reexport safe */ _icons_success__WEBPACK_IMPORTED_MODULE_2__["default"]), +/* harmony export */ "IconWarning": () => (/* reexport safe */ _icons_warning__WEBPACK_IMPORTED_MODULE_3__["default"]) +/* harmony export */ }); +/* harmony import */ var _icons_error__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./icons/error */ "./src/ui/icons/error.js"); +/* harmony import */ var _icons_info__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./icons/info */ "./src/ui/icons/info.js"); +/* harmony import */ var _icons_success__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./icons/success */ "./src/ui/icons/success.js"); +/* harmony import */ var _icons_warning__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./icons/warning */ "./src/ui/icons/warning.js"); + + + + + +/***/ }), + +/***/ "./src/ui/icons/error.js": +/*!*******************************!*\ + !*** ./src/ui/icons/error.js ***! + \*******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* export default binding */ __WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Error Icon + * @param {number} size - Size of the icon. + */ +/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__(size) { + return ` + + `; +} + +/***/ }), + +/***/ "./src/ui/icons/info.js": +/*!******************************!*\ + !*** ./src/ui/icons/info.js ***! + \******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* export default binding */ __WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Info Icon + * @param {number} size - Size of the icon. + */ +/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__(size) { + return ` + + + `; +} + +/***/ }), + +/***/ "./src/ui/icons/success.js": +/*!*********************************!*\ + !*** ./src/ui/icons/success.js ***! + \*********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* export default binding */ __WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Success Icon + * @param {number} size - Size of the icon. + */ +/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__(size) { + return ` + + + `; +} + +/***/ }), + +/***/ "./src/ui/icons/warning.js": +/*!*********************************!*\ + !*** ./src/ui/icons/warning.js ***! + \*********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* export default binding */ __WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/** + * Warning Icon + * @param {number} size - Size of the icon. + */ +/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__(size) { + return ` + + + `; +} + +/***/ }), + +/***/ "./src/ui/modals.js": +/*!**************************!*\ + !*** ./src/ui/modals.js ***! + \**************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Modals) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/** + * Allows an easy way to create and show modals. + * @module Modals + */ + + + +const React = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.React; +const ce = React.createElement; +const Markdown = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m.rules); +const MarkdownParser = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getByProps("defaultRules", "parse"); + +class Modals { + + /** Sizes of modals. */ + static get ModalSizes() {return {};} + + /** + * Shows the user profile modal for a given user. + * @param {string} userId - id of the user to show profile for + */ + static showUserProfile(userId) { + return modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.UserProfileModal.open(userId); + } + + /** + * Acts as a wrapper for {@link module:Modals.showModal} where the `children` is a text element. + * @param {string} title - title of the modal + * @param {string} content - text to show inside the modal. Can be markdown. + * @param {object} [options] - see {@link module:Modals.showModal} + * @see module:Modals.showModal + */ + static showConfirmationModal(title, content, options = {}) { + this.showModal(title, ce(Markdown, null, content), options); + } + + /** + * Shows a very simple alert modal that has title, content and an okay button. + * @param {string} title - title of the modal + * @param {string} body - text to show inside the modal + */ + static showAlertModal(title, body) { + this.showConfirmationModal(title, body, {cancelText: null}); + } + + /** + * Shows a generic but very customizable modal. + * @param {string} title - title of the modal + * @param {(ReactElement|Array)} children - a single or array of rendered react elements to act as children + * @param {object} [options] - options to modify the modal + * @param {boolean} [options.danger=false] - whether the main button should be red or not + * @param {string} [options.confirmText=Okay] - text for the confirmation/submit button + * @param {string} [options.cancelText=Cancel] - text for the cancel button + * @param {callable} [options.onConfirm=NOOP] - callback to occur when clicking the submit button + * @param {callable} [options.onCancel=NOOP] - callback to occur when clicking the cancel button + */ + static showModal(title, children, options = {}) { + const {danger = false, confirmText = "Okay", cancelText = "Cancel", onConfirm = () => {}, onCancel = () => {}} = options; + return modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ModalActions.openModal(props => { + return React.createElement(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ConfirmationModal, Object.assign({ + header: title, + confirmButtonColor: danger ? modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ButtonData.Colors.RED : modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ButtonData.Colors.BRAND, + confirmText: confirmText, + cancelText: cancelText, + onConfirm: onConfirm, + onCancel: onCancel + }, props), children); + }); + } + + /** + * @interface + * @name module:Modals~Changelog + * @property {string} title - title of the changelog section + * @property {string} [type=added] - type information of the section. Options: added, improved, fixed, progress. + * @property {Array} items - itemized list of items to show in that section. Can use markdown. + */ + + /** + * Shows a changelog modal based on changelog data. + * @param {string} title - title of the modal + * @param {string} version - subtitle (usually version or date) of the modal + * @param {module:Modals~Changelog} changelog - changelog to show inside the modal + * @param {string} footer - either an html element or text to show in the footer of the modal. Can use markdown. + */ + static showChangelogModal(title, version, changelog, footer) { + const TextElement = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.TextElement; + const ChangelogModalClasses = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m.modal && m.maxModalWidth); + if (!TextElement || !ChangelogModalClasses || !modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.FlexChild || !modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ModalRoot || !modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ModalActions) return modules__WEBPACK_IMPORTED_MODULE_0__.Logger.warn("Modals", "Unable to show changelog modal--missing modules"); + const changelogItems = []; + for (let c = 0; c < changelog.length; c++) { + const entry = changelog[c]; + const type = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Changelog[entry.type] ? modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Changelog[entry.type] : modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Changelog.added; + const margin = c == 0 ? modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Changelog.marginTop : ""; + changelogItems.push(ce("h1", {className: `${type} ${margin}`,}, entry.title)); + const list = ce("ul", null, entry.items.map(i => ce("li", null, MarkdownParser.parse(i)))); + changelogItems.push(list); + } + const renderHeader = function() { + return ce(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.FlexChild, {className: modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Modals.header.toString(), grow: 0, shrink: 0, direction: modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.FlexChild.Direction.VERTICAL}, + ce(TextElement, {tag: "h1", size: TextElement.Sizes.SIZE_20, strong: true}, title), + ce(TextElement, {size: TextElement.Sizes.SIZE_12, color: TextElement.Colors.STANDARD, className: modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Changelog.date.toString()}, "Version " + version) + ); + }; + const renderFooter = footer ? function() { + return ce(Markdown, null, footer); + } : null; + + const body = ce("div", { + className: `${modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Modals.content} ${modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Changelog.container} ${ChangelogModalClasses.content} ${modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Scrollers.thin}` + }, changelogItems); + + // return DiscordModules.ModalActions.openModal(props => { + // return ce(WebpackModules.getModule(m => m?.toString()?.includes("confirmText")), Object.assign({ + // className: DiscordClasses.Changelog.container.toString(), + // selectable: true, + // onScroll: _ => _, + // onClose: _ => _, + // renderHeader: renderHeader, + // renderFooter: renderFooter, + // }, props), changelogItems); + // }); + return modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ModalActions.openModal(props => { + return React.createElement(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.ModalRoot, Object.assign({ + className: `bd-changelog-modal ${modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Modals.root} ${modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Modals.small} ${ChangelogModalClasses.modal}`, + selectable: true, + onScroll: _ => _, + onClose: _ => _ + }, props), [renderHeader(), body, renderFooter?.()]); + }); + // return Modals.showModal(`${title} v${version}`, [renderHeader(), changelogItems, renderFooter?.()], {cancelText: null}); + } +} + +/***/ }), + +/***/ "./src/ui/popouts.js": +/*!***************************!*\ + !*** ./src/ui/popouts.js ***! + \***************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Popouts) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/** + * Allows an easy way to create and show popouts. + * @module Popouts + */ + + + +const {React, ReactDOM} = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules; +const {useReducer, useEffect, useRef} = React; +const AppLayer = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => Object.values(m).some(m => m?.displayName === "AppLayer")); +const ReferencePositionLayer = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m?.prototype?.calculatePositionStyle, {searchExports: true}); +// const PopoutCSSAnimator = WebpackModules.getByDisplayName("PopoutCSSAnimator"); +const LayerProvider = Object.values(AppLayer).find(m => m.displayName === "AppLayerProvider")?.().props.layerContext.Provider; // eslint-disable-line new-cap +const ComponentDispatch = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m.toString?.().includes("useContext") && m.toString?.().includes("windowDispatch"), {searchExports: true}); +const ComponentActions = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m.POPOUT_SHOW, {searchExports: true}); +const Popout = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m?.defaultProps && m?.Animation, {searchExports: true}); +const ThemeContext = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m?.toString?.().includes("amoled:") && m?.toString?.().includes("Provider"), {searchExports: true}); +const useStateFromStores = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m.toString?.().includes("useStateFromStores")); +const ThemeStore = modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getModule(m => m.theme); + +const createStore = state => { + const listeners = new Set(); + + const setState = function (getter = _ => _) { + const partial = getter(state); + if (partial === state) return; + + state = partial; + + [...listeners].forEach(e => e()); + }; + + setState.getState = () => state; + + function storeListener(getter = _ => _) { + const [, forceUpdate] = useReducer(n => !n, true); + + useEffect(() => { + const dispatch = () => {forceUpdate();}; + + listeners.add(dispatch); + + return () => {listeners.delete(dispatch);}; + }); + + return getter(state); + } + + return [ + setState, + storeListener + ]; +}; + +const [setPopouts, usePopouts] = createStore([]); + +// const AnimationTypes = {FADE: 3, SCALE: 2, TRANSLATE: 1}; + +class Popouts { + + // static get AnimationTypes() {return AnimationTypes;} + + static initialize() { + this.dispose(); + this.popouts = 0; + + this.container = Object.assign(document.createElement("div"), { + className: "ZeresPluginLibraryPopoutsRenderer", + style: "display: none;" + }); + + this.layerContainer = Object.assign(document.createElement("div"), { + id: "ZeresPluginLibraryPopouts", + className: modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClassModules.TooltipLayers.layerContainer + }); + + document.body.append(this.container, this.layerContainer); + ReactDOM.render(React.createElement(PopoutsContainer), this.layerContainer); + } + + /** + * Shows the user popout for a user relative to a target element + * @param {HTMLElement} target - Element to show the popout in relation to + * @param {object} user - Discord User object for the user to show + * @param {object} [options] - Options to modify the request + * @param {string} [options.guild="currentGuildId"] - Id of the guild (uses current if not specified) + * @param {string} [options.channel="currentChannelId"] - Id of the channel (uses current if not specified) + * @param {string} [options.position="right"] - Positioning relative to element + * @param {string} [options.align="top"] - Positioning relative to element + */ + static showUserPopout(target, user, options = {}) { + const {position = "right", align = "top", guild = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.SelectedGuildStore.getGuildId(), channel = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.SelectedChannelStore.getChannelId()} = options; + target = modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.resolveElement(target); + // if (target.getBoundingClientRect().right + 250 >= DOMTools.screenWidth && options.autoInvert) position = "left"; + // if (target.getBoundingClientRect().bottom + 400 >= DOMTools.screenHeight && options.autoInvert) align = "bottom"; + // if (target.getBoundingClientRect().top - 400 >= DOMTools.screenHeight && options.autoInvert) align = "top"; + this.openPopout(target, { + position: position, + align: align, + // animation: options.animation || Popouts.AnimationTypes.TRANSLATE, + autoInvert: options.autoInvert, + nudgeAlignIntoViewport: options.nudgeAlignIntoViewport, + spacing: options.spacing, + render: (props) => { + return modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.React.createElement(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordModules.UserPopout, Object.assign({}, props, { + userId: user.id, + guildId: guild, + channelId: channel, + closePopout: () => this.closePopout(props.popoutId) + })); + } + }); + } + + /** + * Shows a react popout relative to a target element + * @param {HTMLElement} target - Element to show the popout in relation to + * @param {object} [options] - Options to modify the request + * @param {string} [options.position="right"] - General position relative to element + * @param {string} [options.align="top"] - Alignment relative to element + * @param {boolean} [options.autoInvert=true] - Try to automatically adjust the position if it overflows the screen + * @param {boolean} [options.nudgeAlignIntoViewport=true] - Try to automatically adjust the alignment if it overflows the screen + * @param {number} [options.spacing=8] - Spacing between target and popout + */ + static openPopout(target, options) { + const id = this.popouts++; + + setPopouts(popouts => popouts.concat({ + id: id, + element: React.createElement(PopoutWrapper, Object.assign({}, Popout.defaultProps, { + reference: {current: target}, + popoutId: id, + key: "popout_" + id, + spacing: 50 + }, options)) + })); + + return id; + } + + static closePopout(id) { + const popout = setPopouts.getState().find(e => e.id === id); + + if (!popout) return null; + + setPopouts(popouts => { + const clone = [...popouts]; + clone.splice(clone.indexOf(popout), 1); + return clone; + }); + } + + static dispose() { + modules__WEBPACK_IMPORTED_MODULE_0__.Patcher.unpatchAll("Popouts"); + const container = document.querySelector(".ZeresPluginLibraryPopoutsRenderer"); + const layerContainer = document.querySelector("#ZeresPluginLibraryPopouts"); + if (container) ReactDOM.unmountComponentAtNode(container); + if (container) container.remove(); + if (layerContainer) layerContainer.remove(); + } +} + +function DiscordProviders({children, container}) { + const theme = useStateFromStores([ThemeStore], () => ThemeStore.theme); + + return React.createElement(LayerProvider, {value: [container]}, + React.createElement(ThemeContext, {theme}, children) + ); +} + +function PopoutsContainer() { + const popouts = usePopouts(); + + return React.createElement(DiscordProviders, + {container: Popouts.layerContainer}, + popouts.map((popout) => popout.element) + ); +} + +function PopoutWrapper({render, popoutId, ...props}) { + const popoutRef = useRef(); + + useEffect(() => { + if (!popoutRef.current) return; + + const node = ReactDOM.findDOMNode(popoutRef.current); + + const handleClick = ({target}) => { + if (target === node || node.contains(target)) return; + + Popouts.closePopout(popoutId); + }; + + const target = modules__WEBPACK_IMPORTED_MODULE_0__.Utilities.findInTree(node.__reactFiber$, m => m?.stateNode?.updatePosition, {walkable: ["return"]}); + setTimeout(() => target?.stateNode?.updatePosition(), 1); + + document.addEventListener("click", handleClick); + + return () => { + document.removeEventListener("click", handleClick); + }; + }, [popoutRef]); + + // switch (animation) { + // case PopoutCSSAnimator.Types.FADE: + // case PopoutCSSAnimator.Types.SCALE: + // case PopoutCSSAnimator.Types.TRANSLATE: { + // const renderPopout = render; + // render = (renderProps) => { + // return React.createElement(PopoutCSSAnimator, { + // position: renderProps.position, + // type: animation + // }, renderPopout(renderProps)); + // }; + // } + // } + + // eslint-disable-next-line new-cap + const ComponentDispatcher = ComponentDispatch(); + + return React.createElement(ReferencePositionLayer, Object.assign(props, { + ref: popoutRef, + positionKey: "0", + autoInvert: true, + nudgeAlignIntoViewport: true, + id: "popout_" + popoutId, + animation: 2, + onMount() { + ComponentDispatcher.dispatch(ComponentActions.POPOUT_SHOW); + }, + onUnmount() { + ComponentDispatcher.dispatch(ComponentActions.POPOUT_HIDE); + }, + children: (props, ...p) => React.createElement( + "div", + { + style: {transform: "translateZ(0)"}, // for z-index to work properly for sub-popouts + }, + render({popoutId, ...props}, ...p) + ) + })); +} + + + + +/***/ }), + +/***/ "./src/ui/settings/index.js": +/*!**********************************!*\ + !*** ./src/ui/settings/index.js ***! + \**********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "CSS": () => (/* reexport safe */ _styles_settings_css__WEBPACK_IMPORTED_MODULE_0__["default"]), +/* harmony export */ "ColorPicker": () => (/* reexport safe */ _types_color__WEBPACK_IMPORTED_MODULE_5__["default"]), +/* harmony export */ "Dropdown": () => (/* reexport safe */ _types_dropdown__WEBPACK_IMPORTED_MODULE_9__["default"]), +/* harmony export */ "FilePicker": () => (/* reexport safe */ _types_file__WEBPACK_IMPORTED_MODULE_6__["default"]), +/* harmony export */ "Keybind": () => (/* reexport safe */ _types_keybind__WEBPACK_IMPORTED_MODULE_10__["default"]), +/* harmony export */ "RadioGroup": () => (/* reexport safe */ _types_radiogroup__WEBPACK_IMPORTED_MODULE_11__["default"]), +/* harmony export */ "ReactSetting": () => (/* reexport safe */ _settingfield__WEBPACK_IMPORTED_MODULE_1__.ReactSetting), +/* harmony export */ "SettingField": () => (/* reexport safe */ _settingfield__WEBPACK_IMPORTED_MODULE_1__["default"]), +/* harmony export */ "SettingGroup": () => (/* reexport safe */ _settinggroup__WEBPACK_IMPORTED_MODULE_2__["default"]), +/* harmony export */ "SettingPanel": () => (/* reexport safe */ _settingpanel__WEBPACK_IMPORTED_MODULE_3__["default"]), +/* harmony export */ "Slider": () => (/* reexport safe */ _types_slider__WEBPACK_IMPORTED_MODULE_7__["default"]), +/* harmony export */ "Switch": () => (/* reexport safe */ _types_switch__WEBPACK_IMPORTED_MODULE_8__["default"]), +/* harmony export */ "Textbox": () => (/* reexport safe */ _types_textbox__WEBPACK_IMPORTED_MODULE_4__["default"]) +/* harmony export */ }); +/* harmony import */ var _styles_settings_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../styles/settings.css */ "./src/styles/settings.css"); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var _settinggroup__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./settinggroup */ "./src/ui/settings/settinggroup.js"); +/* harmony import */ var _settingpanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./settingpanel */ "./src/ui/settings/settingpanel.js"); +/* harmony import */ var _types_textbox__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./types/textbox */ "./src/ui/settings/types/textbox.js"); +/* harmony import */ var _types_color__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./types/color */ "./src/ui/settings/types/color.js"); +/* harmony import */ var _types_file__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./types/file */ "./src/ui/settings/types/file.js"); +/* harmony import */ var _types_slider__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./types/slider */ "./src/ui/settings/types/slider.js"); +/* harmony import */ var _types_switch__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./types/switch */ "./src/ui/settings/types/switch.js"); +/* harmony import */ var _types_dropdown__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./types/dropdown */ "./src/ui/settings/types/dropdown.js"); +/* harmony import */ var _types_keybind__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./types/keybind */ "./src/ui/settings/types/keybind.js"); +/* harmony import */ var _types_radiogroup__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./types/radiogroup */ "./src/ui/settings/types/radiogroup.js"); +/** + * An object that makes generating settings panel 10x easier. + * @module Settings + */ + + + + + + + + + + + + + + + + +/***/ }), + +/***/ "./src/ui/settings/settingfield.js": +/*!*****************************************!*\ + !*** ./src/ui/settings/settingfield.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "ReactSetting": () => (/* binding */ ReactSetting), +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _structs_listenable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../structs/listenable */ "./src/structs/listenable.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + + +/** + * Setting field to extend to create new settings + * @memberof module:Settings + */ +class SettingField extends _structs_listenable__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {callable} onChange - callback to perform on setting change + * @param {(ReactComponent|HTMLElement)} settingtype - actual setting to render + * @param {object} [props] - object of props to give to the setting and the settingtype + * @param {boolean} [props.noteOnTop=false] - determines if the note should be shown above the element or not. + */ + constructor(name, note, onChange, settingtype, props = {}) { + super(); + this.name = name; + this.note = note; + if (typeof(onChange) == "function") this.addListener(onChange); + this.inputWrapper = modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.parseHTML(`
`); + this.type = typeof(settingtype) == "function" ? settingtype : modules__WEBPACK_IMPORTED_MODULE_1__.ReactTools.wrapElement(settingtype); + this.props = props; + modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.onAdded(this.getElement(), () => {this.onAdded();}); + modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.onRemoved(this.getElement(), () => {this.onRemoved();}); + } + + /** @returns {HTMLElement} - root element for setting */ + getElement() {return this.inputWrapper;} + + /** Fires onchange to listeners */ + onChange() { + this.alertListeners(...arguments); + } + + /** Fired when root node added to DOM */ + onAdded() { + const reactElement = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.ReactDOM.render(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React.createElement(ReactSetting, Object.assign({ + title: this.name, + type: this.type, + note: this.note, + }, this.props)), this.getElement()); + + if (this.props.onChange) reactElement.props.onChange = this.props.onChange(reactElement); + reactElement.forceUpdate(); + } + + /** Fired when root node removed from DOM */ + onRemoved() { + modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.ReactDOM.unmountComponentAtNode(this.getElement()); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SettingField); + +class ReactSetting extends modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React.Component { + get noteElement() { + const className = this.props.noteOnTop ? modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.Margins.marginBottom8 : modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.Margins.marginTop8; + return modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React.createElement(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.SettingsNote, {children: this.props.note, type: "description", className: className.toString()}); + } + + get dividerElement() {return modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React.createElement("div", {className: modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.Dividers.divider.add(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.Margins.marginTop20).toString()});} + + render() { + const ce = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React.createElement; + const SettingElement = ce(this.props.type, this.props); + if (this.props.inline) { + const Flex = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.FlexChild; + const titleDefault = modules__WEBPACK_IMPORTED_MODULE_1__.WebpackModules.getByProps("titleDefault") ? modules__WEBPACK_IMPORTED_MODULE_1__.WebpackModules.getByProps("titleDefault").title : "titleDefault-a8-ZSr title-31JmR4"; + return ce(Flex, {direction: Flex.Direction.VERTICAL, className: modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.Margins.marginTop20.toString()}, + ce(Flex, {align: Flex.Align.START}, + ce(Flex.Child, {wrap: !0}, + ce("div", {className: titleDefault}, this.props.title) + ), + ce(Flex.Child, {grow: 0, shrink: 0}, SettingElement) + ), + this.noteElement, + this.dividerElement + ); + } + + return ce(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.SettingsWrapper, { + className: modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.Margins.marginTop20.toString(), + title: this.props.title, + children: [ + this.props.noteOnTop ? this.noteElement : SettingElement, + this.props.noteOnTop ? SettingElement : this.noteElement, + this.dividerElement + ] + }); + } +} + + + +/***/ }), + +/***/ "./src/ui/settings/settinggroup.js": +/*!*****************************************!*\ + !*** ./src/ui/settings/settinggroup.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _structs_listenable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../structs/listenable */ "./src/structs/listenable.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./settingfield */ "./src/ui/settings/settingfield.js"); + + + + +/** + * Grouping of controls for easier management in settings panels. + * @memberof module:Settings + */ +class SettingGroup extends _structs_listenable__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} groupName - title for the group of settings + * @param {object} [options] - additional options for the group + * @param {callback} [options.callback] - callback called on settings changed + * @param {boolean} [options.collapsible=true] - determines if the group should be collapsible + * @param {boolean} [options.shown=false] - determines if the group should be expanded by default + */ + constructor(groupName, options = {}) { + super(); + const {collapsible = true, shown = false, callback = () => {}} = options; + this.addListener(callback); + this.onChange = this.onChange.bind(this); + + const collapsed = shown || !collapsible ? "" : "collapsed"; + const group = modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.parseHTML(`
+

+ ${groupName} +

+
+
`); + const label = group.querySelector("h2"); + const controls = group.querySelector(".plugin-inputs"); + + this.group = group; + this.label = label; + this.controls = controls; + + if (!collapsible) return; + label.addEventListener("click", async () => { + const button = label.querySelector(".button-collapse"); + const wasCollapsed = button.classList.contains("collapsed"); + group.parentElement.querySelectorAll(":scope > .plugin-input-group > .collapsible:not(.collapsed)").forEach((element) => { + element.style.setProperty("height", element.scrollHeight + "px"); + element.classList.add("collapsed"); + setImmediate(() => {element.style.setProperty("height", "");}); + }); + group.parentElement.querySelectorAll(":scope > .plugin-input-group > h2 > .button-collapse").forEach(e => e.classList.add("collapsed")); + if (!wasCollapsed) return; + controls.style.setProperty("height", controls.scrollHeight + "px"); + controls.classList.remove("collapsed"); + button.classList.remove("collapsed"); + await new Promise(resolve => setTimeout(resolve, 300)); + controls.style.setProperty("height", ""); + }); + } + + /** @returns {HTMLElement} - root node for the group. */ + getElement() {return this.group;} + + /** + * Adds multiple nodes to this group. + * @param {(...HTMLElement|...jQuery|...module:Settings.SettingField|...module:Settings.SettingGroup)} nodes - list of nodes to add to the group container + * @returns {module:Settings.SettingGroup} - returns self for chaining + */ + append(...nodes) { + for (let i = 0; i < nodes.length; i++) { + if (modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.resolveElement(nodes[i]) instanceof Element) this.controls.append(nodes[i]); + else if (nodes[i] instanceof _settingfield__WEBPACK_IMPORTED_MODULE_2__["default"] || nodes[i] instanceof SettingGroup) this.controls.append(nodes[i].getElement()); + if (nodes[i] instanceof _settingfield__WEBPACK_IMPORTED_MODULE_2__["default"]) { + nodes[i].addListener(((node) => (value) => { + this.onChange(node.id || node.name, value); + })(nodes[i])); + } + else if (nodes[i] instanceof SettingGroup) { + nodes[i].addListener(((node) => (settingId, value) => { + this.onChange(node.id || node.name, settingId, value); + })(nodes[i])); + } + } + return this; + } + + /** + * Appends this node to another + * @param {HTMLElement} node - node to attach the group to. + * @returns {module:Settings.SettingGroup} - returns self for chaining + */ + appendTo(node) { + node.append(this.group); + return this; + } + + /** Fires onchange to listeners */ + onChange() { + this.alertListeners(...arguments); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SettingGroup); + +/***/ }), + +/***/ "./src/ui/settings/settingpanel.js": +/*!*****************************************!*\ + !*** ./src/ui/settings/settingpanel.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _structs_listenable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../structs/listenable */ "./src/structs/listenable.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var _settinggroup__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./settinggroup */ "./src/ui/settings/settinggroup.js"); + + + + + +/** + * Grouping of controls for easier management in settings panels. + * @memberof module:Settings + */ +class SettingPanel extends _structs_listenable__WEBPACK_IMPORTED_MODULE_0__["default"] { + + /** + * Creates a new settings panel + * @param {callable} onChange - callback to fire when settings change + * @param {(...HTMLElement|...jQuery|...module:Settings.SettingField|...module:Settings.SettingGroup)} nodes - list of nodes to add to the panel container + */ + constructor(onChange, ...nodes) { + super(); + this.element = modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.parseHTML(`
`); + if (typeof(onChange) == "function") this.addListener(onChange); + this.onChange = this.onChange.bind(this); + this.append(...nodes); + } + + /** + * Creates a new settings panel + * @param {callable} onChange - callback to fire when settings change + * @param {(...HTMLElement|...jQuery|...module:Settings.SettingField|...module:Settings.SettingGroup)} nodes - list of nodes to add to the panel container + * @returns {HTMLElement} - root node for the panel. + */ + static build(onChange, ...nodes) { + return (new SettingPanel(onChange, ...nodes)).getElement(); + } + + /** @returns {HTMLElement} - root node for the panel. */ + getElement() {return this.element;} + + /** + * Adds multiple nodes to this panel. + * @param {(...HTMLElement|...jQuery|...SettingField|...SettingGroup)} nodes - list of nodes to add to the panel container + * @returns {module:Settings.SettingPanel} - returns self for chaining + */ + append(...nodes) { + for (let i = 0; i < nodes.length; i++) { + if (modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.resolveElement(nodes[i]) instanceof Element) this.element.append(nodes[i]); + else if (nodes[i] instanceof _settingfield__WEBPACK_IMPORTED_MODULE_2__["default"] || nodes[i] instanceof _settinggroup__WEBPACK_IMPORTED_MODULE_3__["default"]) this.element.append(nodes[i].getElement()); + if (nodes[i] instanceof _settingfield__WEBPACK_IMPORTED_MODULE_2__["default"]) { + nodes[i].addListener(((node) => (value) => { + this.onChange(node.id || node.name, value); + })(nodes[i])); + } + else if (nodes[i] instanceof _settinggroup__WEBPACK_IMPORTED_MODULE_3__["default"]) { + nodes[i].addListener(((node) => (settingId, value) => { + this.onChange(node.id || node.name, settingId, value); + })(nodes[i])); + } + } + return this; + } + + /** Fires onchange to listeners */ + onChange() { + this.alertListeners(...arguments); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SettingPanel); + +/***/ }), + +/***/ "./src/ui/settings/types/color.js": +/*!****************************************!*\ + !*** ./src/ui/settings/types/color.js ***! + \****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/* harmony import */ var _colorpicker__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../colorpicker */ "./src/ui/colorpicker.js"); + + + + + + +const presetColors = [1752220, 3066993, 3447003, 10181046, 15277667, 15844367, 15105570, 15158332, 9807270, 6323595, 1146986, 2067276, 2123412, 7419530, 11342935, 12745742, 11027200, 10038562, 9936031, 5533306]; + +/** + * Creates a color picker using Discord's built in color picker + * as a base. Input and output using hex strings. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class ColorPicker extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {string} value - current hex color + * @param {callable} onChange - callback to perform on setting change, callback receives hex string + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.disabled=false] - should the setting be disabled + * @param {string} [options.defaultColor] - default color to show as large option + * @param {Array} [options.colors] - preset colors to show in swatch + */ + constructor(name, note, value, onChange, options = {}) { + const ColorPickerComponents = modules__WEBPACK_IMPORTED_MODULE_1__.WebpackModules.getByProps("CustomColorPicker"); + if (ColorPickerComponents) { + const defaultColor = options.defaultColor; + super(name, note, onChange, _colorpicker__WEBPACK_IMPORTED_MODULE_2__["default"], { + disabled: !!options.disabled, + onChange: reactElement => color => { + reactElement.props.value = color; + reactElement.forceUpdate(); + this.onChange(modules__WEBPACK_IMPORTED_MODULE_1__.ColorConverter.int2hex(color)); + }, + colors: Array.isArray(options.colors) ? options.colors : presetColors, + defaultColor: defaultColor && typeof(defaultColor) !== "number" ? modules__WEBPACK_IMPORTED_MODULE_1__.ColorConverter.hex2int(defaultColor) : defaultColor, + value: typeof(value) == "number" ? value : modules__WEBPACK_IMPORTED_MODULE_1__.ColorConverter.hex2int(value), + customPickerPosition: "right" + }); + } + else { + const classes = ["color-input"]; + if (options.disabled) classes.push(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.BasicInputs.disabled); + const ReactColorPicker = modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.parseHTML(``); + if (options.disabled) ReactColorPicker.setAttribute("disabled", ""); + if (value) ReactColorPicker.setAttribute("value", value); + ReactColorPicker.addEventListener("change", (event) => { + this.onChange(event.target.value); + }); + super(name, note, onChange, ReactColorPicker, {inline: true}); + } + } + + /** Default colors for ColorPicker */ + static get presetColors() {return presetColors;} +} + + + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ColorPicker); + +/***/ }), + +/***/ "./src/ui/settings/types/dropdown.js": +/*!*******************************************!*\ + !*** ./src/ui/settings/types/dropdown.js ***! + \*******************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + +/** + * @interface + * @name module:Settings~DropdownItem + * @property {string|ReactElement} label - label to show in the dropdown + * @property {*} value - actual value represented by label (this is passed via onChange) + */ + + const React = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React; + + class CloseButton extends React.Component { + render() { + const size = this.props.size || "14px"; + return React.createElement("svg", {className: this.props.className || "", fill: "currentColor", viewBox: "0 0 24 24", style: {width: size, height: size}, onClick: this.props.onClick}, + React.createElement("path", {d: "M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z"}) + ); + } +} + + class DownArrow extends React.Component { + render() { + const size = this.props.size || "16px"; + return React.createElement("svg", {className: this.props.className || "", fill: "currentColor", viewBox: "0 0 24 24", style: {width: size, height: size}, onClick: this.props.onClick}, + React.createElement("path", {d: "M8.12 9.29L12 13.17l3.88-3.88c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41l-4.59 4.59c-.39.39-1.02.39-1.41 0L6.7 10.7c-.39-.39-.39-1.02 0-1.41.39-.38 1.03-.39 1.42 0z"}) + ); + } +} + +// + +class Select extends React.Component { + constructor(props) { + super(props); + this.state = {open: false, value: this.props.value}; + this.dropdown = React.createRef(); + this.onChange = this.onChange.bind(this); + this.showMenu = this.showMenu.bind(this); + this.hideMenu = this.hideMenu.bind(this); + this.clear = this.clear.bind(this); + } + + showMenu(event) { + event.preventDefault(); + event.stopPropagation(); + + this.setState((state) => ({open: !state.open}), () => { + if (!this.state.open) return; + + document.addEventListener("click", this.hideMenu); + }); + } + + hideMenu() { + this.setState({open: false}, () => { + document.removeEventListener("click", this.hideMenu); + }); + } + + onChange(value) { + this.setState({value}); + if (this.props.onChange) this.props.onChange(value); + } + + get selected() {return this.props.options.find(o => o.value == this.state.value);} + + get options() { + const selected = this.selected; + return React.createElement("div", {className: "z-select-options"}, + this.props.options.map(opt => + React.createElement("div", {className: `z-select-option${selected?.value == opt.value ? " selected" : ""}`, onClick: this.onChange.bind(this, opt.value)}, opt.label) + ) + ); + } + + clear(event) { + event.stopPropagation(); + this.onChange(null); + } + + render() { + const style = this.props.style == "transparent" ? " z-select-transparent" : ""; + const isOpen = this.state.open ? " menu-open" : ""; + return React.createElement("div", {className: `z-select${style}${isOpen}`, ref: this.dropdown, onClick: this.showMenu}, [ + React.createElement("div", {className: "z-select-value"}, this?.selected?.label ?? this.props.placeholder), + React.createElement("div", {className: "z-select-icons"}, + this.props.clearable && this.selected && React.createElement(CloseButton, {className: "z-select-clear", onClick: this.clear}), + React.createElement(DownArrow, {className: "z-select-arrow"}), + ), + this.state.open && this.options + ]); + } +} + +/** + * Creates a dropdown using discord's built in dropdown. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class Dropdown extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {*} defaultValue - currently selected value + * @param {Array} values - array of all options available + * @param {callable} onChange - callback to perform on setting change, callback item value + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.clearable=false] - should be able to empty the field value + * @param {string} [options.placeholder=""] - Placeholder to show when no option is selected, useful when clearable + * @param {boolean} [options.disabled=false] - should the setting be disabled + */ + constructor(name, note, defaultValue, values, onChange, options = {}) { + const {clearable = false, disabled = false, placeholder = ""} = options; + super(name, note, onChange, Select, { + placeholder: placeholder, + clearable: clearable, + disabled: disabled, + options: values, + onChange: dropdown => value => { + dropdown.props.value = value; + dropdown.forceUpdate(); + this.onChange(value); + }, + value: defaultValue + }); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Dropdown); + +/***/ }), + +/***/ "./src/ui/settings/types/file.js": +/*!***************************************!*\ + !*** ./src/ui/settings/types/file.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + +/** + * Creates a file picker using chromium's default. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class FilePicker extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {callable} onChange - callback to perform on setting change, callback receives File object + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.disabled=false] - should the setting be disabled + * @param {Array|string} [options.accept] - what file types should be accepted + * @param {boolean} [options.multiple=false] - should multiple files be accepted + */ + constructor(name, note, onChange, options = {}) { + const classes = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.BasicInputs.inputDefault.add("file-input"); + if (options.disabled) classes.add(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordClasses.BasicInputs.disabled); + const ReactFilePicker = modules__WEBPACK_IMPORTED_MODULE_1__.DOMTools.parseHTML(``); + if (options.disabled) ReactFilePicker.setAttribute("disabled", ""); + if (options.multiple) ReactFilePicker.setAttribute("multiple", ""); + if (options.accept) ReactFilePicker.setAttribute("accept", Array.isArray(options.accept) ? options.accept.join(",") : options.accept); + ReactFilePicker.addEventListener("change", (event) => { + this.onChange(event.target.files[0]); + }); + super(name, note, onChange, ReactFilePicker); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (FilePicker); + +/***/ }), + +/***/ "./src/ui/settings/types/keybind.js": +/*!******************************************!*\ + !*** ./src/ui/settings/types/keybind.js ***! + \******************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + +const React = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React; + +class CloseButton extends React.Component { + render() { + const size = this.props.size || "16px"; + return React.createElement("svg", {className: this.props.className || "", fill: "currentColor", viewBox: "0 0 24 24", style: {width: size, height: size}, onClick: this.props.onClick}, + React.createElement("path", {d: "M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z"}) + ); + } +} + +const toCombo = modules__WEBPACK_IMPORTED_MODULE_1__.WebpackModules.getModule(m => m?.toString?.()?.includes("numpad plus"), {searchExports: true}) ?? (() => [[0, 0], [0, 0]]); +const toEvent = modules__WEBPACK_IMPORTED_MODULE_1__.WebpackModules.getModule(m => m?.toString?.()?.includes("keyCode") && m?.toString?.()?.includes("BROWSER"), {searchExports: true}) ?? (() => ({})); + +class ClearableKeybind extends React.Component { + constructor(props) { + super(props); + + this.state = {value: this.props.defaultValue}; + this.clear = this.clear.bind(this); + } + + clear() { + this.setState({value: []}); + this.props.onChange([]); + } + + render() { + return React.createElement("div", {className: "z-keybind-wrapper"}, + React.createElement(modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.Keybind, { + disabled: this.props.disabled, + defaultValue: this.state.value, + onChange: this.props.onChange + }), + React.createElement(CloseButton, {className: "z-keybind-clear", onClick: this.clear}) + ); + } +} + +/** + * Creates a keybind setting using discord's built in keybind recorder. + * @memberof module:Settings= + * @extends module:Settings.SettingField + */ +class Keybind extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {Array} value - array of key names + * @param {callable} onChange - callback to perform on setting change, callback receives array of keycodes + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.disabled=false] - should the setting be disabled + */ + constructor(label, help, value, onChange, options = {}) { + const {disabled = false} = options; + if (!Array.isArray(value) || value.some(v => typeof(v) !== "string")) value = []; // if non-strings present, not a valid combo + super(label, help, onChange, ClearableKeybind, { + disabled: disabled, + defaultValue: toCombo(value.join("+")) ?? [], + onChange: element => val => { + if (!Array.isArray(val)) return; + element.props.value = val; + this.onChange(toEvent(val)); + } + }); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Keybind); + +/***/ }), + +/***/ "./src/ui/settings/types/radiogroup.js": +/*!*********************************************!*\ + !*** ./src/ui/settings/types/radiogroup.js ***! + \*********************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + +/** + * @interface + * @name module:Settings~RadioItem + * @property {string} name - label to show in the dropdown + * @property {*} value - actual value represented by label (this is passed via onChange) + * @property {string} desc - description/help text to show below name + * @property {string} color - hex string to color the item + */ + +/** + * Creates a radio group using discord's built in radios. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class RadioGroup extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {*} defaultValue - currently selected value + * @param {Array} values - array of all options available + * @param {callable} onChange - callback to perform on setting change, callback item value + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.disabled=false] - should the setting be disabled + */ + constructor(name, note, defaultValue, values, onChange, options = {}) { + super(name, note, onChange, modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.RadioGroup, { + noteOnTop: true, + disabled: !!options.disabled, + options: values, + onChange: reactElement => option => { + reactElement.props.value = option.value; + reactElement.forceUpdate(); + this.onChange(option.value); + }, + value: defaultValue + }); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (RadioGroup); + + + +/***/ }), + +/***/ "./src/ui/settings/types/slider.js": +/*!*****************************************!*\ + !*** ./src/ui/settings/types/slider.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + +/** + * Used to render the marker. + * @param {Number} value - The value to render + * @returns {string} the text to show in the marker + * @callback module:Settings~SliderMarkerValue + */ + +/** + * Used to render the grabber tooltip. + * @param {Number} value - The value to render + * @returns {string} the text to show in the tooltip + * @callback module:Settings~SliderRenderValue + */ + +/** + * Creates a slider/range using discord's built in slider. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class Slider extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {number} min - minimum value allowed + * @param {number} max - maximum value allowed + * @param {number} value - currently selected value + * @param {callable} onChange - callback to fire when setting is changed, callback receives number + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.disabled=false] - should the setting be disabled + * @param {object} [options.fillStyles] - object of css styles to add to active slider + * @param {number} [options.defaultValue] - value highlighted as default + * @param {number} [options.keyboardStep] - step moved when using arrow keys + * @param {Array} [options.markers] - array of vertical markers to show on the slider + * @param {boolean} [options.stickToMarkers] - should the slider be forced to use markers + * @param {boolean} [options.equidistant] - should the markers be scaled to be equidistant + * @param {module:Settings~SliderMarkerValue} [options.onMarkerRender] - function to call to render the value in the marker + * @param {module:Settings~SliderMarkerValue} [options.renderMarker] - alias of `onMarkerRender` + * @param {module:Settings~SliderRenderValue} [options.onValueRender] - function to call to render the value in the tooltip + * @param {module:Settings~SliderRenderValue} [options.renderValue] - alias of `onValueRender` + * @param {string} [options.units] - can be used in place of `onValueRender` will use this string and render Math.round(value) + units + */ + constructor(name, note, min, max, value, onChange, options = {}) { + const props = { + onChange: _ => _, + initialValue: value, + disabled: !!options.disabled, + minValue: min, + maxValue: max, + handleSize: 10 + }; + if (options.fillStyles) props.fillStyles = options.fillStyles; + if (typeof(options.defaultValue) !== "undefined") props.defaultValue = options.defaultValue; + if (options.keyboardStep) props.keyboardStep = options.keyboardStep; + if (options.markers) props.markers = options.markers; + if (options.stickToMarkers) props.stickToMarkers = options.stickToMarkers; + if (typeof(options.equidistant) != "undefined") props.equidistant = options.equidistant; + if (options.units) { + const renderValueLabel = (val) => `${Math.round(val)}${options.units}`; + props.onMarkerRender = renderValueLabel; + props.onValueRender = renderValueLabel; + } + if (options.onMarkerRender || options.renderMarker) props.onMarkerRender = options.onMarkerRender || options.renderMarker; + if (options.onValueRender || options.renderValue) props.onValueRender = options.onValueRender || options.renderValue; + super(name, note, onChange, modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.Slider, Object.assign(props, {onValueChange: v => this.onChange(v)})); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Slider); + +/***/ }), + +/***/ "./src/ui/settings/types/switch.js": +/*!*****************************************!*\ + !*** ./src/ui/settings/types/switch.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + + +const {useCallback, useState, createElement} = modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.React; + +function SwitchComponent({id, checked: initialValue, disabled, onChange}) { + const [checked, setChecked] = useState(initialValue); + const change = useCallback(() => { + onChange?.(!checked); + setChecked(!checked); + }, [checked, onChange]); + + const enabledClass = disabled ? " bd-switch-disabled" : ""; + const checkedClass = checked ? " bd-switch-checked" : ""; + return createElement("div", {className: `bd-switch` + enabledClass + checkedClass}, + createElement("input", {id: id, type: "checkbox", disabled: disabled, checked: checked, onChange: change}), + createElement("div", {className: "bd-switch-body"}, + createElement("svg", {className: "bd-switch-slider", viewBox: "0 0 28 20", preserveAspectRatio: "xMinYMid meet"}, + createElement("rect", {className: "bd-switch-handle", fill: "white", x: "4", y: "0", height: "20", width: "20", rx: "10"}), + createElement("svg", {className: "bd-switch-symbol", viewBox: "0 0 20 20", fill: "none"}, + createElement("path"), + createElement("path") + ) + ) + ) + ); +} + +/** + * Creates a switch using discord's built in switch. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class Switch extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {boolean} isChecked - should switch be checked + * @param {callable} onChange - callback to perform on setting change, callback receives boolean + * @param {object} [options] - object of options to give to the setting + * @param {boolean} [options.disabled=false] - should the setting be disabled + */ + constructor(name, note, isChecked, onChange, options = {}) { + const props = { + disabled: !!options.disabled, + checked: !!isChecked, + onChange: () => value => this.onChange(value), + inline: true + }; + + super(name, note, onChange, SwitchComponent, props); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Switch); + + +/***/ }), + +/***/ "./src/ui/settings/types/textbox.js": +/*!******************************************!*\ + !*** ./src/ui/settings/types/textbox.js ***! + \******************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _settingfield__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../settingfield */ "./src/ui/settings/settingfield.js"); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); + + + +/** + * Creates a textbox using discord's built in textbox. + * @memberof module:Settings + * @extends module:Settings.SettingField + */ +class Textbox extends _settingfield__WEBPACK_IMPORTED_MODULE_0__["default"] { + /** + * @param {string} name - name label of the setting + * @param {string} note - help/note to show underneath or above the setting + * @param {string} value - current text in box + * @param {callable} onChange - callback to perform on setting change, callback receives text + * @param {object} [options] - object of options to give to the setting + * @param {string} [options.placeholder=""] - placeholder for when textbox is empty + * @param {boolean} [options.disabled=false] - should the setting be disabled + */ + constructor(name, note, value, onChange, options = {}) { + const {placeholder = "", disabled = false} = options; + super(name, note, onChange, modules__WEBPACK_IMPORTED_MODULE_1__.DiscordModules.Textbox, { + onChange: textbox => val => { + textbox.props.value = val; + textbox.forceUpdate(); + this.onChange(val); + }, + value: value, + disabled: disabled, + placeholder: placeholder || "" + }); + } +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Textbox); + +/***/ }), + +/***/ "./src/ui/toasts.js": +/*!**************************!*\ + !*** ./src/ui/toasts.js ***! + \**************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Toasts) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/* harmony import */ var ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ui */ "./src/ui/ui.js"); +/* harmony import */ var _styles_toasts_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../styles/toasts.css */ "./src/styles/toasts.css"); +/** + * Toast maker similar to Android. + * + * @module Toasts + */ + + + + + +class Toasts { + + static get CSS() {return _styles_toasts_css__WEBPACK_IMPORTED_MODULE_2__["default"];} + + /** Shorthand for `type = "success"` for {@link module:Toasts.show} */ + static async success(content, options = {}) {return this.show(content, Object.assign(options, {type: "success"}));} + + /** Shorthand for `type = "info"` for {@link module:Toasts.show} */ + static async info(content, options = {}) {return this.show(content, Object.assign(options, {type: "info"}));} + + /** Shorthand for `type = "warning"` for {@link module:Toasts.show} */ + static async warning(content, options = {}) {return this.show(content, Object.assign(options, {type: "warning"}));} + + /** Shorthand for `type = "error"` for {@link module:Toasts.show} */ + static async error(content, options = {}) {return this.show(content, Object.assign(options, {type: "error"}));} + + /** Shorthand for `type = "default"` for {@link module:Toasts.show} */ + static async default(content, options = {}) {return this.show(content, Object.assign(options, {type: "default"}));} + + + /** + * Shows a simple toast, similar to Android, centered over + * the textarea if it exists, and center screen otherwise. + * Vertically it shows towards the bottom like in Android. + * @param {string} content - The string to show in the toast. + * @param {object} options - additional options for the toast + * @param {string} [options.type] - Changes the type of the toast stylistically and semantically. {@link module:Toasts.ToastTypes} + * @param {string} [options.icon] - URL to an optional icon + * @param {number} [options.timeout=3000] - Adjusts the time (in ms) the toast should be shown for before disappearing automatically + * @returns {Promise} - Promise that resolves when the toast is removed from the DOM + */ + static async show(content, options = {}) { + const {type = "", icon = "", timeout = 3000} = options; + this.ensureContainer(); + const toast = modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.parseHTML(this.buildToast(content, this.parseType(type), icon)); + document.querySelector(".toasts").appendChild(toast); + await new Promise(resolve => setTimeout(resolve, timeout)); + toast.classList.add("closing"); + await new Promise(resolve => setTimeout(resolve, 300)); + toast.remove(); + if (!document.querySelectorAll(".toasts .toast").length) document.querySelector(".toasts").remove(); + } + + static buildToast(message, type, icon) { + const hasIcon = type || icon; + const className = `toast ${hasIcon ? "toast-has-icon" : ""} ${type && type != "default" ? `toast-${type}` : ""}`; + if (!icon && type) icon = type; + return modules__WEBPACK_IMPORTED_MODULE_0__.Utilities.formatString(`
{{icon}}
{{message}}
`, { + className: className, + icon: hasIcon ? this.getIcon(icon) : "", + message: message + }); + } + + static getIcon(icon) { + let iconInner = ``; + switch (icon) { + case "success": iconInner = ui__WEBPACK_IMPORTED_MODULE_1__.Icons.IconSuccess(20); break; // eslint-disable-line new-cap + case "warning": iconInner = ui__WEBPACK_IMPORTED_MODULE_1__.Icons.IconWarning(20); break; // eslint-disable-line new-cap + case "info": iconInner = ui__WEBPACK_IMPORTED_MODULE_1__.Icons.IconInfo(20); break; // eslint-disable-line new-cap + case "error": iconInner = ui__WEBPACK_IMPORTED_MODULE_1__.Icons.IconError(20); // eslint-disable-line new-cap + } + return modules__WEBPACK_IMPORTED_MODULE_0__.Utilities.formatString(`
{{icon}}
`, {icon: iconInner}); + } + + static ensureContainer() { + if (document.querySelector(".toasts")) return; + const channelClass = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordSelectors.ChannelList.sidebar; + const container = channelClass ? document.querySelector(`${channelClass} ~ div:not([style])`) : null; + const memberlist = container ? container.querySelector(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordSelectors.MemberList.membersWrap) : null; + const form = container ? container.querySelector("form") : null; + const left = container ? container.getBoundingClientRect().left : 310; + const right = memberlist ? memberlist.getBoundingClientRect().left : 0; + const width = right ? right - container.getBoundingClientRect().left : (container?.offsetWidth ?? document.body.offsetWidth / 2); + const bottom = form ? form.offsetHeight : 80; + const toastWrapper = document.createElement("div"); + toastWrapper.classList.add("toasts"); + toastWrapper.style.setProperty("left", left + "px"); + toastWrapper.style.setProperty("width", width + "px"); + toastWrapper.style.setProperty("bottom", bottom + "px"); + document.querySelector("#app-mount").appendChild(toastWrapper); + } + + static parseType(type) { + return this.ToastTypes.hasOwnProperty(type) ? this.ToastTypes[type] : ""; + } + + /** + * Enumeration of accepted types. + */ + static get ToastTypes() { + return { + "default": "", + "error": "error", + "success": "success", + "warning": "warning", + "info": "info" + }; + } +} + +/***/ }), + +/***/ "./src/ui/tooltip.js": +/*!***************************!*\ + !*** ./src/ui/tooltip.js ***! + \***************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ Tooltip) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/** + * Tooltip that automatically show and hide themselves on mouseenter and mouseleave events. + * Will also remove themselves if the node to watch is removed from DOM through + * a MutationObserver. + * + * Note this is not using Discord's internals but normal DOM manipulation and emulates + * Discord's own tooltips as closely as possible. + * + * @module Tooltip + */ + + + +const getClass = function(sideOrColor) { + const upperCase = sideOrColor[0].toUpperCase() + sideOrColor.slice(1); + const tooltipClass = modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Tooltips[`tooltip${upperCase}`]; + if (tooltipClass) return tooltipClass.value; + return null; +}; + +const classExists = function(sideOrColor) { + return !!getClass(sideOrColor); +}; + +const toPx = function(value) { + return `${value}px`; +}; + +/*
+
+
+ User Settings +
+
*/ + +class Tooltip { + /** + * + * @constructor + * @param {(HTMLElement|jQuery)} node - DOM node to monitor and show the tooltip on + * @param {string} tip - string to show in the tooltip + * @param {object} options - additional options for the tooltip + * @param {string} [options.style=black] - correlates to the discord styling/colors (black, brand, green, grey, red, yellow) + * @param {string} [options.side=top] - can be any of top, right, bottom, left + * @param {boolean} [options.preventFlip=false] - prevents moving the tooltip to the opposite side if it is too big or goes offscreen + * @param {boolean} [options.isTimestamp=false] - adds the timestampTooltip class (disables text wrapping) + * @param {boolean} [options.disablePointerEvents=false] - disables pointer events + * @param {boolean} [options.disabled=false] - whether the tooltip should be disabled from showing on hover + */ + constructor(node, text, options = {}) { + const {style = "black", side = "top", preventFlip = false, isTimestamp = false, disablePointerEvents = false, disabled = false} = options; + this.node = modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.resolveElement(node); + this.label = text; + this.style = style.toLowerCase(); + this.side = side.toLowerCase(); + this.preventFlip = preventFlip; + this.isTimestamp = isTimestamp; + this.disablePointerEvents = disablePointerEvents; + this.disabled = disabled; + this.active = false; + + if (!classExists(this.side)) return modules__WEBPACK_IMPORTED_MODULE_0__.Logger.err("Tooltip", `Side ${this.side} does not exist.`); + if (!classExists(this.style)) return modules__WEBPACK_IMPORTED_MODULE_0__.Logger.err("Tooltip", `Style ${this.style} does not exist.`); + + this.element = modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.createElement(`
`); + this.tooltipElement = modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.createElement(`
${this.label}
`); + this.labelElement = this.tooltipElement.childNodes[1]; + this.element.append(this.tooltipElement); + + if (this.disablePointerEvents) { + this.element.classList.add(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.TooltipLayers.disabledPointerEvents); + this.tooltipElement.classList.add(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Tooltips.tooltipDisablePointerEvents); + } + if (this.isTimestamp) this.tooltipElement.classList.add(modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getByProps("timestampTooltip").timestampTooltip); + + + this.node.addEventListener("mouseenter", () => { + if (this.disabled) return; + this.show(); + }); + + this.node.addEventListener("mouseleave", () => { + this.hide(); + }); + } + + /** Alias for the constructor */ + static create(node, text, options = {}) {return new Tooltip(node, text, options);} + + /** Container where the tooltip will be appended. */ + get container() {return document.querySelector(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordSelectors.App.app.sibling(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordSelectors.TooltipLayers.layerContainer));} + /** Boolean representing if the tooltip will fit on screen above the element */ + get canShowAbove() {return this.node.getBoundingClientRect().top - this.element.offsetHeight >= 0;} + /** Boolean representing if the tooltip will fit on screen below the element */ + get canShowBelow() {return this.node.getBoundingClientRect().top + this.node.offsetHeight + this.element.offsetHeight <= modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.screenHeight;} + /** Boolean representing if the tooltip will fit on screen to the left of the element */ + get canShowLeft() {return this.node.getBoundingClientRect().left - this.element.offsetWidth >= 0;} + /** Boolean representing if the tooltip will fit on screen to the right of the element */ + get canShowRight() {return this.node.getBoundingClientRect().left + this.node.offsetWidth + this.element.offsetWidth <= modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.screenWidth;} + + /** Hides the tooltip. Automatically called on mouseleave. */ + hide() { + /** Don't rehide if already inactive */ + if (!this.active) return; + this.active = false; + this.element.remove(); + this.tooltipElement.className = this._className; + } + + /** Shows the tooltip. Automatically called on mouseenter. Will attempt to flip if position was wrong. */ + show() { + /** Don't reshow if already active */ + if (this.active) return; + this.active = true; + this.tooltipElement.className = `${modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Tooltips.tooltip} ${getClass(this.style)}`; + if (this.disablePointerEvents) this.tooltipElement.classList.add(modules__WEBPACK_IMPORTED_MODULE_0__.DiscordClasses.Tooltips.tooltipDisablePointerEvents); + if (this.isTimestamp) this.tooltipElement.classList.add(modules__WEBPACK_IMPORTED_MODULE_0__.WebpackModules.getByProps("timestampTooltip").timestampTooltip); + this.labelElement.textContent = this.label; + this.container.append(this.element); + + if (this.side == "top") { + if (this.canShowAbove || (!this.canShowAbove && this.preventFlip)) this.showAbove(); + else this.showBelow(); + } + + if (this.side == "bottom") { + if (this.canShowBelow || (!this.canShowBelow && this.preventFlip)) this.showBelow(); + else this.showAbove(); + } + + if (this.side == "left") { + if (this.canShowLeft || (!this.canShowLeft && this.preventFlip)) this.showLeft(); + else this.showRight(); + } + + if (this.side == "right") { + if (this.canShowRight || (!this.canShowRight && this.preventFlip)) this.showRight(); + else this.showLeft(); + } + + /** Do not create a new observer each time if one already exists! */ + if (this.observer) return; + /** Use an observer in show otherwise you'll cause unclosable tooltips */ + this.observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + const nodes = Array.from(mutation.removedNodes); + const directMatch = nodes.indexOf(this.node) > -1; + const parentMatch = nodes.some(parent => parent.contains(this.node)); + if (directMatch || parentMatch) { + this.hide(); + this.observer.disconnect(); + } + }); + }); + + this.observer.observe(document.body, {subtree: true, childList: true}); + } + + /** Force showing the tooltip above the node. */ + showAbove() { + this.tooltipElement.classList.add(getClass("top")); + this.element.style.setProperty("top", toPx(this.node.getBoundingClientRect().top - this.element.offsetHeight - 10)); + this.centerHorizontally(); + } + + /** Force showing the tooltip below the node. */ + showBelow() { + this.tooltipElement.classList.add(getClass("bottom")); + this.element.style.setProperty("top", toPx(this.node.getBoundingClientRect().top + this.node.offsetHeight + 10)); + this.centerHorizontally(); + } + + /** Force showing the tooltip to the left of the node. */ + showLeft() { + this.tooltipElement.classList.add(getClass("left")); + this.element.style.setProperty("left", toPx(this.node.getBoundingClientRect().left - this.element.offsetWidth - 10)); + this.centerVertically(); + } + + /** Force showing the tooltip to the right of the node. */ + showRight() { + this.tooltipElement.classList.add(getClass("right")); + this.element.style.setProperty("left", toPx(this.node.getBoundingClientRect().left + this.node.offsetWidth + 10)); + this.centerVertically(); + } + + centerHorizontally() { + const nodecenter = this.node.getBoundingClientRect().left + (this.node.offsetWidth / 2); + this.element.style.setProperty("left", toPx(nodecenter - (this.element.offsetWidth / 2))); + } + + centerVertically() { + const nodecenter = this.node.getBoundingClientRect().top + (this.node.offsetHeight / 2); + this.element.style.setProperty("top", toPx(nodecenter - (this.element.offsetHeight / 2))); + } +} + +/***/ }), + +/***/ "./src/ui/ui.js": +/*!**********************!*\ + !*** ./src/ui/ui.js ***! + \**********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "ColorPicker": () => (/* reexport safe */ _colorpicker__WEBPACK_IMPORTED_MODULE_8__["default"]), +/* harmony export */ "DiscordContextMenu": () => (/* reexport safe */ _discordcontextmenu__WEBPACK_IMPORTED_MODULE_6__["default"]), +/* harmony export */ "ErrorBoundary": () => (/* reexport safe */ _errorboundary__WEBPACK_IMPORTED_MODULE_7__["default"]), +/* harmony export */ "Icons": () => (/* reexport module object */ _icons__WEBPACK_IMPORTED_MODULE_1__), +/* harmony export */ "Modals": () => (/* reexport safe */ _modals__WEBPACK_IMPORTED_MODULE_5__["default"]), +/* harmony export */ "Popouts": () => (/* reexport safe */ _popouts__WEBPACK_IMPORTED_MODULE_4__["default"]), +/* harmony export */ "Settings": () => (/* reexport module object */ _settings__WEBPACK_IMPORTED_MODULE_0__), +/* harmony export */ "Toasts": () => (/* reexport safe */ _toasts__WEBPACK_IMPORTED_MODULE_3__["default"]), +/* harmony export */ "Tooltip": () => (/* reexport safe */ _tooltip__WEBPACK_IMPORTED_MODULE_2__["default"]) +/* harmony export */ }); +/* harmony import */ var _settings__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./settings */ "./src/ui/settings/index.js"); +/* harmony import */ var _icons__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./icons */ "./src/ui/icons.js"); +/* harmony import */ var _tooltip__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tooltip */ "./src/ui/tooltip.js"); +/* harmony import */ var _toasts__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./toasts */ "./src/ui/toasts.js"); +/* harmony import */ var _popouts__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./popouts */ "./src/ui/popouts.js"); +/* harmony import */ var _modals__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./modals */ "./src/ui/modals.js"); +/* harmony import */ var _discordcontextmenu__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./discordcontextmenu */ "./src/ui/discordcontextmenu.js"); +/* harmony import */ var _errorboundary__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./errorboundary */ "./src/ui/errorboundary.js"); +/* harmony import */ var _colorpicker__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./colorpicker */ "./src/ui/colorpicker.js"); + + + + + + + + + + + + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ (() => { +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ })(); +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; +// This entry need to be wrapped in an IIFE because it need to be in strict mode. +(() => { +"use strict"; +/*!**********************!*\ + !*** ./src/index.js ***! + \**********************/ +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var modules__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! modules */ "./src/modules/modules.js"); +/* harmony import */ var ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ui */ "./src/ui/ui.js"); +/* harmony import */ var _structs_plugin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./structs/plugin */ "./src/structs/plugin.js"); + + + + +const LibraryConfig = __webpack_require__(/*! ./config */ "./src/config.js"); // Use cjs require to prevent polyfill + +const Library = {}; +Library.DCM = ui__WEBPACK_IMPORTED_MODULE_1__.DiscordContextMenu; +Library.ContextMenu = ui__WEBPACK_IMPORTED_MODULE_1__.DiscordContextMenu; +Library.Tooltip = ui__WEBPACK_IMPORTED_MODULE_1__.Tooltip; +Library.Toasts = ui__WEBPACK_IMPORTED_MODULE_1__.Toasts; +Library.Settings = ui__WEBPACK_IMPORTED_MODULE_1__.Settings; +Library.Popouts = ui__WEBPACK_IMPORTED_MODULE_1__.Popouts; +Library.Modals = ui__WEBPACK_IMPORTED_MODULE_1__.Modals; +for (const mod in modules__WEBPACK_IMPORTED_MODULE_0__) Library[mod] = modules__WEBPACK_IMPORTED_MODULE_0__[mod]; + +Library.Components = {ErrorBoundary: ui__WEBPACK_IMPORTED_MODULE_1__.ErrorBoundary, ColorPicker: ui__WEBPACK_IMPORTED_MODULE_1__.ColorPicker}; + +// export default LibraryPlugin(Library.Structs.Plugin, Library); // eslint-disable-line new-cap + +class PluginLibrary extends _structs_plugin__WEBPACK_IMPORTED_MODULE_2__["default"] { + get Library() {return Library;} + + constructor() { + super(LibraryConfig); + + const wasLibLoaded = !!document.getElementById("ZLibraryCSS"); + const isBDLoading = document.getElementById("bd-loading-icon"); + modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.removeStyle("ZLibraryCSS"); + modules__WEBPACK_IMPORTED_MODULE_0__.DOMTools.addStyle("ZLibraryCSS", ui__WEBPACK_IMPORTED_MODULE_1__.Settings.CSS + ui__WEBPACK_IMPORTED_MODULE_1__.Toasts.CSS + modules__WEBPACK_IMPORTED_MODULE_0__.PluginUpdater.CSS); + ui__WEBPACK_IMPORTED_MODULE_1__.Popouts.initialize(); + + /** + * Checking if this is the library first being loaded during init + * This means that subsequent loads will cause dependents to reload + * This also means first load when installing for the first time + * will automatically reload the dependent plugins. This is needed + * for those plugins that prompt to download and install the lib. + */ + + if (!wasLibLoaded && isBDLoading) return; // If the this is the lib's first load AND this is BD's initialization + + /** + * Now we can go ahead and reload any dependent plugins by checking + * for any with instance._config. Both plugins using buildPlugin() + * and plugin skeletons that prompt for download should have this + * instance property. + */ + + // Temporarily disable toasts so people don't get bombarded + const wasEnabled = BdApi.isSettingEnabled("settings", "general", "showToasts"); + if (wasEnabled) BdApi.disableSetting("settings", "general", "showToasts"); + this._reloadPlugins(); + if (wasEnabled) BdApi.enableSetting("settings", "general", "showToasts"); + } + + _reloadPlugins() { + const list = BdApi.Plugins.getAll().reduce((acc, val) => { + if (!val.instance || !val.instance._config) return acc; + const name = val.id || val.instance?.getName(); + if (name === "ZeresPluginLibrary") return acc; + acc.push(name); + return acc; + }, []); + for (let p = 0; p < list.length; p++) BdApi.Plugins.reload(list[p]); + } + + static bindLibrary(name) { + const BoundAPI = { + Logger: { + stacktrace: (message, error) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.stacktrace(name, message, error), + log: (...message) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.log(name, ...message), + error: (...message) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.err(name, ...message), + err: (...message) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.err(name, ...message), + warn: (...message) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.warn(name, ...message), + info: (...message) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.info(name, ...message), + debug: (...message) => modules__WEBPACK_IMPORTED_MODULE_0__.Logger.debug(name, ...message) + }, + Patcher: { + getPatchesByCaller: () => {return modules__WEBPACK_IMPORTED_MODULE_0__.Patcher.getPatchesByCaller(name);}, + unpatchAll: () => {return modules__WEBPACK_IMPORTED_MODULE_0__.Patcher.unpatchAll(name);}, + before: (moduleToPatch, functionName, callback, options = {}) => {return modules__WEBPACK_IMPORTED_MODULE_0__.Patcher.before(name, moduleToPatch, functionName, callback, options);}, + instead: (moduleToPatch, functionName, callback, options = {}) => {return modules__WEBPACK_IMPORTED_MODULE_0__.Patcher.instead(name, moduleToPatch, functionName, callback, options);}, + after: (moduleToPatch, functionName, callback, options = {}) => {return modules__WEBPACK_IMPORTED_MODULE_0__.Patcher.after(name, moduleToPatch, functionName, callback, options);} + } + }; + const BoundLib = Object.assign({}, Library); + BoundLib.Logger = BoundAPI.Logger; + BoundLib.Patcher = BoundAPI.Patcher; + return BoundLib; + } + + static buildPlugin(config) { + return [(0,_structs_plugin__WEBPACK_IMPORTED_MODULE_2__.wrapPluginBase)(config), this.bindLibrary(config.name ?? config.info.name)]; // eslint-disable-line new-cap + } +} + +Object.assign(PluginLibrary, Library); +Library.bindLibrary = PluginLibrary.bindLibrary; +Library.buildPlugin = PluginLibrary.buildPlugin; +window.ZLibrary = Library; +window.ZeresPluginLibrary = PluginLibrary; +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (PluginLibrary); + +})(); + +module.exports.ZeresPluginLibrary = __webpack_exports__["default"]; +/******/ })() +; +/*@end@*/ \ No newline at end of file diff --git a/dotfiles/.config/BetterDiscord/themes/FVUI.theme.css b/dotfiles/.config/BetterDiscord/themes/FVUI.theme.css new file mode 100644 index 0000000..c5658c2 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/themes/FVUI.theme.css @@ -0,0 +1,194 @@ +/** +* @name FVUI +* @author feorev +* @description Blur is the New Black +* @version 2.0.0 +* @authorId 489785940546551831 +* @invite baEMQkgswT +* @source https://github.com/FeoreV/Themes/tree/main/Discord/FVUI +*/ +/* ! DO NOT TOUCH ! */ +@import url(https://feorev.github.io/Themes/Discord/FVUI/Cores/preload.css); +@import url(https://feorev.github.io/Themes/Discord/FVUI/assets/Versions/v2.css); + +/*============================================= += Info = +==============================================* + DiscordServer: https://discord.com/invite/baEMQkgswT + DM - feorev + Site - https://feorev.github.io/ + ThemeEditor - https://bdeditor.dev/theme/FVUI + + Due to the inability to update the theme file frequently, many new additions to the theme will be posted on the discord server. + The FVUI theme extends not only to discord, but also to other sites and areas. You can find a complete list of themes on the discord server. + + + +/*============================================= += FAQ = +==============================================* + +/*---------- How to enable another subtheme? ----------* + + IT IS RECOMMENDED TO USE AN EDITOR ( https://bdeditor.dev/theme/FVUI ) + + Select import and press ctrl+/ + + Enabled - @import url(...) + Disabled - /*@import url(...)*/ + + +/*---------- I want ... ---------- + Make the background a picture - enable WIN11 + Make discord transparent - enable Mica + Change background picture - Change the settings of the win11 subtheme - the --dark/light BG variable + +/*---------- Any other questions? ---------- + Join the discord server c: + https://discord.com/invite/baEMQkgswT + + + +/*============================================= += Subthemes to choose = +=============================================*/ + +/*---------- Default ----------*/ + /* Default */ +@import url(https://feorev.github.io/Themes/Discord/FVUI/assets/SubThemes/Default.css); + /* Lite */ +/* @import url(https://feorev.github.io/Themes/Discord/FVUI/assets/SubThemes/Default_lite.css); */ + +/*---------- CustomBackground ----------*/ + /* WIN11 */ +/* @import url(https://feorev.github.io/Themes/Discord/FVUI/assets/SubThemes/CustomBackground.css); */ + /* Lite11 */ +/* @import url(https://feorev.github.io/Themes/Discord/FVUI/assets/SubThemes/CustomBackground_lite.css); */ + +/*---------- Other ----------*/ + /*MICA (Required "Mica For Everyone" and enabling transparency (Settings=>BD=>Settings=>Window preferences=>Enable transparency)*/ +/* @import url(https://feorev.github.io/Themes/Discord/FVUI/assets/SubThemes/Mica.css); */ + + + +/*============================================= += Addons = +=============================================*/ + + /* Events (Any events and decorations for the holidays. For example, snow and New Year's attributes for the new year.) */ +@import url(https://feorev.github.io/Themes/Discord/FVUI/Addons/Events/Events.css); + + /* ServerRings */ +@import url(https://feorev.github.io/Themes/Discord/FVUI/Addons/ServerRings.css); + + /* RadialStatus */ +@import url(https://feorev.github.io/Themes/Discord/FVUI/Addons/RadialStatus.css); + + /* MaterialYou (The theme color scheme adjusts to the accent color) */ + @import url(https://feorev.github.io/Themes/Discord/FVUI/Addons/MaterialYou.css); */ + + /* ModularDesign (Adds outline to main sections) */ +/* @import url(https://feorev.github.io/Themes/Discord/FVUI/Addons/ModularDesign.css); */ + + /* TopUserPanel (Moves the userpanel to the top corner) */ +/* @import url(https://feorev.github.io/Themes/Discord/FVUI/Addons/TopUserPanel.css); */ + + /* HorizontalServerList (Moves serverlist to the top or bottom corner. For FVUI, it is recommended to use this version of HSL, and not the official one.) */ +/* @import url(https://feorev.github.io/Themes/Discord/FVUI/Addons/HSL/HSL_top.css); */ +/* @import url(https://feorev.github.io/Themes/Discord/FVUI/Addons/HSL/HSL_bottom.css); */ + + /* TitleIsland */ +/* @import url(https://feorev.github.io/Themes/Discord/FVUI/Addons/TitleIsland.css); */ + + + +/*============================================= += SubThemes Settings = +=============================================*/ + + /* Theme editor - https://bdeditor.dev/theme/FVUI */ + + /*---------- General ----------*/ +:root{ + --cursor : url('https://feorev.github.io/Themes/Discord/FVUI/assets/Cursor.cur'); /* Link to cursor / unset - to set standard */ + --font : "Comfortaa"; /* The font must be pre-installed or imported */ + --homeicon: url("https://feorev.github.io/Themes/Discord/FVUI/assets/HomeIcon/Icon.png"); /* Link to image */ +} + +:root{ + --ColorOpacity : 1; /* Opacity multiplier / Default=1 (0.1-...) */ + --DisableOpacity: 0; /* Disable Opacity / Default=0 (0 or 1) */ +} + +:root.theme-dark { + --color1: 30, 40, 50; /* Additional BG / Default= | 30, 40, 50 | (any raw rgb value) */ + --color2: 9, 18, 24; /* Background / Default= | 9, 18, 24 | (any raw rgb value) */ + --color3: 35, 120, 200; /* Additional Color / Default= | 35, 120, 200 | (any raw rgb value) */ + --color4: 63, 43, 150; /* Gradient Color / Default= | 63, 43, 150 | (any raw rgb value) */ +} +:root.theme-light { + --color1: 211, 204, 227; /* Additional BG / Default= | 211, 204, 227 | (any raw rgb value) */ + --color2: 219, 219, 219; /* Background / Default= | 219, 219, 219 | (any raw rgb value) */ + --color3: 96, 205, 255; /* Additional Color / Default= | 96,205,255 | (any raw rgb value) */ + --color4: 63, 43, 150; /* Gradient Color / Default= | 63, 43, 150 | (any raw rgb value) */ +} + + /*---------- CustomBackground ----------*/ +:root{ + --DarkBG : url(https://feorev.github.io/Themes/Discord/FVUI/assets/Wallpapers/dark.avif); /* Link to background image / Default= url(https://feorev.github.io/Themes/Discord/assets/Wallpapers/dark.avif) ( url(link) ) */ + --LightBG : url(https://feorev.github.io/Themes/Discord/FVUI/assets/Wallpapers/light.avif); /* Link to background image / Default= url(https://feorev.github.io/Themes/Discord/assets/Wallpapers/light.avif) ( url(link) ) */ + --OpacityBG: 60%; /* Background image transparency / Default=100% (0%-100%) */ + --BlurBG : 0px; /* Background image blur / Default=0px (any value in px) */ +} + + + /*---------- Mica ----------*/ +:root{ + --BGopacity: 30%; /* Background image transparency / Default=30% (0%-100%) */ + --BGcolor : var(--color2); /* Сolor in RGB / Example = rgb(255,255,255) ( any value in RGB (like example) ) */ +} + + + +/*============================================= += Addons Settings = +=============================================*/ + + /*---------- MaterialYou ----------*/ +:root.theme-dark{ /* First 2 HSL values / Default= | 210, 68% | https://g.co/kgs/83i83K */ + --accentcolor-HUE: 210; + --accentcolor-SATURATION: 68%; +} +:root.theme-light{ /* First 2 HSL values / Default= | 199, 100% | https://g.co/kgs/83i83K */ + --accentcolor-HUE: 199; + --accentcolor-SATURATION: 100%; +} + + /*---------- ModularDesign ----------*/ +:root{ /*MainModules*/ + --outline : 3px; /* outline size / Default=3px (any value in px) */ + --outlineCLR : var(--color03); /* outline color / Example = rgb(255,255,255) ( any value in RGB (like example) ) */ + --outlineBR : 16px; /* outline rounding / Default=16px (any value in px) */ + --outlineMG : 2rem; /* outline margin / Default=2rem (any value in px/rem/...) */ + --outlineBS : 15px; /* outline glow / Default = 15px */ + --outlineBG : unset; /* outline content background color / Example = rgb(255,255,255) ( any value in RGB (like example) */ + --outlineBLUR: 0; /* (ONLY WIN11) backdrop blur / Default=0 ( any value in px */ +} + +:root{ /*AdditionalModules*/ + --outline2 : 0; /* outline size / Default=0px (disabled) (any value in px) */ + --outlineCLR2 : var(--outlineCLR); /* outline color / Example = rgb(255,255,255) ( any value in RGB (like example) ) */ + --outlineBR2 : var(--outlineBR); /* outline rounding / (any value in px) */ + --outlineMG2 : var(--outlineMG); /* outline margin / (any value in px/rem/...) */ + --outlineBS2 : 0; /* outline glow / Default = 0 */ + --outlineBG2 : unset; /* outline content background color / Example = rgb(255,255,255) ( any value in RGB (like example) */ + --outlineBLUR2: 0 /* (ONLY WIN11) (Not recommended - bad performance) backdrop blur / Default=0 ( any value in px ) */ +} + + /*---------- Have you tuned the theme well? ----------*/ +/* You can share your vision of the world in the channel of the Discord server by sending the settings: https://discord.gg/baEMQkgswT */ + +/*============================================= += Credits = += gibbu - RadialStatus / ServerRings = +=============================================*/ diff --git a/dotfiles/.config/BetterDiscord/themes/Translucence.theme.css b/dotfiles/.config/BetterDiscord/themes/Translucence.theme.css new file mode 100644 index 0000000..97cab47 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/themes/Translucence.theme.css @@ -0,0 +1,90 @@ +/** + * @name Translucence + * @version 1.0.7 + * @description A translucent/frosted glass Discord theme + * @author CapnKitten + * + * @website http://github.com/CapnKitten + * @source https://github.com/CapnKitten/BetterDiscord/blob/master/Themes/Translucence/css/source.css + * @donate https://paypal.me/capnkitten + * @invite jzJkA6Z + */ + +@import url(https://capnkitten.github.io/BetterDiscord/Themes/Translucence/css/source.css); + +:root { + /* APP ELEMENTS */ + --app-bg: ""; + --app-blur: 6px; + --app-margin: 24px; + --app-radius: 8px; + + /* ACCENT HSL AND TEXT COLOR SETTINGS */ + --accent-hue: 156; + --accent-saturation: 50%; + --accent-lightness: 25%; + --accent-opacity: 1; + --accent-text-color: hsl(0,0%,0%); + + /* SIDEBARS AND CHAT AREA COLOR SETTINGS */ + --sidebar-color: hsl(0,0%,0%,0.4); + --main-content-color: hsl(0,0%,0%,0.2); + + /* MESSAGE SETTINGS */ + --message-color: hsl(0,0%,0%,0.4); + --message-radius: 8px; + --message-padding-top: 8px; + --message-padding-side: 8px; + + /* REPLY HSL COLOR SETTINGS */ + --reply-hue: 226; + --reply-saturation: 77.4%; + --reply-lightness: 61.8%; + --reply-opacity: 1; + + /* TEXTAREA SETTINGS */ + --textarea-color: 255,255,255; + --textarea-alpha: 0.1; + --textarea-alpha-focus: 0.15; + --textarea-text-color: hsl(0,0%,100%); + --textarea-radius: 22px; + + /* CARD SETTINGS */ + --card-color: hsl(0,0%,0%,0.4); + --card-color-hover: hsl(0,0%,0%,0.5); + --card-color-select: hsl(0,0%,0%,0.7); + + /* BUTTON SETTINGS */ + --button-height: 32px; + --button-padding: 0 16px; + --button-action-color: hsl(0,0%,0%); + --button-radius: 16px; +} + +@supports (color:color-mix(in lch,red,blue)) { + .visual-refresh .theme-dark, + .visual-refresh.theme-dark { + /* TEXT COLOR SETTINGS */ + --text-primary: hsl(0,0%,100%); + --text-secondary: hsl(0,0%,77%); + + /* CHANNEL COLOR SETTINGS */ + --channels-default: hsl(0,0%,62%); + --channel-icon: hsl(0,0%,62%); + + /* ICON COLOR SETTINGS */ + --icon-primary: hsl(0,0%,96%); + --icon-secondary: hsl(0,0%,84%); + --icon-tertiary: hsl(0,0%,71%); + + /* INTERACTIVE COLOR SETTINGS */ + --interactive-normal: hsl(0,0%,79%); + --interactive-hover: hsl(0,0%,91%); + --interactive-active: hsl(0,0%,100%); + --interactive-muted: hsl(0,0%,43%); + + /* BACKGROUND MODIFIER SETTINGS */ + --background-modifier-hover: hsl(0,0%,100%,0.075); + --background-modifier-selected: hsl(0,0%,100%,0.125); + } +} diff --git a/dotfiles/.config/BetterDiscord/themes/arindy.theme.css b/dotfiles/.config/BetterDiscord/themes/arindy.theme.css new file mode 100644 index 0000000..a70d814 --- /dev/null +++ b/dotfiles/.config/BetterDiscord/themes/arindy.theme.css @@ -0,0 +1,49 @@ +/** + * @name Arindy + * @author FeoreV + * @version 2.0.0 + * @description Blur is the new black + * @source https://github.com/FeoreV/Themes/tree/main/Discord/FVUI + * @invite baEMQkgswT + * @website https://feorev.github.io/ + * @authorId 489785940546551831 + * @BDEditor FVUI +*/ + +@import url('https://fonts.googleapis.com/css2?family=Fira+Mono:wght@100;300;400;500;700&display=swap'); +@import url('https://feorev.github.io/Themes/Discord/FVUI/Cores/preload.css'); +@import url('https://feorev.github.io/Themes/Discord/FVUI/assets/SubThemes/Mica.css'); +@import url('https://feorev.github.io/Themes/Discord/FVUI/Addons/ModularDesign.css'); +@import url('https://feorev.github.io/Themes/Discord/FVUI/Addons/RadialStatus.css'); +@import url('https://feorev.github.io/Themes/Discord/FVUI/Addons/TitleIsland.css'); + +:root { + --undefined: 0; + --color1: 48,48,48; + --color2: 0,0,0; + --color3: 76,175,80; + --undefined: 0; + --ColorOpacity: 0.5; + --DisableOpacity: 0; + --cursor: transparent; + --font: Fira Mono; + --homeicon: url('https://feorev.github.io/Themes/Discord/FVUI/assets/HomeIcon/Icon.png'); + --undefined: 0; + --FVUI: Arindy; + --customdesc: ""; + --BGopacity: 50%; + --BGcolor: #00000088; + --undefined: 0; + --outline: 1px; + --outlineCLR: var(--color03); + --outlineBR: 8px; + --outlineMG: 0.7rem; + --outlineBS: 14px; + --outlineBGclr: unset; + --outlineBGalpha: 1; + --outlineBLUR: ""; +} + +/* Any custom CSS below here */ + + diff --git a/dotfiles/.config/ffz/ffz-settings.json b/dotfiles/.config/ffz/ffz-settings.json new file mode 100644 index 0000000..d10daf8 --- /dev/null +++ b/dotfiles/.config/ffz/ffz-settings.json @@ -0,0 +1 @@ +{"version":2,"type":"full","values":{"p:0:chat.rich.minimum-level":2,"p:0:ffzenhancing.doubleclick_username_paste_in_chat":true,"p:0:addon.seventv_emotes.global_emotes":true,"p:0:chat.filtering.display-deleted":"DETAILED","p:0:chat.badges.unify-bot-badge":1,"p:0:addon.fs-chat.bg.color":"rgba(0, 0, 0, 0.76)","p:0:chat.tab-complete.prioritize-favorites":true,"p:0:chat.emote-menu.enabled":true,"p:0:ffzap.core.remove_spaces":true,"p:0:chat.filtering.all-mentions":true,"p:0:addon.pronouns.border":false,"p:0:chat.emote-menu.clear-search":true,"p:0:addon.fs-chat.bg.blur":2,"p:0:chat.filtering.ignore-clear":true,"p:0:chat.emote-menu.tall":true,"p:0:addon.fs-chat.hide-scroll":true,"p:0:chat.badges.version":2,"p:0:layout.hide-discover-luna":true,"p:0:ffzenhancing.hide_rooms_header":true,"p:0:addon.inlinetab.tip-count":true,"p:0:ffzenhancing.auto_reload_on_error_2000":true,"p:0:ffzenhancing.fix_addon_load":true,"p:0:theme.color.background":"#000000","p:0:addon.seventv_emotes.channel_emotes":true,"p:0:chat.filtering.highlight-basic-terms":[{"v":{"v":"Dietmar","t":"text","c":"","remove":false,"w":true,"h":true,"p":0},"id":"1595317445044-0.7792339469748495-0"},{"v":{"remove":false,"v":"iAringlos","t":"text","c":"","w":true,"h":true,"p":0},"id":"1607534685212-0.41745400101864205-1"},{"v":{"remove":false,"v":"Diemtar","t":"text","c":"","s":false,"h":true,"w":true,"p":0},"id":"1648745831596-0.9952435336503118-0"},{"v":{"remove":false,"v":"Sugarindy","t":"text","c":"","s":false,"h":true,"w":true,"p":0},"id":"1650998038610-0.44650505731550405-0"},{"v":{"remove":false,"v":"ᚱᛁᚾᛞᛁ","t":"text","c":"","s":false,"h":true,"w":true,"p":0},"id":"1661372889708-0.5870106770328385-0"},{"v":{"remove":false,"v":"ᚨᚱᛁᚾᛞᛁ","t":"text","c":"","s":false,"h":true,"w":true,"p":0},"id":"1668202598210-0.35010810098518386-0"}],"p:0:chat.filtering.highlight-mentions":true,"p:0:channel.hosting.enable":false,"p:0:ffzenhancing.pin_mentions":false,"p:0:chat.bits.show-pinned":true,"p:0:chat.replies.style":1,"p:0:chat.tab-complete.prioritize-prefix-matches":true,"p:0:player.embed-metadata":true,"p:0:layout.portrait":true,"p:0:theme.font.size":"11","p:0:chat.points.auto-rewards":true,"p:0:layout.theatre-navigation":true,"p:0:theme.color.chat-background":"#000000","p:0:ffzenhancing.fix_emote_select":true,"emoji-tone":"1f3fb","p:0:chat.points.allow-highlight":2,"p:0:theme.color.text":"#E8E8E8","p:0:chat.emoji.style":0,"p:0:chat.pin-resubs":true,"p:0:smokemotes.pinned_desktop_notification":true,"p:0:chat.shared-chat.style":2,"addons.enabled":["7tv-emotes","ffzap-core","ffzap-bttv","fs-chat","smokemotes","inline-tab-completion","chatterino-badges","ModTools","pronouns"],"p:0:chat.font-family":"google:Fira Sans","shared-chat-notice":1,"p:0:addon.seventv_emotes.badges":false,"p:0:chat.font-size":12,"p:0:addon.fs-chat.automatic":true,"p:0:theme.color.accent":"#005656","p:0:chat.me-style":3,"p:0:metadata.viewers.no-native":false,"p:0:player.theatre.auto-enter":false,"p:0:directory.hide-vodcasts":true,"p:0:directory.default-sort":"VIEWER_COUNT","p:0:chat.emote-menu.show-quick-nav":true,"p:0:ffzenhancing.auto_click_claim_bonus_points":true,"p:0:chat.emote-menu.modifiers":1,"p:0:chat.banners.prediction":true,"p:0:chat.emote-menu.icon":false,"p:0:chat.drops.auto-rewards":true,"p:0:chat.community-chest.show":true,"p:0:layout.side-nav.hide-stories":true,"emote-menu.collapsed":["twitch-592920959","twitch-61198352","twitch-472873131","twitch-610186276","twitch-564265402","twitch-173255814","BetterTTV-addon--ffzap.betterttv--channel-twitch:468712293","ffz-649104","BetterTTV-addon--ffzap.betterttv--channel-twitch:97113088","twitch-38547166"],"p:0:ffzenhancing.auto_reload_on_hanged_video":true,"p:0:layout.portrait-invert":false,"p:0:ffzenhancing.highlight_user_messages":true,"p:0:layout.portrait-min-chat":true,"exp-lock":1676310632019,"p:0:chat.filtering.color-mentions":true,"p:0:addon.seventv_emotes.personal_emotes":true,"p:0:layout.turbo-cta":false,"p:0:ffzenhancing.move_users_in_chat_to_bottom":true,"p:0:theme.disable-auto-dark":true,"p:0:ffzenhancing.hide_chat_collapse_button":true,"p:0:player.home.autoplay":false,"p:0:chat.lines.alternate":false,"p:0:chat.filtering.show-reasons":1,"p:0:metadata.modview.hide-info":true,"p:0:player.theatre.metadata":true,"p:0:chat.badges.hidden":{"3":true,"premium":true,"turbo":true,"glhf-pledge":true,"m-tcon":true,"m-owl":true,"m-game":true,"m-ffz":true,"m-addon":false,"m-other":true,"creator-cs-go-2022":true,"chatter-cs-go-2022":true,"addon--ffzap.betterttv--badges-bttv-emote_approver":true,"addon--ffzap.betterttv--badges-bttv-pro":true,"addon--ffzap.betterttv--badges-bttv-translator":true,"addon.chatterino_badges.badge-1":true,"addon.chatterino_badges.badge-0":true,"addon.chatterino_badges.badge-2":true,"addon.chatterino_badges.badge-4":true,"addon.chatterino_badges.badge-3":true,"addon--ffzap.core--badges-supporter":true,"addon--ffzap.betterttv--badges-bttv-developer":true,"addon--ffzap.betterttv--badges-bttv-support_volunteer":true},"p:0:layout.display-bits-button":false,"p:0:channel.raids.no-autojoin":false,"p:0:tooltip.emote-images.animated":true,"cfg-seen":["i18n.debug.capture","i18n.debug.transform","i18n.locale","socket.use-cluster","chat.actions.reasons","chat.actions.inline","chat.actions.user-context","chat.actions.room","chat.actions.room-above","chat.actions.rules-as-reasons","chat.badges.version","chat.badges.fix-colors","chat.badges.custom-mod","chat.badges.style","chat.badges.hidden","chat.fix-bad-emotes","chat.click-emotes","chat.sub-emotes","chat.emote-dialogs","chat.scrollback-length","chat.delay","chat.pin-resubs","chat.emoji.style","chat.font-size","chat.font-family","chat.width","chat.lines.emote-alignment","chat.timestamp-format","chat.lines.alternate","chat.lines.padding","chat.lines.borders","chat.rich.enabled","chat.rich.hide-tokens","chat.rich.all-links","chat.rich.minimum-level","chat.filtering.click-to-reveal","chat.filtering.process-own","chat.filtering.deleted-style","chat.filtering.display-deleted","chat.filtering.display-mod-action","chat.filtering.ignore-clear","chat.filtering.remove-deleted","chat.automod.delete-messages","chat.automod.remove-messages","chat.automod.run-as-mod","chat.filtering.highlight-basic-users","chat.filtering.highlight-basic-users-blocked","chat.filtering.highlight-basic-badges","chat.filtering.highlight-basic-badges-blocked","chat.filtering.highlight-basic-terms","chat.filtering.highlight-basic-blocked","chat.filtering.clickable-mentions","chat.filtering.bold-mentions","chat.filtering.mention-color","chat.filtering.highlight-mentions","chat.filtering.highlight-tokens","tooltip.images","tooltip.badge-images","tooltip.emote-sources","tooltip.emote-images","tooltip.rich-links","tooltip.link-interaction","tooltip.link-images","tooltip.link-nsfw-images","chat.adjustment-mode","chat.adjustment-contrast","chat.bits.stack","chat.bits.animated","chat.bits.show","metadata.player-stats","metadata.stream-delay-warning","metadata.uptime","metadata.viewers","metadata.viewers.no-native","metadata.uptime.no-native","metadata.featured-follow","metadata.host-button","layout.display-bits-button","layout.theatre-navigation","layout.minimal-navigation","layout.discover","layout.prime-offers","ffz.show-new-settings","channel.auto-click-chat","chat.scroller.freeze","chat.scroller.freeze-requires-hover","chat.scroller.hover-delay","chat.scroller.smooth-scroll","chat.emote-menu.shortcut","chat.emote-menu.enabled","chat.emote-menu.default-tab","chat.emote-menu.combine-tabs","chat.emote-menu.icon","chat.emote-menu.show-heading","chat.emote-menu.show-search","chat.emote-menu.reduced-padding","chat.emote-menu.sort-emotes","chat.emote-menu.sort-tiers-last","chat.mru.enabled","chat.tab-complete.ffz-emotes","chat.tab-complete.emoji","chat.tab-complete.emotes-without-colon","chat.tab-complete.limit-results","chat.tab-complete.prioritize-favorites","chat.viewer-cards.highlight-chat","chat.viewer-cards.color","channel.raids.no-autojoin","chat.hide-community-highlights","chat.subs.gift-banner","chat.banners.hype-train","chat.banners.polls","chat.community-chest.show","chat.bits.show-pinned","chat.points.allow-highlight","chat.points.show-callouts","chat.points.show-button","chat.points.show-rewards","chat.bits.show-rewards","chat.rituals.show","chat.subs.show","chat.subs.compact","chat.subs.merge-gifts","chat.subs.merge-gifts-visibility","chat.input.show-mod-view","layout.swap-sidebars","layout.side-nav.show","layout.side-nav.show-avatars","layout.side-nav.show-rec-channels","layout.side-nav.show-friends","layout.side-nav.hide-offline","layout.side-nav.rerun-style","whispers.show","channel.show-celebrations","channel.hide-live-indicator","channel.round-avatars","channel.hide-not-live-bar","player.hide-event-bar","player.hide-rerun-bar","layout.theme.global-font","directory.following.group-hosts","directory.following.host-menus","directory.uptime","directory.show-channel-avatars","directory.hide-live","directory.hide-vodcasts","directory.hide-recommended","directory.hide-viewing-history","directory.hide-latest-videos","layout.portrait","layout.portrait-invert","layout.portrait-threshold","player.theatre.metadata","player.theatre.no-whispers","player.theatre.auto-enter","player.compressor.enable","player.compressor.default","player.compressor.threshold","player.compressor.knee","player.compressor.ratio","player.compressor.attack","player.compressor.release","player.allow-catchup","player.button.reset","player.button.pip","player.hide-mouse","player.mute-click","player.volume-scroll","player.volume-scroll-steps","player.volume-always-shown","player.captions.font-size","player.captions.font-family","player.ext-hide","player.ext-interaction","player.home.autoplay","player.no-autoplay","player.vod.autoplay","sub-button.prime-notice","theme.color.background","theme.color.text","theme.dark","chat.video-chat.timestamps","chat.video-chat.enabled","reports.error.enable","reports.error.include-user","reports.error.include-settings","add-ons","addons.dev.server","experiments","i18n.debug.open","socket.info","profiles","backup","home","faq","feedback","feedback.log","changelog","addon-changelog","legal","reports.error.example","channel.hosting.enable","directory.hide-promoted","directory.hidden.reveal","directory.hidden.style","channel.extra-links","channel.hide-unfollow","metadata.clip-download","chat.filtering.color-mentions","channel.panel-tips","i18n.format.date","i18n.format.datetime","i18n.format.time","theme.color.tooltip.background","theme.color.tooltip.text","theme.color.accent","channel.auto-skip-trailer","chat.replies.style","chat.badges.clickable","chat.filtering.blocked-types","theme.font.size","directory.game.blocked-games","directory.game.hidden-thumbnails","layout.portrait-min-chat","theme.color.chat-background","theme.color.chat-text","theme.color.chat-accent","layout.side-nav.hide-viewers","metadata.modview.hide-info","debug.link-resolver.source","debug.link-resolver.test","ffzenhancing.about","ffzenhancing.doubleclick_username_paste_in_chat","ffzenhancing.focus_input_area_after_emote_select","ffzenhancing.hide_rooms_header","ffzenhancing.move_users_in_chat_to_bottom","ffzenhancing.animate_static_gif_emotes_on_mouse_hover","ffzenhancing.auto_click_claim_bonus_points","ffzenhancing.fix_addon_load","ffzenhancing.fix_emote_select","ffzenhancing.fix_tooltips","ffzenhancing.highlight_user_messages","ffzenhancing.pin_mentions","ffzenhancing.auto_check_player_compressor","ffzenhancing.auto_check_player_quality","ffzenhancing.auto_reload_on_error_2000","ffzenhancing.auto_reload_on_hanged_video","ffzenhancing.auto_reload_on_hanged_video_after","ffzenhancing.fix_video_freeze_on_tab_change","ffzenhancing.visibility_hook_time","ffzenhancing.keep_delay_low","ffzenhancing.keep_delay_low_delay","ffzenhancing.keep_delay_low_delay_low_latency","ffzenhancing.keep_delay_low_rate","ffzenhancing.reset_after_delay","ffzenhancing.reset_after_delay_delay","ffz.search.matches-only","clips.layout.big","layout.hide-discover-luna","theme.disable-auto-dark","chat.actions.size","chat.timestamp-size","chat.extra-timestamps","chat.me-style","chat.banners.drops","chat.banners.prediction","chat.emotes.enabled","chat.emotes.animated","chat.emotes.2x","chat.emotes.limit-size","chat.rich.want-mid","chat.name-format","chat.badges.custom-vip","chat.emoji.replace-joiner","chat.bits.cheer-notice","chat.emote-menu.show-quick-nav","chat.emote-menu.tall","chat.emote-menu.modifiers","chat.emote-menu.show-emoji","chat.emote-menu.stay-loaded","chat.filtering.mention-priority","chat.filtering.debug","chat.filtering.pad-bottom","chat.input.hide-identity","chat.tab-complete.prioritize-prefix-matches","tooltip.emote-images.animated","player.embed-metadata","player.compressor.shortcut","player.gain.enable","player.gain.default","player.gain.max","player.gain.min","player.gain.no-volume","player.gain.scroll","smokemotes.keep_hd_video","smokemotes.auto_live_follow_page","smokemotes.mod_keybinds","smokemotes.auto_exit_viewercard","smokemotes.pinned_mentions","smokemotes.pinned_messages_remove","smokemotes.pinned_reasons","smokemotes.pinned_messages_max","smokemotes.pinned_timer","smokemotes.pinned_border","smokemotes.pinned_font_color","smokemotes.pinned_bg","addon.pronouns.border","addon.pronouns.color","addon.pronouns.color.aeaer","addon.pronouns.color.any","addon.pronouns.color.eem","addon.pronouns.color.faefaer","addon.pronouns.color.hehim","addon.pronouns.color.heshe","addon.pronouns.color.hethem","addon.pronouns.color.itits","addon.pronouns.color.other","addon.pronouns.color.perper","addon.pronouns.color.sheher","addon.pronouns.color.shethem","addon.pronouns.color.theythem","addon.pronouns.color.vever","addon.pronouns.color.xexem","addon.pronouns.color.ziehir","addon.fs-chat.bg.blur","addon.fs-chat.bg.color","addon.fs-chat.metadata","addon.fs-chat.round","addon.fs-chat.minimal","addon.fs-chat.no-input","addon.fs-chat.automatic","addon.fs-chat.shortcut","addon.fs-chat.height","addon.fs-chat.width","ffzap.core.message_deletion","ffzap.core.remove_spaces","ffzap.core.enable_highlight_sound","ffzap.core.highlight_sound","ffzap.core.highlight_sound_volume","ffzap.core.highlight_sound_prevent_own_channel","ffzap.core.highlight_sound_types","ffzap.betterttv.arbitrary_emoticons","ffzap.betterttv.channel_emoticons","ffzap.betterttv.global_emoticons","ffzap.betterttv.pro_emoticons","addon.seventv_emotes.channel_emotes","addon.seventv_emotes.global_emotes","addon.seventv_emotes.unlisted_emotes","addon.seventv_emotes.emote_updates","addon.seventv_emotes.update_messages","addon.seventv_emotes.animated_avatars","addon.seventv_emotes.badges","addon.seventv_emotes.nametag_paints","clear","addon.inlinetab.tip-count","addon.inlinetab.tips","addon.inlinetab.no_commands","addon.inlinetab.no_mentions","addon.inlinetab.mention_prefix","chat.points.auto-rewards","directory.block-titles","chat.banners.last-events","addon.chatterino_badges.badges","addon.fs-chat.hide-scroll","chat.filtering.hidden-tokens","player.compressor.force-legacy","chat.emote-menu.clear-search","chat.input.show-elevate-your-message","player.single-click-pause","chat.actions.hover","chat.input.show-highlight","chat.input.show-shield","player.fade-pause-buffer","chat.actions.hover-size","modtools.about","modtools.highlights.clear","modtools.highlight-color","chat.effects.enable","chat.effects.FlipX","chat.effects.FlipY","chat.effects.Greyscale","chat.effects.GrowX","chat.effects.GrowY","chat.effects.HyperRed","chat.effects.Photocopy","chat.effects.Rainbow","chat.effects.Rotate45","chat.effects.Rotate90","chat.effects.Sepia","chat.effects.Shake","chat.effects.ShrinkX","chat.effects.ShrinkY","chat.emote-menu.effect-tab","ffzap.betterttv.emote_modifiers","chat.update-when-loaded","chat.inline-preview.enabled","ffzap.betterttv.pro_badges","chat.banners.charity","theme.legacy-dark-input","chat.effects.Appear","chat.effects.Bounce","chat.effects.Jam","chat.effects.Leave","chat.effects.Rotate","chat.effects.Slide","ffzenhancing.always_show_open_thread_button","chat.hype.message-style","chat.hype.show-pinned","chat.hype.display-input","player.disable-content-warnings","channel.auto-click-off-featured","layout.subtember","chat.drops.auto-rewards","chat.filtering.all-mentions","chat.tab-complete.matching","channel.raids.blocked-channels","chat.banners.kappa-train","directory.blocked-tags","layout.turbo-cta","player.fullscreen.auto-chat","link-cards.enable","link-cards.use-destination","ffzenhancing.hide_chat_collapse_button","addon.seventv_emotes.personal_emotes","directory.default-sort","directory.wait-flags","directory.block-flags","directory.blur-flags","directory.blur-tags","directory.blur-titles","directory.show-flags","chat.filtering.show-reasons","tooltip.tos.YouTube","theme.disable-high-contrast","theme.high-contrast-tweaks","chat.emote-menu.tooltips","ffzap.betterttv.update_messages","ffzenhancing.pause_vod_on_click","addon.pronouns.streamer","chat.emotes.source-priorities","channel.gift-sub-buttons.hide","layout.side-nav.hide-stories","addon.seventv_emotes.emote_format","addon.seventv_emotes.nametag_paints_drop_shadows","chat.banners.hide-appleplus","chat.banners.pinned-message","chat.callouts.clip","chat.powerup.effects","chat.emotes.allow-gigantify","chat.subs.native","chat.shared-chat.style","chat.shared-chat.username-tooltip","chat.banners.shared-chat","chat.filtering.blocked-callouts","directory.hide-costream-border","directory.block-users","player.cast-button.hide","player.clip-button.custom","player.clip-button.hide-native","smokemotes.timeout_duration","smokemotes.confirm_mod_actions","smokemotes.keybind_timeout","smokemotes.keybind_ban","smokemotes.keybind_purge","smokemotes.keybind_delete","smokemotes.pinned_desktop_notification","smokemotes.pinned_animations","layout.combos","chat.badges.unify-bot-badge"],"p:0:layout.combos":true,"p:0:chat.lines.padding":true,"p:0:chat.banners.charity":true,"p:0:player.ext-interaction":true,"p:0:addon.fs-chat.minimal":true,"p:0:directory.hidden.reveal":true,"p:0:smokemotes.confirm_mod_actions":true,"p:0:player.hide-mouse":false,"p:0:chat.rich.hide-tokens":false,"p:0:chat.rich.all-links":true,"fschat.left":"23px","p:0:chat.subs.merge-gifts":0,"p:0:chat.emote-menu.combine-tabs":true,"p:0:layout.minimal-navigation":false,"p:0:addon.pronouns.color":"rgba(211, 33, 202, 0)","p:0:chat.badges.style":6,"p:0:channel.auto-click-off-featured":true,"p:0:channel.auto-click-chat":true,"p:0:theme.legacy-dark-input":false,"p:0:addon.inlinetab.mention_prefix":false,"p:0:player.disable-content-warnings":true,"p:0:directory.show-flags":true,"p:0:layout.subtember":false,"p:0:ffzenhancing.animate_static_gif_emotes_on_mouse_hover":true,"p:0:ffzenhancing.fix_tooltips":true,"p:0:chat.filtering.remove-deleted":0,"p:0:ffzap.core.enable_highlight_sound":true,"p:0:link-cards.use-destination":true,"p:0:chat.width":500,"p:0:player.ext-hide":0,"p:0:chat.subs.compact":true,"p:0:ffzenhancing.reset_after_delay":true,"cfg-collapsed":["chat.appearance"],"p:0:ffzap.core.highlight_sound_prevent_own_channel":true,"agreed-tos":["YouTube"],"p:0:chat.subs.gift-banner":true,"p:0:chat.filtering.highlight-tokens":true,"p:0:chat.hide-community-highlights":false,"p:0:metadata.viewers":false,"p:0:chat.badges.clickable":false,"p:0:smokemotes.auto_live_follow_page":true,"p:0:tooltip.link-nsfw-images":true,"p:0:channel.panel-tips":true,"p:0:link-cards.enable":true,"p:0:directory.hide-recommended":true,"p:0:player.fullscreen.auto-chat":true,"p:0:channel.hide-unfollow":false,"p:0:metadata.uptime.no-native":true,"favorite-emotes.twitch":["emotesv2_ca751add6fcd418ab7a1fce6f1655d0d","emotesv2_c95c81eab02349c5a2ab5a23354d94de","emotesv2_85abaafb56cb4472a6600aa796747079","emotesv2_0ed53521d91a476a81b22a73a6d3cd63","emotesv2_70b97c46d1e44c54b8a37871eccf796b","emotesv2_2f0fc1f48aa34ba09c3090ecf08e2b0a","emotesv2_49d1569e851b469a96ec4a5c54f6a5ca","emotesv2_abf71afa383046a48c1594d2a45c8293","302602177","emotesv2_1f5784c29bf74755affb1bb4ba84c56a","emotesv2_b5eae7ddd8124de484b581d170e892b1","emotesv2_f8c21a492bee4569a3cd2b742f5f50cc","emotesv2_9e9e6f45e1204a108125e80cbfa4f8b2","emotesv2_f1a23a6ad195406ca6c11207c8c47994","emotesv2_377ed7e0bce04cd6ba7d6a0fdb971e82","emotesv2_c8381bb5233f44e189b9b3f3c72d314a","emotesv2_f86027503d8041168cce9d74c4f9fa51","emotesv2_a78791b959c44424a35b89c76880821e","emotesv2_09e47ed8a3a84b15a830f0b59fbcae59","302413499"],"p:0:layout.side-nav.hide-offline":true,"p:0:chat.emotes.2x":0,"p:0:directory.hide-promoted":true,"p:0:directory.wait-flags":false,"p:0:chat.tab-complete.emotes-without-colon":false,"p:0:chat.points.show-callouts":true,"fschat.top":"428px","p:0:theme.dark":false,"p:0:channel.auto-skip-trailer":true,"p:0:layout.side-nav.show-rec-channels":0,"p:0:layout.theme.global-font":"google:Fira Sans"}} \ No newline at end of file diff --git a/dotfiles/.config/hypr/conf/autostart.conf b/dotfiles/.config/hypr/conf/autostart.conf index c2518ef..264a02e 100644 --- a/dotfiles/.config/hypr/conf/autostart.conf +++ b/dotfiles/.config/hypr/conf/autostart.conf @@ -15,5 +15,5 @@ exec-once = systemctl --user start hyprpolkitagent exec-once = hyprpaper & exec-once = waybar & exec-once = dunst & - +exec-once = discord & diff --git a/dotfiles/.config/hypr/conf/env.conf b/dotfiles/.config/hypr/conf/env.conf index 7e76d48..03eb232 100644 --- a/dotfiles/.config/hypr/conf/env.conf +++ b/dotfiles/.config/hypr/conf/env.conf @@ -49,6 +49,8 @@ env = EDITOR,vim env = SHELL,fish env = BROWSER,$browser +env = MANGOHUD,1 + cursor { no_hardware_cursors = true } \ No newline at end of file diff --git a/dotfiles/.config/hypr/conf/input.conf b/dotfiles/.config/hypr/conf/input.conf index b8eb6ad..f367588 100644 --- a/dotfiles/.config/hypr/conf/input.conf +++ b/dotfiles/.config/hypr/conf/input.conf @@ -13,7 +13,7 @@ input { follow_mouse = 1 sensitivity = 0 # -1.0 - 1.0, 0 means no modification. - + numlock_by_default = 1 touchpad { natural_scroll = false } diff --git a/dotfiles/.config/hypr/conf/keybinds.conf b/dotfiles/.config/hypr/conf/keybinds.conf index f296f21..0ae3016 100644 --- a/dotfiles/.config/hypr/conf/keybinds.conf +++ b/dotfiles/.config/hypr/conf/keybinds.conf @@ -6,16 +6,16 @@ $mainMod = SUPER # Sets "Windows" key as main modifier # Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more -bind = $mainMod, T, exec, $terminal -bind = $mainMod, C, killactive, -bind = $mainMod, E, exec, $fileManager -bind = $mainMod, Q, exec, $browser -bind = $mainMod, V, togglefloating, -bind = $mainMod, space, exec, $menu +bind = $mainMod, T, exec, $terminal # Open Terminal +bind = $mainMod, C, killactive, # Kill active Window +bind = $mainMod, E, exec, $fileManager # Open File Manager +bind = $mainMod, Q, exec, $browser # Open Browser +bind = $mainMod, V, togglefloating, # Toggle Window floating +bind = $mainMod, space, exec, $menu # Open Menu bind = $mainMod, P, pseudo, # dwindle -bind = $mainMod, F11, fullscreen, # -bind = $mainMod, J, togglesplit, # dwindle -bind = $mainMod, L, exec, hyprlock +bind = $mainMod, F11, fullscreen, # Toggle Window fullscreen +bind = $mainMod, J, togglesplit, # Toggle Window split +bind = $mainMod, L, exec, hyprlock # Lock Desktop # Move focus with mainMod + arrow keys bind = $mainMod, left, movefocus, l diff --git a/dotfiles/.config/hypr/conf/windows.conf b/dotfiles/.config/hypr/conf/windows.conf index 95c2d93..1d1a2f6 100644 --- a/dotfiles/.config/hypr/conf/windows.conf +++ b/dotfiles/.config/hypr/conf/windows.conf @@ -8,6 +8,29 @@ # Example windowrules that are useful +# Apps +windowrule { + name = intellij + match:class = jetbrains-idea + opacity = 0.8 +} +windowrule { + name = thunar + match:class = thunar + opacity = 0.8 +} +windowrule { + name = steam + match:class = steam + opacity = 0.8 +} + +windowrule { + name = discord + match:class = discord + monitor = $right-monitor +} + windowrule { # Ignore maximize requests from all apps. You'll probably like this. name = suppress-maximize-events diff --git a/dotfiles/.config/kitty/kitty.conf b/dotfiles/.config/kitty/kitty.conf new file mode 100644 index 0000000..2bb2de6 --- /dev/null +++ b/dotfiles/.config/kitty/kitty.conf @@ -0,0 +1,28 @@ +# __ ___ __ __ +# / //_(_) /_/ /___ __ +# / ,< / / __/ __/ // / +# /_/|_/_/\__/\__/\_, / +# /___/ +# +# Configuration +font_family FiraCode Nerd Font +font_size 11 +bold_font auto +italic_font auto +bold_italic_font auto +remember_window_size no +initial_window_width 950 +initial_window_height 500 +cursor_blink_interval 0.5 +cursor_stop_blinking_after 1 +scrollback_lines 2000 +wheel_scroll_min_lines 1 +enable_audio_bell no +window_padding_width 10 +hide_window_decorations yes +background_opacity 0.7 +dynamic_background_opacity yes +confirm_os_window_close 0 +selection_foreground none +selection_background none +cursor_trail 1 diff --git a/dotfiles/.config/millennium/config.json b/dotfiles/.config/millennium/config.json new file mode 100644 index 0000000..222d86c --- /dev/null +++ b/dotfiles/.config/millennium/config.json @@ -0,0 +1,141 @@ +{ + "general": { + "accentColor": "#117703", + "checkForMillenniumUpdates": true, + "checkForPluginAndThemeUpdates": true, + "injectCSS": true, + "injectJavascript": true, + "millenniumUpdateChannel": "stable", + "onMillenniumUpdate": 1, + "shouldShowThemePluginUpdateNotifications": true + }, + "misc": { + "hasShownWelcomeModal": true + }, + "notifications": { + "showNotifications": true, + "showPluginNotifications": true, + "showUpdateNotifications": true + }, + "plugins": { + "enabledPlugins": [ + "extendium", + "hltb-for-millennium", + "size-on-disk" + ] + }, + "themes": { + "activeTheme": "Minimal-Dark-for-Steam", + "allowedScripts": true, + "allowedStyles": true, + "conditions": { + "Minimal-Dark-for-Steam": { + "Account wallet :": "Hide", + "Alignment fix for 4K resolution :": "no", + "Alternate view of in-game friends :": "Alternative", + "Alternative UI for new Steam menu :": "Minimal new UI (compact)", + "Alternative contacts icon :": "Minimal Dark #3 (fill)", + "Alternative downloads UI :": "New UI (alt)", + "Alternative games names hover effect :": "Default", + "Alternative hover effect :": "Default", + "Alternative ingame friends indicator :": "Minimal Dark default", + "Alternative library navigation button :": "Alternative", + "Alternative root menu icon :": "Minimal Dark", + "Alternative topbar color :": "Grey/dark", + "Alternative ui color for context menus/menus :": "no", + "Alternative view for back to top button :": "no", + "Alternative view for scrolling bloc :": "no", + "Animated buttons hovering :": "yes", + "Animated hovering :": "yes", + "Animated mouseover :": "yes", + "Avatar frame :": "Show", + "Avatar size :": "Default", + "Avatar status border :": "Reduce", + "Bigger buttons :": "no", + "Bigger font size :": "no", + "Bigger gamepage/library buttons :": "no", + "Bigger header :": "Full image", + "Bigger notification button :": "no", + "Blocs backgrounds :": "Blurred", + "Blurred background :": "Blurred", + "Bolder names :": "no", + "Change UI font :": "Default", + "Community content :": "Show", + "Compact Webkit :": "Hide", + "Compact avatar :": "Compact", + "Compact collections :": "no", + "Compact filters :": "yes", + "Compact labels (library + whats news) :": "Compact", + "Compact library :": "yes", + "Compact menus/context menus :": "Compact size (static with left border)", + "Compact mod :": "Default", + "Compact news container :": "Ultra compact", + "Corners radius :": "Rounded", + "Default pointers :": "Minimal Dark", + "Events background :": "Default background", + "Events header :": "Minimal Dark default", + "Font size :": "Smaller", + "Friends conversation tabs header glow :": "Default", + "Friends list header glow :": "no", + "Gamepage/library buttons svg color (Play/Install) :": "White", + "Games names overflow :": "yes", + "Group chats position :": "Default (bottom)", + "Header (compact mod) :": "Show", + "Header size adjustment :": "Yes", + "Hide '+ Add a Showcase' :": "no", + "Hide 'post-party summary' / 'WYRTG ?' :": "Show all", + "Hide VAC ban information on profiles :": "no", + "Hide all activities :": "Show all activities", + "Hide footer (fix) :": "yes", + "Hide footer :": "yes", + "Hide footers :": "Hide", + "Hide scrollbars :": "Hide", + "Hide shelf :": "no", + "Hide topbar elements :": "Show all", + "Hide voice chat :": "no", + "Hours played :": "Show", + "Hover borders on library and webkit blocks :": "Full borders", + "Hover/active borders :": "yes", + "Installed/selected games color :": "Minimal Dark", + "Inverted contact bloc :": "no", + "Inverted gamepage (known bugs) :": "Default", + "Library games hover glow color :": "Default", + "Library labels :": "Show", + "Miniprofile background :": "Hide", + "Mouseover animation of settings menu tabs :": "Animated", + "News container :": "Show", + "No button labels in list mod :": "yes", + "No button labels in normal mod :": "no", + "No left images :": "no", + "No titlebar (Linux) :": "no", + "Offline friends :": "Show", + "Persistent Sidebar :": "yes", + "Pointer fix :": "no", + "Profiles :": "Minimal Dark", + "Profiles headers :": "no", + "Pure black color (OLED) :": "no", + "Resize grip (Steam windows) :": "Default (smaller)", + "Right Sidebar :": "Left", + "Root menu Steam text :": "Hide", + "Show sidebar on mouseover :": "Default", + "Static hovering (paysage games of Recent Games | Play Next) :": "Animated", + "Static hovering (portrait games of Library | Collections) :": "Static", + "Steam bigpicture button :": "Hide", + "Steam news button :": "Hide", + "Tabs text transform :": "no", + "Text on play/download button :": "no", + "Title area buttons :": "Enlarged (w11 users)", + "Topbar Store tab left icon (currency icon) :": "Fill icon (euro)", + "Topbar icons menu hover effect :": "Fill", + "Topbar tabs left icons (hover effect included) :": "Icons active (fill + text)", + "Topbar tabs left icons, no text :": "no", + "URL bar :": "Visible", + "Uninstalled selected games color :": "Minimal Dark", + "Vanilla topbar style :": "Minimal Dark", + "Windows 10 fix corners :": "no", + "Windows corners fix :": "yes", + "Wishlist new Steam menu :": "Compact revisited menu" + } + } + } +} \ No newline at end of file diff --git a/dotfiles/.config/waybar/config.jsonc b/dotfiles/.config/waybar/config.jsonc new file mode 100644 index 0000000..0aa1566 --- /dev/null +++ b/dotfiles/.config/waybar/config.jsonc @@ -0,0 +1,99 @@ +[ + { + "layer": "top", + "position": "top", + "output": [ + "HDMI-A-1" + ], + "margin-top": 14, + "margin-bottom": 0, + "margin-left": 14, + "margin-right": 14, + "spacing": 0, + "include": [ + "~/.config/waybar/modules.jsonc" + ], + "modules-left": [ + "group/workspaces" + ], + // Load Modules + "modules-center": [ + ], + "modules-right": [ + "mpd", + "group/hardware", + "tray" + ] + }, + { + "layer": "top", + "position": "right", + "width": 250, + "spacing": 10, + "output": [ + "DP-2" + ], + "include": [ + "~/.config/waybar/modules.jsonc" + ], + "modules-left": [ + "clock#time", + "clock#date" + ], + "modules-right": [ + "cpu", + "privacy", + "user", + "custom/exit" + ], + "clock#time": { + "format": "{:%H:%M:%S}", + "interval": 1, + "tooltip": false + }, + "clock#date": { + "format": "{:%A %d %h %G}", + "tooltip-format": "{calendar}" + }, + "user": { + "format": "{user} (up {work_d} days)", + "interval": 60, + "height": 30, + "width": 30, + "icon": true, + }, + "privacy": { + "icon-spacing": 4, + "icon-size": 18, + "transition-duration": 250, + "modules": [ + { + "type": "screenshare", + "tooltip": true, + "tooltip-icon-size": 24 + }, + { + "type": "audio-out", + "tooltip": true, + "tooltip-icon-size": 24 + }, + { + "type": "audio-in", + "tooltip": true, + "tooltip-icon-size": 24 + } + ], + "ignore-monitor": true, + "ignore": [ + { + "type": "audio-in", + "name": "cava" + }, + { + "type": "screenshare", + "name": "obs" + } + ] + } + } +] diff --git a/dotfiles/.config/waybar/modules.jsonc b/dotfiles/.config/waybar/modules.jsonc new file mode 100644 index 0000000..607e732 --- /dev/null +++ b/dotfiles/.config/waybar/modules.jsonc @@ -0,0 +1,162 @@ +{ + "group/workspaces": { + "orientation": "inherit", + "modules": [ + "hyprland/workspaces", + "hyprland/window" + ] + }, + "hyprland/workspaces": { + "active-only": false, + "all-outputs": true, + "on-click": "activate", + "workspace-taskbar": { + "enable": true, + "active-window-position": "last", + "update-active-window": true, + "icon-theme": [ + "Silvery-Dark-Icons" + ] + }, + "format-icons": { + "active": "" + }, + "format": "{icon} {windows}" + }, + "hyprland/window": { + "format": "[{class}] {title}", + "rewrite": { + "(.*) - Vivaldi": "$1", + "(.*) - fish": "$1" + }, + "separate-outputs": false + }, + "custom/exit": { + "format": "", + "tooltip-format": "Powermenu", + "on-click": "wlogout -b 4", + "tooltip": false + }, + // System tray + "tray": { + "orientation": "vertical", + "show-passive-items": true, + // "icon-size": 21, + "spacing": 10 + }, + // Clock + "clock": { + // "timezone": "America/New_York", + "tooltip-format": "{:%Y %B}\n{calendar}", + "format-alt": "{:%Y-%m-%d}" + }, + // System + "custom/system": { + "format": "", + "tooltip": false + }, + // CPU + "cpu": { + "interval": 1, + "format": "{icon0}{icon1}{icon2}{icon3}{icon4}{icon5}{icon6}{icon7}{icon8}{icon9}{icon10}{icon11}{icon12}{icon13}{icon14}{icon15}", + "format-icons": [ + "", // green + "", // blue + "", // white + "", // white + "", // yellow + "", // yellow + "", // orange + "" // red + ], + "on-click": "kitty -e htop" + }, + // Memory + "memory": { + "format": "/ M {}% ", + "on-click": "kitty -e htop" + }, + // Harddisc space used + "disk": { + "interval": 30, + "format": "D {percentage_used}% ", + "path": "/", + "on-click": "kitty -e htop" + }, + "hyprland/language": { + "format": "/ K {short}" + }, + // Group Hardware + "group/hardware": { + "orientation": "inherit", + "drawer": { + "transition-duration": 300, + "children-class": "not-memory", + "transition-left-to-right": false + }, + "modules": [ + "custom/system", + "pulseaudio", + "network", + "disk", + "memory", + "hyprland/language" + ] + }, + // Network + "network": { + "format": "{ifname}", + "format-wifi": " {essid} ({signalStrength}%)", + "format-ethernet": " {ifname}", + "format-disconnected": "Disconnected ⚠", + "tooltip-format": " {ifname} via {gwaddri}", + "tooltip-format-wifi": " {ifname} @ {essid}\nIP: {ipaddr}\nStrength: {signalStrength}%\nFreq: {frequency}MHz\nUp: {bandwidthUpBits} Down: {bandwidthDownBits}", + "tooltip-format-ethernet": " {ifname}\nIP: {ipaddr}\n up: {bandwidthUpBits} down: {bandwidthDownBits}", + "tooltip-format-disconnected": "Disconnected", + "max-length": 50, + "on-click": "~/.config/ml4w/settings/networkmanager.sh", + "on-click-right": "~/.config/ml4w/scripts/nm-applet.sh toggle" + }, + // Pulseaudio + "pulseaudio": { + // "scroll-step": 1, // %, can be a float + "format": "{icon} {volume}%", + "format-bluetooth": "{volume}% {icon}", + "format-bluetooth-muted": " {icon}", + "format-muted": "", + "format-source": "{volume}% ", + "format-source-muted": "", + "format-icons": { + "default": [ + "", + " ", + " " + ] + }, + "on-click": "pavucontrol" + }, + // Bluetooth + "bluetooth": { + "format-disabled": "", + "format-off": "", + "interval": 30, + "on-click": "blueman-manager", + "format-no-controller": "" + }, + // Other + "user": { + "format": "{user}", + "interval": 60, + "icon": false + }, + // Idle Inhibator + "idle_inhibitor": { + "format": "{icon}", + "tooltip": true, + "format-icons": { + "activated": "", + "deactivated": "" + }, + "on-click-right": "hyprlock" + } +} diff --git a/dotfiles/.config/waybar/style.css b/dotfiles/.config/waybar/style.css new file mode 100644 index 0000000..c22a6de --- /dev/null +++ b/dotfiles/.config/waybar/style.css @@ -0,0 +1 @@ +@import url("file:///$HOME/.themes/Serpensortia/waybar/style.css"); \ No newline at end of file diff --git a/dotfiles/.themes/Serpensortia/intellij/Serpensortia.jar b/dotfiles/.themes/Serpensortia/intellij/Serpensortia.jar index 1e1e2db..88e1577 100644 Binary files a/dotfiles/.themes/Serpensortia/intellij/Serpensortia.jar and b/dotfiles/.themes/Serpensortia/intellij/Serpensortia.jar differ diff --git a/dotfiles/.themes/Serpensortia/waybar/style.css b/dotfiles/.themes/Serpensortia/waybar/style.css new file mode 100644 index 0000000..f3d0b76 --- /dev/null +++ b/dotfiles/.themes/Serpensortia/waybar/style.css @@ -0,0 +1,509 @@ +/* + * __ __ _ ____ _ _ + * \ \ / /_ _ _ _| |__ __ _ _ __ / ___|| |_ _ _| | ___ + * \ \ /\ / / _` | | | | '_ \ / _` | '__| \___ \| __| | | | |/ _ \ + * \ V V / (_| | |_| | |_) | (_| | | ___) | |_| |_| | | __/ + * \_/\_/ \__,_|\__, |_.__/ \__,_|_| |____/ \__|\__, |_|\___| + * |___/ |___/ + * + * by Stephan Raabe (2024) + * ----------------------------------------------------- +*/ + +@define-color backgroundlight #080c08; +@define-color backgrounddark #080c08; +@define-color workspacesbackground1 #080c08; +@define-color workspacesbackground2 #424a42; +@define-color bordercolor #124326; +@define-color textcolor1 #c0c0c0; +@define-color textcolor2 #c0c0c0; +@define-color textcolor3 #c0c0c0; +@define-color iconcolor #c0c0c0; + +/* ----------------------------------------------------- + * General + * ----------------------------------------------------- */ + + * { + font-family: "Fira Sans Semibold", "Font Awesome 7 Free", "Font Awesome 7 Brands", "Font Awesome 6 Free", "Font Awesome 6 Brands", FontAwesome, Roboto, Helvetica, Arial, sans-serif; + border: none; + border-radius: 0px; +} + +window#waybar { + background-color: rgba(0,0,0,0.8); + border-bottom: 0px solid #ffffff; + /* color: #FFFFFF; */ + background: transparent; + transition-property: background-color; + transition-duration: .5s; +} + +/* ----------------------------------------------------- + * Workspaces + * ----------------------------------------------------- */ + +#workspaces { + background: @workspacesbackground1; + margin: 2px 18px 3px 1px; + padding: 0px 2px; + border-radius: 5px 5px 5px 5px; + font-weight: bold; + font-style: normal; + opacity: 1.0; + color: @textcolor1; +} + +#workspaces button { + padding: 0px 6px; + margin: 3px 2px; + border-radius: 3px 3px 3px 3px; + color: @textcolor1; + background-color: @workspacesbackground2; + transition: all 0.1s linear; + opacity: 0.4; +} + +#workspaces button.active { + color: @textcolor1; + background: @workspacesbackground2; + border-radius: 3px 3px 3px 3px; + min-width: 30px; + transition: all 0.1s linear; + opacity:1.0; +} + +#workspaces button:hover { + color: @textcolor1; + background: @workspacesbackground2; + border-radius: 5px 5px 5px 5px; + opacity:0.7; +} + +/* ----------------------------------------------------- + * Tooltips + * ----------------------------------------------------- */ + +tooltip { + border-radius: 16px; + background-color: @backgroundlight; + opacity:0.9; + padding:20px; + margin:0px; +} + +tooltip label { + color: @textcolor2; +} + +/* ----------------------------------------------------- + * Window + * ----------------------------------------------------- */ + +#window { + margin: 3px 15px 3px 0px; + padding: 2px 10px 0px 10px; + border-radius: 5px 5px 5px 5px; + color:#c0c0c0; + font-size:16px; + font-weight:normal; + opacity:1.0; +} + +window#waybar.empty #window { + background-color:transparent; +} + +/* ----------------------------------------------------- + * Taskbar + * ----------------------------------------------------- */ + +#taskbar { + background: @backgroundlight; + margin: 3px 15px 3px 0px; + padding:0px; + border-radius: 5px 5px 5px 5px; + font-weight: normal; + font-style: normal; + opacity:1.0; + border: 3px solid @backgroundlight; +} + +#taskbar button { + margin:0; + border-radius: 5px 5px 5px 5px; + padding: 0px 5px 0px 5px; +} + +#taskbar.empty { + background:transparent; + border:0; + padding:0; + margin:0; +} + +/* ----------------------------------------------------- + * Modules + * ----------------------------------------------------- */ + +.modules-left > widget:first-child > #workspaces { + margin-left: 0; +} + +.modules-right > widget:last-child > #workspaces { + margin-right: 0; +} + +/* ----------------------------------------------------- + * Custom Quicklinks + * ----------------------------------------------------- */ + +#custom-brave, +#custom-browser, +#custom-keybindings, +#custom-outlook, +#custom-filemanager, +#custom-teams, +#custom-chatgpt, +#custom-calculator, +#custom-windowsvm, +#custom-cliphist, +#custom-settings, +#custom-wallpaper, +#custom-system, +#custom-hyprshade, +#custom-hypridle, +#custom-tools, +#custom-quicklink1, +#custom-quicklink2, +#custom-quicklink3, +#custom-quicklink4, +#custom-quicklink5, +#custom-quicklink6, +#custom-quicklink7, +#custom-quicklink8, +#custom-quicklink9, +#custom-quicklink10, +#custom-quicklink_chromium, +#custom-quicklink_edge, +#custom-quicklink_firefox, +#custom-quicklink_browser, +#custom-quicklink_filemanager, +#custom-quicklink_email, +#custom-quicklink_thunderbird, +#custom-quicklink_calculator, +#custom-waybarthemes { + margin-right: 10px; + font-size: 20px; + font-weight: bold; + opacity: 1.0; + color: @iconcolor; +} + +#custom-quicklink1, +#custom-quicklink2, +#custom-quicklink3, +#custom-quicklink4, +#custom-quicklink5, +#custom-quicklink6, +#custom-quicklink7, +#custom-quicklink8, +#custom-quicklink9, +#custom-quicklink10 { + margin-right: 10px; +} + +#custom-tools { + margin-right:12px; +} + +#custom-hypridle.active { + color: @iconcolor; +} + +#custom-hypridle.notactive { + color: #dc2f2f; +} + +#custom-ml4w-welcome { + margin-right: 12px; + background-image: url("../assets/ml4w-icon.svg"); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + padding-right: 20px; +} + +#custom-chatgpt { + margin-right: 16px; + background-image: url("../assets/openai.svg"); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + padding-right: 18px; + opacity: 0.8; +} + +/* ----------------------------------------------------- + * Idle Inhibator + * ----------------------------------------------------- */ + +#idle_inhibitor { + margin-right: 15px; + font-size: 22px; + font-weight: bold; + opacity: 0.8; + color: @iconcolor; +} + +#idle_inhibitor.activated { + margin-right: 15px; + font-size: 20px; + font-weight: bold; + opacity: 0.8; + color: #dc2f2f; +} + +/* ----------------------------------------------------- + * Custom Modules + * ----------------------------------------------------- */ + +#custom-appmenu { + background-color: @backgrounddark; + font-size: 16px; + color: @textcolor1; + border-radius: 5px 5px 5px 5px; + padding: 0px 10px 0px 10px; + margin: 2px 17px 2px 0px; + opacity:1.0; + border:3px solid @bordercolor; +} + +/* ----------------------------------------------------- + * Custom Notification + * ----------------------------------------------------- */ + + #custom-notification { + margin: 0px 13px 0px 0px; + padding:0px; + font-size:20px; + color: @iconcolor; + opacity: 0.8; +} + +/* ----------------------------------------------------- + * Custom Exit + * ----------------------------------------------------- */ + +#custom-exit { + margin: 0px 13px 0px 0px; + padding:0px; + font-size:20px; + color: @iconcolor; + opacity: 0.8; + border:3px solid @bordercolor; +} + +/* ----------------------------------------------------- + * Custom Updates + * ----------------------------------------------------- */ + +#custom-updates { + background-color: @backgroundlight; + font-size: 16px; + color: @textcolor2; + border-radius: 5px 5px 5px 5px; + padding: 2px 10px 0px 10px; + margin: 3px 15px 3px 0px; + opacity:1.0; +} + +#custom-updates.green { + background-color: @backgroundlight; +} + +#custom-updates.yellow { + background-color: #ff9a3c; + color: #FFFFFF; +} + +#custom-updates.red { + background-color: #dc2f2f; + color: #FFFFFF; +} + +/* ----------------------------------------------------- + * Hardware Group + * ----------------------------------------------------- */ + +#disk,#memory,#cpu,#language { + margin:0px 10px 0px 0px; + padding:0px; + font-size:16px; + color:@iconcolor; +} + +#language { + margin-right:10px; +} + +#group-workspaces { + border:3px solid @bordercolor; +} + +/* ----------------------------------------------------- + * Power Profiles Daemon + * ----------------------------------------------------- */ + +#power-profiles-daemon { + margin: 0px 13px 0px 0px; + padding:0px; + font-size:16px; + color:@iconcolor; +} + +/* ----------------------------------------------------- + * Clock + * ----------------------------------------------------- */ + +#clock { + background-color: @backgrounddark; + font-size: 16px; + color: @textcolor1; + border-radius: 3px 5px 3px 5px; + padding: 1px 10px 0px 10px; + margin: 3px 10px 3px 0px; + opacity:1.0; + border:3px solid @bordercolor; +} + +/* ----------------------------------------------------- + * Backlight + * ----------------------------------------------------- */ + +#backlight { + background-color: @backgroundlight; + font-size: 16px; + color: @textcolor2; + border-radius: 5px 5px 5px 5px; + padding: 2px 10px 0px 10px; + margin: 3px 15px 3px 0px; + opacity:1.0; +} + +/* ----------------------------------------------------- + * Pulseaudio + * ----------------------------------------------------- */ + +#pulseaudio { + background-color: @backgroundlight; + font-size: 16px; + color: @textcolor2; + border-radius: 5px 5px 5px 5px; + padding: 2px 10px 0px 10px; + margin: 3px 15px 3px 0px; + opacity:1.0; +} + +#pulseaudio.muted { + background-color: @backgrounddark; + color: @textcolor1; +} + +/* ----------------------------------------------------- + * Network + * ----------------------------------------------------- */ + +#network { + background-color: @backgroundlight; + font-size: 16px; + color: @textcolor2; + border-radius: 5px 5px 5px 5px; + padding: 2px 10px 0px 10px; + margin: 3px 15px 3px 0px; + opacity:1.0; +} + +#network.ethernet { + background-color: @backgroundlight; + color: @textcolor2; +} + +#network.wifi { + background-color: @backgroundlight; + color: @textcolor2; +} + +/* ----------------------------------------------------- + * Bluetooth + * ----------------------------------------------------- */ + +#bluetooth, #bluetooth.on, #bluetooth.connected { + background-color: @backgroundlight; + font-size: 16px; + color: @textcolor2; + border-radius: 5px 5px 5px 5px; + padding: 2px 10px 0px 10px; + margin: 3px 15px 3px 0px; + opacity:1.0; +} + +#bluetooth.off { + background-color: transparent; + padding: 0px; + margin: 0px; +} + +/* ----------------------------------------------------- + * Battery + * ----------------------------------------------------- */ + +#battery { + background-color: @backgroundlight; + font-size: 16px; + color: @textcolor2; + border-radius: 5px 5px 5px 5px; + padding: 2px 15px 0px 10px; + margin: 3px 15px 3px 0px; + opacity:1.0; +} + +#battery.charging, #battery.plugged { + color: @textcolor2; + background-color: @backgroundlight; +} + +@keyframes blink { + to { + background-color: @backgroundlight; + color: @textcolor2; + } +} + +#battery.critical:not(.charging) { + background-color: #f53c3c; + color: @textcolor3; + animation-name: blink; + animation-duration: 0.5s; + animation-timing-function: linear; + animation-iteration-count: infinite; + animation-direction: alternate; +} + +/* ----------------------------------------------------- + * Tray + * ----------------------------------------------------- */ + +#tray { + padding: 0px 15px 0px 0px; +} + +#tray > .passive { + -gtk-icon-effect: dim; +} + +#tray > .needs-attention { + -gtk-icon-effect: highlight; +} + + diff --git a/setup.sh b/setup.sh index f6c764d..2e6ea1f 100755 --- a/setup.sh +++ b/setup.sh @@ -1,46 +1,59 @@ #!/usr/bin/env bash packages=( - "wget" - "unzip" + "bluez" + "bluez-utils" + "blueman" + "brightnessctl" + "bun" + "discord" + "betterdiscord-installer-bin" + "dunst" + "easyeffects" + "fastfetch" + "flatpak" "git" + "go" "gum" "gvfs" + "hicolor-icon-theme" + "htop" "hyprland" - "waybar" - "rofi" + "hyprlock" + "hyprpaper" + "hyprpolkitagent" + "jetbrains-toolbox" + "jq" "kitty" - "dunst" - "flatpak" + "networkmanager" + "qt5-wayland" + "qt5ct" + "qt6-wayland" + "qt6ct" + "rofi" + "scite" "thunar" "thunar-archive-plugin" "thunar-media-tags-plugin" "thunar-shares-plugin" "thunar-vcs-plugin" "thunar-volman" - "qt5-wayland" - "qt6-wayland" - "qt5ct" - "qt6ct" - "go" - "hyprpaper" - "hyprlock" - "hyprpolkitagent" - "hicolor-icon-theme" - "woff2-font-awesome" + "ttf-fira-code" + "ttf-fira-sans" + "ttf-firacode-nerd" + "unzip" "vim" "vivaldi" - "fastfetch" - "ttf-fira-sans" - "ttf-fira-code" - "ttf-firacode-nerd" - "jq" - "brightnessctl" - "networkmanager" + "waybar" + "wget" "wireplumber" "wlogout" - "jetbrains-toolbox" + "woff2-font-awesome" "xdg-desktop-portal-hyprland" + "steam" + "millennium" + "mangohud" + "goverlay" ) packagesToRemove=( @@ -141,6 +154,7 @@ _copyConfig() { done # use envsubst for qt6ct.conf envsubst < dotfiles/.config/qt6ct/qt6ct.conf > "$HOME/.config/qt6ct/qt6ct.conf" + envsubst < dotfiles/.config/waybar/style.css > "$HOME/.config/waybar/style.css" # copy qt6 to qt5 mkdir -p "$HOME/.config/qt5ct" cp -v "$HOME/.config/qt6ct/qt6ct.conf" "$HOME/.config/qt5ct/qt5ct.conf" @@ -177,6 +191,9 @@ _copyConfig _copyScripts hyprctl reload +killall -9 waybar +sleep 1 +waybar & echo ":::::::::::::::::::::::::::" echo ":: Installation complete ::" echo ":::::::::::::::::::::::::::"