Repository: Vendicated/Vencord Branch: main Commit: cba0eb989741 Files: 752 Total size: 2.4 MB Directory structure: gitextract_c3tzg39v/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── blank.yml │ │ ├── bug_report.yml │ │ └── config.yml │ └── workflows/ │ ├── build.yml │ ├── codeberg-mirror.yml │ ├── publish.yml │ ├── reportBrokenPlugins.yml │ └── test.yml ├── .gitignore ├── .npmrc ├── .stylelintrc.json ├── .vscode/ │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── browser/ │ ├── GMPolyfill.js │ ├── Vencord.ts │ ├── VencordNativeStub.ts │ ├── background.js │ ├── content.js │ ├── manifest.json │ ├── manifestv2.json │ ├── modifyResponseHeaders.json │ ├── monaco.ts │ ├── monacoWin.html │ ├── patch-worker.js │ └── userscript.meta.js ├── eslint.config.mjs ├── package.json ├── packages/ │ ├── discord-types/ │ │ ├── .npmignore │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── enums/ │ │ │ ├── activity.ts │ │ │ ├── channel.ts │ │ │ ├── commands.ts │ │ │ ├── index.ts │ │ │ ├── messages.ts │ │ │ ├── misc.ts │ │ │ └── user.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── common/ │ │ │ │ ├── Activity.d.ts │ │ │ │ ├── Application.d.ts │ │ │ │ ├── Channel.d.ts │ │ │ │ ├── Guild.d.ts │ │ │ │ ├── GuildMember.d.ts │ │ │ │ ├── Record.d.ts │ │ │ │ ├── Role.d.ts │ │ │ │ ├── User.d.ts │ │ │ │ ├── index.d.ts │ │ │ │ └── messages/ │ │ │ │ ├── Commands.d.ts │ │ │ │ ├── Embed.d.ts │ │ │ │ ├── Emoji.d.ts │ │ │ │ ├── Message.d.ts │ │ │ │ ├── Sticker.d.ts │ │ │ │ └── index.d.ts │ │ │ ├── components.d.ts │ │ │ ├── flux.d.ts │ │ │ ├── fluxEvents.d.ts │ │ │ ├── index.d.ts │ │ │ ├── menu.d.ts │ │ │ ├── modules/ │ │ │ │ ├── CloudUpload.d.ts │ │ │ │ └── index.d.ts │ │ │ ├── stores/ │ │ │ │ ├── AccessibilityStore.d.ts │ │ │ │ ├── ActiveJoinedThreadsStore.d.ts │ │ │ │ ├── ApplicationStore.d.ts │ │ │ │ ├── AuthenticationStore.d.ts │ │ │ │ ├── CallStore.d.ts │ │ │ │ ├── ChannelRTCStore.d.ts │ │ │ │ ├── ChannelStore.d.ts │ │ │ │ ├── DraftStore.d.ts │ │ │ │ ├── EmojiStore.d.ts │ │ │ │ ├── FluxStore.d.ts │ │ │ │ ├── FriendsStore.d.ts │ │ │ │ ├── GuildChannelStore.d.ts │ │ │ │ ├── GuildMemberCountStore.d.ts │ │ │ │ ├── GuildMemberStore.d.ts │ │ │ │ ├── GuildRoleStore.d.ts │ │ │ │ ├── GuildScheduledEventStore.d.ts │ │ │ │ ├── GuildStore.d.ts │ │ │ │ ├── InstantInviteStore.d.ts │ │ │ │ ├── InviteStore.d.ts │ │ │ │ ├── LocaleStore.d.ts │ │ │ │ ├── MediaEngineStore.d.ts │ │ │ │ ├── MessageStore.d.ts │ │ │ │ ├── NotificationSettingsStore.d.ts │ │ │ │ ├── OverridePremiumTypeStore.d.ts │ │ │ │ ├── PendingReplyStore.d.ts │ │ │ │ ├── PermissionStore.d.ts │ │ │ │ ├── PopoutWindowStore.d.ts │ │ │ │ ├── PresenceStore.d.ts │ │ │ │ ├── RTCConnectionStore.d.ts │ │ │ │ ├── ReadStateStore.d.ts │ │ │ │ ├── RelationshipStore.d.ts │ │ │ │ ├── RunningGameStore.d.ts │ │ │ │ ├── SelectedChannelStore.d.ts │ │ │ │ ├── SelectedGuildStore.d.ts │ │ │ │ ├── SoundboardStore.d.ts │ │ │ │ ├── SpellCheckStore.d.ts │ │ │ │ ├── SpotifyStore.d.ts │ │ │ │ ├── StickersStore.d.ts │ │ │ │ ├── StreamerModeStore.d.ts │ │ │ │ ├── ThemeStore.d.ts │ │ │ │ ├── TypingStore.d.ts │ │ │ │ ├── UploadAttachmentStore.d.ts │ │ │ │ ├── UserGuildSettingsStore.d.ts │ │ │ │ ├── UserProfileStore.d.ts │ │ │ │ ├── UserSettingsProtoStore.d.ts │ │ │ │ ├── UserStore.d.ts │ │ │ │ ├── VoiceStateStore.d.ts │ │ │ │ ├── WindowStore.d.ts │ │ │ │ └── index.d.ts │ │ │ └── utils.d.ts │ │ └── webpack/ │ │ └── index.d.ts │ └── vencord-types/ │ ├── .gitignore │ ├── .npmignore │ ├── HOW2PUB.md │ ├── README.md │ ├── globals.d.ts │ ├── index.d.ts │ ├── package.json │ └── prepare.ts ├── patches/ │ └── eslint-plugin-path-alias@2.1.0.patch ├── pnpm-workspace.yaml ├── scripts/ │ ├── build/ │ │ ├── build.mjs │ │ ├── buildWeb.mjs │ │ ├── common.mjs │ │ ├── inject/ │ │ │ └── react.mjs │ │ └── module/ │ │ └── style.js │ ├── checkNodeVersion.js │ ├── generatePluginList.ts │ ├── generateReport.ts │ ├── header-new.txt │ ├── header-old.txt │ ├── runInstaller.mjs │ ├── suppressExperimentalWarnings.js │ └── utils.mjs ├── src/ │ ├── Vencord.ts │ ├── VencordNative.ts │ ├── api/ │ │ ├── Badges.ts │ │ ├── ChatButton.css │ │ ├── ChatButtons.tsx │ │ ├── Commands/ │ │ │ ├── commandHelpers.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── ContextMenu.ts │ │ ├── DataStore/ │ │ │ ├── LICENSE │ │ │ └── index.ts │ │ ├── MemberListDecorators.tsx │ │ ├── MessageAccessories.tsx │ │ ├── MessageDecorations.tsx │ │ ├── MessageEvents.ts │ │ ├── MessagePopover.tsx │ │ ├── MessageUpdater.ts │ │ ├── Notices.tsx │ │ ├── Notifications/ │ │ │ ├── NotificationComponent.tsx │ │ │ ├── Notifications.tsx │ │ │ ├── index.ts │ │ │ ├── notificationLog.tsx │ │ │ └── styles.css │ │ ├── PluginManager.ts │ │ ├── ServerList.tsx │ │ ├── Settings.ts │ │ ├── SettingsSync/ │ │ │ ├── cloudSetup.tsx │ │ │ ├── cloudSync.ts │ │ │ └── offline.ts │ │ ├── Styles.ts │ │ ├── Themes.ts │ │ ├── UserSettings.ts │ │ └── index.ts │ ├── components/ │ │ ├── BaseText.css │ │ ├── BaseText.tsx │ │ ├── Button.css │ │ ├── Button.tsx │ │ ├── Card.css │ │ ├── Card.tsx │ │ ├── CheckedTextInput.tsx │ │ ├── CodeBlock.tsx │ │ ├── Divider.css │ │ ├── Divider.tsx │ │ ├── ErrorBoundary.tsx │ │ ├── ErrorCard.css │ │ ├── ErrorCard.tsx │ │ ├── ExpandableCard.css │ │ ├── ExpandableCard.tsx │ │ ├── Flex.tsx │ │ ├── FormSwitch.css │ │ ├── FormSwitch.tsx │ │ ├── Grid.tsx │ │ ├── Heading.css │ │ ├── Heading.tsx │ │ ├── Heart.tsx │ │ ├── Icons.tsx │ │ ├── Link.tsx │ │ ├── Paragraph.tsx │ │ ├── Span.tsx │ │ ├── Switch.css │ │ ├── Switch.tsx │ │ ├── TooltipContainer.tsx │ │ ├── TooltipFallback.tsx │ │ ├── handleComponentFailed.ts │ │ ├── iconStyles.css │ │ ├── index.ts │ │ ├── margins.ts │ │ └── settings/ │ │ ├── AddonCard.css │ │ ├── AddonCard.tsx │ │ ├── DonateButton.tsx │ │ ├── PluginBadge.tsx │ │ ├── QuickAction.css │ │ ├── QuickAction.tsx │ │ ├── SpecialCard.css │ │ ├── SpecialCard.tsx │ │ ├── index.ts │ │ └── tabs/ │ │ ├── BaseTab.tsx │ │ ├── index.ts │ │ ├── patchHelper/ │ │ │ ├── FullPatchInput.tsx │ │ │ ├── PatchPreview.tsx │ │ │ ├── ReplacementInput.tsx │ │ │ └── index.tsx │ │ ├── plugins/ │ │ │ ├── ContributorModal.css │ │ │ ├── ContributorModal.tsx │ │ │ ├── LinkIconButton.css │ │ │ ├── LinkIconButton.tsx │ │ │ ├── PluginCard.tsx │ │ │ ├── PluginModal.css │ │ │ ├── PluginModal.tsx │ │ │ ├── UIElements.css │ │ │ ├── UIElements.tsx │ │ │ ├── components/ │ │ │ │ ├── BooleanSetting.tsx │ │ │ │ ├── Common.tsx │ │ │ │ ├── ComponentSetting.tsx │ │ │ │ ├── NumberSetting.tsx │ │ │ │ ├── SelectSetting.tsx │ │ │ │ ├── SliderSetting.tsx │ │ │ │ ├── TextSetting.tsx │ │ │ │ ├── index.ts │ │ │ │ └── styles.css │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── styles.css │ │ ├── sync/ │ │ │ ├── BackupAndRestoreTab.tsx │ │ │ └── CloudTab.tsx │ │ ├── themes/ │ │ │ ├── CspErrorCard.tsx │ │ │ ├── LocalThemesTab.tsx │ │ │ ├── OnlineThemesTab.tsx │ │ │ ├── ThemeCard.tsx │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── updater/ │ │ │ ├── Components.tsx │ │ │ ├── index.tsx │ │ │ └── runWithDispatch.tsx │ │ └── vencord/ │ │ ├── DonateButton.tsx │ │ ├── MacVibrancySettings.tsx │ │ ├── NotificationSettings.tsx │ │ └── index.tsx │ ├── debug/ │ │ ├── Tracer.ts │ │ ├── loadLazyChunks.ts │ │ └── runReporter.ts │ ├── globals.d.ts │ ├── main/ │ │ ├── csp/ │ │ │ ├── index.ts │ │ │ └── manager.ts │ │ ├── index.ts │ │ ├── ipcMain.ts │ │ ├── ipcPlugins.ts │ │ ├── monacoWin.html │ │ ├── patchWin32Updater.ts │ │ ├── patcher.ts │ │ ├── settings.ts │ │ ├── themes/ │ │ │ ├── LICENSE │ │ │ └── index.ts │ │ ├── updater/ │ │ │ ├── common.ts │ │ │ ├── git.ts │ │ │ ├── http.ts │ │ │ └── index.ts │ │ └── utils/ │ │ ├── constants.ts │ │ ├── crxToZip.ts │ │ ├── extensions.ts │ │ ├── externalLinks.ts │ │ └── http.ts │ ├── modules.d.ts │ ├── nativeModules.d.ts │ ├── plugins/ │ │ ├── _api/ │ │ │ ├── badges/ │ │ │ │ ├── fixDiscordBadgePadding.css │ │ │ │ └── index.tsx │ │ │ ├── chatButtons.ts │ │ │ ├── commands.ts │ │ │ ├── contextMenu.ts │ │ │ ├── dynamicImageModalApi.ts │ │ │ ├── memberListDecorators/ │ │ │ │ ├── index.tsx │ │ │ │ └── style.css │ │ │ ├── menuItemDemangler.ts │ │ │ ├── messageAccessories.ts │ │ │ ├── messageDecorations/ │ │ │ │ ├── index.tsx │ │ │ │ └── style.css │ │ │ ├── messageEvents.ts │ │ │ ├── messagePopover.ts │ │ │ ├── messageUpdater.ts │ │ │ ├── notices.ts │ │ │ ├── serverList.ts │ │ │ └── userSettings.ts │ │ ├── _core/ │ │ │ ├── noTrack.ts │ │ │ ├── settings.tsx │ │ │ └── supportHelper.tsx │ │ ├── accountPanelServerProfile/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── alwaysAnimate/ │ │ │ └── index.ts │ │ ├── alwaysExpandRoles/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── alwaysTrust/ │ │ │ └── index.ts │ │ ├── anonymiseFileNames/ │ │ │ └── index.tsx │ │ ├── appleMusic.desktop/ │ │ │ ├── README.md │ │ │ ├── index.tsx │ │ │ └── native.ts │ │ ├── arRPC.web/ │ │ │ └── index.tsx │ │ ├── autoDndWhilePlaying.discordDesktop/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── betterFolders/ │ │ │ ├── FolderSideBar.tsx │ │ │ ├── README.md │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── betterGifAltText/ │ │ │ └── index.ts │ │ ├── betterGifPicker/ │ │ │ └── index.ts │ │ ├── betterNotes/ │ │ │ └── index.tsx │ │ ├── betterRoleContext/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── betterRoleDot/ │ │ │ └── index.ts │ │ ├── betterSessions/ │ │ │ ├── README.md │ │ │ ├── components/ │ │ │ │ ├── RenameButton.tsx │ │ │ │ ├── RenameModal.tsx │ │ │ │ └── icons.tsx │ │ │ ├── index.tsx │ │ │ ├── styles.css │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── betterSettings/ │ │ │ ├── README.md │ │ │ ├── fullHeightContext.css │ │ │ └── index.tsx │ │ ├── betterUploadButton/ │ │ │ └── index.ts │ │ ├── biggerStreamPreview/ │ │ │ ├── index.tsx │ │ │ └── webpack/ │ │ │ ├── stores.ts │ │ │ └── types/ │ │ │ └── stores.ts │ │ ├── blurNsfw/ │ │ │ └── index.ts │ │ ├── callTimer/ │ │ │ ├── alignedChatInputFix.css │ │ │ └── index.tsx │ │ ├── clearURLs/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── clientTheme/ │ │ │ ├── README.md │ │ │ ├── clientTheme.css │ │ │ ├── components/ │ │ │ │ └── Settings.tsx │ │ │ ├── index.tsx │ │ │ └── utils/ │ │ │ ├── colorUtils.ts │ │ │ └── styleUtils.ts │ │ ├── colorSighted/ │ │ │ └── index.ts │ │ ├── consoleJanitor/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── consoleShortcuts/ │ │ │ ├── index.ts │ │ │ └── native.ts │ │ ├── copyEmojiMarkdown/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── copyFileContents/ │ │ │ ├── README.md │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── copyStickerLinks/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── copyUserURLs/ │ │ │ └── index.tsx │ │ ├── crashHandler/ │ │ │ └── index.ts │ │ ├── ctrlEnterSend/ │ │ │ └── index.ts │ │ ├── customCommands/ │ │ │ ├── CreateTagModal.tsx │ │ │ ├── SettingsTagList.tsx │ │ │ ├── index.ts │ │ │ ├── settings.ts │ │ │ └── styles.css │ │ ├── customIdle/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── customRPC/ │ │ │ ├── README.md │ │ │ ├── RpcSettings.tsx │ │ │ ├── index.tsx │ │ │ └── settings.css │ │ ├── dearrow/ │ │ │ ├── README.md │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── decor/ │ │ │ ├── README.md │ │ │ ├── index.tsx │ │ │ ├── lib/ │ │ │ │ ├── api.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── stores/ │ │ │ │ │ ├── AuthorizationStore.tsx │ │ │ │ │ ├── CurrentUserDecorationsStore.ts │ │ │ │ │ └── UsersDecorationsStore.ts │ │ │ │ └── utils/ │ │ │ │ └── decoration.ts │ │ │ ├── settings.tsx │ │ │ └── ui/ │ │ │ ├── components/ │ │ │ │ ├── DecorDecorationGridDecoration.tsx │ │ │ │ ├── DecorSection.tsx │ │ │ │ ├── DecorationContextMenu.tsx │ │ │ │ ├── DecorationGridCreate.tsx │ │ │ │ ├── DecorationGridNone.tsx │ │ │ │ ├── Grid.tsx │ │ │ │ ├── SectionedGridList.tsx │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── modals/ │ │ │ │ ├── ChangeDecorationModal.tsx │ │ │ │ ├── CreateDecorationModal.tsx │ │ │ │ └── GuidelinesModal.tsx │ │ │ └── styles.css │ │ ├── devCompanion.dev/ │ │ │ └── index.tsx │ │ ├── disableCallIdle/ │ │ │ └── index.ts │ │ ├── dontRoundMyTimestamps/ │ │ │ └── index.ts │ │ ├── experiments/ │ │ │ ├── hideBugReport.css │ │ │ └── index.tsx │ │ ├── expressionCloner/ │ │ │ └── index.tsx │ │ ├── f8break/ │ │ │ └── index.ts │ │ ├── fakeNitro/ │ │ │ └── index.tsx │ │ ├── fakeProfileThemes/ │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── favEmojiFirst/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── favGifSearch/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── fixCodeblockGap/ │ │ │ └── index.ts │ │ ├── fixImagesQuality/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── fixSpotifyEmbeds.desktop/ │ │ │ ├── index.tsx │ │ │ └── native.ts │ │ ├── fixYoutubeEmbeds.desktop/ │ │ │ ├── README.md │ │ │ ├── index.ts │ │ │ └── native.ts │ │ ├── forceOwnerCrown/ │ │ │ └── index.ts │ │ ├── friendInvites/ │ │ │ └── index.ts │ │ ├── friendsSince/ │ │ │ ├── README.md │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── fullSearchContext/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── fullUserInChatbox/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── gameActivityToggle/ │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── gifPaste/ │ │ │ └── index.ts │ │ ├── greetStickerPicker/ │ │ │ └── index.tsx │ │ ├── hideAttachments/ │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── iLoveSpam/ │ │ │ └── index.ts │ │ ├── ignoreActivities/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── imageFilename/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── imageLink/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── imageZoom/ │ │ │ ├── README.md │ │ │ ├── components/ │ │ │ │ └── Magnifier.tsx │ │ │ ├── constants.ts │ │ │ ├── index.tsx │ │ │ ├── styles.css │ │ │ └── utils/ │ │ │ └── waitFor.ts │ │ ├── implicitRelationships/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── ircColors/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── keepCurrentChannel/ │ │ │ └── index.ts │ │ ├── lastfmRichPresence/ │ │ │ └── index.tsx │ │ ├── loadingQuotes/ │ │ │ ├── index.ts │ │ │ └── quotes.txt │ │ ├── memberCount/ │ │ │ ├── CircleIcon.tsx │ │ │ ├── MemberCount.tsx │ │ │ ├── OnlineMemberCountStore.ts │ │ │ ├── VoiceIcon.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── mentionAvatars/ │ │ │ ├── README.md │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── messageClickActions/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── messageLatency/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── messageLinkEmbeds/ │ │ │ └── index.tsx │ │ ├── messageLogger/ │ │ │ ├── HistoryModal.tsx │ │ │ ├── deleteStyleOverlay.css │ │ │ ├── deleteStyleText.css │ │ │ ├── index.tsx │ │ │ └── messageLogger.css │ │ ├── moreQuickReactions/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── mutualGroupDMs/ │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── newGuildSettings/ │ │ │ └── index.tsx │ │ ├── noBlockedMessages/ │ │ │ └── index.ts │ │ ├── noDeepLinks.web/ │ │ │ └── index.ts │ │ ├── noDefaultHangStatus/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── noDevtoolsWarning/ │ │ │ └── index.ts │ │ ├── noF1/ │ │ │ └── index.ts │ │ ├── noMaskedUrlPaste/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── noMosaic/ │ │ │ └── index.ts │ │ ├── noOnboardingDelay/ │ │ │ └── index.ts │ │ ├── noPendingCount/ │ │ │ └── index.ts │ │ ├── noProfileThemes/ │ │ │ └── index.ts │ │ ├── noReplyMention/ │ │ │ └── index.tsx │ │ ├── noServerEmojis/ │ │ │ └── index.ts │ │ ├── noSystemBadge.discordDesktop/ │ │ │ └── index.ts │ │ ├── noTypingAnimation/ │ │ │ └── index.ts │ │ ├── noUnblockToJump/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── notificationVolume/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── onePingPerDM/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── oneko/ │ │ │ └── index.ts │ │ ├── openInApp/ │ │ │ ├── README.md │ │ │ ├── index.ts │ │ │ └── native.ts │ │ ├── overrideForumDefaults/ │ │ │ └── index.tsx │ │ ├── pauseInvitesForever/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── permissionFreeWill/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── permissionsViewer/ │ │ │ ├── components/ │ │ │ │ ├── RolesAndUsersPermissions.tsx │ │ │ │ ├── UserPermissions.tsx │ │ │ │ └── icons.tsx │ │ │ ├── index.tsx │ │ │ ├── styles.css │ │ │ └── utils.ts │ │ ├── petpet/ │ │ │ └── index.ts │ │ ├── pictureInPicture/ │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── pinDms/ │ │ │ ├── components/ │ │ │ │ ├── CreateCategoryModal.tsx │ │ │ │ └── contextMenu.tsx │ │ │ ├── constants.ts │ │ │ ├── data.ts │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── plainFolderIcon/ │ │ │ ├── index.ts │ │ │ └── style.css │ │ ├── platformIndicators/ │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── previewMessage/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── quickMention/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── quickReply/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── reactErrorDecoder/ │ │ │ └── index.ts │ │ ├── readAllNotificationsButton/ │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── relationshipNotifier/ │ │ │ ├── functions.ts │ │ │ ├── index.ts │ │ │ ├── settings.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── replaceGoogleSearch/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── replyTimestamp/ │ │ │ ├── README.md │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── revealAllSpoilers/ │ │ │ └── index.ts │ │ ├── reverseImageSearch/ │ │ │ └── index.tsx │ │ ├── reviewDB/ │ │ │ ├── auth.tsx │ │ │ ├── components/ │ │ │ │ ├── BlockedUserModal.tsx │ │ │ │ ├── MessageButton.tsx │ │ │ │ ├── ReviewBadge.tsx │ │ │ │ ├── ReviewComponent.tsx │ │ │ │ ├── ReviewModal.tsx │ │ │ │ └── ReviewsView.tsx │ │ │ ├── entities.ts │ │ │ ├── index.tsx │ │ │ ├── reviewDbApi.ts │ │ │ ├── settings.tsx │ │ │ ├── style.css │ │ │ └── utils.tsx │ │ ├── roleColorEverywhere/ │ │ │ └── index.tsx │ │ ├── secretRingTone/ │ │ │ └── index.ts │ │ ├── seeSummaries/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── sendTimestamps/ │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── serverInfo/ │ │ │ ├── GuildInfoModal.tsx │ │ │ ├── README.md │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── serverListIndicators/ │ │ │ └── index.tsx │ │ ├── shikiCodeblocks.desktop/ │ │ │ ├── api/ │ │ │ │ ├── languages.ts │ │ │ │ ├── shiki.ts │ │ │ │ └── themes.ts │ │ │ ├── components/ │ │ │ │ ├── ButtonRow.tsx │ │ │ │ ├── Code.tsx │ │ │ │ ├── CopyButton.tsx │ │ │ │ ├── Header.tsx │ │ │ │ └── Highlighter.tsx │ │ │ ├── devicon.css │ │ │ ├── hooks/ │ │ │ │ ├── useCopyCooldown.ts │ │ │ │ ├── useShikiSettings.ts │ │ │ │ └── useTheme.ts │ │ │ ├── index.ts │ │ │ ├── previewExample.tsx │ │ │ ├── settings.ts │ │ │ ├── shiki.css │ │ │ ├── types.ts │ │ │ └── utils/ │ │ │ ├── color.ts │ │ │ ├── createStyle.ts │ │ │ └── misc.ts │ │ ├── showAllMessageButtons/ │ │ │ └── index.ts │ │ ├── showConnections/ │ │ │ ├── VerifiedIcon.tsx │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── showHiddenChannels/ │ │ │ ├── components/ │ │ │ │ └── HiddenChannelLockScreen.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── showHiddenThings/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── showMeYourName/ │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── showTimeoutDuration/ │ │ │ ├── README.md │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── silentMessageToggle/ │ │ │ └── index.tsx │ │ ├── silentTyping/ │ │ │ └── index.tsx │ │ ├── sortFriendRequests/ │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── spotifyControls/ │ │ │ ├── PlayerComponent.tsx │ │ │ ├── SeekBar.ts │ │ │ ├── SpotifyStore.ts │ │ │ ├── hoverOnly.css │ │ │ ├── index.tsx │ │ │ └── spotifyStyles.css │ │ ├── spotifyCrack/ │ │ │ └── index.ts │ │ ├── spotifyShareCommands/ │ │ │ └── index.ts │ │ ├── startupTimings/ │ │ │ ├── StartupTimingPage.tsx │ │ │ └── index.tsx │ │ ├── stickerPaste/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── streamerModeOnStream/ │ │ │ └── index.ts │ │ ├── superReactionTweaks/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── textReplace/ │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── themeAttributes/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── translate/ │ │ │ ├── TranslateIcon.tsx │ │ │ ├── TranslateModal.tsx │ │ │ ├── TranslationAccessory.tsx │ │ │ ├── index.tsx │ │ │ ├── languages.ts │ │ │ ├── native.ts │ │ │ ├── settings.ts │ │ │ ├── styles.css │ │ │ └── utils.ts │ │ ├── typingIndicator/ │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── typingTweaks/ │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── unindent/ │ │ │ └── index.ts │ │ ├── unlockedAvatarZoom/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── unsuppressEmbeds/ │ │ │ └── index.tsx │ │ ├── userMessagesPronouns/ │ │ │ ├── PronounsChatComponent.tsx │ │ │ ├── README.md │ │ │ ├── index.ts │ │ │ ├── settings.ts │ │ │ └── utils.ts │ │ ├── userVoiceShow/ │ │ │ ├── README.md │ │ │ ├── components.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── usrbg/ │ │ │ └── index.tsx │ │ ├── validReply/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── validUser/ │ │ │ └── index.tsx │ │ ├── vcDoubleClick/ │ │ │ └── index.ts │ │ ├── vcNarrator/ │ │ │ ├── VoiceSetting.tsx │ │ │ ├── index.tsx │ │ │ └── settings.ts │ │ ├── vencordToolbox/ │ │ │ ├── index.tsx │ │ │ ├── menu.tsx │ │ │ └── styles.css │ │ ├── viewIcons/ │ │ │ └── index.tsx │ │ ├── viewRaw/ │ │ │ └── index.tsx │ │ ├── voiceDownload/ │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── voiceMessages/ │ │ │ ├── DesktopRecorder.tsx │ │ │ ├── VoicePreview.tsx │ │ │ ├── WebRecorder.tsx │ │ │ ├── index.tsx │ │ │ ├── native.ts │ │ │ ├── settings.ts │ │ │ └── styles.css │ │ ├── volumeBooster/ │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── webContextMenus.web/ │ │ │ └── index.ts │ │ ├── webKeybinds.web/ │ │ │ └── index.ts │ │ ├── webScreenShareFixes.web/ │ │ │ └── index.ts │ │ ├── whoReacted/ │ │ │ ├── README.md │ │ │ └── index.tsx │ │ ├── xsOverlay/ │ │ │ ├── README.md │ │ │ ├── index.tsx │ │ │ └── native.ts │ │ └── youtubeAdblock.desktop/ │ │ ├── README.md │ │ ├── adguard.js │ │ ├── index.ts │ │ └── native.ts │ ├── preload.ts │ ├── shared/ │ │ ├── IpcEvents.ts │ │ ├── SettingsStore.ts │ │ ├── debounce.ts │ │ ├── onceDefined.ts │ │ └── vencordUserAgent.ts │ ├── utils/ │ │ ├── ChangeList.ts │ │ ├── Logger.ts │ │ ├── Queue.ts │ │ ├── apng.ts │ │ ├── clipboard.ts │ │ ├── constants.ts │ │ ├── cspViolations.ts │ │ ├── css.ts │ │ ├── dependencies.ts │ │ ├── discord.tsx │ │ ├── guards.ts │ │ ├── index.ts │ │ ├── intlHash.ts │ │ ├── lazy.ts │ │ ├── lazyReact.tsx │ │ ├── localStorage.ts │ │ ├── margins.ts │ │ ├── mergeDefaults.ts │ │ ├── misc.ts │ │ ├── modal.tsx │ │ ├── native.ts │ │ ├── onlyOnce.ts │ │ ├── patches.ts │ │ ├── react.tsx │ │ ├── text.ts │ │ ├── types.ts │ │ ├── updater.ts │ │ ├── web-metadata.ts │ │ └── web.ts │ └── webpack/ │ ├── common/ │ │ ├── components.ts │ │ ├── index.ts │ │ ├── internal.tsx │ │ ├── menu.ts │ │ ├── react.ts │ │ ├── stores.ts │ │ ├── userSettings.ts │ │ └── utils.ts │ ├── index.ts │ ├── patchWebpack.ts │ ├── types.ts │ └── webpack.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome: https://EditorConfig.org # top-most EditorConfig file root = true [*] indent_style = space indent_size = 4 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf ================================================ FILE: .github/ISSUE_TEMPLATE/blank.yml ================================================ name: Blank Issue description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL. body: - type: markdown attributes: value: | ![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. - type: textarea id: content attributes: label: Content validations: required: true - type: checkboxes id: agreement-check attributes: label: Request Agreement options: - label: I have read the requirements for opening an issue above required: true ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug/Crash Report description: Create a bug or crash report for Vencord. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL. labels: [bug] title: "[Bug] " body: - type: markdown attributes: value: | ![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. - type: textarea id: bug-description attributes: label: What happens when the bug or crash occurs? description: Where does this bug or crash occur, when does it occur, etc. placeholder: The bug/crash happens sometimes when I do ..., causing this to not work/the app to crash. I think it happens because of ... validations: required: true - type: textarea id: expected-behaviour attributes: label: What is the expected behaviour? description: Simply detail what the expected behaviour is. placeholder: I expect Vencord/Discord to open the ... page instead of ..., it prevents me from doing ... validations: required: true - type: textarea id: steps-to-take attributes: label: How do you recreate this bug or crash? description: Give us a list of steps in order to recreate the bug or crash. placeholder: | 1. Do ... 2. Then ... 3. Do this ..., ... and then ... 4. Observe "the bug" or "the crash" validations: required: true - type: textarea id: crash-log attributes: label: Errors description: Open the Developer Console with Ctrl/Cmd + Shift + i. Then look for any red errors (Ignore network errors like Failed to load resource) and paste them between the "```". value: | ``` Replace this text with your crash-log. ``` validations: required: false - type: checkboxes id: agreement-check attributes: label: Request Agreement description: We only accept reports for bugs that happen on Discord Stable. Canary and PTB are Development branches and may be unstable options: - label: I am using Discord Stable or tried on Stable and this bug happens there as well required: true - label: I am a Vencord Developer required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Vencord Support Server url: https://discord.gg/D9uwnFnqmd about: If you need help regarding Vencord, please join our support server! - name: Vencord Installer url: https://github.com/Vencord/Installer about: You can find the Vencord Installer here ================================================ FILE: .github/workflows/build.yml ================================================ name: Build DevBuild on: push: branches: - main paths: - .github/workflows/build.yml - src/** - browser/** - scripts/build/** - package.json - pnpm-lock.yaml env: FORCE_COLOR: true jobs: Build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json - name: Use Node.js 20 uses: actions/setup-node@v4 with: node-version: 20 cache: "pnpm" - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build web run: pnpm buildWebStandalone - name: Build run: pnpm build --standalone - name: Generate plugin list run: pnpm generatePluginJson dist/plugins.json dist/plugin-readmes.json - name: Clean up obsolete files run: | rm -rf dist/*-unpacked dist/vendor Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map - name: Get some values needed for the release id: release_values run: | echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - name: Upload DevBuild as release if: github.repository == 'Vendicated/Vencord' run: | gh release upload devbuild --clobber dist/* gh release edit devbuild --title "DevBuild $RELEASE_TAG" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_TAG: ${{ env.release_tag }} - name: Upload DevBuild to builds repo if: github.repository == 'Vendicated/Vencord' run: | git config --global user.name "$USERNAME" git config --global user.email actions@github.com git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git upload cd upload GLOBIGNORE=.git:.gitignore:README.md:LICENSE rm -rf * cp -r ../dist/* . git add -A git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA" git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git env: API_TOKEN: ${{ secrets.BUILDS_TOKEN }} GH_REPO: Vencord/builds USERNAME: GitHub-Actions ================================================ FILE: .github/workflows/codeberg-mirror.yml ================================================ name: Sync to Codeberg concurrency: group: ${{ github.ref }} cancel-in-progress: true on: push: workflow_dispatch: schedule: - cron: "0 */6 * * *" jobs: codeberg: if: github.repository == 'Vendicated/Vencord' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1.1.1 with: target_repo_url: "git@codeberg.org:Vee/cord.git" ssh_private_key: ${{ secrets.CODEBERG_SSH_PRIVATE_KEY }} ================================================ FILE: .github/workflows/publish.yml ================================================ name: Release Browser Extension on: push: tags: - v* jobs: Publish: if: github.repository == 'Vendicated/Vencord' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: check that tag matches package.json version run: | pkg_version="v$(jq -r .version < package.json)" if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2 exit 1 fi - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json - name: Use Node.js 19 uses: actions/setup-node@v4 with: node-version: 20 cache: "pnpm" - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build web run: pnpm buildWebStandalone - name: Publish extension run: | cd dist/chromium-unpacked pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish env: EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} ================================================ FILE: .github/workflows/reportBrokenPlugins.yml ================================================ name: Test Patches on: workflow_dispatch: inputs: discord_branch: type: choice description: "Discord Branch to test patches on" options: - both - stable - canary default: both webhook_url: type: string description: "Webhook URL that the report will be posted to. This will be visible for everyone, so DO NOT pass sensitive webhooks like discord webhook. This is meant to be used by Venbot." required: false # schedule: # # Every day at midnight # - cron: 0 0 * * * jobs: TestPlugins: if: github.repository == 'Vendicated/Vencord' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 if: ${{ github.event_name == 'schedule' }} with: ref: dev - uses: actions/checkout@v4 if: ${{ github.event_name == 'workflow_dispatch' }} - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json - name: Use Node.js 20 uses: actions/setup-node@v4 with: node-version: 20 cache: "pnpm" - name: Install dependencies run: | pnpm install --frozen-lockfile - name: Build Vencord Reporter Version run: pnpm buildReporter - name: Run Reporter timeout-minutes: 10 run: | export PATH="$PWD/node_modules/.bin:$PATH" export CHROMIUM_BIN=/usr/bin/google-chrome esbuild scripts/generateReport.ts > dist/report.mjs stable_output_file=$(mktemp) canary_output_file=$(mktemp) pids="" branch="${{ inputs.discord_branch }}" if [[ "${{ github.event_name }}" = "schedule" ]]; then branch="both" fi if [[ "$branch" = "both" || "$branch" = "stable" ]]; then node dist/report.mjs > "$stable_output_file" & pids+=" $!" fi if [[ "$branch" = "both" || "$branch" = "canary" ]]; then USE_CANARY=true node dist/report.mjs > "$canary_output_file" & pids+=" $!" fi exit_code=0 for pid in $pids; do if ! wait "$pid"; then exit_code=1 fi done cat "$stable_output_file" "$canary_output_file" >> $GITHUB_STEP_SUMMARY exit $exit_code env: WEBHOOK_URL: ${{ inputs.webhook_url || secrets.DISCORD_WEBHOOK }} WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }} ================================================ FILE: .github/workflows/test.yml ================================================ name: test on: push: pull_request: branches: - main - dev jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json - name: Use Node.js 20 uses: actions/setup-node@v4 with: node-version: 20 cache: "pnpm" - name: Install dependencies run: pnpm install --frozen-lockfile - name: Lint & Test if desktop version compiles run: pnpm test - name: Test if web version compiles run: pnpm buildWeb - name: Test if plugin structure is valid run: pnpm generatePluginJson ================================================ FILE: .gitignore ================================================ dist node_modules *.exe vencord_installer .idea .DS_Store yarn.lock bun.lock package-lock.json *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* .pnpm-debug.log* *.tsbuildinfo src/userplugins ExtensionCache/ /settings ================================================ FILE: .npmrc ================================================ strict-peer-dependencies=false package-manager-strict=false ================================================ FILE: .stylelintrc.json ================================================ { "extends": "stylelint-config-standard", "rules": { "selector-class-pattern": [ "^[a-z][a-zA-Z0-9]*(-[a-z0-9][a-zA-Z0-9]*)*$", { "message": "Expected class selector to be kebab-case with camelCase segments" } ] } } ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": [ "dbaeumer.vscode-eslint", "EditorConfig.EditorConfig", "GregorBiswanger.json2ts", "stylelint.vscode-stylelint", "Vendicated.vencord-companion" ] } ================================================ FILE: .vscode/launch.json ================================================ { // this allows you to debug Vencord from VSCode. // How to use: // You need to run Discord via the command line to pass some flags to it. // If you want to debug the main (node.js) process (preload.ts, ipcMain/*, patcher.ts), // add the --inspect flag // To debug the renderer (99% of Vencord), add the --remote-debugging-port=9223 flag // // Now launch the desired configuration in VSCode and start Discord with the flags. // For example, to debug both process, run Electron: All then launch Discord with // discord --remote-debugging-port=9223 --inspect "version": "0.2.0", "configurations": [ { "name": "Electron: Main", "type": "node", "request": "attach", "port": 9229, "timeout": 30000 }, { "name": "Electron: Renderer", "type": "chrome", "request": "attach", "port": 9223, "timeout": 30000, "webRoot": "${workspaceFolder}/src" } ], "compounds": [ { "name": "Electron: All", "configurations": ["Electron: Main", "Electron: Renderer"] } ] } ================================================ FILE: .vscode/settings.json ================================================ { "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, "[typescript]": { "editor.defaultFormatter": "vscode.typescript-language-features" }, "[typescriptreact]": { "editor.defaultFormatter": "vscode.typescript-language-features" }, "javascript.format.semicolons": "insert", "typescript.format.semicolons": "insert", "typescript.preferences.quoteStyle": "double", "javascript.preferences.quoteStyle": "double", "gitlens.remotes": [ { "domain": "codeberg.org", "type": "Gitea" } ], "css.format.spaceAroundSelectorSeparator": true, "[css]": { "editor.defaultFormatter": "vscode.css-language-features" } } ================================================ FILE: .vscode/tasks.json ================================================ { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "Build", "type": "shell", "command": "pnpm build", "group": { "kind": "build", "isDefault": true } }, { "label": "Watch", "type": "shell", "command": "pnpm watch", "problemMatcher": [], "group": { "kind": "build" } } ] } ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct Our community is welcoming to everyone, regardless of their characteristics. As such, we expect you to treat everyone with respect and contribute to an open and welcoming community. DO - have empathy and be nice to others - be respectful of differing opinions, even if you disagree - give and accept constructive criticism DON'T - use offensive or derogatory language - troll or spam - personally attack or harass others Repetitive violations of these guidelines might get your access to the repository restricted. If you feel like a user is violating these guidelines or feel treated unfairly, please refrain from vigilantism and instead report the issue to a moderator! The best way is joining our [official Discord community](https://vencord.dev/discord) and opening a modmail ticket. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Vencord Vencord is a community project and welcomes any kind of contribution from anyone! We have development documentation for new contributors, which can be found at <https://docs.vencord.dev>. All contributions should be made in accordance with our [Code of Conduct](./CODE_OF_CONDUCT.md). ## How to contribute Contributions can be sent via pull requests. If you're new to Git, check [this guide](https://opensource.com/article/19/7/create-pull-request-github). Pull requests can be made either to the `main` or the `dev` branch. However, unless you're an advanced user, I recommend sticking to `main`. This is because the dev branch might contain unstable changes and be force pushed frequently, which could cause conflicts in your pull request. ## Write a plugin Writing a plugin is the primary way to contribute. Before starting your plugin: - Check existing pull requests to see if someone is already working on a similar plugin - Check our [plugin requests tracker](https://github.com/Vencord/plugin-requests/issues) to see if there is an existing request, or if the same idea has been rejected - If there isn't an existing request, [open one](https://github.com/Vencord/plugin-requests/issues/new?assignees=&labels=&projects=&template=request.yml) yourself and include that you'd like to work on this yourself. Then wait for feedback to see if the idea even has any chance of being accepted. Or maybe others have some ideas to improve it! - Familarise yourself with our plugin rules below to ensure your plugin is not banned ### Plugin Rules - No simple slash command plugins like `/cat`. Instead, make a [user installable Discord bot](https://discord.com/developers/docs/change-log#userinstallable-apps-preview) - No simple text replace plugins like Let me Google that for you. The TextReplace plugin can do this - No raw DOM manipulation. Use proper patches and React - No FakeDeafen or FakeMute - No StereoMic - No plugins that simply hide or redesign ui elements. This can be done with CSS - No plugins that interact with specific Discord bots (official Discord apps like Youtube WatchTogether are okay) - No selfbots or API spam (animated status, message pruner, auto reply, nitro snipers, etc) - No untrusted third party APIs. Popular services like Google or GitHub are fine, but absolutely no self hosted ones - No plugins that require the user to enter their own API key - Do not introduce new dependencies unless absolutely necessary and warranted ## Improve Vencord itself If you have any ideas on how to improve Vencord itself, or want to propose a new plugin API, feel free to open a feature request so we can discuss. Or if you notice any bugs or typos, feel free to fix them! ## Contribute to our Documentation The source code of our documentation is available at <https://github.com/Vencord/Docs> If you see anything outdated, incorrect or lacking, please fix it! If you think a new page should be added, feel free to suggest it via an issue and we can discuss. ## Help out users in our Discord community We have an open support channel in our [Discord community](https://vencord.dev/discord). Helping out users there is always appreciated! The more, the merrier. ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: <program> Copyright (C) <year> <name of author> This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <https://www.gnu.org/licenses/>. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <https://www.gnu.org/licenses/why-not-lgpl.html>. ================================================ FILE: README.md ================================================ # Vencord ![](https://img.shields.io/github/package-json/v/Vendicated/Vencord?style=for-the-badge&logo=github&logoColor=d3869b&label=&color=1d2021&labelColor=282828) [![Codeberg Mirror](https://img.shields.io/static/v1?style=for-the-badge&label=Codeberg%20Mirror&message=codeberg.org/Vee/cord&color=2185D0&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAKbUlEQVR4nNVae3AV5RX/nW/3Pva+b24e5HHzIICQKGoiYiW8NFBFgohaa6ctglpbFSujSGurzUinohWsOij/gGX6R2fqOK0d1FYTEZXaTrWCBbEikJCEyCvkeXNvkrunf+zdkJDkPnex/c3cmd29+53v/M6e73znnF2Cydj4Tntldzi6qrN/qKqzf2jy6b7BnL4B1dI7oMp9AyoRAIdVsNMqhlxWMZjtspzyK/Jhr036OMsm//bh2vzPzNSPzBD6xFutd7R0Dq758ky4orkjYuc05RCAkixbeEq2/UCJ1/LczxcX/c5IPfU5DMHmxpbCpu7o1k/b+xc1n43YjJI7EqV+W2RmvuPt0oDjB2vn5bQbITNjAzzdeKK8qTO0bU9T77zucNQUjzofHrvENWWu3aUBZfW6+ZOOZiIrbYXrmUXo9daX3v6i667O/iGRiRLpwqtIvKDc+0efJ3hb/UIaSkdGWgZ4sqGt9r2m3lc/P9HvSWe80ZiRp3TPL/UsX1+bvyvVsSkb4NE3WjbuPNj5SM8Fcvdk4bAKrqvwv7DxhuCPUxmXNIn6XSy3nWr6R8OhrqrU1btwqJ3m/bgwu/SqZJdEUgbYsuuka09b9/4Pm3tLMlPvwuAbpe6m+RcplfdcURBKdG9CA2zZddLV2Nx1+JO2vlxj1LswqCpynlxc6SxLZIS40bueWfy9vXvv/xt5APhXa1/u7v+EPqvfxXK8++IaoO2Vpn9+cLS33FjVLhw+bOotOX7q6N/i3TOhAX7y+rHN/+sBLxm8fah71k93tjw/0f/jGuDJxtZrdh7setA8tS4sdn7eef+v3mmfP95/Ywxw6x9Yev9I35/6Iubv83WVfl5a6Uu3VkoavZEo7TnS/Vo98xi+Yy6UKC3bDp7sd5ut1OWFDjyzNMib6oq5Oug0ezp8dqLfG3r92Nbzr48ywNONJ8obDnV/z2xlAk4ZW1aUqhaJIAvCb5YVqwFn3GBtCBoO9dz5TOPxUbnMKAM0dYa2d5lc2AgCNi8r5klui3aBgWynjE11QZbI3FV3NjQkjnYNbB+lj36wubGlcE9T71xTNQDw0Px8nlvmHl73GmfCrKCL19Tkmh4P9jT1LHz2vVP5+vmwAZq71a1m1/PXTPXwD68eS5KIEVUZd1yZwwumeEw1Qld/lJrPhF7Sz4cNsO+rUK2ZExd6rfj10iCPZ2GJCCoAZuCJxQUc9FvNVAX72kPX6ccC0Hp4zR0Ru1kT2mTCSzeXqn5l/EAniMAqoDLDYZWwqa5EVSzmhaKmsxHbLxvbbgdiBmjpHFxj2mwANlxXxBdPUib8nwgQgqAyEFUZxT4L1i/MN3UpHDsTWQvEDHDoTLjCrIluuyzAt8zMSkhGFhp5hrYUFk3z8IqZftOMcKRj4GIAEM80tFccM8n9Z+Qq+MXigqRIWCQCMzQvYIbKwH1X53FFnjkr88iZsLKpoXWa6BiIrjbDzF67hK23lKp2Obm1LAstPEZVjTwDkAio/2ZQ9dolw/VjAB0DfKfoCg9WGy2cADy1NMhBX2rR3CIRGICq8rAhAg4Jj9UWsDBhg+4MR6vF2VC0zGjB99fk8eJp3pQdyyrRMHF9KURVxswCB6+alWO4o3b2RyeLU32D2UYKnVPm5gfm5qWlrF0Wo4hzbCmoDNw0089XlboNNcLpvsFc0RtRDXuNle+x4Lkbi9PO6WWJIBFGEY+qjGjswtq5eVzosRilLnoiUavoH1INiTCyIDy/vETNcmRW1dl0L4gRVxmx3YFhlwnrry1QrZIxASE0yJIIDaiGSHt8UQFXF2Ve1zusYgzxkXGhyGvFvePUE+mgfyAqhGqAqKWVPv5udbYhSjmtkpYWq6OJqzFjqCpjTpmbl1Rk3klSGRBWmTISNC3Hjo1LgoYFJ0GA1aIVR+cTVxlQoS2Pb18a4PLszMKXzSJYuCySmq4Al03CiytKVYfBhYvLKk1IXE+XLRLhwZp81WlNf26HTFHhd0jhdAYTgKduCPLkgPHfQjitYkLiAIEZBDBlu2R6aF7euCV2Mgg45bDw2qWOdAavnp3D109PPdlJBvpTnYg4kVY3MDMuylVw62WJi63x4LHLZ0TAIR9OdWBVodPUclUQwWmT4hLXfgCIUDfDi6oiR8rzBJzyl8LnkD9KZVCOU8aLN5eoshnJ+Qh4bFJC4gztmEjgrtk5anaKnWWfXfpIuBTLjmSpSILw/E0laq7LuGxsIngVCYmIa96hLRG3TaZ1C/KTfjAEQLFIO8TPFk7aH/RZI8kMWrdgEs8udqXLKSUoMkEW4ETEQTRsoHyPlVZfmVw+Uuy3hR9bVHBQAMD0XPu/Ew24dqqH777K/La1DiKCxyYlRRzQymgG4+oyDxZOTdxZnp5r3wvEWmJ5btuL8W4uzbJh87LitLebdOFVpKSJx4IlwIzbL81CcYLO8iSX/IImGQCYae6Wg/2tXQNjNnW7LPDKyilqZd7ETU2zEBlifNTSS4i9PNFIx44x4jh2nZlBsUr0dN8QP/6XVhEaHJvnlfhtkXd/NF0BUextKRFXFznfGk+JDdcX8tdBHtDa6YpFsB4I9ac88omf8wbEgqa2XAIOme6bM35foqrQ+QZIKwGG80ifVbrXZZNGDfhOVYBvviS9JMMoaP3AEcQpPnHdOxiMGXkKbrx4dGfZY5c4T8H9+vmwAeqXFLXOKXW9r59fWuDA44sKv1byAOBzyCkTH+kdS2f4MLPgXJI0p9T17vrFxcf181GVxEUB+0qfIqt+RcKWFSWGNR4ygd4RTpW4HiCJgFWzstmnSPA7ZLU827pypPwxDB/687GXl1X6Vs6bbGz/LRN80hZCT+yLFZ0cgHED4egACeiXm89GsP9EePuzy4rvGil7jAGYmQDsBjDHUBYZ4GhHBMfORigd4rpnyIS9u6d4rqgnGrUtjCmmSYuOqwB0GcwjbWh9xviurpNnxnDA1IspMPe6bOL755MHJvhKjIgOA7jbJD4pw22Thj+kSIW47h2KRaydVezeP57sCdspRPQqgGeNJJIuBAE+ReJUiOv32mXaXjPZs21C2QnmXgdghyEsMoRfkVMiDgCywF/by9z3xJMb1wCxeHAPgDczZpAh/Iq+HSYmDjCsstgThmf5t4ii8eQm7CgS0SCA5QBezoRApnBaBSyCEhIHCLJEb4ZUd+2SqZSwzE+qpUpEQ9CC4qb01M8cRIQsh8zxiKsMtsn08nvlnrpkyAPj5AGJwMw3AtgGwJ/q2ExxvHsQB74KxfKBMblAyGmTHq4pc4/5GjQeUm6qE9FrAK4E8H6ie41GlkN/jTk6F5Ak2ueUpNmpkgfSMAAAENERAAsB3AHgZDoy0oFdFnBYpXPEBfU4beLRD6Z4qmumug+kIzPjaoeZfQDWAHgAQFam8hLh4MkwWjsHemyS2OF08IYrCjynzZ4zKTCzi5nXMvOnzBw16bevIxR95JOj7DNKb1PqXWa+HMDtAGoBXII0lxq0N2OfAmgA8Hsi2muMhudgesHPzNkA5gKoADADwFRoS8UHQO+x9wLoBNAB4AsAnwM4AOADIjLVxf8L9kdXUOE0IskAAAAASUVORK5CYII=)](https://codeberg.org/Vee/cord) The cutest Discord client mod ![](https://github.com/user-attachments/assets/3fac98c0-c411-4d2a-97a3-13b7da8687a2) ## Features - Easy to install - [100+ built in plugins](https://vencord.dev/plugins) - Fairly lightweight despite the many inbuilt plugins - Excellent Browser Support: Run Vencord in your Browser via extension or UserScript - Works on any Discord branch: Stable, Canary or PTB all work - Custom CSS and Themes: Inbuilt css editor with support to import any css files (including BetterDiscord themes) - Privacy friendly: blocks Discord analytics & crash reporting out of the box and has no telemetry - Maintained very actively, broken plugins are usually fixed within 12 hours - Settings sync: Keep your plugins and their settings synchronised between devices / apps (optional) ## Installing / Uninstalling Visit https://vencord.dev/download ## Join our Support/Community Server https://discord.gg/D9uwnFnqmd ## Sponsors | **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** | | :------------------------------------------------------------------------------------------: | | [![](https://meow.vendicated.dev/sponsors.png)](https://github.com/sponsors/Vendicated) | | *generated using [github-sponsor-graph](https://github.com/Vendicated/github-sponsor-graph)* | ## Star History <a href="https://star-history.com/#Vendicated/Vencord&Timeline"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Vendicated/Vencord&type=Timeline&theme=dark" /> <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Vendicated/Vencord&type=Timeline" /> <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Vendicated/Vencord&type=Timeline" /> </picture> </a> ## Disclaimer Discord is trademark of Discord Inc. and solely mentioned for the sake of descriptivity. Mention of it does not imply any affiliation with or endorsement by Discord Inc. <details> <summary>Using Vencord violates Discord's terms of service</summary> Client modifications are against Discord’s Terms of Service. However, Discord is pretty indifferent about them and there are no known cases of users getting banned for using client mods! So you should generally be fine as long as you don’t use any plugins that implement abusive behaviour. But no worries, all inbuilt plugins are safe to use! Regardless, if your account is very important to you and it getting disabled would be a disaster for you, you should probably not use any client mods (not exclusive to Vencord), just to be safe Additionally, make sure not to post screenshots with Vencord in a server where you might get banned for it </details> ================================================ FILE: browser/GMPolyfill.js ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ function parseHeaders(headers) { const result = new Headers(); if (!headers) return result; const headersArr = headers.trim().split("\n"); for (var i = 0; i < headersArr.length; i++) { var row = headersArr[i]; var index = row.indexOf(":") , key = row.slice(0, index).trim().toLowerCase() , value = row.slice(index + 1).trim(); result.append(key, value); } return result; } function blobTo(to, blob) { if (to === "arrayBuffer" && blob.arrayBuffer) return blob.arrayBuffer(); return new Promise((resolve, reject) => { var fileReader = new FileReader(); fileReader.onload = event => resolve(event.target.result); if (to === "arrayBuffer") fileReader.readAsArrayBuffer(blob); else if (to === "text") fileReader.readAsText(blob, "utf-8"); else reject("unknown to"); }); } function GM_fetch(url, opt) { return new Promise((resolve, reject) => { // https://www.tampermonkey.net/documentation.php?ext=dhdg#GM_xmlhttpRequest const options = opt || {}; options.url = url; options.data = options.body; options.responseType = "blob"; options.onload = resp => { var blob = resp.response; resp.blob = () => Promise.resolve(blob); resp.arrayBuffer = () => blobTo("arrayBuffer", blob); resp.text = () => blobTo("text", blob); resp.json = async () => JSON.parse(await blobTo("text", blob)); resp.headers = parseHeaders(resp.responseHeaders); resp.ok = resp.status >= 200 && resp.status < 300; resolve(resp); }; options.ontimeout = () => reject("fetch timeout"); options.onerror = () => reject("fetch error"); options.onabort = () => reject("fetch abort"); GM_xmlhttpRequest(options); }); } export const fetch = GM_fetch; ================================================ FILE: browser/Vencord.ts ================================================ /*! * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ import "./VencordNativeStub"; export * from "../src/Vencord"; ================================================ FILE: browser/VencordNativeStub.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ /// <reference path="../src/modules.d.ts" /> /// <reference path="../src/globals.d.ts" /> // Be very careful with imports in this file to avoid circular dependency issues. // Only import pure modules that don't import other parts of Vencord. import monacoHtmlLocal from "file://monacoWin.html?minify"; import * as DataStore from "@api/DataStore"; import type { Settings } from "@api/Settings"; import { getThemeInfo } from "@main/themes"; import { debounce } from "@shared/debounce"; import { localStorage } from "@utils/localStorage"; import { getStylusWebStoreUrl } from "@utils/web"; import { EXTENSION_BASE_URL, metaReady, RENDERER_CSS_URL } from "@utils/web-metadata"; // listeners for ipc.on const cssListeners = new Set<(css: string) => void>(); const NOOP = () => { }; const NOOP_ASYNC = async () => { }; const setCssDebounced = debounce((css: string) => VencordNative.quickCss.set(css)); const themeStore = DataStore.createStore("VencordThemes", "VencordThemeData"); // probably should make this less cursed at some point window.VencordNative = { themes: { uploadTheme: (fileName: string, fileData: string) => DataStore.set(fileName, fileData, themeStore), deleteTheme: (fileName: string) => DataStore.del(fileName, themeStore), getThemesList: () => DataStore.entries(themeStore).then(entries => entries.map(([name, css]) => getThemeInfo(css, name.toString())) ), getThemeData: (fileName: string) => DataStore.get(fileName, themeStore), getSystemValues: async () => ({}), openFolder: async () => Promise.reject("themes:openFolder is not supported on web"), }, native: { getVersions: () => ({}), openExternal: async (url) => void open(url, "_blank"), getRendererCss: async () => { if (IS_USERSCRIPT) // need to wait for next tick for _vcUserScriptRendererCss to be set return Promise.resolve().then(() => window._vcUserScriptRendererCss); await metaReady; return fetch(RENDERER_CSS_URL) .then(res => res.text()); }, onRendererCssUpdate: NOOP, }, updater: { getRepo: async () => ({ ok: true, value: "https://github.com/Vendicated/Vencord" }), getUpdates: async () => ({ ok: true, value: [] }), update: async () => ({ ok: true, value: false }), rebuild: async () => ({ ok: true, value: true }), }, quickCss: { get: () => DataStore.get("VencordQuickCss").then(s => s ?? ""), set: async (css: string) => { await DataStore.set("VencordQuickCss", css); cssListeners.forEach(l => l(css)); }, addChangeListener(cb) { cssListeners.add(cb); }, addThemeChangeListener: NOOP, openFile: NOOP_ASYNC, async openEditor() { if (IS_USERSCRIPT) { const shouldOpenWebStore = confirm("QuickCSS is not supported on the Userscript. You can instead use the Stylus extension.\n\nDo you want to open the Stylus web store page?"); if (shouldOpenWebStore) { window.open(getStylusWebStoreUrl(), "_blank"); } return; } const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`; const win = open("about:blank", "VencordQuickCss", features); if (!win) { alert("Failed to open QuickCSS popup. Make sure to allow popups!"); return; } win.baseUrl = EXTENSION_BASE_URL; win.setCss = setCssDebounced; win.getCurrentCss = () => VencordNative.quickCss.get(); win.getTheme = this.getEditorTheme; win.document.write(monacoHtmlLocal); }, getEditorTheme: () => { const { getTheme, Theme } = require("@utils/discord"); return getTheme() === Theme.Light ? "vs-light" : "vs-dark"; } }, settings: { get: () => { try { return JSON.parse(localStorage.getItem("VencordSettings") || "{}"); } catch (e) { console.error("Failed to parse settings from localStorage: ", e); return {}; } }, set: async (s: Settings) => localStorage.setItem("VencordSettings", JSON.stringify(s)), openFolder: async () => Promise.reject("settings:openFolder is not supported on web"), }, pluginHelpers: {} as any, csp: {} as any, }; ================================================ FILE: browser/background.js ================================================ /** * @template T * @param {T[]} arr * @param {(v: T) => boolean} predicate */ function removeFirst(arr, predicate) { const idx = arr.findIndex(predicate); if (idx !== -1) arr.splice(idx, 1); } chrome.webRequest.onHeadersReceived.addListener( ({ responseHeaders, type, url }) => { if (!responseHeaders) return; if (type === "main_frame" && url.includes("discord.com")) { // In main frame requests, the CSP needs to be removed to enable fetching of custom css // as desired by the user removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-security-policy"); } else if (type === "stylesheet" && url.startsWith("https://raw.githubusercontent.com/")) { // Most users will load css from GitHub, but GitHub doesn't set the correct content type, // so we fix it here removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-type"); responseHeaders.push({ name: "Content-Type", value: "text/css" }); } return { responseHeaders }; }, { urls: ["https://raw.githubusercontent.com/*", "*://*.discord.com/*"], types: ["main_frame", "stylesheet"] }, ["blocking", "responseHeaders"] ); ================================================ FILE: browser/content.js ================================================ if (typeof browser === "undefined") { var browser = chrome; } document.addEventListener( "DOMContentLoaded", () => { window.postMessage({ type: "vencord:meta", meta: { EXTENSION_VERSION: browser.runtime.getManifest().version, EXTENSION_BASE_URL: browser.runtime.getURL(""), RENDERER_CSS_URL: browser.runtime.getURL("dist/Vencord.css"), } }); }, { once: true } ); ================================================ FILE: browser/manifest.json ================================================ { "manifest_version": 3, "minimum_chrome_version": "111", "name": "Vencord Web", "description": "The cutest Discord mod now in your browser", "author": "Vendicated", "homepage_url": "https://github.com/Vendicated/Vencord", "icons": { "128": "icon.png" }, "host_permissions": [ "*://*.discord.com/*", "https://raw.githubusercontent.com/*" ], "permissions": ["declarativeNetRequest"], "content_scripts": [ { "run_at": "document_start", "matches": ["*://*.discord.com/*"], "js": ["content.js"], "all_frames": true, "world": "ISOLATED" }, { "run_at": "document_start", "matches": ["*://*.discord.com/*"], "js": ["dist/Vencord.js"], "all_frames": true, "world": "MAIN" } ], "web_accessible_resources": [ { "resources": ["dist/*", "vendor/*"], "matches": ["*://*.discord.com/*"] } ], "declarative_net_request": { "rule_resources": [ { "id": "modifyResponseHeaders", "enabled": true, "path": "modifyResponseHeaders.json" } ] } } ================================================ FILE: browser/manifestv2.json ================================================ { "manifest_version": 2, "minimum_chrome_version": "91", "name": "Vencord Web", "description": "The cutest Discord mod now in your browser", "author": "Vendicated", "homepage_url": "https://github.com/Vendicated/Vencord", "icons": { "128": "icon.png" }, "permissions": [ "webRequest", "webRequestBlocking", "*://*.discord.com/*", "https://raw.githubusercontent.com/*" ], "content_scripts": [ { "run_at": "document_start", "matches": ["*://*.discord.com/*"], "js": ["content.js"], "all_frames": true, "world": "ISOLATED" }, { "run_at": "document_start", "matches": ["*://*.discord.com/*"], "js": ["dist/Vencord.js"], "all_frames": true, "world": "MAIN" } ], "background": { "scripts": ["background.js"] }, "web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"], "browser_specific_settings": { "gecko": { "id": "vencord-firefox@vendicated.dev", "strict_min_version": "128.0" } } } ================================================ FILE: browser/modifyResponseHeaders.json ================================================ [ { "id": 1, "action": { "type": "modifyHeaders", "responseHeaders": [ { "header": "content-security-policy", "operation": "remove" }, { "header": "content-security-policy-report-only", "operation": "remove" } ] }, "condition": { "resourceTypes": ["main_frame", "sub_frame"], "urlFilter": "||discord.com^" } }, { "id": 2, "action": { "type": "modifyHeaders", "responseHeaders": [ { "header": "content-type", "operation": "set", "value": "text/css" } ] }, "condition": { "resourceTypes": ["stylesheet"], "urlFilter": "||raw.githubusercontent.com^" } } ] ================================================ FILE: browser/monaco.ts ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2023 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import "./patch-worker"; import * as monaco from "monaco-editor/esm/vs/editor/editor.main.js"; declare global { const baseUrl: string; const getCurrentCss: () => Promise<string>; const setCss: (css: string) => void; const getTheme: () => string; } const BASE = "/vendor/monaco/vs"; self.MonacoEnvironment = { getWorkerUrl(_moduleId: unknown, label: string) { const path = label === "css" ? "/language/css/css.worker.js" : "/editor/editor.worker.js"; return new URL(BASE + path, baseUrl).toString(); } }; getCurrentCss().then(css => { const editor = monaco.editor.create( document.getElementById("container")!, { value: css, language: "css", theme: getTheme(), } ); editor.onDidChangeModelContent(() => setCss(editor.getValue()) ); window.addEventListener("resize", () => { // make monaco re-layout editor.layout(); }); }); ================================================ FILE: browser/monacoWin.html ================================================ <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Vencord QuickCSS Editor
================================================ FILE: browser/patch-worker.js ================================================ /* Copyright 2013 Rob Wu Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Target: Chrome 20+ // W3-compliant Worker proxy. // This module replaces the global Worker object. // When invoked, the default Worker object is called. // If this call fails with SECURITY_ERR, the script is fetched // using async XHR, and transparently proxies all calls and // setters/getters to the new Worker object. // Note: This script does not magically circumvent the Same origin policy. (function () { 'use strict'; var Worker_ = window.Worker; var URL = window.URL || window.webkitURL; // Create dummy worker for the following purposes: // 1. Don't override the global Worker object if the fallback isn't // going to work (future API changes?) // 2. Use it to trigger early validation of postMessage calls // Note: Blob constructor is supported since Chrome 20, but since // some of the used Chrome APIs are only supported as of Chrome 20, // I don't bother adding a BlobBuilder fallback. var dummyWorker = new Worker_( URL.createObjectURL(new Blob([], { type: 'text/javascript' }))); window.Worker = function Worker(scriptURL) { if (arguments.length === 0) { throw new TypeError('Not enough arguments'); } try { return new Worker_(scriptURL); } catch (e) { if (e.code === 18/*DOMException.SECURITY_ERR*/) { return new WorkerXHR(scriptURL); } else { throw e; } } }; // Bind events and replay queued messages function bindWorker(worker, workerURL) { if (worker._terminated) { return; } worker.Worker = new Worker_(workerURL); worker.Worker.onerror = worker._onerror; worker.Worker.onmessage = worker._onmessage; var o; while ((o = worker._replayQueue.shift())) { worker.Worker[o.method].apply(worker.Worker, o.arguments); } while ((o = worker._messageQueue.shift())) { worker.Worker.postMessage.apply(worker.Worker, o); } } function WorkerXHR(scriptURL) { var worker = this; var x = new XMLHttpRequest(); x.responseType = 'blob'; x.onload = function () { // http://stackoverflow.com/a/10372280/938089 var workerURL = URL.createObjectURL(x.response); bindWorker(worker, workerURL); }; x.open('GET', scriptURL); x.send(); worker._replayQueue = []; worker._messageQueue = []; } WorkerXHR.prototype = { constructor: Worker_, terminate: function () { if (!this._terminated) { this._terminated = true; if (this.Worker) this.Worker.terminate(); } }, postMessage: function (message, transfer) { if (!(this instanceof WorkerXHR)) throw new TypeError('Illegal invocation'); if (this.Worker) { this.Worker.postMessage.apply(this.Worker, arguments); } else { // Trigger validation: dummyWorker.postMessage(message); // Alright, push the valid message to the queue. this._messageQueue.push(arguments); } } }; // Implement the EventTarget interface [ 'addEventListener', 'removeEventListener', 'dispatchEvent' ].forEach(function (method) { WorkerXHR.prototype[method] = function () { if (!(this instanceof WorkerXHR)) { throw new TypeError('Illegal invocation'); } if (this.Worker) { this.Worker[method].apply(this.Worker, arguments); } else { this._replayQueue.push({ method: method, arguments: arguments }); } }; }); Object.defineProperties(WorkerXHR.prototype, { onmessage: { get: function () { return this._onmessage || null; }, set: function (func) { this._onmessage = typeof func === 'function' ? func : null; } }, onerror: { get: function () { return this._onerror || null; }, set: function (func) { this._onerror = typeof func === 'function' ? func : null; } } }); })(); ================================================ FILE: browser/userscript.meta.js ================================================ // ==UserScript== // @name Vencord // @description A Discord client mod - Web version // @version %version% // @author Vendicated (https://github.com/Vendicated) // @namespace https://github.com/Vendicated/Vencord // @supportURL https://github.com/Vendicated/Vencord // @icon https://raw.githubusercontent.com/Vendicated/Vencord/refs/heads/main/browser/icon.png // @license GPL-3.0 // @match *://*.discord.com/* // @grant GM_xmlhttpRequest // @grant unsafeWindow // @run-at document-start // @compatible chrome Chrome + Tampermonkey or Violentmonkey // @compatible firefox Firefox Tampermonkey // @compatible opera Opera + Tampermonkey or Violentmonkey // @compatible edge Edge + Tampermonkey or Violentmonkey // @compatible safari Safari + Tampermonkey or Violentmonkey // ==/UserScript== // this UserScript DOES NOT work on Firefox with Violentmonkey or Greasemonkey due to a bug that makes it impossible // to overwrite stuff on the window on sites that use CSP. Use Tampermonkey or use a chromium based browser // https://github.com/violentmonkey/violentmonkey/issues/997 // this is a compiled and minified version of Vencord. For the source code, visit the GitHub repo ================================================ FILE: eslint.config.mjs ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2023 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import stylistic from "@stylistic/eslint-plugin"; import pathAlias from "eslint-plugin-path-alias"; import react from "eslint-plugin-react"; import header from "eslint-plugin-simple-header"; import simpleImportSort from "eslint-plugin-simple-import-sort"; import unusedImports from "eslint-plugin-unused-imports"; import tseslint from "typescript-eslint"; export default tseslint.config( { ignores: ["dist", "browser", "packages/vencord-types"] }, { files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"], settings: { react: { version: "18" } }, ...react.configs.flat.recommended, rules: { ...react.configs.flat.recommended.rules, "react/react-in-jsx-scope": "off", "react/prop-types": "off", "react/display-name": "off", "react/no-unescaped-entities": "off", } }, { files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"], plugins: { "simple-header": header, "@stylistic": stylistic, "@typescript-eslint": tseslint.plugin, "simple-import-sort": simpleImportSort, "unused-imports": unusedImports, "path-alias": pathAlias }, settings: { "import/resolver": { map: [ ["@webpack", "./src/webpack"], ["@webpack/common", "./src/webpack/common"], ["@utils", "./src/utils"], ["@api", "./src/api"], ["@components", "./src/components"] ] } }, languageOptions: { parser: tseslint.parser, parserOptions: { project: ["./tsconfig.json"], tsconfigRootDir: import.meta.dirname } }, rules: { /* * Since it's only been a month and Vencord has already been stolen * by random skids who rebranded it to "AlphaCord" and erased all license * information */ "simple-header/header": [ "error", { "files": ["scripts/header-new.txt", "scripts/header-old.txt"], "templates": { "author": [".*", "Vendicated and contributors"] } } ], // Style Rules "@stylistic/jsx-quotes": ["error", "prefer-double"], "@stylistic/quotes": ["error", "double", { "avoidEscape": true }], "@stylistic/no-mixed-spaces-and-tabs": "error", "@stylistic/arrow-parens": ["error", "as-needed"], "@stylistic/eol-last": ["error", "always"], "@stylistic/no-multi-spaces": "error", "@stylistic/no-trailing-spaces": "error", "@stylistic/no-whitespace-before-property": "error", "@stylistic/semi": ["error", "always"], "@stylistic/semi-style": ["error", "last"], "@stylistic/space-in-parens": ["error", "never"], "@stylistic/block-spacing": ["error", "always"], "@stylistic/object-curly-spacing": ["error", "always"], "@stylistic/spaced-comment": ["error", "always", { "markers": ["!"] }], "@stylistic/no-extra-semi": "error", // TS Rules "@stylistic/function-call-spacing": ["error", "never"], // ESLint Rules "yoda": "error", "eqeqeq": ["error", "always", { "null": "ignore" }], "prefer-destructuring": ["error", { "VariableDeclarator": { "array": false, "object": true }, "AssignmentExpression": { "array": false, "object": false } }], "operator-assignment": ["error", "always"], "no-useless-computed-key": "error", "no-unneeded-ternary": ["error", { "defaultAssignment": false }], "no-invalid-regexp": "error", "no-constant-condition": ["error", { "checkLoops": false }], "no-duplicate-imports": "error", "@typescript-eslint/dot-notation": [ "error", { "allowPrivateClassPropertyAccess": true, "allowProtectedClassPropertyAccess": true } ], "no-useless-escape": [ "error", { "allowRegexCharacters": ["i"] } ], "no-fallthrough": "error", "for-direction": "error", "no-async-promise-executor": "error", "no-cond-assign": "error", "no-dupe-else-if": "error", "no-duplicate-case": "error", "no-irregular-whitespace": "error", "no-loss-of-precision": "error", "no-misleading-character-class": "error", "no-prototype-builtins": "error", "no-regex-spaces": "error", "no-shadow-restricted-names": "error", "no-unexpected-multiline": "error", "no-unsafe-optional-chaining": "error", "no-useless-backreference": "error", "use-isnan": "error", "prefer-const": ["error", { destructuring: "all" }], "prefer-spread": "error", // Plugin Rules "simple-import-sort/imports": "error", "simple-import-sort/exports": "error", "unused-imports/no-unused-imports": "error", "path-alias/no-relative": "error" } } ); ================================================ FILE: package.json ================================================ { "name": "vencord", "private": "true", "version": "1.14.6", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { "url": "https://github.com/Vendicated/Vencord/issues" }, "repository": { "type": "git", "url": "git+https://github.com/Vendicated/Vencord.git" }, "license": "GPL-3.0-or-later", "author": "Vendicated", "scripts": { "build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs", "buildStandalone": "pnpm build --standalone", "buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs", "buildWebStandalone": "pnpm buildWeb --standalone", "buildReporter": "pnpm buildWebStandalone --reporter --skip-extension", "buildReporterDesktop": "pnpm build --reporter", "watch": "pnpm build --watch", "dev": "pnpm watch", "watchWeb": "pnpm buildWeb --watch", "generatePluginJson": "tsx scripts/generatePluginList.ts", "generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types --allowJs false", "inject": "node scripts/runInstaller.mjs -- --install", "uninject": "node scripts/runInstaller.mjs -- --uninstall", "lint": "eslint", "lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins", "lint:fix": "pnpm lint --fix", "test": "pnpm buildStandalone && pnpm testTsc && pnpm lint && pnpm lint-styles && pnpm generatePluginJson", "testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc", "testTsc": "tsc --noEmit" }, "dependencies": { "@intrnl/xxhash64": "^0.1.2", "@vap/core": "0.0.12", "@vap/shiki": "0.10.5", "fflate": "^0.8.2", "gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3", "monaco-editor": "^0.54.0", "nanoid": "^5.1.6", "virtual-merge": "^1.0.1" }, "devDependencies": { "@stylistic/eslint-plugin": "^5.6.0", "@types/chrome": "^0.1.30", "@types/lodash": "^4.17.20", "@types/node": "^24.10.1", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@types/yazl": "^3.3.0", "@vencord/discord-types": "link:packages/discord-types", "diff": "^8.0.2", "esbuild": "^0.27.0", "eslint": "9.39.1", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-path-alias": "2.1.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-simple-header": "^1.2.1", "eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-unused-imports": "^4.3.0", "highlight.js": "11.11.1", "html-minifier-terser": "^7.2.0", "moment": "^2.22.2", "p-limit": "^7.3.0", "puppeteer-core": "^24.30.0", "standalone-electron-types": "^34.2.0", "stylelint": "^16.25.0", "stylelint-config-standard": "^39.0.1", "svgo": "^4.0.0", "ts-patch": "^3.3.0", "ts-pattern": "^5.6.0", "tsx": "^4.20.6", "type-fest": "^5.2.0", "typescript": "^5.9.3", "typescript-eslint": "^8.47.0", "typescript-transform-paths": "^3.5.5", "zip-local": "^0.3.5" }, "packageManager": "pnpm@10.4.1", "pnpm": { "patchedDependencies": { "eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch" }, "peerDependencyRules": { "ignoreMissing": [ "eslint-plugin-import", "eslint" ] }, "allowedDeprecatedVersions": { "source-map-resolve": "*", "resolve-url": "*", "source-map-url": "*", "urix": "*", "q": "*" }, "onlyBuiltDependencies": [ "esbuild" ] }, "engines": { "node": ">=18" } } ================================================ FILE: packages/discord-types/.npmignore ================================================ node_modules ================================================ FILE: packages/discord-types/CONTRIBUTING.md ================================================ Hint: https://docs.discord.food is an incredible resource and allows you to copy paste complete enums and interfaces ================================================ FILE: packages/discord-types/LICENSE ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: packages/discord-types/README.md ================================================ # Discord Types This package provides TypeScript types for the Webpack modules of Discord's web app. While it was primarily created for Vencord, other client mods could also benefit from this, so it is published as a standalone package! ## Installation ```bash npm install -D @vencord/discord-types yarn add -D @vencord/discord-types pnpm add -D @vencord/discord-types ``` ## Example Usage ```ts import type { UserStore } from "@vencord/discord-types"; const userStore: UserStore = findStore("UserStore"); // findStore is up to you to implement, this library only provides types and no runtime code ``` ## Enums This library also exports some const enums that you can use from Typescript code: ```ts import { ApplicationCommandType } from "@vencord/discord-types/enums"; console.log(ApplicationCommandType.CHAT_INPUT); // 1 ``` ### License This package is licensed under the [LGPL-3.0](./LICENSE) (or later) license. A very short summary of the license is that you can use this package as a library in both open source and closed source projects, similar to an MIT-licensed project. However, if you modify the code of this package, you must release source code of your modified version under the same license. ### Credit This package was inspired by Swishilicous' [discord-types](https://www.npmjs.com/package/discord-types) package. ================================================ FILE: packages/discord-types/enums/activity.ts ================================================ export const enum ActivityType { PLAYING = 0, STREAMING = 1, LISTENING = 2, WATCHING = 3, CUSTOM_STATUS = 4, COMPETING = 5, HANG_STATUS = 6 } export const enum ActivityFlags { INSTANCE = 1 << 0, JOIN = 1 << 1, /** @deprecated */ SPECTATE = 1 << 2, /** @deprecated */ JOIN_REQUEST = 1 << 3, SYNC = 1 << 4, PLAY = 1 << 5, PARTY_PRIVACY_FRIENDS = 1 << 6, PARTY_PRIVACY_VOICE_CHANNEL = 1 << 7, EMBEDDED = 1 << 8, CONTEXTLESS = 1 << 9 } export const enum ActivityStatusDisplayType { NAME = 0, STATE = 1, DETAILS = 2 } ================================================ FILE: packages/discord-types/enums/channel.ts ================================================ export const enum ChannelType { GUILD_TEXT = 0, DM = 1, GUILD_VOICE = 2, GROUP_DM = 3, GUILD_CATEGORY = 4, GUILD_ANNOUNCEMENT = 5, ANNOUNCEMENT_THREAD = 10, PUBLIC_THREAD = 11, PRIVATE_THREAD = 12, GUILD_STAGE_VOICE = 13, GUILD_DIRECTORY = 14, GUILD_FORUM = 15, GUILD_MEDIA = 16 } ================================================ FILE: packages/discord-types/enums/commands.ts ================================================ export const enum ApplicationCommandOptionType { SUB_COMMAND = 1, SUB_COMMAND_GROUP = 2, STRING = 3, INTEGER = 4, BOOLEAN = 5, USER = 6, CHANNEL = 7, ROLE = 8, MENTIONABLE = 9, NUMBER = 10, ATTACHMENT = 11, } export const enum ApplicationCommandInputType { BUILT_IN = 0, BUILT_IN_TEXT = 1, BUILT_IN_INTEGRATION = 2, BOT = 3, PLACEHOLDER = 4, } export const enum ApplicationCommandType { CHAT_INPUT = 1, USER = 2, MESSAGE = 3, } export const enum ApplicationIntegrationType { GUILD_INSTALL = 0, USER_INSTALL = 1 } ================================================ FILE: packages/discord-types/enums/index.ts ================================================ export * from "./activity"; export * from "./channel"; export * from "./commands"; export * from "./messages"; export * from "./misc"; export * from "./user"; ================================================ FILE: packages/discord-types/enums/messages.ts ================================================ export const enum StickerType { /** an official sticker in a pack */ STANDARD = 1, /** a sticker uploaded to a guild for the guild's members */ GUILD = 2 } export const enum StickerFormatType { PNG = 1, APNG = 2, LOTTIE = 3, GIF = 4 } export const enum MessageType { /** * A default message (see below) * * Value: 0 * Name: DEFAULT * Rendered Content: "{content}" * Deletable: true */ DEFAULT = 0, /** * A message sent when a user is added to a group DM or thread * * Value: 1 * Name: RECIPIENT_ADD * Rendered Content: "{author} added {mentions [0] } to the {group/thread}." * Deletable: false */ RECIPIENT_ADD = 1, /** * A message sent when a user is removed from a group DM or thread * * Value: 2 * Name: RECIPIENT_REMOVE * Rendered Content: "{author} removed {mentions [0] } from the {group/thread}." * Deletable: false */ RECIPIENT_REMOVE = 2, /** * A message sent when a user creates a call in a private channel * * Value: 3 * Name: CALL * Rendered Content: participated ? "{author} started a call{ended ? " that lasted {duration}" : " — Join the call"}." : "You missed a call from {author} that lasted {duration}." * Deletable: false */ CALL = 3, /** * A message sent when a group DM or thread's name is changed * * Value: 4 * Name: CHANNEL_NAME_CHANGE * Rendered Content: "{author} changed the {is_forum ? "post title" : "channel name"}: {content} " * Deletable: false */ CHANNEL_NAME_CHANGE = 4, /** * A message sent when a group DM's icon is changed * * Value: 5 * Name: CHANNEL_ICON_CHANGE * Rendered Content: "{author} changed the channel icon." * Deletable: false */ CHANNEL_ICON_CHANGE = 5, /** * A message sent when a message is pinned in a channel * * Value: 6 * Name: CHANNEL_PINNED_MESSAGE * Rendered Content: "{author} pinned a message to this channel." * Deletable: true */ CHANNEL_PINNED_MESSAGE = 6, /** * A message sent when a user joins a guild * * Value: 7 * Name: USER_JOIN * Rendered Content: See user join message type , obtained via the formula timestamp_ms % 13 * Deletable: true */ USER_JOIN = 7, /** * A message sent when a user subscribes to (boosts) a guild * * Value: 8 * Name: PREMIUM_GUILD_SUBSCRIPTION * Rendered Content: "{author} just boosted the server{content ? " {content} times"}!" * Deletable: true */ PREMIUM_GUILD_SUBSCRIPTION = 8, /** * A message sent when a user subscribes to (boosts) a guild to tier 1 * * Value: 9 * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_1 * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 1! " * Deletable: true */ PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9, /** * A message sent when a user subscribes to (boosts) a guild to tier 2 * * Value: 10 * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_2 * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 2! " * Deletable: true */ PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10, /** * A message sent when a user subscribes to (boosts) a guild to tier 3 * * Value: 11 * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_3 * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 3! " * Deletable: true */ PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11, /** * A message sent when a news channel is followed * * Value: 12 * Name: CHANNEL_FOLLOW_ADD * Rendered Content: "{author} has added {content} to this channel. Its most important updates will show up here." * Deletable: true */ CHANNEL_FOLLOW_ADD = 12, /** * A message sent when a guild is disqualified from discovery * * Value: 14 * Name: GUILD_DISCOVERY_DISQUALIFIED * Rendered Content: "This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details." * Deletable: true */ GUILD_DISCOVERY_DISQUALIFIED = 14, /** * A message sent when a guild requalifies for discovery * * Value: 15 * Name: GUILD_DISCOVERY_REQUALIFIED * Rendered Content: "This server is eligible for Server Discovery again and has been automatically relisted!" * Deletable: true */ GUILD_DISCOVERY_REQUALIFIED = 15, /** * A message sent when a guild has failed discovery requirements for a week * * Value: 16 * Name: GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING * Rendered Content: "This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery." * Deletable: true */ GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16, /** * A message sent when a guild has failed discovery requirements for 3 weeks * * Value: 17 * Name: GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING * Rendered Content: "This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery." * Deletable: true */ GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17, /** * A message sent when a thread is created * * Value: 18 * Name: THREAD_CREATED * Rendered Content: "{author} started a thread: {content} . See all threads." * Deletable: true */ THREAD_CREATED = 18, /** * A message sent when a user replies to a message * * Value: 19 * Name: REPLY * Rendered Content: "{content}" * Deletable: true */ REPLY = 19, /** * A message sent when a user uses a slash command * * Value: 20 * Name: CHAT_INPUT_COMMAND * Rendered Content: "{content}" * Deletable: true */ CHAT_INPUT_COMMAND = 20, /** * A message sent when a thread starter message is added to a thread * * Value: 21 * Name: THREAD_STARTER_MESSAGE * Rendered Content: "{referenced_message?.content}" ?? "Sorry, we couldn't load the first message in this thread" * Deletable: false */ THREAD_STARTER_MESSAGE = 21, /** * A message sent to remind users to invite friends to a guild * * Value: 22 * Name: GUILD_INVITE_REMINDER * Rendered Content: "Wondering who to invite?\nStart by inviting anyone who can help you build the server!" * Deletable: true */ GUILD_INVITE_REMINDER = 22, /** * A message sent when a user uses a context menu command * * Value: 23 * Name: CONTEXT_MENU_COMMAND * Rendered Content: "{content}" * Deletable: true */ CONTEXT_MENU_COMMAND = 23, /** * A message sent when auto moderation takes an action * * Value: 24 * Name: AUTO_MODERATION_ACTION * Rendered Content: Special embed rendered from embeds[0] * Deletable: true 1 */ AUTO_MODERATION_ACTION = 24, /** * A message sent when a user purchases or renews a role subscription * * Value: 25 * Name: ROLE_SUBSCRIPTION_PURCHASE * Rendered Content: "{author} {is_renewal ? "renewed" : "joined"} {role_subscription.tier_name} and has been a subscriber of {guild} for {role_subscription.total_months_subscribed} month(?s)!" * Deletable: true */ ROLE_SUBSCRIPTION_PURCHASE = 25, /** * A message sent when a user is upsold to a premium interaction * * Value: 26 * Name: INTERACTION_PREMIUM_UPSELL * Rendered Content: "{content}" * Deletable: true */ INTERACTION_PREMIUM_UPSELL = 26, /** * A message sent when a stage channel starts * * Value: 27 * Name: STAGE_START * Rendered Content: "{author} started {content} " * Deletable: true */ STAGE_START = 27, /** * A message sent when a stage channel ends * * Value: 28 * Name: STAGE_END * Rendered Content: "{author} ended {content} " * Deletable: true */ STAGE_END = 28, /** * A message sent when a user starts speaking in a stage channel * * Value: 29 * Name: STAGE_SPEAKER * Rendered Content: "{author} is now a speaker." * Deletable: true */ STAGE_SPEAKER = 29, /** * A message sent when a user raises their hand in a stage channel * * Value: 30 * Name: STAGE_RAISE_HAND * Rendered Content: "{author} requested to speak." * Deletable: true */ STAGE_RAISE_HAND = 30, /** * A message sent when a stage channel's topic is changed * * Value: 31 * Name: STAGE_TOPIC * Rendered Content: "{author} changed the Stage topic: {content} " * Deletable: true */ STAGE_TOPIC = 31, /** * A message sent when a user purchases an application premium subscription * * Value: 32 * Name: GUILD_APPLICATION_PREMIUM_SUBSCRIPTION * Rendered Content: "{author} upgraded {application ?? "a deleted application"} to premium for this server!" * Deletable: true */ GUILD_APPLICATION_PREMIUM_SUBSCRIPTION = 32, /** * A message sent when a user gifts a premium (Nitro) referral * * Value: 35 * Name: PREMIUM_REFERRAL * Rendered Content: "{content}" * Deletable: true */ PREMIUM_REFERRAL = 35, /** * A message sent when a user enabled lockdown for the guild * * Value: 36 * Name: GUILD_INCIDENT_ALERT_MODE_ENABLED * Rendered Content: "{author} enabled security actions until {content}." * Deletable: true */ GUILD_INCIDENT_ALERT_MODE_ENABLED = 36, /** * A message sent when a user disables lockdown for the guild * * Value: 37 * Name: GUILD_INCIDENT_ALERT_MODE_DISABLED * Rendered Content: "{author} disabled security actions." * Deletable: true */ GUILD_INCIDENT_ALERT_MODE_DISABLED = 37, /** * A message sent when a user reports a raid for the guild * * Value: 38 * Name: GUILD_INCIDENT_REPORT_RAID * Rendered Content: "{author} reported a raid in {guild}." * Deletable: true */ GUILD_INCIDENT_REPORT_RAID = 38, /** * A message sent when a user reports a false alarm for the guild * * Value: 39 * Name: GUILD_INCIDENT_REPORT_FALSE_ALARM * Rendered Content: "{author} reported a false alarm in {guild}." * Deletable: true */ GUILD_INCIDENT_REPORT_FALSE_ALARM = 39, /** * A message sent when no one sends a message in the current channel for 1 hour * * Value: 40 * Name: GUILD_DEADCHAT_REVIVE_PROMPT * Rendered Content: "{content}" * Deletable: true */ GUILD_DEADCHAT_REVIVE_PROMPT = 40, /** * A message sent when a user buys another user a gift * * Value: 41 * Name: CUSTOM_GIFT * Rendered Content: Special embed rendered from embeds[0].url and gift_info * Deletable: true */ CUSTOM_GIFT = 41, /** * Value: 42 * Name: GUILD_GAMING_STATS_PROMPT * Rendered Content: "{content}" * Deletable: true */ GUILD_GAMING_STATS_PROMPT = 42, /** * A message sent when a user purchases a guild product * * Value: 44 * Name: PURCHASE_NOTIFICATION * Rendered Content: "{author} has purchased {purchase_notification.guild_product_purchase.product_name}!" * Deletable: true */ PURCHASE_NOTIFICATION = 44, /** * A message sent when a poll is finalized * * Value: 46 * Name: POLL_RESULT * Rendered Content: Special embed rendered from embeds[0] * Deletable: true */ POLL_RESULT = 46, /** * A message sent by the Discord Updates account when a new changelog is posted * * Value: 47 * Name: CHANGELOG * Rendered Content: "{content}" * Deletable: true */ CHANGELOG = 47, /** * A message sent when a Nitro promotion is triggered * * Value: 48 * Name: NITRO_NOTIFICATION * Rendered Content: Special embed rendered from content * Deletable: true */ NITRO_NOTIFICATION = 48, /** * A message sent when a voice channel is linked to a lobby * * Value: 49 * Name: CHANNEL_LINKED_TO_LOBBY * Rendered Content: "{content}" * Deletable: true */ CHANNEL_LINKED_TO_LOBBY = 49, /** * A local-only ephemeral message sent when a user is prompted to gift Nitro to a friend on their friendship anniversary * * Value: 50 * Name: GIFTING_PROMPT * Rendered Content: Special embed * Deletable: true */ GIFTING_PROMPT = 50, /** * A local-only message sent when a user receives an in-game message NUX * * Value: 51 * Name: IN_GAME_MESSAGE_NUX * Rendered Content: "{author} messaged you from {application.name}. In-game chat may not include rich messaging features such as images, polls, or apps. Learn More " * Deletable: true */ IN_GAME_MESSAGE_NUX = 51, /** * A message sent when a user accepts a guild join request * * Value: 52 * Name: GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION 2 * Rendered Content: "{join_request.user}'s application to {content} was approved! Welcome!" * Deletable: true */ GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION = 52, /** * A message sent when a user rejects a guild join request * * Value: 53 * Name: GUILD_JOIN_REQUEST_REJECT_NOTIFICATION 2 * Rendered Content: "{join_request.user}'s application to {content} was rejected." * Deletable: true */ GUILD_JOIN_REQUEST_REJECT_NOTIFICATION = 53, /** * A message sent when a user withdraws a guild join request * * Value: 54 * Name: GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION 2 * Rendered Content: "{join_request.user}'s application to {content} has been withdrawn." * Deletable: true */ GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION = 54, /** * A message sent when a user upgrades to HD streaming * * Value: 55 * Name: HD_STREAMING_UPGRADED * Rendered Content: "{author} activated HD Splash Potion " * Deletable: true */ HD_STREAMING_UPGRADED = 55, /** * A message sent when a user resolves a moderation report by deleting the offending message * * Value: 58 * Name: REPORT_TO_MOD_DELETED_MESSAGE * Rendered Content: "{author} deleted the message" * Deletable: true */ REPORT_TO_MOD_DELETED_MESSAGE = 58, /** * A message sent when a user resolves a moderation report by timing out the offending user * * Value: 59 * Name: REPORT_TO_MOD_TIMEOUT_USER * Rendered Content: "{author} timed out {mentions [0] }" * Deletable: true */ REPORT_TO_MOD_TIMEOUT_USER = 59, /** * A message sent when a user resolves a moderation report by kicking the offending user * * Value: 60 * Name: REPORT_TO_MOD_KICK_USER * Rendered Content: "{author} kicked {mentions [0] }" * Deletable: true */ REPORT_TO_MOD_KICK_USER = 60, /** * A message sent when a user resolves a moderation report by banning the offending user * * Value: 61 * Name: REPORT_TO_MOD_BAN_USER * Rendered Content: "{author} banned {mentions [0] }" * Deletable: true */ REPORT_TO_MOD_BAN_USER = 61, /** * A message sent when a user resolves a moderation report * * Value: 62 * Name: REPORT_TO_MOD_CLOSED_REPORT * Rendered Content: "{author} resolved this flag" * Deletable: true */ REPORT_TO_MOD_CLOSED_REPORT = 62, /** * A message sent when a user adds a new emoji to a guild * * Value: 63 * Name: EMOJI_ADDED * Rendered Content: "{author} added a new emoji, {content} :{emoji.name}: " * Deletable: true */ EMOJI_ADDED = 63, } export const enum MessageFlags { /** * Message has been published to subscribed channels (via Channel Following) * * Value: 1 << 0 */ CROSSPOSTED = 1 << 0, /** * Message originated from a message in another channel (via Channel Following) */ IS_CROSSPOST = 1 << 1, /** * Embeds will not be included when serializing this message */ SUPPRESS_EMBEDS = 1 << 2, /** * Source message for this crosspost has been deleted (via Channel Following) */ SOURCE_MESSAGE_DELETED = 1 << 3, /** * Message came from the urgent message system */ URGENT = 1 << 4, /** * Message has an associated thread, with the same ID as the message */ HAS_THREAD = 1 << 5, /** * Message is only visible to the user who invoked the interaction */ EPHEMERAL = 1 << 6, /** * Message is an interaction response and the bot is "thinking" */ LOADING = 1 << 7, /** * Some roles were not mentioned and added to the thread */ FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8, /** * Message is hidden from the guild's feed */ GUILD_FEED_HIDDEN = 1 << 9, /** * Message contains a link that impersonates Discord */ SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10, /** * Message will not trigger push and desktop notifications */ SUPPRESS_NOTIFICATIONS = 1 << 12, /** * Message's audio attachment is rendered as a voice message */ IS_VOICE_MESSAGE = 1 << 13, /** * Message has a forwarded message snapshot attached */ HAS_SNAPSHOT = 1 << 14, /** * Message contains components from version 2 of the UI kit */ IS_COMPONENTS_V2 = 1 << 15, /** * Message was triggered by the social layer integration */ SENT_BY_SOCIAL_LAYER_INTEGRATION = 1 << 16, } ================================================ FILE: packages/discord-types/enums/misc.ts ================================================ export const enum CloudUploadPlatform { REACT_NATIVE = 0, WEB = 1, } export const enum DraftType { ChannelMessage = 0, ThreadSettings = 1, FirstThreadMessage = 2, ApplicationLauncherCommand = 3, Poll = 4, SlashCommand = 5, ForwardContextMessage = 6, } export const enum GuildScheduledEventStatus { SCHEDULED = 1, ACTIVE = 2, COMPLETED = 3, CANCELED = 4, } export const enum GuildScheduledEventEntityType { STAGE_INSTANCE = 1, VOICE = 2, EXTERNAL = 3, } export const enum GuildScheduledEventPrivacyLevel { GUILD_ONLY = 2, } export const enum ParticipantType { STREAM = 0, HIDDEN_STREAM = 1, USER = 2, ACTIVITY = 3, } export const enum RTCPlatform { DESKTOP = 0, MOBILE = 1, XBOX = 2, PLAYSTATION = 3, } export const enum VideoSourceType { VIDEO = 0, CAMERA_PREVIEW = 1, } export const enum EmojiIntention { REACTION = 0, STATUS = 1, COMMUNITY_CONTENT = 2, CHAT = 3, GUILD_STICKER_RELATED_EMOJI = 4, GUILD_ROLE_BENEFIT_EMOJI = 5, SOUNDBOARD = 6, VOICE_CHANNEL_TOPIC = 7, GIFT = 8, AUTO_SUGGESTION = 9, POLLS = 10, PROFILE = 11, MESSAGE_CONFETTI = 12, GUILD_PROFILE = 13, CHANNEL_NAME = 14, DEFAULT_REACT_EMOJI = 15, } export const enum LoadState { NOT_LOADED = 0, LOADING = 1, LOADED = 2, ERROR = 3, } export const enum ConnectionStatsFlags { TRANSPORT = 1, OUTBOUND = 2, INBOUND = 4, ALL = 7, } export const enum SpeakingFlags { NONE = 0, VOICE = 1, SOUNDSHARE = 2, PRIORITY = 4, HIDDEN = 8, } export const enum GoLiveQualityMode { AUTO = 1, FULL = 2, } export const enum VoiceProcessingStateReason { CPU_OVERUSE = 1, FAILED = 2, VAD_CPU_OVERUSE = 3, INITIALIZED = 4, } ================================================ FILE: packages/discord-types/enums/user.ts ================================================ export const enum RelationshipType { NONE = 0, FRIEND = 1, BLOCKED = 2, INCOMING_REQUEST = 3, OUTGOING_REQUEST = 4, IMPLICIT = 5, SUGGESTION = 6 } export enum GiftIntentType { FRIEND_ANNIVERSARY = 0 } export const enum ReadStateType { CHANNEL = 0, GUILD_EVENT = 1, NOTIFICATION_CENTER = 2, GUILD_HOME = 3, GUILD_ONBOARDING_QUESTION = 4, MESSAGE_REQUESTS = 5, } ================================================ FILE: packages/discord-types/package.json ================================================ { "name": "@vencord/discord-types", "author": "Vencord Contributors", "private": false, "description": "Typescript definitions for the webpack modules of the Discord Web app", "version": "1.0.0", "license": "LGPL-3.0-or-later", "types": "src/index.d.ts", "type": "module", "repository": { "type": "git", "url": "git+https://github.com/Vendicated/Vencord.git", "directory": "packages/discord-types" }, "dependencies": { "moment": "^2.22.2", "type-fest": "^4.41.0" }, "peerDependencies": { "@types/react": "^19.0.10" } } ================================================ FILE: packages/discord-types/src/common/Activity.d.ts ================================================ import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "../../enums"; export interface ActivityAssets { large_image?: string; large_text?: string; large_url?: string; small_image?: string; small_text?: string; small_url?: string; } export interface ActivityButton { label: string; url: string; } export interface Activity { name: string; application_id: string; type: ActivityType; state?: string; state_url?: string; details?: string; details_url?: string; url?: string; flags: ActivityFlags; status_display_type?: ActivityStatusDisplayType; timestamps?: { start?: number; end?: number; }; assets?: ActivityAssets; buttons?: string[]; metadata?: { button_urls?: Array; }; party?: { id?: string; size?: [number, number]; }; } export type OnlineStatus = "online" | "idle" | "dnd" | "invisible" | "offline" | "unknown" | "streaming"; ================================================ FILE: packages/discord-types/src/common/Application.d.ts ================================================ import { Guild } from "./Guild"; import { User } from "./User"; export interface ApplicationExecutable { os: "win32" | "darwin" | "linux"; name: string; isLauncher: boolean; } export interface ApplicationThirdPartySku { id: string; sku: string; distributor: string; } export interface ApplicationDeveloper { id: string; name: string; } export interface ApplicationInstallParams { permissions: string | null; scopes: string[]; } export interface Application { id: string; name: string; icon: string | null; description: string; type: number | null; coverImage: string | null; primarySkuId: string | undefined; bot: User | null; splash: string | undefined; thirdPartySkus: ApplicationThirdPartySku[]; isMonetized: boolean; isVerified: boolean; roleConnectionsVerificationUrl: string | undefined; parentId: string | undefined; connectionEntrypointUrl: string | undefined; overlay: boolean; overlayWarn: boolean; overlayCompatibilityHook: boolean; overlayMethods: number; hook: boolean; aliases: string[]; publishers: ApplicationDeveloper[]; developers: ApplicationDeveloper[]; storeListingSkuId: string | undefined; guildId: string | null; guild: Guild | undefined; executables: ApplicationExecutable[]; hashes: string[]; eulaId: string | undefined; slug: string | undefined; flags: number; maxParticipants: number | undefined; tags: string[]; embeddedActivityConfig: Record | undefined; team: ApplicationTeam | undefined; integrationTypesConfig: Record>; storefront_available: boolean; termsOfServiceUrl: string | undefined; privacyPolicyUrl: string | undefined; isDiscoverable: boolean; customInstallUrl: string | undefined; installParams: ApplicationInstallParams | undefined; directoryEntry: Record | undefined; categories: string[] | undefined; linkedGames: string[] | undefined; deepLinkUri: string | undefined; } export interface ApplicationTeam { id: string; name: string; icon: string | null; members: ApplicationTeamMember[]; ownerUserId: string; } export interface ApplicationTeamMember { user: User; teamId: string; membershipState: number; permissions: string[]; role: string; } ================================================ FILE: packages/discord-types/src/common/Channel.d.ts ================================================ import { DiscordRecord } from "./Record"; export class Channel extends DiscordRecord { constructor(channel: object); application_id: number | undefined; bitrate: number; defaultAutoArchiveDuration: number | undefined; flags: number; guild_id: string; icon: string; id: string; lastMessageId: string; lastPinTimestamp: string | undefined; member: unknown; memberCount: number | undefined; memberIdsPreview: string[] | undefined; memberListId: unknown; messageCount: number | undefined; name: string; nicks: Record; nsfw: boolean; originChannelId: unknown; ownerId: string; parent_id: string; permissionOverwrites: { [role: string]: { id: string; type: number; deny: bigint; allow: bigint; }; }; position: number; rateLimitPerUser: number; rawRecipients: { id: string; avatar: string; username: string; public_flags: number; discriminator: string; }[]; recipients: string[]; rtcRegion: string; threadMetadata: { locked: boolean; archived: boolean; invitable: boolean; createTimestamp: string | undefined; autoArchiveDuration: number; archiveTimestamp: string | undefined; }; topic: string; type: number; userLimit: number; videoQualityMode: undefined; get accessPermissions(): bigint; get lastActiveTimestamp(): number; computeLurkerPermissionsAllowList(): unknown; getApplicationId(): unknown; getGuildId(): string; getRecipientId(): unknown; hasFlag(flag: number): boolean; isActiveThread(): boolean; isArchivedThread(): boolean; isCategory(): boolean; isDM(): boolean; isDirectory(): boolean; isForumChannel(): boolean; isGroupDM(): boolean; isGuildStageVoice(): boolean; isGuildVoice(): boolean; isListenModeCapable(): boolean; isManaged(): boolean; isMultiUserDM(): boolean; isNSFW(): boolean; isOwner(): boolean; isPrivate(): boolean; isSystemDM(): boolean; isThread(): boolean; isVocal(): boolean; } ================================================ FILE: packages/discord-types/src/common/Guild.d.ts ================================================ import { Role } from './Role'; import { DiscordRecord } from './Record'; // copy(Object.keys(findByProps("CREATOR_MONETIZABLE")).map(JSON.stringify).join("|")) export type GuildFeatures = "INVITE_SPLASH" | "VIP_REGIONS" | "VANITY_URL" | "MORE_EMOJI" | "MORE_STICKERS" | "MORE_SOUNDBOARD" | "VERIFIED" | "COMMERCE" | "DISCOVERABLE" | "COMMUNITY" | "FEATURABLE" | "NEWS" | "HUB" | "PARTNERED" | "ANIMATED_ICON" | "BANNER" | "ENABLED_DISCOVERABLE_BEFORE" | "WELCOME_SCREEN_ENABLED" | "MEMBER_VERIFICATION_GATE_ENABLED" | "PREVIEW_ENABLED" | "ROLE_SUBSCRIPTIONS_ENABLED" | "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" | "CREATOR_MONETIZABLE" | "CREATOR_MONETIZABLE_PROVISIONAL" | "CREATOR_MONETIZABLE_WHITEGLOVE" | "CREATOR_MONETIZABLE_DISABLED" | "CREATOR_MONETIZABLE_RESTRICTED" | "CREATOR_STORE_PAGE" | "CREATOR_MONETIZABLE_PENDING_NEW_OWNER_ONBOARDING" | "PRODUCTS_AVAILABLE_FOR_PURCHASE" | "GUILD_WEB_PAGE_VANITY_URL" | "THREADS_ENABLED" | "THREADS_ENABLED_TESTING" | "NEW_THREAD_PERMISSIONS" | "ROLE_ICONS" | "TEXT_IN_STAGE_ENABLED" | "TEXT_IN_VOICE_ENABLED" | "HAS_DIRECTORY_ENTRY" | "ANIMATED_BANNER" | "LINKED_TO_HUB" | "EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT" | "GUILD_HOME_DEPRECATION_OVERRIDE" | "GUILD_HOME_TEST" | "GUILD_HOME_OVERRIDE" | "GUILD_ONBOARDING" | "GUILD_ONBOARDING_EVER_ENABLED" | "GUILD_ONBOARDING_HAS_PROMPTS" | "GUILD_SERVER_GUIDE" | "INTERNAL_EMPLOYEE_ONLY" | "AUTO_MODERATION" | "INVITES_DISABLED" | "BURST_REACTIONS" | "SOUNDBOARD" | "SHARD" | "ACTIVITY_FEED_ENABLED_BY_USER" | "ACTIVITY_FEED_DISABLED_BY_USER" | "SUMMARIES_ENABLED_GA" | "LEADERBOARD_ENABLED" | "SUMMARIES_ENABLED_BY_USER" | "SUMMARIES_OPT_OUT_EXPERIENCE" | "CHANNEL_ICON_EMOJIS_GENERATED" | "NON_COMMUNITY_RAID_ALERTS" | "RAID_ALERTS_DISABLED" | "AUTOMOD_TRIGGER_USER_PROFILE" | "ENABLED_MODERATION_EXPERIENCE_FOR_NON_COMMUNITY" | "GUILD_PRODUCTS_ALLOW_ARCHIVED_FILE" | "CLAN" | "MEMBER_VERIFICATION_MANUAL_APPROVAL" | "FORWARDING_DISABLED" | "MEMBER_VERIFICATION_ROLLOUT_TEST" | "AUDIO_BITRATE_128_KBPS" | "AUDIO_BITRATE_256_KBPS" | "AUDIO_BITRATE_384_KBPS" | "VIDEO_BITRATE_ENHANCED" | "MAX_FILE_SIZE_50_MB" | "MAX_FILE_SIZE_100_MB" | "GUILD_TAGS" | "ENHANCED_ROLE_COLORS" | "PREMIUM_TIER_3_OVERRIDE" | "REPORT_TO_MOD_PILOT" | "TIERLESS_BOOSTING_SYSTEM_MESSAGE"; export type GuildPremiumFeatures = "ANIMATED_ICON" | "STAGE_CHANNEL_VIEWERS_150" | "ROLE_ICONS" | "GUILD_TAGS" | "BANNER" | "MAX_FILE_SIZE_50_MB" | "VIDEO_QUALITY_720_60FPS" | "STAGE_CHANNEL_VIEWERS_50" | "VIDEO_QUALITY_1080_60FPS" | "MAX_FILE_SIZE_100_MB" | "VANITY_URL" | "VIDEO_BITRATE_ENHANCED" | "STAGE_CHANNEL_VIEWERS_300" | "AUDIO_BITRATE_128_KBPS" | "ANIMATED_BANNER" | "TIERLESS_BOOSTING" | "ENHANCED_ROLE_COLORS" | "INVITE_SPLASH" | "AUDIO_BITRATE_256_KBPS" | "AUDIO_BITRATE_384_KBPS"; export class Guild extends DiscordRecord { constructor(guild: object); afkChannelId: string | undefined; afkTimeout: number; applicationCommandCounts: { 0: number; 1: number; 2: number; }; application_id: unknown; banner: string | undefined; defaultMessageNotifications: number; description: string | undefined; discoverySplash: string | undefined; explicitContentFilter: number; features: Set; homeHeader: string | undefined; hubType: unknown; icon: string | undefined; id: string; joinedAt: Date; latestOnboardingQuestionId: string | undefined; maxMembers: number; maxStageVideoChannelUsers: number; maxVideoChannelUsers: number; mfaLevel: number; moderatorReporting: unknown; name: string; nsfwLevel: number; ownerConfiguredContentLevel: number; ownerId: string; preferredLocale: string; premiumFeatures: { additionalEmojiSlots: number; additionalSoundSlots: number; additionalStickerSlots: number; features: Array; }; premiumProgressBarEnabled: boolean; premiumSubscriberCount: number; premiumTier: 0 | 1 | 2 | 3; profile: { badge: string | undefined; tag: string | undefined; } | undefined; publicUpdatesChannelId: string | undefined; roles: Record; rulesChannelId: string | undefined; safetyAlertsChannelId: string | undefined; splash: string | undefined; systemChannelFlags: number; systemChannelId: string | undefined; vanityURLCode: string | undefined; verificationLevel: number; } ================================================ FILE: packages/discord-types/src/common/GuildMember.d.ts ================================================ export interface GuildMember { avatar: string | undefined; avatarDecoration: string | undefined; banner: string | undefined; bio: string; colorRoleId: string | undefined; colorString: string; colorStrings: { primaryColor: string | undefined; secondaryColor: string | undefined; tertiaryColor: string | undefined; }; communicationDisabledUntil: string | undefined; flags: number; fullProfileLoadedTimestamp: number; guildId: string; highestRoleId: string; hoistRoleId: string; iconRoleId: string; isPending: boolean | undefined; joinedAt: string | undefined; nick: string | undefined; premiumSince: string | undefined; roles: string[]; userId: string; } ================================================ FILE: packages/discord-types/src/common/Record.d.ts ================================================ type Updater = (value: any) => any; /** * Common Record class extended by various Discord data structures, like User, Channel, Guild, etc. */ export class DiscordRecord { toJS(): Record; set(key: string, value: any): this; merge(data: Record): this; update(key: string, defaultValueOrUpdater: Updater | any, updater?: Updater): this; } ================================================ FILE: packages/discord-types/src/common/Role.d.ts ================================================ export interface Role { color: number; colorString: string | undefined; colorStrings: { primaryColor: string | undefined; secondaryColor: string | undefined; tertiaryColor: string | undefined; }; colors: { primary_color: number | undefined; secondary_color: number | undefined; tertiary_color: number | undefined; }; flags: number; hoist: boolean; icon: string | undefined; id: string; managed: boolean; mentionable: boolean; name: string; originalPosition: number; permissions: bigint; position: number; /** * probably incomplete */ tags: { bot_id: string; integration_id: string; premium_subscriber: unknown; } | undefined; unicodeEmoji: string | undefined; } ================================================ FILE: packages/discord-types/src/common/User.d.ts ================================================ // TODO: a lot of optional params can also be null, not just undef import { DiscordRecord } from "./Record"; export class User extends DiscordRecord { constructor(user: object); accentColor: number; avatar: string; banner: string | null | undefined; bio: string; bot: boolean; desktop: boolean; discriminator: string; email: string | undefined; flags: number; globalName: string | undefined; guildMemberAvatars: Record; id: string; mfaEnabled: boolean; mobile: boolean; nsfwAllowed: boolean | undefined; phone: string | undefined; premiumType: number | undefined; premiumUsageFlags: number; publicFlags: number; purchasedFlags: number; system: boolean; username: string; verified: boolean; get createdAt(): Date; get hasPremiumPerks(): boolean; get tag(): string; get usernameNormalized(): string; addGuildAvatarHash(guildId: string, avatarHash: string): User; getAvatarSource(guildId: string, canAnimate?: boolean): { uri: string; }; getAvatarURL(guildId?: string | null, t?: unknown, canAnimate?: boolean): string; hasAvatarForGuild(guildId: string): boolean; hasDisabledPremium(): boolean; hasFlag(flag: number): boolean; hasFreePremium(): boolean; hasHadSKU(e: unknown): boolean; hasPremiumUsageFlag(flag: number): boolean; hasPurchasedFlag(flag: number): boolean; hasUrgentMessages(): boolean; isClaimed(): boolean; isLocalBot(): boolean; isNonUserBot(): boolean; isPhoneVerified(): boolean; isStaff(): boolean; isSystemUser(): boolean; isVerifiedBot(): boolean; removeGuildAvatarHash(guildId: string): User; toString(): string; } export interface UserJSON { avatar: string; avatarDecoration: unknown | undefined; discriminator: string; id: string; publicFlags: number; username: string; } ================================================ FILE: packages/discord-types/src/common/index.d.ts ================================================ export * from "./Activity"; export * from "./Application"; export * from "./Channel"; export * from "./Guild"; export * from "./GuildMember"; export * from "./messages"; export * from "./Role"; export * from "./User"; export * from "./Record"; ================================================ FILE: packages/discord-types/src/common/messages/Commands.d.ts ================================================ import { Channel } from "../Channel"; import { Guild } from "../Guild"; import { Promisable } from "type-fest"; import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType } from "../../../enums"; export interface CommandContext { channel: Channel; guild?: Guild; } export interface CommandOption { name: string; displayName?: string; type: ApplicationCommandOptionType; description: string; displayDescription?: string; required?: boolean; options?: CommandOption[]; choices?: Array; } export interface ChoicesOption { label: string; value: string; name: string; displayName?: string; } export interface CommandReturnValue { content: string; // TODO: implement // cancel?: boolean; } export interface CommandArgument { type: ApplicationCommandOptionType; name: string; value: string; focused: undefined; options: CommandArgument[]; } export interface Command { id?: string; applicationId?: string; type?: ApplicationCommandType; inputType?: ApplicationCommandInputType; plugin?: string; name: string; untranslatedName?: string; displayName?: string; description: string; untranslatedDescription?: string; displayDescription?: string; options?: CommandOption[]; predicate?(ctx: CommandContext): boolean; execute(args: CommandArgument[], ctx: CommandContext): Promisable; } ================================================ FILE: packages/discord-types/src/common/messages/Embed.d.ts ================================================ export interface Embed { author?: { name: string; url: string; iconURL: string | undefined; iconProxyURL: string | undefined; }; color: string; fields: []; id: string; image?: { height: number; width: number; url: string; proxyURL: string; }; provider?: { name: string; url: string | undefined; }; rawDescription: string; rawTitle: string; referenceId: unknown; timestamp: string; thumbnail?: { height: number; proxyURL: string | undefined; url: string; width: number; }; type: string; url: string | undefined; video?: { height: number; width: number; url: string; proxyURL: string | undefined; }; } export interface EmbedJSON { author?: { name: string; url: string; icon_url: string; proxy_icon_url: string; }; title: string; color: string; description: string; type: string; url: string | undefined; provider?: { name: string; url: string; }; timestamp: string; thumbnail?: { height: number; width: number; url: string; proxy_url: string | undefined; }; video?: { height: number; width: number; url: string; proxy_url: string | undefined; }; } ================================================ FILE: packages/discord-types/src/common/messages/Emoji.d.ts ================================================ /** Union type for both custom (guild) emojis and unicode emojis. */ export type Emoji = CustomEmoji | UnicodeEmoji; /** * Custom emoji uploaded to a guild. */ export interface CustomEmoji { /** Discriminator for custom emojis. */ type: 1; /** Whether the emoji is animated (GIF). */ animated: boolean; /** Whether the emoji is available for use. */ available: boolean; /** Guild id this emoji belongs to. */ guildId: string; /** Unique emoji id (snowflake). */ id: string; /** Whether the emoji is managed by an integration (e.g. Twitch). */ managed: boolean; /** Emoji name without colons. */ name: string; /** Original name before any modifications. */ originalName?: string; /** Whether the emoji requires colons to use. */ require_colons: boolean; /** Role ids that can use this emoji (empty array means everyone). */ roles: string[]; /** Version number, incremented when emoji is updated. */ version?: number; } /** * Built-in unicode emoji. */ export interface UnicodeEmoji { /** Discriminator for unicode emojis. */ type: 0; /** Skin tone variant emojis keyed by diversity surrogate code (e.g. "1f3fb" for light skin). */ diversityChildren: Record; /** Raw emoji data from Discord's emoji dataset. */ emojiObject: EmojiObject; /** Index position in the emoji list. */ index: number; /** Unicode surrogate pair(s) for this emoji. */ surrogates: string; /** Unique name identifier for this emoji. */ uniqueName: string; /** Whether to render using sprite sheet. */ useSpriteSheet: boolean; /** Original name if renamed in context. */ originalName?: string; /** Emoji id when used in custom emoji context. */ id?: string; /** Guild id when used in guild context. */ guildId?: string; /** Formatted string of all emoji names. */ get allNamesString(): string; /** Always false for unicode emojis. */ get animated(): false; /** Default skin tone variant or undefined if no diversity. */ get defaultDiversityChild(): UnicodeEmoji | undefined; /** Whether this emoji supports skin tone modifiers. */ get hasDiversity(): boolean | undefined; /** Whether this emoji is a skin tone variant of another. */ get hasDiversityParent(): boolean | undefined; /** Whether this emoji supports multiple diversity modifiers (e.g. handshake with two skin tones). */ get hasMultiDiversity(): boolean | undefined; /** Whether this emoji is a multi-diversity variant of another. */ get hasMultiDiversityParent(): boolean | undefined; /** Always true for unicode emojis. */ get managed(): true; /** Primary emoji name. */ get name(): string; /** All names/aliases for this emoji. */ get names(): string[]; /** Surrogate sequence with optional diversity modifier. */ get optionallyDiverseSequence(): string | undefined; /** Unicode version when this emoji was added. */ get unicodeVersion(): number; /** CDN url for emoji image. */ get url(): string; /** * Iterates over all diversity variants of this emoji. * @param callback Function called for each diversity variant. */ forEachDiversity(callback: (emoji: UnicodeEmoji) => void): void; /** * Iterates over all names/aliases of this emoji. * @param callback Function called for each name. */ forEachName(callback: (name: string) => void): void; } /** * Raw emoji data from Discord's emoji dataset. */ export interface EmojiObject { /** All names/aliases for this emoji. */ names: string[]; /** Unicode surrogate pair(s). */ surrogates: string; /** Unicode version when this emoji was added. */ unicodeVersion: number; /** Index in the sprite sheet for rendering. */ spriteIndex?: number; /** Whether this emoji supports multiple skin tone modifiers. */ hasMultiDiversity?: boolean; /** Whether this emoji is a diversity variant with a multi-diversity parent. */ hasMultiDiversityParent?: boolean; /** Skin tone modifier codes for this variant (e.g. ["1f3fb"] or ["1f3fb", "1f3fc"]). */ diversity?: string[]; /** Sprite indices of diversity children for parent emojis. */ diversityChildren?: number[]; } ================================================ FILE: packages/discord-types/src/common/messages/Message.d.ts ================================================ import { CommandOption } from './Commands'; import { User, UserJSON } from '../User'; import { Embed, EmbedJSON } from './Embed'; import { DiscordRecord } from "../Record"; import { ApplicationIntegrationType, MessageFlags, MessageType, StickerFormatType } from "../../../enums"; /* * TODO: looks like discord has moved over to Date instead of Moment; */ export class Message extends DiscordRecord { constructor(message: object); activity: unknown; application: unknown; applicationId: string | unknown; attachments: MessageAttachment[]; author: User; blocked: boolean; bot: boolean; call: { duration: moment.Duration; endedTimestamp: moment.Moment; participants: string[]; }; channel_id: string; /** * NOTE: not fully typed */ codedLinks: { code?: string; type: string; }[]; colorString: unknown; components: unknown[]; content: string; customRenderedContent: unknown; editedTimestamp: Date; embeds: Embed[]; flags: MessageFlags; giftCodes: string[]; id: string; interaction: { id: string; name: string; type: number; user: User; }[] | undefined; interactionData: { application_command: { application_id: string; default_member_permissions: unknown; default_permission: boolean; description: string; dm_permission: unknown; id: string; name: string; options: CommandOption[]; permissions: unknown[]; type: number; version: string; }; attachments: MessageAttachment[]; guild_id: string | undefined; id: string; name: string; options: { focused: unknown; name: string; type: number; value: string; }[]; type: number; version: string; }[]; interactionMetadata?: { id: string; type: number; name?: string; command_type?: number; ephemerality_reason?: number; user: User; authorizing_integration_owners: Record; original_response_message_id?: string; interacted_message_id?: string; target_user?: User; target_message_id?: string; }; interactionError: unknown[]; isSearchHit: boolean; loggingName: unknown; mentionChannels: string[]; mentionEveryone: boolean; mentionRoles: string[]; mentioned: boolean; mentions: string[]; messageReference: { guild_id?: string; channel_id: string; message_id: string; } | undefined; messageSnapshots: { message: Message; }[]; nick: unknown; // probably a string nonce: string | undefined; pinned: boolean; reactions: MessageReaction[]; state: string; stickerItems: { format_type: StickerFormatType; id: string; name: string; }[]; stickers: unknown[]; timestamp: moment.Moment; tts: boolean; type: MessageType; webhookId: string | undefined; /** * Doesn't actually update the original message; it just returns a new message instance with the added reaction. */ addReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message; /** * Searches each reaction and if the provided string has an index above -1 it'll return the reaction object. */ getReaction(name: string): MessageReaction; /** * Doesn't actually update the original message; it just returns the message instance without the reaction searched with the provided emoji object. */ removeReactionsForEmoji(emoji: ReactionEmoji): Message; /** * Doesn't actually update the original message; it just returns the message instance without the reaction. */ removeReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message; getChannelId(): string; hasFlag(flag: MessageFlags): boolean; isCommandType(): boolean; isEdited(): boolean; isSystemDM(): boolean; /** Vencord added */ deleted?: boolean; } /** A smaller Message object found in FluxDispatcher and elsewhere. */ export interface MessageJSON { attachments: MessageAttachment[]; author: UserJSON; channel_id: string; components: unknown[]; content: string; edited_timestamp: string; embeds: EmbedJSON[]; flags: number; guild_id: string | undefined; id: string; loggingName: unknown; member: { avatar: string | undefined; communication_disabled_until: string | undefined; deaf: boolean; hoisted_role: string | undefined; is_pending: boolean; joined_at: string; mute: boolean; nick: string | boolean; pending: boolean; premium_since: string | undefined; roles: string[]; } | undefined; mention_everyone: boolean; mention_roles: string[]; mentions: UserJSON[]; message_reference: { guild_id?: string; channel_id: string; message_id: string; } | undefined; nonce: string | undefined; pinned: boolean; referenced_message: MessageJSON | undefined; state: string; timestamp: string; tts: boolean; type: number; } export interface MessageAttachment { filename: string; id: string; proxy_url: string; size: number; spoiler: boolean; url: string; content_type?: string; width?: number; height?: number; } export interface ReactionEmoji { id: string | undefined; name: string; animated: boolean; } export interface MessageReaction { count: number; emoji: ReactionEmoji; me: boolean; } // Object.keys(findByProps("REPLYABLE")).map(JSON.stringify).join("|") export type MessageTypeSets = Record< "UNDELETABLE" | "GUILD_DISCOVERY_STATUS" | "USER_MESSAGE" | "NOTIFIABLE_SYSTEM_MESSAGE" | "REPLYABLE" | "FORWARDABLE" | "REFERENCED_MESSAGE_AVAILABLE" | "AVAILABLE_IN_GUILD_FEED" | "DEADCHAT_PROMPTS" | "NON_COLLAPSIBLE" | "NON_PARSED" | "AUTOMOD_INCIDENT_ACTIONS" | "SELF_MENTIONABLE_SYSTEM" | "SCHEDULABLE", Set >; ================================================ FILE: packages/discord-types/src/common/messages/Sticker.d.ts ================================================ import { StickerFormatType, StickerType } from "../../../enums"; interface BaseSticker { asset: string; available: boolean; description: string; format_type: StickerFormatType; id: string; name: string; sort_value?: number; /** a comma separated string */ tags: string; } export interface PackSticker extends BaseSticker { pack_id: string; type: StickerType.STANDARD; } export interface GuildSticker extends BaseSticker { guild_id: string; type: StickerType.GUILD; } export type Sticker = PackSticker | GuildSticker; export interface PremiumStickerPack { banner_asset_id?: string; cover_sticker_id?: string; description: string; id: string; name: string; sku_id: string; stickers: PackSticker[]; } ================================================ FILE: packages/discord-types/src/common/messages/index.d.ts ================================================ export * from "./Commands"; export * from "./Message"; export * from "./Embed"; export * from "./Emoji"; export * from "./Sticker"; ================================================ FILE: packages/discord-types/src/components.d.ts ================================================ import type { ComponentClass, ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref, RefObject } from "react"; // #region Old compability export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`; export type Margins = Record<"marginTop16" | "marginTop8" | "marginBottom8" | "marginTop20" | "marginBottom20", string>; // copy(find(m => Array.isArray(m) && m.includes("heading-sm/normal")).map(JSON.stringify).join("|")) export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-sm/extrabold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-md/extrabold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-lg/extrabold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/semibold" | "heading-xl/bold" | "heading-xl/extrabold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/semibold" | "heading-xxl/bold" | "heading-xxl/extrabold" | "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"; export type TextProps = PropsWithChildren & { variant?: TextVariant; tag?: "div" | "span" | "p" | "strong" | HeadingTag; }>; export type Text = ComponentType; export interface ButtonProps extends PropsWithChildren, "size">> { /** Button.Looks.FILLED */ look?: string; /** Button.Colors.BRAND */ color?: string; /** Button.Sizes.MEDIUM */ size?: string; className?: string; } export type Button = ComponentType & { Colors: Record<"BRAND" | "RED" | "GREEN" | "PRIMARY" | "LINK" | "WHITE" | "TRANSPARENT" | "CUSTOM", string>; Looks: Record<"FILLED" | "LINK", string>; Sizes: Record<"NONE" | "SMALL" | "MEDIUM" | "LARGE" | "XLARGE" | "MIN", string>; }; // #endregion export interface TooltipChildrenProps { onClick(): void; onMouseEnter(): void; onMouseLeave(): void; onContextMenu(): void; onFocus(): void; onBlur(): void; "aria-label"?: string; } export interface TooltipProps { text: ReactNode | ComponentType; children: FunctionComponent; "aria-label"?: string; allowOverflow?: boolean; forceOpen?: boolean; hide?: boolean; hideOnClick?: boolean; shouldShow?: boolean; spacing?: number; /** Tooltip.Colors.BLACK */ color?: string; /** TooltipPositions.TOP */ position?: PopoutPosition; tooltipClassName?: string; tooltipContentClassName?: string; } export type Tooltip = ComponentType & { Colors: Record<"BLACK" | "BRAND" | "CUSTOM" | "GREEN" | "GREY" | "PRIMARY" | "RED" | "YELLOW", string>; }; export type TooltipPositions = Record<"BOTTOM" | "CENTER" | "LEFT" | "RIGHT" | "TOP" | "WINDOW_CENTER", string>; export type TooltipContainer = ComponentType>; export type Card = ComponentType & { editable?: boolean; outline?: boolean; /** Card.Types.PRIMARY */ type?: string; }>> & { Types: Record<"BRAND" | "CUSTOM" | "DANGER" | "PRIMARY" | "SUCCESS" | "WARNING", string>; }; export type ComboboxPopout = ComponentType; placeholder: string; children(query: string): ReactNode[]; onChange(value: any): void; itemToString?: (item: any) => string; onClose?(): void; className?: string; listClassName?: string; autoFocus?: boolean; multiSelect?: boolean; maxVisibleItems?: number; showScrollbar?: boolean; }>>; export type CheckboxAligns = { CENTER: "center"; TOP: "top"; }; export type CheckboxTypes = { DEFAULT: "default"; INVERTED: "inverted"; GHOST: "ghost"; ROW: "row"; }; export type Checkbox = ComponentType> & { Shapes: Record<"BOX" | "ROUND" | "SMALL_BOX", string>; Aligns: CheckboxAligns; Types: CheckboxTypes; }; export type Timestamp = ComponentType>; export type TextInput = ComponentType; prefixElement?: ReactNode; focusProps?: any; /** TextInput.Sizes.DEFAULT */ size?: string; } & Omit, "onChange" | "maxLength">>> & { Sizes: Record<"DEFAULT" | "MINI", string>; }; // FIXME: this is wrong, it's not actually just HTMLTextAreaElement export type TextArea = ComponentType, "onChange"> & { onChange(v: string): void; inputRef?: Ref; }>; interface SelectOption { disabled?: boolean; value: any; label: string; key?: React.Key; default?: boolean; } export type Select = ComponentType; // TODO /** * - 0 ~ Filled * - 1 ~ Custom */ look?: 0 | 1; className?: string; popoutClassName?: string; popoutPosition?: PopoutPosition; optionClassName?: string; autoFocus?: boolean; isDisabled?: boolean; clearable?: boolean; closeOnSelect?: boolean; hideIcon?: boolean; select(value: any): void; isSelected(value: any): boolean; serialize(value: any): string; clear?(): void; maxVisibleItems?: number; popoutWidth?: number; onClose?(): void; onOpen?(): void; renderOptionLabel?(option: SelectOption): ReactNode; /** discord stupid this gets all options instead of one yeah */ renderOptionValue?(option: SelectOption[]): ReactNode; "aria-label"?: boolean; "aria-labelledby"?: boolean; }>>; export type SearchableSelect = ComponentType; // TODO value?: any; /** * - 0 ~ Filled * - 1 ~ Custom */ look?: 0 | 1; className?: string; popoutClassName?: string; wrapperClassName?: string; popoutPosition?: PopoutPosition; optionClassName?: string; autoFocus?: boolean; isDisabled?: boolean; clearable?: boolean; closeOnSelect?: boolean; clearOnSelect?: boolean; multi?: boolean; onChange(value: any): void; onSearchChange?(value: string): void; onClose?(): void; onOpen?(): void; onBlur?(): void; renderOptionPrefix?(option: SelectOption): ReactNode; renderOptionSuffix?(option: SelectOption): ReactNode; filter?(option: SelectOption[], query: string): SelectOption[]; centerCaret?: boolean; debounceTime?: number; maxVisibleItems?: number; popoutWidth?: number; "aria-labelledby"?: boolean; }>>; export type Slider = ComponentClass>; declare enum PopoutAnimation { NONE = "1", TRANSLATE = "2", SCALE = "3", FADE = "4" } type PopoutPosition = "top" | "bottom" | "left" | "right" | "center" | "window_center"; export type Popout = ComponentType<{ children( thing: { "aria-controls": string; "aria-expanded": boolean; onClick(event: MouseEvent): void; onKeyDown(event: KeyboardEvent): void; onMouseDown(event: MouseEvent): void; }, data: { isShown: boolean; position: PopoutPosition; } ): ReactNode; shouldShow?: boolean; targetElementRef: RefObject; renderPopout(args: { closePopout(): void; isPositioned: boolean; nudge: number; position: PopoutPosition; setPopoutRef(ref: any): void; updatePosition(): void; }): ReactNode; onRequestOpen?(): void; onRequestClose?(): void; /** "center" and others */ align?: "left" | "right" | "center"; /** Popout.Animation */ animation?: PopoutAnimation; autoInvert?: boolean; nudgeAlignIntoViewport?: boolean; /** "bottom" and others */ position?: PopoutPosition; positionKey?: string; spacing?: number; }> & { Animation: typeof PopoutAnimation; }; export type Dialog = ComponentType; type Resolve = (data: { theme: "light" | "dark", saturation: number; }) => { hex(): string; hsl(): string; int(): number; spring(): string; }; export type useToken = (color: { css: string; resolve: Resolve; }) => ReturnType; export type Paginator = ComponentType<{ currentPage: number; maxVisiblePages: number; pageSize: number; totalCount: number; onPageChange?(page: number): void; hideMaxPage?: boolean; }>; export type MaskedLink = ComponentType>; export interface ScrollerBaseProps { className?: string; style?: CSSProperties; dir?: "ltr"; paddingFix?: boolean; onClose?(): void; onScroll?(): void; } export type ScrollerThin = ComponentType>; interface BaseListItem { anchorId: any; listIndex: number; offsetTop: number; section: number; } interface ListSection extends BaseListItem { type: "section"; } interface ListRow extends BaseListItem { type: "row"; row: number; rowIndex: number; } export type ListScrollerThin = ComponentType React.ReactNode; renderRow: (item: ListRow) => React.ReactNode; renderFooter?: (item: any) => React.ReactNode; renderSidebar?: (listVisible: boolean, sidebarVisible: boolean) => React.ReactNode; wrapSection?: (section: number, children: React.ReactNode) => React.ReactNode; sectionHeight: number; rowHeight: number; footerHeight?: number; sidebarHeight?: number; chunkSize?: number; paddingTop?: number; paddingBottom?: number; fade?: boolean; onResize?: Function; getAnchorId?: any; innerTag?: string; innerId?: string; innerClassName?: string; innerRole?: string; innerAriaLabel?: string; // Yes, Discord uses this casing innerAriaMultiselectable?: boolean; innerAriaOrientation?: "vertical" | "horizontal"; }>; export type Clickable = (props: PropsWithChildren> & { tag?: T; }) => ReactNode; export type Avatar = ComponentType>; type FocusLock = ComponentType; }>>; export type Icon = ComponentType>; export type ColorPicker = ComponentType<{ color: number | null; showEyeDropper?: boolean; suggestedColors?: string[]; label?: ReactNode; onChange(value: number | null): void; }>; ================================================ FILE: packages/discord-types/src/flux.d.ts ================================================ import { FluxStore } from "./stores/FluxStore"; export class FluxEmitter { constructor(); changeSentinel: number; changedStores: Set; isBatchEmitting: boolean; isDispatching: boolean; isPaused: boolean; pauseTimer: NodeJS.Timeout | null; reactChangedStores: Set; batched(batch: (...args: any[]) => void): void; destroy(): void; emit(): void; emitNonReactOnce(): void; emitReactOnce(): void; getChangeSentinel(): number; getIsPaused(): boolean; injectBatchEmitChanges(batch: (...args: any[]) => void): void; markChanged(store: FluxStore): void; pause(): void; resume(): void; } export interface Flux { Store: typeof FluxStore; Emitter: FluxEmitter; } ================================================ FILE: packages/discord-types/src/fluxEvents.d.ts ================================================ /* function makeFluxEventList() { // prefill MESSAGE_CREATE so that typescript infers this is a String Set // without explicitly typing so that this function is also valid javascript const events = new Set(["MESSAGE_CREATE"]); const { nodes } = Vencord.Webpack.Common.FluxDispatcher._actionHandlers._dependencyGraph; for (const nodeId in nodes) { for (const event in nodes[nodeId].actionHandler) { events.add(event); } } for (const event in Vencord.Webpack.Common.FluxDispatcher._subscriptions) { events.add(event); } return Array.from(events, e => JSON.stringify(e)).sort().join("|"); } copy(makeFluxEventList()) */ export type FluxEvents = "ACCESSIBILITY_COLORBLIND_TOGGLE" | "ACCESSIBILITY_DARK_SIDEBAR_TOGGLE" | "ACCESSIBILITY_DESATURATE_ROLES_TOGGLE" | "ACCESSIBILITY_FORCED_COLORS_MODAL_SEEN" | "ACCESSIBILITY_KEYBOARD_MODE_DISABLE" | "ACCESSIBILITY_KEYBOARD_MODE_ENABLE" | "ACCESSIBILITY_LOW_CONTRAST_TOGGLE" | "ACCESSIBILITY_RESET_TO_DEFAULT" | "ACCESSIBILITY_SET_ALWAYS_SHOW_LINK_DECORATIONS" | "ACCESSIBILITY_SET_CONTRAST" | "ACCESSIBILITY_SET_FONT_SIZE" | "ACCESSIBILITY_SET_MESSAGE_GROUP_SPACING" | "ACCESSIBILITY_SET_PREFERS_REDUCED_MOTION" | "ACCESSIBILITY_SET_ROLE_STYLE" | "ACCESSIBILITY_SET_SATURATION" | "ACCESSIBILITY_SET_SYNC_FORCED_COLORS" | "ACCESSIBILITY_SET_ZOOM" | "ACCESSIBILITY_SUBMIT_BUTTON_TOGGLE" | "ACCESSIBILITY_SYNC_PROFILE_THEME_WITH_USER_THEME_TOGGLE" | "ACCESSIBILITY_SYSTEM_COLOR_PREFERENCES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CONTRAST_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CROSSFADES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_REDUCED_MOTION_CHANGED" | "ACKNOWLEDGE_CHANNEL_SAFETY_WARNING_TOOLTIP" | "ACK_APPROVED_GUILD_JOIN_REQUEST" | "ACTIVE_AV_ERRORS_CHANGED" | "ACTIVE_BOGO_PROMOTION_FETCH" | "ACTIVE_BOGO_PROMOTION_FETCH_FAIL" | "ACTIVE_BOGO_PROMOTION_FETCH_SUCCESS" | "ACTIVE_CHANNELS_FETCH_FAILURE" | "ACTIVE_CHANNELS_FETCH_START" | "ACTIVE_CHANNELS_FETCH_SUCCESS" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_FAIL" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_SUCCESS" | "ACTIVITY_INVITE_EDUCATION_DISMISS" | "ACTIVITY_INVITE_MODAL_CLOSE" | "ACTIVITY_INVITE_MODAL_OPEN" | "ACTIVITY_INVITE_MODAL_QUERY" | "ACTIVITY_INVITE_MODAL_SEND" | "ACTIVITY_JOIN" | "ACTIVITY_JOIN_FAILED" | "ACTIVITY_JOIN_LOADING" | "ACTIVITY_LAUNCH_FAIL" | "ACTIVITY_LAYOUT_MODE_UPDATE" | "ACTIVITY_METADATA_UPDATE" | "ACTIVITY_PLAY" | "ACTIVITY_POPOUT_WINDOW_OPEN" | "ACTIVITY_SCREEN_ORIENTATION_UPDATE" | "ACTIVITY_START" | "ACTIVITY_SYNC" | "ACTIVITY_SYNC_STOP" | "ACTIVITY_UPDATE_FAIL" | "ACTIVITY_UPDATE_START" | "ACTIVITY_UPDATE_SUCCESS" | "ADD_STICKER_PREVIEW" | "ADMIN_ONBOARDING_GUIDE_HIDE" | "ADYEN_CASH_APP_PAY_SUBMIT_SUCCESS" | "ADYEN_CREATE_CASH_APP_PAY_COMPONENT_SUCCESS" | "ADYEN_CREATE_CLIENT_SUCCESS" | "ADYEN_TEARDOWN_CLIENT" | "AFK" | "AGE_GATE_FAILURE_MODAL_OPEN" | "AGE_GATE_LOGOUT_UNDERAGE_NEW_USER" | "AGE_GATE_MODAL_CLOSE" | "AGE_GATE_MODAL_OPEN" | "AGE_GATE_SUCCESS_MODAL_OPEN" | "APEX_EXPERIMENT_CLEAR_SERVER_ASSIGNMENTS" | "APEX_EXPERIMENT_OVERRIDE_CLEAR" | "APEX_EXPERIMENT_OVERRIDE_CREATE" | "APEX_EXPERIMENT_OVERRIDE_DELETE" | "APPLICATIONS_FETCH" | "APPLICATIONS_FETCH_FAIL" | "APPLICATIONS_FETCH_SUCCESS" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_FAIL" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_START" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "APPLICATION_ASSETS_FETCH" | "APPLICATION_ASSETS_FETCH_SUCCESS" | "APPLICATION_ASSETS_UPDATE" | "APPLICATION_BRANCHES_FETCH_FAIL" | "APPLICATION_BRANCHES_FETCH_SUCCESS" | "APPLICATION_BUILD_FETCH_START" | "APPLICATION_BUILD_FETCH_SUCCESS" | "APPLICATION_BUILD_NOT_FOUND" | "APPLICATION_BUILD_SIZE_FETCH_FAIL" | "APPLICATION_BUILD_SIZE_FETCH_START" | "APPLICATION_BUILD_SIZE_FETCH_SUCCESS" | "APPLICATION_COMMAND_AUTOCOMPLETE_REQUEST" | "APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE" | "APPLICATION_COMMAND_EXECUTE_BAD_VERSION" | "APPLICATION_COMMAND_INDEX_FETCH_FAILURE" | "APPLICATION_COMMAND_INDEX_FETCH_REQUEST" | "APPLICATION_COMMAND_INDEX_FETCH_SUCCESS" | "APPLICATION_COMMAND_SET_ACTIVE_COMMAND" | "APPLICATION_COMMAND_SET_PREFERRED_COMMAND" | "APPLICATION_COMMAND_UPDATE_CHANNEL_STATE" | "APPLICATION_COMMAND_UPDATE_OPTIONS" | "APPLICATION_COMMAND_USED" | "APPLICATION_DIRECTORY_FETCH_APPLICATION" | "APPLICATION_DIRECTORY_FETCH_APPLICATION_FAILURE" | "APPLICATION_DIRECTORY_FETCH_APPLICATION_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_CATEGORIES_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS_FAILURE" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_SEARCH" | "APPLICATION_DIRECTORY_FETCH_SEARCH_FAILURE" | "APPLICATION_DIRECTORY_FETCH_SEARCH_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS_FAILURE" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS_SUCCESS" | "APPLICATION_FETCH" | "APPLICATION_FETCH_FAIL" | "APPLICATION_FETCH_SUCCESS" | "APPLICATION_STORE_ACCEPT_EULA" | "APPLICATION_STORE_ACCEPT_STORE_TERMS" | "APPLICATION_STORE_CLEAR_DATA" | "APPLICATION_STORE_DIRECTORY_LAYOUT_FETCHING" | "APPLICATION_STORE_DIRECTORY_LAYOUT_FETCH_FAILED" | "APPLICATION_STORE_DIRECTORY_LAYOUT_FETCH_SUCCESS" | "APPLICATION_STORE_LOCATION_CHANGE" | "APPLICATION_STORE_MATURE_AGREE" | "APPLICATION_STORE_RESET_NAVIGATION" | "APPLICATION_SUBSCRIPTIONS_CHANNEL_NOTICE_DISMISSED" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "APPLICATION_UPDATE" | "APPLIED_BOOSTS_COOLDOWN_FETCH_SUCCESS" | "APPLIED_GUILD_BOOST_COUNT_RESET" | "APPLIED_GUILD_BOOST_COUNT_UPDATE" | "APP_DM_OPEN" | "APP_ICON_EDITOR_RESET" | "APP_ICON_TRACK_IMPRESSION" | "APP_ICON_UPDATED" | "APP_LAUNCHER_ADD_FAILED_APP_DM_LOAD" | "APP_LAUNCHER_DISMISS" | "APP_LAUNCHER_REMOVE_FAILED_APP_DM_LOAD" | "APP_LAUNCHER_SET_ACTIVE_COMMAND" | "APP_LAUNCHER_SHOW" | "APP_RECOMMENDATIONS_FETCH_RECOMMENDATIONS" | "APP_RECOMMENDATIONS_FETCH_RECOMMENDATIONS_FAILURE" | "APP_RECOMMENDATIONS_FETCH_RECOMMENDATIONS_SUCCESS" | "APP_STATE_UPDATE" | "APP_VIEW_SET_HOME_LINK" | "AUDIO_INPUT_DETECTED" | "AUDIO_RESET" | "AUDIO_SET_ACTIVE_INPUT_PROFILE" | "AUDIO_SET_ATTENUATION" | "AUDIO_SET_AUTOMATIC_GAIN_CONTROL" | "AUDIO_SET_BYPASS_SYSTEM_INPUT_PROCESSING" | "AUDIO_SET_DEBUG_LOGGING" | "AUDIO_SET_DISPLAY_SILENCE_WARNING" | "AUDIO_SET_ECHO_CANCELLATION" | "AUDIO_SET_INPUT_DEVICE" | "AUDIO_SET_INPUT_VOLUME" | "AUDIO_SET_KRISP_MODEL_OVERRIDE" | "AUDIO_SET_KRISP_SUPPRESSION_LEVEL" | "AUDIO_SET_LOCAL_PAN" | "AUDIO_SET_LOCAL_VIDEO_DISABLED" | "AUDIO_SET_LOCAL_VOLUME" | "AUDIO_SET_LOOPBACK" | "AUDIO_SET_MODE" | "AUDIO_SET_NOISE_CANCELLATION" | "AUDIO_SET_NOISE_SUPPRESSION" | "AUDIO_SET_OUTPUT_DEVICE" | "AUDIO_SET_OUTPUT_VOLUME" | "AUDIO_SET_QOS" | "AUDIO_SET_SELF_MUTE" | "AUDIO_SET_SIDECHAIN_COMPRESSION" | "AUDIO_SET_SIDECHAIN_COMPRESSION_STRENGTH" | "AUDIO_SET_SUBSYSTEM" | "AUDIO_SET_TEMPORARY_SELF_MUTE" | "AUDIO_TOGGLE_LOCAL_MUTE" | "AUDIO_TOGGLE_LOCAL_SOUNDBOARD_MUTE" | "AUDIO_TOGGLE_SELF_DEAF" | "AUDIO_TOGGLE_SELF_MUTE" | "AUDIO_VOLUME_CHANGE" | "AUDIT_LOG_FETCH_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_START" | "AUDIT_LOG_FETCH_NEXT_PAGE_SUCCESS" | "AUDIT_LOG_FETCH_START" | "AUDIT_LOG_FETCH_SUCCESS" | "AUDIT_LOG_FILTER_BY_ACTION" | "AUDIT_LOG_FILTER_BY_TARGET" | "AUDIT_LOG_FILTER_BY_USER" | "AUTHENTICATOR_CREATE" | "AUTHENTICATOR_DELETE" | "AUTHENTICATOR_UPDATE" | "AUTH_INVITE_UPDATE" | "AUTH_SESSION_CHANGE" | "AUTO_MODERATION_MENTION_RAID_DETECTION" | "AUTO_MODERATION_MENTION_RAID_NOTICE_DISMISS" | "AUTO_UPDATER_QUIT_AND_INSTALL" | "BACKGROUND_SYNC" | "BACKGROUND_SYNC_CHANNEL_MESSAGES" | "BASIC_GUILD_FETCH" | "BASIC_GUILD_FETCH_FAILURE" | "BASIC_GUILD_FETCH_SUCCESS" | "BILLING_CREATE_REFERRAL_SUCCESS" | "BILLING_IP_COUNTRY_CODE_FAILURE" | "BILLING_IP_COUNTRY_CODE_FETCH_START" | "BILLING_IP_LOCATION_FAILURE" | "BILLING_IP_LOCATION_FETCH_START" | "BILLING_LOCALIZED_PRICING_PROMO_FAILURE" | "BILLING_MOST_RECENT_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_NITRO_AFFINITY_FETCHED" | "BILLING_NITRO_AFFINITY_FETCH_SUCCEEDED" | "BILLING_PAYMENTS_FETCH_SUCCESS" | "BILLING_PAYMENT_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCES_FETCH_FAIL" | "BILLING_PAYMENT_SOURCES_FETCH_START" | "BILLING_PAYMENT_SOURCES_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCE_CREATE_FAIL" | "BILLING_PAYMENT_SOURCE_CREATE_START" | "BILLING_PAYMENT_SOURCE_CREATE_SUCCESS" | "BILLING_PAYMENT_SOURCE_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCE_REMOVE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_REMOVE_FAIL" | "BILLING_PAYMENT_SOURCE_REMOVE_START" | "BILLING_PAYMENT_SOURCE_REMOVE_SUCCESS" | "BILLING_PAYMENT_SOURCE_UPDATE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_UPDATE_FAIL" | "BILLING_PAYMENT_SOURCE_UPDATE_START" | "BILLING_PAYMENT_SOURCE_UPDATE_SUCCESS" | "BILLING_POPUP_BRIDGE_CALLBACK" | "BILLING_POPUP_BRIDGE_STATE_UPDATE" | "BILLING_PREVIOUS_PREMIUM_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_PURCHASE_TOKEN_AUTH_CLEAR_STATE" | "BILLING_REFERRALS_REMAINING_FETCH_FAIL" | "BILLING_REFERRALS_REMAINING_FETCH_START" | "BILLING_REFERRALS_REMAINING_FETCH_SUCCESS" | "BILLING_REFERRAL_RESOLVE_FAIL" | "BILLING_REFERRAL_RESOLVE_SUCCESS" | "BILLING_REFERRAL_TRIAL_OFFER_UPDATE" | "BILLING_SET_IP_COUNTRY_CODE" | "BILLING_SET_IP_LOCATION" | "BILLING_SET_LOCALIZED_PRICING_PROMO" | "BILLING_SUBSCRIPTION_CANCEL_FAIL" | "BILLING_SUBSCRIPTION_CANCEL_START" | "BILLING_SUBSCRIPTION_CANCEL_SUCCESS" | "BILLING_SUBSCRIPTION_FETCH_FAIL" | "BILLING_SUBSCRIPTION_FETCH_START" | "BILLING_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_SUBSCRIPTION_RESET" | "BILLING_SUBSCRIPTION_REWARD_ELIGIBILITY_FETCH_FAILURE" | "BILLING_SUBSCRIPTION_REWARD_ELIGIBILITY_FETCH_START" | "BILLING_SUBSCRIPTION_REWARD_ELIGIBILITY_FETCH_SUCCESS" | "BILLING_SUBSCRIPTION_UPDATE_FAIL" | "BILLING_SUBSCRIPTION_UPDATE_START" | "BILLING_SUBSCRIPTION_UPDATE_SUCCESS" | "BILLING_USER_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_OFFER_FETCH_FAIL" | "BILLING_USER_OFFER_FETCH_START" | "BILLING_USER_OFFER_FETCH_SUCCESS" | "BILLING_USER_TRIAL_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_TRIAL_OFFER_FETCH_SUCCESS" | "BOOSTED_GUILD_GRACE_PERIOD_NOTICE_DISMISS" | "BRAINTREE_CREATE_CLIENT_SUCCESS" | "BRAINTREE_CREATE_PAYPAL_CLIENT_SUCCESS" | "BRAINTREE_CREATE_VENMO_CLIENT_SUCCESS" | "BRAINTREE_TEARDOWN_PAYPAL_CLIENT" | "BRAINTREE_TEARDOWN_VENMO_CLIENT" | "BRAINTREE_TOKENIZE_PAYPAL_FAIL" | "BRAINTREE_TOKENIZE_PAYPAL_START" | "BRAINTREE_TOKENIZE_PAYPAL_SUCCESS" | "BRAINTREE_TOKENIZE_VENMO_FAIL" | "BRAINTREE_TOKENIZE_VENMO_START" | "BRAINTREE_TOKENIZE_VENMO_SUCCESS" | "BROWSER_HANDOFF_BEGIN" | "BROWSER_HANDOFF_FROM_APP" | "BROWSER_HANDOFF_SET_USER" | "BROWSER_HANDOFF_UNAVAILABLE" | "BUILD_OVERRIDE_RESOLVED" | "BULK_ACK" | "BULK_CLEAR_RECENTS" | "BURST_REACTION_ANIMATION_ADD" | "BURST_REACTION_EFFECT_CLEAR" | "BURST_REACTION_EFFECT_PLAY" | "BURST_REACTION_PICKER_ANIMATION_ADD" | "BURST_REACTION_PICKER_ANIMATION_CLEAR" | "CACHED_EMOJIS_LOADED" | "CACHED_STICKERS_LOADED" | "CACHE_LOADED" | "CACHE_LOADED_LAZY" | "CACHE_LOADED_LAZY_NO_CACHE" | "CALL_CHAT_TOASTS_SET_ENABLED" | "CALL_CONNECT" | "CALL_CONNECT_MULTIPLE" | "CALL_CREATE" | "CALL_DELETE" | "CALL_ENQUEUE_RING" | "CALL_UPDATE" | "CANDIDATE_GAMES_CHANGE" | "CATEGORY_COLLAPSE" | "CATEGORY_COLLAPSE_ALL" | "CATEGORY_EXPAND" | "CATEGORY_EXPAND_ALL" | "CERTIFIED_DEVICES_SET" | "CHANGE_LOG_FETCH_FAILED" | "CHANGE_LOG_FETCH_SUCCESS" | "CHANGE_LOG_LOCK" | "CHANGE_LOG_MARK_SEEN" | "CHANGE_LOG_RESOLVED" | "CHANGE_LOG_SET_CONFIG" | "CHANGE_LOG_SET_OVERRIDE" | "CHANGE_LOG_UNLOCK" | "CHANNEL_ACK" | "CHANNEL_CALL_POPOUT_WINDOW_OPEN" | "CHANNEL_COLLAPSE" | "CHANNEL_CREATE" | "CHANNEL_DELETE" | "CHANNEL_FOLLOWER_CREATED" | "CHANNEL_FOLLOWER_STATS_FETCH_FAILURE" | "CHANNEL_FOLLOWER_STATS_FETCH_SUCCESS" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_DISMISSED" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_HIDE_PERMANENTLY" | "CHANNEL_LOCAL_ACK" | "CHANNEL_MEMBER_COUNT_UPDATE" | "CHANNEL_MUTE_EXPIRED" | "CHANNEL_PERMISSIONS_DELETE_OVERWRITE_SUCCESS" | "CHANNEL_PERMISSIONS_PUT_OVERWRITE_SUCCESS" | "CHANNEL_PINS_ACK" | "CHANNEL_PINS_UPDATE" | "CHANNEL_PRELOAD" | "CHANNEL_RECIPIENT_ADD" | "CHANNEL_RECIPIENT_REMOVE" | "CHANNEL_RTC_ACTIVE_CHANNELS" | "CHANNEL_RTC_JUMP_TO_VOICE_CHANNEL_MESSAGE" | "CHANNEL_RTC_SELECT_PARTICIPANT" | "CHANNEL_RTC_UPDATE_CHAT_OPEN" | "CHANNEL_RTC_UPDATE_LAYOUT" | "CHANNEL_RTC_UPDATE_PARTCIPANTS_LIST_OPEN" | "CHANNEL_RTC_UPDATE_PARTICIPANTS_OPEN" | "CHANNEL_RTC_UPDATE_STAGE_STREAM_SIZE" | "CHANNEL_RTC_UPDATE_STAGE_VIDEO_LIMIT_BOOST_UPSELL_DISMISSED" | "CHANNEL_RTC_UPDATE_VOICE_PARTICIPANTS_HIDDEN" | "CHANNEL_SAFETY_WARNING_FEEDBACK" | "CHANNEL_SELECT" | "CHANNEL_SETTINGS_CLOSE" | "CHANNEL_SETTINGS_INIT" | "CHANNEL_SETTINGS_LOADED_INVITES" | "CHANNEL_SETTINGS_OVERWRITE_SELECT" | "CHANNEL_SETTINGS_PERMISSIONS_INIT" | "CHANNEL_SETTINGS_PERMISSIONS_SAVE_SUCCESS" | "CHANNEL_SETTINGS_PERMISSIONS_SELECT_PERMISSION" | "CHANNEL_SETTINGS_PERMISSIONS_SET_ADVANCED_MODE" | "CHANNEL_SETTINGS_PERMISSIONS_SUBMITTING" | "CHANNEL_SETTINGS_PERMISSIONS_UPDATE_PERMISSION" | "CHANNEL_SETTINGS_SET_SECTION" | "CHANNEL_SETTINGS_SUBMIT" | "CHANNEL_SETTINGS_SUBMIT_FAILURE" | "CHANNEL_SETTINGS_SUBMIT_SUCCESS" | "CHANNEL_SETTINGS_UPDATE" | "CHANNEL_STATUSES" | "CHANNEL_TOGGLE_MEMBERS_SECTION" | "CHANNEL_TOGGLE_SUMMARIES_SECTION" | "CHANNEL_UPDATES" | "CHECKING_FOR_UPDATES" | "CHECKOUT_RECOVERY_STATUS_FETCH" | "CHECKOUT_RECOVERY_STATUS_FETCH_FAILURE" | "CHECKOUT_RECOVERY_STATUS_FETCH_SUCCESS" | "CHECK_LAUNCHABLE_GAME" | "CLEAR_CACHES" | "CLEAR_CHANNEL_SAFETY_WARNINGS" | "CLEAR_CONSUMED_ENTITLEMENT" | "CLEAR_CONVERSATION_SUMMARIES" | "CLEAR_INTERACTION_MODAL_STATE" | "CLEAR_LAST_SESSION_VOICE_CHANNEL_ID" | "CLEAR_MENTIONS" | "CLEAR_MESSAGES" | "CLEAR_OLDEST_UNREAD_MESSAGE" | "CLEAR_PENDING_CHANNEL_AND_ROLE_UPDATES" | "CLEAR_REMOTE_DISCONNECT_VOICE_CHANNEL_ID" | "CLEAR_STICKER_PREVIEW" | "CLEAR_THEME_OVERRIDE" | "CLEAR_VIDEO_STREAM_READY_TIMEOUT" | "CLICKER_GAME_ADD_POINTS" | "CLICKER_GAME_PURCHASE_ITEM" | "CLICKER_GAME_PURCHASE_ITEM_UPGRADE" | "CLICKER_GAME_REDEEM_PRIZE_FAIL" | "CLICKER_GAME_REDEEM_PRIZE_START" | "CLICKER_GAME_REDEEM_PRIZE_SUCCESS" | "CLICKER_GAME_RESET" | "CLICKER_GAME_SET_MUTED" | "CLICKER_GAME_SET_VOLUME" | "CLICKER_GAME_UNLOCK_ACHIEVEMENT" | "CLICKER_GAME_UPDATE_ITEM_METADATA" | "CLIENT_THEMES_EDITOR_CLOSE" | "CLIPS_ALLOW_VOICE_RECORDING_UPDATE" | "CLIPS_CLASSIFY_HARDWARE" | "CLIPS_CLEAR_CLIPS_SESSION" | "CLIPS_CLEAR_NEW_CLIP_IDS" | "CLIPS_DELETE_CLIP" | "CLIPS_DISMISS_EDUCATION" | "CLIPS_INIT" | "CLIPS_INIT_FAILURE" | "CLIPS_LOAD_DIRECTORY_SUCCESS" | "CLIPS_RESTART" | "CLIPS_SAVE_ANIMATION_END" | "CLIPS_SAVE_CLIP" | "CLIPS_SAVE_CLIP_ERROR" | "CLIPS_SAVE_CLIP_PLACEHOLDER" | "CLIPS_SAVE_CLIP_PLACEHOLDER_ERROR" | "CLIPS_SAVE_CLIP_START" | "CLIPS_SETTINGS_UPDATE" | "CLIPS_SHOW_CALL_WARNING" | "CLIPS_UPDATE_METADATA" | "CLOSE_AGE_VERIFICATION_MODAL" | "CLOSE_SUSPENDED_USER" | "COLLECTIBLES_CATEGORIES_FETCH" | "COLLECTIBLES_CATEGORIES_FETCH_FAILURE" | "COLLECTIBLES_CATEGORIES_FETCH_SUCCESS" | "COLLECTIBLES_CLAIM" | "COLLECTIBLES_CLAIM_FAILURE" | "COLLECTIBLES_CLAIM_SUCCESS" | "COLLECTIBLES_MARKETING_FETCH" | "COLLECTIBLES_MARKETING_FETCH_SUCCESS" | "COLLECTIBLES_PRODUCT_DETAILS_OPEN" | "COLLECTIBLES_PRODUCT_FETCH" | "COLLECTIBLES_PRODUCT_FETCH_FAILURE" | "COLLECTIBLES_PRODUCT_FETCH_SUCCESS" | "COLLECTIBLES_PURCHASES_FETCH" | "COLLECTIBLES_PURCHASES_FETCH_FAILURE" | "COLLECTIBLES_PURCHASES_FETCH_SUCCESS" | "COLLECTIBLES_SET_SHOP_HOME_CONFIG_OVERRIDE" | "COLLECTIBLES_SHOP_CLOSE" | "COLLECTIBLES_SHOP_HOME_FETCH" | "COLLECTIBLES_SHOP_HOME_FETCH_FAILURE" | "COLLECTIBLES_SHOP_HOME_FETCH_SUCCESS" | "COLLECTIBLES_SHOP_OPEN" | "COLLECTIBLES_SKIP_NUM_CATEGORIES" | "COMMANDS_MIGRATION_NOTICE_DISMISSED" | "COMMANDS_MIGRATION_OVERVIEW_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_TOGGLE_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_UPDATE_SUCCESS" | "COMPLETE_NEW_MEMBER_ACTION" | "CONNECTED_DEVICE_DONT_SWITCH" | "CONNECTED_DEVICE_IGNORE" | "CONNECTED_DEVICE_NEVER_SHOW_MODAL" | "CONNECTED_DEVICE_SWITCH" | "CONNECTIONS_GRID_MODAL_HIDE" | "CONNECTIONS_GRID_MODAL_SHOW" | "CONNECTION_CLOSED" | "CONNECTION_OPEN" | "CONNECTION_OPEN_STATE_UPDATE" | "CONNECTION_OPEN_SUPPLEMENTAL" | "CONNECTION_RESUMED" | "CONSOLE_COMMAND_UPDATE" | "CONSUMABLES_CLEAR_ERROR" | "CONSUMABLES_ENTITLEMENT_FETCH_COMPLETED" | "CONSUMABLES_ENTITLEMENT_FETCH_FAILED" | "CONSUMABLES_ENTITLEMENT_FETCH_STARTED" | "CONSUMABLES_PRICE_FETCH_FAILED" | "CONSUMABLES_PRICE_FETCH_STARTED" | "CONSUMABLES_PRICE_FETCH_SUCCEEDED" | "CONTENT_INVENTORY_CLEAR_DELETE_HISTORY_ERROR" | "CONTENT_INVENTORY_CLEAR_FEED" | "CONTENT_INVENTORY_DEBUG_CLEAR_IMPRESSIONS" | "CONTENT_INVENTORY_DEBUG_LOG_IMPRESSIONS" | "CONTENT_INVENTORY_DEBUG_TOGGLE_FAST_IMPRESSION_CAPPING" | "CONTENT_INVENTORY_DEBUG_TOGGLE_IMPRESSION_CAPPING" | "CONTENT_INVENTORY_DELETE_OUTBOX_ENTRY_FAILURE" | "CONTENT_INVENTORY_DELETE_OUTBOX_ENTRY_START" | "CONTENT_INVENTORY_DELETE_OUTBOX_ENTRY_SUCCESS" | "CONTENT_INVENTORY_FETCH_OUTBOX_FAILURE" | "CONTENT_INVENTORY_FETCH_OUTBOX_START" | "CONTENT_INVENTORY_FETCH_OUTBOX_SUCCESS" | "CONTENT_INVENTORY_FORCE_SHOW_GAME_SHARING" | "CONTENT_INVENTORY_INBOX_STALE" | "CONTENT_INVENTORY_MANUAL_REFRESH" | "CONTENT_INVENTORY_SET_FEED" | "CONTENT_INVENTORY_SET_FEED_STATE" | "CONTENT_INVENTORY_SET_FILTERS" | "CONTENT_INVENTORY_TOGGLE_FEED_HIDDEN" | "CONTENT_INVENTORY_TRACK_ITEM_IMPRESSIONS" | "CONTEXT_MENU_CLOSE" | "CONTEXT_MENU_OPEN" | "CONVERSATION_SUMMARY_UPDATE" | "CREATE_PENDING_REPLY" | "CREATE_PENDING_SCHEDULED_MESSAGE" | "CREATE_REFERRALS_SUCCESS" | "CREATE_SHALLOW_PENDING_REPLY" | "CREATOR_MONETIZATION_NAG_ACTIVATE_ELIGIBLITY_FETCH_SUCCESS" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH_FAILURE" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH_SUCCESS" | "CURRENT_BUILD_OVERRIDE_RESOLVED" | "CURRENT_USER_UPDATE" | "CUSTOM_ACTIVITY_LINK_FETCH_SUCCESS" | "DCF_DAILY_CAP_OVERRIDE" | "DCF_EVENT_LOGGED" | "DCF_HANDLE_DC_DISMISSED" | "DCF_HANDLE_DC_SHOWN" | "DCF_NEW_USER_MIN_AGE_REQUIRED_OVERRIDE" | "DCF_OVERRIDE_LAST_DC_DISMISSED" | "DCF_RESET" | "DECAY_READ_STATES" | "DELETED_ENTITY_IDS" | "DELETE_PENDING_REPLY" | "DELETE_PENDING_SCHEDULED_MESSAGE" | "DELETE_SUMMARY" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH_FAILURE" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH_SUCCESS" | "DETECTED_OFF_PLATFORM_PREMIUM_PERKS_DISMISS" | "DEVELOPER_ACTIVITY_SHELF_FETCH_FAIL" | "DEVELOPER_ACTIVITY_SHELF_FETCH_START" | "DEVELOPER_ACTIVITY_SHELF_FETCH_SUCCESS" | "DEVELOPER_ACTIVITY_SHELF_MARK_ACTIVITY_USED" | "DEVELOPER_ACTIVITY_SHELF_SET_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_TOGGLE_USE_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_UPDATE_FILTER" | "DEVELOPER_OPTIONS_UPDATE_SETTINGS" | "DEVELOPER_TEST_MODE_AUTHORIZATION_FAIL" | "DEVELOPER_TEST_MODE_AUTHORIZATION_START" | "DEVELOPER_TEST_MODE_AUTHORIZATION_SUCCESS" | "DEVELOPER_TEST_MODE_RESET" | "DEVELOPER_TEST_MODE_RESET_ERROR" | "DEV_TOOLS_DESIGN_TOGGLE_SET" | "DEV_TOOLS_DESIGN_TOGGLE_WEB_SET" | "DEV_TOOLS_DEV_SETTING_SET" | "DEV_TOOLS_FRIENDS_LIST_GIFT_INTENTS_SHOWN_RESET" | "DEV_TOOLS_FRIENDS_TAB_BADGE_COOLDOWN_RESET" | "DEV_TOOLS_GIFT_MESSAGE_COOLDOWN_RESET" | "DEV_TOOLS_SETTINGS_UPDATE" | "DEV_TOOLS_SET_FRIEND_ANNIVERSARY_COUNT" | "DISABLE_AUTOMATIC_ACK" | "DISCOVER_CHECKLIST_FETCH_FAILURE" | "DISCOVER_CHECKLIST_FETCH_START" | "DISCOVER_CHECKLIST_FETCH_SUCCESS" | "DISMISS_CHANNEL_SAFETY_WARNINGS" | "DISMISS_FAVORITE_SUGGESTION" | "DISMISS_MEDIA_POST_SHARE_PROMPT" | "DISPATCH_APPLICATION_ADD_TO_INSTALLATIONS" | "DISPATCH_APPLICATION_CANCEL" | "DISPATCH_APPLICATION_ERROR" | "DISPATCH_APPLICATION_INSTALL" | "DISPATCH_APPLICATION_INSTALL_SCRIPTS_PROGRESS_UPDATE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_COMPLETE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_START" | "DISPATCH_APPLICATION_MOVE_UP" | "DISPATCH_APPLICATION_REMOVE_FINISHED" | "DISPATCH_APPLICATION_REPAIR" | "DISPATCH_APPLICATION_STATE_UPDATE" | "DISPATCH_APPLICATION_UNINSTALL" | "DISPATCH_APPLICATION_UPDATE" | "DISPLAYED_INVITE_SHOW" | "DOMAIN_MIGRATION_FAILURE" | "DOMAIN_MIGRATION_SKIP" | "DOMAIN_MIGRATION_START" | "DRAFT_CHANGE" | "DRAFT_CLEAR" | "DRAFT_SAVE" | "EMAIL_SETTINGS_FETCH_SUCCESS" | "EMAIL_SETTINGS_UPDATE" | "EMAIL_SETTINGS_UPDATE_SUCCESS" | "EMBEDDED_ACTIVITY_CLOSE" | "EMBEDDED_ACTIVITY_DEFERRED_OPEN" | "EMBEDDED_ACTIVITY_FETCH_SHELF" | "EMBEDDED_ACTIVITY_FETCH_SHELF_FAIL" | "EMBEDDED_ACTIVITY_FETCH_SHELF_SUCCESS" | "EMBEDDED_ACTIVITY_LAUNCH_FAIL" | "EMBEDDED_ACTIVITY_LAUNCH_START" | "EMBEDDED_ACTIVITY_LAUNCH_SUCCESS" | "EMBEDDED_ACTIVITY_OPEN" | "EMBEDDED_ACTIVITY_SET_CONFIG" | "EMBEDDED_ACTIVITY_SET_FOCUSED_LAYOUT" | "EMBEDDED_ACTIVITY_SET_ORIENTATION_LOCK_STATE" | "EMBEDDED_ACTIVITY_SET_PANEL_MODE" | "EMBEDDED_ACTIVITY_UPDATE" | "EMBEDDED_ACTIVITY_UPDATE_POPOUT_WINDOW_LAYOUT" | "EMBEDDED_ACTIVITY_UPDATE_V2" | "EMOJI_AUTOSUGGESTION_UPDATE" | "EMOJI_DELETE" | "EMOJI_FETCH_FAILURE" | "EMOJI_FETCH_SUCCESS" | "EMOJI_INTERACTION_INITIATED" | "EMOJI_TRACK_USAGE" | "EMOJI_UPLOAD_START" | "EMOJI_UPLOAD_STOP" | "ENABLE_AUTOMATIC_ACK" | "ENTITLEMENTS_FETCH_FOR_USER_FAIL" | "ENTITLEMENTS_FETCH_FOR_USER_START" | "ENTITLEMENTS_FETCH_FOR_USER_SUCCESS" | "ENTITLEMENTS_GIFTABLE_FETCH_SUCCESS" | "ENTITLEMENT_CREATE" | "ENTITLEMENT_DELETE" | "ENTITLEMENT_FETCH_APPLICATION_FAIL" | "ENTITLEMENT_FETCH_APPLICATION_START" | "ENTITLEMENT_FETCH_APPLICATION_SUCCESS" | "ENTITLEMENT_UPDATE" | "EVENT_DIRECTORY_FETCH_FAILURE" | "EVENT_DIRECTORY_FETCH_START" | "EVENT_DIRECTORY_FETCH_SUCCESS" | "EXPERIMENTS_FETCH" | "EXPERIMENTS_FETCH_FAILURE" | "EXPERIMENTS_FETCH_SUCCESS" | "EXPERIMENT_OVERRIDE_BUCKET" | "FAMILY_CENTER_FETCH_START" | "FAMILY_CENTER_HANDLE_TAB_SELECT" | "FAMILY_CENTER_INITIAL_LOAD" | "FAMILY_CENTER_LINKED_USERS_FETCH_SUCCESS" | "FAMILY_CENTER_LINK_CODE_FETCH_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_REMOVE_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_UPDATE_SUCCESS" | "FAMILY_CENTER_TEEN_ACTIVITY_FETCH_SUCCESS" | "FAMILY_CENTER_TEEN_ACTIVITY_MORE_FETCH_SUCCESS" | "FEEDBACK_OVERRIDE_CLEAR" | "FEEDBACK_OVERRIDE_SET" | "FETCH_AUTH_SESSIONS_SUCCESS" | "FETCH_CHAT_WALLPAPERS_FAILURE" | "FETCH_CHAT_WALLPAPERS_START" | "FETCH_CHAT_WALLPAPERS_SUCCESS" | "FETCH_GUILD_EVENT" | "FETCH_GUILD_EVENTS_FOR_GUILD" | "FETCH_GUILD_MEMBER_SUPPLEMENTAL_SUCCESS" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS_FAILURE" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS_SUCCESS" | "FETCH_SCHEDULED_MESSAGES" | "FETCH_SCHEDULED_MESSAGES_FAILURE" | "FETCH_SCHEDULED_MESSAGES_SUCCESS" | "FINGERPRINT" | "FORCE_INVISIBLE" | "FORGOT_PASSWORD_REQUEST" | "FORGOT_PASSWORD_SENT" | "FORUM_SEARCH_CLEAR" | "FORUM_SEARCH_FAILURE" | "FORUM_SEARCH_QUERY_UPDATED" | "FORUM_SEARCH_START" | "FORUM_SEARCH_SUCCESS" | "FORUM_UNREADS" | "FRIENDS_LIST_GIFT_INTENTS_SHOWN" | "FRIENDS_SET_INITIAL_SECTION" | "FRIENDS_SET_SECTION" | "FRIENDS_TAB_BADGE_DISMISS" | "FRIEND_INVITES_FETCH_REQUEST" | "FRIEND_INVITES_FETCH_RESPONSE" | "FRIEND_INVITE_CREATE_FAILURE" | "FRIEND_INVITE_CREATE_REQUEST" | "FRIEND_INVITE_CREATE_SUCCESS" | "FRIEND_INVITE_REVOKE_REQUEST" | "FRIEND_INVITE_REVOKE_SUCCESS" | "FRIEND_SUGGESTION_CREATE" | "FRIEND_SUGGESTION_DELETE" | "GAMES_DATABASE_FETCH" | "GAMES_DATABASE_FETCH_FAIL" | "GAMES_DATABASE_UPDATE" | "GAME_CLOUD_SYNC_COMPLETE" | "GAME_CLOUD_SYNC_CONFLICT" | "GAME_CLOUD_SYNC_ERROR" | "GAME_CLOUD_SYNC_START" | "GAME_CLOUD_SYNC_UPDATE" | "GAME_CONSOLE_FETCH_DEVICES_FAIL" | "GAME_CONSOLE_FETCH_DEVICES_START" | "GAME_CONSOLE_FETCH_DEVICES_SUCCESS" | "GAME_CONSOLE_SELECT_DEVICE" | "GAME_DETECTION_DEBUGGING_START" | "GAME_DETECTION_DEBUGGING_STOP" | "GAME_DETECTION_DEBUGGING_TICK" | "GAME_DETECTION_WATCH_CANDIDATE_GAMES_START" | "GAME_ICON_UPDATE" | "GAME_INVITE_CLEAR_UNSEEN" | "GAME_INVITE_CREATE" | "GAME_INVITE_DELETE" | "GAME_INVITE_DELETE_MANY" | "GAME_INVITE_UPDATE_STATUS" | "GAME_LAUNCHABLE_UPDATE" | "GAME_LAUNCH_FAIL" | "GAME_LAUNCH_START" | "GAME_LAUNCH_SUCCESS" | "GAME_PROFILE_OPEN" | "GAME_RELATIONSHIP_ADD" | "GAME_RELATIONSHIP_REMOVE" | "GENERIC_PUSH_NOTIFICATION_SENT" | "GIFT_CODES_FETCH" | "GIFT_CODES_FETCH_FAILURE" | "GIFT_CODES_FETCH_SUCCESS" | "GIFT_CODE_CREATE" | "GIFT_CODE_CREATE_SUCCESS" | "GIFT_CODE_REDEEM" | "GIFT_CODE_REDEEM_FAILURE" | "GIFT_CODE_REDEEM_SUCCESS" | "GIFT_CODE_RESOLVE" | "GIFT_CODE_RESOLVE_FAILURE" | "GIFT_CODE_RESOLVE_SUCCESS" | "GIFT_CODE_REVOKE_SUCCESS" | "GIFT_CODE_UPDATE" | "GIFT_INTENT_FLOW_PURCHASED_GIFT" | "GIF_PICKER_INITIALIZE" | "GIF_PICKER_QUERY" | "GIF_PICKER_QUERY_FAILURE" | "GIF_PICKER_QUERY_SUCCESS" | "GIF_PICKER_SUGGESTIONS_SUCCESS" | "GIF_PICKER_TRENDING_FETCH_SUCCESS" | "GIF_PICKER_TRENDING_SEARCH_TERMS_SUCCESS" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_CLEAR" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_COUNT_FAILURE" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_COUNT_START" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_COUNT_SUCCESS" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_FAILURE" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_LAYOUT_RESET" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_START" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_SUCCESS" | "GUILD_ACK" | "GUILD_ANALYTICS_ENGAGEMENT_OVERVIEW_FETCH_FAILURE" | "GUILD_ANALYTICS_ENGAGEMENT_OVERVIEW_FETCH_SUCCESS" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_OVERVIEW_FETCH_FAILURE" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_OVERVIEW_FETCH_SUCCESS" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_RETENTION_FETCH_FAILURE" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_RETENTION_FETCH_SUCCESS" | "GUILD_APPLICATIONS_FETCH_SUCCESS" | "GUILD_APPLICATION_COMMAND_INDEX_UPDATE" | "GUILD_APPLIED_BOOSTS_FETCH_SUCCESS" | "GUILD_APPLIED_BOOSTS_UPDATE" | "GUILD_APPLY_BOOST_FAIL" | "GUILD_APPLY_BOOST_START" | "GUILD_APPLY_BOOST_SUCCESS" | "GUILD_BAN_ADD" | "GUILD_BAN_REMOVE" | "GUILD_BOOST_SLOTS_FETCH" | "GUILD_BOOST_SLOTS_FETCH_SUCCESS" | "GUILD_BOOST_SLOT_CREATE" | "GUILD_BOOST_SLOT_UPDATE" | "GUILD_BOOST_SLOT_UPDATE_SUCCESS" | "GUILD_CREATE" | "GUILD_DELETE" | "GUILD_DIRECTORY_ADMIN_ENTRIES_FETCH_SUCCESS" | "GUILD_DIRECTORY_CACHED_SEARCH" | "GUILD_DIRECTORY_CATEGORY_SELECT" | "GUILD_DIRECTORY_COUNTS_FETCH_SUCCESS" | "GUILD_DIRECTORY_ENTRY_CREATE" | "GUILD_DIRECTORY_ENTRY_DELETE" | "GUILD_DIRECTORY_ENTRY_UPDATE" | "GUILD_DIRECTORY_FETCH_FAILURE" | "GUILD_DIRECTORY_FETCH_START" | "GUILD_DIRECTORY_FETCH_SUCCESS" | "GUILD_DIRECTORY_SEARCH_CLEAR" | "GUILD_DIRECTORY_SEARCH_FAILURE" | "GUILD_DIRECTORY_SEARCH_START" | "GUILD_DIRECTORY_SEARCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_ADD" | "GUILD_DISCOVERY_CATEGORY_DELETE" | "GUILD_DISCOVERY_CATEGORY_FETCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_UPDATE_FAIL" | "GUILD_DISCOVERY_METADATA_FETCH_FAIL" | "GUILD_DISCOVERY_SLUG_FETCH_FAIL" | "GUILD_DISCOVERY_SLUG_FETCH_SUCCESS" | "GUILD_EMOJIS_UPDATE" | "GUILD_FEATURE_ACK" | "GUILD_FOLDER_COLLAPSE" | "GUILD_FOLDER_CREATE_LOCAL" | "GUILD_FOLDER_DELETE_LOCAL" | "GUILD_FOLDER_EDIT_LOCAL" | "GUILD_GEO_RESTRICTED" | "GUILD_HOME_SETTINGS_FETCH_FAIL" | "GUILD_HOME_SETTINGS_FETCH_START" | "GUILD_HOME_SETTINGS_FETCH_SUCCESS" | "GUILD_HOME_SETTINGS_TOGGLE_ENABLED" | "GUILD_HOME_SETTINGS_UPDATE_FAIL" | "GUILD_HOME_SETTINGS_UPDATE_START" | "GUILD_HOME_SETTINGS_UPDATE_SUCCESS" | "GUILD_IDENTITY_SETTINGS_CLEAR_ERRORS" | "GUILD_IDENTITY_SETTINGS_INIT" | "GUILD_IDENTITY_SETTINGS_RESET_ALL_PENDING" | "GUILD_IDENTITY_SETTINGS_RESET_AND_CLOSE_FORM" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_MEMBER_CHANGES" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "GUILD_IDENTITY_SETTINGS_SET_GUILD" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR_DECORATION" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BANNER" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BIO" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_NICKNAME" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PROFILE_EFFECT_ID" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PRONOUNS" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_THEME_COLORS" | "GUILD_IDENTITY_SETTINGS_SUBMIT" | "GUILD_IDENTITY_SETTINGS_SUBMIT_FAILURE" | "GUILD_IDENTITY_SETTINGS_SUBMIT_SUCCESS" | "GUILD_INTEGRATIONS_UPDATE" | "GUILD_JOIN" | "GUILD_JOIN_REQUESTS_BULK_ACTION" | "GUILD_JOIN_REQUESTS_FETCH_FAILURE" | "GUILD_JOIN_REQUESTS_FETCH_START" | "GUILD_JOIN_REQUESTS_FETCH_SUCCESS" | "GUILD_JOIN_REQUESTS_SET_APPLICATION_TAB" | "GUILD_JOIN_REQUESTS_SET_SELECTED" | "GUILD_JOIN_REQUESTS_SET_SORT_ORDER" | "GUILD_JOIN_REQUEST_BY_ID_FETCH_SUCCESS" | "GUILD_JOIN_REQUEST_CREATE" | "GUILD_JOIN_REQUEST_DELETE" | "GUILD_JOIN_REQUEST_UPDATE" | "GUILD_LOCAL_RING_START" | "GUILD_MEMBERS_CHUNK_BATCH" | "GUILD_MEMBERS_REQUEST" | "GUILD_MEMBER_ADD" | "GUILD_MEMBER_LIST_UPDATE" | "GUILD_MEMBER_PROFILE_UPDATE" | "GUILD_MEMBER_REMOVE" | "GUILD_MEMBER_REMOVE_LOCAL" | "GUILD_MEMBER_UPDATE" | "GUILD_MEMBER_UPDATE_LOCAL" | "GUILD_MOVE_BY_ID" | "GUILD_MUTE_EXPIRED" | "GUILD_NEW_MEMBER_ACTIONS_DELETE_SUCCESS" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_FAIL" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_START" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_SUCCESS" | "GUILD_NEW_MEMBER_ACTION_UPDATE_SUCCESS" | "GUILD_NSFW_AGREE" | "GUILD_ONBOARDING_COMPLETE" | "GUILD_ONBOARDING_PROMPTS_FETCH_FAILURE" | "GUILD_ONBOARDING_PROMPTS_FETCH_START" | "GUILD_ONBOARDING_PROMPTS_FETCH_SUCCESS" | "GUILD_ONBOARDING_PROMPTS_LOCAL_UPDATE" | "GUILD_ONBOARDING_SELECT_OPTION" | "GUILD_ONBOARDING_SET_STEP" | "GUILD_ONBOARDING_START" | "GUILD_ONBOARDING_UPDATE_RESPONSES_SUCCESS" | "GUILD_POWERUPS_ACK_NOTIFICATION" | "GUILD_POWERUPS_RESET_NOTIFICATIONS" | "GUILD_POWERUP_CATALOG_FETCH_SUCCESS" | "GUILD_POWERUP_ENTITLEMENTS_CREATE" | "GUILD_POWERUP_ENTITLEMENTS_DELETE" | "GUILD_PRODUCTS_FETCH" | "GUILD_PRODUCTS_FETCH_FAILURE" | "GUILD_PRODUCTS_FETCH_SUCCESS" | "GUILD_PRODUCT_CREATE" | "GUILD_PRODUCT_DELETE" | "GUILD_PRODUCT_FETCH" | "GUILD_PRODUCT_FETCH_FAILURE" | "GUILD_PRODUCT_FETCH_SUCCESS" | "GUILD_PRODUCT_UPDATE" | "GUILD_PROFILE_FETCH" | "GUILD_PROFILE_FETCH_FAILURE" | "GUILD_PROFILE_FETCH_SUCCESS" | "GUILD_PROFILE_UPDATE" | "GUILD_PROFILE_UPDATE_FAILURE" | "GUILD_PROFILE_UPDATE_SUCCESS" | "GUILD_PROFILE_UPDATE_VISIBILITY" | "GUILD_PROFILE_UPDATE_VISIBILITY_FAILURE" | "GUILD_PROFILE_UPDATE_VISIBILITY_SUCCESS" | "GUILD_PROGRESS_COMPLETED_SEEN" | "GUILD_PROGRESS_DISMISS" | "GUILD_PROGRESS_INITIALIZE" | "GUILD_PROMPT_VIEWED" | "GUILD_RESOURCE_CHANNEL_UPDATE_SUCCESS" | "GUILD_RING_START" | "GUILD_RING_STOP" | "GUILD_ROLE_CONNECTIONS_CONFIGURATIONS_FETCH_SUCCESS" | "GUILD_ROLE_CONNECTIONS_MODAL_SHOW" | "GUILD_ROLE_CONNECTION_ELIGIBILITY_FETCH_SUCCESS" | "GUILD_ROLE_CREATE" | "GUILD_ROLE_DELETE" | "GUILD_ROLE_MEMBER_ADD" | "GUILD_ROLE_MEMBER_BULK_ADD" | "GUILD_ROLE_MEMBER_COUNT_FETCH_SUCCESS" | "GUILD_ROLE_MEMBER_COUNT_UPDATE" | "GUILD_ROLE_MEMBER_REMOVE" | "GUILD_ROLE_SUBSCRIPTIONS_CREATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_ABORTED" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_TEMPLATES" | "GUILD_ROLE_SUBSCRIPTIONS_STASH_TEMPLATE_CHANNELS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTIONS_SETTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTION_TRIAL" | "GUILD_ROLE_UPDATE" | "GUILD_SCHEDULED_EVENT_CREATE" | "GUILD_SCHEDULED_EVENT_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTIONS_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_CREATE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_UPDATE" | "GUILD_SCHEDULED_EVENT_RSVPS_FETCH_SUCESS" | "GUILD_SCHEDULED_EVENT_UPDATE" | "GUILD_SCHEDULED_EVENT_USERS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_ADD" | "GUILD_SCHEDULED_EVENT_USER_COUNTS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_REMOVE" | "GUILD_SEARCH_RECENT_MEMBERS" | "GUILD_SETTINGS_CANCEL_CHANGES" | "GUILD_SETTINGS_CLOSE" | "GUILD_SETTINGS_DEFAULT_CHANNELS_RESET" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SAVE_FAILED" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SAVE_SUCCESS" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SUBMIT" | "GUILD_SETTINGS_DEFAULT_CHANNELS_TOGGLE" | "GUILD_SETTINGS_INIT" | "GUILD_SETTINGS_JOIN_RULES_APPLY_SET_PENDING_FORM_FIELDS" | "GUILD_SETTINGS_JOIN_RULES_INVITE_SET_PENDING_RULES" | "GUILD_SETTINGS_JOIN_RULES_SET_CONTENT_LEVEL" | "GUILD_SETTINGS_JOIN_RULES_SET_SELECTED_TYPE" | "GUILD_SETTINGS_LOADED_BANS" | "GUILD_SETTINGS_LOADED_BANS_BATCH" | "GUILD_SETTINGS_LOADED_INTEGRATIONS" | "GUILD_SETTINGS_LOADED_INVITES" | "GUILD_SETTINGS_ONBOARDING_ADD_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_ADD_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_DELETE_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_DELETE_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_DISMISS_RESOURCE_CHANNEL_SUGGESTION" | "GUILD_SETTINGS_ONBOARDING_EDUCATION_UPSELL_DISMISSED" | "GUILD_SETTINGS_ONBOARDING_HOME_SETTINGS_RESET" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_EDIT" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_ERRORS" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_RESET" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SAVE_FAILED" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SAVE_SUCCESS" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SUBMIT" | "GUILD_SETTINGS_ONBOARDING_REORDER_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_REORDER_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_SET_MODE" | "GUILD_SETTINGS_ONBOARDING_STEP" | "GUILD_SETTINGS_ONBOARDING_UPDATE_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_UPDATE_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_UPDATE_WELCOME_MESSAGE" | "GUILD_SETTINGS_OPEN" | "GUILD_SETTINGS_PROFILE_UPDATE" | "GUILD_SETTINGS_ROLES_CLEAR_PERMISSIONS" | "GUILD_SETTINGS_ROLES_INIT" | "GUILD_SETTINGS_ROLES_ROLE_STYLE_UPDATE" | "GUILD_SETTINGS_ROLES_SAVE_FAIL" | "GUILD_SETTINGS_ROLES_SAVE_SUCCESS" | "GUILD_SETTINGS_ROLES_SORT_UPDATE" | "GUILD_SETTINGS_ROLES_SUBMITTING" | "GUILD_SETTINGS_ROLES_UPDATE_COLOR" | "GUILD_SETTINGS_ROLES_UPDATE_COLORS" | "GUILD_SETTINGS_ROLES_UPDATE_DESCRIPTION" | "GUILD_SETTINGS_ROLES_UPDATE_NAME" | "GUILD_SETTINGS_ROLES_UPDATE_PERMISSIONS" | "GUILD_SETTINGS_ROLES_UPDATE_PERMISSION_SET" | "GUILD_SETTINGS_ROLES_UPDATE_ROLE_CONNECTION_CONFIGURATIONS" | "GUILD_SETTINGS_ROLES_UPDATE_ROLE_ICON" | "GUILD_SETTINGS_ROLES_UPDATE_SETTINGS" | "GUILD_SETTINGS_ROLE_SELECT" | "GUILD_SETTINGS_SAFETY_PAGE" | "GUILD_SETTINGS_SAFETY_SET_SUBSECTION" | "GUILD_SETTINGS_SAVE_ROUTE_STACK" | "GUILD_SETTINGS_SET_MFA_SUCCESS" | "GUILD_SETTINGS_SET_SEARCH_QUERY" | "GUILD_SETTINGS_SET_SECTION" | "GUILD_SETTINGS_SET_VANITY_URL" | "GUILD_SETTINGS_SET_WIDGET" | "GUILD_SETTINGS_SUBMIT" | "GUILD_SETTINGS_SUBMIT_FAILURE" | "GUILD_SETTINGS_SUBMIT_SUCCESS" | "GUILD_SETTINGS_UPDATE" | "GUILD_SETTINGS_VANITY_URL_ERROR" | "GUILD_SETTINGS_VANITY_URL_RESET" | "GUILD_SETTINGS_VANITY_URL_SET" | "GUILD_SETTINGS_WIDGET_UPDATE" | "GUILD_SOUNDBOARD_FETCH" | "GUILD_SOUNDBOARD_SOUNDS_UPDATE" | "GUILD_SOUNDBOARD_SOUND_CREATE" | "GUILD_SOUNDBOARD_SOUND_DELETE" | "GUILD_SOUNDBOARD_SOUND_PLAY_END" | "GUILD_SOUNDBOARD_SOUND_PLAY_LOCALLY" | "GUILD_SOUNDBOARD_SOUND_PLAY_START" | "GUILD_SOUNDBOARD_SOUND_UPDATE" | "GUILD_STICKERS_CREATE_SUCCESS" | "GUILD_STICKERS_FETCH_SUCCESS" | "GUILD_STICKERS_UPDATE" | "GUILD_STOP_LURKING" | "GUILD_STOP_LURKING_FAILURE" | "GUILD_SUBSCRIPTIONS" | "GUILD_SUBSCRIPTIONS_ADD_MEMBER_UPDATES" | "GUILD_SUBSCRIPTIONS_CHANNEL" | "GUILD_SUBSCRIPTIONS_FLUSH" | "GUILD_SUBSCRIPTIONS_MEMBERS_ADD" | "GUILD_SUBSCRIPTIONS_MEMBERS_REMOVE" | "GUILD_SUBSCRIPTIONS_REMOVE_MEMBER_UPDATES" | "GUILD_TAG_CHANGED_COACHMARK_SEEN" | "GUILD_TEMPLATE_ACCEPT" | "GUILD_TEMPLATE_ACCEPT_FAILURE" | "GUILD_TEMPLATE_ACCEPT_SUCCESS" | "GUILD_TEMPLATE_CREATE_SUCCESS" | "GUILD_TEMPLATE_DELETE_SUCCESS" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_HIDE" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_REFRESH" | "GUILD_TEMPLATE_LOAD_FOR_GUILD_SUCCESS" | "GUILD_TEMPLATE_MODAL_HIDE" | "GUILD_TEMPLATE_MODAL_SHOW" | "GUILD_TEMPLATE_PROMOTION_TOOLTIP_HIDE" | "GUILD_TEMPLATE_RESOLVE" | "GUILD_TEMPLATE_RESOLVE_FAILURE" | "GUILD_TEMPLATE_RESOLVE_SUCCESS" | "GUILD_TEMPLATE_SYNC_SUCCESS" | "GUILD_TOGGLE_COLLAPSE_MUTED" | "GUILD_TOP_READ_CHANNELS_FETCH_SUCCESS" | "GUILD_UNAPPLY_BOOST_FAIL" | "GUILD_UNAPPLY_BOOST_START" | "GUILD_UNAPPLY_BOOST_SUCCESS" | "GUILD_UNAVAILABLE" | "GUILD_UNLOCKED_POWERUPS_FETCH_SUCCESS" | "GUILD_UPDATE" | "GUILD_UPDATE_DISCOVERY_METADATA" | "GUILD_UPDATE_DISCOVERY_METADATA_FAIL" | "GUILD_UPDATE_DISCOVERY_METADATA_FROM_SERVER" | "GUILD_VERIFICATION_CHECK" | "HABITUAL_DND_CLEAR" | "HIDE_ACTION_SHEET" | "HIDE_ACTION_SHEET_QUICK_SWITCHER" | "HIDE_KEYBOARD_SHORTCUTS" | "HIGH_FIVE_COMPLETE" | "HIGH_FIVE_COMPLETE_CLEAR" | "HIGH_FIVE_QUEUE" | "HIGH_FIVE_REMOVE" | "HIGH_FIVE_SET_ENABLED" | "HOTSPOT_HIDE" | "HOTSPOT_OVERRIDE_CLEAR" | "HOTSPOT_OVERRIDE_SET" | "HYPESQUAD_ONLINE_MEMBERSHIP_JOIN_SUCCESS" | "HYPESQUAD_ONLINE_MEMBERSHIP_LEAVE_SUCCESS" | "IDLE" | "IMPERSONATE_STOP" | "IMPERSONATE_UPDATE" | "INBOX_OPEN" | "INCOMING_CALL_MOVE" | "INITIALIZE_MEMBER_SAFETY_STORE" | "INITIATE_AGE_VERIFICATION" | "INSTALLATION_LOCATION_ADD" | "INSTALLATION_LOCATION_FETCH_METADATA" | "INSTALLATION_LOCATION_REMOVE" | "INSTALLATION_LOCATION_UPDATE" | "INSTANT_INVITE_CLEAR" | "INSTANT_INVITE_CREATE" | "INSTANT_INVITE_CREATE_FAILURE" | "INSTANT_INVITE_CREATE_SUCCESS" | "INSTANT_INVITE_REVOKE_SUCCESS" | "INTEGRATION_CREATE" | "INTEGRATION_DELETE" | "INTEGRATION_PERMISSION_SETTINGS_APPLICATION_PERMISSIONS_FETCH_FAILURE" | "INTEGRATION_PERMISSION_SETTINGS_CLEAR" | "INTEGRATION_PERMISSION_SETTINGS_COMMANDS_FETCH_FAILURE" | "INTEGRATION_PERMISSION_SETTINGS_COMMANDS_FETCH_SUCCESS" | "INTEGRATION_PERMISSION_SETTINGS_COMMAND_UPDATE" | "INTEGRATION_PERMISSION_SETTINGS_EDIT" | "INTEGRATION_PERMISSION_SETTINGS_INIT" | "INTEGRATION_PERMISSION_SETTINGS_RESET" | "INTEGRATION_QUERY" | "INTEGRATION_QUERY_FAILURE" | "INTEGRATION_QUERY_SUCCESS" | "INTEGRATION_SETTINGS_INIT" | "INTEGRATION_SETTINGS_SAVE_FAILURE" | "INTEGRATION_SETTINGS_SAVE_SUCCESS" | "INTEGRATION_SETTINGS_SET_SECTION" | "INTEGRATION_SETTINGS_START_EDITING_COMMAND" | "INTEGRATION_SETTINGS_START_EDITING_INTEGRATION" | "INTEGRATION_SETTINGS_START_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_STOP_EDITING_COMMAND" | "INTEGRATION_SETTINGS_STOP_EDITING_INTEGRATION" | "INTEGRATION_SETTINGS_STOP_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_SUBMITTING" | "INTEGRATION_SETTINGS_UPDATE_INTEGRATION" | "INTEGRATION_SETTINGS_UPDATE_WEBHOOK" | "INTERACTION_CREATE" | "INTERACTION_FAILURE" | "INTERACTION_IFRAME_MODAL_CLOSE" | "INTERACTION_IFRAME_MODAL_CREATE" | "INTERACTION_IFRAME_MODAL_KEY_CREATE" | "INTERACTION_MODAL_CREATE" | "INTERACTION_QUEUE" | "INTERACTION_SUCCESS" | "INVITE_ACCEPT" | "INVITE_ACCEPT_FAILURE" | "INVITE_ACCEPT_SUCCESS" | "INVITE_APP_NOT_OPENED" | "INVITE_APP_OPENED" | "INVITE_APP_OPENING" | "INVITE_MODAL_CLOSE" | "INVITE_MODAL_ERROR" | "INVITE_MODAL_OPEN" | "INVITE_RESOLVE" | "INVITE_RESOLVE_FAILURE" | "INVITE_RESOLVE_SUCCESS" | "INVITE_SUGGESTIONS_SEARCH" | "KEYBINDS_ADD_KEYBIND" | "KEYBINDS_DELETE_KEYBIND" | "KEYBINDS_ENABLE_ALL_KEYBINDS" | "KEYBINDS_REGISTER_GLOBAL_KEYBIND_ACTIONS" | "KEYBINDS_SET_KEYBIND" | "KEYBOARD_NAVIGATION_EXPLAINER_MODAL_SEEN" | "LAB_FEATURE_TOGGLE" | "LAYER_POP" | "LAYER_POP_ALL" | "LAYER_PUSH" | "LAYOUT_CREATE" | "LAYOUT_CREATE_WIDGETS" | "LAYOUT_DELETE_ALL_WIDGETS" | "LAYOUT_DELETE_WIDGET" | "LAYOUT_SET_PINNED" | "LAYOUT_SET_TOP_WIDGET" | "LAYOUT_SET_WIDGET_META" | "LAYOUT_UPDATE_WIDGET" | "LIBRARY_APPLICATIONS_TEST_MODE_ENABLED" | "LIBRARY_APPLICATION_ACTIVE_BRANCH_UPDATE" | "LIBRARY_APPLICATION_ACTIVE_LAUNCH_OPTION_UPDATE" | "LIBRARY_APPLICATION_FILTER_UPDATE" | "LIBRARY_APPLICATION_FLAGS_UPDATE_START" | "LIBRARY_APPLICATION_FLAGS_UPDATE_SUCCESS" | "LIBRARY_APPLICATION_UPDATE" | "LIBRARY_FETCH_SUCCESS" | "LIBRARY_TABLE_ACTIVE_ROW_ID_UPDATE" | "LIBRARY_TABLE_SORT_UPDATE" | "LIVE_CHANNEL_NOTICE_HIDE" | "LOAD_ARCHIVED_THREADS" | "LOAD_ARCHIVED_THREADS_FAIL" | "LOAD_ARCHIVED_THREADS_SUCCESS" | "LOAD_CHANNELS" | "LOAD_DATA_HARVEST_TYPE_FAILURE" | "LOAD_DATA_HARVEST_TYPE_START" | "LOAD_FORUM_POSTS" | "LOAD_FRIEND_SUGGESTIONS_FAILURE" | "LOAD_FRIEND_SUGGESTIONS_SUCCESS" | "LOAD_GUILD_AFFINITIES_SUCCESS" | "LOAD_ICYMI_HYDRATED" | "LOAD_INVITE_SUGGESTIONS" | "LOAD_MESSAGES" | "LOAD_MESSAGES_AROUND_SUCCESS" | "LOAD_MESSAGES_FAILURE" | "LOAD_MESSAGES_SUCCESS" | "LOAD_MESSAGES_SUCCESS_CACHED" | "LOAD_MESSAGE_INTERACTION_DATA_SUCCESS" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_ERROR" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_SUCCESS" | "LOAD_NOTIFICATION_CENTER_ITEMS" | "LOAD_NOTIFICATION_CENTER_ITEMS_FAILURE" | "LOAD_NOTIFICATION_CENTER_ITEMS_SUCCESS" | "LOAD_PINNED_MESSAGES" | "LOAD_PINNED_MESSAGES_FAILURE" | "LOAD_PINNED_MESSAGES_SUCCESS" | "LOAD_RECENT_MENTIONS" | "LOAD_RECENT_MENTIONS_FAILURE" | "LOAD_RECENT_MENTIONS_SUCCESS" | "LOAD_REGIONS" | "LOAD_RELATIONSHIPS_FAILURE" | "LOAD_RELATIONSHIPS_SUCCESS" | "LOAD_THREADS_SUCCESS" | "LOAD_USER_AFFINITIES_V2" | "LOAD_USER_AFFINITIES_V2_FAILURE" | "LOAD_USER_AFFINITIES_V2_SUCCESS" | "LOCAL_ACTIVITY_UPDATE" | "LOCAL_MESSAGES_LOADED" | "LOCAL_MESSAGE_CREATE" | "LOGIN" | "LOGIN_ACCOUNT_DISABLED" | "LOGIN_ACCOUNT_SCHEDULED_FOR_DELETION" | "LOGIN_ATTEMPTED" | "LOGIN_FAILURE" | "LOGIN_MFA" | "LOGIN_MFA_STEP" | "LOGIN_PASSWORD_RECOVERY_PHONE_VERIFICATION" | "LOGIN_PHONE_IP_AUTHORIZATION_REQUIRED" | "LOGIN_RESET" | "LOGIN_STATUS_RESET" | "LOGIN_SUCCESS" | "LOGIN_SUSPENDED_USER" | "LOGOUT" | "LOGOUT_AUTH_SESSIONS_SUCCESS" | "MASKED_LINK_ADD_TRUSTED_DOMAIN" | "MASKED_LINK_ADD_TRUSTED_PROTOCOL" | "MAX_MEMBER_COUNT_NOTICE_DISMISS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_ERROR" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_START" | "MEDIA_ENGINE_CONNECTION_STATS" | "MEDIA_ENGINE_CONNECTION_STATS_HISTORY_RESET" | "MEDIA_ENGINE_DEVICES" | "MEDIA_ENGINE_INTERACTION_REQUIRED" | "MEDIA_ENGINE_NOISE_CANCELLATION_ERROR" | "MEDIA_ENGINE_NOISE_CANCELLATION_ERROR_RESET" | "MEDIA_ENGINE_PERMISSION" | "MEDIA_ENGINE_SET_AEC_DUMP" | "MEDIA_ENGINE_SET_AUDIO_ENABLED" | "MEDIA_ENGINE_SET_ENABLE_HARDWARE_MUTE_NOTICE" | "MEDIA_ENGINE_SET_EXPERIMENTAL_ENCODERS" | "MEDIA_ENGINE_SET_EXPERIMENTAL_SOUNDSHARE" | "MEDIA_ENGINE_SET_GO_LIVE_SOURCE" | "MEDIA_ENGINE_SET_HARDWARE_ENCODING" | "MEDIA_ENGINE_SET_OPEN_H264" | "MEDIA_ENGINE_SET_USE_SYSTEM_SCREENSHARE_PICKER" | "MEDIA_ENGINE_SET_VIDEO_DEVICE" | "MEDIA_ENGINE_SET_VIDEO_ENABLED" | "MEDIA_ENGINE_SET_VIDEO_HOOK" | "MEDIA_ENGINE_SOUNDSHARE_FAILED" | "MEDIA_ENGINE_SOUNDSHARE_TRANSMITTING" | "MEDIA_ENGINE_VIDEO_SOURCE_QUALITY_CHANGED" | "MEDIA_ENGINE_VIDEO_STATE_CHANGED" | "MEDIA_ENGINE_VOICE_ACTIVITY_DETECTION_ERROR" | "MEDIA_PLAYBACK_POSITION_UPDATE" | "MEDIA_PLAYBACK_RATE_UPDATE" | "MEDIA_POST_EMBED_FETCH" | "MEDIA_POST_EMBED_FETCH_FAILURE" | "MEDIA_POST_EMBED_FETCH_SUCCESS" | "MEDIA_SESSION_JOINED" | "MEMBER_SAFETY_GUILD_MEMBER_SEARCH_SUCCESS" | "MEMBER_SAFETY_GUILD_MEMBER_UPDATE_BATCH" | "MEMBER_SAFETY_NEW_MEMBER_TIMESTAMP_REFRESH" | "MEMBER_SAFETY_PAGINATION_TOKEN_UPDATE" | "MEMBER_SAFETY_PAGINATION_UPDATE" | "MEMBER_SAFETY_SEARCH_STATE_UPDATE" | "MEMBER_VERIFICATION_FORM_FETCH_FAIL" | "MEMBER_VERIFICATION_FORM_UPDATE" | "MESSAGE_ACK" | "MESSAGE_ACKED" | "MESSAGE_CREATE" | "MESSAGE_DELETE" | "MESSAGE_DELETE_BULK" | "MESSAGE_EDIT_FAILED_AUTOMOD" | "MESSAGE_END_EDIT" | "MESSAGE_EXPLICIT_CONTENT_FP_CREATE" | "MESSAGE_EXPLICIT_CONTENT_FP_SUBMIT" | "MESSAGE_EXPLICIT_CONTENT_SCAN_TIMEOUT" | "MESSAGE_GIFT_INTENT_SHOWN" | "MESSAGE_LENGTH_UPSELL" | "MESSAGE_NOTIFICATION_SHOWN" | "MESSAGE_PREVIEWS_LOADED" | "MESSAGE_PREVIEWS_LOCALLY_LOADED" | "MESSAGE_REACTION_ADD" | "MESSAGE_REACTION_ADD_MANY" | "MESSAGE_REACTION_ADD_USERS" | "MESSAGE_REACTION_REMOVE" | "MESSAGE_REACTION_REMOVE_ALL" | "MESSAGE_REACTION_REMOVE_EMOJI" | "MESSAGE_REMINDER_DUE" | "MESSAGE_REQUEST_ACCEPT_OPTIMISTIC" | "MESSAGE_REQUEST_ACK" | "MESSAGE_REQUEST_CLEAR_ACK" | "MESSAGE_REVEAL" | "MESSAGE_SEND_FAILED" | "MESSAGE_SEND_FAILED_AUTOMOD" | "MESSAGE_START_EDIT" | "MESSAGE_UPDATE" | "MESSAGE_UPDATE_EDIT" | "MFA_CLEAR_BACKUP_CODES" | "MFA_DISABLE_SUCCESS" | "MFA_ENABLE_SUCCESS" | "MFA_SEEN_BACKUP_CODE_PROMPT" | "MFA_SEND_VERIFICATION_KEY" | "MFA_SMS_TOGGLE" | "MFA_SMS_TOGGLE_COMPLETE" | "MFA_VIEW_BACKUP_CODES" | "MFA_WEBAUTHN_CREDENTIALS_LOADED" | "MOBILE_NATIVE_UPDATE_CHECK_FINISHED" | "MOBILE_WEB_SIDEBAR_CLOSE" | "MOBILE_WEB_SIDEBAR_OPEN" | "MODAL_POP" | "MODAL_PUSH" | "MOD_VIEW_SEARCH_FINISH" | "MULTI_ACCOUNT_INVALIDATE_PUSH_SYNC_TOKENS" | "MULTI_ACCOUNT_MOBILE_EXPERIMENT_UPDATE" | "MULTI_ACCOUNT_MOVE_ACCOUNT" | "MULTI_ACCOUNT_REMOVE_ACCOUNT" | "MULTI_ACCOUNT_UPDATE_PUSH_SYNC_TOKEN" | "MULTI_ACCOUNT_VALIDATE_TOKEN_FAILURE" | "MULTI_ACCOUNT_VALIDATE_TOKEN_REQUEST" | "MULTI_ACCOUNT_VALIDATE_TOKEN_SUCCESS" | "MUTUAL_FRIENDS_FETCH_FAILURE" | "MUTUAL_FRIENDS_FETCH_START" | "MUTUAL_FRIENDS_FETCH_SUCCESS" | "NATIVE_APP_MODAL_OPENED" | "NATIVE_APP_MODAL_OPENING" | "NATIVE_APP_MODAL_OPEN_FAILED" | "NATIVE_SCREEN_SHARE_PICKER_CANCEL" | "NATIVE_SCREEN_SHARE_PICKER_ERROR" | "NATIVE_SCREEN_SHARE_PICKER_PRESENT" | "NATIVE_SCREEN_SHARE_PICKER_RELEASE" | "NATIVE_SCREEN_SHARE_PICKER_UPDATE" | "NEWLY_ADDED_EMOJI_SEEN_ACKNOWLEDGED" | "NEWLY_ADDED_EMOJI_SEEN_PENDING" | "NEWLY_ADDED_EMOJI_SEEN_UPDATED" | "NEW_PAYMENT_SOURCE_ADDRESS_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CARD_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CLEAR_ERROR" | "NEW_PAYMENT_SOURCE_STRIPE_PAYMENT_REQUEST_UPDATE" | "NOTICE_DISABLE" | "NOTICE_DISMISS" | "NOTICE_SHOW" | "NOTIFICATIONS_SET_DESKTOP_TYPE" | "NOTIFICATIONS_SET_DISABLED_SOUNDS" | "NOTIFICATIONS_SET_DISABLE_UNREAD_BADGE" | "NOTIFICATIONS_SET_NOTIFY_MESSAGES_IN_SELECTED_CHANNEL" | "NOTIFICATIONS_SET_PERMISSION_STATE" | "NOTIFICATIONS_SET_TASKBAR_FLASH" | "NOTIFICATIONS_SET_TTS_TYPE" | "NOTIFICATIONS_TOGGLE_ALL_DISABLED" | "NOTIFICATION_CENTER_CLEAR_GUILD_MENTIONS" | "NOTIFICATION_CENTER_ITEMS_ACK" | "NOTIFICATION_CENTER_ITEMS_ACK_FAILURE" | "NOTIFICATION_CENTER_ITEMS_LOCAL_ACK" | "NOTIFICATION_CENTER_ITEM_COMPLETED" | "NOTIFICATION_CENTER_ITEM_CREATE" | "NOTIFICATION_CENTER_ITEM_DELETE" | "NOTIFICATION_CENTER_ITEM_DELETE_FAILURE" | "NOTIFICATION_CENTER_REFRESH" | "NOTIFICATION_CENTER_SET_ACTIVE" | "NOTIFICATION_CENTER_SET_TAB" | "NOTIFICATION_CENTER_TAB_FOCUSED" | "NOTIFICATION_CLICK" | "NOTIFICATION_CREATE" | "NOTIFICATION_SETTINGS_UPDATE" | "NOW_PLAYING_MOUNTED" | "NOW_PLAYING_UNMOUNTED" | "NUF_COMPLETE" | "NUF_NEW_USER" | "OAUTH2_TOKEN_REVOKE" | "ONLINE_GUILD_MEMBER_COUNT_UPDATE" | "OUTBOUND_PROMOTIONS_SEEN" | "OUTBOUND_PROMOTION_NOTICE_DISMISS" | "OVERLAY_ACTIVATE_REGION" | "OVERLAY_CALL_PRIVATE_CHANNEL" | "OVERLAY_CRASHED" | "OVERLAY_DEACTIVATE_ALL_REGIONS" | "OVERLAY_DISABLE_EXTERNAL_LINK_ALERT" | "OVERLAY_FOCUSED" | "OVERLAY_FORCE_RENDER_MODE" | "OVERLAY_INCOMPATIBLE_APP" | "OVERLAY_INITIALIZE" | "OVERLAY_JOIN_GAME" | "OVERLAY_MESSAGE_EVENT_ACTION" | "OVERLAY_NOTIFICATION_EVENT" | "OVERLAY_READY" | "OVERLAY_RELOAD" | "OVERLAY_RENDER_DEBUG_CLEAR_TRACKED_PIDS" | "OVERLAY_RENDER_DEBUG_MODE" | "OVERLAY_SELECT_CALL" | "OVERLAY_SELECT_CHANNEL" | "OVERLAY_SET_ASSOCIATED_GAME" | "OVERLAY_SET_AVATAR_SIZE_MODE" | "OVERLAY_SET_CLICK_ZONES" | "OVERLAY_SET_DISABLE_CLICKABLE_REGIONS" | "OVERLAY_SET_DISPLAY_NAME_MODE" | "OVERLAY_SET_DISPLAY_USER_MODE" | "OVERLAY_SET_ENABLED" | "OVERLAY_SET_GAME_INVITE_NOTIFICATION" | "OVERLAY_SET_GPU_BOOST_REQUESTED" | "OVERLAY_SET_INPUT_LOCKED" | "OVERLAY_SET_INVITE_MESSAGE" | "OVERLAY_SET_LIMITED_INTERACTION_OVERRIDE" | "OVERLAY_SET_NOTIFICATION_DISABLED_SETTING" | "OVERLAY_SET_NOTIFICATION_POSITION_MODE" | "OVERLAY_SET_NOT_IDLE" | "OVERLAY_SET_PREVIEW_IN_GAME_MODE" | "OVERLAY_SET_SHOW_KEYBIND_INDICATORS" | "OVERLAY_SET_TEXT_WIDGET_OPACITY" | "OVERLAY_SOUNDBOARD_SOUNDS_FETCH_REQUEST" | "OVERLAY_START_SESSION" | "OVERLAY_SUCCESSFULLY_SHOWN" | "OVERLAY_UPDATE_OVERLAY_METHOD" | "OVERLAY_UPDATE_OVERLAY_STATE" | "OVERLAY_WIDGET_CHANGED" | "PASSIVE_UPDATE_V2" | "PASSWORDLESS_FAILURE" | "PASSWORDLESS_START" | "PASSWORD_UPDATED" | "PAYMENT_AUTHENTICATION_CLEAR_ERROR" | "PAYMENT_AUTHENTICATION_ERROR" | "PAYMENT_UPDATE" | "PERMISSION_CLEAR_ELEVATED_PROCESS" | "PERMISSION_CLEAR_PTT_ADMIN_WARNING" | "PERMISSION_CLEAR_SUPPRESS_WARNING" | "PERMISSION_CLEAR_VAD_WARNING" | "PERMISSION_CONTINUE_NONELEVATED_PROCESS" | "PERMISSION_REQUEST_ELEVATED_PROCESS" | "PHONE_SET_COUNTRY_CODE" | "PICTURE_IN_PICTURE_CLOSE" | "PICTURE_IN_PICTURE_HIDE" | "PICTURE_IN_PICTURE_MOVE" | "PICTURE_IN_PICTURE_OPEN" | "PICTURE_IN_PICTURE_RESIZE" | "PICTURE_IN_PICTURE_SHOW" | "PICTURE_IN_PICTURE_UPDATE_RECT" | "PICTURE_IN_PICTURE_UPDATE_SELECTED_WINDOW" | "POGGERMODE_ACHIEVEMENT_UNLOCK" | "POGGERMODE_SETTINGS_UPDATE" | "POGGERMODE_TEMPORARILY_DISABLED" | "POGGERMODE_UPDATE_COMBO" | "POGGERMODE_UPDATE_MESSAGE_COMBO" | "POPOUT_WINDOW_ADD_STYLESHEET" | "POPOUT_WINDOW_CLOSE" | "POPOUT_WINDOW_OPEN" | "POPOUT_WINDOW_SET_ALWAYS_ON_TOP" | "POST_CONNECTION_OPEN" | "POTIONS_SET_CONFETTI_MODE" | "POTIONS_TRIGGER_MESSAGE_CONFETTI" | "PREMIUM_MARKETING_DATA_READY" | "PREMIUM_MARKETING_PREVIEW" | "PREMIUM_PAYMENT_ERROR_CLEAR" | "PREMIUM_PAYMENT_MODAL_CLOSE" | "PREMIUM_PAYMENT_MODAL_OPEN" | "PREMIUM_PAYMENT_SUBSCRIBE_FAIL" | "PREMIUM_PAYMENT_SUBSCRIBE_START" | "PREMIUM_PAYMENT_SUBSCRIBE_SUCCESS" | "PREMIUM_PAYMENT_UPDATE_FAIL" | "PREMIUM_PAYMENT_UPDATE_SUCCESS" | "PREMIUM_REQUIRED_MODAL_CLOSE" | "PREMIUM_REQUIRED_MODAL_OPEN" | "PRESENCES_REPLACE" | "PRESENCE_SUBSCRIPTIONS_ADD" | "PRESENCE_UPDATES" | "PRIVATE_CHANNEL_RECIPIENTS_ADD_USER" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_CLOSE" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_OPEN" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_QUERY" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_SELECT" | "PRIVATE_CHANNEL_RECIPIENTS_REMOVE_USER" | "PROFILE_CUSTOMIZATION_OPEN_PREVIEW_MODAL" | "PROFILE_EFFECTS_SET_TRY_IT_OUT" | "PROXY_BLOCKED_REQUEST" | "PUBLIC_UPSELL_NOTICE_DISMISS" | "PURCHASED_ITEMS_FESTIVITY_FETCH_WOW_MOMENT_MEDIA_FAILURE" | "PURCHASED_ITEMS_FESTIVITY_FETCH_WOW_MOMENT_MEDIA_SUCCESS" | "PURCHASED_ITEMS_FESTIVITY_IS_FETCHING_WOW_MOMENT_MEDIA" | "PURCHASED_ITEMS_FESTIVITY_SET_CAN_PLAY_WOW_MOMENT" | "PUSH_NOTIFICATION_CLICK" | "QUESTS_CLAIM_REWARD_BEGIN" | "QUESTS_CLAIM_REWARD_FAILURE" | "QUESTS_CLAIM_REWARD_SUCCESS" | "QUESTS_DELIVERY_OVERRIDE" | "QUESTS_DISMISS_CONTENT_BEGIN" | "QUESTS_DISMISS_CONTENT_FAILURE" | "QUESTS_DISMISS_CONTENT_SUCCESS" | "QUESTS_DISMISS_PROGRESS_TRACKING_FAILURE_NOTICE" | "QUESTS_ENROLL_BEGIN" | "QUESTS_ENROLL_FAILURE" | "QUESTS_ENROLL_SUCCESS" | "QUESTS_FETCH_CLAIMED_QUESTS_BEGIN" | "QUESTS_FETCH_CLAIMED_QUESTS_FAILURE" | "QUESTS_FETCH_CLAIMED_QUESTS_SUCCESS" | "QUESTS_FETCH_CURRENT_QUESTS_BEGIN" | "QUESTS_FETCH_CURRENT_QUESTS_FAILURE" | "QUESTS_FETCH_CURRENT_QUESTS_SUCCESS" | "QUESTS_FETCH_QUEST_TO_DELIVER_BEGIN" | "QUESTS_FETCH_QUEST_TO_DELIVER_FAILURE" | "QUESTS_FETCH_QUEST_TO_DELIVER_SUCCESS" | "QUESTS_FETCH_REWARD_CODE_BEGIN" | "QUESTS_FETCH_REWARD_CODE_FAILURE" | "QUESTS_FETCH_REWARD_CODE_SUCCESS" | "QUESTS_PREVIEW_UPDATE_SUCCESS" | "QUESTS_SELECT_TASK_PLATFORM" | "QUESTS_SEND_HEARTBEAT_FAILURE" | "QUESTS_SEND_HEARTBEAT_SUCCESS" | "QUESTS_UPDATE_OPTIMISTIC_PROGRESS" | "QUESTS_USER_COMPLETION_UPDATE" | "QUESTS_USER_STATUS_UPDATE" | "QUEUE_INTERACTION_COMPONENT_STATE" | "QUICKSWITCHER_HIDE" | "QUICKSWITCHER_SEARCH" | "QUICKSWITCHER_SELECT" | "QUICKSWITCHER_SHOW" | "QUICKSWITCHER_SWITCH_TO" | "RECEIVE_CHANNEL_AFFINITIES" | "RECEIVE_CHANNEL_SUMMARIES" | "RECEIVE_CHANNEL_SUMMARIES_BULK" | "RECEIVE_CHANNEL_SUMMARY" | "RECENT_MENTION_DELETE" | "RECOMPUTE_READ_STATES" | "REFERRALS_FETCH_ELIGIBLE_USER_FAIL" | "REFERRALS_FETCH_ELIGIBLE_USER_START" | "REFERRALS_FETCH_ELIGIBLE_USER_SUCCESS" | "REGISTER" | "REGISTER_SUCCESS" | "RELATIONSHIP_ADD" | "RELATIONSHIP_IGNORE_USER_SUCCESS" | "RELATIONSHIP_PENDING_INCOMING_REMOVED" | "RELATIONSHIP_REMOVE" | "RELATIONSHIP_UPDATE" | "REMOTE_COMMAND" | "REMOTE_SESSION_CONNECT" | "REMOTE_SESSION_DISCONNECT" | "REMOVE_AUTOMOD_MESSAGE_NOTICE" | "REPORT_AV_ERROR" | "REPORT_TO_MOD_REPORT_MESSAGE_SUCCESS" | "REQUEST_CHANNEL_AFFINITIES" | "REQUEST_CHANNEL_SUMMARIES" | "REQUEST_CHANNEL_SUMMARIES_BULK" | "REQUEST_CHANNEL_SUMMARY" | "REQUEST_FORUM_UNREADS" | "REQUEST_SOUNDBOARD_SOUNDS" | "RESET_NOTIFICATION_CENTER" | "RESET_PAYMENT_ID" | "RESET_PREVIEW_CLIENT_THEME" | "RESET_SOCKET" | "RESORT_THREADS" | "RPC_APP_AUTHENTICATED" | "RPC_APP_CONNECTED" | "RPC_APP_DISCONNECTED" | "RPC_NOTIFICATION_CREATE" | "RPC_SERVER_READY" | "RTC_CONNECTION_CLIENT_CONNECT" | "RTC_CONNECTION_CLIENT_DISCONNECT" | "RTC_CONNECTION_FLAGS" | "RTC_CONNECTION_LOSS_RATE" | "RTC_CONNECTION_PING" | "RTC_CONNECTION_PLATFORM" | "RTC_CONNECTION_REMOTE_VIDEO_SINK_WANTS" | "RTC_CONNECTION_ROSTER_MAP_UPDATE" | "RTC_CONNECTION_SECURE_FRAMES_UPDATE" | "RTC_CONNECTION_STATE" | "RTC_CONNECTION_UPDATE_ID" | "RTC_CONNECTION_USERS_MERGED" | "RTC_CONNECTION_VIDEO" | "RTC_DEBUG_MODAL_CLOSE" | "RTC_DEBUG_MODAL_OPEN" | "RTC_DEBUG_MODAL_OPEN_REPLAY" | "RTC_DEBUG_MODAL_OPEN_REPLAY_AT_PATH" | "RTC_DEBUG_MODAL_SET_SECTION" | "RTC_DEBUG_MODAL_UPDATE_VIDEO_OUTPUT" | "RTC_DEBUG_POPOUT_WINDOW_OPEN" | "RTC_DEBUG_SET_RECORDING_FLAG" | "RTC_DEBUG_SET_SIMULCAST_OVERRIDE" | "RTC_LATENCY_TEST_COMPLETE" | "RUNNING_GAMES_CHANGE" | "RUNNING_GAME_ADD_OVERRIDE" | "RUNNING_GAME_DELETE_ENTRY" | "RUNNING_GAME_EDIT_NAME" | "RUNNING_GAME_TOGGLE_DETECTION" | "RUNNING_GAME_TOGGLE_OVERLAY" | "RUNNING_STREAMER_TOOLS_CHANGE" | "SAFETY_HUB_APPEAL_CLOSE" | "SAFETY_HUB_APPEAL_OPEN" | "SAFETY_HUB_APPEAL_SIGNAL_CUSTOM_INPUT_CHANGE" | "SAFETY_HUB_APPEAL_SIGNAL_SELECT" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_MODAL_CLOSE" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_MODAL_OPEN" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_START_POLL" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_SUBMIT_SUCCESS" | "SAFETY_HUB_CHECK_AUTOMATED_UNDERAGE_APPEAL_FAILURE" | "SAFETY_HUB_CHECK_AUTOMATED_UNDERAGE_APPEAL_START" | "SAFETY_HUB_CHECK_AUTOMATED_UNDERAGE_APPEAL_SUCCESS" | "SAFETY_HUB_FETCH_CLASSIFICATION_FAILURE" | "SAFETY_HUB_FETCH_CLASSIFICATION_START" | "SAFETY_HUB_FETCH_CLASSIFICATION_SUCCESS" | "SAFETY_HUB_FETCH_FAILURE" | "SAFETY_HUB_FETCH_START" | "SAFETY_HUB_FETCH_SUCCESS" | "SAFETY_HUB_REQUEST_AUTOMATED_UNDERAGE_APPEAL_FAILURE" | "SAFETY_HUB_REQUEST_AUTOMATED_UNDERAGE_APPEAL_START" | "SAFETY_HUB_REQUEST_AUTOMATED_UNDERAGE_APPEAL_SUCCESS" | "SAFETY_HUB_REQUEST_REVIEW_FAILURE" | "SAFETY_HUB_REQUEST_REVIEW_START" | "SAFETY_HUB_REQUEST_REVIEW_SUCCESS" | "SAVED_MESSAGES_UPDATE" | "SAVED_MESSAGE_CREATE" | "SAVED_MESSAGE_DELETE" | "SAVE_LAST_NON_VOICE_ROUTE" | "SAVE_LAST_ROUTE" | "SCHEDULED_MESSAGES_CREATE_SUCCESS" | "SCHEDULED_MESSAGES_DELETE_FAILURE" | "SCHEDULED_MESSAGES_DELETE_START" | "SCHEDULED_MESSAGES_DELETE_SUCCESS" | "SEARCH_ADD_HISTORY" | "SEARCH_AUTOCOMPLETE_QUERY_UPDATE" | "SEARCH_CLEAR_HISTORY" | "SEARCH_EDITOR_STATE_CHANGE" | "SEARCH_EDITOR_STATE_CLEAR" | "SEARCH_ENSURE_SEARCH_STATE" | "SEARCH_FINISH" | "SEARCH_INDEXING" | "SEARCH_MESSAGES_CLEAR_ALL" | "SEARCH_MESSAGES_FAILURE" | "SEARCH_MESSAGES_INDEXING" | "SEARCH_MESSAGES_START" | "SEARCH_MESSAGES_SUCCESS" | "SEARCH_RECENT_MESSAGES_CLEAR" | "SEARCH_REMOVE_HISTORY" | "SEARCH_RESULTS_QUERY_UPDATE" | "SEARCH_SCREEN_OPEN" | "SEARCH_SET_SHOW_BLOCKED_RESULTS" | "SEARCH_SET_SHOW_NO_RESULTS_ALT" | "SEARCH_START" | "SECURE_FRAMES_SETTINGS_UPDATE" | "SECURE_FRAMES_TRANSIENT_KEY_CREATE" | "SECURE_FRAMES_TRANSIENT_KEY_DELETE" | "SECURE_FRAMES_UPLOADED_KEY_VERSION_ADD" | "SECURE_FRAMES_UPLOADED_KEY_VERSION_CLEAR" | "SECURE_FRAMES_USER_VERIFIED_KEYS_DELETE" | "SECURE_FRAMES_VERIFIED_KEY_CREATE" | "SECURE_FRAMES_VERIFIED_KEY_DELETE" | "SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE" | "SELECT_HOME_RESOURCE_CHANNEL" | "SELECT_NEW_MEMBER_ACTION_CHANNEL" | "SELF_PRESENCE_STORE_UPDATE" | "SESSIONS_REPLACE" | "SET_CHANNEL_BITRATE" | "SET_CHANNEL_VIDEO_QUALITY_MODE" | "SET_CONSENT_REQUIRED" | "SET_CREATED_AT_OVERRIDE" | "SET_GUILD_FOLDER_EXPANDED" | "SET_GUILD_LEADERBOARD" | "SET_HIGHLIGHTED_SUMMARY" | "SET_INTERACTION_COMPONENT_STATE" | "SET_LOCATION_METADATA" | "SET_NATIVE_PERMISSION" | "SET_PENDING_REPLY_SHOULD_MENTION" | "SET_PREMIUM_TYPE_OVERRIDE" | "SET_PREVIOUS_GO_LIVE_SETTINGS" | "SET_RECENTLY_ACTIVE_COLLAPSED" | "SET_RECENT_MENTIONS_FILTER" | "SET_RECENT_MENTIONS_STALE" | "SET_RPC_NOTIFICATION_SETTINGS" | "SET_SELECTED_SUMMARY" | "SET_SOUNDPACK" | "SET_STREAM_APP_INTENT" | "SET_SUMMARY_FEEDBACK" | "SET_THEME_OVERRIDE" | "SET_TTS_SPEECH_RATE" | "SET_USER_LEADERBOARD_LAST_UPDATE_REQUESTED" | "SET_VAD_PERMISSION" | "SHARED_CANVAS_CLEAR_DRAWABLES" | "SHARED_CANVAS_DRAW_LINE_POINT" | "SHARED_CANVAS_SET_DRAW_MODE" | "SHARED_CANVAS_UPDATE_EMOJI_HOSE" | "SHARED_CANVAS_UPDATE_LINE_POINTS" | "SHOW_ACTION_SHEET" | "SHOW_ACTION_SHEET_QUICK_SWITCHER" | "SHOW_KEYBOARD_SHORTCUTS" | "SIDEBAR_CLOSE" | "SIDEBAR_CLOSE_GUILD" | "SIDEBAR_CREATE_THREAD" | "SIDEBAR_VIEW_CHANNEL" | "SIDEBAR_VIEW_GUILD" | "SKUS_FETCH_SUCCESS" | "SKU_FETCH_FAIL" | "SKU_FETCH_START" | "SKU_FETCH_SUCCESS" | "SKU_PURCHASE_AWAIT_CONFIRMATION" | "SKU_PURCHASE_CLEAR_ERROR" | "SKU_PURCHASE_FAIL" | "SKU_PURCHASE_MODAL_CLOSE" | "SKU_PURCHASE_MODAL_OPEN" | "SKU_PURCHASE_PREVIEW_FETCH" | "SKU_PURCHASE_PREVIEW_FETCH_FAILURE" | "SKU_PURCHASE_PREVIEW_FETCH_SUCCESS" | "SKU_PURCHASE_SHOW_CONFIRMATION_STEP" | "SKU_PURCHASE_START" | "SKU_PURCHASE_SUCCESS" | "SKU_PURCHASE_UPDATE_IS_GIFT" | "SLOWMODE_RESET_COOLDOWN" | "SLOWMODE_SET_COOLDOWN" | "SOUNDBOARD_FETCH_DEFAULT_SOUNDS" | "SOUNDBOARD_FETCH_DEFAULT_SOUNDS_SUCCESS" | "SOUNDBOARD_MUTE_JOIN_SOUND" | "SOUNDBOARD_SET_OVERLAY_ENABLED" | "SOUNDBOARD_SOUNDS_RECEIVED" | "SPEAKING" | "SPEAKING_MESSAGE" | "SPEAK_MESSAGE" | "SPEAK_TEXT" | "SPELLCHECK_LEARN_WORD" | "SPELLCHECK_TOGGLE" | "SPELLCHECK_UNLEARN_WORD" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN_REVOKE" | "SPOTIFY_NEW_TRACK" | "SPOTIFY_PLAYER_PAUSE" | "SPOTIFY_PLAYER_PLAY" | "SPOTIFY_PLAYER_STATE" | "SPOTIFY_PROFILE_UPDATE" | "SPOTIFY_SET_ACTIVE_DEVICE" | "SPOTIFY_SET_DEVICES" | "SPOTIFY_SET_PROTOCOL_REGISTERED" | "STAGE_INSTANCE_CREATE" | "STAGE_INSTANCE_DELETE" | "STAGE_INSTANCE_UPDATE" | "STAGE_MUSIC_MUTE" | "STAGE_MUSIC_PLAY" | "START_SESSION" | "STATUS_PAGE_INCIDENT" | "STATUS_PAGE_SCHEDULED_MAINTENANCE" | "STATUS_PAGE_SCHEDULED_MAINTENANCE_ACK" | "STICKER_FETCH_SUCCESS" | "STICKER_PACKS_FETCH_START" | "STICKER_PACKS_FETCH_SUCCESS" | "STICKER_PACK_FETCH_SUCCESS" | "STICKER_TRACK_USAGE" | "STOP_SPEAKING" | "STORE_LISTINGS_FETCH_FAIL" | "STORE_LISTINGS_FETCH_START" | "STORE_LISTINGS_FETCH_SUCCESS" | "STORE_LISTING_FETCH_SUCCESS" | "STREAMER_MODE_UPDATE" | "STREAMING_UPDATE" | "STREAM_CLOSE" | "STREAM_CREATE" | "STREAM_DELETE" | "STREAM_LAYOUT_UPDATE" | "STREAM_PREVIEW_FETCH_FAIL" | "STREAM_PREVIEW_FETCH_START" | "STREAM_PREVIEW_FETCH_SUCCESS" | "STREAM_SERVER_UPDATE" | "STREAM_SET_PAUSED" | "STREAM_START" | "STREAM_STOP" | "STREAM_TIMED_OUT" | "STREAM_UPDATE" | "STREAM_UPDATE_SELF_HIDDEN" | "STREAM_UPDATE_SETTINGS" | "STREAM_WATCH" | "STRIPE_TOKEN_FAILURE" | "SUBSCRIPTION_PLANS_FETCH" | "SUBSCRIPTION_PLANS_FETCH_FAILURE" | "SUBSCRIPTION_PLANS_FETCH_SUCCESS" | "SUBSCRIPTION_PLANS_RESET" | "SURVEY_FETCHED" | "SURVEY_HIDE" | "SURVEY_OVERRIDE" | "SURVEY_SEEN" | "SYSTEM_THEME_CHANGE" | "THERMAL_STATE_CHANGE" | "THREAD_CREATE" | "THREAD_CREATE_LOCAL" | "THREAD_DELETE" | "THREAD_LIST_SYNC" | "THREAD_MEMBERS_UPDATE" | "THREAD_MEMBER_LIST_UPDATE" | "THREAD_MEMBER_LOCAL_UPDATE" | "THREAD_MEMBER_UPDATE" | "THREAD_SETTINGS_DRAFT_CHANGE" | "THREAD_UPDATE" | "TOGGLE_GUILD_EXPANDED_STATE" | "TOGGLE_GUILD_FOLDER_EXPAND" | "TOGGLE_OVERLAY_CANVAS" | "TOGGLE_TOPICS_BAR" | "TOP_EMOJIS_FETCH" | "TOP_EMOJIS_FETCH_SUCCESS" | "TRUNCATE_MENTIONS" | "TRUNCATE_MESSAGES" | "TRY_ACK" | "TUTORIAL_INDICATOR_DISMISS" | "TUTORIAL_INDICATOR_HIDE" | "TUTORIAL_INDICATOR_SHOW" | "TUTORIAL_INDICATOR_SUPPRESS_ALL" | "TYPING_START" | "TYPING_START_LOCAL" | "TYPING_STOP" | "TYPING_STOP_LOCAL" | "UNSYNCED_USER_SETTINGS_UPDATE" | "UNVERIFIED_GAME_UPDATE" | "UPCOMING_GUILD_EVENT_NOTICE_HIDE" | "UPCOMING_GUILD_EVENT_NOTICE_SEEN" | "UPDATE_AVAILABLE" | "UPDATE_BACKGROUND_GRADIENT_PRESET" | "UPDATE_CHANNEL_DIMENSIONS" | "UPDATE_CHANNEL_LIST_DIMENSIONS" | "UPDATE_CHANNEL_LIST_SUBTITLES" | "UPDATE_CHAT_WALLPAPER_FLAG_COMPLETE" | "UPDATE_CHAT_WALLPAPER_FLAG_START" | "UPDATE_CHAT_WALLPAPER_OVERRIDES" | "UPDATE_CLIENT_PREMIUM_TYPE" | "UPDATE_CONSENTS" | "UPDATE_DATA_HARVEST_TYPE" | "UPDATE_DOWNLOADED" | "UPDATE_ERROR" | "UPDATE_GUILD_LIST_DIMENSIONS" | "UPDATE_MANUALLY" | "UPDATE_MOBILE_PENDING_THEME_INDEX" | "UPDATE_NOT_AVAILABLE" | "UPDATE_STRANGER_STATUS" | "UPDATE_THEME_PREFERENCES" | "UPDATE_TOKEN" | "UPDATE_VISIBLE_MESSAGES" | "UPLOAD_ATTACHMENT_ADD_FILES" | "UPLOAD_ATTACHMENT_CLEAR_ALL_FILES" | "UPLOAD_ATTACHMENT_POP_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILES" | "UPLOAD_ATTACHMENT_SET_FILE" | "UPLOAD_ATTACHMENT_SET_UPLOADS" | "UPLOAD_ATTACHMENT_UPDATE_FILE" | "UPLOAD_CANCEL_REQUEST" | "UPLOAD_COMPLETE" | "UPLOAD_COMPRESSION_PROGRESS" | "UPLOAD_FAIL" | "UPLOAD_FILE_UPDATE" | "UPLOAD_ITEM_CANCEL_REQUEST" | "UPLOAD_PROGRESS" | "UPLOAD_RESTORE_FAILED_UPLOAD" | "UPLOAD_START" | "USER_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "USER_APPLICATION_REMOVE" | "USER_APPLICATION_UPDATE" | "USER_APPLIED_BOOSTS_FETCH_START" | "USER_APPLIED_BOOSTS_FETCH_SUCCESS" | "USER_AUTHORIZED_APPS_REQUEST" | "USER_AUTHORIZED_APPS_UPDATE" | "USER_CONNECTIONS_CALLBACK" | "USER_CONNECTIONS_INTEGRATION_JOINING" | "USER_CONNECTIONS_INTEGRATION_JOINING_ERROR" | "USER_CONNECTIONS_UPDATE" | "USER_CONNECTION_UPDATE" | "USER_GUILD_JOIN_REQUEST_COACHMARK_CLEAR" | "USER_GUILD_JOIN_REQUEST_COACHMARK_SHOW" | "USER_GUILD_JOIN_REQUEST_COOLDOWN_FETCH" | "USER_GUILD_JOIN_REQUEST_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE_BULK" | "USER_GUILD_SETTINGS_FULL_UPDATE" | "USER_GUILD_SETTINGS_GUILD_AND_CHANNELS_UPDATE" | "USER_GUILD_SETTINGS_GUILD_UPDATE" | "USER_GUILD_SETTINGS_REMOVE_PENDING_CHANNEL_UPDATES" | "USER_JOIN_REQUEST_GUILDS_FETCH" | "USER_NON_CHANNEL_ACK" | "USER_NOTE_LOAD_START" | "USER_NOTE_UPDATE" | "USER_PAYMENT_BROWSER_CHECKOUT_DONE" | "USER_PAYMENT_BROWSER_CHECKOUT_STARTED" | "USER_PAYMENT_CLIENT_ADD" | "USER_PROFILE_EFFECTS_FETCH" | "USER_PROFILE_EFFECTS_FETCH_FAILURE" | "USER_PROFILE_EFFECTS_FETCH_SUCCESS" | "USER_PROFILE_FETCH_FAILURE" | "USER_PROFILE_FETCH_START" | "USER_PROFILE_FETCH_SUCCESS" | "USER_PROFILE_MODAL_CLOSE" | "USER_PROFILE_MODAL_OPEN" | "USER_PROFILE_PIN_BADGES_ON_CLIENT" | "USER_PROFILE_SIDEBAR_TOGGLE_SECTION" | "USER_PROFILE_UPDATE_FAILURE" | "USER_PROFILE_UPDATE_START" | "USER_PROFILE_UPDATE_SUCCESS" | "USER_REQUIRED_ACTION_UPDATE" | "USER_SETTINGS_ACCOUNT_CLOSE" | "USER_SETTINGS_ACCOUNT_INIT" | "USER_SETTINGS_ACCOUNT_RESET_AND_CLOSE_FORM" | "USER_SETTINGS_ACCOUNT_RESET_PENDING_LEGACY_USERNAME_DISABLED" | "USER_SETTINGS_ACCOUNT_SET_PENDING_ACCENT_COLOR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BANNER" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BIO" | "USER_SETTINGS_ACCOUNT_SET_PENDING_GLOBAL_NAME" | "USER_SETTINGS_ACCOUNT_SET_PENDING_LEGACY_USERNAME_DISABLED" | "USER_SETTINGS_ACCOUNT_SET_PENDING_NAMEPLATE" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PROFILE_EFFECT_ID" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PRONOUNS" | "USER_SETTINGS_ACCOUNT_SET_PENDING_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SET_SINGLE_TRY_IT_OUT_COLLECTIBLES_ITEM" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_BANNER" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_PRESET" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_PROFILE_EFFECT_ID" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SUBMIT" | "USER_SETTINGS_ACCOUNT_SUBMIT_FAILURE" | "USER_SETTINGS_ACCOUNT_SUBMIT_SUCCESS" | "USER_SETTINGS_CLEAR_ERRORS" | "USER_SETTINGS_LOCALE_OVERRIDE" | "USER_SETTINGS_MODAL_CLEAR_SCROLL_POSITION" | "USER_SETTINGS_MODAL_CLEAR_SUBSECTION" | "USER_SETTINGS_MODAL_CLOSE" | "USER_SETTINGS_MODAL_INIT" | "USER_SETTINGS_MODAL_OPEN" | "USER_SETTINGS_MODAL_RESET" | "USER_SETTINGS_MODAL_SET_SECTION" | "USER_SETTINGS_MODAL_SUBMIT" | "USER_SETTINGS_MODAL_SUBMIT_COMPLETE" | "USER_SETTINGS_MODAL_SUBMIT_FAILURE" | "USER_SETTINGS_MODAL_UPDATE_ACCOUNT" | "USER_SETTINGS_OVERRIDE_APPLY" | "USER_SETTINGS_OVERRIDE_CLEAR" | "USER_SETTINGS_PROTO_ENQUEUE_UPDATE" | "USER_SETTINGS_PROTO_LOAD_IF_NECESSARY" | "USER_SETTINGS_PROTO_UPDATE" | "USER_SETTINGS_PROTO_UPDATE_EDIT_INFO" | "USER_SETTINGS_RESET_ALL_PENDING" | "USER_SETTINGS_RESET_ALL_TRY_IT_OUT" | "USER_SETTINGS_RESET_PENDING_ACCOUNT_CHANGES" | "USER_SETTINGS_RESET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_RESET_PENDING_PRIMARY_GUILD_CHANGES" | "USER_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "USER_SETTINGS_SET_PENDING_PRIMARY_GUILD_ID" | "USER_SOUNDBOARD_SET_VOLUME" | "USER_UPDATE" | "VIDEO_FILTER_ASSETS_FETCH_SUCCESS" | "VIDEO_FILTER_ASSET_DELETE_SUCCESS" | "VIDEO_FILTER_ASSET_UPLOAD_SUCCESS" | "VIDEO_SAVE_LAST_USED_BACKGROUND_OPTION" | "VIDEO_SIZE_UPDATE" | "VIDEO_STREAM_READY_TIMEOUT" | "VIEW_HISTORY_MARK_VIEW" | "VIRTUAL_CURRENCY_BALANCE_FETCH" | "VIRTUAL_CURRENCY_BALANCE_FETCH_FAIL" | "VIRTUAL_CURRENCY_BALANCE_FETCH_SUCCESS" | "VIRTUAL_CURRENCY_BALANCE_UPDATE" | "VIRTUAL_CURRENCY_EARNED_ORBS_COACHMARK_CLOSE" | "VIRTUAL_CURRENCY_EARNED_ORBS_COACHMARK_OPEN" | "VIRTUAL_CURRENCY_ONBOARDING_MODAL_OPEN" | "VIRTUAL_CURRENCY_ONBOARDING_MODAL_RESET" | "VIRTUAL_CURRENCY_REDEEM_FAIL" | "VIRTUAL_CURRENCY_REDEEM_START" | "VIRTUAL_CURRENCY_REDEEM_SUCCESS" | "VIRTUAL_CURRENCY_SET_BALANCE_PILL_OVERLAY" | "VOICE_CATEGORY_COLLAPSE" | "VOICE_CATEGORY_EXPAND" | "VOICE_CHANNEL_EFFECT_CLEAR" | "VOICE_CHANNEL_EFFECT_RECENT_EMOJI" | "VOICE_CHANNEL_EFFECT_SEND" | "VOICE_CHANNEL_EFFECT_SENT_LOCAL" | "VOICE_CHANNEL_EFFECT_TOGGLE_ANIMATION_TYPE" | "VOICE_CHANNEL_EFFECT_UPDATE_TIME_STAMP" | "VOICE_CHANNEL_SELECT" | "VOICE_CHANNEL_STATUS_UPDATE" | "VOICE_FILTER_APPLIED" | "VOICE_FILTER_APPLY_FAILED" | "VOICE_FILTER_CATALOG_FETCH_FAILED" | "VOICE_FILTER_CATALOG_FETCH_SUCCESS" | "VOICE_FILTER_DEV_TOOLS_SET_UPDATE_TIME" | "VOICE_FILTER_DOWNLOAD_FAILED" | "VOICE_FILTER_DOWNLOAD_PROGRESS" | "VOICE_FILTER_DOWNLOAD_STARTED" | "VOICE_FILTER_FILE_READY" | "VOICE_FILTER_LAGGING" | "VOICE_FILTER_LOOPBACK_TOGGLE" | "VOICE_FILTER_NATIVE_MODULE_STATE_CHANGE" | "VOICE_FILTER_REQUEST_SWITCH" | "VOICE_FILTER_UPDATE_LIMITED_TIME_VOICES" | "VOICE_SERVER_UPDATE" | "VOICE_STATE_UPDATES" | "WAIT_FOR_REMOTE_SESSION" | "WEBHOOKS_FETCHING" | "WEBHOOKS_UPDATE" | "WEBHOOK_CREATE" | "WEBHOOK_DELETE" | "WEBHOOK_UPDATE" | "WELCOME_SCREEN_FETCH_FAIL" | "WELCOME_SCREEN_FETCH_START" | "WELCOME_SCREEN_FETCH_SUCCESS" | "WELCOME_SCREEN_SETTINGS_CLEAR" | "WELCOME_SCREEN_SETTINGS_RESET" | "WELCOME_SCREEN_SETTINGS_UPDATE" | "WELCOME_SCREEN_SUBMIT" | "WELCOME_SCREEN_SUBMIT_FAILURE" | "WELCOME_SCREEN_SUBMIT_SUCCESS" | "WELCOME_SCREEN_UPDATE" | "WELCOME_SCREEN_VIEW" | "WINDOW_FOCUS" | "WINDOW_FULLSCREEN_CHANGE" | "WINDOW_HIDDEN" | "WINDOW_INIT" | "WINDOW_RESIZED" | "WINDOW_UNLOAD" | "WINDOW_VISIBILITY_CHANGE" | "WRITE_CACHES"; ================================================ FILE: packages/discord-types/src/index.d.ts ================================================ export * from "./common"; export * from "./components"; export * from "./flux"; export * from "./fluxEvents"; export * from "./menu"; export * from "./modules"; export * from "./stores"; export * from "./utils"; export * as Webpack from "../webpack"; ================================================ FILE: packages/discord-types/src/menu.d.ts ================================================ import type { ComponentType, CSSProperties, ForwardRefRenderFunction, MouseEvent, PropsWithChildren, ReactNode, UIEvent } from "react"; type RC = ComponentType>>; export interface Menu { Menu: RC<{ navId: string; onClose(): void; className?: string; style?: CSSProperties; hideScroller?: boolean; onSelect?(): void; }>; MenuSeparator: ComponentType; MenuGroup: RC<{ label?: string; }>; MenuItem: RC<{ id: string; label: ReactNode; action?(e: MouseEvent): void; icon?: ComponentType; color?: string; render?: ComponentType; onChildrenScroll?: Function; childRowHeight?: number; listClassName?: string; disabled?: boolean; }>; MenuCheckboxItem: RC<{ id: string; label: string; checked: boolean; action?(e: MouseEvent): void; disabled?: boolean; }>; MenuRadioItem: RC<{ id: string; group: string; label: string; checked: boolean; action?(e: MouseEvent): void; disabled?: boolean; }>; MenuControlItem: RC<{ id: string; interactive?: boolean; label?: string; control: ForwardRefRenderFunction; }>; MenuSliderControl: RC<{ minValue: number, maxValue: number, value: number, onChange(value: number): void, renderValue?(value: number): string, }>; MenuSearchControl: RC<{ query: string; onChange(query: string): void; placeholder?: string; }>; } export interface ContextMenuApi { closeContextMenu(): void; openContextMenu( event: UIEvent, render?: Menu["Menu"], options?: { enableSpellCheck?: boolean; }, renderLazy?: () => Promise ): void; openContextMenuLazy( event: UIEvent, renderLazy?: () => Promise, options?: { enableSpellCheck?: boolean; } ): void; } ================================================ FILE: packages/discord-types/src/modules/CloudUpload.d.ts ================================================ import EventEmitter from "events"; import { CloudUploadPlatform } from "../../enums"; interface BaseUploadItem { platform: CloudUploadPlatform; id?: string; origin?: string; isThumbnail?: boolean; clip?: unknown; } export interface ReactNativeUploadItem extends BaseUploadItem { platform: CloudUploadPlatform.REACT_NATIVE; uri: string; filename?: string; mimeType?: string; durationSecs?: number; waveform?: string; isRemix?: boolean; } export interface WebUploadItem extends BaseUploadItem { platform: CloudUploadPlatform.WEB; file: File; } export type CloudUploadItem = ReactNativeUploadItem | WebUploadItem; export class CloudUpload extends EventEmitter { constructor(item: CloudUploadItem, channelId: string, reactNativeFileIndex?: number); channelId: string; classification: string; clip: unknown; contentHash: unknown; currentSize: number; description: string | null; durationSecs: number | undefined; etag: string | undefined; error: unknown; filename: string; id: string; isImage: boolean; isRemix: boolean | undefined; isThumbnail: boolean; isVideo: boolean; item: { file: File; platform: CloudUploadPlatform; origin: string; }; loaded: number; mimeType: string; origin: string; postCompressionSize: number | undefined; preCompressionSize: number; responseUrl: string; sensitive: boolean; spoiler: boolean; startTime: number; status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED" | "REMOVED_FROM_MSG_DRAFT"; uniqueId: string; uploadedFilename: string; waveform: string | undefined; // there are many more methods than just these but I didn't find them particularly useful upload(): Promise; cancel(): void; delete(): Promise; getSize(): number; maybeConvertToWebP(): Promise; removeFromMsgDraft(): void; } ================================================ FILE: packages/discord-types/src/modules/index.d.ts ================================================ export * from "./CloudUpload"; ================================================ FILE: packages/discord-types/src/stores/AccessibilityStore.d.ts ================================================ import { FluxStore } from ".."; export type ReducedMotionPreference = "auto" | "reduce" | "no-preference"; export type ForcedColorsPreference = "none" | "active"; export type ContrastPreference = "no-preference" | "more" | "less" | "custom"; export type RoleStyle = "username" | "dot" | "hidden"; export interface AccessibilityState { fontSize: number; zoom: number; keyboardModeEnabled: boolean; contrastMode: string; colorblindMode: boolean; lowContrastMode: boolean; saturation: number; contrast: number; desaturateUserColors: boolean; forcedColorsModalSeen: boolean; keyboardNavigationExplainerModalSeen: boolean; messageGroupSpacing: number | null; systemPrefersReducedMotion: ReducedMotionPreference; systemPrefersCrossfades: boolean; prefersReducedMotion: ReducedMotionPreference; systemForcedColors: ForcedColorsPreference; syncForcedColors: boolean; systemPrefersContrast: ContrastPreference; alwaysShowLinkDecorations: boolean; roleStyle: RoleStyle; displayNameStylesEnabled: boolean; submitButtonEnabled: boolean; syncProfileThemeWithUserTheme: boolean; enableCustomCursor: boolean; switchIconsEnabled: boolean; } export class AccessibilityStore extends FluxStore { get fontScale(): number; get fontSize(): number; get isFontScaledUp(): boolean; get isFontScaledDown(): boolean; get fontScaleClass(): string; get zoom(): number; get isZoomedIn(): boolean; get isZoomedOut(): boolean; get keyboardModeEnabled(): boolean; get colorblindMode(): boolean; get lowContrastMode(): boolean; get saturation(): number; get contrast(): number; get desaturateUserColors(): boolean; get forcedColorsModalSeen(): boolean; get keyboardNavigationExplainerModalSeen(): boolean; get messageGroupSpacing(): number; get isMessageGroupSpacingIncreased(): boolean; get isMessageGroupSpacingDecreased(): boolean; get isSubmitButtonEnabled(): boolean; get syncProfileThemeWithUserTheme(): boolean; get systemPrefersReducedMotion(): ReducedMotionPreference; get rawPrefersReducedMotion(): ReducedMotionPreference; get useReducedMotion(): boolean; get systemForcedColors(): ForcedColorsPreference; get syncForcedColors(): boolean; get useForcedColors(): boolean; get systemPrefersContrast(): ContrastPreference; get systemPrefersCrossfades(): boolean; get alwaysShowLinkDecorations(): boolean; get enableCustomCursor(): boolean; get roleStyle(): RoleStyle; get displayNameStylesEnabled(): boolean; get isHighContrastModeEnabled(): boolean; get isSwitchIconsEnabled(): boolean; getUserAgnosticState(): AccessibilityState; } ================================================ FILE: packages/discord-types/src/stores/ActiveJoinedThreadsStore.d.ts ================================================ import { Channel, FluxStore } from ".."; export interface ThreadJoined { channel: Channel; joinTimestamp: number; } export type ThreadsForParent = Record; export type ThreadsForGuild = Record; export type AllActiveJoinedThreads = Record; export interface NewThreadCounts { [parentChannelId: string]: number; } export class ActiveJoinedThreadsStore extends FluxStore { computeAllActiveJoinedThreads(guildId?: string | null): Channel[]; getActiveJoinedRelevantThreadsForGuild(guildId: string): ThreadsForGuild; getActiveJoinedRelevantThreadsForParent(guildId: string, parentChannelId: string): ThreadsForParent; getActiveJoinedThreadsForGuild(guildId: string): ThreadsForGuild; getActiveJoinedThreadsForParent(guildId: string, parentChannelId: string): ThreadsForParent; getActiveJoinedUnreadThreadsForGuild(guildId: string): ThreadsForGuild; getActiveJoinedUnreadThreadsForParent(guildId: string, parentChannelId: string): ThreadsForParent; getActiveThreadCount(guildId: string, parentChannelId: string): number; getActiveUnjoinedThreadsForGuild(guildId: string): ThreadsForGuild; getActiveUnjoinedThreadsForParent(guildId: string, parentChannelId: string): ThreadsForParent; getActiveUnjoinedUnreadThreadsForGuild(guildId: string): ThreadsForGuild; getActiveUnjoinedUnreadThreadsForParent(guildId: string, parentChannelId: string): ThreadsForParent; getAllActiveJoinedThreads(): AllActiveJoinedThreads; getNewThreadCount(guildId: string, parentChannelId: string): number; getNewThreadCountsForGuild(guildId: string): NewThreadCounts; hasActiveJoinedUnreadThreads(guildId: string, parentChannelId: string): boolean; } ================================================ FILE: packages/discord-types/src/stores/ApplicationStore.d.ts ================================================ import { Application, FluxStore } from ".."; export interface ApplicationStoreState { botUserIdToAppUsage: Record; } export interface ApplicationUsage { applicationId: string; lastUsedMs: number; } export class ApplicationStore extends FluxStore { getState(): ApplicationStoreState; getApplication(applicationId: string): Application; getApplicationByName(name: string): Application | undefined; getApplicationLastUpdated(applicationId: string): number | undefined; getGuildApplication(guildId: string, type: number): Application | undefined; getGuildApplicationIds(guildId: string): string[]; getAppIdForBotUserId(botUserId: string): string | undefined; getFetchingOrFailedFetchingIds(): string[]; isFetchingApplication(applicationId: string): boolean; didFetchingApplicationFail(applicationId: string): boolean; } ================================================ FILE: packages/discord-types/src/stores/AuthenticationStore.d.ts ================================================ import { FluxStore } from ".."; export class AuthenticationStore extends FluxStore { /** * Gets the id of the current user */ getId(): string; getSessionId(): string; // This Store has a lot more methods related to everything Auth, but they really should // not be needed, so they are not typed } ================================================ FILE: packages/discord-types/src/stores/CallStore.d.ts ================================================ import { FluxStore } from ".."; export interface Call { channelId: string; messageId: string | null; region: string | null; ringing: string[]; unavailable: boolean; regionUpdated: boolean; } export interface CallStoreState { calls: Record; enqueuedRings: Record; } export class CallStore extends FluxStore { getCall(channelId: string): Call; getCalls(): Call[]; getMessageId(channelId: string): string | null; isCallActive(channelId: string, messageId?: string): boolean; isCallUnavailable(channelId: string): boolean; getInternalState(): CallStoreState; } ================================================ FILE: packages/discord-types/src/stores/ChannelRTCStore.d.ts ================================================ import { FluxStore, User, VoiceState } from ".."; import { ParticipantType, RTCPlatform } from "../../enums"; export type RTCLayout = "normal" | "minimum" | "no-chat" | "full-screen" | "haven"; export type RTCMode = "video" | "voice"; export type RTCLayoutContext = "OVERLAY" | "APP" | "POPOUT" | "CALL_TILE_POPOUT"; export type ParticipantFilterType = "VIDEO" | "STREAM" | "FILTERED" | "SPEAKING" | "ACTIVITY" | "NOT_POPPED_OUT"; export interface StreamResolution { height: number; width: number; } export interface Stream { channelId: string; guildId: string | null; ownerId: string; streamType: string; } export interface BaseParticipant { id: string; type: ParticipantType; isPoppedOut?: boolean; } export interface UserParticipant extends BaseParticipant { type: ParticipantType.USER; user: User; voiceState: VoiceState | null; voicePlatform: RTCPlatform | null; speaking: boolean; voiceDb: number; latched: boolean; lastSpoke: number; soundsharing: boolean; ringing: boolean; userNick: string; // TODO: type userAvatarDecoration: any | null; localVideoDisabled: boolean; userVideo?: boolean; streamId?: string; } export interface StreamParticipant extends BaseParticipant { type: ParticipantType.STREAM | ParticipantType.HIDDEN_STREAM; user: User; userNick: string; userVideo: boolean; stream: Stream; maxResolution?: StreamResolution; maxFrameRate?: number; streamId?: string; } export interface ActivityParticipant extends BaseParticipant { type: ParticipantType.ACTIVITY; applicationId: string; activityType: number; activityUrl: string; participants: string[]; guildId: string | null; sortKey: string; } export type Participant = UserParticipant | StreamParticipant | ActivityParticipant; export interface SelectedParticipantStats { view_mode_grid_duration_ms?: number; view_mode_focus_duration_ms?: number; view_mode_toggle_count?: number; } export interface ChannelRTCState { voiceParticipantsHidden: Record; } export class ChannelRTCStore extends FluxStore { getActivityParticipants(channelId: string): ActivityParticipant[]; getAllChatOpen(): Record; getChatOpen(channelId: string): boolean; getFilteredParticipants(channelId: string): Participant[]; getGuildRingingUsers(channelId: string): Set; getLayout(channelId: string, context?: RTCLayoutContext): RTCLayout; getMode(channelId: string): RTCMode; getParticipant(channelId: string, participantId: string): Participant | null; getParticipants(channelId: string): Participant[]; getParticipantsListOpen(channelId: string): boolean; getParticipantsOpen(channelId: string): boolean; getParticipantsVersion(channelId: string): number; getSelectedParticipant(channelId: string): Participant | null; getSelectedParticipantId(channelId: string): string | null; getSelectedParticipantStats(channelId: string): SelectedParticipantStats; getSpeakingParticipants(channelId: string): UserParticipant[]; getStageStreamSize(channelId: string): StreamResolution | undefined; getStageVideoLimitBoostUpsellDismissed(channelId: string): boolean | undefined; getState(): ChannelRTCState; getStreamParticipants(channelId: string): StreamParticipant[]; getUserParticipantCount(channelId: string): number; getVideoParticipants(channelId: string): UserParticipant[]; getVoiceParticipantsHidden(channelId: string): boolean; isFullscreenInContext(): boolean; isParticipantPoppedOut(channelId: string, participantId: string): boolean; } ================================================ FILE: packages/discord-types/src/stores/ChannelStore.d.ts ================================================ import { Channel, FluxStore } from ".."; export class ChannelStore extends FluxStore { getChannel(channelId: string): Channel; getBasicChannel(channelId: string): Channel | undefined; hasChannel(channelId: string): boolean; getChannelIds(guildId?: string | null): string[]; getMutableBasicGuildChannelsForGuild(guildId: string): Record; getMutableGuildChannelsForGuild(guildId: string): Record; getAllThreadsForGuild(guildId: string): Channel[]; getAllThreadsForParent(channelId: string): Channel[]; getSortedLinkedChannelsForGuild(guildId: string): Channel[]; getDMFromUserId(userId: string): string; getDMChannelFromUserId(userId: string): Channel | undefined; getDMUserIds(): string[]; getMutableDMsByUserIds(): Record; getMutablePrivateChannels(): Record; getSortedPrivateChannels(): Channel[]; getGuildChannelsVersion(guildId: string): number; getPrivateChannelsVersion(): number; getInitialOverlayState(): Record; getDebugInfo(): { loadedGuildIds: string[]; pendingGuildLoads: string[]; guildSizes: string[]; }; } ================================================ FILE: packages/discord-types/src/stores/DraftStore.d.ts ================================================ import { FluxStore } from ".."; import { DraftType } from "../../enums"; export interface Draft { timestamp: number; draft: string; } export interface ThreadSettingsDraft { timestamp: number; parentMessageId?: string; name?: string; isPrivate?: boolean; parentChannelId?: string; location?: string; } export type ChannelDrafts = { [DraftType.ThreadSettings]: ThreadSettingsDraft; } & { [key in Exclude]: Draft; }; export type UserDrafts = Partial>; export type DraftState = Partial>; export class DraftStore extends FluxStore { getState(): DraftState; getRecentlyEditedDrafts(type: DraftType): Array; getDraft(channelId: string, type: DraftType): string; getThreadSettings(channelId: string): ThreadSettingsDraft | null | undefined; getThreadDraftWithParentMessageId(parentMessageId: string): ThreadSettingsDraft | null | undefined; } ================================================ FILE: packages/discord-types/src/stores/EmojiStore.d.ts ================================================ import { Channel, CustomEmoji, Emoji, FluxStore } from ".."; import { EmojiIntention, LoadState } from "../../enums"; /** Emoji picker category names. */ export type EmojiCategory = | "top guild emoji" | "favorites" | "recent" | "custom" | "people" | "nature" | "food" | "activity" | "travel" | "objects" | "symbols" | "flags"; /** * Tracks usage statistics for a single emoji to compute frecency scores. */ export interface EmojiUsageRecord { /** Total number of times this emoji has been used. */ totalUses: number; /** Array of recent usage timestamps in milliseconds. */ recentUses: number[]; /** Computed frecency score combining frequency and recency, -1 when dirty. */ frecency: number; /** Raw score before frecency computation. */ score: number; } /** * Options for tracking emoji usage. */ export interface TrackOptions { /** Timestamp of the usage in milliseconds. */ timestamp?: number; /** Number of uses since last track call. */ usesSinceLastTrack?: number; } /** * Frecency tracker for emoji usage, combines frequency and recency to rank emojis. * Used by both regular emoji picker and reaction emoji picker. */ export interface EmojiFrecency { /** True when data has been modified and needs recomputation. */ dirty: boolean; /** Cached array of frequently used emojis after computation. */ _frequently: Emoji[]; /** Maximum number of frequently used items to track (default 42). */ numFrequentlyItems: number; /** Maximum number of recent usage samples to keep per emoji (default 10). */ maxSamples: number; /** Computes bonus score for frecency calculation (returns 100). */ computeBonus: () => number; /** * Computes weight multiplier based on recency index. * Returns 100 for index <= 3, 70 for <= 15, 50 for <= 30, 30 for <= 45, 10 for <= 80. */ computeWeight: (index: number) => number; /** * Computes frecency score for an emoji. * @param totalUses Total number of times emoji was used. * @param score Raw score value. * @param config Configuration for frecency calculation. */ computeFrecency: (totalUses: number, score: number, config: { /** Number of recent uses to consider. */ numOfRecentUses?: number; /** Maximum total uses to cap at. */ maxTotalUse?: number; }) => number; /** Whether to calculate max total use dynamically. */ calculateMaxTotalUse: boolean; /** * Looks up an emoji by name or id. * @param name Emoji name or id to look up. * @returns The emoji if found. */ lookupKey: (name: string) => Emoji | undefined; /** Usage history keyed by emoji name (for unicode) or id (for custom). */ usageHistory: Record; /** Callback invoked after frecency computation completes. */ afterCompute: () => void; /** * Overwrites the usage history with new data. * @param history New usage history to set. * @param pendingUsages Pending usages to track after overwriting. */ overwriteHistory(history: Record | null, pendingUsages?: PendingUsage[]): void; /** Marks the frecency data as dirty, requiring recomputation. */ markDirty(): void; /** Returns whether the frecency data needs recomputation. */ isDirty(): boolean; /** * Tracks usage of an emoji. * @param key Emoji name or id. * @param options Track options including timestamp. */ track(key: string, options?: TrackOptions): void; /** * Gets the usage record for an emoji, computing if dirty. * @param key Emoji name or id. * @returns The usage record or null if not found. */ getEntry(key: string): EmojiUsageRecord | null; /** * Gets the score for an emoji. * @param key Emoji name or id. * @returns The score or null if not found. */ getScore(key: string): number | null; /** * Gets the frecency for an emoji. * @param key Emoji name or id. * @returns The frecency or null if not found. */ getFrecency(key: string): number | null; /** Recomputes frecency scores for all emojis. */ compute(): void; /** Gets the frequently used emojis, computing if necessary. */ get frequently(): Emoji[]; } /** * Container for a guild's emoji collection with usability checks. */ export interface GuildEmojis { /** Guild id this emoji collection belongs to. */ id: string; /** User id for permission checks. */ _userId: string; /** Internal emoji array. */ _emojis: CustomEmoji[]; /** Fast lookup map of emoji id to emoji. */ _emojiMap: Record; /** Internal emoticons array. */ _emoticons: Emoticon[]; /** Internal usable emojis cache. */ _usableEmojis: CustomEmoji[]; /** Whether user can see server subscription IAP. */ _canSeeServerSubIAP: boolean; /** All custom emojis in this guild. */ get emojis(): CustomEmoji[]; /** Custom emojis the current user can use in this guild. */ get usableEmojis(): CustomEmoji[]; /** Text emoticons configured for this guild. */ get emoticons(): Emoticon[]; /** * Gets an emoji by id from this guild. * @param id Emoji id to look up. */ getEmoji(id: string): CustomEmoji | undefined; /** * Gets a usable emoji by id from this guild. * @param id Emoji id to look up. */ getUsableEmoji(id: string): CustomEmoji | undefined; /** * Checks if an emoji is usable by the current user. * @param emoji Emoji to check. */ isUsable(emoji: CustomEmoji): boolean; /** Returns array of all emoji ids in this guild. */ emojiIds(): string[]; } /** * Text emoticon that can be converted to emoji. */ export interface Emoticon { /** Names/aliases for this emoticon. */ names: string[]; /** The text representation (e.g. ":)" or ":D"). */ surrogates: string; /** Whether this emoticon should use sprite sheet rendering. */ useSpriteSheet: boolean; } /** * Pending emoji usage waiting to be recorded. */ export interface PendingUsage { /** Emoji key (name for unicode, id for custom). */ key: string; /** Timestamp in milliseconds when usage occurred. */ timestamp: number; } /** * Serializable state for EmojiStore persistence. */ export interface EmojiStoreState { /** Pending emoji usages not yet committed. */ pendingUsages: PendingUsage[]; /** Pending reaction emoji usages not yet committed. */ emojiReactionPendingUsages: PendingUsage[]; /** Guild ids with expanded emoji sections in picker. */ expandedSectionsByGuildIds: Set; } /** * Context for emoji disambiguation, caching resolved emoji data for a guild context. * Provides fast lookup of emojis without triggering data fetches. */ export interface DisambiguatedEmojiContext { /** User's favorite emojis or null if not loaded. */ favorites: Emoji[] | null; /** Set of favorite emoji names and ids for fast lookup, or null if not loaded. */ favoriteNamesAndIds: Set | null; /** Top emojis for the current guild or null if not loaded. */ topEmojis: Emoji[] | null; /** Current guild id context or null for DMs. */ guildId: string | null; /** Regex-escaped emoticon names for matching. */ escapedEmoticonNames: string; /** All emojis with disambiguation applied (unique names). */ disambiguatedEmoji: Emoji[]; /** Compiled regex for matching emoticons or null if none. */ emoticonRegex: RegExp | null; /** Frequently used emojis or null if not loaded. */ frequentlyUsed: Emoji[] | null; /** Frequently used reaction emojis or null if not loaded. */ frequentlyUsedReactionEmojis: Emoji[] | null; /** Set of frequently used reaction emoji names and ids, or null if not loaded. */ frequentlyUsedReactionNamesAndIds: Set | null; /** Unicode emoji aliases keyed by alias name, maps to primary name. */ unicodeAliases: Record; /** Custom emojis keyed by emoji id. */ customEmojis: Record; /** Custom emojis grouped by guild id. */ groupedCustomEmojis: Record; /** Emoticons keyed by name for fast lookup. */ emoticonsByName: Record; /** All emojis keyed by name for fast lookup. */ emojisByName: Record; /** Custom emojis keyed by id for fast lookup. */ emojisById: Record; /** Newly added emojis grouped by guild id. */ newlyAddedEmoji: Record; /** * Checks if an emoji is a favorite without triggering a fetch. * @param emoji Emoji to check. */ isFavoriteEmojiWithoutFetchingLatest(emoji: Emoji): boolean; /** Gets favorite emojis without triggering a fetch. */ get favoriteEmojisWithoutFetchingLatest(): Emoji[]; /** Gets all disambiguated emojis. */ getDisambiguatedEmoji(): Emoji[]; /** Gets all custom emojis keyed by name. */ getCustomEmoji(): Record; /** Gets custom emojis grouped by guild id. */ getGroupedCustomEmoji(): Record; /** * Gets an emoji by name. * @param name Emoji name to look up. */ getByName(name: string): Emoji | undefined; /** * Gets an emoticon by name. * @param name Emoticon name to look up. */ getEmoticonByName(name: string): Emoticon | undefined; /** * Gets an emoji by id. * @param id Emoji id to look up. */ getById(id: string): Emoji | undefined; /** * Gets the regex for matching custom emoticons. * @returns RegExp or null if no emoticons. */ getCustomEmoticonRegex(): RegExp | null; /** Gets frequently used emojis without triggering a fetch. */ getFrequentlyUsedEmojisWithoutFetchingLatest(): Emoji[]; /** Rebuilds the frequently used reaction emojis cache and returns it. */ rebuildFrequentlyUsedReactionsEmojisWithoutFetchingLatest(): { frequentlyUsedReactionEmojis: Emoji[]; frequentlyUsedReactionNamesAndIds: Set; }; /** Gets frequently used reaction emojis without triggering a fetch. */ getFrequentlyUsedReactionEmojisWithoutFetchingLatest(): Emoji[]; /** * Checks if an emoji is frequently used for reactions. * @param emoji Emoji to check. */ isFrequentlyUsedReactionEmojiWithoutFetchingLatest(emoji: Emoji): boolean; /** Rebuilds the favorite emojis cache and returns it. */ rebuildFavoriteEmojisWithoutFetchingLatest(): { favorites: Emoji[]; favoriteNamesAndIds: Set; }; /** * Gets emojis in priority order (favorites, frequent, top) without fetching. * @returns Array of emojis in priority order. */ getEmojiInPriorityOrderWithoutFetchingLatest(): Emoji[]; /** * Gets top emojis for a guild without triggering a fetch. * @param guildId Guild id to get top emojis for. */ getTopEmojiWithoutFetchingLatest(guildId: string): Emoji[]; /** * Gets newly added emojis for a specific guild. * @param guildId Guild id. */ getNewlyAddedEmojiForGuild(guildId: string): CustomEmoji[]; /** Gets escaped custom emoticon names for regex matching. */ getEscapedCustomEmoticonNames(): string; /** * Checks if a name matches an emoji name chain. * @param name Name to match. */ nameMatchesChain(name: string): boolean; } /** * Search options for emoji search. */ export interface EmojiSearchOptions { /** Channel context for permission checks. */ channel: Channel; /** Search query string. */ query: string; /** Maximum number of results to return. */ count?: number; /** Intention for using the emoji, affects availability filtering. */ intention: EmojiIntention; /** Whether to include emojis from guilds the user is not in. */ includeExternalGuilds?: boolean; /** Whether to only show unicode emojis in results. */ showOnlyUnicode?: boolean; /** * Custom comparator for matching emoji names. * @param name Emoji name to compare. * @returns True if the name matches. */ matchComparator?(name: string): boolean; } /** * Search results split by availability. */ export interface EmojiSearchResults { /** Emojis that are locked (require Nitro or permissions). */ locked: Emoji[]; /** Emojis that are available for use. */ unlocked: Emoji[]; } /** * Metadata about top emojis for a guild. */ export interface TopEmojisMetadata { /** Array of top emoji ids. */ emojiIds: string[]; /** Time-to-live for this data in milliseconds. */ topEmojisTTL: number; } /** * Flux store managing all emoji data including custom guild emojis, * unicode emojis, favorites, frecency, and search functionality. */ export class EmojiStore extends FluxStore { /** Array of emoji category names for the picker. */ get categories(): EmojiCategory[]; /** * Current skin tone modifier surrogate for emoji diversity. * Empty string for default yellow, or skin tone modifier (🏻🏼🏽🏾🏿). */ get diversitySurrogate(): string; /** Frecency tracker for emoji picker usage. */ get emojiFrecencyWithoutFetchingLatest(): EmojiFrecency; /** Frecency tracker for reaction emoji usage. */ get emojiReactionFrecencyWithoutFetchingLatest(): EmojiFrecency; /** Guild ids with expanded emoji sections in picker. */ get expandedSectionsByGuildIds(): Set; /** Current load state of the emoji store. */ get loadState(): LoadState; /** * Gets a custom emoji by its id. * @param id Emoji id to look up. * @returns The custom emoji if found. */ getCustomEmojiById(id?: string | null): CustomEmoji | undefined; /** * Gets a usable custom emoji by its id. * @param id Emoji id to look up. * @returns The custom emoji if found and usable by current user. */ getUsableCustomEmojiById(id?: string | null): CustomEmoji | undefined; /** * Gets all guild emoji collections keyed by guild id. * @returns Record of guild id to GuildEmojis. */ getGuilds(): Record; /** * Gets all custom emojis for a guild. * @param guildId Guild id to get emojis for, or null for all guilds. * @returns Array of custom emojis. */ getGuildEmoji(guildId?: string | null): CustomEmoji[]; /** * Gets usable custom emojis for a guild. * @param guildId Guild id to get emojis for. * @returns Array of usable custom emojis. */ getUsableGuildEmoji(guildId?: string | null): CustomEmoji[]; /** * Gets newly added emojis for a guild. * @param guildId Guild id to get emojis for. * @returns Array of newly added custom emojis. */ getNewlyAddedEmoji(guildId?: string | null): CustomEmoji[]; /** * Gets top emojis for a guild based on usage. * @param guildId Guild id to get emojis for. * @returns Array of top custom emojis. */ getTopEmoji(guildId?: string | null): CustomEmoji[]; /** * Gets metadata about top emojis for a guild. * @param guildId Guild id to get metadata for. * @returns Metadata including emoji ids and TTL, or undefined if not cached. */ getTopEmojisMetadata(guildId?: string | null): TopEmojisMetadata | undefined; /** * Checks if user has any favorite emojis in a guild context. * @param guildId Guild id to check. * @returns True if user has favorites. */ hasFavoriteEmojis(guildId?: string | null): boolean; /** * Checks if there are pending emoji usages to be recorded. * @returns True if there are pending usages. */ hasPendingUsage(): boolean; /** * Checks if user has any usable custom emojis in any guild. * @returns True if user has usable emojis. */ hasUsableEmojiInAnyGuild(): boolean; /** Internal method for ordering search results. */ getSearchResultsOrder(...args: any[]): any; /** * Gets the serializable state for persistence. * @returns Current store state. */ getState(): EmojiStoreState; /** * Searches for emojis without triggering data fetches. * @param options Search options including query and filters. * @returns Search results split by locked/unlocked. */ searchWithoutFetchingLatest(options: EmojiSearchOptions): EmojiSearchResults; /** * Gets the disambiguated emoji context for a guild. * @param guildId Guild id to get context for, or null/undefined for global context. */ getDisambiguatedEmojiContext(guildId?: string | null): DisambiguatedEmojiContext; } ================================================ FILE: packages/discord-types/src/stores/FluxStore.d.ts ================================================ import { FluxDispatcher, FluxEvents } from ".."; type Callback = () => void; type SyncCallback = () => boolean | void; /* For some reason, this causes type errors when you try to destructure it: ```ts interface FluxEvent { type: FluxEvents; [key: string]: any; } ``` */ export type FluxEvent = any; export type ActionHandler = (event: FluxEvent) => void; /** keyed by FluxEvents action type */ export type ActionHandlers = Partial>; /** * Base class for all Discord Flux stores. * Provides change notification, action handling, and store synchronization. */ export class FluxStore { /** * @param dispatcher the FluxDispatcher instance to register with * @param actionHandlers handlers for Flux actions, keyed by action type * @param band priority band for action handling (default 2), lower runs first */ constructor(dispatcher: FluxDispatcher, actionHandlers?: ActionHandlers, band?: number); /** returns displayName if set, otherwise constructor.name */ getName(): string; /** adds listener to _changeCallbacks, invoked before react listeners and triggers syncWith processing */ addChangeListener(callback: Callback): void; /** * adds a listener that auto-removes when callback returns false. * @param callback returning false removes the listener * @param preemptive if true (default), calls callback immediately and skips adding if it returns false */ addConditionalChangeListener(callback: () => boolean, preemptive?: boolean): void; /** adds listener to _reactChangeCallbacks, invoked after all regular change listeners complete */ addReactChangeListener(callback: Callback): void; removeChangeListener(callback: Callback): void; removeReactChangeListener(callback: Callback): void; /** called by dispatcher after action handlers run, marks changed if listeners exist and may resume paused dispatch */ doEmitChanges(event: FluxEvent): void; /** marks store as changed for batched listener notification */ emitChange(): void; /** unique token identifying this store in the dispatcher */ getDispatchToken(): string; /** override to set up initial state, called once by initializeIfNeeded */ initialize(): void; /** calls initialize() if not already initialized, adds performance mark if init takes >5ms */ initializeIfNeeded(): void; /** * sets callback to determine if changes must emit during paused dispatch. * @param callback if omitted, defaults to () => true (always emit) */ mustEmitChanges(callback?: ActionHandler): void; /** * registers additional action handlers after construction. * @param actionHandlers handlers keyed by action type * @param band priority band, lower runs first */ registerActionHandlers(actionHandlers: ActionHandlers, band?: number): void; /** * syncs this store with other stores, re-emitting when they change. * without timeout: synchronous, callback runs during emitNonReactOnce. * with timeout: debounced, adds regular change listener to each source store. * @param stores stores to sync with * @param callback returning false skips emitChange on this store * @param timeout if provided, debounces the sync callback */ syncWith(stores: FluxStore[], callback: SyncCallback, timeout?: number): void; /** adds dispatcher dependencies so this store's handlers run after the specified stores */ waitFor(...stores: FluxStore[]): void; /** initializes all registered stores, called once at app startup */ static initialize(): void; /** clears all registered stores and destroys the change listener system */ static destroy(): void; /** returns all registered FluxStore instances */ static getAll(): FluxStore[]; } ================================================ FILE: packages/discord-types/src/stores/FriendsStore.d.ts ================================================ import { Activity, FluxStore, Guild, User } from ".."; import { GiftIntentType, RelationshipType } from "../../enums"; export type FriendsSection = "ADD_FRIEND" | "ALL" | "ONLINE" | "PENDING" | "PENDING_IGNORED" | "SPAM" | "SUGGESTIONS"; export type StatusType = "online" | "offline" | "idle" | "dnd" | "invisible" | "streaming" | "unknown"; export interface ApplicationStream { channelId: string; guildId: string | null; ownerId: string; streamType: string; } export interface FriendsRow { key: string; userId: string; /** * 99 means contact based friend suggestions from FriendSuggestionStore, * shown in SUGGESTIONS tab. different from RelationshipType.SUGGESTION * which is for implicit suggestions in RelationshipStore */ type: RelationshipType | 99; status: StatusType; isMobile: boolean; activities: Activity[]; applicationStream: ApplicationStream | null; user: User | null; usernameLower: string | null; mutualGuildsLength: number; mutualGuilds: Guild[]; nickname: string | null; spam: boolean; giftIntentType: GiftIntentType | undefined; ignoredUser: boolean; applicationId: string | undefined; isGameRelationship: boolean; comparator: [RelationshipType | 99, string | null]; } export interface RelationshipCounts { [RelationshipType.FRIEND]: number; [RelationshipType.INCOMING_REQUEST]: number; [RelationshipType.OUTGOING_REQUEST]: number; [RelationshipType.BLOCKED]: number; /** contact based friend suggestions from FriendSuggestionStore */ 99: number; } export interface FriendsRows { _rows: FriendsRow[]; reset(): FriendsRows; clone(): FriendsRows; update(updater: (userId: string) => Partial): boolean; filter(section: FriendsSection, searchQuery?: string | null): FriendsRow[]; getRelationshipCounts(): RelationshipCounts; } export interface FriendsState { fetching: boolean; section: FriendsSection; rows: FriendsRows; } export class FriendsStore extends FluxStore { getState(): FriendsState; } ================================================ FILE: packages/discord-types/src/stores/GuildChannelStore.d.ts ================================================ import { Channel, FluxStore, ThreadJoined } from ".."; import { ChannelType } from "../../enums"; export interface ChannelWithComparator { channel: Channel; comparator: number; } export interface GuildChannels { [ChannelType.GUILD_CATEGORY]: ChannelWithComparator[]; id: string; SELECTABLE: ChannelWithComparator[] | ThreadJoined[]; VOCAL: ChannelWithComparator[]; count: number; } export interface ChannelNameDisambiguation { id: string; name: string; } export class GuildChannelStore extends FluxStore { getAllGuilds(): Record; getChannels(guildId: string): GuildChannels; getDefaultChannel(guildId: string): Channel | null; getDirectoryChannelIds(guildId: string): string[]; getFirstChannel( guildId: string, predicate: (item: ChannelWithComparator) => boolean, includeVocal?: boolean ): Channel | null; getFirstChannelOfType( guildId: string, predicate: (item: ChannelWithComparator) => boolean, type: "SELECTABLE" | "VOCAL" | ChannelType.GUILD_CATEGORY ): Channel | null; getSFWDefaultChannel(guildId: string): Channel | null; getSelectableChannelIds(guildId: string): string[]; getSelectableChannels(guildId: string): ChannelWithComparator[]; getTextChannelNameDisambiguations(guildId: string): Record; getVocalChannelIds(guildId: string): string[]; hasCategories(guildId: string): boolean; hasChannels(guildId: string): boolean; hasElevatedPermissions(guildId: string): boolean; hasSelectableChannel(guildId: string, channelId: string): boolean; } ================================================ FILE: packages/discord-types/src/stores/GuildMemberCountStore.d.ts ================================================ import { FluxStore } from ".."; export class GuildMemberCountStore extends FluxStore { getMemberCounts(): Record; getMemberCount(guildId: string): number; getOnlineCount(guildId: string): number; } ================================================ FILE: packages/discord-types/src/stores/GuildMemberStore.d.ts ================================================ import { FluxStore, GuildMember } from ".."; export interface PendingRoleUpdates { added: string[]; removed: string[]; } export class GuildMemberStore extends FluxStore { /** @returns Format: [guildId-userId: Timestamp (string)] */ getCommunicationDisabledUserMap(): Record; getCommunicationDisabledVersion(): number; getMutableAllGuildsAndMembers(): Record>; getMember(guildId: string, userId: string): GuildMember | null; getTrueMember(guildId: string, userId: string): GuildMember | null; getMemberIds(guildId: string): string[]; getMembers(guildId: string): GuildMember[]; getMemberVersion(): number; getMemberRoleWithPendingUpdates(guildId: string, userId: string): string[]; getPendingRoleUpdates(guildId: string): PendingRoleUpdates; memberOf(userId: string): string[]; getCachedSelfMember(guildId: string): GuildMember | null; getSelfMember(guildId: string): GuildMember | null; getSelfMemberJoinedAt(guildId: string): Date | null; getNick(guildId: string, userId: string): string | null; getNicknameGuildsMapping(userId: string): Record; getNicknames(userId: string): string[]; isMember(guildId: string, userId: string): boolean; isGuestOrLurker(guildId: string, userId: string): boolean; isCurrentUserGuest(guildId: string): boolean; } ================================================ FILE: packages/discord-types/src/stores/GuildRoleStore.d.ts ================================================ import { FluxStore, Guild, Role } from ".."; export class GuildRoleStore extends FluxStore { getRolesSnapshot(guildId: string): Record; getSortedRoles(guildId: string): Role[]; getEveryoneRole(guild: Guild): Role; getManyRoles(guildId: string, roleIds: string[]): Role[]; getNumRoles(guildId: string): number; getRole(guildId: string, roleId: string): Role; getUnsafeMutableRoles(guildId: string): Record; serializeAllGuildRoles(): Array<{ partitionKey: string; values: Record; }>; } ================================================ FILE: packages/discord-types/src/stores/GuildScheduledEventStore.d.ts ================================================ import { FluxStore } from ".."; import { GuildScheduledEventEntityType, GuildScheduledEventPrivacyLevel, GuildScheduledEventStatus } from "../../enums"; export interface GuildScheduledEventEntityMetadata { location?: string; } export interface GuildScheduledEventRecurrenceRule { start: string; end: string | null; frequency: number; interval: number; byWeekday: number[] | null; byNWeekday: { n: number; day: number; }[] | null; byMonth: number[] | null; byMonthDay: number[] | null; byYearDay: number[] | null; count: number | null; } export interface GuildScheduledEvent { id: string; guild_id: string; channel_id: string | null; creator_id: string | null; name: string; description: string | null; image: string | null; scheduled_start_time: string; scheduled_end_time: string | null; privacy_level: GuildScheduledEventPrivacyLevel; status: GuildScheduledEventStatus; entity_type: GuildScheduledEventEntityType; entity_id: string | null; entity_metadata: GuildScheduledEventEntityMetadata | null; sku_ids: string[]; recurrence_rule: GuildScheduledEventRecurrenceRule | null; // TODO: type guild_scheduled_event_exceptions: any[]; auto_start: boolean; } export interface GuildScheduledEventRsvp { guildScheduledEventId: string; userId: string; interested: boolean; } export interface GuildScheduledEventUsers { // TODO: finish typing [userId: string]: any; } export class GuildScheduledEventStore extends FluxStore { getGuildScheduledEvent(eventId: string): GuildScheduledEvent | null; getGuildScheduledEventsForGuild(guildId: string): GuildScheduledEvent[]; getGuildScheduledEventsByIndex(status: GuildScheduledEventStatus): GuildScheduledEvent[]; getGuildEventCountByIndex(status: GuildScheduledEventStatus): number; getRsvpVersion(): number; getRsvp(eventId: string, recurrenceId: string | null, userId: string | null): GuildScheduledEventRsvp | null; isInterestedInEventRecurrence(eventId: string, recurrenceId: string | null): boolean; getUserCount(eventId: string, recurrenceId: string | null): number; hasUserCount(eventId: string, recurrenceId: string | null): boolean; isActive(eventId: string): boolean; getActiveEventByChannel(channelId: string): GuildScheduledEvent | null; getUsersForGuildEvent(eventId: string, recurrenceId: string | null): GuildScheduledEventUsers; } ================================================ FILE: packages/discord-types/src/stores/GuildStore.d.ts ================================================ import { Guild, FluxStore } from ".."; export class GuildStore extends FluxStore { getGuild(guildId: string): Guild; getGuildCount(): number; getGuilds(): Record; getGuildsArray(): Guild[]; getGuildIds(): string[]; } ================================================ FILE: packages/discord-types/src/stores/InstantInviteStore.d.ts ================================================ import { FluxStore } from ".."; import { Invite } from "./InviteStore"; export interface FriendInvite extends Invite { max_age: number; max_uses: number; uses: number; created_at: string; revoked?: boolean; } export class InstantInviteStore extends FluxStore { getInvite(channelId: string): Invite; getFriendInvite(): FriendInvite | null; getFriendInvitesFetching(): boolean; canRevokeFriendInvite(): boolean; } ================================================ FILE: packages/discord-types/src/stores/InviteStore.d.ts ================================================ import { Channel, FluxStore, Guild, User } from ".."; export interface Invite { code: string; guild: Guild | null; channel: Channel | null; inviter: User | null; approximate_member_count?: number; approximate_presence_count?: number; expires_at?: string | null; flags?: number; target_type?: number; target_user?: User; // TODO: type these target_application?: any; stage_instance?: any; guild_scheduled_event?: any; } export class InviteStore extends FluxStore { getInvite(code: string): Invite; // TODO: finish typing getInviteError(code: string): any | undefined; getInvites(): Record; getInviteKeyForGuildId(guildId: string): string | undefined; getFriendMemberIds(code: string): string[] | undefined; } ================================================ FILE: packages/discord-types/src/stores/LocaleStore.d.ts ================================================ import { FluxStore } from ".."; export class LocaleStore extends FluxStore { get locale(): string; get systemLocale(): string; } ================================================ FILE: packages/discord-types/src/stores/MediaEngineStore.d.ts ================================================ import { FluxStore } from ".."; /** Context for media engine settings. */ export type MediaEngineContextType = "default" | "stream"; /** Audio/video device type identifiers. */ export type DeviceType = "audioinput" | "audiooutput" | "videoinput"; /** Voice activation mode for microphone input. */ export type VoiceMode = "PUSH_TO_TALK" | "VOICE_ACTIVITY"; /** WebRTC connection state. */ export type ConnectionState = | "DISCONNECTED" | "CONNECTING" | "CONNECTED" | "NO_ROUTE" | "ICE_CHECKING" | "DTLS_CONNECTING"; /** Video toggle state indicating why video was enabled/disabled. */ export type VideoToggleState = | "NONE" | "video_manual_disable" | "video_manual_enable" | "video_manual_reenable" | "video_auto_disable" | "video_auto_enable" | "video_auto_downgrade" | "video_auto_upgrade"; /** Quality override for video streams. */ export type VideoQualityOverride = "no_override" | "high" | "low"; /** Audio processing subsystem. */ export type AudioSubsystem = "legacy" | "standard" | "experimental" | "automatic"; /** Media stream types. */ export type MediaType = "audio" | "video" | "screen" | "test"; /** Keyframe interval calculation mode. */ export type KeyframeIntervalMode = "fixed" | "source"; /** Bandwidth estimation algorithm type. */ export type BandwidthEstimationType = "remb"; /** Media engine implementation type. */ export type MediaEngineType = "NATIVE" | "WEBRTC" | "DUMMY"; /** Desktop capture source types. */ export type DesktopSourceType = "WINDOW" | "SCREEN"; /** HDR capture mode for screen sharing. */ export type HdrCaptureMode = "never" | "always" | "auto"; /** Input profile type for voice settings. */ export type InputProfile = "DEFAULT" | "CUSTOM"; /** Media engine feature flags for capability checking. */ export type MediaEngineFeature = | "AUTO_ENABLE" | "ATTENUATION" | "AUDIO_INPUT_DEVICE" | "AUDIO_OUTPUT_DEVICE" | "VOICE_PROCESSING" | "QOS" | "NATIVE_PING" | "LEGACY_AUDIO_SUBSYSTEM" | "EXPERIMENTAL_AUDIO_SUBSYSTEM" | "AUTOMATIC_AUDIO_SUBSYSTEM" | "AUDIO_SUBSYSTEM_DEFERRED_SWITCH" | "AUDIO_BYPASS_SYSTEM_INPUT_PROCESSING" | "DEBUG_LOGGING" | "AUTOMATIC_VAD" | "VOICE_PANNING" | "DIAGNOSTICS" | "VIDEO" | "DESKTOP_CAPTURE" | "DESKTOP_CAPTURE_FORMAT" | "DESKTOP_CAPTURE_APPLICATIONS" | "SOUNDSHARE" | "LOOPBACK" | "VIDEO_HOOK" | "EXPERIMENTAL_SOUNDSHARE" | "WUMPUS_VIDEO" | "ELEVATED_HOOK" | "HYBRID_VIDEO" | "REMOTE_LOCUS_NETWORK_CONTROL" | "SCREEN_PREVIEWS" | "WINDOW_PREVIEWS" | "AUDIO_DEBUG_STATE" | "AEC_DUMP" | "DISABLE_VIDEO" | "CONNECTION_REPLAY" | "SIMULCAST" | "RTC_REGION_RANKING" | "ELECTRON_VIDEO" | "MEDIAPIPE" | "FIXED_KEYFRAME_INTERVAL" | "SAMPLE_PLAYBACK" | "FIRST_FRAME_CALLBACK" | "REMOTE_USER_MULTI_STREAM" | "NOISE_SUPPRESSION" | "NOISE_CANCELLATION" | "VOICE_FILTERS" | "AUTOMATIC_GAIN_CONTROL" | "CLIPS" | "SPEED_TEST" | "IMAGE_QUALITY_MEASUREMENT" | "GO_LIVE_HARDWARE" | "SCREEN_CAPTURE_KIT" | "SCREEN_SOUNDSHARE" | "NATIVE_SCREENSHARE_PICKER" | "MLS_PAIRWISE_FINGERPRINTS" | "OFFLOAD_ADM_CONTROLS" | "SIDECHAIN_COMPRESSION" | "VAAPI" | "GAMESCOPE_CAPTURE" | "ASYNC_VIDEO_INPUT_DEVICE_INIT" | "ASYNC_CLIPS_SOURCE_DEINIT" | "PORT_AWARE_LATENCY_TESTING"; /** Events emitted by the media engine. */ export type MediaEngineEvent = | "Destroy" | "Silence" | "Connection" | "DeviceChange" | "VolumeChange" | "VoiceActivity" | "WatchdogTimeout" | "AudioPermission" | "VideoPermission" | "DesktopSourceEnd" | "ConnectionStats" | "VideoInputInitialized" | "AudioInputInitialized" | "ClipsRecordingRestartNeeded" | "ClipsInitFailure" | "ClipsRecordingEnded" | "NativeScreenSharePickerUpdate" | "NativeScreenSharePickerCancel" | "NativeScreenSharePickerError" | "AudioDeviceModuleError" | "VoiceFiltersFailed" | "VideoCodecError" | "VoiceQueueMetrics" | "SystemMicrophoneModeChange" | "SelectedDeviceChange"; /** * Audio input or output device. */ export interface AudioDevice { /** unique device identifier from the system. */ id: string; /** device index in enumeration, -1 for default device. */ index: number; /** human readable device name. */ name: string; /** whether the device is disabled in system settings. */ disabled: boolean; /** camera facing direction if applicable, undefined for audio devices. */ facing?: string; /** windows device GUID for identification. */ guid: string; /** hardware identifier for the device. */ hardwareId: string; /** container identifier grouping related devices. */ containerId: string; /** audio effects supported by device, undefined if none. */ effects?: string[]; } /** * Video input device (webcam). */ export interface VideoDevice { /** unique device identifier from the system. */ id: string; /** device index in enumeration. */ index: number; /** human readable device name. */ name: string; /** whether the device is disabled in system settings. */ disabled: boolean; /** camera facing direction, "front", "back", or "unknown". */ facing?: string; /** windows device GUID for identification. */ guid: string; /** hardware identifier for the device. */ hardwareId?: string; /** container identifier grouping related devices. */ containerId?: string; /** video effects supported by device, undefined if none. */ effects?: string[]; } /** * Quality settings for clips recording. */ export interface ClipsQuality { /** recording frame rate in fps. */ frameRate: number; /** recording resolution height in pixels. */ resolution: number; } /** * Desktop capture configuration for clips. */ export interface DesktopDescription { /** desktop source identifier. */ id: string; /** soundshare source identifier for audio capture. */ soundshareId: number; /** whether to use loopback audio capture. */ useLoopback: boolean; /** whether to use video hook for capture. */ useVideoHook: boolean; /** whether to use windows graphics capture API. */ useGraphicsCapture: boolean; /** whether to use macOS quartz capturer. */ useQuartzCapturer: boolean; /** whether to allow macOS screencapturekit. */ allowScreenCaptureKit: boolean; /** HDR capture behavior. */ hdrCaptureMode: HdrCaptureMode; } /** * Source configuration for clips recording. */ export interface ClipsSource { /** quality settings for the recording. */ quality: ClipsQuality; /** desktop capture configuration. */ desktopDescription: DesktopDescription; } /** * Desktop source for screen sharing. */ export interface DesktopSource { /** source identifier string. */ id: string; /** process id of the source application, null if not applicable. */ sourcePid: number | null; /** soundshare identifier for audio capture, null if not capturing audio. */ soundshareId: string | null; /** soundshare session identifier, null if not active. */ soundshareSession: string | null; } /** * Quality settings for go live streaming. */ export interface GoLiveQuality { /** stream resolution height in pixels. */ resolution: number; /** stream frame rate in fps. */ frameRate: number; } /** * Source configuration for go live streaming. */ export interface GoLiveSource { /** desktop source being streamed. */ desktopSource: DesktopSource; /** quality settings for the stream. */ quality: GoLiveQuality; } /** * Video stream parameter for simulcast layers. */ export interface VideoStreamParameter { /** simulcast layer id, e.g. "100" for full quality, "50" for half. */ rid: string; /** type of media stream. */ type: MediaType; /** quality percentage 0-100. */ quality: number; } /** * Stereo panning for a user's audio. */ export interface LocalPan { /** left channel volume multiplier 0-1, default 1. */ left: number; /** right channel volume multiplier 0-1, default 1. */ right: number; } /** * Voice activity detection and push-to-talk options. */ export interface ModeOptions { /** VAD threshold in dB, default -60. */ threshold: number; /** whether to auto-adjust threshold based on noise floor, default true. */ autoThreshold: boolean; /** whether to use krisp for VAD instead of webrtc, default true. */ vadUseKrisp: boolean; /** krisp activation threshold 0-1, default 0.8. */ vadKrispActivationThreshold: number; /** frames of audio to keep before speech is detected, default 5. */ vadLeading: number; /** frames to keep transmitting after speech ends, default 25. */ vadTrailing: number; /** PTT release delay in milliseconds, default 20. */ delay: number; /** keyboard shortcut keys for PTT, default empty array. */ shortcut: string[]; /** whether to run VAD before audio processing, default false. */ vadDuringPreProcess?: boolean; } /** * Options for audio loopback testing. */ export interface LoopbackOptions { /** whether echo cancellation is enabled. */ echoCancellation: boolean; /** whether noise suppression is enabled. */ noiseSuppression: boolean; /** whether automatic gain control is enabled. */ automaticGainControl: boolean; /** whether krisp noise cancellation is enabled. */ noiseCancellation: boolean; } /** * Screen capture preview thumbnail. */ export interface ScreenPreview { /** screen source identifier. */ id: string; /** data URL of thumbnail image. */ url: string; /** display name, e.g. "Screen 1". */ name: string; } /** * Window capture preview thumbnail. */ export interface WindowPreview { /** window source identifier. */ id: string; /** data URL of thumbnail image. */ url: string; /** window title. */ name: string; } /** * Krisp noise cancellation statistics. */ export interface NoiseCancellationStats { /** milliseconds of detected voice audio. */ voiceMs: number; /** milliseconds of detected music audio. */ musicMs: number; /** milliseconds of detected noise audio. */ noiseMs: number; } /** * MLS signing key for end-to-end encryption. */ export interface MLSSigningKey { /** raw key bytes. */ key: Uint8Array; /** key signature bytes. */ signature: Uint8Array; } /** * Codec capability info for a single codec. */ export interface CodecInfo { /** codec name (H264, VP8, VP9, AV1, H265). */ name: string; /** whether encoding is supported, false if no hardware/software encoder available. */ encode: boolean; /** whether decoding is supported, false if no hardware/software decoder available. */ decode: boolean; } /** * Metadata for saved clips. */ export interface ClipMetadata { /** custom name for the clip. */ name?: string; /** description text for the clip. */ description?: string; } /** * Result of saving a clip. */ export interface SavedClip { /** unique clip identifier. */ id: string; /** path where clip was saved. */ filepath: string; } /** * Result of saving a screenshot. */ export interface Screenshot { /** path where screenshot was saved. */ filepath: string; } /** * Settings for video background filters. */ export interface MediaFilterSettings { /** whether background replacement is enabled. */ backgroundEnabled: boolean; /** background blur intensity 0-100. */ backgroundBlur: number; /** custom background image id, null for blur only. */ backgroundId: string | null; } /** * Options for local audio recording. */ export interface AudioRecordingOptions { /** whether to apply echo cancellation. */ echoCancellation: boolean; /** whether to apply noise suppression. */ noiseSuppression: boolean; } /** * Options for raw audio sample recording. */ export interface RawSamplesOptions { /** number of audio channels. */ channels: number; /** sample rate in hz. */ sampleRate: number; } /** * Options for creating a voice connection. */ export interface ConnectionOptions { /** whether to start muted. */ selfMute: boolean; /** whether to start deafened. */ selfDeaf: boolean; /** whether to start with video enabled. */ selfVideo: boolean; } /** * Options for creating a replay connection. */ export interface ReplayConnectionOptions { /** path to the replay file. */ filePath: string; } /** * Info emitted when video input initializes. */ export interface VideoInputInitializationInfo { /** device that was initialized. */ description: VideoDevice; /** time in seconds until first frame. */ timeToFirstFrame: number; /** whether initialization timed out. */ initializationTimerExpired: boolean; /** entropy value for the video feed. */ entropy: number; } /** * Info emitted when audio input initializes. */ export interface AudioInputInitializationInfo { /** device that was initialized. */ description: AudioDevice; /** time in seconds until initialized. */ timeToInitialized: number; } /** * Video codec error details. */ export interface VideoCodecErrorInfo { /** whether error occurred during encode or decode. */ mode: "encode" | "decode"; /** codec standard name. */ codecStandard: string; /** error message text. */ message: string; /** implementation name that failed. */ implName: string; } /** * Codec information for connection setup. */ export interface ConnectionCodec { /** codec name. */ name: string; /** payload type number. */ payloadType: number; /** priority order. */ priority: number; /** rtx payload type if applicable. */ rtxPayloadType?: number; } /** * Connection transport initialization options. */ export interface ConnectionTransportOptions { /** server address. */ address: string; /** server port. */ port: number; /** audio ssrc. */ ssrc: number; /** available encryption modes. */ modes: string[]; /** stream count. */ streamCount?: number; /** audio codecs. */ audioCodec?: ConnectionCodec; /** video codecs. */ videoCodec?: ConnectionCodec; /** rtx codecs. */ rtxCodec?: ConnectionCodec; /** experiment flags. */ experiments?: string[]; } /** * Input mode options for voice activity or push-to-talk. */ export interface InputModeOptions { /** VAD threshold in dB. */ vadThreshold?: number; /** whether to auto-adjust threshold. */ vadAutoThreshold?: boolean; /** whether to use krisp for VAD. */ vadUseKrisp?: boolean; /** krisp activation threshold. */ vadKrispActivationThreshold?: number; /** frames before speech detection. */ vadLeading?: number; /** frames after speech ends. */ vadTrailing?: number; /** PTT release delay in ms. */ pttReleaseDelay?: number; } /** * Go live source configuration for streaming. */ export interface GoLiveSourceOptions { /** quality settings. */ quality: GoLiveQuality; /** desktop description if streaming desktop. */ desktopDescription?: DesktopDescription; /** camera description if streaming camera. */ cameraDescription?: { deviceId: string; }; } /** * Video stream parameter for simulcast configuration. */ export interface StreamParameter { /** simulcast rid. */ rid: string; /** max bitrate. */ maxBitrate?: number; /** max framerate. */ maxFrameRate?: number; /** max resolution. */ maxResolution?: { width: number; height: number; }; /** quality percentage. */ quality?: number; } /** * Automatic gain control configuration. */ export interface AutomaticGainControlConfig { /** whether AGC is enabled, default true. */ enabled: boolean; /** whether to use AGC2 algorithm, default true. */ useAGC2: boolean; /** whether analog gain control is enabled, default false. */ enableAnalog: boolean; /** whether digital gain control is enabled, default true. */ enableDigital: boolean; /** headroom in decibels, default 5. */ headroom_db: number; /** maximum gain in decibels, default 50. */ max_gain_db: number; /** initial gain in decibels, default 15. */ initial_gain_db: number; /** max gain change per second in decibels, default 6. */ max_gain_change_db_per_second: number; /** max output noise level in dbfs, default -50. */ max_output_noise_level_dbfs: number; /** fixed gain in decibels, default 0. */ fixed_gain_db: number; } /** * Active voice/video connection to a channel. */ export interface MediaEngineConnection { /** context this connection belongs to. */ context: MediaEngineContextType; /** unique identifier for this connection. */ mediaEngineConnectionId: string; /** user id who owns this connection. */ userId: string; /** user id for stream context, undefined in default context. */ streamUserId: string | undefined; /** current connection state. */ connectionState: ConnectionState; /** whether self is muted. */ selfMute: boolean; /** whether self is deafened. */ selfDeaf: boolean; /** whether self video is enabled. */ selfVideo: boolean; /** whether this connection has been destroyed. */ destroyed: boolean; /** audio ssrc for this connection. */ audioSSRC: number; /** video ssrc for this connection. */ videoSSRC: number; /** local mute states keyed by user id. */ localMutes: { [userId: string]: boolean; }; /** local volume levels keyed by user id. */ localVolumes: { [userId: string]: number; }; /** local pan settings keyed by user id. */ localPans: { [userId: string]: LocalPan; }; /** disabled local video states keyed by user id. */ disabledLocalVideos: { [userId: string]: boolean; }; /** current voice bitrate in bps, default 64000. */ voiceBitrate: number; /** whether video is supported. */ videoSupported: boolean; /** video stream parameters. */ videoStreamParameters: StreamParameter[]; /** soundshare source id. */ soundshareId: number | null; /** whether soundshare is active. */ soundshareActive: boolean; /** whether echo cancellation is enabled. */ echoCancellation: boolean; /** whether noise suppression is enabled. */ noiseSuppression: boolean; /** automatic gain control configuration. */ automaticGainControl: AutomaticGainControlConfig; /** whether noise cancellation is enabled. */ noiseCancellation: boolean; /** whether QoS is enabled. */ qos: boolean; /** current input mode. */ inputMode: string; /** VAD threshold in dB, default -60. */ vadThreshold: number; /** whether VAD auto threshold is enabled, default true. */ vadAutoThreshold: boolean; /** PTT release delay in ms, default 20. */ pttReleaseDelay: number; /** keyframe interval in ms, default 0. */ keyframeInterval: number; /** attenuation factor 0-1, default 1 (no attenuation). */ attenuationFactor: number; /** whether to attenuate while self speaking. */ attenuateWhileSpeakingSelf: boolean; /** whether to attenuate while others speaking. */ attenuateWhileSpeakingOthers: boolean; /** * Initializes the connection with transport options. * @param options transport options. */ initialize(options: ConnectionTransportOptions): void; /** destroys this connection and cleans up resources. */ destroy(): void; /** * Sets codecs for the connection. * @param audioCodec audio codec name. * @param videoCodec video codec name. * @param rtxCodec rtx codec name. */ setCodecs(audioCodec: string, videoCodec: string, rtxCodec: string): void; /** * Gets connection statistics. * @returns promise resolving to stats or null. */ getStats(): Promise; /** * Creates a remote user in the connection. * @param userId user id. * @param audioSSRC audio ssrc. * @param videoSSRC video ssrc. */ createUser(userId: string, audioSSRC: number, videoSSRC: number): void; /** * Destroys a remote user from the connection. * @param userId user id. */ destroyUser(userId: string): void; /** * Sets self mute state. * @param mute whether to mute. */ setSelfMute(mute: boolean): void; /** * Gets self mute state. * @returns true if muted. */ getSelfMute(): boolean; /** * Gets self deaf state. * @returns true if deafened. */ getSelfDeaf(): boolean; /** * Sets self deaf state. * @param deaf whether to deafen. */ setSelfDeaf(deaf: boolean): void; /** * Sets soundshare source for this connection. * @param soundshareId soundshare source id. * @param active whether to enable. */ setSoundshareSource(soundshareId: number, active: boolean): void; /** * Sets local mute for a user. * @param userId user to mute. * @param muted whether to mute. */ setLocalMute(userId: string, muted: boolean): void; /** performs a fast UDP reconnect. */ fastUdpReconnect(): void; /** * Gets number of fast UDP reconnects. * @returns reconnect count or null if unsupported. */ getNumFastUdpReconnects(): number | null; /** checks if remote was disconnected. */ wasRemoteDisconnected(): void; /** * Disables receiving video from a user. * @param userId user to disable video for. * @param disabled whether to disable. */ setLocalVideoDisabled(userId: string, disabled: boolean): void; /** * Sets minimum jitter buffer level. * @param level jitter buffer level. */ setMinimumJitterBufferLevel(level: number): void; /** * Sets postpone decode level. * @param level decode level. */ setPostponeDecodeLevel(level: number): void; /** * Sets clip recording for a user. * @param userId user id. * @param type clip type. * @param enabled whether enabled. */ setClipRecordUser(userId: string, type: string, enabled: boolean): void; /** * Sets clips keyframe interval. * @param interval interval in ms. */ setClipsKeyFrameInterval(interval: number): void; /** * Sets viewer side clip. * @param enabled whether enabled. */ setViewerSideClip(enabled: boolean): void; /** * Sets remote audio history duration. * @param durationMs duration in ms. */ setRemoteAudioHistory(durationMs: number): void; /** * Sets quality decoupling. * @param enabled whether enabled. */ setQualityDecoupling(enabled: boolean): void; /** * Gets local volume for a user. * @param userId user id. * @returns volume level. */ getLocalVolume(userId: string): number; /** * Sets local volume for a user. * @param userId user to adjust. * @param volume volume level 0-200, 100 is normal. */ setLocalVolume(userId: string, volume: number): void; /** * Sets stereo pan for a user. * @param userId user to adjust. * @param left left channel 0-1. * @param right right channel 0-1. */ setLocalPan(userId: string, left: number, right: number): void; /** * Checks if currently attenuating. * @returns true if attenuating. */ isAttenuating(): boolean; /** * Sets attenuation settings. * @param factor attenuation factor 0-100. * @param whileSpeakingSelf attenuate while self speaking. * @param whileSpeakingOthers attenuate while others speaking. */ setAttenuation(factor: number, whileSpeakingSelf: boolean, whileSpeakingOthers: boolean): void; /** * Sets whether user can have priority speaker. * @param userId user id. * @param canHavePriority whether can have priority. */ setCanHavePriority(userId: string, canHavePriority: boolean): void; /** * Sets voice bitrate. * @param bitrate bitrate in bps. */ setBitRate(bitrate: number): void; /** * Sets voice bitrate. * @param bitrate bitrate in bps. */ setVoiceBitRate(bitrate: number): void; /** * Sets camera bitrate. * @param maxBitrate max bitrate. * @param minBitrate min bitrate. * @param targetBitrate target bitrate. */ setCameraBitRate(maxBitrate: number, minBitrate: number | null, targetBitrate: number | null): void; /** * Sets echo cancellation. * @param enabled whether enabled. */ setEchoCancellation(enabled: boolean): void; /** * Sets noise suppression. * @param enabled whether enabled. */ setNoiseSuppression(enabled: boolean): void; /** * Sets automatic gain control. * @param config AGC configuration. */ setAutomaticGainControl(config: AutomaticGainControlConfig): void; /** * Sets noise cancellation. * @param enabled whether enabled. */ setNoiseCancellation(enabled: boolean): void; /** * Sets noise cancellation during processing. * @param enabled whether enabled. */ setNoiseCancellationDuringProcessing(enabled: boolean): void; /** * Sets noise cancellation after processing. * @param enabled whether enabled. */ setNoiseCancellationAfterProcessing(enabled: boolean): void; /** * Sets VAD after WebRTC. * @param enabled whether enabled. */ setVADAfterWebrtc(enabled: boolean): void; /** * Gets noise cancellation state. * @returns true if enabled. */ getNoiseCancellation(): boolean; /** * Gets current voice filter id. * @returns voice filter id or null. */ getVoiceFilterId(): string | null; /** * Sets voice filter id. * @param filterId filter id or null. */ setVoiceFilterId(filterId: string | null): void; /** * Sets QoS enabled. * @param enabled whether enabled. */ setQoS(enabled: boolean): void; /** * Sets soundshare discard rear channels. * @param discard whether to discard. */ setSoundshareDiscardRearChannels(discard: boolean): void; /** * Sets input mode. * @param mode input mode. * @param options mode options. */ setInputMode(mode: string, options: InputModeOptions): void; /** * Sets silence threshold. * @param threshold threshold value. */ setSilenceThreshold(threshold: number): void; /** * Sets force audio input. * @param force whether to force. * @param playTone whether to play tone. * @param isSpeaking whether speaking. */ setForceAudioInput(force: boolean, playTone?: boolean, isSpeaking?: boolean): void; /** * Sets speaking flags for a user. * @param userId user id. * @param flags speaking flags. */ setSpeakingFlags(userId: string, flags: number): void; /** clears all speaking states. */ clearAllSpeaking(): void; /** * Sets encryption mode. * @param mode encryption mode. * @param secretKey secret key. */ setEncryption(mode: string, secretKey: Uint8Array): void; /** * Sets reconnect interval. * @param interval interval in ms. */ setReconnectInterval(interval: number): void; /** * Sets keyframe interval. * @param interval interval in ms. */ setKeyframeInterval(interval: number): void; /** * Sets video quality measurement. * @param enabled whether enabled. */ setVideoQualityMeasurement(enabled: boolean): void; /** * Sets video encoder experiments. * @param experiments experiment config. */ setVideoEncoderExperiments(experiments: object): void; /** * Sets video broadcast state. * @param broadcast whether broadcasting. */ setVideoBroadcast(broadcast: boolean): void; /** * Sets go live source. * @param source source options. */ setGoLiveSource(source: GoLiveSourceOptions): void; /** clears go live devices. */ clearGoLiveDevices(): void; /** clears the desktop source from this connection. */ clearDesktopSource(): void; /** * Sets desktop source status callback. * @param callback callback function. */ setDesktopSourceStatusCallback(callback: (status: string) => void): void; /** * Checks if connection has a desktop source. * @returns true if streaming desktop. */ hasDesktopSource(): boolean; /** * Sets desktop encoding options. * @param width width. * @param height height. * @param framerate framerate. */ setDesktopEncodingOptions(width: number, height: number, framerate: number): void; /** * Sets SDP. * @param sdp SDP string. */ setSDP(sdp: string): void; /** * Sets remote video sink wants. * @param wants sink wants config. */ setRemoteVideoSinkWants(wants: object): void; /** * Sets local video sink wants. * @param wants sink wants config. */ setLocalVideoSinkWants(wants: object): void; /** * Starts samples local playback. * @param id playback id. * @param buffer audio buffer. * @param options playback options. * @param callback completion callback. */ startSamplesLocalPlayback(id: string, buffer: AudioBuffer, options: object, callback: (error: number, message: string) => void): void; /** stops all samples local playback. */ stopAllSamplesLocalPlayback(): void; /** * Stops samples local playback. * @param id playback id. */ stopSamplesLocalPlayback(id: string): void; /** * Sets bandwidth estimation experiments. * @param experiments experiment config. */ setBandwidthEstimationExperiments(experiments: object): void; /** * Updates video quality core. * @param options quality options. * @param reason update reason. */ updateVideoQualityCore(options: object, reason: string): void; /** * Sets stream parameters. * @param params stream parameters. * @returns promise. */ setStreamParameters(params: StreamParameter[]): Promise; /** applies video transport options. */ applyVideoTransportOptions(): void; /** * Chooses encryption mode. * @param preferred preferred modes. * @param available available modes. * @returns chosen mode. */ chooseEncryptionMode(preferred: string[], available: string[]): string; /** * Gets user options. * @returns user options array. */ getUserOptions(): object[]; /** * Creates input mode options. * @returns input mode options. */ createInputModeOptions(): InputModeOptions; /** * Gets attenuation options. * @returns attenuation options. */ getAttenuationOptions(): object; /** * Gets codec params. * @param codec codec name. * @param isHardware whether hardware codec. * @returns codec params. */ getCodecParams(codec: string, isHardware: boolean): object; /** * Gets codec options. * @param audioCodec audio codec. * @param videoCodec video codec. * @param rtxCodec rtx codec. * @returns codec options. */ getCodecOptions(audioCodec: string, videoCodec: string, rtxCodec: string): object; /** * Gets keyframe interval. * @returns interval in ms. */ getKeyFrameInterval(): number; /** * Gets connection transport options. * @returns transport options. */ getConnectionTransportOptions(): object; /** * Sets stream (not implemented). * @param stream media stream. */ setStream(stream: MediaStream): void; /** * Gets user id by ssrc. * @param ssrc ssrc value. */ getUserIdBySsrc(ssrc: number): void; /** * Prepares secure frames transition. * @param transitionId transition id. * @param epoch epoch number. * @param callback callback function. */ prepareSecureFramesTransition(transitionId: number, epoch: number, callback: () => void): void; /** * Prepares secure frames epoch. * @param epoch epoch number. * @param data epoch data. * @param callback callback function. */ prepareSecureFramesEpoch(epoch: number, data: Uint8Array, callback: () => void): void; /** * Executes secure frames transition. * @param transitionId transition id. */ executeSecureFramesTransition(transitionId: number): void; /** * Gets MLS key package. * @param callback callback receiving key package. */ getMLSKeyPackage(callback: (keyPackage: Uint8Array) => void): void; /** * Updates MLS external sender. * @param sender external sender data. */ updateMLSExternalSender(sender: Uint8Array): void; /** * Processes MLS proposals. * @param proposals proposals data. * @param callback callback function. */ processMLSProposals(proposals: Uint8Array, callback: () => void): void; /** * Prepares MLS commit transition. * @param transitionId transition id. * @param commit commit data. * @param callback callback function. */ prepareMLSCommitTransition(transitionId: number, commit: Uint8Array, callback: () => void): void; /** * Processes MLS welcome. * @param transitionId transition id. * @param welcome welcome data. * @param callback callback function. */ processMLSWelcome(transitionId: number, welcome: Uint8Array, callback: () => void): void; /** * Gets MLS pairwise fingerprint. * @param userId user id. * @param version version. * @param callback callback receiving fingerprint. */ getMLSPairwiseFingerprint(userId: string, version: number, callback: (fingerprint: Uint8Array) => void): void; /** * Presents desktop source picker. * @param options picker options. */ presentDesktopSourcePicker(options: object): void; /** * Merges users. * @param users user merge data. */ mergeUsers(users: object[]): void; /** * Gets whether there is an active video output sink. * @param userId user id. * @returns true if has active sink. */ getHasActiveVideoOutputSink(userId: string): boolean; /** * Sets whether there is an active video output sink. * @param userId user id. * @param hasActiveSink whether sink is active. * @param reason reason for the change. */ setHasActiveVideoOutputSink(userId: string, hasActiveSink: boolean, reason: string): void; /** * Applies quality constraints to video. * @param constraints quality constraints object. * @param ssrc optional ssrc to apply to. * @returns quality manager result. */ applyQualityConstraints(constraints?: object, ssrc?: number): object; /** * Applies video quality mode preset. * @param mode quality mode to apply. */ applyVideoQualityMode(mode: number): void; /** * Configures go live simulcast settings. * @param enabled whether simulcast is enabled. * @param options simulcast options. */ configureGoLiveSimulcast(enabled: boolean, options: object): void; /** * Emits connection stats. * @returns promise resolving to stats or null. */ emitStats(): Promise; /** * Gets whether active output sink tracking is enabled. * @returns true if enabled. */ getActiveOutputSinkTrackingEnabled(): boolean; /** * Gets local mute state for a user. * @param userId user id. * @returns true if muted. */ getLocalMute(userId: string): boolean; /** * Gets local video disabled state for a user. * @param userId user id. * @returns true if disabled. */ getLocalVideoDisabled(userId: string): boolean; /** * Gets local video quality want for a ssrc. * @param ssrc optional ssrc. * @returns quality want object. */ getLocalWant(ssrc?: number): object; /** * Gets remote video sink pixel count for a user. * @param userId user id. * @returns pixel count. */ getRemoteVideoSinkPixelCount(userId: string): number; /** * Gets remote video sink wants for a user. * @param userId user id. * @returns sink wants object. */ getRemoteVideoSinkWants(userId: string): object; /** * Gets current stream parameters. * @returns array of stream parameters. */ getStreamParameters(): StreamParameter[]; /** * Handles desktop source ended event. * @param reason end reason. * @param errorCode error code. */ handleDesktopSourceEnded(reason: string, errorCode: number): void; /** * Handles first frame received. * @param userId user id. * @param ssrc ssrc. * @param stats stats object. */ handleFirstFrame(userId: string, ssrc: number, stats: object): void; /** * Handles first frame encrypted stats. * @param stats stats object. */ handleFirstFrameEncryptedStats(stats: object): void; /** * Handles first frame stats. * @param stats stats object. */ handleFirstFrameStats(stats: object): void; /** * Handles MLS failure. * @param error error string. * @param code error code. */ handleMLSFailure(error: string, code: number): void; /** * Handles native mute changed. * @param muted new mute state. */ handleNativeMuteChanged(muted: boolean): void; /** * Handles native mute toggled from system. */ handleNativeMuteToggled(): void; /** * Handles new listener for native events. * @param event event name. */ handleNewListenerNative(event: string): void; /** * Handles no input detected. * @param hasInput whether input is detected. */ handleNoInput(hasInput: boolean): void; /** * Handles ping response. * @param latency latency in ms. * @param hostname hostname. * @param port port number. */ handlePing(latency: number, hostname: string, port: number): void; /** * Handles ping timeout. * @param hostname hostname. * @param port port number. * @param attempts attempt count. * @param timeout timeout in ms. */ handlePingTimeout(hostname: string, port: number, attempts: number, timeout: number): void; /** * Handles RTCP message. * @param type message type. * @param data message data. */ handleRTCPMessage(type: string, data: string): void; /** * Handles soundshare attached. * @param attached whether attached. */ handleSoundshare(attached: boolean): void; /** * Handles soundshare ended. */ handleSoundshareEnded(): void; /** * Handles soundshare failed. * @param failureCode failure code. * @param failureReason failure reason. * @param willRetry whether it will retry. */ handleSoundshareFailed(failureCode: number, failureReason: string, willRetry: boolean): void; /** * Handles speaking flags change. * @param userId user id. * @param flags speaking flags. * @param ssrc ssrc. */ handleSpeakingFlags(userId: string, flags: number, ssrc: number): void; /** * Handles native speaking event. * @param userId user id. * @param speaking speaking state or flags. * @param ssrc ssrc. */ handleSpeakingNative(userId: string, speaking: boolean | number, ssrc: number): void; /** * Handles speaking while muted event. */ handleSpeakingWhileMuted(): void; /** * Handles stats received. * @param stats stats object. */ handleStats(stats: object): void; /** * Handles video stream update. * @param userId user id. * @param ssrc ssrc. * @param active whether active. * @param streams stream array. */ handleVideo(userId: string, ssrc: number, active: boolean, streams: object[]): void; /** * Handles video encoder fallback. * @param codecName codec that failed. */ handleVideoEncoderFallback(codecName: string): void; /** * Initializes stream parameters. * @param parameters initial parameters. */ initializeStreamParameters(parameters: StreamParameter[]): void; /** * Callback when desktop encoding options are set. * @param width width. * @param height height. * @param framerate framerate. */ onDesktopEncodingOptionsSet(width: number, height: number, framerate: number): void; /** * Overwrites quality for testing. * @param quality quality value. */ overwriteQualityForTesting(quality: number): void; /** * Sets the connection state. * @param state new connection state. */ setConnectionState(state: ConnectionState): void; /** * Sets an experiment flag. * @param flag flag name. * @param enabled whether enabled. */ setExperimentFlag(flag: string, enabled: boolean): void; /** * Sets whether to use electron video. * @param use whether to use. */ setUseElectronVideo(use: boolean): void; /** * Updates video quality settings. * @param ssrc optional ssrc to update. */ updateVideoQuality(ssrc?: number): void; } /** * Low-level media engine for audio/video processing. * Handles device enumeration, encoding/decoding, and connections. */ export interface MediaEngine { /** camera preview component. */ Camera: React.ComponentType<{ disabled?: boolean; deviceId?: string; width?: number; height?: number; }>; /** video display component. */ Video: React.ComponentType & { onContainerResized: () => void; }; /** set of active voice/video connections. */ connections: Set; /** * Registers a listener for device changes. * @param event event name. * @param listener callback receiving device lists. */ on(event: "DeviceChange", listener: (inputDevices: AudioDevice[], outputDevices: AudioDevice[], videoDevices: VideoDevice[]) => void): this; /** * Registers a listener for volume changes. * @param event event name. * @param listener callback receiving input and output volumes. */ on(event: "VolumeChange", listener: (inputVolume: number, outputVolume: number) => void): this; /** * Registers a listener for voice activity. * @param event event name. * @param listener callback receiving user id and activity level. */ on(event: "VoiceActivity", listener: (userId: string, voiceActivity: number) => void): this; /** * Registers a listener for desktop source end. * @param event event name. * @param listener callback receiving reason and error code. */ on(event: "DesktopSourceEnd", listener: (reason: string, errorCode: number) => void): this; /** * Registers a listener for audio permission changes. * @param event event name. * @param listener callback receiving granted state. */ on(event: "AudioPermission", listener: (granted: boolean) => void): this; /** * Registers a listener for video permission changes. * @param event event name. * @param listener callback receiving granted state. */ on(event: "VideoPermission", listener: (granted: boolean) => void): this; /** * Registers a listener for video input initialization. * @param event event name. * @param listener callback receiving initialization info. */ on(event: "VideoInputInitialized", listener: (info: VideoInputInitializationInfo) => void): this; /** * Registers a listener for audio input initialization. * @param event event name. * @param listener callback receiving initialization info. */ on(event: "AudioInputInitialized", listener: (info: AudioInputInitializationInfo) => void): this; /** * Registers a listener for clips init failure. * @param event event name. * @param listener callback receiving error message and app name. */ on(event: "ClipsInitFailure", listener: (errorMessage: string, applicationName: string) => void): this; /** * Registers a listener for clips recording ended. * @param event event name. * @param listener callback receiving source id and soundshare id. */ on(event: "ClipsRecordingEnded", listener: (sourceId: string, soundshareId: number) => void): this; /** * Registers a listener for native screen share picker update. * @param event event name. * @param listener callback receiving existing state and content. */ on(event: "NativeScreenSharePickerUpdate", listener: (existing: boolean, content: string) => void): this; /** * Registers a listener for native screen share picker cancel. * @param event event name. * @param listener callback receiving existing state. */ on(event: "NativeScreenSharePickerCancel", listener: (existing: boolean) => void): this; /** * Registers a listener for native screen share picker error. * @param event event name. * @param listener callback receiving error string. */ on(event: "NativeScreenSharePickerError", listener: (error: string) => void): this; /** * Registers a listener for audio device module error. * @param event event name. * @param listener callback receiving module, code and device name. */ on(event: "AudioDeviceModuleError", listener: (module: string, code: number, deviceName: string) => void): this; /** * Registers a listener for video codec error. * @param event event name. * @param listener callback receiving error info. */ on(event: "VideoCodecError", listener: (info: VideoCodecErrorInfo) => void): this; /** * Registers a listener for system microphone mode change. * @param event event name. * @param listener callback receiving new mode. */ on(event: "SystemMicrophoneModeChange", listener: (mode: string) => void): this; /** * Registers a listener for events without arguments. * @param event event name. * @param listener callback with no arguments. */ on(event: "Destroy" | "Silence" | "WatchdogTimeout" | "ClipsRecordingRestartNeeded" | "VoiceFiltersFailed", listener: () => void): this; /** * Registers a one-time listener for device changes. * @param event event name. * @param listener callback receiving device lists. */ once(event: "DeviceChange", listener: (inputDevices: AudioDevice[], outputDevices: AudioDevice[], videoDevices: VideoDevice[]) => void): this; /** * Registers a one-time listener for volume changes. * @param event event name. * @param listener callback receiving input and output volumes. */ once(event: "VolumeChange", listener: (inputVolume: number, outputVolume: number) => void): this; /** * Registers a one-time listener for events without arguments. * @param event event name. * @param listener callback with no arguments. */ once(event: "Destroy" | "Silence" | "WatchdogTimeout" | "ClipsRecordingRestartNeeded" | "VoiceFiltersFailed", listener: () => void): this; /** * Removes a listener for device changes. * @param event event name. * @param listener callback to remove. */ off(event: "DeviceChange", listener: (inputDevices: AudioDevice[], outputDevices: AudioDevice[], videoDevices: VideoDevice[]) => void): this; /** * Removes a listener for volume changes. * @param event event name. * @param listener callback to remove. */ off(event: "VolumeChange", listener: (inputVolume: number, outputVolume: number) => void): this; /** * Removes a listener for events without arguments. * @param event event name. * @param listener callback to remove. */ off(event: "Destroy" | "Silence" | "WatchdogTimeout" | "ClipsRecordingRestartNeeded" | "VoiceFiltersFailed", listener: () => void): this; /** * Removes all listeners for an event. * @param event event name, or all if omitted. */ removeAllListeners(event?: MediaEngineEvent): this; /** * Gets the number of listeners for an event. * @param event event name. * @returns listener count. */ listenerCount(event: MediaEngineEvent): number; /** * Applies video background filter settings. * @param settings filter settings to apply. */ applyMediaFilterSettings(settings: MediaFilterSettings): Promise; /** * Creates a new voice connection. * @param userId user id for the connection. * @param channelId channel to connect to. * @param options connection options. * @returns the created connection. */ connect(userId: string, channelId: string, options: ConnectionOptions): MediaEngineConnection; /** * Checks if there are no active connections. * @returns true if no connections exist. */ connectionsEmpty(): boolean; /** * Creates a replay connection from a file. * @param userId user id for the connection. * @param options replay options including file path. * @returns the created connection or null if unsupported. */ createReplayConnection(userId: string, options: ReplayConnectionOptions): MediaEngineConnection | null; /** destroys the media engine and all connections. */ destroy(): void; /** * Iterates over all connections. * @param callback called for each connection. * @param context optional context filter, only iterates connections in this context. */ eachConnection(callback: (connection: MediaEngineConnection) => void, context?: MediaEngineContextType): void; /** * Enables the media engine. * @returns promise that resolves when enabled. */ enable(): Promise; /** * Exports a clip as a blob. * @param clipId clip identifier. * @param userId user who owns the clip. * @returns promise resolving to the clip blob. */ exportClip(clipId: string, userId: string): Promise; /** * Fetches async resources like DAVE keys. * @param options fetch options. */ fetchAsyncResources(options: { fetchDave?: boolean; }): Promise; /** * Gets available audio input devices. * @returns promise resolving to device list. */ getAudioInputDevices(): Promise; /** * Gets the current audio layer name. * @returns audio layer identifier. */ getAudioLayer(): string; /** * Gets available audio output devices. * @returns promise resolving to device list. */ getAudioOutputDevices(): Promise; /** * Gets the current audio subsystem. * @returns active audio subsystem. */ getAudioSubsystem(): AudioSubsystem; /** * Gets codec capabilities as JSON string. * @param callback called with capabilities string. */ getCodecCapabilities(callback: (capabilities: string) => void): void; /** * Gets a survey of supported codecs. * @returns promise resolving to codec info. */ getCodecSurvey(): Promise<{ codecs: CodecInfo[]; }>; /** * Gets whether debug logging is enabled. * @returns true if enabled. */ getDebugLogging(): boolean; /** * Gets the current desktop source. * @returns promise that rejects with NO_STREAM error if not streaming. */ getDesktopSource(): Promise; /** * Gets whether loopback is active. * @returns always false for native engine. */ getLoopback(): boolean; /** * Gets MLS signing key for e2ee. * @param userId user id. * @param guildId guild id. * @returns promise resolving to key and signature. */ getMLSSigningKey(userId: string, guildId: string): Promise; /** * Gets noise cancellation statistics. * @returns promise resolving to stats or null if disabled. */ getNoiseCancellationStats(): Promise; /** * Gets screen preview thumbnails. * @param width thumbnail width. * @param height thumbnail height. * @returns promise resolving to preview list. */ getScreenPreviews(width: number, height: number): Promise; /** * Gets supported bandwidth estimation experiments. * @param callback called with experiment list. */ getSupportedBandwidthEstimationExperiments(callback: (experiments: string[]) => void): void; /** * Gets supported secure frames protocol version. * @returns protocol version number. */ getSupportedSecureFramesProtocolVersion(): number; /** * Gets supported video codecs. * @param callback called with codec name list. */ getSupportedVideoCodecs(callback: (codecs: string[]) => void): void; /** * Gets system microphone mode. * @returns promise resolving to mode string. */ getSystemMicrophoneMode(): Promise; /** * Gets current video input device id. * @returns device id or "disabled". */ getVideoInputDeviceId(): string; /** * Gets available video input devices. * @returns promise resolving to device list. */ getVideoInputDevices(): Promise; /** * Gets window preview thumbnails. * @param width thumbnail width. * @param height thumbnail height. * @returns promise resolving to preview list. */ getWindowPreviews(width: number, height: number): Promise; /** signals user interaction to enable autoplay. */ interact(): void; /** * Shows native screen share picker. * @param options picker options. */ presentNativeScreenSharePicker(options?: string): void; /** * Queues an audio subsystem switch. * @param subsystem subsystem to switch to. */ queueAudioSubsystem(subsystem: AudioSubsystem): void; /** * Ranks RTC regions by latency. * @param regions region ids to test. * @returns promise resolving to sorted region ids. */ rankRtcRegions(regions: string[]): Promise; /** releases native desktop video source picker stream. */ releaseNativeDesktopVideoSourcePickerStream(): void; /** * Saves a clip. * @param clipId clip identifier. * @param userId user who owns the clip. * @returns promise resolving to saved clip info. */ saveClip(clipId: string, userId: string): Promise; /** * Saves a clip for another user. * @param clipId clip identifier. * @param userId user to save for. * @param options clip metadata. * @returns promise resolving to saved clip info. */ saveClipForUser(clipId: string, userId: string, options: ClipMetadata): Promise; /** * Saves a screenshot. * @param channelId channel context. * @param userId user context. * @param width width or null for auto. * @param height height or null for auto. * @param options screenshot metadata. * @returns promise resolving to screenshot info. */ saveScreenshot(channelId: string, userId: string, width: number | null, height: number | null, options: ClipMetadata): Promise; /** * Enables or disables AEC dump. * @param enabled whether to enable. */ setAecDump(enabled: boolean): void; /** * Sets callback for async clips source deinit. * @param callback callback function. */ setAsyncClipsSourceDeinit(callback: () => void): void; /** * Sets callback for async video input device init. * @param callback callback function. */ setAsyncVideoInputDeviceInit(callback: () => void): void; /** * Sets whether to bypass system audio input processing. * @param bypass whether to bypass. */ setAudioInputBypassSystemProcessing(bypass: boolean): void; /** * Sets the audio input device. * @param deviceId device identifier. */ setAudioInputDevice(deviceId: string): void; /** * Sets the audio output device. * @param deviceId device identifier. */ setAudioOutputDevice(deviceId: string): void; /** * Sets the audio subsystem. * @param subsystem subsystem to use. */ setAudioSubsystem(subsystem: AudioSubsystem): void; /** * Enables or disables AV1 codec. * @param enabled whether to enable. */ setAv1Enabled(enabled: boolean): void; /** * Sets clip buffer length in seconds. * @param seconds buffer duration. */ setClipBufferLength(seconds: number): void; /** * Enables or disables clips ML pipeline. * @param enabled whether to enable. */ setClipsMLPipelineEnabled(enabled: boolean): void; /** * Enables or disables a clips ML pipeline type. * @param type pipeline type. * @param enabled whether to enable. */ setClipsMLPipelineTypeEnabled(type: string, enabled: boolean): void; /** * Sets clips quality settings. * @param resolution resolution height. * @param frameRate frame rate. * @param hdr whether HDR is enabled. * @returns true if settings were applied. */ setClipsQualitySettings(resolution: number, frameRate: number, hdr: boolean): boolean; /** * Sets or clears the clips source. * @param source source config or null to clear. */ setClipsSource(source: ClipsSource | null): void; /** * Enables or disables debug logging. * @param enabled whether to enable. */ setDebugLogging(enabled: boolean): void; /** * Sets or clears the go live source. * @param source source config or null to clear. * @param context context to apply to, defaults to "default". */ setGoLiveSource(source: GoLiveSource | null, context?: MediaEngineContextType): void; /** * Enables or disables H264 codec. * @param enabled whether to enable. */ setH264Enabled(enabled: boolean): void; /** * Enables or disables H265 codec. * @param enabled whether to enable. */ setH265Enabled(enabled: boolean): void; /** * Sets whether device has fullband performance. * @param has whether it has fullband performance. */ setHasFullbandPerformance(has: boolean): void; /** * Sets input volume. * @param volume volume 0-100. */ setInputVolume(volume: number): void; /** * Enables loopback for testing. * @param reason reason for enabling loopback. * @param options loopback audio options. */ setLoopback(reason: string, options: LoopbackOptions): void; /** * Sets max sync delay override. * @param delay delay in milliseconds. */ setMaxSyncDelayOverride(delay: number): void; /** * Sets maybe preprocess mute state. * @param mute whether to mute. */ setMaybePreprocessMute(mute: boolean): void; /** * Sets native desktop video source picker active state. * @param active whether picker is active. */ setNativeDesktopVideoSourcePickerActive(active: boolean): void; /** * Enables or disables noise cancellation stats. * @param enabled whether to enable. */ setNoiseCancellationEnableStats(enabled: boolean): void; /** * Sets whether to offload ADM controls. * @param offload whether to offload. */ setOffloadAdmControls(offload: boolean): void; /** * Sets callback for video container resize. * @param callback callback function. */ setOnVideoContainerResized(callback: () => void): void; /** * Sets output volume. * @param volume volume 0-100. */ setOutputVolume(volume: number): void; /** * Enables or disables sidechain compression. * @param enabled whether to enable. */ setSidechainCompression(enabled: boolean): void; /** * Sets sidechain compression strength. * @param strength strength 0-100. */ setSidechainCompressionStrength(strength: number): void; /** * Sets soundshare source. * @param soundshareId soundshare source id. * @param active whether to enable. * @param context context to apply to, defaults to "default". */ setSoundshareSource(soundshareId: number, active: boolean, context?: MediaEngineContextType): void; /** * Sets the video input device. * @param deviceId device identifier. */ setVideoInputDevice(deviceId: string): Promise; /** * Checks if a connection should broadcast video. * @param connection connection to check. * @returns true if should broadcast. */ shouldConnectionBroadcastVideo(connection: MediaEngineConnection): boolean; /** * Shows system capture configuration UI. * @param options options including display id. */ showSystemCaptureConfigurationUI(options: { displayId?: string; }): void; /** starts AEC dump recording. */ startAecDump(): void; /** * Starts local audio recording. * @param options recording options. */ startLocalAudioRecording(options: AudioRecordingOptions): Promise; /** * Starts recording raw audio samples. * @param options sample options. */ startRecordingRawSamples(options: RawSamplesOptions): void; /** stops AEC dump recording. */ stopAecDump(): void; /** * Stops local audio recording. * @param callback called with success and filepath. */ stopLocalAudioRecording(callback: (success: boolean, filepath: string) => void): void; /** stops recording raw audio samples. */ stopRecordingRawSamples(): void; /** * Checks if media engine is supported. * @returns true if supported. */ supported(): boolean; /** * Checks if a feature is supported. * @param feature feature to check. * @returns true if supported. */ supports(feature: MediaEngineFeature): boolean; /** * Updates clip metadata. * @param clipId clip identifier. * @param metadata new metadata. */ updateClipMetadata(clipId: string, metadata: ClipMetadata): Promise; /** ticks the watchdog timer. */ watchdogTick(): void; /** * Writes audio debug state to file. * @returns promise that resolves when written. */ writeAudioDebugState(): Promise; } /** * Persisted media engine settings for a context. */ export interface MediaEngineSettings { /** current voice mode (PTT or VAD), default VOICE_ACTIVITY. */ mode: VoiceMode; /** voice mode configuration options. */ modeOptions: ModeOptions; /** settings version for vadUseKrisp migration. */ vadUseKrispSettingVersion: number; /** settings version for ncUseKrisp migration. */ ncUseKrispSettingVersion: number; /** settings version for ncUseKrispjs migration. */ ncUseKrispjsSettingVersion: number; /** whether self is muted, default false. */ mute: boolean; /** whether self is deafened, default false. */ deaf: boolean; /** whether echo cancellation is enabled, default false. */ echoCancellation: boolean; /** whether noise suppression is enabled, default false. */ noiseSuppression: boolean; /** whether automatic gain control is enabled, default true. */ automaticGainControl: boolean; /** whether krisp noise cancellation is enabled, default false. */ noiseCancellation: boolean; /** whether to bypass system audio input processing, default false. */ bypassSystemInputProcessing: boolean; /** most recently requested voice filter id, null if none. */ mostRecentlyRequestedVoiceFilter: string | null; /** whether voice filter playback is enabled, default false. */ voiceFilterPlaybackEnabled: boolean; /** version for hardware enabled migration. */ hardwareEnabledVersion: number; /** whether silence warning is enabled, default true. */ silenceWarning: boolean; /** attenuation level 0-100 for other users when speaking, default 0. */ attenuation: number; /** whether to attenuate others when self is speaking, default false. */ attenuateWhileSpeakingSelf: boolean; /** whether to attenuate others when others are speaking, default true. */ attenuateWhileSpeakingOthers: boolean; /** per-user local mute states, keyed by user id. */ localMutes: { [userId: string]: boolean; }; /** per-user disabled local video states, keyed by user id. */ disabledLocalVideos: { [userId: string]: boolean; }; /** per-user video toggle states, keyed by user id. */ videoToggleStateMap: { [userId: string]: VideoToggleState; }; /** per-user local volume levels 0-200, keyed by user id, default 100. */ localVolumes: { [userId: string]: number; }; /** per-user stereo pan settings, keyed by user id. */ localPans: { [userId: string]: LocalPan; }; /** microphone input volume 0-100, default 100. */ inputVolume: number; /** speaker output volume 0-100, default 100. */ outputVolume: number; /** selected audio input device id. */ inputDeviceId: string; /** selected audio output device id. */ outputDeviceId: string; /** selected video input device id. */ videoDeviceId: string; /** whether QoS packet priority is enabled. */ qos: boolean; /** whether QoS has been migrated. */ qosMigrated: boolean; /** whether video hook is enabled. */ videoHook: boolean; /** experimental soundshare setting, null if not set. */ experimentalSoundshare2: boolean | null; /** system screenshare picker setting, null if not set. */ useSystemScreensharePicker: boolean | null; /** whether H265 codec is enabled. */ h265Enabled: boolean; /** whether VAD threshold has been migrated. */ vadThrehsoldMigrated: boolean; /** whether AEC dump is enabled. */ aecDumpEnabled: boolean; /** whether sidechain compression is enabled. */ sidechainCompression: boolean; /** settings version for sidechain compression migration. */ sidechainCompressionSettingVersion: number; /** sidechain compression strength 0-100, default 50. */ sidechainCompressionStrength: number; /** whether automatic audio subsystem selection is enabled. */ automaticAudioSubsystem: boolean; /** active input profile or null. */ activeInputProfile: InputProfile | null; } /** * Complete serializable state of MediaEngineStore. */ export interface MediaEngineState { /** settings for each context type, keyed by context. */ settingsByContext: { [context in MediaEngineContextType]: MediaEngineSettings; }; /** available audio input devices, keyed by device id. */ inputDevices: { [deviceId: string]: AudioDevice; }; /** available audio output devices, keyed by device id. */ outputDevices: { [deviceId: string]: AudioDevice; }; /** supported features, keyed by feature name. */ appSupported: { [feature in MediaEngineFeature]?: boolean; }; /** whether krisp module is loaded. */ krispModuleLoaded: boolean; /** krisp module version or undefined. */ krispVersion: string | undefined; /** krisp suppression level or undefined. */ krispSuppressionLevel: number | undefined; /** current go live source or undefined. */ goLiveSource: GoLiveSource | undefined; /** context for go live. */ goLiveContext: MediaEngineContextType; } /** * Keyboard shortcut binding. */ export interface Shortcut { /** action the shortcut triggers. */ action: string; /** keys in the shortcut combination. */ shortcut: string[]; } /** * Flux store managing audio/video settings, devices, and the media engine. * Handles voice activity detection, noise cancellation, device selection, * and go live streaming configuration. */ export class MediaEngineStore extends FluxStore { /** fetches async resources like DAVE keys. */ fetchAsyncResources(): void; /** * Gets the active input profile. * @returns current input profile. */ getActiveInputProfile(): InputProfile; /** * Gets the active voice filter id. * @returns voice filter id or null if none active. */ getActiveVoiceFilter(): string | null; /** * Gets when the active voice filter was applied. * @returns application date or null if none active. */ getActiveVoiceFilterAppliedAt(): Date | null; /** * Gets whether AEC dump is enabled. * @returns true if enabled. */ getAecDump(): boolean; /** * Gets whether to attenuate while others are speaking. * @returns true if enabled. */ getAttenuateWhileSpeakingOthers(): boolean; /** * Gets whether to attenuate while self is speaking. * @returns true if enabled. */ getAttenuateWhileSpeakingSelf(): boolean; /** * Gets the attenuation level. * @returns attenuation 0-100, default 0. */ getAttenuation(): number; /** * Gets the current audio subsystem. * @returns active audio subsystem. */ getAudioSubsystem(): AudioSubsystem; /** * Gets whether automatic gain control is enabled. * @returns true if enabled. */ getAutomaticGainControl(): boolean; /** * Gets whether system audio input processing is bypassed. * @returns true if bypassed. */ getBypassSystemInputProcessing(): boolean; /** * Gets the camera preview component. * @returns react component for camera preview. */ getCameraComponent(): React.ComponentType; /** * Gets whether debug logging is enabled. * @returns true if enabled. */ getDebugLogging(): boolean; /** * Gets whether echo cancellation is enabled. * @returns true if enabled. */ getEchoCancellation(): boolean; /** * Gets whether silence warning is enabled. * @returns true if enabled. */ getEnableSilenceWarning(): boolean; /** * Gets whether user has ever spoken while muted. * @returns true if has spoken while muted. */ getEverSpeakingWhileMuted(): boolean; /** * Gets whether experimental soundshare is enabled. * @returns true if enabled. */ getExperimentalSoundshare(): boolean; /** * Gets the go live context. * @returns current go live context. */ getGoLiveContext(): MediaEngineContextType; /** * Gets the current go live source. * @returns go live source or null if not streaming. */ getGoLiveSource(): GoLiveSource | null; /** * Gets the GPU brand name. * @returns GPU brand string. */ getGpuBrand(): string; /** * Gets whether H265 is enabled. * @returns true if enabled. */ getH265Enabled(): boolean; /** * Gets whether hardware encoding is enabled. * @returns true if enabled. */ getHardwareEncoding(): boolean; /** * Gets whether audio input is detected. * @returns true if detected, false if not, null if unknown. */ getInputDetected(): boolean | null; /** * Gets the selected audio input device id. * @returns device id. */ getInputDeviceId(): string; /** * Gets available audio input devices. * @returns devices keyed by device id. */ getInputDevices(): { [deviceId: string]: AudioDevice; }; /** * Gets the input volume. * @returns volume 0-100, default 100. */ getInputVolume(): number; /** * Gets whether krisp stats are enabled. * @returns true if enabled. */ getKrispEnableStats(): boolean; /** * Gets the krisp model override. * @returns model name or undefined if not set. */ getKrispModelOverride(): string | undefined; /** * Gets available krisp models. * @returns array of model names. */ getKrispModels(): string[]; /** * Gets the krisp suppression level. * @returns suppression level 0-100, default 100. */ getKrispSuppressionLevel(): number; /** * Gets the krisp VAD activation threshold. * @returns threshold 0-1, default 0.8. */ getKrispVadActivationThreshold(): number; /** * Gets the timestamp of the last audio input device change. * @returns timestamp in milliseconds. */ getLastAudioInputDeviceChangeTimestamp(): number; /** * Gets the stereo pan for a user. * @param userId user to get pan for. * @param context settings context, defaults to "default". * @returns pan with left/right 0-1, default {left: 1, right: 1}. */ getLocalPan(userId: string, context?: MediaEngineContextType): LocalPan; /** * Gets the volume for a user. * @param userId user to get volume for. * @param context settings context, defaults to "default". * @returns volume 0-200, default 100. */ getLocalVolume(userId: string, context?: MediaEngineContextType): number; /** * Gets whether loopback is enabled. * @returns true if enabled. */ getLoopback(): boolean; /** * Gets the reasons loopback is enabled. * @returns set of reason strings. */ getLoopbackReasons(): Set; /** * Gets the media engine instance. * @returns the media engine. */ getMediaEngine(): MediaEngine; /** * Gets MLS signing key for e2ee. * @param userId user id. * @param guildId guild id. * @returns promise resolving to key and signature. */ getMLSSigningKey(userId: string, guildId: string): Promise; /** * Gets the current voice mode. * @param context settings context, defaults to "default". * @returns current voice mode. */ getMode(context?: MediaEngineContextType): VoiceMode; /** * Gets the mode options. * @param context settings context, defaults to "default". * @returns current mode options. */ getModeOptions(context?: MediaEngineContextType): ModeOptions; /** * Gets the most recently requested voice filter. * @returns voice filter id or null if none. */ getMostRecentlyRequestedVoiceFilter(): string | null; /** * Gets whether no input detected notice is shown. * @returns true if shown. */ getNoInputDetectedNotice(): boolean; /** * Gets whether noise cancellation is enabled. * @returns true if enabled. */ getNoiseCancellation(): boolean; /** * Gets whether noise suppression is enabled. * @returns true if enabled. */ getNoiseSuppression(): boolean; /** * Gets the selected audio output device id. * @returns device id. */ getOutputDeviceId(): string; /** * Gets available audio output devices. * @returns devices keyed by device id. */ getOutputDevices(): { [deviceId: string]: AudioDevice; }; /** * Gets the output volume. * @returns volume 0-100, default 100. */ getOutputVolume(): number; /** * Gets the packet delay. * @returns delay in milliseconds. */ getPacketDelay(): number; /** * Gets the previous voice filter. * @returns voice filter id or null if none. */ getPreviousVoiceFilter(): string | null; /** * Gets when the previous voice filter was applied. * @returns application date or null if none. */ getPreviousVoiceFilterAppliedAt(): Date | null; /** * Gets whether QoS is enabled. * @returns true if enabled. */ getQoS(): boolean; /** * Gets the settings for a context. * @param context settings context, defaults to "default". * @returns current settings. */ getSettings(context?: MediaEngineContextType): MediaEngineSettings; /** * Gets registered shortcuts. * @returns shortcuts keyed by action. */ getShortcuts(): { [action: string]: Shortcut; }; /** * Gets whether sidechain compression is enabled. * @returns true if enabled. */ getSidechainCompression(): boolean; /** * Gets the sidechain compression strength. * @returns strength 0-100, default 50. */ getSidechainCompressionStrength(): number; /** * Gets whether currently speaking while muted. * @returns true if speaking while muted. */ getSpeakingWhileMuted(): boolean; /** * Gets the complete store state. * @returns current state. */ getState(): MediaEngineState; /** * Gets supported secure frames protocol version. * @returns protocol version number. */ getSupportedSecureFramesProtocolVersion(): number; /** * Gets the system microphone mode. * @returns mode string or undefined if not available. */ getSystemMicrophoneMode(): string | undefined; /** * Gets whether gamescope capture is used. * @returns true if used. */ getUseGamescopeCapture(): boolean; /** * Gets whether system screenshare picker is used. * @returns true if used. */ getUseSystemScreensharePicker(): boolean; /** * Gets whether VA-API encoder is used. * @returns true if used. */ getUseVaapiEncoder(): boolean; /** * Gets the video display component. * @returns react component for video display. */ getVideoComponent(): React.ComponentType; /** * Gets the selected video device id. * @returns device id. */ getVideoDeviceId(): string; /** * Gets available video devices. * @returns devices keyed by device id. */ getVideoDevices(): { [deviceId: string]: VideoDevice; }; /** * Gets whether video hook is enabled. * @returns true if enabled. */ getVideoHook(): boolean; /** * Gets video stream parameters. * @param context settings context, defaults to "default". * @returns array of stream parameters. */ getVideoStreamParameters(context?: MediaEngineContextType): VideoStreamParameter[]; /** * Gets the video toggle state for a user. * @param userId user to check. * @param context settings context, defaults to "default". * @returns toggle state, NONE if not in map. */ getVideoToggleState(userId: string, context?: MediaEngineContextType): VideoToggleState; /** * Gets whether voice filter playback is enabled. * @returns true if enabled. */ getVoiceFilterPlaybackEnabled(): boolean; /** * Gets whether go live simulcast is enabled. * @returns true if enabled. */ goLiveSimulcastEnabled(): boolean; /** * Checks if there is an active CallKit call. * @returns true if active. */ hasActiveCallKitCall(): boolean; /** * Checks if there is a clips source. * @returns true if has source. */ hasClipsSource(): boolean; /** * Checks if a context exists. * @param context context to check. * @returns true if exists. */ hasContext(context: MediaEngineContextType): boolean; /** * Checks if H265 hardware decode is available. * @returns true if available. */ hasH265HardwareDecode(): boolean; /** * Checks if advanced voice activity is supported. * @returns true if supported. */ isAdvancedVoiceActivitySupported(): boolean; /** * Checks if AEC dump is supported. * @returns true if supported. */ isAecDumpSupported(): boolean; /** * Checks if any local video is auto disabled. * @param context settings context, defaults to "default". * @returns true if any auto disabled. */ isAnyLocalVideoAutoDisabled(context?: MediaEngineContextType): boolean; /** * Checks if automatic gain control is supported. * @returns true if supported. */ isAutomaticGainControlSupported(): boolean; /** * Checks if self is deafened. * @returns true if deafened. */ isDeaf(): boolean; /** * Checks if hardware mute notice is enabled. * @returns true if enabled. */ isEnableHardwareMuteNotice(): boolean; /** * Checks if media engine is enabled. * @returns true if enabled. */ isEnabled(): boolean; /** * Checks if hardware mute is active. * @param context settings context, defaults to "default". * @returns true if hardware muted. */ isHardwareMute(context?: MediaEngineContextType): boolean; /** * Checks if input profile is custom. * @returns true if custom. */ isInputProfileCustom(): boolean; /** * Checks if user interaction is required. * @returns true if required. */ isInteractionRequired(): boolean; /** * Checks if a user is locally muted. * @param userId user to check. * @param context settings context, defaults to "default". * @returns true if muted. */ isLocalMute(userId: string, context?: MediaEngineContextType): boolean; /** * Checks if a user's video is auto disabled. * @param userId user to check. * @param context settings context, defaults to "default". * @returns true if auto disabled. */ isLocalVideoAutoDisabled(userId: string, context?: MediaEngineContextType): boolean; /** * Checks if a user's video is disabled. * @param userId user to check. * @param context settings context, defaults to "default". * @returns true if disabled. */ isLocalVideoDisabled(userId: string, context?: MediaEngineContextType): boolean; /** * Checks if media filter settings are loading. * @returns true if loading. */ isMediaFilterSettingLoading(): boolean; /** * Checks if self is muted. * @returns true if muted. */ isMute(): boolean; /** * Checks if native audio permission is ready. * @returns true if ready. */ isNativeAudioPermissionReady(): boolean; /** * Checks if there was a noise cancellation error. * @returns true if error occurred. */ isNoiseCancellationError(): boolean; /** * Checks if noise cancellation is supported. * @returns true if supported. */ isNoiseCancellationSupported(): boolean; /** * Checks if noise suppression is supported. * @returns true if supported. */ isNoiseSuppressionSupported(): boolean; /** * Checks if screen sharing is active. * @param context settings context, defaults to "default". * @returns true if sharing. */ isScreenSharing(context?: MediaEngineContextType): boolean; /** * Checks if self is deafened in context. * @param context settings context, defaults to "default". * @returns true if deafened. */ isSelfDeaf(context?: MediaEngineContextType): boolean; /** * Checks if self is muted in context. * @param context settings context, defaults to "default". * @returns true if muted. */ isSelfMute(context?: MediaEngineContextType): boolean; /** * Checks if self is temporarily muted. * @param context settings context, defaults to "default". * @returns true if temporarily muted. */ isSelfMutedTemporarily(context?: MediaEngineContextType): boolean; /** * Checks if simulcast is supported. * @returns true if supported. */ isSimulcastSupported(): boolean; /** * Checks if sound sharing is active. * @param context settings context, defaults to "default". * @returns true if sharing. */ isSoundSharing(context?: MediaEngineContextType): boolean; /** * Checks if media engine is supported. * @returns true if supported. */ isSupported(): boolean; /** * Checks if video is available. * @returns true if available. */ isVideoAvailable(): boolean; /** * Checks if video is enabled. * @returns true if enabled. */ isVideoEnabled(): boolean; /** notifies that mute/unmute sound was skipped. */ notifyMuteUnmuteSoundWasSkipped(): void; /** * Sets whether a user can have priority speaker. * @param userId user to set. * @param canHavePriority whether can have priority. */ setCanHavePriority(userId: string, canHavePriority: boolean): void; /** * Sets whether there is an active CallKit call. * @param active whether active. */ setHasActiveCallKitCall(active: boolean): void; /** * Checks if manual subsystem selection should be offered. * @returns true if should offer. */ shouldOfferManualSubsystemSelection(): boolean; /** * Checks if mute/unmute sound should be skipped. * @returns true if should skip. */ shouldSkipMuteUnmuteSound(): boolean; /** * Checks if bypass system input processing should be shown. * @returns true if should show. */ showBypassSystemInputProcessing(): boolean; /** starts preloading DAVE encryption. */ startDavePreload(): void; /** * Checks if a feature is supported. * @param feature feature to check. * @returns true if supported. */ supports(feature: MediaEngineFeature): boolean; /** * Checks if disable local video is supported. * @returns true if supported. */ supportsDisableLocalVideo(): boolean; /** * Checks if experimental soundshare is supported. * @returns true if supported. */ supportsExperimentalSoundshare(): boolean; /** * Checks if hook soundshare is supported. * @returns true if supported. */ supportsHookSoundshare(): boolean; /** * Checks if in-app capture is supported for an app. * @param appName application name. * @returns true if supported. */ supportsInApp(appName: string): boolean; /** * Checks if screen soundshare is supported. * @returns true if supported. */ supportsScreenSoundshare(): boolean; /** * Checks if system screenshare picker is supported. * @returns true if supported. */ supportsSystemScreensharePicker(): boolean; /** * Checks if video hook is supported. * @returns true if supported. */ supportsVideoHook(): boolean; } ================================================ FILE: packages/discord-types/src/stores/MessageStore.d.ts ================================================ import { FluxStore, Message } from ".."; export type JumpType = "ANIMATED" | "INSTANT"; export interface MessageCache { _messages: Message[]; _map: Record; _wasAtEdge: boolean; _isCacheBefore: boolean; } export interface ChannelMessages { channelId: string; ready: boolean; cached: boolean; jumpType: JumpType; jumpTargetId: string | null; jumpTargetOffset: number; jumpSequenceId: number; jumped: boolean; jumpedToPresent: boolean; jumpFlash: boolean; jumpReturnTargetId: string | null; focusTargetId: string | null; focusSequenceId: number; initialScrollSequenceId: number; hasMoreBefore: boolean; hasMoreAfter: boolean; loadingMore: boolean; revealedMessageId: string | null; hasFetched: boolean; error: boolean; _array: Message[]; _before: MessageCache; _after: MessageCache; _map: Record; } export class MessageStore extends FluxStore { focusedMessageId(channelId: string): string | undefined; getLastChatCommandMessage(channelId: string): Message | undefined; getLastEditableMessage(channelId: string): Message | undefined; getLastMessage(channelId: string): Message | undefined; getLastNonCurrentUserMessage(channelId: string): Message | undefined; getMessage(channelId: string, messageId: string): Message; /** @see {@link ChannelMessages} */ getMessages(channelId: string): ChannelMessages; hasCurrentUserSentMessage(channelId: string): boolean; hasCurrentUserSentMessageSinceAppStart(channelId: string): boolean; hasPresent(channelId: string): boolean; isLoadingMessages(channelId: string): boolean; isReady(channelId: string): boolean; jumpedMessageId(channelId: string): string | undefined; whenReady(channelId: string, callback: () => void): void; } ================================================ FILE: packages/discord-types/src/stores/NotificationSettingsStore.d.ts ================================================ import { FluxStore } from ".."; export type DesktopNotificationType = "ALL" | "ONLY_MENTIONS" | "NEVER"; export type TTSNotificationType = "ALL" | "ONLY_MENTIONS" | "NEVER"; export interface NotificationSettingsState { desktopType: DesktopNotificationType; disableAllSounds: boolean; disabledSounds: string[]; ttsType: TTSNotificationType; disableUnreadBadge: boolean; taskbarFlash: boolean; notifyMessagesInSelectedChannel: boolean; } export class NotificationSettingsStore extends FluxStore { get taskbarFlash(): boolean; getUserAgnosticState(): NotificationSettingsState; getDesktopType(): DesktopNotificationType; getTTSType(): TTSNotificationType; getDisabledSounds(): string[]; getDisableAllSounds(): boolean; getDisableUnreadBadge(): boolean; getNotifyMessagesInSelectedChannel(): boolean; isSoundDisabled(sound: string): boolean; } ================================================ FILE: packages/discord-types/src/stores/OverridePremiumTypeStore.d.ts ================================================ import { FluxStore } from ".."; export interface OverridePremiumTypeState { createdAtOverride: Date | undefined; premiumTypeActual: number | null; premiumTypeOverride: number | undefined; } export class OverridePremiumTypeStore extends FluxStore { getState(): OverridePremiumTypeState; getCreatedAtOverride(): Date | undefined; getPremiumTypeActual(): number | null; getPremiumTypeOverride(): number | undefined; get premiumType(): number | undefined; } ================================================ FILE: packages/discord-types/src/stores/PendingReplyStore.d.ts ================================================ import { Channel, Message } from "../common"; import { FluxStore } from "./FluxStore"; export interface PendingReply { channel: Channel; message: Message; shouldMention: boolean; showMentionToggle: boolean; } export class PendingReplyStore extends FluxStore { getPendingReply(channelId: string): PendingReply | undefined; /** Discord doesn't use this method. Also seems to always return undefined */ getPendingReplyActionSource(channelId: string): unknown; } ================================================ FILE: packages/discord-types/src/stores/PermissionStore.d.ts ================================================ import { Channel, Guild, Role, FluxStore } from ".."; export interface GuildPermissionProps { canManageGuild: boolean; canManageChannels: boolean; canManageRoles: boolean; canManageBans: boolean; canManageNicknames: boolean; canManageGuildExpressions: boolean; canViewAuditLog: boolean; canViewAuditLogV2: boolean; canManageWebhooks: boolean; canViewGuildAnalytics: boolean; canAccessMembersPage: boolean; isGuildAdmin: boolean; isOwner: boolean; isOwnerWithRequiredMfaLevel: boolean; guild: Guild; } export interface PartialChannelContext { channelId: string; } export interface PartialGuildContext { guildId: string; } export type PartialContext = PartialChannelContext | PartialGuildContext; type PartialChannel = Channel | { id: string; }; type PartialGuild = Guild | { id: string; }; export class PermissionStore extends FluxStore { // TODO: finish typing these can(permission: bigint, channelOrGuild: PartialChannel | PartialGuild, guildId?: string, overwrites?: Record, userId?: string): boolean; canBasicChannel(permission: bigint, channel: PartialChannel, guildId?: string, overwrites?: Record, userId?: string): boolean; canWithPartialContext(permission: bigint, context: PartialContext): boolean; canManageUser(permission: bigint, userOrUserId: string, guild: PartialGuild): boolean; canAccessGuildSettings(guild: PartialGuild): boolean; canAccessMemberSafetyPage(guild: PartialGuild): boolean; canImpersonateRole(guild: PartialGuild, role: Role): boolean; // TODO: finish typing computePermissions(channel: PartialChannel, guildId?: string, overwrites?: Record, userId?: string): bigint; computeBasicPermissions(channel: PartialChannel): number; getChannelPermissions(channel: PartialChannel): bigint; getGuildPermissions(guild: PartialGuild): bigint; getGuildPermissionProps(guild: PartialGuild): GuildPermissionProps; getHighestRole(guild: PartialGuild): Role | null; isRoleHigher(guild: PartialGuild, firstRole: Role | null, secondRole: Role | null): boolean; getGuildVersion(guildId: string): number; getChannelsVersion(): number; } ================================================ FILE: packages/discord-types/src/stores/PopoutWindowStore.d.ts ================================================ import { FluxStore } from ".."; /** * Known popout window key constants. * Used as the key parameter for PopoutWindowStore and PopoutActions methods. */ export type PopoutWindowKey = | "DISCORD_CHANNEL_CALL_POPOUT" | "DISCORD_CALL_TILE_POPOUT" | "DISCORD_SOUNDBOARD" | "DISCORD_RTC_DEBUG_POPOUT" | "DISCORD_CHANNEL_POPOUT" | "DISCORD_ACTIVITY_POPOUT" | "DISCORD_OVERLAY_POPOUT" | "DISCORD_DEVTOOLS_POPOUT"; /** * Popout window lifecycle event types. * Sent via postMessage from popout to parent window. */ export type PopoutWindowEventType = "loaded" | "unloaded"; /** * Persisted window position and size state. * Saved to localStorage and restored when reopening popouts. */ export interface PopoutWindowState { /** window x position on screen in pixels. */ x: number; /** window y position on screen in pixels. */ y: number; /** window inner width in pixels. */ width: number; /** window inner height in pixels. */ height: number; /** whether window stays above other windows, only on desktop app. */ alwaysOnTop?: boolean; } /** * Features passed to window.open() for popout configuration. * Merged with default features (menubar, toolbar, location, directories = false). */ export interface BrowserWindowFeatures { /** whether to show browser toolbar. */ toolbar?: boolean; /** whether to show menu bar. */ menubar?: boolean; /** whether to show location/address bar. */ location?: boolean; /** whether to show directory buttons. */ directories?: boolean; /** window width in pixels. */ width?: number; /** window height in pixels. */ height?: number; /** default width if no persisted state exists. */ defaultWidth?: number; /** default height if no persisted state exists. */ defaultHeight?: number; /** window left position in pixels. */ left?: number; /** window top position in pixels. */ top?: number; /** default always-on-top state, defaults to false. */ defaultAlwaysOnTop?: boolean; /** whether window can be moved by user. */ movable?: boolean; /** whether window can be resized by user. */ resizable?: boolean; /** whether window has a frame/border. */ frame?: boolean; /** whether window stays above other windows. */ alwaysOnTop?: boolean; /** whether window has a shadow (macOS). */ hasShadow?: boolean; /** whether window background is transparent. */ transparent?: boolean; /** whether to hide window from taskbar. */ skipTaskbar?: boolean; /** title bar style, null for default. */ titleBarStyle?: string | null; /** window background color as hex string. */ backgroundColor?: string; /** whether this is an out-of-process overlay window. */ outOfProcessOverlay?: boolean; } /** * Manages Discord's popout windows (voice calls, activities, etc.). * Extends PersistedStore to save window positions across sessions. * * Handles Flux actions: * - POPOUT_WINDOW_OPEN: opens a new popout window * - POPOUT_WINDOW_CLOSE: closes a popout window * - POPOUT_WINDOW_SET_ALWAYS_ON_TOP: toggles always-on-top (desktop only) * - POPOUT_WINDOW_ADD_STYLESHEET: injects stylesheet into all open popouts * - LOGOUT: closes all popout windows */ export class PopoutWindowStore extends FluxStore { /** * Gets the Window object for a popout. * @param key unique identifier for the popout window * @returns Window reference or undefined if not open */ getWindow(key: string): Window | undefined; /** * Gets persisted position/size state for a window. * State is saved when window closes and restored when reopened. * @param key unique identifier for the popout window * @returns saved state or undefined if never opened */ getWindowState(key: string): PopoutWindowState | undefined; /** * Gets all currently open popout window keys. * @returns array of window key identifiers */ getWindowKeys(): string[]; /** * Checks if a popout window is currently open. * @param key unique identifier for the popout window * @returns true if window exists and is not closed */ getWindowOpen(key: string): boolean; /** * Checks if a popout window has always-on-top enabled. * Only functional on desktop app (isPlatformEmbedded). * @param key unique identifier for the popout window * @returns true if always-on-top is enabled */ getIsAlwaysOnTop(key: string): boolean; /** * Checks if a popout window's document has focus. * @param key unique identifier for the popout window * @returns true if window document has focus */ getWindowFocused(key: string): boolean; /** * Checks if a popout window is visible (not minimized/hidden). * Uses document.visibilityState === "visible". * @param key unique identifier for the popout window * @returns true if window is visible */ getWindowVisible(key: string): boolean; /** * Gets all persisted window states. * Keyed by window identifier, contains position/size data. * @returns record of window key to persisted state */ getState(): Record; /** * Checks if a window is fully initialized and ready for rendering. * A window is fully initialized when it has: * - Window object created * - React root mounted * - Render function stored * @param key unique identifier for the popout window * @returns true if window is fully initialized */ isWindowFullyInitialized(key: string): boolean; /** * Checks if a popout window is in fullscreen mode. * Checks if document.fullscreenElement.id === "app-mount". * @param key unique identifier for the popout window * @returns true if window is fullscreen */ isWindowFullScreen(key: string): boolean; /** * Unmounts and closes a popout window. * Saves current position/size before closing. * Logs warning if window was not fully initialized. * @param key unique identifier for the popout window */ unmountWindow(key: string): void; } /** * Actions for managing popout windows. * Dispatches Flux actions to PopoutWindowStore. */ export interface PopoutActions { /** * Opens a new popout window. * If window with key already exists and is not out-of-process: * - On desktop: focuses the existing window via native module * - On web: calls window.focus() * @param key unique identifier for the popout window * @param render function that returns React element to render, receives key as arg * @param features window features (size, position, etc.) */ open(key: string, render: (key: string) => React.ReactNode, features?: BrowserWindowFeatures): void; /** * Closes a popout window. * Saves position/size state before closing unless preventPopoutClose setting is true. * @param key unique identifier for the popout window */ close(key: string): void; /** * Sets always-on-top state for a popout window. * Only functional on desktop app (isPlatformEmbedded). * @param key unique identifier for the popout window * @param alwaysOnTop whether window should stay above others */ setAlwaysOnTop(key: string, alwaysOnTop: boolean): void; /** * Note: Not actually in the Webpack Common. You have to add it yourself if you want to use it * * Injects a stylesheet into all open popout windows. * Validates origin matches current host or webpack public path. * @param url stylesheet URL to inject * @param integrity optional SRI integrity hash */ addStylesheet?(url: string, integrity?: string): void; /** * Note: Not actually in the Webpack Common. You have to add it yourself if you want to use it * * Opens a channel call popout for voice/video calls. * Dispatches CHANNEL_CALL_POPOUT_WINDOW_OPEN action. * @param channel channel object to open call popout for */ openChannelCallPopout?(channel: { id: string; }): void; /** * Note: Not actually in the Webpack Common. You have to add it yourself if you want to use it * * Opens a call tile popout for a specific participant. * Dispatches CALL_TILE_POPOUT_WINDOW_OPEN action. * @param channelId channel ID of the call * @param participantId user ID of the participant */ openCallTilePopout?(channelId: string, participantId: string): void; } ================================================ FILE: packages/discord-types/src/stores/PresenceStore.d.ts ================================================ import { Activity, OnlineStatus } from "../common"; import { FluxStore } from "./FluxStore"; export interface UserAndActivity { userId: string; activity: Activity; } export type DiscordPlatform = "desktop" | "mobile" | "web" | "embedded" | "vr"; export interface PresenceStoreState { presencesForGuilds: Record>; }>>; statuses: Record; activities: Record; filteredActivities: Record; hiddenActivities: Record; // TODO: finish typing activityMetadata: Record; clientStatuses: Record>>; } export class PresenceStore extends FluxStore { findActivity(userId: string, predicate: (activity: Activity) => boolean, guildId?: string): Activity | undefined; getActivities(userId: string, guildId?: string): Activity[]; // TODO: finish typing getActivityMetadata(userId: string): any; getAllApplicationActivities(applicationId: string): UserAndActivity[]; getApplicationActivity(userId: string, applicationId: string, guildId?: string): Activity | null; getClientStatus(userId: string): Record; getHiddenActivities(): Activity[]; /** literally just getActivities(...)[0] */ getPrimaryActivity(userId: string, guildId?: string): Activity | null; getState(): PresenceStoreState; getStatus(userId: string, guildId?: string | null, defaultStatus?: OnlineStatus): OnlineStatus; getUnfilteredActivities(userId: string, guildId?: string): Activity[]; getUserIds(): string[]; isMobileOnline(userId: string): boolean; } ================================================ FILE: packages/discord-types/src/stores/RTCConnectionStore.d.ts ================================================ import { FluxStore } from ".."; export type RTCConnectionState = | "DISCONNECTED" | "AWAITING_ENDPOINT" | "AUTHENTICATING" | "CONNECTING" | "RTC_DISCONNECTED" | "RTC_CONNECTING" | "RTC_CONNECTED" | "NO_ROUTE" | "ICE_CHECKING" | "DTLS_CONNECTING"; export type RTCConnectionQuality = "unknown" | "bad" | "average" | "fine"; export interface LastRTCConnectionState { duration: number | null; mediaSessionId: string | null; rtcConnectionId: string | null; wasEverMultiParticipant: boolean; wasEverRtcConnected: boolean; // TODO: type voiceStateAnalytics: any; channelId: string; } export interface RTCConnectionPacketStats { inbound: number; outbound: number; lost: number; } export interface VoiceStateStats { max_voice_state_count: number; } export interface SecureFramesState { state: string; } export interface SecureFramesRosterMapEntry { pendingVerifyState: number; verifiedState: number; } export class RTCConnectionStore extends FluxStore { // TODO: type getRTCConnection(): any | null; getState(): RTCConnectionState; isConnected(): boolean; isDisconnected(): boolean; getRemoteDisconnectVoiceChannelId(): string | null; getLastSessionVoiceChannelId(): string | null; setLastSessionVoiceChannelId(channelId: string | null): void; getGuildId(): string | undefined; getChannelId(): string | undefined; getHostname(): string; getQuality(): RTCConnectionQuality; getPings(): number[]; getAveragePing(): number; getLastPing(): number | undefined; getOutboundLossRate(): number | undefined; getMediaSessionId(): string | undefined; getRTCConnectionId(): string | undefined; getDuration(): number | undefined; getLastRTCConnectionState(): LastRTCConnectionState | null; getVoiceFilterSpeakingDurationMs(): number | undefined; getPacketStats(): RTCConnectionPacketStats | undefined; getVoiceStateStats(): VoiceStateStats | undefined; // TODO: finish typing getUserVoiceSettingsStats(userId: string): any | undefined; getWasEverMultiParticipant(): boolean; getWasEverRtcConnected(): boolean; getUserIds(): string[] | undefined; getJoinVoiceId(): string | null; isUserConnected(userId: string): boolean | undefined; getSecureFramesState(): SecureFramesState | undefined; getSecureFramesRosterMapEntry(oderId: string): SecureFramesRosterMapEntry | undefined; getLastNonZeroRemoteVideoSinkWantsTime(): number | null; getWasMoved(): boolean; } ================================================ FILE: packages/discord-types/src/stores/ReadStateStore.d.ts ================================================ import { Channel, FluxStore } from ".."; import { ReadStateType } from "../../enums"; export interface GuildChannelUnreadState { mentionCount: number; unread: boolean; isMentionLowImportance: boolean; } export interface ReadStateSnapshot { unread: boolean; mentionCount: number; guildUnread: boolean | null; guildMentionCount: number | null; takenAt: number; } export interface SerializedReadState { channelId: string; type: ReadStateType; _guildId: string; _persisted: boolean; _lastMessageId: string; _lastMessageTimestamp: number; _ackMessageId: string; _ackMessageTimestamp: number; ackPinTimestamp: number; lastPinTimestamp: number; _mentionCount: number; flags: number; lastViewed: number; } export class ReadStateStore extends FluxStore { ackMessageId(channelId: string, type?: ReadStateType): string | null; getAllReadStates(includePrivate?: boolean): SerializedReadState[]; getChannelIdsForWindowId(windowId: string): string[]; getForDebugging(channelId: string): object | undefined; getGuildChannelUnreadState( channel: Channel, isOptInEnabled: boolean, guildHasActiveThreads: boolean, isChannelMuted: boolean, isGuildHome: boolean ): GuildChannelUnreadState; getGuildUnreadsSentinel(guildId: string): number; getIsMentionLowImportance(channelId: string, type?: ReadStateType): boolean; getMentionChannelIds(): string[]; getMentionCount(channelId: string, type?: ReadStateType): number; getNonChannelAckId(type: ReadStateType): string | null; getNotifCenterReadState(channelId: string): object | undefined; getOldestUnreadMessageId(channelId: string, type?: ReadStateType): string | null; getOldestUnreadTimestamp(channelId: string, type?: ReadStateType): number; getReadStatesByChannel(): Record; getSnapshot(channelId: string, maxAge: number): ReadStateSnapshot; getTrackedAckMessageId(channelId: string, type?: ReadStateType): string | null; getUnreadCount(channelId: string, type?: ReadStateType): number; hasOpenedThread(channelId: string): boolean; hasRecentlyVisitedAndRead(channelId: string): boolean; hasTrackedUnread(channelId: string): boolean; hasUnread(channelId: string, type?: ReadStateType): boolean; hasUnreadOrMentions(channelId: string, type?: ReadStateType): boolean; hasUnreadPins(channelId: string): boolean; isEstimated(channelId: string, type?: ReadStateType): boolean; isForumPostUnread(channelId: string): boolean; isNewForumThread(threadId: string, parentChannelId: string, guildId: string): boolean; lastMessageId(channelId: string, type?: ReadStateType): string | null; lastMessageTimestamp(channelId: string, type?: ReadStateType): number; lastPinTimestamp(channelId: string): number; } ================================================ FILE: packages/discord-types/src/stores/RelationshipStore.d.ts ================================================ import { FluxStore } from ".."; import { RelationshipType } from "../../enums"; export class RelationshipStore extends FluxStore { getBlockedIDs(): string[]; getBlockedOrIgnoredIDs(): string[]; getFriendCount(): number; getFriendIDs(): string[]; getIgnoredIDs(): string[]; getMutableRelationships(): Map; getNickname(userId: string): string; getOriginApplicationId(userId: string): string | undefined; getOutgoingCount(): number; getPendingCount(): number; getPendingIgnoredCount(): number; getRelationshipCount(): number; /** @returns Enum value from constants.RelationshipTypes */ getRelationshipType(userId: string): RelationshipType; getSince(userId: string): string; getSinces(): Record; getSpamCount(): number; getVersion(): number; isBlocked(userId: string): boolean; isBlockedForMessage(userId: string): boolean; /** * @see {@link isBlocked} * @see {@link isIgnored} */ isBlockedOrIgnored(userId: string): boolean; isBlockedOrIgnoredForMessage(userId: string): boolean; isFriend(userId: string): boolean; isIgnored(userId: string): boolean; isIgnoredForMessage(userId: string): boolean; isSpam(userId: string): boolean; isStranger(userId: string): boolean; isUnfilteredPendingIncoming(userId: string): boolean; } ================================================ FILE: packages/discord-types/src/stores/RunningGameStore.d.ts ================================================ import { FluxStore } from ".."; export interface RunningGame { id?: string; name: string; exePath: string; cmdLine: string; distributor: string; lastFocused: number; lastLaunched: number; nativeProcessObserverId: number; pid?: number; hidden?: boolean; isLauncher?: boolean; elevated?: boolean; sandboxed?: boolean; } export interface GameOverlayStatus { enabledLegacy: boolean; enabledOOP: boolean; } export interface SystemServiceStatus { state: string; } export class RunningGameStore extends FluxStore { canShowAdminWarning: boolean; addExecutableTrackedByAnalytics(exe: string): void; getCandidateGames(): RunningGame[]; getCurrentGameForAnalytics(): RunningGame | null; getCurrentNonGameForAnalytics(): RunningGame | null; getGameForName(name: string): RunningGame | null; getGameForPID(pid: number): RunningGame | null; getGameOrTransformedSubgameForPID(pid: number): RunningGame | null; getGameOverlayStatus(game: RunningGame): GameOverlayStatus | null; getGamesSeen(includeHidden?: boolean): RunningGame[]; getLauncherForPID(pid: number): RunningGame | null; getObservedAppNameForWindow(windowHandle: number): string | null; getOverlayEnabledForGame(game: RunningGame): boolean; getOverlayOptionsForPID(pid: number): object | null; getOverrideForGame(game: RunningGame): object | null; getOverrides(): object[]; getRunningDiscordApplicationIds(): string[]; getRunningGames(): RunningGame[]; getRunningNonGames(): RunningGame[]; getRunningVerifiedApplicationIds(): string[]; getSeenGameByName(name: string): RunningGame | null; getSystemServiceStatus(service: string): SystemServiceStatus; getVisibleGame(): RunningGame | null; getVisibleRunningGames(): RunningGame[]; isDetectionEnabled(type?: string): boolean; isGamesSeenLoaded(): boolean; isObservedAppRunning(app: string): boolean; isSystemServiceInitialized(service: string): boolean; shouldContinueWithoutElevatedProcessForPID(pid: number): boolean; shouldElevateProcessForPID(pid: number): boolean; } ================================================ FILE: packages/discord-types/src/stores/SelectedChannelStore.d.ts ================================================ import { FluxStore } from ".."; export interface ChannelFollowingDestination { guildId?: string; channelId?: string; } export class SelectedChannelStore extends FluxStore { getChannelId(guildId?: string | null): string; getVoiceChannelId(): string | undefined; getCurrentlySelectedChannelId(guildId?: string): string | undefined; getMostRecentSelectedTextChannelId(guildId: string): string | undefined; getLastSelectedChannelId(guildId?: string): string; getLastSelectedChannels(guildId?: string): string; getLastChannelFollowingDestination(): ChannelFollowingDestination | undefined; } ================================================ FILE: packages/discord-types/src/stores/SelectedGuildStore.d.ts ================================================ import { FluxStore } from ".."; export interface SelectedGuildState { selectedGuildTimestampMillis: Record; selectedGuildId: string | null; lastSelectedGuildId: string | null; } export class SelectedGuildStore extends FluxStore { getGuildId(): string | null; getLastSelectedGuildId(): string | null; getLastSelectedTimestamp(guildId: string): number | null; getState(): SelectedGuildState | undefined; } ================================================ FILE: packages/discord-types/src/stores/SoundboardStore.d.ts ================================================ import { FluxStore } from ".."; export interface SoundboardSound { soundId: string; name: string; volume: number; emojiId: string | null; emojiName: string | null; available: boolean; guildId: string; userId?: string; } export interface TopSoundForGuild { soundId: string; rank: number; } export interface SoundboardOverlayState { soundboardSounds: Record; favoritedSoundIds: string[]; localSoundboardMutes: string[]; } export class SoundboardStore extends FluxStore { getOverlaySerializedState(): SoundboardOverlayState; getSounds(): Map; getSoundsForGuild(guildId: string): SoundboardSound[] | null; getSound(guildId: string, soundId: string): SoundboardSound; getSoundById(soundId: string): SoundboardSound; isFetchingSounds(): boolean; isFetchingDefaultSounds(): boolean; isFetching(): boolean; shouldFetchDefaultSounds(): boolean; hasFetchedDefaultSounds(): boolean; isUserPlayingSounds(userId: string): boolean; isPlayingSound(soundId: string): boolean; isFavoriteSound(soundId: string): boolean; getFavorites(): Set; getAllTopSoundsForGuilds(): Map; isLocalSoundboardMuted(userId: string): boolean; hasHadOtherUserPlaySoundInSession(): boolean; shouldFetchTopSoundsForGuilds(): boolean; hasFetchedTopSoundsForGuilds(): boolean; hasFetchedAllSounds(): boolean; isFetchingAnySounds(): boolean; } ================================================ FILE: packages/discord-types/src/stores/SpellCheckStore.d.ts ================================================ import { FluxStore } from ".."; export class SpellCheckStore extends FluxStore { hasLearnedWord(word: string): boolean; isEnabled(): boolean; } ================================================ FILE: packages/discord-types/src/stores/SpotifyStore.d.ts ================================================ import { FluxStore } from ".."; export interface SpotifyDevice { id: string; is_active: boolean; is_private_session: boolean; is_restricted: boolean; name: string; supports_volume: boolean; type: string; volume_percent: number; } export interface SpotifySocket { accessToken: string; accountId: string; connectionId: string; isPremium: boolean; socket: WebSocket; } export interface SpotifySocketAndDevice { socket: SpotifySocket; device: SpotifyDevice; } export interface SpotifyArtist { id: string; name: string; } export interface SpotifyImage { url: string; height: number; width: number; } export interface SpotifyAlbum { id: string; name: string; type: string; image: SpotifyImage | null; } export interface SpotifyTrack { id: string; name: string; duration: number; isLocal: boolean; type: string; album: SpotifyAlbum; artists: SpotifyArtist[]; } export interface SpotifyPlayerState { track: SpotifyTrack; startTime: number; context: { uri: string } | null; } export interface SpotifyActivity { name: string; assets: { large_image?: string; large_text?: string; }; details: string; state: string | undefined; timestamps: { start: number; end: number; }; party: { id: string; }; sync_id?: string; flags?: number; metadata?: { context_uri: string | undefined; album_id: string; artist_ids: string[]; type: string; button_urls: string[]; }; } export interface SpotifySyncingWith { oderId: string; partyId: string; sessionId: string; userId: string; } export class SpotifyStore extends FluxStore { hasConnectedAccount(): boolean; getActiveSocketAndDevice(): SpotifySocketAndDevice | null; getPlayableComputerDevices(): SpotifySocketAndDevice[]; canPlay(deviceId: string): boolean; getSyncingWith(): SpotifySyncingWith | undefined; wasAutoPaused(): boolean; getLastPlayedTrackId(): string | undefined; getTrack(): SpotifyTrack | null; getPlayerState(accountId: string): SpotifyPlayerState | null; shouldShowActivity(): boolean; getActivity(): SpotifyActivity | null; } ================================================ FILE: packages/discord-types/src/stores/StickersStore.d.ts ================================================ import { FluxStore, GuildSticker, PremiumStickerPack, Sticker } from ".."; export type StickerGuildMap = Map; export type StickerPackMap = Map; export class StickersStore extends FluxStore { hasLoadedStickerPacks: boolean; isFetchingStickerPacks: boolean; isLoaded: boolean; loadState: number; getAllGuildStickers(): StickerGuildMap; getAllPackStickers(): StickerPackMap; getPremiumPacks(): PremiumStickerPack[]; getRawStickersByGuild(): StickerGuildMap; getStickerById(id: string): Sticker | undefined; // TODO: type getStickerMetadataArrays(): any[]; getStickerPack(id: string): PremiumStickerPack | undefined; getStickersByGuildId(guildId: string): Sticker[] | undefined; isPremiumPack(id: string): boolean; } ================================================ FILE: packages/discord-types/src/stores/StreamerModeStore.d.ts ================================================ import { FluxStore } from ".."; export interface StreamerModeSettings { enabled: boolean; autoToggle: boolean; hideInstantInvites: boolean; hidePersonalInformation: boolean; disableSounds: boolean; disableNotifications: boolean; enableContentProtection: boolean; } export class StreamerModeStore extends FluxStore { get autoToggle(): boolean; get disableNotifications(): boolean; get disableSounds(): boolean; get enableContentProtection(): boolean; get enabled(): boolean; get hideInstantInvites(): boolean; get hidePersonalInformation(): boolean; getSettings(): StreamerModeSettings; getState(): Record; } ================================================ FILE: packages/discord-types/src/stores/ThemeStore.d.ts ================================================ import { FluxStore } from ".."; export type ThemePreference = "dark" | "light" | "unknown"; export type SystemTheme = "dark" | "light"; export type Theme = "light" | "dark" | "darker" | "midnight"; export interface ThemeState { theme: Theme; /** 0 = not loaded, 1 = loaded */ status: 0 | 1; preferences: Record; } export class ThemeStore extends FluxStore { get systemTheme(): SystemTheme; get theme(): Theme; getState(): ThemeState; themePreferenceForSystemTheme(preference: ThemePreference): Theme; } ================================================ FILE: packages/discord-types/src/stores/TypingStore.d.ts ================================================ import { FluxStore } from ".."; export class TypingStore extends FluxStore { /** * returns a map of user ids to timeout ids */ getTypingUsers(channelId: string): Record; isTyping(channelId: string, userId: string): boolean; } ================================================ FILE: packages/discord-types/src/stores/UploadAttachmentStore.d.ts ================================================ import { CloudUpload, FluxStore } from ".."; import { DraftType } from "../../enums"; export class UploadAttachmentStore extends FluxStore { getFirstUpload(channelId: string, draftType: DraftType): CloudUpload | null; hasAdditionalUploads(channelId: string, draftType: DraftType): boolean; getUploads(channelId: string, draftType: DraftType): CloudUpload[]; getUploadCount(channelId: string, draftType: DraftType): number; getUpload(channelId: string, uploadId: string, draftType: DraftType): CloudUpload; findUpload(channelId: string, draftType: DraftType, predicate: (upload: CloudUpload) => boolean): CloudUpload | undefined; } ================================================ FILE: packages/discord-types/src/stores/UserGuildSettingsStore.d.ts ================================================ import { Channel, FluxStore } from ".."; export interface MuteConfig { selected_time_window: number; end_time: string | null; } export interface ChannelOverride { muted: boolean; mute_config: MuteConfig | null; message_notifications: number; flags: number; collapsed: boolean; channel_id: string; } export interface GuildSettings { suppress_everyone: boolean; suppress_roles: boolean; mute_scheduled_events: boolean; mobile_push: boolean; muted: boolean; message_notifications: number; flags: number; channel_overrides: Record; notify_highlights: number; hide_muted_channels: boolean; version: number; mute_config: MuteConfig | null; guild_id: string; } export interface AccountNotificationSettings { flags: number; } export interface UserGuildSettingsState { useNewNotifications: boolean; } export class UserGuildSettingsStore extends FluxStore { get accountNotificationSettings(): AccountNotificationSettings; get mentionOnAllMessages(): boolean; get useNewNotifications(): boolean; allowAllMessages(guildId: string): boolean; allowNoMessages(guildId: string): boolean; getAddedToMessages(): string[]; // TODO: finish typing getAllSettings(): { userGuildSettings: Record; }; getChannelFlags(channel: Channel): number; getChannelIdFlags(guildId: string, channelId: string): number; getChannelMessageNotifications(guildId: string, channelId: string): number | null; getChannelMuteConfig(guildId: string, channelId: string): MuteConfig | null; getChannelOverrides(guildId: string): Record; getChannelRecordUnreadSetting(channel: Channel): number; getChannelUnreadSetting(guildId: string, channelId: string): number; getGuildFavorites(guildId: string): string[]; getGuildFlags(guildId: string): number; getGuildUnreadSetting(guildId: string): number; getMessageNotifications(guildId: string): number; getMuteConfig(guildId: string): MuteConfig | null; getMutedChannels(guildId: string): string[]; getNewForumThreadsCreated(guildId: string): boolean; getNotifyHighlights(guildId: string): number; getOptedInChannels(guildId: string): string[]; // TODO: finish typing these getOptedInChannelsWithPendingUpdates(guildId: string): Record; getPendingChannelUpdates(guildId: string): Record; getState(): UserGuildSettingsState; isAddedToMessages(channelId: string): boolean; isCategoryMuted(guildId: string, channelId: string): boolean; isChannelMuted(guildId: string, channelId: string): boolean; isChannelOptedIn(guildId: string, channelId: string, usePending?: boolean): boolean; isChannelOrParentOptedIn(guildId: string, channelId: string, usePending?: boolean): boolean; isChannelRecordOrParentOptedIn(channel: Channel, usePending?: boolean): boolean; isFavorite(guildId: string, channelId: string): boolean; isGuildCollapsed(guildId: string): boolean; isGuildOrCategoryOrChannelMuted(guildId: string, channelId: string): boolean; isMessagesFavorite(guildId: string): boolean; isMobilePushEnabled(guildId: string): boolean; isMuteScheduledEventsEnabled(guildId: string): boolean; isMuted(guildId: string): boolean; isOptInEnabled(guildId: string): boolean; isSuppressEveryoneEnabled(guildId: string): boolean; isSuppressRolesEnabled(guildId: string): boolean; isTemporarilyMuted(guildId: string): boolean; resolveGuildUnreadSetting(guildId: string): number; resolveUnreadSetting(channel: Channel): number; resolvedMessageNotifications(guildId: string): number; } ================================================ FILE: packages/discord-types/src/stores/UserProfileStore.d.ts ================================================ import { FluxStore, Guild, User, Application, ApplicationInstallParams } from ".."; import { ApplicationIntegrationType } from "../../enums"; export interface MutualFriend { /** * the userid of the mutual friend */ key: string; /** * the status of the mutual friend */ status: "online" | "offline" | "idle" | "dnd"; /** * the user object of the mutual friend */ user: User; } export interface MutualGuild { /** * the guild object of the mutual guild */ guild: Guild; /** * the user's nickname in the guild, if any */ nick: string | null; } export interface ProfileBadge { id: string; description: string; icon: string; link?: string; } export interface ConnectedAccount { type: "twitch" | "youtube" | "skype" | "steam" | "leagueoflegends" | "battlenet" | "bluesky" | "bungie" | "reddit" | "twitter" | "twitter_legacy" | "spotify" | "facebook" | "xbox" | "samsung" | "contacts" | "instagram" | "mastodon" | "soundcloud" | "github" | "playstation" | "playstation-stg" | "epicgames" | "riotgames" | "roblox" | "paypal" | "ebay" | "tiktok" | "crunchyroll" | "domain" | "amazon-music"; /** * underlying id of connected account * eg. account uuid */ id: string; /** * display name of connected account */ name: string; verified: boolean; metadata?: Record; } export interface ProfileApplication { id: string; customInstallUrl: string | undefined; installParams: ApplicationInstallParams | undefined; flags: number; popularApplicationCommandIds?: string[]; integrationTypesConfig: Record>; primarySkuId: string | undefined; storefront_available: boolean; } export interface UserProfileBase extends Pick { accentColor: number | null; /** * often empty for guild profiles, get the user profile for badges */ badges: ProfileBadge[]; bio: string | undefined; popoutAnimationParticleType: string | null; profileEffectExpiresAt: number | Date | undefined; profileEffectId: undefined | string; /** * often an empty string when not set */ pronouns: string | "" | undefined; themeColors: [number, number] | undefined; userId: string; } export interface ApplicationRoleConnection { application: Application; application_metadata: Record; metadata: Record; platform_name: string; platform_username: string; } export interface UserProfile extends UserProfileBase, Pick { /** If this is a bot user profile, this will be its application */ application: ProfileApplication | null; applicationRoleConnections: ApplicationRoleConnection[] | undefined; connectedAccounts: ConnectedAccount[] | undefined; fetchStartedAt: number; fetchEndedAt: number; legacyUsername: string | undefined; premiumGuildSince: Date | null; premiumSince: Date | null; } export interface ApplicationWidgetConfig { applicationId: string; widgetType: number; } export interface WishlistSettings { privacy: number; } export class UserProfileStore extends FluxStore { get applicationWidgetApplicationConfigs(): Record; get isSubmitting(): boolean; getApplicationWidgetApplicationConfig(applicationId: string): ApplicationWidgetConfig | undefined; getFirstWishlistId(userId: string): string | null; getGuildMemberProfile(userId: string, guildId: string | undefined): UserProfileBase | null; /** * Get the mutual friends of a user. * * @param userId the user ID of the user to get the mutual friends of. * * @returns an array of mutual friends, or undefined if the user has no mutual friends */ getMutualFriends(userId: string): MutualFriend[] | undefined; /** * Get the count of mutual friends for a user. * * @param userId the user ID of the user to get the mutual friends count of. * * @returns the count of mutual friends, or undefined if the user has no mutual friends */ getMutualFriendsCount(userId: string): number | undefined; /** * Get the mutual guilds of a user. * * @param userId the user ID of the user to get the mutual guilds of. * * @returns an array of mutual guilds, or undefined if the user has no mutual guilds */ getMutualGuilds(userId: string): MutualGuild[] | undefined; getUserProfile(userId: string): UserProfile | undefined; // TODO: finish typing getWidgets(userId: string): any[] | undefined; getWishlistIds(userId: string): string[]; getWishlistSettings(userId: string): WishlistSettings | null; /** * Check if mutual friends for {@link userId} are currently being fetched. * * @param userId the user ID of the mutual friends being fetched. * * @returns true if mutual friends are being fetched, false otherwise. */ isFetchingFriends(userId: string): boolean; /** * @param userId the user ID of the profile being fetched. * @param guildId the guild ID to of the profile being fetched. * defaults to the internal symbol `NO GUILD ID` if nullish * * @returns true if the profile is being fetched, false otherwise. */ isFetchingProfile(userId: string, guildId?: string): boolean; takeSnapshot(): Record; } ================================================ FILE: packages/discord-types/src/stores/UserSettingsProtoStore.d.ts ================================================ import { FluxStore } from ".."; export interface GuildFolder { guildIds: string[]; folderId?: number; folderName?: string; folderColor?: number; } export interface GuildProto { // TODO: finish typing channels: Record; hubProgress: number; guildOnboardingProgress: number; dismissedGuildContent: Record; disableRaidAlertPush: boolean; disableRaidAlertNag: boolean; leaderboardsDisabled: boolean; // TODO: finish typing guildDismissibleContentStates: Record; } export interface UserSettingsVersions { clientVersion: number; serverVersion: number; dataVersion: number; } export interface InboxSettings { currentTab: number; viewedTutorial: boolean; } export interface GuildsSettings { guilds: Record; } export interface UserContentSettings { dismissedContents: string; lastReceivedChangelogId: string; // TODO: finish typing recurringDismissibleContentStates: Record; // TODO: type lastDismissedOutboundPromotionStartDate: any; premiumTier0ModalDismissedAt: any; } export interface VoiceAndVideoSettings { // TODO: type videoBackgroundFilterDesktop: any; alwaysPreviewVideo: boolean; afkTimeout: number; streamNotificationsEnabled: boolean; nativePhoneIntegrationEnabled: boolean; disableStreamPreviews: boolean; soundmojiVolume: number; } export interface TextAndImagesSettings { emojiPickerCollapsedSections: string[]; stickerPickerCollapsedSections: string[]; soundboardPickerCollapsedSections: string[]; dmSpamFilterV2: number; viewImageDescriptions: boolean; inlineAttachmentMedia: boolean; inlineEmbedMedia: boolean; gifAutoPlay: boolean; renderEmbeds: boolean; renderReactions: boolean; animateEmoji: boolean; animateStickers: number; enableTtsCommand: boolean; messageDisplayCompact: boolean; explicitContentFilter: number; viewNsfwGuilds: boolean; convertEmoticons: boolean; viewNsfwCommands: boolean; includeStickersInAutocomplete: boolean; // TODO: type these explicitContentSettings: any; goreContentSettings: any; showMentionSuggestions: boolean; } export interface NotificationsSettings { notificationCenterAckedBeforeId: string; focusModeExpiresAtMs: string; reactionNotifications: number; gameActivityNotifications: boolean; customStatusPushNotifications: boolean; showInAppNotifications: boolean; notifyFriendsOnGoLive: boolean; enableVoiceActivityNotifications: boolean; enableUserResurrectionNotifications: boolean; } export interface PrivacySettings { restrictedGuildIds: string[]; defaultGuildsRestricted: boolean; allowAccessibilityDetection: boolean; activityRestrictedGuildIds: string[]; defaultGuildsActivityRestricted: boolean; activityJoiningRestrictedGuildIds: string[]; messageRequestRestrictedGuildIds: string[]; guildsLeaderboardOptOutDefault: boolean; slayerSdkReceiveDmsInGame: boolean; defaultGuildsActivityRestrictedV2: boolean; detectPlatformAccounts: boolean; passwordless: boolean; contactSyncEnabled: boolean; friendSourceFlags: number; friendDiscoveryFlags: number; dropsOptedOut: boolean; hideLegacyUsername: boolean; defaultGuildsRestrictedV2: boolean; quests3PDataOptedOut: boolean; } export interface GameLibrarySettings { disableGamesTab: boolean; } export interface StatusSettings { statusExpiresAtMs: string; status: { status: string; } | null; showCurrentGame: boolean; statusCreatedAtMs: string; } export interface LocalizationSettings { locale: { localeCode: string; } | null; timezoneOffset: { offset: number; } | null; } export interface AppearanceSettings { theme: number; developerMode: boolean; mobileRedesignDisabled: boolean; timestampHourCycle: number; launchPadMode: number; uiDensity: number; swipeRightToLeftMode: number; // TODO: type clientThemeSettings: any; } export interface GuildFoldersSettings { folders: GuildFolder[]; guildPositions: string[]; } export interface AudioContextSettings { // TODO: finish these user: Record; stream: Record; } export interface ClipsSettings { allowVoiceRecording: boolean; } export interface InAppFeedbackSettings { // TODO: finish typing inAppFeedbackStates: Record; } export interface UserSettings { versions: UserSettingsVersions; inbox: InboxSettings; guilds: GuildsSettings; userContent: UserContentSettings; voiceAndVideo: VoiceAndVideoSettings; textAndImages: TextAndImagesSettings; notifications: NotificationsSettings; privacy: PrivacySettings; // TODO: finish typing debug: Record; gameLibrary: GameLibrarySettings; status: StatusSettings; localization: LocalizationSettings; appearance: AppearanceSettings; guildFolders: GuildFoldersSettings; audioContextSettings: AudioContextSettings; clips: ClipsSettings; inAppFeedbackSettings: InAppFeedbackSettings; } export interface FrecencySettings { // TODO: type all of these versions: any; favoriteGifs: any; favoriteStickers: any; stickerFrecency: any; favoriteEmojis: any; emojiFrecency: any; applicationCommandFrecency: any; favoriteSoundboardSounds: any; applicationFrecency: any; playedSoundFrecency: any; guildAndChannelFrecency: any; emojiReactionFrecency: any; } export interface ProtoState { // TODO: type proto: any; } export class UserSettingsProtoStore extends FluxStore { settings: UserSettings; frecencyWithoutFetchingLatest: FrecencySettings; wasMostRecentUpdateFromServer: boolean; getState(): Record; computeState(): Record; getFullState(): Record; hasLoaded(settingsType: number): boolean; getGuildFolders(): GuildFolder[]; getGuildRecentsDismissedAt(guildId: string): number; getDismissedGuildContent(guildId: string): Record | null; // TODO: finish typing getGuildDismissedContentState(guildId: string): any; getGuildsProto(): Record; } ================================================ FILE: packages/discord-types/src/stores/UserStore.d.ts ================================================ import { FluxStore, User } from ".."; /** returned by takeSnapshot for persistence */ export interface UserStoreSnapshot { /** snapshot format version, currently 1 */ version: number; data: { /** contains only the current user */ users: User[]; }; } export class UserStore extends FluxStore { /** * filters users and optionally sorts results. * @param sort if true (default false), sorts alphabetically by username */ filter(filter: (user: User) => boolean, sort?: boolean): User[]; /** * finds user by username and discriminator. * for new username system (unique usernames), pass null/undefined as discriminator. */ findByTag(username: string, discriminator?: string | null): User | undefined; /** @param action return false to break iteration early */ forEach(action: (user: User) => boolean | void): void; getCurrentUser(): User; getUser(userId: string): User; /** keyed by user ID */ getUsers(): Record; /** increments when users are added/updated/removed */ getUserStoreVersion(): number; /** only includes current user, used for persistence */ takeSnapshot(): UserStoreSnapshot; } ================================================ FILE: packages/discord-types/src/stores/VoiceStateStore.d.ts ================================================ import { DiscordRecord } from "../common"; import { FluxStore } from "./FluxStore"; export type UserVoiceStateRecords = Record; export type VoiceStates = Record; export interface VoiceState extends DiscordRecord { userId: string; channelId: string | null | undefined; sessionId: string | null | undefined; mute: boolean; deaf: boolean; selfMute: boolean; selfDeaf: boolean; selfVideo: boolean; selfStream: boolean | undefined; suppress: boolean; requestToSpeakTimestamp: string | null | undefined; discoverable: boolean; isVoiceMuted(): boolean; isVoiceDeafened(): boolean; } export class VoiceStateStore extends FluxStore { getAllVoiceStates(): VoiceStates; getVoiceStateVersion(): number; getVoiceStates(guildId?: string | null): UserVoiceStateRecords; getVoiceStatesForChannel(channelId: string): UserVoiceStateRecords; getVideoVoiceStatesForChannel(channelId: string): UserVoiceStateRecords; getVoiceState(guildId: string | null, userId: string): VoiceState | undefined; getDiscoverableVoiceState(guildId: string | null, userId: string): VoiceState | null; getVoiceStateForChannel(channelId: string, userId?: string): VoiceState | undefined; getVoiceStateForUser(userId: string): VoiceState | undefined; getDiscoverableVoiceStateForUser(userId: string): VoiceState | undefined; getVoiceStateForSession(userId: string, sessionId?: string | null): VoiceState | null | undefined; getUserVoiceChannelId(guildId: string | null, userId: string): string | undefined; getCurrentClientVoiceChannelId(guildId: string | null): string | undefined; getUsersWithVideo(channelId: string): Set; getVoicePlatformForChannel(channelId: string, guildId: string): string | undefined; isCurrentClientInVoiceChannel(): boolean; isInChannel(channelId: string, userId?: string): boolean; hasVideo(channelId: string): boolean; get userHasBeenMovedVersion(): number; } ================================================ FILE: packages/discord-types/src/stores/WindowStore.d.ts ================================================ import { FluxStore } from ".."; export interface WindowSize { width: number; height: number; } export class WindowStore extends FluxStore { /** returns focused window ID, or null if no window is focused */ getFocusedWindowId(): string | null; getLastFocusedWindowId(): string; /** true if any window is focused (getFocusedWindowId() !== null) */ isAppFocused(): boolean; /** @param windowId defaults to current window */ isElementFullScreen(windowId?: string): boolean; /** @param windowId defaults to current window */ isFocused(windowId?: string): boolean; /** @param windowId defaults to current window */ isVisible(windowId?: string): boolean; /** @param windowId defaults to current window, returns {width: 0, height: 0} for invalid ID */ windowSize(windowId?: string): WindowSize; } ================================================ FILE: packages/discord-types/src/stores/index.d.ts ================================================ // please keep in alphabetical order export * from "./AccessibilityStore"; export * from "./ActiveJoinedThreadsStore"; export * from "./ApplicationStore"; export * from "./AuthenticationStore"; export * from "./CallStore"; export * from "./ChannelRTCStore"; export * from "./ChannelStore"; export * from "./DraftStore"; export * from "./EmojiStore"; export * from "./FluxStore"; export * from "./FriendsStore"; export * from "./GuildChannelStore"; export * from "./GuildMemberCountStore"; export * from "./GuildMemberStore"; export * from "./GuildRoleStore"; export * from "./GuildScheduledEventStore"; export * from "./GuildStore"; export * from "./InstantInviteStore"; export * from "./InviteStore"; export * from "./LocaleStore"; export * from "./MediaEngineStore"; export * from "./MessageStore"; export * from "./NotificationSettingsStore"; export * from "./OverridePremiumTypeStore"; export * from "./PendingReplyStore"; export * from "./PermissionStore"; export * from "./PopoutWindowStore"; export * from "./PresenceStore"; export * from "./ReadStateStore"; export * from "./RelationshipStore"; export * from "./RTCConnectionStore"; export * from "./RunningGameStore"; export * from "./SelectedChannelStore"; export * from "./SelectedGuildStore"; export * from "./SoundboardStore"; export * from "./SpellCheckStore"; export * from "./SpotifyStore"; export * from "./StickersStore"; export * from "./StreamerModeStore"; export * from "./ThemeStore"; export * from "./TypingStore"; export * from "./UploadAttachmentStore"; export * from "./UserGuildSettingsStore"; export * from "./UserProfileStore"; export * from "./UserSettingsProtoStore"; export * from "./UserStore"; export * from "./VoiceStateStore"; export * from "./WindowStore"; /** * React hook that returns stateful data for one or more stores * You might need a custom comparator (4th argument) if your store data is an object * @param stores The stores to listen to * @param mapper A function that returns the data you need * @param dependencies An array of reactive values which the hook depends on. Use this if your mapper or equality function depends on the value of another hook * @param isEqual A custom comparator for the data returned by mapper * * @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id); */ export type useStateFromStores = ( stores: any[], mapper: () => T, dependencies?: any, isEqual?: (old: T, newer: T) => boolean ) => T; ================================================ FILE: packages/discord-types/src/utils.d.ts ================================================ import { Channel, Guild, GuildMember, Message, User } from "."; import type { ReactNode } from "react"; import { LiteralUnion } from "type-fest"; import type { FluxEvents } from "./fluxEvents"; export { FluxEvents }; type FluxEventsAutoComplete = LiteralUnion; export interface FluxDispatcher { _actionHandlers: any; _subscriptions: any; dispatch(event: { [key: string]: unknown; type: FluxEventsAutoComplete; }): Promise; isDispatching(): boolean; subscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void; unsubscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void; wait(callback: () => void): void; } export type Parser = Record< | "parse" | "parseTopic" | "parseEmbedTitle" | "parseInlineReply" | "parseGuildVerificationFormRule" | "parseGuildEventDescription" | "parseAutoModerationSystemMessage" | "parseForumPostGuidelines" | "parseForumPostMostRecentMessage", (content: string, inline?: boolean, state?: Record) => ReactNode[] > & Record<"defaultRules" | "guildEventRules", Record>>; export interface Alerts { show(alert: { title: any; body: React.ReactNode; className?: string; confirmColor?: string; cancelText?: string; confirmText?: string; secondaryConfirmText?: string; onCancel?(): void; onConfirm?(): void; onConfirmSecondary?(): void; onCloseCallback?(): void; }): void; /** This is a noop, it does nothing. */ close(): void; } export interface SnowflakeUtils { fromTimestamp(timestamp: number): string; extractTimestamp(snowflake: string): number; age(snowflake: string): number; atPreviousMillisecond(snowflake: string): string; compare(snowflake1?: string, snowflake2?: string): number; } interface RestRequestData { url: string; query?: Record; body?: Record; oldFormErrors?: boolean; retries?: number; } export type RestAPI = Record<"del" | "get" | "patch" | "post" | "put", (data: RestRequestData) => Promise>; export type Permissions = "CREATE_INSTANT_INVITE" | "KICK_MEMBERS" | "BAN_MEMBERS" | "ADMINISTRATOR" | "MANAGE_CHANNELS" | "MANAGE_GUILD" | "CHANGE_NICKNAME" | "MANAGE_NICKNAMES" | "MANAGE_ROLES" | "MANAGE_WEBHOOKS" | "MANAGE_GUILD_EXPRESSIONS" | "CREATE_GUILD_EXPRESSIONS" | "VIEW_AUDIT_LOG" | "VIEW_CHANNEL" | "VIEW_GUILD_ANALYTICS" | "VIEW_CREATOR_MONETIZATION_ANALYTICS" | "MODERATE_MEMBERS" | "SEND_MESSAGES" | "SEND_TTS_MESSAGES" | "MANAGE_MESSAGES" | "EMBED_LINKS" | "ATTACH_FILES" | "READ_MESSAGE_HISTORY" | "MENTION_EVERYONE" | "USE_EXTERNAL_EMOJIS" | "ADD_REACTIONS" | "USE_APPLICATION_COMMANDS" | "MANAGE_THREADS" | "CREATE_PUBLIC_THREADS" | "CREATE_PRIVATE_THREADS" | "USE_EXTERNAL_STICKERS" | "SEND_MESSAGES_IN_THREADS" | "SEND_VOICE_MESSAGES" | "CONNECT" | "SPEAK" | "MUTE_MEMBERS" | "DEAFEN_MEMBERS" | "MOVE_MEMBERS" | "USE_VAD" | "PRIORITY_SPEAKER" | "STREAM" | "USE_EMBEDDED_ACTIVITIES" | "USE_SOUNDBOARD" | "USE_EXTERNAL_SOUNDS" | "REQUEST_TO_SPEAK" | "MANAGE_EVENTS" | "CREATE_EVENTS"; export type PermissionsBits = Record; export interface MessageSnapshot { message: Message; } export interface Locale { name: string; value: string; localizedName: string; } export interface LocaleInfo { code: string; enabled: boolean; name: string; englishName: string; postgresLang: string; } export interface Clipboard { copy(text: string): void; SUPPORTS_COPY: boolean; } export interface NavigationRouter { back(): void; forward(): void; transitionTo(path: string, ...args: unknown[]): void; transitionToGuild(guildId: string, ...args: unknown[]): void; } export interface ChannelRouter { transitionToChannel: (channelId: string) => void; transitionToThread: (channel: Channel) => void; } export interface IconUtils { getUserAvatarURL(user: User, canAnimate?: boolean, size?: number, format?: string): string; getDefaultAvatarURL(id: string, discriminator?: string): string; getUserBannerURL(data: { id: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined; getAvatarDecorationURL(dara: { avatarDecoration: string, size: number; canCanimate?: boolean; }): string | undefined; getGuildMemberAvatarURL(member: GuildMember, canAnimate?: string): string | null; getGuildMemberAvatarURLSimple(data: { guildId: string, userId: string, avatar: string, canAnimate?: boolean; size?: number; }): string; getGuildMemberBannerURL(data: { id: string, guildId: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined; getGuildIconURL(data: { id: string, icon?: string, size?: number, canAnimate?: boolean; }): string | undefined; getGuildBannerURL(guild: Guild, canAnimate?: boolean): string | null; getChannelIconURL(data: { id: string; icon?: string; applicationId?: string; size?: number; }): string | undefined; getEmojiURL(data: { id: string, animated: boolean, size: number, forcePNG?: boolean; }): string; hasAnimatedGuildIcon(guild: Guild): boolean; isAnimatedIconHash(hash: string): boolean; getGuildSplashURL: any; getGuildDiscoverySplashURL: any; getGuildHomeHeaderURL: any; getResourceChannelIconURL: any; getNewMemberActionIconURL: any; getGuildTemplateIconURL: any; getApplicationIconURL: any; getGameAssetURL: any; getVideoFilterAssetURL: any; getGuildMemberAvatarSource: any; getUserAvatarSource: any; getGuildSplashSource: any; getGuildDiscoverySplashSource: any; makeSource: any; getGameAssetSource: any; getGuildIconSource: any; getGuildTemplateIconSource: any; getGuildBannerSource: any; getGuildHomeHeaderSource: any; getChannelIconSource: any; getApplicationIconSource: any; getAnimatableSourceWithFallback: any; } export interface Constants { Endpoints: Record; UserFlags: Record; FriendsSections: Record; } export type ActiveView = LiteralUnion<"emoji" | "gif" | "sticker" | "soundboard", string>; export interface ExpressionPickerStoreState extends Record { activeView: ActiveView | null; lastActiveView: ActiveView | null; activeViewType: any | null; searchQuery: string; isSearchSuggestion: boolean, pickerId: string; } export interface ExpressionPickerStore { openExpressionPicker(activeView: ActiveView, activeViewType?: any): void; closeExpressionPicker(activeViewType?: any): void; toggleMultiExpressionPicker(activeViewType?: any): void; toggleExpressionPicker(activeView: ActiveView, activeViewType?: any): void; setExpressionPickerView(activeView: ActiveView): void; setSearchQuery(searchQuery: string, isSearchSuggestion?: boolean): void; useExpressionPickerStore(): ExpressionPickerStoreState; useExpressionPickerStore(selector: (state: ExpressionPickerStoreState) => T): T; } export { BrowserWindowFeatures, PopoutActions } from "./stores/PopoutWindowStore"; export type UserNameUtilsTagInclude = LiteralUnion<"auto" | "always" | "never", string>; export interface UserNameUtilsTagOptions { forcePomelo?: boolean; identifiable?: UserNameUtilsTagInclude; decoration?: UserNameUtilsTagInclude; mode?: "full" | "username"; } export interface UsernameUtils { getGlobalName(user: User): string; getFormattedName(user: User, useTagInsteadOfUsername?: boolean): string; getName(user: User): string; useName(user: User): string; getUserTag(user: User, options?: UserNameUtilsTagOptions): string; useUserTag(user: User, options?: UserNameUtilsTagOptions): string; useDirectMessageRecipient: any; humanizeStatus: any; } // TODO: fix type export class DisplayProfile { userId: string; banner?: string; bio?: string; pronouns?: string; accentColor?: number; themeColors?: number[]; popoutAnimationParticleType?: any; profileEffectId?: string; _userProfile?: any; _guildMemberProfile?: any; canUsePremiumProfileCustomization: boolean; canEditThemes: boolean; premiumGuildSince: Date | null; premiumSince: Date | null; premiumType?: number; primaryColor?: number; getBadges(): Array<{ id: string; description: string; icon: string; link?: string; }>; getBannerURL(options: { canAnimate: boolean; size: number; }): string; getLegacyUsername(): string | null; hasFullProfile(): boolean; hasPremiumCustomization(): boolean; hasThemeColors(): boolean; isUsingGuildMemberBanner(): boolean; isUsingGuildMemberBio(): boolean; isUsingGuildMemberPronouns(): boolean; } export interface DisplayProfileUtils { getDisplayProfile(userId: string, guildId?: string, customStores?: any): DisplayProfile | null; useDisplayProfile(userId: string, guildId?: string, customStores?: any): DisplayProfile | null; } export interface DateUtils { isSameDay(date1: Date, date2: Date): boolean; calendarFormat(date: Date): string; dateFormat(date: Date, format: string): string; diffAsUnits(start: Date, end: Date, stopAtOneSecond?: boolean): Record<"days" | "hours" | "minutes" | "seconds", number>; } export interface CommandOptions { type: number; name: string; description: string; required?: boolean; choices?: { name: string; values: string | number; }[]; options?: CommandOptions[]; channel_types?: number[]; min_value?: number; max_value?: number; autocomplete?: boolean; } ================================================ FILE: packages/discord-types/webpack/index.d.ts ================================================ /* * @vencord/discord-types * Copyright (c) 2024 Vendicated, Nuckyz and contributors * SPDX-License-Identifier: LGPL-3.0-or-later */ export type ModuleExports = any; export type Module = { id: PropertyKey; loaded: boolean; exports: ModuleExports; }; /** exports can be anything, however initially it is always an empty object */ export type ModuleFactory = (this: ModuleExports, module: Module, exports: ModuleExports, require: WebpackRequire) => void; /** Keys here can be symbols too, but we can't properly type them */ export type AsyncModulePromise = Promise & { "__webpack_queues__": (fnQueue: ((queue: any[]) => any)) => any; "__webpack_exports__": ModuleExports; "__webpack_error__"?: any; }; export type AsyncModuleBody = ( handleAsyncDependencies: (deps: AsyncModulePromise[]) => Promise<() => ModuleExports[]> | (() => ModuleExports[]), asyncResult: (error?: any) => void ) => Promise; export type EnsureChunkHandlers = { /** * Ensures the js file for this chunk is loaded, or starts to load if it's not. * @param chunkId The chunk id * @param promises The promises array to add the loading promise to */ j: (this: EnsureChunkHandlers, chunkId: PropertyKey, promises: Promise) => void; /** * Ensures the css file for this chunk is loaded, or starts to load if it's not. * @param chunkId The chunk id * @param promises The promises array to add the loading promise to. This array will likely contain the promise of the js file too */ css: (this: EnsureChunkHandlers, chunkId: PropertyKey, promises: Promise) => void; /** * Trigger for prefetching next chunks. This is called after ensuring a chunk is loaded and internally looks up * a map to see if the chunk that just loaded has next chunks to prefetch. * * Note that this does not add an extra promise to the promises array, and instead only executes the prefetching after * calling Promise.all on the promises array. * @param chunkId The chunk id * @param promises The promises array of ensuring the chunk is loaded */ prefetch: (this: EnsureChunkHandlers, chunkId: PropertyKey, promises: Promise) => void; }; export type PrefetchChunkHandlers = { /** * Prefetches the js file for this chunk. * @param chunkId The chunk id */ j: (this: PrefetchChunkHandlers, chunkId: PropertyKey) => void; }; export type ScriptLoadDone = (event: Event) => void; export type OnChunksLoaded = ((this: WebpackRequire, result: any, chunkIds: PropertyKey[] | undefined | null, callback: () => any, priority: number) => any) & { /** Check if a chunk has been loaded */ j: (this: OnChunksLoaded, chunkId: PropertyKey) => boolean; }; export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & { /** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */ m: Record; /** The module cache, where all modules which have been WebpackRequire'd are stored */ c: Record; // /** // * Export star. Sets properties of "fromObject" to "toObject" as getters that return the value from "fromObject", like this: // * @example // * const fromObject = { a: 1 }; // * Object.keys(fromObject).forEach(key => { // * if (key !== "default" && !Object.hasOwn(toObject, key)) { // * Object.defineProperty(toObject, key, { // * get: () => fromObject[key], // * enumerable: true // * }); // * } // * }); // * @returns fromObject // */ // es: (this: WebpackRequire, fromObject: AnyRecord, toObject: AnyRecord) => AnyRecord; /** * Creates an async module. A module that which has top level await, or requires an export from an async module. * * The body function must be an async function. "module.exports" will become an {@link AsyncModulePromise}. * * The body function will be called with a function to handle requires that import from an async module, and a function to resolve this async module. An example on how to handle async dependencies: * @example * const factory = (module, exports, wreq) => { * wreq.a(module, async (handleAsyncDependencies, asyncResult) => { * try { * const asyncRequireA = wreq(...); * * const asyncDependencies = handleAsyncDependencies([asyncRequire]); * const [requireAResult] = asyncDependencies.then != null ? (await asyncDependencies)() : asyncDependencies; * * // Use the required module * console.log(requireAResult); * * // Mark this async module as resolved * asyncResult(); * } catch(error) { * // Mark this async module as rejected with an error * asyncResult(error); * } * }, false); // false because our module does not have an await after dealing with the async requires * } */ a: (this: WebpackRequire, module: Module, body: AsyncModuleBody, hasAwaitAfterDependencies?: boolean) => void; /** getDefaultExport function for compatibility with non-harmony modules */ n: (this: WebpackRequire, exports: any) => () => ModuleExports; /** * Create a fake namespace object, useful for faking an __esModule with a default export. * * mode & 1: Value is a module id, require it * * mode & 2: Merge all properties of value into the namespace * * mode & 4: Return value when already namespace object * * mode & 16: Return value when it's Promise-like * * mode & (8|1): Behave like require */ t: (this: WebpackRequire, value: any, mode: number) => any; /** * Define getter functions for harmony exports. For every prop in "definiton" (the module exports), set a getter in "exports" for the getter function in the "definition", like this: * @example * const exports = {}; * const definition = { exportName: () => someExportedValue }; * for (const key in definition) { * if (Object.hasOwn(definition, key) && !Object.hasOwn(exports, key)) { * Object.defineProperty(exports, key, { * get: definition[key], * enumerable: true * }); * } * } * // exports is now { exportName: someExportedValue } (but each value is actually a getter) */ d: (this: WebpackRequire, exports: Record, definiton: Record ModuleExports>) => void; /** The ensure chunk handlers, which are used to ensure the files of the chunks are loaded, or load if necessary */ f: EnsureChunkHandlers; /** * The ensure chunk function, it ensures a chunk is loaded, or loads if needed. * Internally it uses the handlers in {@link WebpackRequire.f} to load/ensure the chunk is loaded. */ e: (this: WebpackRequire, chunkId: PropertyKey) => Promise; /** The prefetch chunk handlers, which are used to prefetch the files of the chunks */ F: PrefetchChunkHandlers; /** * The prefetch chunk function. * Internally it uses the handlers in {@link WebpackRequire.F} to prefetch a chunk. */ E: (this: WebpackRequire, chunkId: PropertyKey) => void; /** Get the filename for the css part of a chunk */ k: (this: WebpackRequire, chunkId: PropertyKey) => string; /** Get the filename for the js part of a chunk */ u: (this: WebpackRequire, chunkId: PropertyKey) => string; /** The global object, will likely always be the window */ g: typeof globalThis; /** Harmony module decorator. Decorates a module as an ES Module, and prevents Node.js "module.exports" from being set */ hmd: (this: WebpackRequire, module: Module) => any; /** Shorthand for Object.prototype.hasOwnProperty */ o: typeof Object.prototype.hasOwnProperty; /** * Function to load a script tag. "done" is called when the loading has finished or a timeout has occurred. * "done" will be attached to existing scripts loading if src === url or data-webpack === `${uniqueName}:${key}`, * so it will be called when that existing script finishes loading. */ l: (this: WebpackRequire, url: string, done: ScriptLoadDone, key?: string | number, chunkId?: PropertyKey) => void; /** Defines __esModule on the exports, marking ES Modules compatibility as true */ r: (this: WebpackRequire, exports: ModuleExports) => void; /** Node.js module decorator. Decorates a module as a Node.js module */ nmd: (this: WebpackRequire, module: Module) => any; /** * Register deferred code which will be executed when the passed chunks are loaded. * * If chunkIds is defined, it defers the execution of the callback and returns undefined. * * If chunkIds is undefined, and no deferred code exists or can be executed, it returns the value of the result argument. * * If chunkIds is undefined, and some deferred code can already be executed, it returns the result of the callback function of the last deferred code. * * When (priority & 1) it will wait for all other handlers with lower priority to be executed before itself is executed. */ O: OnChunksLoaded; /** * Instantiate a wasm instance with source using "wasmModuleHash", and importObject "importsObj", and then assign the exports of its instance to "exports". * @returns The exports argument, but now assigned with the exports of the wasm instance */ v: (this: WebpackRequire, exports: ModuleExports, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => Promise; /** Bundle public path, where chunk files are stored. Used by other methods which load chunks to obtain the full asset url */ p: string; /** The runtime id of the current runtime */ j: string; /** Document baseURI or WebWorker location.href */ b: string; /* rspack only */ /** rspack version */ rv: (this: WebpackRequire) => string; /** rspack unique id */ ruid: string; }; ================================================ FILE: packages/vencord-types/.gitignore ================================================ * !.*ignore !package.json !*.md !prepare.ts !index.d.ts !globals.d.ts ================================================ FILE: packages/vencord-types/.npmignore ================================================ node_modules prepare.ts .gitignore HOW2PUB.md ================================================ FILE: packages/vencord-types/HOW2PUB.md ================================================ # How to publish 1. run `pnpm generateTypes` in the project root 2. bump package.json version 3. npm publish ================================================ FILE: packages/vencord-types/README.md ================================================ # Vencord Types Typings for Vencord's api, published to npm ```sh npm i @vencord/types yarn add @vencord/types pnpm add @vencord/types ``` ================================================ FILE: packages/vencord-types/globals.d.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ declare global { export var VencordNative: typeof import("./VencordNative").default; export var Vencord: typeof import("./Vencord"); } export { }; ================================================ FILE: packages/vencord-types/index.d.ts ================================================ /* eslint-disable */ /// /// /// ================================================ FILE: packages/vencord-types/package.json ================================================ { "name": "@vencord/types", "private": false, "version": "1.14.1", "description": "", "types": "index.d.ts", "scripts": { "prepublishOnly": "tsx ./prepare.ts", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "Vencord", "license": "GPL-3.0", "devDependencies": { "@types/fs-extra": "^11.0.4", "fs-extra": "^11.3.0", "tsx": "^4.19.2" }, "dependencies": { "@types/lodash": "4.17.15", "@types/node": "^22.13.4", "@vencord/discord-types": "^1.0.0", "highlight.js": "11.11.1", "moment": "^2.22.2", "ts-pattern": "^5.6.0", "type-fest": "^4.35.0" }, "peerDependencies": { "@types/react": "18.3.1", "@types/react-dom": "18.3.1" } } ================================================ FILE: packages/vencord-types/prepare.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { cpSync, moveSync, readdirSync, rmSync } from "fs-extra"; import { join } from "path"; readdirSync(join(__dirname, "src")) .forEach(child => moveSync(join(__dirname, "src", child), join(__dirname, child), { overwrite: true })); const VencordSrc = join(__dirname, "..", "..", "src"); for (const file of ["preload.d.ts", "userplugins", "main", "debug", "src", "browser", "scripts"]) { rmSync(join(__dirname, file), { recursive: true, force: true }); } function copyDtsFiles(from: string, to: string) { for (const file of readdirSync(from, { withFileTypes: true })) { // bad if (from === VencordSrc && file.name === "globals.d.ts") continue; const fullFrom = join(from, file.name); const fullTo = join(to, file.name); if (file.isDirectory()) { copyDtsFiles(fullFrom, fullTo); } else if (file.name.endsWith(".d.ts")) { cpSync(fullFrom, fullTo); } } } copyDtsFiles(VencordSrc, __dirname); ================================================ FILE: patches/eslint-plugin-path-alias@2.1.0.patch ================================================ diff --git a/dist/index.js b/dist/index.js index 67de6fb139070fd0e49beca65e3b63c531202e16..aa2883c8126e4952a42872ee920f59547a066430 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1 +1 @@ -var C=Object.create;var f=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var $=(e,t)=>{for(var r in t)f(e,r,{get:t[r],enumerable:!0})},y=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of U(t))!F.call(e,s)&&s!==r&&f(e,s,{get:()=>t[s],enumerable:!(i=I(t,s))||i.enumerable});return e};var b=(e,t,r)=>(r=e!=null?C(S(e)):{},y(t||!e||!e.__esModule?f(r,"default",{value:e,enumerable:!0}):r,e)),D=e=>y(f({},"__esModule",{value:!0}),e);var N={};$(N,{default:()=>J});module.exports=D(N);var h="eslint-plugin-path-alias",v="2.0.0";var l=require("path"),M=b(require("nanomatch"));function j(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}var R=require("get-tsconfig"),a=require("path"),w=b(require("find-pkg")),O=require("fs");function P(e){if(e.options[0]?.paths)return z(e);let t=e.getFilename?.()??e.filename,r=(0,R.getTsconfig)(t);if(r?.config?.compilerOptions?.paths)return q(r);let i=w.default.sync((0,a.dirname)(t));if(!i)return;let s=JSON.parse((0,O.readFileSync)(i).toString());if(s?.imports)return L(s,i)}function L(e,t){let r=new Map,i=e.imports??{},s=(0,a.dirname)(t);return Object.entries(i).forEach(([o,n])=>{if(!n||typeof n!="string")return;let p=(0,a.resolve)(s,n);r.set(o,[p])}),r}function q(e){let t=new Map,r=e?.config?.compilerOptions?.paths??{},i=(0,a.dirname)(e.path);return e.config.compilerOptions?.baseUrl&&(i=(0,a.resolve)((0,a.dirname)(e.path),e.config.compilerOptions.baseUrl)),Object.entries(r).forEach(([s,o])=>{s=s.replace(/\/\*$/,""),o=o.map(n=>(0,a.resolve)(i,n.replace(/\/\*$/,""))),t.set(s,o)}),t}function z(e){let t=new Map,r=e.options[0]?.paths??{};return Object.entries(r).forEach(([i,s])=>{if(!s||typeof s!="string")return;if(s.startsWith("/")){t.set(i,[s]);return}let o=e.getCwd?.()??e.cwd,n=(0,a.resolve)(o,s);t.set(i,[n])}),t}var T={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:j("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let t=e.options[0]?.exceptions,r=e.getFilename?.()??e.filename,i=P(e);return i?.size?{ImportExpression(s){if(s.source.type!=="Literal"||typeof s.source.value!="string")return;let o=s.source.raw,n=s.source.value;if(!/^(\.?\.\/)/.test(n))return;let p=(0,l.resolve)((0,l.dirname)(r),n);if(A(p,t))return;let c=k(p,i);c&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:c},fix(m){let g=E(p,c,i.get(c)),d=o.replace(n,g);return m.replaceText(s.source,d)}})},ImportDeclaration(s){if(typeof s.source.value!="string")return;let o=s.source.value;if(!/^(\.?\.\/)/.test(o))return;let n=(0,l.resolve)((0,l.dirname)(r),o),p=A(n,t),u=k(n,i);p||u&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:u},fix(c){let m=s.source.raw,g=E(n,u,i.get(u)),d=m.replace(o,g);return c.replaceText(s.source,d)}})}}:{}}};function k(e,t){return Array.from(t.keys()).find(r=>t.get(r).some(s=>e.indexOf(s)===0))}function A(e,t){if(!t)return!1;let r=(0,l.basename)(e);return(0,M.default)(r,t).includes(r)}function E(e,t,r){for(let i of r)if(e.indexOf(i)===0)return e.replace(i,t)}var J={name:h,version:v,meta:{name:h,version:v},rules:{"no-relative":T}}; +var C=Object.create;var f=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var $=(e,t)=>{for(var r in t)f(e,r,{get:t[r],enumerable:!0})},y=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of U(t))!F.call(e,s)&&s!==r&&f(e,s,{get:()=>t[s],enumerable:!(i=I(t,s))||i.enumerable});return e};var b=(e,t,r)=>(r=e!=null?C(S(e)):{},y(t||!e||!e.__esModule?f(r,"default",{value:e,enumerable:!0}):r,e)),D=e=>y(f({},"__esModule",{value:!0}),e);var N={};$(N,{default:()=>J});module.exports=D(N);var h="eslint-plugin-path-alias",v="2.0.0";var l=require("path"),M=b(require("nanomatch"));function j(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}var R=require("get-tsconfig"),a=require("path"),w=b(require("find-pkg")),O=require("fs");function P(e){if(e.options[0]?.paths)return z(e);let t=e.getFilename?.()??e.filename,r=(0,R.getTsconfig)(t);if(r?.config?.compilerOptions?.paths)return q(r);let i=w.default.sync((0,a.dirname)(t));if(!i)return;let s=JSON.parse((0,O.readFileSync)(i).toString());if(s?.imports)return L(s,i)}function L(e,t){let r=new Map,i=e.imports??{},s=(0,a.dirname)(t);return Object.entries(i).forEach(([o,n])=>{if(!n||typeof n!="string")return;let p=(0,a.resolve)(s,n);r.set(o,[p])}),r}function q(e){let t=new Map,r=e?.config?.compilerOptions?.paths??{},i=(0,a.dirname)(e.path);return e.config.compilerOptions?.baseUrl&&(i=(0,a.resolve)((0,a.dirname)(e.path),e.config.compilerOptions.baseUrl)),Object.entries(r).forEach(([s,o])=>{s=s.replace(/\/\*$/,""),o=o.map(n=>(0,a.resolve)(i,n.replace(/\/\*$/,""))),t.set(s,o)}),t}function z(e){let t=new Map,r=e.options[0]?.paths??{};return Object.entries(r).forEach(([i,s])=>{if(!s||typeof s!="string")return;if(s.startsWith("/")){t.set(i,[s]);return}let o=e.getCwd?.()??e.cwd,n=(0,a.resolve)(o,s);t.set(i,[n])}),t}var T={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:j("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let t=e.options[0]?.exceptions,r=e.getFilename?.()??e.filename,i=P(e);return i?.size?{ImportExpression(s){if(s.source.type!=="Literal"||typeof s.source.value!="string")return;let o=s.source.raw,n=s.source.value;if(!/^(\.\.\/)/.test(n))return;let p=(0,l.resolve)((0,l.dirname)(r),n);if(A(p,t))return;let c=k(p,i);c&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:c},fix(m){let g=E(p,c,i.get(c)),d=o.replace(n,g);return m.replaceText(s.source,d)}})},ImportDeclaration(s){if(typeof s.source.value!="string")return;let o=s.source.value;if(!/^(\.\.\/)/.test(o))return;let n=(0,l.resolve)((0,l.dirname)(r),o),p=A(n,t),u=k(n,i);p||u&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:u},fix(c){let m=s.source.raw,g=E(n,u,i.get(u)),d=m.replace(o,g);return c.replaceText(s.source,d)}})}}:{}}};function k(e,t){return Array.from(t.keys()).find(r=>t.get(r).some(s=>e.indexOf(s)===0))}function A(e,t){if(!t)return!1;let r=(0,l.basename)(e);return(0,M.default)(r,t).includes(r)}function E(e,t,r){for(let i of r)if(e.indexOf(i)===0)return e.replace(i,t)}var J={name:h,version:v,meta:{name:h,version:v},rules:{"no-relative":T}}; diff --git a/dist/index.mjs b/dist/index.mjs index 96de18e06d4cc413e11af038cd760e4804c32e59..27e8c4e3e2c942400cc3982e52159904ca6eedfa 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -1 +1 @@ -var d="eslint-plugin-path-alias",h="2.0.0";import{dirname as x,resolve as j,basename as I}from"path";import U from"nanomatch";function y(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}import{getTsconfig as k}from"get-tsconfig";import{resolve as c,dirname as u}from"path";import A from"find-pkg";import{readFileSync as E}from"fs";function b(e){if(e.options[0]?.paths)return C(e);let s=e.getFilename?.()??e.filename,i=k(s);if(i?.config?.compilerOptions?.paths)return T(i);let r=A.sync(u(s));if(!r)return;let t=JSON.parse(E(r).toString());if(t?.imports)return M(t,r)}function M(e,s){let i=new Map,r=e.imports??{},t=u(s);return Object.entries(r).forEach(([o,n])=>{if(!n||typeof n!="string")return;let a=c(t,n);i.set(o,[a])}),i}function T(e){let s=new Map,i=e?.config?.compilerOptions?.paths??{},r=u(e.path);return e.config.compilerOptions?.baseUrl&&(r=c(u(e.path),e.config.compilerOptions.baseUrl)),Object.entries(i).forEach(([t,o])=>{t=t.replace(/\/\*$/,""),o=o.map(n=>c(r,n.replace(/\/\*$/,""))),s.set(t,o)}),s}function C(e){let s=new Map,i=e.options[0]?.paths??{};return Object.entries(i).forEach(([r,t])=>{if(!t||typeof t!="string")return;if(t.startsWith("/")){s.set(r,[t]);return}let o=e.getCwd?.()??e.cwd,n=c(o,t);s.set(r,[n])}),s}var P={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:y("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let s=e.options[0]?.exceptions,i=e.getFilename?.()??e.filename,r=b(e);return r?.size?{ImportExpression(t){if(t.source.type!=="Literal"||typeof t.source.value!="string")return;let o=t.source.raw,n=t.source.value;if(!/^(\.?\.\/)/.test(n))return;let a=j(x(i),n);if(w(a,s))return;let l=R(a,r);l&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:l},fix(f){let m=O(a,l,r.get(l)),g=o.replace(n,m);return f.replaceText(t.source,g)}})},ImportDeclaration(t){if(typeof t.source.value!="string")return;let o=t.source.value;if(!/^(\.?\.\/)/.test(o))return;let n=j(x(i),o),a=w(n,s),p=R(n,r);a||p&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:p},fix(l){let f=t.source.raw,m=O(n,p,r.get(p)),g=f.replace(o,m);return l.replaceText(t.source,g)}})}}:{}}};function R(e,s){return Array.from(s.keys()).find(i=>s.get(i).some(t=>e.indexOf(t)===0))}function w(e,s){if(!s)return!1;let i=I(e);return U(i,s).includes(i)}function O(e,s,i){for(let r of i)if(e.indexOf(r)===0)return e.replace(r,s)}var Q={name:d,version:h,meta:{name:d,version:h},rules:{"no-relative":P}};export{Q as default}; +var d="eslint-plugin-path-alias",h="2.0.0";import{dirname as x,resolve as j,basename as I}from"path";import U from"nanomatch";function y(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}import{getTsconfig as k}from"get-tsconfig";import{resolve as c,dirname as u}from"path";import A from"find-pkg";import{readFileSync as E}from"fs";function b(e){if(e.options[0]?.paths)return C(e);let s=e.getFilename?.()??e.filename,i=k(s);if(i?.config?.compilerOptions?.paths)return T(i);let r=A.sync(u(s));if(!r)return;let t=JSON.parse(E(r).toString());if(t?.imports)return M(t,r)}function M(e,s){let i=new Map,r=e.imports??{},t=u(s);return Object.entries(r).forEach(([o,n])=>{if(!n||typeof n!="string")return;let a=c(t,n);i.set(o,[a])}),i}function T(e){let s=new Map,i=e?.config?.compilerOptions?.paths??{},r=u(e.path);return e.config.compilerOptions?.baseUrl&&(r=c(u(e.path),e.config.compilerOptions.baseUrl)),Object.entries(i).forEach(([t,o])=>{t=t.replace(/\/\*$/,""),o=o.map(n=>c(r,n.replace(/\/\*$/,""))),s.set(t,o)}),s}function C(e){let s=new Map,i=e.options[0]?.paths??{};return Object.entries(i).forEach(([r,t])=>{if(!t||typeof t!="string")return;if(t.startsWith("/")){s.set(r,[t]);return}let o=e.getCwd?.()??e.cwd,n=c(o,t);s.set(r,[n])}),s}var P={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:y("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let s=e.options[0]?.exceptions,i=e.getFilename?.()??e.filename,r=b(e);return r?.size?{ImportExpression(t){if(t.source.type!=="Literal"||typeof t.source.value!="string")return;let o=t.source.raw,n=t.source.value;if(!/^(\.\.\/)/.test(n))return;let a=j(x(i),n);if(w(a,s))return;let l=R(a,r);l&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:l},fix(f){let m=O(a,l,r.get(l)),g=o.replace(n,m);return f.replaceText(t.source,g)}})},ImportDeclaration(t){if(typeof t.source.value!="string")return;let o=t.source.value;if(!/^(\.\.\/)/.test(o))return;let n=j(x(i),o),a=w(n,s),p=R(n,r);a||p&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:p},fix(l){let f=t.source.raw,m=O(n,p,r.get(p)),g=f.replace(o,m);return l.replaceText(t.source,g)}})}}:{}}};function R(e,s){return Array.from(s.keys()).find(i=>s.get(i).some(t=>e.indexOf(t)===0))}function w(e,s){if(!s)return!1;let i=I(e);return U(i,s).includes(i)}function O(e,s,i){for(let r of i)if(e.indexOf(r)===0)return e.replace(r,s)}var Q={name:d,version:h,meta:{name:d,version:h},rules:{"no-relative":P}};export{Q as default}; ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - packages/* ================================================ FILE: scripts/build/build.mjs ================================================ #!/usr/bin/node /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // @ts-check import { readdir } from "fs/promises"; import { join, resolve } from "path"; import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_ANTI_CRASH_TEST, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch, buildOrWatchAll, stringifyValues } from "./common.mjs"; const defines = stringifyValues({ IS_STANDALONE, IS_DEV, IS_REPORTER, IS_ANTI_CRASH_TEST, IS_UPDATER_DISABLED, IS_WEB: false, IS_EXTENSION: false, IS_USERSCRIPT: false, VERSION, BUILD_TIMESTAMP }); if (defines.IS_STANDALONE === "false") { // If this is a local build (not standalone), optimize // for the specific platform we're on defines["process.platform"] = JSON.stringify(process.platform); } /** * @type {import("esbuild").BuildOptions} */ const nodeCommonOpts = { ...commonOpts, define: defines, format: "cjs", platform: "node", target: ["esnext"], // @ts-expect-error this is never undefined external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external] }; const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`; const sourcemap = watch ? "inline" : "external"; /** * @type {import("esbuild").Plugin} */ const globNativesPlugin = { name: "glob-natives-plugin", setup: build => { const filter = /^~pluginNatives$/; build.onResolve({ filter }, args => { return { namespace: "import-natives", path: args.path }; }); build.onLoad({ filter, namespace: "import-natives" }, async () => { const pluginDirs = ["plugins", "userplugins"]; let code = ""; let natives = "\n"; let i = 0; /** * @type {string[]} */ const watchFiles = []; for (const dir of pluginDirs) { const dirPath = join("src", dir); if (!await exists(dirPath)) continue; const plugins = await readdir(dirPath, { withFileTypes: true }); for (const file of plugins) { const fileName = file.name; const nativePath = join(dirPath, fileName, "native.ts"); const indexNativePath = join(dirPath, fileName, "native/index.ts"); watchFiles.push(resolve(nativePath), resolve(indexNativePath)); if (!(await exists(nativePath)) && !(await exists(indexNativePath))) continue; const pluginName = await resolvePluginName(dirPath, file); const mod = `p${i}`; code += `import * as ${mod} from "./${dir}/${fileName}/native";\n`; natives += `${JSON.stringify(pluginName)}:${mod},\n`; i++; } } code += `export default {${natives}};`; return { contents: code, resolveDir: "./src", watchDirs: pluginDirs.map(d => resolve("src", d)), watchFiles, }; }); } }; /** @type {import("esbuild").BuildOptions[]} */ const buildConfigs = ([ // Discord Desktop main & renderer & preload { ...nodeCommonOpts, entryPoints: ["src/main/index.ts"], outfile: "dist/patcher.js", footer: { js: "//# sourceURL=file:///VencordPatcher\n" + sourceMapFooter("patcher") }, sourcemap, plugins: [ // @ts-ignore this is never undefined ...nodeCommonOpts.plugins, globNativesPlugin ], define: { ...defines, IS_DISCORD_DESKTOP: "true", IS_VESKTOP: "false" } }, { ...commonOpts, entryPoints: ["src/Vencord.ts"], outfile: "dist/renderer.js", format: "iife", target: ["esnext"], footer: { js: "//# sourceURL=file:///VencordRenderer\n" + sourceMapFooter("renderer") }, globalName: "Vencord", sourcemap, plugins: [ globPlugins("discordDesktop"), ...commonRendererPlugins ], define: { ...defines, IS_DISCORD_DESKTOP: "true", IS_VESKTOP: "false" } }, { ...nodeCommonOpts, entryPoints: ["src/preload.ts"], outfile: "dist/preload.js", footer: { js: "//# sourceURL=file:///VencordPreload\n" + sourceMapFooter("preload") }, sourcemap, define: { ...defines, IS_DISCORD_DESKTOP: "true", IS_VESKTOP: "false" } }, // Vencord Desktop main & renderer & preload { ...nodeCommonOpts, entryPoints: ["src/main/index.ts"], outfile: "dist/vencordDesktopMain.js", footer: { js: "//# sourceURL=file:///VencordDesktopMain\n" + sourceMapFooter("vencordDesktopMain") }, sourcemap, plugins: [ ...nodeCommonOpts.plugins, globNativesPlugin ], define: { ...defines, IS_DISCORD_DESKTOP: "false", IS_VESKTOP: "true" } }, { ...commonOpts, entryPoints: ["src/Vencord.ts"], outfile: "dist/vencordDesktopRenderer.js", format: "iife", target: ["esnext"], footer: { js: "//# sourceURL=file:///VencordDesktopRenderer\n" + sourceMapFooter("vencordDesktopRenderer") }, globalName: "Vencord", sourcemap, plugins: [ globPlugins("vesktop"), ...commonRendererPlugins ], define: { ...defines, IS_DISCORD_DESKTOP: "false", IS_VESKTOP: "true" } }, { ...nodeCommonOpts, entryPoints: ["src/preload.ts"], outfile: "dist/vencordDesktopPreload.js", footer: { js: "//# sourceURL=file:///VencordPreload\n" + sourceMapFooter("vencordDesktopPreload") }, sourcemap, define: { ...defines, IS_DISCORD_DESKTOP: "false", IS_VESKTOP: "true" } } ]); await buildOrWatchAll(buildConfigs); ================================================ FILE: scripts/build/buildWeb.mjs ================================================ #!/usr/bin/node /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // @ts-check import { readFileSync } from "fs"; import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises"; import { join } from "path"; import Zip from "zip-local"; import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, IS_ANTI_CRASH_TEST, VERSION, commonRendererPlugins, buildOrWatchAll, stringifyValues } from "./common.mjs"; /** * @type {import("esbuild").BuildOptions} */ const commonOptions = { ...commonOpts, entryPoints: ["browser/Vencord.ts"], format: "iife", globalName: "Vencord", external: ["~plugins", "~git-hash", "/assets/*"], target: ["esnext"], plugins: [ globPlugins("web"), ...commonRendererPlugins ], define: stringifyValues({ IS_WEB: true, IS_EXTENSION: false, IS_USERSCRIPT: false, IS_STANDALONE: true, IS_DEV, IS_REPORTER, IS_ANTI_CRASH_TEST, IS_DISCORD_DESKTOP: false, IS_VESKTOP: false, IS_UPDATER_DISABLED: true, VERSION, BUILD_TIMESTAMP }) }; const MonacoWorkerEntryPoints = [ "vs/language/css/css.worker.js", "vs/editor/editor.worker.js" ]; /** @type {import("esbuild").BuildOptions[]} */ const buildConfigs = [ { entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`), bundle: true, minify: true, format: "iife", outbase: "node_modules/monaco-editor/esm/", outdir: "dist/vendor/monaco" }, { entryPoints: ["browser/monaco.ts"], bundle: true, minify: true, format: "iife", outfile: "dist/vendor/monaco/index.js", loader: { ".ttf": "file" } }, { ...commonOptions, outfile: "dist/browser.js", footer: { js: "//# sourceURL=file:///VencordWeb" } }, { ...commonOptions, outfile: "dist/extension.js", define: { ...commonOptions.define, IS_EXTENSION: "true" }, footer: { js: "//# sourceURL=file:///VencordWeb" } }, { ...commonOptions, inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])], define: { ...commonOptions.define, IS_USERSCRIPT: "true", window: "unsafeWindow", }, outfile: "dist/Vencord.user.js", banner: { js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${VERSION}.${new Date().getTime()}`) }, footer: { // UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});" } } ]; await buildOrWatchAll(buildConfigs); /** * @type {(dir: string) => Promise} */ async function globDir(dir) { const files = []; for (const child of await readdir(dir, { withFileTypes: true })) { const p = join(dir, child.name); if (child.isDirectory()) files.push(...await globDir(p)); else files.push(p); } return files; } /** * @type {(dir: string, basePath?: string) => Promise>} */ async function loadDir(dir, basePath = "") { const files = await globDir(dir); return Object.fromEntries(await Promise.all(files.map(async f => [f.slice(basePath.length), await readFile(f)]))); } /** * @type {(target: string, files: string[]) => Promise} */ async function buildExtension(target, files) { const entries = { "dist/Vencord.js": await readFile("dist/extension.js"), "dist/Vencord.css": await readFile("dist/extension.css"), ...await loadDir("dist/vendor/monaco", "dist/"), ...Object.fromEntries(await Promise.all(files.map(async f => { let content = await readFile(join("browser", f)); if (f.startsWith("manifest")) { const json = JSON.parse(content.toString("utf-8")); json.version = VERSION; content = Buffer.from(new TextEncoder().encode(JSON.stringify(json))); } return [ f.startsWith("manifest") ? "manifest.json" : f, content ]; }))) }; await rm(target, { recursive: true, force: true }); await Promise.all(Object.entries(entries).map(async ([file, content]) => { const dest = join("dist", target, file); const parentDirectory = join(dest, ".."); await mkdir(parentDirectory, { recursive: true }); await writeFile(dest, content); })); console.info("Unpacked Extension written to dist/" + target); } const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => { const cssRuntime = `unsafeWindow._vcUserScriptRendererCss=\`${content.replaceAll("`", "\\`")}\``; return appendFile("dist/Vencord.user.js", cssRuntime); }); if (!process.argv.includes("--skip-extension")) { await Promise.all([ appendCssRuntime, buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]), buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]), ]); Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip"); console.info("Packed Chromium Extension written to dist/extension-chrome.zip"); Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip"); console.info("Packed Firefox Extension written to dist/extension-firefox.zip"); } else { await appendCssRuntime; } ================================================ FILE: scripts/build/common.mjs ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // @ts-check import "../suppressExperimentalWarnings.js"; import "../checkNodeVersion.js"; import { exec, execSync } from "child_process"; import esbuild, { build, context } from "esbuild"; import { constants as FsConstants, readFileSync } from "fs"; import { access, readdir, readFile } from "fs/promises"; import { minify as minifyHtml } from "html-minifier-terser"; import { optimize as optimizeSvg } from 'svgo'; import { join, relative, resolve } from "path"; import { promisify } from "util"; import { getPluginTarget } from "../utils.mjs"; import { builtinModules } from "module"; /** @type {import("../../package.json")} */ const PackageJSON = JSON.parse(readFileSync("package.json", "utf-8")); export const VERSION = PackageJSON.version; // https://reproducible-builds.org/docs/source-date-epoch/ export const BUILD_TIMESTAMP = Number(process.env.SOURCE_DATE_EPOCH) || Date.now(); export const watch = process.argv.includes("--watch"); export const IS_DEV = watch || process.argv.includes("--dev"); export const IS_REPORTER = process.argv.includes("--reporter"); export const IS_ANTI_CRASH_TEST = process.argv.includes("--anti-crash-test"); export const IS_STANDALONE = process.argv.includes("--standalone"); export const IS_UPDATER_DISABLED = process.argv.includes("--disable-updater"); export const gitHash = process.env.VENCORD_HASH || execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim(); export const banner = { js: ` // Vencord ${gitHash} // Standalone: ${IS_STANDALONE} // Platform: ${IS_STANDALONE === false ? process.platform : "Universal"} // Updater Disabled: ${IS_UPDATER_DISABLED} `.trim() }; /** * JSON.stringify all values in an object * @type {(obj: Record) => Record} */ export function stringifyValues(obj) { for (const key in obj) { obj[key] = JSON.stringify(obj[key]); } return obj; } /** * @param {import("esbuild").BuildOptions[]} buildConfigs */ export async function buildOrWatchAll(buildConfigs) { if (watch) { await Promise.all(buildConfigs.map(cfg => context(cfg).then(ctx => ctx.watch()) )); } else { await Promise.all(buildConfigs.map(cfg => build(cfg))) .catch(error => { console.error(error.message); process.exit(1); // exit immediately to skip the rest of the builds }); } } const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/; /** * @param {string} base * @param {import("fs").Dirent} dirent */ export async function resolvePluginName(base, dirent) { const fullPath = join(base, dirent.name); const content = dirent.isFile() ? await readFile(fullPath, "utf-8") : await (async () => { for (const file of ["index.ts", "index.tsx"]) { try { return await readFile(join(fullPath, file), "utf-8"); } catch { continue; } } throw new Error(`Invalid plugin ${fullPath}: could not resolve entry point`); })(); return PluginDefinitionNameMatcher.exec(content)?.[3] ?? (() => { throw new Error(`Invalid plugin ${fullPath}: must contain definePlugin call with simple string name property as first property`); })(); } export async function exists(path) { return await access(path, FsConstants.F_OK) .then(() => true) .catch(() => false); } // https://github.com/evanw/esbuild/issues/619#issuecomment-751995294 /** * @type {import("esbuild").Plugin} */ export const makeAllPackagesExternalPlugin = { name: "make-all-packages-external", setup(build) { const filter = /^[^./]|^\.[^./]|^\.\.[^/]/; // Must not start with "/" or "./" or "../" build.onResolve({ filter }, args => ({ path: args.path, external: true })); } }; /** * @type {(kind: "web" | "discordDesktop" | "vesktop") => import("esbuild").Plugin} */ export const globPlugins = kind => ({ name: "glob-plugins", setup: build => { const filter = /^~plugins$/; build.onResolve({ filter }, args => { return { namespace: "import-plugins", path: args.path }; }); build.onLoad({ filter, namespace: "import-plugins" }, async () => { const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"]; let code = ""; let pluginsCode = "\n"; let metaCode = "\n"; let excludedCode = "\n"; let i = 0; for (const dir of pluginDirs) { const userPlugin = dir === "userplugins"; const fullDir = `./src/${dir}`; if (!await exists(fullDir)) continue; const files = await readdir(fullDir, { withFileTypes: true }); for (const file of files) { const fileName = file.name; if (fileName.startsWith("_") || fileName.startsWith(".")) continue; if (fileName === "index.ts") continue; const target = getPluginTarget(fileName); if (target && !IS_REPORTER) { const excluded = (target === "dev" && !IS_DEV) || (target === "web" && kind === "discordDesktop") || (target === "desktop" && kind === "web") || (target === "discordDesktop" && kind !== "discordDesktop") || (target === "vesktop" && kind !== "vesktop"); if (excluded) { const name = await resolvePluginName(fullDir, file); excludedCode += `${JSON.stringify(name)}:${JSON.stringify(target)},\n`; continue; } } const folderName = `src/${dir}/${fileName}`.replace(/^src\/plugins\//, ""); const mod = `p${i}`; code += `import ${mod} from "./${dir}/${fileName.replace(/\.tsx?$/, "")}";\n`; pluginsCode += `[${mod}.name]:${mod},\n`; metaCode += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; i++; } } code += `export default {${pluginsCode}};export const PluginMeta={${metaCode}};export const ExcludedPlugins={${excludedCode}};`; return { contents: code, resolveDir: "./src", watchDirs: pluginDirs.map(d => resolve("src", d)), }; }); } }); /** * @type {import("esbuild").Plugin} */ export const gitHashPlugin = { name: "git-hash-plugin", setup: build => { const filter = /^~git-hash$/; build.onResolve({ filter }, args => ({ namespace: "git-hash", path: args.path })); build.onLoad({ filter, namespace: "git-hash" }, () => ({ contents: `export default "${gitHash}"` })); } }; /** * @type {import("esbuild").Plugin} */ export const gitRemotePlugin = { name: "git-remote-plugin", setup: build => { const filter = /^~git-remote$/; build.onResolve({ filter }, args => ({ namespace: "git-remote", path: args.path })); build.onLoad({ filter, namespace: "git-remote" }, async () => { let remote = process.env.VENCORD_REMOTE; if (!remote) { const res = await promisify(exec)("git remote get-url origin", { encoding: "utf-8" }); remote = res.stdout.trim() .replace("https://github.com/", "") .replace("git@github.com:", "") .replace(/.git$/, ""); } return { contents: `export default "${remote}"` }; }); } }; /** * @type {import("esbuild").Plugin} */ export const fileUrlPlugin = { name: "file-uri-plugin", setup: build => { const filter = /^file:\/\/.+$/; build.onResolve({ filter }, args => ({ namespace: "file-uri", path: args.path, pluginData: { uri: args.path, path: join(args.resolveDir, args.path.slice("file://".length).split("?")[0]) } })); build.onLoad({ filter, namespace: "file-uri" }, async ({ pluginData: { path, uri } }) => { const { searchParams } = new URL(uri); const base64 = searchParams.has("base64"); const minify = searchParams.has("minify"); const noTrim = searchParams.get("trim") === "false"; const encoding = base64 ? "base64" : "utf-8"; let content; if (!minify) { content = await readFile(path, encoding); if (!noTrim) content = content.trimEnd(); } else { if (path.endsWith(".html")) { content = await minifyHtml(await readFile(path, "utf-8"), { collapseWhitespace: true, removeComments: true, minifyCSS: true, minifyJS: true, removeEmptyAttributes: true, removeRedundantAttributes: true, removeScriptTypeAttributes: true, removeStyleLinkTypeAttributes: true, useShortDoctype: true }); } else if (path.endsWith(".svg")) { content = optimizeSvg(await readFile(path, "utf-8"), { datauri: base64 ? "base64" : void 0, multipass: true, floatPrecision: 2, }).data; } else if (/[mc]?[jt]sx?$/.test(path)) { const res = await esbuild.build({ entryPoints: [path], write: false, minify: true }); content = res.outputFiles[0].text; } else { throw new Error(`Don't know how to minify file type: ${path}`); } if (base64 && !content.startsWith("data:")) content = Buffer.from(content).toString("base64"); } return { contents: `export default ${JSON.stringify(content)}` }; }); } }; const styleModule = readFileSync("./scripts/build/module/style.js", "utf-8"); /** * @type {import("esbuild").Plugin} */ export const stylePlugin = { name: "style-plugin", setup: ({ onResolve, onLoad }) => { onResolve({ filter: /\.css\?managed$/, namespace: "file" }, ({ path, resolveDir }) => ({ path: relative(process.cwd(), join(resolveDir, path.replace("?managed", ""))), namespace: "managed-style", })); onLoad({ filter: /\.css$/, namespace: "managed-style" }, async ({ path }) => { const css = await readFile(path, "utf-8"); const name = relative(process.cwd(), path).replaceAll("\\", "/"); return { loader: "js", contents: styleModule .replaceAll("STYLE_SOURCE", JSON.stringify(css)) .replaceAll("STYLE_NAME", JSON.stringify(name)) }; }); } }; /** * @type {(filter: RegExp, message: string) => import("esbuild").Plugin} */ export const banImportPlugin = (filter, message) => ({ name: "ban-imports", setup: build => { build.onResolve({ filter }, () => { return { errors: [{ text: message }] }; }); } }); /** * @type {import("esbuild").BuildOptions} */ export const commonOpts = { logLevel: "info", bundle: true, minify: !watch && !IS_REPORTER, sourcemap: watch ? "inline" : "external", legalComments: "linked", banner, plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin], external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"], inject: ["./scripts/build/inject/react.mjs"], jsx: "transform", jsxFactory: "VencordCreateElement", jsxFragment: "VencordFragment" }; const escapedBuiltinModules = builtinModules .map(m => m.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")) .join("|"); const builtinModuleRegex = new RegExp(`^(node:)?(${escapedBuiltinModules})$`); export const commonRendererPlugins = [ banImportPlugin(builtinModuleRegex, "Cannot import node inbuilt modules in browser code. You need to use a native.ts file"), banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"), banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"), banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"), // @ts-expect-error this is never undefined ...commonOpts.plugins ]; ================================================ FILE: scripts/build/inject/react.mjs ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ export const VencordFragment = /* #__PURE__*/ Symbol.for("react.fragment"); export let VencordCreateElement = (...args) => (VencordCreateElement = Vencord.Webpack.Common.React.createElement)(...args); ================================================ FILE: scripts/build/module/style.js ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ (window.VencordStyles ??= new Map()).set(STYLE_NAME, { name: STYLE_NAME, source: STYLE_SOURCE, classNames: {}, dom: null, }); export default STYLE_NAME; ================================================ FILE: scripts/checkNodeVersion.js ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ if (Number(process.versions.node.split(".")[0]) < 18) throw `Your node version (${process.version}) is too old, please update to v18 or higher https://nodejs.org/en/download/`; ================================================ FILE: scripts/generatePluginList.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { Dirent, readdirSync, readFileSync, writeFileSync } from "fs"; import { access, readFile } from "fs/promises"; import { join, sep } from "path"; import { normalize as posixNormalize, sep as posixSep } from "path/posix"; import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, isVariableStatement, NamedDeclaration, NodeArray, ObjectLiteralExpression, ScriptTarget, StringLiteral, SyntaxKind } from "typescript"; import { getPluginTarget } from "./utils.mjs"; interface Dev { name: string; id: string; } interface PluginData { name: string; description: string; tags: string[]; authors: Dev[]; dependencies: string[]; hasPatches: boolean; hasCommands: boolean; required: boolean; enabledByDefault: boolean; target: "discordDesktop" | "vesktop" | "desktop" | "web" | "dev"; filePath: string; } const devs = {} as Record; function getName(node: NamedDeclaration) { return node.name && isIdentifier(node.name) ? node.name.text : undefined; } function hasName(node: NamedDeclaration, name: string) { return getName(node) === name; } function getObjectProp(node: ObjectLiteralExpression, name: string) { const prop = node.properties.find(p => hasName(p, name)); if (prop && isPropertyAssignment(prop)) return prop.initializer; return prop; } function parseDevs() { const file = createSourceFile("constants.ts", readFileSync("src/utils/constants.ts", "utf8"), ScriptTarget.Latest); for (const child of file.getChildAt(0).getChildren()) { if (!isVariableStatement(child)) continue; const devsDeclaration = child.declarationList.declarations.find(d => hasName(d, "Devs")); if (!devsDeclaration?.initializer || !isCallExpression(devsDeclaration.initializer)) continue; const value = devsDeclaration.initializer.arguments[0]; if (!isSatisfiesExpression(value) || !isObjectLiteralExpression(value.expression)) throw new Error("Failed to parse devs: not an object literal"); for (const prop of value.expression.properties) { const name = (prop.name as Identifier).text; const value = isPropertyAssignment(prop) ? prop.initializer : prop; if (!isObjectLiteralExpression(value)) throw new Error(`Failed to parse devs: ${name} is not an object literal`); devs[name] = { name: (getObjectProp(value, "name") as StringLiteral).text, id: (getObjectProp(value, "id") as BigIntLiteral).text.slice(0, -1) }; } return; } throw new Error("Could not find Devs constant"); } async function parseFile(fileName: string) { const file = createSourceFile(fileName, await readFile(fileName, "utf8"), ScriptTarget.Latest); const fail = (reason: string) => { return new Error(`Invalid plugin ${fileName}, because ${reason}`); }; for (const node of file.getChildAt(0).getChildren()) { if (!isExportAssignment(node) || !isCallExpression(node.expression)) continue; const call = node.expression; if (!isIdentifier(call.expression) || call.expression.text !== "definePlugin") continue; const pluginObj = node.expression.arguments[0]; if (!isObjectLiteralExpression(pluginObj)) throw fail("no object literal passed to definePlugin"); const data = { hasPatches: false, hasCommands: false, enabledByDefault: false, required: false, tags: [] as string[] } as PluginData; for (const prop of pluginObj.properties) { const key = getName(prop); const value = isPropertyAssignment(prop) ? prop.initializer : prop; switch (key) { case "name": case "description": if (!isStringLiteral(value)) throw fail(`${key} is not a string literal`); data[key] = value.text; break; case "patches": data.hasPatches = true; break; case "commands": data.hasCommands = true; break; case "authors": if (!isArrayLiteralExpression(value)) throw fail("authors is not an array literal"); data.authors = value.elements.map(e => { if (!isPropertyAccessExpression(e)) throw fail("authors array contains non-property access expressions"); const d = devs[getName(e)!]; if (!d) throw fail(`couldn't look up author ${getName(e)}`); return d; }); break; case "tags": if (!isArrayLiteralExpression(value)) throw fail("tags is not an array literal"); data.tags = value.elements.map(e => { if (!isStringLiteral(e)) throw fail("tags array contains non-string literals"); return e.text; }); break; case "dependencies": if (!isArrayLiteralExpression(value)) throw fail("dependencies is not an array literal"); const { elements } = value; if (elements.some(e => !isStringLiteral(e))) throw fail("dependencies array contains non-string elements"); data.dependencies = (elements as NodeArray).map(e => e.text); break; case "required": case "enabledByDefault": data[key] = value.kind === SyntaxKind.TrueKeyword; break; } } if (!data.name || !data.description || !data.authors) throw fail("name, description or authors are missing"); const target = getPluginTarget(fileName); if (target) { if (!["web", "discordDesktop", "vesktop", "desktop", "dev"].includes(target)) throw fail(`invalid target ${target}`); data.target = target as any; } data.filePath = posixNormalize(fileName) .split(sep) .join(posixSep) .replace(/\/index\.([jt]sx?)$/, "") .replace(/^src\/plugins\//, ""); let readme = ""; try { readme = readFileSync(join(fileName, "..", "README.md"), "utf-8"); } catch { } return [data, readme] as const; } throw fail("no default export called 'definePlugin' found"); } async function getEntryPoint(dir: string, dirent: Dirent) { const base = join(dir, dirent.name); if (!dirent.isDirectory()) return base; for (const name of ["index.ts", "index.tsx"]) { const full = join(base, name); try { await access(full); return full; } catch { } } throw new Error(`${dirent.name}: Couldn't find entry point`); } function isPluginFile({ name }: { name: string; }) { if (name === "index.ts") return false; return !name.startsWith("_") && !name.startsWith("."); } (async () => { parseDevs(); const plugins = [] as PluginData[]; const readmes = {} as Record; await Promise.all(["src/plugins", "src/plugins/_core"].flatMap(dir => readdirSync(dir, { withFileTypes: true }) .filter(isPluginFile) .map(async dirent => { const [data, readme] = await parseFile(await getEntryPoint(dir, dirent)); plugins.push(data); if (readme) readmes[data.name] = readme; }) )); const data = JSON.stringify(plugins); if (process.argv.length > 3) { writeFileSync(process.argv[2], data); writeFileSync(process.argv[3], JSON.stringify(readmes)); } else { console.log(data); } })(); ================================================ FILE: scripts/generateReport.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /// /// import { createHmac } from "crypto"; import { readFileSync } from "fs"; import pup, { JSHandle } from "puppeteer-core"; const logStderr = (...data: any[]) => console.error(`${CANARY ? "CANARY" : "STABLE"} ---`, ...data); for (const variable of ["CHROMIUM_BIN"]) { if (!process.env[variable]) { logStderr(`Missing environment variable ${variable}`); process.exit(1); } } const CANARY = process.env.USE_CANARY === "true"; let metaData = { buildNumber: "Unknown Build Number", buildHash: "Unknown Build Hash" }; const browser = await pup.launch({ headless: true, executablePath: process.env.CHROMIUM_BIN, args: ["--no-sandbox"] }); const page = await browser.newPage(); await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"); await page.setBypassCSP(true); async function maybeGetError(handle: JSHandle): Promise { return await (handle as JSHandle)?.getProperty("message") .then(m => m?.jsonValue()) .catch(() => undefined); } interface PatchInfo { plugin: string; type: string; id: string; match: string; error?: string; }; const report = { badPatches: [] as PatchInfo[], slowPatches: [] as PatchInfo[], badStarts: [] as { plugin: string; error: string; }[], otherErrors: [] as string[], ignoredErrors: [] as string[], badWebpackFinds: [] as string[] }; const IGNORED_DISCORD_ERRORS = [ "KeybindStore: Looking for callback action", "Unable to process domain list delta: Client revision number is null", "Downloading the full bad domains file", /\[GatewaySocket\].{0,110}Cannot access '/, "search for 'name' in undefined", "Attempting to set fast connect zstd when unsupported" ] as Array; function toCodeBlock(s: string, indentation = 0, isDiscord = false) { s = s.replace(/```/g, "`\u200B`\u200B`"); const indentationStr = Array(!isDiscord ? indentation : 0).fill(" ").join(""); return `\`\`\`\n${s.split("\n").map(s => indentationStr + s).join("\n")}\n${indentationStr}\`\`\``; } async function printReport() { console.log(); console.log("# Vencord Report" + (CANARY ? " (Canary)" : "")); console.log(); console.log("## Bad Patches"); report.badPatches.forEach(p => { console.log(`- ${p.plugin} (${p.type})`); console.log(` - ID: \`${p.id}\``); console.log(` - Match: ${toCodeBlock(p.match, " - Match: ".length)}`); if (p.error) console.log(` - Error: ${toCodeBlock(p.error, " - Error: ".length)}`); }); console.log(); console.log("## Bad Webpack Finds"); report.badWebpackFinds.forEach(p => console.log("- " + toCodeBlock(p, "- ".length))); console.log(); console.log("## Bad Starts"); report.badStarts.forEach(p => { console.log(`- ${p.plugin}`); console.log(` - Error: ${toCodeBlock(p.error, " - Error: ".length)}`); }); console.log(); console.log("## Discord Errors"); report.otherErrors.forEach(e => { console.log(`- ${toCodeBlock(e, "- ".length)}`); }); console.log(); console.log("## Ignored Discord Errors"); report.ignoredErrors.forEach(e => { console.log(`- ${toCodeBlock(e, "- ".length)}`); }); console.log(); if (process.env.WEBHOOK_URL) { const patchesToEmbed = (title: string, patches: PatchInfo[], color: number) => ({ title, color, description: patches.map(p => { const lines = [ `**__${p.plugin} (${p.type}):__**`, `ID: \`${p.id}\``, `Match: ${toCodeBlock(p.match, "Match: ".length, true)}` ]; if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`); return lines.join("\n"); }).join("\n\n"), }); const embeds = [ { author: { name: `Discord ${CANARY ? "Canary" : "Stable"} (${metaData.buildNumber})`, url: `https://nelly.tools/builds/app/${metaData.buildHash}`, icon_url: CANARY ? "https://cdn.discordapp.com/emojis/1252721945699549327.png?size=128" : "https://cdn.discordapp.com/emojis/1252721943463985272.png?size=128" }, color: CANARY ? 0xfbb642 : 0x5865f2 }, report.badPatches.length > 0 && patchesToEmbed("Bad Patches", report.badPatches, 0xff0000), report.slowPatches.length > 0 && patchesToEmbed("Slow Patches", report.slowPatches, 0xf0b232), report.badWebpackFinds.length > 0 && { title: "Bad Webpack Finds", description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None", color: 0xff0000 }, report.badStarts.length > 0 && { title: "Bad Starts", description: report.badStarts.map(p => { const lines = [ `**__${p.plugin}:__**`, toCodeBlock(p.error, 0, true) ]; return lines.join("\n"); } ).join("\n\n") || "None", color: 0xff0000 }, report.otherErrors.length > 0 && { title: "Discord Errors", description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None", color: 0xff0000 } ].filter(Boolean); if (embeds.length === 1) { embeds.push({ title: "No issues found", description: "Seems like everything is working fine (for now) <:shipit:1330992641466433556>", color: 0x00ff00 }); } const body = JSON.stringify({ username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""), embeds }); const headers = { "Content-Type": "application/json" }; // functions similar to https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries // used by venbot to ensure webhook invocations are genuine (since we will pass the webhook url as a workflow input which is publicly visible) // generate a secret with something like `openssl rand -hex 128` if (process.env.WEBHOOK_SECRET) { headers["X-Signature"] = "sha256=" + createHmac("sha256", process.env.WEBHOOK_SECRET).update(body).digest("hex"); } await fetch(process.env.WEBHOOK_URL, { method: "POST", headers, body }).then(res => { if (!res.ok) logStderr(`Webhook failed with status ${res.status}`); else logStderr("Posted to Webhook successfully"); }); } } page.on("console", async e => { const level = e.type(); const rawArgs = e.args(); async function getText(skipFirst = true) { let args = e.args(); if (skipFirst) args = args.slice(1); try { return await Promise.all( args.map(async a => { return await maybeGetError(a) || await a.jsonValue(); }) ).then(a => a.join(" ").trim()); } catch { return e.text(); } } const firstArg = await rawArgs[0]?.jsonValue(); const isVencord = firstArg === "[Vencord]"; const isDebug = firstArg === "[PUP_DEBUG]"; const isReporterMeta = firstArg === "[REPORTER_META]"; if (isReporterMeta) { metaData = await rawArgs[1].jsonValue() as any; return; } outer: if (isVencord) { try { var args = await Promise.all(e.args().map(a => a.jsonValue())); } catch { break outer; } const [, tag, message, otherMessage] = args as Array; switch (tag) { case "WebpackPatcher:": const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/); const patchSlowMatch = message.match(/Patch by (.+?) (took [\d.]+?ms) \(Module id is (.+?)\): (.+)/); const match = patchFailMatch ?? patchSlowMatch; if (!match) break; logStderr(await getText()); process.exitCode = 1; const [, plugin, type, id, regex] = match; const list = patchFailMatch ? report.badPatches : report.slowPatches; list.push({ plugin, type, id, match: regex, error: await maybeGetError(e.args()[3]) }); break; case "PluginManager:": const failedToStartMatch = message.match(/Failed to start (.+)/); if (!failedToStartMatch) break; logStderr(await getText()); process.exitCode = 1; const [, name] = failedToStartMatch; report.badStarts.push({ plugin: name, error: await maybeGetError(e.args()[3]) ?? "Unknown error" }); break; case "LazyChunkLoader:": logStderr(await getText()); switch (message) { case "A fatal error occurred:": process.exit(1); } break; case "Reporter:": logStderr(await getText()); switch (message) { case "A fatal error occurred:": process.exit(1); case "Webpack Find Fail:": process.exitCode = 1; report.badWebpackFinds.push(otherMessage); break; case "Finished test": await browser.close(); await printReport(); process.exit(); } } } if (isDebug) { logStderr(await getText()); } else if (level === "error") { const text = await getText(false); if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) { if (IGNORED_DISCORD_ERRORS.some(regex => text.match(regex))) { report.ignoredErrors.push(text); } else { logStderr("[Unexpected Error]", text); report.otherErrors.push(text); } } } }); page.on("error", e => logStderr("[Error]", e.message)); page.on("pageerror", (e: any) => { if (e.message.includes("Sentry successfully disabled")) return; if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) { logStderr("[Page Error]", e.message); report.otherErrors.push(e.message); } else { report.ignoredErrors.push(e.message); } }); await page.evaluateOnNewDocument(` if (location.host.endsWith("discord.com")) { ${readFileSync("./dist/browser.js", "utf-8")}; } `); await page.goto(CANARY ? "https://canary.discord.com/login" : "https://discord.com/login"); ================================================ FILE: scripts/header-new.txt ================================================ Vencord, a Discord client mod Copyright (c) {year} {author} SPDX-License-Identifier: GPL-3.0-or-later ================================================ FILE: scripts/header-old.txt ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) {year} {author} * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ ================================================ FILE: scripts/runInstaller.mjs ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import "./checkNodeVersion.js"; import { execFileSync, execSync } from "child_process"; import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; import { dirname, join } from "path"; import { Readable } from "stream"; import { finished } from "stream/promises"; import { fileURLToPath } from "url"; const BASE_URL = "https://github.com/Vencord/Installer/releases/latest/download/"; const INSTALLER_PATH_DARWIN = "VencordInstaller.app/Contents/MacOS/VencordInstaller"; const BASE_DIR = join(dirname(fileURLToPath(import.meta.url)), ".."); const FILE_DIR = join(BASE_DIR, "dist", "Installer"); const ETAG_FILE = join(FILE_DIR, "etag.txt"); function getFilename() { switch (process.platform) { case "win32": return "VencordInstallerCli.exe"; case "darwin": return "VencordInstaller.MacOS.zip"; case "linux": return "VencordInstallerCli-linux"; default: throw new Error("Unsupported platform: " + process.platform); } } async function ensureBinary() { const filename = getFilename(); console.log("Downloading " + filename); mkdirSync(FILE_DIR, { recursive: true }); const downloadName = join(FILE_DIR, filename); const outputFile = process.platform === "darwin" ? join(FILE_DIR, "VencordInstaller") : downloadName; const etag = existsSync(outputFile) && existsSync(ETAG_FILE) ? readFileSync(ETAG_FILE, "utf-8") : null; const res = await fetch(BASE_URL + filename, { headers: { "User-Agent": "Vencord (https://github.com/Vendicated/Vencord)", "If-None-Match": etag } }); if (res.status === 304) { console.log("Up to date, not redownloading!"); return outputFile; } if (!res.ok) throw new Error(`Failed to download installer: ${res.status} ${res.statusText}`); writeFileSync(ETAG_FILE, res.headers.get("etag")); if (process.platform === "darwin") { console.log("Unzipping..."); const zip = new Uint8Array(await res.arrayBuffer()); const ff = await import("fflate"); const bytes = ff.unzipSync(zip, { filter: f => f.name === INSTALLER_PATH_DARWIN })[INSTALLER_PATH_DARWIN]; writeFileSync(outputFile, bytes, { mode: 0o755 }); console.log("Overriding security policy for installer binary (this is required to run it)"); console.log("xattr might error, that's okay"); const logAndRun = cmd => { console.log("Running", cmd); try { execSync(cmd); } catch { } }; logAndRun(`sudo spctl --add '${outputFile}' --label "Vencord Installer"`); logAndRun(`sudo xattr -d com.apple.quarantine '${outputFile}'`); } else { // WHY DOES NODE FETCH RETURN A WEB STREAM OH MY GOD const body = Readable.fromWeb(res.body); await finished(body.pipe(createWriteStream(outputFile, { mode: 0o755, autoClose: true }))); } console.log("Finished downloading!"); return outputFile; } const installerBin = await ensureBinary(); console.log("Now running Installer..."); const argStart = process.argv.indexOf("--"); const args = argStart === -1 ? [] : process.argv.slice(argStart + 1); try { execFileSync(installerBin, args, { stdio: "inherit", env: { ...process.env, VENCORD_USER_DATA_DIR: BASE_DIR, VENCORD_DEV_INSTALL: "1" } }); } catch { console.error("Something went wrong. Please check the logs above."); } ================================================ FILE: scripts/suppressExperimentalWarnings.js ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ process.emit = (originalEmit => function (name, data) { if (name === "warning" && data?.name === "ExperimentalWarning") return false; return originalEmit.apply(process, arguments); })(process.emit); ================================================ FILE: scripts/utils.mjs ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * @param {string} filePath * @returns {string | null} */ export function getPluginTarget(filePath) { const pathParts = filePath.split(/[/\\]/); if (/^index\.tsx?$/.test(pathParts.at(-1))) pathParts.pop(); const identifier = pathParts.at(-1).replace(/\.tsx?$/, ""); const identiferBits = identifier.split("."); return identiferBits.length === 1 ? null : identiferBits.at(-1); } ================================================ FILE: src/Vencord.ts ================================================ /*! * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // DO NOT REMOVE UNLESS YOU WISH TO FACE THE WRATH OF THE CIRCULAR DEPENDENCY DEMON!!!!!!! import "~plugins"; export * as Api from "./api"; export * as Plugins from "./api/PluginManager"; export * as Components from "./components"; export * as Util from "./utils"; export * as Updater from "./utils/updater"; export * as Webpack from "./webpack"; export * as WebpackPatcher from "./webpack/patchWebpack"; export { PlainSettings, Settings }; import { coreStyleRootNode, initStyles } from "@api/Styles"; import { openSettingsTabModal, UpdaterTab } from "@components/settings"; import { debounce } from "@shared/debounce"; import { IS_WINDOWS } from "@utils/constants"; import { createAndAppendStyle } from "@utils/css"; import { StartAt } from "@utils/types"; import { SettingsRouter } from "@webpack/common"; import { get as dsGet } from "./api/DataStore"; import { NotificationData, showNotification } from "./api/Notifications"; import { initPluginManager, PMLogger, startAllPlugins } from "./api/PluginManager"; import { PlainSettings, Settings, SettingsStore } from "./api/Settings"; import { getCloudSettings, putCloudSettings, shouldCloudSync } from "./api/SettingsSync/cloudSync"; import { localStorage } from "./utils/localStorage"; import { relaunch } from "./utils/native"; import { checkForUpdates, update, UpdateLogger } from "./utils/updater"; import { onceReady } from "./webpack"; import { patches } from "./webpack/patchWebpack"; if (IS_REPORTER) { require("./debug/runReporter"); } async function syncSettings() { if (localStorage.Vencord_cloudSyncDirection === undefined) { // by default, sync bi-directionally localStorage.Vencord_cloudSyncDirection = "both"; } // pre-check for local shared settings if ( Settings.cloud.authenticated && !await dsGet("Vencord_cloudSecret") // this has been enabled due to local settings share or some other bug ) { // show a notification letting them know and tell them how to fix it showNotification({ title: "Cloud Integrations", body: "We've noticed you have cloud integrations enabled in another client! Due to limitations, you will " + "need to re-authenticate to continue using them. Click here to go to the settings page to do so!", color: "var(--yellow-360)", onClick: () => SettingsRouter.openUserSettings("vencord_cloud_panel") }); return; } if ( Settings.cloud.settingsSync && // if it's enabled Settings.cloud.authenticated && // if cloud integrations are enabled localStorage.Vencord_cloudSyncDirection !== "manual" // if we're not in manual mode ) { if (localStorage.Vencord_settingsDirty && shouldCloudSync("push")) { await putCloudSettings(); } else if (shouldCloudSync("pull") && await getCloudSettings(false)) { // if we synchronized something (false means no sync) // we show a notification here instead of allowing getCloudSettings() to show one to declutter the amount of // potential notifications that might occur. getCloudSettings() will always send a notification regardless if // there was an error to notify the user, but besides that we only want to show one notification instead of all // of the possible ones it has (such as when your settings are newer). showNotification({ title: "Cloud Settings", body: "Your settings have been updated! Click here to restart to fully apply changes!", color: "var(--green-360)", onClick: relaunch }); } } const saveSettingsOnFrequentAction = debounce(async () => { if (Settings.cloud.settingsSync && Settings.cloud.authenticated && shouldCloudSync("push")) { await putCloudSettings(); } }, 60_000); SettingsStore.addGlobalChangeListener(() => { localStorage.Vencord_settingsDirty = true; saveSettingsOnFrequentAction(); }); } let notifiedForUpdatesThisSession = false; async function runUpdateCheck() { if (IS_UPDATER_DISABLED) return; const notify = (data: NotificationData) => { if (notifiedForUpdatesThisSession) return; notifiedForUpdatesThisSession = true; setTimeout(() => showNotification({ permanent: true, noPersist: true, ...data }), 10_000); }; try { const isOutdated = await checkForUpdates(); if (!isOutdated) return; if (Settings.autoUpdate) { await update(); if (Settings.autoUpdateNotification) { notify({ title: "Vencord has been updated!", body: "Click here to restart", onClick: relaunch }); } return; } notify({ title: "A Vencord update is available!", body: "Click here to view the update", onClick: () => openSettingsTabModal(UpdaterTab!) }); } catch (err) { UpdateLogger.error("Failed to check for updates", err); } } async function init() { await onceReady; startAllPlugins(StartAt.WebpackReady); syncSettings(); if (!IS_WEB && !IS_UPDATER_DISABLED) { runUpdateCheck(); // this tends to get really annoying, so only do this if the user has auto-update without notification enabled if (Settings.autoUpdate && !Settings.autoUpdateNotification) { setInterval(runUpdateCheck, 1000 * 60 * 30); // 30 minutes } } if (IS_DEV) { const pendingPatches = patches.filter(p => !p.all && p.predicate?.() !== false); if (pendingPatches.length) PMLogger.warn( "Webpack has finished initialising, but some patches haven't been applied yet.", "This might be expected since some Modules are lazy loaded, but please verify", "that all plugins are working as intended.", "You are seeing this warning because this is a Development build of Vencord.", "\nThe following patches have not been applied:", "\n\n" + pendingPatches.map(p => `${p.plugin}: ${p.find}`).join("\n") ); } } initPluginManager(); initStyles(); startAllPlugins(StartAt.Init); init(); document.addEventListener("DOMContentLoaded", () => { startAllPlugins(StartAt.DOMContentLoaded); // FIXME if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && IS_WINDOWS) { createAndAppendStyle("vencord-native-titlebar-style", coreStyleRootNode).textContent = "[class*=titleBar]{display: none!important}"; } }, { once: true }); ================================================ FILE: src/VencordNative.ts ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2023 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import type { Settings } from "@api/Settings"; import type { CspRequestResult } from "@main/csp/manager"; import type { PluginIpcMappings } from "@main/ipcPlugins"; import type { UserThemeHeader } from "@main/themes"; import { IpcEvents } from "@shared/IpcEvents"; import type { IpcRes } from "@utils/types"; import { ipcRenderer } from "electron/renderer"; export function invoke(event: IpcEvents, ...args: any[]) { return ipcRenderer.invoke(event, ...args) as Promise; } export function sendSync(event: IpcEvents, ...args: any[]) { return ipcRenderer.sendSync(event, ...args) as T; } const PluginHelpers = {} as Record Promise>>; const pluginIpcMap = sendSync(IpcEvents.GET_PLUGIN_IPC_METHOD_MAP); for (const [plugin, methods] of Object.entries(pluginIpcMap)) { const map = PluginHelpers[plugin] = {}; for (const [methodName, method] of Object.entries(methods)) { map[methodName] = (...args: any[]) => invoke(method as IpcEvents, ...args); } } export default { themes: { uploadTheme: async (fileName: string, fileData: string): Promise => { throw new Error("uploadTheme is WEB only"); }, deleteTheme: async (fileName: string): Promise => { throw new Error("deleteTheme is WEB only"); }, getThemesList: () => invoke>(IpcEvents.GET_THEMES_LIST), getThemeData: (fileName: string) => invoke(IpcEvents.GET_THEME_DATA, fileName), getSystemValues: () => invoke>(IpcEvents.GET_THEME_SYSTEM_VALUES), openFolder: () => invoke(IpcEvents.OPEN_THEMES_FOLDER), }, updater: { getUpdates: () => invoke[]>>(IpcEvents.GET_UPDATES), update: () => invoke>(IpcEvents.UPDATE), rebuild: () => invoke>(IpcEvents.BUILD), getRepo: () => invoke>(IpcEvents.GET_REPO), }, settings: { get: () => sendSync(IpcEvents.GET_SETTINGS), set: (settings: Settings, pathToNotify?: string) => invoke(IpcEvents.SET_SETTINGS, settings, pathToNotify), openFolder: () => invoke(IpcEvents.OPEN_SETTINGS_FOLDER), }, quickCss: { get: () => invoke(IpcEvents.GET_QUICK_CSS), set: (css: string) => invoke(IpcEvents.SET_QUICK_CSS, css), addChangeListener(cb: (newCss: string) => void) { ipcRenderer.on(IpcEvents.QUICK_CSS_UPDATE, (_, css) => cb(css)); }, addThemeChangeListener(cb: () => void) { ipcRenderer.on(IpcEvents.THEME_UPDATE, () => cb()); }, openFile: () => invoke(IpcEvents.OPEN_QUICKCSS), openEditor: () => invoke(IpcEvents.OPEN_MONACO_EDITOR), getEditorTheme: () => sendSync(IpcEvents.GET_MONACO_THEME), }, native: { getVersions: () => process.versions as Partial, openExternal: (url: string) => invoke(IpcEvents.OPEN_EXTERNAL, url), getRendererCss: () => invoke(IpcEvents.GET_RENDERER_CSS), onRendererCssUpdate: (cb: (newCss: string) => void) => { if (!IS_DEV) return; ipcRenderer.on(IpcEvents.RENDERER_CSS_UPDATE, (_e, newCss: string) => cb(newCss)); } }, csp: { /** * Note: Only supports full explicit matches, not wildcards. * * If `*.example.com` is allowed, `isDomainAllowed("https://sub.example.com")` will return false. */ isDomainAllowed: (url: string, directives: string[]) => invoke(IpcEvents.CSP_IS_DOMAIN_ALLOWED, url, directives), removeOverride: (url: string) => invoke(IpcEvents.CSP_REMOVE_OVERRIDE, url), requestAddOverride: (url: string, directives: string[], callerName: string) => invoke(IpcEvents.CSP_REQUEST_ADD_OVERRIDE, url, directives, callerName), }, pluginHelpers: PluginHelpers }; ================================================ FILE: src/api/Badges.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import ErrorBoundary from "@components/ErrorBoundary"; import BadgeAPIPlugin from "@plugins/_api/badges"; import { ComponentType, HTMLProps } from "react"; export const enum BadgePosition { START, END } export interface ProfileBadge { /** The tooltip to show on hover. Required for image badges */ description?: string; /** Custom component for the badge (tooltip not included) */ component?: ComponentType; /** The custom image to use */ iconSrc?: string; link?: string; /** Action to perform when you click the badge */ onClick?(event: React.MouseEvent, props: ProfileBadge & BadgeUserArgs): void; /** Action to perform when you right click the badge */ onContextMenu?(event: React.MouseEvent, props: BadgeUserArgs & BadgeUserArgs): void; /** Should the user display this badge? */ shouldShow?(userInfo: BadgeUserArgs): boolean; /** Optional props (e.g. style) for the badge, ignored for component badges */ props?: HTMLProps; /** Insert at start or end? */ position?: BadgePosition; /** The badge name to display, Discord uses this. Required for component badges */ key?: string; /** * Allows dynamically returning multiple badges. * Must not call hooks */ getBadges?(userInfo: BadgeUserArgs): ProfileBadge[]; } const Badges = new Set(); /** * Register a new badge with the Badges API * @param badge The badge to register */ export function addProfileBadge(badge: ProfileBadge) { badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true }); Badges.add(badge); } /** * Unregister a badge from the Badges API * @param badge The badge to remove */ export function removeProfileBadge(badge: ProfileBadge) { return Badges.delete(badge); } /** * Inject badges into the profile badges array. * You probably don't need to use this. */ export function _getBadges(args: BadgeUserArgs) { const badges = [] as ProfileBadge[]; for (const badge of Badges) { if (badge.shouldShow && !badge.shouldShow(args)) { continue; } const b = badge.getBadges ? badge.getBadges(args).map(badge => ({ ...args, ...badge, component: badge.component && ErrorBoundary.wrap(badge.component, { noop: true }) })) : [{ ...args, ...badge }]; if (badge.position === BadgePosition.START) { badges.unshift(...b); } else { badges.push(...b); } } const donorBadges = BadgeAPIPlugin.getDonorBadges(args.userId); if (donorBadges) { badges.unshift( ...donorBadges.map(badge => ({ ...args, ...badge, })) ); } return badges; } export interface BadgeUserArgs { userId: string; guildId: string; } ================================================ FILE: src/api/ChatButton.css ================================================ .vc-chatbar-button { display: flex; align-items: center; } ================================================ FILE: src/api/ChatButtons.tsx ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2024 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import "./ChatButton.css"; import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; import { classes } from "@utils/misc"; import { IconComponent } from "@utils/types"; import { Channel } from "@vencord/discord-types"; import { findCssClassesLazy } from "@webpack"; import { Clickable, Menu, Tooltip } from "@webpack/common"; import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react"; import { addContextMenuPatch, findGroupChildrenByChildId } from "./ContextMenu"; import { useSettings } from "./Settings"; const ButtonWrapperClasses = findCssClassesLazy("button", "buttonWrapper", "notificationDot"); const ChannelTextAreaClasses = findCssClassesLazy("buttonContainer", "channelTextArea", "button"); export interface ChatBarProps { channel: Channel; disabled: boolean; isEmpty: boolean; type: { analyticsName: string; attachments: boolean; autocomplete: { addReactionShortcut: boolean, forceChatLayer: boolean, reactions: boolean; }, commands: { enabled: boolean; }, drafts: { type: number, commandType: number, autoSave: boolean; }, emojis: { button: boolean; }, gifs: { button: boolean, allowSending: boolean; }, gifts: { button: boolean; }, permissions: { requireSendMessages: boolean; }, showThreadPromptOnReply: boolean, stickers: { button: boolean, allowSending: boolean, autoSuggest: boolean; }, users: { allowMentioning: boolean; }, submit: { button: boolean, ignorePreference: boolean, disableEnterToSubmit: boolean, clearOnSubmit: boolean, useDisabledStylesOnSubmit: boolean; }, uploadLongMessages: boolean, upsellLongMessages: { iconOnly: boolean; }, showCharacterCount: boolean, sedReplace: boolean; }; } export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; isAnyChat: boolean; }) => JSX.Element | null; export type ChatBarButtonData = { render: ChatBarButtonFactory; /** * This icon is used only for Settings UI. Your render function must still render an icon, * and it can be different from this one. */ icon: IconComponent; }; /** * Don't use this directly, use {@link addChatBarButton} and {@link removeChatBarButton} instead. */ export const ChatBarButtonMap = new Map(); const logger = new Logger("ChatButtons"); function VencordChatBarButtons(props: ChatBarProps) { const { chatBarButtons } = useSettings(["uiElements.chatBarButtons.*"]).uiElements; const { analyticsName } = props.type; return ( <> {Array.from(ChatBarButtonMap) .filter(([key]) => chatBarButtons[key]?.enabled !== false) .map(([key, { render: Button }]) => ( logger.error(`Failed to render ${key}`, e.error)}> {richBody ??

{body}

} {image && } {timeout !== 0 && !permanent && (
)} ); }, { onError: ({ props }) => props.onClose!() }); ================================================ FILE: src/api/Notifications/Notifications.tsx ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { Settings } from "@api/Settings"; import { Queue } from "@utils/Queue"; import { createRoot } from "@webpack/common"; import type { ReactNode } from "react"; import type { Root } from "react-dom/client"; import NotificationComponent from "./NotificationComponent"; import { persistNotification } from "./notificationLog"; const NotificationQueue = new Queue(); let reactRoot: Root; let id = 42; function getRoot() { if (!reactRoot) { const container = document.createElement("div"); container.id = "vc-notification-container"; document.body.append(container); reactRoot = createRoot(container); } return reactRoot; } export interface NotificationData { title: string; body: string; /** * Same as body but can be a custom component. * Will be used over body if present. * Not supported on desktop notifications, those will fall back to body */ richBody?: ReactNode; /** Small icon. This is for things like profile pictures and should be square */ icon?: string; /** Large image. Optimally, this should be around 16x9 but it doesn't matter much. Desktop Notifications might not support this */ image?: string; onClick?(): void; onClose?(): void; color?: string; /** Whether this notification should not have a timeout */ permanent?: boolean; /** Whether this notification should not be persisted in the Notification Log */ noPersist?: boolean; /** Whether this notification should be dismissed when clicked (defaults to true) */ dismissOnClick?: boolean; } function _showNotification(notification: NotificationData, id: number) { const root = getRoot(); return new Promise(resolve => { root.render( { notification.onClose?.(); root.render(null); resolve(); }} />, ); }); } function shouldBeNative() { if (typeof Notification === "undefined") return false; const { useNative } = Settings.notifications; if (useNative === "always") return true; if (useNative === "not-focused") return !document.hasFocus(); return false; } export async function requestPermission() { return ( Notification.permission === "granted" || (Notification.permission !== "denied" && (await Notification.requestPermission()) === "granted") ); } export async function showNotification(data: NotificationData) { persistNotification(data); if (shouldBeNative() && await requestPermission()) { const { title, body, icon, image, onClick = null, onClose = null } = data; const n = new Notification(title, { body, icon, // @ts-expect-error ts is drunk image }); n.onclick = onClick; n.onclose = onClose; } else { NotificationQueue.push(() => _showNotification(data, id++)); } } ================================================ FILE: src/api/Notifications/index.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ export * from "./Notifications"; ================================================ FILE: src/api/Notifications/notificationLog.tsx ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import * as DataStore from "@api/DataStore"; import { Settings } from "@api/Settings"; import { Flex } from "@components/Flex"; import { openNotificationSettingsModal } from "@components/settings/tabs/vencord/NotificationSettings"; import { classNameFactory } from "@utils/css"; import { closeModal, ModalCloseButton, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; import { Alerts, Button, Forms, ListScrollerThin, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; import { nanoid } from "nanoid"; import type { DispatchWithoutAction } from "react"; import NotificationComponent from "./NotificationComponent"; import type { NotificationData } from "./Notifications"; interface PersistentNotificationData extends Pick { timestamp: number; id: string; } const KEY = "notification-log"; const getLog = async () => { const log = await DataStore.get(KEY) as PersistentNotificationData[] | undefined; return log ?? []; }; const cl = classNameFactory("vc-notification-log-"); const signals = new Set(); export async function persistNotification(notification: NotificationData) { if (notification.noPersist) return; const limit = Settings.notifications.logLimit; if (limit === 0) return; await DataStore.update(KEY, (old: PersistentNotificationData[] | undefined) => { const log = old ?? []; // Omit stuff we don't need const { onClick, onClose, richBody, permanent, noPersist, dismissOnClick, ...pureNotification } = notification; log.unshift({ ...pureNotification, timestamp: Date.now(), id: nanoid() }); if (log.length > limit && limit !== 200) log.length = limit; return log; }); signals.forEach(x => x()); } export async function deleteNotification(timestamp: number) { const log = await getLog(); const index = log.findIndex(x => x.timestamp === timestamp); if (index === -1) return; log.splice(index, 1); await DataStore.set(KEY, log); signals.forEach(x => x()); } export function useLogs() { const [signal, setSignal] = useReducer(x => x + 1, 0); useEffect(() => { signals.add(setSignal); return () => void signals.delete(setSignal); }, []); const [log, _, pending] = useAwaiter(getLog, { fallbackValue: [], deps: [signal] }); return [log, pending] as const; } function NotificationEntry({ data }: { data: PersistentNotificationData; }) { const [removing, setRemoving] = useState(false); return (
{ if (removing) return; setRemoving(true); setTimeout(() => deleteNotification(data.timestamp), 200); }} richBody={
{data.body}
} />
); } export function NotificationLog({ log, pending }: { log: PersistentNotificationData[], pending: boolean; }) { if (!log.length && !pending) return (
No notifications yet
); return ( null} renderRow={item => } /> ); } function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void; }) { const [log, pending] = useLogs(); return ( Notification Log
); } export function openNotificationLogModal() { const key = openModal(modalProps => ( closeModal(key)} /> )); } ================================================ FILE: src/api/Notifications/styles.css ================================================ .vc-notification-root { /* clear default button styles */ all: unset; display: flex; flex-direction: column; color: var(--text-default); background-color: var(--background-base-low); border-radius: 6px; overflow: hidden; cursor: pointer; width: 100%; } .vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) { position: absolute; z-index: 2147483647; right: 1rem; width: 25vw; min-height: 10vh; } .vc-notification { display: flex; flex-direction: row; padding: 1.25rem; gap: 1.25rem; } .vc-notification-content { width: 100%; overflow: hidden; } .vc-notification-header { display: flex; justify-content: space-between; } .vc-notification-title { color: var(--text-strong); font-size: 1rem; font-weight: 600; line-height: 1.25rem; text-transform: uppercase; } .vc-notification-close-btn { all: unset; cursor: pointer; color: var(--interactive-icon-default); opacity: 0.5; transition: opacity 0.2s ease-in-out, color 0.2s ease-in-out; } .vc-notification-close-btn:hover { color: var(--interactive-icon-hover); opacity: 1; } .vc-notification-icon { height: 4rem; width: 4rem; border-radius: 6px; } .vc-notification-progressbar { height: 0.25rem; border-radius: 5px; margin-top: auto; } .vc-notification-p { margin: 0.5rem 0 0; line-height: 140%; } .vc-notification-img { width: 100%; } .vc-notification-log-modal { max-width: 962px; width: clamp(var(--modal-width-large, 800px), 962px, 85vw); } .vc-notification-log-empty { height: 218px; background: url("/assets/b36de980b174d7b798c89f35c116e5c6.svg") center no-repeat; margin-bottom: 40px; } .vc-notification-log-container { padding: 1em; max-height: min(750px, 75vh); width: 100%; } .vc-notification-log-wrapper { height: 120px; width: 100%; padding-bottom: 16px; box-sizing: border-box; transition: 200ms ease; transition-property: height, opacity; /* stylelint-disable-next-line no-descending-specificity */ .vc-notification-root { height: 104px; } } .vc-notification-log-removing { height: 0 !important; opacity: 0; margin-bottom: 1em; } .vc-notification-log-body-wrapper { display: flex; flex-direction: column; width: 100%; box-sizing: border-box; } .vc-notification-log-body { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; line-height: 1.2em; } .vc-notification-log-timestamp { margin-left: auto; font-size: 0.8em; font-weight: lighter; } .vc-notification-log-danger-btn { color: var(--control-critical-primary-text-default); background-color: var(--control-critical-primary-background-default); } ================================================ FILE: src/api/PluginManager.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { addProfileBadge, removeProfileBadge } from "@api/Badges"; import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { registerCommand, unregisterCommand } from "@api/Commands"; import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu"; import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators"; import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories"; import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations"; import { addMessageClickListener, addMessagePreEditListener, addMessagePreSendListener, removeMessageClickListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents"; import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover"; import { Settings, SettingsStore } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; import { Logger } from "@utils/Logger"; import { onlyOnce } from "@utils/onlyOnce"; import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches"; import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types"; import { FluxEvents } from "@vencord/discord-types"; import { FluxDispatcher } from "@webpack/common"; import { patches } from "@webpack/patcher"; import Plugins from "~plugins"; export { Plugins as plugins }; import { traceFunction } from "../debug/Tracer"; const logger = new Logger("PluginManager", "#a6d189"); export const PMLogger = logger; /** Whether we have subscribed to flux events of all the enabled plugins when FluxDispatcher was ready */ let enabledPluginsSubscribedFlux = false; const subscribedFluxEventsPlugins = new Set(); export function isPluginEnabled(p: string) { return ( Plugins[p]?.required || Plugins[p]?.isDependency || Settings.plugins[p]?.enabled ) ?? false; } export function addPatch(newPatch: Omit, pluginName: string, pluginPath = `Vencord.Plugins.plugins[${JSON.stringify(pluginName)}]`) { const patch = newPatch as Patch; patch.plugin = pluginName; if (IS_REPORTER) { delete patch.predicate; delete patch.group; } if (patch.predicate && !patch.predicate()) return; canonicalizeFind(patch); if (!Array.isArray(patch.replacement)) { patch.replacement = [patch.replacement]; } for (const replacement of patch.replacement) { canonicalizeReplacement(replacement, pluginPath); if (IS_REPORTER) { delete replacement.predicate; } } patch.replacement = patch.replacement.filter(({ predicate }) => !predicate || predicate()); patches.push(patch); } function isReporterTestable(p: Plugin, part: ReporterTestable) { return p.reporterTestable == null ? true : (p.reporterTestable & part) === part; } export function pluginRequiresRestart(p: Plugin) { return p.requiresRestart !== false && (p.requiresRestart || !!p.patches?.length); } export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins(target: StartAt) { logger.info(`Starting plugins (stage ${target})`); for (const name in Plugins) { if (isPluginEnabled(name) && (!IS_REPORTER || isReporterTestable(Plugins[name], ReporterTestable.Start))) { const p = Plugins[name]; const startAt = p.startAt ?? StartAt.WebpackReady; if (startAt !== target) continue; startPlugin(Plugins[name]); } } }); export function startDependenciesRecursive(p: Plugin) { const settings = Settings.plugins; let restartNeeded = false; const failures: string[] = []; p.dependencies?.forEach(d => { if (!settings[d].enabled) { const dep = Plugins[d]; startDependenciesRecursive(dep); // If the plugin has patches, don't start the plugin, just enable it. settings[d].enabled = true; dep.isDependency = true; if (pluginRequiresRestart(dep)) { logger.warn(`Enabling dependency ${d} requires restart.`); restartNeeded = true; return; } const result = startPlugin(dep); if (!result) failures.push(d); } }); return { restartNeeded, failures }; } export function subscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof FluxDispatcher) { if (p.flux && !subscribedFluxEventsPlugins.has(p.name) && (!IS_REPORTER || isReporterTestable(p, ReporterTestable.FluxEvents))) { subscribedFluxEventsPlugins.add(p.name); logger.debug("Subscribing to flux events of plugin", p.name); for (const [event, handler] of Object.entries(p.flux)) { const wrappedHandler = p.flux[event] = function () { try { const res = handler!.apply(p, arguments as any); return res instanceof Promise ? res.catch(e => logger.error(`${p.name}: Error while handling ${event}\n`, e)) : res; } catch (e) { logger.error(`${p.name}: Error while handling ${event}\n`, e); } }; fluxDispatcher.subscribe(event as FluxEvents, wrappedHandler); } } } export function unsubscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof FluxDispatcher) { if (p.flux) { subscribedFluxEventsPlugins.delete(p.name); logger.debug("Unsubscribing from flux events of plugin", p.name); for (const [event, handler] of Object.entries(p.flux)) { fluxDispatcher.unsubscribe(event as FluxEvents, handler!); } } } export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatcher) { enabledPluginsSubscribedFlux = true; for (const name in Plugins) { if (!isPluginEnabled(name)) continue; subscribePluginFluxEvents(Plugins[name], fluxDispatcher); } } export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) { const { name, commands, contextMenus, managedStyle, userProfileBadge, onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton } = p; if (p.start) { logger.info("Starting plugin", name); if (p.started) { logger.warn(`${name} already started`); return false; } try { p.start(); } catch (e) { logger.error(`Failed to start ${name}\n`, e); return false; } } p.started = true; if (commands?.length) { logger.debug("Registering commands of plugin", name); for (const cmd of commands) { try { registerCommand(cmd, name); } catch (e) { logger.error(`Failed to register command ${cmd.name}\n`, e); return false; } } } if (enabledPluginsSubscribedFlux) { subscribePluginFluxEvents(p, FluxDispatcher); } if (contextMenus) { logger.debug("Adding context menus patches of plugin", name); for (const navId in contextMenus) { addContextMenuPatch(navId, contextMenus[navId]); } } if (managedStyle) enableStyle(managedStyle); if (userProfileBadge) addProfileBadge(userProfileBadge); if (onBeforeMessageEdit) addMessagePreEditListener(onBeforeMessageEdit); if (onBeforeMessageSend) addMessagePreSendListener(onBeforeMessageSend); if (onMessageClick) addMessageClickListener(onMessageClick); if (chatBarButton) addChatBarButton(name, chatBarButton.render, chatBarButton.icon); // @ts-expect-error: legacy code doesn't have icon else if (renderChatBarButton) addChatBarButton(name, renderChatBarButton); if (renderMemberListDecorator) addMemberListDecorator(name, renderMemberListDecorator); if (renderMessageDecoration) addMessageDecoration(name, renderMessageDecoration); if (renderMessageAccessory) addMessageAccessory(name, renderMessageAccessory); if (messagePopoverButton) addMessagePopoverButton(name, messagePopoverButton.render, messagePopoverButton.icon); // @ts-expect-error: legacy code doesn't have icon else if (renderMessagePopoverButton) addMessagePopoverButton(name, renderMessagePopoverButton); return true; }, p => `startPlugin ${p.name}`); export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) { const { name, commands, contextMenus, managedStyle, userProfileBadge, onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton } = p; if (p.stop) { logger.info("Stopping plugin", name); if (!p.started) { logger.warn(`${name} already stopped`); return false; } try { p.stop(); } catch (e) { logger.error(`Failed to stop ${name}\n`, e); return false; } } p.started = false; if (commands?.length) { logger.debug("Unregistering commands of plugin", name); for (const cmd of commands) { try { unregisterCommand(cmd.name); } catch (e) { logger.error(`Failed to unregister command ${cmd.name}\n`, e); return false; } } } unsubscribePluginFluxEvents(p, FluxDispatcher); if (contextMenus) { logger.debug("Removing context menus patches of plugin", name); for (const navId in contextMenus) { removeContextMenuPatch(navId, contextMenus[navId]); } } if (managedStyle) disableStyle(managedStyle); if (userProfileBadge) removeProfileBadge(userProfileBadge); if (onBeforeMessageEdit) removeMessagePreEditListener(onBeforeMessageEdit); if (onBeforeMessageSend) removeMessagePreSendListener(onBeforeMessageSend); if (onMessageClick) removeMessageClickListener(onMessageClick); if (chatBarButton || renderChatBarButton) removeChatBarButton(name); if (renderMemberListDecorator) removeMemberListDecorator(name); if (renderMessageDecoration) removeMessageDecoration(name); if (renderMessageAccessory) removeMessageAccessory(name); if (messagePopoverButton || renderMessagePopoverButton) removeMessagePopoverButton(name); return true; }, p => `stopPlugin ${p.name}`); export const initPluginManager = onlyOnce(function init() { const pluginsValues = Object.values(Plugins); const settings = Settings.plugins; const pluginKeysToBind: Array = [ "onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick", "renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton" ]; const neededApiPlugins = new Set(); // First round-trip to mark and force enable dependencies // // FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only // goes for the top level and their children, but for now this works okay with the current API plugins for (const p of pluginsValues) if (isPluginEnabled(p.name)) { p.dependencies?.forEach(d => { const dep = Plugins[d]; if (!dep) { const error = new Error(`Plugin ${p.name} has unresolved dependency ${d}`); if (IS_DEV) { throw error; } logger.warn(error); return; } settings[d].enabled = true; dep.isDependency = true; }); if (p.commands?.length) neededApiPlugins.add("CommandsAPI"); if (p.onBeforeMessageEdit || p.onBeforeMessageSend || p.onMessageClick) neededApiPlugins.add("MessageEventsAPI"); if (p.chatBarButton || p.renderChatBarButton) neededApiPlugins.add("ChatInputButtonAPI"); if (p.renderMemberListDecorator) neededApiPlugins.add("MemberListDecoratorsAPI"); if (p.renderMessageAccessory) neededApiPlugins.add("MessageAccessoriesAPI"); if (p.renderMessageDecoration) neededApiPlugins.add("MessageDecorationsAPI"); if (p.messagePopoverButton || p.renderMessagePopoverButton) neededApiPlugins.add("MessagePopoverAPI"); if (p.userProfileBadge) neededApiPlugins.add("BadgeAPI"); for (const key of pluginKeysToBind) { p[key] &&= p[key].bind(p) as any; } } for (const p of neededApiPlugins) { Plugins[p].isDependency = true; settings[p].enabled = true; } for (const p of pluginsValues) { if (p.settings) { p.options ??= {}; p.settings.pluginName = p.name; for (const name in p.settings.def) { const def = p.settings.def[name]; const checks = p.settings.checks?.[name]; p.options[name] = { ...def, ...checks }; } } if (p.options) { for (const name in p.options) { const opt = p.options[name]; if (opt.onChange != null) { SettingsStore.addChangeListener(`plugins.${p.name}.${name}`, opt.onChange); } } } if (p.patches && isPluginEnabled(p.name)) { if (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) { for (const patch of p.patches) { addPatch(patch, p.name); } } } } }); ================================================ FILE: src/api/ServerList.tsx ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import ErrorBoundary from "@components/ErrorBoundary"; import { ComponentType } from "react"; export const enum ServerListRenderPosition { Above, In, } const componentsAbove = new Set(); const componentsBelow = new Set(); function getRenderFunctions(position: ServerListRenderPosition) { return position === ServerListRenderPosition.Above ? componentsAbove : componentsBelow; } export function addServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) { getRenderFunctions(position).add(renderFunction); } export function removeServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) { getRenderFunctions(position).delete(renderFunction); } export const renderAll = (position: ServerListRenderPosition) => { return Array.from( getRenderFunctions(position), (Component, i) => ( ) ); }; ================================================ FILE: src/api/Settings.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { SettingsStore as SettingsStoreClass } from "@shared/SettingsStore"; import { Logger } from "@utils/Logger"; import { mergeDefaults } from "@utils/mergeDefaults"; import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types"; import { React, useEffect } from "@webpack/common"; import plugins from "~plugins"; const logger = new Logger("Settings"); export interface SettingsPluginUiElement { enabled: boolean; // TODO /** not implemented for now */ order?: number; } export type SettingsPluginUiElements = { /** id will be whatever id the element was registered with. Usually, but not always, the plugin name */ [id: string]: SettingsPluginUiElement; }; export interface Settings { autoUpdate: boolean; autoUpdateNotification: boolean, useQuickCss: boolean; eagerPatches: boolean; enabledThemes: string[]; enableReactDevtools: boolean; themeLinks: string[]; frameless: boolean; transparent: boolean; winCtrlQ: boolean; macosVibrancyStyle: | "content" | "fullscreen-ui" | "header" | "hud" | "menu" | "popover" | "selection" | "sidebar" | "titlebar" | "tooltip" | "under-page" | "window" | undefined; disableMinSize: boolean; winNativeTitleBar: boolean; plugins: { [plugin: string]: { enabled: boolean; [setting: string]: any; }; }; uiElements: { messagePopoverButtons: SettingsPluginUiElements; chatBarButtons: SettingsPluginUiElements; }, notifications: { timeout: number; position: "top-right" | "bottom-right"; useNative: "always" | "never" | "not-focused"; logLimit: number; }; cloud: { authenticated: boolean; url: string; settingsSync: boolean; settingsSyncVersion: number; }; } const DefaultSettings: Settings = { autoUpdate: true, autoUpdateNotification: true, useQuickCss: true, themeLinks: [], eagerPatches: IS_REPORTER, enabledThemes: [], enableReactDevtools: false, frameless: false, transparent: false, winCtrlQ: false, macosVibrancyStyle: undefined, disableMinSize: false, winNativeTitleBar: false, plugins: {}, uiElements: { chatBarButtons: {}, messagePopoverButtons: {} }, notifications: { timeout: 5000, position: "bottom-right", useNative: "not-focused", logLimit: 50 }, cloud: { authenticated: false, url: "https://api.vencord.dev/", settingsSync: false, settingsSyncVersion: 0 } }; const settings = !IS_REPORTER ? VencordNative.settings.get() : {} as Settings; mergeDefaults(settings, DefaultSettings); export const SettingsStore = new SettingsStoreClass(settings, { readOnly: true, getDefaultValue({ target, key, path }) { const v = target[key]; if (!plugins) return v; // plugins not initialised yet. this means this path was reached by being called on the top level if (path === "plugins" && key in plugins) return target[key] = { enabled: IS_REPORTER || plugins[key].required || plugins[key].enabledByDefault || false }; // Since the property is not set, check if this is a plugin's setting and if so, try to resolve // the default value. if (path.startsWith("plugins.")) { const plugin = path.slice("plugins.".length); if (plugin in plugins) { const setting = plugins[plugin].options?.[key]; if (!setting) return v; if ("default" in setting) // normal setting with a default value return (target[key] = setting.default); if (setting.type === OptionType.SELECT) { const def = setting.options.find(o => o.default); if (def) target[key] = def.value; return def?.value; } } } return v; } }); if (!IS_REPORTER) { SettingsStore.addGlobalChangeListener((_, path) => { SettingsStore.plain.cloud.settingsSyncVersion = Date.now(); VencordNative.settings.set(SettingsStore.plain, path); }); } /** * Same as {@link Settings} but unproxied. You should treat this as readonly, * as modifying properties on this will not save to disk or call settings * listeners. * WARNING: default values specified in plugin.options will not be ensured here. In other words, * settings for which you specified a default value may be uninitialised. If you need proper * handling for default values, use {@link Settings} */ export const PlainSettings = settings; /** * A smart settings object. Altering props automagically saves * the updated settings to disk. * This recursively proxies objects. If you need the object non proxied, use {@link PlainSettings} */ export const Settings = SettingsStore.store; /** * Settings hook for React components. Returns a smart settings * object that automagically triggers a rerender if any properties * are altered * @param paths An optional list of paths to whitelist for rerenders * @returns Settings */ // TODO: Representing paths as essentially "string[].join('.')" wont allow dots in paths, change to "paths?: string[][]" later export function useSettings(paths?: UseSettings[]) { const [, forceUpdate] = React.useReducer(() => ({}), {}); useEffect(() => { if (paths) { paths.forEach(p => { if (p.endsWith(".*")) { SettingsStore.addPrefixChangeListener(p.slice(0, -2), forceUpdate); } else { SettingsStore.addChangeListener(p, forceUpdate); } }); return () => paths.forEach(p => { if (p.endsWith(".*")) { SettingsStore.removePrefixChangeListener(p.slice(0, -2), forceUpdate); } else { SettingsStore.removeChangeListener(p, forceUpdate); } }); } else { SettingsStore.addGlobalChangeListener(forceUpdate); return () => SettingsStore.removeGlobalChangeListener(forceUpdate); } }, [paths]); return SettingsStore.store; } export function migratePluginSettings(name: string, ...oldNames: string[]) { const { plugins } = SettingsStore.plain; if (name in plugins) return; for (const oldName of oldNames) { if (oldName in plugins) { logger.info(`Migrating settings from old name ${oldName} to ${name}`); plugins[name] = plugins[oldName]; delete plugins[oldName]; SettingsStore.markAsChanged(); break; } } } export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) { const settings = SettingsStore.plain.plugins[pluginName]; if (!settings) return; if (!Object.hasOwn(settings, oldSetting) || Object.hasOwn(settings, newSetting)) return; settings[newSetting] = settings[oldSetting]; delete settings[oldSetting]; SettingsStore.markAsChanged(); } export function definePluginSettings< Def extends SettingsDefinition, Checks extends SettingsChecks, PrivateSettings extends object = {} >(def: Def, checks?: Checks) { const definedSettings: DefinedSettings = { get store() { if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized"); return Settings.plugins[definedSettings.pluginName] as any; }, get plain() { if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized"); return PlainSettings.plugins[definedSettings.pluginName] as any; }, use: settings => useSettings(( settings ? settings.map(name => `plugins.${definedSettings.pluginName}.${name}`) : [`plugins.${definedSettings.pluginName}.*`] ) as UseSettings[]).plugins[definedSettings.pluginName] as any, def, checks: checks ?? {} as any, pluginName: "", withPrivateSettings() { return this as DefinedSettings; } }; return definedSettings; } type UseSettings = ResolveUseSettings[keyof T]; type ResolveUseSettings = { [Key in keyof T]: Key extends string ? T[Key] extends Record // @ts-expect-error "Type instantiation is excessively deep and possibly infinite" ? `${Key}.*` | (ResolveUseSettings extends Record ? `${Key}.${ResolveUseSettings[keyof T[Key]]}` : never) : Key : never; }; ================================================ FILE: src/api/SettingsSync/cloudSetup.tsx ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import * as DataStore from "@api/DataStore"; import { showNotification } from "@api/Notifications"; import { Settings } from "@api/Settings"; import { Logger } from "@utils/Logger"; import { openModal } from "@utils/modal"; import { relaunch } from "@utils/native"; import { Alerts, OAuth2AuthorizeModal, UserStore } from "@webpack/common"; export const logger = new Logger("SettingsSync:CloudSetup", "#39b7e0"); export const getCloudUrl = () => new URL(Settings.cloud.url); const getCloudUrlOrigin = () => getCloudUrl().origin; export async function checkCloudUrlCsp() { if (IS_WEB) return true; const { host } = getCloudUrl(); if (host === "api.vencord.dev") return true; if (await VencordNative.csp.isDomainAllowed(Settings.cloud.url, ["connect-src"])) { return true; } const res = await VencordNative.csp.requestAddOverride(Settings.cloud.url, ["connect-src"], "Cloud Sync"); if (res === "ok") { Alerts.show({ title: "Cloud Integration enabled", body: `${host} has been added to the whitelist. Please restart the app for the changes to take effect.`, confirmText: "Restart now", cancelText: "Later!", onConfirm: relaunch }); } return false; } const getUserId = () => { const id = UserStore.getCurrentUser()?.id; if (!id) throw new Error("User not yet logged in"); return id; }; export async function getAuthorization() { const secrets = await DataStore.get>("Vencord_cloudSecret") ?? {}; const origin = getCloudUrlOrigin(); // we need to migrate from the old format here if (secrets[origin]) { await DataStore.update>("Vencord_cloudSecret", secrets => { secrets ??= {}; // use the current user ID secrets[`${origin}:${getUserId()}`] = secrets[origin]; delete secrets[origin]; return secrets; }); // since this doesn't update the original object, we'll early return the existing authorization return secrets[origin]; } return secrets[`${origin}:${getUserId()}`]; } async function setAuthorization(secret: string) { await DataStore.update>("Vencord_cloudSecret", secrets => { secrets ??= {}; secrets[`${getCloudUrlOrigin()}:${getUserId()}`] = secret; return secrets; }); } export async function deauthorizeCloud() { await DataStore.update>("Vencord_cloudSecret", secrets => { secrets ??= {}; delete secrets[`${getCloudUrlOrigin()}:${getUserId()}`]; return secrets; }); } export async function authorizeCloud() { if (await getAuthorization() !== undefined) { Settings.cloud.authenticated = true; return; } if (!await checkCloudUrlCsp()) return; try { const oauthConfiguration = await fetch(new URL("/v1/oauth/settings", getCloudUrl())); var { clientId, redirectUri } = await oauthConfiguration.json(); } catch { showNotification({ title: "Cloud Integration", body: "Setup failed (couldn't retrieve OAuth configuration)." }); Settings.cloud.authenticated = false; return; } openModal((props: any) => { if (!location) { Settings.cloud.authenticated = false; return; } try { const res = await fetch(location, { headers: { Accept: "application/json" } }); const { secret } = await res.json(); if (secret) { logger.info("Authorized with secret"); await setAuthorization(secret); showNotification({ title: "Cloud Integration", body: "Cloud integrations enabled!" }); Settings.cloud.authenticated = true; } else { showNotification({ title: "Cloud Integration", body: "Setup failed (no secret returned?)." }); Settings.cloud.authenticated = false; } } catch (e: any) { logger.error("Failed to authorize", e); showNotification({ title: "Cloud Integration", body: `Setup failed (${e.toString()}).` }); Settings.cloud.authenticated = false; } } } />); } export async function getCloudAuth() { const secret = await getAuthorization(); return window.btoa(`${secret}:${getUserId()}`); } ================================================ FILE: src/api/SettingsSync/cloudSync.ts ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import { showNotification } from "@api/Notifications"; import { PlainSettings, Settings } from "@api/Settings"; import { localStorage } from "@utils/localStorage"; import { Logger } from "@utils/Logger"; import { relaunch } from "@utils/native"; import { deflateSync, inflateSync } from "fflate"; import { checkCloudUrlCsp, deauthorizeCloud, getCloudAuth, getCloudUrl } from "./cloudSetup"; import { exportSettings, importSettings } from "./offline"; const logger = new Logger("SettingsSync:Cloud", "#39b7e0"); export function shouldCloudSync(direction: "push" | "pull") { const localDirection = localStorage.Vencord_cloudSyncDirection; return localDirection === direction || localDirection === "both"; } export async function putCloudSettings(manual?: boolean) { const settings = await exportSettings({ minify: true }); if (!await checkCloudUrlCsp()) return; try { const res = await fetch(new URL("/v1/settings", getCloudUrl()), { method: "PUT", headers: { Authorization: await getCloudAuth(), "Content-Type": "application/octet-stream" }, body: deflateSync(new TextEncoder().encode(settings)) as Uint8Array }); if (!res.ok) { logger.error(`Failed to sync up, API returned ${res.status}`); showNotification({ title: "Cloud Settings", body: `Could not synchronize settings to cloud (API returned ${res.status}).`, color: "var(--red-360)" }); return; } const { written } = await res.json(); PlainSettings.cloud.settingsSyncVersion = written; VencordNative.settings.set(PlainSettings); logger.info("Settings uploaded to cloud successfully"); if (manual) { showNotification({ title: "Cloud Settings", body: "Synchronized settings to the cloud!", noPersist: true, }); } delete localStorage.Vencord_settingsDirty; } catch (e: any) { logger.error("Failed to sync up", e); showNotification({ title: "Cloud Settings", body: `Could not synchronize settings to the cloud (${e.toString()}).`, color: "var(--red-360)" }); } } export async function getCloudSettings(shouldNotify = true, force = false) { if (!await checkCloudUrlCsp()) return; try { const res = await fetch(new URL("/v1/settings", getCloudUrl()), { method: "GET", headers: { Authorization: await getCloudAuth(), Accept: "application/octet-stream", "If-None-Match": Settings.cloud.settingsSyncVersion.toString() }, }); if (res.status === 404) { logger.info("No settings on the cloud"); if (shouldNotify) showNotification({ title: "Cloud Settings", body: "There are no settings in the cloud.", noPersist: true }); return false; } if (res.status === 304) { logger.info("Settings up to date"); if (shouldNotify) showNotification({ title: "Cloud Settings", body: "Your settings are up to date.", noPersist: true }); return false; } if (!res.ok) { logger.error(`Failed to sync down, API returned ${res.status}`); showNotification({ title: "Cloud Settings", body: `Could not synchronize settings from the cloud (API returned ${res.status}).`, color: "var(--red-360)" }); return false; } const written = Number(res.headers.get("etag")!); const localWritten = Settings.cloud.settingsSyncVersion; // don't need to check for written > localWritten because the server will return 304 due to if-none-match if (!force && written < localWritten) { if (shouldNotify) showNotification({ title: "Cloud Settings", body: "Your local settings are newer than the cloud ones.", noPersist: true, }); return; } const data = await res.arrayBuffer(); const settings = new TextDecoder().decode(inflateSync(new Uint8Array(data))); await importSettings(settings); // sync with server timestamp instead of local one PlainSettings.cloud.settingsSyncVersion = written; VencordNative.settings.set(PlainSettings); logger.info("Settings loaded from cloud successfully"); if (shouldNotify) showNotification({ title: "Cloud Settings", body: "Your settings have been updated! Click here to restart to fully apply changes!", color: "var(--green-360)", onClick: IS_WEB ? () => location.reload() : relaunch, noPersist: true }); delete localStorage.Vencord_settingsDirty; return true; } catch (e: any) { logger.error("Failed to sync down", e); showNotification({ title: "Cloud Settings", body: `Could not synchronize settings from the cloud (${e.toString()}).`, color: "var(--red-360)" }); return false; } } export async function deleteCloudSettings() { if (!await checkCloudUrlCsp()) return; try { const res = await fetch(new URL("/v1/settings", getCloudUrl()), { method: "DELETE", headers: { Authorization: await getCloudAuth() }, }); if (!res.ok) { logger.error(`Failed to delete, API returned ${res.status}`); showNotification({ title: "Cloud Settings", body: `Could not delete settings (API returned ${res.status}).`, color: "var(--red-360)" }); return; } logger.info("Settings deleted from cloud successfully"); showNotification({ title: "Cloud Settings", body: "Settings deleted from cloud!", color: "var(--green-360)" }); } catch (e: any) { logger.error("Failed to delete", e); showNotification({ title: "Cloud Settings", body: `Could not delete settings (${e.toString()}).`, color: "var(--red-360)" }); } } export async function eraseAllCloudData() { if (!await checkCloudUrlCsp()) return; const res = await fetch(new URL("/v1/", getCloudUrl()), { method: "DELETE", headers: { Authorization: await getCloudAuth() } }); if (!res.ok) { logger.error(`Failed to erase data, API returned ${res.status}`); showNotification({ title: "Cloud Integrations", body: `Could not erase all data (API returned ${res.status}), please contact support.`, color: "var(--red-360)" }); return; } Settings.cloud.authenticated = false; await deauthorizeCloud(); showNotification({ title: "Cloud Integrations", body: "Successfully erased all data.", color: "var(--green-360)" }); } ================================================ FILE: src/api/SettingsSync/offline.ts ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import { PlainSettings } from "@api/Settings"; import { Logger } from "@utils/Logger"; import { chooseFile, saveFile } from "@utils/web"; import { moment, Toasts } from "@webpack/common"; const toast = (type: string, message: string) => Toasts.show({ type, message, id: Toasts.genId() }); const toastSuccess = () => toast(Toasts.Type.SUCCESS, "Settings successfully imported. Restart to apply changes!"); const toastFailure = (err: any) => toast(Toasts.Type.FAILURE, `Failed to import settings: ${String(err)}`); const logger = new Logger("SettingsSync:Offline", "#39b7e0"); function isSafeObject(obj: any) { if (obj == null || typeof obj !== "object") return true; for (const key in obj) { if (["__proto__", "constructor", "prototype"].includes(key)) { return false; } if (!isSafeObject(obj[key])) { return false; } } return true; } export async function importSettings(data: string) { try { var parsed = JSON.parse(data); } catch (err) { console.log(data); throw new Error("Failed to parse JSON: " + String(err)); } if (!isSafeObject(parsed)) throw new Error("Unsafe Settings"); if ("settings" in parsed && "quickCss" in parsed) { Object.assign(PlainSettings, parsed.settings); await VencordNative.settings.set(parsed.settings); await VencordNative.quickCss.set(parsed.quickCss); } else throw new Error("Invalid Settings. Is this even a Vencord Settings file?"); } export async function exportSettings({ minify }: { minify?: boolean; } = {}) { const settings = VencordNative.settings.get(); const quickCss = await VencordNative.quickCss.get(); return JSON.stringify({ settings, quickCss }, null, minify ? undefined : 4); } export async function downloadSettingsBackup() { const filename = `vencord-settings-backup-${moment().format("YYYY-MM-DD")}.json`; const backup = await exportSettings(); const data = new TextEncoder().encode(backup); if (IS_DISCORD_DESKTOP) { DiscordNative.fileManager.saveWithDialog(data, filename); } else { saveFile(new File([data], filename, { type: "application/json" })); } } export async function uploadSettingsBackup(showToast = true): Promise { if (IS_DISCORD_DESKTOP) { const [file] = await DiscordNative.fileManager.openFiles({ filters: [ { name: "Vencord Settings Backup", extensions: ["json"] }, { name: "all", extensions: ["*"] } ] }); if (file) { try { await importSettings(new TextDecoder().decode(file.data)); if (showToast) toastSuccess(); } catch (err) { logger.error(err); if (showToast) toastFailure(err); } } } else { const file = await chooseFile("application/json"); if (!file) return; const reader = new FileReader(); reader.onload = async () => { try { await importSettings(reader.result as string); if (showToast) toastSuccess(); } catch (err) { logger.error(err); if (showToast) toastFailure(err); } }; reader.readAsText(file); } } ================================================ FILE: src/api/Styles.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { generateTextCss } from "@components/BaseText"; import { generateMarginCss } from "@components/margins"; import { classNameFactory as _classNameFactory, classNameToSelector, createAndAppendStyle } from "@utils/css"; // Backwards compat for Vesktop /** @deprecated Import this from `@utils/css` instead */ export const classNameFactory = _classNameFactory; export interface Style { name: string; source: string; classNames: Record; dom: HTMLStyleElement | null; } export const styleMap = window.VencordStyles ??= new Map(); export const vencordRootNode = document.createElement("vencord-root"); /** * Houses all Vencord core styles. This includes all imported css files */ export const coreStyleRootNode = document.createElement("vencord-styles"); /** * Houses all plugin specific managed styles */ export const managedStyleRootNode = document.createElement("vencord-managed-styles"); /** * Houses the user's themes and quick css */ export const userStyleRootNode = document.createElement("vencord-user-styles"); vencordRootNode.style.display = "none"; vencordRootNode.append(coreStyleRootNode, managedStyleRootNode, userStyleRootNode); export function initStyles() { const osValuesNode = createAndAppendStyle("vencord-os-theme-values", coreStyleRootNode); createAndAppendStyle("vencord-text", coreStyleRootNode).textContent = generateTextCss(); const rendererCssNode = createAndAppendStyle("vencord-css-core", coreStyleRootNode); const vesktopCssNode = IS_VESKTOP ? createAndAppendStyle("vesktop-css-core", coreStyleRootNode) : null; createAndAppendStyle("vencord-margins", coreStyleRootNode).textContent = generateMarginCss(); VencordNative.native.getRendererCss().then(css => rendererCssNode.textContent = css); if (IS_DEV) { VencordNative.native.onRendererCssUpdate(newCss => { rendererCssNode.textContent = newCss; }); } if (IS_VESKTOP && VesktopNative.app.getRendererCss) { VesktopNative.app.getRendererCss().then(css => vesktopCssNode!.textContent = css); VesktopNative.app.onRendererCssUpdate(newCss => { vesktopCssNode!.textContent = newCss; }); } VencordNative.themes.getSystemValues().then(values => { const variables = Object.entries(values) .filter(([, v]) => !!v) .map(([k, v]) => `--${k}: ${v};`) .join(""); osValuesNode.textContent = `:root{${variables}}`; }); } document.addEventListener("DOMContentLoaded", () => { document.documentElement.append(vencordRootNode); }, { once: true }); export function requireStyle(name: string) { const style = styleMap.get(name); if (!style) throw new Error(`Style "${name}" does not exist`); return style; } /** * A style's name can be obtained from importing a stylesheet with `?managed` at the end of the import * @param name The name of the style * @returns `false` if the style was already enabled, `true` otherwise * @example * import pluginStyle from "./plugin.css?managed"; * * // Inside some plugin method like "start()" or "[option].onChange()" * enableStyle(pluginStyle); */ export function enableStyle(name: string) { const style = requireStyle(name); if (style.dom?.isConnected) return false; if (!style.dom) { style.dom = document.createElement("style"); style.dom.dataset.vencordName = style.name; } compileStyle(style); managedStyleRootNode.appendChild(style.dom); return true; } /** * @param name The name of the style * @returns `false` if the style was already disabled, `true` otherwise * @see {@link enableStyle} for info on getting the name of an imported style */ export function disableStyle(name: string) { const style = requireStyle(name); if (!style.dom?.isConnected) return false; style.dom.remove(); style.dom = null; return true; } /** * @param name The name of the style * @returns `true` in most cases, may return `false` in some edge cases * @see {@link enableStyle} for info on getting the name of an imported style */ export const toggleStyle = (name: string) => isStyleEnabled(name) ? disableStyle(name) : enableStyle(name); /** * @param name The name of the style * @returns Whether the style is enabled * @see {@link enableStyle} for info on getting the name of an imported style */ export const isStyleEnabled = (name: string) => requireStyle(name).dom?.isConnected ?? false; /** * Sets the variables of a style * ```ts * // -- plugin.ts -- * import pluginStyle from "./plugin.css?managed"; * import { setStyleVars } from "@api/Styles"; * import { findByPropsLazy } from "@webpack"; * const classNames = findByPropsLazy("thin", "scrollerBase"); // { thin: "thin-31rlnD scrollerBase-_bVAAt", ... } * * // Inside some plugin method like "start()" * setStyleClassNames(pluginStyle, classNames); * enableStyle(pluginStyle); * ``` * ```scss * // -- plugin.css -- * .plugin-root [--thin]::-webkit-scrollbar { ... } * ``` * ```scss * // -- final stylesheet -- * .plugin-root .thin-31rlnD.scrollerBase-_bVAAt::-webkit-scrollbar { ... } * ``` * @param name The name of the style * @param classNames An object where the keys are the variable names and the values are the variable values * @param recompile Whether to recompile the style after setting the variables, defaults to `true` * @see {@link enableStyle} for info on getting the name of an imported style */ export const setStyleClassNames = (name: string, classNames: Record, recompile = true) => { const style = requireStyle(name); style.classNames = classNames; if (recompile && isStyleEnabled(style.name)) compileStyle(style); }; /** * Updates the stylesheet after doing the following to the sourcecode: * - Interpolate style classnames * @param style **_Must_ be a style with a DOM element** * @see {@link setStyleClassNames} for more info on style classnames */ export const compileStyle = (style: Style) => { if (!style.dom) throw new Error("Style has no DOM element"); style.dom.textContent = style.source .replace(/\[--(\w+)\]/g, (match, name) => { const className = style.classNames[name]; return className ? classNameToSelector(className) : match; }); }; ================================================ FILE: src/api/Themes.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { Settings, SettingsStore } from "@api/Settings"; import { createAndAppendStyle } from "@utils/css"; import { ThemeStore } from "@vencord/discord-types"; import { PopoutWindowStore } from "@webpack/common"; import { userStyleRootNode, vencordRootNode } from "./Styles"; let style: HTMLStyleElement; let themesStyle: HTMLStyleElement; async function toggle(isEnabled: boolean) { if (!style) { if (isEnabled) { style = createAndAppendStyle("vencord-custom-css", userStyleRootNode); VencordNative.quickCss.addChangeListener(css => { style.textContent = css; // At the time of writing this, changing textContent resets the disabled state style.disabled = !Settings.useQuickCss; updatePopoutWindows(); }); style.textContent = await VencordNative.quickCss.get(); } } else style.disabled = !isEnabled; } async function initThemes() { themesStyle ??= createAndAppendStyle("vencord-themes", userStyleRootNode); const { themeLinks, enabledThemes } = Settings; const { ThemeStore } = require("@webpack/common/stores") as typeof import("@webpack/common/stores"); // "darker" and "midnight" both count as dark // This function is first called on DOMContentLoaded, so ThemeStore may not have been loaded yet const activeTheme = ThemeStore == null ? undefined : ThemeStore.theme === "light" ? "light" : "dark"; const links = themeLinks .map(rawLink => { const match = /^@(light|dark) (.*)/.exec(rawLink); if (!match) return rawLink; const [, mode, link] = match; return mode === activeTheme ? link : null; }) .filter(link => link !== null); if (IS_WEB) { for (const theme of enabledThemes) { const themeData = await VencordNative.themes.getThemeData(theme); if (!themeData) continue; const blob = new Blob([themeData], { type: "text/css" }); links.push(URL.createObjectURL(blob)); } } else { const localThemes = enabledThemes.map(theme => `vencord:///themes/${theme}?v=${Date.now()}`); links.push(...localThemes); } themesStyle.textContent = links.map(link => `@import url("${link.trim()}");`).join("\n"); updatePopoutWindows(); } function applyToPopout(popoutWindow: Window | undefined, key: string) { if (!popoutWindow?.document) return; // skip game overlay cuz it needs to stay transparent, themes broke it if (key === "DISCORD_OutOfProcessOverlay") return; const doc = popoutWindow.document; doc.querySelector("vencord-root")?.remove(); doc.documentElement.appendChild(vencordRootNode.cloneNode(true)); } function updatePopoutWindows() { if (!PopoutWindowStore) return; for (const key of PopoutWindowStore.getWindowKeys()) { applyToPopout(PopoutWindowStore.getWindow(key), key); } } document.addEventListener("DOMContentLoaded", () => { if (IS_USERSCRIPT) return; initThemes(); toggle(Settings.useQuickCss); SettingsStore.addChangeListener("useQuickCss", toggle); SettingsStore.addChangeListener("themeLinks", initThemes); SettingsStore.addChangeListener("enabledThemes", initThemes); window.addEventListener("message", event => { const { discordPopoutEvent } = event.data || {}; if (discordPopoutEvent?.type !== "loaded") return; applyToPopout(PopoutWindowStore.getWindow(discordPopoutEvent.key), discordPopoutEvent.key); }); if (!IS_WEB) { VencordNative.quickCss.addThemeChangeListener(initThemes); } }, { once: true }); export function initQuickCssThemeStore(themeStore: ThemeStore) { if (IS_USERSCRIPT) return; initThemes(); let currentTheme = themeStore.theme; themeStore.addChangeListener(() => { if (currentTheme === themeStore.theme) return; currentTheme = themeStore.theme; initThemes(); }); } ================================================ FILE: src/api/UserSettings.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { proxyLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; import { findModuleId, proxyLazyWebpack, wreq } from "@webpack"; import { isPluginEnabled } from "./PluginManager"; interface UserSettingDefinition { /** * Get the setting value */ getSetting(): T; /** * Update the setting value * @param value The new value */ updateSetting(value: T): Promise; /** * Update the setting value * @param value A callback that accepts the old value as the first argument, and returns the new value */ updateSetting(value: (old: T) => T): Promise; /** * Stateful React hook for this setting value */ useSetting(): T; userSettingsAPIGroup: string; userSettingsAPIName: string; } export const UserSettings: Record> | undefined = proxyLazyWebpack(() => { const modId = findModuleId('"textAndImages","renderSpoilers"'); if (modId == null) return new Logger("UserSettingsAPI").error("Didn't find settings module."); return wreq(modId as any); }); /** * Get the setting with the given setting group and name. * * @param group The setting group * @param name The name of the setting */ export function getUserSetting(group: string, name: string): UserSettingDefinition | undefined { if (!isPluginEnabled("UserSettingsAPI")) throw new Error("Cannot use UserSettingsAPI without setting it as a dependency."); for (const key in UserSettings) { const userSetting = UserSettings[key]; if (userSetting.userSettingsAPIGroup === group && userSetting.userSettingsAPIName === name) { return userSetting; } } } /** * {@link getUserSettingDefinition}, lazy. * * Get the setting with the given setting group and name. * * @param group The setting group * @param name The name of the setting */ export function getUserSettingLazy(group: string, name: string) { return proxyLazy(() => getUserSetting(group, name)); } ================================================ FILE: src/api/index.ts ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import * as $Badges from "./Badges"; import * as $ChatButtons from "./ChatButtons"; import * as $Commands from "./Commands"; import * as $ContextMenu from "./ContextMenu"; import * as $DataStore from "./DataStore"; import * as $MemberListDecorators from "./MemberListDecorators"; import * as $MessageAccessories from "./MessageAccessories"; import * as $MessageDecorations from "./MessageDecorations"; import * as $MessageEventsAPI from "./MessageEvents"; import * as $MessagePopover from "./MessagePopover"; import * as $MessageUpdater from "./MessageUpdater"; import * as $Notices from "./Notices"; import * as $Notifications from "./Notifications"; export * as PluginManager from "./PluginManager"; import * as $ServerList from "./ServerList"; import * as $Settings from "./Settings"; import * as $Styles from "./Styles"; import * as $Themes from "./Themes"; import * as $UserSettings from "./UserSettings"; /** * An API allowing you to listen to Message Clicks or run your own logic * before a message is sent * * If your plugin uses this, you must add MessageEventsAPI to its dependencies */ export const MessageEvents = $MessageEventsAPI; /** * An API allowing you to create custom notices * (snackbars on the top, like the Update prompt) */ export const Notices = $Notices; /** * An API allowing you to register custom commands */ export const Commands = $Commands; /** * A wrapper around IndexedDB. This can store arbitrarily * large data and supports a lot of datatypes (Blob, Map, ...). * For a full list, see the mdn link below * * This should always be preferred over the Settings API if possible, as * localstorage has very strict size restrictions and blocks the event loop * * Make sure your keys are unique (tip: prefix them with ur plugin name) * and please clean up no longer needed entries. * * This is actually just idb-keyval, so if you're familiar with that, you're golden! * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types} */ export const DataStore = $DataStore; /** * An API allowing you to add custom components as message accessories */ export const MessageAccessories = $MessageAccessories; /** * An API allowing you to add custom buttons in the message popover */ export const MessagePopover = $MessagePopover; /** * An API allowing you to add badges to user profiles */ export const Badges = $Badges; /** * An API allowing you to add custom elements to the server list */ export const ServerList = $ServerList; /** * An API allowing you to add components as message accessories */ export const MessageDecorations = $MessageDecorations; /** * An API allowing you to add components to member list users, in both DM's and servers */ export const MemberListDecorators = $MemberListDecorators; /** * An API allowing you to persist data */ export const Settings = $Settings; /** * An API allowing you to dynamically load styles * a */ export const Styles = $Styles; /** * An API allowing you to display notifications */ export const Notifications = $Notifications; /** * An api allowing you to patch and add/remove items to/from context menus */ export const ContextMenu = $ContextMenu; /** * An API allowing you to add buttons to the chat input */ export const ChatButtons = $ChatButtons; /** * An API allowing you to update and re-render messages */ export const MessageUpdater = $MessageUpdater; /** * An API allowing you to get an user setting */ export const UserSettings = $UserSettings; /** * Don't use this */ export const Themes = $Themes; ================================================ FILE: src/components/BaseText.css ================================================ .vc-text-base { font-family: var(--font-primary); line-height: normal; /* Discord puts an insane default margin on p tags, so reset that here */ margin: 0; } .vc-text-defaultColor { color: var(--text-default); } ================================================ FILE: src/components/BaseText.tsx ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import "./BaseText.css"; import { classNameFactory } from "@utils/css"; import { classes } from "@utils/misc"; import type { Text as DiscordText } from "@vencord/discord-types"; import type { ComponentPropsWithoutRef, ReactNode } from "react"; const textCls = classNameFactory("vc-text-"); const Sizes = { xxs: "0.625rem", xs: "0.75rem", sm: "0.875rem", md: "1rem", lg: "1.25rem", xl: "1.5rem", xxl: "2rem" } as const; const Weights = { thin: "100", extralight: "200", light: "300", normal: "400", medium: "500", semibold: "600", bold: "700", extrabold: "800", } as const; export function generateTextCss() { let css = ""; for (const [size, value] of Object.entries(Sizes)) { css += `.${textCls(size)}{font-size:${value};}`; } for (const [weight, value] of Object.entries(Weights)) { css += `.${textCls(weight)}{font-weight:${value};}`; } return css; } export type TextSize = keyof typeof Sizes; export type TextWeight = keyof typeof Weights; export type TextTag = "div" | "span" | "p" | `h${1 | 2 | 3 | 4 | 5 | 6}`; export type BaseTextProps = ComponentPropsWithoutRef & { size?: TextSize; weight?: TextWeight; tag?: Tag; defaultColor?: boolean; }; export function BaseText(props: BaseTextProps): ReactNode { const { size = "md", weight = "normal", tag: Tag = "div", defaultColor = true, children, className, ...restProps } = props; return ( {children} ); } // #region Old compability export const TextCompat: DiscordText = function TextCompat({ color, variant, ...restProps }) { const newBaseTextProps = restProps as BaseTextProps; if (variant) { const [left, right] = variant.split("/"); if (left && right) { const size = left.split("-").pop(); newBaseTextProps.size = size as TextSize; newBaseTextProps.weight = right as TextWeight; } } if (color) { newBaseTextProps.style ??= {}; newBaseTextProps.style.color = `var(--${color}, var(--text-default))`; } return ; }; // #endregion ================================================ FILE: src/components/Button.css ================================================ .vc-btn-base { position: relative; display: flex; justify-content: center; align-items: center; max-width: 100%; border: 1px solid transparent; border-radius: var(--radius-sm, 8px); font-family: var(--font-primary); text-align: start; transition: 50ms ease-in; transition-property: background-color, color, border-color, opacity; background: var(--control-secondary-background-default); color: var(--text-default); white-space: nowrap; &:hover { transition: .15s ease-out; } &:disabled { opacity: .5; pointer-events: none; cursor: not-allowed; } &:focus-visible { /* stylelint-disable-next-line custom-property-pattern */ box-shadow: 0 0 0 4px var(--__adaptive-focus-ring-color, var(--border-focus, #00b0f4)); } } .vc-btn-min, .vc-btn-xs { padding: 3px 7px; min-height: 22px; min-width: unset; font-size: 12px; font-weight: 400; line-height: 1.3333; } .vc-btn-xs { min-width: 60px; } .vc-btn-small { padding: 3px 11px; min-height: 30px; min-width: 60px; font-size: 14px; font-weight: 500; line-height: 1.2857; } .vc-btn-medium { padding: 7px 15px; min-height: 38px; min-width: 100px; font-size: 16px; font-weight: 500; line-height: 1.25; } .vc-btn-iconOnly { width: 32px; height: 32px; min-width: unset; min-height: unset; padding: 0; background-color: transparent; border-color: transparent; &:hover { background-color: var(--control-icon-only-background-hover); border-color: var(--control-icon-only-border-hover); } &:active { background-color: var(--control-icon-only-background-active); border-color: var(--control-icon-only-border-active); } } .vc-btn-primary { background-color: var(--control-primary-background-default); border-color: var(--control-primary-border-default); color: var(--control-primary-text-default); &:hover { background-color: var(--control-primary-background-hover); border-color: var(--control-primary-border-hover); color: var(--control-primary-text-hover); } } .vc-btn-secondary, .vc-btn-link { background-color: var(--control-secondary-background-default); border-color: var(--control-secondary-border-default); color: var(--control-secondary-text-default); &:hover { background-color: var(--control-secondary-background-hover); border-color: var(--control-secondary-border-hover); color: var(--control-secondary-text-hover); } } .vc-btn-dangerPrimary { background-color: var(--control-critical-primary-background-default); border-color: var(--control-critical-primary-border-default); color: var(--control-critical-primary-text-default); &:hover { background-color: var(--control-critical-primary-background-hover); border-color: var(--control-critical-primary-border-hover); color: var(--control-critical-primary-text-hover); } } .vc-btn-dangerSecondary { background-color: var(--control-critical-secondary-background-default); border-color: var(--control-critical-secondary-border-default); color: var(--control-critical-secondary-text-default); &:hover { background-color: var(--control-critical-secondary-background-hover); border-color: var(--control-critical-secondary-border-hover); color: var(--control-critical-secondary-text-hover); } } .vc-btn-overlayPrimary { background-color: var(--control-overlay-primary-background-default); border-color: var(--control-overlay-primary-border-default); color: var(--control-overlay-primary-text-default); &:hover { background-color: var(--control-overlay-primary-background-hover); border-color: var(--control-overlay-primary-border-hover); color: var(--control-overlay-primary-text-hover); } } .vc-btn-positive { background-color: var(--control-connected-background-default, var(--green-430)); color: var(--white); &:hover { background-color: var(--control-connected-background-hover, var(--green-460)); } } .vc-btn-none { background-color: transparent; border-color: transparent; color: var(--control-icon-only-icon-default); &:hover { background-color: var(--control-icon-only-background-hover); border-color: var(--control-icon-only-border-hover); color: var(--control-icon-only-icon-hover); } } .vc-btn-link-icon { width: 0.875em; height: 0.875em; margin-left: 8px; flex-shrink: 0; } .vc-text-btn-base { display: inline-flex; justify-content: center; align-items: center; gap: var(--space-4, 4px); background: initial; color: var(--text-default); font-size: medium; font-weight: 400; margin: 0; padding: 0; text-align: start; text-decoration: none; max-width: 100%; white-space: nowrap; &:disabled { opacity: 0.5; } &:hover { text-decoration: underline; } &:focus-visible { /* stylelint-disable-next-line custom-property-pattern */ box-shadow: 0 0 0 4px var(--__adaptive-focus-ring-color, var(--border-focus, #00b0f4)); } } .vc-text-btn-primary { color: var(--text-brand); } .vc-text-btn-secondary { color: var(--text-strong, var(--text-default)); } .vc-text-btn-danger { color: var(--text-feedback-critical); } .vc-text-btn-link { color: var(--text-link); } ================================================ FILE: src/components/Button.tsx ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import "./Button.css"; import { classNameFactory } from "@utils/css"; import { classes } from "@utils/misc"; import type { Button as DiscordButton } from "@vencord/discord-types"; import type { ComponentPropsWithRef } from "react"; import { OpenExternalIcon } from "./Icons"; import { Link } from "./Link"; const btnCls = classNameFactory("vc-btn-"); const textBtnCls = classNameFactory("vc-text-btn-"); export type ButtonVariant = "primary" | "secondary" | "dangerPrimary" | "dangerSecondary" | "overlayPrimary" | "positive" | "link" | "none"; export type ButtonSize = "min" | "xs" | "small" | "medium" | "iconOnly"; export type ButtonProps = ComponentPropsWithRef<"button"> & { variant?: ButtonVariant; size?: ButtonSize; }; export type LinkButtonProps = ComponentPropsWithRef<"a"> & { size?: ButtonSize; variant?: ButtonVariant; }; export function Button({ variant = "primary", size = "medium", children, className, ...restProps }: ButtonProps) { return ( ); } export function LinkButton({ variant = "link", size = "medium", className, children, ...restProps }: LinkButtonProps) { return ( {children} ); } export type TextButtonVariant = "primary" | "secondary" | "danger" | "link"; export type TextButtonProps = ComponentPropsWithRef<"button"> & { variant?: TextButtonVariant; }; export function TextButton({ variant = "primary", className, ...restProps }: TextButtonProps) { return ( ); } ================================================ FILE: src/components/settings/PluginBadge.tsx ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ export function AddonBadge({ text, color }) { return (
{text}
); } ================================================ FILE: src/components/settings/QuickAction.css ================================================ .vc-settings-quickActions-card { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5em; padding: 0.5em; margin-bottom: 1em; } @media (width <=1040px) { .vc-settings-quickActions-card { grid-template-columns: repeat(2, 1fr); } } .vc-settings-quickActions-pill { all: unset; cursor: pointer; background: var(--control-secondary-background-default); color: var(--control-secondary-text-default, var(--text-default)); display: flex; align-items: center; gap: 0.5em; padding: 8px 9px; border-radius: 8px; transition: 0.1s ease-out; box-sizing: border-box; } .vc-settings-quickActions-pill:hover { background: var(--control-secondary-background-hover); transform: translateY(-1px); box-shadow: var(--elevation-high); } .vc-settings-quickActions-pill:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; } .vc-settings-quickActions-img { width: 24px; height: 24px; } ================================================ FILE: src/components/settings/QuickAction.tsx ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2024 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import "./QuickAction.css"; import { Card } from "@components/Card"; import { classNameFactory } from "@utils/css"; import type { ComponentType, PropsWithChildren, ReactNode } from "react"; const cl = classNameFactory("vc-settings-quickActions-"); export interface QuickActionProps { Icon: ComponentType<{ className?: string; }>; text: ReactNode; action?: () => void; disabled?: boolean; } export function QuickAction(props: QuickActionProps) { const { Icon, action, text, disabled } = props; return ( ); } export function QuickActionCard(props: PropsWithChildren) { return ( {props.children} ); } ================================================ FILE: src/components/settings/SpecialCard.css ================================================ .vc-donate-button { overflow: visible !important; } .vc-donate-button .vc-heart-icon { transition: transform 0.3s; margin-right: 0.5em; } .vc-donate-button:hover .vc-heart-icon { transform: scale(1.1); z-index: 10; position: relative; } .vc-special-card-special { padding: 1em 1.5em; margin-bottom: 1em; background-size: cover; background-position: center; } .vc-special-card-flex { display: flex; flex-direction: row; } .vc-special-card-flex-main { width: 100%; } .vc-special-title { color: black; } .vc-special-subtitle { color: black; font-size: 1.2em; font-weight: bold; margin-top: 0.5em; } .vc-special-text { color: black; font-size: 1em; margin-top: .75em; white-space: pre-line; } .vc-special-seperator { margin-top: .75em; border-top: 1px solid white; opacity: 0.4; } .vc-special-hyperlink { margin-top: 1em; cursor: pointer; .vc-special-hyperlink-text { color: black; font-size: 1em; font-weight: bold; text-align: center; transition: text-decoration 0.5s; cursor: pointer; } &:hover .vc-special-hyperlink-text { text-decoration: underline; } } .vc-special-image-container { display: flex; justify-content: center; align-items: center; margin-left: 1em; flex-shrink: 0; width: 100px; height: 100px; border-radius: 50%; background-color: white; } .vc-special-image { width: 65%; } ================================================ FILE: src/components/settings/SpecialCard.tsx ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import "./SpecialCard.css"; import { Card } from "@components/Card"; import { Divider } from "@components/Divider"; import { classNameFactory } from "@utils/css"; import { Clickable, Forms } from "@webpack/common"; import type { PropsWithChildren } from "react"; const cl = classNameFactory("vc-special-"); interface StyledCardProps { title: string; subtitle?: string; description: string; cardImage?: string; backgroundImage?: string; backgroundColor?: string; buttonTitle?: string; buttonOnClick?: () => void; } export function SpecialCard({ title, subtitle, description, cardImage, backgroundImage, backgroundColor, buttonTitle, buttonOnClick: onClick, children }: PropsWithChildren) { const cardStyle: React.CSSProperties = { backgroundColor: backgroundColor || "#9c85ef", backgroundImage: `url(${backgroundImage || ""})`, }; return (
{title} {subtitle} {description} {children}
{cardImage && (
)}
{buttonTitle && ( <> {buttonTitle} )}
); } ================================================ FILE: src/components/settings/index.ts ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ export * from "../Switch"; export * from "./AddonCard"; export * from "./DonateButton"; export * from "./PluginBadge"; export * from "./QuickAction"; export * from "./SpecialCard"; export * from "./tabs"; ================================================ FILE: src/components/settings/tabs/BaseTab.tsx ================================================ /* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import ErrorBoundary from "@components/ErrorBoundary"; import { handleComponentFailed } from "@components/handleComponentFailed"; import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { onlyOnce } from "@utils/onlyOnce"; import type { ComponentType, PropsWithChildren } from "react"; export function SettingsTab({ children }: PropsWithChildren) { return (
{children}
); } export const handleSettingsTabError = onlyOnce(handleComponentFailed); export function wrapTab(component: ComponentType, tab: string) { const wrapped = ErrorBoundary.wrap(component, { displayName: `${tab}SettingsTab`, message: `Failed to render the ${tab} tab. If this issue persists, try using the installer to reinstall!`, onError: handleSettingsTabError, }); return wrapped; } export function openSettingsTabModal(Tab: ComponentType) { try { openModal(wrapTab((modalProps: ModalProps) => ( ), Tab.displayName || "Settings Tab")); } catch { handleSettingsTabError(); } } ================================================ FILE: src/components/settings/tabs/index.ts ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import "./styles.css"; export * from "./BaseTab"; export { default as PatchHelperTab } from "./patchHelper"; export { default as PluginsTab } from "./plugins"; export { openContributorModal } from "./plugins/ContributorModal"; export { openPluginModal } from "./plugins/PluginModal"; export { default as BackupAndRestoreTab } from "./sync/BackupAndRestoreTab"; export { default as CloudTab } from "./sync/CloudTab"; export { default as ThemesTab } from "./themes"; export { default as UpdaterTab } from "./updater"; export { default as VencordTab } from "./vencord"; ================================================ FILE: src/components/settings/tabs/patchHelper/FullPatchInput.tsx ================================================ /* * Vencord, a Discord client mod * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import { Margins } from "@utils/margins"; import { Patch, ReplaceFn } from "@utils/types"; import { Forms, TextArea, useEffect, useRef, useState } from "@webpack/common"; export interface FullPatchInputProps { setFind(v: string): void; setParsedFind(v: string | RegExp): void; setMatch(v: string): void; setReplacement(v: string | ReplaceFn): void; } export function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: FullPatchInputProps) { const [patch, setPatch] = useState(""); const [error, setError] = useState(""); const textAreaRef = useRef(null); function update() { if (patch === "") { setError(""); setFind(""); setParsedFind(""); setMatch(""); setReplacement(""); return; } try { let { find, replacement } = (0, eval)(`([${patch}][0])`) as Patch; if (!find) throw new Error("No 'find' field"); if (!replacement) throw new Error("No 'replacement' field"); if (replacement instanceof Array) { if (replacement.length === 0) throw new Error("Invalid replacement"); // Only test the first replacement replacement = replacement[0]; } if (!replacement.match) throw new Error("No 'replacement.match' field"); if (replacement.replace == null) throw new Error("No 'replacement.replace' field"); setFind(find instanceof RegExp ? `/${find.source}/` : find); setParsedFind(find); setMatch(replacement.match instanceof RegExp ? replacement.match.source : replacement.match); setReplacement(replacement.replace); setError(""); } catch (e) { setError((e as Error).message); } } useEffect(() => { const { current: textArea } = textAreaRef; if (textArea) { textArea.style.height = "auto"; textArea.style.height = `${textArea.scrollHeight}px`; } }, [patch]); return ( <> Paste your full JSON patch here to fill out the fields