{ "version": 3, "sources": ["../scripts/build/inject/react.mjs", "../src/utils/Logger.ts", "../src/utils/margins.ts", "../src/utils/lazy.ts", "../src/utils/lazyReact.tsx", "../src/utils/patches.ts", "../src/debug/Tracer.ts", "../src/webpack/webpack.ts", "../src/webpack/common/classes.ts", "../src/webpack/common/internal.tsx", "../src/webpack/common/components.ts", "../src/webpack/common/menu.ts", "../src/webpack/common/react.ts", "../src/webpack/common/settingsStores.ts", "../src/webpack/common/stores.ts", "../src/webpack/common/types/components.d.ts", "../src/webpack/common/types/menu.d.ts", "../src/webpack/common/types/utils.d.ts", "../src/webpack/common/utils.ts", "../src/webpack/common/index.ts", "../src/utils/constants.ts", "../src/utils/misc.tsx", "../src/utils/react.tsx", "../src/components/ErrorCard.tsx", "../src/components/ErrorBoundary.tsx", "../src/components/Heart.tsx", "../src/components/DonateButton.tsx", "../src/components/Flex.tsx", "../src/utils/modal.tsx", "../src/utils/types.ts", "../src/plugins/_api/badges.tsx", "../src/plugins/_api/chatButtons.ts", "../src/plugins/_api/commands.ts", "../src/plugins/_api/contextMenu.ts", "../src/plugins/_api/memberListDecorators.ts", "../src/plugins/_api/messageAccessories.ts", "../src/plugins/_api/messageDecorations.ts", "../src/plugins/_api/messageEvents.ts", "../src/plugins/_api/messagePopover.ts", "../src/plugins/_api/notices.ts", "../src/plugins/_api/serverList.ts", "../src/plugins/_core/noTrack.ts", "../src/shared/debounce.ts", "../src/shared/SettingsStore.ts", "../src/utils/localStorage.ts", "../src/utils/Queue.ts", "../src/api/Notifications/NotificationComponent.tsx", "../src/api/DataStore/index.ts", "../src/api/Styles.ts", "../node_modules/.pnpm/nanoid@4.0.2/node_modules/nanoid/index.browser.js", "../src/api/Notifications/notificationLog.tsx", "../src/api/Notifications/Notifications.tsx", "../src/api/Notifications/index.ts", "../node_modules/.pnpm/fflate@0.7.4/node_modules/fflate/esm/browser.js", "../src/utils/cloud.tsx", "../src/utils/native.ts", "../src/utils/web.ts", "../src/utils/settingsSync.ts", "../src/api/Settings.ts", "git-hash:~git-hash", "../src/utils/updater.ts", "../src/components/handleComponentFailed.ts", "../src/utils/onlyOnce.ts", "../src/components/VencordSettings/shared.tsx", "../src/components/VencordSettings/VencordTab.tsx", "../src/api/Notices.ts", "../src/components/Icons.tsx", "../src/utils/text.ts", "../src/api/Commands/commandHelpers.ts", "../src/api/Commands/types.ts", "../src/api/Commands/index.ts", "../src/components/Badge.tsx", "../src/components/PluginSettings/components/SettingBooleanComponent.tsx", "../src/components/PluginSettings/components/SettingCustomComponent.tsx", "../src/components/PluginSettings/components/SettingNumericComponent.tsx", "../src/components/PluginSettings/components/SettingSelectComponent.tsx", "../src/components/PluginSettings/components/SettingSliderComponent.tsx", "../src/components/PluginSettings/components/SettingTextComponent.tsx", "../src/components/PluginSettings/components/index.ts", "../src/utils/discord.tsx", "../src/components/PluginSettings/ContributorModal.tsx", "../src/components/PluginSettings/PluginModal.tsx", "../src/components/Switch.tsx", "../src/components/VencordSettings/AddonCard.tsx", "../src/utils/ChangeList.ts", "../src/api/ContextMenu.ts", "../src/plugins/index.ts", "../src/components/PluginSettings/index.tsx", "../src/components/VencordSettings/PluginsTab.tsx", "../src/components/Link.tsx", "../src/components/VencordSettings/ThemesTab.tsx", "../src/components/VencordSettings/UpdaterTab.tsx", "../src/components/CheckedTextInput.tsx", "../src/components/VencordSettings/CloudTab.tsx", "../src/components/VencordSettings/BackupAndRestoreTab.tsx", "../src/plugins/_core/settings.tsx", "../src/plugins/_core/supportHelper.tsx", "../src/plugins/alwaysAnimate/index.ts", "../src/plugins/alwaysTrust/index.ts", "../src/plugins/anonymiseFileNames/index.tsx", "../src/plugins/arRPC.web/index.tsx", "../src/plugins/banger/index.ts", "../src/plugins/betterFolders/FolderSideBar.tsx", "../src/plugins/betterFolders/index.tsx", "../src/plugins/betterGifAltText/index.ts", "../src/plugins/betterGifPicker/index.ts", "../src/plugins/betterNotes/index.tsx", "../src/plugins/betterRoleContext/index.tsx", "../src/plugins/betterRoleDot/index.ts", "../src/plugins/betterSettings/index.tsx", "../src/plugins/betterUploadButton/index.ts", "../src/plugins/biggerStreamPreview/webpack/stores.ts", "../src/plugins/biggerStreamPreview/index.tsx", "../src/plugins/blurNsfw/index.ts", "../src/plugins/callTimer/index.tsx", "../src/api/MessageEvents.ts", "../src/plugins/clearURLs/defaultRules.ts", "../src/plugins/clearURLs/index.ts", "../src/plugins/clientTheme/index.tsx", "../src/plugins/colorSighted/index.ts", "../src/plugins/consoleShortcuts/index.ts", "../src/plugins/copyUserURLs/index.tsx", "../src/plugins/crashHandler/index.ts", "../src/utils/guards.ts", "../src/plugins/customRPC/index.tsx", "../src/plugins/dearrow/index.tsx", "../src/plugins/decor/lib/constants.ts", "../src/plugins/decor/lib/stores/AuthorizationStore.tsx", "../src/plugins/decor/lib/api.ts", "../src/plugins/decor/lib/utils/decoration.ts", "../src/plugins/decor/lib/stores/UsersDecorationsStore.ts", "../src/plugins/decor/lib/stores/CurrentUserDecorationsStore.ts", "../src/plugins/decor/ui/index.ts", "../src/plugins/decor/ui/components/index.ts", "../src/plugins/decor/ui/components/DecorationGridCreate.tsx", "../src/plugins/decor/ui/components/DecorationGridNone.tsx", "../src/plugins/decor/ui/components/DecorationContextMenu.tsx", "../src/plugins/decor/ui/components/DecorDecorationGridDecoration.tsx", "../src/plugins/decor/ui/components/Grid.tsx", "../src/plugins/decor/ui/components/SectionedGridList.tsx", "../src/plugins/decor/ui/modals/CreateDecorationModal.tsx", "../src/plugins/decor/ui/modals/GuidelinesModal.tsx", "../src/plugins/decor/ui/modals/ChangeDecorationModal.tsx", "../src/plugins/decor/ui/components/DecorSection.tsx", "../src/plugins/decor/settings.tsx", "../src/plugins/decor/index.tsx", "../src/plugins/disableCallIdle/index.ts", "../src/plugins/emoteCloner/index.tsx", "../src/plugins/experiments/index.tsx", "../src/plugins/f8break/index.ts", "../src/utils/web-metadata.ts", "../src/utils/apng-canvas.js", "../src/utils/dependencies.ts", "../node_modules/.pnpm/github.com+mattdesl+gifenc@64842fca317b112a8590f8fef2bf3825da8f6fe3/node_modules/gifenc/src/index.js", "../node_modules/.pnpm/github.com+mattdesl+gifenc@64842fca317b112a8590f8fef2bf3825da8f6fe3/node_modules/gifenc/src/constants.js", "../node_modules/.pnpm/github.com+mattdesl+gifenc@64842fca317b112a8590f8fef2bf3825da8f6fe3/node_modules/gifenc/src/stream.js", "../node_modules/.pnpm/github.com+mattdesl+gifenc@64842fca317b112a8590f8fef2bf3825da8f6fe3/node_modules/gifenc/src/lzwEncode.js", "../node_modules/.pnpm/github.com+mattdesl+gifenc@64842fca317b112a8590f8fef2bf3825da8f6fe3/node_modules/gifenc/src/rgb-packing.js", "../node_modules/.pnpm/github.com+mattdesl+gifenc@64842fca317b112a8590f8fef2bf3825da8f6fe3/node_modules/gifenc/src/pnnquant2.js", "../node_modules/.pnpm/github.com+mattdesl+gifenc@64842fca317b112a8590f8fef2bf3825da8f6fe3/node_modules/gifenc/src/color.js", "../node_modules/.pnpm/github.com+mattdesl+gifenc@64842fca317b112a8590f8fef2bf3825da8f6fe3/node_modules/gifenc/src/palettize.js", "../src/plugins/fakeNitro/index.tsx", "../node_modules/.pnpm/virtual-merge@1.0.1/node_modules/virtual-merge/dist/index.mjs", "../src/plugins/fakeProfileThemes/index.tsx", "../src/plugins/favEmojiFirst/index.ts", "../src/plugins/favGifSearch/index.tsx", "../src/plugins/fixCodeblockGap/index.ts", "../src/plugins/fixSpotifyEmbeds.desktop/index.ts", "../src/plugins/fixYoutubeEmbeds.desktop/index.ts", "../src/plugins/forceOwnerCrown/index.ts", "../src/plugins/friendInvites/index.ts", "../src/plugins/friendsSince/index.tsx", "managed-style:src/plugins/gameActivityToggle/style.css", "../src/plugins/gameActivityToggle/index.tsx", "../src/plugins/gifPaste/index.ts", "../src/plugins/greetStickerPicker/index.tsx", "../src/api/MessagePopover.ts", "../src/plugins/hideAttachments/index.tsx", "../src/plugins/iLoveSpam/index.ts", "../src/plugins/ignoreActivities/index.tsx", "../src/plugins/imageZoom/constants.ts", "../src/plugins/imageZoom/utils/waitFor.ts", "../src/plugins/imageZoom/components/Magnifier.tsx", "managed-style:src/plugins/imageZoom/styles.css", "../src/plugins/imageZoom/index.tsx", "../src/api/ChatButtons.tsx", "../src/plugins/invisibleChat.desktop/components/DecryptionModal.tsx", "../src/plugins/invisibleChat.desktop/components/EncryptionModal.tsx", "../src/plugins/invisibleChat.desktop/index.tsx", "../src/plugins/keepCurrentChannel/index.ts", "../src/plugins/lastfm/index.tsx", "../src/plugins/loadingQuotes/index.ts", "../src/plugins/memberCount/OnlineMemberCountStore.ts", "../src/plugins/memberCount/MemberCount.tsx", "../src/plugins/memberCount/index.tsx", "../src/plugins/messageClickActions/index.ts", "../src/api/MessageAccessories.ts", "../src/plugins/messageLinkEmbeds/index.tsx", "managed-style:src/plugins/messageLogger/deleteStyleOverlay.css", "managed-style:src/plugins/messageLogger/deleteStyleText.css", "../src/plugins/messageLogger/index.tsx", "../src/plugins/messageTags/index.ts", "../src/plugins/moreCommands/index.ts", "../src/plugins/moreKaomoji/index.ts", "../src/plugins/moreUserTags/index.tsx", "../src/plugins/moyai/index.ts", "../src/plugins/mutualGroupDMs/index.tsx", "../src/plugins/newGuildSettings/index.tsx", "../src/plugins/noBlockedMessages/index.ts", "../src/plugins/noDevtoolsWarning/index.ts", "../src/plugins/noF1/index.ts", "managed-style:src/plugins/noMosaic/styles.css", "../src/plugins/noMosaic/index.ts", "../src/plugins/noPendingCount/index.ts", "../src/plugins/noProfileThemes/index.ts", "../src/plugins/noReplyMention/index.tsx", "../src/plugins/noScreensharePreview/index.ts", "../src/plugins/noTypingAnimation/index.ts", "../src/plugins/noUnblockToJump/index.ts", "../src/plugins/normalizeMessageLinks/index.ts", "../src/plugins/notificationVolume/index.ts", "../src/plugins/nsfwGateBypass/index.ts", "../src/plugins/onePingPerDM/index.ts", "../src/plugins/oneko/index.ts", "../src/plugins/openInApp/index.ts", "../src/plugins/overrideForumDefaults/index.tsx", "../src/plugins/partyMode/index.ts", "../src/plugins/permissionFreeWill/index.ts", "../src/plugins/permissionsViewer/utils.ts", "../src/plugins/permissionsViewer/components/icons.tsx", "../src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx", "../src/components/ExpandableHeader.tsx", "../src/plugins/permissionsViewer/components/UserPermissions.tsx", "../src/plugins/permissionsViewer/index.tsx", "../src/plugins/petpet/index.ts", "../src/plugins/pictureInPicture/index.tsx", "../src/plugins/pinDms/constants.ts", "../src/plugins/pinDms/data.ts", "../src/plugins/pinDms/components/CreateCategoryModal.tsx", "../src/plugins/pinDms/components/contextMenu.tsx", "../src/plugins/pinDms/index.tsx", "../src/plugins/plainFolderIcon/index.ts", "../src/api/MemberListDecorators.ts", "../src/api/MessageDecorations.ts", "../src/plugins/platformIndicators/index.tsx", "../src/plugins/previewMessage/index.tsx", "../src/plugins/pronoundb/components/PronounsAboutComponent.tsx", "git-remote:~git-remote", "../src/shared/vencordUserAgent.ts", "../src/plugins/pronoundb/settings.ts", "../src/plugins/pronoundb/types.ts", "../src/plugins/pronoundb/pronoundbUtils.ts", "../src/plugins/pronoundb/components/PronounsChatComponent.tsx", "../src/plugins/pronoundb/index.ts", "../src/plugins/quickMention/index.tsx", "../src/plugins/quickReply/index.ts", "../src/plugins/reactErrorDecoder/index.ts", "../src/api/ServerList.ts", "../src/plugins/readAllNotificationsButton/index.tsx", "../src/plugins/relationshipNotifier/settings.ts", "../src/plugins/relationshipNotifier/types.ts", "../src/plugins/relationshipNotifier/utils.ts", "../src/plugins/relationshipNotifier/functions.ts", "../src/plugins/relationshipNotifier/index.ts", "../src/plugins/resurrectHome/index.tsx", "../src/plugins/revealAllSpoilers/index.ts", "../src/plugins/reverseImageSearch/index.tsx", "../src/plugins/reviewDB/auth.tsx", "../src/plugins/reviewDB/entities.ts", "../src/plugins/reviewDB/utils.tsx", "../src/plugins/reviewDB/components/BlockedUserModal.tsx", "../src/plugins/reviewDB/settings.tsx", "../src/plugins/reviewDB/reviewDbApi.ts", "../src/plugins/reviewDB/components/MessageButton.tsx", "../src/plugins/reviewDB/components/ReviewBadge.tsx", "../src/plugins/reviewDB/components/ReviewComponent.tsx", "../src/plugins/reviewDB/components/ReviewsView.tsx", "../src/plugins/reviewDB/components/ReviewModal.tsx", "../src/plugins/reviewDB/index.tsx", "../src/plugins/roleColorEverywhere/index.tsx", "../src/plugins/searchReply/index.tsx", "../src/plugins/secretRingTone/index.ts", "../src/plugins/sendTimestamps/index.tsx", "../src/plugins/serverListIndicators/index.tsx", "../src/plugins/serverProfile/GuildProfileModal.tsx", "../src/plugins/serverProfile/index.tsx", "include-file:~fileContent/previewExample.tsx", "../node_modules/.pnpm/eventemitter3@4.0.7/node_modules/eventemitter3/index.js", "../node_modules/.pnpm/@vap+core@0.0.12/node_modules/@vap/core/ipc/channel.js", "../node_modules/.pnpm/@vap+core@0.0.12/node_modules/@vap/core/ipc/rpc.js", "../node_modules/.pnpm/@vap+core@0.0.12/node_modules/@vap/core/ipc/worker.js", "../node_modules/.pnpm/@vap+core@0.0.12/node_modules/@vap/core/ipc/index.js", "../src/plugins/shikiCodeblocks.desktop/hooks/useTheme.ts", "../src/plugins/shikiCodeblocks.desktop/api/languages.ts", "../src/plugins/shikiCodeblocks.desktop/api/themes.ts", "../src/plugins/shikiCodeblocks.desktop/api/shiki.ts", "managed-style:src/plugins/shikiCodeblocks.desktop/devicon.css", "../src/plugins/shikiCodeblocks.desktop/types.ts", "../src/plugins/shikiCodeblocks.desktop/settings.ts", "../src/plugins/shikiCodeblocks.desktop/hooks/useShikiSettings.ts", "../src/plugins/shikiCodeblocks.desktop/utils/color.ts", "../src/plugins/shikiCodeblocks.desktop/utils/misc.ts", "../src/plugins/shikiCodeblocks.desktop/hooks/useCopyCooldown.ts", "../src/plugins/shikiCodeblocks.desktop/components/CopyButton.tsx", "../src/plugins/shikiCodeblocks.desktop/components/ButtonRow.tsx", "../src/plugins/shikiCodeblocks.desktop/components/Code.tsx", "../src/plugins/shikiCodeblocks.desktop/components/Header.tsx", "../src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx", "../src/plugins/shikiCodeblocks.desktop/utils/createStyle.ts", "../src/plugins/shikiCodeblocks.desktop/index.ts", "../src/plugins/showAllMessageButtons/index.ts", "../src/plugins/showConnections/VerifiedIcon.tsx", "../src/plugins/showConnections/index.tsx", "../src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx", "../src/plugins/showHiddenChannels/index.tsx", "../src/plugins/showMeYourName/index.tsx", "../src/plugins/showTimeouts/index.ts", "../src/plugins/silentMessageToggle/index.tsx", "../src/plugins/silentTyping/index.tsx", "../src/plugins/sortFriendRequests/index.tsx", "managed-style:src/plugins/spotifyControls/hoverOnly.css", "../src/plugins/spotifyControls/SpotifyStore.ts", "../src/plugins/spotifyControls/PlayerComponent.tsx", "../src/plugins/spotifyControls/index.tsx", "../src/plugins/spotifyCrack/index.ts", "../src/plugins/spotifyShareCommands/index.ts", "../src/plugins/startupTimings/StartupTimingPage.tsx", "../src/plugins/startupTimings/index.tsx", "../src/plugins/superReactionTweaks/index.ts", "../src/plugins/textReplace/index.tsx", "../src/plugins/themeAttributes/index.ts", "../src/plugins/timeBarAllActivities/index.ts", "../src/plugins/translate/settings.ts", "../src/plugins/translate/languages.ts", "../src/plugins/translate/utils.ts", "../src/plugins/translate/TranslateModal.tsx", "../src/plugins/translate/TranslateIcon.tsx", "../src/plugins/translate/TranslationAccessory.tsx", "../src/plugins/translate/index.tsx", "../src/plugins/typingTweaks/index.tsx", "../src/plugins/typingIndicator/index.tsx", "../src/plugins/unindent/index.ts", "../src/plugins/unsuppressEmbeds/index.tsx", "../src/plugins/urbanDictionary/index.ts", "../src/plugins/userVoiceShow/components/VoiceChannelSection.tsx", "../src/plugins/userVoiceShow/index.tsx", "managed-style:src/plugins/usrbg/index.css", "../src/plugins/usrbg/index.tsx", "../src/plugins/validUser/index.tsx", "../src/plugins/vcDoubleClick/index.ts", "../src/plugins/vcNarrator/index.tsx", "../src/plugins/vencordToolbox/index.tsx", "../src/plugins/viewIcons/index.tsx", "../src/components/CodeBlock.tsx", "../src/plugins/viewRaw/index.tsx", "../src/plugins/voiceMessages/settings.ts", "../src/plugins/voiceMessages/DesktopRecorder.tsx", "../src/plugins/voiceMessages/utils.ts", "../src/plugins/voiceMessages/VoicePreview.tsx", "../src/plugins/voiceMessages/WebRecorder.tsx", "../src/plugins/voiceMessages/index.tsx", "../src/plugins/webContextMenus.web/index.ts", "../src/plugins/webKeybinds.web/index.ts", "../src/plugins/whoReacted/index.tsx", "../src/plugins/wikisearch/index.ts", "../src/plugins/xsOverlay.desktop/index.ts", "import-plugins:~plugins", "../src/api/Badges.ts", "../src/api/index.ts", "../src/Vencord.ts", "../src/utils/index.ts", "../src/shared/onceDefined.ts", "../src/utils/quickCss.ts", "../src/webpack/index.ts", "../src/webpack/patchWebpack.ts"], "sourcesContent": ["/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nexport const VencordFragment = /* #__PURE__*/ Symbol.for(\"react.fragment\");\nexport let VencordCreateElement =\n (...args) => (VencordCreateElement = Vencord.Webpack.Common.React.createElement)(...args);\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nexport class Logger {\n /**\n * Returns the console format args for a title with the specified background colour and black text\n * @param color Background colour\n * @param title Text\n * @returns Array. Destructure this into {@link Logger}.errorCustomFmt or console.log\n *\n * @example logger.errorCustomFmt(...Logger.makeTitleElements(\"white\", \"Hello\"), \"World\");\n */\n static makeTitle(color: string, title: string): [string, ...string[]] {\n return [\"%c %c %s \", \"\", `background: ${color}; color: black; font-weight: bold; border-radius: 5px;`, title];\n }\n\n constructor(public name: string, public color: string = \"white\") { }\n\n private _log(level: \"log\" | \"error\" | \"warn\" | \"info\" | \"debug\", levelColor: string, args: any[], customFmt = \"\") {\n console[level](\n `%c Vencord %c %c ${this.name} ${customFmt}`,\n `background: ${levelColor}; color: black; font-weight: bold; border-radius: 5px;`,\n \"\",\n `background: ${this.color}; color: black; font-weight: bold; border-radius: 5px;`\n , ...args\n );\n }\n\n public log(...args: any[]) {\n this._log(\"log\", \"#a6d189\", args);\n }\n\n public info(...args: any[]) {\n this._log(\"info\", \"#a6d189\", args);\n }\n\n public error(...args: any[]) {\n this._log(\"error\", \"#e78284\", args);\n }\n\n public errorCustomFmt(fmt: string, ...args: any[]) {\n this._log(\"error\", \"#e78284\", args, fmt);\n }\n\n public warn(...args: any[]) {\n this._log(\"warn\", \"#e5c890\", args);\n }\n\n public debug(...args: any[]) {\n this._log(\"debug\", \"#eebebe\", args);\n }\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nlet styleStr = \"\";\n\nexport const Margins: Record<`${\"top\" | \"bottom\" | \"left\" | \"right\"}${8 | 16 | 20}`, string> = {} as any;\n\nfor (const dir of [\"top\", \"bottom\", \"left\", \"right\"] as const) {\n for (const size of [8, 16, 20] as const) {\n const cl = `vc-m-${dir}-${size}`;\n Margins[`${dir}${size}`] = cl;\n styleStr += `.${cl}{margin-${dir}:${size}px;}`;\n }\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", () =>\n document.head.append(Object.assign(document.createElement(\"style\"), {\n textContent: styleStr,\n id: \"vencord-margins\"\n })), { once: true });\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nexport function makeLazy(factory: () => T, attempts = 5): () => T {\n let tries = 0;\n let cache: T;\n return () => {\n if (!cache && attempts > tries++) {\n cache = factory();\n if (!cache && attempts === tries)\n console.error(\"Lazy factory failed:\", factory);\n }\n return cache;\n };\n}\n\n// Proxies demand that these properties be unmodified, so proxyLazy\n// will always return the function default for them.\nconst unconfigurable = [\"arguments\", \"caller\", \"prototype\"];\n\nconst handler: ProxyHandler = {};\n\nconst kGET = Symbol.for(\"vencord.lazy.get\");\nconst kCACHE = Symbol.for(\"vencord.lazy.cached\");\n\nfor (const method of [\n \"apply\",\n \"construct\",\n \"defineProperty\",\n \"deleteProperty\",\n \"getOwnPropertyDescriptor\",\n \"getPrototypeOf\",\n \"has\",\n \"isExtensible\",\n \"ownKeys\",\n \"preventExtensions\",\n \"set\",\n \"setPrototypeOf\"\n]) {\n handler[method] =\n (target: any, ...args: any[]) => Reflect[method](target[kGET](), ...args);\n}\n\nhandler.ownKeys = target => {\n const v = target[kGET]();\n const keys = Reflect.ownKeys(v);\n for (const key of unconfigurable) {\n if (!keys.includes(key)) keys.push(key);\n }\n return keys;\n};\n\nhandler.getOwnPropertyDescriptor = (target, p) => {\n if (typeof p === \"string\" && unconfigurable.includes(p))\n return Reflect.getOwnPropertyDescriptor(target, p);\n\n const descriptor = Reflect.getOwnPropertyDescriptor(target[kGET](), p);\n\n if (descriptor) Object.defineProperty(target, p, descriptor);\n return descriptor;\n};\n\n/**\n * Wraps the result of {@link makeLazy} in a Proxy you can consume as if it wasn't lazy.\n * On first property access, the lazy is evaluated\n * @param factory lazy factory\n * @param attempts how many times to try to evaluate the lazy before giving up\n * @returns Proxy\n *\n * Note that the example below exists already as an api, see {@link findByPropsLazy}\n * @example const mod = proxyLazy(() => findByProps(\"blah\")); console.log(mod.blah);\n */\nexport function proxyLazy(factory: () => T, attempts = 5, isChild = false): T {\n let isSameTick = true;\n if (!isChild)\n setTimeout(() => isSameTick = false, 0);\n\n let tries = 0;\n const proxyDummy = Object.assign(function () { }, {\n [kCACHE]: void 0 as T | undefined,\n [kGET]() {\n if (!proxyDummy[kCACHE] && attempts > tries++) {\n proxyDummy[kCACHE] = factory();\n if (!proxyDummy[kCACHE] && attempts === tries)\n console.error(\"Lazy factory failed:\", factory);\n }\n return proxyDummy[kCACHE];\n }\n });\n\n return new Proxy(proxyDummy, {\n ...handler,\n get(target, p, receiver) {\n // if we're still in the same tick, it means the lazy was immediately used.\n // thus, we lazy proxy the get access to make things like destructuring work as expected\n // meow here will also be a lazy\n // `const { meow } = findByPropsLazy(\"meow\");`\n if (!isChild && isSameTick)\n return proxyLazy(\n () => Reflect.get(target[kGET](), p, receiver),\n attempts,\n true\n );\n\n return Reflect.get(target[kGET](), p, receiver);\n }\n }) as any;\n}\n", "/*\n * Vencord, a Discord client mod\n * Copyright (c) 2023 Vendicated and contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\n\nimport { ComponentType } from \"react\";\n\nimport { makeLazy } from \"./lazy\";\n\nconst NoopComponent = () => null;\n\n/**\n * A lazy component. The factory method is called on first render.\n * @param factory Function returning a Component\n * @param attempts How many times to try to get the component before giving up\n * @returns Result of factory function\n */\nexport function LazyComponent(factory: () => React.ComponentType, attempts = 5) {\n const get = makeLazy(factory, attempts);\n const LazyComponent = (props: T) => {\n const Component = get() ?? NoopComponent;\n return ;\n };\n\n LazyComponent.$$vencordInternal = get;\n\n return LazyComponent as ComponentType;\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { PatchReplacement, ReplaceFn } from \"./types\";\n\nexport function canonicalizeMatch(match: RegExp | string) {\n if (typeof match === \"string\") return match;\n const canonSource = match.source\n .replaceAll(\"\\\\i\", \"[A-Za-z_$][\\\\w$]*\");\n return new RegExp(canonSource, match.flags);\n}\n\nexport function canonicalizeReplace(replace: string | ReplaceFn, pluginName: string): string | ReplaceFn {\n const self = `Vencord.Plugins.plugins[${JSON.stringify(pluginName)}]`;\n\n if (typeof replace !== \"function\")\n return replace.replaceAll(\"$self\", self);\n\n return (...args) => replace(...args).replaceAll(\"$self\", self);\n}\n\nexport function canonicalizeDescriptor(descriptor: TypedPropertyDescriptor, canonicalize: (value: T) => T) {\n if (descriptor.get) {\n const original = descriptor.get;\n descriptor.get = function () {\n return canonicalize(original.call(this));\n };\n } else if (descriptor.value) {\n descriptor.value = canonicalize(descriptor.value);\n }\n return descriptor;\n}\n\nexport function canonicalizeReplacement(replacement: Pick, plugin: string) {\n const descriptors = Object.getOwnPropertyDescriptors(replacement);\n descriptors.match = canonicalizeDescriptor(descriptors.match, canonicalizeMatch);\n descriptors.replace = canonicalizeDescriptor(\n descriptors.replace,\n replace => canonicalizeReplace(replace, plugin),\n );\n Object.defineProperties(replacement, descriptors);\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Logger } from \"@utils/Logger\";\n\nif (IS_DEV) {\n var traces = {} as Record;\n var logger = new Logger(\"Tracer\", \"#FFD166\");\n}\n\nconst noop = function () { };\n\nexport const beginTrace = !IS_DEV ? noop :\n function beginTrace(name: string, ...args: any[]) {\n if (name in traces)\n throw new Error(`Trace ${name} already exists!`);\n\n traces[name] = [performance.now(), args];\n };\n\nexport const finishTrace = !IS_DEV ? noop : function finishTrace(name: string) {\n const end = performance.now();\n\n const [start, args] = traces[name];\n delete traces[name];\n\n logger.debug(`${name} took ${end - start}ms`, args);\n};\n\ntype Func = (...args: any[]) => any;\ntype TraceNameMapper = (...args: Parameters) => string;\n\nconst noopTracer =\n (name: string, f: F, mapper?: TraceNameMapper) => f;\n\nexport const traceFunction = !IS_DEV\n ? noopTracer\n : function traceFunction(name: string, f: F, mapper?: TraceNameMapper): F {\n return function (this: any, ...args: Parameters) {\n const traceName = mapper?.(...args) ?? name;\n\n beginTrace(traceName, ...arguments);\n try {\n return f.apply(this, args);\n } finally {\n finishTrace(traceName);\n }\n } as F;\n };\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { proxyLazy } from \"@utils/lazy\";\nimport { LazyComponent } from \"@utils/lazyReact\";\nimport { Logger } from \"@utils/Logger\";\nimport { canonicalizeMatch } from \"@utils/patches\";\nimport type { WebpackInstance } from \"discord-types/other\";\n\nimport { traceFunction } from \"../debug/Tracer\";\n\nconst logger = new Logger(\"Webpack\");\n\nexport let _resolveReady: () => void;\n/**\n * Fired once a gateway connection to Discord has been established.\n * This indicates that the core webpack modules have been initialised\n */\nexport const onceReady = new Promise(r => _resolveReady = r);\n\nexport let wreq: WebpackInstance;\nexport let cache: WebpackInstance[\"c\"];\n\nexport type FilterFn = (mod: any) => boolean;\n\nexport const filters = {\n byProps: (...props: string[]): FilterFn =>\n props.length === 1\n ? m => m[props[0]] !== void 0\n : m => props.every(p => m[p] !== void 0),\n\n byCode: (...code: string[]): FilterFn => m => {\n if (typeof m !== \"function\") return false;\n const s = Function.prototype.toString.call(m);\n for (const c of code) {\n if (!s.includes(c)) return false;\n }\n return true;\n },\n byStoreName: (name: string): FilterFn => m =>\n m.constructor?.displayName === name,\n\n componentByCode: (...code: string[]): FilterFn => {\n const filter = filters.byCode(...code);\n return m => {\n if (filter(m)) return true;\n if (!m.$$typeof) return false;\n if (m.type && m.type.render) return filter(m.type.render); // memo + forwardRef\n if (m.type) return filter(m.type); // memos\n if (m.render) return filter(m.render); // forwardRefs\n return false;\n };\n }\n};\n\nexport const subscriptions = new Map();\nexport const listeners = new Set();\n\nexport type CallbackFn = (mod: any, id: string) => void;\n\nexport function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {\n if (cache !== void 0) throw \"no.\";\n\n instance.push([[Symbol(\"Vencord\")], {}, r => wreq = r]);\n instance.pop();\n if (!wreq) return false;\n\n cache = wreq.c;\n return true;\n}\n\nlet devToolsOpen = false;\nif (IS_DEV && IS_DISCORD_DESKTOP) {\n // At this point in time, DiscordNative has not been exposed yet, so setImmediate is needed\n setTimeout(() => {\n DiscordNative/* just to make sure */?.window.setDevtoolsCallbacks(() => devToolsOpen = true, () => devToolsOpen = false);\n }, 0);\n}\n\nfunction handleModuleNotFound(method: string, ...filter: unknown[]) {\n const err = new Error(`webpack.${method} found no module`);\n logger.error(err, \"Filter:\", filter);\n\n // Strict behaviour in DevBuilds to fail early and make sure the issue is found\n if (IS_DEV && !devToolsOpen)\n throw err;\n}\n\n/**\n * Find the first module that matches the filter\n */\nexport const find = traceFunction(\"find\", function find(filter: FilterFn, { isIndirect = false, isWaitFor = false }: { isIndirect?: boolean; isWaitFor?: boolean; } = {}) {\n if (typeof filter !== \"function\")\n throw new Error(\"Invalid filter. Expected a function got \" + typeof filter);\n\n for (const key in cache) {\n const mod = cache[key];\n if (!mod?.exports) continue;\n\n if (filter(mod.exports)) {\n return isWaitFor ? [mod.exports, key] : mod.exports;\n }\n\n if (mod.exports.default && filter(mod.exports.default)) {\n const found = mod.exports.default;\n return isWaitFor ? [found, key] : found;\n }\n }\n\n if (!isIndirect) {\n handleModuleNotFound(\"find\", filter);\n }\n\n return isWaitFor ? [null, null] : null;\n});\n\nexport function findAll(filter: FilterFn) {\n if (typeof filter !== \"function\")\n throw new Error(\"Invalid filter. Expected a function got \" + typeof filter);\n\n const ret = [] as any[];\n for (const key in cache) {\n const mod = cache[key];\n if (!mod?.exports) continue;\n\n if (filter(mod.exports))\n ret.push(mod.exports);\n\n if (mod.exports.default && filter(mod.exports.default))\n ret.push(mod.exports.default);\n }\n\n return ret;\n}\n\n/**\n * Same as {@link find} but in bulk\n * @param filterFns Array of filters. Please note that this array will be modified in place, so if you still\n * need it afterwards, pass a copy.\n * @returns Array of results in the same order as the passed filters\n */\nexport const findBulk = traceFunction(\"findBulk\", function findBulk(...filterFns: FilterFn[]) {\n if (!Array.isArray(filterFns))\n throw new Error(\"Invalid filters. Expected function[] got \" + typeof filterFns);\n\n const { length } = filterFns;\n\n if (length === 0)\n throw new Error(\"Expected at least two filters.\");\n\n if (length === 1) {\n if (IS_DEV) {\n throw new Error(\"bulk called with only one filter. Use find\");\n }\n return find(filterFns[0]);\n }\n\n const filters = filterFns as Array;\n\n let found = 0;\n const results = Array(length);\n\n outer:\n for (const key in cache) {\n const mod = cache[key];\n if (!mod?.exports) continue;\n\n for (let j = 0; j < length; j++) {\n const filter = filters[j];\n // Already done\n if (filter === undefined) continue;\n\n if (filter(mod.exports)) {\n results[j] = mod.exports;\n filters[j] = undefined;\n if (++found === length) break outer;\n break;\n }\n\n if (mod.exports.default && filter(mod.exports.default)) {\n results[j] = mod.exports.default;\n filters[j] = undefined;\n if (++found === length) break outer;\n break;\n }\n }\n }\n\n if (found !== length) {\n const err = new Error(`Got ${length} filters, but only found ${found} modules!`);\n if (IS_DEV) {\n if (!devToolsOpen)\n // Strict behaviour in DevBuilds to fail early and make sure the issue is found\n throw err;\n } else {\n logger.warn(err);\n }\n }\n\n return results;\n});\n\n/**\n * Find the id of the first module factory that includes all the given code\n * @returns string or null\n */\nexport const findModuleId = traceFunction(\"findModuleId\", function findModuleId(...code: string[]) {\n outer:\n for (const id in wreq.m) {\n const str = wreq.m[id].toString();\n\n for (const c of code) {\n if (!str.includes(c)) continue outer;\n }\n return id;\n }\n\n const err = new Error(\"Didn't find module with code(s):\\n\" + code.join(\"\\n\"));\n if (IS_DEV) {\n if (!devToolsOpen)\n // Strict behaviour in DevBuilds to fail early and make sure the issue is found\n throw err;\n } else {\n logger.warn(err);\n }\n\n return null;\n});\n\n/**\n * Find the first module factory that includes all the given code\n * @returns The module factory or null\n */\nexport function findModuleFactory(...code: string[]) {\n const id = findModuleId(...code);\n if (!id) return null;\n\n return wreq.m[id];\n}\n\nexport const lazyWebpackSearchHistory = [] as Array<[\"find\" | \"findByProps\" | \"findByCode\" | \"findStore\" | \"findComponent\" | \"findComponentByCode\" | \"findExportedComponent\" | \"waitFor\" | \"waitForComponent\" | \"waitForStore\" | \"proxyLazyWebpack\" | \"LazyComponentWebpack\" | \"extractAndLoadChunks\", any[]]>;\n\n/**\n * This is just a wrapper around {@link proxyLazy} to make our reporter test for your webpack finds.\n *\n * Wraps the result of {@link makeLazy} in a Proxy you can consume as if it wasn't lazy.\n * On first property access, the lazy is evaluated\n * @param factory lazy factory\n * @param attempts how many times to try to evaluate the lazy before giving up\n * @returns Proxy\n *\n * Note that the example below exists already as an api, see {@link findByPropsLazy}\n * @example const mod = proxyLazy(() => findByProps(\"blah\")); console.log(mod.blah);\n */\nexport function proxyLazyWebpack(factory: () => any, attempts?: number) {\n if (IS_DEV) lazyWebpackSearchHistory.push([\"proxyLazyWebpack\", [factory]]);\n\n return proxyLazy(factory, attempts);\n}\n\n/**\n * This is just a wrapper around {@link LazyComponent} to make our reporter test for your webpack finds.\n *\n * A lazy component. The factory method is called on first render.\n * @param factory Function returning a Component\n * @param attempts How many times to try to get the component before giving up\n * @returns Result of factory function\n */\nexport function LazyComponentWebpack(factory: () => any, attempts?: number) {\n if (IS_DEV) lazyWebpackSearchHistory.push([\"LazyComponentWebpack\", [factory]]);\n\n return LazyComponent(factory, attempts);\n}\n\n/**\n * Find the first module that matches the filter, lazily\n */\nexport function findLazy(filter: FilterFn) {\n if (IS_DEV) lazyWebpackSearchHistory.push([\"find\", [filter]]);\n\n return proxyLazy(() => find(filter));\n}\n\n/**\n * Find the first module that has the specified properties\n */\nexport function findByProps(...props: string[]) {\n const res = find(filters.byProps(...props), { isIndirect: true });\n if (!res)\n handleModuleNotFound(\"findByProps\", ...props);\n return res;\n}\n\n/**\n * Find the first module that has the specified properties, lazily\n */\nexport function findByPropsLazy(...props: string[]) {\n if (IS_DEV) lazyWebpackSearchHistory.push([\"findByProps\", props]);\n\n return proxyLazy(() => findByProps(...props));\n}\n\n/**\n * Find the first function that includes all the given code\n */\nexport function findByCode(...code: string[]) {\n const res = find(filters.byCode(...code), { isIndirect: true });\n if (!res)\n handleModuleNotFound(\"findByCode\", ...code);\n return res;\n}\n\n/**\n * Find the first function that includes all the given code, lazily\n */\nexport function findByCodeLazy(...code: string[]) {\n if (IS_DEV) lazyWebpackSearchHistory.push([\"findByCode\", code]);\n\n return proxyLazy(() => findByCode(...code));\n}\n\n/**\n * Find a store by its displayName\n */\nexport function findStore(name: string) {\n const res = find(filters.byStoreName(name), { isIndirect: true });\n if (!res)\n handleModuleNotFound(\"findStore\", name);\n return res;\n}\n\n/**\n * Find a store by its displayName, lazily\n */\nexport function findStoreLazy(name: string) {\n if (IS_DEV) lazyWebpackSearchHistory.push([\"findStore\", [name]]);\n\n return proxyLazy(() => findStore(name));\n}\n\n/**\n * Finds the component which includes all the given code. Checks for plain components, memos and forwardRefs\n */\nexport function findComponentByCode(...code: string[]) {\n const res = find(filters.componentByCode(...code), { isIndirect: true });\n if (!res)\n handleModuleNotFound(\"findComponentByCode\", ...code);\n return res;\n}\n\n/**\n * Finds the first component that matches the filter, lazily.\n */\nexport function findComponentLazy(filter: FilterFn) {\n if (IS_DEV) lazyWebpackSearchHistory.push([\"findComponent\", [filter]]);\n\n\n return LazyComponent(() => {\n const res = find(filter, { isIndirect: true });\n if (!res)\n handleModuleNotFound(\"findComponent\", filter);\n return res;\n });\n}\n\n/**\n * Finds the first component that includes all the given code, lazily\n */\nexport function findComponentByCodeLazy(...code: string[]) {\n if (IS_DEV) lazyWebpackSearchHistory.push([\"findComponentByCode\", code]);\n\n return LazyComponent(() => {\n const res = find(filters.componentByCode(...code), { isIndirect: true });\n if (!res)\n handleModuleNotFound(\"findComponentByCode\", ...code);\n return res;\n });\n}\n\n/**\n * Finds the first component that is exported by the first prop name, lazily\n */\nexport function findExportedComponentLazy(...props: string[]) {\n if (IS_DEV) lazyWebpackSearchHistory.push([\"findExportedComponent\", props]);\n\n return LazyComponent(() => {\n const res = find(filters.byProps(...props), { isIndirect: true });\n if (!res)\n handleModuleNotFound(\"findExportedComponent\", ...props);\n return res[props[0]];\n });\n}\n\n/**\n * Extract and load chunks using their entry point\n * @param code An array of all the code the module factory containing the entry point (as of using it to load chunks) must include\n * @param matcher A RegExp that returns the entry point id as the first capture group. Defaults to a matcher that captures the first entry point found in the module factory\n */\nexport async function extractAndLoadChunks(code: string[], matcher: RegExp = /\\.el\\(\"(.+?)\"\\)(?<=(\\i)\\.el.+?)\\.then\\(\\2\\.bind\\(\\2,\"\\1\"\\)\\)/) {\n const module = findModuleFactory(...code);\n if (!module) {\n const err = new Error(\"extractAndLoadChunks: Couldn't find module factory\");\n logger.warn(err, \"Code:\", code, \"Matcher:\", matcher);\n\n return;\n }\n\n const match = module.toString().match(canonicalizeMatch(matcher));\n if (!match) {\n const err = new Error(\"extractAndLoadChunks: Couldn't find entry point id in module factory code\");\n logger.warn(err, \"Code:\", code, \"Matcher:\", matcher);\n\n // Strict behaviour in DevBuilds to fail early and make sure the issue is found\n if (IS_DEV && !devToolsOpen)\n throw err;\n\n return;\n }\n\n const [, id] = match;\n if (!id || !Number(id)) {\n const err = new Error(\"extractAndLoadChunks: Matcher didn't return a capturing group with the entry point, or the entry point returned wasn't a number\");\n logger.warn(err, \"Code:\", code, \"Matcher:\", matcher);\n\n // Strict behaviour in DevBuilds to fail early and make sure the issue is found\n if (IS_DEV && !devToolsOpen)\n throw err;\n\n return;\n }\n\n await (wreq as any).el(id);\n return wreq(id as any);\n}\n\n/**\n * This is just a wrapper around {@link extractAndLoadChunks} to make our reporter test for your webpack finds.\n *\n * Extract and load chunks using their entry point\n * @param code An array of all the code the module factory containing the entry point (as of using it to load chunks) must include\n * @param matcher A RegExp that returns the entry point id as the first capture group. Defaults to a matcher that captures the first entry point found in the module factory\n * @returns A function that loads the chunks on first call\n */\nexport function extractAndLoadChunksLazy(code: string[], matcher: RegExp = /\\.el\\(\"(.+?)\"\\)(?<=(\\i)\\.el.+?)\\.then\\(\\2\\.bind\\(\\2,\"\\1\"\\)\\)/) {\n if (IS_DEV) lazyWebpackSearchHistory.push([\"extractAndLoadChunks\", [code, matcher]]);\n\n return () => extractAndLoadChunks(code, matcher);\n}\n\n/**\n * Wait for a module that matches the provided filter to be registered,\n * then call the callback with the module as the first argument\n */\nexport function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn, { isIndirect = false }: { isIndirect?: boolean; } = {}) {\n if (IS_DEV && !isIndirect) lazyWebpackSearchHistory.push([\"waitFor\", Array.isArray(filter) ? filter : [filter]]);\n\n if (typeof filter === \"string\")\n filter = filters.byProps(filter);\n else if (Array.isArray(filter))\n filter = filters.byProps(...filter);\n else if (typeof filter !== \"function\")\n throw new Error(\"filter must be a string, string[] or function, got \" + typeof filter);\n\n if (cache != null) {\n const [existing, id] = find(filter, { isIndirect: true, isWaitFor: true });\n if (existing) return void callback(existing, id);\n }\n\n subscriptions.set(filter, callback);\n}\n\nexport function addListener(callback: CallbackFn) {\n listeners.add(callback);\n}\n\nexport function removeListener(callback: CallbackFn) {\n listeners.delete(callback);\n}\n\n/**\n * Search modules by keyword. This searches the factory methods,\n * meaning you can search all sorts of things, displayName, methodName, strings somewhere in the code, etc\n * @param filters One or more strings or regexes\n * @returns Mapping of found modules\n */\nexport function search(...filters: Array) {\n const results = {} as Record;\n const factories = wreq.m;\n outer:\n for (const id in factories) {\n const factory = factories[id].original ?? factories[id];\n const str: string = factory.toString();\n for (const filter of filters) {\n if (typeof filter === \"string\" && !str.includes(filter)) continue outer;\n if (filter instanceof RegExp && !filter.test(str)) continue outer;\n }\n results[id] = factory;\n }\n\n return results;\n}\n\n/**\n * Extract a specific module by id into its own Source File. This has no effect on\n * the code, it is only useful to be able to look at a specific module without having\n * to view a massive file. extract then returns the extracted module so you can jump to it.\n * As mentioned above, note that this extracted module is not actually used,\n * so putting breakpoints or similar will have no effect.\n * @param id The id of the module to extract\n */\nexport function extract(id: string | number) {\n const mod = wreq.m[id] as Function;\n if (!mod) return null;\n\n const code = `\n// [EXTRACTED] WebpackModule${id}\n// WARNING: This module was extracted to be more easily readable.\n// This module is NOT ACTUALLY USED! This means putting breakpoints will have NO EFFECT!!\n\n0,${mod.toString()}\n//# sourceURL=ExtractedWebpackModule${id}\n`;\n const extracted = (0, eval)(code);\n return extracted as Function;\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { findByPropsLazy, findLazy } from \"@webpack\";\n\nimport * as t from \"./types/classes\";\n\nexport const ModalImageClasses: t.ImageModalClasses = findLazy(m => m.image && m.modal && !m.applicationIcon);\nexport const ButtonWrapperClasses: t.ButtonWrapperClasses = findByPropsLazy(\"buttonWrapper\", \"buttonContent\");\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { LazyComponent } from \"@utils/react\";\n\n// eslint-disable-next-line path-alias/no-relative\nimport { FilterFn, filters, lazyWebpackSearchHistory, waitFor } from \"../webpack\";\n\nexport function waitForComponent = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]): T {\n if (IS_DEV) lazyWebpackSearchHistory.push([\"waitForComponent\", Array.isArray(filter) ? filter : [filter]]);\n\n let myValue: T = function () {\n throw new Error(`Vencord could not find the ${name} Component`);\n } as any;\n\n const lazyComponent = LazyComponent(() => myValue) as T;\n waitFor(filter, (v: any) => {\n myValue = v;\n Object.assign(lazyComponent, v);\n }, { isIndirect: true });\n\n return lazyComponent;\n}\n\nexport function waitForStore(name: string, cb: (v: any) => void) {\n if (IS_DEV) lazyWebpackSearchHistory.push([\"waitForStore\", [name]]);\n\n waitFor(filters.byStoreName(name), cb, { isIndirect: true });\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\n// eslint-disable-next-line path-alias/no-relative\nimport { filters, findByPropsLazy, waitFor } from \"@webpack\";\n\nimport { waitForComponent } from \"./internal\";\nimport * as t from \"./types/components\";\n\nexport let Forms = {} as {\n FormTitle: t.FormTitle,\n FormSection: t.FormSection,\n FormDivider: t.FormDivider,\n FormText: t.FormText,\n};\n\nexport let Card: t.Card;\nexport let Button: t.Button;\nexport let Switch: t.Switch;\nexport let Tooltip: t.Tooltip;\nexport let TextInput: t.TextInput;\nexport let TextArea: t.TextArea;\nexport let Text: t.Text;\nexport let Select: t.Select;\nexport let SearchableSelect: t.SearchableSelect;\nexport let Slider: t.Slider;\nexport let ButtonLooks: t.ButtonLooks;\nexport let Popout: t.Popout;\nexport let Dialog: t.Dialog;\nexport let TabBar: any;\nexport let Paginator: t.Paginator;\nexport let ScrollerThin: t.ScrollerThin;\nexport let Clickable: t.Clickable;\nexport let Avatar: t.Avatar;\nexport let FocusLock: t.FocusLock;\n// token lagger real\n/** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */\nexport let useToken: t.useToken;\n\nexport const MaskedLink = waitForComponent(\"MaskedLink\", filters.componentByCode(\"MASKED_LINK)\"));\nexport const Timestamp = waitForComponent(\"Timestamp\", filters.byCode(\".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format\"));\nexport const Flex = waitForComponent(\"Flex\", [\"Justify\", \"Align\", \"Wrap\"]);\n\nexport const { OAuth2AuthorizeModal } = findByPropsLazy(\"OAuth2AuthorizeModal\");\n\nwaitFor([\"FormItem\", \"Button\"], m => {\n ({ useToken, Card, Button, FormSwitch: Switch, Tooltip, TextInput, TextArea, Text, Select, SearchableSelect, Slider, ButtonLooks, TabBar, Popout, Dialog, Paginator, ScrollerThin, Clickable, Avatar, FocusLock } = m);\n Forms = m;\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\n// eslint-disable-next-line path-alias/no-relative\nimport { findByPropsLazy, waitFor } from \"../webpack\";\nimport type * as t from \"./types/menu\";\n\nexport let Menu = {} as t.Menu;\n\nwaitFor([\"MenuItem\", \"MenuSliderControl\"], m => Menu = m);\n\nexport const ContextMenuApi: t.ContextMenuApi = findByPropsLazy(\"closeContextMenu\", \"openContextMenu\");\n\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\n// eslint-disable-next-line path-alias/no-relative\nimport { findByPropsLazy, waitFor } from \"../webpack\";\n\nexport let React: typeof import(\"react\");\nexport let useState: typeof React.useState;\nexport let useEffect: typeof React.useEffect;\nexport let useMemo: typeof React.useMemo;\nexport let useRef: typeof React.useRef;\nexport let useReducer: typeof React.useReducer;\nexport let useCallback: typeof React.useCallback;\n\nexport const ReactDOM: typeof import(\"react-dom\") & typeof import(\"react-dom/client\") = findByPropsLazy(\"createPortal\", \"render\");\n\nwaitFor(\"useState\", m => {\n React = m;\n ({ useEffect, useState, useMemo, useRef, useReducer, useCallback } = React);\n});\n", "/*\n * Vencord, a Discord client mod\n * Copyright (c) 2023 Vendicated and contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\n\nimport { findByPropsLazy } from \"@webpack\";\n\nimport * as t from \"./types/settingsStores\";\n\n\nexport const TextAndImagesSettingsStores = findByPropsLazy(\"MessageDisplayCompact\") as Record;\nexport const StatusSettingsStores = findByPropsLazy(\"ShowCurrentGame\") as Record;\n\nexport const UserSettingsActionCreators = findByPropsLazy(\"PreloadedUserSettingsActionCreators\");\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport type * as Stores from \"discord-types/stores\";\n\n// eslint-disable-next-line path-alias/no-relative\nimport { findByPropsLazy } from \"../webpack\";\nimport { waitForStore } from \"./internal\";\nimport * as t from \"./types/stores\";\n\nexport const Flux: t.Flux = findByPropsLazy(\"connectStores\");\n\nexport type GenericStore = t.FluxStore & Record;\n\nexport enum DraftType {\n ChannelMessage = 0,\n ThreadSettings = 1,\n FirstThreadMessage = 2,\n ApplicationLauncherCommand = 3\n}\n\nexport let MessageStore: Omit & {\n getMessages(chanId: string): any;\n};\n\n// this is not actually a FluxStore\nexport const PrivateChannelsStore = findByPropsLazy(\"openPrivateChannel\");\nexport let PermissionStore: GenericStore;\nexport let GuildChannelStore: GenericStore;\nexport let ReadStateStore: GenericStore;\nexport let PresenceStore: GenericStore;\nexport let PoggerModeSettingsStore: GenericStore;\n\nexport let GuildStore: t.GuildStore;\nexport let UserStore: Stores.UserStore & t.FluxStore;\nexport let UserProfileStore: GenericStore;\nexport let SelectedChannelStore: Stores.SelectedChannelStore & t.FluxStore;\nexport let SelectedGuildStore: t.FluxStore & Record;\nexport let ChannelStore: Stores.ChannelStore & t.FluxStore;\nexport let GuildMemberStore: Stores.GuildMemberStore & t.FluxStore;\nexport let RelationshipStore: Stores.RelationshipStore & t.FluxStore & {\n /** Get the date (as a string) that the relationship was created */\n getSince(userId: string): string;\n};\n\nexport let EmojiStore: t.EmojiStore;\nexport let WindowStore: t.WindowStore;\nexport let DraftStore: t.DraftStore;\n\n/**\n * React hook that returns stateful data for one or more stores\n * You might need a custom comparator (4th argument) if your store data is an object\n *\n * @param stores The stores to listen to\n * @param mapper A function that returns the data you need\n * @param idk some thing, idk just pass null\n * @param isEqual A custom comparator for the data returned by mapper\n *\n * @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id);\n */\nexport const { useStateFromStores }: {\n useStateFromStores: (\n stores: t.FluxStore[],\n mapper: () => T,\n idk?: any,\n isEqual?: (old: T, newer: T) => boolean\n ) => T;\n}\n = findByPropsLazy(\"useStateFromStores\");\n\nwaitForStore(\"DraftStore\", s => DraftStore = s);\nwaitForStore(\"UserStore\", s => UserStore = s);\nwaitForStore(\"UserProfileStore\", m => UserProfileStore = m);\nwaitForStore(\"ChannelStore\", m => ChannelStore = m);\nwaitForStore(\"SelectedChannelStore\", m => SelectedChannelStore = m);\nwaitForStore(\"SelectedGuildStore\", m => SelectedGuildStore = m);\nwaitForStore(\"GuildStore\", m => GuildStore = m);\nwaitForStore(\"GuildMemberStore\", m => GuildMemberStore = m);\nwaitForStore(\"RelationshipStore\", m => RelationshipStore = m);\nwaitForStore(\"PermissionStore\", m => PermissionStore = m);\nwaitForStore(\"PresenceStore\", m => PresenceStore = m);\nwaitForStore(\"ReadStateStore\", m => ReadStateStore = m);\nwaitForStore(\"GuildChannelStore\", m => GuildChannelStore = m);\nwaitForStore(\"MessageStore\", m => MessageStore = m);\nwaitForStore(\"WindowStore\", m => WindowStore = m);\nwaitForStore(\"EmojiStore\", m => EmojiStore = m);\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from \"react\";\n\nexport type TextVariant = \"heading-sm/normal\" | \"heading-sm/medium\" | \"heading-sm/semibold\" | \"heading-sm/bold\" | \"heading-md/normal\" | \"heading-md/medium\" | \"heading-md/semibold\" | \"heading-md/bold\" | \"heading-lg/normal\" | \"heading-lg/medium\" | \"heading-lg/semibold\" | \"heading-lg/bold\" | \"heading-xl/normal\" | \"heading-xl/medium\" | \"heading-xl/bold\" | \"heading-xxl/normal\" | \"heading-xxl/medium\" | \"heading-xxl/bold\" | \"eyebrow\" | \"heading-deprecated-14/normal\" | \"heading-deprecated-14/medium\" | \"heading-deprecated-14/bold\" | \"text-xxs/normal\" | \"text-xxs/medium\" | \"text-xxs/semibold\" | \"text-xxs/bold\" | \"text-xs/normal\" | \"text-xs/medium\" | \"text-xs/semibold\" | \"text-xs/bold\" | \"text-sm/normal\" | \"text-sm/medium\" | \"text-sm/semibold\" | \"text-sm/bold\" | \"text-md/normal\" | \"text-md/medium\" | \"text-md/semibold\" | \"text-md/bold\" | \"text-lg/normal\" | \"text-lg/medium\" | \"text-lg/semibold\" | \"text-lg/bold\" | \"display-sm\" | \"display-md\" | \"display-lg\" | \"code\";\nexport type FormTextTypes = Record<\"DEFAULT\" | \"INPUT_PLACEHOLDER\" | \"DESCRIPTION\" | \"LABEL_BOLD\" | \"LABEL_SELECTED\" | \"LABEL_DESCRIPTOR\" | \"ERROR\" | \"SUCCESS\", string>;\nexport type Heading = `h${1 | 2 | 3 | 4 | 5 | 6}`;\n\nexport type Margins = Record<\"marginTop16\" | \"marginTop8\" | \"marginBottom8\" | \"marginTop20\" | \"marginBottom20\", string>;\nexport type ButtonLooks = Record<\"FILLED\" | \"INVERTED\" | \"OUTLINED\" | \"LINK\" | \"BLANK\", string>;\n\nexport type TextProps = PropsWithChildren & {\n variant?: TextVariant;\n tag?: \"div\" | \"span\" | \"p\" | \"strong\" | Heading;\n selectable?: boolean;\n lineClamp?: number;\n}>;\n\nexport type Text = ComponentType;\n\nexport type FormTitle = ComponentType & PropsWithChildren<{\n /** default is h5 */\n tag?: Heading;\n faded?: boolean;\n disabled?: boolean;\n required?: boolean;\n error?: ReactNode;\n}>>;\n\nexport type FormSection = ComponentType>;\n\nexport type FormDivider = ComponentType<{\n className?: string;\n style?: CSSProperties;\n}>;\n\n\nexport type FormText = ComponentType & TextProps> & { Types: FormTextTypes; };\n\nexport type Tooltip = ComponentType<{\n text: ReactNode;\n children: FunctionComponent<{\n onClick(): void;\n onMouseEnter(): void;\n onMouseLeave(): void;\n onContextMenu(): void;\n onFocus(): void;\n onBlur(): void;\n \"aria-label\"?: string;\n }>;\n \"aria-label\"?: string;\n\n allowOverflow?: boolean;\n forceOpen?: boolean;\n hide?: boolean;\n hideOnClick?: boolean;\n shouldShow?: boolean;\n spacing?: number;\n\n /** Tooltip.Colors.BLACK */\n color?: string;\n /** TooltipPositions.TOP */\n position?: string;\n\n tooltipClassName?: string;\n tooltipContentClassName?: string;\n}> & {\n Colors: Record<\"BLACK\" | \"BRAND\" | \"CUSTOM\" | \"GREEN\" | \"GREY\" | \"PRIMARY\" | \"RED\" | \"YELLOW\", string>;\n};\n\nexport type TooltipPositions = Record<\"BOTTOM\" | \"CENTER\" | \"LEFT\" | \"RIGHT\" | \"TOP\" | \"WINDOW_CENTER\", string>;\n\nexport type Card = ComponentType & {\n editable?: boolean;\n outline?: boolean;\n /** Card.Types.PRIMARY */\n type?: string;\n}>> & {\n Types: Record<\"BRAND\" | \"CUSTOM\" | \"DANGER\" | \"PRIMARY\" | \"SUCCESS\" | \"WARNING\", string>;\n};\n\nexport type Button = ComponentType, \"size\"> & {\n /** Button.Looks.FILLED */\n look?: string;\n /** Button.Colors.BRAND */\n color?: string;\n /** Button.Sizes.MEDIUM */\n size?: string;\n /** Button.BorderColors.BLACK */\n borderColor?: string;\n\n wrapperClassName?: string;\n className?: string;\n innerClassName?: string;\n\n buttonRef?: Ref;\n focusProps?: any;\n submitting?: boolean;\n\n submittingStartedLabel?: string;\n submittingFinishedLabel?: string;\n}>> & {\n BorderColors: Record<\"BLACK\" | \"BRAND\" | \"BRAND_NEW\" | \"GREEN\" | \"LINK\" | \"PRIMARY\" | \"RED\" | \"TRANSPARENT\" | \"WHITE\" | \"YELLOW\", string>;\n Colors: Record<\"BRAND\" | \"RED\" | \"GREEN\" | \"YELLOW\" | \"PRIMARY\" | \"LINK\" | \"WHITE\" | \"BLACK\" | \"TRANSPARENT\" | \"BRAND_NEW\" | \"CUSTOM\", string>;\n Hovers: Record<\"DEFAULT\" | \"BRAND\" | \"RED\" | \"GREEN\" | \"YELLOW\" | \"PRIMARY\" | \"LINK\" | \"WHITE\" | \"BLACK\" | \"TRANSPARENT\", string>;\n Looks: Record<\"FILLED\" | \"INVERTED\" | \"OUTLINED\" | \"LINK\" | \"BLANK\", string>;\n Sizes: Record<\"NONE\" | \"TINY\" | \"SMALL\" | \"MEDIUM\" | \"LARGE\" | \"XLARGE\" | \"MIN\" | \"MAX\" | \"ICON\", string>;\n\n Link: any;\n};\n\nexport type Switch = ComponentType>;\n\nexport type Timestamp = ComponentType>;\n\nexport type TextInput = ComponentType;\n prefixElement?: ReactNode;\n\n focusProps?: any;\n\n /** TextInput.Sizes.DEFAULT */\n size?: string;\n} & Omit, \"onChange\">>> & {\n Sizes: Record<\"DEFAULT\" | \"MINI\", string>;\n};\n\nexport type TextArea = ComponentType, \"onChange\"> & {\n onChange(v: string): void;\n}>>;\n\ninterface SelectOption {\n disabled?: boolean;\n value: any;\n label: string;\n key?: React.Key;\n default?: boolean;\n}\n\nexport type Select = ComponentType; // TODO\n\n /**\n * - 0 ~ Filled\n * - 1 ~ Custom\n */\n look?: 0 | 1;\n className?: string;\n popoutClassName?: string;\n popoutPosition?: \"top\" | \"left\" | \"right\" | \"bottom\" | \"center\" | \"window_center\";\n optionClassName?: string;\n\n autoFocus?: boolean;\n isDisabled?: boolean;\n clearable?: boolean;\n closeOnSelect?: boolean;\n hideIcon?: boolean;\n\n select(value: any): void;\n isSelected(value: any): boolean;\n serialize(value: any): string;\n clear?(): void;\n\n maxVisibleItems?: number;\n popoutWidth?: number;\n\n onClose?(): void;\n onOpen?(): void;\n\n renderOptionLabel?(option: SelectOption): ReactNode;\n /** discord stupid this gets all options instead of one yeah */\n renderOptionValue?(option: SelectOption[]): ReactNode;\n\n \"aria-label\"?: boolean;\n \"aria-labelledby\"?: boolean;\n}>>;\n\nexport type SearchableSelect = ComponentType; // TODO\n value?: SelectOption;\n\n /**\n * - 0 ~ Filled\n * - 1 ~ Custom\n */\n look?: 0 | 1;\n className?: string;\n popoutClassName?: string;\n wrapperClassName?: string;\n popoutPosition?: \"top\" | \"left\" | \"right\" | \"bottom\" | \"center\" | \"window_center\";\n optionClassName?: string;\n\n autoFocus?: boolean;\n isDisabled?: boolean;\n clearable?: boolean;\n closeOnSelect?: boolean;\n clearOnSelect?: boolean;\n multi?: boolean;\n\n onChange(value: any): void;\n onSearchChange?(value: string): void;\n\n onClose?(): void;\n onOpen?(): void;\n onBlur?(): void;\n\n renderOptionPrefix?(option: SelectOption): ReactNode;\n renderOptionSuffix?(option: SelectOption): ReactNode;\n\n filter?(option: SelectOption[], query: string): SelectOption[];\n\n centerCaret?: boolean;\n debounceTime?: number;\n maxVisibleItems?: number;\n popoutWidth?: number;\n\n \"aria-labelledby\"?: boolean;\n}>>;\n\nexport type Slider = ComponentType>;\n\n// TODO - type maybe idk probably not that useful other than the constants\nexport type Flex = ComponentType> & {\n Align: Record<\"START\" | \"END\" | \"CENTER\" | \"STRETCH\" | \"BASELINE\", string>;\n Direction: Record<\"VERTICAL\" | \"HORIZONTAL\" | \"HORIZONTAL_REVERSE\", string>;\n Justify: Record<\"START\" | \"END\" | \"CENTER\" | \"BETWEEN\" | \"AROUND\", string>;\n Wrap: Record<\"NO_WRAP\" | \"WRAP\" | \"WRAP_REVERSE\", string>;\n};\n\ndeclare enum PopoutAnimation {\n NONE = \"1\",\n TRANSLATE = \"2\",\n SCALE = \"3\",\n FADE = \"4\"\n}\n\nexport type Popout = ComponentType<{\n children(\n thing: {\n \"aria-controls\": string;\n \"aria-expanded\": boolean;\n onClick(event: MouseEvent): void;\n onKeyDown(event: KeyboardEvent): void;\n onMouseDown(event: MouseEvent): void;\n },\n data: {\n isShown: boolean;\n position: string;\n }\n ): ReactNode;\n shouldShow?: boolean;\n renderPopout(args: {\n closePopout(): void;\n isPositioned: boolean;\n nudge: number;\n position: string;\n setPopoutRef(ref: any): void;\n updatePosition(): void;\n }): ReactNode;\n\n onRequestOpen?(): void;\n onRequestClose?(): void;\n\n /** \"center\" and others */\n align?: string;\n /** Popout.Animation */\n animation?: PopoutAnimation;\n autoInvert?: boolean;\n nudgeAlignIntoViewport?: boolean;\n /** \"bottom\" and others */\n position?: string;\n positionKey?: string;\n spacing?: number;\n}> & {\n Animation: typeof PopoutAnimation;\n};\n\nexport type Dialog = ComponentType>;\n\ntype Resolve = (data: { theme: \"light\" | \"dark\", saturation: number; }) => {\n hex(): string;\n hsl(): string;\n int(): number;\n spring(): string;\n};\n\nexport type useToken = (color: {\n css: string;\n resolve: Resolve;\n}) => ReturnType;\n\nexport type Paginator = ComponentType<{\n currentPage: number;\n maxVisiblePages: number;\n pageSize: number;\n totalCount: number;\n\n onPageChange?(page: number): void;\n hideMaxPage?: boolean;\n}>;\n\nexport type MaskedLink = ComponentType>;\n\nexport type ScrollerThin = ComponentType>;\n\nexport type Clickable = ComponentType>;\n\nexport type Avatar = ComponentType>;\n\ntype FocusLock = ComponentType\n}>>;\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport type { ComponentType, CSSProperties, MouseEvent, PropsWithChildren, ReactNode, UIEvent } from \"react\";\n\ntype RC = ComponentType>>;\n\nexport interface Menu {\n Menu: RC<{\n navId: string;\n onClose(): void;\n className?: string;\n style?: CSSProperties;\n hideScroller?: boolean;\n onSelect?(): void;\n }>;\n MenuSeparator: ComponentType;\n MenuGroup: RC<{\n label?: string;\n }>;\n MenuItem: RC<{\n id: string;\n label: ReactNode;\n action?(e: MouseEvent): void;\n icon?: ComponentType;\n\n color?: string;\n render?: ComponentType;\n onChildrenScroll?: Function;\n childRowHeight?: number;\n listClassName?: string;\n disabled?: boolean;\n }>;\n MenuCheckboxItem: RC<{\n id: string;\n label: string;\n checked: boolean;\n action?(e: MouseEvent): void;\n disabled?: boolean;\n }>;\n MenuRadioItem: RC<{\n id: string;\n group: string;\n label: string;\n checked: boolean;\n action?(e: MouseEvent): void;\n disabled?: boolean;\n }>;\n MenuControlItem: RC<{\n id: string;\n interactive?: boolean;\n }>;\n MenuSliderControl: RC<{\n minValue: number,\n maxValue: number,\n value: number,\n onChange(value: number): void,\n renderValue?(value: number): string,\n }>;\n}\n\nexport interface ContextMenuApi {\n closeContextMenu(): void;\n openContextMenu(\n event: UIEvent,\n render?: Menu[\"Menu\"],\n options?: { enableSpellCheck?: boolean; },\n renderLazy?: () => Promise\n ): void;\n openContextMenuLazy(\n event: UIEvent,\n renderLazy?: () => Promise,\n options?: { enableSpellCheck?: boolean; }\n ): void;\n}\n\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Guild, GuildMember } from \"discord-types/general\";\nimport type { ReactNode } from \"react\";\n\nimport type { FluxEvents } from \"./fluxEvents\";\nimport { i18nMessages } from \"./i18nMessages\";\n\nexport { FluxEvents };\n\nexport interface FluxDispatcher {\n _actionHandlers: any;\n _subscriptions: any;\n dispatch(event: { [key: string]: unknown; type: FluxEvents; }): Promise;\n isDispatching(): boolean;\n subscribe(event: FluxEvents, callback: (data: any) => void): void;\n unsubscribe(event: FluxEvents, callback: (data: any) => void): void;\n wait(callback: () => void): void;\n}\n\nexport type Parser = Record<\n | \"parse\"\n | \"parseTopic\"\n | \"parseEmbedTitle\"\n | \"parseInlineReply\"\n | \"parseGuildVerificationFormRule\"\n | \"parseGuildEventDescription\"\n | \"parseAutoModerationSystemMessage\"\n | \"parseForumPostGuidelines\"\n | \"parseForumPostMostRecentMessage\",\n (content: string, inline?: boolean, state?: Record) => ReactNode[]\n> & Record<\"defaultRules\" | \"guildEventRules\", Record>>;\n\nexport interface Alerts {\n show(alert: {\n title: any;\n body: React.ReactNode;\n className?: string;\n confirmColor?: string;\n cancelText?: string;\n confirmText?: string;\n secondaryConfirmText?: string;\n onCancel?(): void;\n onConfirm?(): void;\n onConfirmSecondary?(): void;\n onCloseCallback?(): void;\n }): void;\n /** This is a noop, it does nothing. */\n close(): void;\n}\n\nexport interface SnowflakeUtils {\n fromTimestamp(timestamp: number): string;\n extractTimestamp(snowflake: string): number;\n age(snowflake: string): number;\n atPreviousMillisecond(snowflake: string): string;\n compare(snowflake1?: string, snowflake2?: string): number;\n}\n\ninterface RestRequestData {\n url: string;\n query?: Record;\n body?: Record;\n oldFormErrors?: boolean;\n retries?: number;\n}\n\nexport type RestAPI = Record<\"delete\" | \"get\" | \"patch\" | \"post\" | \"put\", (data: RestRequestData) => Promise>;\n\nexport type Permissions = \"CREATE_INSTANT_INVITE\"\n | \"KICK_MEMBERS\"\n | \"BAN_MEMBERS\"\n | \"ADMINISTRATOR\"\n | \"MANAGE_CHANNELS\"\n | \"MANAGE_GUILD\"\n | \"CHANGE_NICKNAME\"\n | \"MANAGE_NICKNAMES\"\n | \"MANAGE_ROLES\"\n | \"MANAGE_WEBHOOKS\"\n | \"MANAGE_GUILD_EXPRESSIONS\"\n | \"CREATE_GUILD_EXPRESSIONS\"\n | \"VIEW_AUDIT_LOG\"\n | \"VIEW_CHANNEL\"\n | \"VIEW_GUILD_ANALYTICS\"\n | \"VIEW_CREATOR_MONETIZATION_ANALYTICS\"\n | \"MODERATE_MEMBERS\"\n | \"SEND_MESSAGES\"\n | \"SEND_TTS_MESSAGES\"\n | \"MANAGE_MESSAGES\"\n | \"EMBED_LINKS\"\n | \"ATTACH_FILES\"\n | \"READ_MESSAGE_HISTORY\"\n | \"MENTION_EVERYONE\"\n | \"USE_EXTERNAL_EMOJIS\"\n | \"ADD_REACTIONS\"\n | \"USE_APPLICATION_COMMANDS\"\n | \"MANAGE_THREADS\"\n | \"CREATE_PUBLIC_THREADS\"\n | \"CREATE_PRIVATE_THREADS\"\n | \"USE_EXTERNAL_STICKERS\"\n | \"SEND_MESSAGES_IN_THREADS\"\n | \"SEND_VOICE_MESSAGES\"\n | \"CONNECT\"\n | \"SPEAK\"\n | \"MUTE_MEMBERS\"\n | \"DEAFEN_MEMBERS\"\n | \"MOVE_MEMBERS\"\n | \"USE_VAD\"\n | \"PRIORITY_SPEAKER\"\n | \"STREAM\"\n | \"USE_EMBEDDED_ACTIVITIES\"\n | \"USE_SOUNDBOARD\"\n | \"USE_EXTERNAL_SOUNDS\"\n | \"REQUEST_TO_SPEAK\"\n | \"MANAGE_EVENTS\"\n | \"CREATE_EVENTS\";\n\nexport type PermissionsBits = Record;\n\nexport interface Locale {\n name: string;\n value: string;\n localizedName: string;\n}\n\nexport interface LocaleInfo {\n code: string;\n enabled: boolean;\n name: string;\n englishName: string;\n postgresLang: string;\n}\n\nexport interface i18n {\n getAvailableLocales(): Locale[];\n getLanguages(): LocaleInfo[];\n getDefaultLocale(): string;\n getLocale(): string;\n getLocaleInfo(): LocaleInfo;\n setLocale(locale: string): void;\n\n loadPromise: Promise;\n\n Messages: Record;\n}\n\nexport interface Clipboard {\n copy(text: string): void;\n SUPPORTS_COPY: boolean;\n}\n\nexport interface NavigationRouter {\n back(): void;\n forward(): void;\n hasNavigated(): boolean;\n getHistory(): {\n action: string;\n length: 50;\n [key: string]: any;\n };\n transitionTo(path: string, ...args: unknown[]): void;\n transitionToGuild(guildId: string, ...args: unknown[]): void;\n replaceWith(...args: unknown[]): void;\n getLastRouteChangeSource(): any;\n getLastRouteChangeSourceLocationStack(): any;\n}\n\nexport interface IconUtils {\n getUserAvatarURL(user: User, canAnimate?: boolean, size?: number, format?: string): string;\n getDefaultAvatarURL(id: string, discriminator?: string): string;\n getUserBannerURL(data: { id: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined;\n getAvatarDecorationURL(dara: { avatarDecoration: string, size: number; canCanimate?: boolean; }): string | undefined;\n\n getGuildMemberAvatarURL(member: GuildMember, canAnimate?: string): string | null;\n getGuildMemberAvatarURLSimple(data: { guildId: string, userId: string, avatar: string, canAnimate?: boolean; size?: number; }): string;\n getGuildMemberBannerURL(data: { id: string, guildId: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined;\n\n getGuildIconURL(data: { id: string, icon?: string, size?: number, canAnimate?: boolean; }): string | undefined;\n getGuildBannerURL(guild: Guild, canAnimate?: boolean): string | null;\n\n getChannelIconURL(data: { id: string; icon?: string; applicationId?: string; size?: number; }): string | undefined;\n getEmojiURL(data: { id: string, animated: boolean, size: number, forcePNG?: boolean; }): string;\n\n hasAnimatedGuildIcon(guild: Guild): boolean;\n isAnimatedIconHash(hash: string): boolean;\n\n getGuildSplashURL: any;\n getGuildDiscoverySplashURL: any;\n getGuildHomeHeaderURL: any;\n getResourceChannelIconURL: any;\n getNewMemberActionIconURL: any;\n getGuildTemplateIconURL: any;\n getApplicationIconURL: any;\n getGameAssetURL: any;\n getVideoFilterAssetURL: any;\n\n getGuildMemberAvatarSource: any;\n getUserAvatarSource: any;\n getGuildSplashSource: any;\n getGuildDiscoverySplashSource: any;\n makeSource: any;\n getGameAssetSource: any;\n getGuildIconSource: any;\n getGuildTemplateIconSource: any;\n getGuildBannerSource: any;\n getGuildHomeHeaderSource: any;\n getChannelIconSource: any;\n getApplicationIconSource: any;\n getAnimatableSourceWithFallback: any;\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport type { Channel, User } from \"discord-types/general\";\n\n// eslint-disable-next-line path-alias/no-relative\nimport { _resolveReady, filters, findByCodeLazy, findByProps, findByPropsLazy, findLazy, proxyLazyWebpack, waitFor } from \"../webpack\";\nimport type * as t from \"./types/utils\";\n\nexport let FluxDispatcher: t.FluxDispatcher;\n\nwaitFor([\"dispatch\", \"subscribe\"], m => {\n FluxDispatcher = m;\n const cb = () => {\n m.unsubscribe(\"CONNECTION_OPEN\", cb);\n _resolveReady();\n };\n m.subscribe(\"CONNECTION_OPEN\", cb);\n});\n\nexport let ComponentDispatch;\nwaitFor([\"ComponentDispatch\", \"ComponentDispatcher\"], m => ComponentDispatch = m.ComponentDispatch);\n\n\nexport const RestAPI: t.RestAPI = proxyLazyWebpack(() => {\n const mod = findByProps(\"getAPIBaseURL\");\n return mod.HTTP ?? mod;\n});\nexport const moment: typeof import(\"moment\") = findByPropsLazy(\"parseTwoDigitYear\");\n\nexport const hljs: typeof import(\"highlight.js\") = findByPropsLazy(\"highlight\", \"registerLanguage\");\n\nexport const lodash: typeof import(\"lodash\") = findByPropsLazy(\"debounce\", \"cloneDeep\");\n\nexport const i18n: t.i18n = findLazy(m => m.Messages?.[\"en-US\"]);\n\nexport let SnowflakeUtils: t.SnowflakeUtils;\nwaitFor([\"fromTimestamp\", \"extractTimestamp\"], m => SnowflakeUtils = m);\n\nexport let Parser: t.Parser;\nwaitFor(\"parseTopic\", m => Parser = m);\nexport let Alerts: t.Alerts;\nwaitFor([\"show\", \"close\"], m => Alerts = m);\n\nconst ToastType = {\n MESSAGE: 0,\n SUCCESS: 1,\n FAILURE: 2,\n CUSTOM: 3\n};\nconst ToastPosition = {\n TOP: 0,\n BOTTOM: 1\n};\n\nexport const Toasts = {\n Type: ToastType,\n Position: ToastPosition,\n // what's less likely than getting 0 from Math.random()? Getting it twice in a row\n genId: () => (Math.random() || Math.random()).toString(36).slice(2),\n\n // hack to merge with the following interface, dunno if there's a better way\n ...{} as {\n show(data: {\n message: string,\n id: string,\n /**\n * Toasts.Type\n */\n type: number,\n options?: {\n /**\n * Toasts.Position\n */\n position?: number;\n component?: React.ReactNode,\n duration?: number;\n };\n }): void;\n pop(): void;\n }\n};\n\n// This is the same module but this is easier\nwaitFor(\"showToast\", m => {\n Toasts.show = m.showToast;\n Toasts.pop = m.popToast;\n});\n\n\n/**\n * Show a simple toast. If you need more options, use Toasts.show manually\n */\nexport function showToast(message: string, type = ToastType.MESSAGE) {\n Toasts.show({\n id: Toasts.genId(),\n message,\n type\n });\n}\n\nexport const UserUtils = findByPropsLazy(\"getUser\", \"fetchCurrentUser\") as { getUser: (id: string) => Promise; };\nexport const UploadHandler = findByPropsLazy(\"showUploadFileSizeExceededError\", \"promptToUpload\") as {\n promptToUpload: (files: File[], channel: Channel, draftType: Number) => void;\n};\n\nexport const ApplicationAssetUtils = findByPropsLazy(\"fetchAssetIds\", \"getAssetImage\") as {\n fetchAssetIds: (applicationId: string, e: string[]) => Promise;\n};\n\nexport const Clipboard: t.Clipboard = findByPropsLazy(\"SUPPORTS_COPY\", \"copy\");\n\nexport const NavigationRouter: t.NavigationRouter = findByPropsLazy(\"transitionTo\", \"replaceWith\", \"transitionToGuild\");\n\nexport let SettingsRouter: any;\nwaitFor([\"open\", \"saveAccountChanges\"], m => SettingsRouter = m);\n\nexport const { Permissions: PermissionsBits } = findLazy(m => typeof m.Permissions?.ADMINISTRATOR === \"bigint\") as { Permissions: t.PermissionsBits; };\n\nexport const zustandCreate: typeof import(\"zustand\").default = findByCodeLazy(\"will be removed in v4\");\n\nconst persistFilter = filters.byCode(\"[zustand persist middleware]\");\nexport const { persist: zustandPersist }: typeof import(\"zustand/middleware\") = findLazy(m => m.persist && persistFilter(m.persist));\n\nexport const MessageActions = findByPropsLazy(\"editMessage\", \"sendMessage\");\nexport const UserProfileActions = findByPropsLazy(\"openUserProfileModal\", \"closeUserProfileModal\");\nexport const InviteActions = findByPropsLazy(\"resolveInvite\");\n\nexport const IconUtils: t.IconUtils = findByPropsLazy(\"getGuildBannerURL\", \"getUserAvatarURL\");\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nexport * from \"./classes\";\nexport * from \"./components\";\nexport * from \"./menu\";\nexport * from \"./react\";\nexport * from \"./settingsStores\";\nexport * from \"./stores\";\nexport * as ComponentTypes from \"./types/components.d\";\nexport * as MenuTypes from \"./types/menu.d\";\nexport * as UtilTypes from \"./types/utils.d\";\nexport * from \"./utils\";\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nexport const WEBPACK_CHUNK = \"webpackChunkdiscord_app\";\nexport const REACT_GLOBAL = \"Vencord.Webpack.Common.React\";\nexport const SUPPORT_CHANNEL_ID = \"1026515880080842772\";\n\nexport interface Dev {\n name: string;\n id: bigint;\n badge?: boolean;\n}\n\n/**\n * If you made a plugin or substantial contribution, add yourself here.\n * This object is used for the plugin author list, as well as to add a contributor badge to your profile.\n * If you wish to stay fully anonymous, feel free to set ID to 0n.\n * If you are fine with attribution but don't want the badge, add badge: false\n */\nexport const Devs = /* #__PURE__*/ Object.freeze({\n Nobody: {\n name: \"Nobody\",\n id: 0n,\n },\n Ven: {\n name: \"Vendicated\",\n id: 343383572805058560n\n },\n Arjix: {\n name: \"ArjixWasTaken\",\n id: 674710789138939916n\n },\n Cyn: {\n name: \"Cynosphere\",\n id: 150745989836308480n\n },\n Trwy: {\n name: \"trey\",\n id: 354427199023218689n\n },\n Megu: {\n name: \"Megumin\",\n id: 545581357812678656n\n },\n botato: {\n name: \"botato\",\n id: 440990343899643943n\n },\n fawn: {\n name: \"fawn\",\n id: 336678828233588736n,\n },\n rushii: {\n name: \"rushii\",\n id: 295190422244950017n\n },\n Glitch: {\n name: \"Glitchy\",\n id: 269567451199569920n\n },\n Samu: {\n name: \"Samu\",\n id: 702973430449832038n,\n },\n Nyako: {\n name: \"nyako\",\n id: 118437263754395652n\n },\n MaiKokain: {\n name: \"Mai\",\n id: 722647978577363026n\n },\n echo: {\n name: \"ECHO\",\n id: 712639419785412668n\n },\n katlyn: {\n name: \"katlyn\",\n id: 250322741406859265n\n },\n nea: {\n name: \"nea\",\n id: 310702108997320705n,\n },\n Nuckyz: {\n name: \"Nuckyz\",\n id: 235834946571337729n\n },\n D3SOX: {\n name: \"D3SOX\",\n id: 201052085641281538n\n },\n Nickyux: {\n name: \"Nickyux\",\n id: 427146305651998721n\n },\n mantikafasi: {\n name: \"mantikafasi\",\n id: 287555395151593473n\n },\n Xinto: {\n name: \"Xinto\",\n id: 423915768191647755n\n },\n JacobTm: {\n name: \"Jacob.Tm\",\n id: 302872992097107991n\n },\n DustyAngel47: {\n name: \"DustyAngel47\",\n id: 714583473804935238n\n },\n BanTheNons: {\n name: \"BanTheNons\",\n id: 460478012794863637n\n },\n BigDuck: {\n name: \"BigDuck\",\n id: 1024588272623681609n\n },\n AverageReactEnjoyer: {\n name: \"Average React Enjoyer\",\n id: 1004904120056029256n\n },\n adryd: {\n name: \"adryd\",\n id: 0n\n },\n Tyman: {\n name: \"Tyman\",\n id: 487443883127472129n\n },\n afn: {\n name: \"afn\",\n id: 420043923822608384n\n },\n KraXen72: {\n name: \"KraXen72\",\n id: 379304073515499530n\n },\n kemo: {\n name: \"kemo\",\n id: 299693897859465228n\n },\n dzshn: {\n name: \"dzshn\",\n id: 310449948011528192n\n },\n Ducko: {\n name: \"Ducko\",\n id: 506482395269169153n\n },\n jewdev: {\n name: \"jewdev\",\n id: 222369866529636353n\n },\n Luna: {\n name: \"Luny\",\n id: 821472922140803112n\n },\n Vap: {\n name: \"Vap0r1ze\",\n id: 454072114492866560n\n },\n KingFish: {\n name: \"King Fish\",\n id: 499400512559382538n\n },\n Commandtechno: {\n name: \"Commandtechno\",\n id: 296776625432035328n,\n },\n TheSun: {\n name: \"ActuallyTheSun\",\n id: 406028027768733696n\n },\n axyie: {\n name: \"'ax\",\n id: 273562710745284628n,\n },\n pointy: {\n name: \"pointy\",\n id: 99914384989519872n\n },\n SammCheese: {\n name: \"Samm-Cheese\",\n id: 372148345894076416n\n },\n zt: {\n name: \"zt\",\n id: 289556910426816513n\n },\n captain: {\n name: \"Captain\",\n id: 347366054806159360n\n },\n nick: {\n name: \"nick\",\n id: 347884694408265729n,\n badge: false\n },\n whqwert: {\n name: \"whqwert\",\n id: 586239091520176128n\n },\n lewisakura: {\n name: \"lewisakura\",\n id: 96269247411400704n\n },\n RuiNtD: {\n name: \"RuiNtD\",\n id: 157917665162297344n\n },\n hunt: {\n name: \"hunt-g\",\n id: 222800179697287168n\n },\n cloudburst: {\n name: \"cloudburst\",\n id: 892128204150685769n\n },\n Aria: {\n name: \"Syncxv\",\n id: 549244932213309442n,\n },\n TheKodeToad: {\n name: \"TheKodeToad\",\n id: 706152404072267788n\n },\n LordElias: {\n name: \"LordElias\",\n id: 319460781567639554n\n },\n juby: {\n name: \"Juby210\",\n id: 324622488644616195n\n },\n Alyxia: {\n name: \"Alyxia Sother\",\n id: 952185386350829688n\n },\n Remty: {\n name: \"Remty\",\n id: 335055032204656642n\n },\n skyevg: {\n name: \"skyevg\",\n id: 1090310844283363348n\n },\n Dziurwa: {\n name: \"Dziurwa\",\n id: 1001086404203389018n\n },\n F53: {\n name: \"F53\",\n id: 280411966126948353n\n },\n AutumnVN: {\n name: \"AutumnVN\",\n id: 393694671383166998n\n },\n pylix: {\n name: \"pylix\",\n id: 492949202121261067n\n },\n Tyler: {\n name: \"\\\\\\\\GGTyler\\\\\\\\\",\n id: 143117463788191746n\n },\n RyanCaoDev: {\n name: \"RyanCaoDev\",\n id: 952235800110694471n,\n },\n FieryFlames: {\n name: \"Fiery\",\n id: 890228870559698955n\n },\n KannaDev: {\n name: \"Kanna\",\n id: 317728561106518019n\n },\n carince: {\n name: \"carince\",\n id: 818323528755314698n\n },\n PandaNinjas: {\n name: \"PandaNinjas\",\n id: 455128749071925248n\n },\n CatNoir: {\n name: \"CatNoir\",\n id: 260371016348336128n\n },\n outfoxxed: {\n name: \"outfoxxed\",\n id: 837425748435796060n\n },\n UwUDev: {\n name: \"UwU\",\n id: 691413039156690994n,\n },\n amia: {\n name: \"amia\",\n id: 142007603549962240n\n },\n phil: {\n name: \"phil\",\n id: 305288513941667851n\n },\n ImLvna: {\n name: \"Luna <3\",\n id: 799319081723232267n\n },\n rad: {\n name: \"rad\",\n id: 610945092504780823n\n },\n AndrewDLO: {\n name: \"Andrew-DLO\",\n id: 434135504792059917n\n },\n HypedDomi: {\n name: \"HypedDomi\",\n id: 354191516979429376n\n },\n Rini: {\n name: \"Rini\",\n id: 1079479184478441643n\n },\n castdrian: {\n name: \"castdrian\",\n id: 224617799434108928n\n },\n Arrow: {\n name: \"arrow\",\n id: 958158495302176778n\n },\n bb010g: {\n name: \"bb010g\",\n id: 72791153467990016n,\n },\n Dolfies: {\n name: \"Dolfies\",\n id: 852892297661906993n,\n },\n RuukuLada: {\n name: \"RuukuLada\",\n id: 119705748346241027n,\n },\n blahajZip: {\n name: \"blahaj.zip\",\n id: 683954422241427471n,\n },\n archeruwu: {\n name: \"archer_uwu\",\n id: 160068695383736320n\n },\n ProffDea: {\n name: \"ProffDea\",\n id: 609329952180928513n\n },\n ant0n: {\n name: \"ant0n\",\n id: 145224646868860928n\n },\n philipbry: {\n name: \"philipbry\",\n id: 554994003318276106n\n },\n Korbo: {\n name: \"Korbo\",\n id: 455856406420258827n\n },\n maisymoe: {\n name: \"maisy\",\n id: 257109471589957632n,\n },\n Mopi: {\n name: \"Mopi\",\n id: 1022189106614243350n\n },\n Grzesiek11: {\n name: \"Grzesiek11\",\n id: 368475654662127616n,\n },\n Samwich: {\n name: \"Samwich\",\n id: 976176454511509554n,\n },\n coolelectronics: {\n name: \"coolelectronics\",\n id: 696392247205298207n,\n },\n Av32000: {\n name: \"Av32000\",\n id: 593436735380127770n,\n },\n Kyuuhachi: {\n name: \"Kyuuhachi\",\n id: 236588665420251137n,\n },\n Elvyra: {\n name: \"Elvyra\",\n id: 708275751816003615n,\n },\n Inbestigator: {\n name: \"Inbestigator\",\n id: 761777382041714690n\n },\n newwares: {\n name: \"newwares\",\n id: 421405303951851520n\n }\n} satisfies Record);\n\n// iife so #__PURE__ works correctly\nexport const DevsById = /* #__PURE__*/ (() =>\n Object.freeze(Object.fromEntries(\n Object.entries(Devs)\n .filter(d => d[1].id !== 0n)\n .map(([_, v]) => [v.id, v] as const)\n ))\n)() as Record;\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Clipboard, Toasts } from \"@webpack/common\";\n\nimport { DevsById } from \"./constants\";\n\n/**\n * Recursively merges defaults into an object and returns the same object\n * @param obj Object\n * @param defaults Defaults\n * @returns obj\n */\nexport function mergeDefaults(obj: T, defaults: T): T {\n for (const key in defaults) {\n const v = defaults[key];\n if (typeof v === \"object\" && !Array.isArray(v)) {\n obj[key] ??= {} as any;\n mergeDefaults(obj[key], v);\n } else {\n obj[key] ??= v;\n }\n }\n return obj;\n}\n\n/**\n * Calls .join(\" \") on the arguments\n * classes(\"one\", \"two\") => \"one two\"\n */\nexport function classes(...classes: Array) {\n return classes.filter(Boolean).join(\" \");\n}\n\n/**\n * Returns a promise that resolves after the specified amount of time\n */\nexport function sleep(ms: number): Promise {\n return new Promise(r => setTimeout(r, ms));\n}\n\nexport function copyWithToast(text: string, toastMessage = \"Copied to clipboard!\") {\n if (Clipboard.SUPPORTS_COPY) {\n Clipboard.copy(text);\n } else {\n toastMessage = \"Your browser does not support copying to clipboard\";\n }\n Toasts.show({\n message: toastMessage,\n id: Toasts.genId(),\n type: Toasts.Type.SUCCESS\n });\n}\n\n/**\n * Check if obj is a true object: of type \"object\" and not null or array\n */\nexport function isObject(obj: unknown): obj is object {\n return typeof obj === \"object\" && obj !== null && !Array.isArray(obj);\n}\n\n/**\n * Check if an object is empty or in other words has no own properties\n */\nexport function isObjectEmpty(obj: object) {\n for (const k in obj)\n if (Object.hasOwn(obj, k)) return false;\n\n return true;\n}\n\n/**\n * Returns null if value is not a URL, otherwise return URL object.\n * Avoids having to wrap url checks in a try/catch\n */\nexport function parseUrl(urlString: string): URL | null {\n try {\n return new URL(urlString);\n } catch {\n return null;\n }\n}\n\n/**\n * Checks whether an element is on screen\n */\nexport const checkIntersecting = (el: Element) => {\n const elementBox = el.getBoundingClientRect();\n const documentHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);\n return !(elementBox.bottom < 0 || elementBox.top - documentHeight >= 0);\n};\n\nexport function identity(value: T): T {\n return value;\n}\n\n// https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#mobile_tablet_or_desktop\n// \"In summary, we recommend looking for the string Mobi anywhere in the User Agent to detect a mobile device.\"\nexport const isMobile = navigator.userAgent.includes(\"Mobi\");\n\nexport const isPluginDev = (id: string) => Object.hasOwn(DevsById, id);\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { React, useEffect, useMemo, useReducer, useState } from \"@webpack/common\";\n\nimport { checkIntersecting } from \"./misc\";\n\nexport * from \"./lazyReact\";\n\nexport const NoopComponent = () => null;\n\n/**\n * Check if an element is on screen\n * @param intersectOnly If `true`, will only update the state when the element comes into view\n * @returns [refCallback, isIntersecting]\n */\nexport const useIntersection = (intersectOnly = false): [\n refCallback: React.RefCallback,\n isIntersecting: boolean,\n] => {\n const observerRef = React.useRef(null);\n const [isIntersecting, setIntersecting] = useState(false);\n\n const refCallback = (element: Element | null) => {\n observerRef.current?.disconnect();\n observerRef.current = null;\n\n if (!element) return;\n\n if (checkIntersecting(element)) {\n setIntersecting(true);\n if (intersectOnly) return;\n }\n\n observerRef.current = new IntersectionObserver(entries => {\n for (const entry of entries) {\n if (entry.target !== element) continue;\n if (entry.isIntersecting && intersectOnly) {\n setIntersecting(true);\n observerRef.current?.disconnect();\n observerRef.current = null;\n } else {\n setIntersecting(entry.isIntersecting);\n }\n }\n });\n observerRef.current.observe(element);\n };\n\n return [refCallback, isIntersecting];\n};\n\ntype AwaiterRes = [T, any, boolean];\ninterface AwaiterOpts {\n fallbackValue: T;\n deps?: unknown[];\n onError?(e: any): void;\n onSuccess?(value: T): void;\n}\n/**\n * Await a promise\n * @param factory Factory\n * @param fallbackValue The fallback value that will be used until the promise resolved\n * @returns [value, error, isPending]\n */\nexport function useAwaiter(factory: () => Promise): AwaiterRes;\nexport function useAwaiter(factory: () => Promise, providedOpts: AwaiterOpts): AwaiterRes;\nexport function useAwaiter(factory: () => Promise, providedOpts?: AwaiterOpts): AwaiterRes {\n const opts: Required> = Object.assign({\n fallbackValue: null,\n deps: [],\n onError: null,\n }, providedOpts);\n const [state, setState] = useState({\n value: opts.fallbackValue,\n error: null,\n pending: true\n });\n\n useEffect(() => {\n let isAlive = true;\n if (!state.pending) setState({ ...state, pending: true });\n\n factory()\n .then(value => {\n if (!isAlive) return;\n setState({ value, error: null, pending: false });\n opts.onSuccess?.(value);\n })\n .catch(error => {\n if (!isAlive) return;\n setState({ value: null, error, pending: false });\n opts.onError?.(error);\n });\n\n return () => void (isAlive = false);\n }, opts.deps);\n\n return [state.value, state.error, state.pending];\n}\n\n/**\n * Returns a function that can be used to force rerender react components\n */\nexport function useForceUpdater(): () => void;\nexport function useForceUpdater(withDep: true): [unknown, () => void];\nexport function useForceUpdater(withDep?: true) {\n const r = useReducer(x => x + 1, 0);\n return withDep ? r : r[1];\n}\n\ninterface TimerOpts {\n interval?: number;\n deps?: unknown[];\n}\n\nexport function useTimer({ interval = 1000, deps = [] }: TimerOpts) {\n const [time, setTime] = useState(0);\n const start = useMemo(() => Date.now(), deps);\n\n useEffect(() => {\n const intervalId = setInterval(() => setTime(Date.now() - start), interval);\n\n return () => {\n setTime(0);\n clearInterval(intervalId);\n };\n }, deps);\n\n return time;\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport \"./ErrorCard.css\";\n\nimport { classes } from \"@utils/misc\";\nimport type { HTMLProps } from \"react\";\n\nexport function ErrorCard(props: React.PropsWithChildren>) {\n return (\n
\n {props.children}\n
\n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Logger } from \"@utils/Logger\";\nimport { Margins } from \"@utils/margins\";\nimport { LazyComponent } from \"@utils/react\";\nimport { React } from \"@webpack/common\";\n\nimport { ErrorCard } from \"./ErrorCard\";\n\ninterface Props {\n /** Render nothing if an error occurs */\n noop?: boolean;\n /** Fallback component to render if an error occurs */\n fallback?: React.ComponentType>;\n /** called when an error occurs. The props property is only available if using .wrap */\n onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;\n /** Custom error message */\n message?: string;\n\n /** The props passed to the wrapped component. Only used by wrap */\n wrappedProps?: T;\n}\n\nconst color = \"#e78284\";\n\nconst logger = new Logger(\"React ErrorBoundary\", color);\n\nconst NO_ERROR = {};\n\n// We might want to import this in a place where React isn't ready yet.\n// Thus, wrap in a LazyComponent\nconst ErrorBoundary = LazyComponent(() => {\n return class ErrorBoundary extends React.PureComponent> {\n state = {\n error: NO_ERROR as any,\n stack: \"\",\n message: \"\"\n };\n\n static getDerivedStateFromError(error: any) {\n let stack = error?.stack ?? \"\";\n let message = error?.message || String(error);\n\n if (error instanceof Error && stack) {\n const eolIdx = stack.indexOf(\"\\n\");\n if (eolIdx !== -1) {\n message = stack.slice(0, eolIdx);\n stack = stack.slice(eolIdx + 1).replace(/https:\\/\\/\\S+\\/assets\\//g, \"\");\n }\n }\n\n return { error, stack, message };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });\n logger.error(\"A component threw an Error\\n\", error);\n logger.error(\"Component Stack\", errorInfo.componentStack);\n }\n\n render() {\n if (this.state.error === NO_ERROR) return this.props.children;\n\n if (this.props.noop) return null;\n\n if (this.props.fallback)\n return ;\n\n const msg = this.props.message || \"An error occurred while rendering this Component. More info can be found below and in your console.\";\n\n return (\n \n

Oh no!

\n

{msg}

\n \n {this.state.message}\n {!!this.state.stack && (\n
\n                                {this.state.stack}\n                            
\n )}\n
\n
\n );\n }\n };\n}) as\n React.ComponentType> & {\n wrap(Component: React.ComponentType, errorBoundaryProps?: Omit, \"wrappedProps\">): React.FunctionComponent;\n };\n\nErrorBoundary.wrap = (Component, errorBoundaryProps) => props => (\n \n \n \n);\n\nexport default ErrorBoundary;\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nexport function Heart() {\n return (\n \n \n \n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Button } from \"@webpack/common\";\n\nimport { Heart } from \"./Heart\";\n\nexport default function DonateButton(props: any) {\n return (\n VencordNative.native.openExternal(\"https://github.com/sponsors/Vendicated\")}\n >\n \n Donate\n \n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport type { React } from \"@webpack/common\";\n\nexport function Flex(props: React.PropsWithChildren<{\n flexDirection?: React.CSSProperties[\"flexDirection\"];\n style?: React.CSSProperties;\n className?: string;\n} & React.HTMLProps>) {\n props.style ??= {};\n props.style.display = \"flex\";\n // TODO(ven): Remove me, what was I thinking??\n props.style.gap ??= \"1em\";\n props.style.flexDirection ||= props.flexDirection;\n delete props.flexDirection;\n return (\n
\n {props.children}\n
\n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { findByPropsLazy, findExportedComponentLazy } from \"@webpack\";\nimport type { ComponentType, PropsWithChildren, ReactNode, Ref } from \"react\";\n\nimport { LazyComponent } from \"./react\";\n\nexport const enum ModalSize {\n SMALL = \"small\",\n MEDIUM = \"medium\",\n LARGE = \"large\",\n DYNAMIC = \"dynamic\",\n}\n\nconst enum ModalTransitionState {\n ENTERING,\n ENTERED,\n EXITING,\n EXITED,\n HIDDEN,\n}\n\nexport interface ModalProps {\n transitionState: ModalTransitionState;\n onClose(): Promise;\n}\n\nexport interface ModalOptions {\n modalKey?: string;\n onCloseRequest?: (() => void);\n onCloseCallback?: (() => void);\n}\n\ntype RenderFunction = (props: ModalProps) => ReactNode;\n\nexport const Modals = findByPropsLazy(\"ModalRoot\", \"ModalCloseButton\") as {\n ModalRoot: ComponentType>;\n ModalHeader: ComponentType>;\n /** This also accepts Scroller props but good luck with that */\n ModalContent: ComponentType;\n [prop: string]: any;\n }>>;\n ModalFooter: ComponentType>;\n ModalCloseButton: ComponentType<{\n focusProps?: any;\n onClick(): void;\n withCircleBackground?: boolean;\n hideOnFullscreen?: boolean;\n className?: string;\n }>;\n};\n\nexport type ImageModal = ComponentType<{\n className?: string;\n src: string;\n placeholder: string;\n original: string;\n width?: number;\n height?: number;\n animated?: boolean;\n responsive?: boolean;\n renderLinkComponent(props: any): ReactNode;\n maxWidth?: number;\n maxHeight?: number;\n shouldAnimate?: boolean;\n onClose?(): void;\n shouldHideMediaOptions?: boolean;\n}>;\n\nexport const ImageModal = findExportedComponentLazy(\"ImageModal\") as ImageModal;\n\nexport const ModalRoot = LazyComponent(() => Modals.ModalRoot);\nexport const ModalHeader = LazyComponent(() => Modals.ModalHeader);\nexport const ModalContent = LazyComponent(() => Modals.ModalContent);\nexport const ModalFooter = LazyComponent(() => Modals.ModalFooter);\nexport const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);\n\nconst ModalAPI = findByPropsLazy(\"openModalLazy\");\n\n/**\n * Wait for the render promise to resolve, then open a modal with it.\n * This is equivalent to render().then(openModal)\n * You should use the Modal components exported by this file\n */\nexport function openModalLazy(render: () => Promise, options?: ModalOptions & { contextKey?: string; }): Promise {\n return ModalAPI.openModalLazy(render, options);\n}\n\n/**\n * Open a Modal with the given render function.\n * You should use the Modal components exported by this file\n */\nexport function openModal(render: RenderFunction, options?: ModalOptions, contextKey?: string): string {\n return ModalAPI.openModal(render, options, contextKey);\n}\n\n/**\n * Close a modal by its key\n */\nexport function closeModal(modalKey: string, contextKey?: string): void {\n return ModalAPI.closeModal(modalKey, contextKey);\n}\n\n/**\n * Close all open modals\n */\nexport function closeAllModals(): void {\n return ModalAPI.closeAllModals();\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Command } from \"@api/Commands\";\nimport { NavContextMenuPatchCallback } from \"@api/ContextMenu\";\nimport { FluxEvents } from \"@webpack/types\";\nimport { Promisable } from \"type-fest\";\n\n// exists to export default definePlugin({...})\nexport default function definePlugin

(p: P & Record) {\n return p;\n}\n\nexport type ReplaceFn = (match: string, ...groups: string[]) => string;\n\nexport interface PatchReplacement {\n match: string | RegExp;\n replace: string | ReplaceFn;\n predicate?(): boolean;\n}\n\nexport interface Patch {\n plugin: string;\n find: string;\n replacement: PatchReplacement | PatchReplacement[];\n /** Whether this patch should apply to multiple modules */\n all?: boolean;\n /** Do not warn if this patch did no changes */\n noWarn?: boolean;\n /** Only apply this set of replacements if all of them succeed. Use this if your replacements depend on each other */\n group?: boolean;\n predicate?(): boolean;\n}\n\nexport interface PluginAuthor {\n name: string;\n id: BigInt;\n}\n\nexport interface Plugin extends PluginDef {\n patches?: Patch[];\n started: boolean;\n isDependency?: boolean;\n}\n\nexport interface PluginDef {\n name: string;\n description: string;\n authors: PluginAuthor[];\n start?(): void;\n stop?(): void;\n patches?: Omit[];\n /**\n * List of commands. If you specify these, you must add CommandsAPI to dependencies\n */\n commands?: Command[];\n /**\n * A list of other plugins that your plugin depends on.\n * These will automatically be enabled and loaded before your plugin\n * Common examples are CommandsAPI, MessageEventsAPI...\n */\n dependencies?: string[],\n /**\n * Whether this plugin is required and forcefully enabled\n */\n required?: boolean;\n /**\n * Whether this plugin should be enabled by default, but can be disabled\n */\n enabledByDefault?: boolean;\n /**\n * When to call the start() method\n * @default StartAt.WebpackReady\n */\n startAt?: StartAt,\n /**\n * Optionally provide settings that the user can configure in the Plugins tab of settings.\n * @deprecated Use `settings` instead\n */\n // TODO: Remove when everything is migrated to `settings`\n options?: Record;\n /**\n * Optionally provide settings that the user can configure in the Plugins tab of settings.\n */\n settings?: DefinedSettings;\n /**\n * Check that this returns true before allowing a save to complete.\n * If a string is returned, show the error to the user.\n */\n beforeSave?(options: Record): Promisable;\n /**\n * Allows you to specify a custom Component that will be rendered in your\n * plugin's settings page\n */\n settingsAboutComponent?: React.ComponentType<{\n tempSettings?: Record;\n }>;\n /**\n * Allows you to subscribe to Flux events\n */\n flux?: {\n [E in FluxEvents]?: (event: any) => void;\n };\n /**\n * Allows you to manipulate context menus\n */\n contextMenus?: Record;\n /**\n * Allows you to add custom actions to the Vencord Toolbox.\n * The key will be used as text for the button\n */\n toolboxActions?: Record void>;\n\n tags?: string[];\n}\n\nexport const enum StartAt {\n /** Right away, as soon as Vencord initialised */\n Init = \"Init\",\n /** On the DOMContentLoaded event, so once the document is ready */\n DOMContentLoaded = \"DOMContentLoaded\",\n /** Once Discord's core webpack modules have finished loading, so as soon as things like react and flux are available */\n WebpackReady = \"WebpackReady\"\n}\n\nexport const enum OptionType {\n STRING,\n NUMBER,\n BIGINT,\n BOOLEAN,\n SELECT,\n SLIDER,\n COMPONENT,\n}\n\nexport type SettingsDefinition = Record;\nexport type SettingsChecks = {\n [K in keyof D]?: D[K] extends PluginSettingComponentDef ? IsDisabled> :\n (IsDisabled> & IsValid, DefinedSettings>);\n};\n\nexport type PluginSettingDef = (\n | PluginSettingStringDef\n | PluginSettingNumberDef\n | PluginSettingBooleanDef\n | PluginSettingSelectDef\n | PluginSettingSliderDef\n | PluginSettingComponentDef\n | PluginSettingBigIntDef\n) & PluginSettingCommon;\n\nexport interface PluginSettingCommon {\n description: string;\n placeholder?: string;\n onChange?(newValue: any): void;\n /**\n * Whether changing this setting requires a restart\n */\n restartNeeded?: boolean;\n componentProps?: Record;\n /**\n * Hide this setting from the settings UI\n */\n hidden?: boolean;\n /**\n * Set this if the setting only works on Browser or Desktop, not both\n */\n target?: \"WEB\" | \"DESKTOP\" | \"BOTH\";\n}\ninterface IsDisabled {\n /**\n * Checks if this setting should be disabled\n */\n disabled?(this: D): boolean;\n}\ninterface IsValid {\n /**\n * Prevents the user from saving settings if this is false or a string\n */\n isValid?(this: D, value: T): boolean | string;\n}\n\nexport interface PluginSettingStringDef {\n type: OptionType.STRING;\n default?: string;\n}\nexport interface PluginSettingNumberDef {\n type: OptionType.NUMBER;\n default?: number;\n}\nexport interface PluginSettingBigIntDef {\n type: OptionType.BIGINT;\n default?: BigInt;\n}\nexport interface PluginSettingBooleanDef {\n type: OptionType.BOOLEAN;\n default?: boolean;\n}\n\nexport interface PluginSettingSelectDef {\n type: OptionType.SELECT;\n options: readonly PluginSettingSelectOption[];\n}\nexport interface PluginSettingSelectOption {\n label: string;\n value: string | number | boolean;\n default?: boolean;\n}\n\nexport interface PluginSettingSliderDef {\n type: OptionType.SLIDER;\n /**\n * All the possible values in the slider. Needs at least two values.\n */\n markers: number[];\n /**\n * Default value to use\n */\n default: number;\n /**\n * If false, allow users to select values in-between your markers.\n */\n stickToMarkers?: boolean;\n}\n\ninterface IPluginOptionComponentProps {\n /**\n * Run this when the value changes.\n *\n * NOTE: The user will still need to click save to apply these changes.\n */\n setValue(newValue: any): void;\n /**\n * Set to true to prevent the user from saving.\n *\n * NOTE: This will not show the error to the user. It will only stop them saving.\n * Make sure to show the error in your component.\n */\n setError(error: boolean): void;\n /**\n * The options object\n */\n option: PluginSettingComponentDef;\n}\n\nexport interface PluginSettingComponentDef {\n type: OptionType.COMPONENT;\n component: (props: IPluginOptionComponentProps) => JSX.Element;\n}\n\n/** Maps a `PluginSettingDef` to its value type */\ntype PluginSettingType = O extends PluginSettingStringDef ? string :\n O extends PluginSettingNumberDef ? number :\n O extends PluginSettingBigIntDef ? BigInt :\n O extends PluginSettingBooleanDef ? boolean :\n O extends PluginSettingSelectDef ? O[\"options\"][number][\"value\"] :\n O extends PluginSettingSliderDef ? number :\n O extends PluginSettingComponentDef ? any :\n never;\ntype PluginSettingDefaultType = O extends PluginSettingSelectDef ? (\n O[\"options\"] extends { default?: boolean; }[] ? O[\"options\"][number][\"value\"] : undefined\n) : O extends { default: infer T; } ? T : undefined;\n\ntype SettingsStore = {\n [K in keyof D]: PluginSettingType | PluginSettingDefaultType;\n};\n\n/** An instance of defined plugin settings */\nexport interface DefinedSettings<\n Def extends SettingsDefinition = SettingsDefinition,\n Checks extends SettingsChecks = {},\n PrivateSettings extends object = {}\n> {\n /** Shorthand for `Vencord.Settings.plugins.PluginName`, but with typings */\n store: SettingsStore & PrivateSettings;\n /**\n * React hook for getting the settings for this plugin\n * @param filter optional filter to avoid rerenders for irrelevent settings\n */\n use>(filter?: F[]): Pick & PrivateSettings, F>;\n /** Definitions of each setting */\n def: Def;\n /** Setting methods with return values that could rely on other settings */\n checks: Checks;\n /**\n * Name of the plugin these settings belong to,\n * will be an empty string until plugin is initialized\n */\n pluginName: string;\n\n withPrivateSettings(): DefinedSettings;\n}\n\nexport type PartialExcept = Partial & Required>;\n\nexport type IpcRes = { ok: true; value: V; } | { ok: false, error: any; };\n\n/* -------------------------------------------- */\n/* Legacy Options Types */\n/* -------------------------------------------- */\n\nexport type PluginOptionBase = PluginSettingCommon & IsDisabled;\nexport type PluginOptionsItem =\n | PluginOptionString\n | PluginOptionNumber\n | PluginOptionBoolean\n | PluginOptionSelect\n | PluginOptionSlider\n | PluginOptionComponent;\nexport type PluginOptionString = PluginSettingStringDef & PluginSettingCommon & IsDisabled & IsValid;\nexport type PluginOptionNumber = (PluginSettingNumberDef | PluginSettingBigIntDef) & PluginSettingCommon & IsDisabled & IsValid;\nexport type PluginOptionBoolean = PluginSettingBooleanDef & PluginSettingCommon & IsDisabled & IsValid;\nexport type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid;\nexport type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid;\nexport type PluginOptionComponent = PluginSettingComponentDef & PluginSettingCommon;\n\nexport type PluginNative any>> = {\n [key in keyof PluginExports]:\n PluginExports[key] extends (event: Electron.IpcMainInvokeEvent, ...args: infer Args) => infer Return\n ? (...args: Args) => Return extends Promise ? Return : Promise\n : never;\n};\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { BadgePosition, BadgeUserArgs, ProfileBadge } from \"@api/Badges\";\nimport DonateButton from \"@components/DonateButton\";\nimport ErrorBoundary from \"@components/ErrorBoundary\";\nimport { Flex } from \"@components/Flex\";\nimport { Heart } from \"@components/Heart\";\nimport { Devs } from \"@utils/constants\";\nimport { Margins } from \"@utils/margins\";\nimport { isPluginDev } from \"@utils/misc\";\nimport { closeModal, Modals, openModal } from \"@utils/modal\";\nimport definePlugin from \"@utils/types\";\nimport { Forms, Toasts } from \"@webpack/common\";\n\nconst CONTRIBUTOR_BADGE = \"https://vencord.dev/assets/favicon.png\";\n\nconst ContributorBadge: ProfileBadge = {\n description: \"Vencord Contributor\",\n image: CONTRIBUTOR_BADGE,\n position: BadgePosition.START,\n props: {\n style: {\n borderRadius: \"50%\",\n transform: \"scale(0.9)\" // The image is a bit too big compared to default badges\n }\n },\n shouldShow: ({ user }) => isPluginDev(user.id),\n link: \"https://github.com/Vendicated/Vencord\"\n};\n\nlet DonorBadges = {} as Record>>;\n\nasync function loadBadges(noCache = false) {\n DonorBadges = {};\n\n const init = {} as RequestInit;\n if (noCache)\n init.cache = \"no-cache\";\n\n DonorBadges = await fetch(\"https://badges.vencord.dev/badges.json\", init)\n .then(r => r.json());\n}\n\nexport default definePlugin({\n name: \"BadgeAPI\",\n description: \"API to add badges to users.\",\n authors: [Devs.Megu, Devs.Ven, Devs.TheSun],\n required: true,\n patches: [\n /* Patch the badge list component on user profiles */\n {\n find: \"Messages.PROFILE_USER_BADGES,role:\",\n replacement: [\n {\n match: /&&(\\i)\\.push\\(\\{id:\"premium\".+?\\}\\);/,\n replace: \"$&$1.unshift(...Vencord.Api.Badges._getBadges(arguments[0]));\",\n },\n {\n // alt: \"\", aria-hidden: false, src: originalSrc\n match: /alt:\" \",\"aria-hidden\":!0,src:(?=(\\i)\\.src)/,\n // ...badge.props, ..., src: badge.image ?? ...\n replace: \"...$1.props,$& $1.image??\"\n },\n // replace their component with ours if applicable\n {\n match: /(?<=text:(\\i)\\.description,spacing:12,)children:/,\n replace: \"children:$1.component ? () => $self.renderBadgeComponent($1) :\"\n },\n // conditionally override their onClick with badge.onClick if it exists\n {\n match: /href:(\\i)\\.link/,\n replace: \"...($1.onClick && { onClick: $1.onClick }),$&\"\n }\n ]\n }\n ],\n\n toolboxActions: {\n async \"Refetch Badges\"() {\n await loadBadges(true);\n Toasts.show({\n id: Toasts.genId(),\n message: \"Successfully refetched badges!\",\n type: Toasts.Type.SUCCESS\n });\n }\n },\n\n async start() {\n Vencord.Api.Badges.addBadge(ContributorBadge);\n await loadBadges();\n },\n\n renderBadgeComponent: ErrorBoundary.wrap((badge: ProfileBadge & BadgeUserArgs) => {\n const Component = badge.component!;\n return ;\n }, { noop: true }),\n\n\n getDonorBadges(userId: string) {\n return DonorBadges[userId]?.map(badge => ({\n image: badge.badge,\n description: badge.tooltip,\n position: BadgePosition.START,\n props: {\n style: {\n borderRadius: \"50%\",\n transform: \"scale(0.9)\" // The image is a bit too big compared to default badges\n }\n },\n onClick() {\n const modalKey = openModal(props => (\n {\n closeModal(modalKey);\n VencordNative.native.openExternal(\"https://github.com/sponsors/Vendicated\");\n }}>\n \n \n \n \n \n Vencord Donor\n \n \n \n \n \n \n \n \n

\n \n This Badge is a special perk for Vencord Donors\n \n \n Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!\n \n
\n \n \n \n \n \n \n \n \n ));\n },\n }));\n }\n});\n", "/*\n * Vencord, a Discord client mod\n * Copyright (c) 2024 Vendicated and contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"ChatInputButtonAPI\",\n description: \"API to add buttons to the chat input\",\n authors: [Devs.Ven],\n\n patches: [{\n find: 'location:\"ChannelTextAreaButtons\"',\n replacement: {\n match: /if\\(!\\i\\.isMobile\\)\\{(?=.+?&&(\\i)\\.push\\(.{0,50}\"gift\")/,\n replace: \"$&Vencord.Api.ChatButtons._injectButtons($1,arguments[0]);\"\n }\n }]\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"CommandsAPI\",\n authors: [Devs.Arjix],\n description: \"Api required by anything that uses commands\",\n patches: [\n // obtain BUILT_IN_COMMANDS instance\n {\n find: ',\"tenor\"',\n replacement: [\n {\n // Matches BUILT_IN_COMMANDS. This is not exported so this is\n // the only way. _init() just returns the same object to make the\n // patch simpler\n\n // textCommands = builtInCommands.filter(...)\n match: /(?<=\\w=)(\\w)(\\.filter\\(.{0,60}tenor)/,\n replace: \"Vencord.Api.Commands._init($1)$2\",\n }\n ],\n },\n // command error handling\n {\n find: \"Unexpected value for option\",\n replacement: {\n // return [2, cmd.execute(args, ctx)]\n match: /,(\\i)\\.execute\\((\\i),(\\i)\\)/,\n replace: (_, cmd, args, ctx) => `,Vencord.Api.Commands._handleCommand(${cmd}, ${args}, ${ctx})`\n }\n },\n // Show plugin name instead of \"Built-In\"\n {\n find: \".source,children\",\n replacement: {\n // ...children: p?.name\n match: /(?<=:(.{1,3})\\.displayDescription\\}.{0,200}\\.source,children:)[^}]+/,\n replace: \"$1.plugin||($&)\"\n }\n }\n ],\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"ContextMenuAPI\",\n description: \"API for adding/removing items to/from context menus.\",\n authors: [Devs.Nuckyz, Devs.Ven, Devs.Kyuuhachi],\n required: true,\n\n patches: [\n {\n find: \"\u266B (\u3064\uFF61\u25D5\u203F\u203F\u25D5\uFF61)\u3064 \u266A\",\n replacement: {\n match: /(?=let{navId:)(?<=function \\i\\((\\i)\\).+?)/,\n replace: \"$1=Vencord.Api.ContextMenu._usePatchContextMenu($1);\"\n }\n },\n {\n find: \".Menu,{\",\n all: true,\n replacement: {\n match: /Menu,{(?<=\\.jsxs?\\)\\(\\i\\.Menu,{)/g,\n replace: \"$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[],\"\n }\n }\n ]\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"MemberListDecoratorsAPI\",\n description: \"API to add decorators to member list (both in servers and DMs)\",\n authors: [Devs.TheSun, Devs.Ven],\n patches: [\n {\n find: \".lostPermission)\",\n replacement: [\n {\n match: /let\\{[^}]*lostPermissionTooltipText:\\i[^}]*\\}=(\\i),/,\n replace: \"$&vencordProps=$1,\"\n }, {\n match: /decorators:.{0,100}?children:\\[/,\n replace: \"$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),\"\n }\n ]\n },\n {\n find: \"PrivateChannel.renderAvatar\",\n replacement: {\n match: /decorators:(\\i\\.isSystemDM\\(\\))\\?(.+?):null/,\n replace: \"decorators:[...Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]), $1?$2:null]\"\n }\n }\n ],\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"MessageAccessoriesAPI\",\n description: \"API to add message accessories.\",\n authors: [Devs.Cyn],\n patches: [\n {\n find: \".Messages.REMOVE_ATTACHMENT_BODY\",\n replacement: {\n match: /(?<=.container\\)?,children:)(\\[.+?\\])/,\n replace: \"Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)\",\n },\n },\n ],\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"MessageDecorationsAPI\",\n description: \"API to add decorations to messages\",\n authors: [Devs.TheSun],\n patches: [\n {\n find: '\"Message Username\"',\n replacement: {\n match: /\\.Messages\\.GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE.+?}\\),\\i(?=\\])/,\n replace: \"$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])\"\n }\n }\n ],\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"MessageEventsAPI\",\n description: \"Api required by anything using message events.\",\n authors: [Devs.Arjix, Devs.hunt, Devs.Ven],\n patches: [\n {\n find: \".Messages.EDIT_TEXTAREA_HELP\",\n replacement: {\n match: /(?<=,channel:\\i\\}\\)\\.then\\().+?(?=return \\i\\.content!==this\\.props\\.message\\.content&&\\i\\((.+?)\\))/,\n replace: (match, args) => \"\" +\n `async ${match}` +\n `if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +\n \"return Promise.resolve({shoudClear:true,shouldRefocus:true});\"\n }\n },\n {\n find: \".handleSendMessage=\",\n replacement: {\n // props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);\n // Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)\n match: /(type:this\\.props\\.chatInputType.+?\\.then\\()(\\i=>\\{.+?let (\\i)=\\i\\.\\i\\.parse\\((\\i),.+?let (\\i)=\\i\\.\\i\\.getSendMessageOptionsForReply\\(\\i\\);)(?<=\\)\\(({.+?})\\)\\.then.+?)/,\n // props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true };\n replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => \"\" +\n `${rest1}async ${rest2}` +\n `if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +\n \"return{shoudClear:true,shouldRefocus:true};\"\n }\n },\n {\n find: '(\"interactionUsernameProfile',\n replacement: {\n match: /let\\{id:\\i}=(\\i),{id:\\i}=(\\i);return \\i\\.useCallback\\((\\i)=>\\{/,\n replace: (m, message, channel, event) =>\n // the message param is shadowed by the event param, so need to alias them\n `const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg, vcChan, ${event});`\n }\n }\n ]\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"MessagePopoverAPI\",\n description: \"API to add buttons to message popovers.\",\n authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],\n patches: [{\n find: \"Messages.MESSAGE_UTILITIES_A11Y_LABEL\",\n replacement: {\n // foo && !bar ? createElement(reactionStuffs)... createElement(blah,...makeElement(reply-other))\n match: /\\i&&!\\i\\?\\(0,\\i\\.jsxs?\\)\\(.{0,200}renderEmojiPicker:.{0,500}\\?(\\i)\\(\\{key:\"reply-other\"/,\n replace: (m, makeElement) => {\n const msg = m.match(/message:(.{1,3}),/)?.[1];\n if (!msg) throw new Error(\"Could not find message variable\");\n return `...Vencord.Api.MessagePopover._buildPopoverElements(${msg},${makeElement}),${m}`;\n }\n }\n }],\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"NoticesAPI\",\n description: \"Fixes notices being automatically dismissed\",\n authors: [Devs.Ven],\n required: true,\n patches: [\n {\n find: 'displayName=\"NoticeStore\"',\n replacement: [\n {\n match: /\\i=null;(?=.{0,80}getPremiumSubscription\\(\\))/g,\n replace: \"if(Vencord.Api.Notices.currentNotice)return false;$&\"\n },\n {\n match: /(?<=,NOTICE_DISMISS:function\\(\\i\\){)return null!=(\\i)/,\n replace: \"if($1.id==\\\"VencordNotice\\\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&\"\n }\n ]\n }\n ],\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"ServerListAPI\",\n authors: [Devs.kemo],\n description: \"Api required for plugins that modify the server list\",\n patches: [\n {\n find: \"Messages.DISCODO_DISABLED\",\n replacement: {\n match: /(?<=Messages\\.DISCODO_DISABLED.+?return)(\\(.{0,75}?tutorialContainer.+?}\\))(?=}function)/,\n replace: \"[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))\"\n }\n },\n {\n find: \"Messages.SERVERS,children\",\n replacement: {\n match: /(?<=Messages\\.SERVERS,children:).+?default:return null\\}\\}\\)/,\n replace: \"Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)\"\n }\n }\n ]\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"NoTrack\",\n description: \"Disable Discord's tracking ('science'), metrics and Sentry crash reporting\",\n authors: [Devs.Cyn, Devs.Ven, Devs.Nuckyz, Devs.Arrow],\n required: true,\n patches: [\n {\n find: \"AnalyticsActionHandlers.handle\",\n replacement: {\n match: /^.+$/,\n replace: \"()=>{}\",\n },\n },\n {\n find: \"window.DiscordSentry=\",\n replacement: {\n match: /^.+$/,\n replace: \"()=>{}\",\n }\n },\n {\n find: \".METRICS,\",\n replacement: [\n {\n match: /this\\._intervalId=/,\n replace: \"this._intervalId=undefined&&\"\n },\n {\n match: /(increment\\(\\i\\){)/,\n replace: \"$1return;\"\n }\n ]\n },\n {\n find: \".installedLogHooks)\",\n replacement: {\n // if getDebugLogging() returns false, the hooks don't get installed.\n match: \"getDebugLogging(){\",\n replace: \"getDebugLogging(){return false;\"\n }\n },\n ]\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\n/**\n * Returns a new function that will call the wrapped function\n * after the specified delay. If the function is called again\n * within the delay, the timer will be reset.\n * @param func The function to wrap\n * @param delay The delay in milliseconds\n */\nexport function debounce(func: T, delay = 300): T {\n let timeout: NodeJS.Timeout;\n return function (...args: any[]) {\n clearTimeout(timeout);\n timeout = setTimeout(() => { func(...args); }, delay);\n } as any;\n}\n", "/*\n * Vencord, a Discord client mod\n * Copyright (c) 2024 Vendicated and contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\n\nimport { LiteralUnion } from \"type-fest\";\n\n// Resolves a possibly nested prop in the form of \"some.nested.prop\" to type of T.some.nested.prop\ntype ResolvePropDeep = P extends `${infer Pre}.${infer Suf}`\n ? Pre extends keyof T\n ? ResolvePropDeep\n : any\n : P extends keyof T\n ? T[P]\n : any;\n\ninterface SettingsStoreOptions {\n readOnly?: boolean;\n getDefaultValue?: (data: {\n target: any;\n key: string;\n root: any;\n path: string;\n }) => any;\n}\n\n// merges the SettingsStoreOptions type into the class\nexport interface SettingsStore extends SettingsStoreOptions { }\n\n/**\n * The SettingsStore allows you to easily create a mutable store that\n * has support for global and path-based change listeners.\n */\nexport class SettingsStore {\n private pathListeners = new Map void>>();\n private globalListeners = new Set<(newData: T, path: string) => void>();\n\n /**\n * The store object. Making changes to this object will trigger the applicable change listeners\n */\n public declare store: T;\n /**\n * The plain data. Changes to this object will not trigger any change listeners\n */\n public declare plain: T;\n\n public constructor(plain: T, options: SettingsStoreOptions = {}) {\n this.plain = plain;\n this.store = this.makeProxy(plain);\n Object.assign(this, options);\n }\n\n private makeProxy(object: any, root: T = object, path: string = \"\") {\n const self = this;\n\n return new Proxy(object, {\n get(target, key: string) {\n let v = target[key];\n\n if (!(key in target) && self.getDefaultValue) {\n v = self.getDefaultValue({\n target,\n key,\n root,\n path\n });\n }\n\n if (typeof v === \"object\" && v !== null && !Array.isArray(v))\n return self.makeProxy(v, root, `${path}${path && \".\"}${key}`);\n\n return v;\n },\n set(target, key: string, value) {\n if (target[key] === value) return true;\n\n Reflect.set(target, key, value);\n const setPath = `${path}${path && \".\"}${key}`;\n\n self.globalListeners.forEach(cb => cb(value, setPath));\n self.pathListeners.get(setPath)?.forEach(cb => cb(value));\n\n return true;\n }\n });\n }\n\n /**\n * Set the data of the store.\n * This will update this.store and this.plain (and old references to them will be stale! Avoid storing them in variables)\n *\n * Additionally, all global listeners (and those for pathToNotify, if specified) will be called with the new data\n * @param value New data\n * @param pathToNotify Optional path to notify instead of globally. Used to transfer path via ipc\n */\n public setData(value: T, pathToNotify?: string) {\n if (this.readOnly) throw new Error(\"SettingsStore is read-only\");\n\n this.plain = value;\n this.store = this.makeProxy(value);\n\n if (pathToNotify) {\n let v = value;\n\n const path = pathToNotify.split(\".\");\n for (const p of path) {\n if (!v) {\n console.warn(\n `Settings#setData: Path ${pathToNotify} does not exist in new data. Not dispatching update`\n );\n return;\n }\n v = v[p];\n }\n\n this.pathListeners.get(pathToNotify)?.forEach(cb => cb(v));\n }\n\n this.markAsChanged();\n }\n\n /**\n * Add a global change listener, that will fire whenever any setting is changed\n *\n * @param data The new data. This is either the new value set on the path, or the new root object if it was changed\n * @param path The path of the setting that was changed. Empty string if the root object was changed\n */\n public addGlobalChangeListener(cb: (data: any, path: string) => void) {\n this.globalListeners.add(cb);\n }\n\n /**\n * Add a scoped change listener that will fire whenever a setting matching the specified path is changed.\n *\n * For example if path is `\"foo.bar\"`, the listener will fire on\n * ```js\n * Setting.store.foo.bar = \"hi\"\n * ```\n * but not on\n * ```js\n * Setting.store.foo.baz = \"hi\"\n * ```\n * @param path\n * @param cb\n */\n public addChangeListener

>(\n path: P,\n cb: (data: ResolvePropDeep) => void\n ) {\n const listeners = this.pathListeners.get(path as string) ?? new Set();\n listeners.add(cb);\n this.pathListeners.set(path as string, listeners);\n }\n\n /**\n * Remove a global listener\n * @see {@link addGlobalChangeListener}\n */\n public removeGlobalChangeListener(cb: (data: any, path: string) => void) {\n this.globalListeners.delete(cb);\n }\n\n /**\n * Remove a scoped listener\n * @see {@link addChangeListener}\n */\n public removeChangeListener(path: LiteralUnion, cb: (data: any) => void) {\n const listeners = this.pathListeners.get(path as string);\n if (!listeners) return;\n\n listeners.delete(cb);\n if (!listeners.size) this.pathListeners.delete(path as string);\n }\n\n /**\n * Call all global change listeners\n */\n public markAsChanged() {\n this.globalListeners.forEach(cb => cb(this.plain, \"\"));\n }\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nexport const { localStorage } = window;\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Promisable } from \"type-fest\";\n\n/**\n * A queue that can be used to run tasks consecutively.\n * Highly recommended for things like fetching data from Discord\n */\nexport class Queue {\n /**\n * @param maxSize The maximum amount of functions that can be queued at once.\n * If the queue is full, the oldest function will be removed.\n */\n constructor(public readonly maxSize = Infinity) { }\n\n private queue = [] as Array<() => Promisable>;\n\n private promise?: Promise;\n\n private next() {\n const func = this.queue.shift();\n if (func)\n this.promise = Promise.resolve()\n .then(func)\n .finally(() => this.next());\n else\n this.promise = undefined;\n }\n\n private run() {\n if (!this.promise)\n this.next();\n }\n\n /**\n * Append a task at the end of the queue. This task will be executed after all other tasks\n * If the queue exceeds the specified maxSize, the first task in queue will be removed.\n * @param func Task\n */\n push(func: () => Promisable) {\n if (this.size >= this.maxSize)\n this.queue.shift();\n\n this.queue.push(func);\n this.run();\n }\n\n /**\n * Prepend a task at the beginning of the queue. This task will be executed next\n * If the queue exceeds the specified maxSize, the last task in queue will be removed.\n * @param func Task\n */\n unshift(func: () => Promisable) {\n if (this.size >= this.maxSize)\n this.queue.pop();\n\n this.queue.unshift(func);\n this.run();\n }\n\n /**\n * The amount of tasks in the queue\n */\n get size() {\n return this.queue.length;\n }\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport \"./styles.css\";\n\nimport { useSettings } from \"@api/Settings\";\nimport ErrorBoundary from \"@components/ErrorBoundary\";\nimport { classes } from \"@utils/misc\";\nimport { React, useEffect, useMemo, useState, useStateFromStores, WindowStore } from \"@webpack/common\";\n\nimport { NotificationData } from \"./Notifications\";\n\nexport default ErrorBoundary.wrap(function NotificationComponent({\n title,\n body,\n richBody,\n color,\n icon,\n onClick,\n onClose,\n image,\n permanent,\n className,\n dismissOnClick\n}: NotificationData & { className?: string; }) {\n const { timeout, position } = useSettings([\"notifications.timeout\", \"notifications.position\"]).notifications;\n const hasFocus = useStateFromStores([WindowStore], () => WindowStore.isFocused());\n\n const [isHover, setIsHover] = useState(false);\n const [elapsed, setElapsed] = useState(0);\n\n const start = useMemo(() => Date.now(), [timeout, isHover, hasFocus]);\n\n useEffect(() => {\n if (isHover || !hasFocus || timeout === 0 || permanent) return void setElapsed(0);\n\n const intervalId = setInterval(() => {\n const elapsed = Date.now() - start;\n if (elapsed >= timeout)\n onClose!();\n else\n setElapsed(elapsed);\n }, 10);\n\n return () => clearInterval(intervalId);\n }, [timeout, isHover, hasFocus]);\n\n const timeoutProgress = elapsed / timeout;\n\n return (\n {\n onClick?.();\n if (dismissOnClick !== false)\n onClose!();\n }}\n onContextMenu={e => {\n e.preventDefault();\n e.stopPropagation();\n onClose!();\n }}\n onMouseEnter={() => setIsHover(true)}\n onMouseLeave={() => setIsHover(false)}\n >\n

\n {icon && \"\"}\n
\n
\n

{title}

\n {\n e.preventDefault();\n e.stopPropagation();\n onClose!();\n }}\n >\n \n Dismiss Notification\n \n \n \n
\n
\n {richBody ??

{body}

}\n
\n
\n
\n {image && \"\"}\n {timeout !== 0 && !permanent && (\n \n )}\n \n );\n}, {\n onError: ({ props }) => props.onClose!()\n});\n", "/* eslint-disable simple-header/header */\n\n/*!\n * idb-keyval v6.2.0\n * Copyright 2016, Jake Archibald\n * Copyright 2022, Vendicated\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport function promisifyRequest(\n request: IDBRequest | IDBTransaction,\n): Promise {\n return new Promise((resolve, reject) => {\n // @ts-ignore - file size hacks\n request.oncomplete = request.onsuccess = () => resolve(request.result);\n // @ts-ignore - file size hacks\n request.onabort = request.onerror = () => reject(request.error);\n });\n}\n\nexport function createStore(dbName: string, storeName: string): UseStore {\n const request = indexedDB.open(dbName);\n request.onupgradeneeded = () => request.result.createObjectStore(storeName);\n const dbp = promisifyRequest(request);\n\n return (txMode, callback) =>\n dbp.then(db =>\n callback(db.transaction(storeName, txMode).objectStore(storeName)),\n );\n}\n\nexport type UseStore = (\n txMode: IDBTransactionMode,\n callback: (store: IDBObjectStore) => T | PromiseLike,\n) => Promise;\n\nlet defaultGetStoreFunc: UseStore | undefined;\n\nfunction defaultGetStore() {\n if (!defaultGetStoreFunc) {\n defaultGetStoreFunc = createStore(\"VencordData\", \"VencordStore\");\n }\n return defaultGetStoreFunc;\n}\n\n/**\n * Get a value by its key.\n *\n * @param key\n * @param customStore Method to get a custom store. Use with caution (see the docs).\n */\nexport function get(\n key: IDBValidKey,\n customStore = defaultGetStore(),\n): Promise {\n return customStore(\"readonly\", store => promisifyRequest(store.get(key)));\n}\n\n/**\n * Set a value with a key.\n *\n * @param key\n * @param value\n * @param customStore Method to get a custom store. Use with caution (see the docs).\n */\nexport function set(\n key: IDBValidKey,\n value: any,\n customStore = defaultGetStore(),\n): Promise {\n return customStore(\"readwrite\", store => {\n store.put(value, key);\n return promisifyRequest(store.transaction);\n });\n}\n\n/**\n * Set multiple values at once. This is faster than calling set() multiple times.\n * It's also atomic \u2013 if one of the pairs can't be added, none will be added.\n *\n * @param entries Array of entries, where each entry is an array of `[key, value]`.\n * @param customStore Method to get a custom store. Use with caution (see the docs).\n */\nexport function setMany(\n entries: [IDBValidKey, any][],\n customStore = defaultGetStore(),\n): Promise {\n return customStore(\"readwrite\", store => {\n entries.forEach(entry => store.put(entry[1], entry[0]));\n return promisifyRequest(store.transaction);\n });\n}\n\n/**\n * Get multiple values by their keys\n *\n * @param keys\n * @param customStore Method to get a custom store. Use with caution (see the docs).\n */\nexport function getMany(\n keys: IDBValidKey[],\n customStore = defaultGetStore(),\n): Promise {\n return customStore(\"readonly\", store =>\n Promise.all(keys.map(key => promisifyRequest(store.get(key)))),\n );\n}\n\n/**\n * Update a value. This lets you see the old value and update it as an atomic operation.\n *\n * @param key\n * @param updater A callback that takes the old value and returns a new value.\n * @param customStore Method to get a custom store. Use with caution (see the docs).\n */\nexport function update(\n key: IDBValidKey,\n updater: (oldValue: T | undefined) => T,\n customStore = defaultGetStore(),\n): Promise {\n return customStore(\n \"readwrite\",\n store =>\n // Need to create the promise manually.\n // If I try to chain promises, the transaction closes in browsers\n // that use a promise polyfill (IE10/11).\n new Promise((resolve, reject) => {\n store.get(key).onsuccess = function () {\n try {\n store.put(updater(this.result), key);\n resolve(promisifyRequest(store.transaction));\n } catch (err) {\n reject(err);\n }\n };\n }),\n );\n}\n\n/**\n * Delete a particular key from the store.\n *\n * @param key\n * @param customStore Method to get a custom store. Use with caution (see the docs).\n */\nexport function del(\n key: IDBValidKey,\n customStore = defaultGetStore(),\n): Promise {\n return customStore(\"readwrite\", store => {\n store.delete(key);\n return promisifyRequest(store.transaction);\n });\n}\n\n/**\n * Delete multiple keys at once.\n *\n * @param keys List of keys to delete.\n * @param customStore Method to get a custom store. Use with caution (see the docs).\n */\nexport function delMany(\n keys: IDBValidKey[],\n customStore = defaultGetStore(),\n): Promise {\n return customStore(\"readwrite\", (store: IDBObjectStore) => {\n keys.forEach((key: IDBValidKey) => store.delete(key));\n return promisifyRequest(store.transaction);\n });\n}\n\n/**\n * Clear all values in the store.\n *\n * @param customStore Method to get a custom store. Use with caution (see the docs).\n */\nexport function clear(customStore = defaultGetStore()): Promise {\n return customStore(\"readwrite\", store => {\n store.clear();\n return promisifyRequest(store.transaction);\n });\n}\n\nfunction eachCursor(\n store: IDBObjectStore,\n callback: (cursor: IDBCursorWithValue) => void,\n): Promise {\n store.openCursor().onsuccess = function () {\n if (!this.result) return;\n callback(this.result);\n this.result.continue();\n };\n return promisifyRequest(store.transaction);\n}\n\n/**\n * Get all keys in the store.\n *\n * @param customStore Method to get a custom store. Use with caution (see the docs).\n */\nexport function keys(\n customStore = defaultGetStore(),\n): Promise {\n return customStore(\"readonly\", store => {\n // Fast path for modern browsers\n if (store.getAllKeys) {\n return promisifyRequest(\n store.getAllKeys() as unknown as IDBRequest,\n );\n }\n\n const items: KeyType[] = [];\n\n return eachCursor(store, cursor =>\n items.push(cursor.key as KeyType),\n ).then(() => items);\n });\n}\n\n/**\n * Get all values in the store.\n *\n * @param customStore Method to get a custom store. Use with caution (see the docs).\n */\nexport function values(customStore = defaultGetStore()): Promise {\n return customStore(\"readonly\", store => {\n // Fast path for modern browsers\n if (store.getAll) {\n return promisifyRequest(store.getAll() as IDBRequest);\n }\n\n const items: T[] = [];\n\n return eachCursor(store, cursor => items.push(cursor.value as T)).then(\n () => items,\n );\n });\n}\n\n/**\n * Get all entries in the store. Each entry is an array of `[key, value]`.\n *\n * @param customStore Method to get a custom store. Use with caution (see the docs).\n */\nexport function entries(\n customStore = defaultGetStore(),\n): Promise<[KeyType, ValueType][]> {\n return customStore(\"readonly\", store => {\n // Fast path for modern browsers\n // (although, hopefully we'll get a simpler path some day)\n if (store.getAll && store.getAllKeys) {\n return Promise.all([\n promisifyRequest(\n store.getAllKeys() as unknown as IDBRequest,\n ),\n promisifyRequest(store.getAll() as IDBRequest),\n ]).then(([keys, values]) => keys.map((key, i) => [key, values[i]]));\n }\n\n const items: [KeyType, ValueType][] = [];\n\n return customStore(\"readonly\", store =>\n eachCursor(store, cursor =>\n items.push([cursor.key as KeyType, cursor.value]),\n ).then(() => items),\n );\n });\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport type { MapValue } from \"type-fest/source/entry\";\n\nexport type Style = MapValue;\n\nexport const styleMap = window.VencordStyles ??= new Map();\n\nexport function requireStyle(name: string) {\n const style = styleMap.get(name);\n if (!style) throw new Error(`Style \"${name}\" does not exist`);\n return style;\n}\n\n/**\n * A style's name can be obtained from importing a stylesheet with `?managed` at the end of the import\n * @param name The name of the style\n * @returns `false` if the style was already enabled, `true` otherwise\n * @example\n * import pluginStyle from \"./plugin.css?managed\";\n *\n * // Inside some plugin method like \"start()\" or \"[option].onChange()\"\n * enableStyle(pluginStyle);\n */\nexport function enableStyle(name: string) {\n const style = requireStyle(name);\n\n if (style.dom?.isConnected)\n return false;\n\n if (!style.dom) {\n style.dom = document.createElement(\"style\");\n style.dom.dataset.vencordName = style.name;\n }\n compileStyle(style);\n\n document.head.appendChild(style.dom);\n return true;\n}\n\n/**\n * @param name The name of the style\n * @returns `false` if the style was already disabled, `true` otherwise\n * @see {@link enableStyle} for info on getting the name of an imported style\n */\nexport function disableStyle(name: string) {\n const style = requireStyle(name);\n if (!style.dom?.isConnected)\n return false;\n\n style.dom.remove();\n style.dom = null;\n return true;\n}\n\n/**\n * @param name The name of the style\n * @returns `true` in most cases, may return `false` in some edge cases\n * @see {@link enableStyle} for info on getting the name of an imported style\n */\nexport const toggleStyle = (name: string) => isStyleEnabled(name) ? disableStyle(name) : enableStyle(name);\n\n/**\n * @param name The name of the style\n * @returns Whether the style is enabled\n * @see {@link enableStyle} for info on getting the name of an imported style\n */\nexport const isStyleEnabled = (name: string) => requireStyle(name).dom?.isConnected ?? false;\n\n/**\n * Sets the variables of a style\n * ```ts\n * // -- plugin.ts --\n * import pluginStyle from \"./plugin.css?managed\";\n * import { setStyleVars } from \"@api/Styles\";\n * import { findByPropsLazy } from \"@webpack\";\n * const classNames = findByPropsLazy(\"thin\", \"scrollerBase\"); // { thin: \"thin-31rlnD scrollerBase-_bVAAt\", ... }\n *\n * // Inside some plugin method like \"start()\"\n * setStyleClassNames(pluginStyle, classNames);\n * enableStyle(pluginStyle);\n * ```\n * ```scss\n * // -- plugin.css --\n * .plugin-root [--thin]::-webkit-scrollbar { ... }\n * ```\n * ```scss\n * // -- final stylesheet --\n * .plugin-root .thin-31rlnD.scrollerBase-_bVAAt::-webkit-scrollbar { ... }\n * ```\n * @param name The name of the style\n * @param classNames An object where the keys are the variable names and the values are the variable values\n * @param recompile Whether to recompile the style after setting the variables, defaults to `true`\n * @see {@link enableStyle} for info on getting the name of an imported style\n */\nexport const setStyleClassNames = (name: string, classNames: Record, recompile = true) => {\n const style = requireStyle(name);\n style.classNames = classNames;\n if (recompile && isStyleEnabled(style.name))\n compileStyle(style);\n};\n\n/**\n * Updates the stylesheet after doing the following to the sourcecode:\n * - Interpolate style classnames\n * @param style **_Must_ be a style with a DOM element**\n * @see {@link setStyleClassNames} for more info on style classnames\n */\nexport const compileStyle = (style: Style) => {\n if (!style.dom) throw new Error(\"Style has no DOM element\");\n\n style.dom.textContent = style.source\n .replace(/\\[--(\\w+)\\]/g, (match, name) => {\n const className = style.classNames[name];\n return className ? classNameToSelector(className) : match;\n });\n};\n\n/**\n * @param name The classname\n * @param prefix A prefix to add each class, defaults to `\"\"`\n * @return A css selector for the classname\n * @example\n * classNameToSelector(\"foo bar\") // => \".foo.bar\"\n */\nexport const classNameToSelector = (name: string, prefix = \"\") => name.split(\" \").map(n => `.${prefix}${n}`).join(\"\");\n\ntype ClassNameFactoryArg = string | string[] | Record | false | null | undefined | 0 | \"\";\n/**\n * @param prefix The prefix to add to each class, defaults to `\"\"`\n * @returns A classname generator function\n * @example\n * const cl = classNameFactory(\"plugin-\");\n *\n * cl(\"base\", [\"item\", \"editable\"], { selected: null, disabled: true })\n * // => \"plugin-base plugin-item plugin-editable plugin-disabled\"\n */\nexport const classNameFactory = (prefix: string = \"\") => (...args: ClassNameFactoryArg[]) => {\n const classNames = new Set();\n for (const arg of args) {\n if (arg && typeof arg === \"string\") classNames.add(arg);\n else if (Array.isArray(arg)) arg.forEach(name => classNames.add(name));\n else if (arg && typeof arg === \"object\") Object.entries(arg).forEach(([name, value]) => value && classNames.add(name));\n }\n return Array.from(classNames, name => prefix + name).join(\" \");\n};\n", "export { urlAlphabet } from './url-alphabet/index.js'\nexport let random = bytes => crypto.getRandomValues(new Uint8Array(bytes))\nexport let customRandom = (alphabet, defaultSize, getRandom) => {\n let mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1\n let step = -~((1.6 * mask * defaultSize) / alphabet.length)\n return (size = defaultSize) => {\n let id = ''\n while (true) {\n let bytes = getRandom(step)\n let j = step\n while (j--) {\n id += alphabet[bytes[j] & mask] || ''\n if (id.length === size) return id\n }\n }\n }\n}\nexport let customAlphabet = (alphabet, size = 21) =>\n customRandom(alphabet, size, random)\nexport let nanoid = (size = 21) =>\n crypto.getRandomValues(new Uint8Array(size)).reduce((id, byte) => {\n byte &= 63\n if (byte < 36) {\n id += byte.toString(36)\n } else if (byte < 62) {\n id += (byte - 26).toString(36).toUpperCase()\n } else if (byte > 62) {\n id += '-'\n } else {\n id += '_'\n }\n return id\n }, '')\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport * as DataStore from \"@api/DataStore\";\nimport { Settings } from \"@api/Settings\";\nimport { classNameFactory } from \"@api/Styles\";\nimport { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from \"@utils/modal\";\nimport { useAwaiter } from \"@utils/react\";\nimport { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from \"@webpack/common\";\nimport { nanoid } from \"nanoid\";\nimport type { DispatchWithoutAction } from \"react\";\n\nimport NotificationComponent from \"./NotificationComponent\";\nimport type { NotificationData } from \"./Notifications\";\n\ninterface PersistentNotificationData extends Pick {\n timestamp: number;\n id: string;\n}\n\nconst KEY = \"notification-log\";\n\nconst getLog = async () => {\n const log = await DataStore.get(KEY) as PersistentNotificationData[] | undefined;\n return log ?? [];\n};\n\nconst cl = classNameFactory(\"vc-notification-log-\");\nconst signals = new Set();\n\nexport async function persistNotification(notification: NotificationData) {\n if (notification.noPersist) return;\n\n const limit = Settings.notifications.logLimit;\n if (limit === 0) return;\n\n await DataStore.update(KEY, (old: PersistentNotificationData[] | undefined) => {\n const log = old ?? [];\n\n // Omit stuff we don't need\n const {\n onClick, onClose, richBody, permanent, noPersist, dismissOnClick,\n ...pureNotification\n } = notification;\n\n log.unshift({\n ...pureNotification,\n timestamp: Date.now(),\n id: nanoid()\n });\n\n if (log.length > limit && limit !== 200)\n log.length = limit;\n\n return log;\n });\n\n signals.forEach(x => x());\n}\n\nexport async function deleteNotification(timestamp: number) {\n const log = await getLog();\n const index = log.findIndex(x => x.timestamp === timestamp);\n if (index === -1) return;\n\n log.splice(index, 1);\n await DataStore.set(KEY, log);\n signals.forEach(x => x());\n}\n\nexport function useLogs() {\n const [signal, setSignal] = useReducer(x => x + 1, 0);\n\n useEffect(() => {\n signals.add(setSignal);\n return () => void signals.delete(setSignal);\n }, []);\n\n const [log, _, pending] = useAwaiter(getLog, {\n fallbackValue: [],\n deps: [signal]\n });\n\n return [log, pending] as const;\n}\n\nfunction NotificationEntry({ data }: { data: PersistentNotificationData; }) {\n const [removing, setRemoving] = useState(false);\n const ref = React.useRef(null);\n\n useEffect(() => {\n const div = ref.current!;\n\n const setHeight = () => {\n if (div.clientHeight === 0) return requestAnimationFrame(setHeight);\n div.style.height = `${div.clientHeight}px`;\n };\n\n setHeight();\n }, []);\n\n return (\n
\n {\n if (removing) return;\n setRemoving(true);\n\n setTimeout(() => deleteNotification(data.timestamp), 200);\n }}\n richBody={\n
\n {data.body}\n \n
\n }\n />\n
\n );\n}\n\nexport function NotificationLog({ log, pending }: { log: PersistentNotificationData[], pending: boolean; }) {\n if (!log.length && !pending)\n return (\n
\n
\n \n No notifications yet\n \n
\n );\n\n return (\n
\n {log.map(n => )}\n
\n );\n}\n\nfunction LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void; }) {\n const [log, pending] = useLogs();\n\n return (\n \n \n Notification Log\n \n \n\n \n \n \n\n \n {\n Alerts.show({\n title: \"Are you sure?\",\n body: `This will permanently remove ${log.length} notification${log.length === 1 ? \"\" : \"s\"}. This action cannot be undone.`,\n async onConfirm() {\n await DataStore.set(KEY, []);\n signals.forEach(x => x());\n },\n confirmText: \"Do it!\",\n confirmColor: \"vc-notification-log-danger-btn\",\n cancelText: \"Nevermind\"\n });\n }}\n >\n Clear Notification Log\n \n \n \n );\n}\n\nexport function openNotificationLogModal() {\n const key = openModal(modalProps => (\n closeModal(key)}\n />\n ));\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Settings } from \"@api/Settings\";\nimport { Queue } from \"@utils/Queue\";\nimport { ReactDOM } from \"@webpack/common\";\nimport type { ReactNode } from \"react\";\nimport type { Root } from \"react-dom/client\";\n\nimport NotificationComponent from \"./NotificationComponent\";\nimport { persistNotification } from \"./notificationLog\";\n\nconst NotificationQueue = new Queue();\n\nlet reactRoot: Root;\nlet id = 42;\n\nfunction getRoot() {\n if (!reactRoot) {\n const container = document.createElement(\"div\");\n container.id = \"vc-notification-container\";\n document.body.append(container);\n reactRoot = ReactDOM.createRoot(container);\n }\n return reactRoot;\n}\n\nexport interface NotificationData {\n title: string;\n body: string;\n /**\n * Same as body but can be a custom component.\n * Will be used over body if present.\n * Not supported on desktop notifications, those will fall back to body */\n richBody?: ReactNode;\n /** Small icon. This is for things like profile pictures and should be square */\n icon?: string;\n /** Large image. Optimally, this should be around 16x9 but it doesn't matter much. Desktop Notifications might not support this */\n image?: string;\n onClick?(): void;\n onClose?(): void;\n color?: string;\n /** Whether this notification should not have a timeout */\n permanent?: boolean;\n /** Whether this notification should not be persisted in the Notification Log */\n noPersist?: boolean;\n /** Whether this notification should be dismissed when clicked (defaults to true) */\n dismissOnClick?: boolean;\n}\n\nfunction _showNotification(notification: NotificationData, id: number) {\n const root = getRoot();\n return new Promise(resolve => {\n root.render(\n {\n notification.onClose?.();\n root.render(null);\n resolve();\n }} />,\n );\n });\n}\n\nfunction shouldBeNative() {\n if (typeof Notification === \"undefined\") return false;\n\n const { useNative } = Settings.notifications;\n if (useNative === \"always\") return true;\n if (useNative === \"not-focused\") return !document.hasFocus();\n return false;\n}\n\nexport async function requestPermission() {\n return (\n Notification.permission === \"granted\" ||\n (Notification.permission !== \"denied\" && (await Notification.requestPermission()) === \"granted\")\n );\n}\n\nexport async function showNotification(data: NotificationData) {\n persistNotification(data);\n\n if (shouldBeNative() && await requestPermission()) {\n const { title, body, icon, image, onClick = null, onClose = null } = data;\n const n = new Notification(title, {\n body,\n icon,\n image\n });\n n.onclick = onClick;\n n.onclose = onClose;\n } else {\n NotificationQueue.push(() => _showNotification(data, id++));\n }\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nexport * from \"./Notifications\";\n", "// DEFLATE is a complex format; to read this code, you should probably check the RFC first:\n// https://tools.ietf.org/html/rfc1951\n// You may also wish to take a look at the guide I made about this program:\n// https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad\n// Some of the following code is similar to that of UZIP.js:\n// https://github.com/photopea/UZIP.js\n// However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size.\n// Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint\n// is better for memory in most engines (I *think*).\nvar ch2 = {};\nvar wk = (function (c, id, msg, transfer, cb) {\n var w = new Worker(ch2[id] || (ch2[id] = URL.createObjectURL(new Blob([\n c + ';addEventListener(\"error\",function(e){e=e.error;postMessage({$e$:[e.message,e.code,e.stack]})})'\n ], { type: 'text/javascript' }))));\n w.onmessage = function (e) {\n var d = e.data, ed = d.$e$;\n if (ed) {\n var err = new Error(ed[0]);\n err['code'] = ed[1];\n err.stack = ed[2];\n cb(err, null);\n }\n else\n cb(null, d);\n };\n w.postMessage(msg, transfer);\n return w;\n});\n\n// aliases for shorter compressed code (most minifers don't do this)\nvar u8 = Uint8Array, u16 = Uint16Array, u32 = Uint32Array;\n// fixed length extra bits\nvar fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]);\n// fixed distance extra bits\n// see fleb note\nvar fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]);\n// code length index map\nvar clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);\n// get base, reverse index map from extra bits\nvar freb = function (eb, start) {\n var b = new u16(31);\n for (var i = 0; i < 31; ++i) {\n b[i] = start += 1 << eb[i - 1];\n }\n // numbers here are at max 18 bits\n var r = new u32(b[30]);\n for (var i = 1; i < 30; ++i) {\n for (var j = b[i]; j < b[i + 1]; ++j) {\n r[j] = ((j - b[i]) << 5) | i;\n }\n }\n return [b, r];\n};\nvar _a = freb(fleb, 2), fl = _a[0], revfl = _a[1];\n// we can ignore the fact that the other numbers are wrong; they never happen anyway\nfl[28] = 258, revfl[258] = 28;\nvar _b = freb(fdeb, 0), fd = _b[0], revfd = _b[1];\n// map of value to reverse (assuming 16 bits)\nvar rev = new u16(32768);\nfor (var i = 0; i < 32768; ++i) {\n // reverse table algorithm from SO\n var x = ((i & 0xAAAA) >>> 1) | ((i & 0x5555) << 1);\n x = ((x & 0xCCCC) >>> 2) | ((x & 0x3333) << 2);\n x = ((x & 0xF0F0) >>> 4) | ((x & 0x0F0F) << 4);\n rev[i] = (((x & 0xFF00) >>> 8) | ((x & 0x00FF) << 8)) >>> 1;\n}\n// create huffman tree from u8 \"map\": index -> code length for code index\n// mb (max bits) must be at most 15\n// TODO: optimize/split up?\nvar hMap = (function (cd, mb, r) {\n var s = cd.length;\n // index\n var i = 0;\n // u16 \"map\": index -> # of codes with bit length = index\n var l = new u16(mb);\n // length of cd must be 288 (total # of codes)\n for (; i < s; ++i) {\n if (cd[i])\n ++l[cd[i] - 1];\n }\n // u16 \"map\": index -> minimum code for bit length = index\n var le = new u16(mb);\n for (i = 0; i < mb; ++i) {\n le[i] = (le[i - 1] + l[i - 1]) << 1;\n }\n var co;\n if (r) {\n // u16 \"map\": index -> number of actual bits, symbol for code\n co = new u16(1 << mb);\n // bits to remove for reverser\n var rvb = 15 - mb;\n for (i = 0; i < s; ++i) {\n // ignore 0 lengths\n if (cd[i]) {\n // num encoding both symbol and bits read\n var sv = (i << 4) | cd[i];\n // free bits\n var r_1 = mb - cd[i];\n // start value\n var v = le[cd[i] - 1]++ << r_1;\n // m is end value\n for (var m = v | ((1 << r_1) - 1); v <= m; ++v) {\n // every 16 bit value starting with the code yields the same result\n co[rev[v] >>> rvb] = sv;\n }\n }\n }\n }\n else {\n co = new u16(s);\n for (i = 0; i < s; ++i) {\n if (cd[i]) {\n co[i] = rev[le[cd[i] - 1]++] >>> (15 - cd[i]);\n }\n }\n }\n return co;\n});\n// fixed length tree\nvar flt = new u8(288);\nfor (var i = 0; i < 144; ++i)\n flt[i] = 8;\nfor (var i = 144; i < 256; ++i)\n flt[i] = 9;\nfor (var i = 256; i < 280; ++i)\n flt[i] = 7;\nfor (var i = 280; i < 288; ++i)\n flt[i] = 8;\n// fixed distance tree\nvar fdt = new u8(32);\nfor (var i = 0; i < 32; ++i)\n fdt[i] = 5;\n// fixed length map\nvar flm = /*#__PURE__*/ hMap(flt, 9, 0), flrm = /*#__PURE__*/ hMap(flt, 9, 1);\n// fixed distance map\nvar fdm = /*#__PURE__*/ hMap(fdt, 5, 0), fdrm = /*#__PURE__*/ hMap(fdt, 5, 1);\n// find max of array\nvar max = function (a) {\n var m = a[0];\n for (var i = 1; i < a.length; ++i) {\n if (a[i] > m)\n m = a[i];\n }\n return m;\n};\n// read d, starting at bit p and mask with m\nvar bits = function (d, p, m) {\n var o = (p / 8) | 0;\n return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m;\n};\n// read d, starting at bit p continuing for at least 16 bits\nvar bits16 = function (d, p) {\n var o = (p / 8) | 0;\n return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7));\n};\n// get end of byte\nvar shft = function (p) { return ((p + 7) / 8) | 0; };\n// typed array slice - allows garbage collector to free original reference,\n// while being more compatible than .slice\nvar slc = function (v, s, e) {\n if (s == null || s < 0)\n s = 0;\n if (e == null || e > v.length)\n e = v.length;\n // can't use .constructor in case user-supplied\n var n = new (v.BYTES_PER_ELEMENT == 2 ? u16 : v.BYTES_PER_ELEMENT == 4 ? u32 : u8)(e - s);\n n.set(v.subarray(s, e));\n return n;\n};\n/**\n * Codes for errors generated within this library\n */\nexport var FlateErrorCode = {\n UnexpectedEOF: 0,\n InvalidBlockType: 1,\n InvalidLengthLiteral: 2,\n InvalidDistance: 3,\n StreamFinished: 4,\n NoStreamHandler: 5,\n InvalidHeader: 6,\n NoCallback: 7,\n InvalidUTF8: 8,\n ExtraFieldTooLong: 9,\n InvalidDate: 10,\n FilenameTooLong: 11,\n StreamFinishing: 12,\n InvalidZipData: 13,\n UnknownCompressionMethod: 14\n};\n// error codes\nvar ec = [\n 'unexpected EOF',\n 'invalid block type',\n 'invalid length/literal',\n 'invalid distance',\n 'stream finished',\n 'no stream handler',\n ,\n 'no callback',\n 'invalid UTF-8 data',\n 'extra field too long',\n 'date not in range 1980-2099',\n 'filename too long',\n 'stream finishing',\n 'invalid zip data'\n // determined by unknown compression method\n];\n;\nvar err = function (ind, msg, nt) {\n var e = new Error(msg || ec[ind]);\n e.code = ind;\n if (Error.captureStackTrace)\n Error.captureStackTrace(e, err);\n if (!nt)\n throw e;\n return e;\n};\n// expands raw DEFLATE data\nvar inflt = function (dat, buf, st) {\n // source length\n var sl = dat.length;\n if (!sl || (st && st.f && !st.l))\n return buf || new u8(0);\n // have to estimate size\n var noBuf = !buf || st;\n // no state\n var noSt = !st || st.i;\n if (!st)\n st = {};\n // Assumes roughly 33% compression ratio average\n if (!buf)\n buf = new u8(sl * 3);\n // ensure buffer can fit at least l elements\n var cbuf = function (l) {\n var bl = buf.length;\n // need to increase size to fit\n if (l > bl) {\n // Double or set to necessary, whichever is greater\n var nbuf = new u8(Math.max(bl * 2, l));\n nbuf.set(buf);\n buf = nbuf;\n }\n };\n // last chunk bitpos bytes\n var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;\n // total bits\n var tbts = sl * 8;\n do {\n if (!lm) {\n // BFINAL - this is only 1 when last chunk is next\n final = bits(dat, pos, 1);\n // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman\n var type = bits(dat, pos + 1, 3);\n pos += 3;\n if (!type) {\n // go to end of byte boundary\n var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l;\n if (t > sl) {\n if (noSt)\n err(0);\n break;\n }\n // ensure size\n if (noBuf)\n cbuf(bt + l);\n // Copy over uncompressed data\n buf.set(dat.subarray(s, t), bt);\n // Get new bitpos, update byte count\n st.b = bt += l, st.p = pos = t * 8, st.f = final;\n continue;\n }\n else if (type == 1)\n lm = flrm, dm = fdrm, lbt = 9, dbt = 5;\n else if (type == 2) {\n // literal lengths\n var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;\n var tl = hLit + bits(dat, pos + 5, 31) + 1;\n pos += 14;\n // length+distance tree\n var ldt = new u8(tl);\n // code length tree\n var clt = new u8(19);\n for (var i = 0; i < hcLen; ++i) {\n // use index map to get real code\n clt[clim[i]] = bits(dat, pos + i * 3, 7);\n }\n pos += hcLen * 3;\n // code lengths bits\n var clb = max(clt), clbmsk = (1 << clb) - 1;\n // code lengths map\n var clm = hMap(clt, clb, 1);\n for (var i = 0; i < tl;) {\n var r = clm[bits(dat, pos, clbmsk)];\n // bits read\n pos += r & 15;\n // symbol\n var s = r >>> 4;\n // code length to copy\n if (s < 16) {\n ldt[i++] = s;\n }\n else {\n // copy count\n var c = 0, n = 0;\n if (s == 16)\n n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1];\n else if (s == 17)\n n = 3 + bits(dat, pos, 7), pos += 3;\n else if (s == 18)\n n = 11 + bits(dat, pos, 127), pos += 7;\n while (n--)\n ldt[i++] = c;\n }\n }\n // length tree distance tree\n var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);\n // max length bits\n lbt = max(lt);\n // max dist bits\n dbt = max(dt);\n lm = hMap(lt, lbt, 1);\n dm = hMap(dt, dbt, 1);\n }\n else\n err(1);\n if (pos > tbts) {\n if (noSt)\n err(0);\n break;\n }\n }\n // Make sure the buffer can hold this + the largest possible addition\n // Maximum chunk size (practically, theoretically infinite) is 2^17;\n if (noBuf)\n cbuf(bt + 131072);\n var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;\n var lpos = pos;\n for (;; lpos = pos) {\n // bits read, code\n var c = lm[bits16(dat, pos) & lms], sym = c >>> 4;\n pos += c & 15;\n if (pos > tbts) {\n if (noSt)\n err(0);\n break;\n }\n if (!c)\n err(2);\n if (sym < 256)\n buf[bt++] = sym;\n else if (sym == 256) {\n lpos = pos, lm = null;\n break;\n }\n else {\n var add = sym - 254;\n // no extra bits needed if less\n if (sym > 264) {\n // index\n var i = sym - 257, b = fleb[i];\n add = bits(dat, pos, (1 << b) - 1) + fl[i];\n pos += b;\n }\n // dist\n var d = dm[bits16(dat, pos) & dms], dsym = d >>> 4;\n if (!d)\n err(3);\n pos += d & 15;\n var dt = fd[dsym];\n if (dsym > 3) {\n var b = fdeb[dsym];\n dt += bits16(dat, pos) & ((1 << b) - 1), pos += b;\n }\n if (pos > tbts) {\n if (noSt)\n err(0);\n break;\n }\n if (noBuf)\n cbuf(bt + 131072);\n var end = bt + add;\n for (; bt < end; bt += 4) {\n buf[bt] = buf[bt - dt];\n buf[bt + 1] = buf[bt + 1 - dt];\n buf[bt + 2] = buf[bt + 2 - dt];\n buf[bt + 3] = buf[bt + 3 - dt];\n }\n bt = end;\n }\n }\n st.l = lm, st.p = lpos, st.b = bt, st.f = final;\n if (lm)\n final = 1, st.m = lbt, st.d = dm, st.n = dbt;\n } while (!final);\n return bt == buf.length ? buf : slc(buf, 0, bt);\n};\n// starting at p, write the minimum number of bits that can hold v to d\nvar wbits = function (d, p, v) {\n v <<= p & 7;\n var o = (p / 8) | 0;\n d[o] |= v;\n d[o + 1] |= v >>> 8;\n};\n// starting at p, write the minimum number of bits (>8) that can hold v to d\nvar wbits16 = function (d, p, v) {\n v <<= p & 7;\n var o = (p / 8) | 0;\n d[o] |= v;\n d[o + 1] |= v >>> 8;\n d[o + 2] |= v >>> 16;\n};\n// creates code lengths from a frequency table\nvar hTree = function (d, mb) {\n // Need extra info to make a tree\n var t = [];\n for (var i = 0; i < d.length; ++i) {\n if (d[i])\n t.push({ s: i, f: d[i] });\n }\n var s = t.length;\n var t2 = t.slice();\n if (!s)\n return [et, 0];\n if (s == 1) {\n var v = new u8(t[0].s + 1);\n v[t[0].s] = 1;\n return [v, 1];\n }\n t.sort(function (a, b) { return a.f - b.f; });\n // after i2 reaches last ind, will be stopped\n // freq must be greater than largest possible number of symbols\n t.push({ s: -1, f: 25001 });\n var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;\n t[0] = { s: -1, f: l.f + r.f, l: l, r: r };\n // efficient algorithm from UZIP.js\n // i0 is lookbehind, i2 is lookahead - after processing two low-freq\n // symbols that combined have high freq, will start processing i2 (high-freq,\n // non-composite) symbols instead\n // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/\n while (i1 != s - 1) {\n l = t[t[i0].f < t[i2].f ? i0++ : i2++];\n r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];\n t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r };\n }\n var maxSym = t2[0].s;\n for (var i = 1; i < s; ++i) {\n if (t2[i].s > maxSym)\n maxSym = t2[i].s;\n }\n // code lengths\n var tr = new u16(maxSym + 1);\n // max bits in tree\n var mbt = ln(t[i1 - 1], tr, 0);\n if (mbt > mb) {\n // more algorithms from UZIP.js\n // TODO: find out how this code works (debt)\n // ind debt\n var i = 0, dt = 0;\n // left cost\n var lft = mbt - mb, cst = 1 << lft;\n t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; });\n for (; i < s; ++i) {\n var i2_1 = t2[i].s;\n if (tr[i2_1] > mb) {\n dt += cst - (1 << (mbt - tr[i2_1]));\n tr[i2_1] = mb;\n }\n else\n break;\n }\n dt >>>= lft;\n while (dt > 0) {\n var i2_2 = t2[i].s;\n if (tr[i2_2] < mb)\n dt -= 1 << (mb - tr[i2_2]++ - 1);\n else\n ++i;\n }\n for (; i >= 0 && dt; --i) {\n var i2_3 = t2[i].s;\n if (tr[i2_3] == mb) {\n --tr[i2_3];\n ++dt;\n }\n }\n mbt = mb;\n }\n return [new u8(tr), mbt];\n};\n// get the max length and assign length codes\nvar ln = function (n, l, d) {\n return n.s == -1\n ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1))\n : (l[n.s] = d);\n};\n// length codes generation\nvar lc = function (c) {\n var s = c.length;\n // Note that the semicolon was intentional\n while (s && !c[--s])\n ;\n var cl = new u16(++s);\n // ind num streak\n var cli = 0, cln = c[0], cls = 1;\n var w = function (v) { cl[cli++] = v; };\n for (var i = 1; i <= s; ++i) {\n if (c[i] == cln && i != s)\n ++cls;\n else {\n if (!cln && cls > 2) {\n for (; cls > 138; cls -= 138)\n w(32754);\n if (cls > 2) {\n w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305);\n cls = 0;\n }\n }\n else if (cls > 3) {\n w(cln), --cls;\n for (; cls > 6; cls -= 6)\n w(8304);\n if (cls > 2)\n w(((cls - 3) << 5) | 8208), cls = 0;\n }\n while (cls--)\n w(cln);\n cls = 1;\n cln = c[i];\n }\n }\n return [cl.subarray(0, cli), s];\n};\n// calculate the length of output from tree, code lengths\nvar clen = function (cf, cl) {\n var l = 0;\n for (var i = 0; i < cl.length; ++i)\n l += cf[i] * cl[i];\n return l;\n};\n// writes a fixed block\n// returns the new bit pos\nvar wfblk = function (out, pos, dat) {\n // no need to write 00 as type: TypedArray defaults to 0\n var s = dat.length;\n var o = shft(pos + 2);\n out[o] = s & 255;\n out[o + 1] = s >>> 8;\n out[o + 2] = out[o] ^ 255;\n out[o + 3] = out[o + 1] ^ 255;\n for (var i = 0; i < s; ++i)\n out[o + i + 4] = dat[i];\n return (o + 4 + s) * 8;\n};\n// writes a block\nvar wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) {\n wbits(out, p++, final);\n ++lf[256];\n var _a = hTree(lf, 15), dlt = _a[0], mlb = _a[1];\n var _b = hTree(df, 15), ddt = _b[0], mdb = _b[1];\n var _c = lc(dlt), lclt = _c[0], nlc = _c[1];\n var _d = lc(ddt), lcdt = _d[0], ndc = _d[1];\n var lcfreq = new u16(19);\n for (var i = 0; i < lclt.length; ++i)\n lcfreq[lclt[i] & 31]++;\n for (var i = 0; i < lcdt.length; ++i)\n lcfreq[lcdt[i] & 31]++;\n var _e = hTree(lcfreq, 7), lct = _e[0], mlcb = _e[1];\n var nlcc = 19;\n for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)\n ;\n var flen = (bl + 5) << 3;\n var ftlen = clen(lf, flt) + clen(df, fdt) + eb;\n var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + (2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]);\n if (flen <= ftlen && flen <= dtlen)\n return wfblk(out, p, dat.subarray(bs, bs + bl));\n var lm, ll, dm, dl;\n wbits(out, p, 1 + (dtlen < ftlen)), p += 2;\n if (dtlen < ftlen) {\n lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;\n var llm = hMap(lct, mlcb, 0);\n wbits(out, p, nlc - 257);\n wbits(out, p + 5, ndc - 1);\n wbits(out, p + 10, nlcc - 4);\n p += 14;\n for (var i = 0; i < nlcc; ++i)\n wbits(out, p + 3 * i, lct[clim[i]]);\n p += 3 * nlcc;\n var lcts = [lclt, lcdt];\n for (var it = 0; it < 2; ++it) {\n var clct = lcts[it];\n for (var i = 0; i < clct.length; ++i) {\n var len = clct[i] & 31;\n wbits(out, p, llm[len]), p += lct[len];\n if (len > 15)\n wbits(out, p, (clct[i] >>> 5) & 127), p += clct[i] >>> 12;\n }\n }\n }\n else {\n lm = flm, ll = flt, dm = fdm, dl = fdt;\n }\n for (var i = 0; i < li; ++i) {\n if (syms[i] > 255) {\n var len = (syms[i] >>> 18) & 31;\n wbits16(out, p, lm[len + 257]), p += ll[len + 257];\n if (len > 7)\n wbits(out, p, (syms[i] >>> 23) & 31), p += fleb[len];\n var dst = syms[i] & 31;\n wbits16(out, p, dm[dst]), p += dl[dst];\n if (dst > 3)\n wbits16(out, p, (syms[i] >>> 5) & 8191), p += fdeb[dst];\n }\n else {\n wbits16(out, p, lm[syms[i]]), p += ll[syms[i]];\n }\n }\n wbits16(out, p, lm[256]);\n return p + ll[256];\n};\n// deflate options (nice << 13) | chain\nvar deo = /*#__PURE__*/ new u32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);\n// empty\nvar et = /*#__PURE__*/ new u8(0);\n// compresses data into a raw DEFLATE buffer\nvar dflt = function (dat, lvl, plvl, pre, post, lst) {\n var s = dat.length;\n var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post);\n // writing to this writes to the output buffer\n var w = o.subarray(pre, o.length - post);\n var pos = 0;\n if (!lvl || s < 8) {\n for (var i = 0; i <= s; i += 65535) {\n // end\n var e = i + 65535;\n if (e >= s) {\n // write final block\n w[pos >> 3] = lst;\n }\n pos = wfblk(w, pos + 1, dat.subarray(i, e));\n }\n }\n else {\n var opt = deo[lvl - 1];\n var n = opt >>> 13, c = opt & 8191;\n var msk_1 = (1 << plvl) - 1;\n // prev 2-byte val map curr 2-byte val map\n var prev = new u16(32768), head = new u16(msk_1 + 1);\n var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;\n var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; };\n // 24576 is an arbitrary number of maximum symbols per block\n // 424 buffer for last block\n var syms = new u32(25000);\n // length/literal freq distance freq\n var lf = new u16(288), df = new u16(32);\n // l/lcnt exbits index l/lind waitdx bitpos\n var lc_1 = 0, eb = 0, i = 0, li = 0, wi = 0, bs = 0;\n for (; i < s; ++i) {\n // hash value\n // deopt when i > s - 3 - at end, deopt acceptable\n var hv = hsh(i);\n // index mod 32768 previous index mod\n var imod = i & 32767, pimod = head[hv];\n prev[imod] = pimod;\n head[hv] = imod;\n // We always should modify head and prev, but only add symbols if\n // this data is not yet processed (\"wait\" for wait index)\n if (wi <= i) {\n // bytes remaining\n var rem = s - i;\n if ((lc_1 > 7000 || li > 24576) && rem > 423) {\n pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);\n li = lc_1 = eb = 0, bs = i;\n for (var j = 0; j < 286; ++j)\n lf[j] = 0;\n for (var j = 0; j < 30; ++j)\n df[j] = 0;\n }\n // len dist chain\n var l = 2, d = 0, ch_1 = c, dif = (imod - pimod) & 32767;\n if (rem > 2 && hv == hsh(i - dif)) {\n var maxn = Math.min(n, rem) - 1;\n var maxd = Math.min(32767, i);\n // max possible length\n // not capped at dif because decompressors implement \"rolling\" index population\n var ml = Math.min(258, rem);\n while (dif <= maxd && --ch_1 && imod != pimod) {\n if (dat[i + l] == dat[i + l - dif]) {\n var nl = 0;\n for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)\n ;\n if (nl > l) {\n l = nl, d = dif;\n // break out early when we reach \"nice\" (we are satisfied enough)\n if (nl > maxn)\n break;\n // now, find the rarest 2-byte sequence within this\n // length of literals and search for that instead.\n // Much faster than just using the start\n var mmd = Math.min(dif, nl - 2);\n var md = 0;\n for (var j = 0; j < mmd; ++j) {\n var ti = (i - dif + j + 32768) & 32767;\n var pti = prev[ti];\n var cd = (ti - pti + 32768) & 32767;\n if (cd > md)\n md = cd, pimod = ti;\n }\n }\n }\n // check the previous match\n imod = pimod, pimod = prev[imod];\n dif += (imod - pimod + 32768) & 32767;\n }\n }\n // d will be nonzero only when a match was found\n if (d) {\n // store both dist and len data in one Uint32\n // Make sure this is recognized as a len/dist with 28th bit (2^28)\n syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d];\n var lin = revfl[l] & 31, din = revfd[d] & 31;\n eb += fleb[lin] + fdeb[din];\n ++lf[257 + lin];\n ++df[din];\n wi = i + l;\n ++lc_1;\n }\n else {\n syms[li++] = dat[i];\n ++lf[dat[i]];\n }\n }\n }\n pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);\n // this is the easiest way to avoid needing to maintain state\n if (!lst && pos & 7)\n pos = wfblk(w, pos + 1, et);\n }\n return slc(o, 0, pre + shft(pos) + post);\n};\n// CRC32 table\nvar crct = /*#__PURE__*/ (function () {\n var t = new Int32Array(256);\n for (var i = 0; i < 256; ++i) {\n var c = i, k = 9;\n while (--k)\n c = ((c & 1) && -306674912) ^ (c >>> 1);\n t[i] = c;\n }\n return t;\n})();\n// CRC32\nvar crc = function () {\n var c = -1;\n return {\n p: function (d) {\n // closures have awful performance\n var cr = c;\n for (var i = 0; i < d.length; ++i)\n cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8);\n c = cr;\n },\n d: function () { return ~c; }\n };\n};\n// Alder32\nvar adler = function () {\n var a = 1, b = 0;\n return {\n p: function (d) {\n // closures have awful performance\n var n = a, m = b;\n var l = d.length | 0;\n for (var i = 0; i != l;) {\n var e = Math.min(i + 2655, l);\n for (; i < e; ++i)\n m += n += d[i];\n n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16);\n }\n a = n, b = m;\n },\n d: function () {\n a %= 65521, b %= 65521;\n return (a & 255) << 24 | (a >>> 8) << 16 | (b & 255) << 8 | (b >>> 8);\n }\n };\n};\n;\n// deflate with opts\nvar dopt = function (dat, opt, pre, post, st) {\n return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, !st);\n};\n// Walmart object spread\nvar mrg = function (a, b) {\n var o = {};\n for (var k in a)\n o[k] = a[k];\n for (var k in b)\n o[k] = b[k];\n return o;\n};\n// worker clone\n// This is possibly the craziest part of the entire codebase, despite how simple it may seem.\n// The only parameter to this function is a closure that returns an array of variables outside of the function scope.\n// We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization.\n// We will return an object mapping of true variable name to value (basically, the current scope as a JS object).\n// The reason we can't just use the original variable names is minifiers mangling the toplevel scope.\n// This took me three weeks to figure out how to do.\nvar wcln = function (fn, fnStr, td) {\n var dt = fn();\n var st = fn.toString();\n var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/\\s+/g, '').split(',');\n for (var i = 0; i < dt.length; ++i) {\n var v = dt[i], k = ks[i];\n if (typeof v == 'function') {\n fnStr += ';' + k + '=';\n var st_1 = v.toString();\n if (v.prototype) {\n // for global objects\n if (st_1.indexOf('[native code]') != -1) {\n var spInd = st_1.indexOf(' ', 8) + 1;\n fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd));\n }\n else {\n fnStr += st_1;\n for (var t in v.prototype)\n fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString();\n }\n }\n else\n fnStr += st_1;\n }\n else\n td[k] = v;\n }\n return [fnStr, td];\n};\nvar ch = [];\n// clone bufs\nvar cbfs = function (v) {\n var tl = [];\n for (var k in v) {\n if (v[k].buffer) {\n tl.push((v[k] = new v[k].constructor(v[k])).buffer);\n }\n }\n return tl;\n};\n// use a worker to execute code\nvar wrkr = function (fns, init, id, cb) {\n var _a;\n if (!ch[id]) {\n var fnStr = '', td_1 = {}, m = fns.length - 1;\n for (var i = 0; i < m; ++i)\n _a = wcln(fns[i], fnStr, td_1), fnStr = _a[0], td_1 = _a[1];\n ch[id] = wcln(fns[m], fnStr, td_1);\n }\n var td = mrg({}, ch[id][1]);\n return wk(ch[id][0] + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb);\n};\n// base async inflate fn\nvar bInflt = function () { return [u8, u16, u32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, ec, hMap, max, bits, bits16, shft, slc, err, inflt, inflateSync, pbf, gu8]; };\nvar bDflt = function () { return [u8, u16, u32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; };\n// gzip extra\nvar gze = function () { return [gzh, gzhl, wbytes, crc, crct]; };\n// gunzip extra\nvar guze = function () { return [gzs, gzl]; };\n// zlib extra\nvar zle = function () { return [zlh, wbytes, adler]; };\n// unzlib extra\nvar zule = function () { return [zlv]; };\n// post buf\nvar pbf = function (msg) { return postMessage(msg, [msg.buffer]); };\n// get u8\nvar gu8 = function (o) { return o && o.size && new u8(o.size); };\n// async helper\nvar cbify = function (dat, opts, fns, init, id, cb) {\n var w = wrkr(fns, init, id, function (err, dat) {\n w.terminate();\n cb(err, dat);\n });\n w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []);\n return function () { w.terminate(); };\n};\n// auto stream\nvar astrm = function (strm) {\n strm.ondata = function (dat, final) { return postMessage([dat, final], [dat.buffer]); };\n return function (ev) { return strm.push(ev.data[0], ev.data[1]); };\n};\n// async stream attach\nvar astrmify = function (fns, strm, opts, init, id) {\n var t;\n var w = wrkr(fns, init, id, function (err, dat) {\n if (err)\n w.terminate(), strm.ondata.call(strm, err);\n else {\n if (dat[1])\n w.terminate();\n strm.ondata.call(strm, err, dat[0], dat[1]);\n }\n });\n w.postMessage(opts);\n strm.push = function (d, f) {\n if (!strm.ondata)\n err(5);\n if (t)\n strm.ondata(err(4, 0, 1), null, !!f);\n w.postMessage([d, t = f], [d.buffer]);\n };\n strm.terminate = function () { w.terminate(); };\n};\n// read 2 bytes\nvar b2 = function (d, b) { return d[b] | (d[b + 1] << 8); };\n// read 4 bytes\nvar b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; };\nvar b8 = function (d, b) { return b4(d, b) + (b4(d, b + 4) * 4294967296); };\n// write bytes\nvar wbytes = function (d, b, v) {\n for (; v; ++b)\n d[b] = v, v >>>= 8;\n};\n// gzip header\nvar gzh = function (c, o) {\n var fn = o.filename;\n c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix\n if (o.mtime != 0)\n wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000));\n if (fn) {\n c[3] = 8;\n for (var i = 0; i <= fn.length; ++i)\n c[i + 10] = fn.charCodeAt(i);\n }\n};\n// gzip footer: -8 to -4 = CRC, -4 to -0 is length\n// gzip start\nvar gzs = function (d) {\n if (d[0] != 31 || d[1] != 139 || d[2] != 8)\n err(6, 'invalid gzip data');\n var flg = d[3];\n var st = 10;\n if (flg & 4)\n st += d[10] | (d[11] << 8) + 2;\n for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++])\n ;\n return st + (flg & 2);\n};\n// gzip length\nvar gzl = function (d) {\n var l = d.length;\n return ((d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16) | (d[l - 1] << 24)) >>> 0;\n};\n// gzip header length\nvar gzhl = function (o) { return 10 + ((o.filename && (o.filename.length + 1)) || 0); };\n// zlib header\nvar zlh = function (c, o) {\n var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2;\n c[0] = 120, c[1] = (fl << 6) | (fl ? (32 - 2 * fl) : 1);\n};\n// zlib valid\nvar zlv = function (d) {\n if ((d[0] & 15) != 8 || (d[0] >>> 4) > 7 || ((d[0] << 8 | d[1]) % 31))\n err(6, 'invalid zlib data');\n if (d[1] & 32)\n err(6, 'invalid zlib data: preset dictionaries not supported');\n};\nfunction AsyncCmpStrm(opts, cb) {\n if (!cb && typeof opts == 'function')\n cb = opts, opts = {};\n this.ondata = cb;\n return opts;\n}\n// zlib footer: -4 to -0 is Adler32\n/**\n * Streaming DEFLATE compression\n */\nvar Deflate = /*#__PURE__*/ (function () {\n function Deflate(opts, cb) {\n if (!cb && typeof opts == 'function')\n cb = opts, opts = {};\n this.ondata = cb;\n this.o = opts || {};\n }\n Deflate.prototype.p = function (c, f) {\n this.ondata(dopt(c, this.o, 0, 0, !f), f);\n };\n /**\n * Pushes a chunk to be deflated\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Deflate.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n if (this.d)\n err(4);\n this.d = final;\n this.p(chunk, final || false);\n };\n return Deflate;\n}());\nexport { Deflate };\n/**\n * Asynchronous streaming DEFLATE compression\n */\nvar AsyncDeflate = /*#__PURE__*/ (function () {\n function AsyncDeflate(opts, cb) {\n astrmify([\n bDflt,\n function () { return [astrm, Deflate]; }\n ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) {\n var strm = new Deflate(ev.data);\n onmessage = astrm(strm);\n }, 6);\n }\n return AsyncDeflate;\n}());\nexport { AsyncDeflate };\nexport function deflate(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bDflt,\n ], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb);\n}\n/**\n * Compresses data with DEFLATE without any wrapper\n * @param data The data to compress\n * @param opts The compression options\n * @returns The deflated version of the data\n */\nexport function deflateSync(data, opts) {\n return dopt(data, opts || {}, 0, 0);\n}\n/**\n * Streaming DEFLATE decompression\n */\nvar Inflate = /*#__PURE__*/ (function () {\n /**\n * Creates an inflation stream\n * @param cb The callback to call whenever data is inflated\n */\n function Inflate(cb) {\n this.s = {};\n this.p = new u8(0);\n this.ondata = cb;\n }\n Inflate.prototype.e = function (c) {\n if (!this.ondata)\n err(5);\n if (this.d)\n err(4);\n var l = this.p.length;\n var n = new u8(l + c.length);\n n.set(this.p), n.set(c, l), this.p = n;\n };\n Inflate.prototype.c = function (final) {\n this.d = this.s.i = final || false;\n var bts = this.s.b;\n var dt = inflt(this.p, this.o, this.s);\n this.ondata(slc(dt, bts, this.s.b), this.d);\n this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length;\n this.p = slc(this.p, (this.s.p / 8) | 0), this.s.p &= 7;\n };\n /**\n * Pushes a chunk to be inflated\n * @param chunk The chunk to push\n * @param final Whether this is the final chunk\n */\n Inflate.prototype.push = function (chunk, final) {\n this.e(chunk), this.c(final);\n };\n return Inflate;\n}());\nexport { Inflate };\n/**\n * Asynchronous streaming DEFLATE decompression\n */\nvar AsyncInflate = /*#__PURE__*/ (function () {\n /**\n * Creates an asynchronous inflation stream\n * @param cb The callback to call whenever data is deflated\n */\n function AsyncInflate(cb) {\n this.ondata = cb;\n astrmify([\n bInflt,\n function () { return [astrm, Inflate]; }\n ], this, 0, function () {\n var strm = new Inflate();\n onmessage = astrm(strm);\n }, 7);\n }\n return AsyncInflate;\n}());\nexport { AsyncInflate };\nexport function inflate(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bInflt\n ], function (ev) { return pbf(inflateSync(ev.data[0], gu8(ev.data[1]))); }, 1, cb);\n}\n/**\n * Expands DEFLATE data with no wrapper\n * @param data The data to decompress\n * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length.\n * @returns The decompressed version of the data\n */\nexport function inflateSync(data, out) {\n return inflt(data, out);\n}\n// before you yell at me for not just using extends, my reason is that TS inheritance is hard to workerize.\n/**\n * Streaming GZIP compression\n */\nvar Gzip = /*#__PURE__*/ (function () {\n function Gzip(opts, cb) {\n this.c = crc();\n this.l = 0;\n this.v = 1;\n Deflate.call(this, opts, cb);\n }\n /**\n * Pushes a chunk to be GZIPped\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Gzip.prototype.push = function (chunk, final) {\n Deflate.prototype.push.call(this, chunk, final);\n };\n Gzip.prototype.p = function (c, f) {\n this.c.p(c);\n this.l += c.length;\n var raw = dopt(c, this.o, this.v && gzhl(this.o), f && 8, !f);\n if (this.v)\n gzh(raw, this.o), this.v = 0;\n if (f)\n wbytes(raw, raw.length - 8, this.c.d()), wbytes(raw, raw.length - 4, this.l);\n this.ondata(raw, f);\n };\n return Gzip;\n}());\nexport { Gzip };\n/**\n * Asynchronous streaming GZIP compression\n */\nvar AsyncGzip = /*#__PURE__*/ (function () {\n function AsyncGzip(opts, cb) {\n astrmify([\n bDflt,\n gze,\n function () { return [astrm, Deflate, Gzip]; }\n ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) {\n var strm = new Gzip(ev.data);\n onmessage = astrm(strm);\n }, 8);\n }\n return AsyncGzip;\n}());\nexport { AsyncGzip };\nexport function gzip(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bDflt,\n gze,\n function () { return [gzipSync]; }\n ], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb);\n}\n/**\n * Compresses data with GZIP\n * @param data The data to compress\n * @param opts The compression options\n * @returns The gzipped version of the data\n */\nexport function gzipSync(data, opts) {\n if (!opts)\n opts = {};\n var c = crc(), l = data.length;\n c.p(data);\n var d = dopt(data, opts, gzhl(opts), 8), s = d.length;\n return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d;\n}\n/**\n * Streaming GZIP decompression\n */\nvar Gunzip = /*#__PURE__*/ (function () {\n /**\n * Creates a GUNZIP stream\n * @param cb The callback to call whenever data is inflated\n */\n function Gunzip(cb) {\n this.v = 1;\n Inflate.call(this, cb);\n }\n /**\n * Pushes a chunk to be GUNZIPped\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Gunzip.prototype.push = function (chunk, final) {\n Inflate.prototype.e.call(this, chunk);\n if (this.v) {\n var s = this.p.length > 3 ? gzs(this.p) : 4;\n if (s >= this.p.length && !final)\n return;\n this.p = this.p.subarray(s), this.v = 0;\n }\n if (final) {\n if (this.p.length < 8)\n err(6, 'invalid gzip data');\n this.p = this.p.subarray(0, -8);\n }\n // necessary to prevent TS from using the closure value\n // This allows for workerization to function correctly\n Inflate.prototype.c.call(this, final);\n };\n return Gunzip;\n}());\nexport { Gunzip };\n/**\n * Asynchronous streaming GZIP decompression\n */\nvar AsyncGunzip = /*#__PURE__*/ (function () {\n /**\n * Creates an asynchronous GUNZIP stream\n * @param cb The callback to call whenever data is deflated\n */\n function AsyncGunzip(cb) {\n this.ondata = cb;\n astrmify([\n bInflt,\n guze,\n function () { return [astrm, Inflate, Gunzip]; }\n ], this, 0, function () {\n var strm = new Gunzip();\n onmessage = astrm(strm);\n }, 9);\n }\n return AsyncGunzip;\n}());\nexport { AsyncGunzip };\nexport function gunzip(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bInflt,\n guze,\n function () { return [gunzipSync]; }\n ], function (ev) { return pbf(gunzipSync(ev.data[0])); }, 3, cb);\n}\n/**\n * Expands GZIP data\n * @param data The data to decompress\n * @param out Where to write the data. GZIP already encodes the output size, so providing this doesn't save memory.\n * @returns The decompressed version of the data\n */\nexport function gunzipSync(data, out) {\n return inflt(data.subarray(gzs(data), -8), out || new u8(gzl(data)));\n}\n/**\n * Streaming Zlib compression\n */\nvar Zlib = /*#__PURE__*/ (function () {\n function Zlib(opts, cb) {\n this.c = adler();\n this.v = 1;\n Deflate.call(this, opts, cb);\n }\n /**\n * Pushes a chunk to be zlibbed\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Zlib.prototype.push = function (chunk, final) {\n Deflate.prototype.push.call(this, chunk, final);\n };\n Zlib.prototype.p = function (c, f) {\n this.c.p(c);\n var raw = dopt(c, this.o, this.v && 2, f && 4, !f);\n if (this.v)\n zlh(raw, this.o), this.v = 0;\n if (f)\n wbytes(raw, raw.length - 4, this.c.d());\n this.ondata(raw, f);\n };\n return Zlib;\n}());\nexport { Zlib };\n/**\n * Asynchronous streaming Zlib compression\n */\nvar AsyncZlib = /*#__PURE__*/ (function () {\n function AsyncZlib(opts, cb) {\n astrmify([\n bDflt,\n zle,\n function () { return [astrm, Deflate, Zlib]; }\n ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) {\n var strm = new Zlib(ev.data);\n onmessage = astrm(strm);\n }, 10);\n }\n return AsyncZlib;\n}());\nexport { AsyncZlib };\nexport function zlib(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bDflt,\n zle,\n function () { return [zlibSync]; }\n ], function (ev) { return pbf(zlibSync(ev.data[0], ev.data[1])); }, 4, cb);\n}\n/**\n * Compress data with Zlib\n * @param data The data to compress\n * @param opts The compression options\n * @returns The zlib-compressed version of the data\n */\nexport function zlibSync(data, opts) {\n if (!opts)\n opts = {};\n var a = adler();\n a.p(data);\n var d = dopt(data, opts, 2, 4);\n return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d;\n}\n/**\n * Streaming Zlib decompression\n */\nvar Unzlib = /*#__PURE__*/ (function () {\n /**\n * Creates a Zlib decompression stream\n * @param cb The callback to call whenever data is inflated\n */\n function Unzlib(cb) {\n this.v = 1;\n Inflate.call(this, cb);\n }\n /**\n * Pushes a chunk to be unzlibbed\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Unzlib.prototype.push = function (chunk, final) {\n Inflate.prototype.e.call(this, chunk);\n if (this.v) {\n if (this.p.length < 2 && !final)\n return;\n this.p = this.p.subarray(2), this.v = 0;\n }\n if (final) {\n if (this.p.length < 4)\n err(6, 'invalid zlib data');\n this.p = this.p.subarray(0, -4);\n }\n // necessary to prevent TS from using the closure value\n // This allows for workerization to function correctly\n Inflate.prototype.c.call(this, final);\n };\n return Unzlib;\n}());\nexport { Unzlib };\n/**\n * Asynchronous streaming Zlib decompression\n */\nvar AsyncUnzlib = /*#__PURE__*/ (function () {\n /**\n * Creates an asynchronous Zlib decompression stream\n * @param cb The callback to call whenever data is deflated\n */\n function AsyncUnzlib(cb) {\n this.ondata = cb;\n astrmify([\n bInflt,\n zule,\n function () { return [astrm, Inflate, Unzlib]; }\n ], this, 0, function () {\n var strm = new Unzlib();\n onmessage = astrm(strm);\n }, 11);\n }\n return AsyncUnzlib;\n}());\nexport { AsyncUnzlib };\nexport function unzlib(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bInflt,\n zule,\n function () { return [unzlibSync]; }\n ], function (ev) { return pbf(unzlibSync(ev.data[0], gu8(ev.data[1]))); }, 5, cb);\n}\n/**\n * Expands Zlib data\n * @param data The data to decompress\n * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length.\n * @returns The decompressed version of the data\n */\nexport function unzlibSync(data, out) {\n return inflt((zlv(data), data.subarray(2, -4)), out);\n}\n// Default algorithm for compression (used because having a known output size allows faster decompression)\nexport { gzip as compress, AsyncGzip as AsyncCompress };\n// Default algorithm for compression (used because having a known output size allows faster decompression)\nexport { gzipSync as compressSync, Gzip as Compress };\n/**\n * Streaming GZIP, Zlib, or raw DEFLATE decompression\n */\nvar Decompress = /*#__PURE__*/ (function () {\n /**\n * Creates a decompression stream\n * @param cb The callback to call whenever data is decompressed\n */\n function Decompress(cb) {\n this.G = Gunzip;\n this.I = Inflate;\n this.Z = Unzlib;\n this.ondata = cb;\n }\n /**\n * Pushes a chunk to be decompressed\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Decompress.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n if (!this.s) {\n if (this.p && this.p.length) {\n var n = new u8(this.p.length + chunk.length);\n n.set(this.p), n.set(chunk, this.p.length);\n }\n else\n this.p = chunk;\n if (this.p.length > 2) {\n var _this_1 = this;\n var cb = function () { _this_1.ondata.apply(_this_1, arguments); };\n this.s = (this.p[0] == 31 && this.p[1] == 139 && this.p[2] == 8)\n ? new this.G(cb)\n : ((this.p[0] & 15) != 8 || (this.p[0] >> 4) > 7 || ((this.p[0] << 8 | this.p[1]) % 31))\n ? new this.I(cb)\n : new this.Z(cb);\n this.s.push(this.p, final);\n this.p = null;\n }\n }\n else\n this.s.push(chunk, final);\n };\n return Decompress;\n}());\nexport { Decompress };\n/**\n * Asynchronous streaming GZIP, Zlib, or raw DEFLATE decompression\n */\nvar AsyncDecompress = /*#__PURE__*/ (function () {\n /**\n * Creates an asynchronous decompression stream\n * @param cb The callback to call whenever data is decompressed\n */\n function AsyncDecompress(cb) {\n this.G = AsyncGunzip;\n this.I = AsyncInflate;\n this.Z = AsyncUnzlib;\n this.ondata = cb;\n }\n /**\n * Pushes a chunk to be decompressed\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n AsyncDecompress.prototype.push = function (chunk, final) {\n Decompress.prototype.push.call(this, chunk, final);\n };\n return AsyncDecompress;\n}());\nexport { AsyncDecompress };\nexport function decompress(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return (data[0] == 31 && data[1] == 139 && data[2] == 8)\n ? gunzip(data, opts, cb)\n : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31))\n ? inflate(data, opts, cb)\n : unzlib(data, opts, cb);\n}\n/**\n * Expands compressed GZIP, Zlib, or raw DEFLATE data, automatically detecting the format\n * @param data The data to decompress\n * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length.\n * @returns The decompressed version of the data\n */\nexport function decompressSync(data, out) {\n return (data[0] == 31 && data[1] == 139 && data[2] == 8)\n ? gunzipSync(data, out)\n : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31))\n ? inflateSync(data, out)\n : unzlibSync(data, out);\n}\n// flatten a directory structure\nvar fltn = function (d, p, t, o) {\n for (var k in d) {\n var val = d[k], n = p + k, op = o;\n if (Array.isArray(val))\n op = mrg(o, val[1]), val = val[0];\n if (val instanceof u8)\n t[n] = [val, op];\n else {\n t[n += '/'] = [new u8(0), op];\n fltn(val, n, t, o);\n }\n }\n};\n// text encoder\nvar te = typeof TextEncoder != 'undefined' && /*#__PURE__*/ new TextEncoder();\n// text decoder\nvar td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder();\n// text decoder stream\nvar tds = 0;\ntry {\n td.decode(et, { stream: true });\n tds = 1;\n}\ncatch (e) { }\n// decode UTF8\nvar dutf8 = function (d) {\n for (var r = '', i = 0;;) {\n var c = d[i++];\n var eb = (c > 127) + (c > 223) + (c > 239);\n if (i + eb > d.length)\n return [r, slc(d, i - 1)];\n if (!eb)\n r += String.fromCharCode(c);\n else if (eb == 3) {\n c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)) - 65536,\n r += String.fromCharCode(55296 | (c >> 10), 56320 | (c & 1023));\n }\n else if (eb & 1)\n r += String.fromCharCode((c & 31) << 6 | (d[i++] & 63));\n else\n r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63));\n }\n};\n/**\n * Streaming UTF-8 decoding\n */\nvar DecodeUTF8 = /*#__PURE__*/ (function () {\n /**\n * Creates a UTF-8 decoding stream\n * @param cb The callback to call whenever data is decoded\n */\n function DecodeUTF8(cb) {\n this.ondata = cb;\n if (tds)\n this.t = new TextDecoder();\n else\n this.p = et;\n }\n /**\n * Pushes a chunk to be decoded from UTF-8 binary\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n DecodeUTF8.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n final = !!final;\n if (this.t) {\n this.ondata(this.t.decode(chunk, { stream: true }), final);\n if (final) {\n if (this.t.decode().length)\n err(8);\n this.t = null;\n }\n return;\n }\n if (!this.p)\n err(4);\n var dat = new u8(this.p.length + chunk.length);\n dat.set(this.p);\n dat.set(chunk, this.p.length);\n var _a = dutf8(dat), ch = _a[0], np = _a[1];\n if (final) {\n if (np.length)\n err(8);\n this.p = null;\n }\n else\n this.p = np;\n this.ondata(ch, final);\n };\n return DecodeUTF8;\n}());\nexport { DecodeUTF8 };\n/**\n * Streaming UTF-8 encoding\n */\nvar EncodeUTF8 = /*#__PURE__*/ (function () {\n /**\n * Creates a UTF-8 decoding stream\n * @param cb The callback to call whenever data is encoded\n */\n function EncodeUTF8(cb) {\n this.ondata = cb;\n }\n /**\n * Pushes a chunk to be encoded to UTF-8\n * @param chunk The string data to push\n * @param final Whether this is the last chunk\n */\n EncodeUTF8.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n if (this.d)\n err(4);\n this.ondata(strToU8(chunk), this.d = final || false);\n };\n return EncodeUTF8;\n}());\nexport { EncodeUTF8 };\n/**\n * Converts a string into a Uint8Array for use with compression/decompression methods\n * @param str The string to encode\n * @param latin1 Whether or not to interpret the data as Latin-1. This should\n * not need to be true unless decoding a binary string.\n * @returns The string encoded in UTF-8/Latin-1 binary\n */\nexport function strToU8(str, latin1) {\n if (latin1) {\n var ar_1 = new u8(str.length);\n for (var i = 0; i < str.length; ++i)\n ar_1[i] = str.charCodeAt(i);\n return ar_1;\n }\n if (te)\n return te.encode(str);\n var l = str.length;\n var ar = new u8(str.length + (str.length >> 1));\n var ai = 0;\n var w = function (v) { ar[ai++] = v; };\n for (var i = 0; i < l; ++i) {\n if (ai + 5 > ar.length) {\n var n = new u8(ai + 8 + ((l - i) << 1));\n n.set(ar);\n ar = n;\n }\n var c = str.charCodeAt(i);\n if (c < 128 || latin1)\n w(c);\n else if (c < 2048)\n w(192 | (c >> 6)), w(128 | (c & 63));\n else if (c > 55295 && c < 57344)\n c = 65536 + (c & 1023 << 10) | (str.charCodeAt(++i) & 1023),\n w(240 | (c >> 18)), w(128 | ((c >> 12) & 63)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63));\n else\n w(224 | (c >> 12)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63));\n }\n return slc(ar, 0, ai);\n}\n/**\n * Converts a Uint8Array to a string\n * @param dat The data to decode to string\n * @param latin1 Whether or not to interpret the data as Latin-1. This should\n * not need to be true unless encoding to binary string.\n * @returns The original UTF-8/Latin-1 string\n */\nexport function strFromU8(dat, latin1) {\n if (latin1) {\n var r = '';\n for (var i = 0; i < dat.length; i += 16384)\n r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384));\n return r;\n }\n else if (td)\n return td.decode(dat);\n else {\n var _a = dutf8(dat), out = _a[0], ext = _a[1];\n if (ext.length)\n err(8);\n return out;\n }\n}\n;\n// deflate bit flag\nvar dbf = function (l) { return l == 1 ? 3 : l < 6 ? 2 : l == 9 ? 1 : 0; };\n// skip local zip header\nvar slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); };\n// read zip header\nvar zh = function (d, b, z) {\n var fnl = b2(d, b + 28), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20);\n var _a = z && bs == 4294967295 ? z64e(d, es) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a[0], su = _a[1], off = _a[2];\n return [b2(d, b + 10), sc, su, fn, es + b2(d, b + 30) + b2(d, b + 32), off];\n};\n// read zip64 extra field\nvar z64e = function (d, b) {\n for (; b2(d, b) != 1; b += 4 + b2(d, b + 2))\n ;\n return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)];\n};\n// extra field length\nvar exfl = function (ex) {\n var le = 0;\n if (ex) {\n for (var k in ex) {\n var l = ex[k].length;\n if (l > 65535)\n err(9);\n le += l + 4;\n }\n }\n return le;\n};\n// write zip header\nvar wzh = function (d, b, f, fn, u, c, ce, co) {\n var fl = fn.length, ex = f.extra, col = co && co.length;\n var exl = exfl(ex);\n wbytes(d, b, ce != null ? 0x2014B50 : 0x4034B50), b += 4;\n if (ce != null)\n d[b++] = 20, d[b++] = f.os;\n d[b] = 20, b += 2; // spec compliance? what's that?\n d[b++] = (f.flag << 1) | (c < 0 && 8), d[b++] = u && 8;\n d[b++] = f.compression & 255, d[b++] = f.compression >> 8;\n var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980;\n if (y < 0 || y > 119)\n err(10);\n wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >>> 1)), b += 4;\n if (c != -1) {\n wbytes(d, b, f.crc);\n wbytes(d, b + 4, c < 0 ? -c - 2 : c);\n wbytes(d, b + 8, f.size);\n }\n wbytes(d, b + 12, fl);\n wbytes(d, b + 14, exl), b += 16;\n if (ce != null) {\n wbytes(d, b, col);\n wbytes(d, b + 6, f.attrs);\n wbytes(d, b + 10, ce), b += 14;\n }\n d.set(fn, b);\n b += fl;\n if (exl) {\n for (var k in ex) {\n var exf = ex[k], l = exf.length;\n wbytes(d, b, +k);\n wbytes(d, b + 2, l);\n d.set(exf, b + 4), b += 4 + l;\n }\n }\n if (col)\n d.set(co, b), b += col;\n return b;\n};\n// write zip footer (end of central directory)\nvar wzf = function (o, b, c, d, e) {\n wbytes(o, b, 0x6054B50); // skip disk\n wbytes(o, b + 8, c);\n wbytes(o, b + 10, c);\n wbytes(o, b + 12, d);\n wbytes(o, b + 16, e);\n};\n/**\n * A pass-through stream to keep data uncompressed in a ZIP archive.\n */\nvar ZipPassThrough = /*#__PURE__*/ (function () {\n /**\n * Creates a pass-through stream that can be added to ZIP archives\n * @param filename The filename to associate with this data stream\n */\n function ZipPassThrough(filename) {\n this.filename = filename;\n this.c = crc();\n this.size = 0;\n this.compression = 0;\n }\n /**\n * Processes a chunk and pushes to the output stream. You can override this\n * method in a subclass for custom behavior, but by default this passes\n * the data through. You must call this.ondata(err, chunk, final) at some\n * point in this method.\n * @param chunk The chunk to process\n * @param final Whether this is the last chunk\n */\n ZipPassThrough.prototype.process = function (chunk, final) {\n this.ondata(null, chunk, final);\n };\n /**\n * Pushes a chunk to be added. If you are subclassing this with a custom\n * compression algorithm, note that you must push data from the source\n * file only, pre-compression.\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n ZipPassThrough.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n this.c.p(chunk);\n this.size += chunk.length;\n if (final)\n this.crc = this.c.d();\n this.process(chunk, final || false);\n };\n return ZipPassThrough;\n}());\nexport { ZipPassThrough };\n// I don't extend because TypeScript extension adds 1kB of runtime bloat\n/**\n * Streaming DEFLATE compression for ZIP archives. Prefer using AsyncZipDeflate\n * for better performance\n */\nvar ZipDeflate = /*#__PURE__*/ (function () {\n /**\n * Creates a DEFLATE stream that can be added to ZIP archives\n * @param filename The filename to associate with this data stream\n * @param opts The compression options\n */\n function ZipDeflate(filename, opts) {\n var _this_1 = this;\n if (!opts)\n opts = {};\n ZipPassThrough.call(this, filename);\n this.d = new Deflate(opts, function (dat, final) {\n _this_1.ondata(null, dat, final);\n });\n this.compression = 8;\n this.flag = dbf(opts.level);\n }\n ZipDeflate.prototype.process = function (chunk, final) {\n try {\n this.d.push(chunk, final);\n }\n catch (e) {\n this.ondata(e, null, final);\n }\n };\n /**\n * Pushes a chunk to be deflated\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n ZipDeflate.prototype.push = function (chunk, final) {\n ZipPassThrough.prototype.push.call(this, chunk, final);\n };\n return ZipDeflate;\n}());\nexport { ZipDeflate };\n/**\n * Asynchronous streaming DEFLATE compression for ZIP archives\n */\nvar AsyncZipDeflate = /*#__PURE__*/ (function () {\n /**\n * Creates a DEFLATE stream that can be added to ZIP archives\n * @param filename The filename to associate with this data stream\n * @param opts The compression options\n */\n function AsyncZipDeflate(filename, opts) {\n var _this_1 = this;\n if (!opts)\n opts = {};\n ZipPassThrough.call(this, filename);\n this.d = new AsyncDeflate(opts, function (err, dat, final) {\n _this_1.ondata(err, dat, final);\n });\n this.compression = 8;\n this.flag = dbf(opts.level);\n this.terminate = this.d.terminate;\n }\n AsyncZipDeflate.prototype.process = function (chunk, final) {\n this.d.push(chunk, final);\n };\n /**\n * Pushes a chunk to be deflated\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n AsyncZipDeflate.prototype.push = function (chunk, final) {\n ZipPassThrough.prototype.push.call(this, chunk, final);\n };\n return AsyncZipDeflate;\n}());\nexport { AsyncZipDeflate };\n// TODO: Better tree shaking\n/**\n * A zippable archive to which files can incrementally be added\n */\nvar Zip = /*#__PURE__*/ (function () {\n /**\n * Creates an empty ZIP archive to which files can be added\n * @param cb The callback to call whenever data for the generated ZIP archive\n * is available\n */\n function Zip(cb) {\n this.ondata = cb;\n this.u = [];\n this.d = 1;\n }\n /**\n * Adds a file to the ZIP archive\n * @param file The file stream to add\n */\n Zip.prototype.add = function (file) {\n var _this_1 = this;\n if (!this.ondata)\n err(5);\n // finishing or finished\n if (this.d & 2)\n this.ondata(err(4 + (this.d & 1) * 8, 0, 1), null, false);\n else {\n var f = strToU8(file.filename), fl_1 = f.length;\n var com = file.comment, o = com && strToU8(com);\n var u = fl_1 != file.filename.length || (o && (com.length != o.length));\n var hl_1 = fl_1 + exfl(file.extra) + 30;\n if (fl_1 > 65535)\n this.ondata(err(11, 0, 1), null, false);\n var header = new u8(hl_1);\n wzh(header, 0, file, f, u, -1);\n var chks_1 = [header];\n var pAll_1 = function () {\n for (var _i = 0, chks_2 = chks_1; _i < chks_2.length; _i++) {\n var chk = chks_2[_i];\n _this_1.ondata(null, chk, false);\n }\n chks_1 = [];\n };\n var tr_1 = this.d;\n this.d = 0;\n var ind_1 = this.u.length;\n var uf_1 = mrg(file, {\n f: f,\n u: u,\n o: o,\n t: function () {\n if (file.terminate)\n file.terminate();\n },\n r: function () {\n pAll_1();\n if (tr_1) {\n var nxt = _this_1.u[ind_1 + 1];\n if (nxt)\n nxt.r();\n else\n _this_1.d = 1;\n }\n tr_1 = 1;\n }\n });\n var cl_1 = 0;\n file.ondata = function (err, dat, final) {\n if (err) {\n _this_1.ondata(err, dat, final);\n _this_1.terminate();\n }\n else {\n cl_1 += dat.length;\n chks_1.push(dat);\n if (final) {\n var dd = new u8(16);\n wbytes(dd, 0, 0x8074B50);\n wbytes(dd, 4, file.crc);\n wbytes(dd, 8, cl_1);\n wbytes(dd, 12, file.size);\n chks_1.push(dd);\n uf_1.c = cl_1, uf_1.b = hl_1 + cl_1 + 16, uf_1.crc = file.crc, uf_1.size = file.size;\n if (tr_1)\n uf_1.r();\n tr_1 = 1;\n }\n else if (tr_1)\n pAll_1();\n }\n };\n this.u.push(uf_1);\n }\n };\n /**\n * Ends the process of adding files and prepares to emit the final chunks.\n * This *must* be called after adding all desired files for the resulting\n * ZIP file to work properly.\n */\n Zip.prototype.end = function () {\n var _this_1 = this;\n if (this.d & 2) {\n this.ondata(err(4 + (this.d & 1) * 8, 0, 1), null, true);\n return;\n }\n if (this.d)\n this.e();\n else\n this.u.push({\n r: function () {\n if (!(_this_1.d & 1))\n return;\n _this_1.u.splice(-1, 1);\n _this_1.e();\n },\n t: function () { }\n });\n this.d = 3;\n };\n Zip.prototype.e = function () {\n var bt = 0, l = 0, tl = 0;\n for (var _i = 0, _a = this.u; _i < _a.length; _i++) {\n var f = _a[_i];\n tl += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0);\n }\n var out = new u8(tl + 22);\n for (var _b = 0, _c = this.u; _b < _c.length; _b++) {\n var f = _c[_b];\n wzh(out, bt, f, f.f, f.u, -f.c - 2, l, f.o);\n bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b;\n }\n wzf(out, bt, this.u.length, tl, l);\n this.ondata(null, out, true);\n this.d = 2;\n };\n /**\n * A method to terminate any internal workers used by the stream. Subsequent\n * calls to add() will fail.\n */\n Zip.prototype.terminate = function () {\n for (var _i = 0, _a = this.u; _i < _a.length; _i++) {\n var f = _a[_i];\n f.t();\n }\n this.d = 2;\n };\n return Zip;\n}());\nexport { Zip };\nexport function zip(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n var r = {};\n fltn(data, '', r, opts);\n var k = Object.keys(r);\n var lft = k.length, o = 0, tot = 0;\n var slft = lft, files = new Array(lft);\n var term = [];\n var tAll = function () {\n for (var i = 0; i < term.length; ++i)\n term[i]();\n };\n var cbd = function (a, b) {\n mt(function () { cb(a, b); });\n };\n mt(function () { cbd = cb; });\n var cbf = function () {\n var out = new u8(tot + 22), oe = o, cdl = tot - o;\n tot = 0;\n for (var i = 0; i < slft; ++i) {\n var f = files[i];\n try {\n var l = f.c.length;\n wzh(out, tot, f, f.f, f.u, l);\n var badd = 30 + f.f.length + exfl(f.extra);\n var loc = tot + badd;\n out.set(f.c, loc);\n wzh(out, o, f, f.f, f.u, l, tot, f.m), o += 16 + badd + (f.m ? f.m.length : 0), tot = loc + l;\n }\n catch (e) {\n return cbd(e, null);\n }\n }\n wzf(out, o, files.length, cdl, oe);\n cbd(null, out);\n };\n if (!lft)\n cbf();\n var _loop_1 = function (i) {\n var fn = k[i];\n var _a = r[fn], file = _a[0], p = _a[1];\n var c = crc(), size = file.length;\n c.p(file);\n var f = strToU8(fn), s = f.length;\n var com = p.comment, m = com && strToU8(com), ms = m && m.length;\n var exl = exfl(p.extra);\n var compression = p.level == 0 ? 0 : 8;\n var cbl = function (e, d) {\n if (e) {\n tAll();\n cbd(e, null);\n }\n else {\n var l = d.length;\n files[i] = mrg(p, {\n size: size,\n crc: c.d(),\n c: d,\n f: f,\n m: m,\n u: s != fn.length || (m && (com.length != ms)),\n compression: compression\n });\n o += 30 + s + exl + l;\n tot += 76 + 2 * (s + exl) + (ms || 0) + l;\n if (!--lft)\n cbf();\n }\n };\n if (s > 65535)\n cbl(err(11, 0, 1), null);\n if (!compression)\n cbl(null, file);\n else if (size < 160000) {\n try {\n cbl(null, deflateSync(file, p));\n }\n catch (e) {\n cbl(e, null);\n }\n }\n else\n term.push(deflate(file, p, cbl));\n };\n // Cannot use lft because it can decrease\n for (var i = 0; i < slft; ++i) {\n _loop_1(i);\n }\n return tAll;\n}\n/**\n * Synchronously creates a ZIP file. Prefer using `zip` for better performance\n * with more than one file.\n * @param data The directory structure for the ZIP archive\n * @param opts The main options, merged with per-file options\n * @returns The generated ZIP archive\n */\nexport function zipSync(data, opts) {\n if (!opts)\n opts = {};\n var r = {};\n var files = [];\n fltn(data, '', r, opts);\n var o = 0;\n var tot = 0;\n for (var fn in r) {\n var _a = r[fn], file = _a[0], p = _a[1];\n var compression = p.level == 0 ? 0 : 8;\n var f = strToU8(fn), s = f.length;\n var com = p.comment, m = com && strToU8(com), ms = m && m.length;\n var exl = exfl(p.extra);\n if (s > 65535)\n err(11);\n var d = compression ? deflateSync(file, p) : file, l = d.length;\n var c = crc();\n c.p(file);\n files.push(mrg(p, {\n size: file.length,\n crc: c.d(),\n c: d,\n f: f,\n m: m,\n u: s != fn.length || (m && (com.length != ms)),\n o: o,\n compression: compression\n }));\n o += 30 + s + exl + l;\n tot += 76 + 2 * (s + exl) + (ms || 0) + l;\n }\n var out = new u8(tot + 22), oe = o, cdl = tot - o;\n for (var i = 0; i < files.length; ++i) {\n var f = files[i];\n wzh(out, f.o, f, f.f, f.u, f.c.length);\n var badd = 30 + f.f.length + exfl(f.extra);\n out.set(f.c, f.o + badd);\n wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0);\n }\n wzf(out, o, files.length, cdl, oe);\n return out;\n}\n/**\n * Streaming pass-through decompression for ZIP archives\n */\nvar UnzipPassThrough = /*#__PURE__*/ (function () {\n function UnzipPassThrough() {\n }\n UnzipPassThrough.prototype.push = function (data, final) {\n this.ondata(null, data, final);\n };\n UnzipPassThrough.compression = 0;\n return UnzipPassThrough;\n}());\nexport { UnzipPassThrough };\n/**\n * Streaming DEFLATE decompression for ZIP archives. Prefer AsyncZipInflate for\n * better performance.\n */\nvar UnzipInflate = /*#__PURE__*/ (function () {\n /**\n * Creates a DEFLATE decompression that can be used in ZIP archives\n */\n function UnzipInflate() {\n var _this_1 = this;\n this.i = new Inflate(function (dat, final) {\n _this_1.ondata(null, dat, final);\n });\n }\n UnzipInflate.prototype.push = function (data, final) {\n try {\n this.i.push(data, final);\n }\n catch (e) {\n this.ondata(e, null, final);\n }\n };\n UnzipInflate.compression = 8;\n return UnzipInflate;\n}());\nexport { UnzipInflate };\n/**\n * Asynchronous streaming DEFLATE decompression for ZIP archives\n */\nvar AsyncUnzipInflate = /*#__PURE__*/ (function () {\n /**\n * Creates a DEFLATE decompression that can be used in ZIP archives\n */\n function AsyncUnzipInflate(_, sz) {\n var _this_1 = this;\n if (sz < 320000) {\n this.i = new Inflate(function (dat, final) {\n _this_1.ondata(null, dat, final);\n });\n }\n else {\n this.i = new AsyncInflate(function (err, dat, final) {\n _this_1.ondata(err, dat, final);\n });\n this.terminate = this.i.terminate;\n }\n }\n AsyncUnzipInflate.prototype.push = function (data, final) {\n if (this.i.terminate)\n data = slc(data, 0);\n this.i.push(data, final);\n };\n AsyncUnzipInflate.compression = 8;\n return AsyncUnzipInflate;\n}());\nexport { AsyncUnzipInflate };\n/**\n * A ZIP archive decompression stream that emits files as they are discovered\n */\nvar Unzip = /*#__PURE__*/ (function () {\n /**\n * Creates a ZIP decompression stream\n * @param cb The callback to call whenever a file in the ZIP archive is found\n */\n function Unzip(cb) {\n this.onfile = cb;\n this.k = [];\n this.o = {\n 0: UnzipPassThrough\n };\n this.p = et;\n }\n /**\n * Pushes a chunk to be unzipped\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Unzip.prototype.push = function (chunk, final) {\n var _this_1 = this;\n if (!this.onfile)\n err(5);\n if (!this.p)\n err(4);\n if (this.c > 0) {\n var len = Math.min(this.c, chunk.length);\n var toAdd = chunk.subarray(0, len);\n this.c -= len;\n if (this.d)\n this.d.push(toAdd, !this.c);\n else\n this.k[0].push(toAdd);\n chunk = chunk.subarray(len);\n if (chunk.length)\n return this.push(chunk, final);\n }\n else {\n var f = 0, i = 0, is = void 0, buf = void 0;\n if (!this.p.length)\n buf = chunk;\n else if (!chunk.length)\n buf = this.p;\n else {\n buf = new u8(this.p.length + chunk.length);\n buf.set(this.p), buf.set(chunk, this.p.length);\n }\n var l = buf.length, oc = this.c, add = oc && this.d;\n var _loop_2 = function () {\n var _a;\n var sig = b4(buf, i);\n if (sig == 0x4034B50) {\n f = 1, is = i;\n this_1.d = null;\n this_1.c = 0;\n var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28);\n if (l > i + 30 + fnl + es) {\n var chks_3 = [];\n this_1.k.unshift(chks_3);\n f = 2;\n var sc_1 = b4(buf, i + 18), su_1 = b4(buf, i + 22);\n var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u);\n if (sc_1 == 4294967295) {\n _a = dd ? [-2] : z64e(buf, i), sc_1 = _a[0], su_1 = _a[1];\n }\n else if (dd)\n sc_1 = -1;\n i += es;\n this_1.c = sc_1;\n var d_1;\n var file_1 = {\n name: fn_1,\n compression: cmp_1,\n start: function () {\n if (!file_1.ondata)\n err(5);\n if (!sc_1)\n file_1.ondata(null, et, true);\n else {\n var ctr = _this_1.o[cmp_1];\n if (!ctr)\n file_1.ondata(err(14, 'unknown compression type ' + cmp_1, 1), null, false);\n d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1);\n d_1.ondata = function (err, dat, final) { file_1.ondata(err, dat, final); };\n for (var _i = 0, chks_4 = chks_3; _i < chks_4.length; _i++) {\n var dat = chks_4[_i];\n d_1.push(dat, false);\n }\n if (_this_1.k[0] == chks_3 && _this_1.c)\n _this_1.d = d_1;\n else\n d_1.push(et, true);\n }\n },\n terminate: function () {\n if (d_1 && d_1.terminate)\n d_1.terminate();\n }\n };\n if (sc_1 >= 0)\n file_1.size = sc_1, file_1.originalSize = su_1;\n this_1.onfile(file_1);\n }\n return \"break\";\n }\n else if (oc) {\n if (sig == 0x8074B50) {\n is = i += 12 + (oc == -2 && 8), f = 3, this_1.c = 0;\n return \"break\";\n }\n else if (sig == 0x2014B50) {\n is = i -= 4, f = 3, this_1.c = 0;\n return \"break\";\n }\n }\n };\n var this_1 = this;\n for (; i < l - 4; ++i) {\n var state_1 = _loop_2();\n if (state_1 === \"break\")\n break;\n }\n this.p = et;\n if (oc < 0) {\n var dat = f ? buf.subarray(0, is - 12 - (oc == -2 && 8) - (b4(buf, is - 16) == 0x8074B50 && 4)) : buf.subarray(0, i);\n if (add)\n add.push(dat, !!f);\n else\n this.k[+(f == 2)].push(dat);\n }\n if (f & 2)\n return this.push(buf.subarray(i), final);\n this.p = buf.subarray(i);\n }\n if (final) {\n if (this.c)\n err(13);\n this.p = null;\n }\n };\n /**\n * Registers a decoder with the stream, allowing for files compressed with\n * the compression type provided to be expanded correctly\n * @param decoder The decoder constructor\n */\n Unzip.prototype.register = function (decoder) {\n this.o[decoder.compression] = decoder;\n };\n return Unzip;\n}());\nexport { Unzip };\nvar mt = typeof queueMicrotask == 'function' ? queueMicrotask : typeof setTimeout == 'function' ? setTimeout : function (fn) { fn(); };\nexport function unzip(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n var term = [];\n var tAll = function () {\n for (var i = 0; i < term.length; ++i)\n term[i]();\n };\n var files = {};\n var cbd = function (a, b) {\n mt(function () { cb(a, b); });\n };\n mt(function () { cbd = cb; });\n var e = data.length - 22;\n for (; b4(data, e) != 0x6054B50; --e) {\n if (!e || data.length - e > 65558) {\n cbd(err(13, 0, 1), null);\n return tAll;\n }\n }\n ;\n var lft = b2(data, e + 8);\n if (lft) {\n var c = lft;\n var o = b4(data, e + 16);\n var z = o == 4294967295 || c == 65535;\n if (z) {\n var ze = b4(data, e - 12);\n z = b4(data, ze) == 0x6064B50;\n if (z) {\n c = lft = b4(data, ze + 32);\n o = b4(data, ze + 48);\n }\n }\n var fltr = opts && opts.filter;\n var _loop_3 = function (i) {\n var _a = zh(data, o, z), c_1 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off);\n o = no;\n var cbl = function (e, d) {\n if (e) {\n tAll();\n cbd(e, null);\n }\n else {\n if (d)\n files[fn] = d;\n if (!--lft)\n cbd(null, files);\n }\n };\n if (!fltr || fltr({\n name: fn,\n size: sc,\n originalSize: su,\n compression: c_1\n })) {\n if (!c_1)\n cbl(null, slc(data, b, b + sc));\n else if (c_1 == 8) {\n var infl = data.subarray(b, b + sc);\n if (sc < 320000) {\n try {\n cbl(null, inflateSync(infl, new u8(su)));\n }\n catch (e) {\n cbl(e, null);\n }\n }\n else\n term.push(inflate(infl, { size: su }, cbl));\n }\n else\n cbl(err(14, 'unknown compression type ' + c_1, 1), null);\n }\n else\n cbl(null, null);\n };\n for (var i = 0; i < c; ++i) {\n _loop_3(i);\n }\n }\n else\n cbd(null, {});\n return tAll;\n}\n/**\n * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better\n * performance with more than one file.\n * @param data The raw compressed ZIP file\n * @param opts The ZIP extraction options\n * @returns The decompressed files\n */\nexport function unzipSync(data, opts) {\n var files = {};\n var e = data.length - 22;\n for (; b4(data, e) != 0x6054B50; --e) {\n if (!e || data.length - e > 65558)\n err(13);\n }\n ;\n var c = b2(data, e + 8);\n if (!c)\n return {};\n var o = b4(data, e + 16);\n var z = o == 4294967295 || c == 65535;\n if (z) {\n var ze = b4(data, e - 12);\n z = b4(data, ze) == 0x6064B50;\n if (z) {\n c = b4(data, ze + 32);\n o = b4(data, ze + 48);\n }\n }\n var fltr = opts && opts.filter;\n for (var i = 0; i < c; ++i) {\n var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off);\n o = no;\n if (!fltr || fltr({\n name: fn,\n size: sc,\n originalSize: su,\n compression: c_2\n })) {\n if (!c_2)\n files[fn] = slc(data, b, b + sc);\n else if (c_2 == 8)\n files[fn] = inflateSync(data.subarray(b, b + sc), new u8(su));\n else\n err(14, 'unknown compression type ' + c_2);\n }\n }\n return files;\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport * as DataStore from \"@api/DataStore\";\nimport { showNotification } from \"@api/Notifications\";\nimport { Settings } from \"@api/Settings\";\nimport { OAuth2AuthorizeModal, UserStore } from \"@webpack/common\";\n\nimport { Logger } from \"./Logger\";\nimport { openModal } from \"./modal\";\n\nexport const cloudLogger = new Logger(\"Cloud\", \"#39b7e0\");\nexport const getCloudUrl = () => new URL(Settings.cloud.url);\n\nconst cloudUrlOrigin = () => getCloudUrl().origin;\nconst getUserId = () => {\n const id = UserStore.getCurrentUser()?.id;\n if (!id) throw new Error(\"User not yet logged in\");\n return id;\n};\n\nexport async function getAuthorization() {\n const secrets = await DataStore.get>(\"Vencord_cloudSecret\") ?? {};\n\n const origin = cloudUrlOrigin();\n\n // we need to migrate from the old format here\n if (secrets[origin]) {\n await DataStore.update>(\"Vencord_cloudSecret\", secrets => {\n secrets ??= {};\n // use the current user ID\n secrets[`${origin}:${getUserId()}`] = secrets[origin];\n delete secrets[origin];\n return secrets;\n });\n\n // since this doesn't update the original object, we'll early return the existing authorization\n return secrets[origin];\n }\n\n return secrets[`${origin}:${getUserId()}`];\n}\n\nasync function setAuthorization(secret: string) {\n await DataStore.update>(\"Vencord_cloudSecret\", secrets => {\n secrets ??= {};\n secrets[`${cloudUrlOrigin()}:${getUserId()}`] = secret;\n return secrets;\n });\n}\n\nexport async function deauthorizeCloud() {\n await DataStore.update>(\"Vencord_cloudSecret\", secrets => {\n secrets ??= {};\n delete secrets[`${cloudUrlOrigin()}:${getUserId()}`];\n return secrets;\n });\n}\n\nexport async function authorizeCloud() {\n if (await getAuthorization() !== undefined) {\n Settings.cloud.authenticated = true;\n return;\n }\n\n try {\n const oauthConfiguration = await fetch(new URL(\"/v1/oauth/settings\", getCloudUrl()));\n var { clientId, redirectUri } = await oauthConfiguration.json();\n } catch {\n showNotification({\n title: \"Cloud Integration\",\n body: \"Setup failed (couldn't retrieve OAuth configuration).\"\n });\n Settings.cloud.authenticated = false;\n return;\n }\n\n openModal((props: any) => {\n if (!location) {\n Settings.cloud.authenticated = false;\n return;\n }\n\n try {\n const res = await fetch(location, {\n headers: { Accept: \"application/json\" }\n });\n const { secret } = await res.json();\n if (secret) {\n cloudLogger.info(\"Authorized with secret\");\n await setAuthorization(secret);\n showNotification({\n title: \"Cloud Integration\",\n body: \"Cloud integrations enabled!\"\n });\n Settings.cloud.authenticated = true;\n } else {\n showNotification({\n title: \"Cloud Integration\",\n body: \"Setup failed (no secret returned?).\"\n });\n Settings.cloud.authenticated = false;\n }\n } catch (e: any) {\n cloudLogger.error(\"Failed to authorize\", e);\n showNotification({\n title: \"Cloud Integration\",\n body: `Setup failed (${e.toString()}).`\n });\n Settings.cloud.authenticated = false;\n }\n }\n }\n />);\n}\n\nexport async function getCloudAuth() {\n const secret = await getAuthorization();\n\n return window.btoa(`${secret}:${getUserId()}`);\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nexport function relaunch() {\n if (IS_DISCORD_DESKTOP)\n window.DiscordNative.app.relaunch();\n else\n window.VesktopNative.app.relaunch();\n}\n\nexport function showItemInFolder(path: string) {\n if (IS_DISCORD_DESKTOP)\n window.DiscordNative.fileManager.showItemInFolder(path);\n else\n window.VesktopNative.fileManager.showItemInFolder(path);\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\n/**\n * Prompts the user to save a file to their system\n * @param file The file to save\n */\nexport function saveFile(file: File) {\n const a = document.createElement(\"a\");\n a.href = URL.createObjectURL(file);\n a.download = file.name;\n\n document.body.appendChild(a);\n a.click();\n setImmediate(() => {\n URL.revokeObjectURL(a.href);\n document.body.removeChild(a);\n });\n}\n\n/**\n * Prompts the user to choose a file from their system\n * @param mimeTypes A comma separated list of mime types to accept, see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers\n * @returns A promise that resolves to the chosen file or null if the user cancels\n */\nexport function chooseFile(mimeTypes: string) {\n return new Promise(resolve => {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.style.display = \"none\";\n input.accept = mimeTypes;\n input.onchange = async () => {\n resolve(input.files?.[0] ?? null);\n };\n\n document.body.appendChild(input);\n input.click();\n setImmediate(() => document.body.removeChild(input));\n });\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { showNotification } from \"@api/Notifications\";\nimport { PlainSettings, Settings } from \"@api/Settings\";\nimport { Toasts } from \"@webpack/common\";\nimport { deflateSync, inflateSync } from \"fflate\";\n\nimport { getCloudAuth, getCloudUrl } from \"./cloud\";\nimport { Logger } from \"./Logger\";\nimport { relaunch } from \"./native\";\nimport { chooseFile, saveFile } from \"./web\";\n\nexport async function importSettings(data: string) {\n try {\n var parsed = JSON.parse(data);\n } catch (err) {\n console.log(data);\n throw new Error(\"Failed to parse JSON: \" + String(err));\n }\n\n if (\"settings\" in parsed && \"quickCss\" in parsed) {\n Object.assign(PlainSettings, parsed.settings);\n await VencordNative.settings.set(parsed.settings);\n await VencordNative.quickCss.set(parsed.quickCss);\n } else\n throw new Error(\"Invalid Settings. Is this even a Vencord Settings file?\");\n}\n\nexport async function exportSettings({ minify }: { minify?: boolean; } = {}) {\n const settings = VencordNative.settings.get();\n const quickCss = await VencordNative.quickCss.get();\n return JSON.stringify({ settings, quickCss }, null, minify ? undefined : 4);\n}\n\nexport async function downloadSettingsBackup() {\n const filename = \"vencord-settings-backup.json\";\n const backup = await exportSettings();\n const data = new TextEncoder().encode(backup);\n\n if (IS_DISCORD_DESKTOP) {\n DiscordNative.fileManager.saveWithDialog(data, filename);\n } else {\n saveFile(new File([data], filename, { type: \"application/json\" }));\n }\n}\n\nconst toast = (type: number, message: string) =>\n Toasts.show({\n type,\n message,\n id: Toasts.genId()\n });\n\nconst toastSuccess = () =>\n toast(Toasts.Type.SUCCESS, \"Settings successfully imported. Restart to apply changes!\");\n\nconst toastFailure = (err: any) =>\n toast(Toasts.Type.FAILURE, `Failed to import settings: ${String(err)}`);\n\nexport async function uploadSettingsBackup(showToast = true): Promise {\n if (IS_DISCORD_DESKTOP) {\n const [file] = await DiscordNative.fileManager.openFiles({\n filters: [\n { name: \"Vencord Settings Backup\", extensions: [\"json\"] },\n { name: \"all\", extensions: [\"*\"] }\n ]\n });\n\n if (file) {\n try {\n await importSettings(new TextDecoder().decode(file.data));\n if (showToast) toastSuccess();\n } catch (err) {\n new Logger(\"SettingsSync\").error(err);\n if (showToast) toastFailure(err);\n }\n }\n } else {\n const file = await chooseFile(\"application/json\");\n if (!file) return;\n\n const reader = new FileReader();\n reader.onload = async () => {\n try {\n await importSettings(reader.result as string);\n if (showToast) toastSuccess();\n } catch (err) {\n new Logger(\"SettingsSync\").error(err);\n if (showToast) toastFailure(err);\n }\n };\n reader.readAsText(file);\n }\n}\n\n// Cloud settings\nconst cloudSettingsLogger = new Logger(\"Cloud:Settings\", \"#39b7e0\");\n\nexport async function putCloudSettings(manual?: boolean) {\n const settings = await exportSettings({ minify: true });\n\n try {\n const res = await fetch(new URL(\"/v1/settings\", getCloudUrl()), {\n method: \"PUT\",\n headers: {\n Authorization: await getCloudAuth(),\n \"Content-Type\": \"application/octet-stream\"\n },\n body: deflateSync(new TextEncoder().encode(settings))\n });\n\n if (!res.ok) {\n cloudSettingsLogger.error(`Failed to sync up, API returned ${res.status}`);\n showNotification({\n title: \"Cloud Settings\",\n body: `Could not synchronize settings to cloud (API returned ${res.status}).`,\n color: \"var(--red-360)\"\n });\n return;\n }\n\n const { written } = await res.json();\n PlainSettings.cloud.settingsSyncVersion = written;\n VencordNative.settings.set(PlainSettings);\n\n cloudSettingsLogger.info(\"Settings uploaded to cloud successfully\");\n\n if (manual) {\n showNotification({\n title: \"Cloud Settings\",\n body: \"Synchronized settings to the cloud!\",\n noPersist: true,\n });\n }\n } catch (e: any) {\n cloudSettingsLogger.error(\"Failed to sync up\", e);\n showNotification({\n title: \"Cloud Settings\",\n body: `Could not synchronize settings to the cloud (${e.toString()}).`,\n color: \"var(--red-360)\"\n });\n }\n}\n\nexport async function getCloudSettings(shouldNotify = true, force = false) {\n try {\n const res = await fetch(new URL(\"/v1/settings\", getCloudUrl()), {\n method: \"GET\",\n headers: {\n Authorization: await getCloudAuth(),\n Accept: \"application/octet-stream\",\n \"If-None-Match\": Settings.cloud.settingsSyncVersion.toString()\n },\n });\n\n if (res.status === 404) {\n cloudSettingsLogger.info(\"No settings on the cloud\");\n if (shouldNotify)\n showNotification({\n title: \"Cloud Settings\",\n body: \"There are no settings in the cloud.\",\n noPersist: true\n });\n return false;\n }\n\n if (res.status === 304) {\n cloudSettingsLogger.info(\"Settings up to date\");\n if (shouldNotify)\n showNotification({\n title: \"Cloud Settings\",\n body: \"Your settings are up to date.\",\n noPersist: true\n });\n return false;\n }\n\n if (!res.ok) {\n cloudSettingsLogger.error(`Failed to sync down, API returned ${res.status}`);\n showNotification({\n title: \"Cloud Settings\",\n body: `Could not synchronize settings from the cloud (API returned ${res.status}).`,\n color: \"var(--red-360)\"\n });\n return false;\n }\n\n const written = Number(res.headers.get(\"etag\")!);\n const localWritten = Settings.cloud.settingsSyncVersion;\n\n // don't need to check for written > localWritten because the server will return 304 due to if-none-match\n if (!force && written < localWritten) {\n if (shouldNotify)\n showNotification({\n title: \"Cloud Settings\",\n body: \"Your local settings are newer than the cloud ones.\",\n noPersist: true,\n });\n return;\n }\n\n const data = await res.arrayBuffer();\n\n const settings = new TextDecoder().decode(inflateSync(new Uint8Array(data)));\n await importSettings(settings);\n\n // sync with server timestamp instead of local one\n PlainSettings.cloud.settingsSyncVersion = written;\n VencordNative.settings.set(PlainSettings);\n\n cloudSettingsLogger.info(\"Settings loaded from cloud successfully\");\n if (shouldNotify)\n showNotification({\n title: \"Cloud Settings\",\n body: \"Your settings have been updated! Click here to restart to fully apply changes!\",\n color: \"var(--green-360)\",\n onClick: IS_WEB ? () => location.reload() : relaunch,\n noPersist: true\n });\n\n return true;\n } catch (e: any) {\n cloudSettingsLogger.error(\"Failed to sync down\", e);\n showNotification({\n title: \"Cloud Settings\",\n body: `Could not synchronize settings from the cloud (${e.toString()}).`,\n color: \"var(--red-360)\"\n });\n\n return false;\n }\n}\n\nexport async function deleteCloudSettings() {\n try {\n const res = await fetch(new URL(\"/v1/settings\", getCloudUrl()), {\n method: \"DELETE\",\n headers: { Authorization: await getCloudAuth() },\n });\n\n if (!res.ok) {\n cloudSettingsLogger.error(`Failed to delete, API returned ${res.status}`);\n showNotification({\n title: \"Cloud Settings\",\n body: `Could not delete settings (API returned ${res.status}).`,\n color: \"var(--red-360)\"\n });\n return;\n }\n\n cloudSettingsLogger.info(\"Settings deleted from cloud successfully\");\n showNotification({\n title: \"Cloud Settings\",\n body: \"Settings deleted from cloud!\",\n color: \"var(--green-360)\"\n });\n } catch (e: any) {\n cloudSettingsLogger.error(\"Failed to delete\", e);\n showNotification({\n title: \"Cloud Settings\",\n body: `Could not delete settings (${e.toString()}).`,\n color: \"var(--red-360)\"\n });\n }\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { debounce } from \"@shared/debounce\";\nimport { SettingsStore as SettingsStoreClass } from \"@shared/SettingsStore\";\nimport { localStorage } from \"@utils/localStorage\";\nimport { Logger } from \"@utils/Logger\";\nimport { mergeDefaults } from \"@utils/misc\";\nimport { putCloudSettings } from \"@utils/settingsSync\";\nimport { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from \"@utils/types\";\nimport { React } from \"@webpack/common\";\n\nimport plugins from \"~plugins\";\n\nconst logger = new Logger(\"Settings\");\nexport interface Settings {\n notifyAboutUpdates: boolean;\n autoUpdate: boolean;\n autoUpdateNotification: boolean,\n useQuickCss: boolean;\n enableReactDevtools: boolean;\n themeLinks: string[];\n enabledThemes: string[];\n frameless: boolean;\n transparent: boolean;\n winCtrlQ: boolean;\n macosVibrancyStyle:\n | \"content\"\n | \"fullscreen-ui\"\n | \"header\"\n | \"hud\"\n | \"menu\"\n | \"popover\"\n | \"selection\"\n | \"sidebar\"\n | \"titlebar\"\n | \"tooltip\"\n | \"under-page\"\n | \"window\"\n | undefined;\n disableMinSize: boolean;\n winNativeTitleBar: boolean;\n plugins: {\n [plugin: string]: {\n enabled: boolean;\n [setting: string]: any;\n };\n };\n\n notifications: {\n timeout: number;\n position: \"top-right\" | \"bottom-right\";\n useNative: \"always\" | \"never\" | \"not-focused\";\n logLimit: number;\n };\n\n cloud: {\n authenticated: boolean;\n url: string;\n settingsSync: boolean;\n settingsSyncVersion: number;\n };\n}\n\nconst DefaultSettings: Settings = {\n notifyAboutUpdates: true,\n autoUpdate: false,\n autoUpdateNotification: true,\n useQuickCss: true,\n themeLinks: [],\n enabledThemes: [],\n enableReactDevtools: false,\n frameless: false,\n transparent: false,\n winCtrlQ: false,\n macosVibrancyStyle: undefined,\n disableMinSize: false,\n winNativeTitleBar: false,\n plugins: {},\n\n notifications: {\n timeout: 5000,\n position: \"bottom-right\",\n useNative: \"not-focused\",\n logLimit: 50\n },\n\n cloud: {\n authenticated: false,\n url: \"https://api.vencord.dev/\",\n settingsSync: false,\n settingsSyncVersion: 0\n }\n};\n\nconst settings = VencordNative.settings.get();\nmergeDefaults(settings, DefaultSettings);\n\nconst saveSettingsOnFrequentAction = debounce(async () => {\n if (Settings.cloud.settingsSync && Settings.cloud.authenticated) {\n await putCloudSettings();\n delete localStorage.Vencord_settingsDirty;\n }\n}, 60_000);\n\n\nexport const SettingsStore = new SettingsStoreClass(settings, {\n readOnly: true,\n getDefaultValue({\n target,\n key,\n path\n }) {\n const v = target[key];\n if (!plugins) return v; // plugins not initialised yet. this means this path was reached by being called on the top level\n\n if (path === \"plugins\" && key in plugins)\n return target[key] = {\n enabled: plugins[key].required ?? plugins[key].enabledByDefault ?? false\n };\n\n // Since the property is not set, check if this is a plugin's setting and if so, try to resolve\n // the default value.\n if (path.startsWith(\"plugins.\")) {\n const plugin = path.slice(\"plugins.\".length);\n if (plugin in plugins) {\n const setting = plugins[plugin].options?.[key];\n if (!setting) return v;\n\n if (\"default\" in setting)\n // normal setting with a default value\n return (target[key] = setting.default);\n\n if (setting.type === OptionType.SELECT) {\n const def = setting.options.find(o => o.default);\n if (def)\n target[key] = def.value;\n return def?.value;\n }\n }\n }\n return v;\n }\n});\n\nSettingsStore.addGlobalChangeListener((_, path) => {\n SettingsStore.plain.cloud.settingsSyncVersion = Date.now();\n localStorage.Vencord_settingsDirty = true;\n saveSettingsOnFrequentAction();\n VencordNative.settings.set(SettingsStore.plain, path);\n});\n\n/**\n * Same as {@link Settings} but unproxied. You should treat this as readonly,\n * as modifying properties on this will not save to disk or call settings\n * listeners.\n * WARNING: default values specified in plugin.options will not be ensured here. In other words,\n * settings for which you specified a default value may be uninitialised. If you need proper\n * handling for default values, use {@link Settings}\n */\nexport const PlainSettings = settings;\n/**\n * A smart settings object. Altering props automagically saves\n * the updated settings to disk.\n * This recursively proxies objects. If you need the object non proxied, use {@link PlainSettings}\n */\nexport const Settings = SettingsStore.store;\n\n/**\n * Settings hook for React components. Returns a smart settings\n * object that automagically triggers a rerender if any properties\n * are altered\n * @param paths An optional list of paths to whitelist for rerenders\n * @returns Settings\n */\n// TODO: Representing paths as essentially \"string[].join('.')\" wont allow dots in paths, change to \"paths?: string[][]\" later\nexport function useSettings(paths?: UseSettings[]) {\n const [, forceUpdate] = React.useReducer(() => ({}), {});\n\n React.useEffect(() => {\n if (paths) {\n paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));\n return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));\n } else {\n SettingsStore.addGlobalChangeListener(forceUpdate);\n return () => SettingsStore.removeGlobalChangeListener(forceUpdate);\n }\n }, []);\n\n return SettingsStore.store;\n}\n\nexport function migratePluginSettings(name: string, ...oldNames: string[]) {\n const { plugins } = SettingsStore.plain;\n if (name in plugins) return;\n\n for (const oldName of oldNames) {\n if (oldName in plugins) {\n logger.info(`Migrating settings from old name ${oldName} to ${name}`);\n plugins[name] = plugins[oldName];\n delete plugins[oldName];\n SettingsStore.markAsChanged();\n break;\n }\n }\n}\n\nexport function definePluginSettings<\n Def extends SettingsDefinition,\n Checks extends SettingsChecks,\n PrivateSettings extends object = {}\n>(def: Def, checks?: Checks) {\n const definedSettings: DefinedSettings = {\n get store() {\n if (!definedSettings.pluginName) throw new Error(\"Cannot access settings before plugin is initialized\");\n return Settings.plugins[definedSettings.pluginName] as any;\n },\n use: settings => useSettings(\n settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings[]\n ).plugins[definedSettings.pluginName] as any,\n def,\n checks: checks ?? {} as any,\n pluginName: \"\",\n\n withPrivateSettings() {\n return this as DefinedSettings;\n }\n };\n\n return definedSettings;\n}\n\ntype UseSettings = ResolveUseSettings[keyof T];\n\ntype ResolveUseSettings = {\n [Key in keyof T]:\n Key extends string\n ? T[Key] extends Record\n // @ts-ignore \"Type instantiation is excessively deep and possibly infinite\"\n ? UseSettings extends string ? `${Key}.${UseSettings}` : never\n : Key\n : never;\n};\n", "export default \"ca18b6e\"", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport gitHash from \"~git-hash\";\n\nimport { Logger } from \"./Logger\";\nimport { relaunch } from \"./native\";\nimport { IpcRes } from \"./types\";\n\nexport const UpdateLogger = /* #__PURE__*/ new Logger(\"Updater\", \"white\");\nexport let isOutdated = false;\nexport let isNewer = false;\nexport let updateError: any;\nexport let changes: Record<\"hash\" | \"author\" | \"message\", string>[];\n\nasync function Unwrap(p: Promise>) {\n const res = await p;\n\n if (res.ok) return res.value;\n\n updateError = res.error;\n throw res.error;\n}\n\nexport async function checkForUpdates() {\n changes = await Unwrap(VencordNative.updater.getUpdates());\n if (changes.some(c => c.hash === gitHash)) {\n isNewer = true;\n return (isOutdated = false);\n }\n return (isOutdated = changes.length > 0);\n}\n\nexport async function update() {\n if (!isOutdated) return true;\n\n const res = await Unwrap(VencordNative.updater.update());\n\n if (res) {\n isOutdated = false;\n if (!await Unwrap(VencordNative.updater.rebuild()))\n throw new Error(\"The Build failed. Please try manually building the new update\");\n }\n\n return res;\n}\n\nexport const getRepo = () => Unwrap(VencordNative.updater.getRepo());\n\nexport async function maybePromptToUpdate(confirmMessage: string, checkForDev = false) {\n if (IS_WEB || IS_UPDATER_DISABLED) return;\n if (checkForDev && IS_DEV) return;\n\n try {\n const isOutdated = await checkForUpdates();\n if (isOutdated) {\n const wantsUpdate = confirm(confirmMessage);\n if (wantsUpdate && isNewer) return alert(\"Your local copy has more recent commits. Please stash or reset them.\");\n if (wantsUpdate) {\n await update();\n relaunch();\n }\n }\n } catch (err) {\n UpdateLogger.error(err);\n alert(\"That also failed :( Try updating or re-installing with the installer!\");\n }\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { maybePromptToUpdate } from \"@utils/updater\";\n\nexport function handleComponentFailed() {\n maybePromptToUpdate(\n \"Uh Oh! Failed to render this Page.\" +\n \" However, there is an update available that might fix it.\" +\n \" Would you like to update and restart now?\"\n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nexport function onlyOnce(f: F): F {\n let called = false;\n let result: any;\n return function onlyOnceWrapper(this: unknown) {\n if (called) return result;\n\n called = true;\n\n return (result = f.apply(this, arguments));\n } as unknown as F;\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport \"./settingsStyles.css\";\nimport \"./themesStyles.css\";\n\nimport ErrorBoundary from \"@components/ErrorBoundary\";\nimport { handleComponentFailed } from \"@components/handleComponentFailed\";\nimport { Margins } from \"@utils/margins\";\nimport { onlyOnce } from \"@utils/onlyOnce\";\nimport { Forms, Text } from \"@webpack/common\";\nimport type { ComponentType, PropsWithChildren } from \"react\";\n\nexport function SettingsTab({ title, children }: PropsWithChildren<{ title: string; }>) {\n return (\n \n \n {title}\n \n\n {children}\n \n );\n}\n\nconst onError = onlyOnce(handleComponentFailed);\n\nexport function wrapTab(component: ComponentType, tab: string) {\n return ErrorBoundary.wrap(component, {\n message: `Failed to render the ${tab} tab. If this issue persists, try using the installer to reinstall!`,\n onError,\n });\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { openNotificationLogModal } from \"@api/Notifications/notificationLog\";\nimport { Settings, useSettings } from \"@api/Settings\";\nimport { classNameFactory } from \"@api/Styles\";\nimport DonateButton from \"@components/DonateButton\";\nimport { ErrorCard } from \"@components/ErrorCard\";\nimport { Margins } from \"@utils/margins\";\nimport { identity } from \"@utils/misc\";\nimport { relaunch, showItemInFolder } from \"@utils/native\";\nimport { useAwaiter } from \"@utils/react\";\nimport { Button, Card, Forms, React, Select, Slider, Switch } from \"@webpack/common\";\n\nimport { SettingsTab, wrapTab } from \"./shared\";\n\nconst cl = classNameFactory(\"vc-settings-\");\n\nconst DEFAULT_DONATE_IMAGE = \"https://cdn.discordapp.com/emojis/1026533090627174460.png\";\nconst SHIGGY_DONATE_IMAGE = \"https://media.discordapp.net/stickers/1039992459209490513.png\";\n\ntype KeysOfType = {\n [K in keyof Object]: Object[K] extends Type ? K : never;\n}[keyof Object];\n\nfunction VencordSettings() {\n const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {\n fallbackValue: \"Loading...\"\n });\n const settings = useSettings();\n\n const donateImage = React.useMemo(() => Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE, []);\n\n const isWindows = navigator.platform.toLowerCase().startsWith(\"win\");\n const isMac = navigator.platform.toLowerCase().startsWith(\"mac\");\n const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;\n\n const Switches: Array;\n title: string;\n note: string;\n }> =\n [\n {\n key: \"useQuickCss\",\n title: \"Enable Custom CSS\",\n note: \"Loads your Custom CSS\"\n },\n !IS_WEB && {\n key: \"enableReactDevtools\",\n title: \"Enable React Developer Tools\",\n note: \"Requires a full restart\"\n },\n !IS_WEB && (!IS_DISCORD_DESKTOP || !isWindows ? {\n key: \"frameless\",\n title: \"Disable the window frame\",\n note: \"Requires a full restart\"\n } : {\n key: \"winNativeTitleBar\",\n title: \"Use Windows' native title bar instead of Discord's custom one\",\n note: \"Requires a full restart\"\n }),\n !IS_WEB && {\n key: \"transparent\",\n title: \"Enable window transparency.\",\n note: \"You need a theme that supports transparency or this will do nothing. Will stop the window from being resizable. Requires a full restart\"\n },\n !IS_WEB && isWindows && {\n key: \"winCtrlQ\",\n title: \"Register Ctrl+Q as shortcut to close Discord (Alternative to Alt+F4)\",\n note: \"Requires a full restart\"\n },\n IS_DISCORD_DESKTOP && {\n key: \"disableMinSize\",\n title: \"Disable minimum window size\",\n note: \"Requires a full restart\"\n },\n ];\n\n return (\n \n \n \n \n \n {!IS_WEB && (\n \n Restart Client\n \n )}\n VencordNative.quickCss.openEditor()}\n size={Button.Sizes.SMALL}\n disabled={settingsDir === \"Loading...\"}>\n Open QuickCSS File\n \n {!IS_WEB && (\n showItemInFolder(settingsDir)}\n size={Button.Sizes.SMALL}\n disabled={settingsDirPending}>\n Open Settings Folder\n \n )}\n VencordNative.native.openExternal(\"https://github.com/Vendicated/Vencord\")}\n size={Button.Sizes.SMALL}\n disabled={settingsDirPending}>\n Open in GitHub\n \n \n \n \n\n \n\n \n \n Hint: You can change the position of this settings section in the settings of the \"Settings\" plugin!\n \n {Switches.map(s => s && (\n settings[s.key] = v}\n note={s.note}\n >\n {s.title}\n \n ))}\n \n\n\n {needsVibrancySettings && <>\n Window vibrancy style (requires restart)\n settings.macosVibrancyStyle = v}\n isSelected={v => settings.macosVibrancyStyle === v}\n serialize={identity} />\n }\n\n {typeof Notification !== \"undefined\" && }\n \n );\n}\n\nfunction NotificationSection({ settings }: { settings: typeof Settings[\"notifications\"]; }) {\n return (\n <>\n Notification Style\n {settings.useNative !== \"never\" && Notification?.permission === \"denied\" && (\n \n Desktop Notification Permission denied\n You have denied Notification Permissions. Thus, Desktop notifications will not work!\n \n )}\n \n Some plugins may show you notifications. These come in two styles:\n
    \n
  • Vencord Notifications: These are in-app notifications
  • \n
  • Desktop Notifications: Native Desktop notifications (like when you get a ping)
  • \n
\n
\n >}\n closeOnSelect={true}\n select={v => settings.useNative = v}\n isSelected={v => v === settings.useNative}\n serialize={identity}\n />\n\n Notification Position\n >}\n select={v => settings.position = v}\n isSelected={v => v === settings.position}\n serialize={identity}\n />\n\n Notification Timeout\n Set to 0s to never automatically time out\n settings.timeout = v}\n onValueRender={v => (v / 1000).toFixed(2) + \"s\"}\n onMarkerRender={v => (v / 1000) + \"s\"}\n stickToMarkers={false}\n />\n\n Notification Log Limit\n \n The amount of notifications to save in the log until old ones are removed.\n Set to 0 to disable Notification log and \u221E to never automatically remove old Notifications\n \n settings.logLimit = v}\n onValueRender={v => v === 200 ? \"\u221E\" : v}\n onMarkerRender={v => v === 200 ? \"\u221E\" : v}\n />\n\n \n Open Notification Log\n \n \n );\n}\n\ninterface DonateCardProps {\n image: string;\n}\n\nfunction DonateCard({ image }: DonateCardProps) {\n return (\n \n
\n Support the Project\n Please consider supporting the development of Vencord by donating!\n \n
\n \n
\n );\n}\n\nexport default wrapTab(VencordSettings, \"Vencord Settings\");\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { waitFor } from \"@webpack\";\n\nlet NoticesModule: any;\nwaitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m);\n\nexport const noticesQueue = [] as any[];\nexport let currentNotice: any = null;\n\nexport function popNotice() {\n NoticesModule.dismiss();\n}\n\nexport function nextNotice() {\n currentNotice = noticesQueue.shift();\n\n if (currentNotice) {\n NoticesModule.show(...currentNotice, \"VencordNotice\");\n }\n}\n\nexport function showNotice(message: string, buttonText: string, onOkClick: () => void) {\n noticesQueue.push([\"GENERIC\", message, buttonText, onOkClick]);\n if (!currentNotice) nextNotice();\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport \"./iconStyles.css\";\n\nimport { classes } from \"@utils/misc\";\nimport { i18n } from \"@webpack/common\";\nimport type { PropsWithChildren, SVGProps } from \"react\";\n\ninterface BaseIconProps extends IconProps {\n viewBox: string;\n}\n\ninterface IconProps extends SVGProps {\n className?: string;\n height?: string | number;\n width?: string | number;\n}\n\nfunction Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren) {\n return (\n \n {children}\n \n );\n}\n\n/**\n * Discord's link icon, as seen in the Message context menu \"Copy Message Link\" option\n */\nexport function LinkIcon({ height = 24, width = 24, className }: IconProps) {\n return (\n \n \n \n \n \n \n );\n}\n\n/**\n * Discord's copy icon, as seen in the user popout right of the username when clicking\n * your own username in the bottom left user panel\n */\nexport function CopyIcon(props: IconProps) {\n return (\n \n \n \n \n \n \n );\n}\n\n/**\n * Discord's open external icon, as seen in the user profile connections\n */\nexport function OpenExternalIcon(props: IconProps) {\n return (\n \n \n \n );\n}\n\nexport function ImageIcon(props: IconProps) {\n return (\n \n \n \n );\n}\n\nexport function InfoIcon(props: IconProps) {\n return (\n \n \n \n );\n}\n\nexport function OwnerCrownIcon(props: IconProps) {\n return (\n \n \n \n );\n}\n\n/**\n * Discord's screenshare icon, as seen in the connection panel\n */\nexport function ScreenshareIcon(props: IconProps) {\n return (\n \n \n \n );\n}\n\nexport function ImageVisible(props: IconProps) {\n return (\n \n \n \n );\n}\n\nexport function ImageInvisible(props: IconProps) {\n return (\n \n \n \n );\n}\n\nexport function Microphone(props: IconProps) {\n return (\n \n \n \n \n );\n}\n\nexport function CogWheel(props: IconProps) {\n return (\n \n \n \n );\n}\n\nexport function ReplyIcon(props: IconProps) {\n return (\n \n \n \n );\n}\n\nexport function DeleteIcon(props: IconProps) {\n return (\n \n \n \n \n );\n}\n\nexport function PlusIcon(props: IconProps) {\n return (\n \n \n \n );\n}\n\nexport function NoEntrySignIcon(props: IconProps) {\n return (\n \n \n \n \n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { moment } from \"@webpack/common\";\n\n// Utils for readable text transformations eg: `toTitle(fromKebab())`\n\n// Case style to words\nexport const wordsFromCamel = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase());\nexport const wordsFromSnake = (text: string) => text.toLowerCase().split(\"_\");\nexport const wordsFromKebab = (text: string) => text.toLowerCase().split(\"-\");\nexport const wordsFromPascal = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase());\nexport const wordsFromTitle = (text: string) => text.toLowerCase().split(\" \");\n\n// Words to case style\nexport const wordsToCamel = (words: string[]) =>\n words.map((w, i) => (i ? w[0].toUpperCase() + w.slice(1) : w)).join(\"\");\nexport const wordsToSnake = (words: string[]) => words.join(\"_\").toUpperCase();\nexport const wordsToKebab = (words: string[]) => words.join(\"-\").toLowerCase();\nexport const wordsToPascal = (words: string[]) =>\n words.map(w => w[0].toUpperCase() + w.slice(1)).join(\"\");\nexport const wordsToTitle = (words: string[]) =>\n words.map(w => w[0].toUpperCase() + w.slice(1)).join(\" \");\n\nconst units = [\"years\", \"months\", \"weeks\", \"days\", \"hours\", \"minutes\", \"seconds\"] as const;\ntype Units = typeof units[number];\n\nfunction getUnitStr(unit: Units, isOne: boolean, short: boolean) {\n if (short === false) return isOne ? unit.slice(0, -1) : unit;\n\n return unit[0];\n}\n\n/**\n * Forms time into a human readable string link \"1 day, 2 hours, 3 minutes and 4 seconds\"\n * @param time The time on the specified unit\n * @param unit The unit the time is on\n * @param short Whether to use short units like \"d\" instead of \"days\"\n */\nexport function formatDuration(time: number, unit: Units, short: boolean = false) {\n const dur = moment.duration(time, unit);\n\n let unitsAmounts = units.map(unit => ({ amount: dur[unit](), unit }));\n\n let amountsToBeRemoved = 0;\n\n outer:\n for (let i = 0; i < unitsAmounts.length; i++) {\n if (unitsAmounts[i].amount === 0 || !(i + 1 < unitsAmounts.length)) continue;\n for (let v = i + 1; v < unitsAmounts.length; v++) {\n if (unitsAmounts[v].amount !== 0) continue outer;\n }\n\n amountsToBeRemoved = unitsAmounts.length - (i + 1);\n }\n unitsAmounts = amountsToBeRemoved === 0 ? unitsAmounts : unitsAmounts.slice(0, -amountsToBeRemoved);\n\n const daysAmountIndex = unitsAmounts.findIndex(({ unit }) => unit === \"days\");\n if (daysAmountIndex !== -1) {\n const daysAmount = unitsAmounts[daysAmountIndex];\n\n const daysMod = daysAmount.amount % 7;\n if (daysMod === 0) unitsAmounts.splice(daysAmountIndex, 1);\n else daysAmount.amount = daysMod;\n }\n\n let res: string = \"\";\n while (unitsAmounts.length) {\n const { amount, unit } = unitsAmounts.shift()!;\n\n if (res.length) res += unitsAmounts.length ? \", \" : \" and \";\n\n if (amount > 0 || res.length) {\n res += `${amount} ${getUnitStr(unit, amount === 1, short)}`;\n }\n }\n\n return res.length ? res : `0 ${getUnitStr(unit, false, short)}`;\n}\n\n/**\n * Join an array of strings in a human readable way (1, 2 and 3)\n * @param elements Elements\n */\nexport function humanFriendlyJoin(elements: string[]): string;\n/**\n * Join an array of strings in a human readable way (1, 2 and 3)\n * @param elements Elements\n * @param mapper Function that converts elements to a string\n */\nexport function humanFriendlyJoin(elements: T[], mapper: (e: T) => string): string;\nexport function humanFriendlyJoin(elements: any[], mapper: (e: any) => string = s => s): string {\n const { length } = elements;\n if (length === 0)\n return \"\";\n if (length === 1)\n return mapper(elements[0]);\n\n let s = \"\";\n\n for (let i = 0; i < length; i++) {\n s += mapper(elements[i]);\n if (length - i > 2)\n s += \", \";\n else if (length - i > 1)\n s += \" and \";\n }\n\n return s;\n}\n\n/**\n * Wrap the text in ``` with an optional language\n */\nexport function makeCodeblock(text: string, language?: string) {\n const chars = \"```\";\n return `${chars}${language || \"\"}\\n${text.replaceAll(\"```\", \"\\\\`\\\\`\\\\`\")}\\n${chars}`;\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { mergeDefaults } from \"@utils/misc\";\nimport { findByPropsLazy } from \"@webpack\";\nimport { MessageActions, SnowflakeUtils } from \"@webpack/common\";\nimport { Message } from \"discord-types/general\";\nimport type { PartialDeep } from \"type-fest\";\n\nimport { Argument } from \"./types\";\n\nconst MessageCreator = findByPropsLazy(\"createBotMessage\");\n\nexport function generateId() {\n return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;\n}\n\n/**\n * Send a message as Clyde\n * @param {string} channelId ID of channel to send message to\n * @param {Message} message Message to send\n * @returns {Message}\n */\nexport function sendBotMessage(channelId: string, message: PartialDeep): Message {\n const botMessage = MessageCreator.createBotMessage({ channelId, content: \"\", embeds: [] });\n\n MessageActions.receiveMessage(channelId, mergeDefaults(message, botMessage));\n\n return message as Message;\n}\n\n/**\n * Get the value of an option by name\n * @param args Arguments array (first argument passed to execute)\n * @param name Name of the argument\n * @param fallbackValue Fallback value in case this option wasn't passed\n * @returns Value\n */\nexport function findOption(args: Argument[], name: string): T & {} | undefined;\nexport function findOption(args: Argument[], name: string, fallbackValue: T): T & {};\nexport function findOption(args: Argument[], name: string, fallbackValue?: any) {\n return (args.find(a => a.name === name)?.value || fallbackValue) as any;\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Channel, Guild } from \"discord-types/general\";\nimport { Promisable } from \"type-fest\";\n\nexport interface CommandContext {\n channel: Channel;\n guild?: Guild;\n}\n\nexport const enum ApplicationCommandOptionType {\n SUB_COMMAND = 1,\n SUB_COMMAND_GROUP = 2,\n STRING = 3,\n INTEGER = 4,\n BOOLEAN = 5,\n USER = 6,\n CHANNEL = 7,\n ROLE = 8,\n MENTIONABLE = 9,\n NUMBER = 10,\n ATTACHMENT = 11,\n}\n\nexport const enum ApplicationCommandInputType {\n BUILT_IN = 0,\n BUILT_IN_TEXT = 1,\n BUILT_IN_INTEGRATION = 2,\n BOT = 3,\n PLACEHOLDER = 4,\n}\n\nexport interface Option {\n name: string;\n displayName?: string;\n type: ApplicationCommandOptionType;\n description: string;\n displayDescription?: string;\n required?: boolean;\n options?: Option[];\n choices?: Array;\n}\n\nexport interface ChoicesOption {\n label: string;\n value: string;\n name: string;\n displayName?: string;\n}\n\nexport const enum ApplicationCommandType {\n CHAT_INPUT = 1,\n USER = 2,\n MESSAGE = 3,\n}\n\nexport interface CommandReturnValue {\n content: string;\n /** TODO: implement */\n cancel?: boolean;\n}\n\nexport interface Argument {\n type: ApplicationCommandOptionType;\n name: string;\n value: string;\n focused: undefined;\n options: Argument[];\n}\n\nexport interface Command {\n id?: string;\n applicationId?: string;\n type?: ApplicationCommandType;\n inputType?: ApplicationCommandInputType;\n plugin?: string;\n isVencordCommand?: boolean;\n\n name: string;\n displayName?: string;\n description: string;\n displayDescription?: string;\n\n options?: Option[];\n predicate?(ctx: CommandContext): boolean;\n\n execute(args: Argument[], ctx: CommandContext): Promisable;\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { makeCodeblock } from \"@utils/text\";\n\nimport { sendBotMessage } from \"./commandHelpers\";\nimport { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, Argument, Command, CommandContext, Option } from \"./types\";\n\nexport * from \"./commandHelpers\";\nexport * from \"./types\";\n\nexport let BUILT_IN: Command[];\nexport const commands = {} as Record;\n\n// hack for plugins being evaluated before we can grab these from webpack\nconst OptPlaceholder = Symbol(\"OptionalMessageOption\") as any as Option;\nconst ReqPlaceholder = Symbol(\"RequiredMessageOption\") as any as Option;\n/**\n * Optional message option named \"message\" you can use in commands.\n * Used in \"tableflip\" or \"shrug\"\n * @see {@link RequiredMessageOption}\n */\nexport let OptionalMessageOption: Option = OptPlaceholder;\n/**\n * Required message option named \"message\" you can use in commands.\n * Used in \"me\"\n * @see {@link OptionalMessageOption}\n */\nexport let RequiredMessageOption: Option = ReqPlaceholder;\n\nexport const _init = function (cmds: Command[]) {\n try {\n BUILT_IN = cmds;\n OptionalMessageOption = cmds.find(c => c.name === \"shrug\")!.options![0];\n RequiredMessageOption = cmds.find(c => c.name === \"me\")!.options![0];\n } catch (e) {\n console.error(\"Failed to load CommandsApi\");\n }\n return cmds;\n} as never;\n\nexport const _handleCommand = function (cmd: Command, args: Argument[], ctx: CommandContext) {\n if (!cmd.isVencordCommand)\n return cmd.execute(args, ctx);\n\n const handleError = (err: any) => {\n // TODO: cancel send if cmd.inputType === BUILT_IN_TEXT\n const msg = `An Error occurred while executing command \"${cmd.name}\"`;\n const reason = err instanceof Error ? err.stack || err.message : String(err);\n\n console.error(msg, err);\n sendBotMessage(ctx.channel.id, {\n content: `${msg}:\\n${makeCodeblock(reason)}`,\n author: {\n username: \"Vencord\"\n }\n });\n };\n\n try {\n const res = cmd.execute(args, ctx);\n return res instanceof Promise ? res.catch(handleError) : res;\n } catch (err) {\n return handleError(err);\n }\n} as never;\n\n\n/**\n * Prepare a Command Option for Discord by filling missing fields\n * @param opt\n */\nexport function prepareOption(opt: O): O {\n opt.displayName ||= opt.name;\n opt.displayDescription ||= opt.description;\n opt.options?.forEach((opt, i, opts) => {\n // See comment above Placeholders\n if (opt === OptPlaceholder) opts[i] = OptionalMessageOption;\n else if (opt === ReqPlaceholder) opts[i] = RequiredMessageOption;\n opt.choices?.forEach(x => x.displayName ||= x.name);\n\n prepareOption(opts[i]);\n });\n return opt;\n}\n\n// Yes, Discord registers individual commands for each subcommand\n// TODO: This probably doesn't support nested subcommands. If that is ever needed,\n// investigate\nfunction registerSubCommands(cmd: Command, plugin: string) {\n cmd.options?.forEach(o => {\n if (o.type !== ApplicationCommandOptionType.SUB_COMMAND)\n throw new Error(\"When specifying sub-command options, all options must be sub-commands.\");\n const subCmd = {\n ...cmd,\n ...o,\n type: ApplicationCommandType.CHAT_INPUT,\n name: `${cmd.name} ${o.name}`,\n id: `${o.name}-${cmd.id}`,\n displayName: `${cmd.name} ${o.name}`,\n subCommandPath: [{\n name: o.name,\n type: o.type,\n displayName: o.name\n }],\n rootCommand: cmd\n };\n registerCommand(subCmd as any, plugin);\n });\n}\n\nexport function registerCommand(command: C, plugin: string) {\n if (!BUILT_IN) {\n console.warn(\n \"[CommandsAPI]\",\n `Not registering ${command.name} as the CommandsAPI hasn't been initialised.`,\n \"Please restart to use commands\"\n );\n return;\n }\n\n if (BUILT_IN.some(c => c.name === command.name))\n throw new Error(`Command '${command.name}' already exists.`);\n\n command.isVencordCommand = true;\n command.id ??= `-${BUILT_IN.length + 1}`;\n command.applicationId ??= \"-1\"; // BUILT_IN;\n command.type ??= ApplicationCommandType.CHAT_INPUT;\n command.inputType ??= ApplicationCommandInputType.BUILT_IN_TEXT;\n command.plugin ||= plugin;\n\n prepareOption(command);\n\n if (command.options?.[0]?.type === ApplicationCommandOptionType.SUB_COMMAND) {\n registerSubCommands(command, plugin);\n return;\n }\n\n commands[command.name] = command;\n BUILT_IN.push(command);\n}\n\nexport function unregisterCommand(name: string) {\n const idx = BUILT_IN.findIndex(c => c.name === name);\n if (idx === -1)\n return false;\n\n BUILT_IN.splice(idx, 1);\n delete commands[name];\n\n return true;\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nexport function Badge({ text, color }): JSX.Element {\n return (\n
\n {text}\n
\n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { wordsFromCamel, wordsToTitle } from \"@utils/text\";\nimport { PluginOptionBoolean } from \"@utils/types\";\nimport { Forms, React, Switch } from \"@webpack/common\";\n\nimport { ISettingElementProps } from \".\";\n\nexport function SettingBooleanComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps) {\n const def = pluginSettings[id] ?? option.default;\n\n const [state, setState] = React.useState(def ?? false);\n const [error, setError] = React.useState(null);\n\n React.useEffect(() => {\n onError(error !== null);\n }, [error]);\n\n function handleChange(newValue: boolean): void {\n const isValid = option.isValid?.call(definedSettings, newValue) ?? true;\n if (typeof isValid === \"string\") setError(isValid);\n else if (!isValid) setError(\"Invalid input provided.\");\n else {\n setError(null);\n setState(newValue);\n onChange(newValue);\n }\n }\n\n return (\n \n \n {wordsToTitle(wordsFromCamel(id))}\n \n {error && {error}}\n \n );\n}\n\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { PluginOptionComponent } from \"@utils/types\";\n\nimport { ISettingElementProps } from \".\";\n\nexport function SettingCustomComponent({ option, onChange, onError }: ISettingElementProps) {\n return option.component({ setValue: onChange, setError: onError, option });\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { OptionType, PluginOptionNumber } from \"@utils/types\";\nimport { Forms, React, TextInput } from \"@webpack/common\";\n\nimport { ISettingElementProps } from \".\";\n\nconst MAX_SAFE_NUMBER = BigInt(Number.MAX_SAFE_INTEGER);\n\nexport function SettingNumericComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps) {\n function serialize(value: any) {\n if (option.type === OptionType.BIGINT) return BigInt(value);\n return Number(value);\n }\n\n const [state, setState] = React.useState(`${pluginSettings[id] ?? option.default ?? 0}`);\n const [error, setError] = React.useState(null);\n\n React.useEffect(() => {\n onError(error !== null);\n }, [error]);\n\n function handleChange(newValue) {\n const isValid = option.isValid?.call(definedSettings, newValue) ?? true;\n\n setError(null);\n if (typeof isValid === \"string\") setError(isValid);\n else if (!isValid) setError(\"Invalid input provided.\");\n\n if (option.type === OptionType.NUMBER && BigInt(newValue) >= MAX_SAFE_NUMBER) {\n setState(`${Number.MAX_SAFE_INTEGER}`);\n onChange(serialize(newValue));\n } else {\n setState(newValue);\n onChange(serialize(newValue));\n }\n }\n\n return (\n \n {option.description}\n \n {error && {error}}\n \n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { PluginOptionSelect } from \"@utils/types\";\nimport { Forms, React, Select } from \"@webpack/common\";\n\nimport { ISettingElementProps } from \".\";\n\nexport function SettingSelectComponent({ option, pluginSettings, definedSettings, onChange, onError, id }: ISettingElementProps) {\n const def = pluginSettings[id] ?? option.options?.find(o => o.default)?.value;\n\n const [state, setState] = React.useState(def ?? null);\n const [error, setError] = React.useState(null);\n\n React.useEffect(() => {\n onError(error !== null);\n }, [error]);\n\n function handleChange(newValue) {\n const isValid = option.isValid?.call(definedSettings, newValue) ?? true;\n if (typeof isValid === \"string\") setError(isValid);\n else if (!isValid) setError(\"Invalid input provided.\");\n else {\n setError(null);\n setState(newValue);\n onChange(newValue);\n }\n }\n\n return (\n \n {option.description}\n v === state}\n serialize={v => String(v)}\n {...option.componentProps}\n />\n {error && {error}}\n \n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { PluginOptionSlider } from \"@utils/types\";\nimport { Forms, React, Slider } from \"@webpack/common\";\n\nimport { ISettingElementProps } from \".\";\n\nexport function makeRange(start: number, end: number, step = 1) {\n const ranges: number[] = [];\n for (let value = start; value <= end; value += step) {\n ranges.push(Math.round(value * 100) / 100);\n }\n return ranges;\n}\n\nexport function SettingSliderComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps) {\n const def = pluginSettings[id] ?? option.default;\n\n const [error, setError] = React.useState(null);\n\n React.useEffect(() => {\n onError(error !== null);\n }, [error]);\n\n function handleChange(newValue: number): void {\n const isValid = option.isValid?.call(definedSettings, newValue) ?? true;\n if (typeof isValid === \"string\") setError(isValid);\n else if (!isValid) setError(\"Invalid input provided.\");\n else {\n setError(null);\n onChange(newValue);\n }\n }\n\n return (\n \n {option.description}\n String(v.toFixed(2))}\n stickToMarkers={option.stickToMarkers ?? true}\n {...option.componentProps}\n />\n \n );\n}\n\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { PluginOptionString } from \"@utils/types\";\nimport { Forms, React, TextInput } from \"@webpack/common\";\n\nimport { ISettingElementProps } from \".\";\n\nexport function SettingTextComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps) {\n const [state, setState] = React.useState(pluginSettings[id] ?? option.default ?? null);\n const [error, setError] = React.useState(null);\n\n React.useEffect(() => {\n onError(error !== null);\n }, [error]);\n\n function handleChange(newValue) {\n const isValid = option.isValid?.call(definedSettings, newValue) ?? true;\n if (typeof isValid === \"string\") setError(isValid);\n else if (!isValid) setError(\"Invalid input provided.\");\n else setError(null);\n\n setState(newValue);\n onChange(newValue);\n }\n\n return (\n \n {option.description}\n \n {error && {error}}\n \n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { DefinedSettings, PluginOptionBase } from \"@utils/types\";\n\nexport interface ISettingElementProps {\n option: T;\n onChange(newValue: any): void;\n pluginSettings: {\n [setting: string]: any;\n enabled: boolean;\n };\n id: string;\n onError(hasError: boolean): void;\n definedSettings?: DefinedSettings;\n}\n\nexport * from \"../../Badge\";\nexport * from \"./SettingBooleanComponent\";\nexport * from \"./SettingCustomComponent\";\nexport * from \"./SettingNumericComponent\";\nexport * from \"./SettingSelectComponent\";\nexport * from \"./SettingSliderComponent\";\nexport * from \"./SettingTextComponent\";\n\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { MessageObject } from \"@api/MessageEvents\";\nimport { ChannelStore, ComponentDispatch, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from \"@webpack/common\";\nimport { Guild, Message, User } from \"discord-types/general\";\n\nimport { ImageModal, ModalRoot, ModalSize, openModal } from \"./modal\";\n\n/**\n * Open the invite modal\n * @param code The invite code\n * @returns Whether the invite was accepted\n */\nexport async function openInviteModal(code: string) {\n const { invite } = await InviteActions.resolveInvite(code, \"Desktop Modal\");\n if (!invite) throw new Error(\"Invalid invite: \" + code);\n\n FluxDispatcher.dispatch({\n type: \"INVITE_MODAL_OPEN\",\n invite,\n code,\n context: \"APP\"\n });\n\n return new Promise(r => {\n let onClose: () => void, onAccept: () => void;\n let inviteAccepted = false;\n\n FluxDispatcher.subscribe(\"INVITE_ACCEPT\", onAccept = () => {\n inviteAccepted = true;\n });\n\n FluxDispatcher.subscribe(\"INVITE_MODAL_CLOSE\", onClose = () => {\n FluxDispatcher.unsubscribe(\"INVITE_MODAL_CLOSE\", onClose);\n FluxDispatcher.unsubscribe(\"INVITE_ACCEPT\", onAccept);\n r(inviteAccepted);\n });\n });\n}\n\nexport function getCurrentChannel() {\n return ChannelStore.getChannel(SelectedChannelStore.getChannelId());\n}\n\nexport function getCurrentGuild(): Guild | undefined {\n return GuildStore.getGuild(getCurrentChannel()?.guild_id);\n}\n\nexport function openPrivateChannel(userId: string) {\n PrivateChannelsStore.openPrivateChannel(userId);\n}\n\nexport const enum Theme {\n Dark = 1,\n Light = 2\n}\n\nexport function getTheme(): Theme {\n return UserSettingsActionCreators.PreloadedUserSettingsActionCreators.getCurrentValue()?.appearance?.theme;\n}\n\nexport function insertTextIntoChatInputBox(text: string) {\n ComponentDispatch.dispatchToLastSubscribed(\"INSERT_TEXT\", {\n rawText: text,\n plainText: text\n });\n}\n\ninterface MessageExtra {\n messageReference: Message[\"messageReference\"];\n allowedMentions: {\n parse: string[];\n replied_user: boolean;\n };\n stickerIds: string[];\n}\n\nexport function sendMessage(\n channelId: string,\n data: Partial,\n waitForChannelReady?: boolean,\n extra?: Partial\n) {\n const messageData = {\n content: \"\",\n invalidEmojis: [],\n tts: false,\n validNonShortcutEmojis: [],\n ...data\n };\n\n return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, extra);\n}\n\nexport function openImageModal(url: string, props?: Partial>): string {\n return openModal(modalProps => (\n \n }\n shouldHideMediaOptions={false}\n shouldAnimate\n {...props}\n />\n \n ));\n}\n\nexport async function openUserProfile(id: string) {\n const user = await UserUtils.getUser(id);\n if (!user) throw new Error(\"No such user: \" + id);\n\n const guildId = SelectedGuildStore.getGuildId();\n UserProfileActions.openUserProfileModal({\n userId: id,\n guildId,\n channelId: SelectedChannelStore.getChannelId(),\n analyticsLocation: {\n page: guildId ? \"Guild Channel\" : \"DM Channel\",\n section: \"Profile Popout\"\n }\n });\n}\n\ninterface FetchUserProfileOptions {\n friend_token?: string;\n connections_role_id?: string;\n guild_id?: string;\n with_mutual_guilds?: boolean;\n with_mutual_friends_count?: boolean;\n}\n\n/**\n * Fetch a user's profile\n */\nexport async function fetchUserProfile(id: string, options?: FetchUserProfileOptions) {\n const cached = UserProfileStore.getUserProfile(id);\n if (cached) return cached;\n\n FluxDispatcher.dispatch({ type: \"USER_PROFILE_FETCH_START\", userId: id });\n\n const { body } = await RestAPI.get({\n url: `/users/${id}/profile`,\n query: {\n with_mutual_guilds: false,\n with_mutual_friends_count: false,\n ...options\n },\n oldFormErrors: true,\n });\n\n FluxDispatcher.dispatch({ type: \"USER_UPDATE\", user: body.user });\n await FluxDispatcher.dispatch({ type: \"USER_PROFILE_FETCH_SUCCESS\", ...body });\n if (options?.guild_id && body.guild_member)\n FluxDispatcher.dispatch({ type: \"GUILD_MEMBER_PROFILE_UPDATE\", guildId: options.guild_id, guildMember: body.guild_member });\n\n return UserProfileStore.getUserProfile(id);\n}\n\n/**\n * Get the unique username for a user. Returns user.username for pomelo people, user.tag otherwise\n */\nexport function getUniqueUsername(user: User) {\n return user.discriminator === \"0\" ? user.username : user.tag;\n}\n", "/*\n * Vencord, a Discord client mod\n * Copyright (c) 2023 Vendicated and contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\n\nimport \"./contributorModal.css\";\n\nimport { useSettings } from \"@api/Settings\";\nimport { classNameFactory } from \"@api/Styles\";\nimport ErrorBoundary from \"@components/ErrorBoundary\";\nimport { DevsById } from \"@utils/constants\";\nimport { fetchUserProfile, getTheme, Theme } from \"@utils/discord\";\nimport { ModalContent, ModalRoot, openModal } from \"@utils/modal\";\nimport { Forms, MaskedLink, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from \"@webpack/common\";\nimport { User } from \"discord-types/general\";\n\nimport Plugins from \"~plugins\";\n\nimport { PluginCard } from \".\";\n\nconst WebsiteIconDark = \"/assets/e1e96d89e192de1997f73730db26e94f.svg\";\nconst WebsiteIconLight = \"/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg\";\nconst GithubIconLight = \"/assets/3ff98ad75ac94fa883af5ed62d17c459.svg\";\nconst GithubIconDark = \"/assets/6a853b4c87fce386cbfef4a2efbacb09.svg\";\n\nconst cl = classNameFactory(\"vc-author-modal-\");\n\nexport function openContributorModal(user: User) {\n openModal(modalProps =>\n \n \n \n \n \n \n \n );\n}\n\nfunction GithubIcon() {\n const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;\n return \"GitHub\";\n}\n\nfunction WebsiteIcon() {\n const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;\n return \"Website\";\n}\n\nfunction ContributorModal({ user }: { user: User; }) {\n useSettings();\n\n const profile = useStateFromStores([UserProfileStore], () => UserProfileStore.getUserProfile(user.id));\n\n useEffect(() => {\n if (!profile && !user.bot && user.id)\n fetchUserProfile(user.id);\n }, [user.id]);\n\n const githubName = profile?.connectedAccounts?.find(a => a.type === \"github\")?.name;\n const website = profile?.connectedAccounts?.find(a => a.type === \"domain\")?.name;\n\n const plugins = useMemo(() => {\n const allPlugins = Object.values(Plugins);\n const pluginsByAuthor = DevsById[user.id]\n ? allPlugins.filter(p => p.authors.includes(DevsById[user.id]))\n : allPlugins.filter(p => p.authors.some(a => a.name === user.username));\n\n return pluginsByAuthor\n .filter(p => !p.name.endsWith(\"API\"))\n .sort((a, b) => Number(a.required ?? false) - Number(b.required ?? false));\n }, [user.id, user.username]);\n\n return (\n <>\n
\n \n {user.username}\n\n
\n {website && (\n \n \n \n )}\n {githubName && (\n \n \n \n )}\n
\n
\n\n
\n {plugins.map(p =>\n showToast(\"Restart to apply changes!\")}\n />\n )}\n
\n \n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { generateId } from \"@api/Commands\";\nimport { useSettings } from \"@api/Settings\";\nimport ErrorBoundary from \"@components/ErrorBoundary\";\nimport { Flex } from \"@components/Flex\";\nimport { proxyLazy } from \"@utils/lazy\";\nimport { Margins } from \"@utils/margins\";\nimport { classes, isObjectEmpty } from \"@utils/misc\";\nimport { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from \"@utils/modal\";\nimport { OptionType, Plugin } from \"@utils/types\";\nimport { findByPropsLazy, findComponentByCodeLazy } from \"@webpack\";\nimport { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from \"@webpack/common\";\nimport { User } from \"discord-types/general\";\nimport { Constructor } from \"type-fest\";\n\nimport {\n ISettingElementProps,\n SettingBooleanComponent,\n SettingCustomComponent,\n SettingNumericComponent,\n SettingSelectComponent,\n SettingSliderComponent,\n SettingTextComponent\n} from \"./components\";\nimport { openContributorModal } from \"./ContributorModal\";\n\nconst UserSummaryItem = findComponentByCodeLazy(\"defaultRenderUser\", \"showDefaultAvatarsForNullUsers\");\nconst AvatarStyles = findByPropsLazy(\"moreUsers\", \"emptyUser\", \"avatarContainer\", \"clickableAvatar\");\nconst UserRecord: Constructor> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any;\n\ninterface PluginModalProps extends ModalProps {\n plugin: Plugin;\n onRestartNeeded(): void;\n}\n\nfunction makeDummyUser(user: { username: string; id?: string; avatar?: string; }) {\n const newUser = new UserRecord({\n username: user.username,\n id: user.id ?? generateId(),\n avatar: user.avatar,\n /** To stop discord making unwanted requests... */\n bot: true,\n });\n FluxDispatcher.dispatch({\n type: \"USER_UPDATE\",\n user: newUser,\n });\n return newUser;\n}\n\nconst Components: Record>> = {\n [OptionType.STRING]: SettingTextComponent,\n [OptionType.NUMBER]: SettingNumericComponent,\n [OptionType.BIGINT]: SettingNumericComponent,\n [OptionType.BOOLEAN]: SettingBooleanComponent,\n [OptionType.SELECT]: SettingSelectComponent,\n [OptionType.SLIDER]: SettingSliderComponent,\n [OptionType.COMPONENT]: SettingCustomComponent\n};\n\nexport default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {\n const [authors, setAuthors] = React.useState[]>([]);\n\n const pluginSettings = useSettings().plugins[plugin.name];\n\n const [tempSettings, setTempSettings] = React.useState>({});\n\n const [errors, setErrors] = React.useState>({});\n const [saveError, setSaveError] = React.useState(null);\n\n const canSubmit = () => Object.values(errors).every(e => !e);\n\n const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options));\n\n React.useEffect(() => {\n (async () => {\n for (const user of plugin.authors.slice(0, 6)) {\n const author = user.id\n ? await UserUtils.getUser(`${user.id}`)\n .catch(() => makeDummyUser({ username: user.name }))\n : makeDummyUser({ username: user.name });\n\n setAuthors(a => [...a, author]);\n }\n })();\n }, []);\n\n async function saveAndClose() {\n if (!plugin.options) {\n onClose();\n return;\n }\n\n if (plugin.beforeSave) {\n const result = await Promise.resolve(plugin.beforeSave(tempSettings));\n if (result !== true) {\n setSaveError(result);\n return;\n }\n }\n\n let restartNeeded = false;\n for (const [key, value] of Object.entries(tempSettings)) {\n const option = plugin.options[key];\n pluginSettings[key] = value;\n option?.onChange?.(value);\n if (option?.restartNeeded) restartNeeded = true;\n }\n if (restartNeeded) onRestartNeeded();\n onClose();\n }\n\n function renderSettings() {\n if (!hasSettings || !plugin.options) {\n return There are no settings for this plugin.;\n } else {\n const options = Object.entries(plugin.options).map(([key, setting]) => {\n if (setting.hidden) return null;\n\n function onChange(newValue: any) {\n setTempSettings(s => ({ ...s, [key]: newValue }));\n }\n\n function onError(hasError: boolean) {\n setErrors(e => ({ ...e, [key]: hasError }));\n }\n\n const Component = Components[setting.type];\n return (\n \n );\n });\n\n return {options};\n }\n }\n\n function renderMoreUsers(_label: string, count: number) {\n const sliceCount = plugin.authors.length - count;\n const sliceStart = plugin.authors.length - sliceCount;\n const sliceEnd = sliceStart + plugin.authors.length - count;\n\n return (\n u.name).join(\", \")}>\n {({ onMouseEnter, onMouseLeave }) => (\n \n +{sliceCount}\n
\n )}\n \n );\n }\n\n return (\n \n \n {plugin.name}\n \n \n \n \n About {plugin.name}\n {plugin.description}\n Authors\n
\n (\n openContributorModal(user)}\n >\n \n \n )}\n />\n
\n
\n {!!plugin.settingsAboutComponent && (\n
\n \n \n \n \n \n
\n )}\n \n Settings\n {renderSettings()}\n \n
\n {hasSettings && \n \n \n \n Cancel\n \n \n {({ onMouseEnter, onMouseLeave }) => (\n \n Save & Close\n \n )}\n \n \n {saveError && Error while saving: {saveError}}\n \n }\n
\n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport \"./Switch.css\";\n\nimport { classes } from \"@utils/misc\";\nimport { findByPropsLazy } from \"@webpack\";\n\ninterface SwitchProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n disabled?: boolean;\n}\n\nconst SWITCH_ON = \"var(--green-360)\";\nconst SWITCH_OFF = \"var(--primary-400)\";\nconst SwitchClasses = findByPropsLazy(\"slider\", \"input\", \"container\");\n\nexport function Switch({ checked, onChange, disabled }: SwitchProps) {\n return (\n
\n
\n \n \n \n {checked ? (\n <>\n \n \n \n ) : (\n <>\n \n \n \n )}\n\n \n \n onChange(e.currentTarget.checked)}\n />\n
\n
\n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport \"./addonCard.css\";\n\nimport { classNameFactory } from \"@api/Styles\";\nimport { Badge } from \"@components/Badge\";\nimport { Switch } from \"@components/Switch\";\nimport { Text } from \"@webpack/common\";\nimport type { MouseEventHandler, ReactNode } from \"react\";\n\nconst cl = classNameFactory(\"vc-addon-\");\n\ninterface Props {\n name: ReactNode;\n description: ReactNode;\n enabled: boolean;\n setEnabled: (enabled: boolean) => void;\n disabled?: boolean;\n isNew?: boolean;\n onMouseEnter?: MouseEventHandler;\n onMouseLeave?: MouseEventHandler;\n\n infoButton?: ReactNode;\n footer?: ReactNode;\n author?: ReactNode;\n}\n\nexport function AddonCard({ disabled, isNew, name, infoButton, footer, author, enabled, setEnabled, description, onMouseEnter, onMouseLeave }: Props) {\n return (\n \n
\n
\n \n {name}{isNew && }\n \n {!!author && (\n \n {author}\n \n )}\n
\n\n {infoButton}\n\n \n
\n\n {description}\n\n {footer}\n \n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nexport class ChangeList{\n private set = new Set();\n\n public get changeCount() {\n return this.set.size;\n }\n\n public get hasChanges() {\n return this.changeCount > 0;\n }\n\n public handleChange(item: T) {\n if (!this.set.delete(item))\n this.set.add(item);\n }\n\n public add(item: T) {\n return this.set.add(item);\n }\n\n public remove(item: T) {\n return this.set.delete(item);\n }\n\n public getChanges() {\n return this.set.values();\n }\n\n public map(mapper: (v: T, idx: number, arr: T[]) => R): R[] {\n return [...this.getChanges()].map(mapper);\n }\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Logger } from \"@utils/Logger\";\nimport { Menu, React } from \"@webpack/common\";\nimport type { ReactElement } from \"react\";\n\n/**\n * @param children The rendered context menu elements\n * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example\n */\nexport type NavContextMenuPatchCallback = (children: Array, ...args: Array) => void;\n/**\n * @param navId The navId of the context menu being patched\n * @param children The rendered context menu elements\n * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example\n */\nexport type GlobalContextMenuPatchCallback = (navId: string, children: Array, ...args: Array) => void;\n\nconst ContextMenuLogger = new Logger(\"ContextMenu\");\n\nexport const navPatches = new Map>();\nexport const globalPatches = new Set();\n\n/**\n * Add a context menu patch\n * @param navId The navId(s) for the context menu(s) to patch\n * @param patch The patch to be applied\n */\nexport function addContextMenuPatch(navId: string | Array, patch: NavContextMenuPatchCallback) {\n if (!Array.isArray(navId)) navId = [navId];\n for (const id of navId) {\n let contextMenuPatches = navPatches.get(id);\n if (!contextMenuPatches) {\n contextMenuPatches = new Set();\n navPatches.set(id, contextMenuPatches);\n }\n\n contextMenuPatches.add(patch);\n }\n}\n\n/**\n * Add a global context menu patch that fires the patch for all context menus\n * @param patch The patch to be applied\n */\nexport function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback) {\n globalPatches.add(patch);\n}\n\n/**\n * Remove a context menu patch\n * @param navId The navId(s) for the context menu(s) to remove the patch\n * @param patch The patch to be removed\n * @returns Whether the patch was successfully removed from the context menu(s)\n */\nexport function removeContextMenuPatch>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array {\n const navIds = Array.isArray(navId) ? navId : [navId as string];\n\n const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);\n\n return (Array.isArray(navId) ? results : results[0]) as T extends string ? boolean : Array;\n}\n\n/**\n * Remove a global context menu patch\n * @param patch The patch to be removed\n * @returns Whether the patch was successfully removed\n */\nexport function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback): boolean {\n return globalPatches.delete(patch);\n}\n\n/**\n * A helper function for finding the children array of a group nested inside a context menu based on the id(s) of its children\n * @param id The id of the child. If an array is specified, all ids will be tried\n * @param children The context menu children\n */\nexport function findGroupChildrenByChildId(id: string | string[], children: Array): Array | null {\n for (const child of children) {\n if (child == null) continue;\n\n if (Array.isArray(child)) {\n const found = findGroupChildrenByChildId(id, child);\n if (found !== null) return found;\n }\n\n if (\n (Array.isArray(id) && id.some(id => child.props?.id === id))\n || child.props?.id === id\n ) return children;\n\n let nextChildren = child.props?.children;\n if (nextChildren) {\n if (!Array.isArray(nextChildren)) {\n nextChildren = [nextChildren];\n child.props.children = nextChildren;\n }\n\n const found = findGroupChildrenByChildId(id, nextChildren);\n if (found !== null) return found;\n }\n }\n\n return null;\n}\n\ninterface ContextMenuProps {\n contextMenuApiArguments?: Array;\n navId: string;\n children: Array;\n \"aria-label\": string;\n onSelect: (() => void) | undefined;\n onClose: (callback: (...args: Array) => any) => void;\n}\n\nexport function _usePatchContextMenu(props: ContextMenuProps) {\n props = {\n ...props,\n children: cloneMenuChildren(props.children),\n };\n\n props.contextMenuApiArguments ??= [];\n const contextMenuPatches = navPatches.get(props.navId);\n\n if (!Array.isArray(props.children)) props.children = [props.children];\n\n if (contextMenuPatches) {\n for (const patch of contextMenuPatches) {\n try {\n patch(props.children, ...props.contextMenuApiArguments);\n } catch (err) {\n ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);\n }\n }\n }\n\n for (const patch of globalPatches) {\n try {\n patch(props.navId, props.children, ...props.contextMenuApiArguments);\n } catch (err) {\n ContextMenuLogger.error(\"Global patch errored,\", err);\n }\n }\n\n return props;\n}\n\nfunction cloneMenuChildren(obj: ReactElement | Array | null) {\n if (Array.isArray(obj)) {\n return obj.map(cloneMenuChildren);\n }\n\n if (React.isValidElement(obj)) {\n obj = React.cloneElement(obj);\n\n if (\n obj?.props?.children &&\n (obj.type !== Menu.MenuControlItem || obj.type === Menu.MenuControlItem && obj.props.control != null)\n ) {\n obj.props.children = cloneMenuChildren(obj.props.children);\n }\n }\n\n return obj;\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { registerCommand, unregisterCommand } from \"@api/Commands\";\nimport { addContextMenuPatch, removeContextMenuPatch } from \"@api/ContextMenu\";\nimport { Settings } from \"@api/Settings\";\nimport { Logger } from \"@utils/Logger\";\nimport { Patch, Plugin, StartAt } from \"@utils/types\";\nimport { FluxDispatcher } from \"@webpack/common\";\nimport { FluxEvents } from \"@webpack/types\";\n\nimport Plugins from \"~plugins\";\n\nimport { traceFunction } from \"../debug/Tracer\";\n\nconst logger = new Logger(\"PluginManager\", \"#a6d189\");\n\nexport const PMLogger = logger;\nexport const plugins = Plugins;\nexport const patches = [] as Patch[];\n\nconst settings = Settings.plugins;\n\nexport function isPluginEnabled(p: string) {\n return (\n Plugins[p]?.required ||\n Plugins[p]?.isDependency ||\n settings[p]?.enabled\n ) ?? false;\n}\n\nconst pluginsValues = Object.values(Plugins);\n\n// First roundtrip to mark and force enable dependencies (only for enabled plugins)\n//\n// FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only\n// goes for the top level and their children, but for now this works okay with the current API plugins\nfor (const p of pluginsValues) if (settings[p.name]?.enabled) {\n p.dependencies?.forEach(d => {\n const dep = Plugins[d];\n if (dep) {\n settings[d].enabled = true;\n dep.isDependency = true;\n }\n else {\n const error = new Error(`Plugin ${p.name} has unresolved dependency ${d}`);\n if (IS_DEV)\n throw error;\n logger.warn(error);\n }\n });\n}\n\nfor (const p of pluginsValues) {\n if (p.settings) {\n p.settings.pluginName = p.name;\n p.options ??= {};\n for (const [name, def] of Object.entries(p.settings.def)) {\n const checks = p.settings.checks?.[name];\n p.options[name] = { ...def, ...checks };\n }\n }\n\n if (p.patches && isPluginEnabled(p.name)) {\n for (const patch of p.patches) {\n patch.plugin = p.name;\n if (!Array.isArray(patch.replacement))\n patch.replacement = [patch.replacement];\n patches.push(patch);\n }\n }\n}\n\nexport const startAllPlugins = traceFunction(\"startAllPlugins\", function startAllPlugins(target: StartAt) {\n logger.info(`Starting plugins (stage ${target})`);\n for (const name in Plugins)\n if (isPluginEnabled(name)) {\n const p = Plugins[name];\n\n const startAt = p.startAt ?? StartAt.WebpackReady;\n if (startAt !== target) continue;\n\n startPlugin(Plugins[name]);\n }\n});\n\nexport function startDependenciesRecursive(p: Plugin) {\n let restartNeeded = false;\n const failures: string[] = [];\n p.dependencies?.forEach(dep => {\n if (!Settings.plugins[dep].enabled) {\n startDependenciesRecursive(Plugins[dep]);\n // If the plugin has patches, don't start the plugin, just enable it.\n Settings.plugins[dep].enabled = true;\n if (Plugins[dep].patches) {\n logger.warn(`Enabling dependency ${dep} requires restart.`);\n restartNeeded = true;\n return;\n }\n const result = startPlugin(Plugins[dep]);\n if (!result) failures.push(dep);\n }\n });\n return { restartNeeded, failures };\n}\n\nexport const startPlugin = traceFunction(\"startPlugin\", function startPlugin(p: Plugin) {\n const { name, commands, flux, contextMenus } = p;\n\n if (p.start) {\n logger.info(\"Starting plugin\", name);\n if (p.started) {\n logger.warn(`${name} already started`);\n return false;\n }\n try {\n p.start();\n p.started = true;\n } catch (e) {\n logger.error(`Failed to start ${name}\\n`, e);\n return false;\n }\n }\n\n if (commands?.length) {\n logger.info(\"Registering commands of plugin\", name);\n for (const cmd of commands) {\n try {\n registerCommand(cmd, name);\n } catch (e) {\n logger.error(`Failed to register command ${cmd.name}\\n`, e);\n return false;\n }\n }\n }\n\n if (flux) {\n for (const event in flux) {\n FluxDispatcher.subscribe(event as FluxEvents, flux[event]);\n }\n }\n\n if (contextMenus) {\n for (const navId in contextMenus) {\n addContextMenuPatch(navId, contextMenus[navId]);\n }\n }\n\n return true;\n}, p => `startPlugin ${p.name}`);\n\nexport const stopPlugin = traceFunction(\"stopPlugin\", function stopPlugin(p: Plugin) {\n const { name, commands, flux, contextMenus } = p;\n if (p.stop) {\n logger.info(\"Stopping plugin\", name);\n if (!p.started) {\n logger.warn(`${name} already stopped`);\n return false;\n }\n try {\n p.stop();\n p.started = false;\n } catch (e) {\n logger.error(`Failed to stop ${name}\\n`, e);\n return false;\n }\n }\n\n if (commands?.length) {\n logger.info(\"Unregistering commands of plugin\", name);\n for (const cmd of commands) {\n try {\n unregisterCommand(cmd.name);\n } catch (e) {\n logger.error(`Failed to unregister command ${cmd.name}\\n`, e);\n return false;\n }\n }\n }\n\n if (flux) {\n for (const event in flux) {\n FluxDispatcher.unsubscribe(event as FluxEvents, flux[event]);\n }\n }\n\n if (contextMenus) {\n for (const navId in contextMenus) {\n removeContextMenuPatch(navId, contextMenus[navId]);\n }\n }\n\n return true;\n}, p => `stopPlugin ${p.name}`);\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport \"./styles.css\";\n\nimport * as DataStore from \"@api/DataStore\";\nimport { showNotice } from \"@api/Notices\";\nimport { Settings, useSettings } from \"@api/Settings\";\nimport { classNameFactory } from \"@api/Styles\";\nimport { CogWheel, InfoIcon } from \"@components/Icons\";\nimport PluginModal from \"@components/PluginSettings/PluginModal\";\nimport { AddonCard } from \"@components/VencordSettings/AddonCard\";\nimport { SettingsTab } from \"@components/VencordSettings/shared\";\nimport { ChangeList } from \"@utils/ChangeList\";\nimport { Logger } from \"@utils/Logger\";\nimport { Margins } from \"@utils/margins\";\nimport { classes, isObjectEmpty } from \"@utils/misc\";\nimport { openModalLazy } from \"@utils/modal\";\nimport { useAwaiter } from \"@utils/react\";\nimport { Plugin } from \"@utils/types\";\nimport { findByPropsLazy } from \"@webpack\";\nimport { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from \"@webpack/common\";\n\nimport Plugins from \"~plugins\";\n\nimport { startDependenciesRecursive, startPlugin, stopPlugin } from \"../../plugins\";\n\n\nconst cl = classNameFactory(\"vc-plugins-\");\nconst logger = new Logger(\"PluginSettings\", \"#a6d189\");\n\nconst InputStyles = findByPropsLazy(\"inputDefault\", \"inputWrapper\");\nconst ButtonClasses = findByPropsLazy(\"button\", \"disabled\", \"enabled\");\n\n\nfunction showErrorToast(message: string) {\n Toasts.show({\n message,\n type: Toasts.Type.FAILURE,\n id: Toasts.genId(),\n options: {\n position: Toasts.Position.BOTTOM\n }\n });\n}\n\nfunction ReloadRequiredCard({ required }: { required: boolean; }) {\n return (\n \n {required ? (\n <>\n Restart required!\n \n Restart now to apply new plugins and their settings\n \n \n \n ) : (\n <>\n Plugin Management\n Press the cog wheel or info icon to get more info on a plugin\n Plugins with a cog wheel have settings you can modify!\n \n )}\n \n );\n}\n\ninterface PluginCardProps extends React.HTMLProps {\n plugin: Plugin;\n disabled: boolean;\n onRestartNeeded(name: string): void;\n isNew?: boolean;\n}\n\nexport function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {\n const settings = Settings.plugins[plugin.name];\n\n const isEnabled = () => settings.enabled ?? false;\n\n function openModal() {\n openModalLazy(async () => {\n return modalProps => {\n return onRestartNeeded(plugin.name)} />;\n };\n });\n }\n\n function toggleEnabled() {\n const wasEnabled = isEnabled();\n\n // If we're enabling a plugin, make sure all deps are enabled recursively.\n if (!wasEnabled) {\n const { restartNeeded, failures } = startDependenciesRecursive(plugin);\n if (failures.length) {\n logger.error(`Failed to start dependencies for ${plugin.name}: ${failures.join(\", \")}`);\n showNotice(\"Failed to start dependencies: \" + failures.join(\", \"), \"Close\", () => null);\n return;\n } else if (restartNeeded) {\n // If any dependencies have patches, don't start the plugin yet.\n settings.enabled = true;\n onRestartNeeded(plugin.name);\n return;\n }\n }\n\n // if the plugin has patches, dont use stopPlugin/startPlugin. Wait for restart to apply changes.\n if (plugin.patches?.length) {\n settings.enabled = !wasEnabled;\n onRestartNeeded(plugin.name);\n return;\n }\n\n // If the plugin is enabled, but hasn't been started, then we can just toggle it off.\n if (wasEnabled && !plugin.started) {\n settings.enabled = !wasEnabled;\n return;\n }\n\n const result = wasEnabled ? stopPlugin(plugin) : startPlugin(plugin);\n\n if (!result) {\n settings.enabled = false;\n\n const msg = `Error while ${wasEnabled ? \"stopping\" : \"starting\"} plugin ${plugin.name}`;\n logger.error(msg);\n showErrorToast(msg);\n return;\n }\n\n settings.enabled = !wasEnabled;\n }\n\n return (\n openModal()} className={classes(ButtonClasses.button, cl(\"info-button\"))}>\n {plugin.options && !isObjectEmpty(plugin.options)\n ? \n : }\n \n }\n />\n );\n}\n\nconst enum SearchStatus {\n ALL,\n ENABLED,\n DISABLED,\n NEW\n}\n\nexport default function PluginSettings() {\n const settings = useSettings();\n const changes = React.useMemo(() => new ChangeList(), []);\n\n React.useEffect(() => {\n return () => void (changes.hasChanges && Alerts.show({\n title: \"Restart required\",\n body: (\n <>\n

The following plugins require a restart:

\n
{changes.map((s, i) => (\n <>\n {i > 0 && \", \"}\n {Parser.parse(\"`\" + s + \"`\")}\n \n ))}
\n \n ),\n confirmText: \"Restart now\",\n cancelText: \"Later!\",\n onConfirm: () => location.reload()\n }));\n }, []);\n\n const depMap = React.useMemo(() => {\n const o = {} as Record;\n for (const plugin in Plugins) {\n const deps = Plugins[plugin].dependencies;\n if (deps) {\n for (const dep of deps) {\n o[dep] ??= [];\n o[dep].push(plugin);\n }\n }\n }\n return o;\n }, []);\n\n const sortedPlugins = React.useMemo(() => Object.values(Plugins)\n .sort((a, b) => a.name.localeCompare(b.name)), []);\n\n const [searchValue, setSearchValue] = React.useState({ value: \"\", status: SearchStatus.ALL });\n\n const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));\n const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));\n\n const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {\n const enabled = settings.plugins[plugin.name]?.enabled;\n if (enabled && searchValue.status === SearchStatus.DISABLED) return false;\n if (!enabled && searchValue.status === SearchStatus.ENABLED) return false;\n if (searchValue.status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;\n if (!searchValue.value.length) return true;\n\n const v = searchValue.value.toLowerCase();\n return (\n plugin.name.toLowerCase().includes(v) ||\n plugin.description.toLowerCase().includes(v) ||\n plugin.tags?.some(t => t.toLowerCase().includes(v))\n );\n };\n\n const [newPlugins] = useAwaiter(() => DataStore.get(\"Vencord_existingPlugins\").then((cachedPlugins: Record | undefined) => {\n const now = Date.now() / 1000;\n const existingTimestamps: Record = {};\n const sortedPluginNames = Object.values(sortedPlugins).map(plugin => plugin.name);\n\n const newPlugins: string[] = [];\n for (const { name: p } of sortedPlugins) {\n const time = existingTimestamps[p] = cachedPlugins?.[p] ?? now;\n if ((time + 60 * 60 * 24 * 2) > now) {\n newPlugins.push(p);\n }\n }\n DataStore.set(\"Vencord_existingPlugins\", existingTimestamps);\n\n return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;\n }));\n\n type P = JSX.Element | JSX.Element[];\n let plugins: P, requiredPlugins: P;\n if (sortedPlugins?.length) {\n plugins = [];\n requiredPlugins = [];\n\n for (const p of sortedPlugins) {\n if (!p.options && p.name.endsWith(\"API\") && searchValue.value !== \"API\")\n continue;\n\n if (!pluginFilter(p)) continue;\n\n const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);\n\n if (isRequired) {\n const tooltipText = p.required\n ? \"This plugin is required for Vencord to function.\"\n : makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));\n\n requiredPlugins.push(\n \n {({ onMouseLeave, onMouseEnter }) => (\n changes.handleChange(name)}\n disabled={true}\n plugin={p}\n />\n )}\n \n );\n } else {\n plugins.push(\n changes.handleChange(name)}\n disabled={false}\n plugin={p}\n isNew={newPlugins?.includes(p.name)}\n key={p.name}\n />\n );\n }\n\n }\n } else {\n plugins = requiredPlugins = No plugins meet search criteria.;\n }\n\n return (\n \n \n\n \n Filters\n \n\n
\n \n
\n v === searchValue.status}\n closeOnSelect={true}\n />\n
\n
\n\n Plugins\n\n
\n {plugins}\n
\n\n \n\n \n Required Plugins\n \n
\n {requiredPlugins}\n
\n
\n );\n}\n\nfunction makeDependencyList(deps: string[]) {\n return (\n \n This plugin is required by:\n {deps.map((dep: string) => {dep})}\n \n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport PluginSettings from \"@components/PluginSettings\";\n\nimport { wrapTab } from \"./shared\";\n\nexport default wrapTab(PluginSettings, \"Plugins\");\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { React } from \"@webpack/common\";\n\ninterface Props extends React.DetailedHTMLProps, HTMLAnchorElement> {\n disabled?: boolean;\n}\n\nexport function Link(props: React.PropsWithChildren) {\n if (props.disabled) {\n props.style ??= {};\n props.style.pointerEvents = \"none\";\n props[\"aria-disabled\"] = true;\n }\n return (\n \n {props.children}\n \n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { useSettings } from \"@api/Settings\";\nimport { classNameFactory } from \"@api/Styles\";\nimport { Flex } from \"@components/Flex\";\nimport { DeleteIcon } from \"@components/Icons\";\nimport { Link } from \"@components/Link\";\nimport PluginModal from \"@components/PluginSettings/PluginModal\";\nimport type { UserThemeHeader } from \"@main/themes\";\nimport { openInviteModal } from \"@utils/discord\";\nimport { Margins } from \"@utils/margins\";\nimport { classes } from \"@utils/misc\";\nimport { openModal } from \"@utils/modal\";\nimport { showItemInFolder } from \"@utils/native\";\nimport { useAwaiter } from \"@utils/react\";\nimport { findByPropsLazy, findLazy } from \"@webpack\";\nimport { Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from \"@webpack/common\";\nimport type { ComponentType, Ref, SyntheticEvent } from \"react\";\n\nimport { AddonCard } from \"./AddonCard\";\nimport { SettingsTab, wrapTab } from \"./shared\";\n\ntype FileInput = ComponentType<{\n ref: Ref;\n onChange: (e: SyntheticEvent) => void;\n multiple?: boolean;\n filters?: { name?: string; extensions: string[]; }[];\n}>;\n\nconst InviteActions = findByPropsLazy(\"resolveInvite\");\nconst FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);\nconst TextAreaProps = findLazy(m => typeof m.textarea === \"string\");\n\nconst cl = classNameFactory(\"vc-settings-theme-\");\n\nfunction Validator({ link }: { link: string; }) {\n const [res, err, pending] = useAwaiter(() => fetch(link).then(res => {\n if (res.status > 300) throw `${res.status} ${res.statusText}`;\n const contentType = res.headers.get(\"Content-Type\");\n if (!contentType?.startsWith(\"text/css\") && !contentType?.startsWith(\"text/plain\"))\n throw \"Not a CSS file. Remember to use the raw link!\";\n\n return \"Okay!\";\n }));\n\n const text = pending\n ? \"Checking...\"\n : err\n ? `Error: ${err instanceof Error ? err.message : String(err)}`\n : \"Valid!\";\n\n return {text};\n}\n\nfunction Validators({ themeLinks }: { themeLinks: string[]; }) {\n if (!themeLinks.length) return null;\n\n return (\n <>\n Validator\n This section will tell you whether your themes can successfully be loaded\n
\n {themeLinks.map(link => (\n \n \n {link}\n \n \n \n ))}\n
\n \n );\n}\n\ninterface ThemeCardProps {\n theme: UserThemeHeader;\n enabled: boolean;\n onChange: (enabled: boolean) => void;\n onDelete: () => void;\n}\n\nfunction ThemeCard({ theme, enabled, onChange, onDelete }: ThemeCardProps) {\n return (\n \n \n \n )\n }\n footer={\n \n {!!theme.website && Website}\n {!!(theme.website && theme.invite) && \" \u2022 \"}\n {!!theme.invite && (\n {\n e.preventDefault();\n theme.invite != null && openInviteModal(theme.invite).catch(() => showToast(\"Invalid or expired invite\"));\n }}\n >\n Discord Server\n \n )}\n \n }\n />\n );\n}\n\nenum ThemeTab {\n LOCAL,\n ONLINE\n}\n\nfunction ThemesTab() {\n const settings = useSettings([\"themeLinks\", \"enabledThemes\"]);\n\n const fileInputRef = useRef(null);\n const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL);\n const [themeText, setThemeText] = useState(settings.themeLinks.join(\"\\n\"));\n const [userThemes, setUserThemes] = useState(null);\n const [themeDir, , themeDirPending] = useAwaiter(VencordNative.themes.getThemesDir);\n\n useEffect(() => {\n refreshLocalThemes();\n }, []);\n\n async function refreshLocalThemes() {\n const themes = await VencordNative.themes.getThemesList();\n setUserThemes(themes);\n }\n\n // When a local theme is enabled/disabled, update the settings\n function onLocalThemeChange(fileName: string, value: boolean) {\n if (value) {\n if (settings.enabledThemes.includes(fileName)) return;\n settings.enabledThemes = [...settings.enabledThemes, fileName];\n } else {\n settings.enabledThemes = settings.enabledThemes.filter(f => f !== fileName);\n }\n }\n\n async function onFileUpload(e: SyntheticEvent) {\n e.stopPropagation();\n e.preventDefault();\n if (!e.currentTarget?.files?.length) return;\n const { files } = e.currentTarget;\n\n const uploads = Array.from(files, file => {\n const { name } = file;\n if (!name.endsWith(\".css\")) return;\n\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => {\n VencordNative.themes.uploadTheme(name, reader.result as string)\n .then(resolve)\n .catch(reject);\n };\n reader.readAsText(file);\n });\n });\n\n await Promise.all(uploads);\n refreshLocalThemes();\n }\n\n function renderLocalThemes() {\n return (\n <>\n \n Find Themes:\n
\n \n BetterDiscord Themes\n \n GitHub\n
\n If using the BD site, click on \"Download\" and place the downloaded .theme.css file into your themes folder.\n
\n\n \n \n <>\n {IS_WEB ?\n (\n \n Upload Theme\n \n \n ) : (\n showItemInFolder(themeDir!)}\n size={Button.Sizes.SMALL}\n disabled={themeDirPending}\n >\n Open Themes Folder\n \n )}\n \n Load missing Themes\n \n VencordNative.quickCss.openEditor()}\n size={Button.Sizes.SMALL}\n >\n Edit QuickCSS\n \n\n {Vencord.Settings.plugins.ClientTheme.enabled && (\n openModal(modalProps => (\n { }}\n />\n ))}\n size={Button.Sizes.SMALL}\n >\n Edit ClientTheme\n \n )}\n \n \n\n
\n {userThemes?.map(theme => (\n onLocalThemeChange(theme.fileName, enabled)}\n onDelete={async () => {\n onLocalThemeChange(theme.fileName, false);\n await VencordNative.themes.deleteTheme(theme.fileName);\n refreshLocalThemes();\n }}\n theme={theme}\n />\n ))}\n
\n
\n \n );\n }\n\n // When the user leaves the online theme textbox, update the settings\n function onBlur() {\n settings.themeLinks = [...new Set(\n themeText\n .trim()\n .split(/\\n+/)\n .map(s => s.trim())\n .filter(Boolean)\n )];\n }\n\n function renderOnlineThemes() {\n return (\n <>\n \n Paste links to css files here\n One link per line\n Make sure to use direct links to files (raw or github.io)!\n \n\n \n \n \n \n \n );\n }\n\n return (\n \n \n \n Local Themes\n \n \n Online Themes\n \n \n\n {currentTab === ThemeTab.LOCAL && renderLocalThemes()}\n {currentTab === ThemeTab.ONLINE && renderOnlineThemes()}\n \n );\n}\n\nexport default wrapTab(ThemesTab, \"Themes\");\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { useSettings } from \"@api/Settings\";\nimport { ErrorCard } from \"@components/ErrorCard\";\nimport { Flex } from \"@components/Flex\";\nimport { Link } from \"@components/Link\";\nimport { Margins } from \"@utils/margins\";\nimport { classes } from \"@utils/misc\";\nimport { relaunch } from \"@utils/native\";\nimport { useAwaiter } from \"@utils/react\";\nimport { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from \"@utils/updater\";\nimport { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from \"@webpack/common\";\n\nimport gitHash from \"~git-hash\";\n\nimport { SettingsTab, wrapTab } from \"./shared\";\n\nfunction withDispatcher(dispatcher: React.Dispatch>, action: () => any) {\n return async () => {\n dispatcher(true);\n try {\n await action();\n } catch (e: any) {\n UpdateLogger.error(\"Failed to update\", e);\n if (!e) {\n var err = \"An unknown error occurred (error is undefined).\\nPlease try again.\";\n } else if (e.code && e.cmd) {\n const { code, path, cmd, stderr } = e;\n\n if (code === \"ENOENT\")\n var err = `Command \\`${path}\\` not found.\\nPlease install it and try again`;\n else {\n var err = `An error occurred while running \\`${cmd}\\`:\\n`;\n err += stderr || `Code \\`${code}\\`. See the console for more info`;\n }\n\n } else {\n var err = \"An unknown error occurred. See the console for more info.\";\n }\n Alerts.show({\n title: \"Oops!\",\n body: (\n \n {err.split(\"\\n\").map(line =>
{Parser.parse(line)}
)}\n
\n )\n });\n }\n finally {\n dispatcher(false);\n }\n };\n}\n\ninterface CommonProps {\n repo: string;\n repoPending: boolean;\n}\n\nfunction HashLink({ repo, hash, disabled = false }: { repo: string, hash: string, disabled?: boolean; }) {\n return \n {hash}\n ;\n}\n\nfunction Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {\n return (\n \n {updates.map(({ hash, author, message }) => (\n
\n \n {message} - {author}\n
\n ))}\n
\n );\n}\n\nfunction Updatable(props: CommonProps) {\n const [updates, setUpdates] = React.useState(changes);\n const [isChecking, setIsChecking] = React.useState(false);\n const [isUpdating, setIsUpdating] = React.useState(false);\n\n const isOutdated = (updates?.length ?? 0) > 0;\n\n return (\n <>\n {!updates && updateError ? (\n <>\n Failed to check updates. Check the console for more info\n \n

{updateError.stderr || updateError.stdout || \"An unknown error occurred\"}

\n
\n \n ) : (\n \n {isOutdated ? (updates.length === 1 ? \"There is 1 Update\" : `There are ${updates.length} Updates`) : \"Up to Date!\"}\n \n )}\n\n {isOutdated && }\n\n \n {isOutdated && {\n if (await update()) {\n setUpdates([]);\n await new Promise(r => {\n Alerts.show({\n title: \"Update Success!\",\n body: \"Successfully updated. Restart now to apply the changes?\",\n confirmText: \"Restart\",\n cancelText: \"Not now!\",\n onConfirm() {\n relaunch();\n r();\n },\n onCancel: r\n });\n });\n }\n })}\n >\n Update Now\n }\n {\n const outdated = await checkForUpdates();\n if (outdated) {\n setUpdates(changes);\n } else {\n setUpdates([]);\n Toasts.show({\n message: \"No updates found!\",\n id: Toasts.genId(),\n type: Toasts.Type.MESSAGE,\n options: {\n position: Toasts.Position.BOTTOM\n }\n });\n }\n })}\n >\n Check for Updates\n \n \n \n );\n}\n\nfunction Newer(props: CommonProps) {\n return (\n <>\n \n Your local copy has more recent commits. Please stash or reset them.\n \n \n \n );\n}\n\nfunction Updater() {\n const settings = useSettings([\"notifyAboutUpdates\", \"autoUpdate\", \"autoUpdateNotification\"]);\n\n const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: \"Loading...\" });\n\n React.useEffect(() => {\n if (err)\n UpdateLogger.error(\"Failed to retrieve repo\", err);\n }, [err]);\n\n const commonProps: CommonProps = {\n repo,\n repoPending\n };\n\n return (\n \n Updater Settings\n settings.notifyAboutUpdates = v}\n note=\"Shows a notification on startup\"\n disabled={settings.autoUpdate}\n >\n Get notified about new updates\n \n settings.autoUpdate = v}\n note=\"Automatically update Vencord without confirmation prompt\"\n >\n Automatically update\n \n settings.autoUpdateNotification = v}\n note=\"Shows a notification when Vencord automatically updates\"\n disabled={!settings.autoUpdate}\n >\n Get notified when an automatic update completes\n \n\n Repo\n\n \n {repoPending\n ? repo\n : err\n ? \"Failed to retrieve - check console\"\n : (\n \n {repo.split(\"/\").slice(-2).join(\"/\")}\n \n )\n }\n {\" \"}()\n \n\n \n\n Updates\n\n {isNewer ? : }\n \n );\n}\n\nexport default IS_UPDATER_DISABLED ? null : wrapTab(Updater, \"Updater\");\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { React, TextInput } from \"@webpack/common\";\n\n// TODO: Refactor settings to use this as well\ninterface TextInputProps {\n /**\n * WARNING: Changing this between renders will have no effect!\n */\n value: string;\n /**\n * This will only be called if the new value passed validate()\n */\n onChange(newValue: string): void;\n /**\n * Optionally validate the user input\n * Return true if the input is valid\n * Otherwise, return a string containing the reason for this input being invalid\n */\n validate(v: string): true | string;\n}\n\n/**\n * A very simple wrapper around Discord's TextInput that validates input and shows\n * the user an error message and only calls your onChange when the input is valid\n */\nexport function CheckedTextInput({ value: initialValue, onChange, validate }: TextInputProps) {\n const [value, setValue] = React.useState(initialValue);\n const [error, setError] = React.useState();\n\n function handleChange(v: string) {\n setValue(v);\n const res = validate(v);\n if (res === true) {\n setError(void 0);\n onChange(v);\n } else {\n setError(res);\n }\n }\n\n return (\n <>\n \n \n );\n}\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { showNotification } from \"@api/Notifications\";\nimport { Settings, useSettings } from \"@api/Settings\";\nimport { CheckedTextInput } from \"@components/CheckedTextInput\";\nimport { Link } from \"@components/Link\";\nimport { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from \"@utils/cloud\";\nimport { Margins } from \"@utils/margins\";\nimport { deleteCloudSettings, getCloudSettings, putCloudSettings } from \"@utils/settingsSync\";\nimport { Alerts, Button, Forms, Switch, Tooltip } from \"@webpack/common\";\n\nimport { SettingsTab, wrapTab } from \"./shared\";\n\nfunction validateUrl(url: string) {\n try {\n new URL(url);\n return true;\n } catch {\n return \"Invalid URL\";\n }\n}\n\nasync function eraseAllData() {\n const res = await fetch(new URL(\"/v1/\", getCloudUrl()), {\n method: \"DELETE\",\n headers: { Authorization: await getCloudAuth() }\n });\n\n if (!res.ok) {\n cloudLogger.error(`Failed to erase data, API returned ${res.status}`);\n showNotification({\n title: \"Cloud Integrations\",\n body: `Could not erase all data (API returned ${res.status}), please contact support.`,\n color: \"var(--red-360)\"\n });\n return;\n }\n\n Settings.cloud.authenticated = false;\n await deauthorizeCloud();\n\n showNotification({\n title: \"Cloud Integrations\",\n body: \"Successfully erased all data.\",\n color: \"var(--green-360)\"\n });\n}\n\nfunction SettingsSyncSection() {\n const { cloud } = useSettings([\"cloud.authenticated\", \"cloud.settingsSync\"]);\n const sectionEnabled = cloud.authenticated && cloud.settingsSync;\n\n return (\n \n \n Synchronize your settings to the cloud. This allows easy synchronization across multiple devices with\n minimal effort.\n \n { cloud.settingsSync = v; }}\n >\n Settings Sync\n \n
\n putCloudSettings(true)}\n >Sync to Cloud\n \n {({ onMouseLeave, onMouseEnter }) => (\n getCloudSettings(true, true)}\n >Sync from Cloud\n )}\n \n deleteCloudSettings()}\n >Delete Cloud Settings\n
\n
\n );\n}\n\nfunction CloudTab() {\n const settings = useSettings([\"cloud.authenticated\", \"cloud.url\"]);\n\n return (\n \n \n \n Vencord comes with a cloud integration that adds goodies like settings sync across devices.\n It respects your privacy, and\n the source code is AGPL 3.0 licensed so you\n can host it yourself.\n \n { v && authorizeCloud(); if (!v) settings.cloud.authenticated = v; }}\n note=\"This will request authorization if you have not yet set up cloud integrations.\"\n >\n Enable Cloud Integrations\n \n Backend URL\n \n Which backend to use when using cloud integrations.\n \n { settings.cloud.url = v; settings.cloud.authenticated = false; deauthorizeCloud(); }}\n validate={validateUrl}\n />\n Alerts.show({\n title: \"Are you sure?\",\n body: \"Once your data is erased, we cannot recover it. There's no going back!\",\n onConfirm: eraseAllData,\n confirmText: \"Erase it!\",\n confirmColor: \"vc-cloud-erase-data-danger-btn\",\n cancelText: \"Nevermind\"\n })}\n >Erase All Data\n \n \n \n \n );\n}\n\nexport default wrapTab(CloudTab, \"Cloud\");\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Flex } from \"@components/Flex\";\nimport { Margins } from \"@utils/margins\";\nimport { classes } from \"@utils/misc\";\nimport { downloadSettingsBackup, uploadSettingsBackup } from \"@utils/settingsSync\";\nimport { Button, Card, Text } from \"@webpack/common\";\n\nimport { SettingsTab, wrapTab } from \"./shared\";\n\nfunction BackupRestoreTab() {\n return (\n \n \n \n Warning\n Importing a settings file will overwrite your current settings.\n \n \n \n You can import and export your Vencord settings as a JSON file.\n This allows you to easily transfer your settings to another device,\n or recover your settings after reinstalling Vencord or Discord.\n \n \n Settings Export contains:\n
    \n
  • — Custom QuickCSS
  • \n
  • — Theme Links
  • \n
  • — Plugin Settings
  • \n
\n
\n \n uploadSettingsBackup()}\n size={Button.Sizes.SMALL}\n >\n Import Settings\n \n \n Export Settings\n \n \n
\n );\n}\n\nexport default wrapTab(BackupRestoreTab, \"Backup & Restore\");\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and Megumin\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Settings } from \"@api/Settings\";\nimport { Devs } from \"@utils/constants\";\nimport definePlugin, { OptionType } from \"@utils/types\";\nimport { React } from \"@webpack/common\";\n\nimport gitHash from \"~git-hash\";\n\nexport default definePlugin({\n name: \"Settings\",\n description: \"Adds Settings UI and debug info\",\n authors: [Devs.Ven, Devs.Megu],\n required: true,\n\n patches: [{\n find: \".versionHash\",\n replacement: [\n {\n match: /\\[\\(0,.{1,3}\\.jsxs?\\)\\((.{1,10}),(\\{[^{}}]+\\{.{0,20}.versionHash,.+?\\})\\),\" \"/,\n replace: (m, component, props) => {\n props = props.replace(/children:\\[.+\\]/, \"\");\n return `${m},Vencord.Plugins.plugins.Settings.makeInfoElements(${component}, ${props})`;\n }\n }\n ]\n }, {\n find: \"Messages.ACTIVITY_SETTINGS\",\n replacement: {\n get match() {\n switch (Settings.plugins.Settings.settingsLocation) {\n case \"top\": return /\\{section:(\\i\\.\\i)\\.HEADER,\\s*label:(\\i)\\.\\i\\.Messages\\.USER_SETTINGS\\}/;\n case \"aboveNitro\": return /\\{section:(\\i\\.\\i)\\.HEADER,\\s*label:(\\i)\\.\\i\\.Messages\\.BILLING_SETTINGS\\}/;\n case \"belowNitro\": return /\\{section:(\\i\\.\\i)\\.HEADER,\\s*label:(\\i)\\.\\i\\.Messages\\.APP_SETTINGS\\}/;\n case \"belowActivity\": return /(?<=\\{section:(\\i\\.\\i)\\.DIVIDER},)\\{section:\"changelog\"/;\n case \"bottom\": return /\\{section:(\\i\\.\\i)\\.CUSTOM,\\s*element:.+?}/;\n case \"aboveActivity\":\n default:\n return /\\{section:(\\i\\.\\i)\\.HEADER,\\s*label:(\\i)\\.\\i\\.Messages\\.ACTIVITY_SETTINGS\\}/;\n }\n },\n replace: \"...$self.makeSettingsCategories($1),$&\"\n }\n }, {\n find: \"Messages.USER_SETTINGS_ACTIONS_MENU_LABEL\",\n replacement: {\n match: /(?<=function\\((\\i),\\i\\)\\{)(?=let \\i=Object.values\\(\\i.UserSettingsSections\\).*?(\\i)\\.default\\.open\\()/,\n replace: \"$2.default.open($1);return;\"\n }\n }],\n\n customSections: [] as ((SectionTypes: Record) => any)[],\n\n makeSettingsCategories(SectionTypes: Record) {\n return [\n {\n section: SectionTypes.HEADER,\n label: \"Vencord\",\n className: \"vc-settings-header\"\n },\n {\n section: \"VencordSettings\",\n label: \"Vencord\",\n element: require(\"@components/VencordSettings/VencordTab\").default,\n className: \"vc-settings\"\n },\n {\n section: \"VencordPlugins\",\n label: \"Plugins\",\n element: require(\"@components/VencordSettings/PluginsTab\").default,\n className: \"vc-plugins\"\n },\n {\n section: \"VencordThemes\",\n label: \"Themes\",\n element: require(\"@components/VencordSettings/ThemesTab\").default,\n className: \"vc-themes\"\n },\n !IS_UPDATER_DISABLED && {\n section: \"VencordUpdater\",\n label: \"Updater\",\n element: require(\"@components/VencordSettings/UpdaterTab\").default,\n className: \"vc-updater\"\n },\n {\n section: \"VencordCloud\",\n label: \"Cloud\",\n element: require(\"@components/VencordSettings/CloudTab\").default,\n className: \"vc-cloud\"\n },\n {\n section: \"VencordSettingsSync\",\n label: \"Backup & Restore\",\n element: require(\"@components/VencordSettings/BackupAndRestoreTab\").default,\n className: \"vc-backup-restore\"\n },\n IS_DEV && {\n section: \"VencordPatchHelper\",\n label: \"Patch Helper\",\n element: require(\"@components/VencordSettings/PatchHelperTab\").default,\n className: \"vc-patch-helper\"\n },\n ...this.customSections.map(func => func(SectionTypes)),\n {\n section: SectionTypes.DIVIDER\n }\n ].filter(Boolean);\n },\n\n options: {\n settingsLocation: {\n type: OptionType.SELECT,\n description: \"Where to put the Vencord settings section\",\n options: [\n { label: \"At the very top\", value: \"top\" },\n { label: \"Above the Nitro section\", value: \"aboveNitro\" },\n { label: \"Below the Nitro section\", value: \"belowNitro\" },\n { label: \"Above Activity Settings\", value: \"aboveActivity\", default: true },\n { label: \"Below Activity Settings\", value: \"belowActivity\" },\n { label: \"At the very bottom\", value: \"bottom\" },\n ],\n restartNeeded: true\n },\n },\n\n get electronVersion() {\n return VencordNative.native.getVersions().electron || window.armcord?.electron || null;\n },\n\n get chromiumVersion() {\n try {\n return VencordNative.native.getVersions().chrome\n // @ts-ignore Typescript will add userAgentData IMMEDIATELY\n || navigator.userAgentData?.brands?.find(b => b.brand === \"Chromium\" || b.brand === \"Google Chrome\")?.version\n || null;\n } catch { // inb4 some stupid browser throws unsupported error for navigator.userAgentData, it's only in chromium\n return null;\n }\n },\n\n get additionalInfo() {\n if (IS_DEV) return \" (Dev)\";\n if (IS_WEB) return \" (Web)\";\n if (IS_VESKTOP) return ` (Vesktop v${VesktopNative.app.getVersion()})`;\n if (IS_STANDALONE) return \" (Standalone)\";\n return \"\";\n },\n\n makeInfoElements(Component: React.ComponentType, props: React.PropsWithChildren) {\n const { electronVersion, chromiumVersion, additionalInfo } = this;\n\n return (\n <>\n Vencord {gitHash}{additionalInfo}\n {electronVersion && Electron {electronVersion}}\n {chromiumVersion && Chromium {chromiumVersion}}\n \n );\n }\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { DataStore } from \"@api/index\";\nimport { Devs, SUPPORT_CHANNEL_ID } from \"@utils/constants\";\nimport { isPluginDev } from \"@utils/misc\";\nimport { makeCodeblock } from \"@utils/text\";\nimport definePlugin from \"@utils/types\";\nimport { isOutdated } from \"@utils/updater\";\nimport { Alerts, Forms, UserStore } from \"@webpack/common\";\n\nimport gitHash from \"~git-hash\";\nimport plugins from \"~plugins\";\n\nimport settings from \"./settings\";\n\nconst REMEMBER_DISMISS_KEY = \"Vencord-SupportHelper-Dismiss\";\n\nconst AllowedChannelIds = [\n SUPPORT_CHANNEL_ID,\n \"1024286218801926184\", // Vencord > #bot-spam\n \"1033680203433660458\", // Vencord > #v\n];\n\nexport default definePlugin({\n name: \"SupportHelper\",\n required: true,\n description: \"Helps us provide support to you\",\n authors: [Devs.Ven],\n dependencies: [\"CommandsAPI\"],\n\n commands: [{\n name: \"vencord-debug\",\n description: \"Send Vencord Debug info\",\n predicate: ctx => AllowedChannelIds.includes(ctx.channel.id),\n async execute() {\n const { RELEASE_CHANNEL } = window.GLOBAL_ENV;\n\n const client = (() => {\n if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;\n if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;\n if (\"armcord\" in window) return `ArmCord v${window.armcord.version}`;\n\n // @ts-expect-error\n const name = typeof unsafeWindow !== \"undefined\" ? \"UserScript\" : \"Web\";\n return `${name} (${navigator.userAgent})`;\n })();\n\n const isApiPlugin = (plugin: string) => plugin.endsWith(\"API\") || plugins[plugin].required;\n\n const enabledPlugins = Object.keys(plugins).filter(p => Vencord.Plugins.isPluginEnabled(p) && !isApiPlugin(p));\n const enabledApiPlugins = Object.keys(plugins).filter(p => Vencord.Plugins.isPluginEnabled(p) && isApiPlugin(p));\n\n const info = {\n Vencord: `v${VERSION} \u2022 ${gitHash}${settings.additionalInfo} - ${Intl.DateTimeFormat(\"en-GB\", { dateStyle: \"medium\" }).format(BUILD_TIMESTAMP)}`,\n \"Discord Branch\": RELEASE_CHANNEL,\n Client: client,\n Platform: window.navigator.platform,\n Outdated: isOutdated,\n OpenAsar: \"openasar\" in window,\n };\n\n if (IS_DISCORD_DESKTOP) {\n info[\"Last Crash Reason\"] = (await DiscordNative.processUtils.getLastCrash())?.rendererCrashReason ?? \"N/A\";\n }\n\n const debugInfo = `\n**Vencord Debug Info**\n>>> ${Object.entries(info).map(([k, v]) => `${k}: ${v}`).join(\"\\n\")}\n\nEnabled Plugins (${enabledPlugins.length + enabledApiPlugins.length}):\n${makeCodeblock(enabledPlugins.join(\", \") + \"\\n\\n\" + enabledApiPlugins.join(\", \"))}\n`;\n\n return {\n content: debugInfo.trim().replaceAll(\"```\\n\", \"```\")\n };\n }\n }],\n\n flux: {\n async CHANNEL_SELECT({ channelId }) {\n if (channelId !== SUPPORT_CHANNEL_ID) return;\n\n if (isPluginDev(UserStore.getCurrentUser().id)) return;\n\n if (isOutdated && gitHash !== await DataStore.get(REMEMBER_DISMISS_KEY)) {\n const rememberDismiss = () => DataStore.set(REMEMBER_DISMISS_KEY, gitHash);\n\n Alerts.show({\n title: \"Hold on!\",\n body:
\n You are using an outdated version of Vencord! Chances are, your issue is already fixed.\n \n Please first update using the Updater Page in Settings, or use the VencordInstaller (Update Vencord Button)\n to do so, in case you can't access the Updater page.\n \n
,\n onCancel: rememberDismiss,\n onConfirm: rememberDismiss\n });\n }\n }\n }\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"AlwaysAnimate\",\n description: \"Animates anything that can be animated\",\n authors: [Devs.FieryFlames],\n\n patches: [\n {\n find: \"canAnimate:\",\n all: true,\n // Some modules match the find but the replacement is returned untouched\n noWarn: true,\n replacement: {\n match: /canAnimate:.+?(?=([,}].*?\\)))/g,\n replace: (m, rest) => {\n const destructuringMatch = rest.match(/}=.+/);\n if (destructuringMatch == null) return \"canAnimate:!0\";\n return m;\n }\n }\n },\n {\n // Status emojis\n find: \".Messages.GUILD_OWNER,\",\n replacement: {\n match: /(?<=\\.activityEmoji,.+?animate:)\\i/,\n replace: \"!0\"\n }\n },\n {\n // Guild Banner\n find: \".animatedBannerHoverLayer,onMouseEnter:\",\n replacement: {\n match: /(?<=guildBanner:\\i,animate:)\\i(?=}\\))/,\n replace: \"!0\"\n }\n }\n ]\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { definePluginSettings } from \"@api/Settings\";\nimport { Devs } from \"@utils/constants\";\nimport definePlugin, { OptionType } from \"@utils/types\";\n\nconst settings = definePluginSettings({\n domain: {\n type: OptionType.BOOLEAN,\n default: true,\n description: \"Remove the untrusted domain popup when opening links\",\n restartNeeded: true\n },\n file: {\n type: OptionType.BOOLEAN,\n default: true,\n description: \"Remove the 'Potentially Dangerous Download' popup when opening links\",\n restartNeeded: true\n }\n});\n\nexport default definePlugin({\n name: \"AlwaysTrust\",\n description: \"Removes the annoying untrusted domain and suspicious file popup\",\n authors: [Devs.zt, Devs.Trwy],\n patches: [\n {\n find: \".displayName=\\\"MaskedLinkStore\\\"\",\n replacement: {\n match: /(?<=isTrustedDomain\\(\\i\\){)return \\i\\(\\i\\)/,\n replace: \"return true\"\n },\n predicate: () => settings.store.domain\n },\n {\n find: \"isSuspiciousDownload:\",\n replacement: {\n match: /function \\i\\(\\i\\){(?=.{0,60}\\.parse\\(\\i\\))/,\n replace: \"$&return null;\"\n },\n predicate: () => settings.store.file\n }\n ],\n settings\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Upload } from \"@api/MessageEvents\";\nimport { definePluginSettings } from \"@api/Settings\";\nimport ErrorBoundary from \"@components/ErrorBoundary\";\nimport { Devs } from \"@utils/constants\";\nimport definePlugin, { OptionType } from \"@utils/types\";\nimport { findByCodeLazy, findByPropsLazy } from \"@webpack\";\n\ntype AnonUpload = Upload & { anonymise?: boolean; };\n\nconst ActionBarIcon = findByCodeLazy(\".actionBarIcon)\");\nconst UploadDraft = findByPropsLazy(\"popFirstFile\", \"update\");\n\nconst enum Methods {\n Random,\n Consistent,\n Timestamp,\n}\n\nconst tarExtMatcher = /\\.tar\\.\\w+$/;\n\nconst settings = definePluginSettings({\n anonymiseByDefault: {\n description: \"Whether to anonymise file names by default\",\n type: OptionType.BOOLEAN,\n default: true,\n },\n method: {\n description: \"Anonymising method\",\n type: OptionType.SELECT,\n options: [\n { label: \"Random Characters\", value: Methods.Random, default: true },\n { label: \"Consistent\", value: Methods.Consistent },\n { label: \"Timestamp\", value: Methods.Timestamp },\n ],\n },\n randomisedLength: {\n description: \"Random characters length\",\n type: OptionType.NUMBER,\n default: 7,\n disabled: () => settings.store.method !== Methods.Random,\n },\n consistent: {\n description: \"Consistent filename\",\n type: OptionType.STRING,\n default: \"image\",\n disabled: () => settings.store.method !== Methods.Consistent,\n },\n});\n\nexport default definePlugin({\n name: \"AnonymiseFileNames\",\n authors: [Devs.fawn],\n description: \"Anonymise uploaded file names\",\n patches: [\n {\n find: \"instantBatchUpload:function\",\n replacement: {\n match: /uploadFiles:(.{1,2}),/,\n replace:\n \"uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=$self.anonymise(f)),$1(...args)),\",\n },\n },\n {\n find: \"message.attachments\",\n replacement: {\n match: /(\\i.uploadFiles\\((\\i),)/,\n replace: \"$2.forEach(f=>f.filename=$self.anonymise(f)),$1\"\n }\n },\n {\n find: \".Messages.ATTACHMENT_UTILITIES_SPOILER\",\n replacement: {\n match: /(?<=children:\\[)(?=.{10,80}tooltip:.{0,100}\\i\\.\\i\\.Messages\\.ATTACHMENT_UTILITIES_SPOILER)/,\n replace: \"arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null,\"\n },\n },\n ],\n settings,\n\n renderIcon: ErrorBoundary.wrap(({ upload, channelId, draftType }: { upload: AnonUpload; draftType: unknown; channelId: string; }) => {\n const anonymise = upload.anonymise ?? settings.store.anonymiseByDefault;\n return (\n {\n upload.anonymise = !anonymise;\n UploadDraft.update(channelId, upload.id, draftType, {}); // dummy update so component rerenders\n }}\n >\n {anonymise\n ? \n : \n }\n \n );\n }, { noop: true }),\n\n anonymise(upload: AnonUpload) {\n if ((upload.anonymise ?? settings.store.anonymiseByDefault) === false) return upload.filename;\n\n const file = upload.filename;\n const tarMatch = tarExtMatcher.exec(file);\n const extIdx = tarMatch?.index ?? file.lastIndexOf(\".\");\n const ext = extIdx !== -1 ? file.slice(extIdx) : \"\";\n\n switch (settings.store.method) {\n case Methods.Random:\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n return Array.from(\n { length: settings.store.randomisedLength },\n () => chars[Math.floor(Math.random() * chars.length)]\n ).join(\"\") + ext;\n case Methods.Consistent:\n return settings.store.consistent + ext;\n case Methods.Timestamp:\n return Date.now() + ext;\n }\n },\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 OpenAsar\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { popNotice, showNotice } from \"@api/Notices\";\nimport { Link } from \"@components/Link\";\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\nimport { findByPropsLazy } from \"@webpack\";\nimport { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from \"@webpack/common\";\n\nconst RpcUtils = findByPropsLazy(\"fetchApplicationsRPC\", \"getRemoteIconURL\");\n\nasync function lookupAsset(applicationId: string, key: string): Promise {\n return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];\n}\n\nconst apps: any = {};\nasync function lookupApp(applicationId: string): Promise {\n const socket: any = {};\n await RpcUtils.fetchApplicationsRPC(socket, applicationId);\n return socket.application;\n}\n\nlet ws: WebSocket;\nexport default definePlugin({\n name: \"WebRichPresence (arRPC)\",\n description: \"Client plugin for arRPC to enable RPC on Discord Web (experimental)\",\n authors: [Devs.Ducko],\n\n settingsAboutComponent: () => (\n <>\n How to use arRPC\n \n Follow the instructions in the GitHub repo to get the server running, and then enable the plugin.\n \n \n ),\n\n async handleEvent(e: MessageEvent) {\n const data = JSON.parse(e.data);\n\n const { activity } = data;\n const assets = activity?.assets;\n\n if (assets?.large_image) assets.large_image = await lookupAsset(activity.application_id, assets.large_image);\n if (assets?.small_image) assets.small_image = await lookupAsset(activity.application_id, assets.small_image);\n\n if (activity) {\n const appId = activity.application_id;\n apps[appId] ||= await lookupApp(appId);\n\n const app = apps[appId];\n activity.name ||= app.name;\n }\n\n FluxDispatcher.dispatch({ type: \"LOCAL_ACTIVITY_UPDATE\", ...data });\n },\n\n async start() {\n // ArmCord comes with its own arRPC implementation, so this plugin just confuses users\n if (\"armcord\" in window) return;\n\n if (ws) ws.close();\n ws = new WebSocket(\"ws://127.0.0.1:1337\"); // try to open WebSocket\n\n ws.onmessage = this.handleEvent;\n\n const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 1000)); // check if open after 1s\n if (!connectionSuccessful) {\n showNotice(\"Failed to connect to arRPC, is it running?\", \"Retry\", () => { // show notice about failure to connect, with retry/ignore\n popNotice();\n this.start();\n });\n return;\n }\n\n Toasts.show({ // show toast on success\n message: \"Connected to arRPC\",\n type: Toasts.Type.SUCCESS,\n id: Toasts.genId(),\n options: {\n duration: 1000,\n position: Toasts.Position.BOTTOM\n }\n });\n },\n\n stop() {\n FluxDispatcher.dispatch({ type: \"LOCAL_ACTIVITY_UPDATE\", activity: null }); // clear status\n ws?.close(); // close WebSocket\n }\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin, { OptionType } from \"@utils/types\";\n\nexport default definePlugin({\n name: \"BANger\",\n description: \"Replaces the GIF in the ban dialogue with a custom one.\",\n authors: [Devs.Xinto, Devs.Glitch],\n patches: [\n {\n find: \"BAN_CONFIRM_TITLE.\",\n replacement: {\n match: /src:\\i\\(\"\\d+\"\\)/g,\n replace: \"src: Vencord.Settings.plugins.BANger.source\"\n }\n }\n ],\n options: {\n source: {\n description: \"Source to replace ban GIF with (Video or Gif)\",\n type: OptionType.STRING,\n default: \"https://i.imgur.com/wp5q52C.mp4\",\n restartNeeded: true,\n }\n }\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport ErrorBoundary from \"@components/ErrorBoundary\";\nimport { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from \"@webpack\";\nimport { useStateFromStores } from \"@webpack/common\";\nimport type { CSSProperties } from \"react\";\n\nimport { ExpandedGuildFolderStore, settings } from \".\";\n\nconst ChannelRTCStore = findStoreLazy(\"ChannelRTCStore\");\nconst Animations = findByPropsLazy(\"a\", \"animated\", \"useTransition\");\nconst GuildsBar = findComponentByCodeLazy('(\"guildsnav\")');\n\nexport default ErrorBoundary.wrap(guildsBarProps => {\n const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());\n const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());\n\n const Sidebar = (\n \n );\n\n const visible = !!expandedFolders.size;\n const guilds = document.querySelector(guildsBarProps.className.split(\" \").map(c => `.${c}`).join(\"\"));\n\n // We need to display none if we are in fullscreen. Yes this seems horrible doing with css, but it's literally how Discord does it.\n // Also display flex otherwise to fix scrolling\n const barStyle = {\n display: isFullscreen ? \"none\" : \"flex\",\n } as CSSProperties;\n\n if (!guilds || !settings.store.sidebarAnim) {\n return visible\n ?
{Sidebar}
\n : null;\n }\n\n return (\n \n {(animationStyle, show) =>\n show && (\n \n {Sidebar}\n \n )\n }\n \n );\n}, { noop: true });\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2023 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { definePluginSettings } from \"@api/Settings\";\nimport { Devs } from \"@utils/constants\";\nimport definePlugin, { OptionType } from \"@utils/types\";\nimport { findByPropsLazy, findStoreLazy } from \"@webpack\";\nimport { FluxDispatcher, i18n } from \"@webpack/common\";\n\nimport FolderSideBar from \"./FolderSideBar\";\n\nenum FolderIconDisplay {\n Never,\n Always,\n MoreThanOneFolderExpanded\n}\n\nconst { GuildsTree } = findByPropsLazy(\"GuildsTree\");\nconst SortedGuildStore = findStoreLazy(\"SortedGuildStore\");\nexport const ExpandedGuildFolderStore = findStoreLazy(\"ExpandedGuildFolderStore\");\nconst FolderUtils = findByPropsLazy(\"move\", \"toggleGuildFolderExpand\");\n\nlet lastGuildId = null as string | null;\nlet dispatchingFoldersClose = false;\n\nfunction getGuildFolder(id: string) {\n return SortedGuildStore.getGuildFolders().find(folder => folder.guildIds.includes(id));\n}\n\nfunction closeFolders() {\n for (const id of ExpandedGuildFolderStore.getExpandedFolders())\n FolderUtils.toggleGuildFolderExpand(id);\n}\n\nexport const settings = definePluginSettings({\n sidebar: {\n type: OptionType.BOOLEAN,\n description: \"Display servers from folder on dedicated sidebar\",\n restartNeeded: true,\n default: true\n },\n sidebarAnim: {\n type: OptionType.BOOLEAN,\n description: \"Animate opening the folder sidebar\",\n default: true\n },\n closeAllFolders: {\n type: OptionType.BOOLEAN,\n description: \"Close all folders when selecting a server not in a folder\",\n default: false\n },\n closeAllHomeButton: {\n type: OptionType.BOOLEAN,\n description: \"Close all folders when clicking on the home button\",\n restartNeeded: true,\n default: false\n },\n closeOthers: {\n type: OptionType.BOOLEAN,\n description: \"Close other folders when opening a folder\",\n default: false\n },\n forceOpen: {\n type: OptionType.BOOLEAN,\n description: \"Force a folder to open when switching to a server of that folder\",\n default: false\n },\n keepIcons: {\n type: OptionType.BOOLEAN,\n description: \"Keep showing guild icons in the primary guild bar folder when it's open in the BetterFolders sidebar\",\n restartNeeded: true,\n default: false\n },\n showFolderIcon: {\n type: OptionType.SELECT,\n description: \"Show the folder icon above the folder guilds in the BetterFolders sidebar\",\n options: [\n { label: \"Never\", value: FolderIconDisplay.Never },\n { label: \"Always\", value: FolderIconDisplay.Always, default: true },\n { label: \"When more than one folder is expanded\", value: FolderIconDisplay.MoreThanOneFolderExpanded }\n ],\n restartNeeded: true\n }\n});\n\nexport default definePlugin({\n name: \"BetterFolders\",\n description: \"Shows server folders on dedicated sidebar and adds folder related improvements\",\n authors: [Devs.juby, Devs.AutumnVN, Devs.Nuckyz],\n\n settings,\n\n patches: [\n {\n find: '(\"guildsnav\")',\n predicate: () => settings.store.sidebar,\n replacement: [\n // Create the isBetterFolders variable in the GuildsBar component\n {\n match: /(?<=let{disableAppDownload:\\i=\\i\\.isPlatformEmbedded,isOverlay:.+?)(?=}=\\i,)/,\n replace: \",isBetterFolders\"\n },\n // If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders\n {\n match: /(useStateFromStoresArray\\).{0,25}let \\i)=(\\i\\.\\i.getGuildsTree\\(\\))/,\n replace: (_, rest, guildsTree) => `${rest}=$self.getGuildTree(!!arguments[0].isBetterFolders,${guildsTree},arguments[0].betterFoldersExpandedIds)`\n },\n // If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children\n {\n match: /lastTargetNode:\\i\\[\\i\\.length-1\\].+?Fragment.+?\\]}\\)\\]/,\n replace: \"$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0].isBetterFolders))\"\n },\n // If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children\n {\n match: /unreadMentionsIndicatorBottom,barClassName.+?}\\)\\]/,\n replace: \"$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0].isBetterFolders))\"\n },\n // Export the isBetterFolders variable to the folders component\n {\n match: /(?<=\\.Messages\\.SERVERS.+?switch\\((\\i)\\.type\\){case \\i\\.\\i\\.FOLDER:.+?folderNode:\\i,)/,\n replace: 'isBetterFolders:typeof isBetterFolders!==\"undefined\"?isBetterFolders:false,'\n }\n ]\n },\n {\n // This is the parent folder component\n find: \".MAX_GUILD_FOLDER_NAME_LENGTH,\",\n predicate: () => settings.store.sidebar && settings.store.showFolderIcon !== FolderIconDisplay.Always,\n replacement: [\n {\n // Modify the expanded state to instead return the list of expanded folders\n match: /(useStateFromStores\\).{0,20}=>)(\\i\\.\\i)\\.isFolderExpanded\\(\\i\\)/,\n replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders()`,\n },\n {\n // Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds\n // Also export the list of expanded folders to the child folder component if the patch above succeeds, else export undefined\n match: /(?<=folderNode:(\\i),expanded:)\\i(?=,)/,\n replace: (isExpandedOrExpandedIds, folderNote) => \"\"\n + `typeof ${isExpandedOrExpandedIds}===\"boolean\"?${isExpandedOrExpandedIds}:${isExpandedOrExpandedIds}.has(${folderNote}.id),`\n + `betterFoldersExpandedIds:${isExpandedOrExpandedIds} instanceof Set?${isExpandedOrExpandedIds}:void 0`\n }\n ]\n },\n {\n find: \".FOLDER_ITEM_GUILD_ICON_MARGIN);\",\n predicate: () => settings.store.sidebar,\n replacement: [\n // We use arguments[0] to access the isBetterFolders variable in this nested folder component (the parent exports all the props so we don't have to patch it)\n\n // If we are rendering the normal GuildsBar sidebar, we make Discord think the folder is always collapsed to show better icons (the mini guild icons) and avoid transitions\n {\n predicate: () => settings.store.keepIcons,\n match: /(?<=let{folderNode:\\i,setNodeRef:\\i,.+?expanded:(\\i),.+?;)(?=let)/,\n replace: (_, isExpanded) => `${isExpanded}=!!arguments[0].isBetterFolders&&${isExpanded};`\n },\n // Disable expanding and collapsing folders transition in the normal GuildsBar sidebar\n {\n predicate: () => !settings.store.keepIcons,\n match: /(?<=\\.Messages\\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\\)\\()/,\n replace: \"!!arguments[0].isBetterFolders&&\"\n },\n // If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded\n {\n predicate: () => !settings.store.keepIcons,\n match: /expandedFolderBackground,.+?,(?=\\i\\(\\(\\i,\\i,\\i\\)=>{let{key.{0,45}ul)(?<=selected:\\i,expanded:(\\i),.+?)/,\n replace: (m, isExpanded) => `${m}!arguments[0].isBetterFolders&&${isExpanded}?null:`\n },\n {\n // Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar\n predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,\n match: /(?<=\\.wrapper,children:\\[)/,\n replace: \"$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)&&\"\n },\n {\n // Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar\n predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,\n match: /(?<=\\.expandedFolderBackground.+?}\\),)(?=\\i,)/,\n replace: \"!$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)?null:\"\n }\n ]\n },\n {\n find: \"APPLICATION_LIBRARY,render\",\n predicate: () => settings.store.sidebar,\n replacement: {\n // Render the Better Folders sidebar\n match: /(?<=({className:\\i\\.guilds,themeOverride:\\i})\\))/,\n replace: \",$self.FolderSideBar($1)\"\n }\n },\n {\n find: \".Messages.DISCODO_DISABLED\",\n predicate: () => settings.store.closeAllHomeButton,\n replacement: {\n // Close all folders when clicking the home button\n match: /(?<=onClick:\\(\\)=>{)(?=.{0,200}\"discodo\")/,\n replace: \"$self.closeFolders();\"\n }\n }\n ],\n\n flux: {\n CHANNEL_SELECT(data) {\n if (!settings.store.closeAllFolders && !settings.store.forceOpen)\n return;\n\n if (lastGuildId !== data.guildId) {\n lastGuildId = data.guildId;\n const guildFolder = getGuildFolder(data.guildId);\n\n if (guildFolder?.folderId) {\n if (settings.store.forceOpen && !ExpandedGuildFolderStore.isFolderExpanded(guildFolder.folderId)) {\n FolderUtils.toggleGuildFolderExpand(guildFolder.folderId);\n }\n } else if (settings.store.closeAllFolders) {\n closeFolders();\n }\n }\n },\n\n TOGGLE_GUILD_FOLDER_EXPAND(data) {\n if (settings.store.closeOthers && !dispatchingFoldersClose) {\n dispatchingFoldersClose = true;\n\n FluxDispatcher.wait(() => {\n const expandedFolders = ExpandedGuildFolderStore.getExpandedFolders();\n\n if (expandedFolders.size > 1) {\n for (const id of expandedFolders) if (id !== data.folderId)\n FolderUtils.toggleGuildFolderExpand(id);\n }\n\n dispatchingFoldersClose = false;\n });\n }\n }\n },\n\n getGuildTree(isBetterFolders: boolean, oldTree: any, expandedFolderIds?: Set) {\n if (!isBetterFolders || expandedFolderIds == null) return oldTree;\n\n const newTree = new GuildsTree();\n // Children is every folder and guild which is not in a folder, this filters out only the expanded folders\n newTree.root.children = oldTree.root.children.filter(guildOrFolder => expandedFolderIds.has(guildOrFolder.id));\n // Nodes is every folder and guild, even if it's in a folder, this filters out only the expanded folders and guilds inside them\n newTree.nodes = Object.fromEntries(\n Object.entries(oldTree.nodes)\n .filter(([_, guildOrFolder]: any[]) => expandedFolderIds.has(guildOrFolder.id) || expandedFolderIds.has(guildOrFolder.parentId))\n );\n\n return newTree;\n },\n\n makeGuildsBarGuildListFilter(isBetterFolders: boolean) {\n return child => {\n if (isBetterFolders) {\n return child?.props?.[\"aria-label\"] === i18n.Messages.SERVERS;\n }\n return true;\n };\n },\n\n makeGuildsBarTreeFilter(isBetterFolders: boolean) {\n return child => {\n if (isBetterFolders) {\n return \"onScroll\" in child.props;\n }\n return true;\n };\n },\n\n shouldShowFolderIconAndBackground(isBetterFolders: boolean, expandedFolderIds?: Set) {\n if (!isBetterFolders) return true;\n\n switch (settings.store.showFolderIcon) {\n case FolderIconDisplay.Never:\n return false;\n case FolderIconDisplay.Always:\n return true;\n case FolderIconDisplay.MoreThanOneFolderExpanded:\n return (expandedFolderIds?.size ?? 0) > 1;\n default:\n return true;\n }\n },\n\n FolderSideBar: guildsBarProps => ,\n\n closeFolders\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"BetterGifAltText\",\n authors: [Devs.Ven],\n description:\n \"Change GIF alt text from simply being 'GIF' to containing the gif tags / filename\",\n patches: [\n {\n find: \"onCloseImage=\",\n replacement: {\n match: /(return.{0,10}\\.jsx.{0,50}isWindowFocused)/,\n replace:\n \"$self.altify(e);$1\",\n },\n },\n {\n find: \".Messages.GIF,\",\n replacement: {\n match: /alt:(\\i)=(\\i\\.default\\.Messages\\.GIF)(?=,[^}]*\\}=(\\i))/,\n replace:\n // rename prop so we can always use default value\n \"alt_$$:$1=$self.altify($3)||$2\",\n },\n },\n ],\n\n altify(props: any) {\n props.alt ??= \"GIF\";\n if (props.alt !== \"GIF\") return props.alt;\n\n let url: string = props.original || props.src;\n try {\n url = decodeURI(url);\n } catch { }\n\n let name = url\n .slice(url.lastIndexOf(\"/\") + 1)\n .replace(/\\d/g, \"\") // strip numbers\n .replace(/.gif$/, \"\") // strip extension\n .split(/[,\\-_ ]+/g)\n .slice(0, 20)\n .join(\" \");\n if (name.length > 300) {\n name = name.slice(0, 300) + \"...\";\n }\n\n if (name) props.alt += ` - ${name}`;\n\n return props.alt;\n },\n});\n", "/*\n * Vencord, a Discord client mod\n * Copyright (c) 2024 Vendicated and contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\n\nimport { Devs } from \"@utils/constants\";\nimport definePlugin from \"@utils/types\";\n\nexport default definePlugin({\n name: \"BetterGifPicker\",\n description: \"Makes the gif picker open the favourite category by default\",\n authors: [Devs.Samwich],\n patches: [\n {\n find: \".GIFPickerResultTypes.SEARCH\",\n replacement: [{\n match: \"this.state={resultType:null}\",\n replace: 'this.state={resultType:\"Favorites\"}'\n }]\n }\n ]\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Settings } from \"@api/Settings\";\nimport { Devs } from \"@utils/constants\";\nimport definePlugin, { OptionType } from \"@utils/types\";\nimport { findByPropsLazy } from \"@webpack\";\n\nconst UserPopoutSectionCssClasses = findByPropsLazy(\"section\", \"lastSection\");\n\nexport default definePlugin({\n name: \"BetterNotesBox\",\n description: \"Hide notes or disable spellcheck (Configure in settings!!)\",\n authors: [Devs.Ven],\n\n patches: [\n {\n find: \"hideNote:\",\n all: true,\n // Some modules match the find but the replacement is returned untouched\n noWarn: true,\n predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide,\n replacement: {\n match: /hideNote:.+?(?=([,}].*?\\)))/g,\n replace: (m, rest) => {\n const destructuringMatch = rest.match(/}=.+/);\n if (destructuringMatch == null) return \"hideNote:!0\";\n return m;\n }\n }\n },\n {\n find: \"Messages.NOTE_PLACEHOLDER\",\n replacement: {\n match: /\\.NOTE_PLACEHOLDER,/,\n replace: \"$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck,\"\n }\n },\n {\n find: \".Messages.NOTE}\",\n replacement: {\n match: /(?<=return \\i\\?)null(?=:\\(0,\\i\\.jsxs)/,\n replace: \"$self.patchPadding(arguments[0])\"\n }\n }\n ],\n\n options: {\n hide: {\n type: OptionType.BOOLEAN,\n description: \"Hide notes\",\n default: false,\n restartNeeded: true\n },\n noSpellCheck: {\n type: OptionType.BOOLEAN,\n description: \"Disable spellcheck in notes\",\n disabled: () => Settings.plugins.BetterNotesBox.hide,\n default: false\n }\n },\n\n patchPadding(e: any) {\n if (!e.lastSection) return;\n return (\n
\n );\n }\n});\n", "/*\n * Vencord, a Discord client mod\n * Copyright (c) 2024 Vendicated and contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\n\nimport { Devs } from \"@utils/constants\";\nimport { getCurrentGuild } from \"@utils/discord\";\nimport definePlugin from \"@utils/types\";\nimport { findByPropsLazy } from \"@webpack\";\nimport { Clipboard, GuildStore, Menu, PermissionStore, TextAndImagesSettingsStores } from \"@webpack/common\";\n\nconst GuildSettingsActions = findByPropsLazy(\"open\", \"selectRole\", \"updateGuild\");\n\nfunction PencilIcon() {\n return (\n \n \n \n );\n}\n\nfunction AppearanceIcon() {\n return (\n \n \n \n );\n}\n\nexport default definePlugin({\n name: \"BetterRoleContext\",\n description: \"Adds options to copy role color / edit role when right clicking roles in the user profile\",\n authors: [Devs.Ven],\n\n start() {\n // DeveloperMode needs to be enabled for the context menu to be shown\n TextAndImagesSettingsStores.DeveloperMode.updateSetting(true);\n },\n\n contextMenus: {\n \"dev-context\"(children, { id }: { id: string; }) {\n const guild = getCurrentGuild();\n if (!guild) return;\n\n const role = GuildStore.getRole(guild.id, id);\n if (!role) return;\n\n if (role.colorString) {\n children.push(\n Clipboard.copy(role.colorString!)}\n icon={AppearanceIcon}\n />\n );\n }\n\n if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {\n children.push(\n {\n await GuildSettingsActions.open(guild.id, \"ROLES\");\n GuildSettingsActions.selectRole(id);\n }}\n icon={PencilIcon}\n />\n );\n }\n }\n }\n});\n", "/*\n * Vencord, a modification for Discord's desktop app\n * Copyright (c) 2022 Vendicated and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n*/\n\nimport { Settings } from \"@api/Settings\";\nimport { Devs } from \"@utils/constants\";\nimport definePlugin, { OptionType } from \"@utils/types\";\nimport { Clipboard, Toasts } from \"@webpack/common\";\n\nexport default definePlugin({\n name: \"BetterRoleDot\",\n authors: [Devs.Ven, Devs.AutumnVN],\n description:\n \"Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously\",\n\n patches: [\n {\n find: \".dotBorderBase\",\n replacement: {\n match: /,viewBox:\"0 0 20 20\"/,\n replace: \"$&,onClick:()=>$self.copyToClipBoard(arguments[0].color),style:{cursor:'pointer'}\",\n },\n },\n {\n find: '\"dot\"===',\n all: true,\n noWarn: true,\n predicate: () => Settings.plugins.BetterRoleDot.bothStyles,\n replacement: {\n match: /\"(?:username|dot)\"===\\i(?!\\.\\i)/g,\n replace: \"true\",\n },\n },\n\n {\n find: \".ADD_ROLE_A11Y_LABEL\",\n predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,\n noWarn: true,\n replacement: {\n match: /\"dot\"===\\i/,\n replace: \"true\"\n }\n },\n {\n find: \".roleVerifiedIcon\",\n predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,\n noWarn: true,\n replacement: {\n match: /\"dot\"===\\i/,\n replace: \"true\"\n }\n }\n ],\n\n options: {\n bothStyles: {\n type: OptionType.BOOLEAN,\n description: \"Show both role dot and coloured names\",\n restartNeeded: true,\n default: false,\n },\n copyRoleColorInProfilePopout: {\n type: OptionType.BOOLEAN,\n description: \"Allow click on role dot in profile popout to copy role color\",\n restartNeeded: true,\n default: false\n }\n },\n\n copyToClipBoard(color: string) {\n Clipboard.copy(color);\n Toasts.show({\n message: \"Copied to Clipboard!\",\n type: Toasts.Type.SUCCESS,\n id: Toasts.genId(),\n options: {\n duration: 1000,\n position: Toasts.Position.BOTTOM\n }\n });\n },\n});\n", "/*\n * Vencord, a Discord client mod\n * Copyright (c) 2024 Vendicated and contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\n\nimport { definePluginSettings } from \"@api/Settings\";\nimport { classNameFactory } from \"@api/Styles\";\nimport ErrorBoundary from \"@components/ErrorBoundary\";\nimport { Devs } from \"@utils/constants\";\nimport definePlugin, { OptionType } from \"@utils/types\";\nimport { findByPropsLazy } from \"@webpack\";\nimport { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from \"@webpack/common\";\nimport type { HTMLAttributes, ReactElement } from \"react\";\n\ntype SettingsEntry = { section: string, label: string; };\n\nconst cl = classNameFactory(\"\");\nconst Classes = findByPropsLazy(\"animating\", \"baseLayer\", \"bg\", \"layer\", \"layers\");\n\nconst settings = definePluginSettings({\n disableFade: {\n description: \"Disable the crossfade animation\",\n type: OptionType.BOOLEAN,\n default: true,\n restartNeeded: true\n },\n organizeMenu: {\n description: \"Organizes the settings cog context menu into categories\",\n type: OptionType.BOOLEAN,\n default: true\n },\n eagerLoad: {\n description: \"Removes the loading delay when opening the menu for the first time\",\n type: OptionType.BOOLEAN,\n default: true,\n restartNeeded: true\n }\n});\n\ninterface LayerProps extends HTMLAttributes {\n mode: \"SHOWN\" | \"HIDDEN\";\n baseLayer?: boolean;\n}\n\nfunction Layer({ mode, baseLayer = false, ...props }: LayerProps) {\n const hidden = mode === \"HIDDEN\";\n const containerRef = useRef(null);\n\n useEffect(() => () => {\n ComponentDispatch.dispatch(\"LAYER_POP_START\");\n ComponentDispatch.dispatch(\"LAYER_POP_COMPLETE\");\n }, []);\n\n const node = (\n