Showing preview only (2,924K chars total). Download the full file or copy to clipboard to get everything.
Repository: Dragory/ZeppelinBot
Branch: master
Commit: 6795bf7adc92
Files: 1116
Total size: 2.6 MB
Directory structure:
gitextract_rchn42w_/
├── .clabot
├── .cursorignore
├── .devcontainer/
│ └── devcontainer.json
├── .dockerignore
├── .editorconfig
├── .eslintrc.js
├── .gitattributes
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── codequality.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── AGENTS.md
├── DEVELOPMENT.md
├── Dockerfile
├── LICENSE.md
├── MANAGEMENT.md
├── PRODUCTION.md
├── README.md
├── assets/
│ └── icons/
│ ├── LICENSE
│ └── case_icons.afphoto
├── backend/
│ ├── .gitignore
│ ├── .prettierignore
│ ├── package.json
│ ├── register-tsconfig-paths.js
│ ├── src/
│ │ ├── Blocker.ts
│ │ ├── DiscordJSError.ts
│ │ ├── Queue.ts
│ │ ├── QueuedEventEmitter.ts
│ │ ├── RecoverablePluginError.ts
│ │ ├── RegExpRunner.ts
│ │ ├── SimpleCache.ts
│ │ ├── SimpleError.ts
│ │ ├── api/
│ │ │ ├── archives.ts
│ │ │ ├── auth.ts
│ │ │ ├── docs.ts
│ │ │ ├── guilds/
│ │ │ │ ├── importExport.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── misc.ts
│ │ │ ├── guilds.ts
│ │ │ ├── index.ts
│ │ │ ├── permissions.ts
│ │ │ ├── rateLimits.ts
│ │ │ ├── responses.ts
│ │ │ ├── staff.ts
│ │ │ ├── start.ts
│ │ │ └── tasks.ts
│ │ ├── commandTypes.ts
│ │ ├── configValidator.ts
│ │ ├── data/
│ │ │ ├── AllowedGuilds.ts
│ │ │ ├── ApiAuditLog.ts
│ │ │ ├── ApiLogins.ts
│ │ │ ├── ApiPermissionAssignments.ts
│ │ │ ├── ApiUserInfo.ts
│ │ │ ├── Archives.ts
│ │ │ ├── BaseGuildRepository.ts
│ │ │ ├── BaseRepository.ts
│ │ │ ├── CaseTypes.ts
│ │ │ ├── Configs.ts
│ │ │ ├── DefaultLogMessages.json
│ │ │ ├── FishFish.ts
│ │ │ ├── GuildAntiraidLevels.ts
│ │ │ ├── GuildArchives.ts
│ │ │ ├── GuildAutoReactions.ts
│ │ │ ├── GuildButtonRoles.ts
│ │ │ ├── GuildCases.ts
│ │ │ ├── GuildContextMenuLinks.ts
│ │ │ ├── GuildCounters.ts
│ │ │ ├── GuildEvents.ts
│ │ │ ├── GuildLogs.ts
│ │ │ ├── GuildMemberCache.ts
│ │ │ ├── GuildMemberTimezones.ts
│ │ │ ├── GuildMutes.ts
│ │ │ ├── GuildNicknameHistory.ts
│ │ │ ├── GuildPersistedData.ts
│ │ │ ├── GuildPingableRoles.ts
│ │ │ ├── GuildReactionRoles.ts
│ │ │ ├── GuildReminders.ts
│ │ │ ├── GuildRoleButtons.ts
│ │ │ ├── GuildRoleQueue.ts
│ │ │ ├── GuildSavedMessages.ts
│ │ │ ├── GuildScheduledPosts.ts
│ │ │ ├── GuildSlowmodes.ts
│ │ │ ├── GuildStarboardMessages.ts
│ │ │ ├── GuildStarboardReactions.ts
│ │ │ ├── GuildStats.ts
│ │ │ ├── GuildTags.ts
│ │ │ ├── GuildTempbans.ts
│ │ │ ├── GuildVCAlerts.ts
│ │ │ ├── LogType.ts
│ │ │ ├── MemberCache.ts
│ │ │ ├── MuteTypes.ts
│ │ │ ├── Mutes.ts
│ │ │ ├── Reminders.ts
│ │ │ ├── ScheduledPosts.ts
│ │ │ ├── Supporters.ts
│ │ │ ├── Tempbans.ts
│ │ │ ├── UsernameHistory.ts
│ │ │ ├── VCAlerts.ts
│ │ │ ├── Webhooks.ts
│ │ │ ├── Zalgo.ts
│ │ │ ├── apiAuditLogTypes.ts
│ │ │ ├── buildEntity.ts
│ │ │ ├── cleanup/
│ │ │ │ ├── configs.ts
│ │ │ │ ├── messages.ts
│ │ │ │ ├── nicknames.ts
│ │ │ │ └── usernames.ts
│ │ │ ├── dataSource.ts
│ │ │ ├── db.ts
│ │ │ ├── entities/
│ │ │ │ ├── AllowedGuild.ts
│ │ │ │ ├── AntiraidLevel.ts
│ │ │ │ ├── ApiAuditLogEntry.ts
│ │ │ │ ├── ApiLogin.ts
│ │ │ │ ├── ApiPermissionAssignment.ts
│ │ │ │ ├── ApiUserInfo.ts
│ │ │ │ ├── ArchiveEntry.ts
│ │ │ │ ├── AutoReaction.ts
│ │ │ │ ├── ButtonRole.ts
│ │ │ │ ├── Case.ts
│ │ │ │ ├── CaseNote.ts
│ │ │ │ ├── Config.ts
│ │ │ │ ├── ContextMenuLink.ts
│ │ │ │ ├── Counter.ts
│ │ │ │ ├── CounterTrigger.ts
│ │ │ │ ├── CounterTriggerState.ts
│ │ │ │ ├── CounterValue.ts
│ │ │ │ ├── MemberCacheItem.ts
│ │ │ │ ├── MemberTimezone.ts
│ │ │ │ ├── Mute.ts
│ │ │ │ ├── NicknameHistoryEntry.ts
│ │ │ │ ├── PersistedData.ts
│ │ │ │ ├── PingableRole.ts
│ │ │ │ ├── ReactionRole.ts
│ │ │ │ ├── Reminder.ts
│ │ │ │ ├── RoleButtonsItem.ts
│ │ │ │ ├── RoleQueueItem.ts
│ │ │ │ ├── SavedMessage.ts
│ │ │ │ ├── ScheduledPost.ts
│ │ │ │ ├── SlowmodeChannel.ts
│ │ │ │ ├── SlowmodeUser.ts
│ │ │ │ ├── StarboardMessage.ts
│ │ │ │ ├── StarboardReaction.ts
│ │ │ │ ├── StatValue.ts
│ │ │ │ ├── Supporter.ts
│ │ │ │ ├── Tag.ts
│ │ │ │ ├── TagResponse.ts
│ │ │ │ ├── Tempban.ts
│ │ │ │ ├── UsernameHistoryEntry.ts
│ │ │ │ ├── VCAlert.ts
│ │ │ │ └── Webhook.ts
│ │ │ ├── getChannelIdFromMessageId.ts
│ │ │ ├── loops/
│ │ │ │ ├── expiredArchiveDeletionLoop.ts
│ │ │ │ ├── expiredMemberCacheDeletionLoop.ts
│ │ │ │ ├── expiringMutesLoop.ts
│ │ │ │ ├── expiringTempbansLoop.ts
│ │ │ │ ├── expiringVCAlertsLoop.ts
│ │ │ │ ├── memberCacheDeletionLoop.ts
│ │ │ │ ├── savedMessageCleanupLoop.ts
│ │ │ │ ├── upcomingRemindersLoop.ts
│ │ │ │ └── upcomingScheduledPostsLoop.ts
│ │ │ ├── queryLogger.ts
│ │ │ └── redis.ts
│ │ ├── debugCounters.ts
│ │ ├── env.ts
│ │ ├── exportSchemas.ts
│ │ ├── globals.ts
│ │ ├── humanizeDuration.ts
│ │ ├── index.ts
│ │ ├── logger.ts
│ │ ├── migrateConfigsToDB.ts
│ │ ├── migrations/
│ │ │ ├── 1540519249973-CreatePreTypeORMTables.ts
│ │ │ ├── 1543053430712-CreateMessagesTable.ts
│ │ │ ├── 1544877081073-CreateSlowmodeTables.ts
│ │ │ ├── 1544887946307-CreateStarboardTable.ts
│ │ │ ├── 1546770935261-CreateTagResponsesTable.ts
│ │ │ ├── 1546778415930-CreateNameHistoryTable.ts
│ │ │ ├── 1546788508314-MakeNameHistoryValueLengthLonger.ts
│ │ │ ├── 1547290549908-CreateAutoReactionsTable.ts
│ │ │ ├── 1547293464842-CreatePingableRolesTable.ts
│ │ │ ├── 1547392046629-AddIndexToArchivesExpiresAt.ts
│ │ │ ├── 1547393619900-AddIsHiddenToCases.ts
│ │ │ ├── 1549649586803-AddPPFieldsToCases.ts
│ │ │ ├── 1550409894008-FixEmojiIndexInReactionRoles.ts
│ │ │ ├── 1550521627877-CreateSelfGrantableRolesTable.ts
│ │ │ ├── 1550609900261-CreateRemindersTable.ts
│ │ │ ├── 1556908589679-CreateUsernameHistoryTable.ts
│ │ │ ├── 1556909512501-MigrateUsernamesToNewHistoryTable.ts
│ │ │ ├── 1556913287547-TurnNameHistoryToNicknameHistory.ts
│ │ │ ├── 1556973844545-CreateScheduledPostsTable.ts
│ │ │ ├── 1558804433320-CreateDashboardLoginsTable.ts
│ │ │ ├── 1558804449510-CreateDashboardUsersTable.ts
│ │ │ ├── 1561111990357-CreateConfigsTable.ts
│ │ │ ├── 1561117545258-CreateAllowedGuildsTable.ts
│ │ │ ├── 1561282151982-RenameBackendDashboardStuffToAPI.ts
│ │ │ ├── 1561282552734-RenameAllowedGuildGuildIdToId.ts
│ │ │ ├── 1561282950483-CreateApiUserInfoTable.ts
│ │ │ ├── 1561283165823-RenameApiUsersToApiPermissions.ts
│ │ │ ├── 1561283405201-DropUserDataFromLoginsAndPermissions.ts
│ │ │ ├── 1561391921385-AddVCAlertTable.ts
│ │ │ ├── 1562838838927-AddMoreIndicesToVCAlerts.ts
│ │ │ ├── 1573158035867-AddTypeAndPermissionsToApiPermissions.ts
│ │ │ ├── 1573248462469-MoveStarboardsToConfig.ts
│ │ │ ├── 1573248794313-CreateStarboardReactionsTable.ts
│ │ │ ├── 1575145703039-AddIsExclusiveToReactionRoles.ts
│ │ │ ├── 1575199835233-CreateStatsTable.ts
│ │ │ ├── 1575230079526-AddRepeatColumnsToScheduledPosts.ts
│ │ │ ├── 1578445483917-CreateReminderCreatedAtField.ts
│ │ │ ├── 1580038836906-CreateAntiraidLevelsTable.ts
│ │ │ ├── 1580654617890-AddActiveFollowsToLocateUser.ts
│ │ │ ├── 1590616691907-CreateSupportersTable.ts
│ │ │ ├── 1591036185142-OptimizeMessageIndices.ts
│ │ │ ├── 1591038041635-OptimizeMessageTimestamps.ts
│ │ │ ├── 1596994103885-AddCaseNotesForeignKey.ts
│ │ │ ├── 1597015567215-AddLogMessageIdToCases.ts
│ │ │ ├── 1597109357201-CreateMemberTimezonesTable.ts
│ │ │ ├── 1600283341726-EncryptExistingMessages.ts
│ │ │ ├── 1600285077890-EncryptArchives.ts
│ │ │ ├── 1608608903570-CreateRestoredRolesColumn.ts
│ │ │ ├── 1608692857722-FixStarboardReactionsIndices.ts
│ │ │ ├── 1608753440716-CreateTempBansTable.ts
│ │ │ ├── 1612010765767-CreateCounterTables.ts
│ │ │ ├── 1617363975046-UpdateCounterTriggers.ts
│ │ │ ├── 1622939525343-OrderReactionRoles.ts
│ │ │ ├── 1623018101018-CreateButtonRolesTable.ts
│ │ │ ├── 1628809879962-CreateContextMenuTable.ts
│ │ │ ├── 1630837386329-AddExpiresAtToApiPermissions.ts
│ │ │ ├── 1630837718830-CreateApiAuditLogTable.ts
│ │ │ ├── 1630840428694-AddTimestampsToAllowedGuilds.ts
│ │ │ ├── 1631474131804-AddIndexToIsBot.ts
│ │ │ ├── 1632582078622-SplitScheduledPostsPostAtIndex.ts
│ │ │ ├── 1632582299400-AddIndexToRemindersRemindAt.ts
│ │ │ ├── 1634459708599-RemoveTagResponsesForeignKeys.ts
│ │ │ ├── 1634563901575-CreatePhishermanCacheTable.ts
│ │ │ ├── 1635596150234-CreatePhishermanKeyCacheTable.ts
│ │ │ ├── 1635779678653-CreateWebhooksTable.ts
│ │ │ ├── 1650709103864-CreateRoleQueueTable.ts
│ │ │ ├── 1650712828384-CreateRoleButtonsTable.ts
│ │ │ ├── 1650721020704-RemoveButtonRolesTable.ts
│ │ │ ├── 1680354053183-AddTimeoutColumnsToMutes.ts
│ │ │ └── 1682788165866-CreateMemberCacheTable.ts
│ │ ├── paths.ts
│ │ ├── pluginUtils.ts
│ │ ├── plugins/
│ │ │ ├── AutoDelete/
│ │ │ │ ├── AutoDeletePlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── util/
│ │ │ │ ├── addMessageToDeletionQueue.ts
│ │ │ │ ├── deleteNextItem.ts
│ │ │ │ ├── onMessageCreate.ts
│ │ │ │ ├── onMessageDelete.ts
│ │ │ │ ├── onMessageDeleteBulk.ts
│ │ │ │ └── scheduleNextDeletion.ts
│ │ │ ├── AutoReactions/
│ │ │ │ ├── AutoReactionsPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── DisableAutoReactionsCmd.ts
│ │ │ │ │ └── NewAutoReactionsCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ └── AddReactionsEvt.ts
│ │ │ │ └── types.ts
│ │ │ ├── Automod/
│ │ │ │ ├── AutomodPlugin.ts
│ │ │ │ ├── actions/
│ │ │ │ │ ├── addRoles.ts
│ │ │ │ │ ├── addToCounter.ts
│ │ │ │ │ ├── alert.ts
│ │ │ │ │ ├── archiveThread.ts
│ │ │ │ │ ├── availableActions.ts
│ │ │ │ │ ├── ban.ts
│ │ │ │ │ ├── changeNickname.ts
│ │ │ │ │ ├── changePerms.ts
│ │ │ │ │ ├── clean.ts
│ │ │ │ │ ├── exampleAction.ts
│ │ │ │ │ ├── kick.ts
│ │ │ │ │ ├── log.ts
│ │ │ │ │ ├── mute.ts
│ │ │ │ │ ├── pauseInvites.ts
│ │ │ │ │ ├── removeRoles.ts
│ │ │ │ │ ├── reply.ts
│ │ │ │ │ ├── setAntiraidLevel.ts
│ │ │ │ │ ├── setCounter.ts
│ │ │ │ │ ├── setSlowmode.ts
│ │ │ │ │ ├── startThread.ts
│ │ │ │ │ └── warn.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── AntiraidClearCmd.ts
│ │ │ │ │ ├── DebugAutomodCmd.ts
│ │ │ │ │ ├── SetAntiraidCmd.ts
│ │ │ │ │ └── ViewAntiraidCmd.ts
│ │ │ │ ├── constants.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ ├── RunAutomodOnJoinLeaveEvt.ts
│ │ │ │ │ ├── RunAutomodOnMemberUpdate.ts
│ │ │ │ │ ├── runAutomodOnAntiraidLevel.ts
│ │ │ │ │ ├── runAutomodOnCounterTrigger.ts
│ │ │ │ │ ├── runAutomodOnMessage.ts
│ │ │ │ │ ├── runAutomodOnModAction.ts
│ │ │ │ │ └── runAutomodOnThreadEvents.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── addRecentActionsFromMessage.ts
│ │ │ │ │ ├── applyCooldown.ts
│ │ │ │ │ ├── checkCooldown.ts
│ │ │ │ │ ├── clearOldNicknameChanges.ts
│ │ │ │ │ ├── clearOldRecentActions.ts
│ │ │ │ │ ├── clearOldRecentSpam.ts
│ │ │ │ │ ├── clearRecentActionsForMessage.ts
│ │ │ │ │ ├── createMessageSpamTrigger.ts
│ │ │ │ │ ├── findRecentSpam.ts
│ │ │ │ │ ├── getMatchingMessageRecentActions.ts
│ │ │ │ │ ├── getMatchingRecentActions.ts
│ │ │ │ │ ├── getSpamIdentifier.ts
│ │ │ │ │ ├── getTextMatchPartialSummary.ts
│ │ │ │ │ ├── ignoredRoleChanges.ts
│ │ │ │ │ ├── matchMultipleTextTypesOnMessage.ts
│ │ │ │ │ ├── resolveActionContactMethods.ts
│ │ │ │ │ ├── runAutomod.ts
│ │ │ │ │ ├── setAntiraidLevel.ts
│ │ │ │ │ └── sumRecentActionCounts.ts
│ │ │ │ ├── helpers.ts
│ │ │ │ ├── triggers/
│ │ │ │ │ ├── antiraidLevel.ts
│ │ │ │ │ ├── anyMessage.ts
│ │ │ │ │ ├── attachmentSpam.ts
│ │ │ │ │ ├── availableTriggers.ts
│ │ │ │ │ ├── ban.ts
│ │ │ │ │ ├── characterSpam.ts
│ │ │ │ │ ├── counterTrigger.ts
│ │ │ │ │ ├── emojiSpam.ts
│ │ │ │ │ ├── exampleTrigger.ts
│ │ │ │ │ ├── hasAttachments.ts
│ │ │ │ │ ├── kick.ts
│ │ │ │ │ ├── lineSpam.ts
│ │ │ │ │ ├── linkSpam.ts
│ │ │ │ │ ├── matchAttachmentType.ts
│ │ │ │ │ ├── matchInvites.ts
│ │ │ │ │ ├── matchLinks.ts
│ │ │ │ │ ├── matchMimeType.ts
│ │ │ │ │ ├── matchRegex.ts
│ │ │ │ │ ├── matchWords.ts
│ │ │ │ │ ├── memberJoin.ts
│ │ │ │ │ ├── memberJoinSpam.ts
│ │ │ │ │ ├── memberLeave.ts
│ │ │ │ │ ├── mentionSpam.ts
│ │ │ │ │ ├── messageSpam.ts
│ │ │ │ │ ├── mute.ts
│ │ │ │ │ ├── note.ts
│ │ │ │ │ ├── roleAdded.ts
│ │ │ │ │ ├── roleRemoved.ts
│ │ │ │ │ ├── stickerSpam.ts
│ │ │ │ │ ├── threadArchive.ts
│ │ │ │ │ ├── threadCreate.ts
│ │ │ │ │ ├── threadCreateSpam.ts
│ │ │ │ │ ├── threadDelete.ts
│ │ │ │ │ ├── threadUnarchive.ts
│ │ │ │ │ ├── unban.ts
│ │ │ │ │ ├── unmute.ts
│ │ │ │ │ └── warn.ts
│ │ │ │ └── types.ts
│ │ │ ├── BotControl/
│ │ │ │ ├── BotControlPlugin.ts
│ │ │ │ ├── activeReload.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── AddDashboardUserCmd.ts
│ │ │ │ │ ├── AddServerFromInviteCmd.ts
│ │ │ │ │ ├── AllowServerCmd.ts
│ │ │ │ │ ├── ChannelToServerCmd.ts
│ │ │ │ │ ├── DebugCountersCmd.ts
│ │ │ │ │ ├── DisallowServerCmd.ts
│ │ │ │ │ ├── EligibleCmd.ts
│ │ │ │ │ ├── LeaveServerCmd.ts
│ │ │ │ │ ├── ListDashboardPermsCmd.ts
│ │ │ │ │ ├── ListDashboardUsersCmd.ts
│ │ │ │ │ ├── ProfilerDataCmd.ts
│ │ │ │ │ ├── RateLimitPerformanceCmd.ts
│ │ │ │ │ ├── ReloadGlobalPluginsCmd.ts
│ │ │ │ │ ├── ReloadServerCmd.ts
│ │ │ │ │ ├── RemoveDashboardUserCmd.ts
│ │ │ │ │ ├── RestPerformanceCmd.ts
│ │ │ │ │ └── ServersCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── functions/
│ │ │ │ │ └── isEligible.ts
│ │ │ │ └── types.ts
│ │ │ ├── Cases/
│ │ │ │ ├── CasesPlugin.ts
│ │ │ │ ├── caseAbbreviations.ts
│ │ │ │ ├── caseColors.ts
│ │ │ │ ├── caseIcons.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── createCase.ts
│ │ │ │ │ ├── createCaseNote.ts
│ │ │ │ │ ├── getCaseColor.ts
│ │ │ │ │ ├── getCaseEmbed.ts
│ │ │ │ │ ├── getCaseIcon.ts
│ │ │ │ │ ├── getCaseSummary.ts
│ │ │ │ │ ├── getCaseTypeAmountForUserId.ts
│ │ │ │ │ ├── getRecentCasesByMod.ts
│ │ │ │ │ ├── getTotalCasesByMod.ts
│ │ │ │ │ ├── postToCaseLogChannel.ts
│ │ │ │ │ └── resolveCaseId.ts
│ │ │ │ └── types.ts
│ │ │ ├── Censor/
│ │ │ │ ├── CensorPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── util/
│ │ │ │ ├── applyFiltersToMsg.ts
│ │ │ │ ├── censorMessage.ts
│ │ │ │ ├── onMessageCreate.ts
│ │ │ │ └── onMessageUpdate.ts
│ │ │ ├── ChannelArchiver/
│ │ │ │ ├── ChannelArchiverPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ └── ArchiveChannelCmd.ts
│ │ │ │ ├── rehostAttachment.ts
│ │ │ │ └── types.ts
│ │ │ ├── CommandAliases/
│ │ │ │ ├── CommandAliasesPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ └── DispatchAliasEvt.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── buildAliasMatchers.ts
│ │ │ │ │ └── normalizeAliases.ts
│ │ │ │ └── types.ts
│ │ │ ├── Common/
│ │ │ │ ├── CommonPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── functions/
│ │ │ │ │ └── getEmoji.ts
│ │ │ │ └── types.ts
│ │ │ ├── CompanionChannels/
│ │ │ │ ├── CompanionChannelsPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ └── VoiceStateUpdateEvt.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── getCompanionChannelOptsForVoiceChannelId.ts
│ │ │ │ │ └── handleCompanionPermissions.ts
│ │ │ │ └── types.ts
│ │ │ ├── ContextMenus/
│ │ │ │ ├── ContextMenuPlugin.ts
│ │ │ │ ├── actions/
│ │ │ │ │ ├── ban.ts
│ │ │ │ │ ├── clean.ts
│ │ │ │ │ ├── mute.ts
│ │ │ │ │ ├── note.ts
│ │ │ │ │ ├── update.ts
│ │ │ │ │ └── warn.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── BanUserCtxCmd.ts
│ │ │ │ │ ├── CleanMessageCtxCmd.ts
│ │ │ │ │ ├── ModMenuUserCtxCmd.ts
│ │ │ │ │ ├── MuteUserCtxCmd.ts
│ │ │ │ │ ├── NoteUserCtxCmd.ts
│ │ │ │ │ └── WarnUserCtxCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ └── types.ts
│ │ │ ├── Counters/
│ │ │ │ ├── CountersPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── AddCounterCmd.ts
│ │ │ │ │ ├── CountersListCmd.ts
│ │ │ │ │ ├── ResetAllCounterValuesCmd.ts
│ │ │ │ │ ├── ResetCounterCmd.ts
│ │ │ │ │ ├── SetCounterCmd.ts
│ │ │ │ │ └── ViewCounterCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── changeCounterValue.ts
│ │ │ │ │ ├── checkAllValuesForReverseTrigger.ts
│ │ │ │ │ ├── checkAllValuesForTrigger.ts
│ │ │ │ │ ├── checkCounterTrigger.ts
│ │ │ │ │ ├── checkReverseCounterTrigger.ts
│ │ │ │ │ ├── counterExists.ts
│ │ │ │ │ ├── decayCounter.ts
│ │ │ │ │ ├── emitCounterEvent.ts
│ │ │ │ │ ├── getPrettyNameForCounter.ts
│ │ │ │ │ ├── getPrettyNameForCounterTrigger.ts
│ │ │ │ │ ├── offCounterEvent.ts
│ │ │ │ │ ├── onCounterEvent.ts
│ │ │ │ │ ├── resetAllCounterValues.ts
│ │ │ │ │ └── setCounterValue.ts
│ │ │ │ └── types.ts
│ │ │ ├── CustomEvents/
│ │ │ │ ├── ActionError.ts
│ │ │ │ ├── CustomEventsPlugin.ts
│ │ │ │ ├── actions/
│ │ │ │ │ ├── addRoleAction.ts
│ │ │ │ │ ├── createCaseAction.ts
│ │ │ │ │ ├── makeRoleMentionableAction.ts
│ │ │ │ │ ├── makeRoleUnmentionableAction.ts
│ │ │ │ │ ├── messageAction.ts
│ │ │ │ │ ├── moveToVoiceChannelAction.ts
│ │ │ │ │ └── setChannelPermissionOverrides.ts
│ │ │ │ ├── catchTemplateError.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── functions/
│ │ │ │ │ └── runEvent.ts
│ │ │ │ └── types.ts
│ │ │ ├── GuildAccessMonitor/
│ │ │ │ ├── GuildAccessMonitorPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ └── types.ts
│ │ │ ├── GuildConfigReloader/
│ │ │ │ ├── GuildConfigReloaderPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── functions/
│ │ │ │ │ └── reloadChangedGuilds.ts
│ │ │ │ └── types.ts
│ │ │ ├── GuildInfoSaver/
│ │ │ │ ├── GuildInfoSaverPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ └── types.ts
│ │ │ ├── GuildMemberCache/
│ │ │ │ ├── GuildMemberCachePlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ ├── cancelDeletionOnMemberJoin.ts
│ │ │ │ │ ├── removeMemberCacheOnMemberLeave.ts
│ │ │ │ │ ├── updateMemberCacheOnMemberUpdate.ts
│ │ │ │ │ ├── updateMemberCacheOnMessage.ts
│ │ │ │ │ ├── updateMemberCacheOnRoleChange.ts
│ │ │ │ │ └── updateMemberCacheOnVoiceStateUpdate.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── getCachedMemberData.ts
│ │ │ │ │ └── updateMemberCacheForMember.ts
│ │ │ │ └── types.ts
│ │ │ ├── InternalPoster/
│ │ │ │ ├── InternalPosterPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── editMessage.ts
│ │ │ │ │ ├── getOrCreateWebhookClientForChannel.ts
│ │ │ │ │ ├── getOrCreateWebhookForChannel.ts
│ │ │ │ │ └── sendMessage.ts
│ │ │ │ └── types.ts
│ │ │ ├── LocateUser/
│ │ │ │ ├── LocateUserPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── FollowCmd.ts
│ │ │ │ │ ├── ListFollowCmd.ts
│ │ │ │ │ └── WhereCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ ├── BanRemoveAlertsEvt.ts
│ │ │ │ │ └── SendAlertsEvts.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── utils/
│ │ │ │ ├── clearExpiredAlert.ts
│ │ │ │ ├── createOrReuseInvite.ts
│ │ │ │ ├── fillAlertsList.ts
│ │ │ │ ├── moveMember.ts
│ │ │ │ ├── removeUserIdFromActiveAlerts.ts
│ │ │ │ ├── sendAlerts.ts
│ │ │ │ └── sendWhere.ts
│ │ │ ├── Logs/
│ │ │ │ ├── LogsPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ ├── LogsChannelModifyEvts.ts
│ │ │ │ │ ├── LogsEmojiAndStickerModifyEvts.ts
│ │ │ │ │ ├── LogsGuildBanEvts.ts
│ │ │ │ │ ├── LogsGuildMemberAddEvt.ts
│ │ │ │ │ ├── LogsGuildMemberRemoveEvt.ts
│ │ │ │ │ ├── LogsGuildMemberRoleChangeEvt.ts
│ │ │ │ │ ├── LogsRoleModifyEvts.ts
│ │ │ │ │ ├── LogsStageInstanceModifyEvts.ts
│ │ │ │ │ ├── LogsThreadModifyEvts.ts
│ │ │ │ │ ├── LogsUserUpdateEvts.ts
│ │ │ │ │ └── LogsVoiceChannelEvts.ts
│ │ │ │ ├── logFunctions/
│ │ │ │ │ ├── logAutomodAction.ts
│ │ │ │ │ ├── logBotAlert.ts
│ │ │ │ │ ├── logCaseCreate.ts
│ │ │ │ │ ├── logCaseDelete.ts
│ │ │ │ │ ├── logCaseUpdate.ts
│ │ │ │ │ ├── logCensor.ts
│ │ │ │ │ ├── logChannelCreate.ts
│ │ │ │ │ ├── logChannelDelete.ts
│ │ │ │ │ ├── logChannelUpdate.ts
│ │ │ │ │ ├── logClean.ts
│ │ │ │ │ ├── logDmFailed.ts
│ │ │ │ │ ├── logEmojiCreate.ts
│ │ │ │ │ ├── logEmojiDelete.ts
│ │ │ │ │ ├── logEmojiUpdate.ts
│ │ │ │ │ ├── logMassBan.ts
│ │ │ │ │ ├── logMassMute.ts
│ │ │ │ │ ├── logMassUnban.ts
│ │ │ │ │ ├── logMemberBan.ts
│ │ │ │ │ ├── logMemberForceban.ts
│ │ │ │ │ ├── logMemberJoin.ts
│ │ │ │ │ ├── logMemberJoinWithPriorRecords.ts
│ │ │ │ │ ├── logMemberKick.ts
│ │ │ │ │ ├── logMemberLeave.ts
│ │ │ │ │ ├── logMemberMute.ts
│ │ │ │ │ ├── logMemberMuteExpired.ts
│ │ │ │ │ ├── logMemberMuteRejoin.ts
│ │ │ │ │ ├── logMemberNickChange.ts
│ │ │ │ │ ├── logMemberNote.ts
│ │ │ │ │ ├── logMemberRestore.ts
│ │ │ │ │ ├── logMemberRoleAdd.ts
│ │ │ │ │ ├── logMemberRoleChanges.ts
│ │ │ │ │ ├── logMemberRoleRemove.ts
│ │ │ │ │ ├── logMemberTimedBan.ts
│ │ │ │ │ ├── logMemberTimedMute.ts
│ │ │ │ │ ├── logMemberTimedUnban.ts
│ │ │ │ │ ├── logMemberTimedUnmute.ts
│ │ │ │ │ ├── logMemberUnban.ts
│ │ │ │ │ ├── logMemberUnmute.ts
│ │ │ │ │ ├── logMemberWarn.ts
│ │ │ │ │ ├── logMessageDelete.ts
│ │ │ │ │ ├── logMessageDeleteAuto.ts
│ │ │ │ │ ├── logMessageDeleteBare.ts
│ │ │ │ │ ├── logMessageDeleteBulk.ts
│ │ │ │ │ ├── logMessageEdit.ts
│ │ │ │ │ ├── logMessageSpamDetected.ts
│ │ │ │ │ ├── logOtherSpamDetected.ts
│ │ │ │ │ ├── logPostedScheduledMessage.ts
│ │ │ │ │ ├── logRepeatedMessage.ts
│ │ │ │ │ ├── logRoleCreate.ts
│ │ │ │ │ ├── logRoleDelete.ts
│ │ │ │ │ ├── logRoleUpdate.ts
│ │ │ │ │ ├── logScheduledMessage.ts
│ │ │ │ │ ├── logScheduledRepeatedMessage.ts
│ │ │ │ │ ├── logSetAntiraidAuto.ts
│ │ │ │ │ ├── logSetAntiraidUser.ts
│ │ │ │ │ ├── logStageInstanceCreate.ts
│ │ │ │ │ ├── logStageInstanceDelete.ts
│ │ │ │ │ ├── logStageInstanceUpdate.ts
│ │ │ │ │ ├── logStickerCreate.ts
│ │ │ │ │ ├── logStickerDelete.ts
│ │ │ │ │ ├── logStickerUpdate.ts
│ │ │ │ │ ├── logThreadCreate.ts
│ │ │ │ │ ├── logThreadDelete.ts
│ │ │ │ │ ├── logThreadUpdate.ts
│ │ │ │ │ ├── logVoiceChannelForceDisconnect.ts
│ │ │ │ │ ├── logVoiceChannelForceMove.ts
│ │ │ │ │ ├── logVoiceChannelJoin.ts
│ │ │ │ │ ├── logVoiceChannelLeave.ts
│ │ │ │ │ └── logVoiceChannelMove.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── util/
│ │ │ │ ├── getLogMessage.ts
│ │ │ │ ├── getMessageReplyLogInfo.ts
│ │ │ │ ├── isLogIgnored.ts
│ │ │ │ ├── log.ts
│ │ │ │ ├── onMessageDelete.ts
│ │ │ │ ├── onMessageDeleteBulk.ts
│ │ │ │ └── onMessageUpdate.ts
│ │ │ ├── MessageSaver/
│ │ │ │ ├── MessageSaverPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── SaveMessagesToDB.ts
│ │ │ │ │ └── SavePinsToDB.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ └── SaveMessagesEvts.ts
│ │ │ │ ├── saveMessagesToDB.ts
│ │ │ │ └── types.ts
│ │ │ ├── ModActions/
│ │ │ │ ├── ModActionsPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── addcase/
│ │ │ │ │ │ ├── AddCaseMsgCmd.ts
│ │ │ │ │ │ ├── AddCaseSlashCmd.ts
│ │ │ │ │ │ └── actualAddCaseCmd.ts
│ │ │ │ │ ├── ban/
│ │ │ │ │ │ ├── BanMsgCmd.ts
│ │ │ │ │ │ ├── BanSlashCmd.ts
│ │ │ │ │ │ └── actualBanCmd.ts
│ │ │ │ │ ├── case/
│ │ │ │ │ │ ├── CaseMsgCmd.ts
│ │ │ │ │ │ ├── CaseSlashCmd.ts
│ │ │ │ │ │ └── actualCaseCmd.ts
│ │ │ │ │ ├── cases/
│ │ │ │ │ │ ├── CasesModMsgCmd.ts
│ │ │ │ │ │ ├── CasesSlashCmd.ts
│ │ │ │ │ │ ├── CasesUserMsgCmd.ts
│ │ │ │ │ │ └── actualCasesCmd.ts
│ │ │ │ │ ├── constants.ts
│ │ │ │ │ ├── deletecase/
│ │ │ │ │ │ ├── DeleteCaseMsgCmd.ts
│ │ │ │ │ │ ├── DeleteCaseSlashCmd.ts
│ │ │ │ │ │ └── actualDeleteCaseCmd.ts
│ │ │ │ │ ├── forceban/
│ │ │ │ │ │ ├── ForceBanMsgCmd.ts
│ │ │ │ │ │ ├── ForceBanSlashCmd.ts
│ │ │ │ │ │ └── actualForceBanCmd.ts
│ │ │ │ │ ├── forcemute/
│ │ │ │ │ │ ├── ForceMuteMsgCmd.ts
│ │ │ │ │ │ └── ForceMuteSlashCmd.ts
│ │ │ │ │ ├── forceunmute/
│ │ │ │ │ │ ├── ForceUnmuteMsgCmd.ts
│ │ │ │ │ │ └── ForceUnmuteSlashCmd.ts
│ │ │ │ │ ├── hidecase/
│ │ │ │ │ │ ├── HideCaseMsgCmd.ts
│ │ │ │ │ │ ├── HideCaseSlashCmd.ts
│ │ │ │ │ │ └── actualHideCaseCmd.ts
│ │ │ │ │ ├── kick/
│ │ │ │ │ │ ├── KickMsgCmd.ts
│ │ │ │ │ │ ├── KickSlashCmd.ts
│ │ │ │ │ │ └── actualKickCmd.ts
│ │ │ │ │ ├── massban/
│ │ │ │ │ │ ├── MassBanMsgCmd.ts
│ │ │ │ │ │ ├── MassBanSlashCmd.ts
│ │ │ │ │ │ └── actualMassBanCmd.ts
│ │ │ │ │ ├── massmute/
│ │ │ │ │ │ ├── MassMuteMsgCmd.ts
│ │ │ │ │ │ ├── MassMuteSlashCmd.ts
│ │ │ │ │ │ └── actualMassMuteCmd.ts
│ │ │ │ │ ├── massunban/
│ │ │ │ │ │ ├── MassUnbanMsgCmd.ts
│ │ │ │ │ │ ├── MassUnbanSlashCmd.ts
│ │ │ │ │ │ └── actualMassUnbanCmd.ts
│ │ │ │ │ ├── mute/
│ │ │ │ │ │ ├── MuteMsgCmd.ts
│ │ │ │ │ │ ├── MuteSlashCmd.ts
│ │ │ │ │ │ └── actualMuteCmd.ts
│ │ │ │ │ ├── note/
│ │ │ │ │ │ ├── NoteMsgCmd.ts
│ │ │ │ │ │ ├── NoteSlashCmd.ts
│ │ │ │ │ │ └── actualNoteCmd.ts
│ │ │ │ │ ├── unban/
│ │ │ │ │ │ ├── UnbanMsgCmd.ts
│ │ │ │ │ │ ├── UnbanSlashCmd.ts
│ │ │ │ │ │ └── actualUnbanCmd.ts
│ │ │ │ │ ├── unhidecase/
│ │ │ │ │ │ ├── UnhideCaseMsgCmd.ts
│ │ │ │ │ │ ├── UnhideCaseSlashCmd.ts
│ │ │ │ │ │ └── actualUnhideCaseCmd.ts
│ │ │ │ │ ├── unmute/
│ │ │ │ │ │ ├── UnmuteMsgCmd.ts
│ │ │ │ │ │ ├── UnmuteSlashCmd.ts
│ │ │ │ │ │ └── actualUnmuteCmd.ts
│ │ │ │ │ ├── update/
│ │ │ │ │ │ ├── UpdateMsgCmd.ts
│ │ │ │ │ │ └── UpdateSlashCmd.ts
│ │ │ │ │ └── warn/
│ │ │ │ │ ├── WarnMsgCmd.ts
│ │ │ │ │ ├── WarnSlashCmd.ts
│ │ │ │ │ └── actualWarnCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ ├── AuditLogEvents.ts
│ │ │ │ │ ├── CreateBanCaseOnManualBanEvt.ts
│ │ │ │ │ ├── CreateKickCaseOnManualKickEvt.ts
│ │ │ │ │ ├── CreateUnbanCaseOnManualUnbanEvt.ts
│ │ │ │ │ └── PostAlertOnMemberJoinEvt.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── attachmentLinkReaction.ts
│ │ │ │ │ ├── banUserId.ts
│ │ │ │ │ ├── clearIgnoredEvents.ts
│ │ │ │ │ ├── clearTempban.ts
│ │ │ │ │ ├── formatReasonForAttachments.ts
│ │ │ │ │ ├── getDefaultContactMethods.ts
│ │ │ │ │ ├── hasModActionPerm.ts
│ │ │ │ │ ├── ignoreEvent.ts
│ │ │ │ │ ├── isBanned.ts
│ │ │ │ │ ├── isEventIgnored.ts
│ │ │ │ │ ├── kickMember.ts
│ │ │ │ │ ├── offModActionsEvent.ts
│ │ │ │ │ ├── onModActionsEvent.ts
│ │ │ │ │ ├── readContactMethodsFromArgs.ts
│ │ │ │ │ ├── updateCase.ts
│ │ │ │ │ └── warnMember.ts
│ │ │ │ └── types.ts
│ │ │ ├── Mutes/
│ │ │ │ ├── MutesPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── ClearBannedMutesCmd.ts
│ │ │ │ │ ├── ClearMutesCmd.ts
│ │ │ │ │ ├── ClearMutesWithoutRoleCmd.ts
│ │ │ │ │ └── MutesCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ ├── ClearActiveMuteOnMemberBanEvt.ts
│ │ │ │ │ ├── ClearActiveMuteOnRoleRemovalEvt.ts
│ │ │ │ │ ├── ReapplyActiveMuteOnJoinEvt.ts
│ │ │ │ │ └── RegisterManualTimeoutsEvt.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── clearMute.ts
│ │ │ │ │ ├── getDefaultMuteType.ts
│ │ │ │ │ ├── getTimeoutExpiryTime.ts
│ │ │ │ │ ├── memberHasMutedRole.ts
│ │ │ │ │ ├── muteUser.ts
│ │ │ │ │ ├── offMutesEvent.ts
│ │ │ │ │ ├── onMutesEvent.ts
│ │ │ │ │ ├── renewTimeoutMute.ts
│ │ │ │ │ └── unmuteUser.ts
│ │ │ │ └── types.ts
│ │ │ ├── NameHistory/
│ │ │ │ ├── NameHistoryPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ └── NamesCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ └── UpdateNameEvts.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── updateNickname.ts
│ │ │ ├── Persist/
│ │ │ │ ├── PersistPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ ├── LoadDataEvt.ts
│ │ │ │ │ └── StoreDataEvt.ts
│ │ │ │ └── types.ts
│ │ │ ├── Phisherman/
│ │ │ │ ├── PhishermanPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ └── types.ts
│ │ │ ├── PingableRoles/
│ │ │ │ ├── PingableRolesPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── PingableRoleDisableCmd.ts
│ │ │ │ │ └── PingableRoleEnableCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ └── ChangePingableEvts.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── utils/
│ │ │ │ ├── disablePingableRoles.ts
│ │ │ │ ├── enablePingableRoles.ts
│ │ │ │ └── getPingableRolesForChannel.ts
│ │ │ ├── Post/
│ │ │ │ ├── PostPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── EditCmd.ts
│ │ │ │ │ ├── EditEmbedCmd.ts
│ │ │ │ │ ├── PostCmd.ts
│ │ │ │ │ ├── PostEmbedCmd.ts
│ │ │ │ │ ├── ScheduledPostsDeleteCmd.ts
│ │ │ │ │ ├── ScheduledPostsListCmd.ts
│ │ │ │ │ └── ScheduledPostsShowCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── util/
│ │ │ │ ├── actualPostCmd.ts
│ │ │ │ ├── formatContent.ts
│ │ │ │ ├── parseScheduleTime.ts
│ │ │ │ ├── postMessage.ts
│ │ │ │ └── postScheduledPost.ts
│ │ │ ├── ReactionRoles/
│ │ │ │ ├── ReactionRolesPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── ClearReactionRolesCmd.ts
│ │ │ │ │ ├── InitReactionRolesCmd.ts
│ │ │ │ │ └── RefreshReactionRolesCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ ├── AddReactionRoleEvt.ts
│ │ │ │ │ └── MessageDeletedEvt.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── util/
│ │ │ │ ├── addMemberPendingRoleChange.ts
│ │ │ │ ├── applyReactionRoleReactionsToMessage.ts
│ │ │ │ ├── autoRefreshLoop.ts
│ │ │ │ ├── refreshReactionRoles.ts
│ │ │ │ └── runAutoRefresh.ts
│ │ │ ├── Reminders/
│ │ │ │ ├── RemindersPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── RemindCmd.ts
│ │ │ │ │ ├── RemindersCmd.ts
│ │ │ │ │ └── RemindersDeleteCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── functions/
│ │ │ │ │ └── postReminder.ts
│ │ │ │ └── types.ts
│ │ │ ├── RoleButtons/
│ │ │ │ ├── RoleButtonsPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ └── resetButtons.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ └── buttonInteraction.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── TooManyComponentsError.ts
│ │ │ │ │ ├── applyAllRoleButtons.ts
│ │ │ │ │ ├── applyRoleButtons.ts
│ │ │ │ │ ├── convertButtonStyleStringToEnum.ts
│ │ │ │ │ ├── createButtonComponents.ts
│ │ │ │ │ └── getAllRolesInButtons.ts
│ │ │ │ └── types.ts
│ │ │ ├── RoleManager/
│ │ │ │ ├── RoleManagerPlugin.ts
│ │ │ │ ├── constants.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── addPriorityRole.ts
│ │ │ │ │ ├── addRole.ts
│ │ │ │ │ ├── removePriorityRole.ts
│ │ │ │ │ ├── removeRole.ts
│ │ │ │ │ └── runRoleAssignmentLoop.ts
│ │ │ │ └── types.ts
│ │ │ ├── Roles/
│ │ │ │ ├── RolesPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── AddRoleCmd.ts
│ │ │ │ │ ├── MassAddRoleCmd.ts
│ │ │ │ │ ├── MassRemoveRoleCmd.ts
│ │ │ │ │ └── RemoveRoleCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ └── types.ts
│ │ │ ├── SelfGrantableRoles/
│ │ │ │ ├── SelfGrantableRolesPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── RoleAddCmd.ts
│ │ │ │ │ ├── RoleHelpCmd.ts
│ │ │ │ │ └── RoleRemoveCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── util/
│ │ │ │ ├── findMatchingRoles.ts
│ │ │ │ ├── getApplyingEntries.ts
│ │ │ │ ├── normalizeRoleNames.ts
│ │ │ │ └── splitRoleNames.ts
│ │ │ ├── Slowmode/
│ │ │ │ ├── SlowmodePlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── SlowmodeClearCmd.ts
│ │ │ │ │ ├── SlowmodeDisableCmd.ts
│ │ │ │ │ ├── SlowmodeGetCmd.ts
│ │ │ │ │ ├── SlowmodeListCmd.ts
│ │ │ │ │ └── SlowmodeSetCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── requiredPermissions.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── util/
│ │ │ │ ├── actualDisableSlowmodeCmd.ts
│ │ │ │ ├── applyBotSlowmodeToUserId.ts
│ │ │ │ ├── clearBotSlowmodeFromUserId.ts
│ │ │ │ ├── clearExpiredSlowmodes.ts
│ │ │ │ ├── disableBotSlowmodeForChannel.ts
│ │ │ │ └── onMessageCreate.ts
│ │ │ ├── Spam/
│ │ │ │ ├── SpamPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ └── SpamVoiceEvt.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── util/
│ │ │ │ ├── addRecentAction.ts
│ │ │ │ ├── clearOldRecentActions.ts
│ │ │ │ ├── clearRecentUserActions.ts
│ │ │ │ ├── getRecentActionCount.ts
│ │ │ │ ├── getRecentActions.ts
│ │ │ │ ├── logAndDetectMessageSpam.ts
│ │ │ │ ├── logAndDetectOtherSpam.ts
│ │ │ │ ├── logCensor.ts
│ │ │ │ ├── onMessageCreate.ts
│ │ │ │ └── saveSpamArchives.ts
│ │ │ ├── Starboard/
│ │ │ │ ├── StarboardPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ └── MigratePinsCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ ├── StarboardReactionAddEvt.ts
│ │ │ │ │ └── StarboardReactionRemoveEvts.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── util/
│ │ │ │ ├── createStarboardEmbedFromMessage.ts
│ │ │ │ ├── createStarboardPseudoFooterForMessage.ts
│ │ │ │ ├── onMessageDelete.ts
│ │ │ │ ├── removeMessageFromStarboard.ts
│ │ │ │ ├── removeMessageFromStarboardMessages.ts
│ │ │ │ ├── saveMessageToStarboard.ts
│ │ │ │ └── updateStarboardMessageStarCount.ts
│ │ │ ├── Tags/
│ │ │ │ ├── TagsPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── TagCreateCmd.ts
│ │ │ │ │ ├── TagDeleteCmd.ts
│ │ │ │ │ ├── TagEvalCmd.ts
│ │ │ │ │ ├── TagListCmd.ts
│ │ │ │ │ └── TagSourceCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── templateFunctions.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── util/
│ │ │ │ ├── findTagByName.ts
│ │ │ │ ├── matchAndRenderTagFromString.ts
│ │ │ │ ├── onMessageCreate.ts
│ │ │ │ ├── onMessageDelete.ts
│ │ │ │ ├── renderTagBody.ts
│ │ │ │ └── renderTagFromString.ts
│ │ │ ├── TimeAndDate/
│ │ │ │ ├── TimeAndDatePlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── ResetTimezoneCmd.ts
│ │ │ │ │ ├── SetTimezoneCmd.ts
│ │ │ │ │ └── ViewTimezoneCmd.ts
│ │ │ │ ├── defaultDateFormats.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── getDateFormat.ts
│ │ │ │ │ ├── getGuildTz.ts
│ │ │ │ │ ├── getMemberTz.ts
│ │ │ │ │ ├── inGuildTz.ts
│ │ │ │ │ └── inMemberTz.ts
│ │ │ │ └── types.ts
│ │ │ ├── UsernameSaver/
│ │ │ │ ├── UsernameSaverPlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ └── UpdateUsernameEvts.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── updateUsername.ts
│ │ │ ├── Utility/
│ │ │ │ ├── UtilityPlugin.ts
│ │ │ │ ├── commands/
│ │ │ │ │ ├── AboutCmd.ts
│ │ │ │ │ ├── AvatarCmd.ts
│ │ │ │ │ ├── BanSearchCmd.ts
│ │ │ │ │ ├── ChannelInfoCmd.ts
│ │ │ │ │ ├── CleanCmd.ts
│ │ │ │ │ ├── ContextCmd.ts
│ │ │ │ │ ├── EmojiInfoCmd.ts
│ │ │ │ │ ├── HelpCmd.ts
│ │ │ │ │ ├── InfoCmd.ts
│ │ │ │ │ ├── InviteInfoCmd.ts
│ │ │ │ │ ├── JumboCmd.ts
│ │ │ │ │ ├── LevelCmd.ts
│ │ │ │ │ ├── MessageInfoCmd.ts
│ │ │ │ │ ├── NicknameCmd.ts
│ │ │ │ │ ├── NicknameResetCmd.ts
│ │ │ │ │ ├── PingCmd.ts
│ │ │ │ │ ├── ReloadGuildCmd.ts
│ │ │ │ │ ├── RoleInfoCmd.ts
│ │ │ │ │ ├── RolesCmd.ts
│ │ │ │ │ ├── SearchCmd.ts
│ │ │ │ │ ├── ServerInfoCmd.ts
│ │ │ │ │ ├── SnowflakeInfoCmd.ts
│ │ │ │ │ ├── SourceCmd.ts
│ │ │ │ │ ├── UserInfoCmd.ts
│ │ │ │ │ ├── VcdisconnectCmd.ts
│ │ │ │ │ └── VcmoveCmd.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ └── AutoJoinThreadEvt.ts
│ │ │ │ ├── functions/
│ │ │ │ │ ├── cleanMessages.ts
│ │ │ │ │ ├── fetchChannelMessagesToClean.ts
│ │ │ │ │ ├── getChannelInfoEmbed.ts
│ │ │ │ │ ├── getCustomEmojiId.ts
│ │ │ │ │ ├── getEmojiInfoEmbed.ts
│ │ │ │ │ ├── getGuildPreview.ts
│ │ │ │ │ ├── getInviteInfoEmbed.ts
│ │ │ │ │ ├── getMessageInfoEmbed.ts
│ │ │ │ │ ├── getRoleInfoEmbed.ts
│ │ │ │ │ ├── getServerInfoEmbed.ts
│ │ │ │ │ ├── getSnowflakeInfoEmbed.ts
│ │ │ │ │ ├── getUserInfoEmbed.ts
│ │ │ │ │ └── hasPermission.ts
│ │ │ │ ├── guildReloads.ts
│ │ │ │ ├── refreshMembers.ts
│ │ │ │ ├── search.ts
│ │ │ │ └── types.ts
│ │ │ ├── WelcomeMessage/
│ │ │ │ ├── WelcomeMessagePlugin.ts
│ │ │ │ ├── docs.ts
│ │ │ │ ├── events/
│ │ │ │ │ └── SendWelcomeMessageEvt.ts
│ │ │ │ └── types.ts
│ │ │ └── availablePlugins.ts
│ │ ├── profiler.ts
│ │ ├── rateLimitStats.ts
│ │ ├── regExpRunners.ts
│ │ ├── restCallStats.ts
│ │ ├── staff.ts
│ │ ├── templateFormatter.test.ts
│ │ ├── templateFormatter.ts
│ │ ├── threadsSignalFix.ts
│ │ ├── types.ts
│ │ ├── uptime.ts
│ │ ├── utils/
│ │ │ ├── DecayingCounter.ts
│ │ │ ├── MessageBuffer.ts
│ │ │ ├── async.ts
│ │ │ ├── buildCustomId.ts
│ │ │ ├── calculateEmbedSize.ts
│ │ │ ├── canAssignRole.ts
│ │ │ ├── canReadChannel.ts
│ │ │ ├── categorize.ts
│ │ │ ├── createPaginatedMessage.ts
│ │ │ ├── crypt.test.ts
│ │ │ ├── crypt.ts
│ │ │ ├── cryptHelpers.ts
│ │ │ ├── cryptWorker.ts
│ │ │ ├── easyProfiler.ts
│ │ │ ├── erisAllowedMentionsToDjsMentionOptions.ts
│ │ │ ├── filterObject.ts
│ │ │ ├── findMatchingAuditLogEntry.ts
│ │ │ ├── formatZodIssue.ts
│ │ │ ├── getChunkedEmbedFields.ts
│ │ │ ├── getGuildPrefix.ts
│ │ │ ├── getMissingChannelPermissions.ts
│ │ │ ├── getMissingPermissions.ts
│ │ │ ├── getOrFetchGuildMember.ts
│ │ │ ├── getOrFetchUser.ts
│ │ │ ├── getPermissionNames.ts
│ │ │ ├── hasDiscordPermissions.ts
│ │ │ ├── idToTimestamp.ts
│ │ │ ├── intToRgb.ts
│ │ │ ├── isDefaultSticker.ts
│ │ │ ├── isDmChannel.ts
│ │ │ ├── isGuildChannel.ts
│ │ │ ├── isScalar.ts
│ │ │ ├── isThreadChannel.ts
│ │ │ ├── isValidTimezone.ts
│ │ │ ├── loadYamlSafely.ts
│ │ │ ├── lockNameHelpers.ts
│ │ │ ├── mergeRegexes.ts
│ │ │ ├── mergeWordsIntoRegex.ts
│ │ │ ├── messageHasContent.ts
│ │ │ ├── messageIsEmpty.ts
│ │ │ ├── missingPermissionError.ts
│ │ │ ├── multipleSlashOptions.ts
│ │ │ ├── normalizeText.test.ts
│ │ │ ├── normalizeText.ts
│ │ │ ├── parseColor.ts
│ │ │ ├── parseCustomId.ts
│ │ │ ├── parseFuzzyTimezone.ts
│ │ │ ├── permissionNames.ts
│ │ │ ├── readChannelPermissions.ts
│ │ │ ├── registerEventListenersFromMap.ts
│ │ │ ├── resolveChannelIds.ts
│ │ │ ├── resolveMessageTarget.ts
│ │ │ ├── rgbToInt.ts
│ │ │ ├── sendDM.ts
│ │ │ ├── snowflakeToTimestamp.ts
│ │ │ ├── stripMarkdown.ts
│ │ │ ├── templateSafeObjects.ts
│ │ │ ├── typeUtils.ts
│ │ │ ├── unregisterEventListenersFromMap.ts
│ │ │ ├── validateNoObjectAliases.test.ts
│ │ │ ├── validateNoObjectAliases.ts
│ │ │ ├── waitForInteraction.ts
│ │ │ ├── zColor.ts
│ │ │ ├── zValidTimezone.ts
│ │ │ └── zodDeepPartial.ts
│ │ ├── utils.test.ts
│ │ ├── utils.ts
│ │ └── validateActiveConfigs.ts
│ ├── start-dev.js
│ └── tsconfig.json
├── build-image.sh
├── config-checker/
│ ├── .gitignore
│ ├── index.html
│ ├── package.json
│ ├── public/
│ │ └── config-schema.json
│ ├── src/
│ │ ├── main.ts
│ │ ├── style.css
│ │ ├── vite-env.d.ts
│ │ └── yaml.worker.js
│ └── tsconfig.json
├── dashboard/
│ ├── .editorconfig
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── .prettierignore
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── public/
│ │ └── env.js
│ ├── serve.js
│ ├── src/
│ │ ├── api.ts
│ │ ├── auth.ts
│ │ ├── components/
│ │ │ ├── App.vue
│ │ │ ├── Expandable.vue
│ │ │ ├── PrivacyPolicy.vue
│ │ │ ├── Splash.vue
│ │ │ ├── Tab.vue
│ │ │ ├── Tabs.vue
│ │ │ ├── Title.vue
│ │ │ ├── dashboard/
│ │ │ │ ├── GuildAccess.vue
│ │ │ │ ├── GuildConfigEditor.vue
│ │ │ │ ├── GuildImportExport.vue
│ │ │ │ ├── GuildInfo.vue
│ │ │ │ ├── GuildList.vue
│ │ │ │ ├── Layout.vue
│ │ │ │ ├── PermissionTree.vue
│ │ │ │ └── permissionTreeUtils.ts
│ │ │ └── docs/
│ │ │ ├── ArgumentTypes.vue
│ │ │ ├── CodeBlock.vue
│ │ │ ├── ConfigurationFormat.vue
│ │ │ ├── Counters.vue
│ │ │ ├── DocsLayout.vue
│ │ │ ├── Introduction.vue
│ │ │ ├── MarkdownBlock.vue
│ │ │ ├── Moderation.vue
│ │ │ ├── Permissions.vue
│ │ │ ├── Plugin.vue
│ │ │ ├── PluginConfiguration.vue
│ │ │ └── WorkInProgress.vue
│ │ ├── directives/
│ │ │ └── trim-indents.ts
│ │ ├── index.ts
│ │ ├── routes.ts
│ │ ├── store/
│ │ │ ├── auth.ts
│ │ │ ├── docs.ts
│ │ │ ├── guilds.ts
│ │ │ ├── index.ts
│ │ │ ├── staff.ts
│ │ │ └── types.ts
│ │ ├── style/
│ │ │ ├── app.css
│ │ │ ├── base.css
│ │ │ ├── components.css
│ │ │ ├── content.css
│ │ │ ├── docs.css
│ │ │ ├── privacy-policy.css
│ │ │ ├── reset.css
│ │ │ └── splash.css
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── docker/
│ ├── development/
│ │ ├── devenv/
│ │ │ └── Dockerfile
│ │ └── nginx/
│ │ ├── Dockerfile
│ │ └── default.conf
│ └── production/
│ └── nginx/
│ ├── Dockerfile
│ └── default.conf
├── docker-compose.development.yml
├── docker-compose.lightweight.yml
├── docker-compose.standalone.yml
├── docs/
│ ├── DEVELOPMENT.md
│ ├── MANAGEMENT.md
│ ├── MIGRATE_DEV.md
│ ├── MIGRATE_PROD.md
│ └── PRODUCTION.md
├── package.json
├── pnpm-workspace.yaml
├── presetup-configurator/
│ ├── .gitignore
│ ├── .prettierignore
│ ├── package.json
│ ├── snowpack.config.js
│ ├── src/
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── Configurator.css
│ │ ├── Configurator.tsx
│ │ ├── Levels.tsx
│ │ ├── LogChannels.css
│ │ ├── LogChannels.tsx
│ │ ├── index.css
│ │ ├── index.html
│ │ └── index.tsx
│ └── tsconfig.json
├── shared/
│ ├── .gitignore
│ ├── package.json
│ ├── src/
│ │ ├── apiPermissions.test.ts
│ │ └── apiPermissions.ts
│ └── tsconfig.json
├── tsconfig.base.json
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .clabot
================================================
{
"contributors": [
"almeidx",
"axisiscool",
"BanTheNons",
"Benricheson101",
"brawaru",
"CleverSource",
"Dalkskkskk",
"DarkView",
"DenverCoder1",
"dexbiobot",
"greenbigfrog",
"hawkeye7662",
"iamshoXy",
"Jernik",
"k200-1",
"LilyBergonzat",
"martinbndr",
"metal0",
"Obliie",
"paolojpa",
"roflmaoqwerty",
"Rstar284",
"rubyowo",
"rukogit",
"Scraayp",
"seeyebe",
"TheKodeToad",
"thewilloftheshadow",
"usoka",
"vcokltfre",
"WeebHiroyuki",
"zayKenyon",
"Dragory",
"app/dependabot",
"dependabot[bot]"
],
"message": "Thank you for contributing to Zeppelin! We require contributors to sign our Contributor License Agreement (CLA). To let us review and merge your code, please visit https://github.com/ZeppelinBot/CLA to sign the CLA!"
}
================================================
FILE: .cursorignore
================================================
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.clinic
.clinic-bot
.clinic-api
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
*.env
.env
# windows folder options
desktop.ini
# PHPStorm
.idea/
# Misc
/convert.js
/startscript.js
.cache
npm-ls.txt
npm-audit.txt
.vscode/launch.json
# Debug files
*.debug.ts
*.debug.js
.vscode/
config-errors.txt
/config-schema.json
*.tsbuildinfo
# Legacy data folders
/docker/development/data
/docker/production/data
================================================
FILE: .devcontainer/devcontainer.json
================================================
{
"name": "Zeppelin Development",
"dockerComposeFile": "../docker-compose.development.yml",
"service": "devenv",
"remoteUser": "ubuntu",
"workspaceFolder": "/workspace/zeppelin",
"customizations": {
"vscode": {
"extensions": [
"Vue.volar"
]
}
}
}
================================================
FILE: .dockerignore
================================================
**/.git
**/.github
**/.idea
**/.devcontainer
/docker/development/data
/docker/production/data
**/node_modules
**/dist
**/.pnpm-store
**/.docker
**/*.log
**/npm-debug.log*
**/yarn-debug.log*
**/yarn-error.log*
**/.clinic
**/.clinic-bot
**/.clinic-api
# dotenv environment variables file
**/*.env
**/.env
# windows folder options
**/desktop.ini
# PHPStorm
**/.idea
# Misc
**/npm-ls.txt
**/npm-audit.txt
**/.cache
# Debug files
**/*.debug.ts
**/*.debug.js
/debug
**/.vscode
config-errors.txt
/config-schema.json
**/*.tsbuildinfo
================================================
FILE: .editorconfig
================================================
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
================================================
FILE: .eslintrc.js
================================================
module.exports = {
root: true,
env: {
node: true,
browser: true,
es6: true,
},
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
rules: {
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"no-async-promise-executor": 0,
"@typescript-eslint/no-empty-interface": 0,
"no-constant-condition": ["error", {
checkLoops: false,
}],
"prefer-const": ["error", {
destructuring: "all",
ignoreReadBeforeAssign: true,
}],
"@typescript-eslint/no-namespace": ["error", {
allowDeclarations: true,
}],
},
};
================================================
FILE: .gitattributes
================================================
package-lock.json binary
pnpm-lock.yaml binary
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: daily
groups:
non-major:
update-types:
- minor
- patch
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
github-actions:
patterns:
- "*"
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
================================================
FILE: .github/workflows/codequality.yml
================================================
name: Code quality checks
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903
with:
node-version: 24
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061
with:
version: 10.19.0
run_install: true
- run: |
pnpm run lint
pnpm run codestyle-check
================================================
FILE: .gitignore
================================================
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.clinic
.clinic-bot
.clinic-api
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
.pnpm-store
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
*.env
.env
# windows folder options
desktop.ini
# PHPStorm
.idea/
# Misc
/convert.js
/startscript.js
.cache
npm-ls.txt
npm-audit.txt
.vscode/launch.json
# Debug files
*.debug.ts
*.debug.js
.vscode/
config-errors.txt
/config-schema.json
*.tsbuildinfo
# Legacy data folders
/docker/development/data
/docker/production/data
================================================
FILE: .nvmrc
================================================
24
================================================
FILE: .prettierignore
================================================
.github
.idea
node_modules
/assets
/debug
================================================
FILE: .prettierrc
================================================
{
"printWidth": 120,
"trailingComma": "all"
}
================================================
FILE: AGENTS.md
================================================
The project is called Zeppelin. It's a Discord bot that uses Discord.js. The bot is built on the Vety framework (formerly called Knub).
This repository is a monorepository that contains these projects:
1. **Backend**: The shared codebase of the bot and API. Located in `backend`.
2. **Dashboard**: The web dashboard that contains the bot's management interface and documentation. Located in `dashboard`.
3. **Config checker**: A tool to check the configuration of the bot. Located in `config-checker`.
There is also a `shared` folder that contains shared code used by all projects, such as types and utilities.
# Backend
The backend codebase is located in the `backend` directory. It contains the main bot code, API code, and shared code used by both the bot and API.
Zeppelin's functionality is split into plugins, which are located in the `src/plugins` directory.
Each plugin has its own directory, with a `types.ts` for config types, `docs.ts` for a `ZeppelinPluginDocs` structure, and the plugin's main file.
Each plugin has an internal name, such as "common". In this example, the folder would be `src/plugins/Common` (note the capitalization). The plugin's main file would be `src/plugins/CommonPlugin.ts`.
There are two types of plugins: "guild plugins" and "global plugins". Guild plugins are loaded on a per-guild basis, while global plugins are loaded once for the entire bot.
Plugins can specify dependencies on other plugins and call their public methods. Likewise, plugins can specify public methods in the main file.
Available plugins are specified in `src/plugins/availablePlugins.ts`.
Zeppelin's data layer uses TypeORM. Entities are located in `src/data/entities`, while repositories are in `src/data`. If the repository name is prefixed with "Guild", it's a guild-specific repository. If it's prefixed with "User", it's a user-specific repository. If it has no prefix, it's a global repository.
Environment variables are parsed in `src/env.ts`.
================================================
FILE: DEVELOPMENT.md
================================================
Moved to [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)
================================================
FILE: Dockerfile
================================================
FROM node:24 AS build
ARG COMMIT_HASH
ARG BUILD_TIME
RUN mkdir /zeppelin
RUN chown node:node /zeppelin
# Install pnpm
RUN npm install -g pnpm@10.19.0
USER node
# Install dependencies before copying over any other files
COPY --chown=node:node package.json pnpm-workspace.yaml pnpm-lock.yaml /zeppelin
RUN mkdir /zeppelin/backend
COPY --chown=node:node backend/package.json /zeppelin/backend
RUN mkdir /zeppelin/shared
COPY --chown=node:node shared/package.json /zeppelin/shared
RUN mkdir /zeppelin/dashboard
COPY --chown=node:node dashboard/package.json /zeppelin/dashboard
WORKDIR /zeppelin
RUN CI=true pnpm install
COPY --chown=node:node . /zeppelin
# Build backend
WORKDIR /zeppelin/backend
RUN pnpm run build
# Build dashboard
WORKDIR /zeppelin/dashboard
RUN pnpm run build
# Only keep prod dependencies
WORKDIR /zeppelin
RUN CI=true pnpm install --prod
# Add version info
RUN echo "${COMMIT_HASH}" > /zeppelin/.commit-hash
RUN echo "${BUILD_TIME}" > /zeppelin/.build-time
# --- Main image ---
FROM node:24-alpine AS main
RUN npm install -g pnpm@10.19.0
USER node
COPY --from=build --chown=node:node /zeppelin /zeppelin
WORKDIR /zeppelin
================================================
FILE: LICENSE.md
================================================
# Elastic License 2.0 (ELv2)
## Elastic License
### Acceptance
By using the software, you agree to all of the terms and conditions below.
### Copyright License
The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations and conditions below.
### Limitations
You may not provide the software to third parties as a hosted or managed service, where the service provides users with access to any substantial set of the features or functionality of the software.
You may not move, change, disable, or circumvent the license key functionality in the software, and you may not remove or obscure any functionality in the software that is protected by the license key.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor’s trademarks is subject to applicable law.
### Patents
The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
### Notices
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms.
If you modify the software, you must include in any modified copies of the software prominent notices stating that you have modified the software.
### No Other Rights
These terms do not imply any licenses other than those expressly granted in these terms.
### Termination
If you use the software in violation of these terms, such use is not licensed, and your licenses will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your licenses will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your licenses to terminate automatically and permanently.
### No Liability
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
### Definitions
The **licensor** is the entity offering these terms, and the **software** is the software the licensor makes available under these terms, including any portion of it.
**you** refers to the individual or entity agreeing to these terms.
**your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
**your licenses** are all the licenses granted to you for the software under these terms.
**use** means anything you do with the software requiring one of your licenses.
**trademark** means trademarks, service marks, and similar rights.
================================================
FILE: MANAGEMENT.md
================================================
Moved to [docs/MANAGEMENT.md](docs/MANAGEMENT.md)
================================================
FILE: PRODUCTION.md
================================================
Moved to [docs/PRODUCTION.md](docs/PRODUCTION.md)
================================================
FILE: README.md
================================================

# Zeppelin
Zeppelin is a moderation bot for Discord, designed with large servers and reliability in mind.
**Main features include:**
- Extensive automoderator features (automod)
- Word filters, spam detection, etc.
- Detailed moderator action tracking and notes (cases)
- Customizable server logs
- Tags/custom commands
- Reaction roles
- Tons of utility commands, including a granular member search
- Full configuration via a web dashboard
- Override specific settings and permissions on e.g. a per-user, per-channel, or per-permission-level basis
- Bot-managed slowmodes
- Automatically switches between native slowmodes (for 6h or less) and bot-enforced (for longer slowmodes)
- Starboard
- And more!
See https://zeppelin.gg/ for more details.
## Usage documentation
For information on how to use the bot, see https://zeppelin.gg/docs
## Development
See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for instructions on running the development environment.
Once you have the environment up and running, see [docs/MANAGEMENT.md](docs/MANAGEMENT.md) for how to manage your bot.
## Production
See [docs/PRODUCTION.md](docs/PRODUCTION.md) for instructions on how to run the bot in production.
Once you have the environment up and running, see [docs/MANAGEMENT.md](docs/MANAGEMENT.md) for how to manage your bot.
================================================
FILE: assets/icons/LICENSE
================================================
# TWEMOJI
Copyright 2020 Twitter, Inc and other contributors
Code licensed under the MIT License: http://opensource.org/licenses/MIT
Graphics licensed under CC-BY 4.0: https://creativecommons.org/licenses/by/4.0/
================================================
FILE: backend/.gitignore
================================================
/.cache
/dist
/node_modules
================================================
FILE: backend/.prettierignore
================================================
/dist
================================================
FILE: backend/package.json
================================================
{
"name": "@zeppelinbot/backend",
"version": "0.0.1",
"description": "",
"private": true,
"type": "module",
"exports": {
"./*": "./dist/*"
},
"scripts": {
"watch": "tsc-watch --build --onSuccess \"node start-dev.js\"",
"watch-yaml-parse-test": "tsc-watch --build --onSuccess \"node dist/yamlParseTest.js\"",
"build": "tsc --build",
"typecheck": "tsc --noEmit",
"start-bot-dev": "node --enable-source-maps --stack-trace-limit=30 --trace-warnings --inspect=0.0.0.0:9229 dist/index.js",
"start-bot-prod": "node --enable-source-maps --stack-trace-limit=30 --trace-warnings dist/index.js",
"watch-bot": "tsc-watch --build --onSuccess \"pnpm run start-bot-dev\"",
"start-api-dev": "node --enable-source-maps --stack-trace-limit=30 --inspect=0.0.0.0:9239 dist/api/index.js",
"start-api-prod": "node --enable-source-maps --stack-trace-limit=30 dist/api/index.js",
"watch-api": "tsc-watch --build --onSuccess \"pnpm run start-api-dev\"",
"migrate": "pnpm exec typeorm migration:run -d dist/data/dataSource.js",
"migrate-prod": "pnpm run migrate",
"migrate-dev": "pnpm run build && pnpm run migrate",
"migrate-rollback": "pnpm exec typeorm migration:revert -d dist/data/dataSource.js",
"migrate-rollback-prod": "pnpm run migrate-rollback",
"migrate-rollback-dev": "pnpm run build && pnpm run migrate-rollback",
"validate-active-configs": "node --enable-source-maps dist/validateActiveConfigs.js > ../config-errors.txt",
"export-config-json-schema": "node --enable-source-maps dist/exportSchemas.js ../config-checker/public/config-schema.json",
"test": "pnpm run build && pnpm run run-tests",
"run-tests": "ava",
"test-watch": "tsc-watch --build --onSuccess \"pnpm exec ava\""
},
"dependencies": {
"@silvia-odwyer/photon-node": "^0.3.1",
"@zeppelinbot/shared": "workspace:*",
"bufferutil": "^4.0.3",
"cors": "^2.8.5",
"cross-env": "^7.0.3",
"deep-diff": "^1.0.2",
"discord.js": "*",
"emoji-regex": "^8.0.0",
"escape-string-regexp": "^1.0.5",
"express": "^4.20.0",
"fp-ts": "^2.0.1",
"humanize-duration": "^3.15.0",
"js-yaml": "^4.1.0",
"knub-command-manager": "^9.1.0",
"lodash-es": "^4.17.21",
"moment-timezone": "^0.5.21",
"multer": "^2.0.2",
"mysql2": "^3.9.8",
"parse-color": "^1.0.0",
"passport": "^0.6.0",
"passport-custom": "^1.0.5",
"passport-oauth2": "^1.6.1",
"pkg-up": "^3.1.0",
"redis": "^5.9.0",
"reflect-metadata": "^0.1.12",
"regexp-worker": "^1.1.0",
"safe-regex": "^2.0.2",
"seedrandom": "^3.0.1",
"strip-combining-marks": "^1.0.0",
"threads": "^1.7.0",
"tlds": "^1.221.1",
"tmp": "0.2.5",
"tsconfig-paths": "^3.9.0",
"twemoji": "^12.1.4",
"typeorm": "^0.3.27",
"utf-8-validate": "^5.0.5",
"uuid": "^9.0.0",
"vety": "1.0.0-rc2",
"zod": "^4.1.12"
},
"devDependencies": {
"@types/cors": "^2.8.5",
"@types/express": "^4.16.1",
"@types/js-yaml": "^3.12.1",
"@types/lodash-es": "^4.17.12",
"@types/multer": "^1.4.7",
"@types/passport": "^1.0.0",
"@types/passport-oauth2": "^1.4.8",
"@types/passport-strategy": "^0.2.35",
"@types/safe-regex": "^1.1.2",
"@types/tmp": "0.0.33",
"@types/twemoji": "^12.1.0",
"@types/uuid": "^9.0.2",
"ava": "^5.3.1",
"source-map-support": "^0.5.16"
},
"ava": {
"files": [
"dist/**/*.test.js"
],
"require": [
"./register-tsconfig-paths.js"
]
}
}
================================================
FILE: backend/register-tsconfig-paths.js
================================================
/**
* See:
* https://github.com/dividab/tsconfig-paths
* https://github.com/TypeStrong/ts-node/issues/138
* https://github.com/TypeStrong/ts-node/issues/138#issuecomment-519602402
* https://github.com/TypeStrong/ts-node/pull/254
*/
const path = require("path");
const tsconfig = require("./tsconfig.json");
const tsconfigPaths = require("tsconfig-paths");
// E.g. ./dist/backend
const baseUrl = path.resolve(tsconfig.compilerOptions.outDir, path.basename(__dirname));
tsconfigPaths.register({
baseUrl,
paths: tsconfig.compilerOptions.paths || [],
});
================================================
FILE: backend/src/Blocker.ts
================================================
export type Block = {
count: number;
unblock: () => void;
getPromise: () => Promise<void>;
};
export class Blocker {
#blocks: Map<string, Block> = new Map();
block(key: string): void {
if (!this.#blocks.has(key)) {
const promise = new Promise<void>((resolve) => {
this.#blocks.set(key, {
count: 0, // Incremented to 1 further below
unblock() {
this.count--;
if (this.count === 0) {
resolve();
}
},
getPromise: () => promise, // :d
});
});
}
this.#blocks.get(key)!.count++;
}
unblock(key: string): void {
if (this.#blocks.has(key)) {
this.#blocks.get(key)!.unblock();
}
}
async waitToBeUnblocked(key: string): Promise<void> {
if (!this.#blocks.has(key)) {
return;
}
await this.#blocks.get(key)!.getPromise();
}
}
================================================
FILE: backend/src/DiscordJSError.ts
================================================
import util from "util";
export class DiscordJSError extends Error {
code: number | string | undefined;
shardId: number;
constructor(message: string, code: number | string | undefined, shardId: number) {
super(message);
this.code = code;
this.shardId = shardId;
}
[util.inspect.custom]() {
return `[DISCORDJS] [ERROR CODE ${this.code ?? "?"}] [SHARD ${this.shardId}] ${this.message}`;
}
}
================================================
FILE: backend/src/Queue.ts
================================================
import { SECONDS } from "./utils.js";
type InternalQueueFn = () => Promise<void>;
type AnyFn = (...args: any[]) => any;
const DEFAULT_TIMEOUT = 10 * SECONDS;
export class Queue<TQueueFunction extends AnyFn = AnyFn> {
protected running = false;
protected queue: InternalQueueFn[] = [];
protected _timeout: number;
constructor(timeout = DEFAULT_TIMEOUT) {
this._timeout = timeout;
}
get timeout(): number {
return this._timeout;
}
/**
* The number of operations that are currently queued up or running.
* I.e. backlog (queue) + current running process, if any.
*
* If this is 0, queueing a function will run it as soon as possible.
*/
get length(): number {
return this.queue.length + (this.running ? 1 : 0);
}
public add(fn: TQueueFunction): Promise<any> {
const promise = new Promise<any>((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await fn();
resolve(result);
} catch (err) {
reject(err);
}
});
if (!this.running) this.next();
});
return promise;
}
public next(): void {
this.running = true;
if (this.queue.length === 0) {
this.running = false;
return;
}
const fn = this.queue.shift()!;
new Promise((resolve) => {
// Either fn() completes or the timeout is reached
void fn().then(resolve);
setTimeout(resolve, this._timeout);
}).then(() => this.next());
}
public clear() {
this.queue.splice(0, this.queue.length);
}
}
================================================
FILE: backend/src/QueuedEventEmitter.ts
================================================
import { Queue } from "./Queue.js";
type Listener = (...args: any[]) => void;
export class QueuedEventEmitter {
protected listeners: Map<string, Listener[]>;
protected queue: Queue;
constructor() {
this.listeners = new Map();
this.queue = new Queue();
}
on(eventName: string, listener: Listener): Listener {
if (!this.listeners.has(eventName)) {
this.listeners.set(eventName, []);
}
this.listeners.get(eventName)!.push(listener);
return listener;
}
off(eventName: string, listener: Listener) {
if (!this.listeners.has(eventName)) {
return;
}
const listeners = this.listeners.get(eventName)!;
listeners.splice(listeners.indexOf(listener), 1);
}
once(eventName: string, listener: Listener): Listener {
const handler = this.on(eventName, (...args) => {
const result = listener(...args);
this.off(eventName, handler);
return result;
});
return handler;
}
emit(eventName: string, args: any[] = []): Promise<void> {
const listeners = [...(this.listeners.get(eventName) || []), ...(this.listeners.get("*") || [])];
let promise: Promise<any> = Promise.resolve();
listeners.forEach((listener) => {
promise = this.queue.add(listener.bind(null, ...args));
});
return promise;
}
}
================================================
FILE: backend/src/RecoverablePluginError.ts
================================================
import { Guild } from "discord.js";
export enum ERRORS {
NO_MUTE_ROLE_IN_CONFIG = 1,
UNKNOWN_NOTE_CASE,
INVALID_EMOJI,
NO_USER_NOTIFICATION_CHANNEL,
INVALID_USER_NOTIFICATION_CHANNEL,
INVALID_USER,
INVALID_MUTE_ROLE_ID,
MUTE_ROLE_ABOVE_ZEP,
USER_ABOVE_ZEP,
USER_NOT_MODERATABLE,
TEMPLATE_PARSE_ERROR,
}
export const RECOVERABLE_PLUGIN_ERROR_MESSAGES = {
[ERRORS.NO_MUTE_ROLE_IN_CONFIG]: "No mute role specified in config",
[ERRORS.UNKNOWN_NOTE_CASE]: "Tried to add a note to an unknown case",
[ERRORS.INVALID_EMOJI]: "Invalid emoji",
[ERRORS.NO_USER_NOTIFICATION_CHANNEL]: "No user notify channel specified",
[ERRORS.INVALID_USER_NOTIFICATION_CHANNEL]: "Invalid user notify channel specified",
[ERRORS.INVALID_USER]: "Invalid user",
[ERRORS.INVALID_MUTE_ROLE_ID]: "Specified mute role is not valid",
[ERRORS.MUTE_ROLE_ABOVE_ZEP]: "Specified mute role is above Zeppelin in the role hierarchy",
[ERRORS.USER_ABOVE_ZEP]: "Cannot mute user, specified user is above Zeppelin in the role hierarchy",
[ERRORS.USER_NOT_MODERATABLE]: "Cannot mute user, specified user is not moderatable",
[ERRORS.TEMPLATE_PARSE_ERROR]: "Template parse error",
};
export class RecoverablePluginError extends Error {
public readonly code: ERRORS;
public readonly guild?: Guild;
constructor(code: ERRORS, guild?: Guild) {
super(RECOVERABLE_PLUGIN_ERROR_MESSAGES[code]);
this.guild = guild;
this.code = code;
}
}
================================================
FILE: backend/src/RegExpRunner.ts
================================================
import { CooldownManager } from "vety";
import { EventEmitter } from "node:events";
import { RegExpWorker, TimeoutError } from "regexp-worker";
import { MINUTES, SECONDS } from "./utils.js";
import Timeout = NodeJS.Timeout;
const isTimeoutError = (a): a is TimeoutError => {
return a.message != null && a.elapsedTimeMs != null;
};
export class RegExpTimeoutError extends Error {
constructor(
message: string,
public elapsedTimeMs: number,
) {
super(message);
}
}
export function allowTimeout(err: RegExpTimeoutError | Error) {
if (err instanceof RegExpTimeoutError) {
return null;
}
throw err;
}
// Regex timeout starts at a higher value while the bot loads initially, and gets lowered afterwards
const INITIAL_REGEX_TIMEOUT = 5 * SECONDS;
const INITIAL_REGEX_TIMEOUT_DURATION = 30 * SECONDS;
const FINAL_REGEX_TIMEOUT = 5 * SECONDS;
const regexTimeoutUpgradePromise = new Promise((resolve) => setTimeout(resolve, INITIAL_REGEX_TIMEOUT_DURATION));
let newWorkerTimeout = INITIAL_REGEX_TIMEOUT;
regexTimeoutUpgradePromise.then(() => (newWorkerTimeout = FINAL_REGEX_TIMEOUT));
const REGEX_FAIL_TO_COOLDOWN_COUNT = 5; // If a regex times out this many times...
const REGEX_FAIL_DECAY_TIME = 2 * MINUTES; // ...in this interval...
const REGEX_FAIL_COOLDOWN = 2 * MINUTES + 30 * SECONDS; // ...it goes on cooldown for this long
export interface RegExpRunner {
on(event: "timeout", listener: (regexSource: string, timeoutMs: number) => void);
on(event: "repeatedTimeout", listener: (regexSource: string, timeoutMs: number, failTimes: number) => void);
}
/**
* Leverages RegExpWorker to run regular expressions in worker threads with a timeout.
* Repeatedly failing regexes are put on a cooldown where requests to execute them are ignored.
*/
export class RegExpRunner extends EventEmitter {
private _worker: RegExpWorker | null;
private readonly _failedTimesInterval: Timeout;
private cooldown: CooldownManager;
private failedTimes: Map<string, number>;
constructor() {
super();
this.cooldown = new CooldownManager();
this.failedTimes = new Map();
this._failedTimesInterval = setInterval(() => {
for (const [pattern, times] of this.failedTimes.entries()) {
this.failedTimes.set(pattern, times - 1);
}
}, REGEX_FAIL_DECAY_TIME);
}
private get worker(): RegExpWorker {
if (!this._worker) {
this._worker = new RegExpWorker(newWorkerTimeout);
if (newWorkerTimeout !== FINAL_REGEX_TIMEOUT) {
regexTimeoutUpgradePromise.then(() => {
if (!this._worker) return;
this._worker.timeout = FINAL_REGEX_TIMEOUT;
});
}
}
return this._worker;
}
public async exec(regex: RegExp, str: string): Promise<null | RegExpExecArray[]> {
if (this.cooldown.isOnCooldown(regex.source)) {
return null;
}
try {
const result = await this.worker.execRegExp(regex, str);
return result.matches.length || regex.global ? result.matches : null;
} catch (e) {
if (isTimeoutError(e)) {
if (this.failedTimes.has(regex.source)) {
// Regex has failed before, increment fail counter
this.failedTimes.set(regex.source, this.failedTimes.get(regex.source)! + 1);
} else {
// This is the first time this regex failed, init fail counter
this.failedTimes.set(regex.source, 1);
}
if (this.failedTimes.has(regex.source) && this.failedTimes.get(regex.source)! >= REGEX_FAIL_TO_COOLDOWN_COUNT) {
// Regex has failed too many times, set it on cooldown
this.cooldown.setCooldown(regex.source, REGEX_FAIL_COOLDOWN);
this.failedTimes.delete(regex.source);
this.emit("repeatedTimeout", regex.source, this.worker.timeout, REGEX_FAIL_TO_COOLDOWN_COUNT);
}
this.emit("timeout", regex.source, this.worker.timeout);
throw new RegExpTimeoutError(e.message, e.elapsedTimeMs);
}
throw e;
}
}
public async dispose() {
await this.worker.dispose();
this._worker = null;
clearInterval(this._failedTimesInterval);
}
}
================================================
FILE: backend/src/SimpleCache.ts
================================================
import Timeout = NodeJS.Timeout;
const CLEAN_INTERVAL = 1000;
export class SimpleCache<T = any> {
protected readonly retentionTime: number;
protected readonly maxItems: number;
protected cleanTimeout: Timeout;
protected unloaded: boolean;
protected store: Map<string, { remove_at: number; value: T }>;
constructor(retentionTime: number, maxItems?: number) {
this.retentionTime = retentionTime;
if (maxItems) {
this.maxItems = maxItems;
}
this.store = new Map();
}
unload() {
this.unloaded = true;
clearTimeout(this.cleanTimeout);
}
cleanLoop() {
const now = Date.now();
for (const [key, info] of this.store.entries()) {
if (now >= info.remove_at) {
this.store.delete(key);
}
}
if (!this.unloaded) {
this.cleanTimeout = setTimeout(() => this.cleanLoop(), CLEAN_INTERVAL);
}
}
set(key: string, value: T) {
this.store.set(key, {
remove_at: Date.now() + this.retentionTime,
value,
});
if (this.maxItems && this.store.size > this.maxItems) {
const keyToDelete = this.store.keys().next().value!;
this.store.delete(keyToDelete);
}
}
get(key: string): T | null {
const info = this.store.get(key);
if (!info) return null;
return info.value;
}
has(key: string) {
return this.store.has(key);
}
delete(key: string) {
this.store.delete(key);
}
clear() {
this.store.clear();
}
}
================================================
FILE: backend/src/SimpleError.ts
================================================
import util from "util";
export class SimpleError extends Error {
public message: string;
constructor(message: string) {
super(message);
}
[util.inspect.custom]() {
return `Error: ${this.message}`;
}
}
================================================
FILE: backend/src/api/archives.ts
================================================
import express, { Request, Response } from "express";
import moment from "moment-timezone";
import { GuildArchives } from "../data/GuildArchives.js";
import { notFound } from "./responses.js";
export function initArchives(router: express.Router) {
const archives = new GuildArchives(null);
// Legacy redirect
router.get("/spam-logs/:id", (req: Request, res: Response) => {
res.redirect("/archives/" + req.params.id);
});
router.get("/archives/:id", async (req: Request, res: Response) => {
const archive = await archives.find(req.params.id);
if (!archive) return notFound(res);
let body = archive.body;
// Add some metadata at the end of the log file (but only if it doesn't already have it directly in the body)
// TODO: Use server timezone / date formats
if (archive.body.indexOf("Log file generated on") === -1) {
const createdAt = moment.utc(archive.created_at).format("YYYY-MM-DD [at] HH:mm:ss [(+00:00)]");
body += `\n\nLog file generated on ${createdAt}`;
if (archive.expires_at !== null) {
const expiresAt = moment.utc(archive.expires_at).format("YYYY-MM-DD [at] HH:mm:ss [(+00:00)]");
body += `\nExpires at ${expiresAt}`;
}
}
res.setHeader("Content-Type", "text/plain; charset=UTF-8");
res.setHeader("X-Content-Type-Options", "nosniff");
res.end(body);
});
}
================================================
FILE: backend/src/api/auth.ts
================================================
import express, { Request, Response } from "express";
import https from "https";
import { pick } from "lodash-es";
import passport from "passport";
import { Strategy as CustomStrategy } from "passport-custom";
import OAuth2Strategy from "passport-oauth2";
import { ApiLogins } from "../data/ApiLogins.js";
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments.js";
import { ApiUserInfo } from "../data/ApiUserInfo.js";
import { ApiUserInfoData } from "../data/entities/ApiUserInfo.js";
import { env } from "../env.js";
import { ok } from "./responses.js";
interface IPassportApiUser {
apiKey: string;
userId: string;
}
declare global {
namespace Express {
interface User extends IPassportApiUser {}
}
}
const DISCORD_API_URL = "https://discord.com/api";
function simpleDiscordAPIRequest(bearerToken, path): Promise<any> {
return new Promise((resolve, reject) => {
const request = https.get(
`${DISCORD_API_URL}/${path}`,
{
headers: {
Authorization: `Bearer ${bearerToken}`,
},
},
(res) => {
if (res.statusCode !== 200) {
reject(new Error(`Discord API error ${res.statusCode}`));
return;
}
let rawData = "";
res.on("data", (data) => (rawData += data));
res.on("end", () => {
resolve(JSON.parse(rawData));
});
},
);
request.on("error", (err) => reject(err));
});
}
export function initAuth(router: express.Router) {
router.use(passport.initialize());
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user as IPassportApiUser));
const apiLogins = new ApiLogins();
const apiUserInfo = new ApiUserInfo();
const apiPermissionAssignments = new ApiPermissionAssignments();
// Initialize API tokens
passport.use(
"api-token",
new CustomStrategy(async (req, cb) => {
const apiKey = req.header("X-Api-Key") || req.body?.["X-Api-Key"];
if (!apiKey) return cb("API key missing");
const userId = await apiLogins.getUserIdByApiKey(apiKey);
if (userId) {
void apiLogins.refreshApiKeyExpiryTime(apiKey); // Refresh expiry time in the background
return cb(null, { apiKey, userId });
}
cb("API key not found");
}),
);
// Initialize OAuth2 for Discord login
// When the user logs in through OAuth2, we create them a "login" (= api token) and update their user info in the DB
passport.use(
new OAuth2Strategy(
{
authorizationURL: "https://discord.com/api/oauth2/authorize",
tokenURL: "https://discord.com/api/oauth2/token",
clientID: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
callbackURL: `${env.API_URL}/auth/oauth-callback`,
scope: ["identify"],
},
async (accessToken, refreshToken, profile, cb) => {
const user = await simpleDiscordAPIRequest(accessToken, "users/@me");
// Make sure the user is able to access at least 1 guild
const permissions = await apiPermissionAssignments.getByUserId(user.id);
if (permissions.length === 0) {
cb(null, {});
return;
}
// Generate API key
const apiKey = await apiLogins.addLogin(user.id);
const userData = pick(user, ["username", "discriminator", "avatar"]) as ApiUserInfoData;
await apiUserInfo.update(user.id, userData);
// TODO: Revoke access token, we don't need it anymore
cb(null, { apiKey });
},
),
);
router.get("/auth/login", passport.authenticate("oauth2"));
router.get(
"/auth/oauth-callback",
passport.authenticate("oauth2", { failureRedirect: "/", session: false }),
(req: Request, res: Response) => {
if (req.user && req.user.apiKey) {
res.redirect(`${env.DASHBOARD_URL}/login-callback/?apiKey=${req.user.apiKey}`);
} else {
res.redirect(`${env.DASHBOARD_URL}/login-callback/?error=noAccess`);
}
},
);
router.post("/auth/validate-key", async (req: Request, res: Response) => {
const key = req.body.key;
if (!key) {
return res.status(400).json({ error: "No key supplied" });
}
const userId = await apiLogins.getUserIdByApiKey(key);
if (!userId) {
return res.json({ valid: false });
}
res.json({ valid: true, userId });
});
router.post("/auth/logout", ...apiTokenAuthHandlers(), async (req: Request, res: Response) => {
await apiLogins.expireApiKey(req.user!.apiKey);
return ok(res);
});
// API route to refresh the given API token's expiry time
// The actual refreshing happens in the api-token passport strategy above, so we just return 200 OK here
router.post("/auth/refresh", ...apiTokenAuthHandlers(), (req, res) => {
return ok(res);
});
}
export function apiTokenAuthHandlers() {
return [
passport.authenticate("api-token", { failWithError: true, session: false }),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(err, req: Request, res: Response, next) => {
return res.status(401).json({ error: err.message });
},
];
}
================================================
FILE: backend/src/api/docs.ts
================================================
import express from "express";
import { z } from "zod";
import { $ZodPipeDef } from "zod/v4/core";
import { availableGuildPlugins } from "../plugins/availablePlugins.js";
import { ZeppelinGuildPluginInfo } from "../types.js";
import { indentLines } from "../utils.js";
import { notFound } from "./responses.js";
function isZodObject(schema: z.ZodType): schema is z.ZodObject<any> {
return schema.def.type === "object";
}
function isZodRecord(schema: z.ZodType): schema is z.ZodRecord<any> {
return schema.def.type === "record";
}
function isZodOptional(schema: z.ZodType): schema is z.ZodOptional<any> {
return schema.def.type === "optional";
}
function isZodArray(schema: z.ZodType): schema is z.ZodArray<any> {
return schema.def.type === "array";
}
function isZodUnion(schema: z.ZodType): schema is z.ZodUnion<any> {
return schema.def.type === "union";
}
function isZodNullable(schema: z.ZodType): schema is z.ZodNullable<any> {
return schema.def.type === "nullable";
}
function isZodDefault(schema: z.ZodType): schema is z.ZodDefault<any> {
return schema.def.type === "default";
}
function isZodLiteral(schema: z.ZodType): schema is z.ZodLiteral<any> {
return schema.def.type === "literal";
}
function isZodIntersection(schema: z.ZodType): schema is z.ZodIntersection<any, any> {
return schema.def.type === "intersection";
}
function formatZodConfigSchema(schema: z.ZodType) {
if (isZodObject(schema)) {
return (
`{\n` +
Object.entries(schema.def.shape)
.map(([k, value]) => indentLines(`${k}: ${formatZodConfigSchema(value as z.ZodType)}`, 2))
.join("\n") +
"\n}"
);
}
if (isZodRecord(schema)) {
return "{\n" + indentLines(`[string]: ${formatZodConfigSchema(schema.valueType as z.ZodType)}`, 2) + "\n}";
}
if (isZodOptional(schema)) {
return `Optional<${formatZodConfigSchema(schema.def.innerType)}>`;
}
if (isZodArray(schema)) {
return `Array<${formatZodConfigSchema(schema.def.element)}>`;
}
if (isZodUnion(schema)) {
return schema.def.options.map((t) => formatZodConfigSchema(t)).join(" | ");
}
if (isZodNullable(schema)) {
return `Nullable<${formatZodConfigSchema(schema.def.innerType)}>`;
}
if (isZodDefault(schema)) {
return formatZodConfigSchema(schema.def.innerType);
}
if (isZodLiteral(schema)) {
return schema.def.values;
}
if (isZodIntersection(schema)) {
return [
formatZodConfigSchema(schema.def.left as z.ZodType),
formatZodConfigSchema(schema.def.right as z.ZodType),
].join(" & ");
}
if (schema.def.type === "string") {
return "string";
}
if (schema.def.type === "number") {
return "number";
}
if (schema.def.type === "boolean") {
return "boolean";
}
if (schema.def.type === "never") {
return "never";
}
if (schema.def.type === "pipe") {
return formatZodConfigSchema((schema.def as $ZodPipeDef).in as z.ZodType);
}
return "unknown";
}
const availableGuildPluginsByName = availableGuildPlugins.reduce<Record<string, ZeppelinGuildPluginInfo>>(
(map, obj) => {
map[obj.plugin.name] = obj;
return map;
},
{},
);
export function initDocs(router: express.Router) {
const docsPlugins = availableGuildPlugins.filter((obj) => obj.docs.type !== "internal");
router.get("/docs/plugins", (req: express.Request, res: express.Response) => {
res.json(
docsPlugins.map((obj) => ({
name: obj.plugin.name,
info: {
prettyName: obj.docs.prettyName,
type: obj.docs.type,
},
})),
);
});
router.get("/docs/plugins/:pluginName", (req: express.Request, res: express.Response) => {
const pluginInfo = availableGuildPluginsByName[req.params.pluginName];
if (!pluginInfo) {
return notFound(res);
}
const { configSchema, ...info } = pluginInfo.docs;
const formattedConfigSchema = formatZodConfigSchema(configSchema);
const messageCommands = (pluginInfo.plugin.messageCommands || []).map((cmd) => ({
trigger: cmd.trigger,
permission: cmd.permission,
signature: cmd.signature,
description: cmd.description,
usage: cmd.usage,
config: cmd.config,
}));
const defaultOptions = pluginInfo.docs.configSchema.safeParse({}).data ?? {};
res.json({
name: pluginInfo.plugin.name,
info,
configSchema: formattedConfigSchema,
defaultOptions,
messageCommands,
});
});
}
================================================
FILE: backend/src/api/guilds/importExport.ts
================================================
import { ApiPermissions } from "@zeppelinbot/shared/apiPermissions.js";
import express, { Request, Response } from "express";
import moment from "moment-timezone";
import { z } from "zod";
import { GuildCases } from "../../data/GuildCases.js";
import { Case } from "../../data/entities/Case.js";
import { MINUTES } from "../../utils.js";
import { requireGuildPermission } from "../permissions.js";
import { rateLimit } from "../rateLimits.js";
import { clientError, ok } from "../responses.js";
const caseHandlingModeSchema = z.union([
z.literal("replace"),
z.literal("bumpExistingCases"),
z.literal("bumpImportedCases"),
]);
type CaseHandlingMode = z.infer<typeof caseHandlingModeSchema>;
const caseNoteData = z.object({
mod_id: z.string(),
mod_name: z.string(),
body: z.string(),
created_at: z.string(),
});
const caseData = z.object({
case_number: z.number(),
user_id: z.string(),
user_name: z.string(),
mod_id: z.nullable(z.string()),
mod_name: z.nullable(z.string()),
type: z.number(),
created_at: z.string(),
is_hidden: z.boolean(),
pp_id: z.nullable(z.string()),
pp_name: z.nullable(z.string()),
log_message_id: z.string().optional(),
notes: z.array(caseNoteData),
});
const importExportData = z.object({
cases: z.array(caseData),
});
type TImportExportData = z.infer<typeof importExportData>;
export function initGuildsImportExportAPI(guildRouter: express.Router) {
const importExportRouter = express.Router();
importExportRouter.get(
"/:guildId/pre-import",
requireGuildPermission(ApiPermissions.ManageAccess),
async (req: Request) => {
const guildCases = GuildCases.getGuildInstance(req.params.guildId);
const minNum = await guildCases.getMinCaseNumber();
const maxNum = await guildCases.getMaxCaseNumber();
return {
minCaseNumber: minNum,
maxCaseNumber: maxNum,
};
},
);
importExportRouter.post(
"/:guildId/import",
requireGuildPermission(ApiPermissions.ManageAccess),
rateLimit(
(req) => `import-${req.params.guildId}`,
5 * MINUTES,
"A single server can only import data once every 5 minutes",
),
async (req: Request, res: Response) => {
let data: TImportExportData;
try {
data = importExportData.parse(req.body.data);
} catch (err) {
const prettyMessage = `${err.issues[0].code}: expected ${err.issues[0].expected}, received ${
err.issues[0].received
} at /${err.issues[0].path.join("/")}`;
return clientError(res, `Invalid import data format: ${prettyMessage}`);
return;
}
let caseHandlingMode: CaseHandlingMode;
try {
caseHandlingMode = caseHandlingModeSchema.parse(req.body.caseHandlingMode);
} catch (err) {
return clientError(res, "Invalid case handling mode");
return;
}
const seenCaseNumbers = new Set();
for (const theCase of data.cases) {
if (seenCaseNumbers.has(theCase.case_number)) {
return clientError(res, `Duplicate case number: ${theCase.case_number}`);
}
seenCaseNumbers.add(theCase.case_number);
}
const guildCases = GuildCases.getGuildInstance(req.params.guildId);
// Prepare cases
if (caseHandlingMode === "replace") {
// Replace existing cases
await guildCases.deleteAllCases();
} else if (caseHandlingMode === "bumpExistingCases") {
// Bump existing numbers
const maxNumberInData = data.cases.reduce((max, theCase) => Math.max(max, theCase.case_number), 0);
await guildCases.bumpCaseNumbers(maxNumberInData);
} else if (caseHandlingMode === "bumpImportedCases") {
const maxExistingNumber = await guildCases.getMaxCaseNumber();
for (const theCase of data.cases) {
theCase.case_number += maxExistingNumber;
}
}
// Import cases
for (const theCase of data.cases) {
const insertData: any = {
...theCase,
is_hidden: theCase.is_hidden ? 1 : 0,
guild_id: req.params.guildId,
notes: undefined,
};
const caseInsertData = await guildCases.createInternal(insertData);
for (const note of theCase.notes) {
await guildCases.createNote(caseInsertData.identifiers[0].id, note);
}
}
ok(res);
},
);
const exportBatchSize = 500;
importExportRouter.post(
"/:guildId/export",
requireGuildPermission(ApiPermissions.ManageAccess),
rateLimit(
(req) => `export-${req.params.guildId}`,
5 * MINUTES,
"A single server can only export data once every 5 minutes",
),
async (req: Request, res: Response) => {
const guildCases = GuildCases.getGuildInstance(req.params.guildId);
const data: TImportExportData = {
cases: [],
};
let n = 0;
let cases: Case[];
do {
cases = await guildCases.getExportCases(n, exportBatchSize);
n += cases.length;
for (const theCase of cases) {
data.cases.push({
case_number: theCase.case_number,
user_id: theCase.user_id,
user_name: theCase.user_name,
mod_id: theCase.mod_id,
mod_name: theCase.mod_name,
type: theCase.type,
created_at: theCase.created_at,
is_hidden: theCase.is_hidden,
pp_id: theCase.pp_id,
pp_name: theCase.pp_name,
log_message_id: theCase.log_message_id ?? undefined,
notes: theCase.notes.map((note) => ({
mod_id: note.mod_id,
mod_name: note.mod_name,
body: note.body,
created_at: note.created_at,
})),
});
}
} while (cases.length === exportBatchSize);
const filename = `export_${req.params.guildId}_${moment().format("YYYY-MM-DD_HH-mm-ss")}.json`;
const serialized = JSON.stringify(data, null, 2);
res.setHeader("Content-Disposition", `attachment; filename=${filename}`);
res.setHeader("Content-Type", "application/octet-stream");
res.setHeader("Content-Length", serialized.length);
res.send(serialized);
},
);
guildRouter.use("/", importExportRouter);
}
================================================
FILE: backend/src/api/guilds/index.ts
================================================
import express from "express";
import { apiTokenAuthHandlers } from "../auth.js";
import { initGuildsImportExportAPI } from "./importExport.js";
import { initGuildsMiscAPI } from "./misc.js";
export function initGuildsAPI(router: express.Router) {
const guildRouter = express.Router();
guildRouter.use(...apiTokenAuthHandlers());
initGuildsMiscAPI(guildRouter);
initGuildsImportExportAPI(guildRouter);
router.use("/guilds", guildRouter);
}
================================================
FILE: backend/src/api/guilds/misc.ts
================================================
import { ApiPermissions } from "@zeppelinbot/shared/apiPermissions.js";
import express, { Request, Response } from "express";
import { YAMLException } from "js-yaml";
import moment from "moment-timezone";
import { Queue } from "../../Queue.js";
import { validateGuildConfig } from "../../configValidator.js";
import { AllowedGuilds } from "../../data/AllowedGuilds.js";
import { ApiAuditLog } from "../../data/ApiAuditLog.js";
import { ApiPermissionAssignments, ApiPermissionTypes } from "../../data/ApiPermissionAssignments.js";
import { Configs } from "../../data/Configs.js";
import { AuditLogEventTypes } from "../../data/apiAuditLogTypes.js";
import { isSnowflake } from "../../utils.js";
import { loadYamlSafely } from "../../utils/loadYamlSafely.js";
import { ObjectAliasError } from "../../utils/validateNoObjectAliases.js";
import { hasGuildPermission, requireGuildPermission } from "../permissions.js";
import { clientError, ok, serverError, unauthorized } from "../responses.js";
const apiPermissionAssignments = new ApiPermissionAssignments();
const auditLog = new ApiAuditLog();
export function initGuildsMiscAPI(router: express.Router) {
const allowedGuilds = new AllowedGuilds();
const configs = new Configs();
const miscRouter = express.Router();
miscRouter.get("/available", async (req: Request, res: Response) => {
const guilds = await allowedGuilds.getForApiUser(req.user!.userId);
res.json(guilds);
});
miscRouter.get(
"/my-permissions", // a
async (req: Request, res: Response) => {
const permissions = await apiPermissionAssignments.getByUserId(req.user!.userId);
res.json(permissions);
},
);
miscRouter.get("/:guildId", async (req: Request, res: Response) => {
if (!(await hasGuildPermission(req.user!.userId, req.params.guildId, ApiPermissions.ViewGuild))) {
return unauthorized(res);
}
const guild = await allowedGuilds.find(req.params.guildId);
res.json(guild);
});
miscRouter.post("/:guildId/check-permission", async (req: Request, res: Response) => {
const permission = req.body.permission;
const hasPermission = await hasGuildPermission(req.user!.userId, req.params.guildId, permission);
res.json({ result: hasPermission });
});
miscRouter.get(
"/:guildId/config",
requireGuildPermission(ApiPermissions.ReadConfig),
async (req: Request, res: Response) => {
const config = await configs.getActiveByKey(`guild-${req.params.guildId}`);
res.json({ config: config ? config.config : "" });
},
);
miscRouter.post("/:guildId/config", requireGuildPermission(ApiPermissions.EditConfig), async (req, res) => {
let config = req.body.config;
if (config == null) return clientError(res, "No config supplied");
config = config.trim() + "\n"; // Normalize start/end whitespace in the config
const currentConfig = await configs.getActiveByKey(`guild-${req.params.guildId}`);
if (currentConfig && config === currentConfig.config) {
return ok(res);
}
// Validate config
let parsedConfig;
try {
parsedConfig = loadYamlSafely(config);
} catch (e) {
if (e instanceof YAMLException) {
return res.status(400).json({ errors: [e.message] });
}
if (e instanceof ObjectAliasError) {
return res.status(400).json({ errors: [e.message] });
}
// tslint:disable-next-line:no-console
console.error("Error when loading YAML: " + e.message);
return serverError(res, "Server error");
}
if (parsedConfig == null) {
parsedConfig = {};
}
const error = await validateGuildConfig(parsedConfig);
if (error) {
return res.status(422).json({ errors: [error] });
}
await configs.saveNewRevision(`guild-${req.params.guildId}`, config, req.user!.userId);
ok(res);
});
miscRouter.get(
"/:guildId/permissions",
requireGuildPermission(ApiPermissions.ManageAccess),
async (req: Request, res: Response) => {
const permissions = await apiPermissionAssignments.getByGuildId(req.params.guildId);
res.json(permissions);
},
);
const permissionManagementQueue = new Queue();
miscRouter.post(
"/:guildId/set-target-permissions",
requireGuildPermission(ApiPermissions.ManageAccess),
async (req: Request, res: Response) => {
await permissionManagementQueue.add(async () => {
const { type, targetId, permissions, expiresAt } = req.body;
if (type !== ApiPermissionTypes.User) {
return clientError(res, "Invalid type");
}
if (!isSnowflake(targetId)) {
return clientError(res, "Invalid targetId");
}
const validPermissions = new Set(Object.values(ApiPermissions));
validPermissions.delete(ApiPermissions.Owner);
if (!Array.isArray(permissions) || permissions.some((p) => !validPermissions.has(p))) {
return clientError(res, "Invalid permissions");
}
if (expiresAt != null && !moment.utc(expiresAt).isValid()) {
return clientError(res, "Invalid expiresAt");
}
const existingAssignment = await apiPermissionAssignments.getByGuildAndUserId(req.params.guildId, targetId);
if (existingAssignment && existingAssignment.permissions.includes(ApiPermissions.Owner)) {
return clientError(res, "Can't change owner permissions");
}
if (permissions.length === 0) {
await apiPermissionAssignments.removeUser(req.params.guildId, targetId);
await auditLog.addEntry(req.params.guildId, req.user!.userId, AuditLogEventTypes.REMOVE_API_PERMISSION, {
type: ApiPermissionTypes.User,
target_id: targetId,
});
} else {
const existing = await apiPermissionAssignments.getByGuildAndUserId(req.params.guildId, targetId);
if (existing) {
await apiPermissionAssignments.updateUserPermissions(req.params.guildId, targetId, permissions);
await auditLog.addEntry(req.params.guildId, req.user!.userId, AuditLogEventTypes.EDIT_API_PERMISSION, {
type: ApiPermissionTypes.User,
target_id: targetId,
permissions,
expires_at: existing.expires_at,
});
} else {
await apiPermissionAssignments.addUser(req.params.guildId, targetId, permissions, expiresAt);
await auditLog.addEntry(req.params.guildId, req.user!.userId, AuditLogEventTypes.ADD_API_PERMISSION, {
type: ApiPermissionTypes.User,
target_id: targetId,
permissions,
expires_at: expiresAt,
});
}
}
ok(res);
});
},
);
router.use("/", miscRouter);
}
================================================
FILE: backend/src/api/guilds.ts
================================================
import { ApiPermissions } from "@zeppelinbot/shared/apiPermissions.js";
import express, { Request, Response } from "express";
import jsYaml from "js-yaml";
import moment from "moment-timezone";
import { Queue } from "../Queue.js";
import { validateGuildConfig } from "../configValidator.js";
import { AllowedGuilds } from "../data/AllowedGuilds.js";
import { ApiAuditLog } from "../data/ApiAuditLog.js";
import { ApiPermissionAssignments, ApiPermissionTypes } from "../data/ApiPermissionAssignments.js";
import { Configs } from "../data/Configs.js";
import { AuditLogEventTypes } from "../data/apiAuditLogTypes.js";
import { isSnowflake } from "../utils.js";
import { loadYamlSafely } from "../utils/loadYamlSafely.js";
import { ObjectAliasError } from "../utils/validateNoObjectAliases.js";
import { apiTokenAuthHandlers } from "./auth.js";
import { hasGuildPermission, requireGuildPermission } from "./permissions.js";
import { clientError, ok, serverError, unauthorized } from "./responses.js";
const YAMLException = jsYaml.YAMLException;
const apiPermissionAssignments = new ApiPermissionAssignments();
const auditLog = new ApiAuditLog();
export function initGuildsAPI(app: express.Express) {
const allowedGuilds = new AllowedGuilds();
const configs = new Configs();
const guildRouter = express.Router();
guildRouter.use(...apiTokenAuthHandlers());
guildRouter.get("/available", async (req: Request, res: Response) => {
const guilds = await allowedGuilds.getForApiUser(req.user!.userId);
res.json(guilds);
});
guildRouter.get(
"/my-permissions", // a
async (req: Request, res: Response) => {
const permissions = await apiPermissionAssignments.getByUserId(req.user!.userId);
res.json(permissions);
},
);
guildRouter.get("/:guildId", async (req: Request, res: Response) => {
if (!(await hasGuildPermission(req.user!.userId, req.params.guildId, ApiPermissions.ViewGuild))) {
return unauthorized(res);
}
const guild = await allowedGuilds.find(req.params.guildId);
res.json(guild);
});
guildRouter.post("/:guildId/check-permission", async (req: Request, res: Response) => {
const permission = req.body.permission;
const hasPermission = await hasGuildPermission(req.user!.userId, req.params.guildId, permission);
res.json({ result: hasPermission });
});
guildRouter.get(
"/:guildId/config",
requireGuildPermission(ApiPermissions.ReadConfig),
async (req: Request, res: Response) => {
const config = await configs.getActiveByKey(`guild-${req.params.guildId}`);
res.json({ config: config ? config.config : "" });
},
);
guildRouter.post("/:guildId/config", requireGuildPermission(ApiPermissions.EditConfig), async (req, res) => {
let config = req.body.config;
if (config == null) return clientError(res, "No config supplied");
config = config.trim() + "\n"; // Normalize start/end whitespace in the config
const currentConfig = await configs.getActiveByKey(`guild-${req.params.guildId}`);
if (currentConfig && config === currentConfig.config) {
return ok(res);
}
// Validate config
let parsedConfig;
try {
parsedConfig = loadYamlSafely(config);
} catch (e) {
if (e instanceof YAMLException) {
return res.status(400).json({ errors: [e.message] });
}
if (e instanceof ObjectAliasError) {
return res.status(400).json({ errors: [e.message] });
}
// tslint:disable-next-line:no-console
console.error("Error when loading YAML: " + e.message);
return serverError(res, "Server error");
}
if (parsedConfig == null) {
parsedConfig = {};
}
const error = await validateGuildConfig(parsedConfig);
if (error) {
return res.status(422).json({ errors: [error] });
}
await configs.saveNewRevision(`guild-${req.params.guildId}`, config, req.user!.userId);
ok(res);
});
guildRouter.get(
"/:guildId/permissions",
requireGuildPermission(ApiPermissions.ManageAccess),
async (req: Request, res: Response) => {
const permissions = await apiPermissionAssignments.getByGuildId(req.params.guildId);
res.json(permissions);
},
);
const permissionManagementQueue = new Queue();
guildRouter.post(
"/:guildId/set-target-permissions",
requireGuildPermission(ApiPermissions.ManageAccess),
async (req: Request, res: Response) => {
await permissionManagementQueue.add(async () => {
const { type, targetId, permissions, expiresAt } = req.body;
if (type !== ApiPermissionTypes.User) {
return clientError(res, "Invalid type");
}
if (!isSnowflake(targetId) || targetId === req.user!.userId) {
return clientError(res, "Invalid targetId");
}
const validPermissions = new Set(Object.values(ApiPermissions));
validPermissions.delete(ApiPermissions.Owner);
if (!Array.isArray(permissions) || permissions.some((p) => !validPermissions.has(p))) {
return clientError(res, "Invalid permissions");
}
if (expiresAt != null && !moment.utc(expiresAt).isValid()) {
return clientError(res, "Invalid expiresAt");
}
const existingAssignment = await apiPermissionAssignments.getByGuildAndUserId(req.params.guildId, targetId);
if (existingAssignment && existingAssignment.permissions.includes(ApiPermissions.Owner)) {
return clientError(res, "Can't change owner permissions");
}
if (permissions.length === 0) {
await apiPermissionAssignments.removeUser(req.params.guildId, targetId);
await auditLog.addEntry(req.params.guildId, req.user!.userId, AuditLogEventTypes.REMOVE_API_PERMISSION, {
type: ApiPermissionTypes.User,
target_id: targetId,
});
} else {
const existing = await apiPermissionAssignments.getByGuildAndUserId(req.params.guildId, targetId);
if (existing) {
await apiPermissionAssignments.updateUserPermissions(req.params.guildId, targetId, permissions);
await auditLog.addEntry(req.params.guildId, req.user!.userId, AuditLogEventTypes.EDIT_API_PERMISSION, {
type: ApiPermissionTypes.User,
target_id: targetId,
permissions,
expires_at: existing.expires_at,
});
} else {
await apiPermissionAssignments.addUser(req.params.guildId, targetId, permissions, expiresAt);
await auditLog.addEntry(req.params.guildId, req.user!.userId, AuditLogEventTypes.ADD_API_PERMISSION, {
type: ApiPermissionTypes.User,
target_id: targetId,
permissions,
expires_at: expiresAt,
});
}
}
ok(res);
});
},
);
app.use("/guilds", guildRouter);
}
================================================
FILE: backend/src/api/index.ts
================================================
// KEEP THIS AS FIRST IMPORT
// See comment in module for details
import "../threadsSignalFix.js";
import { connect } from "../data/db.js";
import { env } from "../env.js";
import { setIsAPI } from "../globals.js";
if (!env.KEY) {
// tslint:disable-next-line:no-console
console.error("Project root .env with KEY is required!");
process.exit(1);
}
function errorHandler(err) {
console.error(err.stack || err); // tslint:disable-line:no-console
process.exit(1);
}
process.on("unhandledRejection", errorHandler);
setIsAPI(true);
// Connect to the database before loading the rest of the code (that depend on the database connection)
console.log("Connecting to database..."); // tslint:disable-line
connect().then(() => {
import("./start.js");
});
================================================
FILE: backend/src/api/permissions.ts
================================================
import { ApiPermissions, hasPermission, permissionArrToSet } from "@zeppelinbot/shared/apiPermissions.js";
import { Request, Response } from "express";
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments.js";
import { isStaff } from "../staff.js";
import { unauthorized } from "./responses.js";
const apiPermissionAssignments = new ApiPermissionAssignments();
export const hasGuildPermission = async (userId: string, guildId: string, permission: ApiPermissions) => {
if (isStaff(userId)) {
return true;
}
const permAssignment = await apiPermissionAssignments.getByGuildAndUserId(guildId, userId);
if (!permAssignment) {
return false;
}
return hasPermission(permissionArrToSet(permAssignment.permissions), permission);
};
/**
* Requires `guildId` in req.params
*/
export function requireGuildPermission(permission: ApiPermissions) {
return async (req: Request, res: Response, next) => {
if (!(await hasGuildPermission(req.user!.userId, req.params.guildId, permission))) {
return unauthorized(res);
}
next();
};
}
================================================
FILE: backend/src/api/rateLimits.ts
================================================
import { Request, Response } from "express";
import { error } from "./responses.js";
const lastRequestsByKey: Map<string, number> = new Map();
export function rateLimit(getKey: (req: Request) => string, limitMs: number, message = "Rate limited") {
return async (req: Request, res: Response, next) => {
const key = getKey(req);
if (lastRequestsByKey.has(key)) {
if (lastRequestsByKey.get(key)! > Date.now() - limitMs) {
return error(res, message, 429);
}
}
lastRequestsByKey.set(key, Date.now());
next();
};
}
================================================
FILE: backend/src/api/responses.ts
================================================
import { Response } from "express";
export function unauthorized(res: Response) {
res.status(403).json({ error: "Unauthorized" });
}
export function error(res: Response, message: string, statusCode = 500) {
res.status(statusCode).json({ error: message });
}
export function serverError(res: Response, message = "Server error") {
error(res, message, 500);
}
export function clientError(res: Response, message: string) {
error(res, message, 400);
}
export function notFound(res: Response) {
res.status(404).json({ error: "Not found" });
}
export function ok(res: Response) {
res.json({ result: "ok" });
}
================================================
FILE: backend/src/api/staff.ts
================================================
import express, { Request, Response } from "express";
import { isStaff } from "../staff.js";
import { apiTokenAuthHandlers } from "./auth.js";
export function initStaff(app: express.Express) {
const staffRouter = express.Router();
staffRouter.use(...apiTokenAuthHandlers());
staffRouter.get("/status", (req: Request, res: Response) => {
const userIsStaff = isStaff(req.user!.userId);
res.json({ isStaff: userIsStaff });
});
app.use("/staff", staffRouter);
}
================================================
FILE: backend/src/api/start.ts
================================================
import cors from "cors";
import express from "express";
import multer from "multer";
import { TokenError } from "passport-oauth2";
import { env } from "../env.js";
import { initArchives } from "./archives.js";
import { initAuth } from "./auth.js";
import { initDocs } from "./docs.js";
import { initGuildsAPI } from "./guilds/index.js";
import { clientError, error, notFound } from "./responses.js";
import { startBackgroundTasks } from "./tasks.js";
const apiPathPrefix = env.API_PATH_PREFIX || (env.NODE_ENV === "development" ? "/api" : "");
const app = express();
app.use(
cors({
origin: env.DASHBOARD_URL,
}),
);
app.use(
express.json({
limit: "50mb",
}),
);
app.use(multer().none());
const rootRouter = express.Router();
initAuth(rootRouter);
initGuildsAPI(rootRouter);
initArchives(rootRouter);
initDocs(rootRouter);
// Default route
rootRouter.get("/", (req, res) => {
res.json({ status: "cookies", with: "milk" });
});
app.use(apiPathPrefix, rootRouter);
// Error response
// eslint-disable-next-line @typescript-eslint/no-unused-vars
app.use((err, req, res, next) => {
if (err instanceof TokenError) {
clientError(res, "Invalid code");
} else {
console.error(err); // tslint:disable-line
error(res, "Server error", err.status || 500);
}
});
// 404 response
// eslint-disable-next-line @typescript-eslint/no-unused-vars
app.use((req, res, next) => {
return notFound(res);
});
const port = 3001;
app.listen(port, "0.0.0.0", () => console.log(`API server listening on port ${port}`)); // tslint:disable-line
startBackgroundTasks();
================================================
FILE: backend/src/api/tasks.ts
================================================
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments.js";
import { MINUTES } from "../utils.js";
export function startBackgroundTasks() {
// Clear expired API permissions every minute
const apiPermissions = new ApiPermissionAssignments();
setInterval(() => {
apiPermissions.clearExpiredPermissions();
}, 1 * MINUTES);
}
================================================
FILE: backend/src/commandTypes.ts
================================================
import {
escapeCodeBlock,
escapeInlineCode,
GuildChannel,
GuildMember,
GuildTextBasedChannel,
Snowflake,
User,
} from "discord.js";
import {
baseCommandParameterTypeHelpers,
CommandContext,
messageCommandBaseTypeConverters,
TypeConversionError,
} from "vety";
import { createTypeHelper } from "knub-command-manager";
import {
channelMentionRegex,
convertDelayStringToMS,
inputPatternToRegExp,
isValidSnowflake,
resolveMember,
resolveUser,
resolveUserId,
roleMentionRegex,
UnknownUser,
} from "./utils.js";
import { isValidTimezone } from "./utils/isValidTimezone.js";
import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget.js";
export const commandTypes = {
...messageCommandBaseTypeConverters,
delay(value) {
const result = convertDelayStringToMS(value);
if (result == null) {
throw new TypeConversionError(`Could not convert ${value} to a delay`);
}
return result;
},
async resolvedUser(value, context: CommandContext<any>) {
const result = await resolveUser(context.pluginData.client, value, "commandTypes:resolvedUser");
if (result == null || result instanceof UnknownUser) {
throw new TypeConversionError(`User \`${escapeCodeBlock(value)}\` was not found`);
}
return result;
},
async resolvedUserLoose(value, context: CommandContext<any>) {
const result = await resolveUser(context.pluginData.client, value, "commandTypes:resolvedUserLoose");
if (result == null) {
throw new TypeConversionError(`Invalid user: \`${escapeCodeBlock(value)}\``);
}
return result;
},
async resolvedMember(value, context: CommandContext<any>) {
if (!(context.message.channel instanceof GuildChannel)) {
throw new TypeConversionError(`Cannot resolve member for non-guild channels`);
}
const result = await resolveMember(context.pluginData.client, context.message.channel.guild, value);
if (result == null) {
throw new TypeConversionError(`Member \`${escapeCodeBlock(value)}\` was not found or they have left the server`);
}
return result;
},
async messageTarget(value: string, context: CommandContext<any>) {
value = String(value).trim();
const result = await resolveMessageTarget(context.pluginData, value);
if (!result) {
throw new TypeConversionError(`Unknown message \`${escapeInlineCode(value)}\``);
}
return result;
},
async anyId(value: string, context: CommandContext<any>) {
const userId = resolveUserId(context.pluginData.client, value);
if (userId) return userId as Snowflake;
const channelIdMatch = value.match(channelMentionRegex);
if (channelIdMatch) return channelIdMatch[1] as Snowflake;
const roleIdMatch = value.match(roleMentionRegex);
if (roleIdMatch) return roleIdMatch[1] as Snowflake;
if (isValidSnowflake(value)) {
return value as Snowflake;
}
throw new TypeConversionError(`Could not parse ID: \`${escapeInlineCode(value)}\``);
},
regex(value: string): RegExp {
try {
return inputPatternToRegExp(value);
} catch (e) {
throw new TypeConversionError(`Could not parse RegExp: \`${escapeInlineCode(e.message)}\``);
}
},
timezone(value: string) {
if (!isValidTimezone(value)) {
throw new TypeConversionError(`Invalid timezone: ${escapeInlineCode(value)}`);
}
return value;
},
guildTextBasedChannel(value: string, context: CommandContext<any>) {
return messageCommandBaseTypeConverters.textChannel(value, context);
},
};
export const commandTypeHelpers = {
...baseCommandParameterTypeHelpers,
delay: createTypeHelper<number>(commandTypes.delay),
resolvedUser: createTypeHelper<Promise<User>>(commandTypes.resolvedUser),
resolvedUserLoose: createTypeHelper<Promise<User | UnknownUser>>(commandTypes.resolvedUserLoose),
resolvedMember: createTypeHelper<Promise<GuildMember>>(commandTypes.resolvedMember),
messageTarget: createTypeHelper<Promise<MessageTarget>>(commandTypes.messageTarget),
anyId: createTypeHelper<Promise<Snowflake>>(commandTypes.anyId),
regex: createTypeHelper<RegExp>(commandTypes.regex),
timezone: createTypeHelper<string>(commandTypes.timezone),
guildTextBasedChannel: createTypeHelper<GuildTextBasedChannel>(commandTypes.guildTextBasedChannel),
};
================================================
FILE: backend/src/configValidator.ts
================================================
import { BaseConfig, ConfigValidationError, GuildPluginBlueprint, PluginConfigManager } from "vety";
import { z, ZodError } from "zod";
import { availableGuildPlugins } from "./plugins/availablePlugins.js";
import { zZeppelinGuildConfig } from "./types.js";
import { formatZodIssue } from "./utils/formatZodIssue.js";
const pluginNameToPlugin = new Map<string, GuildPluginBlueprint<any, any>>();
for (const pluginInfo of availableGuildPlugins) {
pluginNameToPlugin.set(pluginInfo.plugin.name, pluginInfo.plugin);
}
export async function validateGuildConfig(config: any): Promise<string | null> {
const validationResult = zZeppelinGuildConfig.safeParse(config);
if (!validationResult.success) {
return validationResult.error.issues.map(formatZodIssue).join("\n");
}
const guildConfig = config as BaseConfig;
if (guildConfig.plugins) {
for (const [pluginName, pluginOptions] of Object.entries(guildConfig.plugins)) {
if (!pluginNameToPlugin.has(pluginName)) {
return `Unknown plugin: ${pluginName}`;
}
if (typeof pluginOptions !== "object" || pluginOptions == null) {
return `Invalid options specified for plugin ${pluginName}`;
}
const plugin = pluginNameToPlugin.get(pluginName)!;
const configManager = new PluginConfigManager(pluginOptions, {
configSchema: plugin.configSchema,
defaultOverrides: plugin.defaultOverrides ?? [],
levels: {},
customOverrideCriteriaFunctions: plugin.customOverrideCriteriaFunctions,
});
try {
await configManager.init();
} catch (err) {
if (err instanceof ZodError) {
return `${pluginName}:\n${z.prettifyError(err)}`;
}
if (err instanceof ConfigValidationError) {
return `${pluginName}: ${err.message}`;
}
throw err;
}
}
}
return null;
}
================================================
FILE: backend/src/data/AllowedGuilds.ts
================================================
import moment from "moment-timezone";
import { Repository } from "typeorm";
import { DBDateFormat } from "../utils.js";
import { ApiPermissionTypes } from "./ApiPermissionAssignments.js";
import { BaseRepository } from "./BaseRepository.js";
import { dataSource } from "./dataSource.js";
import { AllowedGuild } from "./entities/AllowedGuild.js";
export class AllowedGuilds extends BaseRepository {
private allowedGuilds: Repository<AllowedGuild>;
constructor() {
super();
this.allowedGuilds = dataSource.getRepository(AllowedGuild);
}
async isAllowed(guildId: string) {
const count = await this.allowedGuilds.count({
where: {
id: guildId,
},
});
return count !== 0;
}
find(guildId: string) {
return this.allowedGuilds.findOne({
where: {
id: guildId,
},
});
}
getForApiUser(userId: string) {
return this.allowedGuilds
.createQueryBuilder("allowed_guilds")
.innerJoin(
"api_permissions",
"api_permissions",
"api_permissions.guild_id = allowed_guilds.id AND api_permissions.type = :type AND api_permissions.target_id = :userId",
{ type: ApiPermissionTypes.User, userId },
)
.getMany();
}
updateInfo(id, name, icon, ownerId) {
return this.allowedGuilds.update(
{ id },
{ name, icon, owner_id: ownerId, updated_at: moment.utc().format(DBDateFormat) },
);
}
add(id, data: Partial<Omit<AllowedGuild, "id">> = {}) {
return this.allowedGuilds.insert({
name: "Server",
icon: null,
owner_id: "0",
...data,
id,
});
}
remove(id) {
return this.allowedGuilds.delete({ id });
}
}
================================================
FILE: backend/src/data/ApiAuditLog.ts
================================================
import { Repository } from "typeorm";
import { BaseRepository } from "./BaseRepository.js";
import { AuditLogEventData, AuditLogEventType } from "./apiAuditLogTypes.js";
import { dataSource } from "./dataSource.js";
import { ApiAuditLogEntry } from "./entities/ApiAuditLogEntry.js";
export class ApiAuditLog extends BaseRepository {
private auditLog: Repository<ApiAuditLogEntry<any>>;
constructor() {
super();
this.auditLog = dataSource.getRepository(ApiAuditLogEntry);
}
addEntry<TEventType extends AuditLogEventType>(
guildId: string,
authorId: string,
eventType: TEventType,
eventData: AuditLogEventData[TEventType],
) {
this.auditLog.insert({
guild_id: guildId,
author_id: authorId,
event_type: eventType as any,
event_data: eventData as any,
});
}
}
================================================
FILE: backend/src/data/ApiLogins.ts
================================================
import crypto from "crypto";
import moment from "moment-timezone";
import { Repository } from "typeorm";
// tslint:disable-next-line:no-submodule-imports
import { v4 as uuidv4 } from "uuid";
import { DAYS, DBDateFormat } from "../utils.js";
import { BaseRepository } from "./BaseRepository.js";
import { dataSource } from "./dataSource.js";
import { ApiLogin } from "./entities/ApiLogin.js";
const LOGIN_EXPIRY_TIME = 1 * DAYS;
export class ApiLogins extends BaseRepository {
private apiLogins: Repository<ApiLogin>;
constructor() {
super();
this.apiLogins = dataSource.getRepository(ApiLogin);
}
async getUserIdByApiKey(apiKey: string): Promise<string | null> {
const [loginId, token] = apiKey.split(".");
if (!loginId || !token) {
return null;
}
const login = await this.apiLogins
.createQueryBuilder()
.where("id = :id", { id: loginId })
.andWhere("expires_at > NOW()")
.getOne();
if (!login) {
return null;
}
const hash = crypto.createHash("sha256");
hash.update(loginId + token); // Remember to use loginId as the salt
const hashedToken = hash.digest("hex");
if (hashedToken !== login.token) {
return null;
}
return login.user_id;
}
async addLogin(userId: string): Promise<string> {
// Generate random login id
let loginId;
while (true) {
loginId = uuidv4();
const existing = await this.apiLogins.findOne({
where: {
id: loginId,
},
});
if (!existing) break;
}
// Generate token
const token = uuidv4();
const hash = crypto.createHash("sha256");
hash.update(loginId + token); // Use loginId as a salt
const hashedToken = hash.digest("hex");
// Save this to the DB
await this.apiLogins.insert({
id: loginId,
token: hashedToken,
user_id: userId,
logged_in_at: moment.utc().format(DBDateFormat),
expires_at: moment.utc().add(LOGIN_EXPIRY_TIME, "ms").format(DBDateFormat),
});
return `${loginId}.${token}`;
}
expireApiKey(apiKey) {
const [loginId, token] = apiKey.split(".");
if (!loginId || !token) return;
return this.apiLogins.update(
{ id: loginId },
{
expires_at: moment.utc().format(DBDateFormat),
},
);
}
async refreshApiKeyExpiryTime(apiKey) {
const [loginId, token] = apiKey.split(".");
if (!loginId || !token) return;
const updatedTime = moment().utc().add(LOGIN_EXPIRY_TIME, "ms");
const login = await this.apiLogins.createQueryBuilder().where("id = :id", { id: loginId }).getOne();
if (!login || moment.utc(login.expires_at).isSameOrAfter(updatedTime)) return;
await this.apiLogins.update(
{ id: loginId },
{
expires_at: updatedTime.format(DBDateFormat),
},
);
}
}
================================================
FILE: backend/src/data/ApiPermissionAssignments.ts
================================================
import { ApiPermissions } from "@zeppelinbot/shared/apiPermissions.js";
import { Repository } from "typeorm";
import { ApiAuditLog } from "./ApiAuditLog.js";
import { BaseRepository } from "./BaseRepository.js";
import { AuditLogEventTypes } from "./apiAuditLogTypes.js";
import { dataSource } from "./dataSource.js";
import { ApiPermissionAssignment } from "./entities/ApiPermissionAssignment.js";
export enum ApiPermissionTypes {
User = "USER",
Role = "ROLE",
}
export class ApiPermissionAssignments extends BaseRepository {
private apiPermissions: Repository<ApiPermissionAssignment>;
private auditLogs: ApiAuditLog;
constructor() {
super();
this.apiPermissions = dataSource.getRepository(ApiPermissionAssignment);
this.auditLogs = new ApiAuditLog();
}
getByGuildId(guildId) {
return this.apiPermissions.find({
where: {
guild_id: guildId,
},
});
}
getByUserId(userId) {
return this.apiPermissions.find({
where: {
type: ApiPermissionTypes.User,
target_id: userId,
},
});
}
getByGuildAndUserId(guildId, userId) {
return this.apiPermissions.findOne({
where: {
guild_id: guildId,
type: ApiPermissionTypes.User,
target_id: userId,
},
});
}
addUser(guildId, userId, permissions: ApiPermissions[], expiresAt: string | null = null) {
return this.apiPermissions.insert({
guild_id: guildId,
type: ApiPermissionTypes.User,
target_id: userId,
permissions,
expires_at: expiresAt,
});
}
removeUser(guildId, userId) {
return this.apiPermissions.delete({ guild_id: guildId, type: ApiPermissionTypes.User, target_id: userId });
}
async updateUserPermissions(guildId: string, userId: string, permissions: ApiPermissions[]): Promise<void> {
await this.apiPermissions.update(
{
guild_id: guildId,
type: ApiPermissionTypes.User,
target_id: userId,
},
{
permissions,
},
);
}
async clearExpiredPermissions() {
await this.apiPermissions
.createQueryBuilder()
.where("expires_at IS NOT NULL")
.andWhere("expires_at <= NOW()")
.delete()
.execute();
}
async applyOwnerChange(guildId: string, newOwnerId: string) {
const existingPermissions = await this.getByGuildId(guildId);
let updatedOwner = false;
for (const perm of existingPermissions) {
let hasChanges = false;
// Remove owner permission from anyone who currently has it
if (perm.permissions.includes(ApiPermissions.Owner)) {
perm.permissions.splice(perm.permissions.indexOf(ApiPermissions.Owner), 1);
hasChanges = true;
}
// Add owner permission if we encounter the new owner
if (perm.type === ApiPermissionTypes.User && perm.target_id === newOwnerId) {
perm.permissions.push(ApiPermissions.Owner);
updatedOwner = true;
hasChanges = true;
}
if (hasChanges) {
const criteria = {
guild_id: perm.guild_id,
type: perm.type,
target_id: perm.target_id,
};
if (perm.permissions.length === 0) {
// No remaining permissions -> remove entry
this.auditLogs.addEntry(guildId, "0", AuditLogEventTypes.REMOVE_API_PERMISSION, {
type: perm.type,
target_id: perm.target_id,
});
await this.apiPermissions.delete(criteria);
} else {
this.auditLogs.addEntry(guildId, "0", AuditLogEventTypes.EDIT_API_PERMISSION, {
type: perm.type,
target_id: perm.target_id,
permissions: perm.permissions,
expires_at: perm.expires_at,
});
await this.apiPermissions.update(criteria, {
permissions: perm.permissions,
});
}
}
}
if (!updatedOwner) {
this.auditLogs.addEntry(guildId, "0", AuditLogEventTypes.ADD_API_PERMISSION, {
type: ApiPermissionTypes.User,
target_id: newOwnerId,
permissions: [ApiPermissions.Owner],
expires_at: null,
});
await this.apiPermissions.insert({
guild_id: guildId,
type: ApiPermissionTypes.User,
target_id: newOwnerId,
permissions: [ApiPermissions.Owner],
});
}
}
}
================================================
FILE: backend/src/data/ApiUserInfo.ts
================================================
import moment from "moment-timezone";
import { Repository } from "typeorm";
import { DBDateFormat } from "../utils.js";
import { BaseRepository } from "./BaseRepository.js";
import { dataSource } from "./dataSource.js";
import { ApiUserInfoData, ApiUserInfo as ApiUserInfoEntity } from "./entities/ApiUserInfo.js";
export class ApiUserInfo extends BaseRepository {
private apiUserInfo: Repository<ApiUserInfoEntity>;
constructor() {
super();
this.apiUserInfo = dataSource.getRepository(ApiUserInfoEntity);
}
get(id) {
return this.apiUserInfo.findOne({
where: {
id,
},
});
}
update(id, data: ApiUserInfoData) {
return dataSource.transaction(async (entityManager) => {
const repo = entityManager.getRepository(ApiUserInfoEntity);
const existingInfo = await repo.findOne({ where: { id } });
const updatedAt = moment.utc().format(DBDateFormat);
if (existingInfo) {
await repo.update({ id }, { data, updated_at: updatedAt });
} else {
await repo.insert({ id, data, updated_at: updatedAt });
}
});
}
}
================================================
FILE: backend/src/data/Archives.ts
================================================
import { Repository } from "typeorm";
import { BaseRepository } from "./BaseRepository.js";
import { dataSource } from "./dataSource.js";
import { ArchiveEntry } from "./entities/ArchiveEntry.js";
export class Archives extends BaseRepository {
protected archives: Repository<ArchiveEntry>;
constructor() {
super();
this.archives = dataSource.getRepository(ArchiveEntry);
}
public deleteExpiredArchives() {
this.archives
.createQueryBuilder()
.andWhere("expires_at IS NOT NULL")
.andWhere("expires_at <= NOW()")
.delete()
.execute();
}
}
================================================
FILE: backend/src/data/BaseGuildRepository.ts
================================================
import { BaseRepository } from "./BaseRepository.js";
export class BaseGuildRepository<TEntity = unknown> extends BaseRepository<TEntity> {
private static guildInstances: Map<string, any>;
protected guildId: string;
constructor(guildId: string) {
super();
this.guildId = guildId;
}
/**
* Returns a cached instance of the inheriting class for the specified guildId,
* or creates a new instance if one doesn't exist yet
*/
public static getGuildInstance<T extends typeof BaseGuildRepository>(this: T, guildId: string): InstanceType<T> {
if (!this.guildInstances) {
this.guildInstances = new Map();
}
if (!this.guildInstances.has(guildId)) {
this.guildInstances.set(guildId, new this(guildId));
}
return this.guildInstances.get(guildId) as InstanceType<T>;
}
}
================================================
FILE: backend/src/data/BaseRepository.ts
================================================
import { asyncMap } from "../utils/async.js";
export class BaseRepository<TEntity = unknown> {
private nextRelations: string[];
constructor() {
this.nextRelations = [];
}
/**
* Primes the specified relation(s) to be used in the next database operation.
* Can be chained.
*/
public with(relations: string | string[]): this {
if (Array.isArray(relations)) {
this.nextRelations.push(...relations);
} else {
this.nextRelations.push(relations);
}
return this;
}
/**
* Gets and resets the relations primed using with()
*/
protected getRelations(): string[] {
const relations = this.nextRelations || [];
this.nextRelations = [];
return relations;
}
protected async _processEntityFromDB(entity) {
// No-op, override in repository
return entity;
}
protected async _processEntityToDB(entity) {
// No-op, override in repository
return entity;
}
protected async processEntityFromDB<T extends TEntity | null>(entity: T): Promise<T> {
return this._processEntityFromDB(entity);
}
protected async processMultipleEntitiesFromDB<TArr extends TEntity[]>(entities: TArr): Promise<TArr> {
return asyncMap(entities, (entity) => this.processEntityFromDB(entity)) as Promise<TArr>;
}
protected async processEntityToDB<T extends Partial<TEntity>>(entity: T): Promise<T> {
return this._processEntityToDB(entity);
}
}
================================================
FILE: backend/src/data/CaseTypes.ts
================================================
export enum CaseTypes {
Ban = 1,
Unban,
Note,
Warn,
Kick,
Mute,
Unmute,
Deleted,
Softban,
}
export const CaseNameToType = {
ban: CaseTypes.Ban,
unban: CaseTypes.Unban,
note: CaseTypes.Note,
warn: CaseTypes.Warn,
kick: CaseTypes.Kick,
mute: CaseTypes.Mute,
unmute: CaseTypes.Unmute,
deleted: CaseTypes.Deleted,
softban: CaseTypes.Softban,
};
export const CaseTypeToName = Object.entries(CaseNameToType).reduce((map, [name, type]) => {
map[type] = name;
return map;
}, {}) as Record<CaseTypes, string>;
================================================
FILE: backend/src/data/Configs.ts
================================================
import { Repository } from "typeorm";
import { isAPI } from "../globals.js";
import { HOURS, SECONDS } from "../utils.js";
import { BaseRepository } from "./BaseRepository.js";
import { cleanupConfigs } from "./cleanup/configs.js";
import { dataSource } from "./dataSource.js";
import { Config } from "./entities/Config.js";
const CLEANUP_INTERVAL = 1 * HOURS;
async function cleanup() {
await cleanupConfigs();
setTimeout(cleanup, CLEANUP_INTERVAL);
}
if (isAPI()) {
// Start first cleanup 30 seconds after startup
// TODO: Move to bot startup code
setTimeout(cleanup, 30 * SECONDS);
}
export class Configs extends BaseRepository {
private configs: Repository<Config>;
constructor() {
super();
this.configs = dataSource.getRepository(Config);
}
getActive() {
return this.configs.find({
where: { is_active: true },
});
}
getActiveByKey(key) {
return this.configs.findOne({
where: {
key,
is_active: true,
},
});
}
async getHighestId(): Promise<number> {
const rows = await dataSource.query("SELECT MAX(id) AS highest_id FROM configs");
return (rows.length && rows[0].highest_id) || 0;
}
getActiveLargerThanId(id) {
return this.configs.createQueryBuilder().where("id > :id", { id }).andWhere("is_active = 1").getMany();
}
async hasConfig(key) {
return (await this.getActiveByKey(key)) != null;
}
getRevisions(key, num = 10) {
return this.configs.find({
relations: this.getRelations(),
where: { key },
select: ["id", "key", "is_active", "edited_by", "edited_at"],
order: {
edited_at: "DESC",
},
take: num,
});
}
async saveNewRevision(key, config, editedBy) {
return dataSource.transaction(async (entityManager) => {
const repo = entityManager.getRepository(Config);
// Mark all old revisions inactive
await repo.update({ key }, { is_active: false });
// Add new, active revision
await repo.insert({
key,
config,
is_active: true,
edited_by: editedBy,
});
});
}
}
================================================
FILE: backend/src/data/DefaultLogMessages.json
================================================
{
"MEMBER_NOTE": "{timestamp} 🖊 Note added on {userMention(user)} by {userMention(mod)}",
"MEMBER_WARN": "{timestamp} ⚠️ {userMention(member)} was warned by {userMention(mod)}",
"MEMBER_MUTE": "{timestamp} 🔇 {userMention(user)} was muted indefinitely by {userMention(mod)}",
"MEMBER_TIMED_MUTE": "{timestamp} 🔇 {userMention(user)} was muted for **{time}** by {userMention(mod)}",
"MEMBER_UNMUTE": "{timestamp} 🔊 {userMention(user)} was unmuted by {userMention(mod)}",
"MEMBER_TIMED_UNMUTE": "{timestamp} 🔊 {userMention(user)} was scheduled to be unmuted in **{time}** by {userMention(mod)}",
"MEMBER_MUTE_EXPIRED": "{timestamp} 🔊 {userMention(member)}'s mute expired",
"MEMBER_KICK": "{timestamp} 👢 {userMention(user)} was kicked by {userMention(mod)}",
"MEMBER_BAN": "{timestamp} 🔨 {userMention(user)} was banned by {userMention(mod)}",
"MEMBER_UNBAN": "{timestamp} 🔓 User (`{userId}`) was unbanned by {userMention(mod)}",
"MEMBER_FORCEBAN": "{timestamp} 🔨 User (`{userId}`) was forcebanned by {userMention(mod)}",
"MEMBER_SOFTBAN": "{timestamp} 🔨 {userMention(member)} was softbanned by {userMention(mod)}",
"MEMBER_JOIN": "{timestamp} 📥 {new} {userMention(member)} joined (created <t:{account_age_ts}:R>)",
"MEMBER_LEAVE": "{timestamp} 📤 {userMention(member)} left the server",
"MEMBER_ROLE_ADD": "{timestamp} 🔑 {userMention(mod)} added roles for {userMention(member)}: **{roles}**",
"MEMBER_ROLE_REMOVE": "{timestamp} 🔑 {userMention(mod)} removed roles from {userMention(member)}: **{roles}**",
"MEMBER_ROLE_CHANGES": "{timestamp} 🔑 {userMention(member)} had role changes: received **{addedRoles}**, lost **{removedRoles}**",
"MEMBER_NICK_CHANGE": "{timestamp} ✏ {userMention(member)}: nickname changed from **{oldNick}** to **{newNick}**",
"MEMBER_USERNAME_CHANGE": "{timestamp} ✏ {userMention(user)}: username changed from **{oldName}** to **{newName}**",
"MEMBER_RESTORE": "{timestamp} 💿 Restored {restoredData} for {userMention(member)} on rejoin",
"MEMBER_TIMED_BAN": "{timestamp} 🔨 {userMention(user)} was tempbanned by {userMention(mod)} for {banTime}",
"MEMBER_TIMED_UNBAN": "{timestamp} 🔓 User (`{userId}`) was automatically unbanned by {userMention(mod)} after a tempban for {banTime}",
"CHANNEL_CREATE": "{timestamp} 🖊 Channel {channelMention(channel)} was created",
"CHANNEL_DELETE": "{timestamp} 🗑 Channel {channelMention(channel)} was deleted",
"CHANNEL_UPDATE": "{timestamp} ✏ Channel {channelMention(newChannel)} was edited. Changes:\n{differenceString}",
"THREAD_CREATE": "{timestamp} 🖊 Thread {channelMention(thread)} was created in channel <#{thread.parentId}>",
"THREAD_DELETE": "{timestamp} 🗑 Thread {channelMention(thread)} was deleted/archived from channel <#{thread.parentId}>",
"THREAD_UPDATE": "{timestamp} ✏ Thread {channelMention(newThread)} was edited. Changes:\n{differenceString}",
"ROLE_CREATE": "{timestamp} 🖊 Role **{role.name}** (`{role.id}`) was created",
"ROLE_DELETE": "{timestamp} 🖊 Role **{role.name}** (`{role.id}`) was deleted",
"ROLE_UPDATE": "{timestamp} 🖊 Role **{newRole.name}** (`{newRole.id}`) was edited. Changes:\n{differenceString}",
"MESSAGE_EDIT": "{timestamp} ✏ {userMention(user)} edited their message (`{after.id}`) in {channelMention(channel)}:\n**Before:**{messageSummary(before)}**After:**{messageSummary(after)}",
"MESSAGE_DELETE": "{timestamp} 🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}{replyInfo}",
"MESSAGE_DELETE_BULK": "{timestamp} 🗑 **{count}** messages by {authorIds} deleted in {channelMention(channel)} ({archiveUrl})",
"MESSAGE_DELETE_BARE": "{timestamp} 🗑 Message (`{messageId}`) deleted in {channelMention(channel)} (no more info available)",
"MESSAGE_DELETE_AUTO": "{timestamp} 🗑 Auto-deleted message (`{message.id}`) from {userMention(user)} in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}{replyInfo}",
"VOICE_CHANNEL_JOIN": "{timestamp} 🎙 🔵 {userMention(member)} joined {channelMention(channel)}",
"VOICE_CHANNEL_MOVE": "{timestamp} 🎙 ↔ {userMention(member)} moved from {channelMention(oldChannel)} to {channelMention(newChannel)}",
"VOICE_CHANNEL_LEAVE": "{timestamp} 🎙 🔴 {userMention(member)} left {channelMention(channel)}",
"VOICE_CHANNEL_FORCE_MOVE": "{timestamp} \uD83C\uDF99 ✍ {userMention(member)} was moved from **{oldChannel.name}** to **{newChannel.name}** by {userMention(mod)}",
"VOICE_CHANNEL_FORCE_DISCONNECT": "{timestamp} \uD83C\uDF99 🚫 {userMention(member)} was forcefully disconnected from **{oldChannel.name}** by {userMention(mod)}",
"STAGE_INSTANCE_CREATE": "{timestamp} 📣 Stage Instance `{stageInstance.topic}` was created in Stage Channel <#{stageChannel.id}>",
"STAGE_INSTANCE_DELETE": "{timestamp} 📣 Stage Instance `{stageInstance.topic}` was deleted in Stage Channel <#{stageChannel.id}>",
"STAGE_INSTANCE_UPDATE": "{timestamp} 📣 Stage Instance `{newStageInstance.topic}` was edited in Stage Channel <#{stageChannel.id}>. Changes:\n{differenceString}",
"EMOJI_CREATE": "{timestamp} {emoji.mention} Emoji **{emoji.name}** (`{emoji.id}`) was created",
"EMOJI_DELETE": "{timestamp} 👋 Emoji **{emoji.name}** (`{emoji.id}`) was deleted",
"EMOJI_UPDATE": "{timestamp} {newEmoji.mention} Emoji **{newEmoji.name}** (`{newEmoji.id}`) was updated. Changes:\n{differenceString}",
"STICKER_CREATE": "{timestamp} 🖼️ Sticker `{sticker.name} ({sticker.id})` was created. Description: `{sticker.description}` Format: {emoji.format}",
"STICKER_DELETE": "{timestamp} 🖼️ Sticker `{sticker.name} ({sticker.id})` was deleted.",
"STICKER_UPDATE": "{timestamp} 🖼️ Sticker `{newSticker.name} ({sticker.id})` was updated. Changes:\n{differenceString}",
"COMMAND": "{timestamp} 🤖 {userMention(member)} used command in {channelMention(channel)}:\n`{command}`",
"MESSAGE_SPAM_DETECTED": "{timestamp} 🛑 {userMention(member)} spam detected in {channelMention(channel)}: {description} (more than {limit} in {interval}s)\n{archiveUrl}",
"OTHER_SPAM_DETECTED": "{timestamp} 🛑 {userMention(member)} spam detected: {description} (more than {limit} in {interval}s)",
"CENSOR": "{timestamp} 🛑 Censored message (`{message.id}`) from {userMention(user)} in {channelMention(channel)}: {reason}:\n```{messageText}```",
"CLEAN": "{timestamp} 🚿 {userMention(mod)} cleaned **{count}** message(s) in {channelMention(channel)}\n{archiveUrl}",
"CASE_CREATE": "{timestamp} ✏ {userMention(mod)} manually created new **{caseType}** case (#{caseNum})",
"CASE_DELETE": "{timestamp} ✂️ **Case #{case.case_number}** was deleted by {userMention(mod)}",
"MASSUNBAN": "{timestamp} ⚒ {userMention(mod)} mass-unbanned {count} users",
"MASSBAN": "{timestamp} ⚒ {userMention(mod)} massbanned {count} users",
"MASSMUTE": "{timestamp} 📢🚫 {userMention(mod)} massmuted {count} users",
"MEMBER_JOIN_WITH_PRIOR_RECORDS": "{timestamp} ⚠ {userMention(member)} joined with prior records. Recent cases:\n{recentCaseSummary}",
"CASE_UPDATE": "{timestamp} ✏ {userMention(mod)} updated case #{caseNumber} ({caseType}) with note:\n```{note}```",
"MEMBER_MUTE_REJOIN": "{timestamp} ⚠ Reapplied active mute for {userMention(member)} on rejoin",
"SCHEDULED_MESSAGE": "{timestamp} ⏰ {userMention(author)} scheduled a message to be posted to {channelMention(channel)} on {datetime}",
"SCHEDULED_REPEATED_MESSAGE": "{timestamp} ⏰ {userMention(author)} scheduled a message to be posted to {channelMention(channel)} on {datetime}, repeated {repeatDetails}",
"REPEATED_MESSAGE": "{timestamp} ⏰ {userMention(author)} scheduled a message to be posted to {channelMention(channel)} {repeatDetails}",
"POSTED_SCHEDULED_MESSAGE": "{timestamp} \uD83D\uDCE8 Posted scheduled message (`{messageId}`) to {channelMention(channel)} as scheduled by {userMention(author)}",
"BOT_ALERT": "{timestamp} ⚠ **BOT ALERT:** {tmplEval(body)}",
"DM_FAILED": "{timestamp} \uD83D\uDEA7 Failed to send DM ({source}) to {userMention(user)}",
"AUTOMOD_ACTION": "{timestamp} \uD83E\uDD16 Automod rule **{if(not(prettyName), rule, prettyName)}** triggered by {userMention(users)}\n{matchSummary}\nActions taken: **{actionsTaken}**",
"SET_ANTIRAID_USER": "{timestamp} ⚔ {userMention(user)} set anti-raid to **{level}**",
"SET_ANTIRAID_AUTO": "{timestamp} ⚔ Anti-raid automatically set to **{level}**"
}
================================================
FILE: backend/src/data/FishFish.ts
================================================
import { z } from "zod";
import { env } from "../env.js";
import { HOURS, MINUTES, SECONDS } from "../utils.js";
const API_ROOT = "https://api.fishfish.gg/v1";
const zDomainCategory = z.literal(["safe", "malware", "phishing"]);
const zDomain = z.object({
name: z.string(),
category: zDomainCategory,
description: z.string(),
added: z.number(),
checked: z.number(),
});
export type FishFishDomain = z.output<typeof zDomain>;
const FULL_REFRESH_INTERVAL = 6 * HOURS;
const domains = new Map<string, FishFishDomain>();
let sessionTokenPromise: Promise<string> | null = null;
const WS_RECONNECT_DELAY = 30 * SECONDS;
let updatesWs: WebSocket | null = null;
export class FishFishError extends Error {}
const zTokenResponse = z.object({
expires: z.number(),
token: z.string(),
});
async function getSessionToken(): Promise<string> {
if (sessionTokenPromise) {
return sessionTokenPromise;
}
const apiKey = env.FISHFISH_API_KEY;
if (!apiKey) {
throw new FishFishError("FISHFISH_API_KEY is missing");
}
sessionTokenPromise = (async () => {
const response = await fetch(`${API_ROOT}/users/@me/tokens`, {
method: "POST",
headers: {
Authorization: apiKey,
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new FishFishError(`Failed to get session token: ${response.status} ${response.statusText}`);
}
const parseResult = zTokenResponse.safeParse(await response.json());
if (!parseResult.success) {
throw new FishFishError(`Parse error when fetching session token: ${parseResult.error.message}`);
}
const timeUntilExpiry = parseResult.data.expires * 1000 - Date.now();
setTimeout(
() => {
sessionTokenPromise = null;
},
timeUntilExpiry - 1 * MINUTES,
); // Subtract a minute to ensure we refresh before expiry
return parseResult.data.token;
})();
sessionTokenPromise.catch((err) => {
sessionTokenPromise = null;
throw err;
});
return sessionTokenPromise;
}
async function fishFishApiCall(method: string, path: string, query: Record<string, string> = {}): Promise<unknown> {
const sessionToken = await getSessionToken();
const queryParams = new URLSearchParams(query);
const response = await fetch(`https://api.fishfish.gg/v1/${path}?${queryParams}`, {
method,
headers: {
Authorization: sessionToken,
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new FishFishError(`FishFish API call failed: ${response.status} ${response.statusText}`);
}
return response.json();
}
async function refreshFishFishDomains() {
const rawData = await fishFishApiCall("GET", "domains", { full: "true" });
const parseResult = z.array(zDomain).safeParse(rawData);
if (!parseResult.success) {
throw new FishFishError(`Parse error when refreshing domains: ${parseResult.error.message}`);
}
domains.clear();
for (const domain of parseResult.data) {
domains.set(domain.name, domain);
}
domains.set("malware-link.test.zeppelin.gg", {
name: "malware-link.test.zeppelin.gg",
category: "malware",
description: "",
added: Date.now(),
checked: Date.now(),
});
domains.set("phishing-link.test.zeppelin.gg", {
name: "phishing-link.test.zeppelin.gg",
category: "phishing",
description: "",
added: Date.now(),
checked: Date.now(),
});
domains.set("safe-link.test.zeppelin.gg", {
name: "safe-link.test.zeppelin.gg",
category: "safe",
description: "",
added: Date.now(),
checked: Date.now(),
});
console.log("[FISHFISH] Refreshed FishFish domains, total count:", domains.size);
}
export async function initFishFish() {
if (!env.FISHFISH_API_KEY) {
console.warn("[FISHFISH] FISHFISH_API_KEY is not set, FishFish functionality will be disabled.");
return;
}
await refreshFishFishDomains();
// Real-time updates disabled until we switch to a WebSocket lib that supports authorization headers
// void subscribeToFishFishUpdates();
setInterval(() => refreshFishFishDomains(), FULL_REFRESH_INTERVAL);
}
export function getFishFishDomain(domain: string): FishFishDomain | undefined {
return domains.get(domain.toLowerCase());
}
================================================
FILE: backend/src/data/GuildAntiraidLevels.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { AntiraidLevel } from "./entities/AntiraidLevel.js";
export class GuildAntiraidLevels extends BaseGuildRepository {
protected antiraidLevels: Repository<AntiraidLevel>;
constructor(guildId: string) {
super(guildId);
this.antiraidLevels = dataSource.getRepository(AntiraidLevel);
}
async get() {
const row = await this.antiraidLevels.findOne({
where: {
guild_id: this.guildId,
},
});
return row?.level ?? null;
}
async set(level: string | null) {
if (level === null) {
await this.antiraidLevels.delete({
guild_id: this.guildId,
});
} else {
// Upsert: https://stackoverflow.com/a/47064558/316944
// But the MySQL version: https://github.com/typeorm/typeorm/issues/1090#issuecomment-634391487
await this.antiraidLevels
.createQueryBuilder()
.insert()
.values({
guild_id: this.guildId,
level,
})
.orUpdate({
conflict_target: ["guild_id"],
overwrite: ["level"],
})
.execute();
}
}
}
================================================
FILE: backend/src/data/GuildArchives.ts
================================================
import { Guild, Snowflake } from "discord.js";
import moment from "moment-timezone";
import { Repository } from "typeorm";
import { TemplateSafeValueContainer, renderTemplate } from "../templateFormatter.js";
import { renderUsername, trimLines } from "../utils.js";
import { decrypt, encrypt } from "../utils/crypt.js";
import { isDefaultSticker } from "../utils/isDefaultSticker.js";
import { channelToTemplateSafeChannel, guildToTemplateSafeGuild } from "../utils/templateSafeObjects.js";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { ArchiveEntry } from "./entities/ArchiveEntry.js";
import { SavedMessage } from "./entities/SavedMessage.js";
const DEFAULT_EXPIRY_DAYS = 30;
const MESSAGE_ARCHIVE_HEADER_FORMAT = trimLines(`
Server: {guild.name} ({guild.id})
`);
const MESSAGE_ARCHIVE_MESSAGE_FORMAT =
"[#{channel.name}] [{user.id}] [{timestamp}] {username}: {content}{attachments}{stickers}";
export class GuildArchives extends BaseGuildRepository<ArchiveEntry> {
protected archives: Repository<ArchiveEntry>;
constructor(guildId) {
super(guildId);
this.archives = dataSource.getRepository(ArchiveEntry);
}
protected async _processEntityFromDB(entity: ArchiveEntry | undefined) {
if (entity == null) {
return entity;
}
entity.body = await decrypt(entity.body);
return entity;
}
protected async _processEntityToDB(entity: Partial<ArchiveEntry>) {
if (entity.body) {
entity.body = await encrypt(entity.body);
}
return entity;
}
async find(id: string): Promise<ArchiveEntry | null> {
const result = await this.archives.findOne({
where: { id },
relations: this.getRelations(),
});
return this.processEntityFromDB(result);
}
async makePermanent(id: string): Promise<void> {
await this.archives.update(
{ id },
{
expires_at: null,
},
);
}
/**
* @return - ID of the created archive
*/
async create(body: string, expiresAt?: moment.Moment): Promise<string> {
if (!expiresAt) {
expiresAt = moment.utc().add(DEFAULT_EXPIRY_DAYS, "days");
}
const data = await this.processEntityToDB({
guild_id: this.guildId,
body,
expires_at: expiresAt.format("YYYY-MM-DD HH:mm:ss"),
});
const result = await this.archives.insert(data);
return result.identifiers[0].id;
}
protected async renderLinesFromSavedMessages(savedMessages: SavedMessage[], guild: Guild): Promise<string[]> {
const msgLines: string[] = [];
for (const msg of savedMessages) {
const channel = guild.channels.cache.get(msg.channel_id as Snowflake);
const partialUser = new TemplateSafeValueContainer({ ...msg.data.author, id: msg.user_id });
const line = await renderTemplate(
MESSAGE_ARCHIVE_MESSAGE_FORMAT,
new TemplateSafeValueContainer({
id: msg.id,
timestamp: moment.utc(msg.posted_at).format("YYYY-MM-DD HH:mm:ss"),
content: msg.data.content,
attachments: msg.data.attachments?.map((att) => {
return JSON.stringify({ name: att.name, url: att.url, type: att.contentType });
}),
stickers: msg.data.stickers?.map((sti) => {
return JSON.stringify({ name: sti.name, id: sti.id, isDefault: isDefaultSticker(sti.id) });
}),
user: partialUser,
channel: channel ? channelToTemplateSafeChannel(channel) : null,
username: renderUsername(msg.data.author.username, msg.data.author.discriminator),
}),
);
msgLines.push(line);
}
return msgLines;
}
/**
* @return - ID of the created archive
*/
async createFromSavedMessages(
savedMessages: SavedMessage[],
guild: Guild,
expiresAt?: moment.Moment,
): Promise<string> {
if (expiresAt == null) {
expiresAt = moment.utc().add(DEFAULT_EXPIRY_DAYS, "days");
}
const headerStr = await renderTemplate(
MESSAGE_ARCHIVE_HEADER_FORMAT,
new TemplateSafeValueContainer({
guild: guildToTemplateSafeGuild(guild),
}),
);
const msgLines = await this.renderLinesFromSavedMessages(savedMessages, guild);
const messagesStr = msgLines.join("\n");
return this.create([headerStr, messagesStr].join("\n\n"), expiresAt);
}
async addSavedMessagesToArchive(archiveId: string, savedMessages: SavedMessage[], guild: Guild) {
const msgLines = await this.renderLinesFromSavedMessages(savedMessages, guild);
const messagesStr = msgLines.join("\n");
let archive = await this.find(archiveId);
if (archive == null) {
throw new Error("Archive not found");
}
archive.body += "\n" + messagesStr;
archive = await this.processEntityToDB(archive);
await this.archives.update({ id: archiveId }, { body: archive.body });
}
getUrl(baseUrl, archiveId) {
return baseUrl ? `${baseUrl}/archives/${archiveId}` : `Archive ID: ${archiveId}`;
}
}
================================================
FILE: backend/src/data/GuildAutoReactions.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { AutoReaction } from "./entities/AutoReaction.js";
export class GuildAutoReactions extends BaseGuildRepository {
private autoReactions: Repository<AutoReaction>;
constructor(guildId) {
super(guildId);
this.autoReactions = dataSource.getRepository(AutoReaction);
}
async all(): Promise<AutoReaction[]> {
return this.autoReactions.find({
where: {
guild_id: this.guildId,
},
});
}
async getForChannel(channelId: string): Promise<AutoReaction | null> {
return this.autoReactions.findOne({
where: {
guild_id: this.guildId,
channel_id: channelId,
},
});
}
async removeFromChannel(channelId: string) {
await this.autoReactions.delete({
guild_id: this.guildId,
channel_id: channelId,
});
}
async set(channelId: string, reactions: string[]) {
const existingRecord = await this.getForChannel(channelId);
if (existingRecord) {
this.autoReactions.update(
{
guild_id: this.guildId,
channel_id: channelId,
},
{
reactions,
},
);
} else {
await this.autoReactions.insert({
guild_id: this.guildId,
channel_id: channelId,
reactions,
});
}
}
}
================================================
FILE: backend/src/data/GuildButtonRoles.ts
================================================
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { ButtonRole } from "./entities/ButtonRole.js";
export class GuildButtonRoles extends BaseGuildRepository {
private buttonRoles: Repository<ButtonRole>;
constructor(guildId) {
super(guildId);
this.buttonRoles = getRepository(ButtonRole);
}
async getForButtonId(buttonId: string) {
return this.buttonRoles.findOne({
where: {
guild_id: this.guildId,
button_id: buttonId,
},
});
}
async getAllForMessageId(messageId: string) {
return this.buttonRoles.find({
where: {
guild_id: this.guildId,
message_id: messageId,
},
});
}
async removeForButtonId(buttonId: string) {
return this.buttonRoles.delete({
guild_id: this.guildId,
button_id: buttonId,
});
}
async removeAllForMessageId(messageId: string) {
return this.buttonRoles.delete({
guild_id: this.guildId,
message_id: messageId,
});
}
async getForButtonGroup(buttonGroup: string) {
return this.buttonRoles.find({
where: {
guild_id: this.guildId,
button_group: buttonGroup,
},
});
}
async add(channelId: string, messageId: string, buttonId: string, buttonGroup: string, buttonName: string) {
await this.buttonRoles.insert({
guild_id: this.guildId,
channel_id: channelId,
message_id: messageId,
button_id: buttonId,
button_group: buttonGroup,
button_name: buttonName,
});
}
}
================================================
FILE: backend/src/data/GuildCases.ts
================================================
import { FindOptionsWhere, In, InsertResult, Repository } from "typeorm";
import { Queue } from "../Queue.js";
import { chunkArray } from "../utils.js";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { CaseTypes } from "./CaseTypes.js";
import { dataSource } from "./dataSource.js";
import { Case } from "./entities/Case.js";
import { CaseNote } from "./entities/CaseNote.js";
export class GuildCases extends BaseGuildRepository {
private cases: Repository<Case>;
private caseNotes: Repository<CaseNote>;
protected createQueue: Queue;
constructor(guildId) {
super(guildId);
this.cases = dataSource.getRepository(Case);
this.caseNotes = dataSource.getRepository(CaseNote);
this.createQueue = new Queue();
}
async get(ids: number[]): Promise<Case[]> {
return this.cases.find({
relations: this.getRelations(),
where: {
guild_id: this.guildId,
id: In(ids),
},
});
}
async find(id: number): Promise<Case | null> {
return this.cases.findOne({
relations: this.getRelations(),
where: {
guild_id: this.guildId,
id,
},
});
}
async findByCaseNumber(caseNumber: number): Promise<Case | null> {
return this.cases.findOne({
relations: this.getRelations(),
where: {
guild_id: this.guildId,
case_number: caseNumber,
},
});
}
async findLatestByModId(modId: string): Promise<Case | null> {
return this.cases.findOne({
relations: this.getRelations(),
where: {
guild_id: this.guildId,
mod_id: modId,
},
order: {
case_number: "DESC",
},
});
}
async findByAuditLogId(auditLogId: string): Promise<Case | null> {
return this.cases.findOne({
relations: this.getRelations(),
where: {
guild_id: this.guildId,
audit_log_id: auditLogId,
},
});
}
async getByUserId(
userId: string,
filters: Omit<FindOptionsWhere<Case>, "guild_id" | "user_id"> = {},
): Promise<Case[]> {
return this.cases.find({
relations: this.getRelations(),
where: {
guild_id: this.guildId,
user_id: userId,
...filters,
},
});
}
async getRecentByUserId(userId: string, count: number, skip = 0): Promise<Case[]> {
return this.cases.find({
relations: this.getRelations(),
where: {
guild_id: this.guildId,
user_id: userId,
},
skip,
take: count,
order: {
case_number: "DESC",
},
});
}
async getTotalCasesByModId(
modId: string,
filters: Omit<FindOptionsWhere<Case>, "guild_id" | "mod_id" | "is_hidden"> = {},
): Promise<number> {
return this.cases.count({
where: {
guild_id: this.guildId,
mod_id: modId,
is_hidden: false,
...filters,
},
});
}
async getRecentByModId(
modId: string,
count: number,
skip = 0,
filters: Omit<FindOptionsWhere<Case>, "guild_id" | "mod_id"> = {},
): Promise<Case[]> {
const where: FindOptionsWhere<Case> = {
guild_id: this.guildId,
mod_id: modId,
is_hidden: false,
...filters,
};
if (where.is_hidden === true) {
delete where.is_hidden;
}
return this.cases.find({
relations: this.getRelations(),
where,
skip,
take: count,
order: {
case_number: "DESC",
},
});
}
async getMinCaseNumber(): Promise<number> {
const result = await this.cases
.createQueryBuilder()
.where("guild_id = :guildId", { guildId: this.guildId })
.select(["MIN(case_number) AS min_case_number"])
.getRawOne<{ min_case_number: number }>();
return result?.min_case_number || 0;
}
async getMaxCaseNumber(): Promise<number> {
const result = await this.cases
.createQueryBuilder()
.where("guild_id = :guildId", { guildId: this.guildId })
.select(["MAX(case_number) AS max_case_number"])
.getRawOne<{ max_case_number: number }>();
return result?.max_case_number || 0;
}
async setHidden(id: number, hidden: boolean): Promise<void> {
await this.cases.update(
{ id },
{
is_hidden: hidden,
},
);
}
async createInternal(data): Promise<InsertResult> {
return this.createQueue.add(async () => {
const lastCaseNumberRow = await this.cases
.createQueryBuilder()
.select(["MAX(case_number) AS last_case_number"])
.where("guild_id = :guildId", { guildId: this.guildId })
.getRawOne();
const lastCaseNumber = lastCaseNumberRow?.last_case_number || 0;
return this.cases
.insert({
case_number: lastCaseNumber + 1,
...data,
guild_id: this.guildId,
})
.catch((err) => {
if (err?.code === "ER_DUP_ENTRY") {
if (data.audit_log_id) {
// FIXME: Debug
// tslint:disable-next-line:no-console
console.trace(`Tried to insert case with duplicate audit_log_id`);
return this.createInternal({
...data,
audit_log_id: undefined,
});
}
}
throw err;
});
});
}
async create(data): Promise<Case> {
const result = await this.createInternal(data);
return (await this.find(result.identifiers[0].id))!;
}
update(id, data) {
return this.cases.update(id, data);
}
async softDelete(id: number, deletedById: string, deletedByName: string, deletedByText: string) {
return dataSource.transaction(async (entityManager) => {
const cases = entityManager.getRepository(Case);
const caseNotes = entityManager.getRepository(CaseNote);
await Promise.all([
caseNotes.delete({
case_id: id,
}),
cases.update(id, {
user_id: "0",
user_name: "Unknown#0000",
mod_id: null,
mod_name: "Unknown#0000",
type: CaseTypes.Deleted,
audit_log_id: null,
is_hidden: false,
pp_id: null,
pp_name: null,
}),
]);
await caseNotes.insert({
case_id: id,
mod_id: deletedById,
mod_name: deletedByName,
body: deletedByText,
});
});
}
async createNote(caseId: number, data: any): Promise<void> {
await this.caseNotes.insert({
...data,
case_id: caseId,
});
}
async deleteAllCases(): Promise<void> {
const idRows = await this.cases
.createQueryBuilder()
.where("guild_id = :guildId", { guildId: this.guildId })
.select(["id"])
.getRawMany<{ id: number }>();
const ids = idRows.map((r) => r.id);
const batches = chunkArray(ids, 500);
for (const batch of batches) {
await this.cases.createQueryBuilder().where("id IN (:ids)", { ids: batch }).delete().execute();
}
}
async bumpCaseNumbers(amount: number): Promise<void> {
await this.cases
.createQueryBuilder()
.where("guild_id = :guildId", { guildId: this.guildId })
.update()
.set({
case_number: () => `case_number + ${parseInt(amount as unknown as string, 10)}`,
})
.execute();
}
getExportCases(skip: number, take: number): Promise<Case[]> {
return this.cases.find({
where: {
guild_id: this.guildId,
},
relations: ["notes"],
order: {
case_number: "ASC",
},
skip,
take,
});
}
}
================================================
FILE: backend/src/data/GuildContextMenuLinks.ts
================================================
import { DeleteResult, InsertResult, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { ContextMenuLink } from "./entities/ContextMenuLink.js";
export class GuildContextMenuLinks extends BaseGuildRepository {
private contextLinks: Repository<ContextMenuLink>;
constructor(guildId) {
super(guildId);
this.contextLinks = dataSource.getRepository(ContextMenuLink);
}
async get(id: string): Promise<ContextMenuLink | null> {
return this.contextLinks.findOne({
where: {
guild_id: this.guildId,
context_id: id,
},
});
}
async create(contextId: string, contextAction: string): Promise<InsertResult> {
return this.contextLinks.insert({
guild_id: this.guildId,
context_id: contextId,
action_name: contextAction,
});
}
async deleteAll(): Promise<DeleteResult> {
return this.contextLinks.delete({
guild_id: this.guildId,
});
}
}
================================================
FILE: backend/src/data/GuildCounters.ts
================================================
import moment from "moment-timezone";
import { FindOptionsWhere, In, IsNull, Not, Repository } from "typeorm";
import { Queue } from "../Queue.js";
import { DAYS, DBDateFormat, HOURS, MINUTES } from "../utils.js";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { Counter } from "./entities/Counter.js";
import { CounterTrigger, TriggerComparisonOp, isValidCounterComparisonOp } from "./entities/CounterTrigger.js";
import { CounterTriggerState } from "./entities/CounterTriggerState.js";
import { CounterValue } from "./entities/CounterValue.js";
const DELETE_UNUSED_COUNTERS_AFTER = 1 * DAYS;
const DELETE_UNUSED_COUNTER_TRIGGERS_AFTER = 1 * DAYS;
export const MIN_COUNTER_VALUE = 0;
export const MAX_COUNTER_VALUE = 2147483647; // 2^31-1, for MySQL INT
const decayQueue = new Queue();
async function deleteCountersMarkedToBeDeleted(): Promise<void> {
await dataSource.getRepository(Counter).createQueryBuilder().where("delete_at <= NOW()").delete().execute();
}
async function deleteTriggersMarkedToBeDeleted(): Promise<void> {
await dataSource.getRepository(CounterTrigger).createQueryBuilder().where("delete_at <= NOW()").delete().execute();
}
setInterval(deleteCountersMarkedToBeDeleted, 1 * HOURS);
setInterval(deleteTriggersMarkedToBeDeleted, 1 * HOURS);
setTimeout(deleteCountersMarkedToBeDeleted, 1 * MINUTES);
setTimeout(deleteTriggersMarkedToBeDeleted, 1 * MINUTES);
export class GuildCounters extends BaseGuildRepository {
private counters: Repository<Counter>;
private counterValues: Repository<CounterValue>;
private counterTriggers: Repository<CounterTrigger>;
private counterTriggerStates: Repository<CounterTriggerState>;
constructor(guildId) {
super(guildId);
this.counters = dataSource.getRepository(Counter);
this.counterValues = dataSource.getRepository(CounterValue);
this.counterTriggers = dataSource.getRepository(CounterTrigger);
this.counterTriggerStates = dataSource.getRepository(CounterTriggerState);
}
async findOrCreateCounter(name: string, perChannel: boolean, perUser: boolean): Promise<Counter> {
const existing = await this.counters.findOne({
where: {
guild_id: this.guildId,
name,
},
});
if (existing) {
// If the existing counter's properties match the ones we're looking for, return it.
// Otherwise, delete the existing counter and re-create it with the proper properties.
if (existing.per_channel === perChannel && existing.per_user === perUser) {
await this.counters.update({ id: existing.id }, { delete_at: null });
return existing;
}
await this.counters.delete({ id: existing.id });
}
const insertResult = await this.counters.insert({
guild_id: this.guildId,
name,
per_channel: perChannel,
per_user: perUser,
last_decay_at: moment.utc().format(DBDateFormat),
});
return (await this.counters.findOne({
where: {
id: insertResult.identifiers[0].id,
},
}))!;
}
async markUnusedCountersToBeDeleted(idsToKeep: number[]): Promise<void> {
const criteria: FindOptionsWhere<Counter> = {
guild_id: this.guildId,
delete_at: IsNull(),
};
if (idsToKeep.length) {
criteria.id = Not(In(idsToKeep));
}
const deleteAt = moment.utc().add(DELETE_UNUSED_COUNTERS_AFTER, "ms").format(DBDateFormat);
await this.counters.update(criteria, {
delete_at: deleteAt,
});
}
async deleteCountersMarkedToBeDeleted(): Promise<void> {
await this.counters.createQueryBuilder().where("delete_at <= NOW()").delete().execute();
}
async changeCounterValue(
id: number,
channelId: string | null,
userId: string | null,
change: number,
initialValue: number,
): Promise<void> {
if (typeof change !== "number" || Number.isNaN(change) || !Number.isFinite(change)) {
throw new Error(`changeCounterValue() change argument must be a number`);
}
channelId = channelId || "0";
userId = userId || "0";
const rawUpdate =
change >= 0
? `value = LEAST(value + ${change}, ${MAX_COUNTER_VALUE})`
: `value = GREATEST(value ${change}, ${MIN_COUNTER_VALUE})`;
await this.counterValues.query(
`
INSERT INTO counter_values (counter_id, channel_id, user_id, value)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE ${rawUpdate}
`,
[id, channelId, userId, Math.max(initialValue + change, 0)],
);
}
async setCounterValue(id: number, channelId: string | null, userId: string | null, value: number): Promise<void> {
if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
throw new Error(`setCounterValue() value argument must be a number`);
}
channelId = channelId || "0";
userId = userId || "0";
value = Math.max(value, 0);
await this.counterValues.query(
`
INSERT INTO counter_values (counter_id, channel_id, user_id, value)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE value = ?
`,
[id, channelId, userId, value, value],
);
}
decay(id: number, decayPeriodMs: number, decayAmount: number) {
return decayQueue.add(async () => {
const counter = (await this.counters.findOne({
where: {
id,
},
}))!;
const diffFromLastDecayMs = moment.utc().diff(moment.utc(counter.last_decay_at!), "ms");
if (diffFromLastDecayMs < decayPeriodMs) {
return;
}
const decayAmountToApply = Math.round((diffFromLastDecayMs / decayPeriodMs) * decayAmount);
if (decayAmountToApply === 0 || Number.isNaN(decayAmountToApply)) {
return;
}
// Calculate new last_decay_at based on the rounded decay amount we applied. This makes it so that over time, the decayed amount will stay accurate, even if we round some here.
const newLastDecayDate = moment
.utc(counter.last_decay_at)
.add((decayAmountToApply / decayAmount) * decayPeriodMs, "ms")
.format(DBDateFormat);
const rawUpdate =
decayAmountToApply >= 0
? `GREATEST(value - ${decayAmountToApply}, ${MIN_COUNTER_VALUE})`
: `LEAST(value + ${Math.abs(decayAmountToApply)}, ${MAX_COUNTER_VALUE})`;
// Using an UPDATE with ORDER BY in an attempt to avoid deadlocks from simultaneous decays
// Also see https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks-handling.html
await this.counterValues
.createQueryBuilder("CounterValue")
.where("counter_id = :id", { id })
.orderBy("id")
.update({
value: () => rawUpdate,
})
.execute();
await this.counters.update(
{
id,
},
{
last_decay_at: newLastDecayDate,
},
);
});
}
async markUnusedTriggersToBeDeleted(triggerIdsToKeep: number[]) {
let triggersToMarkQuery = this.counterTriggers
.createQueryBuilder("counterTriggers")
.innerJoin(Counter, "counters", "counters.id = counterTriggers.counter_id")
.where("counters.guild_id = :guildId", { guildId: this.guildId });
// If there are no active triggers, we just mark all triggers from the guild to be deleted.
// Otherwise, we mark all but the active triggers in the guild.
if (triggerIdsToKeep.length) {
triggersToMarkQuery = triggersToMarkQuery.andWhere("counterTriggers.id NOT IN (:...triggerIds)", {
triggerIds: triggerIdsToKeep,
});
}
const triggersToMark = await triggersToMarkQuery.getMany();
if (triggersToMark.length) {
const deleteAt = moment.utc().add(DELETE_UNUSED_COUNTER_TRIGGERS_AFTER, "ms").format(DBDateFormat);
await this.counterTriggers.update(
{
id: In(triggersToMark.map((t) => t.id)),
},
{
delete_at: deleteAt,
},
);
}
}
async deleteTriggersMarkedToBeDeleted(): Promise<void> {
await this.counterTriggers.createQueryBuilder().where("delete_at <= NOW()").delete().execute();
}
async initCounterTrigger(
counterId: number,
triggerName: string,
comparisonOp: TriggerComparisonOp,
comparisonValue: number,
reverseComparisonOp: TriggerComparisonOp,
reverseComparisonValue: number,
): Promise<CounterTrigger> {
if (!isValidCounterComparisonOp(comparisonOp)) {
throw new Error(`Invalid comparison op: ${comparisonOp}`);
}
if (!isValidCounterComparisonOp(reverseComparisonOp)) {
throw new Error(`Invalid comparison op: ${reverseComparisonOp}`);
}
if (typeof comparisonValue !== "number") {
throw new Error(`Invalid comparison value: ${comparisonValue}`);
}
if (typeof reverseComparisonValue !== "number") {
throw new Error(`Invalid comparison value: ${reverseComparisonValue}`);
}
return dataSource.transaction(async (entityManager) => {
const existing = await entityManager.findOne(CounterTrigger, {
where: {
counter_id: counterId,
name: triggerName,
},
});
if (existing) {
// Since all existing triggers are marked as to-be-deleted before they are re-initialized, this needs to be reset
await entityManager.update(CounterTrigger, existing.id, {
comparison_op: comparisonOp,
comparison_value: comparisonValue,
reverse_comparison_op: reverseComparisonOp,
reverse_comparison_value: reverseComparisonValue,
delete_at: null,
});
return existing;
}
const insertResult = await entityManager.insert(CounterTrigger, {
counter_id: counterId,
name: triggerName,
comparison_op: comparisonOp,
comparison_value: comparisonValue,
reverse_comparison_op: reverseComparisonOp,
reverse_comparison_value: reverseComparisonValue,
});
return (await entityManager.findOne(CounterTrigger, {
where: {
id: insertResult.identifiers[0].id,
},
}))!;
});
}
/**
* Checks if a counter value with the given parameters triggers the specified comparison for the specified counter.
* If it does, mark this comparison for these parameters as triggered.
* Note that if this comparison for these parameters was already triggered previously, this function will return false.
* This means that a specific comparison for the specific parameters specified will only trigger *once* until the reverse trigger is triggered.
*
* @param counterId
* @param comparisonOp
* @param comparisonValue
* @param userId
* @param channelId
* @return Whether the given parameters newly triggered the given comparison
*/
async checkForTrigger(
counterTrigger: CounterTrigger,
channelId: string | null,
userId: string | null,
): Promise<boolean> {
channelId = channelId || "0";
userId = userId || "0";
return dataSource.transaction(async (entityManager) => {
const previouslyTriggered = await entityManager.findOne(CounterTriggerState, {
where: {
trigger_id: counterTrigger.id,
user_id: userId!,
channel_id: channelId!,
},
});
if (previouslyTriggered) {
return false;
}
const matchingValue = await entityManager
.createQueryBuilder(CounterValue, "cv")
.leftJoin(
CounterTriggerState,
"triggerStates",
"triggerStates.trigger_id = :triggerId AND triggerStates.user_id = cv.user_id AND triggerStates.channel_id = cv.channel_id",
{ triggerId: counterTrigger.id },
)
.where(`cv.value ${counterTrigger.comparison_op} :value`, { value: counterTrigger.comparison_value })
.andWhere(`cv.counter_id = :counterId`, { counterId: counterTrigger.counter_id })
.andWhere("cv.channel_id = :channelId AND cv.user_id = :userId", { channelId, userId })
.andWhere("triggerStates.id IS NULL")
.getOne();
if (matchingValue) {
await entityManager.insert(CounterTriggerState, {
trigger_id: counterTrigger.id,
user_id: userId!,
channel_id: channelId!,
});
return true;
}
return false;
});
}
/**
* Checks if any counter values of the specified counter match the specified comparison.
* Like checkForTrigger(), this can only happen *once* per unique counter value parameters until the reverse trigger is triggered for those values.
*
* @return Counter value parameters that triggered the condition
*/
async checkAllValuesForTrigger(
counterTrigger: CounterTrigger,
): Promise<Array<{ channelId: string; userId: string }>> {
return dataSource.transaction(async (entityManager) => {
const matchingValues = await entityManager
.createQueryBuilder(CounterValue, "cv")
.leftJoin(
CounterTriggerState,
"triggerStates",
"triggerStates.trigger_id = :triggerId AND triggerStates.user_id = cv.user_id AND triggerStates.channel_id = cv.channel_id",
{ triggerId: counterTrigger.id },
)
.where(`cv.value ${counterTrigger.comparison_op} :value`, { value: counterTrigger.comparison_value })
.andWhere(`cv.counter_id = :counterId`, { counterId: counterTrigger.counter_id })
.andWhere("triggerStates.id IS NULL")
.getMany();
if (matchingValues.length) {
await entityManager.insert(
CounterTriggerState,
matchingValues.map((row) => ({
trigger_id: counterTrigger.id,
channel_id: row.channel_id,
user_id: row.user_id,
})),
);
}
return matchingValues.map((row) => ({
channelId: row.channel_id,
userId: row.user_id,
}));
});
}
/**
* Checks if a counter value with the given parameters *no longer* matches the specified comparison, and thus triggers a "reverse trigger".
* Like checkForTrigger(), this can only happen *once* until the comparison is triggered normally again.
*
* @param counterId
* @param comparisonOp
* @param comparisonValue
* @param userId
* @param channelId
* @return Whether the given parameters triggered a reverse trigger for the given comparison
*/
async checkForReverseTrigger(
counterTrigger: CounterTrigger,
channelId: string | null,
userId: string | null,
): Promise<boolean> {
channelId = channelId || "0";
userId = userId || "0";
return dataSource.transaction(async (entityManager) => {
const matchingValue = await entityManager
.createQueryBuilder(CounterValue, "cv")
.innerJoin(
CounterTriggerState,
"triggerStates",
"triggerStates.trigger_id = :triggerId AND triggerStates.user_id = cv.user_id AND triggerStates.channel_id = cv.channel_id",
{ triggerId: counterTrigger.id },
)
.where(`cv.value ${counterTrigger.reverse_comparison_op} :value`, {
value: counterTrigger.reverse_comparison_value,
})
.andWhere(`cv.counter_id = :counterId`, { counterId: counterTrigger.counter_id })
.andWhere(`cv.channel_id = :channelId AND cv.user_id = :userId`, { channelId, userId })
.getOne();
if (matchingValue) {
await entityManager.delete(CounterTriggerState, {
trigger_id: counterTrigger.id,
user_id: userId!,
channel_id: channelId!,
});
return true;
}
return false;
});
}
/**
* Checks if any counter values of the specified counter *no longer* match the specified comparison, and thus triggers a "reverse trigger" for those values.
* Like checkForTrigger(), this can only happen *once* per unique counter value parameters until the comparison is triggered normally again.
*
* @return Counter value parameters that triggered a reverse trigger
*/
async checkAllValuesForReverseTrigger(
counterTrigger: CounterTrigger,
): Promise<Array<{ channelId: string; userId: string }>> {
return dataSource.transaction(async (entityManager) => {
const matchingValues: Array<{
id: string;
triggerStateId: string;
user_id: string;
channel_id: string;
}> = await entityManager
.createQueryBuilder(CounterValue, "cv")
.innerJoin(
CounterTriggerState,
"triggerStates",
"triggerStates.trigger_id = :triggerId AND triggerStates.user_id = cv.user_id AND triggerStates.channel_id = cv.channel_id",
{ triggerId: counterTrigger.id },
)
.where(`cv.value ${counterTrigger.reverse_comparison_op} :value`, {
value: counterTrigger.reverse_comparison_value,
})
.andWhere(`cv.counter_id = :counterId`, { counterId: counterTrigger.counter_id })
.select([
"cv.id AS id",
"cv.user_id AS user_id",
"cv.channel_id AS channel_id",
"triggerStates.id AS triggerStateId",
])
.getRawMany();
if (matchingValues.length) {
await entityManager.delete(CounterTriggerState, {
id: In(matchingValues.map((v) => v.triggerStateId)),
});
}
return matchingValues.map((row) => ({
channelId: row.channel_id,
userId: row.user_id,
}));
});
}
async getCurrentValue(
counterId: number,
channelId: string | null,
userId: string | null,
): Promise<number | undefined> {
const value = await this.counterValues.findOne({
where: {
counter_id: counterId,
channel_id: channelId || "0",
user_id: userId || "0",
},
});
return value?.value;
}
async resetAllCounterValues(counterId: number): Promise<void> {
// Foreign keys will remove any related triggers and counter values
await this.counters.delete({
id: counterId,
});
}
}
================================================
FILE: backend/src/data/GuildEvents.ts
================================================
import { Mute } from "./entities/Mute.js";
import { Reminder } from "./entities/Reminder.js";
import { ScheduledPost } from "./entities/ScheduledPost.js";
import { Tempban } from "./entities/Tempban.js";
import { VCAlert } from "./entities/VCAlert.js";
interface GuildEventArgs extends Record<string, unknown[]> {
expiredMute: [Mute];
timeoutMuteToRenew: [Mute];
scheduledPost: [ScheduledPost];
reminder: [Reminder];
expiredTempban: [Tempban];
expiredVCAlert: [VCAlert];
}
type GuildEvent = keyof GuildEventArgs;
type GuildEventListener<K extends GuildEvent> = (...args: GuildEventArgs[K]) => void;
type ListenerMap = {
[K in GuildEvent]?: Array<GuildEventListener<K>>;
};
const guildListeners: Map<string, ListenerMap> = new Map();
/**
* @return - Function to unregister the listener
*/
export function onGuildEvent<K extends GuildEvent>(
guildId: string,
eventName: K,
listener: GuildEventListener<K>,
): () => void {
if (!guildListeners.has(guildId)) {
guildListeners.set(guildId, {});
}
const listenerMap = guildListeners.get(guildId)!;
if (listenerMap[eventName] == null) {
listenerMap[eventName] = [];
}
listenerMap[eventName]!.push(listener);
return () => {
listenerMap[eventName]!.splice(listenerMap[eventName]!.indexOf(listener), 1);
};
}
export function emitGuildEvent<K extends GuildEvent>(guildId: string, eventName: K, args: GuildEventArgs[K]): void {
if (!guildListeners.has(guildId)) {
return;
}
const listenerMap = guildListeners.get(guildId)!;
if (listenerMap[eventName] == null) {
return;
}
for (const listener of listenerMap[eventName]!) {
listener(...args);
}
}
export function hasGuildEventListener<K extends GuildEvent>(guildId: string, eventName: K): boolean {
if (!guildListeners.has(guildId)) {
return false;
}
const listenerMap = guildListeners.get(guildId)!;
if (listenerMap[eventName] == null || listenerMap[eventName]!.length === 0) {
return false;
}
return true;
}
================================================
FILE: backend/src/data/GuildLogs.ts
================================================
import * as events from "events";
import { LogType } from "./LogType.js";
// Use the same instance for the same guild, even if a new instance is created
const guildInstances: Map<string, GuildLogs> = new Map();
interface IIgnoredLog {
type: keyof typeof LogType;
ignoreId: any;
}
export class GuildLogs extends events.EventEmitter {
protected guildId: string;
protected ignoredLogs: IIgnoredLog[];
constructor(guildId) {
if (guildInstances.has(guildId)) {
// Return existing instance for this guild if one exists
return guildInstances.get(guildId)!;
}
super();
this.guildId = guildId;
this.ignoredLogs = [];
// Store the instance for this guild so it can be returned later if a new instance for this guild is requested
guildInstances.set(guildId, this);
}
log(type: keyof typeof LogType, data: any, ignoreId?: string) {
if (ignoreId && this.isLogIgnored(type, ignoreId)) {
this.clearIgnoredLog(type, ignoreId);
return;
}
this.emit("log", { type, data });
}
ignoreLog(type: keyof typeof LogType, ignoreId: any, timeout?: number) {
this.ignoredLogs.push({ type, ignoreId });
// Clear after expiry (15sec by default)
setTimeout(
() => {
this.clearIgnoredLog(type, ignoreId);
},
timeout || 1000 * 15,
);
}
isLogIgnored(type: keyof typeof LogType, ignoreId: any) {
return this.ignoredLogs.some((info) => type === info.type && ignoreId === info.ignoreId);
}
clearIgnoredLog(type: keyof typeof LogType, ignoreId: any) {
this.ignoredLogs.splice(
this.ignoredLogs.findIndex((info) => type === info.type && ignoreId === info.ignoreId),
1,
);
}
}
================================================
FILE: backend/src/data/GuildMemberCache.ts
================================================
import moment from "moment-timezone";
import { Repository } from "typeorm";
import { Blocker } from "../Blocker.js";
import { DBDateFormat, MINUTES } from "../utils.js";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { MemberCacheItem } from "./entities/MemberCacheItem.js";
const SAVE_PENDING_BLOCKER_KEY = "save-pending" as const;
const DELETION_DELAY = 5 * MINUTES;
type UpdateData = Pick<MemberCacheItem, "username" | "nickname" | "roles">;
export class GuildMemberCache extends BaseGuildRepository {
#memberCache: Repository<MemberCacheItem>;
#pendingUpdates: Map<string, Partial<MemberCacheItem>>;
#blocker: Blocker;
constructor(guildId: string) {
super(guildId);
this.#memberCache = dataSource.getRepository(MemberCacheItem);
this.#pendingUpdates = new Map();
this.#blocker = new Blocker();
}
async savePendingUpdates(): Promise<void> {
await this.#blocker.waitToBeUnblocked(SAVE_PENDING_BLOCKER_KEY);
if (this.#pendingUpdates.size === 0) {
return;
}
this.#blocker.block(SAVE_PENDING_BLOCKER_KEY);
const entitiesToSave = Array.from(this.#pendingUpdates.values());
this.#pendingUpdates.clear();
await this.#memberCache.upsert(entitiesToSave, ["guild_id", "user_id"]).finally(() => {
this.#blocker.unblock(SAVE_PENDING_BLOCKER_KEY);
});
}
async getCachedMemberData(userId: string): Promise<MemberCacheItem | null> {
await this.#blocker.waitToBeUnblocked(SAVE_PENDING_BLOCKER_KEY);
const dbItem = await this.#memberCache.findOne({
where: {
guild_id: this.guildId,
user_id: userId,
},
});
const pendingItem = this.#pendingUpdates.get(userId);
if (!dbItem && !pendingItem) {
return null;
}
const item = new MemberCacheItem();
Object.assign(item, dbItem ?? {});
Object.assign(item, pendingItem ?? {});
return item;
}
async setCachedMemberData(userId: string, data: UpdateData): Promise<void> {
await this.#blocker.waitToBeUnblocked(SAVE_PENDING_BLOCKER_KEY);
if (!this.#pendingUpdates.has(userId)) {
const newItem = new MemberCacheItem();
newItem.guild_id = this.guildId;
newItem.user_id = userId;
this.#pendingUpdates.set(userId, newItem);
}
Object.assign(this.#pendingUpdates.get(userId)!, data);
this.#pendingUpdates.get(userId)!.last_seen = moment().format("YYYY-MM-DD");
}
async markMemberForDeletion(userId: string): Promise<void> {
await this.#memberCache.update(
{
guild_id: this.guildId,
user_id: userId,
},
{
delete_at: moment().add(DELETION_DELAY, "ms").format(DBDateFormat),
},
);
}
async unmarkMemberForDeletion(userId: string): Promise<void> {
await this.#memberCache.update(
{
guild_id: this.guildId,
user_id: userId,
},
{
delete_at: null,
},
);
}
}
================================================
FILE: backend/src/data/GuildMemberTimezones.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { MemberTimezone } from "./entities/MemberTimezone.js";
export class GuildMemberTimezones extends BaseGuildRepository {
protected memberTimezones: Repository<MemberTimezone>;
constructor(guildId: string) {
super(guildId);
this.memberTimezones = dataSource.getRepository(MemberTimezone);
}
get(memberId: string) {
return this.memberTimezones.findOne({
where: {
guild_id: this.guildId,
member_id: memberId,
},
});
}
async set(memberId, timezone: string) {
await dataSource.transaction(async (entityManager) => {
const repo = entityManager.getRepository(MemberTimezone);
const existingRow = await repo.findOne({
where: {
guild_id: this.guildId,
member_id: memberId,
},
});
if (existingRow) {
await repo.update(
{
guild_id: this.guildId,
member_id: memberId,
},
{
timezone,
},
);
} else {
await repo.insert({
guild_id: this.guildId,
member_id: memberId,
timezone,
});
}
});
}
reset(memberId: string) {
return this.memberTimezones.delete({
guild_id: this.guildId,
member_id: memberId,
});
}
}
================================================
FILE: backend/src/data/GuildMutes.ts
================================================
import moment from "moment-timezone";
import { Brackets, Repository } from "typeorm";
import { DBDateFormat } from "../utils.js";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { MuteTypes } from "./MuteTypes.js";
import { dataSource } from "./dataSource.js";
import { Mute } from "./entities/Mute.js";
export type AddMuteParams = {
userId: Mute["user_id"];
type: MuteTypes;
expiresAt: number | null;
rolesToRestore?: Mute["roles_to_restore"];
muteRole?: string | null;
timeoutExpiresAt?: number;
};
export class GuildMutes extends BaseGuildRepository {
private mutes: Repository<Mute>;
constructor(guildId) {
super(guildId);
this.mutes = dataSource.getRepository(Mute);
}
async getExpiredMutes(): Promise<Mute[]> {
return this.mutes
.createQueryBuilder("mutes")
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("expires_at IS NOT NULL")
.andWhere("expires_at <= NOW()")
.getMany();
}
async findExistingMuteForUserId(userId: string): Promise<Mute | null> {
return this.mutes.findOne({
where: {
guild_id: this.guildId,
user_id: userId,
},
});
}
async isMuted(userId: string): Promise<boolean> {
const mute = await this.findExistingMuteForUserId(userId);
return mute != null;
}
async addMute(params: AddMuteParams): Promise<Mute> {
const expiresAt = params.expiresAt ? moment.utc(params.expiresAt).format(DBDateFormat) : null;
const timeoutExpiresAt = params.timeoutExpiresAt ? moment.utc(params.timeoutExpiresAt).format(DBDateFormat) : null;
const result = await this.mutes.insert({
guild_id: this.guildId,
user_id: params.userId,
type: params.type,
expires_at: expiresAt,
roles_to_restore: params.rolesToRestore ?? [],
mute_role: params.muteRole,
timeout_expires_at: timeoutExpiresAt,
});
return (await this.mutes.findOne({ where: result.identifiers[0] }))!;
}
async updateExpiryTime(userId, newExpiryTime, rolesToRestore?: string[]): Promise<void> {
const expiresAt = newExpiryTime ? moment.utc().add(newExpiryTime, "ms").format("YYYY-MM-DD HH:mm:ss") : null;
if (rolesToRestore && rolesToRestore.length) {
await this.mutes.update(
{
guild_id: this.guildId,
user_id: userId,
},
{
expires_at: expiresAt,
roles_to_restore: rolesToRestore,
},
);
} else {
await this.mutes.update(
{
guild_id: this.guildId,
user_id: userId,
},
{
expires_at: expiresAt,
},
);
}
}
async updateExpiresAt(userId: string, timestamp: number | null): Promise<void> {
const expiresAt = timestamp ? moment.utc(timestamp).format("YYYY-MM-DD HH:mm:ss") : null;
await this.mutes.update(
{
guild_id: this.guildId,
user_id: userId,
},
{
expires_at: expiresAt,
},
);
}
async updateTimeoutExpiresAt(userId: string, timestamp: number): Promise<void> {
const timeoutExpiresAt = moment.utc(timestamp).format(DBDateFormat);
await this.mutes.update(
{
guild_id: this.guildId,
user_id: userId,
},
{
timeout_expires_at: timeoutExpiresAt,
},
);
}
async getActiveMutes(): Promise<Mute[]> {
return this.mutes
.createQueryBuilder("mutes")
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere(
new Brackets((qb) => {
qb.where("expires_at > NOW()").orWhere("expires_at IS NULL");
}),
)
.getMany();
}
async setCaseId(userId: string, caseId: number) {
await this.mutes.update(
{
guild_id: this.guildId,
user_id: userId,
},
{
case_id: caseId,
},
);
}
async clear(userId) {
await this.mutes.delete({
guild_id: this.guildId,
user_id: userId,
});
}
async fillMissingMuteRole(muteRole: string): Promise<void> {
await this.mutes
.createQueryBuilder()
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("type = :type", { type: MuteTypes.Role })
.andWhere("mute_role IS NULL")
.update({
mute_role: muteRole,
})
.execute();
}
}
================================================
FILE: backend/src/data/GuildNicknameHistory.ts
================================================
import { In, Repository } from "typeorm";
import { isAPI } from "../globals.js";
import { MINUTES, SECONDS } from "../utils.js";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { cleanupNicknames } from "./cleanup/nicknames.js";
import { dataSource } from "./dataSource.js";
import { NicknameHistoryEntry } from "./entities/NicknameHistoryEntry.js";
const CLEANUP_INTERVAL = 5 * MINUTES;
async function cleanup() {
await cleanupNicknames();
setTimeout(cleanup, CLEANUP_INTERVAL);
}
if (!isAPI()) {
// Start first cleanup 30 seconds after startup
// TODO: Move to bot startup code
setTimeout(cleanup, 30 * SECONDS);
}
export const MAX_NICKNAME_ENTRIES_PER_USER = 10;
export class GuildNicknameHistory extends BaseGuildRepository {
private nicknameHistory: Repository<NicknameHistoryEntry>;
constructor(guildId) {
super(guildId);
this.nicknameHistory = dataSource.getRepository(NicknameHistoryEntry);
}
async getByUserId(userId): Promise<NicknameHistoryEntry[]> {
return this.nicknameHistory.find({
where: {
guild_id: this.guildId,
user_id: userId,
},
order: {
id: "DESC",
},
});
}
getLastEntry(userId): Promise<NicknameHistoryEntry | null> {
return this.nicknameHistory.findOne({
where: {
guild_id: this.guildId,
user_id: userId,
},
order: {
id: "DESC",
},
});
}
async addEntry(userId, nickname) {
await this.nicknameHistory.insert({
guild_id: this.guildId,
user_id: userId,
nickname,
});
// Cleanup (leave only the last MAX_USERNAME_ENTRIES_PER_USER entries)
const toDelete = await this.nicknameHistory
.createQueryBuilder()
.where("guild_id = :guildId", { guildId: this.guildId })
.andWhere("user_id = :userId", { userId })
.orderBy("id", "DESC")
.skip(MAX_NICKNAME_ENTRIES_PER_USER)
.take(99_999)
.getMany();
if (toDelete.length > 0) {
await this.nicknameHistory.delete({
id: In(toDelete.map((v) => v.id)),
});
}
}
}
================================================
FILE: backend/src/data/GuildPersistedData.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { PersistedData } from "./entities/PersistedData.js";
export class GuildPersistedData extends BaseGuildRepository {
private persistedData: Repository<PersistedData>;
constructor(guildId) {
super(guildId);
this.persistedData = dataSource.getRepository(PersistedData);
}
async find(userId: string) {
return this.persistedData.findOne({
where: {
guild_id: this.guildId,
user_id: userId,
},
});
}
async set(userId: string, data: Partial<PersistedData> = {}) {
const existing = await this.find(userId);
if (existing) {
await this.persistedData.update(
{
guild_id: this.guildId,
user_id: userId,
},
data,
);
} else {
await this.persistedData.insert({
...data,
guild_id: this.guildId,
user_id: userId,
});
}
}
async clear(userId: string) {
await this.persistedData.delete({
guild_id: this.guildId,
user_id: userId,
});
}
}
================================================
FILE: backend/src/data/GuildPingableRoles.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { PingableRole } from "./entities/PingableRole.js";
export class GuildPingableRoles extends BaseGuildRepository {
private pingableRoles: Repository<PingableRole>;
constructor(guildId) {
super(guildId);
this.pingableRoles = dataSource.getRepository(PingableRole);
}
async all(): Promise<PingableRole[]> {
return this.pingableRoles.find({
where: {
guild_id: this.guildId,
},
});
}
async getForChannel(channelId: string): Promise<PingableRole[]> {
return this.pingableRoles.find({
where: {
guild_id: this.guildId,
channel_id: channelId,
},
});
}
async getByChannelAndRoleId(channelId: string, roleId: string): Promise<PingableRole | null> {
return this.pingableRoles.findOne({
where: {
guild_id: this.guildId,
channel_id: channelId,
role_id: roleId,
},
});
}
async delete(channelId: string, roleId: string) {
await this.pingableRoles.delete({
guild_id: this.guildId,
channel_id: channelId,
role_id: roleId,
});
}
async add(channelId: string, roleId: string) {
await this.pingableRoles.insert({
guild_id: this.guildId,
channel_id: channelId,
role_id: roleId,
});
}
}
================================================
FILE: backend/src/data/GuildReactionRoles.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { ReactionRole } from "./entities/ReactionRole.js";
export class GuildReactionRoles extends BaseGuildRepository {
private reactionRoles: Repository<ReactionRole>;
constructor(guildId) {
super(guildId);
this.reactionRoles = dataSource.getRepository(ReactionRole);
}
async all(): Promise<ReactionRole[]> {
return this.reactionRoles.find({
where: {
guild_id: this.guildId,
},
});
}
async getForMessage(messageId: string): Promise<ReactionRole[]> {
return this.reactionRoles.find({
where: {
guild_id: this.guildId,
message_id: messageId,
},
order: {
order: "ASC",
},
});
}
async getByMessageAndEmoji(messageId: string, emoji: string): Promise<ReactionRole | null> {
return this.reactionRoles.findOne({
where: {
guild_id: this.guildId,
message_id: messageId,
emoji,
},
});
}
async removeFromMessage(messageId: string, emoji?: string) {
const criteria: any = {
guild_id: this.guildId,
message_id: messageId,
};
if (emoji) {
criteria.emoji = emoji;
}
await this.reactionRoles.delete(criteria);
}
async add(
channelId: string,
messageId: string,
emoji: string,
roleId: string,
exclusive?: boolean,
position?: number,
) {
await this.reactionRoles.insert({
guild_id: this.guildId,
channel_id: channelId,
message_id: messageId,
emoji,
role_id: roleId,
is_exclusive: Boolean(exclusive),
order: position,
});
}
}
================================================
FILE: backend/src/data/GuildReminders.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { Reminder } from "./entities/Reminder.js";
export class GuildReminders extends BaseGuildRepository {
private reminders: Repository<Reminder>;
constructor(guildId) {
super(guildId);
this.reminders = dataSource.getRepository(Reminder);
}
async getDueReminders(): Promise<Reminder[]> {
return this.reminders
.createQueryBuilder()
.where("guild_id = :guildId", { guildId: this.guildId })
.andWhere("remind_at <= NOW()")
.getMany();
}
async getRemindersByUserId(userId: string): Promise<Reminder[]> {
return this.reminders.find({
where: {
guild_id: this.guildId,
user_id: userId,
},
});
}
find(id: number) {
return this.reminders.findOne({
where: { id },
});
}
async delete(id) {
await this.reminders.delete({
guild_id: this.guildId,
id,
});
}
async add(userId: string, channelId: string, remindAt: string, body: string, created_at: string) {
const result = await this.reminders.insert({
guild_id: this.guildId,
user_id: userId,
channel_id: channelId,
remind_at: remindAt,
body,
created_at,
});
return (await this.find(result.identifiers[0].id))!;
}
}
================================================
FILE: backend/src/data/GuildRoleButtons.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { RoleButtonsItem } from "./entities/RoleButtonsItem.js";
export class GuildRoleButtons extends BaseGuildRepository {
private roleButtons: Repository<RoleButtonsItem>;
constructor(guildId) {
super(guildId);
this.roleButtons = dataSource.getRepository(RoleButtonsItem);
}
getSavedRoleButtons(): Promise<RoleButtonsItem[]> {
return this.roleButtons.find({
where: {
guild_id: this.guildId,
},
});
}
async deleteRoleButtonItem(name: string): Promise<void> {
await this.roleButtons.delete({
guild_id: this.guildId,
name,
});
}
async saveRoleButtonItem(name: string, channelId: string, messageId: string, hash: string): Promise<void> {
await this.roleButtons.insert({
guild_id: this.guildId,
name,
channel_id: channelId,
message_id: messageId,
hash,
});
}
}
================================================
FILE: backend/src/data/GuildRoleQueue.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { RoleQueueItem } from "./entities/RoleQueueItem.js";
export class GuildRoleQueue extends BaseGuildRepository {
private roleQueue: Repository<RoleQueueItem>;
constructor(guildId) {
super(guildId);
this.roleQueue = dataSource.getRepository(RoleQueueItem);
}
consumeNextRoleAssignments(count: number): Promise<RoleQueueItem[]> {
return dataSource.transaction(async (entityManager) => {
const repository = entityManager.getRepository(RoleQueueItem);
const nextAssignments = await repository
.createQueryBuilder()
.where("guild_id = :guildId", { guildId: this.guildId })
.addOrderBy("priority", "DESC")
.addOrderBy("id", "ASC")
.take(count)
.getMany();
if (nextAssignments.length > 0) {
const ids = nextAssignments.map((assignment) => assignment.id);
await repository.createQueryBuilder().where("id IN (:ids)", { ids }).delete().execute();
}
return nextAssignments;
});
}
async addQueueItem(userId: string, roleId: string, shouldAdd: boolean, priority = 0) {
await this.roleQueue.insert({
guild_id: this.guildId,
user_id: userId,
role_id: roleId,
should_add: shouldAdd,
priority,
});
}
}
================================================
FILE: backend/src/data/GuildSavedMessages.ts
================================================
import { GuildChannel, Message } from "discord.js";
import moment from "moment-timezone";
import { Repository } from "typeorm";
import { QueuedEventEmitter } from "../QueuedEventEmitter.js";
import { noop } from "../utils.js";
import { asyncMap } from "../utils/async.js";
import { decryptJson, encryptJson } from "../utils/cryptHelpers.js";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { buildEntity } from "./buildEntity.js";
import { dataSource } from "./dataSource.js";
import { ISavedMessageData, SavedMessage } from "./entities/SavedMessage.js";
export class GuildSavedMessages extends BaseGuildRepository<SavedMessage> {
private messages: Repository<SavedMessage>;
protected toBePermanent: Set<string>;
public events: QueuedEventEmitter;
constructor(guildId) {
super(guildId);
this.messages = dataSource.getRepository(SavedMessage);
this.events = new QueuedEventEmitter();
this.toBePermanent = new Set();
}
protected msgToSavedMessageData(msg: Message): ISavedMessageData {
const data: ISavedMessageData = {
author: {
username: msg.author.username,
discriminator: msg.author.discriminator,
},
content: msg.content,
timestamp: msg.createdTimestamp,
};
if (msg.attachments.size) {
data.attachments = Array.from(msg.attachments.values()).map((att) => ({
id: att.id,
contentType: att.contentType,
name: att.name,
proxyURL: att.proxyURL,
size: att.size,
spoiler: att.spoiler,
url: att.url,
width: att.width,
}));
}
if (msg.embeds.length) {
data.embeds = msg.embeds.map((embed) => ({
title: embed.title,
description: embed.description,
url: embed.url,
timestamp: embed.timestamp ? Date.parse(embed.timestamp) : null,
color: embed.color,
fields: embed.fields.map((field) => ({
name: field.name,
value: field.value,
inline: field.inline ?? false,
})),
author: embed.author
? {
name: embed.author.name,
url: embed.author.url,
iconURL: embed.author.iconURL,
proxyIconURL: embed.author.proxyIconURL,
}
: undefined,
thumbnail: embed.thumbnail
? {
url: embed.thumbnail.url,
proxyURL: embed.thumbnail.proxyURL,
height: embed.thumbnail.height,
width: embed.thumbnail.width,
}
: undefined,
image: embed.image
? {
url: embed.image.url,
proxyURL: embed.image.proxyURL,
height: embed.image.height,
width: embed.image.width,
}
: undefined,
video: embed.video
? {
url: embed.video.url,
proxyURL: embed.video.proxyURL,
height: embed.video.height,
width: embed.video.width,
}
: undefined,
footer: embed.footer
? {
text: embed.footer.text,
iconURL: embed.footer.iconURL,
proxyIconURL: embed.footer.proxyIconURL,
}
: undefined,
}));
}
if (msg.stickers?.size) {
data.stickers = Array.from(msg.stickers.values()).map((sticker) => ({
format: sticker.format,
guildId: sticker.guildId,
id: sticker.id,
name: sticker.name,
description: sticker.description,
available: sticker.available,
type: sticker.type,
}));
}
if (msg.reference && (msg.reference.messageId || msg.reference.channelId || msg.reference.guildId)) {
data.reference = {
messageId: msg.reference.messageId ?? null,
channelId: msg.reference.channelId ?? null,
guildId: msg.reference.guildId ?? null,
};
}
return data;
}
protected async _processEntityFromDB(entity: SavedMessage | undefined) {
if (entity == null) {
return entity;
}
entity.data = (await decryptJson(entity.data as unknown as string)) as ISavedMessageData;
return entity;
}
protected async _processEntityToDB(entity: Partial<SavedMessage>) {
if (entity.data) {
entity.data = (await encryptJson(entity.data)) as any;
}
return entity;
}
async find(id: string, includeDeleted = false): Promise<SavedMessage | null> {
let query = this.messages
.createQueryBuilder()
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("id = :id", { id });
if (!includeDeleted) {
query = query.andWhere("deleted_at IS NULL");
}
const result = await query.getOne();
return this.processEntityFromDB(result);
}
async getUserMessagesByChannelAfterId(userId, channelId, afterId, limit?: number): Promise<SavedMessage[]> {
let query = this.messages
.createQueryBuilder()
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("channel_id = :channel_id", { channel_id: channelId })
.andWhere("user_id = :user_id", { user_id: userId })
.andWhere("id > :afterId", { afterId })
.andWhere("deleted_at IS NULL");
if (limit != null) {
query = query.limit(limit);
}
const results = await query.getMany();
return this.processMultipleEntitiesFromDB(results);
}
async getMultiple(messageIds: string[]): Promise<SavedMessage[]> {
if (messageIds.length === 0) {
return [];
}
const results = await this.messages
.createQueryBuilder()
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("id IN (:messageIds)", { messageIds })
.getMany();
return this.processMultipleEntitiesFromDB(results);
}
async createFromMsg(msg: Message, overrides = {}): Promise<void> {
// FIXME: Hotfix
if (!msg.channel) {
return;
}
// Don't actually save bot messages. Just pass them through as if they were saved.
if (msg.author.bot) {
const fakeSavedMessage = buildEntity(SavedMessage, await this.msgToInsertReadyEntity(msg));
this.fireCreateEvents(fakeSavedMessage);
return;
}
await this.createFromMessages([msg], overrides);
}
async createFromMessages(messages: Message[], overrides = {}): Promise<void> {
const items = await asyncMap(messages, async (msg) => ({
...(await this.msgToInsertReadyEntity(msg)),
...overrides,
}));
await this.insertBulk(items);
}
protected async msgToInsertReadyEntity(msg: Message): Promise<Partial<SavedMessage>> {
const savedMessageData = this.msgToSavedMessageData(msg);
const postedAt = moment.utc(msg.createdTimestamp, "x").format("YYYY-MM-DD HH:mm:ss");
return {
id: msg.id,
guild_id: (msg.channel as GuildChannel).guild.id,
channel_id: msg.channel.id,
user_id: msg.author.id,
is_bot: msg.author.bot,
data: savedMessageData,
posted_at: postedAt,
};
}
protected async insertBulk(items: Array<Partial<SavedMessage>>): Promise<void> {
for (const item of items) {
if (this.toBePermanent.has(item.id!)) {
item.is_permanent = true;
this.toBePermanent.delete(item.id!);
}
}
const itemsToInsert = await asyncMap(items, (item) => this.processEntityToDB({ ...item }));
await this.messages.createQueryBuilder().insert().values(itemsToInsert).execute().catch(noop);
for (const item of items) {
// perf: save a db lookup and message content decryption by building the entity manually
const inserted = buildEntity(SavedMessage, item);
this.fireCreateEvents(inserted);
}
}
protected async fireCreateEvents(message: SavedMessage) {
this.events.emit("create", [message]);
this.events.emit(`create:${message.id}`, [message]);
}
async markAsDeleted(id): Promise<void> {
await this.messages
.createQueryBuilder("messages")
.update()
.set({
deleted_at: () => "NOW(3)",
})
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("id = :id", { id })
.execute();
const deleted = await this.find(id, true);
if (deleted) {
this.events.emit("delete", [deleted]);
this.events.emit(`delete:${id}`, [deleted]);
}
}
/**
* Marks the specified messages as deleted in the database (if they weren't already marked before).
* If any messages were marked as deleted, also emits the deleteBulk event.
*/
async markBulkAsDeleted(ids) {
const deletedAt = moment.utc().format("YYYY-MM-DD HH:mm:ss");
await this.messages
.createQueryBuilder()
.update()
.set({ deleted_at: deletedAt })
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("id IN (:ids)", { ids })
.andWhere("deleted_at IS NULL")
.execute();
let deleted = await this.messages
.createQueryBuilder()
.where("id IN (:ids)", { ids })
.andWhere("deleted_at = :deletedAt", { deletedAt })
.getMany();
deleted = await this.processMultipleEntitiesFromDB(deleted);
if (deleted.length) {
this.events.emit("deleteBulk", [deleted]);
}
}
async saveEdit(id, newData: ISavedMessageData): Promise<void> {
const oldMessage = await this.find(id);
if (!oldMessage) return;
const newMessage = { ...oldMessage, data: newData };
// @ts-ignore
const updateData = await this.processEntityToDB({
data: newData,
});
await this.messages.update({ id }, updateData);
this.events.emit("update", [newMessage, oldMessage]);
this.events.emit(`update:${id}`, [newMessage, oldMessage]);
}
async saveEditFromMsg(msg: Message): Promise<void> {
const newData = this.msgToSavedMessageData(msg);
await this.saveEdit(msg.id, newData);
}
async setPermanent(id: string): Promise<void> {
const savedMsg = await this.find(id);
if (savedMsg) {
await this.messages.update(
{ id },
{
is_permanent: true,
},
);
} else {
this.toBePermanent.add(id);
}
}
async onceMessageAvailable(
id: string,
handler: (msg?: SavedMessage) => any,
timeout: number = 60 * 1000,
): Promise<void> {
let called = false;
let onceEventListener;
let timeoutFn;
const callHandler = async (msg?: SavedMessage) => {
this.events.off(`create:${id}`, onceEventListener);
clearTimeout(timeoutFn);
if (called) return;
called = true;
await handler(msg);
};
onceEventListener = this.events.once(`create:${id}`, callHandler);
timeoutFn = setTimeout(() => {
called = true;
callHandler(undefined);
}, timeout);
const messageInDB = await this.find(id);
if (messageInDB) {
callHandler(messageInDB);
}
}
}
================================================
FILE: backend/src/data/GuildScheduledPosts.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { ScheduledPost } from "./entities/ScheduledPost.js";
export class GuildScheduledPosts extends BaseGuildRepository {
private scheduledPosts: Repository<ScheduledPost>;
constructor(guildId) {
super(guildId);
this.scheduledPosts = dataSource.getRepository(ScheduledPost);
}
all(): Promise<ScheduledPost[]> {
return this.scheduledPosts.createQueryBuilder().where("guild_id = :guildId", { guildId: this.guildId }).getMany();
}
getDueScheduledPosts(): Promise<ScheduledPost[]> {
return this.scheduledPosts
.createQueryBuilder()
.where("guild_id = :guildId", { guildId: this.guildId })
.andWhere("post_at <= NOW()")
.getMany();
}
find(id: number) {
return this.scheduledPosts.findOne({
where: {
id,
},
});
}
async delete(id) {
await this.scheduledPosts.delete({
guild_id: this.guildId,
id,
});
}
async create(data: Partial<ScheduledPost>) {
const result = await this.scheduledPosts.insert({
...data,
guild_id: this.guildId,
});
return (await this.find(result.identifiers[0].id))!;
}
async update(id: number, data: Partial<ScheduledPost>) {
await this.scheduledPosts.update(id, data);
}
}
================================================
FILE: backend/src/data/GuildSlowmodes.ts
================================================
import moment from "moment-timezone";
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { SlowmodeChannel } from "./entities/SlowmodeChannel.js";
import { SlowmodeUser } from "./entities/SlowmodeUser.js";
export class GuildSlowmodes extends BaseGuildRepository {
private slowmodeChannels: Repository<SlowmodeChannel>;
private slowmodeUsers: Repository<SlowmodeUser>;
constructor(guildId) {
super(guildId);
this.slowmodeChannels = dataSource.getRepository(SlowmodeChannel);
this.slowmodeUsers = dataSource.getRepository(SlowmodeUser);
}
async getChannelSlowmode(channelId): Promise<SlowmodeChannel | null> {
return this.slowmodeChannels.findOne({
where: {
guild_id: this.guildId,
channel_id: channelId,
},
});
}
async setChannelSlowmode(channelId, seconds): Promise<void> {
const existingSlowmode = await this.getChannelSlowmode(channelId);
if (existingSlowmode) {
await this.slowmodeChannels.update(
{
guild_id: this.guildId,
channel_id: channelId,
},
{
slowmode_seconds: seconds,
},
);
} else {
await this.slowmodeChannels.insert({
guild_id: this.guildId,
channel_id: channelId,
slowmode_seconds: seconds,
});
}
}
async deleteChannelSlowmode(channelId): Promise<void> {
await this.slowmodeChannels.delete({
guild_id: this.guildId,
channel_id: channelId,
});
}
async getChannelSlowmodeUser(channelId, userId): Promise<SlowmodeUser | null> {
return this.slowmodeUsers.findOne({
where: {
guild_id: this.guildId,
channel_id: channelId,
user_id: userId,
},
});
}
async userHasSlowmode(channelId, userId): Promise<boolean> {
return (await this.getChannelSlowmodeUser(channelId, userId)) != null;
}
async addSlowmodeUser(channelId, userId): Promise<void> {
const slowmode = await this.getChannelSlowmode(channelId);
if (!slowmode) return;
const expiresAt = moment.utc().add(slowmode.slowmode_seconds, "seconds").format("YYYY-MM-DD HH:mm:ss");
if (await this.userHasSlowmode(channelId, userId)) {
// Update existing
await this.slowmodeUsers.update(
{
guild_id: this.guildId,
channel_id: channelId,
user_id: userId,
},
{
expires_at: expiresAt,
},
);
} else {
// Add new
await this.slowmodeUsers.insert({
guild_id: this.guildId,
channel_id: channelId,
user_id: userId,
expires_at: expiresAt,
});
}
}
async clearSlowmodeUser(channelId, userId): Promise<void> {
await this.slowmodeUsers.delete({
guild_id: this.guildId,
channel_id: channelId,
user_id: userId,
});
}
async getChannelSlowmodeUsers(channelId): Promise<SlowmodeUser[]> {
return this.slowmodeUsers.find({
where: {
guild_id: this.guildId,
channel_id: channelId,
},
});
}
async getExpiredSlowmodeUsers(): Promise<SlowmodeUser[]> {
return this.slowmodeUsers
.createQueryBuilder()
.where("guild_id = :guildId", { guildId: this.guildId })
.andWhere("expires_at <= NOW()")
.getMany();
}
}
================================================
FILE: backend/src/data/GuildStarboardMessages.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { StarboardMessage } from "./entities/StarboardMessage.js";
export class GuildStarboardMessages extends BaseGuildRepository {
private allStarboardMessages: Repository<StarboardMessage>;
constructor(guildId) {
super(guildId);
this.allStarboardMessages = dataSource.getRepository(StarboardMessage);
}
async getStarboardMessagesForMessageId(messageId: string) {
return this.allStarboardMessages
.createQueryBuilder()
.where("guild_id = :gid", { gid: this.guildId })
.andWhere("message_id = :msgid", { msgid: messageId })
.getMany();
}
async getStarboardMessagesForStarboardMessageId(starboardMessageId: string) {
return this.allStarboardMessages
.createQueryBuilder()
.where("guild_id = :gid", { gid: this.guildId })
.andWhere("starboard_message_id = :messageId", { messageId: starboardMessageId })
.getMany();
}
async getMatchingStarboardMessages(starboardChannelId: string, sourceMessageId: string) {
return this.allStarboardMessages
.createQueryBuilder()
.where("guild_id = :guildId", { guildId: this.guildId })
.andWhere("message_id = :msgId", { msgId: sourceMessageId })
.andWhere("starboard_channel_id = :channelId", { channelId: starboardChannelId })
.getMany();
}
async createStarboardMessage(starboardId: string, messageId: string, starboardMessageId: string) {
await this.allStarboardMessages.insert({
message_id: messageId,
starboard_message_id: starboardMessageId,
starboard_channel_id: starboardId,
guild_id: this.guildId,
});
}
async deleteStarboardMessage(starboardMessageId: string, starboardChannelId: string) {
await this.allStarboardMessages.delete({
guild_id: this.guildId,
starboard_message_id: starboardMessageId,
starboard_channel_id: starboardChannelId,
});
}
}
================================================
FILE: backend/src/data/GuildStarboardReactions.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { StarboardReaction } from "./entities/StarboardReaction.js";
export class GuildStarboardReactions extends BaseGuildRepository {
private allStarboardReactions: Repository<StarboardReaction>;
constructor(guildId) {
super(guildId);
this.allStarboardReactions = dataSource.getRepository(StarboardReaction);
}
async getAllReactionsForMessageId(messageId: string) {
return this.allStarboardReactions
.createQueryBuilder()
.where("guild_id = :gid", { gid: this.guildId })
.andWhere("message_id = :msgid", { msgid: messageId })
.getMany();
}
async createStarboardReaction(messageId: string, reactorId: string) {
const existingReaction = await this.allStarboardReactions.findOne({
where: {
guild_id: this.guildId,
message_id: messageId,
reactor_id: reactorId,
},
});
if (existingReaction) {
return;
}
await this.allStarboardReactions.insert({
guild_id: this.guildId,
message_id: messageId,
reactor_id: reactorId,
});
}
async deleteAllStarboardReactionsForMessageId(messageId: string) {
await this.allStarboardReactions.delete({
guild_id: this.guildId,
message_id: messageId,
});
}
async deleteStarboardReaction(messageId: string, reactorId: string) {
await this.allStarboardReactions.delete({
guild_id: this.guildId,
reactor_id: reactorId,
message_id: messageId,
});
}
}
================================================
FILE: backend/src/data/GuildStats.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { StatValue } from "./entities/StatValue.js";
export class GuildStats extends BaseGuildRepository {
private stats: Repository<StatValue>;
constructor(guildId) {
super(guildId);
this.stats = dataSource.getRepository(StatValue);
}
async saveValue(source: string, key: string, value: number): Promise<void> {
await this.stats.insert({
guild_id: this.guildId,
source,
key,
value,
});
}
async deleteOldValues(source: string, cutoff: string): Promise<void> {
await this.stats
.createQueryBuilder()
.where("source = :source", { source })
.andWhere("created_at < :cutoff", { cutoff })
.delete();
}
}
================================================
FILE: backend/src/data/GuildTags.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { Tag } from "./entities/Tag.js";
import { TagResponse } from "./entities/TagResponse.js";
export class GuildTags extends BaseGuildRepository {
private tags: Repository<Tag>;
private tagResponses: Repository<TagResponse>;
constructor(guildId) {
super(guildId);
this.tags = dataSource.getRepository(Tag);
this.tagResponses = dataSource.getRepository(TagResponse);
}
async all(): Promise<Tag[]> {
return this.tags.find({
where: {
guild_id: this.guildId,
},
});
}
async find(tag): Promise<Tag | null> {
return this.tags.findOne({
where: {
guild_id: this.guildId,
tag,
},
});
}
async createOrUpdate(tag, body, userId) {
const existingTag = await this.find(tag);
if (existingTag) {
await this.tags
.createQueryBuilder()
.update()
.set({
body,
user_id: userId,
created_at: () => "NOW()",
})
.where("guild_id = :guildId", { guildId: this.guildId })
.andWhere("tag = :tag", { tag })
.execute();
} else {
await this.tags.insert({
guild_id: this.guildId,
user_id: userId,
tag,
body,
});
}
}
async delete(tag) {
await this.tags.delete({
guild_id: this.guildId,
tag,
});
}
async findResponseByCommandMessageId(messageId: string): Promise<TagResponse | null> {
return this.tagResponses.findOne({
where: {
guild_id: this.guildId,
command_message_id: messageId,
},
});
}
async findResponseByResponseMessageId(messageId: string): Promise<TagResponse | null> {
return this.tagResponses.findOne({
where: {
guild_id: this.guildId,
response_message_id: messageId,
},
});
}
async addResponse(cmdMessageId, responseMessageId) {
await this.tagResponses.insert({
guild_id: this.guildId,
command_message_id: cmdMessageId,
response_message_id: responseMessageId,
});
}
async deleteResponseByCommandMessageId(messageId: string): Promise<void> {
await this.tagResponses.delete({
guild_id: this.guildId,
command_message_id: messageId,
});
}
async deleteResponseByResponseMessageId(messageId: string): Promise<void> {
await this.tagResponses.delete({
guild_id: this.guildId,
response_message_id: messageId,
});
}
}
================================================
FILE: backend/src/data/GuildTempbans.ts
================================================
import moment from "moment-timezone";
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { Tempban } from "./entities/Tempban.js";
export class GuildTempbans extends BaseGuildRepository {
private tempbans: Repository<Tempban>;
constructor(guildId) {
super(guildId);
this.tempbans = dataSource.getRepository(Tempban);
}
async getExpiredTempbans(): Promise<Tempban[]> {
return this.tempbans
.createQueryBuilder("mutes")
.where("guild_id = :guild_id", { guild_id: this.guildId })
.andWhere("expires_at IS NOT NULL")
.andWhere("expires_at <= NOW()")
.getMany();
}
async findExistingTempbanForUserId(userId: string): Promise<Tempban | null> {
return this.tempbans.findOne({
where: {
guild_id: this.guildId,
user_id: userId,
},
});
}
async addTempban(userId, expiryTime, modId): Promise<Tempban> {
const expiresAt = moment.utc().add(expiryTime, "ms").format("YYYY-MM-DD HH:mm:ss");
const result = await this.tempbans.insert({
guild_id: this.guildId,
user_id: userId,
mod_id: modId,
expires_at: expiresAt,
created_at: moment.utc().format("YYYY-MM-DD HH:mm:ss"),
});
return (await this.tempbans.findOne({ where: result.identifiers[0] }))!;
}
async updateExpiryTime(userId, newExpiryTime, modId) {
const expiresAt = moment.utc().add(newExpiryTime, "ms").format("YYYY-MM-DD HH:mm:ss");
return this.tempbans.update(
{
guild_id: this.guildId,
user_id: userId,
},
{
created_at: moment.utc().format("YYYY-MM-DD HH:mm:ss"),
expires_at: expiresAt,
mod_id: modId,
},
);
}
async clear(userId) {
await this.tempbans.delete({
guild_id: this.guildId,
user_id: userId,
});
}
}
================================================
FILE: backend/src/data/GuildVCAlerts.ts
================================================
import { Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository.js";
import { dataSource } from "./dataSource.js";
import { VCAlert } from "./entities/VCAlert.js";
export class GuildVCAlerts extends BaseGuildRepository {
private allAlerts: Repository<VCAlert>;
constructor(guildId) {
super(guildId);
this.allAlerts = dataSource.getRepository(VCAlert);
}
async getOutdatedAlerts(): Promise<VCAlert[]> {
return this.allAlerts
.createQueryBuilder()
.where("guild_id = :guildId", { guildId: this.guildId })
.andWhere("expires_at <= NOW()")
.getMany();
}
async getAllGuildAlerts(): Promise<VCAlert[]> {
return this.allAlerts.createQueryBuilder().where("guild_id = :guildId", { guildId: this.guildId }).getMany();
}
async getAlertsByUserId(userId: string): Promise<VCAlert[]> {
return this.allAlerts.find({
where: {
guild_id: this.guildId,
user_id: userId,
},
});
}
async getAlertsByRequestorId(requestorId: string): Promise<VCAlert[]> {
return this.allAlerts.find({
where: {
guild_id: this.guildId,
requestor_id: requestorId,
},
});
}
find(id: number) {
return this.allAlerts.findOne({
where: { id },
});
}
async delete(id) {
await this.allAlerts.delete({
guild_id: this.guildId,
id,
});
}
async add(requestorId: string, userId: string, channelId: string, expiresAt: string, body: string, active: boolean) {
const result = await this.allAlerts.insert({
guild_id: this.guildId,
requestor_id: requestorId,
user_id: userId,
channel_id: channelId,
expires_at: expiresAt,
body,
active,
});
return (await this.find(result.identifiers[0].id))!;
}
}
================================================
FILE: backend/src/data/LogType.ts
================================================
export const LogType = {
MEMBER_WARN: "MEMBER_WARN",
MEMBER_MUTE: "MEMBER_MUTE",
MEMBER_UNMUTE: "MEMBER_UNMUTE",
MEMBER_MUTE_EXPIRED: "MEMBER_MUTE_EXPIRED",
MEMBER_KICK: "MEMBER_KICK",
MEMBER_BAN: "MEM
gitextract_rchn42w_/ ├── .clabot ├── .cursorignore ├── .devcontainer/ │ └── devcontainer.json ├── .dockerignore ├── .editorconfig ├── .eslintrc.js ├── .gitattributes ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ └── codequality.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── AGENTS.md ├── DEVELOPMENT.md ├── Dockerfile ├── LICENSE.md ├── MANAGEMENT.md ├── PRODUCTION.md ├── README.md ├── assets/ │ └── icons/ │ ├── LICENSE │ └── case_icons.afphoto ├── backend/ │ ├── .gitignore │ ├── .prettierignore │ ├── package.json │ ├── register-tsconfig-paths.js │ ├── src/ │ │ ├── Blocker.ts │ │ ├── DiscordJSError.ts │ │ ├── Queue.ts │ │ ├── QueuedEventEmitter.ts │ │ ├── RecoverablePluginError.ts │ │ ├── RegExpRunner.ts │ │ ├── SimpleCache.ts │ │ ├── SimpleError.ts │ │ ├── api/ │ │ │ ├── archives.ts │ │ │ ├── auth.ts │ │ │ ├── docs.ts │ │ │ ├── guilds/ │ │ │ │ ├── importExport.ts │ │ │ │ ├── index.ts │ │ │ │ └── misc.ts │ │ │ ├── guilds.ts │ │ │ ├── index.ts │ │ │ ├── permissions.ts │ │ │ ├── rateLimits.ts │ │ │ ├── responses.ts │ │ │ ├── staff.ts │ │ │ ├── start.ts │ │ │ └── tasks.ts │ │ ├── commandTypes.ts │ │ ├── configValidator.ts │ │ ├── data/ │ │ │ ├── AllowedGuilds.ts │ │ │ ├── ApiAuditLog.ts │ │ │ ├── ApiLogins.ts │ │ │ ├── ApiPermissionAssignments.ts │ │ │ ├── ApiUserInfo.ts │ │ │ ├── Archives.ts │ │ │ ├── BaseGuildRepository.ts │ │ │ ├── BaseRepository.ts │ │ │ ├── CaseTypes.ts │ │ │ ├── Configs.ts │ │ │ ├── DefaultLogMessages.json │ │ │ ├── FishFish.ts │ │ │ ├── GuildAntiraidLevels.ts │ │ │ ├── GuildArchives.ts │ │ │ ├── GuildAutoReactions.ts │ │ │ ├── GuildButtonRoles.ts │ │ │ ├── GuildCases.ts │ │ │ ├── GuildContextMenuLinks.ts │ │ │ ├── GuildCounters.ts │ │ │ ├── GuildEvents.ts │ │ │ ├── GuildLogs.ts │ │ │ ├── GuildMemberCache.ts │ │ │ ├── GuildMemberTimezones.ts │ │ │ ├── GuildMutes.ts │ │ │ ├── GuildNicknameHistory.ts │ │ │ ├── GuildPersistedData.ts │ │ │ ├── GuildPingableRoles.ts │ │ │ ├── GuildReactionRoles.ts │ │ │ ├── GuildReminders.ts │ │ │ ├── GuildRoleButtons.ts │ │ │ ├── GuildRoleQueue.ts │ │ │ ├── GuildSavedMessages.ts │ │ │ ├── GuildScheduledPosts.ts │ │ │ ├── GuildSlowmodes.ts │ │ │ ├── GuildStarboardMessages.ts │ │ │ ├── GuildStarboardReactions.ts │ │ │ ├── GuildStats.ts │ │ │ ├── GuildTags.ts │ │ │ ├── GuildTempbans.ts │ │ │ ├── GuildVCAlerts.ts │ │ │ ├── LogType.ts │ │ │ ├── MemberCache.ts │ │ │ ├── MuteTypes.ts │ │ │ ├── Mutes.ts │ │ │ ├── Reminders.ts │ │ │ ├── ScheduledPosts.ts │ │ │ ├── Supporters.ts │ │ │ ├── Tempbans.ts │ │ │ ├── UsernameHistory.ts │ │ │ ├── VCAlerts.ts │ │ │ ├── Webhooks.ts │ │ │ ├── Zalgo.ts │ │ │ ├── apiAuditLogTypes.ts │ │ │ ├── buildEntity.ts │ │ │ ├── cleanup/ │ │ │ │ ├── configs.ts │ │ │ │ ├── messages.ts │ │ │ │ ├── nicknames.ts │ │ │ │ └── usernames.ts │ │ │ ├── dataSource.ts │ │ │ ├── db.ts │ │ │ ├── entities/ │ │ │ │ ├── AllowedGuild.ts │ │ │ │ ├── AntiraidLevel.ts │ │ │ │ ├── ApiAuditLogEntry.ts │ │ │ │ ├── ApiLogin.ts │ │ │ │ ├── ApiPermissionAssignment.ts │ │ │ │ ├── ApiUserInfo.ts │ │ │ │ ├── ArchiveEntry.ts │ │ │ │ ├── AutoReaction.ts │ │ │ │ ├── ButtonRole.ts │ │ │ │ ├── Case.ts │ │ │ │ ├── CaseNote.ts │ │ │ │ ├── Config.ts │ │ │ │ ├── ContextMenuLink.ts │ │ │ │ ├── Counter.ts │ │ │ │ ├── CounterTrigger.ts │ │ │ │ ├── CounterTriggerState.ts │ │ │ │ ├── CounterValue.ts │ │ │ │ ├── MemberCacheItem.ts │ │ │ │ ├── MemberTimezone.ts │ │ │ │ ├── Mute.ts │ │ │ │ ├── NicknameHistoryEntry.ts │ │ │ │ ├── PersistedData.ts │ │ │ │ ├── PingableRole.ts │ │ │ │ ├── ReactionRole.ts │ │ │ │ ├── Reminder.ts │ │ │ │ ├── RoleButtonsItem.ts │ │ │ │ ├── RoleQueueItem.ts │ │ │ │ ├── SavedMessage.ts │ │ │ │ ├── ScheduledPost.ts │ │ │ │ ├── SlowmodeChannel.ts │ │ │ │ ├── SlowmodeUser.ts │ │ │ │ ├── StarboardMessage.ts │ │ │ │ ├── StarboardReaction.ts │ │ │ │ ├── StatValue.ts │ │ │ │ ├── Supporter.ts │ │ │ │ ├── Tag.ts │ │ │ │ ├── TagResponse.ts │ │ │ │ ├── Tempban.ts │ │ │ │ ├── UsernameHistoryEntry.ts │ │ │ │ ├── VCAlert.ts │ │ │ │ └── Webhook.ts │ │ │ ├── getChannelIdFromMessageId.ts │ │ │ ├── loops/ │ │ │ │ ├── expiredArchiveDeletionLoop.ts │ │ │ │ ├── expiredMemberCacheDeletionLoop.ts │ │ │ │ ├── expiringMutesLoop.ts │ │ │ │ ├── expiringTempbansLoop.ts │ │ │ │ ├── expiringVCAlertsLoop.ts │ │ │ │ ├── memberCacheDeletionLoop.ts │ │ │ │ ├── savedMessageCleanupLoop.ts │ │ │ │ ├── upcomingRemindersLoop.ts │ │ │ │ └── upcomingScheduledPostsLoop.ts │ │ │ ├── queryLogger.ts │ │ │ └── redis.ts │ │ ├── debugCounters.ts │ │ ├── env.ts │ │ ├── exportSchemas.ts │ │ ├── globals.ts │ │ ├── humanizeDuration.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── migrateConfigsToDB.ts │ │ ├── migrations/ │ │ │ ├── 1540519249973-CreatePreTypeORMTables.ts │ │ │ ├── 1543053430712-CreateMessagesTable.ts │ │ │ ├── 1544877081073-CreateSlowmodeTables.ts │ │ │ ├── 1544887946307-CreateStarboardTable.ts │ │ │ ├── 1546770935261-CreateTagResponsesTable.ts │ │ │ ├── 1546778415930-CreateNameHistoryTable.ts │ │ │ ├── 1546788508314-MakeNameHistoryValueLengthLonger.ts │ │ │ ├── 1547290549908-CreateAutoReactionsTable.ts │ │ │ ├── 1547293464842-CreatePingableRolesTable.ts │ │ │ ├── 1547392046629-AddIndexToArchivesExpiresAt.ts │ │ │ ├── 1547393619900-AddIsHiddenToCases.ts │ │ │ ├── 1549649586803-AddPPFieldsToCases.ts │ │ │ ├── 1550409894008-FixEmojiIndexInReactionRoles.ts │ │ │ ├── 1550521627877-CreateSelfGrantableRolesTable.ts │ │ │ ├── 1550609900261-CreateRemindersTable.ts │ │ │ ├── 1556908589679-CreateUsernameHistoryTable.ts │ │ │ ├── 1556909512501-MigrateUsernamesToNewHistoryTable.ts │ │ │ ├── 1556913287547-TurnNameHistoryToNicknameHistory.ts │ │ │ ├── 1556973844545-CreateScheduledPostsTable.ts │ │ │ ├── 1558804433320-CreateDashboardLoginsTable.ts │ │ │ ├── 1558804449510-CreateDashboardUsersTable.ts │ │ │ ├── 1561111990357-CreateConfigsTable.ts │ │ │ ├── 1561117545258-CreateAllowedGuildsTable.ts │ │ │ ├── 1561282151982-RenameBackendDashboardStuffToAPI.ts │ │ │ ├── 1561282552734-RenameAllowedGuildGuildIdToId.ts │ │ │ ├── 1561282950483-CreateApiUserInfoTable.ts │ │ │ ├── 1561283165823-RenameApiUsersToApiPermissions.ts │ │ │ ├── 1561283405201-DropUserDataFromLoginsAndPermissions.ts │ │ │ ├── 1561391921385-AddVCAlertTable.ts │ │ │ ├── 1562838838927-AddMoreIndicesToVCAlerts.ts │ │ │ ├── 1573158035867-AddTypeAndPermissionsToApiPermissions.ts │ │ │ ├── 1573248462469-MoveStarboardsToConfig.ts │ │ │ ├── 1573248794313-CreateStarboardReactionsTable.ts │ │ │ ├── 1575145703039-AddIsExclusiveToReactionRoles.ts │ │ │ ├── 1575199835233-CreateStatsTable.ts │ │ │ ├── 1575230079526-AddRepeatColumnsToScheduledPosts.ts │ │ │ ├── 1578445483917-CreateReminderCreatedAtField.ts │ │ │ ├── 1580038836906-CreateAntiraidLevelsTable.ts │ │ │ ├── 1580654617890-AddActiveFollowsToLocateUser.ts │ │ │ ├── 1590616691907-CreateSupportersTable.ts │ │ │ ├── 1591036185142-OptimizeMessageIndices.ts │ │ │ ├── 1591038041635-OptimizeMessageTimestamps.ts │ │ │ ├── 1596994103885-AddCaseNotesForeignKey.ts │ │ │ ├── 1597015567215-AddLogMessageIdToCases.ts │ │ │ ├── 1597109357201-CreateMemberTimezonesTable.ts │ │ │ ├── 1600283341726-EncryptExistingMessages.ts │ │ │ ├── 1600285077890-EncryptArchives.ts │ │ │ ├── 1608608903570-CreateRestoredRolesColumn.ts │ │ │ ├── 1608692857722-FixStarboardReactionsIndices.ts │ │ │ ├── 1608753440716-CreateTempBansTable.ts │ │ │ ├── 1612010765767-CreateCounterTables.ts │ │ │ ├── 1617363975046-UpdateCounterTriggers.ts │ │ │ ├── 1622939525343-OrderReactionRoles.ts │ │ │ ├── 1623018101018-CreateButtonRolesTable.ts │ │ │ ├── 1628809879962-CreateContextMenuTable.ts │ │ │ ├── 1630837386329-AddExpiresAtToApiPermissions.ts │ │ │ ├── 1630837718830-CreateApiAuditLogTable.ts │ │ │ ├── 1630840428694-AddTimestampsToAllowedGuilds.ts │ │ │ ├── 1631474131804-AddIndexToIsBot.ts │ │ │ ├── 1632582078622-SplitScheduledPostsPostAtIndex.ts │ │ │ ├── 1632582299400-AddIndexToRemindersRemindAt.ts │ │ │ ├── 1634459708599-RemoveTagResponsesForeignKeys.ts │ │ │ ├── 1634563901575-CreatePhishermanCacheTable.ts │ │ │ ├── 1635596150234-CreatePhishermanKeyCacheTable.ts │ │ │ ├── 1635779678653-CreateWebhooksTable.ts │ │ │ ├── 1650709103864-CreateRoleQueueTable.ts │ │ │ ├── 1650712828384-CreateRoleButtonsTable.ts │ │ │ ├── 1650721020704-RemoveButtonRolesTable.ts │ │ │ ├── 1680354053183-AddTimeoutColumnsToMutes.ts │ │ │ └── 1682788165866-CreateMemberCacheTable.ts │ │ ├── paths.ts │ │ ├── pluginUtils.ts │ │ ├── plugins/ │ │ │ ├── AutoDelete/ │ │ │ │ ├── AutoDeletePlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── types.ts │ │ │ │ └── util/ │ │ │ │ ├── addMessageToDeletionQueue.ts │ │ │ │ ├── deleteNextItem.ts │ │ │ │ ├── onMessageCreate.ts │ │ │ │ ├── onMessageDelete.ts │ │ │ │ ├── onMessageDeleteBulk.ts │ │ │ │ └── scheduleNextDeletion.ts │ │ │ ├── AutoReactions/ │ │ │ │ ├── AutoReactionsPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── DisableAutoReactionsCmd.ts │ │ │ │ │ └── NewAutoReactionsCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ └── AddReactionsEvt.ts │ │ │ │ └── types.ts │ │ │ ├── Automod/ │ │ │ │ ├── AutomodPlugin.ts │ │ │ │ ├── actions/ │ │ │ │ │ ├── addRoles.ts │ │ │ │ │ ├── addToCounter.ts │ │ │ │ │ ├── alert.ts │ │ │ │ │ ├── archiveThread.ts │ │ │ │ │ ├── availableActions.ts │ │ │ │ │ ├── ban.ts │ │ │ │ │ ├── changeNickname.ts │ │ │ │ │ ├── changePerms.ts │ │ │ │ │ ├── clean.ts │ │ │ │ │ ├── exampleAction.ts │ │ │ │ │ ├── kick.ts │ │ │ │ │ ├── log.ts │ │ │ │ │ ├── mute.ts │ │ │ │ │ ├── pauseInvites.ts │ │ │ │ │ ├── removeRoles.ts │ │ │ │ │ ├── reply.ts │ │ │ │ │ ├── setAntiraidLevel.ts │ │ │ │ │ ├── setCounter.ts │ │ │ │ │ ├── setSlowmode.ts │ │ │ │ │ ├── startThread.ts │ │ │ │ │ └── warn.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── AntiraidClearCmd.ts │ │ │ │ │ ├── DebugAutomodCmd.ts │ │ │ │ │ ├── SetAntiraidCmd.ts │ │ │ │ │ └── ViewAntiraidCmd.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ ├── RunAutomodOnJoinLeaveEvt.ts │ │ │ │ │ ├── RunAutomodOnMemberUpdate.ts │ │ │ │ │ ├── runAutomodOnAntiraidLevel.ts │ │ │ │ │ ├── runAutomodOnCounterTrigger.ts │ │ │ │ │ ├── runAutomodOnMessage.ts │ │ │ │ │ ├── runAutomodOnModAction.ts │ │ │ │ │ └── runAutomodOnThreadEvents.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── addRecentActionsFromMessage.ts │ │ │ │ │ ├── applyCooldown.ts │ │ │ │ │ ├── checkCooldown.ts │ │ │ │ │ ├── clearOldNicknameChanges.ts │ │ │ │ │ ├── clearOldRecentActions.ts │ │ │ │ │ ├── clearOldRecentSpam.ts │ │ │ │ │ ├── clearRecentActionsForMessage.ts │ │ │ │ │ ├── createMessageSpamTrigger.ts │ │ │ │ │ ├── findRecentSpam.ts │ │ │ │ │ ├── getMatchingMessageRecentActions.ts │ │ │ │ │ ├── getMatchingRecentActions.ts │ │ │ │ │ ├── getSpamIdentifier.ts │ │ │ │ │ ├── getTextMatchPartialSummary.ts │ │ │ │ │ ├── ignoredRoleChanges.ts │ │ │ │ │ ├── matchMultipleTextTypesOnMessage.ts │ │ │ │ │ ├── resolveActionContactMethods.ts │ │ │ │ │ ├── runAutomod.ts │ │ │ │ │ ├── setAntiraidLevel.ts │ │ │ │ │ └── sumRecentActionCounts.ts │ │ │ │ ├── helpers.ts │ │ │ │ ├── triggers/ │ │ │ │ │ ├── antiraidLevel.ts │ │ │ │ │ ├── anyMessage.ts │ │ │ │ │ ├── attachmentSpam.ts │ │ │ │ │ ├── availableTriggers.ts │ │ │ │ │ ├── ban.ts │ │ │ │ │ ├── characterSpam.ts │ │ │ │ │ ├── counterTrigger.ts │ │ │ │ │ ├── emojiSpam.ts │ │ │ │ │ ├── exampleTrigger.ts │ │ │ │ │ ├── hasAttachments.ts │ │ │ │ │ ├── kick.ts │ │ │ │ │ ├── lineSpam.ts │ │ │ │ │ ├── linkSpam.ts │ │ │ │ │ ├── matchAttachmentType.ts │ │ │ │ │ ├── matchInvites.ts │ │ │ │ │ ├── matchLinks.ts │ │ │ │ │ ├── matchMimeType.ts │ │ │ │ │ ├── matchRegex.ts │ │ │ │ │ ├── matchWords.ts │ │ │ │ │ ├── memberJoin.ts │ │ │ │ │ ├── memberJoinSpam.ts │ │ │ │ │ ├── memberLeave.ts │ │ │ │ │ ├── mentionSpam.ts │ │ │ │ │ ├── messageSpam.ts │ │ │ │ │ ├── mute.ts │ │ │ │ │ ├── note.ts │ │ │ │ │ ├── roleAdded.ts │ │ │ │ │ ├── roleRemoved.ts │ │ │ │ │ ├── stickerSpam.ts │ │ │ │ │ ├── threadArchive.ts │ │ │ │ │ ├── threadCreate.ts │ │ │ │ │ ├── threadCreateSpam.ts │ │ │ │ │ ├── threadDelete.ts │ │ │ │ │ ├── threadUnarchive.ts │ │ │ │ │ ├── unban.ts │ │ │ │ │ ├── unmute.ts │ │ │ │ │ └── warn.ts │ │ │ │ └── types.ts │ │ │ ├── BotControl/ │ │ │ │ ├── BotControlPlugin.ts │ │ │ │ ├── activeReload.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── AddDashboardUserCmd.ts │ │ │ │ │ ├── AddServerFromInviteCmd.ts │ │ │ │ │ ├── AllowServerCmd.ts │ │ │ │ │ ├── ChannelToServerCmd.ts │ │ │ │ │ ├── DebugCountersCmd.ts │ │ │ │ │ ├── DisallowServerCmd.ts │ │ │ │ │ ├── EligibleCmd.ts │ │ │ │ │ ├── LeaveServerCmd.ts │ │ │ │ │ ├── ListDashboardPermsCmd.ts │ │ │ │ │ ├── ListDashboardUsersCmd.ts │ │ │ │ │ ├── ProfilerDataCmd.ts │ │ │ │ │ ├── RateLimitPerformanceCmd.ts │ │ │ │ │ ├── ReloadGlobalPluginsCmd.ts │ │ │ │ │ ├── ReloadServerCmd.ts │ │ │ │ │ ├── RemoveDashboardUserCmd.ts │ │ │ │ │ ├── RestPerformanceCmd.ts │ │ │ │ │ └── ServersCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── functions/ │ │ │ │ │ └── isEligible.ts │ │ │ │ └── types.ts │ │ │ ├── Cases/ │ │ │ │ ├── CasesPlugin.ts │ │ │ │ ├── caseAbbreviations.ts │ │ │ │ ├── caseColors.ts │ │ │ │ ├── caseIcons.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── createCase.ts │ │ │ │ │ ├── createCaseNote.ts │ │ │ │ │ ├── getCaseColor.ts │ │ │ │ │ ├── getCaseEmbed.ts │ │ │ │ │ ├── getCaseIcon.ts │ │ │ │ │ ├── getCaseSummary.ts │ │ │ │ │ ├── getCaseTypeAmountForUserId.ts │ │ │ │ │ ├── getRecentCasesByMod.ts │ │ │ │ │ ├── getTotalCasesByMod.ts │ │ │ │ │ ├── postToCaseLogChannel.ts │ │ │ │ │ └── resolveCaseId.ts │ │ │ │ └── types.ts │ │ │ ├── Censor/ │ │ │ │ ├── CensorPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── types.ts │ │ │ │ └── util/ │ │ │ │ ├── applyFiltersToMsg.ts │ │ │ │ ├── censorMessage.ts │ │ │ │ ├── onMessageCreate.ts │ │ │ │ └── onMessageUpdate.ts │ │ │ ├── ChannelArchiver/ │ │ │ │ ├── ChannelArchiverPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ └── ArchiveChannelCmd.ts │ │ │ │ ├── rehostAttachment.ts │ │ │ │ └── types.ts │ │ │ ├── CommandAliases/ │ │ │ │ ├── CommandAliasesPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ └── DispatchAliasEvt.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── buildAliasMatchers.ts │ │ │ │ │ └── normalizeAliases.ts │ │ │ │ └── types.ts │ │ │ ├── Common/ │ │ │ │ ├── CommonPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── functions/ │ │ │ │ │ └── getEmoji.ts │ │ │ │ └── types.ts │ │ │ ├── CompanionChannels/ │ │ │ │ ├── CompanionChannelsPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ └── VoiceStateUpdateEvt.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── getCompanionChannelOptsForVoiceChannelId.ts │ │ │ │ │ └── handleCompanionPermissions.ts │ │ │ │ └── types.ts │ │ │ ├── ContextMenus/ │ │ │ │ ├── ContextMenuPlugin.ts │ │ │ │ ├── actions/ │ │ │ │ │ ├── ban.ts │ │ │ │ │ ├── clean.ts │ │ │ │ │ ├── mute.ts │ │ │ │ │ ├── note.ts │ │ │ │ │ ├── update.ts │ │ │ │ │ └── warn.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── BanUserCtxCmd.ts │ │ │ │ │ ├── CleanMessageCtxCmd.ts │ │ │ │ │ ├── ModMenuUserCtxCmd.ts │ │ │ │ │ ├── MuteUserCtxCmd.ts │ │ │ │ │ ├── NoteUserCtxCmd.ts │ │ │ │ │ └── WarnUserCtxCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ └── types.ts │ │ │ ├── Counters/ │ │ │ │ ├── CountersPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── AddCounterCmd.ts │ │ │ │ │ ├── CountersListCmd.ts │ │ │ │ │ ├── ResetAllCounterValuesCmd.ts │ │ │ │ │ ├── ResetCounterCmd.ts │ │ │ │ │ ├── SetCounterCmd.ts │ │ │ │ │ └── ViewCounterCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── changeCounterValue.ts │ │ │ │ │ ├── checkAllValuesForReverseTrigger.ts │ │ │ │ │ ├── checkAllValuesForTrigger.ts │ │ │ │ │ ├── checkCounterTrigger.ts │ │ │ │ │ ├── checkReverseCounterTrigger.ts │ │ │ │ │ ├── counterExists.ts │ │ │ │ │ ├── decayCounter.ts │ │ │ │ │ ├── emitCounterEvent.ts │ │ │ │ │ ├── getPrettyNameForCounter.ts │ │ │ │ │ ├── getPrettyNameForCounterTrigger.ts │ │ │ │ │ ├── offCounterEvent.ts │ │ │ │ │ ├── onCounterEvent.ts │ │ │ │ │ ├── resetAllCounterValues.ts │ │ │ │ │ └── setCounterValue.ts │ │ │ │ └── types.ts │ │ │ ├── CustomEvents/ │ │ │ │ ├── ActionError.ts │ │ │ │ ├── CustomEventsPlugin.ts │ │ │ │ ├── actions/ │ │ │ │ │ ├── addRoleAction.ts │ │ │ │ │ ├── createCaseAction.ts │ │ │ │ │ ├── makeRoleMentionableAction.ts │ │ │ │ │ ├── makeRoleUnmentionableAction.ts │ │ │ │ │ ├── messageAction.ts │ │ │ │ │ ├── moveToVoiceChannelAction.ts │ │ │ │ │ └── setChannelPermissionOverrides.ts │ │ │ │ ├── catchTemplateError.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── functions/ │ │ │ │ │ └── runEvent.ts │ │ │ │ └── types.ts │ │ │ ├── GuildAccessMonitor/ │ │ │ │ ├── GuildAccessMonitorPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ └── types.ts │ │ │ ├── GuildConfigReloader/ │ │ │ │ ├── GuildConfigReloaderPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── functions/ │ │ │ │ │ └── reloadChangedGuilds.ts │ │ │ │ └── types.ts │ │ │ ├── GuildInfoSaver/ │ │ │ │ ├── GuildInfoSaverPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ └── types.ts │ │ │ ├── GuildMemberCache/ │ │ │ │ ├── GuildMemberCachePlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ ├── cancelDeletionOnMemberJoin.ts │ │ │ │ │ ├── removeMemberCacheOnMemberLeave.ts │ │ │ │ │ ├── updateMemberCacheOnMemberUpdate.ts │ │ │ │ │ ├── updateMemberCacheOnMessage.ts │ │ │ │ │ ├── updateMemberCacheOnRoleChange.ts │ │ │ │ │ └── updateMemberCacheOnVoiceStateUpdate.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── getCachedMemberData.ts │ │ │ │ │ └── updateMemberCacheForMember.ts │ │ │ │ └── types.ts │ │ │ ├── InternalPoster/ │ │ │ │ ├── InternalPosterPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── editMessage.ts │ │ │ │ │ ├── getOrCreateWebhookClientForChannel.ts │ │ │ │ │ ├── getOrCreateWebhookForChannel.ts │ │ │ │ │ └── sendMessage.ts │ │ │ │ └── types.ts │ │ │ ├── LocateUser/ │ │ │ │ ├── LocateUserPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── FollowCmd.ts │ │ │ │ │ ├── ListFollowCmd.ts │ │ │ │ │ └── WhereCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ ├── BanRemoveAlertsEvt.ts │ │ │ │ │ └── SendAlertsEvts.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils/ │ │ │ │ ├── clearExpiredAlert.ts │ │ │ │ ├── createOrReuseInvite.ts │ │ │ │ ├── fillAlertsList.ts │ │ │ │ ├── moveMember.ts │ │ │ │ ├── removeUserIdFromActiveAlerts.ts │ │ │ │ ├── sendAlerts.ts │ │ │ │ └── sendWhere.ts │ │ │ ├── Logs/ │ │ │ │ ├── LogsPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ ├── LogsChannelModifyEvts.ts │ │ │ │ │ ├── LogsEmojiAndStickerModifyEvts.ts │ │ │ │ │ ├── LogsGuildBanEvts.ts │ │ │ │ │ ├── LogsGuildMemberAddEvt.ts │ │ │ │ │ ├── LogsGuildMemberRemoveEvt.ts │ │ │ │ │ ├── LogsGuildMemberRoleChangeEvt.ts │ │ │ │ │ ├── LogsRoleModifyEvts.ts │ │ │ │ │ ├── LogsStageInstanceModifyEvts.ts │ │ │ │ │ ├── LogsThreadModifyEvts.ts │ │ │ │ │ ├── LogsUserUpdateEvts.ts │ │ │ │ │ └── LogsVoiceChannelEvts.ts │ │ │ │ ├── logFunctions/ │ │ │ │ │ ├── logAutomodAction.ts │ │ │ │ │ ├── logBotAlert.ts │ │ │ │ │ ├── logCaseCreate.ts │ │ │ │ │ ├── logCaseDelete.ts │ │ │ │ │ ├── logCaseUpdate.ts │ │ │ │ │ ├── logCensor.ts │ │ │ │ │ ├── logChannelCreate.ts │ │ │ │ │ ├── logChannelDelete.ts │ │ │ │ │ ├── logChannelUpdate.ts │ │ │ │ │ ├── logClean.ts │ │ │ │ │ ├── logDmFailed.ts │ │ │ │ │ ├── logEmojiCreate.ts │ │ │ │ │ ├── logEmojiDelete.ts │ │ │ │ │ ├── logEmojiUpdate.ts │ │ │ │ │ ├── logMassBan.ts │ │ │ │ │ ├── logMassMute.ts │ │ │ │ │ ├── logMassUnban.ts │ │ │ │ │ ├── logMemberBan.ts │ │ │ │ │ ├── logMemberForceban.ts │ │ │ │ │ ├── logMemberJoin.ts │ │ │ │ │ ├── logMemberJoinWithPriorRecords.ts │ │ │ │ │ ├── logMemberKick.ts │ │ │ │ │ ├── logMemberLeave.ts │ │ │ │ │ ├── logMemberMute.ts │ │ │ │ │ ├── logMemberMuteExpired.ts │ │ │ │ │ ├── logMemberMuteRejoin.ts │ │ │ │ │ ├── logMemberNickChange.ts │ │ │ │ │ ├── logMemberNote.ts │ │ │ │ │ ├── logMemberRestore.ts │ │ │ │ │ ├── logMemberRoleAdd.ts │ │ │ │ │ ├── logMemberRoleChanges.ts │ │ │ │ │ ├── logMemberRoleRemove.ts │ │ │ │ │ ├── logMemberTimedBan.ts │ │ │ │ │ ├── logMemberTimedMute.ts │ │ │ │ │ ├── logMemberTimedUnban.ts │ │ │ │ │ ├── logMemberTimedUnmute.ts │ │ │ │ │ ├── logMemberUnban.ts │ │ │ │ │ ├── logMemberUnmute.ts │ │ │ │ │ ├── logMemberWarn.ts │ │ │ │ │ ├── logMessageDelete.ts │ │ │ │ │ ├── logMessageDeleteAuto.ts │ │ │ │ │ ├── logMessageDeleteBare.ts │ │ │ │ │ ├── logMessageDeleteBulk.ts │ │ │ │ │ ├── logMessageEdit.ts │ │ │ │ │ ├── logMessageSpamDetected.ts │ │ │ │ │ ├── logOtherSpamDetected.ts │ │ │ │ │ ├── logPostedScheduledMessage.ts │ │ │ │ │ ├── logRepeatedMessage.ts │ │ │ │ │ ├── logRoleCreate.ts │ │ │ │ │ ├── logRoleDelete.ts │ │ │ │ │ ├── logRoleUpdate.ts │ │ │ │ │ ├── logScheduledMessage.ts │ │ │ │ │ ├── logScheduledRepeatedMessage.ts │ │ │ │ │ ├── logSetAntiraidAuto.ts │ │ │ │ │ ├── logSetAntiraidUser.ts │ │ │ │ │ ├── logStageInstanceCreate.ts │ │ │ │ │ ├── logStageInstanceDelete.ts │ │ │ │ │ ├── logStageInstanceUpdate.ts │ │ │ │ │ ├── logStickerCreate.ts │ │ │ │ │ ├── logStickerDelete.ts │ │ │ │ │ ├── logStickerUpdate.ts │ │ │ │ │ ├── logThreadCreate.ts │ │ │ │ │ ├── logThreadDelete.ts │ │ │ │ │ ├── logThreadUpdate.ts │ │ │ │ │ ├── logVoiceChannelForceDisconnect.ts │ │ │ │ │ ├── logVoiceChannelForceMove.ts │ │ │ │ │ ├── logVoiceChannelJoin.ts │ │ │ │ │ ├── logVoiceChannelLeave.ts │ │ │ │ │ └── logVoiceChannelMove.ts │ │ │ │ ├── types.ts │ │ │ │ └── util/ │ │ │ │ ├── getLogMessage.ts │ │ │ │ ├── getMessageReplyLogInfo.ts │ │ │ │ ├── isLogIgnored.ts │ │ │ │ ├── log.ts │ │ │ │ ├── onMessageDelete.ts │ │ │ │ ├── onMessageDeleteBulk.ts │ │ │ │ └── onMessageUpdate.ts │ │ │ ├── MessageSaver/ │ │ │ │ ├── MessageSaverPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── SaveMessagesToDB.ts │ │ │ │ │ └── SavePinsToDB.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ └── SaveMessagesEvts.ts │ │ │ │ ├── saveMessagesToDB.ts │ │ │ │ └── types.ts │ │ │ ├── ModActions/ │ │ │ │ ├── ModActionsPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── addcase/ │ │ │ │ │ │ ├── AddCaseMsgCmd.ts │ │ │ │ │ │ ├── AddCaseSlashCmd.ts │ │ │ │ │ │ └── actualAddCaseCmd.ts │ │ │ │ │ ├── ban/ │ │ │ │ │ │ ├── BanMsgCmd.ts │ │ │ │ │ │ ├── BanSlashCmd.ts │ │ │ │ │ │ └── actualBanCmd.ts │ │ │ │ │ ├── case/ │ │ │ │ │ │ ├── CaseMsgCmd.ts │ │ │ │ │ │ ├── CaseSlashCmd.ts │ │ │ │ │ │ └── actualCaseCmd.ts │ │ │ │ │ ├── cases/ │ │ │ │ │ │ ├── CasesModMsgCmd.ts │ │ │ │ │ │ ├── CasesSlashCmd.ts │ │ │ │ │ │ ├── CasesUserMsgCmd.ts │ │ │ │ │ │ └── actualCasesCmd.ts │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── deletecase/ │ │ │ │ │ │ ├── DeleteCaseMsgCmd.ts │ │ │ │ │ │ ├── DeleteCaseSlashCmd.ts │ │ │ │ │ │ └── actualDeleteCaseCmd.ts │ │ │ │ │ ├── forceban/ │ │ │ │ │ │ ├── ForceBanMsgCmd.ts │ │ │ │ │ │ ├── ForceBanSlashCmd.ts │ │ │ │ │ │ └── actualForceBanCmd.ts │ │ │ │ │ ├── forcemute/ │ │ │ │ │ │ ├── ForceMuteMsgCmd.ts │ │ │ │ │ │ └── ForceMuteSlashCmd.ts │ │ │ │ │ ├── forceunmute/ │ │ │ │ │ │ ├── ForceUnmuteMsgCmd.ts │ │ │ │ │ │ └── ForceUnmuteSlashCmd.ts │ │ │ │ │ ├── hidecase/ │ │ │ │ │ │ ├── HideCaseMsgCmd.ts │ │ │ │ │ │ ├── HideCaseSlashCmd.ts │ │ │ │ │ │ └── actualHideCaseCmd.ts │ │ │ │ │ ├── kick/ │ │ │ │ │ │ ├── KickMsgCmd.ts │ │ │ │ │ │ ├── KickSlashCmd.ts │ │ │ │ │ │ └── actualKickCmd.ts │ │ │ │ │ ├── massban/ │ │ │ │ │ │ ├── MassBanMsgCmd.ts │ │ │ │ │ │ ├── MassBanSlashCmd.ts │ │ │ │ │ │ └── actualMassBanCmd.ts │ │ │ │ │ ├── massmute/ │ │ │ │ │ │ ├── MassMuteMsgCmd.ts │ │ │ │ │ │ ├── MassMuteSlashCmd.ts │ │ │ │ │ │ └── actualMassMuteCmd.ts │ │ │ │ │ ├── massunban/ │ │ │ │ │ │ ├── MassUnbanMsgCmd.ts │ │ │ │ │ │ ├── MassUnbanSlashCmd.ts │ │ │ │ │ │ └── actualMassUnbanCmd.ts │ │ │ │ │ ├── mute/ │ │ │ │ │ │ ├── MuteMsgCmd.ts │ │ │ │ │ │ ├── MuteSlashCmd.ts │ │ │ │ │ │ └── actualMuteCmd.ts │ │ │ │ │ ├── note/ │ │ │ │ │ │ ├── NoteMsgCmd.ts │ │ │ │ │ │ ├── NoteSlashCmd.ts │ │ │ │ │ │ └── actualNoteCmd.ts │ │ │ │ │ ├── unban/ │ │ │ │ │ │ ├── UnbanMsgCmd.ts │ │ │ │ │ │ ├── UnbanSlashCmd.ts │ │ │ │ │ │ └── actualUnbanCmd.ts │ │ │ │ │ ├── unhidecase/ │ │ │ │ │ │ ├── UnhideCaseMsgCmd.ts │ │ │ │ │ │ ├── UnhideCaseSlashCmd.ts │ │ │ │ │ │ └── actualUnhideCaseCmd.ts │ │ │ │ │ ├── unmute/ │ │ │ │ │ │ ├── UnmuteMsgCmd.ts │ │ │ │ │ │ ├── UnmuteSlashCmd.ts │ │ │ │ │ │ └── actualUnmuteCmd.ts │ │ │ │ │ ├── update/ │ │ │ │ │ │ ├── UpdateMsgCmd.ts │ │ │ │ │ │ └── UpdateSlashCmd.ts │ │ │ │ │ └── warn/ │ │ │ │ │ ├── WarnMsgCmd.ts │ │ │ │ │ ├── WarnSlashCmd.ts │ │ │ │ │ └── actualWarnCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ ├── AuditLogEvents.ts │ │ │ │ │ ├── CreateBanCaseOnManualBanEvt.ts │ │ │ │ │ ├── CreateKickCaseOnManualKickEvt.ts │ │ │ │ │ ├── CreateUnbanCaseOnManualUnbanEvt.ts │ │ │ │ │ └── PostAlertOnMemberJoinEvt.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── attachmentLinkReaction.ts │ │ │ │ │ ├── banUserId.ts │ │ │ │ │ ├── clearIgnoredEvents.ts │ │ │ │ │ ├── clearTempban.ts │ │ │ │ │ ├── formatReasonForAttachments.ts │ │ │ │ │ ├── getDefaultContactMethods.ts │ │ │ │ │ ├── hasModActionPerm.ts │ │ │ │ │ ├── ignoreEvent.ts │ │ │ │ │ ├── isBanned.ts │ │ │ │ │ ├── isEventIgnored.ts │ │ │ │ │ ├── kickMember.ts │ │ │ │ │ ├── offModActionsEvent.ts │ │ │ │ │ ├── onModActionsEvent.ts │ │ │ │ │ ├── readContactMethodsFromArgs.ts │ │ │ │ │ ├── updateCase.ts │ │ │ │ │ └── warnMember.ts │ │ │ │ └── types.ts │ │ │ ├── Mutes/ │ │ │ │ ├── MutesPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── ClearBannedMutesCmd.ts │ │ │ │ │ ├── ClearMutesCmd.ts │ │ │ │ │ ├── ClearMutesWithoutRoleCmd.ts │ │ │ │ │ └── MutesCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ ├── ClearActiveMuteOnMemberBanEvt.ts │ │ │ │ │ ├── ClearActiveMuteOnRoleRemovalEvt.ts │ │ │ │ │ ├── ReapplyActiveMuteOnJoinEvt.ts │ │ │ │ │ └── RegisterManualTimeoutsEvt.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── clearMute.ts │ │ │ │ │ ├── getDefaultMuteType.ts │ │ │ │ │ ├── getTimeoutExpiryTime.ts │ │ │ │ │ ├── memberHasMutedRole.ts │ │ │ │ │ ├── muteUser.ts │ │ │ │ │ ├── offMutesEvent.ts │ │ │ │ │ ├── onMutesEvent.ts │ │ │ │ │ ├── renewTimeoutMute.ts │ │ │ │ │ └── unmuteUser.ts │ │ │ │ └── types.ts │ │ │ ├── NameHistory/ │ │ │ │ ├── NameHistoryPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ └── NamesCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ └── UpdateNameEvts.ts │ │ │ │ ├── types.ts │ │ │ │ └── updateNickname.ts │ │ │ ├── Persist/ │ │ │ │ ├── PersistPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ ├── LoadDataEvt.ts │ │ │ │ │ └── StoreDataEvt.ts │ │ │ │ └── types.ts │ │ │ ├── Phisherman/ │ │ │ │ ├── PhishermanPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ └── types.ts │ │ │ ├── PingableRoles/ │ │ │ │ ├── PingableRolesPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── PingableRoleDisableCmd.ts │ │ │ │ │ └── PingableRoleEnableCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ └── ChangePingableEvts.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils/ │ │ │ │ ├── disablePingableRoles.ts │ │ │ │ ├── enablePingableRoles.ts │ │ │ │ └── getPingableRolesForChannel.ts │ │ │ ├── Post/ │ │ │ │ ├── PostPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── EditCmd.ts │ │ │ │ │ ├── EditEmbedCmd.ts │ │ │ │ │ ├── PostCmd.ts │ │ │ │ │ ├── PostEmbedCmd.ts │ │ │ │ │ ├── ScheduledPostsDeleteCmd.ts │ │ │ │ │ ├── ScheduledPostsListCmd.ts │ │ │ │ │ └── ScheduledPostsShowCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── types.ts │ │ │ │ └── util/ │ │ │ │ ├── actualPostCmd.ts │ │ │ │ ├── formatContent.ts │ │ │ │ ├── parseScheduleTime.ts │ │ │ │ ├── postMessage.ts │ │ │ │ └── postScheduledPost.ts │ │ │ ├── ReactionRoles/ │ │ │ │ ├── ReactionRolesPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── ClearReactionRolesCmd.ts │ │ │ │ │ ├── InitReactionRolesCmd.ts │ │ │ │ │ └── RefreshReactionRolesCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ ├── AddReactionRoleEvt.ts │ │ │ │ │ └── MessageDeletedEvt.ts │ │ │ │ ├── types.ts │ │ │ │ └── util/ │ │ │ │ ├── addMemberPendingRoleChange.ts │ │ │ │ ├── applyReactionRoleReactionsToMessage.ts │ │ │ │ ├── autoRefreshLoop.ts │ │ │ │ ├── refreshReactionRoles.ts │ │ │ │ └── runAutoRefresh.ts │ │ │ ├── Reminders/ │ │ │ │ ├── RemindersPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── RemindCmd.ts │ │ │ │ │ ├── RemindersCmd.ts │ │ │ │ │ └── RemindersDeleteCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── functions/ │ │ │ │ │ └── postReminder.ts │ │ │ │ └── types.ts │ │ │ ├── RoleButtons/ │ │ │ │ ├── RoleButtonsPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ └── resetButtons.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ └── buttonInteraction.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── TooManyComponentsError.ts │ │ │ │ │ ├── applyAllRoleButtons.ts │ │ │ │ │ ├── applyRoleButtons.ts │ │ │ │ │ ├── convertButtonStyleStringToEnum.ts │ │ │ │ │ ├── createButtonComponents.ts │ │ │ │ │ └── getAllRolesInButtons.ts │ │ │ │ └── types.ts │ │ │ ├── RoleManager/ │ │ │ │ ├── RoleManagerPlugin.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── addPriorityRole.ts │ │ │ │ │ ├── addRole.ts │ │ │ │ │ ├── removePriorityRole.ts │ │ │ │ │ ├── removeRole.ts │ │ │ │ │ └── runRoleAssignmentLoop.ts │ │ │ │ └── types.ts │ │ │ ├── Roles/ │ │ │ │ ├── RolesPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── AddRoleCmd.ts │ │ │ │ │ ├── MassAddRoleCmd.ts │ │ │ │ │ ├── MassRemoveRoleCmd.ts │ │ │ │ │ └── RemoveRoleCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ └── types.ts │ │ │ ├── SelfGrantableRoles/ │ │ │ │ ├── SelfGrantableRolesPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── RoleAddCmd.ts │ │ │ │ │ ├── RoleHelpCmd.ts │ │ │ │ │ └── RoleRemoveCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── types.ts │ │ │ │ └── util/ │ │ │ │ ├── findMatchingRoles.ts │ │ │ │ ├── getApplyingEntries.ts │ │ │ │ ├── normalizeRoleNames.ts │ │ │ │ └── splitRoleNames.ts │ │ │ ├── Slowmode/ │ │ │ │ ├── SlowmodePlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── SlowmodeClearCmd.ts │ │ │ │ │ ├── SlowmodeDisableCmd.ts │ │ │ │ │ ├── SlowmodeGetCmd.ts │ │ │ │ │ ├── SlowmodeListCmd.ts │ │ │ │ │ └── SlowmodeSetCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── requiredPermissions.ts │ │ │ │ ├── types.ts │ │ │ │ └── util/ │ │ │ │ ├── actualDisableSlowmodeCmd.ts │ │ │ │ ├── applyBotSlowmodeToUserId.ts │ │ │ │ ├── clearBotSlowmodeFromUserId.ts │ │ │ │ ├── clearExpiredSlowmodes.ts │ │ │ │ ├── disableBotSlowmodeForChannel.ts │ │ │ │ └── onMessageCreate.ts │ │ │ ├── Spam/ │ │ │ │ ├── SpamPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ └── SpamVoiceEvt.ts │ │ │ │ ├── types.ts │ │ │ │ └── util/ │ │ │ │ ├── addRecentAction.ts │ │ │ │ ├── clearOldRecentActions.ts │ │ │ │ ├── clearRecentUserActions.ts │ │ │ │ ├── getRecentActionCount.ts │ │ │ │ ├── getRecentActions.ts │ │ │ │ ├── logAndDetectMessageSpam.ts │ │ │ │ ├── logAndDetectOtherSpam.ts │ │ │ │ ├── logCensor.ts │ │ │ │ ├── onMessageCreate.ts │ │ │ │ └── saveSpamArchives.ts │ │ │ ├── Starboard/ │ │ │ │ ├── StarboardPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ └── MigratePinsCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ ├── StarboardReactionAddEvt.ts │ │ │ │ │ └── StarboardReactionRemoveEvts.ts │ │ │ │ ├── types.ts │ │ │ │ └── util/ │ │ │ │ ├── createStarboardEmbedFromMessage.ts │ │ │ │ ├── createStarboardPseudoFooterForMessage.ts │ │ │ │ ├── onMessageDelete.ts │ │ │ │ ├── removeMessageFromStarboard.ts │ │ │ │ ├── removeMessageFromStarboardMessages.ts │ │ │ │ ├── saveMessageToStarboard.ts │ │ │ │ └── updateStarboardMessageStarCount.ts │ │ │ ├── Tags/ │ │ │ │ ├── TagsPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── TagCreateCmd.ts │ │ │ │ │ ├── TagDeleteCmd.ts │ │ │ │ │ ├── TagEvalCmd.ts │ │ │ │ │ ├── TagListCmd.ts │ │ │ │ │ └── TagSourceCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── templateFunctions.ts │ │ │ │ ├── types.ts │ │ │ │ └── util/ │ │ │ │ ├── findTagByName.ts │ │ │ │ ├── matchAndRenderTagFromString.ts │ │ │ │ ├── onMessageCreate.ts │ │ │ │ ├── onMessageDelete.ts │ │ │ │ ├── renderTagBody.ts │ │ │ │ └── renderTagFromString.ts │ │ │ ├── TimeAndDate/ │ │ │ │ ├── TimeAndDatePlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── ResetTimezoneCmd.ts │ │ │ │ │ ├── SetTimezoneCmd.ts │ │ │ │ │ └── ViewTimezoneCmd.ts │ │ │ │ ├── defaultDateFormats.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── getDateFormat.ts │ │ │ │ │ ├── getGuildTz.ts │ │ │ │ │ ├── getMemberTz.ts │ │ │ │ │ ├── inGuildTz.ts │ │ │ │ │ └── inMemberTz.ts │ │ │ │ └── types.ts │ │ │ ├── UsernameSaver/ │ │ │ │ ├── UsernameSaverPlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ └── UpdateUsernameEvts.ts │ │ │ │ ├── types.ts │ │ │ │ └── updateUsername.ts │ │ │ ├── Utility/ │ │ │ │ ├── UtilityPlugin.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── AboutCmd.ts │ │ │ │ │ ├── AvatarCmd.ts │ │ │ │ │ ├── BanSearchCmd.ts │ │ │ │ │ ├── ChannelInfoCmd.ts │ │ │ │ │ ├── CleanCmd.ts │ │ │ │ │ ├── ContextCmd.ts │ │ │ │ │ ├── EmojiInfoCmd.ts │ │ │ │ │ ├── HelpCmd.ts │ │ │ │ │ ├── InfoCmd.ts │ │ │ │ │ ├── InviteInfoCmd.ts │ │ │ │ │ ├── JumboCmd.ts │ │ │ │ │ ├── LevelCmd.ts │ │ │ │ │ ├── MessageInfoCmd.ts │ │ │ │ │ ├── NicknameCmd.ts │ │ │ │ │ ├── NicknameResetCmd.ts │ │ │ │ │ ├── PingCmd.ts │ │ │ │ │ ├── ReloadGuildCmd.ts │ │ │ │ │ ├── RoleInfoCmd.ts │ │ │ │ │ ├── RolesCmd.ts │ │ │ │ │ ├── SearchCmd.ts │ │ │ │ │ ├── ServerInfoCmd.ts │ │ │ │ │ ├── SnowflakeInfoCmd.ts │ │ │ │ │ ├── SourceCmd.ts │ │ │ │ │ ├── UserInfoCmd.ts │ │ │ │ │ ├── VcdisconnectCmd.ts │ │ │ │ │ └── VcmoveCmd.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ └── AutoJoinThreadEvt.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── cleanMessages.ts │ │ │ │ │ ├── fetchChannelMessagesToClean.ts │ │ │ │ │ ├── getChannelInfoEmbed.ts │ │ │ │ │ ├── getCustomEmojiId.ts │ │ │ │ │ ├── getEmojiInfoEmbed.ts │ │ │ │ │ ├── getGuildPreview.ts │ │ │ │ │ ├── getInviteInfoEmbed.ts │ │ │ │ │ ├── getMessageInfoEmbed.ts │ │ │ │ │ ├── getRoleInfoEmbed.ts │ │ │ │ │ ├── getServerInfoEmbed.ts │ │ │ │ │ ├── getSnowflakeInfoEmbed.ts │ │ │ │ │ ├── getUserInfoEmbed.ts │ │ │ │ │ └── hasPermission.ts │ │ │ │ ├── guildReloads.ts │ │ │ │ ├── refreshMembers.ts │ │ │ │ ├── search.ts │ │ │ │ └── types.ts │ │ │ ├── WelcomeMessage/ │ │ │ │ ├── WelcomeMessagePlugin.ts │ │ │ │ ├── docs.ts │ │ │ │ ├── events/ │ │ │ │ │ └── SendWelcomeMessageEvt.ts │ │ │ │ └── types.ts │ │ │ └── availablePlugins.ts │ │ ├── profiler.ts │ │ ├── rateLimitStats.ts │ │ ├── regExpRunners.ts │ │ ├── restCallStats.ts │ │ ├── staff.ts │ │ ├── templateFormatter.test.ts │ │ ├── templateFormatter.ts │ │ ├── threadsSignalFix.ts │ │ ├── types.ts │ │ ├── uptime.ts │ │ ├── utils/ │ │ │ ├── DecayingCounter.ts │ │ │ ├── MessageBuffer.ts │ │ │ ├── async.ts │ │ │ ├── buildCustomId.ts │ │ │ ├── calculateEmbedSize.ts │ │ │ ├── canAssignRole.ts │ │ │ ├── canReadChannel.ts │ │ │ ├── categorize.ts │ │ │ ├── createPaginatedMessage.ts │ │ │ ├── crypt.test.ts │ │ │ ├── crypt.ts │ │ │ ├── cryptHelpers.ts │ │ │ ├── cryptWorker.ts │ │ │ ├── easyProfiler.ts │ │ │ ├── erisAllowedMentionsToDjsMentionOptions.ts │ │ │ ├── filterObject.ts │ │ │ ├── findMatchingAuditLogEntry.ts │ │ │ ├── formatZodIssue.ts │ │ │ ├── getChunkedEmbedFields.ts │ │ │ ├── getGuildPrefix.ts │ │ │ ├── getMissingChannelPermissions.ts │ │ │ ├── getMissingPermissions.ts │ │ │ ├── getOrFetchGuildMember.ts │ │ │ ├── getOrFetchUser.ts │ │ │ ├── getPermissionNames.ts │ │ │ ├── hasDiscordPermissions.ts │ │ │ ├── idToTimestamp.ts │ │ │ ├── intToRgb.ts │ │ │ ├── isDefaultSticker.ts │ │ │ ├── isDmChannel.ts │ │ │ ├── isGuildChannel.ts │ │ │ ├── isScalar.ts │ │ │ ├── isThreadChannel.ts │ │ │ ├── isValidTimezone.ts │ │ │ ├── loadYamlSafely.ts │ │ │ ├── lockNameHelpers.ts │ │ │ ├── mergeRegexes.ts │ │ │ ├── mergeWordsIntoRegex.ts │ │ │ ├── messageHasContent.ts │ │ │ ├── messageIsEmpty.ts │ │ │ ├── missingPermissionError.ts │ │ │ ├── multipleSlashOptions.ts │ │ │ ├── normalizeText.test.ts │ │ │ ├── normalizeText.ts │ │ │ ├── parseColor.ts │ │ │ ├── parseCustomId.ts │ │ │ ├── parseFuzzyTimezone.ts │ │ │ ├── permissionNames.ts │ │ │ ├── readChannelPermissions.ts │ │ │ ├── registerEventListenersFromMap.ts │ │ │ ├── resolveChannelIds.ts │ │ │ ├── resolveMessageTarget.ts │ │ │ ├── rgbToInt.ts │ │ │ ├── sendDM.ts │ │ │ ├── snowflakeToTimestamp.ts │ │ │ ├── stripMarkdown.ts │ │ │ ├── templateSafeObjects.ts │ │ │ ├── typeUtils.ts │ │ │ ├── unregisterEventListenersFromMap.ts │ │ │ ├── validateNoObjectAliases.test.ts │ │ │ ├── validateNoObjectAliases.ts │ │ │ ├── waitForInteraction.ts │ │ │ ├── zColor.ts │ │ │ ├── zValidTimezone.ts │ │ │ └── zodDeepPartial.ts │ │ ├── utils.test.ts │ │ ├── utils.ts │ │ └── validateActiveConfigs.ts │ ├── start-dev.js │ └── tsconfig.json ├── build-image.sh ├── config-checker/ │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public/ │ │ └── config-schema.json │ ├── src/ │ │ ├── main.ts │ │ ├── style.css │ │ ├── vite-env.d.ts │ │ └── yaml.worker.js │ └── tsconfig.json ├── dashboard/ │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── .prettierignore │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── public/ │ │ └── env.js │ ├── serve.js │ ├── src/ │ │ ├── api.ts │ │ ├── auth.ts │ │ ├── components/ │ │ │ ├── App.vue │ │ │ ├── Expandable.vue │ │ │ ├── PrivacyPolicy.vue │ │ │ ├── Splash.vue │ │ │ ├── Tab.vue │ │ │ ├── Tabs.vue │ │ │ ├── Title.vue │ │ │ ├── dashboard/ │ │ │ │ ├── GuildAccess.vue │ │ │ │ ├── GuildConfigEditor.vue │ │ │ │ ├── GuildImportExport.vue │ │ │ │ ├── GuildInfo.vue │ │ │ │ ├── GuildList.vue │ │ │ │ ├── Layout.vue │ │ │ │ ├── PermissionTree.vue │ │ │ │ └── permissionTreeUtils.ts │ │ │ └── docs/ │ │ │ ├── ArgumentTypes.vue │ │ │ ├── CodeBlock.vue │ │ │ ├── ConfigurationFormat.vue │ │ │ ├── Counters.vue │ │ │ ├── DocsLayout.vue │ │ │ ├── Introduction.vue │ │ │ ├── MarkdownBlock.vue │ │ │ ├── Moderation.vue │ │ │ ├── Permissions.vue │ │ │ ├── Plugin.vue │ │ │ ├── PluginConfiguration.vue │ │ │ └── WorkInProgress.vue │ │ ├── directives/ │ │ │ └── trim-indents.ts │ │ ├── index.ts │ │ ├── routes.ts │ │ ├── store/ │ │ │ ├── auth.ts │ │ │ ├── docs.ts │ │ │ ├── guilds.ts │ │ │ ├── index.ts │ │ │ ├── staff.ts │ │ │ └── types.ts │ │ ├── style/ │ │ │ ├── app.css │ │ │ ├── base.css │ │ │ ├── components.css │ │ │ ├── content.css │ │ │ ├── docs.css │ │ │ ├── privacy-policy.css │ │ │ ├── reset.css │ │ │ └── splash.css │ │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.ts ├── docker/ │ ├── development/ │ │ ├── devenv/ │ │ │ └── Dockerfile │ │ └── nginx/ │ │ ├── Dockerfile │ │ └── default.conf │ └── production/ │ └── nginx/ │ ├── Dockerfile │ └── default.conf ├── docker-compose.development.yml ├── docker-compose.lightweight.yml ├── docker-compose.standalone.yml ├── docs/ │ ├── DEVELOPMENT.md │ ├── MANAGEMENT.md │ ├── MIGRATE_DEV.md │ ├── MIGRATE_PROD.md │ └── PRODUCTION.md ├── package.json ├── pnpm-workspace.yaml ├── presetup-configurator/ │ ├── .gitignore │ ├── .prettierignore │ ├── package.json │ ├── snowpack.config.js │ ├── src/ │ │ ├── App.css │ │ ├── App.tsx │ │ ├── Configurator.css │ │ ├── Configurator.tsx │ │ ├── Levels.tsx │ │ ├── LogChannels.css │ │ ├── LogChannels.tsx │ │ ├── index.css │ │ ├── index.html │ │ └── index.tsx │ └── tsconfig.json ├── shared/ │ ├── .gitignore │ ├── package.json │ ├── src/ │ │ ├── apiPermissions.test.ts │ │ └── apiPermissions.ts │ └── tsconfig.json ├── tsconfig.base.json └── tsconfig.json
Showing preview only (255K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (2379 symbols across 928 files)
FILE: backend/src/Blocker.ts
type Block (line 1) | type Block = {
class Blocker (line 7) | class Blocker {
method block (line 10) | block(key: string): void {
method unblock (line 28) | unblock(key: string): void {
method waitToBeUnblocked (line 34) | async waitToBeUnblocked(key: string): Promise<void> {
FILE: backend/src/DiscordJSError.ts
class DiscordJSError (line 3) | class DiscordJSError extends Error {
method constructor (line 7) | constructor(message: string, code: number | string | undefined, shardI...
method [util.inspect.custom] (line 13) | [util.inspect.custom]() {
FILE: backend/src/Queue.ts
type InternalQueueFn (line 3) | type InternalQueueFn = () => Promise<void>;
type AnyFn (line 4) | type AnyFn = (...args: any[]) => any;
constant DEFAULT_TIMEOUT (line 6) | const DEFAULT_TIMEOUT = 10 * SECONDS;
class Queue (line 8) | class Queue<TQueueFunction extends AnyFn = AnyFn> {
method constructor (line 13) | constructor(timeout = DEFAULT_TIMEOUT) {
method timeout (line 17) | get timeout(): number {
method length (line 27) | get length(): number {
method add (line 31) | public add(fn: TQueueFunction): Promise<any> {
method next (line 48) | public next(): void {
method clear (line 64) | public clear() {
FILE: backend/src/QueuedEventEmitter.ts
type Listener (line 3) | type Listener = (...args: any[]) => void;
class QueuedEventEmitter (line 5) | class QueuedEventEmitter {
method constructor (line 9) | constructor() {
method on (line 14) | on(eventName: string, listener: Listener): Listener {
method off (line 23) | off(eventName: string, listener: Listener) {
method once (line 32) | once(eventName: string, listener: Listener): Listener {
method emit (line 41) | emit(eventName: string, args: any[] = []): Promise<void> {
FILE: backend/src/RecoverablePluginError.ts
type ERRORS (line 3) | enum ERRORS {
constant RECOVERABLE_PLUGIN_ERROR_MESSAGES (line 17) | const RECOVERABLE_PLUGIN_ERROR_MESSAGES = {
class RecoverablePluginError (line 31) | class RecoverablePluginError extends Error {
method constructor (line 35) | constructor(code: ERRORS, guild?: Guild) {
FILE: backend/src/RegExpRunner.ts
class RegExpTimeoutError (line 11) | class RegExpTimeoutError extends Error {
method constructor (line 12) | constructor(
function allowTimeout (line 20) | function allowTimeout(err: RegExpTimeoutError | Error) {
constant INITIAL_REGEX_TIMEOUT (line 29) | const INITIAL_REGEX_TIMEOUT = 5 * SECONDS;
constant INITIAL_REGEX_TIMEOUT_DURATION (line 30) | const INITIAL_REGEX_TIMEOUT_DURATION = 30 * SECONDS;
constant FINAL_REGEX_TIMEOUT (line 31) | const FINAL_REGEX_TIMEOUT = 5 * SECONDS;
constant REGEX_FAIL_TO_COOLDOWN_COUNT (line 38) | const REGEX_FAIL_TO_COOLDOWN_COUNT = 5;
constant REGEX_FAIL_DECAY_TIME (line 39) | const REGEX_FAIL_DECAY_TIME = 2 * MINUTES;
constant REGEX_FAIL_COOLDOWN (line 40) | const REGEX_FAIL_COOLDOWN = 2 * MINUTES + 30 * SECONDS;
type RegExpRunner (line 42) | interface RegExpRunner {
method constructor (line 58) | constructor() {
method worker (line 69) | private get worker(): RegExpWorker {
method exec (line 83) | public async exec(regex: RegExp, str: string): Promise<null | RegExpEx...
method dispose (line 117) | public async dispose() {
class RegExpRunner (line 51) | class RegExpRunner extends EventEmitter {
method constructor (line 58) | constructor() {
method worker (line 69) | private get worker(): RegExpWorker {
method exec (line 83) | public async exec(regex: RegExp, str: string): Promise<null | RegExpEx...
method dispose (line 117) | public async dispose() {
FILE: backend/src/SimpleCache.ts
constant CLEAN_INTERVAL (line 3) | const CLEAN_INTERVAL = 1000;
class SimpleCache (line 5) | class SimpleCache<T = any> {
method constructor (line 14) | constructor(retentionTime: number, maxItems?: number) {
method unload (line 24) | unload() {
method cleanLoop (line 29) | cleanLoop() {
method set (line 42) | set(key: string, value: T) {
method get (line 54) | get(key: string): T | null {
method has (line 61) | has(key: string) {
method delete (line 65) | delete(key: string) {
method clear (line 69) | clear() {
FILE: backend/src/SimpleError.ts
class SimpleError (line 3) | class SimpleError extends Error {
method constructor (line 6) | constructor(message: string) {
method [util.inspect.custom] (line 10) | [util.inspect.custom]() {
FILE: backend/src/api/archives.ts
function initArchives (line 6) | function initArchives(router: express.Router) {
FILE: backend/src/api/auth.ts
type IPassportApiUser (line 14) | interface IPassportApiUser {
type User (line 21) | interface User extends IPassportApiUser {}
constant DISCORD_API_URL (line 25) | const DISCORD_API_URL = "https://discord.com/api";
function simpleDiscordAPIRequest (line 27) | function simpleDiscordAPIRequest(bearerToken, path): Promise<any> {
function initAuth (line 54) | function initAuth(router: express.Router) {
function apiTokenAuthHandlers (line 150) | function apiTokenAuthHandlers() {
FILE: backend/src/api/docs.ts
function isZodObject (line 9) | function isZodObject(schema: z.ZodType): schema is z.ZodObject<any> {
function isZodRecord (line 13) | function isZodRecord(schema: z.ZodType): schema is z.ZodRecord<any> {
function isZodOptional (line 17) | function isZodOptional(schema: z.ZodType): schema is z.ZodOptional<any> {
function isZodArray (line 21) | function isZodArray(schema: z.ZodType): schema is z.ZodArray<any> {
function isZodUnion (line 25) | function isZodUnion(schema: z.ZodType): schema is z.ZodUnion<any> {
function isZodNullable (line 29) | function isZodNullable(schema: z.ZodType): schema is z.ZodNullable<any> {
function isZodDefault (line 33) | function isZodDefault(schema: z.ZodType): schema is z.ZodDefault<any> {
function isZodLiteral (line 37) | function isZodLiteral(schema: z.ZodType): schema is z.ZodLiteral<any> {
function isZodIntersection (line 41) | function isZodIntersection(schema: z.ZodType): schema is z.ZodIntersecti...
function formatZodConfigSchema (line 45) | function formatZodConfigSchema(schema: z.ZodType) {
function initDocs (line 108) | function initDocs(router: express.Router) {
FILE: backend/src/api/guilds.ts
function initGuildsAPI (line 24) | function initGuildsAPI(app: express.Express) {
FILE: backend/src/api/guilds/importExport.ts
type CaseHandlingMode (line 18) | type CaseHandlingMode = z.infer<typeof caseHandlingModeSchema>;
type TImportExportData (line 45) | type TImportExportData = z.infer<typeof importExportData>;
function initGuildsImportExportAPI (line 47) | function initGuildsImportExportAPI(guildRouter: express.Router) {
FILE: backend/src/api/guilds/index.ts
function initGuildsAPI (line 6) | function initGuildsAPI(router: express.Router) {
FILE: backend/src/api/guilds/misc.ts
function initGuildsMiscAPI (line 21) | function initGuildsMiscAPI(router: express.Router) {
FILE: backend/src/api/index.ts
function errorHandler (line 15) | function errorHandler(err) {
FILE: backend/src/api/permissions.ts
function requireGuildPermission (line 25) | function requireGuildPermission(permission: ApiPermissions) {
FILE: backend/src/api/rateLimits.ts
function rateLimit (line 6) | function rateLimit(getKey: (req: Request) => string, limitMs: number, me...
FILE: backend/src/api/responses.ts
function unauthorized (line 3) | function unauthorized(res: Response) {
function error (line 7) | function error(res: Response, message: string, statusCode = 500) {
function serverError (line 11) | function serverError(res: Response, message = "Server error") {
function clientError (line 15) | function clientError(res: Response, message: string) {
function notFound (line 19) | function notFound(res: Response) {
function ok (line 23) | function ok(res: Response) {
FILE: backend/src/api/staff.ts
function initStaff (line 5) | function initStaff(app: express.Express) {
FILE: backend/src/api/tasks.ts
function startBackgroundTasks (line 4) | function startBackgroundTasks() {
FILE: backend/src/commandTypes.ts
method delay (line 34) | delay(value) {
method resolvedUser (line 43) | async resolvedUser(value, context: CommandContext<any>) {
method resolvedUserLoose (line 51) | async resolvedUserLoose(value, context: CommandContext<any>) {
method resolvedMember (line 59) | async resolvedMember(value, context: CommandContext<any>) {
method messageTarget (line 71) | async messageTarget(value: string, context: CommandContext<any>) {
method anyId (line 82) | async anyId(value: string, context: CommandContext<any>) {
method regex (line 99) | regex(value: string): RegExp {
method timezone (line 107) | timezone(value: string) {
method guildTextBasedChannel (line 115) | guildTextBasedChannel(value: string, context: CommandContext<any>) {
FILE: backend/src/configValidator.ts
function validateGuildConfig (line 12) | async function validateGuildConfig(config: any): Promise<string | null> {
FILE: backend/src/data/AllowedGuilds.ts
class AllowedGuilds (line 9) | class AllowedGuilds extends BaseRepository {
method constructor (line 12) | constructor() {
method isAllowed (line 17) | async isAllowed(guildId: string) {
method find (line 26) | find(guildId: string) {
method getForApiUser (line 34) | getForApiUser(userId: string) {
method updateInfo (line 46) | updateInfo(id, name, icon, ownerId) {
method add (line 53) | add(id, data: Partial<Omit<AllowedGuild, "id">> = {}) {
method remove (line 63) | remove(id) {
FILE: backend/src/data/ApiAuditLog.ts
class ApiAuditLog (line 7) | class ApiAuditLog extends BaseRepository {
method constructor (line 10) | constructor() {
method addEntry (line 15) | addEntry<TEventType extends AuditLogEventType>(
FILE: backend/src/data/ApiLogins.ts
constant LOGIN_EXPIRY_TIME (line 11) | const LOGIN_EXPIRY_TIME = 1 * DAYS;
class ApiLogins (line 13) | class ApiLogins extends BaseRepository {
method constructor (line 16) | constructor() {
method getUserIdByApiKey (line 21) | async getUserIdByApiKey(apiKey: string): Promise<string | null> {
method addLogin (line 47) | async addLogin(userId: string): Promise<string> {
method expireApiKey (line 78) | expireApiKey(apiKey) {
method refreshApiKeyExpiryTime (line 90) | async refreshApiKeyExpiryTime(apiKey) {
FILE: backend/src/data/ApiPermissionAssignments.ts
type ApiPermissionTypes (line 9) | enum ApiPermissionTypes {
class ApiPermissionAssignments (line 14) | class ApiPermissionAssignments extends BaseRepository {
method constructor (line 18) | constructor() {
method getByGuildId (line 24) | getByGuildId(guildId) {
method getByUserId (line 32) | getByUserId(userId) {
method getByGuildAndUserId (line 41) | getByGuildAndUserId(guildId, userId) {
method addUser (line 51) | addUser(guildId, userId, permissions: ApiPermissions[], expiresAt: str...
method removeUser (line 61) | removeUser(guildId, userId) {
method updateUserPermissions (line 65) | async updateUserPermissions(guildId: string, userId: string, permissio...
method clearExpiredPermissions (line 78) | async clearExpiredPermissions() {
method applyOwnerChange (line 87) | async applyOwnerChange(guildId: string, newOwnerId: string) {
FILE: backend/src/data/ApiUserInfo.ts
class ApiUserInfo (line 8) | class ApiUserInfo extends BaseRepository {
method constructor (line 11) | constructor() {
method get (line 16) | get(id) {
method update (line 24) | update(id, data: ApiUserInfoData) {
FILE: backend/src/data/Archives.ts
class Archives (line 6) | class Archives extends BaseRepository {
method constructor (line 9) | constructor() {
method deleteExpiredArchives (line 14) | public deleteExpiredArchives() {
FILE: backend/src/data/BaseGuildRepository.ts
class BaseGuildRepository (line 3) | class BaseGuildRepository<TEntity = unknown> extends BaseRepository<TEnt...
method constructor (line 8) | constructor(guildId: string) {
method getGuildInstance (line 17) | public static getGuildInstance<T extends typeof BaseGuildRepository>(t...
FILE: backend/src/data/BaseRepository.ts
class BaseRepository (line 3) | class BaseRepository<TEntity = unknown> {
method constructor (line 6) | constructor() {
method with (line 14) | public with(relations: string | string[]): this {
method getRelations (line 27) | protected getRelations(): string[] {
method _processEntityFromDB (line 33) | protected async _processEntityFromDB(entity) {
method _processEntityToDB (line 38) | protected async _processEntityToDB(entity) {
method processEntityFromDB (line 43) | protected async processEntityFromDB<T extends TEntity | null>(entity: ...
method processMultipleEntitiesFromDB (line 47) | protected async processMultipleEntitiesFromDB<TArr extends TEntity[]>(...
method processEntityToDB (line 51) | protected async processEntityToDB<T extends Partial<TEntity>>(entity: ...
FILE: backend/src/data/CaseTypes.ts
type CaseTypes (line 1) | enum CaseTypes {
FILE: backend/src/data/Configs.ts
constant CLEANUP_INTERVAL (line 9) | const CLEANUP_INTERVAL = 1 * HOURS;
function cleanup (line 11) | async function cleanup() {
class Configs (line 22) | class Configs extends BaseRepository {
method constructor (line 25) | constructor() {
method getActive (line 30) | getActive() {
method getActiveByKey (line 36) | getActiveByKey(key) {
method getHighestId (line 45) | async getHighestId(): Promise<number> {
method getActiveLargerThanId (line 50) | getActiveLargerThanId(id) {
method hasConfig (line 54) | async hasConfig(key) {
method getRevisions (line 58) | getRevisions(key, num = 10) {
method saveNewRevision (line 70) | async saveNewRevision(key, config, editedBy) {
FILE: backend/src/data/FishFish.ts
constant API_ROOT (line 5) | const API_ROOT = "https://api.fishfish.gg/v1";
type FishFishDomain (line 16) | type FishFishDomain = z.output<typeof zDomain>;
constant FULL_REFRESH_INTERVAL (line 18) | const FULL_REFRESH_INTERVAL = 6 * HOURS;
constant WS_RECONNECT_DELAY (line 23) | const WS_RECONNECT_DELAY = 30 * SECONDS;
class FishFishError (line 26) | class FishFishError extends Error {}
function getSessionToken (line 33) | async function getSessionToken(): Promise<string> {
function fishFishApiCall (line 79) | async function fishFishApiCall(method: string, path: string, query: Reco...
function refreshFishFishDomains (line 97) | async function refreshFishFishDomains() {
function initFishFish (line 134) | async function initFishFish() {
function getFishFishDomain (line 146) | function getFishFishDomain(domain: string): FishFishDomain | undefined {
FILE: backend/src/data/GuildAntiraidLevels.ts
class GuildAntiraidLevels (line 6) | class GuildAntiraidLevels extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId: string) {
method get (line 14) | async get() {
method set (line 24) | async set(level: string | null) {
FILE: backend/src/data/GuildArchives.ts
constant DEFAULT_EXPIRY_DAYS (line 14) | const DEFAULT_EXPIRY_DAYS = 30;
constant MESSAGE_ARCHIVE_HEADER_FORMAT (line 16) | const MESSAGE_ARCHIVE_HEADER_FORMAT = trimLines(`
constant MESSAGE_ARCHIVE_MESSAGE_FORMAT (line 19) | const MESSAGE_ARCHIVE_MESSAGE_FORMAT =
class GuildArchives (line 22) | class GuildArchives extends BaseGuildRepository<ArchiveEntry> {
method constructor (line 25) | constructor(guildId) {
method _processEntityFromDB (line 30) | protected async _processEntityFromDB(entity: ArchiveEntry | undefined) {
method _processEntityToDB (line 39) | protected async _processEntityToDB(entity: Partial<ArchiveEntry>) {
method find (line 46) | async find(id: string): Promise<ArchiveEntry | null> {
method makePermanent (line 54) | async makePermanent(id: string): Promise<void> {
method create (line 66) | async create(body: string, expiresAt?: moment.Moment): Promise<string> {
method renderLinesFromSavedMessages (line 81) | protected async renderLinesFromSavedMessages(savedMessages: SavedMessa...
method createFromSavedMessages (line 113) | async createFromSavedMessages(
method addSavedMessagesToArchive (line 134) | async addSavedMessagesToArchive(archiveId: string, savedMessages: Save...
method getUrl (line 149) | getUrl(baseUrl, archiveId) {
FILE: backend/src/data/GuildAutoReactions.ts
class GuildAutoReactions (line 6) | class GuildAutoReactions extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method all (line 14) | async all(): Promise<AutoReaction[]> {
method getForChannel (line 22) | async getForChannel(channelId: string): Promise<AutoReaction | null> {
method removeFromChannel (line 31) | async removeFromChannel(channelId: string) {
method set (line 38) | async set(channelId: string, reactions: string[]) {
FILE: backend/src/data/GuildButtonRoles.ts
class GuildButtonRoles (line 5) | class GuildButtonRoles extends BaseGuildRepository {
method constructor (line 8) | constructor(guildId) {
method getForButtonId (line 13) | async getForButtonId(buttonId: string) {
method getAllForMessageId (line 22) | async getAllForMessageId(messageId: string) {
method removeForButtonId (line 31) | async removeForButtonId(buttonId: string) {
method removeAllForMessageId (line 38) | async removeAllForMessageId(messageId: string) {
method getForButtonGroup (line 45) | async getForButtonGroup(buttonGroup: string) {
method add (line 54) | async add(channelId: string, messageId: string, buttonId: string, butt...
FILE: backend/src/data/GuildCases.ts
class GuildCases (line 10) | class GuildCases extends BaseGuildRepository {
method constructor (line 16) | constructor(guildId) {
method get (line 23) | async get(ids: number[]): Promise<Case[]> {
method find (line 33) | async find(id: number): Promise<Case | null> {
method findByCaseNumber (line 43) | async findByCaseNumber(caseNumber: number): Promise<Case | null> {
method findLatestByModId (line 53) | async findLatestByModId(modId: string): Promise<Case | null> {
method findByAuditLogId (line 66) | async findByAuditLogId(auditLogId: string): Promise<Case | null> {
method getByUserId (line 76) | async getByUserId(
method getRecentByUserId (line 90) | async getRecentByUserId(userId: string, count: number, skip = 0): Prom...
method getTotalCasesByModId (line 105) | async getTotalCasesByModId(
method getRecentByModId (line 119) | async getRecentByModId(
method getMinCaseNumber (line 147) | async getMinCaseNumber(): Promise<number> {
method getMaxCaseNumber (line 157) | async getMaxCaseNumber(): Promise<number> {
method setHidden (line 167) | async setHidden(id: number, hidden: boolean): Promise<void> {
method createInternal (line 176) | async createInternal(data): Promise<InsertResult> {
method create (line 209) | async create(data): Promise<Case> {
method update (line 214) | update(id, data) {
method softDelete (line 218) | async softDelete(id: number, deletedById: string, deletedByName: strin...
method createNote (line 249) | async createNote(caseId: number, data: any): Promise<void> {
method deleteAllCases (line 256) | async deleteAllCases(): Promise<void> {
method bumpCaseNumbers (line 269) | async bumpCaseNumbers(amount: number): Promise<void> {
method getExportCases (line 280) | getExportCases(skip: number, take: number): Promise<Case[]> {
FILE: backend/src/data/GuildContextMenuLinks.ts
class GuildContextMenuLinks (line 6) | class GuildContextMenuLinks extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method get (line 14) | async get(id: string): Promise<ContextMenuLink | null> {
method create (line 23) | async create(contextId: string, contextAction: string): Promise<Insert...
method deleteAll (line 31) | async deleteAll(): Promise<DeleteResult> {
FILE: backend/src/data/GuildCounters.ts
constant DELETE_UNUSED_COUNTERS_AFTER (line 12) | const DELETE_UNUSED_COUNTERS_AFTER = 1 * DAYS;
constant DELETE_UNUSED_COUNTER_TRIGGERS_AFTER (line 13) | const DELETE_UNUSED_COUNTER_TRIGGERS_AFTER = 1 * DAYS;
constant MIN_COUNTER_VALUE (line 15) | const MIN_COUNTER_VALUE = 0;
constant MAX_COUNTER_VALUE (line 16) | const MAX_COUNTER_VALUE = 2147483647;
function deleteCountersMarkedToBeDeleted (line 20) | async function deleteCountersMarkedToBeDeleted(): Promise<void> {
function deleteTriggersMarkedToBeDeleted (line 24) | async function deleteTriggersMarkedToBeDeleted(): Promise<void> {
class GuildCounters (line 34) | class GuildCounters extends BaseGuildRepository {
method constructor (line 40) | constructor(guildId) {
method findOrCreateCounter (line 48) | async findOrCreateCounter(name: string, perChannel: boolean, perUser: ...
method markUnusedCountersToBeDeleted (line 83) | async markUnusedCountersToBeDeleted(idsToKeep: number[]): Promise<void> {
method deleteCountersMarkedToBeDeleted (line 100) | async deleteCountersMarkedToBeDeleted(): Promise<void> {
method changeCounterValue (line 104) | async changeCounterValue(
method setCounterValue (line 133) | async setCounterValue(id: number, channelId: string | null, userId: st...
method decay (line 153) | decay(id: number, decayPeriodMs: number, decayAmount: number) {
method markUnusedTriggersToBeDeleted (line 204) | async markUnusedTriggersToBeDeleted(triggerIdsToKeep: number[]) {
method deleteTriggersMarkedToBeDeleted (line 234) | async deleteTriggersMarkedToBeDeleted(): Promise<void> {
method initCounterTrigger (line 238) | async initCounterTrigger(
method checkForTrigger (line 312) | async checkForTrigger(
method checkAllValuesForTrigger (line 367) | async checkAllValuesForTrigger(
method checkForReverseTrigger (line 413) | async checkForReverseTrigger(
method checkAllValuesForReverseTrigger (line 457) | async checkAllValuesForReverseTrigger(
method getCurrentValue (line 499) | async getCurrentValue(
method resetAllCounterValues (line 515) | async resetAllCounterValues(counterId: number): Promise<void> {
FILE: backend/src/data/GuildEvents.ts
type GuildEventArgs (line 7) | interface GuildEventArgs extends Record<string, unknown[]> {
type GuildEvent (line 16) | type GuildEvent = keyof GuildEventArgs;
type GuildEventListener (line 18) | type GuildEventListener<K extends GuildEvent> = (...args: GuildEventArgs...
type ListenerMap (line 20) | type ListenerMap = {
function onGuildEvent (line 29) | function onGuildEvent<K extends GuildEvent>(
function emitGuildEvent (line 48) | function emitGuildEvent<K extends GuildEvent>(guildId: string, eventName...
function hasGuildEventListener (line 61) | function hasGuildEventListener<K extends GuildEvent>(guildId: string, ev...
FILE: backend/src/data/GuildLogs.ts
type IIgnoredLog (line 7) | interface IIgnoredLog {
class GuildLogs (line 12) | class GuildLogs extends events.EventEmitter {
method constructor (line 16) | constructor(guildId) {
method log (line 30) | log(type: keyof typeof LogType, data: any, ignoreId?: string) {
method ignoreLog (line 39) | ignoreLog(type: keyof typeof LogType, ignoreId: any, timeout?: number) {
method isLogIgnored (line 51) | isLogIgnored(type: keyof typeof LogType, ignoreId: any) {
method clearIgnoredLog (line 55) | clearIgnoredLog(type: keyof typeof LogType, ignoreId: any) {
FILE: backend/src/data/GuildMemberCache.ts
constant SAVE_PENDING_BLOCKER_KEY (line 9) | const SAVE_PENDING_BLOCKER_KEY = "save-pending" as const;
constant DELETION_DELAY (line 11) | const DELETION_DELAY = 5 * MINUTES;
type UpdateData (line 13) | type UpdateData = Pick<MemberCacheItem, "username" | "nickname" | "roles">;
class GuildMemberCache (line 15) | class GuildMemberCache extends BaseGuildRepository {
method constructor (line 22) | constructor(guildId: string) {
method savePendingUpdates (line 29) | async savePendingUpdates(): Promise<void> {
method getCachedMemberData (line 46) | async getCachedMemberData(userId: string): Promise<MemberCacheItem | n...
method setCachedMemberData (line 66) | async setCachedMemberData(userId: string, data: UpdateData): Promise<v...
method markMemberForDeletion (line 79) | async markMemberForDeletion(userId: string): Promise<void> {
method unmarkMemberForDeletion (line 91) | async unmarkMemberForDeletion(userId: string): Promise<void> {
FILE: backend/src/data/GuildMemberTimezones.ts
class GuildMemberTimezones (line 6) | class GuildMemberTimezones extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId: string) {
method get (line 14) | get(memberId: string) {
method set (line 23) | async set(memberId, timezone: string) {
method reset (line 53) | reset(memberId: string) {
FILE: backend/src/data/GuildMutes.ts
type AddMuteParams (line 9) | type AddMuteParams = {
class GuildMutes (line 18) | class GuildMutes extends BaseGuildRepository {
method constructor (line 21) | constructor(guildId) {
method getExpiredMutes (line 26) | async getExpiredMutes(): Promise<Mute[]> {
method findExistingMuteForUserId (line 35) | async findExistingMuteForUserId(userId: string): Promise<Mute | null> {
method isMuted (line 44) | async isMuted(userId: string): Promise<boolean> {
method addMute (line 49) | async addMute(params: AddMuteParams): Promise<Mute> {
method updateExpiryTime (line 66) | async updateExpiryTime(userId, newExpiryTime, rolesToRestore?: string[...
method updateExpiresAt (line 93) | async updateExpiresAt(userId: string, timestamp: number | null): Promi...
method updateTimeoutExpiresAt (line 106) | async updateTimeoutExpiresAt(userId: string, timestamp: number): Promi...
method getActiveMutes (line 119) | async getActiveMutes(): Promise<Mute[]> {
method setCaseId (line 131) | async setCaseId(userId: string, caseId: number) {
method clear (line 143) | async clear(userId) {
method fillMissingMuteRole (line 150) | async fillMissingMuteRole(muteRole: string): Promise<void> {
FILE: backend/src/data/GuildNicknameHistory.ts
constant CLEANUP_INTERVAL (line 9) | const CLEANUP_INTERVAL = 5 * MINUTES;
function cleanup (line 11) | async function cleanup() {
constant MAX_NICKNAME_ENTRIES_PER_USER (line 22) | const MAX_NICKNAME_ENTRIES_PER_USER = 10;
class GuildNicknameHistory (line 24) | class GuildNicknameHistory extends BaseGuildRepository {
method constructor (line 27) | constructor(guildId) {
method getByUserId (line 32) | async getByUserId(userId): Promise<NicknameHistoryEntry[]> {
method getLastEntry (line 44) | getLastEntry(userId): Promise<NicknameHistoryEntry | null> {
method addEntry (line 56) | async addEntry(userId, nickname) {
FILE: backend/src/data/GuildPersistedData.ts
class GuildPersistedData (line 6) | class GuildPersistedData extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method find (line 14) | async find(userId: string) {
method set (line 23) | async set(userId: string, data: Partial<PersistedData> = {}) {
method clear (line 42) | async clear(userId: string) {
FILE: backend/src/data/GuildPingableRoles.ts
class GuildPingableRoles (line 6) | class GuildPingableRoles extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method all (line 14) | async all(): Promise<PingableRole[]> {
method getForChannel (line 22) | async getForChannel(channelId: string): Promise<PingableRole[]> {
method getByChannelAndRoleId (line 31) | async getByChannelAndRoleId(channelId: string, roleId: string): Promis...
method delete (line 41) | async delete(channelId: string, roleId: string) {
method add (line 49) | async add(channelId: string, roleId: string) {
FILE: backend/src/data/GuildReactionRoles.ts
class GuildReactionRoles (line 6) | class GuildReactionRoles extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method all (line 14) | async all(): Promise<ReactionRole[]> {
method getForMessage (line 22) | async getForMessage(messageId: string): Promise<ReactionRole[]> {
method getByMessageAndEmoji (line 34) | async getByMessageAndEmoji(messageId: string, emoji: string): Promise<...
method removeFromMessage (line 44) | async removeFromMessage(messageId: string, emoji?: string) {
method add (line 57) | async add(
FILE: backend/src/data/GuildReminders.ts
class GuildReminders (line 6) | class GuildReminders extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method getDueReminders (line 14) | async getDueReminders(): Promise<Reminder[]> {
method getRemindersByUserId (line 22) | async getRemindersByUserId(userId: string): Promise<Reminder[]> {
method find (line 31) | find(id: number) {
method delete (line 37) | async delete(id) {
method add (line 44) | async add(userId: string, channelId: string, remindAt: string, body: s...
FILE: backend/src/data/GuildRoleButtons.ts
class GuildRoleButtons (line 6) | class GuildRoleButtons extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method getSavedRoleButtons (line 14) | getSavedRoleButtons(): Promise<RoleButtonsItem[]> {
method deleteRoleButtonItem (line 22) | async deleteRoleButtonItem(name: string): Promise<void> {
method saveRoleButtonItem (line 29) | async saveRoleButtonItem(name: string, channelId: string, messageId: s...
FILE: backend/src/data/GuildRoleQueue.ts
class GuildRoleQueue (line 6) | class GuildRoleQueue extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method consumeNextRoleAssignments (line 14) | consumeNextRoleAssignments(count: number): Promise<RoleQueueItem[]> {
method addQueueItem (line 35) | async addQueueItem(userId: string, roleId: string, shouldAdd: boolean,...
FILE: backend/src/data/GuildSavedMessages.ts
class GuildSavedMessages (line 13) | class GuildSavedMessages extends BaseGuildRepository<SavedMessage> {
method constructor (line 19) | constructor(guildId) {
method msgToSavedMessageData (line 27) | protected msgToSavedMessageData(msg: Message): ISavedMessageData {
method _processEntityFromDB (line 133) | protected async _processEntityFromDB(entity: SavedMessage | undefined) {
method _processEntityToDB (line 142) | protected async _processEntityToDB(entity: Partial<SavedMessage>) {
method find (line 149) | async find(id: string, includeDeleted = false): Promise<SavedMessage |...
method getUserMessagesByChannelAfterId (line 164) | async getUserMessagesByChannelAfterId(userId, channelId, afterId, limi...
method getMultiple (line 182) | async getMultiple(messageIds: string[]): Promise<SavedMessage[]> {
method createFromMsg (line 196) | async createFromMsg(msg: Message, overrides = {}): Promise<void> {
method createFromMessages (line 212) | async createFromMessages(messages: Message[], overrides = {}): Promise...
method msgToInsertReadyEntity (line 220) | protected async msgToInsertReadyEntity(msg: Message): Promise<Partial<...
method insertBulk (line 235) | protected async insertBulk(items: Array<Partial<SavedMessage>>): Promi...
method fireCreateEvents (line 253) | protected async fireCreateEvents(message: SavedMessage) {
method markAsDeleted (line 258) | async markAsDeleted(id): Promise<void> {
method markBulkAsDeleted (line 281) | async markBulkAsDeleted(ids) {
method saveEdit (line 305) | async saveEdit(id, newData: ISavedMessageData): Promise<void> {
method saveEditFromMsg (line 321) | async saveEditFromMsg(msg: Message): Promise<void> {
method setPermanent (line 326) | async setPermanent(id: string): Promise<void> {
method onceMessageAvailable (line 340) | async onceMessageAvailable(
FILE: backend/src/data/GuildScheduledPosts.ts
class GuildScheduledPosts (line 6) | class GuildScheduledPosts extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method all (line 14) | all(): Promise<ScheduledPost[]> {
method getDueScheduledPosts (line 18) | getDueScheduledPosts(): Promise<ScheduledPost[]> {
method find (line 26) | find(id: number) {
method delete (line 34) | async delete(id) {
method create (line 41) | async create(data: Partial<ScheduledPost>) {
method update (line 50) | async update(id: number, data: Partial<ScheduledPost>) {
FILE: backend/src/data/GuildSlowmodes.ts
class GuildSlowmodes (line 8) | class GuildSlowmodes extends BaseGuildRepository {
method constructor (line 12) | constructor(guildId) {
method getChannelSlowmode (line 18) | async getChannelSlowmode(channelId): Promise<SlowmodeChannel | null> {
method setChannelSlowmode (line 27) | async setChannelSlowmode(channelId, seconds): Promise<void> {
method deleteChannelSlowmode (line 48) | async deleteChannelSlowmode(channelId): Promise<void> {
method getChannelSlowmodeUser (line 55) | async getChannelSlowmodeUser(channelId, userId): Promise<SlowmodeUser ...
method userHasSlowmode (line 65) | async userHasSlowmode(channelId, userId): Promise<boolean> {
method addSlowmodeUser (line 69) | async addSlowmodeUser(channelId, userId): Promise<void> {
method clearSlowmodeUser (line 98) | async clearSlowmodeUser(channelId, userId): Promise<void> {
method getChannelSlowmodeUsers (line 106) | async getChannelSlowmodeUsers(channelId): Promise<SlowmodeUser[]> {
method getExpiredSlowmodeUsers (line 115) | async getExpiredSlowmodeUsers(): Promise<SlowmodeUser[]> {
FILE: backend/src/data/GuildStarboardMessages.ts
class GuildStarboardMessages (line 6) | class GuildStarboardMessages extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method getStarboardMessagesForMessageId (line 14) | async getStarboardMessagesForMessageId(messageId: string) {
method getStarboardMessagesForStarboardMessageId (line 22) | async getStarboardMessagesForStarboardMessageId(starboardMessageId: st...
method getMatchingStarboardMessages (line 30) | async getMatchingStarboardMessages(starboardChannelId: string, sourceM...
method createStarboardMessage (line 39) | async createStarboardMessage(starboardId: string, messageId: string, s...
method deleteStarboardMessage (line 48) | async deleteStarboardMessage(starboardMessageId: string, starboardChan...
FILE: backend/src/data/GuildStarboardReactions.ts
class GuildStarboardReactions (line 6) | class GuildStarboardReactions extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method getAllReactionsForMessageId (line 14) | async getAllReactionsForMessageId(messageId: string) {
method createStarboardReaction (line 22) | async createStarboardReaction(messageId: string, reactorId: string) {
method deleteAllStarboardReactionsForMessageId (line 42) | async deleteAllStarboardReactionsForMessageId(messageId: string) {
method deleteStarboardReaction (line 49) | async deleteStarboardReaction(messageId: string, reactorId: string) {
FILE: backend/src/data/GuildStats.ts
class GuildStats (line 6) | class GuildStats extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method saveValue (line 14) | async saveValue(source: string, key: string, value: number): Promise<v...
method deleteOldValues (line 23) | async deleteOldValues(source: string, cutoff: string): Promise<void> {
FILE: backend/src/data/GuildTags.ts
class GuildTags (line 7) | class GuildTags extends BaseGuildRepository {
method constructor (line 11) | constructor(guildId) {
method all (line 17) | async all(): Promise<Tag[]> {
method find (line 25) | async find(tag): Promise<Tag | null> {
method createOrUpdate (line 34) | async createOrUpdate(tag, body, userId) {
method delete (line 58) | async delete(tag) {
method findResponseByCommandMessageId (line 65) | async findResponseByCommandMessageId(messageId: string): Promise<TagRe...
method findResponseByResponseMessageId (line 74) | async findResponseByResponseMessageId(messageId: string): Promise<TagR...
method addResponse (line 83) | async addResponse(cmdMessageId, responseMessageId) {
method deleteResponseByCommandMessageId (line 91) | async deleteResponseByCommandMessageId(messageId: string): Promise<voi...
method deleteResponseByResponseMessageId (line 98) | async deleteResponseByResponseMessageId(messageId: string): Promise<vo...
FILE: backend/src/data/GuildTempbans.ts
class GuildTempbans (line 7) | class GuildTempbans extends BaseGuildRepository {
method constructor (line 10) | constructor(guildId) {
method getExpiredTempbans (line 15) | async getExpiredTempbans(): Promise<Tempban[]> {
method findExistingTempbanForUserId (line 24) | async findExistingTempbanForUserId(userId: string): Promise<Tempban | ...
method addTempban (line 33) | async addTempban(userId, expiryTime, modId): Promise<Tempban> {
method updateExpiryTime (line 47) | async updateExpiryTime(userId, newExpiryTime, modId) {
method clear (line 63) | async clear(userId) {
FILE: backend/src/data/GuildVCAlerts.ts
class GuildVCAlerts (line 6) | class GuildVCAlerts extends BaseGuildRepository {
method constructor (line 9) | constructor(guildId) {
method getOutdatedAlerts (line 14) | async getOutdatedAlerts(): Promise<VCAlert[]> {
method getAllGuildAlerts (line 22) | async getAllGuildAlerts(): Promise<VCAlert[]> {
method getAlertsByUserId (line 26) | async getAlertsByUserId(userId: string): Promise<VCAlert[]> {
method getAlertsByRequestorId (line 35) | async getAlertsByRequestorId(requestorId: string): Promise<VCAlert[]> {
method find (line 44) | find(id: number) {
method delete (line 50) | async delete(id) {
method add (line 57) | async add(requestorId: string, userId: string, channelId: string, expi...
FILE: backend/src/data/MemberCache.ts
constant STALE_PERIOD (line 8) | const STALE_PERIOD = 90 * DAYS;
class MemberCache (line 10) | class MemberCache extends BaseRepository {
method constructor (line 13) | constructor() {
method deleteStaleData (line 18) | async deleteStaleData(): Promise<void> {
method deleteMarkedToBeDeletedEntries (line 23) | async deleteMarkedToBeDeletedEntries(): Promise<void> {
FILE: backend/src/data/MuteTypes.ts
type MuteTypes (line 1) | enum MuteTypes {
FILE: backend/src/data/Mutes.ts
constant OLD_EXPIRED_MUTE_THRESHOLD (line 9) | const OLD_EXPIRED_MUTE_THRESHOLD = 7 * DAYS;
constant MAX_TIMEOUT_DURATION (line 11) | const MAX_TIMEOUT_DURATION = 27 * DAYS;
constant TIMEOUT_RENEWAL_THRESHOLD (line 13) | const TIMEOUT_RENEWAL_THRESHOLD = 21 * DAYS;
class Mutes (line 15) | class Mutes extends BaseRepository {
method constructor (line 18) | constructor() {
method findMute (line 23) | findMute(guildId: string, userId: string): Promise<Mute | null> {
method getSoonExpiringMutes (line 32) | getSoonExpiringMutes(threshold: number): Promise<Mute[]> {
method getTimeoutMutesToRenew (line 41) | getTimeoutMutesToRenew(threshold: number): Promise<Mute[]> {
method clearOldExpiredMutes (line 51) | async clearOldExpiredMutes(): Promise<void> {
FILE: backend/src/data/Reminders.ts
class Reminders (line 8) | class Reminders extends BaseRepository {
method constructor (line 11) | constructor() {
method getRemindersDueSoon (line 16) | async getRemindersDueSoon(threshold: number): Promise<Reminder[]> {
FILE: backend/src/data/ScheduledPosts.ts
class ScheduledPosts (line 8) | class ScheduledPosts extends BaseRepository {
method constructor (line 11) | constructor() {
method getScheduledPostsDueSoon (line 16) | getScheduledPostsDueSoon(threshold: number): Promise<ScheduledPost[]> {
FILE: backend/src/data/Supporters.ts
class Supporters (line 6) | class Supporters extends BaseRepository {
method constructor (line 9) | constructor() {
method getAll (line 14) | getAll() {
FILE: backend/src/data/Tempbans.ts
class Tempbans (line 8) | class Tempbans extends BaseRepository {
method constructor (line 11) | constructor() {
method getSoonExpiringTempbans (line 16) | getSoonExpiringTempbans(threshold: number): Promise<Tempban[]> {
FILE: backend/src/data/UsernameHistory.ts
constant CLEANUP_INTERVAL (line 9) | const CLEANUP_INTERVAL = 5 * MINUTES;
function cleanup (line 11) | async function cleanup() {
constant MAX_USERNAME_ENTRIES_PER_USER (line 22) | const MAX_USERNAME_ENTRIES_PER_USER = 5;
class UsernameHistory (line 24) | class UsernameHistory extends BaseRepository {
method constructor (line 27) | constructor() {
method getByUserId (line 32) | async getByUserId(userId): Promise<UsernameHistoryEntry[]> {
method getLastEntry (line 44) | getLastEntry(userId): Promise<UsernameHistoryEntry | null> {
method addEntry (line 55) | async addEntry(userId, username) {
FILE: backend/src/data/VCAlerts.ts
class VCAlerts (line 8) | class VCAlerts extends BaseRepository {
method constructor (line 11) | constructor() {
method getSoonExpiringAlerts (line 16) | async getSoonExpiringAlerts(threshold: number): Promise<VCAlert[]> {
FILE: backend/src/data/Webhooks.ts
class Webhooks (line 7) | class Webhooks extends BaseRepository {
method _processEntityFromDB (line 10) | protected async _processEntityFromDB(entity) {
method _processEntityToDB (line 15) | protected async _processEntityToDB(entity) {
method find (line 20) | async find(id: string): Promise<Webhook | null> {
method findByChannelId (line 30) | async findByChannelId(channelId: string): Promise<Webhook | null> {
method create (line 40) | async create(data: Partial<Webhook>): Promise<void> {
method delete (line 45) | async delete(id: string): Promise<void> {
FILE: backend/src/data/apiAuditLogTypes.ts
type AuditLogEventType (line 10) | type AuditLogEventType = keyof typeof AuditLogEventTypes;
type AddApiPermissionEventData (line 12) | type AddApiPermissionEventData = {
type RemoveApiPermissionEventData (line 18) | type RemoveApiPermissionEventData = {
type AuditLogEventData (line 22) | interface AuditLogEventData extends Record<AuditLogEventType, unknown> {
type AnyAuditLogEventData (line 45) | type AnyAuditLogEventData = AuditLogEventData[AuditLogEventType];
FILE: backend/src/data/buildEntity.ts
function buildEntity (line 1) | function buildEntity<T extends object>(Entity: new () => T, data: Partia...
FILE: backend/src/data/cleanup/configs.ts
constant CLEAN_PER_LOOP (line 7) | const CLEAN_PER_LOOP = 50;
function cleanupConfigs (line 9) | async function cleanupConfigs() {
FILE: backend/src/data/cleanup/messages.ts
constant RETENTION_PERIOD (line 11) | const RETENTION_PERIOD = 1 * DAYS;
constant BOT_MESSAGE_RETENTION_PERIOD (line 12) | const BOT_MESSAGE_RETENTION_PERIOD = 30 * MINUTES;
constant DELETED_MESSAGE_RETENTION_PERIOD (line 13) | const DELETED_MESSAGE_RETENTION_PERIOD = 5 * MINUTES;
constant CLEAN_PER_LOOP (line 14) | const CLEAN_PER_LOOP = 100;
function cleanupMessages (line 16) | async function cleanupMessages(): Promise<number> {
FILE: backend/src/data/cleanup/nicknames.ts
constant NICKNAME_RETENTION_PERIOD (line 7) | const NICKNAME_RETENTION_PERIOD = 30 * DAYS;
constant CLEAN_PER_LOOP (line 8) | const CLEAN_PER_LOOP = 500;
function cleanupNicknames (line 10) | async function cleanupNicknames(): Promise<number> {
FILE: backend/src/data/cleanup/usernames.ts
constant USERNAME_RETENTION_PERIOD (line 7) | const USERNAME_RETENTION_PERIOD = 30 * DAYS;
constant CLEAN_PER_LOOP (line 8) | const CLEAN_PER_LOOP = 500;
function cleanupUsernames (line 10) | async function cleanupUsernames(): Promise<number> {
FILE: backend/src/data/dataSource.ts
method typeCast (line 33) | typeCast(field, next) {
FILE: backend/src/data/db.ts
function connect (line 6) | function connect() {
function disconnect (line 19) | function disconnect() {
FILE: backend/src/data/entities/AllowedGuild.ts
class AllowedGuild (line 4) | class AllowedGuild {
FILE: backend/src/data/entities/AntiraidLevel.ts
class AntiraidLevel (line 4) | class AntiraidLevel {
FILE: backend/src/data/entities/ApiAuditLogEntry.ts
class ApiAuditLogEntry (line 5) | class ApiAuditLogEntry<TEventType extends AuditLogEventType> {
FILE: backend/src/data/entities/ApiLogin.ts
class ApiLogin (line 5) | class ApiLogin {
FILE: backend/src/data/entities/ApiPermissionAssignment.ts
class ApiPermissionAssignment (line 6) | class ApiPermissionAssignment {
FILE: backend/src/data/entities/ApiUserInfo.ts
type ApiUserInfoData (line 5) | interface ApiUserInfoData {
class ApiUserInfo (line 12) | class ApiUserInfo {
FILE: backend/src/data/entities/ArchiveEntry.ts
class ArchiveEntry (line 4) | class ArchiveEntry {
FILE: backend/src/data/entities/AutoReaction.ts
class AutoReaction (line 4) | class AutoReaction {
FILE: backend/src/data/entities/ButtonRole.ts
class ButtonRole (line 4) | class ButtonRole {
FILE: backend/src/data/entities/Case.ts
class Case (line 5) | class Case {
FILE: backend/src/data/entities/CaseNote.ts
class CaseNote (line 5) | class CaseNote {
FILE: backend/src/data/entities/Config.ts
class Config (line 5) | class Config {
FILE: backend/src/data/entities/ContextMenuLink.ts
class ContextMenuLink (line 4) | class ContextMenuLink {
FILE: backend/src/data/entities/Counter.ts
class Counter (line 4) | class Counter {
FILE: backend/src/data/entities/CounterTrigger.ts
constant TRIGGER_COMPARISON_OPS (line 3) | const TRIGGER_COMPARISON_OPS = ["=", "!=", ">", "<", ">=", "<="] as const;
type TriggerComparisonOp (line 5) | type TriggerComparisonOp = (typeof TRIGGER_COMPARISON_OPS)[number];
constant REVERSE_OPS (line 7) | const REVERSE_OPS: Record<TriggerComparisonOp, TriggerComparisonOp> = {
function getReverseCounterComparisonOp (line 16) | function getReverseCounterComparisonOp(op: TriggerComparisonOp): Trigger...
function parseCounterConditionString (line 25) | function parseCounterConditionString(str: string): [TriggerComparisonOp,...
function buildCounterConditionString (line 30) | function buildCounterConditionString(comparisonOp: TriggerComparisonOp, ...
function isValidCounterComparisonOp (line 34) | function isValidCounterComparisonOp(op: string): boolean {
class CounterTrigger (line 39) | class CounterTrigger {
FILE: backend/src/data/entities/CounterTriggerState.ts
class CounterTriggerState (line 4) | class CounterTriggerState {
FILE: backend/src/data/entities/CounterValue.ts
class CounterValue (line 4) | class CounterValue {
FILE: backend/src/data/entities/MemberCacheItem.ts
class MemberCacheItem (line 4) | class MemberCacheItem {
FILE: backend/src/data/entities/MemberTimezone.ts
class MemberTimezone (line 4) | class MemberTimezone {
FILE: backend/src/data/entities/Mute.ts
class Mute (line 4) | class Mute {
FILE: backend/src/data/entities/NicknameHistoryEntry.ts
class NicknameHistoryEntry (line 4) | class NicknameHistoryEntry {
FILE: backend/src/data/entities/PersistedData.ts
class PersistedData (line 4) | class PersistedData {
FILE: backend/src/data/entities/PingableRole.ts
class PingableRole (line 4) | class PingableRole {
FILE: backend/src/data/entities/ReactionRole.ts
class ReactionRole (line 4) | class ReactionRole {
FILE: backend/src/data/entities/Reminder.ts
class Reminder (line 4) | class Reminder {
FILE: backend/src/data/entities/RoleButtonsItem.ts
class RoleButtonsItem (line 4) | class RoleButtonsItem {
FILE: backend/src/data/entities/RoleQueueItem.ts
class RoleQueueItem (line 4) | class RoleQueueItem {
FILE: backend/src/data/entities/SavedMessage.ts
type ISavedMessageAttachmentData (line 4) | interface ISavedMessageAttachmentData {
type ISavedMessageEmbedData (line 15) | interface ISavedMessageEmbedData {
type ISavedMessageStickerData (line 58) | interface ISavedMessageStickerData {
type ISavedMessageData (line 68) | interface ISavedMessageData {
class SavedMessage (line 86) | class SavedMessage {
FILE: backend/src/data/entities/ScheduledPost.ts
class ScheduledPost (line 6) | class ScheduledPost {
FILE: backend/src/data/entities/SlowmodeChannel.ts
class SlowmodeChannel (line 4) | class SlowmodeChannel {
FILE: backend/src/data/entities/SlowmodeUser.ts
class SlowmodeUser (line 4) | class SlowmodeUser {
FILE: backend/src/data/entities/StarboardMessage.ts
class StarboardMessage (line 5) | class StarboardMessage {
FILE: backend/src/data/entities/StarboardReaction.ts
class StarboardReaction (line 5) | class StarboardReaction {
FILE: backend/src/data/entities/StatValue.ts
class StatValue (line 4) | class StatValue {
FILE: backend/src/data/entities/Supporter.ts
class Supporter (line 4) | class Supporter {
FILE: backend/src/data/entities/Tag.ts
class Tag (line 4) | class Tag {
FILE: backend/src/data/entities/TagResponse.ts
class TagResponse (line 4) | class TagResponse {
FILE: backend/src/data/entities/Tempban.ts
class Tempban (line 4) | class Tempban {
FILE: backend/src/data/entities/UsernameHistoryEntry.ts
class UsernameHistoryEntry (line 4) | class UsernameHistoryEntry {
FILE: backend/src/data/entities/VCAlert.ts
class VCAlert (line 4) | class VCAlert {
FILE: backend/src/data/entities/Webhook.ts
class Webhook (line 4) | class Webhook {
FILE: backend/src/data/getChannelIdFromMessageId.ts
function getChannelIdFromMessageId (line 7) | async function getChannelIdFromMessageId(messageId: string): Promise<str...
FILE: backend/src/data/loops/expiredArchiveDeletionLoop.ts
constant LOOP_INTERVAL (line 6) | const LOOP_INTERVAL = 15 * MINUTES;
function runExpiredArchiveDeletionLoop (line 9) | async function runExpiredArchiveDeletionLoop() {
FILE: backend/src/data/loops/expiredMemberCacheDeletionLoop.ts
constant LOOP_INTERVAL (line 6) | const LOOP_INTERVAL = 6 * HOURS;
function runExpiredMemberCacheDeletionLoop (line 9) | async function runExpiredMemberCacheDeletionLoop() {
FILE: backend/src/data/loops/expiringMutesLoop.ts
constant LOOP_INTERVAL (line 10) | const LOOP_INTERVAL = 15 * MINUTES;
constant MAX_TRIES_PER_SERVER (line 11) | const MAX_TRIES_PER_SERVER = 3;
function muteToKey (line 15) | function muteToKey(mute: Mute) {
function broadcastExpiredMute (line 19) | async function broadcastExpiredMute(guildId: string, userId: string, tri...
function broadcastTimeoutMuteToRenew (line 44) | function broadcastTimeoutMuteToRenew(mute: Mute, tries = 0) {
function runExpiringMutesLoop (line 59) | async function runExpiringMutesLoop() {
function registerExpiringMute (line 88) | function registerExpiringMute(mute: Mute) {
function clearExpiringMute (line 107) | function clearExpiringMute(mute: Mute) {
FILE: backend/src/data/loops/expiringTempbansLoop.ts
constant LOOP_INTERVAL (line 10) | const LOOP_INTERVAL = 15 * MINUTES;
constant MAX_TRIES_PER_SERVER (line 11) | const MAX_TRIES_PER_SERVER = 3;
function tempbanToKey (line 15) | function tempbanToKey(tempban: Tempban) {
function broadcastExpiredTempban (line 19) | function broadcastExpiredTempban(tempban: Tempban, tries = 0) {
function runExpiringTempbansLoop (line 34) | async function runExpiringTempbansLoop() {
function registerExpiringTempban (line 54) | function registerExpiringTempban(tempban: Tempban) {
function clearExpiringTempban (line 69) | function clearExpiringTempban(tempban: Tempban) {
FILE: backend/src/data/loops/expiringVCAlertsLoop.ts
constant LOOP_INTERVAL (line 10) | const LOOP_INTERVAL = 15 * MINUTES;
constant MAX_TRIES_PER_SERVER (line 11) | const MAX_TRIES_PER_SERVER = 3;
function broadcastExpiredVCAlert (line 15) | function broadcastExpiredVCAlert(alert: VCAlert, tries = 0) {
function runExpiringVCAlertsLoop (line 30) | async function runExpiringVCAlertsLoop() {
function registerExpiringVCAlert (line 50) | function registerExpiringVCAlert(alert: VCAlert) {
function clearExpiringVCAlert (line 65) | function clearExpiringVCAlert(alert: VCAlert) {
FILE: backend/src/data/loops/memberCacheDeletionLoop.ts
constant LOOP_INTERVAL (line 6) | const LOOP_INTERVAL = 5 * MINUTES;
function runMemberCacheDeletionLoop (line 9) | async function runMemberCacheDeletionLoop() {
FILE: backend/src/data/loops/savedMessageCleanupLoop.ts
constant LOOP_INTERVAL (line 6) | const LOOP_INTERVAL = 5 * MINUTES;
function runSavedMessageCleanupLoop (line 8) | async function runSavedMessageCleanupLoop() {
FILE: backend/src/data/loops/upcomingRemindersLoop.ts
constant LOOP_INTERVAL (line 10) | const LOOP_INTERVAL = 15 * MINUTES;
constant MAX_TRIES_PER_SERVER (line 11) | const MAX_TRIES_PER_SERVER = 3;
function broadcastReminder (line 15) | function broadcastReminder(reminder: Reminder, tries = 0) {
function runUpcomingRemindersLoop (line 29) | async function runUpcomingRemindersLoop() {
function registerUpcomingReminder (line 49) | function registerUpcomingReminder(reminder: Reminder) {
function clearUpcomingReminder (line 64) | function clearUpcomingReminder(reminder: Reminder) {
FILE: backend/src/data/loops/upcomingScheduledPostsLoop.ts
constant LOOP_INTERVAL (line 10) | const LOOP_INTERVAL = 15 * MINUTES;
constant MAX_TRIES_PER_SERVER (line 11) | const MAX_TRIES_PER_SERVER = 3;
function broadcastScheduledPost (line 15) | function broadcastScheduledPost(post: ScheduledPost, tries = 0) {
function runUpcomingScheduledPostsLoop (line 29) | async function runUpcomingScheduledPostsLoop() {
function registerUpcomingScheduledPost (line 49) | function registerUpcomingScheduledPost(post: ScheduledPost) {
function clearUpcomingScheduledPost (line 68) | function clearUpcomingScheduledPost(post: ScheduledPost) {
FILE: backend/src/data/queryLogger.ts
class QueryLogger (line 10) | class QueryLogger extends AdvancedConsoleLogger {
method logQuery (line 11) | logQuery(query: string): any {
function consumeQueryStats (line 37) | function consumeQueryStats() {
FILE: backend/src/data/redis.ts
type RedisClient (line 5) | type RedisClient = ReturnType<typeof createClient>;
FILE: backend/src/debugCounters.ts
type DebugCounterValue (line 1) | type DebugCounterValue = {
function incrementDebugCounter (line 6) | function incrementDebugCounter(name: string) {
function getDebugCounterValues (line 13) | function getDebugCounterValues() {
FILE: backend/src/exportSchemas.ts
method zzz_dummy_property_do_not_use (line 39) | get zzz_dummy_property_do_not_use() {
method all (line 42) | get all() {
method any (line 45) | get any() {
method not (line 48) | get not() {
function getPartialConfig (line 63) | function getPartialConfig(configSchema: z.ZodType) {
function overrides (line 70) | function overrides(configSchema: z.ZodType): z.ZodType {
FILE: backend/src/globals.ts
function isAPI (line 3) | function isAPI() {
function setIsAPI (line 7) | function setIsAPI(value: boolean) {
FILE: backend/src/index.ts
constant RECENT_PLUGIN_ERROR_EXIT_THRESHOLD (line 60) | const RECENT_PLUGIN_ERROR_EXIT_THRESHOLD = 5;
constant RECENT_DISCORD_ERROR_EXIT_THRESHOLD (line 63) | const RECENT_DISCORD_ERROR_EXIT_THRESHOLD = 5;
constant SAFE_TO_IGNORE_ERIS_ERROR_CODES (line 69) | const SAFE_TO_IGNORE_ERIS_ERROR_CODES = [
constant SAFE_TO_IGNORE_ERIS_ERROR_MESSAGES (line 75) | const SAFE_TO_IGNORE_ERIS_ERROR_MESSAGES = ["Server didn't acknowledge p...
function errorHandler (line 80) | function errorHandler(err) {
constant REQUIRED_NODE_VERSION (line 182) | const REQUIRED_NODE_VERSION = "16.9.0";
method canLoadGuild (line 303) | canLoadGuild(guildId): Promise<boolean> {
method getEnabledGuildPlugins (line 313) | async getEnabledGuildPlugins(ctx, plugins): Promise<string[]> {
method getConfig (line 327) | async getConfig(id) {
method sendSuccessMessageFn (line 388) | sendSuccessMessageFn(channel, body) {
method sendErrorMessageFn (line 396) | sendErrorMessageFn(channel, body) {
FILE: backend/src/logger.ts
method info (line 4) | info(...args: Parameters<typeof console.log>) {
method warn (line 8) | warn(...args: Parameters<typeof console.warn>) {
method error (line 12) | error(...args: Parameters<typeof console.error>) {
method debug (line 16) | debug(...args: Parameters<typeof console.log>) {
method log (line 20) | log(...args: Parameters<typeof console.log>) {
FILE: backend/src/migrations/1540519249973-CreatePreTypeORMTables.ts
class CreatePreTypeORMTables1540519249973 (line 3) | class CreatePreTypeORMTables1540519249973 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 109) | public async down(): Promise<any> {
FILE: backend/src/migrations/1543053430712-CreateMessagesTable.ts
class CreateMessagesTable1543053430712 (line 3) | class CreateMessagesTable1543053430712 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 69) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1544877081073-CreateSlowmodeTables.ts
class CreateSlowmodeTables1544877081073 (line 3) | class CreateSlowmodeTables1544877081073 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 67) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1544887946307-CreateStarboardTable.ts
class CreateStarboardTable1544887946307 (line 3) | class CreateStarboardTable1544887946307 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 82) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1546770935261-CreateTagResponsesTable.ts
class CreateTagResponsesTable1546770935261 (line 3) | class CreateTagResponsesTable1546770935261 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 56) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1546778415930-CreateNameHistoryTable.ts
class CreateNameHistoryTable1546778415930 (line 3) | class CreateNameHistoryTable1546778415930 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 59) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1546788508314-MakeNameHistoryValueLengthLonger.ts
class MakeNameHistoryValueLengthLonger1546788508314 (line 3) | class MakeNameHistoryValueLengthLonger1546788508314 implements Migration...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 11) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1547290549908-CreateAutoReactionsTable.ts
class CreateAutoReactionsTable1547290549908 (line 3) | class CreateAutoReactionsTable1547290549908 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 30) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1547293464842-CreatePingableRolesTable.ts
class CreatePingableRolesTable1547293464842 (line 3) | class CreatePingableRolesTable1547293464842 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 46) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1547392046629-AddIndexToArchivesExpiresAt.ts
class AddIndexToArchivesExpiresAt1547392046629 (line 3) | class AddIndexToArchivesExpiresAt1547392046629 implements MigrationInter...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 13) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1547393619900-AddIsHiddenToCases.ts
class AddIsHiddenToCases1547393619900 (line 3) | class AddIsHiddenToCases1547393619900 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 22) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1549649586803-AddPPFieldsToCases.ts
class AddPPFieldsToCases1549649586803 (line 3) | class AddPPFieldsToCases1549649586803 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 12) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1550409894008-FixEmojiIndexInReactionRoles.ts
class FixEmojiIndexInReactionRoles1550409894008 (line 3) | class FixEmojiIndexInReactionRoles1550409894008 implements MigrationInte...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 13) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1550521627877-CreateSelfGrantableRolesTable.ts
class CreateSelfGrantableRolesTable1550521627877 (line 3) | class CreateSelfGrantableRolesTable1550521627877 implements MigrationInt...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 48) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1550609900261-CreateRemindersTable.ts
class CreateRemindersTable1550609900261 (line 3) | class CreateRemindersTable1550609900261 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 50) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1556908589679-CreateUsernameHistoryTable.ts
class CreateUsernameHistoryTable1556908589679 (line 3) | class CreateUsernameHistoryTable1556908589679 implements MigrationInterf...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 43) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1556909512501-MigrateUsernamesToNewHistoryTable.ts
constant BATCH_SIZE (line 3) | const BATCH_SIZE = 200;
class MigrateUsernamesToNewHistoryTable1556909512501 (line 5) | class MigrateUsernamesToNewHistoryTable1556909512501 implements Migratio...
method up (line 6) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 81) | public async down(): Promise<any> {}
FILE: backend/src/migrations/1556913287547-TurnNameHistoryToNicknameHistory.ts
class TurnNameHistoryToNicknameHistory1556913287547 (line 3) | class TurnNameHistoryToNicknameHistory1556913287547 implements Migration...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 19) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1556973844545-CreateScheduledPostsTable.ts
class CreateScheduledPostsTable1556973844545 (line 3) | class CreateScheduledPostsTable1556973844545 implements MigrationInterfa...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 65) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1558804433320-CreateDashboardLoginsTable.ts
class CreateDashboardLoginsTable1558804433320 (line 3) | class CreateDashboardLoginsTable1558804433320 implements MigrationInterf...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 51) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1558804449510-CreateDashboardUsersTable.ts
class CreateDashboardUsersTable1558804449510 (line 3) | class CreateDashboardUsersTable1558804449510 implements MigrationInterfa...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 41) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1561111990357-CreateConfigsTable.ts
class CreateConfigsTable1561111990357 (line 3) | class CreateConfigsTable1561111990357 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 48) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1561117545258-CreateAllowedGuildsTable.ts
class CreateAllowedGuildsTable1561117545258 (line 3) | class CreateAllowedGuildsTable1561117545258 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 36) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1561282151982-RenameBackendDashboardStuffToAPI.ts
class RenameBackendDashboardStuffToAPI1561282151982 (line 3) | class RenameBackendDashboardStuffToAPI1561282151982 implements Migration...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 9) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1561282552734-RenameAllowedGuildGuildIdToId.ts
class RenameAllowedGuildGuildIdToId1561282552734 (line 3) | class RenameAllowedGuildGuildIdToId1561282552734 implements MigrationInt...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 8) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1561282950483-CreateApiUserInfoTable.ts
class CreateApiUserInfoTable1561282950483 (line 3) | class CreateApiUserInfoTable1561282950483 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 28) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1561283165823-RenameApiUsersToApiPermissions.ts
class RenameApiUsersToApiPermissions1561283165823 (line 3) | class RenameApiUsersToApiPermissions1561283165823 implements MigrationIn...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 8) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1561283405201-DropUserDataFromLoginsAndPermissions.ts
class DropUserDataFromLoginsAndPermissions1561283405201 (line 3) | class DropUserDataFromLoginsAndPermissions1561283405201 implements Migra...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 9) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1561391921385-AddVCAlertTable.ts
class AddVCAlertTable1561391921385 (line 3) | class AddVCAlertTable1561391921385 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 55) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1562838838927-AddMoreIndicesToVCAlerts.ts
class AddMoreIndicesToVCAlerts1562838838927 (line 3) | class AddMoreIndicesToVCAlerts1562838838927 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 18) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1573158035867-AddTypeAndPermissionsToApiPermissions.ts
class AddTypeAndPermissionsToApiPermissions1573158035867 (line 3) | class AddTypeAndPermissionsToApiPermissions1573158035867 implements Migr...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 51) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1573248462469-MoveStarboardsToConfig.ts
class MoveStarboardsToConfig1573248462469 (line 3) | class MoveStarboardsToConfig1573248462469 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 48) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1573248794313-CreateStarboardReactionsTable.ts
class CreateStarboardReactionsTable1573248794313 (line 3) | class CreateStarboardReactionsTable1573248794313 implements MigrationInt...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 41) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1575145703039-AddIsExclusiveToReactionRoles.ts
class AddIsExclusiveToReactionRoles1575145703039 (line 3) | class AddIsExclusiveToReactionRoles1575145703039 implements MigrationInt...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 16) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1575199835233-CreateStatsTable.ts
class CreateStatsTable1575199835233 (line 3) | class CreateStatsTable1575199835233 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 56) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1575230079526-AddRepeatColumnsToScheduledPosts.ts
class AddRepeatColumnsToScheduledPosts1575230079526 (line 3) | class AddRepeatColumnsToScheduledPosts1575230079526 implements Migration...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 26) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1578445483917-CreateReminderCreatedAtField.ts
class CreateReminderCreatedAtField1578445483917 (line 3) | class CreateReminderCreatedAtField1578445483917 implements MigrationInte...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 15) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1580038836906-CreateAntiraidLevelsTable.ts
class CreateAntiraidLevelsTable1580038836906 (line 3) | class CreateAntiraidLevelsTable1580038836906 implements MigrationInterfa...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 25) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1580654617890-AddActiveFollowsToLocateUser.ts
class AddActiveFollowsToLocateUser1580654617890 (line 3) | class AddActiveFollowsToLocateUser1580654617890 implements MigrationInte...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 15) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1590616691907-CreateSupportersTable.ts
class CreateSupportersTable1590616691907 (line 3) | class CreateSupportersTable1590616691907 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 33) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1591036185142-OptimizeMessageIndices.ts
class OptimizeMessageIndices1591036185142 (line 3) | class OptimizeMessageIndices1591036185142 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 30) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1591038041635-OptimizeMessageTimestamps.ts
class OptimizeMessageTimestamps1591038041635 (line 3) | class OptimizeMessageTimestamps1591038041635 implements MigrationInterfa...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 13) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1596994103885-AddCaseNotesForeignKey.ts
class AddCaseNotesForeignKey1596994103885 (line 3) | class AddCaseNotesForeignKey1596994103885 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 18) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1597015567215-AddLogMessageIdToCases.ts
class AddLogMessageIdToCases1597015567215 (line 3) | class AddLogMessageIdToCases1597015567215 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 17) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1597109357201-CreateMemberTimezonesTable.ts
class CreateMemberTimezonesTable1597109357201 (line 3) | class CreateMemberTimezonesTable1597109357201 implements MigrationInterf...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 29) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1600283341726-EncryptExistingMessages.ts
class EncryptExistingMessages1600283341726 (line 4) | class EncryptExistingMessages1600283341726 implements MigrationInterface {
method up (line 5) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 17) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1600285077890-EncryptArchives.ts
class EncryptArchives1600285077890 (line 4) | class EncryptArchives1600285077890 implements MigrationInterface {
method up (line 5) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 13) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1608608903570-CreateRestoredRolesColumn.ts
class CreateRestoredRolesColumn1608608903570 (line 3) | class CreateRestoredRolesColumn1608608903570 implements MigrationInterfa...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 15) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1608692857722-FixStarboardReactionsIndices.ts
class FixStarboardReactionsIndices1608692857722 (line 3) | class FixStarboardReactionsIndices1608692857722 implements MigrationInte...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 22) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1608753440716-CreateTempBansTable.ts
class CreateTempBansTable1608753440716 (line 3) | class CreateTempBansTable1608753440716 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 42) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1612010765767-CreateCounterTables.ts
class CreateCounterTables1612010765767 (line 3) | class CreateCounterTables1612010765767 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<any> {
method down (line 197) | public async down(queryRunner: QueryRunner): Promise<any> {
FILE: backend/src/migrations/1617363975046-UpdateCounterTriggers.ts
class UpdateCounterTriggers1617363975046 (line 3) | class UpdateCounterTriggers1617363975046 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 54) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1622939525343-OrderReactionRoles.ts
class OrderReactionRoles1622939525343 (line 3) | class OrderReactionRoles1622939525343 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 15) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1623018101018-CreateButtonRolesTable.ts
class CreateButtonRolesTable1623018101018 (line 3) | class CreateButtonRolesTable1623018101018 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 46) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1628809879962-CreateContextMenuTable.ts
class CreateContextMenuTable1628809879962 (line 3) | class CreateContextMenuTable1628809879962 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 29) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1630837386329-AddExpiresAtToApiPermissions.ts
class AddExpiresAtToApiPermissions1630837386329 (line 3) | class AddExpiresAtToApiPermissions1630837386329 implements MigrationInte...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 15) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1630837718830-CreateApiAuditLogTable.ts
class CreateApiAuditLogTable1630837718830 (line 3) | class CreateApiAuditLogTable1630837718830 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 55) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1630840428694-AddTimestampsToAllowedGuilds.ts
class AddTimestampsToAllowedGuilds1630840428694 (line 3) | class AddTimestampsToAllowedGuilds1630840428694 implements MigrationInte...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 20) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1631474131804-AddIndexToIsBot.ts
class AddIndexToIsBot1631474131804 (line 3) | class AddIndexToIsBot1631474131804 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 13) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1632582078622-SplitScheduledPostsPostAtIndex.ts
class SplitScheduledPostsPostAtIndex1632582078622 (line 3) | class SplitScheduledPostsPostAtIndex1632582078622 implements MigrationIn...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 20) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1632582299400-AddIndexToRemindersRemindAt.ts
class AddIndexToRemindersRemindAt1632582299400 (line 3) | class AddIndexToRemindersRemindAt1632582299400 implements MigrationInter...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 13) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1634459708599-RemoveTagResponsesForeignKeys.ts
class RemoveTagResponsesForeignKeys1634459708599 (line 3) | class RemoveTagResponsesForeignKeys1634459708599 implements MigrationInt...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 9) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1634563901575-CreatePhishermanCacheTable.ts
class CreatePhishermanCacheTable1634563901575 (line 3) | class CreatePhishermanCacheTable1634563901575 implements MigrationInterf...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 35) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1635596150234-CreatePhishermanKeyCacheTable.ts
class CreatePhishermanKeyCacheTable1635596150234 (line 3) | class CreatePhishermanKeyCacheTable1635596150234 implements MigrationInt...
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 35) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1635779678653-CreateWebhooksTable.ts
class CreateWebhooksTable1635779678653 (line 3) | class CreateWebhooksTable1635779678653 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 42) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1650709103864-CreateRoleQueueTable.ts
class CreateRoleQueueTable1650709103864 (line 3) | class CreateRoleQueueTable1650709103864 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 47) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1650712828384-CreateRoleButtonsTable.ts
class CreateRoleButtonsTable1650712828384 (line 3) | class CreateRoleButtonsTable1650712828384 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 48) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1650721020704-RemoveButtonRolesTable.ts
class RemoveButtonRolesTable1650721020704 (line 3) | class RemoveButtonRolesTable1650721020704 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 8) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1680354053183-AddTimeoutColumnsToMutes.ts
class AddTimeoutColumnsToMutes1680354053183 (line 3) | class AddTimeoutColumnsToMutes1680354053183 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 40) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/migrations/1682788165866-CreateMemberCacheTable.ts
class CreateMemberCacheTable1682788165866 (line 3) | class CreateMemberCacheTable1682788165866 implements MigrationInterface {
method up (line 4) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 66) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: backend/src/pluginUtils.ts
function canActOn (line 39) | function canActOn(
function hasPermission (line 60) | async function hasPermission(
type GenericCommandSource (line 69) | type GenericCommandSource = Message | CommandInteraction | ModalSubmitIn...
function isContextInteraction (line 71) | function isContextInteraction(
function isContextMessage (line 77) | function isContextMessage(context: GenericCommandSource): context is Mes...
function getContextChannel (line 81) | async function getContextChannel(context: GenericCommandSource): Promise...
function getContextChannelId (line 91) | function getContextChannelId(context: GenericCommandSource): string | nu...
function fetchContextChannel (line 95) | async function fetchContextChannel(context: GenericCommandSource) {
function flagsWithEphemeral (line 106) | function flagsWithEphemeral<TFlags extends string, TType extends number ...
type ContextResponseOptions (line 116) | type ContextResponseOptions = MessageCreateOptions & InteractionReplyOpt...
type ContextResponse (line 117) | type ContextResponse = Message | InteractionResponse;
function sendContextResponse (line 119) | async function sendContextResponse(
type ContextResponseEditOptions (line 153) | type ContextResponseEditOptions = MessageEditOptions & InteractionEditRe...
function editContextResponse (line 155) | function editContextResponse(
function deleteContextResponse (line 162) | async function deleteContextResponse(response: ContextResponse): Promise...
function getConfigForContext (line 166) | async function getConfigForContext<TPluginData extends BasePluginData<an...
function getBaseUrl (line 183) | function getBaseUrl(pluginData: AnyPluginData<any>) {
function isOwner (line 189) | function isOwner(pluginData: AnyPluginData<any>, userId: string) {
type AnyFn (line 204) | type AnyFn = (...args: any[]) => any;
function mapToPublicFn (line 209) | function mapToPublicFn<T extends AnyFn>(inputFn: T) {
type FnWithPluginData (line 217) | type FnWithPluginData<TPluginData> = (pluginData: TPluginData, ...args: ...
function makePublicFn (line 219) | function makePublicFn<TPluginData extends BasePluginData<any>, T extends...
function resolveMessageMember (line 228) | function resolveMessageMember(message: Message<true>) {
FILE: backend/src/plugins/AutoDelete/AutoDeletePlugin.ts
method beforeLoad (line 17) | beforeLoad(pluginData) {
method afterLoad (line 30) | afterLoad(pluginData) {
method beforeUnload (line 43) | beforeUnload(pluginData) {
FILE: backend/src/plugins/AutoDelete/types.ts
constant MAX_DELAY (line 9) | const MAX_DELAY = 5 * MINUTES;
type IDeletionQueueItem (line 11) | interface IDeletionQueueItem {
type AutoDeletePluginType (line 21) | interface AutoDeletePluginType extends BasePluginType {
FILE: backend/src/plugins/AutoDelete/util/addMessageToDeletionQueue.ts
function addMessageToDeletionQueue (line 7) | function addMessageToDeletionQueue(
FILE: backend/src/plugins/AutoDelete/util/deleteNextItem.ts
function deleteNextItem (line 13) | async function deleteNextItem(pluginData: GuildPluginData<AutoDeletePlug...
FILE: backend/src/plugins/AutoDelete/util/onMessageCreate.ts
function onMessageCreate (line 8) | async function onMessageCreate(pluginData: GuildPluginData<AutoDeletePlu...
FILE: backend/src/plugins/AutoDelete/util/onMessageDelete.ts
function onMessageDelete (line 6) | function onMessageDelete(pluginData: GuildPluginData<AutoDeletePluginTyp...
FILE: backend/src/plugins/AutoDelete/util/onMessageDeleteBulk.ts
function onMessageDeleteBulk (line 6) | function onMessageDeleteBulk(pluginData: GuildPluginData<AutoDeletePlugi...
FILE: backend/src/plugins/AutoDelete/util/scheduleNextDeletion.ts
function scheduleNextDeletion (line 5) | function scheduleNextDeletion(pluginData: GuildPluginData<AutoDeletePlug...
FILE: backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts
method beforeLoad (line 42) | beforeLoad(pluginData) {
method beforeStart (line 50) | beforeStart(pluginData) {
FILE: backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts
method run (line 13) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts
method run (line 21) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/AutoReactions/events/AddReactionsEvt.ts
method listener (line 17) | async listener({ pluginData, args: { message } }) {
FILE: backend/src/plugins/AutoReactions/types.ts
type AutoReactionsPluginType (line 13) | interface AutoReactionsPluginType extends BasePluginType {
FILE: backend/src/plugins/Automod/AutomodPlugin.ts
method beforeLoad (line 73) | async beforeLoad(pluginData) {
method beforeStart (line 98) | beforeStart(pluginData) {
method afterLoad (line 102) | async afterLoad(pluginData) {
method beforeUnload (line 153) | async beforeUnload(pluginData) {
FILE: backend/src/plugins/Automod/actions/addRoles.ts
method apply (line 19) | async apply({ pluginData, contexts, actionConfig, ruleName }) {
FILE: backend/src/plugins/Automod/actions/addToCounter.ts
method apply (line 15) | async apply({ pluginData, contexts, actionConfig, ruleName }) {
FILE: backend/src/plugins/Automod/actions/alert.ts
method apply (line 37) | async apply({ pluginData, contexts, actionConfig, ruleName, matchResult,...
FILE: backend/src/plugins/Automod/actions/archiveThread.ts
method apply (line 11) | async apply({ pluginData, contexts }) {
FILE: backend/src/plugins/Automod/actions/ban.ts
method apply (line 29) | async apply({ pluginData, contexts, actionConfig, matchResult }) {
FILE: backend/src/plugins/Automod/actions/changeNickname.ts
method apply (line 14) | async apply({ pluginData, contexts, actionConfig }) {
FILE: backend/src/plugins/Automod/actions/changePerms.ts
type LegacyPermMap (line 14) | type LegacyPermMap = Record<string, keyof (typeof PermissionsBitField)["...
method apply (line 84) | async apply({ pluginData, contexts, actionConfig, ruleName }) {
FILE: backend/src/plugins/Automod/actions/clean.ts
method apply (line 10) | async apply({ pluginData, contexts, ruleName }) {
FILE: backend/src/plugins/Automod/actions/exampleAction.ts
method apply (line 11) | async apply({ pluginData, contexts, actionConfig }) {
FILE: backend/src/plugins/Automod/actions/kick.ts
method apply (line 18) | async apply({ pluginData, contexts, actionConfig, matchResult }) {
FILE: backend/src/plugins/Automod/actions/log.ts
method apply (line 9) | async apply({ pluginData, contexts, ruleName, matchResult, prettyName }) {
FILE: backend/src/plugins/Automod/actions/mute.ts
method apply (line 36) | async apply({ pluginData, contexts, actionConfig, ruleName, matchResult ...
FILE: backend/src/plugins/Automod/actions/pauseInvites.ts
method apply (line 10) | async apply({ pluginData, actionConfig }) {
FILE: backend/src/plugins/Automod/actions/removeRoles.ts
method apply (line 17) | async apply({ pluginData, contexts, actionConfig, ruleName }) {
FILE: backend/src/plugins/Automod/actions/reply.ts
method apply (line 32) | async apply({ pluginData, contexts, actionConfig, ruleName }) {
FILE: backend/src/plugins/Automod/actions/setAntiraidLevel.ts
method apply (line 8) | async apply({ pluginData, actionConfig }) {
FILE: backend/src/plugins/Automod/actions/setCounter.ts
method apply (line 14) | async apply({ pluginData, contexts, actionConfig, ruleName }) {
FILE: backend/src/plugins/Automod/actions/setSlowmode.ts
method apply (line 13) | async apply({ pluginData, actionConfig, contexts }) {
FILE: backend/src/plugins/Automod/actions/startThread.ts
method apply (line 25) | async apply({ pluginData, contexts, actionConfig, ruleName }) {
FILE: backend/src/plugins/Automod/actions/warn.ts
method apply (line 18) | async apply({ pluginData, contexts, actionConfig, matchResult }) {
FILE: backend/src/plugins/Automod/commands/AntiraidClearCmd.ts
method run (line 9) | async run({ pluginData, message }) {
FILE: backend/src/plugins/Automod/commands/DebugAutomodCmd.ts
method run (line 18) | async run({ pluginData, message, args }) {
FILE: backend/src/plugins/Automod/commands/SetAntiraidCmd.ts
method run (line 14) | async run({ pluginData, message, args }) {
FILE: backend/src/plugins/Automod/commands/ViewAntiraidCmd.ts
method run (line 8) | async run({ pluginData, message }) {
FILE: backend/src/plugins/Automod/constants.ts
constant RECENT_SPAM_EXPIRY_TIME (line 4) | const RECENT_SPAM_EXPIRY_TIME = 10 * SECONDS;
constant RECENT_ACTION_EXPIRY_TIME (line 5) | const RECENT_ACTION_EXPIRY_TIME = 5 * MINUTES;
constant RECENT_NICKNAME_CHANGE_EXPIRY_TIME (line 6) | const RECENT_NICKNAME_CHANGE_EXPIRY_TIME = 5 * MINUTES;
type RecentActionType (line 8) | enum RecentActionType {
FILE: backend/src/plugins/Automod/events/RunAutomodOnJoinLeaveEvt.ts
method listener (line 8) | listener({ pluginData, args: { member } }) {
method listener (line 31) | listener({ pluginData, args: { member } }) {
FILE: backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts
method listener (line 8) | listener({ pluginData, args: { oldMember, newMember } }) {
FILE: backend/src/plugins/Automod/events/runAutomodOnAntiraidLevel.ts
function runAutomodOnAntiraidLevel (line 6) | async function runAutomodOnAntiraidLevel(
FILE: backend/src/plugins/Automod/events/runAutomodOnCounterTrigger.ts
function runAutomodOnCounterTrigger (line 7) | async function runAutomodOnCounterTrigger(
FILE: backend/src/plugins/Automod/events/runAutomodOnMessage.ts
function runAutomodOnMessage (line 14) | async function runAutomodOnMessage(
FILE: backend/src/plugins/Automod/events/runAutomodOnModAction.ts
function runAutomodOnModAction (line 7) | async function runAutomodOnModAction(
FILE: backend/src/plugins/Automod/events/runAutomodOnThreadEvents.ts
method listener (line 8) | async listener({ pluginData, args: { thread } }) {
method listener (line 37) | async listener({ pluginData, args: { thread } }) {
method listener (line 59) | async listener({ pluginData, args: { oldThread, newThread: thread } }) {
FILE: backend/src/plugins/Automod/functions/addRecentActionsFromMessage.ts
function addRecentActionsFromMessage (line 6) | function addRecentActionsFromMessage(pluginData: GuildPluginData<Automod...
FILE: backend/src/plugins/Automod/functions/applyCooldown.ts
function applyCooldown (line 5) | function applyCooldown(
FILE: backend/src/plugins/Automod/functions/checkCooldown.ts
function checkCooldown (line 4) | function checkCooldown(
FILE: backend/src/plugins/Automod/functions/clearOldNicknameChanges.ts
function clearOldRecentNicknameChanges (line 5) | function clearOldRecentNicknameChanges(pluginData: GuildPluginData<Autom...
FILE: backend/src/plugins/Automod/functions/clearOldRecentActions.ts
function clearOldRecentActions (line 6) | function clearOldRecentActions(pluginData: GuildPluginData<AutomodPlugin...
FILE: backend/src/plugins/Automod/functions/clearOldRecentSpam.ts
function clearOldRecentSpam (line 6) | function clearOldRecentSpam(pluginData: GuildPluginData<AutomodPluginTyp...
FILE: backend/src/plugins/Automod/functions/clearRecentActionsForMessage.ts
function clearRecentActionsForMessage (line 5) | function clearRecentActionsForMessage(pluginData: GuildPluginData<Automo...
FILE: backend/src/plugins/Automod/functions/createMessageSpamTrigger.ts
type TMessageSpamMatchResultType (line 12) | interface TMessageSpamMatchResultType {
function createMessageSpamTrigger (line 22) | function createMessageSpamTrigger(spamType: RecentActionType, prettyName...
FILE: backend/src/plugins/Automod/functions/findRecentSpam.ts
function findRecentSpam (line 6) | function findRecentSpam(
FILE: backend/src/plugins/Automod/functions/getMatchingMessageRecentActions.ts
function getMatchingMessageRecentActions (line 9) | function getMatchingMessageRecentActions(
FILE: backend/src/plugins/Automod/functions/getMatchingRecentActions.ts
function getMatchingRecentActions (line 6) | function getMatchingRecentActions(
FILE: backend/src/plugins/Automod/functions/getSpamIdentifier.ts
function getMessageSpamIdentifier (line 3) | function getMessageSpamIdentifier(message: SavedMessage, perChannel: boo...
FILE: backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts
function getTextMatchPartialSummary (line 7) | function getTextMatchPartialSummary(
FILE: backend/src/plugins/Automod/functions/ignoredRoleChanges.ts
constant IGNORED_ROLE_CHANGE_LIFETIME (line 5) | const IGNORED_ROLE_CHANGE_LIFETIME = 5 * MINUTES;
function cleanupIgnoredRoleChanges (line 7) | function cleanupIgnoredRoleChanges(pluginData: GuildPluginData<AutomodPl...
function ignoreRoleChange (line 16) | function ignoreRoleChange(pluginData: GuildPluginData<AutomodPluginType>...
function consumeIgnoredRoleChange (line 29) | function consumeIgnoredRoleChange(
FILE: backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts
type TextTriggerWithMultipleMatchTypes (line 8) | type TextTriggerWithMultipleMatchTypes = {
type MatchableTextType (line 17) | type MatchableTextType = "message" | "embed" | "visiblename" | "username...
type YieldedContent (line 19) | type YieldedContent = [MatchableTextType, string];
FILE: backend/src/plugins/Automod/functions/resolveActionContactMethods.ts
function resolveActionContactMethods (line 7) | function resolveActionContactMethods(
FILE: backend/src/plugins/Automod/functions/runAutomod.ts
type MatchedTriggerResult (line 22) | interface MatchedTriggerResult {
type RuleResultOutcomeSuccess (line 28) | interface RuleResultOutcomeSuccess {
type RuleResultOutcomeFailure (line 33) | interface RuleResultOutcomeFailure {
type RuleResultOutcome (line 38) | type RuleResultOutcome = RuleResultOutcomeSuccess | RuleResultOutcomeFai...
type RuleResult (line 40) | interface RuleResult {
type AutomodRunResult (line 46) | interface AutomodRunResult {
function runAutomod (line 51) | async function runAutomod(
FILE: backend/src/plugins/Automod/functions/setAntiraidLevel.ts
function setAntiraidLevel (line 7) | async function setAntiraidLevel(
FILE: backend/src/plugins/Automod/functions/sumRecentActionCounts.ts
function sumRecentActionCounts (line 3) | function sumRecentActionCounts(actions: RecentAction[]) {
FILE: backend/src/plugins/Automod/helpers.ts
type BaseAutomodTriggerMatchResult (line 6) | interface BaseAutomodTriggerMatchResult {
type AutomodTriggerMatchResult (line 15) | type AutomodTriggerMatchResult<TExtra = unknown> = unknown extends TExtra
type AutomodTriggerMatchFn (line 19) | type AutomodTriggerMatchFn<TConfigType, TMatchResultExtra> = (meta: {
type AutomodTriggerRenderMatchInformationFn (line 26) | type AutomodTriggerRenderMatchInformationFn<TConfigType, TMatchResultExt...
type AutomodTriggerBlueprint (line 34) | interface AutomodTriggerBlueprint<TConfigSchema extends ZodTypeAny, TMat...
function automodTrigger (line 48) | function automodTrigger(...args) {
type AutomodActionApplyFn (line 56) | type AutomodActionApplyFn<TConfigType> = (meta: {
type AutomodActionBlueprint (line 65) | interface AutomodActionBlueprint<TConfigSchema extends ZodTypeAny> {
function automodAction (line 70) | function automodAction<TConfigSchema extends ZodTypeAny>(
FILE: backend/src/plugins/Automod/triggers/antiraidLevel.ts
type AntiraidLevelTriggerResult (line 4) | interface AntiraidLevelTriggerResult {}
method match (line 14) | async match({ triggerConfig, context }) {
method renderMatchInformation (line 36) | renderMatchInformation({ contexts }) {
FILE: backend/src/plugins/Automod/triggers/anyMessage.ts
type AnyMessageResultType (line 6) | interface AnyMessageResultType {}
method match (line 13) | async match({ context }) {
method renderMatchInformation (line 23) | renderMatchInformation({ pluginData, contexts }) {
FILE: backend/src/plugins/Automod/triggers/ban.ts
type BanTriggerResultType (line 5) | interface BanTriggerResultType {}
method match (line 15) | async match({ context, triggerConfig }) {
method renderMatchInformation (line 30) | renderMatchInformation() {
FILE: backend/src/plugins/Automod/triggers/counterTrigger.ts
type CounterTriggerResult (line 5) | interface CounterTriggerResult {}
method match (line 16) | async match({ triggerConfig, context }) {
method renderMatchInformation (line 39) | renderMatchInformation({ contexts }) {
FILE: backend/src/plugins/Automod/triggers/exampleTrigger.ts
type ExampleMatchResultType (line 4) | interface ExampleMatchResultType {
method match (line 15) | async match({ triggerConfig, context }) {
method renderMatchInformation (line 26) | renderMatchInformation({ matchResult }) {
FILE: backend/src/plugins/Automod/triggers/hasAttachments.ts
type HasAttachmentsMatchResult (line 6) | interface HasAttachmentsMatchResult {
method match (line 19) | async match({ context, triggerConfig }) {
method renderMatchInformation (line 45) | renderMatchInformation({ pluginData, contexts, matchResult }) {
FILE: backend/src/plugins/Automod/triggers/kick.ts
type KickTriggerResultType (line 5) | interface KickTriggerResultType {}
method match (line 15) | async match({ context, triggerConfig }) {
method renderMatchInformation (line 29) | renderMatchInformation() {
FILE: backend/src/plugins/Automod/triggers/matchAttachmentType.ts
type MatchResultType (line 7) | interface MatchResultType {
method match (line 22) | async match({ context, triggerConfig: trigger }) {
method renderMatchInformation (line 64) | renderMatchInformation({ pluginData, contexts, matchResult }) {
FILE: backend/src/plugins/Automod/triggers/matchInvites.ts
type MatchResultType (line 7) | interface MatchResultType {
method match (line 40) | async match({ pluginData, context, triggerConfig: trigger }) {
method renderMatchInformation (line 82) | renderMatchInformation({ pluginData, contexts, matchResult }) {
FILE: backend/src/plugins/Automod/triggers/matchLinks.ts
type MatchResultType (line 12) | interface MatchResultType {
method match (line 55) | async match({ pluginData, context, triggerConfig: trigger }) {
method renderMatchInformation (line 184) | renderMatchInformation({ pluginData, contexts, matchResult }) {
FILE: backend/src/plugins/Automod/triggers/matchMimeType.ts
type MatchResultType (line 6) | interface MatchResultType {
method match (line 21) | async match({ context, triggerConfig: trigger }) {
method renderMatchInformation (line 61) | renderMatchInformation({ pluginData, contexts, matchResult }) {
FILE: backend/src/plugins/Automod/triggers/matchRegex.ts
type MatchResultType (line 11) | interface MatchResultType {
method match (line 34) | async match({ pluginData, context, triggerConfig: trigger }) {
method renderMatchInformation (line 74) | renderMatchInformation({ pluginData, contexts, matchResult }) {
FILE: backend/src/plugins/Automod/triggers/matchWords.ts
type MatchResultType (line 10) | interface MatchResultType {
method match (line 35) | async match({ pluginData, context, triggerConfig: trigger }) {
method renderMatchInformation (line 98) | renderMatchInformation({ pluginData, contexts, matchResult }) {
FILE: backend/src/plugins/Automod/triggers/memberJoin.ts
method match (line 13) | async match({ context, triggerConfig }) {
method renderMatchInformation (line 26) | renderMatchInformation() {
FILE: backend/src/plugins/Automod/triggers/memberJoinSpam.ts
method match (line 17) | async match({ pluginData, context, triggerConfig }) {
method renderMatchInformation (line 48) | renderMatchInformation() {
FILE: backend/src/plugins/Automod/triggers/memberLeave.ts
method match (line 9) | async match({ context }) {
method renderMatchInformation (line 17) | renderMatchInformation() {
FILE: backend/src/plugins/Automod/triggers/mute.ts
type MuteTriggerResultType (line 5) | interface MuteTriggerResultType {}
method match (line 15) | async match({ context, triggerConfig }) {
method renderMatchInformation (line 29) | renderMatchInformation() {
FILE: backend/src/plugins/Automod/triggers/note.ts
type NoteTriggerResultType (line 5) | interface NoteTriggerResultType {}
method match (line 12) | async match({ context }) {
method renderMatchInformation (line 22) | renderMatchInformation() {
FILE: backend/src/plugins/Automod/triggers/roleAdded.ts
type RoleAddedMatchResult (line 7) | interface RoleAddedMatchResult {
method match (line 16) | async match({ triggerConfig, context, pluginData }) {
method renderMatchInformation (line 37) | renderMatchInformation({ matchResult, pluginData, contexts }) {
FILE: backend/src/plugins/Automod/triggers/roleRemoved.ts
type RoleAddedMatchResult (line 7) | interface RoleAddedMatchResult {
method match (line 16) | async match({ triggerConfig, context, pluginData }) {
method renderMatchInformation (line 37) | renderMatchInformation({ matchResult, pluginData, contexts }) {
FILE: backend/src/plugins/Automod/triggers/threadArchive.ts
type ThreadArchiveResult (line 6) | interface ThreadArchiveResult {
method match (line 21) | async match({ context, triggerConfig }) {
method renderMatchInformation (line 43) | async renderMatchInformation({ matchResult }) {
FILE: backend/src/plugins/Automod/triggers/threadCreate.ts
type ThreadCreateResult (line 6) | interface ThreadCreateResult {
method match (line 19) | async match({ context }) {
method renderMatchInformation (line 37) | async renderMatchInformation({ matchResult }) {
FILE: backend/src/plugins/Automod/triggers/threadCreateSpam.ts
method match (line 17) | async match({ pluginData, context, triggerConfig }) {
method renderMatchInformation (line 48) | renderMatchInformation() {
FILE: backend/src/plugins/Automod/triggers/threadDelete.ts
type ThreadDeleteResult (line 6) | interface ThreadDeleteResult {
method match (line 19) | async match({ context }) {
method renderMatchInformation (line 37) | renderMatchInformation({ matchResult }) {
FILE: backend/src/plugins/Automod/triggers/threadUnarchive.ts
type ThreadUnarchiveResult (line 6) | interface ThreadUnarchiveResult {
method match (line 21) | async match({ context, triggerConfig }) {
method renderMatchInformation (line 43) | async renderMatchInformation({ matchResult }) {
FILE: backend/src/plugins/Automod/triggers/unban.ts
type UnbanTriggerResultType (line 5) | interface UnbanTriggerResultType {}
method match (line 12) | async match({ context }) {
method renderMatchInformation (line 22) | renderMatchInformation() {
FILE: backend/src/plugins/Automod/triggers/unmute.ts
type UnmuteTriggerResultType (line 5) | interface UnmuteTriggerResultType {}
method match (line 12) | async match({ context }) {
method renderMatchInformation (line 22) | renderMatchInformation() {
FILE: backend/src/plugins/Automod/triggers/warn.ts
type WarnTriggerResultType (line 5) | interface WarnTriggerResultType {}
method match (line 15) | async match({ context, triggerConfig }) {
method renderMatchInformation (line 29) | renderMatchInformation() {
FILE: backend/src/plugins/Automod/types.ts
type ZTriggersMapHelper (line 22) | type ZTriggersMapHelper = {
type ZActionsMapHelper (line 34) | type ZActionsMapHelper = {
type TRule (line 58) | type TRule = z.infer<typeof zRule>;
type AutomodPluginType (line 68) | interface AutomodPluginType extends BasePluginType {
type AutomodContext (line 132) | interface AutomodContext {
type RecentAction (line 174) | interface RecentAction {
type RecentSpam (line 181) | interface RecentSpam {
FILE: backend/src/plugins/BotControl/BotControlPlugin.ts
method afterLoad (line 52) | async afterLoad(pluginData) {
FILE: backend/src/plugins/BotControl/activeReload.ts
function getActiveReload (line 3) | function getActiveReload() {
function setActiveReload (line 7) | function setActiveReload(guildId: string, channelId: string) {
function resetActiveReload (line 11) | function resetActiveReload() {
FILE: backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts
method run (line 19) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts
method run (line 17) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/AllowServerCmd.ts
method run (line 20) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts
method run (line 16) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/DebugCountersCmd.ts
type SortableDebugCounter (line 7) | type SortableDebugCounter = {
method run (line 18) | async run({ pluginData, message: msg }) {
FILE: backend/src/plugins/BotControl/commands/DisallowServerCmd.ts
method run (line 18) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/EligibleCmd.ts
method run (line 15) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/LeaveServerCmd.ts
method run (line 17) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts
method run (line 16) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts
method run (line 13) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/ProfilerDataCmd.ts
method run (line 23) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts
method run (line 13) | async run({ pluginData, message: msg }) {
FILE: backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts
method run (line 12) | async run({ pluginData, message }) {
FILE: backend/src/plugins/BotControl/commands/ReloadServerCmd.ts
method run (line 17) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts
method run (line 18) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/RestPerformanceCmd.ts
method run (line 16) | async run({ message: msg, args }) {
FILE: backend/src/plugins/BotControl/commands/ServersCmd.ts
method run (line 22) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/BotControl/functions/isEligible.ts
constant REQUIRED_MEMBER_COUNT (line 6) | const REQUIRED_MEMBER_COUNT = 5000;
function isEligible (line 8) | async function isEligible(
FILE: backend/src/plugins/BotControl/types.ts
type BotControlPluginType (line 18) | interface BotControlPluginType extends BasePluginType {
FILE: backend/src/plugins/Cases/CasesPlugin.ts
function getLogsPlugin (line 19) | function getLogsPlugin(): Promise<any> {
method public (line 29) | public(pluginData) {
method afterLoad (line 42) | afterLoad(pluginData) {
FILE: backend/src/plugins/Cases/functions/createCase.ts
function createCase (line 9) | async function createCase(pluginData: GuildPluginData<CasesPluginType>, ...
FILE: backend/src/plugins/Cases/functions/createCaseNote.ts
function createCaseNote (line 8) | async function createCaseNote(pluginData: GuildPluginData<CasesPluginTyp...
FILE: backend/src/plugins/Cases/functions/getCaseColor.ts
function getCaseColor (line 6) | function getCaseColor(pluginData: GuildPluginData<CasesPluginType>, case...
FILE: backend/src/plugins/Cases/functions/getCaseEmbed.ts
function getCaseEmbed (line 18) | async function getCaseEmbed(
FILE: backend/src/plugins/Cases/functions/getCaseIcon.ts
function getCaseIcon (line 6) | function getCaseIcon(pluginData: GuildPluginData<CasesPluginType>, caseT...
FILE: backend/src/plugins/Cases/functions/getCaseSummary.ts
constant CASE_SUMMARY_REASON_MAX_LENGTH (line 11) | const CASE_SUMMARY_REASON_MAX_LENGTH = 300;
constant INCLUDE_MORE_NOTES_THRESHOLD (line 12) | const INCLUDE_MORE_NOTES_THRESHOLD = 20;
constant UPDATE_STR (line 13) | const UPDATE_STR = "**[Update]**";
function getCaseSummary (line 15) | async function getCaseSummary(
FILE: backend/src/plugins/Cases/functions/getCaseTypeAmountForUserId.ts
function getCaseTypeAmountForUserId (line 5) | async function getCaseTypeAmountForUserId(
FILE: backend/src/plugins/Cases/functions/getRecentCasesByMod.ts
function getRecentCasesByMod (line 6) | function getRecentCasesByMod(
FILE: backend/src/plugins/Cases/functions/getTotalCasesByMod.ts
function getTotalCasesByMod (line 6) | function getTotalCasesByMod(
FILE: backend/src/plugins/Cases/functions/postToCaseLogChannel.ts
function postToCaseLogChannel (line 12) | async function postToCaseLogChannel(
function postCaseToCaseLogChannel (line 48) | async function postCaseToCaseLogChannel(
FILE: backend/src/plugins/Cases/functions/resolveCaseId.ts
function resolveCaseId (line 3) | function resolveCaseId(caseOrCaseId: Case | number): number {
FILE: backend/src/plugins/Cases/types.ts
type CasesPluginType (line 38) | interface CasesPluginType extends BasePluginType {
type CaseArgs (line 50) | type CaseArgs = {
type CaseNoteArgs (line 64) | type CaseNoteArgs = {
FILE: backend/src/plugins/Censor/CensorPlugin.ts
method beforeLoad (line 31) | beforeLoad(pluginData) {
method afterLoad (line 40) | afterLoad(pluginData) {
method beforeUnload (line 50) | beforeUnload(pluginData) {
FILE: backend/src/plugins/Censor/types.ts
type CensorPluginType (line 27) | interface CensorPluginType extends BasePluginType {
FILE: backend/src/plugins/Censor/util/applyFiltersToMsg.ts
type ManipulatedEmbedData (line 18) | type ManipulatedEmbedData = Partial<ISavedMessageEmbedData>;
function applyFiltersToMsg (line 20) | async function applyFiltersToMsg(
FILE: backend/src/plugins/Censor/util/censorMessage.ts
function censorMessage (line 9) | async function censorMessage(
FILE: backend/src/plugins/Censor/util/onMessageCreate.ts
function onMessageCreate (line 7) | async function onMessageCreate(pluginData: GuildPluginData<CensorPluginT...
FILE: backend/src/plugins/Censor/util/onMessageUpdate.ts
function onMessageUpdate (line 7) | async function onMessageUpdate(pluginData: GuildPluginData<CensorPluginT...
FILE: backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts
method beforeStart (line 18) | beforeStart(pluginData) {
FILE: backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts
constant MAX_ARCHIVED_MESSAGES (line 10) | const MAX_ARCHIVED_MESSAGES = 5000;
constant MAX_MESSAGES_PER_FETCH (line 11) | const MAX_MESSAGES_PER_FETCH = 100;
constant PROGRESS_UPDATE_INTERVAL (line 12) | const PROGRESS_UPDATE_INTERVAL = 5 * SECONDS;
method run (line 33) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/ChannelArchiver/rehostAttachment.ts
constant MAX_ATTACHMENT_REHOST_SIZE (line 6) | const MAX_ATTACHMENT_REHOST_SIZE = 1024 * 1024 * 8;
function rehostAttachment (line 8) | async function rehostAttachment(attachment: Attachment, targetChannel: G...
FILE: backend/src/plugins/ChannelArchiver/types.ts
type ChannelArchiverPluginType (line 7) | interface ChannelArchiverPluginType extends BasePluginType {
FILE: backend/src/plugins/CommandAliases/CommandAliasesPlugin.ts
method beforeLoad (line 12) | beforeLoad(pluginData) {
FILE: backend/src/plugins/CommandAliases/events/DispatchAliasEvt.ts
method listener (line 6) | async listener({ args: { message: msg }, pluginData }) {
FILE: backend/src/plugins/CommandAliases/functions/buildAliasMatchers.ts
type AliasMatcher (line 4) | interface AliasMatcher {
function buildAliasMatchers (line 9) | function buildAliasMatchers(prefix: string, aliases: NormalizedAlias[]):...
FILE: backend/src/plugins/CommandAliases/functions/normalizeAliases.ts
type NormalizedAlias (line 1) | interface NormalizedAlias {
function normalizeAliases (line 6) | function normalizeAliases(aliases: Record<string, string> | undefined | ...
FILE: backend/src/plugins/CommandAliases/types.ts
type CommandAliasesPluginType (line 9) | interface CommandAliasesPluginType extends BasePluginType {
FILE: backend/src/plugins/Common/CommonPlugin.ts
method public (line 12) | public(pluginData) {
FILE: backend/src/plugins/Common/functions/getEmoji.ts
function getSuccessEmoji (line 5) | function getSuccessEmoji(pluginData: GuildPluginData<CommonPluginType>) {
function getErrorEmoji (line 9) | function getErrorEmoji(pluginData: GuildPluginData<CommonPluginType>) {
FILE: backend/src/plugins/Common/types.ts
type CommonPluginType (line 10) | interface CommonPluginType extends BasePluginType {
FILE: backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts
method beforeLoad (line 15) | beforeLoad(pluginData) {
method afterLoad (line 19) | afterLoad(pluginData) {
FILE: backend/src/plugins/CompanionChannels/events/VoiceStateUpdateEvt.ts
method listener (line 6) | listener({ pluginData, args: { oldState, newState } }) {
FILE: backend/src/plugins/CompanionChannels/functions/getCompanionChannelOptsForVoiceChannelId.ts
function getCompanionChannelOptsForVoiceChannelId (line 9) | async function getCompanionChannelOptsForVoiceChannelId(
FILE: backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts
constant ERROR_COOLDOWN_KEY (line 10) | const ERROR_COOLDOWN_KEY = "errorCooldown";
constant ERROR_COOLDOWN (line 11) | const ERROR_COOLDOWN = 5 * MINUTES;
constant MAX_OVERWRITES (line 15) | const MAX_OVERWRITES = 450;
function handleCompanionPermissions (line 17) | async function handleCompanionPermissions(
FILE: backend/src/plugins/CompanionChannels/types.ts
type TCompanionChannelOpts (line 13) | type TCompanionChannelOpts = z.infer<typeof zCompanionChannelOpts>;
type CompanionChannelsPluginType (line 19) | interface CompanionChannelsPluginType extends BasePluginType {
FILE: backend/src/plugins/ContextMenus/ContextMenuPlugin.ts
method beforeLoad (line 36) | beforeLoad(pluginData) {
FILE: backend/src/plugins/ContextMenus/actions/ban.ts
function banAction (line 21) | async function banAction(
function launchBanActionModal (line 73) | async function launchBanActionModal(
FILE: backend/src/plugins/ContextMenus/actions/clean.ts
function cleanAction (line 16) | async function cleanAction(
function launchCleanActionModal (line 75) | async function launchCleanActionModal(
FILE: backend/src/plugins/ContextMenus/actions/mute.ts
function muteAction (line 24) | async function muteAction(
function launchMuteActionModal (line 95) | async function launchMuteActionModal(
FILE: backend/src/plugins/ContextMenus/actions/note.ts
function noteAction (line 21) | async function noteAction(
function launchNoteActionModal (line 78) | async function launchNoteActionModal(
FILE: backend/src/plugins/ContextMenus/actions/update.ts
function updateAction (line 9) | async function updateAction(
FILE: backend/src/plugins/ContextMenus/actions/warn.ts
function warnAction (line 20) | async function warnAction(
function launchWarnActionModal (line 76) | async function launchWarnActionModal(
FILE: backend/src/plugins/ContextMenus/commands/BanUserCtxCmd.ts
method run (line 8) | async run({ pluginData, interaction }) {
FILE: backend/src/plugins/ContextMenus/commands/CleanMessageCtxCmd.ts
method run (line 8) | async run({ pluginData, interaction }) {
FILE: backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts
constant MODAL_TIMEOUT (line 34) | const MODAL_TIMEOUT = 60 * SECONDS;
constant MOD_MENU_TIMEOUT (line 35) | const MOD_MENU_TIMEOUT = 60 * SECONDS;
constant CASES_PER_PAGE (line 36) | const CASES_PER_PAGE = 10;
method run (line 41) | async run({ pluginData, interaction }) {
function displayModMenu (line 115) | async function displayModMenu(
function serializeCustomId (line 292) | function serializeCustomId(opts: ModMenuActionOpts) {
function deserializeCustomId (line 296) | function deserializeCustomId(customId: string): ModMenuActionOpts {
function updateNavButtonState (line 305) | function updateNavButtonState(
FILE: backend/src/plugins/ContextMenus/commands/MuteUserCtxCmd.ts
method run (line 8) | async run({ pluginData, interaction }) {
FILE: backend/src/plugins/ContextMenus/commands/NoteUserCtxCmd.ts
method run (line 8) | async run({ pluginData, interaction }) {
FILE: backend/src/plugins/ContextMenus/commands/WarnUserCtxCmd.ts
method run (line 8) | async run({ pluginData, interaction }) {
FILE: backend/src/plugins/ContextMenus/types.ts
type ContextMenuPluginType (line 11) | interface ContextMenuPluginType extends BasePluginType {
type ModMenuActionType (line 18) | const enum ModMenuActionType {
type ModMenuNavigationType (line 27) | const enum ModMenuNavigationType {
type ModMenuActionOpts (line 36) | interface ModMenuActionOpts {
type LoadModMenuPageFn (line 41) | type LoadModMenuPageFn = (page: number) => Awaitable<APIEmbed>;
FILE: backend/src/plugins/Counters/CountersPlugin.ts
constant DECAY_APPLY_INTERVAL (line 29) | const DECAY_APPLY_INTERVAL = 5 * MINUTES;
method public (line 62) | public(pluginData) {
method beforeLoad (line 84) | async beforeLoad(pluginData) {
method beforeStart (line 130) | beforeStart(pluginData) {
method afterLoad (line 134) | async afterLoad(pluginData) {
method beforeUnload (line 160) | beforeUnload(pluginData) {
FILE: backend/src/plugins/Counters/commands/AddCounterCmd.ts
method run (line 42) | async run({ pluginData, message, args }) {
FILE: backend/src/plugins/Counters/commands/CountersListCmd.ts
method run (line 12) | async run({ pluginData, message }) {
FILE: backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts
method run (line 15) | async run({ pluginData, message, args }) {
FILE: backend/src/plugins/Counters/commands/ResetCounterCmd.ts
method run (line 37) | async run({ pluginData, message, args }) {
FILE: backend/src/plugins/Counters/commands/SetCounterCmd.ts
method run (line 42) | async run({ pluginData, message, args }) {
FILE: backend/src/plugins/Counters/commands/ViewCounterCmd.ts
method run (line 36) | async run({ pluginData, message, args }) {
FILE: backend/src/plugins/Counters/functions/changeCounterValue.ts
function changeCounterValue (line 7) | async function changeCounterValue(
FILE: backend/src/plugins/Counters/functions/checkAllValuesForReverseTrigger.ts
function checkAllValuesForReverseTrigger (line 6) | async function checkAllValuesForReverseTrigger(
FILE: backend/src/plugins/Counters/functions/checkAllValuesForTrigger.ts
function checkAllValuesForTrigger (line 6) | async function checkAllValuesForTrigger(
FILE: backend/src/plugins/Counters/functions/checkCounterTrigger.ts
function checkCounterTrigger (line 6) | async function checkCounterTrigger(
FILE: backend/src/plugins/Counters/functions/checkReverseCounterTrigger.ts
function checkReverseCounterTrigger (line 6) | async function checkReverseCounterTrigger(
FILE: backend/src/plugins/Counters/functions/counterExists.ts
function counterExists (line 4) | function counterExists(pluginData: GuildPluginData<CountersPluginType>, ...
FILE: backend/src/plugins/Counters/functions/decayCounter.ts
function decayCounter (line 7) | async function decayCounter(
FILE: backend/src/plugins/Counters/functions/emitCounterEvent.ts
function emitCounterEvent (line 4) | function emitCounterEvent<TEvent extends keyof CounterEvents>(
FILE: backend/src/plugins/Counters/functions/getPrettyNameForCounter.ts
function getPrettyNameForCounter (line 4) | function getPrettyNameForCounter(pluginData: GuildPluginData<CountersPlu...
FILE: backend/src/plugins/Counters/functions/getPrettyNameForCounterTrigger.ts
function getPrettyNameForCounterTrigger (line 4) | function getPrettyNameForCounterTrigger(
FILE: backend/src/plugins/Counters/functions/offCounterEvent.ts
function offCounterEvent (line 4) | function offCounterEvent(
FILE: backend/src/plugins/Counters/functions/onCounterEvent.ts
function onCounterEvent (line 4) | function onCounterEvent<TEvent extends keyof CounterEvents>(
FILE: backend/src/plugins/Counters/functions/resetAllCounterValues.ts
function resetAllCounterValues (line 5) | async function resetAllCounterValues(pluginData: GuildPluginData<Counter...
FILE: backend/src/plugins/Counters/functions/setCounterValue.ts
function setCounterValue (line 7) | async function setCounterValue(
FILE: backend/src/plugins/Counters/types.ts
constant MAX_COUNTERS (line 15) | const MAX_COUNTERS = 5;
constant MAX_TRIGGERS_PER_COUNTER (line 16) | const MAX_TRIGGERS_PER_COUNTER = 5;
type CounterEvents (line 77) | interface CounterEvents {
type CounterEventEmitter (line 82) | interface CounterEventEmitter extends EventEmitter {
type CountersPluginType (line 87) | interface CountersPluginType extends BasePluginType {
FILE: backend/src/plugins/CustomEvents/ActionError.ts
class ActionError (line 1) | class ActionError extends Error {}
FILE: backend/src/plugins/CustomEvents/CustomEventsPlugin.ts
method beforeStart (line 25) | beforeStart(pluginData) {
method afterLoad (line 29) | afterLoad(pluginData) {
method beforeUnload (line 67) | beforeUnload() {
FILE: backend/src/plugins/CustomEvents/actions/addRoleAction.ts
type TAddRoleAction (line 15) | type TAddRoleAction = z.infer<typeof zAddRoleAction>;
function addRoleAction (line 17) | async function addRoleAction(
FILE: backend/src/plugins/CustomEvents/actions/createCaseAction.ts
type TCreateCaseAction (line 18) | type TCreateCaseAction = z.infer<typeof zCreateCaseAction>;
function createCaseAction (line 20) | async function createCaseAction(
FILE: backend/src/plugins/CustomEvents/actions/makeRoleMentionableAction.ts
type TMakeRoleMentionableAction (line 14) | type TMakeRoleMentionableAction = z.infer<typeof zMakeRoleMentionableAct...
function makeRoleMentionableAction (line 16) | async function makeRoleMentionableAction(
FILE: backend/src/plugins/CustomEvents/actions/makeRoleUnmentionableAction.ts
type TMakeRoleUnmentionableAction (line 13) | type TMakeRoleUnmentionableAction = z.infer<typeof zMakeRoleUnmentionabl...
function makeRoleUnmentionableAction (line 15) | async function makeRoleUnmentionableAction(
FILE: backend/src/plugins/CustomEvents/actions/messageAction.ts
type TMessageAction (line 15) | type TMessageAction = z.infer<typeof zMessageAction>;
function messageAction (line 17) | async function messageAction(
FILE: backend/src/plugins/CustomEvents/actions/moveToVoiceChannelAction.ts
type TMoveToVoiceChannelAction (line 16) | type TMoveToVoiceChannelAction = z.infer<typeof zMoveToVoiceChannelAction>;
function moveToVoiceChannelAction (line 18) | async function moveToVoiceChannelAction(
FILE: backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts
type TSetChannelPermissionOverridesAction (line 23) | type TSetChannelPermissionOverridesAction = z.infer<typeof zSetChannelPe...
function setChannelPermissionOverridesAction (line 25) | async function setChannelPermissionOverridesAction(
FILE: backend/src/plugins/CustomEvents/catchTemplateError.ts
function catchTemplateError (line 4) | function catchTemplateError(fn: () => Promise<string>, errorText: string...
FILE: backend/src/plugins/CustomEvents/functions/runEvent.ts
function runEvent (line 13) | async function runEvent(
FILE: backend/src/plugins/CustomEvents/types.ts
type TCustomEvent (line 37) | type TCustomEvent = z.infer<typeof zCustomEvent>;
type CustomEventsPluginType (line 43) | interface CustomEventsPluginType extends BasePluginType {
FILE: backend/src/plugins/GuildAccessMonitor/GuildAccessMonitorPlugin.ts
function checkGuild (line 8) | async function checkGuild(pluginData: GlobalPluginData<GuildAccessMonito...
method listener (line 26) | listener({ pluginData, args: { guild } }) {
method beforeLoad (line 32) | async beforeLoad(pluginData) {
method afterLoad (line 49) | afterLoad(pluginData) {
FILE: backend/src/plugins/GuildAccessMonitor/types.ts
type GuildAccessMonitorPluginType (line 7) | interface GuildAccessMonitorPluginType extends BasePluginType {
FILE: backend/src/plugins/GuildConfigReloader/GuildConfigReloaderPlugin.ts
method beforeLoad (line 11) | async beforeLoad(pluginData) {
method afterLoad (line 18) | afterLoad(pluginData) {
method beforeUnload (line 22) | beforeUnload(pluginData) {
FILE: backend/src/plugins/GuildConfigReloader/functions/reloadChangedGuilds.ts
constant CHECK_INTERVAL (line 6) | const CHECK_INTERVAL = 1 * SECONDS;
function reloadChangedGuilds (line 8) | async function reloadChangedGuilds(pluginData: GlobalPluginData<GuildCon...
FILE: backend/src/plugins/GuildConfigReloader/types.ts
type GuildConfigReloaderPluginType (line 8) | interface GuildConfigReloaderPluginType extends BasePluginType {
FILE: backend/src/plugins/GuildInfoSaver/GuildInfoSaverPlugin.ts
method listener (line 16) | listener({ args }) {
method afterLoad (line 22) | afterLoad(pluginData) {
method beforeUnload (line 27) | beforeUnload(pluginData) {
function updateGuildInfo (line 32) | async function updateGuildInfo(guild: Guild) {
FILE: backend/src/plugins/GuildInfoSaver/types.ts
type GuildInfoSaverPluginType (line 6) | interface GuildInfoSaverPluginType extends BasePluginType {
FILE: backend/src/plugins/GuildMemberCache/GuildMemberCachePlugin.ts
constant PENDING_SAVE_INTERVAL (line 14) | const PENDING_SAVE_INTERVAL = 30 * SECONDS;
method public (line 30) | public(pluginData) {
method beforeLoad (line 36) | beforeLoad(pluginData) {
method afterLoad (line 42) | afterLoad(pluginData) {
method beforeUnload (line 49) | async beforeUnload(pluginData) {
FILE: backend/src/plugins/GuildMemberCache/events/cancelDeletionOnMemberJoin.ts
method listener (line 6) | async listener({ pluginData, args: { member } }) {
FILE: backend/src/plugins/GuildMemberCache/events/removeMemberCacheOnMemberLeave.ts
method listener (line 6) | async listener({ pluginData, args: { member } }) {
FILE: backend/src/plugins/GuildMemberCache/events/updateMemberCacheOnMemberUpdate.ts
method listener (line 7) | async listener({ pluginData, args: { newMember } }) {
FILE: backend/src/plugins/GuildMemberCache/events/updateMemberCacheOnMessage.ts
method listener (line 7) | listener({ pluginData, args }) {
FILE: backend/src/plugins/GuildMemberCache/events/updateMemberCacheOnRoleChange.ts
method listener (line 8) | async listener({ pluginData, args: { auditLogEntry } }) {
FILE: backend/src/plugins/GuildMemberCache/events/updateMemberCacheOnVoiceStateUpdate.ts
method listener (line 7) | listener({ pluginData, args }) {
FILE: backend/src/plugins/GuildMemberCache/functions/getCachedMemberData.ts
function getCachedMemberData (line 5) | function getCachedMemberData(
FILE: backend/src/plugins/GuildMemberCache/functions/updateMemberCacheForMember.ts
function updateMemberCacheForMember (line 4) | async function updateMemberCacheForMember(
FILE: backend/src/plugins/GuildMemberCache/types.ts
type GuildMemberCachePluginType (line 7) | interface GuildMemberCachePluginType extends BasePluginType {
FILE: backend/src/plugins/InternalPoster/InternalPosterPlugin.ts
method public (line 14) | public(pluginData) {
method beforeLoad (line 21) | async beforeLoad(pluginData) {
FILE: backend/src/plugins/InternalPoster/functions/editMessage.ts
function editMessage (line 9) | async function editMessage(
FILE: backend/src/plugins/InternalPoster/functions/getOrCreateWebhookClientForChannel.ts
function getOrCreateWebhookClientForChannel (line 6) | async function getOrCreateWebhookClientForChannel(
FILE: backend/src/plugins/InternalPoster/functions/getOrCreateWebhookForChannel.ts
type WebhookInfo (line 6) | type WebhookInfo = [id: string, token: string];
type WebhookableChannel (line 8) | type WebhookableChannel = Extract<GuildBasedChannel, { createWebhook: (....
function channelIsWebhookable (line 10) | function channelIsWebhookable(channel: GuildBasedChannel): channel is We...
function getOrCreateWebhookForChannel (line 14) | async function getOrCreateWebhookForChannel(
FILE: backend/src/plugins/InternalPoster/functions/sendMessage.ts
type InternalPosterMessageResult (line 8) | type InternalPosterMessageResult = {
function sendDirectly (line 13) | async function sendDirectly(
function sendMessage (line 26) | async function sendMessage(
FILE: backend/src/plugins/InternalPoster/types.ts
type InternalPosterPluginType (line 9) | interface InternalPosterPluginType extends BasePluginType {
FILE: backend/src/plugins/LocateUser/LocateUserPlugin.ts
method beforeLoad (line 42) | beforeLoad(pluginData) {
method beforeStart (line 49) | beforeStart(pluginData) {
method afterLoad (line 53) | afterLoad(pluginData) {
method beforeUnload (line 62) | beforeUnload(pluginData) {
FILE: backend/src/plugins/LocateUser/commands/FollowCmd.ts
method run (line 22) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/LocateUser/commands/ListFollowCmd.ts
method run (line 12) | async run({ message: msg, pluginData }) {
method run (line 43) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/LocateUser/commands/WhereCmd.ts
method run (line 15) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/LocateUser/events/BanRemoveAlertsEvt.ts
method listener (line 7) | async listener(meta) {
FILE: backend/src/plugins/LocateUser/events/SendAlertsEvts.ts
method listener (line 8) | async listener(meta) {
FILE: backend/src/plugins/LocateUser/types.ts
type LocateUserPluginType (line 11) | interface LocateUserPluginType extends BasePluginType {
FILE: backend/src/plugins/LocateUser/utils/clearExpiredAlert.ts
function clearExpiredAlert (line 6) | async function clearExpiredAlert(pluginData: GuildPluginData<LocateUserP...
FILE: backend/src/plugins/LocateUser/utils/createOrReuseInvite.ts
function createOrReuseInvite (line 3) | async function createOrReuseInvite(vc: VoiceChannel) {
FILE: backend/src/plugins/LocateUser/utils/fillAlertsList.ts
function fillActiveAlertsList (line 4) | async function fillActiveAlertsList(pluginData: GuildPluginData<LocateUs...
FILE: backend/src/plugins/LocateUser/utils/moveMember.ts
function moveMember (line 5) | async function moveMember(
FILE: backend/src/plugins/LocateUser/utils/removeUserIdFromActiveAlerts.ts
function removeUserIdFromActiveAlerts (line 4) | async function removeUserIdFromActiveAlerts(pluginData: GuildPluginData<...
FILE: backend/src/plugins/LocateUser/utils/sendAlerts.ts
function sendAlerts (line 8) | async function sendAlerts(pluginData: GuildPluginData<LocateUserPluginTy...
FILE: backend/src/plugins/LocateUser/utils/sendWhere.ts
function sendWhere (line 7) | async function sendWhere(
FILE: backend/src/plugins/Logs/LogsPlugin.ts
function getCasesPlugin (line 113) | function getCasesPlugin(): Promise<any> {
method public (line 158) | public(pluginData) {
method beforeLoad (line 233) | beforeLoad(pluginData) {
method afterLoad (line 247) | afterLoad(pluginData) {
method beforeUnload (line 281) | beforeUnload(pluginData) {
FILE: backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts
method listener (line 12) | async listener(meta) {
method listener (line 22) | async listener(meta) {
method listener (line 41) | async listener(meta) {
FILE: backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts
method listener (line 15) | async listener(meta) {
method listener (line 25) | async listener(meta) {
method listener (line 37) | async listener(meta) {
method listener (line 58) | async listener(meta) {
method listener (line 68) | async listener(meta) {
method listener (line 80) | async listener(meta) {
FILE: backend/src/plugins/Logs/events/LogsGuildBanEvts.ts
method listener (line 12) | async listener(meta) {
method listener (line 38) | async listener(meta) {
FILE: backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts
method listener (line 7) | async listener(meta) {
FILE: backend/src/plugins/Logs/events/LogsGuildMemberRemoveEvt.ts
method listener (line 7) | async listener(meta) {
FILE: backend/src/plugins/Logs/events/LogsGuildMemberRoleChangeEvt.ts
type RoleAddChange (line 8) | type RoleAddChange = AuditLogChange & {
function isRoleAddChange (line 13) | function isRoleAddChange(change: AuditLogChange): change is RoleAddChange {
type RoleRemoveChange (line 17) | type RoleRemoveChange = AuditLogChange & {
function isRoleRemoveChange (line 22) | function isRoleRemoveChange(change: AuditLogChange): change is RoleRemov...
method listener (line 28) | async listener({ pluginData, args: { auditLogEntry } }) {
FILE: backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts
method listener (line 12) | async listener(meta) {
method listener (line 22) | async listener(meta) {
method listener (line 34) | async listener(meta) {
FILE: backend/src/plugins/Logs/events/LogsStageInstanceModifyEvts.ts
method listener (line 12) | async listener(meta) {
method listener (line 27) | async listener(meta) {
method listener (line 48) | async listener(meta) {
FILE: backend/src/plugins/Logs/events/LogsThreadModifyEvts.ts
method listener (line 12) | async listener(meta) {
method listener (line 22) | async listener(meta) {
method listener (line 34) | async listener(meta) {
FILE: backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts
method listener (line 7) | async listener(meta) {
FILE: backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts
method listener (line 9) | async listener(meta) {
FILE: backend/src/plugins/Logs/logFunctions/logAutomodAction.ts
type LogAutomodActionData (line 9) | interface LogAutomodActionData {
function logAutomodAction (line 18) | function logAutomodAction(pluginData: GuildPluginData<LogsPluginType>, d...
FILE: backend/src/plugins/Logs/logFunctions/logBotAlert.ts
type LogBotAlertData (line 7) | interface LogBotAlertData {
function logBotAlert (line 11) | function logBotAlert(pluginData: GuildPluginData<LogsPluginType>, data: ...
FILE: backend/src/plugins/Logs/logFunctions/logCaseCreate.ts
type LogCaseCreateData (line 9) | interface LogCaseCreateData {
function logCaseCreate (line 17) | function logCaseCreate(pluginData: GuildPluginData<LogsPluginType>, data...
FILE: backend/src/plugins/Logs/logFunctions/logCaseDelete.ts
type LogCaseDeleteData (line 10) | interface LogCaseDeleteData {
function logCaseDelete (line 15) | function logCaseDelete(pluginData: GuildPluginData<LogsPluginType>, data...
FILE: backend/src/plugins/Logs/logFunctions/logCaseUpdate.ts
type LogCaseUpdateData (line 9) | interface LogCaseUpdateData {
function logCaseUpdate (line 16) | function logCaseUpdate(pluginData: GuildPluginData<LogsPluginType>, data...
FILE: backend/src/plugins/Logs/logFunctions/logCensor.ts
type LogCensorData (line 17) | interface LogCensorData {
function logCensor (line 24) | function logCensor(pluginData: GuildPluginData<LogsPluginType>, data: Lo...
FILE: backend/src/plugins/Logs/logFunctions/logChannelCreate.ts
type LogChannelCreateData (line 10) | interface LogChannelCreateData {
function logChannelCreate (line 14) | function logChannelCreate(pluginData: GuildPluginData<LogsPluginType>, d...
FILE: backend/src/plugins/Logs/logFunctions/logChannelDelete.ts
type LogChannelDeleteData (line 10) | interface LogChannelDeleteData {
function logChannelDelete (line 14) | function logChannelDelete(pluginData: GuildPluginData<LogsPluginType>, d...
FILE: backend/src/plugins/Logs/logFunctions/logChannelUpdate.ts
type LogChannelUpdateData (line 10) | interface LogChannelUpdateData {
function logChannelUpdate (line 16) | function logChannelUpdate(pluginData: GuildPluginData<LogsPluginType>, d...
FILE: backend/src/plugins/Logs/logFunctions/logClean.ts
type LogCleanData (line 10) | interface LogCleanData {
function logClean (line 17) | function logClean(pluginData: GuildPluginData<LogsPluginType>, data: Log...
FILE: backend/src/plugins/Logs/logFunctions/logDmFailed.ts
type LogDmFailedData (line 10) | interface LogDmFailedData {
function logDmFailed (line 15) | function logDmFailed(pluginData: GuildPluginData<LogsPluginType>, data: ...
FILE: backend/src/plugins/Logs/logFunctions/logEmojiCreate.ts
type LogEmojiCreateData (line 9) | interface LogEmojiCreateData {
function logEmojiCreate (line 13) | function logEmojiCreate(pluginData: GuildPluginData<LogsPluginType>, dat...
FILE: backend/src/plugins/Logs/logFunctions/logEmojiDelete.ts
type LogEmojiDeleteData (line 9) | interface LogEmojiDeleteData {
function logEmojiDelete (line 13) | function logEmojiDelete(pluginData: GuildPluginData<LogsPluginType>, dat...
FILE: backend/src/plugins/Logs/logFunctions/logEmojiUpdate.ts
type LogEmojiUpdateData (line 9) | interface LogEmojiUpdateData {
function logEmojiUpdate (line 15) | function logEmojiUpdate(pluginData: GuildPluginData<LogsPluginType>, dat...
FILE: backend/src/plugins/Logs/logFunctions/logMassBan.ts
type LogMassBanData (line 9) | interface LogMassBanData {
function logMassBan (line 15) | function logMassBan(pluginData: GuildPluginData<LogsPluginType>, data: L...
FILE: backend/src/plugins/Logs/logFunctions/logMassMute.ts
type LogMassMuteData (line 9) | interface LogMassMuteData {
function logMassMute (line 14) | function logMassMute(pluginData: GuildPluginData<LogsPluginType>, data: ...
FILE: backend/src/plugins/Logs/logFunctions/logMassUnban.ts
type LogMassUnbanData (line 9) | interface LogMassUnbanData {
function logMassUnban (line 15) | function logMassUnban(pluginData: GuildPluginData<LogsPluginType>, data:...
FILE: backend/src/plugins/Logs/logFunctions/logMemberBan.ts
type LogMemberBanData (line 10) | interface LogMemberBanData {
function logMemberBan (line 17) | function logMemberBan(pluginData: GuildPluginData<LogsPluginType>, data:...
FILE: backend/src/plugins/Logs/logFunctions/logMemberForceban.ts
type LogMemberForcebanData (line 9) | interface LogMemberForcebanData {
function logMemberForceban (line 16) | function logMemberForceban(pluginData: GuildPluginData<LogsPluginType>, ...
FILE: backend/src/plugins/Logs/logFunctions/logMemberJoin.ts
type LogMemberJoinData (line 11) | interface LogMemberJoinData {
function logMemberJoin (line 15) | function logMemberJoin(pluginData: GuildPluginData<LogsPluginType>, data...
FILE: backend/src/plugins/Logs/logFunctions/logMemberJoinWithPriorRecords.ts
type LogMemberJoinWithPriorRecordsData (line 9) | interface LogMemberJoinWithPriorRecordsData {
function logMemberJoinWithPriorRecords (line 14) | function logMemberJoinWithPriorRecords(
FILE: backend/src/plugins/Logs/logFunctions/logMemberKick.ts
type LogMemberKickData (line 10) | interface LogMemberKickData {
function logMemberKick (line 17) | function logMemberKick(pluginData: GuildPluginData<LogsPluginType>, data...
FILE: backend/src/plugins/Logs/logFunctions/logMemberLeave.ts
type LogMemberLeaveData (line 9) | interface LogMemberLeaveData {
function logMemberLeave (line 13) | function logMemberLeave(pluginData: GuildPluginData<LogsPluginType>, dat...
FILE: backend/src/plugins/Logs/logFunctions/logMemberMute.ts
type LogMemberMuteData (line 10) | interface LogMemberMuteData {
function logMemberMute (line 17) | function logMemberMute(pluginData: GuildPluginData<LogsPluginType>, data...
FILE: backend/src/plugins/Logs/logFunctions/logMemberMuteExpired.ts
type LogMemberMuteExpiredData (line 14) | interface LogMemberMuteExpiredData {
function logMemberMuteExpired (line 18) | function logMemberMuteExpired(pluginData: GuildPluginData<LogsPluginType...
FILE: backend/src/plugins/Logs/logFunctions/logMemberMuteRejoin.ts
type LogMemberMuteRejoinData (line 9) | interface LogMemberMuteRejoinData {
function logMemberMuteRejoin (line 13) | function logMemberMuteRejoin(pluginData: GuildPluginData<LogsPluginType>...
FILE: backend/src/plugins/Logs/logFunctions/logMemberNickChange.ts
type LogMemberNickChangeData (line 9) | interface LogMemberNickChangeData {
function logMemberNickChange (line 15) | function logMemberNickChange(pluginData: GuildPluginData<LogsPluginType>...
FILE: backend/src/plugins/Logs/logFunctions/logMemberNote.ts
type LogMemberNoteData (line 10) | interface LogMemberNoteData {
function logMemberNote (line 17) | function logMemberNote(pluginData: GuildPluginData<LogsPluginType>, data...
FILE: backend/src/plugins/Logs/logFunctions/logMemberRestore.ts
type LogMemberRestoreData (line 9) | interface LogMemberRestoreData {
function logMemberRestore (line 14) | function logMemberRestore(pluginData: GuildPluginData<LogsPluginType>, d...
FILE: backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts
type LogMemberRoleAddData (line 10) | interface LogMemberRoleAddData {
function logMemberRoleAdd (line 16) | function logMemberRoleAdd(pluginData: GuildPluginData<LogsPluginType>, d...
FILE: backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts
type LogMemberRoleChangesData (line 10) | interface LogMemberRoleChangesData {
function logMemberRoleChanges (line 20) | function logMemberRoleChanges(pluginData: GuildPluginData<LogsPluginType...
FILE: backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts
type LogMemberRoleRemoveData (line 10) | interface LogMemberRoleRemoveData {
function logMemberRoleRemove (line 16) | function logMemberRoleRemove(pluginData: GuildPluginData<LogsPluginType>...
FILE: backend/src/plugins/Logs/logFunctions/logMemberTimedBan.ts
type LogMemberTimedBanData (line 10) | interface LogMemberTimedBanData {
function logMemberTimedBan (line 18) | function logMemberTimedBan(pluginData: GuildPluginData<LogsPluginType>, ...
FILE: backend/src/plugins/Logs/logFunctions/logMemberTimedMute.ts
type LogMemberTimedMuteData (line 10) | interface LogMemberTimedMuteData {
function logMemberTimedMute (line 18) | function logMemberTimedMute(pluginData: GuildPluginData<LogsPluginType>,...
FILE: backend/src/plugins/Logs/logFunctions/logMemberTimedUnban.ts
type LogMemberTimedUnbanData (line 10) | interface LogMemberTimedUnbanData {
function logMemberTimedUnban (line 18) | function logMemberTimedUnban(pluginData: GuildPluginData<LogsPluginType>...
FILE: backend/src/plugins/Logs/logFunctions/logMemberTimedUnmute.ts
type LogMemberTimedUnmuteData (line 10) | interface LogMemberTimedUnmuteData {
function logMemberTimedUnmute (line 18) | function logMemberTimedUnmute(pluginData: GuildPluginData<LogsPluginType...
FILE: backend/src/plugins/Logs/logFunctions/logMemberUnban.ts
type LogMemberUnbanData (line 10) | interface LogMemberUnbanData {
function logMemberUnban (line 17) | function logMemberUnban(pluginData: GuildPluginData<LogsPluginType>, dat...
FILE: backend/src/plugins/Logs/logFunctions/logMemberUnmute.ts
type LogMemberUnmuteData (line 10) | interface LogMemberUnmuteData {
function logMemberUnmute (line 17) | function logMemberUnmute(pluginData: GuildPluginData<LogsPluginType>, da...
FILE: backend/src/plugins/Logs/logFunctions/logMemberWarn.ts
type LogMemberWarnData (line 9) | interface LogMemberWarnData {
function logMemberWarn (line 16) | function logMemberWarn(pluginData: GuildPluginData<LogsPluginType>, data...
FILE: backend/src/plugins/Logs/logFunctions/logMessageDelete.ts
type LogMessageDeleteData (line 19) | interface LogMessageDeleteData {
function logMessageDelete (line 25) | async function logMessageDelete(pluginData: GuildPluginData<LogsPluginTy...
FILE: backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts
type LogMessageDeleteAutoData (line 17) | interface LogMessageDeleteAutoData {
function logMessageDeleteAuto (line 24) | async function logMessageDeleteAuto(pluginData: GuildPluginData<LogsPlug...
FILE: backend/src/plugins/Logs/logFunctions/logMessageDeleteBare.ts
type LogMessageDeleteBareData (line 10) | interface LogMessageDeleteBareData {
function logMessageDeleteBare (line 15) | function logMessageDeleteBare(pluginData: GuildPluginData<LogsPluginType...
FILE: backend/src/plugins/Logs/logFunctions/logMessageDeleteBulk.ts
type LogMessageDeleteBulkData (line 10) | interface LogMessageDeleteBulkData {
function logMessageDeleteBulk (line 17) | function logMessageDeleteBulk(pluginData: GuildPluginData<LogsPluginType...
FILE: backend/src/plugins/Logs/logFunctions/logMessageEdit.ts
type LogMessageEditData (line 16) | interface LogMessageEditData {
function logMessageEdit (line 23) | function logMessageEdit(pluginData: GuildPluginData<LogsPluginType>, dat...
FILE: backend/src/plugins/Logs/logFunctions/logMessageSpamDetected.ts
type LogMessageSpamDetectedData (line 10) | interface LogMessageSpamDetectedData {
function logMessageSpamDetected (line 19) | function logMessageSpamDetected(pluginData: GuildPluginData<LogsPluginTy...
FILE: backend/src/plugins/Logs/logFunctions/logOtherSpamDetected.ts
type LogOtherSpamDetectedData (line 9) | interface LogOtherSpamDetectedData {
function logOtherSpamDetected (line 16) | function logOtherSpamDetected(pluginData: GuildPluginData<LogsPluginType...
FILE: backend/src/plugins/Logs/logFunctions/logPostedScheduledMessage.ts
type LogPostedScheduledMessageData (line 10) | interface LogPostedScheduledMessageData {
function logPostedScheduledMessage (line 16) | function logPostedScheduledMessage(
FILE: backend/src/plugins/Logs/logFunctions/logRepeatedMessage.ts
type LogRepeatedMessageData (line 10) | interface LogRepeatedMessageData {
function logRepeatedMessage (line 20) | function logRepeatedMessage(pluginData: GuildPluginData<LogsPluginType>,...
FILE: backend/src/plugins/Logs/logFunctions/logRoleCreate.ts
type LogRoleCreateData (line 9) | interface LogRoleCreateData {
function logRoleCreate (line 13) | function logRoleCreate(pluginData: GuildPluginData<LogsPluginType>, data...
FILE: backend/src/plugins/Logs/logFunctions/logRoleDelete.ts
type LogRoleDeleteData (line 9) | interface LogRoleDeleteData {
function logRoleDelete (line 13) | function logRoleDelete(pluginData: GuildPluginData<LogsPluginType>, data...
FILE: backend/src/plugins/Logs/logFunctions/logRoleUpdate.ts
type LogRoleUpdateData (line 9) | interface LogRoleUpdateData {
function logRoleUpdate (line 15) | function logRoleUpdate(pluginData: GuildPluginData<LogsPluginType>, data...
FILE: backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts
type LogScheduledMessageData (line 10) | interface LogScheduledMessageData {
function logScheduledMessage (line 18) | function logScheduledMessage(pluginData: GuildPluginData<LogsPluginType>...
FILE: backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts
type LogScheduledRepeatedMessageData (line 10) | interface LogScheduledRepeatedMessageData {
function logScheduledRepeatedMessage (line 20) | function logScheduledRepeatedMessage(
FILE: backend/src/plugins/Logs/logFunctions/logSetAntiraidAuto.ts
type LogSetAntiraidAutoData (line 7) | interface LogSetAntiraidAutoData {
function logSetAntiraidAuto (line 11) | function logSetAntiraidAuto(pluginData: GuildPluginData<LogsPluginType>,...
FILE: backend/src/plugins/Logs/logFunctions/logSetAntiraidUser.ts
type LogSetAntiraidUserData (line 9) | interface LogSetAntiraidUserData {
function logSetAntiraidUser (line 14) | function logSetAntiraidUser(pluginData: GuildPluginData<LogsPluginType>,...
FILE: backend/src/plugins/Logs/logFunctions/logStageInstanceCreate.ts
type LogStageInstanceCreateData (line 10) | interface LogStageInstanceCreateData {
function logStageInstanceCreate (line 15) | function logStageInstanceCreate(pluginData: GuildPluginData<LogsPluginTy...
FILE: backend/src/plugins/Logs/logFunctions/logStageInstanceDelete.ts
type LogStageInstanceDeleteData (line 10) | interface LogStageInstanceDeleteData {
function logStageInstanceDelete (line 15) | function logStageInstanceDelete(pluginData: GuildPluginData<LogsPluginTy...
FILE: backend/src/plugins/Logs/logFunctions/logStageInstanceUpdate.ts
type LogStageInstanceUpdateData (line 10) | interface LogStageInstanceUpdateData {
function logStageInstanceUpdate (line 17) | function logStageInstanceUpdate(pluginData: GuildPluginData<LogsPluginTy...
FILE: backend/src/plugins/Logs/logFunctions/logStickerCreate.ts
type LogStickerCreateData (line 9) | interface LogStickerCreateData {
function logStickerCreate (line 13) | function logStickerCreate(pluginData: GuildPluginData<LogsPluginType>, d...
FILE: backend/src/plugins/Logs/logFunctions/logStickerDelete.ts
type LogStickerDeleteData (line 9) | interface LogStickerDeleteData {
function logStickerDelete (line 13) | function logStickerDelete(pluginData: GuildPluginData<LogsPluginType>, d...
FILE: backend/src/plugins/Logs/logFunctions/logStickerUpdate.ts
type LogStickerUpdateData (line 9) | interface LogStickerUpdateData {
function logStickerUpdate (line 15) | function logStickerUpdate(pluginData: GuildPluginData<LogsPluginType>, d...
FILE: backend/src/plugins/Logs/logFunctions/logThreadCreate.ts
type LogThreadCreateData (line 10) | interface LogThreadCreateData {
function logThreadCreate (line 14) | function logThreadCreate(pluginData: GuildPluginData<LogsPluginType>, da...
FILE: backend/src/plugins/Logs/logFunctions/logThreadDelete.ts
type LogThreadDeleteData (line 10) | interface LogThreadDeleteData {
function logThreadDelete (line 14) | function logThreadDelete(pluginData: GuildPluginData<LogsPluginType>, da...
FILE: backend/src/plugins/Logs/logFunctions/logThreadUpdate.ts
type LogThreadUpdateData (line 10) | interface LogThreadUpdateData {
function logThreadUpdate (line 16) | function logThreadUpdate(pluginData: GuildPluginData<LogsPluginType>, da...
FILE: backend/src/plugins/Logs/logFunctions/logVoiceChannelForceDisconnect.ts
type LogVoiceChannelForceDisconnectData (line 14) | interface LogVoiceChannelForceDisconnectData {
function logVoiceChannelForceDisconnect (line 20) | function logVoiceChannelForceDisconnect(
FILE: backend/src/plugins/Logs/logFunctions/logVoiceChannelForceMove.ts
type LogVoiceChannelForceMoveData (line 14) | interface LogVoiceChannelForceMoveData {
function logVoiceChannelForceMove (line 21) | function logVoiceChannelForceMove(
FILE: backend/src/plugins/Logs/logFunctions/logVoiceChannelJoin.ts
type LogVoiceChannelJoinData (line 10) | interface LogVoiceChannelJoinData {
function logVoiceChannelJoin (line 15) | function logVoiceChannelJoin(pluginData: GuildPluginData<LogsPluginType>...
FILE: backend/src/plugins/Logs/logFunctions/logVoiceChannelLeave.ts
type LogVoiceChannelLeaveData (line 10) | interface LogVoiceChannelLeaveData {
function logVoiceChannelLeave (line 15) | function logVoiceChannelLeave(pluginData: GuildPluginData<LogsPluginType...
FILE: backend/src/plugins/Logs/logFunctions/logVoiceChannelMove.ts
type LogVoiceChannelMoveData (line 10) | interface LogVoiceChannelMoveData {
function logVoiceChannelMove (line 16) | function logVoiceChannelMove(pluginData: GuildPluginData<LogsPluginType>...
FILE: backend/src/plugins/Logs/types.ts
constant DEFAULT_BATCH_TIME (line 28) | const DEFAULT_BATCH_TIME = 1000;
constant MIN_BATCH_TIME (line 29) | const MIN_BATCH_TIME = 250;
constant MAX_BATCH_TIME (line 30) | const MAX_BATCH_TIME = 5000;
type TLogChannel (line 60) | type TLogChannel = z.infer<typeof zLogChannel>;
type TLogChannelMap (line 63) | type TLogChannelMap = z.infer<typeof zLogChannelMap>;
constant FORMAT_NO_TIMESTAMP (line 76) | const FORMAT_NO_TIMESTAMP = "__NO_TIMESTAMP__";
type LogsPluginType (line 78) | interface LogsPluginType extends BasePluginType {
type ILogTypeData (line 527) | type ILogTypeData = z.infer<typeof LogTypeData>;
FILE: backend/src/plugins/Logs/util/getLogMessage.ts
function getLogMessage (line 30) | async function getLogMessage<TLogType extends keyof ILogTypeData>(
FILE: backend/src/plugins/Logs/util/getMessageReplyLogInfo.ts
type MessageReplyLogInfo (line 8) | interface MessageReplyLogInfo {
function getMessageReplyLogInfo (line 13) | async function getMessageReplyLogInfo(
FILE: backend/src/plugins/Logs/util/isLogIgnored.ts
function isLogIgnored (line 5) | function isLogIgnored(
FILE: backend/src/plugins/Logs/util/log.ts
type ExclusionData (line 12) | interface ExclusionData {
constant DEFAULT_BATCH_TIME (line 22) | const DEFAULT_BATCH_TIME = 1000;
constant MIN_BATCH_TIME (line 23) | const MIN_BATCH_TIME = 250;
constant MAX_BATCH_TIME (line 24) | const MAX_BATCH_TIME = 5000;
function shouldExclude (line 26) | async function shouldExclude(
function log (line 74) | async function log<TLogType extends keyof ILogTypeData>(
FILE: backend/src/plugins/Logs/util/onMessageDelete.ts
function onMessageDelete (line 11) | async function onMessageDelete(pluginData: GuildPluginData<LogsPluginTyp...
FILE: backend/src/plugins/Logs/util/onMessageDeleteBulk.ts
function onMessageDeleteBulk (line 10) | async function onMessageDeleteBulk(pluginData: GuildPluginData<LogsPlugi...
FILE: backend/src/plugins/Logs/util/onMessageUpdate.ts
function onMessageUpdate (line 8) | async function onMessageUpdate(
FILE: backend/src/plugins/MessageSaver/MessageSaverPlugin.ts
method beforeLoad (line 41) | beforeLoad(pluginData) {
method beforeStart (line 46) | beforeStart(pluginData) {
FILE: backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts
method run (line 15) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts
method run (line 14) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts
constant AFFECTED_MESSAGE_TYPES (line 4) | const AFFECTED_MESSAGE_TYPES: MessageType[] = [MessageType.Default, Mess...
method listener (line 11) | async listener(meta) {
method listener (line 30) | async listener(meta) {
method listener (line 48) | async listener(meta) {
method listener (line 62) | async listener(meta) {
FILE: backend/src/plugins/MessageSaver/saveMessagesToDB.ts
function saveMessagesToDB (line 5) | async function saveMessagesToDB(
FILE: backend/src/plugins/MessageSaver/types.ts
type MessageSaverPluginType (line 10) | interface MessageSaverPluginType extends BasePluginType {
FILE: backend/src/plugins/ModActions/ModActionsPlugin.ts
method public (line 164) | public(pluginData) {
method beforeLoad (line 181) | beforeLoad(pluginData) {
method beforeStart (line 198) | beforeStart(pluginData) {
method afterLoad (line 202) | afterLoad(pluginData) {
method beforeUnload (line 210) | beforeUnload(pluginData) {
FILE: backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts
method run (line 27) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts
method run (line 40) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/addcase/actualAddCaseCmd.ts
function actualAddCaseCmd (line 13) | async function actualAddCaseCmd(
FILE: backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts
method run (line 36) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts
method run (line 49) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/ban/actualBanCmd.ts
function actualBanCmd (line 22) | async function actualBanCmd(
FILE: backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts
method run (line 16) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts
method run (line 21) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/case/actualCaseCmd.ts
function actualCaseCmd (line 7) | async function actualCaseCmd(
FILE: backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts
method run (line 32) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts
method run (line 34) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts
method run (line 35) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/cases/actualCasesCmd.ts
function sendExpandedCases (line 25) | async function sendExpandedCases(
function casesUserCmd (line 49) | async function casesUserCmd(
function casesModCmd (line 143) | async function casesModCmd(
function actualCasesCmd (line 219) | async function actualCasesCmd(
FILE: backend/src/plugins/ModActions/commands/constants.ts
constant NUMBER_ATTACHMENTS_CASE_CREATION (line 1) | const NUMBER_ATTACHMENTS_CASE_CREATION = 1;
constant NUMBER_ATTACHMENTS_CASE_UPDATE (line 2) | const NUMBER_ATTACHMENTS_CASE_UPDATE = 3;
FILE: backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts
method run (line 21) | async run({ pluginData, message, args }) {
FILE: backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts
method run (line 20) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/deletecase/actualDeleteCaseCmd.ts
function actualDeleteCaseCmd (line 11) | async function actualDeleteCaseCmd(
FILE: backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts
method run (line 26) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts
method run (line 27) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/forceban/actualForceBanCmd.ts
function actualForceBanCmd (line 16) | async function actualForceBanCmd(
FILE: backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts
method run (line 35) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts
method run (line 44) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts
method run (line 32) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts
method run (line 28) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts
method run (line 16) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts
method run (line 15) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/hidecase/actualHideCaseCmd.ts
function actualHideCaseCmd (line 5) | async function actualHideCaseCmd(
FILE: backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts
method run (line 30) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts
method run (line 48) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/kick/actualKickCmd.ts
function actualKickCmd (line 23) | async function actualKickCmd(
FILE: backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts
method run (line 18) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts
method run (line 28) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/massban/actualMassBanCmd.ts
function actualMassBanCmd (line 25) | async function actualMassBanCmd(
FILE: backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts
method run (line 18) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts
method run (line 28) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/massmute/actualMassMuteCmd.ts
function actualMassMuteCmd (line 16) | async function actualMassMuteCmd(
FILE: backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts
method run (line 18) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts
method run (line 28) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/massunban/actualMassUnbanCmd.ts
function actualMassUnbanCmd (line 15) | async function actualMassUnbanCmd(
type UnbanFailReasons (line 115) | enum UnbanFailReasons {
FILE: backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts
method run (line 37) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts
method run (line 46) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/mute/actualMuteCmd.ts
function actualMuteCmd (line 26) | async function actualMuteCmd(
FILE: backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts
method run (line 16) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts
method run (line 23) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/note/actualNoteCmd.ts
function actualNoteCmd (line 11) | async function actualNoteCmd(
FILE: backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts
method run (line 25) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts
method run (line 27) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/unban/actualUnbanCmd.ts
function actualUnbanCmd (line 14) | async function actualUnbanCmd(
FILE: backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts
method run (line 16) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts
method run (line 15) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/unhidecase/actualUnhideCaseCmd.ts
function actualUnhideCaseCmd (line 5) | async function actualUnhideCaseCmd(
FILE: backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts
method run (line 35) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts
method run (line 31) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/unmute/actualUnmuteCmd.ts
function actualUnmuteCmd (line 10) | async function actualUnmuteCmd(
FILE: backend/src/plugins/ModActions/commands/update/UpdateMsgCmd.ts
method run (line 21) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts
method run (line 24) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts
method run (line 23) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts
method run (line 44) | async run({ interaction, options, pluginData }) {
FILE: backend/src/plugins/ModActions/commands/warn/actualWarnCmd.ts
function actualWarnCmd (line 15) | async function actualWarnCmd(
FILE: backend/src/plugins/ModActions/events/AuditLogEvents.ts
method listener (line 10) | async listener({ pluginData, args: { auditLogEntry } }) {
FILE: backend/src/plugins/ModActions/events/CreateBanCaseOnManualBanEvt.ts
method listener (line 19) | async listener({ pluginData, args: { ban } }) {
FILE: backend/src/plugins/ModActions/events/CreateKickCaseOnManualKickEvt.ts
method listener (line 19) | async listener({ pluginData, args: { member } }) {
FILE: backend/src/plugins/ModActions/events/CreateUnbanCaseOnManualUnbanEvt.ts
method listener (line 18) | async listener({ pluginData, args: { ban } }) {
FILE: backend/src/plugins/ModActions/events/PostAlertOnMemberJoinEvt.ts
method listener (line 12) | async listener({ pluginData, args: { member } }) {
FILE: backend/src/plugins/ModActions/functions/attachmentLinkReaction.ts
function shouldReactToAttachmentLink (line 5) | function shouldReactToAttachmentLink(pluginData: GuildPluginData<ModActi...
function attachmentLinkShouldRestrict (line 10) | function attachmentLinkShouldRestrict(pluginData: GuildPluginData<ModAct...
function detectAttachmentLink (line 14) | function detectAttachmentLink(reason: string | null | undefined) {
function sendAttachmentLinkDetectionErrorMessage (line 18) | function sendAttachmentLinkDetectionErrorMessage(
function handleAttachmentLinkDetectionAndGetRestriction (line 33) | async function handleAttachmentLinkDetectionAndGetRestriction(
FILE: backend/src/plugins/ModActions/functions/banUserId.ts
function banUserId (line 29) | async function banUserId(
FILE: backend/src/plugins/ModActions/functions/clearIgnoredEvents.ts
function clearIgnoredEvents (line 4) | function clearIgnoredEvents(
FILE: backend/src/plugins/ModActions/functions/clearTempban.ts
function clearTempban (line 16) | async function clearTempban(pluginData: GuildPluginData<ModActionsPlugin...
FILE: backend/src/plugins/ModActions/functions/formatReasonForAttachments.ts
function formatReasonWithMessageLinkForAttachments (line 6) | async function formatReasonWithMessageLinkForAttachments(
function formatReasonWithAttachments (line 27) | function formatReasonWithAttachments(reason: string, attachments: Attach...
FILE: backend/src/plugins/ModActions/functions/getDefaultContactMethods.ts
function getDefaultContactMethods (line 6) | function getDefaultContactMethods(
FILE: backend/src/plugins/ModActions/functions/hasModActionPerm.ts
function hasNotePermission (line 5) | async function hasNotePermission(
function hasWarnPermission (line 13) | async function hasWarnPermission(
function hasMutePermission (line 21) | async function hasMutePermission(
function hasBanPermission (line 29) | async function hasBanPermission(
FILE: backend/src/plugins/ModActions/functions/ignoreEvent.ts
constant DEFAULT_TIMEOUT (line 6) | const DEFAULT_TIMEOUT = 15 * SECONDS;
function ignoreEvent (line 8) | function ignoreEvent(
FILE: backend/src/plugins/ModActions/functions/isBanned.ts
function isBanned (line 8) | async function isBanned(
FILE: backend/src/plugins/ModActions/functions/isEventIgnored.ts
function isEventIgnored (line 4) | function isEventIgnored(
FILE: backend/src/plugins/ModActions/functions/kickMember.ts
function kickMember (line 23) | async function kickMember(
FILE: backend/src/plugins/ModActions/functions/offModActionsEvent.ts
function offModActionsEvent (line 4) | function offModActionsEvent<TEvent extends keyof ModActionsEvents>(
FILE: backend/src/plugins/ModActions/functions/onModActionsEvent.ts
function onModActionsEvent (line 4) | function onModActionsEvent<TEvent extends keyof ModActionsEvents>(
FILE: backend/src/plugins/ModActions/functions/readContactMethodsFromArgs.ts
function readContactMethodsFromArgs (line 4) | function readContactMethodsFromArgs(args: {
FILE: backend/src/plugins/ModActions/functions/updateCase.ts
function updateCase (line 11) | async function updateCase(
FILE: backend/src/plugins/ModActions/functions/warnMember.ts
function warnMember (line 19) | async function warnMember(
FILE: backend/src/plugins/ModActions/types.ts
type AttachmentLinkReactionType (line 22) | type AttachmentLinkReactionType = "none" | "warn" | "restrict" | null;
type ModActionsEvents (line 72) | interface ModActionsEvents {
type ModActionsEventEmitter (line 81) | interface ModActionsEventEmitter extends EventEmitter {
type ModActionsPluginType (line 86) | interface ModActionsPluginType extends BasePluginType {
type IgnoredEventType (line 105) | enum IgnoredEventType {
type IIgnoredEvent (line 111) | interface IIgnoredEvent {
type WarnResult (line 116) | type WarnResult =
type KickResult (line 127) | type KickResult =
type BanResult (line 138) | type BanResult =
type WarnMemberNotifyRetryCallback (line 149) | type WarnMemberNotifyRetryCallback = () => boolean | Promise<boolean>;
type WarnOptions (line 151) | interface WarnOptions {
type KickOptions (line 158) | interface KickOptions {
type BanOptions (line 164) | interface BanOptions {
type ModActionType (line 172) | type ModActionType = "note" | "warn" | "mute" | "unmute" | "kick" | "ban...
FILE: backend/src/plugins/Mutes/MutesPlugin.ts
method public (line 65) | public(pluginData) {
method beforeLoad (line 79) | beforeLoad(pluginData) {
method beforeStart (line 90) | beforeStart(pluginData) {
method afterLoad (line 94) | afterLoad(pluginData) {
method beforeUnload (line 110) | beforeUnload(pluginData) {
FILE: backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts
method run (line 9) | async run({ pluginData, message: msg }) {
FILE: backend/src/plugins/Mutes/commands/ClearMutesCmd.ts
method run (line 13) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts
method run (line 10) | async run({ pluginData, message: msg }) {
FILE: backend/src/plugins/Mutes/commands/MutesCmd.ts
method run (line 31) | async run({ pluginData, message: msg, args }) {
FILE: backend/src/plugins/Mutes/events/ClearActiveMuteOnMemberBanEvt.ts
method listener (line 8) | async listener({ pluginData, args: { ban } }) {
FILE: backend/src/plugins/Mutes/events/ClearActiveMuteOnRoleRemovalEvt.ts
method listener (line 9) | async listener({ pluginData, args: { newMember: member } }) {
FILE: backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts
method listener (line 14) | async listener({ pluginData, args: { member } }) {
FILE: backend/src/plugins/Mutes/events/RegisterManualTimeoutsEvt.ts
method listener (line 9) | async listener({ pluginData, args: { auditLogEntry } }) {
FILE: backend/src/plugins/Mutes/functions/clearMute.ts
function clearMute (line 12) | async function clearMute(
FILE: backend/src/plugins/Mutes/functions/getDefaultMuteType.ts
function getDefaultMuteType (line 5) | function getDefaultMuteType(pluginData: GuildPluginData<MutesPluginType>...
FILE: backend/src/plugins/Mutes/functions/getTimeoutExpiryTime.ts
function getTimeoutExpiryTime (line 9) | function getTimeoutExpiryTime(muteExpiresAt: number | null | undefined):...
FILE: backend/src/plugins/Mutes/functions/memberHasMutedRole.ts
function memberHasMutedRole (line 5) | function memberHasMutedRole(pluginData: GuildPluginData<MutesPluginType>...
FILE: backend/src/plugins/Mutes/functions/muteUser.ts
function muteUser (line 33) | async function muteUser(
FILE: backend/src/plugins/Mutes/functions/offMutesEvent.ts
function offMutesEvent (line 4) | function offMutesEvent<TEvent extends keyof MutesEvents>(
FILE: backend/src/plugins/Mutes/functions/onMutesEvent.ts
function onMutesEvent (line 4) | function onMutesEvent<TEvent extends keyof MutesEvents>(
FILE: backend/src/plugins/Mutes/functions/renewTimeoutMute.ts
function renewTimeoutMute (line 10) | async function renewTimeoutMute(pluginData: GuildPluginData<MutesPluginT...
FILE: backend/src/plugins/Mutes/functions/unmuteUser.ts
function unmuteUser (line 18) | async function unmuteUser(
FILE: backend/src/plugins/Mutes/types.ts
type MutesEvents (line 38) | interface MutesEvents {
type MutesEventEmitter (line 43) | interface MutesEventEmitter extends EventEmitter {
type MutesPluginType (line 48) | interface MutesPluginType extends BasePluginType {
type IMuteWithDetails (line 65) | interface IMuteWithDetails extends Mute {
type MuteResult (line 70) | type MuteResult = {
type UnmuteResult (line 76) | type UnmuteResult = {
type MuteOptions (line 80) | interface MuteOptions {
FILE: backend/src/plugins/NameHistory/NameHistoryPlugin.ts
method beforeLoad (line 34) | beforeLoad(pluginData) {
method beforeStart (line 42) | beforeStart(pluginData) {
FILE: backend/src/plugins/NameHistory/commands/NamesCmd.ts
method run (line 18) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/NameHistory/events/UpdateNameEvts.ts
method listener (line 7) | async listener(meta) {
method listener (line 17) | async listener(meta) {
FILE: backend/src/plugins/NameHistory/types.ts
type NameHistoryPluginType (line 12) | interface NameHistoryPluginType extends BasePluginType {
FILE: backend/src/plugins/NameHistory/updateNickname.ts
function updateNickname (line 5) | async function updateNickname(pluginData: GuildPluginData<NameHistoryPlu...
FILE: backend/src/plugins/Persist/PersistPlugin.ts
method beforeLoad (line 22) | beforeLoad(pluginData) {
FILE: backend/src/plugins/Persist/events/LoadDataEvt.ts
function applyPersistedData (line 15) | async function applyPersistedData(
method listener (line 52) | async listener(meta) {
FILE: backend/src/plugins/Persist/events/StoreDataEvt.ts
method listener (line 7) | async listener({ pluginData, args: { member } }) {
FILE: backend/src/plugins/Persist/types.ts
type PersistPluginType (line 13) | interface PersistPluginType extends BasePluginType {
FILE: backend/src/plugins/Phisherman/types.ts
type PhishermanPluginType (line 8) | interface PhishermanPluginType extends BasePluginType {
FILE: backend/src/plugins/PingableRoles/PingableRolesPlugin.ts
method beforeLoad (line 34) | beforeLoad(pluginData) {
method beforeStart (line 42) | beforeStart(pluginData) {
FILE: backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts
method run (line 13) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts
method run (line 13) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/PingableRoles/events/ChangePingableEvts.ts
constant TIMEOUT (line 6) | const TIMEOUT = 10 * 1000;
method listener (line 11) | async listener(meta) {
method listener (line 34) | async listener(meta) {
FILE: backend/src/plugins/PingableRoles/types.ts
type PingableRolesPluginType (line 11) | interface PingableRolesPluginType extends BasePluginType {
FILE: backend/src/plugins/PingableRoles/utils/disablePingableRoles.ts
function disablePingableRoles (line 6) | function disablePingableRoles(
FILE: backend/src/plugins/PingableRoles/utils/enablePingableRoles.ts
function enablePingableRoles (line 6) | function enablePingableRoles(
FILE: backend/src/plugins/PingableRoles/utils/getPingableRolesForChannel.ts
function getPingableRolesForChannel (line 5) | async function getPingableRolesForChannel(
FILE: backend/src/plugins/Post/PostPlugin.ts
method beforeLoad (line 44) | beforeLoad(pluginData) {
method beforeStart (line 52) | beforeStart(pluginData) {
method afterLoad (line 56) | afterLoad(pluginData) {
method beforeUnload (line 64) | beforeUnload(pluginData) {
FILE: backend/src/plugins/Post/commands/EditCmd.ts
method run (line 14) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Post/commands/EditEmbedCmd.ts
method run (line 23) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Post/commands/PostCmd.ts
method run (line 20) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Post/commands/PostEmbedCmd.ts
method run (line 29) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts
method run (line 14) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts
constant SCHEDULED_POST_PREVIEW_TEXT_LENGTH (line 8) | const SCHEDULED_POST_PREVIEW_TEXT_LENGTH = 50;
method run (line 14) | async run({ message: msg, pluginData }) {
FILE: backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts
method run (line 14) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Post/types.ts
type PostPluginType (line 12) | interface PostPluginType extends BasePluginType {
FILE: backend/src/plugins/Post/util/actualPostCmd.ts
constant MIN_REPEAT_TIME (line 13) | const MIN_REPEAT_TIME = 5 * MINUTES;
constant MAX_REPEAT_TIME (line 14) | const MAX_REPEAT_TIME = Math.pow(2, 32);
constant MAX_REPEAT_UNTIL (line 15) | const MAX_REPEAT_UNTIL = moment.utc().add(100, "years");
function actualPostCmd (line 17) | async function actualPostCmd(
FILE: backend/src/plugins/Post/util/formatContent.ts
function formatContent (line 1) | function formatContent(str: string) {
FILE: backend/src/plugins/Post/util/parseScheduleTime.ts
function parseScheduleTime (line 7) | async function parseScheduleTime(
FILE: backend/src/plugins/Post/util/postMessage.ts
function postMessage (line 10) | async function postMessage(
FILE: backend/src/plugins/Post/util/postScheduledPost.ts
function postScheduledPost (line 12) | async function postScheduledPost(pluginData: GuildPluginData<PostPluginT...
FILE: backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts
method beforeLoad (line 41) | beforeLoad(pluginData) {
method beforeStart (line 52) | beforeStart(pluginData) {
method afterLoad (line 56) | afterLoad(pluginData) {
method beforeUnload (line 65) | beforeUnload(pluginData) {
FILE: backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts
method run (line 14) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts
constant CLEAR_ROLES_EMOJI (line 9) | const CLEAR_ROLES_EMOJI = "❌";
method run (line 35) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts
method run (line 13) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/ReactionRoles/events/AddReactionRoleEvt.ts
constant CLEAR_ROLES_EMOJI (line 6) | const CLEAR_ROLES_EMOJI = "❌";
method listener (line 11) | async listener(meta) {
FILE: backend/src/plugins/ReactionRoles/events/MessageDeletedEvt.ts
method listener (line 8) | async listener(meta) {
FILE: backend/src/plugins/ReactionRoles/types.ts
constant MIN_AUTO_REFRESH (line 8) | const MIN_AUTO_REFRESH = 1000 * 60 * 15;
type RoleChangeMode (line 17) | type RoleChangeMode = "+" | "-";
type PendingMemberRoleChanges (line 19) | type PendingMemberRoleChanges = {
type TReactionRolePair (line 29) | type TReactionRolePair = z.infer<typeof zReactionRolePair>;
type ReactionRolesPluginType (line 31) | interface ReactionRolesPluginType extends BasePluginType {
FILE: backend/src/plugins/ReactionRoles/util/addMemberPendingRoleChange.ts
constant ROLE_CHANGE_BATCH_DEBOUNCE_TIME (line 8) | const ROLE_CHANGE_BATCH_DEBOUNCE_TIME = 1500;
function addMemberPendingRoleChange (line 10) | async function addMemberPendingRoleChange(
FILE: backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts
constant CLEAR_ROLES_EMOJI (line 8) | const CLEAR_ROLES_EMOJI = "❌";
function applyReactionRoleReactionsToMessage (line 13) | async function applyReactionRoleReactionsToMessage(
FILE: backend/src/plugins/ReactionRoles/util/autoRefreshLoop.ts
function autoRefreshLoop (line 5) | async function autoRefreshLoop(pluginData: GuildPluginData<ReactionRoles...
FILE: backend/src/plugins/ReactionRoles/util/refreshReactionRoles.ts
function refreshReactionRoles (line 5) | async function refreshReactionRoles(
FILE: backend/src/plugins/ReactionRoles/util/runAutoRefresh.ts
function runAutoRefresh (line 5) | async function runAutoRefresh(pluginData: GuildPluginData<ReactionRolesP...
FILE: backend/src/plugins/Reminders/RemindersPlugin.ts
method beforeLoad (line 33) | beforeLoad(pluginData) {
method beforeStart (line 41) | beforeStart(pluginData) {
method afterLoad (line 45) | afterLoad(pluginData) {
method beforeUnload (line 53) | beforeUnload(pluginData) {
FILE: backend/src/plugins/Reminders/commands/RemindCmd.ts
method run (line 19) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Reminders/commands/RemindersCmd.ts
method run (line 11) | async run({ message: msg, pluginData }) {
FILE: backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts
method run (line 14) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Reminders/functions/postReminder.ts
function postReminder (line 9) | async function postReminder(pluginData: GuildPluginData<RemindersPluginT...
FILE: backend/src/plugins/Reminders/types.ts
type RemindersPluginType (line 10) | interface RemindersPluginType extends BasePluginType {
FILE: backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts
method beforeLoad (line 30) | beforeLoad(pluginData) {
method beforeStart (line 34) | beforeStart(pluginData) {
method afterLoad (line 38) | async afterLoad(pluginData) {
FILE: backend/src/plugins/RoleButtons/commands/resetButtons.ts
method run (line 15) | async run({ pluginData, args, message }) {
FILE: backend/src/plugins/RoleButtons/events/buttonInteraction.ts
constant ROLE_BUTTON_CD (line 12) | const ROLE_BUTTON_CD = 5 * SECONDS;
method listener (line 16) | async listener({ pluginData, args }) {
FILE: backend/src/plugins/RoleButtons/functions/TooManyComponentsError.ts
class TooManyComponentsError (line 1) | class TooManyComponentsError extends Error {}
FILE: backend/src/plugins/RoleButtons/functions/applyAllRoleButtons.ts
function applyAllRoleButtons (line 6) | async function applyAllRoleButtons(pluginData: GuildPluginData<RoleButto...
FILE: backend/src/plugins/RoleButtons/functions/applyRoleButtons.ts
function applyRoleButtons (line 8) | async function applyRoleButtons(
FILE: backend/src/plugins/RoleButtons/functions/convertButtonStyleStringToEnum.ts
function convertButtonStyleStringToEnum (line 4) | function convertButtonStyleStringToEnum(input: TRoleButtonOption["style"...
FILE: backend/src/plugins/RoleButtons/functions/createButtonComponents.ts
function createButtonComponents (line 7) | function createButtonComponents(
FILE: backend/src/plugins/RoleButtons/functions/getAllRolesInButtons.ts
function getAllRolesInButtons (line 4) | function getAllRolesInButtons(buttons: TRoleButtonsConfigItem): string[] {
FILE: backend/src/plugins/RoleButtons/types.ts
type TRoleButtonOption (line 33) | type TRoleButtonOption = z.infer<typeof zRoleButtonOption>;
type TRoleButtonsConfigItem (line 68) | type TRoleButtonsConfigItem = z.infer<typeof zRoleButtonsConfigItem>;
type RoleButtonsPluginType (line 95) | interface RoleButtonsPluginType extends BasePluginType {
FILE: backend/src/plugins/RoleManager/RoleManagerPlugin.ts
method public (line 18) | public(pluginData) {
method beforeLoad (line 27) | beforeLoad(pluginData) {
method afterLoad (line 34) | afterLoad(pluginData) {
method afterUnload (line 38) | async afterUnload(pluginData) {
FILE: backend/src/plugins/RoleManager/constants.ts
constant PRIORITY_ROLE_PRIORITY (line 1) | const PRIORITY_ROLE_PRIORITY = 10;
FILE: backend/src/plugins/RoleManager/functions/addPriorityRole.ts
function addPriorityRole (line 6) | async function addPriorityRole(
FILE: backend/src/plugins/RoleManager/functions/addRole.ts
function addRole (line 5) | async function addRole(pluginData: GuildPluginData<RoleManagerPluginType...
FILE: backend/src/plugins/RoleManager/functions/removePriorityRole.ts
function removePriorityRole (line 6) | async function removePriorityRole(
FILE: backend/src/plugins/RoleManager/functions/removeRole.ts
function removeRole (line 5) | async function removeRole(pluginData: GuildPluginData<RoleManagerPluginT...
FILE: backend/src/plugins/RoleManager/functions/runRoleAssignmentLoop.ts
constant ROLE_ASSIGNMENTS_PER_BATCH (line 6) | const ROLE_ASSIGNMENTS_PER_BATCH = 10;
function runRoleAssignmentLoop (line 8) | async function runRoleAssignmentLoop(pluginData: GuildPluginData<RoleMan...
FILE: backend/src/plugins/RoleManager/types.ts
type RoleManagerPluginType (line 7) | interface RoleManagerPluginType extends BasePluginType {
FILE: backend/src/plugins/Roles/RolesPlugin.ts
method beforeLoad (line 40) | beforeLoad(pluginData) {
method beforeStart (line 46) | beforeStart(pluginData) {
FILE: backend/src/plugins/Roles/commands/AddRoleCmd.ts
method run (line 19) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Roles/commands/MassAddRoleCmd.ts
method run (line 19) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts
method run (line 18) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Roles/commands/RemoveRoleCmd.ts
method run (line 19) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Roles/types.ts
type RolesPluginType (line 12) | interface RolesPluginType extends BasePluginType {
FILE: backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts
method beforeLoad (line 20) | beforeLoad(pluginData) {
method beforeStart (line 24) | beforeStart(pluginData) {
FILE: backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts
method run (line 19) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/SelfGrantableRoles/commands/RoleHelpCmd.ts
method run (line 9) | async run({ message: msg, pluginData }) {
FILE: backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts
method run (line 19) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/SelfGrantableRoles/types.ts
type TSelfGrantableRoleEntry (line 20) | type TSelfGrantableRoleEntry = z.infer<typeof zSelfGrantableRoleEntry>;
type SelfGrantableRolesPluginType (line 27) | interface SelfGrantableRolesPluginType extends BasePluginType {
FILE: backend/src/plugins/SelfGrantableRoles/util/findMatchingRoles.ts
function findMatchingRoles (line 3) | function findMatchingRoles(roleNames: string[], entries: TSelfGrantableR...
FILE: backend/src/plugins/SelfGrantableRoles/util/getApplyingEntries.ts
function getApplyingEntries (line 4) | async function getApplyingEntries(
FILE: backend/src/plugins/SelfGrantableRoles/util/normalizeRoleNames.ts
function normalizeRoleNames (line 1) | function normalizeRoleNames(roleNames: string[]) {
FILE: backend/src/plugins/SelfGrantableRoles/util/splitRoleNames.ts
function splitRoleNames (line 1) | function splitRoleNames(roleNames: string[]) {
FILE: backend/src/plugins/Slowmode/SlowmodePlugin.ts
constant BOT_SLOWMODE_CLEAR_INTERVAL (line 17) | const BOT_SLOWMODE_CLEAR_INTERVAL = 60 * SECONDS;
method beforeLoad (line 47) | beforeLoad(pluginData) {
method beforeStart (line 56) | beforeStart(pluginData) {
method afterLoad (line 60) | afterLoad(pluginData) {
method beforeUnload (line 70) | beforeUnload(pluginData) {
FILE: backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts
method run (line 21) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Slowmode/commands/SlowmodeDisableCmd.ts
method run (line 13) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Slowmode/commands/SlowmodeGetCmd.ts
method run (line 14) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Slowmode/commands/SlowmodeListCmd.ts
method run (line 11) | async run({ message: msg, pluginData }) {
FILE: backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts
constant MAX_NATIVE_SLOWMODE (line 12) | const MAX_NATIVE_SLOWMODE = 6 * HOURS;
constant MAX_BOT_SLOWMODE (line 13) | const MAX_BOT_SLOWMODE = DAYS * 365 * 100;
constant MIN_BOT_SLOWMODE (line 14) | const MIN_BOT_SLOWMODE = 15 * MINUTES;
type TMode (line 17) | type TMode = "bot" | "native";
method run (line 38) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Slowmode/requiredPermissions.ts
constant NATIVE_SLOWMODE_PERMISSIONS (line 5) | const NATIVE_SLOWMODE_PERMISSIONS = p.ViewChannel | p.ManageChannels;
constant BOT_SLOWMODE_PERMISSIONS (line 6) | const BOT_SLOWMODE_PERMISSIONS = p.ViewChannel | p.ManageRoles | p.Manag...
constant BOT_SLOWMODE_CLEAR_PERMISSIONS (line 7) | const BOT_SLOWMODE_CLEAR_PERMISSIONS = p.ViewChannel | p.ManageRoles;
constant BOT_SLOWMODE_DISABLE_PERMISSIONS (line 8) | const BOT_SLOWMODE_DISABLE_PERMISSIONS = p.ViewChannel | p.ManageRoles;
FILE: backend/src/plugins/Slowmode/types.ts
type SlowmodePluginType (line 16) | interface SlowmodePluginType extends BasePluginType {
FILE: backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts
function actualDisableSlowmodeCmd (line 8) | async function actualDisableSlowmodeCmd(msg: Message, args, pluginData) {
FILE: backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts
function applyBotSlowmodeToUserId (line 9) | async function applyBotSlowmodeToUserId(
FILE: backend/src/plugins/Slowmode/util/clearBotSlowmodeFromUserId.ts
function clearBotSlowmodeFromUserId (line 6) | async function clearBotSlowmodeFromUserId(
FILE: backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts
function clearExpiredSlowmodes (line 9) | async function clearExpiredSlowmodes(pluginData: GuildPluginData<Slowmod...
FILE: backend/src/plugins/Slowmode/util/disableBotSlowmodeForChannel.ts
function disableBotSlowmodeForChannel (line 6) | async function disableBotSlowmodeForChannel(
FILE: backend/src/plugins/Slowmode/util/onMessageCreate.ts
function onMessageCreate (line 15) | async function onMessageCreate(pluginData: GuildPluginData<SlowmodePlugi...
FILE: backend/src/plugins/Spam/SpamPlugin.ts
method beforeLoad (line 39) | beforeLoad(pluginData) {
method afterLoad (line 53) | afterLoad(pluginData) {
method beforeUnload (line 61) | beforeUnload(pluginData) {
FILE: backend/src/plugins/Spam/events/SpamVoiceEvt.ts
method listener (line 7) | async listener(meta) {
FILE: backend/src/plugins/Spam/types.ts
type TBaseSingleSpamConfig (line 18) | type TBaseSingleSpamConfig = z.infer<typeof zBaseSingleSpamConfig>;
type RecentActionType (line 33) | enum RecentActionType {
type IRecentAction (line 45) | interface IRecentAction<T> {
type SpamPluginType (line 54) | interface SpamPluginType extends BasePluginType {
FILE: backend/src/plugins/Spam/util/addRecentAction.ts
function addRecentAction (line 4) | function addRecentAction(
FILE: backend/src/plugins/Spam/util/clearOldRecentActions.ts
constant MAX_INTERVAL (line 4) | const MAX_INTERVAL = 300;
function clearOldRecentActions (line 6) | function clearOldRecentActions(pluginData: GuildPluginData<SpamPluginTyp...
FILE: backend/src/plugins/Spam/util/clearRecentUserActions.ts
function clearRecentUserActions (line 4) | function clearRecentUserActions(
FILE: backend/src/plugins/Spam/util/getRecentActionCount.ts
function getRecentActionCount (line 4) | function getRecentActionCount(
FILE: backend/src/plugins/Spam/util/getRecentActions.ts
function getRecentActions (line 4) | function getRecentActions(
FILE: backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts
function logAndDetectMessageSpam (line 21) | async function logAndDetectMessageSpam(
FILE: backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts
function logAndDetectOtherSpam (line 13) | async function logAndDetectOtherSpam(
FILE: backend/src/plugins/Spam/util/logCensor.ts
function logCensor (line 7) | async function logCensor(pluginData: GuildPluginData<SpamPluginType>, sa...
FILE: backend/src/plugins/Spam/util/onMessageCreate.ts
function onMessageCreate (line 8) | async function onMessageCreate(pluginData: GuildPluginData<SpamPluginTyp...
FILE: backend/src/plugins/Spam/util/saveSpamArchives.ts
constant SPAM_ARCHIVE_EXPIRY_DAYS (line 7) | const SPAM_ARCHIVE_EXPIRY_DAYS = 90;
function saveSpamArchives (line 9) | async function saveSpamArchives(pluginData: GuildPluginData<SpamPluginTy...
FILE: backend/src/plugins/Starboard/StarboardPlugin.ts
method beforeLoad (line 37) | beforeLoad(pluginData) {
method beforeStart (line 45) | beforeStart(pluginData) {
method afterLoad (line 49) | afterLoad(pluginData) {
method beforeUnload (line 56) | beforeUnload(pluginData) {
FILE: backend/src/plugins/Starboard/commands/MigratePinsCmd.ts
method run (line 17) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts
method listener (line 11) | async listener(meta) {
FILE: backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts
method listener (line 7) | async listener(meta) {
method listener (line 20) | async listener(meta) {
FILE: backend/src/plugins/Starboard/types.ts
type TStarboardOpts (line 19) | type TStarboardOpts = z.infer<typeof zStarboardOpts>;
type StarboardPluginType (line 26) | interface StarboardPluginType extends BasePluginType {
FILE: backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts
type StarboardEmbed (line 9) | type StarboardEmbed = EmbedWith<"footer" | "author" | "fields" | "timest...
function createStarboardEmbedFromMessage (line 11) | function createStarboardEmbedFromMessage(
FILE: backend/src/plugins/Starboard/util/createStarboardPseudoFooterForMessage.ts
function createStarboardPseudoFooterForMessage (line 5) | function createStarboardPseudoFooterForMessage(
FILE: backend/src/plugins/Starboard/util/onMessageDelete.ts
function onMessageDelete (line 7) | async function onMessageDelete(pluginData: GuildPluginData<StarboardPlug...
FILE: backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts
function removeMessageFromStarboard (line 7) | async function removeMessageFromStarboard(
FILE: backend/src/plugins/Starboard/util/removeMessageFromStarboardMessages.ts
function removeMessageFromStarboardMessages (line 4) | async function removeMessageFromStarboardMessages(
FILE: backend/src/plugins/Starboard/util/saveMessageToStarboard.ts
function saveMessageToStarboard (line 7) | async function saveMessageToStarboard(
FILE: backend/src/plugins/Starboard/util/updateStarboardMessageStarCount.ts
constant DEBOUNCE_DELAY (line 6) | const DEBOUNCE_DELAY = 1000;
function updateStarboardMessageStarCount (line 9) | async function updateStarboardMessageStarCount(
FILE: backend/src/plugins/Tags/TagsPlugin.ts
method public (line 55) | public(pluginData) {
method beforeLoad (line 62) | beforeLoad(pluginData) {
method beforeStart (line 73) | beforeStart(pluginData) {
method afterLoad (line 77) | afterLoad(pluginData) {
method beforeUnload (line 224) | beforeUnload(pluginData) {
FILE: backend/src/plugins/Tags/commands/TagCreateCmd.ts
method run (line 14) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Tags/commands/TagDeleteCmd.ts
method run (line 12) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Tags/commands/TagEvalCmd.ts
method run (line 18) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Tags/commands/TagListCmd.ts
method run (line 14) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Tags/commands/TagSourceCmd.ts
method run (line 16) | async run({ message: msg, args, pluginData }) {
FILE: backend/src/plugins/Tags/docs.ts
function generateTemplateMarkdown (line 23) | function generateTemplateMarkdown(definitions: TemplateFunction[]): stri...
FILE: backend/src/plugins/Tags/types.ts
type TTag (line 11) | type TTag = z.infer<typeof zTag>;
type TTagCategory (line 32) | type TTagCategory = z.infer<typeof zTagCategory>;
type TagsPluginType (line 56) | interface TagsPluginType extends BasePluginType {
type TemplateFunction (line 71) | interface TemplateFunction {
FILE: backend/src/plugins/Tags/util/findTagByName.ts
function findTagByName (line 4) | async function findTagByName(
FILE: backend/src/plugins/Tags/util/matchAndRenderTagFromString.ts
type BaseResult (line 8) | interface BaseResult {
type ResultWithCategory (line 13) | type ResultWithCategory = BaseResult & {
type ResultWithoutCategory (line 18) | type ResultWithoutCategory = BaseResult & {
type Result (line 23) | type Result = ResultWithCategory | ResultWithoutCategory;
function matchAndRenderTagFromString (line 25) | async function matchAndRenderTagFromString(
FILE: backend/src/plugins/Tags/util/onMessageCreat
Condensed preview — 1116 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,905K chars).
[
{
"path": ".clabot",
"chars": 878,
"preview": "{\n \"contributors\": [\n \"almeidx\",\n \"axisiscool\",\n \"BanTheNons\",\n \"Benricheson101\",\n \"brawaru\",\n \"Cleve"
},
{
"path": ".cursorignore",
"chars": 1306,
"preview": "# Created by .ignore support plugin (hsz.mobi)\n### Node template\n# Logs\n/logs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-"
},
{
"path": ".devcontainer/devcontainer.json",
"chars": 291,
"preview": "{\n \"name\": \"Zeppelin Development\",\n\n \"dockerComposeFile\": \"../docker-compose.development.yml\",\n\n \"service\": \"devenv\","
},
{
"path": ".dockerignore",
"chars": 537,
"preview": "**/.git\n**/.github\n**/.idea\n**/.devcontainer\n\n/docker/development/data\n/docker/production/data\n\n**/node_modules\n**/dist\n"
},
{
"path": ".editorconfig",
"chars": 99,
"preview": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\n"
},
{
"path": ".eslintrc.js",
"chars": 770,
"preview": "module.exports = {\n root: true,\n env: {\n node: true,\n browser: true,\n es6: true,\n },\n extends: [\"eslint:rec"
},
{
"path": ".gitattributes",
"chars": 47,
"preview": "package-lock.json binary\npnpm-lock.yaml binary\n"
},
{
"path": ".github/dependabot.yml",
"chars": 434,
"preview": "version: 2\n\nupdates:\n - package-ecosystem: npm\n directory: /\n schedule:\n interval: daily\n groups:\n n"
},
{
"path": ".github/workflows/codequality.yml",
"chars": 491,
"preview": "name: Code quality checks\n\non: [push, pull_request]\n\njobs:\n build:\n runs-on: ubuntu-latest\n\n steps:\n - uses: a"
},
{
"path": ".gitignore",
"chars": 1318,
"preview": "# Created by .ignore support plugin (hsz.mobi)\n### Node template\n# Logs\n/logs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-"
},
{
"path": ".nvmrc",
"chars": 3,
"preview": "24\n"
},
{
"path": ".prettierignore",
"chars": 42,
"preview": ".github\n.idea\nnode_modules\n/assets\n/debug\n"
},
{
"path": ".prettierrc",
"chars": 50,
"preview": "{\n \"printWidth\": 120,\n \"trailingComma\": \"all\"\n}\n"
},
{
"path": "AGENTS.md",
"chars": 1968,
"preview": "The project is called Zeppelin. It's a Discord bot that uses Discord.js. The bot is built on the Vety framework (formerl"
},
{
"path": "DEVELOPMENT.md",
"chars": 52,
"preview": "Moved to [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)\n"
},
{
"path": "Dockerfile",
"chars": 1157,
"preview": "FROM node:24 AS build\n\nARG COMMIT_HASH\nARG BUILD_TIME\n\nRUN mkdir /zeppelin\nRUN chown node:node /zeppelin\n\n# Install pnpm"
},
{
"path": "LICENSE.md",
"chars": 3845,
"preview": "# Elastic License 2.0 (ELv2)\n\n## Elastic License\n\n### Acceptance\n\nBy using the software, you agree to all of the terms a"
},
{
"path": "MANAGEMENT.md",
"chars": 50,
"preview": "Moved to [docs/MANAGEMENT.md](docs/MANAGEMENT.md)\n"
},
{
"path": "PRODUCTION.md",
"chars": 50,
"preview": "Moved to [docs/PRODUCTION.md](docs/PRODUCTION.md)\n"
},
{
"path": "README.md",
"chars": 1362,
"preview": "\n# Zeppelin\nZeppelin is a moderation bot for Discord, designed with large server"
},
{
"path": "assets/icons/LICENSE",
"chars": 213,
"preview": "# TWEMOJI\nCopyright 2020 Twitter, Inc and other contributors\nCode licensed under the MIT License: http://opensource.org/"
},
{
"path": "backend/.gitignore",
"chars": 28,
"preview": "/.cache\n/dist\n/node_modules\n"
},
{
"path": "backend/.prettierignore",
"chars": 6,
"preview": "/dist\n"
},
{
"path": "backend/package.json",
"chars": 3534,
"preview": "{\n \"name\": \"@zeppelinbot/backend\",\n \"version\": \"0.0.1\",\n \"description\": \"\",\n \"private\": true,\n \"type\": \"module\",\n "
},
{
"path": "backend/register-tsconfig-paths.js",
"chars": 563,
"preview": "/**\n * See:\n * https://github.com/dividab/tsconfig-paths\n * https://github.com/TypeStrong/ts-node/issues/138\n * https://"
},
{
"path": "backend/src/Blocker.ts",
"chars": 898,
"preview": "export type Block = {\n count: number;\n unblock: () => void;\n getPromise: () => Promise<void>;\n};\n\nexport class Blocke"
},
{
"path": "backend/src/DiscordJSError.ts",
"chars": 420,
"preview": "import util from \"util\";\n\nexport class DiscordJSError extends Error {\n code: number | string | undefined;\n shardId: nu"
},
{
"path": "backend/src/Queue.ts",
"chars": 1564,
"preview": "import { SECONDS } from \"./utils.js\";\n\ntype InternalQueueFn = () => Promise<void>;\ntype AnyFn = (...args: any[]) => any;"
},
{
"path": "backend/src/QueuedEventEmitter.ts",
"chars": 1312,
"preview": "import { Queue } from \"./Queue.js\";\n\ntype Listener = (...args: any[]) => void;\n\nexport class QueuedEventEmitter {\n prot"
},
{
"path": "backend/src/RecoverablePluginError.ts",
"chars": 1452,
"preview": "import { Guild } from \"discord.js\";\n\nexport enum ERRORS {\n NO_MUTE_ROLE_IN_CONFIG = 1,\n UNKNOWN_NOTE_CASE,\n INVALID_E"
},
{
"path": "backend/src/RegExpRunner.ts",
"chars": 4133,
"preview": "import { CooldownManager } from \"vety\";\nimport { EventEmitter } from \"node:events\";\nimport { RegExpWorker, TimeoutError "
},
{
"path": "backend/src/SimpleCache.ts",
"chars": 1464,
"preview": "import Timeout = NodeJS.Timeout;\n\nconst CLEAN_INTERVAL = 1000;\n\nexport class SimpleCache<T = any> {\n protected readonly"
},
{
"path": "backend/src/SimpleError.ts",
"chars": 223,
"preview": "import util from \"util\";\n\nexport class SimpleError extends Error {\n public message: string;\n\n constructor(message: str"
},
{
"path": "backend/src/api/archives.ts",
"chars": 1371,
"preview": "import express, { Request, Response } from \"express\";\nimport moment from \"moment-timezone\";\nimport { GuildArchives } fro"
},
{
"path": "backend/src/api/auth.ts",
"chars": 5170,
"preview": "import express, { Request, Response } from \"express\";\nimport https from \"https\";\nimport { pick } from \"lodash-es\";\nimpor"
},
{
"path": "backend/src/api/docs.ts",
"chars": 4448,
"preview": "import express from \"express\";\nimport { z } from \"zod\";\nimport { $ZodPipeDef } from \"zod/v4/core\";\nimport { availableGui"
},
{
"path": "backend/src/api/guilds/importExport.ts",
"chars": 6280,
"preview": "import { ApiPermissions } from \"@zeppelinbot/shared/apiPermissions.js\";\nimport express, { Request, Response } from \"expr"
},
{
"path": "backend/src/api/guilds/index.ts",
"chars": 453,
"preview": "import express from \"express\";\nimport { apiTokenAuthHandlers } from \"../auth.js\";\nimport { initGuildsImportExportAPI } f"
},
{
"path": "backend/src/api/guilds/misc.ts",
"chars": 6772,
"preview": "import { ApiPermissions } from \"@zeppelinbot/shared/apiPermissions.js\";\nimport express, { Request, Response } from \"expr"
},
{
"path": "backend/src/api/guilds.ts",
"chars": 6910,
"preview": "import { ApiPermissions } from \"@zeppelinbot/shared/apiPermissions.js\";\nimport express, { Request, Response } from \"expr"
},
{
"path": "backend/src/api/index.ts",
"chars": 762,
"preview": "// KEEP THIS AS FIRST IMPORT\n// See comment in module for details\nimport \"../threadsSignalFix.js\";\n\nimport { connect } f"
},
{
"path": "backend/src/api/permissions.ts",
"chars": 1085,
"preview": "import { ApiPermissions, hasPermission, permissionArrToSet } from \"@zeppelinbot/shared/apiPermissions.js\";\nimport { Requ"
},
{
"path": "backend/src/api/rateLimits.ts",
"chars": 556,
"preview": "import { Request, Response } from \"express\";\nimport { error } from \"./responses.js\";\n\nconst lastRequestsByKey: Map<strin"
},
{
"path": "backend/src/api/responses.ts",
"chars": 621,
"preview": "import { Response } from \"express\";\n\nexport function unauthorized(res: Response) {\n res.status(403).json({ error: \"Unau"
},
{
"path": "backend/src/api/staff.ts",
"chars": 479,
"preview": "import express, { Request, Response } from \"express\";\nimport { isStaff } from \"../staff.js\";\nimport { apiTokenAuthHandle"
},
{
"path": "backend/src/api/start.ts",
"chars": 1587,
"preview": "import cors from \"cors\";\nimport express from \"express\";\nimport multer from \"multer\";\nimport { TokenError } from \"passpor"
},
{
"path": "backend/src/api/tasks.ts",
"chars": 355,
"preview": "import { ApiPermissionAssignments } from \"../data/ApiPermissionAssignments.js\";\nimport { MINUTES } from \"../utils.js\";\n\n"
},
{
"path": "backend/src/commandTypes.ts",
"chars": 4331,
"preview": "import {\n escapeCodeBlock,\n escapeInlineCode,\n GuildChannel,\n GuildMember,\n GuildTextBasedChannel,\n Snowflake,\n U"
},
{
"path": "backend/src/configValidator.ts",
"chars": 1881,
"preview": "import { BaseConfig, ConfigValidationError, GuildPluginBlueprint, PluginConfigManager } from \"vety\";\nimport { z, ZodErro"
},
{
"path": "backend/src/data/AllowedGuilds.ts",
"chars": 1693,
"preview": "import moment from \"moment-timezone\";\nimport { Repository } from \"typeorm\";\nimport { DBDateFormat } from \"../utils.js\";\n"
},
{
"path": "backend/src/data/ApiAuditLog.ts",
"chars": 827,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseRepository } from \"./BaseRepository.js\";\nimport { AuditLogEventData, "
},
{
"path": "backend/src/data/ApiLogins.ts",
"chars": 2842,
"preview": "import crypto from \"crypto\";\nimport moment from \"moment-timezone\";\nimport { Repository } from \"typeorm\";\n// tslint:disab"
},
{
"path": "backend/src/data/ApiPermissionAssignments.ts",
"chars": 4349,
"preview": "import { ApiPermissions } from \"@zeppelinbot/shared/apiPermissions.js\";\nimport { Repository } from \"typeorm\";\nimport { A"
},
{
"path": "backend/src/data/ApiUserInfo.ts",
"chars": 1112,
"preview": "import moment from \"moment-timezone\";\nimport { Repository } from \"typeorm\";\nimport { DBDateFormat } from \"../utils.js\";\n"
},
{
"path": "backend/src/data/Archives.ts",
"chars": 592,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseRepository } from \"./BaseRepository.js\";\nimport { dataSource } from \""
},
{
"path": "backend/src/data/BaseGuildRepository.ts",
"chars": 827,
"preview": "import { BaseRepository } from \"./BaseRepository.js\";\n\nexport class BaseGuildRepository<TEntity = unknown> extends BaseR"
},
{
"path": "backend/src/data/BaseRepository.ts",
"chars": 1426,
"preview": "import { asyncMap } from \"../utils/async.js\";\n\nexport class BaseRepository<TEntity = unknown> {\n private nextRelations:"
},
{
"path": "backend/src/data/CaseTypes.ts",
"chars": 543,
"preview": "export enum CaseTypes {\n Ban = 1,\n Unban,\n Note,\n Warn,\n Kick,\n Mute,\n Unmute,\n Deleted,\n Softban,\n}\n\nexport co"
},
{
"path": "backend/src/data/Configs.ts",
"chars": 2117,
"preview": "import { Repository } from \"typeorm\";\nimport { isAPI } from \"../globals.js\";\nimport { HOURS, SECONDS } from \"../utils.js"
},
{
"path": "backend/src/data/DefaultLogMessages.json",
"chars": 8417,
"preview": "{\n \"MEMBER_NOTE\": \"{timestamp} 🖊 Note added on {userMention(user)} by {userMention(mod)}\",\n \"MEMBER_WARN\": \"{timestamp"
},
{
"path": "backend/src/data/FishFish.ts",
"chars": 4260,
"preview": "import { z } from \"zod\";\nimport { env } from \"../env.js\";\nimport { HOURS, MINUTES, SECONDS } from \"../utils.js\";\n\nconst "
},
{
"path": "backend/src/data/GuildAntiraidLevels.ts",
"chars": 1240,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildArchives.ts",
"chars": 5005,
"preview": "import { Guild, Snowflake } from \"discord.js\";\nimport moment from \"moment-timezone\";\nimport { Repository } from \"typeorm"
},
{
"path": "backend/src/data/GuildAutoReactions.ts",
"chars": 1427,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildButtonRoles.ts",
"chars": 1584,
"preview": "import { getRepository, Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimp"
},
{
"path": "backend/src/data/GuildCases.ts",
"chars": 7552,
"preview": "import { FindOptionsWhere, In, InsertResult, Repository } from \"typeorm\";\nimport { Queue } from \"../Queue.js\";\nimport { "
},
{
"path": "backend/src/data/GuildContextMenuLinks.ts",
"chars": 1022,
"preview": "import { DeleteResult, InsertResult, Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildReposi"
},
{
"path": "backend/src/data/GuildCounters.ts",
"chars": 18020,
"preview": "import moment from \"moment-timezone\";\nimport { FindOptionsWhere, In, IsNull, Not, Repository } from \"typeorm\";\nimport { "
},
{
"path": "backend/src/data/GuildEvents.ts",
"chars": 2000,
"preview": "import { Mute } from \"./entities/Mute.js\";\nimport { Reminder } from \"./entities/Reminder.js\";\nimport { ScheduledPost } f"
},
{
"path": "backend/src/data/GuildLogs.ts",
"chars": 1706,
"preview": "import * as events from \"events\";\nimport { LogType } from \"./LogType.js\";\n\n// Use the same instance for the same guild, "
},
{
"path": "backend/src/data/GuildMemberCache.ts",
"chars": 2975,
"preview": "import moment from \"moment-timezone\";\nimport { Repository } from \"typeorm\";\nimport { Blocker } from \"../Blocker.js\";\nimp"
},
{
"path": "backend/src/data/GuildMemberTimezones.ts",
"chars": 1449,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildMutes.ts",
"chars": 4357,
"preview": "import moment from \"moment-timezone\";\nimport { Brackets, Repository } from \"typeorm\";\nimport { DBDateFormat } from \"../u"
},
{
"path": "backend/src/data/GuildNicknameHistory.ts",
"chars": 2105,
"preview": "import { In, Repository } from \"typeorm\";\nimport { isAPI } from \"../globals.js\";\nimport { MINUTES, SECONDS } from \"../ut"
},
{
"path": "backend/src/data/GuildPersistedData.ts",
"chars": 1166,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildPingableRoles.ts",
"chars": 1419,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildReactionRoles.ts",
"chars": 1740,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildReminders.ts",
"chars": 1391,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildRoleButtons.ts",
"chars": 1024,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildRoleQueue.ts",
"chars": 1408,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildSavedMessages.ts",
"chars": 10910,
"preview": "import { GuildChannel, Message } from \"discord.js\";\nimport moment from \"moment-timezone\";\nimport { Repository } from \"ty"
},
{
"path": "backend/src/data/GuildScheduledPosts.ts",
"chars": 1392,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildSlowmodes.ts",
"chars": 3392,
"preview": "import moment from \"moment-timezone\";\nimport { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseG"
},
{
"path": "backend/src/data/GuildStarboardMessages.ts",
"chars": 2027,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildStarboardReactions.ts",
"chars": 1614,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildStats.ts",
"chars": 833,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildTags.ts",
"chars": 2581,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/GuildTempbans.ts",
"chars": 1915,
"preview": "import moment from \"moment-timezone\";\nimport { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseG"
},
{
"path": "backend/src/data/GuildVCAlerts.ts",
"chars": 1814,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseGuildRepository } from \"./BaseGuildRepository.js\";\nimport { dataSourc"
},
{
"path": "backend/src/data/LogType.ts",
"chars": 2783,
"preview": "export const LogType = {\n MEMBER_WARN: \"MEMBER_WARN\",\n MEMBER_MUTE: \"MEMBER_MUTE\",\n MEMBER_UNMUTE: \"MEMBER_UNMUTE\",\n "
},
{
"path": "backend/src/data/MemberCache.ts",
"chars": 962,
"preview": "import moment from \"moment-timezone\";\nimport { Repository } from \"typeorm\";\nimport { DAYS } from \"../utils.js\";\nimport {"
},
{
"path": "backend/src/data/MuteTypes.ts",
"chars": 53,
"preview": "export enum MuteTypes {\n Role = 1,\n Timeout = 2,\n}\n"
},
{
"path": "backend/src/data/Mutes.ts",
"chars": 2059,
"preview": "import moment from \"moment-timezone\";\nimport { Repository } from \"typeorm\";\nimport { DAYS, DBDateFormat } from \"../utils"
},
{
"path": "backend/src/data/Reminders.ts",
"chars": 735,
"preview": "import moment from \"moment-timezone\";\nimport { Repository } from \"typeorm\";\nimport { DBDateFormat } from \"../utils.js\";\n"
},
{
"path": "backend/src/data/ScheduledPosts.ts",
"chars": 777,
"preview": "import moment from \"moment-timezone\";\nimport { Repository } from \"typeorm\";\nimport { DBDateFormat } from \"../utils.js\";\n"
},
{
"path": "backend/src/data/Supporters.ts",
"chars": 436,
"preview": "import { Repository } from \"typeorm\";\nimport { BaseRepository } from \"./BaseRepository.js\";\nimport { dataSource } from \""
},
{
"path": "backend/src/data/Tempbans.ts",
"chars": 722,
"preview": "import moment from \"moment-timezone\";\nimport { Repository } from \"typeorm\";\nimport { DBDateFormat } from \"../utils.js\";\n"
},
{
"path": "backend/src/data/UsernameHistory.ts",
"chars": 1953,
"preview": "import { In, Repository } from \"typeorm\";\nimport { isAPI } from \"../globals.js\";\nimport { MINUTES, SECONDS } from \"../ut"
},
{
"path": "backend/src/data/VCAlerts.ts",
"chars": 732,
"preview": "import moment from \"moment-timezone\";\nimport { Repository } from \"typeorm\";\nimport { DBDateFormat } from \"../utils.js\";\n"
},
{
"path": "backend/src/data/Webhooks.ts",
"chars": 1307,
"preview": "import { Repository } from \"typeorm\";\nimport { decrypt, encrypt } from \"../utils/crypt.js\";\nimport { BaseRepository } fr"
},
{
"path": "backend/src/data/Zalgo.ts",
"chars": 1510,
"preview": "// From https://github.com/b1naryth1ef/rowboat/blob/master/rowboat/util/zalgo.py\nconst zalgoChars = [\n \"\\u030d\",\n \"\\u0"
},
{
"path": "backend/src/data/apiAuditLogTypes.ts",
"chars": 1135,
"preview": "import { ApiPermissionTypes } from \"./ApiPermissionAssignments.js\";\n\nexport const AuditLogEventTypes = {\n ADD_API_PERMI"
},
{
"path": "backend/src/data/buildEntity.ts",
"chars": 228,
"preview": "export function buildEntity<T extends object>(Entity: new () => T, data: Partial<T>): T {\n const instance = new Entity("
},
{
"path": "backend/src/data/cleanup/configs.ts",
"chars": 2212,
"preview": "import moment from \"moment-timezone\";\nimport { In } from \"typeorm\";\nimport { DBDateFormat } from \"../../utils.js\";\nimpor"
},
{
"path": "backend/src/data/cleanup/messages.ts",
"chars": 2472,
"preview": "import moment from \"moment-timezone\";\nimport { In } from \"typeorm\";\nimport { DAYS, DBDateFormat, MINUTES, SECONDS, sleep"
},
{
"path": "backend/src/data/cleanup/nicknames.ts",
"chars": 1068,
"preview": "import moment from \"moment-timezone\";\nimport { In } from \"typeorm\";\nimport { DAYS, DBDateFormat } from \"../../utils.js\";"
},
{
"path": "backend/src/data/cleanup/usernames.ts",
"chars": 1068,
"preview": "import moment from \"moment-timezone\";\nimport { In } from \"typeorm\";\nimport { DAYS, DBDateFormat } from \"../../utils.js\";"
},
{
"path": "backend/src/data/dataSource.ts",
"chars": 1192,
"preview": "import moment from \"moment-timezone\";\nimport path from \"path\";\nimport { DataSource } from \"typeorm\";\nimport { env } from"
},
{
"path": "backend/src/data/db.ts",
"chars": 692,
"preview": "import { SimpleError } from \"../SimpleError.js\";\nimport { dataSource } from \"./dataSource.js\";\n\nlet connectionPromise: P"
},
{
"path": "backend/src/data/entities/AllowedGuild.ts",
"chars": 359,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"allowed_guilds\")\nexport class AllowedGuild {\n @Colum"
},
{
"path": "backend/src/data/entities/AntiraidLevel.ts",
"chars": 197,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"antiraid_levels\")\nexport class AntiraidLevel {\n @Col"
},
{
"path": "backend/src/data/entities/ApiAuditLogEntry.ts",
"chars": 507,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\nimport { AuditLogEventData, AuditLogEventType } from \"../apiAud"
},
{
"path": "backend/src/data/entities/ApiLogin.ts",
"chars": 500,
"preview": "import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn, Relation } from \"typeorm\";\nimport { ApiUserInfo } from \"."
},
{
"path": "backend/src/data/entities/ApiPermissionAssignment.ts",
"chars": 733,
"preview": "import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn, Relation } from \"typeorm\";\nimport { ApiPermissionTypes } "
},
{
"path": "backend/src/data/entities/ApiUserInfo.ts",
"chars": 694,
"preview": "import { Column, Entity, OneToMany, PrimaryColumn, Relation } from \"typeorm\";\nimport { ApiLogin } from \"./ApiLogin.js\";\n"
},
{
"path": "backend/src/data/entities/ArchiveEntry.ts",
"chars": 371,
"preview": "import { Column, Entity, PrimaryGeneratedColumn } from \"typeorm\";\n\n@Entity(\"archives\")\nexport class ArchiveEntry {\n @Co"
},
{
"path": "backend/src/data/entities/AutoReaction.ts",
"chars": 267,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"auto_reactions\")\nexport class AutoReaction {\n @Colum"
},
{
"path": "backend/src/data/entities/ButtonRole.ts",
"chars": 391,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"button_roles\")\nexport class ButtonRole {\n @Column()\n"
},
{
"path": "backend/src/data/entities/Case.ts",
"chars": 1045,
"preview": "import { Column, Entity, OneToMany, PrimaryGeneratedColumn, Relation } from \"typeorm\";\nimport { CaseNote } from \"./CaseN"
},
{
"path": "backend/src/data/entities/CaseNote.ts",
"chars": 485,
"preview": "import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, Relation } from \"typeorm\";\nimport { Case } from "
},
{
"path": "backend/src/data/entities/Config.ts",
"chars": 474,
"preview": "import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from \"typeorm\";\nimport { ApiUserInfo } from \"./ApiUserIn"
},
{
"path": "backend/src/data/entities/ContextMenuLink.ts",
"chars": 230,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"context_menus\")\nexport class ContextMenuLink {\n @Col"
},
{
"path": "backend/src/data/entities/Counter.ts",
"chars": 402,
"preview": "import { Column, Entity, PrimaryGeneratedColumn } from \"typeorm\";\n\n@Entity(\"counters\")\nexport class Counter {\n @Primary"
},
{
"path": "backend/src/data/entities/CounterTrigger.ts",
"chars": 1698,
"preview": "import { Column, Entity, PrimaryGeneratedColumn } from \"typeorm\";\n\nexport const TRIGGER_COMPARISON_OPS = [\"=\", \"!=\", \">\""
},
{
"path": "backend/src/data/entities/CounterTriggerState.ts",
"chars": 354,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"counter_trigger_states\")\nexport class CounterTriggerS"
},
{
"path": "backend/src/data/entities/CounterValue.ts",
"chars": 327,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"counter_values\")\nexport class CounterValue {\n @Colum"
},
{
"path": "backend/src/data/entities/MemberCacheItem.ts",
"chars": 472,
"preview": "import { Column, Entity, PrimaryGeneratedColumn } from \"typeorm\";\n\n@Entity(\"member_cache\")\nexport class MemberCacheItem "
},
{
"path": "backend/src/data/entities/MemberTimezone.ts",
"chars": 253,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"member_timezones\")\nexport class MemberTimezone {\n @C"
},
{
"path": "backend/src/data/entities/Mute.ts",
"chars": 567,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"mutes\")\nexport class Mute {\n @Column()\n @PrimaryCol"
},
{
"path": "backend/src/data/entities/NicknameHistoryEntry.ts",
"chars": 293,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"nickname_history\")\nexport class NicknameHistoryEntry "
},
{
"path": "backend/src/data/entities/PersistedData.ts",
"chars": 349,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"persisted_data\")\nexport class PersistedData {\n @Colu"
},
{
"path": "backend/src/data/entities/PingableRole.ts",
"chars": 253,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"pingable_roles\")\nexport class PingableRole {\n @Colum"
},
{
"path": "backend/src/data/entities/ReactionRole.ts",
"chars": 416,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"reaction_roles\")\nexport class ReactionRole {\n @Colum"
},
{
"path": "backend/src/data/entities/Reminder.ts",
"chars": 342,
"preview": "import { Column, Entity, PrimaryGeneratedColumn } from \"typeorm\";\n\n@Entity(\"reminders\")\nexport class Reminder {\n @Prima"
},
{
"path": "backend/src/data/entities/RoleButtonsItem.ts",
"chars": 315,
"preview": "import { Column, Entity, PrimaryGeneratedColumn } from \"typeorm\";\n\n@Entity(\"role_buttons\")\nexport class RoleButtonsItem "
},
{
"path": "backend/src/data/entities/RoleQueueItem.ts",
"chars": 316,
"preview": "import { Column, Entity, PrimaryGeneratedColumn } from \"typeorm\";\n\n@Entity(\"role_queue\")\nexport class RoleQueueItem {\n "
},
{
"path": "backend/src/data/entities/SavedMessage.ts",
"chars": 2126,
"preview": "import { EmbedType, Snowflake, StickerFormatType, StickerType } from \"discord.js\";\nimport { Column, Entity, PrimaryColum"
},
{
"path": "backend/src/data/entities/ScheduledPost.ts",
"chars": 900,
"preview": "import { Attachment } from \"discord.js\";\nimport { Column, Entity, PrimaryGeneratedColumn } from \"typeorm\";\nimport { Stri"
},
{
"path": "backend/src/data/entities/SlowmodeChannel.ts",
"chars": 264,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"slowmode_channels\")\nexport class SlowmodeChannel {\n "
},
{
"path": "backend/src/data/entities/SlowmodeUser.ts",
"chars": 303,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"slowmode_users\")\nexport class SlowmodeUser {\n @Colum"
},
{
"path": "backend/src/data/entities/StarboardMessage.ts",
"chars": 466,
"preview": "import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from \"typeorm\";\nimport { SavedMessage } from \"./SavedMess"
},
{
"path": "backend/src/data/entities/StarboardReaction.ts",
"chars": 440,
"preview": "import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from \"typeorm\";\nimport { SavedMessage } from \"./SavedMess"
},
{
"path": "backend/src/data/entities/StatValue.ts",
"chars": 298,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"stats\")\nexport class StatValue {\n @Column()\n @Prima"
},
{
"path": "backend/src/data/entities/Supporter.ts",
"chars": 256,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"supporters\")\nexport class Supporter {\n @Column()\n @"
},
{
"path": "backend/src/data/entities/Tag.ts",
"chars": 283,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"tags\")\nexport class Tag {\n @Column()\n @PrimaryColum"
},
{
"path": "backend/src/data/entities/TagResponse.ts",
"chars": 271,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"tag_responses\")\nexport class TagResponse {\n @Column("
},
{
"path": "backend/src/data/entities/Tempban.ts",
"chars": 300,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"tempbans\")\nexport class Tempban {\n @Column()\n @Prim"
},
{
"path": "backend/src/data/entities/UsernameHistoryEntry.ts",
"chars": 262,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"username_history\")\nexport class UsernameHistoryEntry "
},
{
"path": "backend/src/data/entities/VCAlert.ts",
"chars": 374,
"preview": "import { Column, Entity, PrimaryGeneratedColumn } from \"typeorm\";\n\n@Entity(\"vc_alerts\")\nexport class VCAlert {\n @Primar"
},
{
"path": "backend/src/data/entities/Webhook.ts",
"chars": 240,
"preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity(\"webhooks\")\nexport class Webhook {\n @Column()\n @Prim"
},
{
"path": "backend/src/data/getChannelIdFromMessageId.ts",
"chars": 522,
"preview": "import { Repository } from \"typeorm\";\nimport { dataSource } from \"./dataSource.js\";\nimport { SavedMessage } from \"./enti"
},
{
"path": "backend/src/data/loops/expiredArchiveDeletionLoop.ts",
"chars": 490,
"preview": "// tslint:disable:no-console\n\nimport { lazyMemoize, MINUTES } from \"../../utils.js\";\nimport { Archives } from \"../Archiv"
},
{
"path": "backend/src/data/loops/expiredMemberCacheDeletionLoop.ts",
"chars": 517,
"preview": "// tslint:disable:no-console\n\nimport { HOURS, lazyMemoize } from \"../../utils.js\";\nimport { MemberCache } from \"../Membe"
},
{
"path": "backend/src/data/loops/expiringMutesLoop.ts",
"chars": 3891,
"preview": "// tslint:disable:no-console\n\nimport moment from \"moment-timezone\";\nimport { lazyMemoize, MINUTES, SECONDS } from \"../.."
},
{
"path": "backend/src/data/loops/expiringTempbansLoop.ts",
"chars": 2609,
"preview": "// tslint:disable:no-console\n\nimport moment from \"moment-timezone\";\nimport { lazyMemoize, MINUTES } from \"../../utils.js"
},
{
"path": "backend/src/data/loops/expiringVCAlertsLoop.ts",
"chars": 2424,
"preview": "// tslint:disable:no-console\n\nimport moment from \"moment-timezone\";\nimport { lazyMemoize, MINUTES } from \"../../utils.js"
},
{
"path": "backend/src/data/loops/memberCacheDeletionLoop.ts",
"chars": 516,
"preview": "// tslint:disable:no-console\n\nimport { lazyMemoize, MINUTES } from \"../../utils.js\";\nimport { MemberCache } from \"../Mem"
},
{
"path": "backend/src/data/loops/savedMessageCleanupLoop.ts",
"chars": 564,
"preview": "// tslint:disable:no-console\n\nimport { MINUTES } from \"../../utils.js\";\nimport { cleanupMessages } from \"../cleanup/mess"
},
{
"path": "backend/src/data/loops/upcomingRemindersLoop.ts",
"chars": 2308,
"preview": "// tslint:disable:no-console\n\nimport moment from \"moment-timezone\";\nimport { lazyMemoize, MINUTES } from \"../../utils.js"
},
{
"path": "backend/src/data/loops/upcomingScheduledPostsLoop.ts",
"chars": 2432,
"preview": "// tslint:disable:no-console\n\nimport moment from \"moment-timezone\";\nimport { lazyMemoize, MINUTES } from \"../../utils.js"
},
{
"path": "backend/src/data/queryLogger.ts",
"chars": 1216,
"preview": "import { AdvancedConsoleLogger } from \"typeorm\";\n\nlet groupedQueryStats: Map<string, number> = new Map();\n\nconst selectT"
},
{
"path": "backend/src/data/redis.ts",
"chars": 316,
"preview": "import { createClient } from \"redis\";\nimport { env } from \"../env.js\";\n\n// Silly type inference issue... https://github."
},
{
"path": "backend/src/debugCounters.ts",
"chars": 385,
"preview": "type DebugCounterValue = {\n count: number;\n};\nconst debugCounterValueMap = new Map<string, DebugCounterValue>();\n\nexpor"
},
{
"path": "backend/src/env.ts",
"chars": 1752,
"preview": "import dotenv from \"dotenv\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { z } from \"zod\";\nimport { rootDir } f"
},
{
"path": "backend/src/exportSchemas.ts",
"chars": 2653,
"preview": "import fs from \"node:fs\";\nimport { z } from \"zod\";\nimport { availableGuildPlugins } from \"./plugins/availablePlugins.js\""
},
{
"path": "backend/src/globals.ts",
"chars": 142,
"preview": "let isAPIValue = false;\n\nexport function isAPI() {\n return isAPIValue;\n}\n\nexport function setIsAPI(value: boolean) {\n "
},
{
"path": "backend/src/humanizeDuration.ts",
"chars": 798,
"preview": "import humanizeduration from \"humanize-duration\";\n\nexport const delayStringMultipliers = {\n y: 1000 * 60 * 60 * 24 * (3"
},
{
"path": "backend/src/index.ts",
"chars": 16406,
"preview": "// KEEP THIS AS FIRST IMPORT\n// See comment in module for details\nimport \"./threadsSignalFix.js\";\n\nimport {\n Client,\n "
},
{
"path": "backend/src/logger.ts",
"chars": 515,
"preview": "// tslint:disable:no-console\n\nexport const logger = {\n info(...args: Parameters<typeof console.log>) {\n console.log("
},
{
"path": "backend/src/migrateConfigsToDB.ts",
"chars": 1184,
"preview": "// tslint:disable:no-console\nimport * as _fs from \"fs\";\nimport path from \"path\";\nimport { Configs } from \"./data/Configs"
},
{
"path": "backend/src/migrations/1540519249973-CreatePreTypeORMTables.ts",
"chars": 5039,
"preview": "import { MigrationInterface, QueryRunner } from \"typeorm\";\n\nexport class CreatePreTypeORMTables1540519249973 implements "
},
{
"path": "backend/src/migrations/1543053430712-CreateMessagesTable.ts",
"chars": 1809,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateMessagesTable1543053430712 impleme"
},
{
"path": "backend/src/migrations/1544877081073-CreateSlowmodeTables.ts",
"chars": 1711,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateSlowmodeTables1544877081073 implem"
},
{
"path": "backend/src/migrations/1544887946307-CreateStarboardTable.ts",
"chars": 2081,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateStarboardTable1544887946307 implem"
},
{
"path": "backend/src/migrations/1546770935261-CreateTagResponsesTable.ts",
"chars": 1543,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateTagResponsesTable1546770935261 imp"
},
{
"path": "backend/src/migrations/1546778415930-CreateNameHistoryTable.ts",
"chars": 1492,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateNameHistoryTable1546778415930 impl"
},
{
"path": "backend/src/migrations/1546788508314-MakeNameHistoryValueLengthLonger.ts",
"chars": 676,
"preview": "import { MigrationInterface, QueryRunner } from \"typeorm\";\n\nexport class MakeNameHistoryValueLengthLonger1546788508314 i"
},
{
"path": "backend/src/migrations/1547290549908-CreateAutoReactionsTable.ts",
"chars": 836,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateAutoReactionsTable1547290549908 im"
},
{
"path": "backend/src/migrations/1547293464842-CreatePingableRolesTable.ts",
"chars": 1238,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreatePingableRolesTable1547293464842 im"
},
{
"path": "backend/src/migrations/1547392046629-AddIndexToArchivesExpiresAt.ts",
"chars": 549,
"preview": "import { MigrationInterface, QueryRunner, TableIndex } from \"typeorm\";\n\nexport class AddIndexToArchivesExpiresAt15473920"
},
{
"path": "backend/src/migrations/1547393619900-AddIsHiddenToCases.ts",
"chars": 662,
"preview": "import { MigrationInterface, QueryRunner, TableColumn, TableIndex } from \"typeorm\";\n\nexport class AddIsHiddenToCases1547"
},
{
"path": "backend/src/migrations/1549649586803-AddPPFieldsToCases.ts",
"chars": 572,
"preview": "import { MigrationInterface, QueryRunner } from \"typeorm\";\n\nexport class AddPPFieldsToCases1549649586803 implements Migr"
},
{
"path": "backend/src/migrations/1550409894008-FixEmojiIndexInReactionRoles.ts",
"chars": 851,
"preview": "import { MigrationInterface, QueryRunner } from \"typeorm\";\n\nexport class FixEmojiIndexInReactionRoles1550409894008 imple"
},
{
"path": "backend/src/migrations/1550521627877-CreateSelfGrantableRolesTable.ts",
"chars": 1287,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateSelfGrantableRolesTable15505216278"
},
{
"path": "backend/src/migrations/1550609900261-CreateRemindersTable.ts",
"chars": 1267,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateRemindersTable1550609900261 implem"
},
{
"path": "backend/src/migrations/1556908589679-CreateUsernameHistoryTable.ts",
"chars": 1157,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateUsernameHistoryTable1556908589679 "
},
{
"path": "backend/src/migrations/1556909512501-MigrateUsernamesToNewHistoryTable.ts",
"chars": 2735,
"preview": "import { MigrationInterface, QueryRunner } from \"typeorm\";\n\nconst BATCH_SIZE = 200;\n\nexport class MigrateUsernamesToNewH"
},
{
"path": "backend/src/migrations/1556913287547-TurnNameHistoryToNicknameHistory.ts",
"chars": 1397,
"preview": "import { MigrationInterface, QueryRunner, TableColumn } from \"typeorm\";\n\nexport class TurnNameHistoryToNicknameHistory15"
},
{
"path": "backend/src/migrations/1556973844545-CreateScheduledPostsTable.ts",
"chars": 1628,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateScheduledPostsTable1556973844545 i"
},
{
"path": "backend/src/migrations/1558804433320-CreateDashboardLoginsTable.ts",
"chars": 1289,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateDashboardLoginsTable1558804433320 "
},
{
"path": "backend/src/migrations/1558804449510-CreateDashboardUsersTable.ts",
"chars": 1062,
"preview": "import { MigrationInterface, QueryRunner, Table, TableIndex } from \"typeorm\";\n\nexport class CreateDashboardUsersTable155"
},
{
"path": "backend/src/migrations/1561111990357-CreateConfigsTable.ts",
"chars": 1208,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateConfigsTable1561111990357 implemen"
},
{
"path": "backend/src/migrations/1561117545258-CreateAllowedGuildsTable.ts",
"chars": 1004,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateAllowedGuildsTable1561117545258 im"
},
{
"path": "backend/src/migrations/1561282151982-RenameBackendDashboardStuffToAPI.ts",
"chars": 596,
"preview": "import { MigrationInterface, QueryRunner } from \"typeorm\";\n\nexport class RenameBackendDashboardStuffToAPI1561282151982 i"
},
{
"path": "backend/src/migrations/1561282552734-RenameAllowedGuildGuildIdToId.ts",
"chars": 517,
"preview": "import { MigrationInterface, QueryRunner } from \"typeorm\";\n\nexport class RenameAllowedGuildGuildIdToId1561282552734 impl"
},
{
"path": "backend/src/migrations/1561282950483-CreateApiUserInfoTable.ts",
"chars": 768,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class CreateApiUserInfoTable1561282950483 impl"
},
{
"path": "backend/src/migrations/1561283165823-RenameApiUsersToApiPermissions.ts",
"chars": 436,
"preview": "import { MigrationInterface, QueryRunner } from \"typeorm\";\n\nexport class RenameApiUsersToApiPermissions1561283165823 imp"
},
{
"path": "backend/src/migrations/1561283405201-DropUserDataFromLoginsAndPermissions.ts",
"chars": 770,
"preview": "import { MigrationInterface, QueryRunner } from \"typeorm\";\n\nexport class DropUserDataFromLoginsAndPermissions15612834052"
},
{
"path": "backend/src/migrations/1561391921385-AddVCAlertTable.ts",
"chars": 1391,
"preview": "import { MigrationInterface, QueryRunner, Table } from \"typeorm\";\n\nexport class AddVCAlertTable1561391921385 implements "
},
{
"path": "backend/src/migrations/1562838838927-AddMoreIndicesToVCAlerts.ts",
"chars": 834,
"preview": "import { MigrationInterface, QueryRunner, TableIndex } from \"typeorm\";\n\nexport class AddMoreIndicesToVCAlerts15628388389"
}
]
// ... and 916 more files (download for full content)
About this extraction
This page contains the full source code of the Dragory/ZeppelinBot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 1116 files (2.6 MB), approximately 732.8k tokens, and a symbol index with 2379 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.