From d2dd1031751694bbaebb8c7c47f68a8798532e10 Mon Sep 17 00:00:00 2001 From: Dark <7890309+DarkView@users.noreply.github.com> Date: Tue, 6 Jul 2021 05:23:47 +0200 Subject: [PATCH] Start move to configAccessibleObjects, exclude perm overrides from logs configAccessibleObjects are used to guarantee backwards compatibility and consistency. Perm overrides from our own plugins are ignored as to not spam logs through bot managed slowmode or companion channels --- backend/package-lock.json | 18 +-- backend/package.json | 2 +- .../functions/getTextMatchPartialSummary.ts | 2 +- .../matchMultipleTextTypesOnMessage.ts | 2 +- .../CompanionChannelsPlugin.ts | 5 + .../functions/handleCompanionPermissions.ts | 2 + .../src/plugins/CompanionChannels/types.ts | 2 + .../Logs/events/LogsChannelModifyEvts.ts | 21 ++-- .../plugins/Logs/events/LogsGuildBanEvts.ts | 12 +- .../Logs/events/LogsGuildMemberAddEvt.ts | 3 +- .../Logs/events/LogsGuildMemberRemoveEvt.ts | 4 +- .../plugins/Logs/events/LogsRoleModifyEvts.ts | 11 +- .../events/LogsStageInstanceModifyEvts.ts | 23 ++-- .../Logs/events/LogsThreadModifyEvts.ts | 19 +-- .../plugins/Logs/events/LogsUserUpdateEvts.ts | 12 +- .../Logs/events/LogsVoiceChannelEvts.ts | 21 ++-- .../src/plugins/Slowmode/SlowmodePlugin.ts | 1 + backend/src/plugins/Slowmode/types.ts | 1 + .../Slowmode/util/applyBotSlowmodeToUserId.ts | 1 + .../util/clearBotSlowmodeFromUserId.ts | 2 + .../Slowmode/util/clearExpiredSlowmodes.ts | 6 +- .../events/StarboardReactionAddEvt.ts | 6 +- backend/src/plugins/Starboard/types.ts | 1 + backend/src/plugins/Utility/UtilityPlugin.ts | 3 +- .../Utility/events/AutoJoinThreadEvt.ts | 15 +++ .../Utility/functions/getServerInfoEmbed.ts | 2 +- backend/src/utils.ts | 21 ++++ backend/src/utils/configAccessibleObjects.ts | 116 ++++++++++++++++++ 28 files changed, 259 insertions(+), 75 deletions(-) create mode 100644 backend/src/utils/configAccessibleObjects.ts diff --git a/backend/package-lock.json b/backend/package-lock.json index 461ff917..984a6020 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -14,7 +14,7 @@ "cors": "^2.8.5", "cross-env": "^5.2.0", "deep-diff": "^1.0.2", - "discord.js": "^13.0.0-dev.a7c6678.1625427504", + "discord.js": "^13.0.0-dev.2e078e4.1625529824", "dotenv": "^4.0.0", "emoji-regex": "^8.0.0", "erlpack": "github:discord/erlpack", @@ -82,7 +82,7 @@ "license": "MIT", "dependencies": { "discord-api-types": "^0.18.1", - "discord.js": "^13.0.0-dev.a7c6678.1625427504", + "discord.js": "^13.0.0-dev.2e078e4.1625529824", "knub-command-manager": "^9.1.0", "ts-essentials": "^6.0.7" }, @@ -2159,9 +2159,9 @@ } }, "node_modules/discord.js": { - "version": "13.0.0-dev.a7c6678.1625427504", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.0.0-dev.a7c6678.1625427504.tgz", - "integrity": "sha512-S4ntnGVUCRyyOtz8YvoWNpSZyZEFwXTkTjXbU0shveD/QVT+0cBCcMX6vp0zPt3lCkMUgdW+crrOfEYKMGwKoQ==", + "version": "13.0.0-dev.2e078e4.1625529824", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.0.0-dev.2e078e4.1625529824.tgz", + "integrity": "sha512-NKNnoEmh3+4eCG44BOIBjlJS0bKQEH8OgWoEjdJ9K53MYa5lfqbSho4NKXe5+hVpMr+U43wJ8ByUvw96ogqu8w==", "dependencies": { "@discordjs/builders": "^0.2.0", "@discordjs/collection": "^0.1.6", @@ -8034,9 +8034,9 @@ "integrity": "sha512-hNC38R9ZF4uaujaZQtQfm5CdQO58uhdkoHQAVvMfIL0LgOSZeW575W8H6upngQOuoxWd8tiRII3LLJm9zuQKYg==" }, "discord.js": { - "version": "13.0.0-dev.a7c6678.1625427504", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.0.0-dev.a7c6678.1625427504.tgz", - "integrity": "sha512-S4ntnGVUCRyyOtz8YvoWNpSZyZEFwXTkTjXbU0shveD/QVT+0cBCcMX6vp0zPt3lCkMUgdW+crrOfEYKMGwKoQ==", + "version": "13.0.0-dev.2e078e4.1625529824", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.0.0-dev.2e078e4.1625529824.tgz", + "integrity": "sha512-NKNnoEmh3+4eCG44BOIBjlJS0bKQEH8OgWoEjdJ9K53MYa5lfqbSho4NKXe5+hVpMr+U43wJ8ByUvw96ogqu8w==", "requires": { "@discordjs/builders": "^0.2.0", "@discordjs/collection": "^0.1.6", @@ -8878,7 +8878,7 @@ "@typescript-eslint/parser": "^4.23.0", "chai": "^4.3.4", "discord-api-types": "^0.18.1", - "discord.js": "^13.0.0-dev.a7c6678.1625427504", + "discord.js": "^13.0.0-dev.2e078e4.1625529824", "eslint": "^7.2.0", "husky": "^4.3.8", "knub-command-manager": "^9.1.0", diff --git a/backend/package.json b/backend/package.json index 75169098..792d8126 100644 --- a/backend/package.json +++ b/backend/package.json @@ -29,7 +29,7 @@ "cors": "^2.8.5", "cross-env": "^5.2.0", "deep-diff": "^1.0.2", - "discord.js": "^13.0.0-dev.a7c6678.1625427504", + "discord.js": "^13.0.0-dev.2e078e4.1625529824", "dotenv": "^4.0.0", "emoji-regex": "^8.0.0", "erlpack": "github:discord/erlpack", diff --git a/backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts b/backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts index f88f9b69..fe0466e4 100644 --- a/backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts +++ b/backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts @@ -29,6 +29,6 @@ export function getTextMatchPartialSummary( const visibleName = context.member?.nickname || context.user!.username; return `visible name: ${visibleName}`; } else if (type === "customstatus") { - return `custom status: ${context.member!.presence.activities.find(a => a.type === "CUSTOM")?.name}`; + return `custom status: ${context.member!.presence?.activities.find(a => a.type === "CUSTOM")?.name}`; } } diff --git a/backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts b/backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts index 6b2e1038..10188f7e 100644 --- a/backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts +++ b/backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts @@ -52,7 +52,7 @@ export async function* matchMultipleTextTypesOnMessage( yield ["nickname", member.nickname]; } - for (const activity of member.presence.activities) { + for (const activity of member.presence?.activities ?? []) { if (activity.type === Constants.ActivityTypes[4]) { yield ["customstatus", `${activity.emoji} ${activity.name}`]; break; diff --git a/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts b/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts index 64bda1c1..8565c67b 100644 --- a/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts +++ b/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts @@ -1,4 +1,5 @@ import { CooldownManager } from "knub"; +import { GuildLogs } from "../../../data/GuildLogs"; import { trimPluginDescription } from "../../utils"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; @@ -32,4 +33,8 @@ export const CompanionChannelsPlugin = zeppelinGuildPlugin pluginData.guild.roles.cache.get(roleId) || { id: roleId, name: `Unknown (${roleId})` }) .map(r => r.name) .join(", "), - mod: stripObjectToScalars(mod), + mod: mod ? userToConfigAccessibleUser(mod) : {}, }, member.id, ); @@ -82,7 +82,7 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ .map(roleId => pluginData.guild.roles.cache.get(roleId) || { id: roleId, name: `Unknown (${roleId})` }) .map(r => r.name) .join(", "), - mod: stripObjectToScalars(mod), + mod: mod ? userToConfigAccessibleUser(mod) : {}, }, member.id, ); @@ -96,7 +96,7 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ .map(roleId => pluginData.guild.roles.cache.get(roleId) || { id: roleId, name: `Unknown (${roleId})` }) .map(r => r.name) .join(", "), - mod: stripObjectToScalars(mod), + mod: mod ? userToConfigAccessibleUser(mod) : {}, }, member.id, ); diff --git a/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts b/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts index 50d31bfa..ae19eb53 100644 --- a/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts +++ b/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts @@ -1,3 +1,4 @@ +import { channelToConfigAccessibleChannel, memberToConfigAccessibleMember } from "src/utils/configAccessibleObjects"; import { LogType } from "../../../data/LogType"; import { stripObjectToScalars } from "../../../utils"; import { logsEvt } from "../types"; @@ -10,25 +11,23 @@ export const LogsVoiceStateUpdateEvt = logsEvt({ const newChannel = meta.args.newState.channel; const member = meta.args.newState.member ?? meta.args.oldState.member!; - if (!newChannel) { + if (!newChannel && oldChannel) { // Leave evt meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_LEAVE, { - member: stripObjectToScalars(member, ["user", "roles"]), - oldChannel: stripObjectToScalars(oldChannel), - newChannel: stripObjectToScalars(newChannel), + member: memberToConfigAccessibleMember(member), + oldChannel: channelToConfigAccessibleChannel(oldChannel!), }); - } else if (!oldChannel) { + } else if (!oldChannel && newChannel) { // Join Evt meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_JOIN, { - member: stripObjectToScalars(member, ["user", "roles"]), - oldChannel: stripObjectToScalars(oldChannel), - newChannel: stripObjectToScalars(newChannel), + member: memberToConfigAccessibleMember(member), + newChannel: channelToConfigAccessibleChannel(newChannel), }); } else { meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_MOVE, { - member: stripObjectToScalars(member, ["user", "roles"]), - oldChannel: stripObjectToScalars(oldChannel), - newChannel: stripObjectToScalars(newChannel), + member: memberToConfigAccessibleMember(member), + oldChannel: channelToConfigAccessibleChannel(oldChannel!), + newChannel: channelToConfigAccessibleChannel(newChannel!), }); } }, diff --git a/backend/src/plugins/Slowmode/SlowmodePlugin.ts b/backend/src/plugins/Slowmode/SlowmodePlugin.ts index 5dda25c9..f7dd06a3 100644 --- a/backend/src/plugins/Slowmode/SlowmodePlugin.ts +++ b/backend/src/plugins/Slowmode/SlowmodePlugin.ts @@ -66,6 +66,7 @@ export const SlowmodePlugin = zeppelinGuildPlugin()({ afterLoad(pluginData) { const { state } = pluginData; + state.serverLogs = new GuildLogs(pluginData.guild.id); state.clearInterval = setInterval(() => clearExpiredSlowmodes(pluginData), BOT_SLOWMODE_CLEAR_INTERVAL); state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg); diff --git a/backend/src/plugins/Slowmode/types.ts b/backend/src/plugins/Slowmode/types.ts index 3c3824da..7679df34 100644 --- a/backend/src/plugins/Slowmode/types.ts +++ b/backend/src/plugins/Slowmode/types.ts @@ -19,6 +19,7 @@ export interface SlowmodePluginType extends BasePluginType { savedMessages: GuildSavedMessages; logs: GuildLogs; clearInterval: NodeJS.Timeout; + serverLogs: GuildLogs; onMessageCreateFn; }; diff --git a/backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts b/backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts index bcc32d55..2bcb7c9d 100644 --- a/backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts +++ b/backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts @@ -13,6 +13,7 @@ export async function applyBotSlowmodeToUserId( // Deny sendMessage permission from the user. If there are existing permission overwrites, take those into account. const existingOverride = channel.permissionOverwrites.resolve(userId as Snowflake); try { + pluginData.state.serverLogs.ignoreLog(LogType.CHANNEL_UPDATE, channel.id, 5 * 1000); if (existingOverride) { await existingOverride.edit({ SEND_MESSAGES: false }); } else { diff --git a/backend/src/plugins/Slowmode/util/clearBotSlowmodeFromUserId.ts b/backend/src/plugins/Slowmode/util/clearBotSlowmodeFromUserId.ts index 733a2cdd..381fa930 100644 --- a/backend/src/plugins/Slowmode/util/clearBotSlowmodeFromUserId.ts +++ b/backend/src/plugins/Slowmode/util/clearBotSlowmodeFromUserId.ts @@ -1,5 +1,6 @@ import { GuildChannel, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; +import { LogType } from "../../../data/LogType"; import { SlowmodePluginType } from "../types"; export async function clearBotSlowmodeFromUserId( @@ -13,6 +14,7 @@ export async function clearBotSlowmodeFromUserId( // Previously we diffed the overrides so we could clear the "send messages" override without touching other // overrides. Unfortunately, it seems that was a bit buggy - we didn't always receive the event for the changed // overrides and then we also couldn't diff against them. For consistency's sake, we just delete the override now. + pluginData.state.serverLogs.ignoreLog(LogType.CHANNEL_UPDATE, channel.id, 3 * 1000); await channel.permissionOverwrites.resolve(userId as Snowflake)?.delete(); } catch (e) { if (!force) { diff --git a/backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts b/backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts index dd7ced57..6c5fe46d 100644 --- a/backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts +++ b/backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts @@ -1,5 +1,6 @@ import { GuildChannel, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; +import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "src/utils/configAccessibleObjects"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; import { stripObjectToScalars, UnknownUser } from "../../../utils"; @@ -22,10 +23,11 @@ export async function clearExpiredSlowmodes(pluginData: GuildPluginData()({ // prettier-ignore events: [ AutoJoinThreadEvt, + AutoJoinThreadSyncEvt, ], beforeLoad(pluginData) { diff --git a/backend/src/plugins/Utility/events/AutoJoinThreadEvt.ts b/backend/src/plugins/Utility/events/AutoJoinThreadEvt.ts index c7d49d70..6789073c 100644 --- a/backend/src/plugins/Utility/events/AutoJoinThreadEvt.ts +++ b/backend/src/plugins/Utility/events/AutoJoinThreadEvt.ts @@ -10,3 +10,18 @@ export const AutoJoinThreadEvt = utilityEvt({ } }, }); + +export const AutoJoinThreadSyncEvt = utilityEvt({ + event: "threadListSync", + + async listener(meta) { + const config = meta.pluginData.config.get(); + if (config.autojoin_threads) { + for (const thread of meta.args.threads.values()) { + if (!thread.joined && thread.joinable) { + await thread.join(); + } + } + } + }, +}); diff --git a/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts b/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts index f6a00847..2eb04a60 100644 --- a/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts @@ -126,7 +126,7 @@ export async function getServerInfoEmbed( } if (!onlineMemberCount && thisServer) { - onlineMemberCount = thisServer.members.cache.filter(m => m.presence.status !== "offline").size; // Extremely inaccurate fallback + onlineMemberCount = thisServer.members.cache.filter(m => m.presence?.status !== "offline").size; // Extremely inaccurate fallback } const offlineMemberCount = totalMembers - onlineMemberCount; diff --git a/backend/src/utils.ts b/backend/src/utils.ts index f9d9094a..be9c6767 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -36,6 +36,7 @@ import { sendDM } from "./utils/sendDM"; import { waitForButtonConfirm } from "./utils/waitForInteraction"; import { decodeAndValidateStrict, StrictValidationError } from "./validatorUtils"; import { isEqual } from "lodash"; +import humanizeDuration from "humanize-duration"; const fsp = fs.promises; @@ -184,8 +185,28 @@ export function getScalarDifference( return diff; } +// This is a stupid, messy solution that is not extendable at all. +// If anyone plans on adding anything to this, they should rewrite this first. +// I just want to get this done and this works for now :) +export function prettyDifference(diff: Map): Map { + const toReturn = new Map(); + + for (let [key, difference] of diff) { + if (key === "rateLimitPerUser") { + difference.is = humanizeDuration(difference.is * 1000); + difference.was = humanizeDuration(difference.was * 1000); + key = "slowmode"; + } + + toReturn.set(key, { was: difference.was, is: difference.is }); + } + + return toReturn; +} + export function differenceToString(diff: Map): string { let toReturn = ""; + diff = prettyDifference(diff); for (const [key, difference] of diff) { toReturn += `${key[0].toUpperCase() + key.slice(1)}: \`${difference.was}\` ➜ \`${difference.is}\`\n`; } diff --git a/backend/src/utils/configAccessibleObjects.ts b/backend/src/utils/configAccessibleObjects.ts new file mode 100644 index 00000000..92dc17c0 --- /dev/null +++ b/backend/src/utils/configAccessibleObjects.ts @@ -0,0 +1,116 @@ +import { + GuildChannel, + GuildMember, + PartialGuildMember, + Role, + Snowflake, + StageInstance, + ThreadChannel, + User, +} from "discord.js"; + +export interface IConfigAccessibleUser { + id: Snowflake; + username: string; + discriminator: string; + mention: string; + avatarURL: string; + bot: boolean; + createdAt: number; +} + +export interface IConfigAccessibleRole { + id: Snowflake; + name: string; + createdAt: number; + hexColor: string; + hoist: boolean; +} + +export interface IConfigAccessibleMember extends IConfigAccessibleUser { + user: IConfigAccessibleUser; + nick: string; + roles: IConfigAccessibleRole[]; + joinedAt?: number; + // guildAvatarURL: string, Once DJS supports per-server avatars + guildName: string; +} + +export function userToConfigAccessibleUser(user: User): IConfigAccessibleUser { + const toReturn: IConfigAccessibleUser = { + id: user.id, + username: user.username, + discriminator: user.discriminator, + mention: `<@${user.id}>`, + avatarURL: user.displayAvatarURL({ dynamic: true }), + bot: user.bot, + createdAt: user.createdTimestamp, + }; + + return toReturn; +} + +export function roleToConfigAccessibleRole(role: Role): IConfigAccessibleRole { + const toReturn: IConfigAccessibleRole = { + id: role.id, + name: role.name, + createdAt: role.createdTimestamp, + hexColor: role.hexColor, + hoist: role.hoist, + }; + + return toReturn; +} + +export function memberToConfigAccessibleMember(member: GuildMember | PartialGuildMember): IConfigAccessibleMember { + const user = userToConfigAccessibleUser(member.user!); + + const toReturn: IConfigAccessibleMember = { + ...user, + user, + nick: member.nickname ?? "*None*", + roles: member.roles.cache.mapValues(r => roleToConfigAccessibleRole(r)).array(), + joinedAt: member.joinedTimestamp ?? undefined, + guildName: member.guild.name, + }; + + return toReturn; +} + +export interface IConfigAccessibleChannel { + id: Snowflake; + name: string; + mention: string; + parentId?: Snowflake; +} + +export function channelToConfigAccessibleChannel(channel: GuildChannel | ThreadChannel): IConfigAccessibleChannel { + const toReturn: IConfigAccessibleChannel = { + id: channel.id, + name: channel.name, + mention: `<#${channel.id}>`, + parentId: channel.parentId ?? undefined, + }; + + return toReturn; +} + +export interface IConfigAccessibleStage { + channelId: Snowflake; + channelMention: string; + createdAt: number; + discoverable: boolean; + topic: string; +} + +export function stageToConfigAccessibleStage(stage: StageInstance): IConfigAccessibleStage { + const toReturn: IConfigAccessibleStage = { + channelId: stage.channelId, + channelMention: `<#${stage.channelId}>`, + createdAt: stage.createdTimestamp, + discoverable: !stage.discoverableDisabled, + topic: stage.topic, + }; + + return toReturn; +}