diff --git a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts new file mode 100644 index 00000000..2e2f7e2e --- /dev/null +++ b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts @@ -0,0 +1,47 @@ +import { zeppelinPlugin } from "../ZeppelinPluginBlueprint"; +import { ConfigSchema, MessageSaverPluginType } from "./types"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { PluginOptions } from "knub"; +import { MessageCreateEvt, MessageUpdateEvt, MessageDeleteEvt, MessageDeleteBulkEvt } from "./events/SaveMessagesEvts"; +import { SaveMessagesToDBCmd } from "./commands/SaveMessagesToDB"; +import { SavePinsToDBCmd } from "./commands/SavePinsToDB"; + +const defaultOptions: PluginOptions = { + config: { + can_manage: false, + }, + overrides: [ + { + level: ">=100", + config: { + can_manage: true, + }, + }, + ], +}; + +export const MessageSaverPlugin = zeppelinPlugin()("message_saver", { + configSchema: ConfigSchema, + defaultOptions, + + showInDocs: false, + + // prettier-ignore + commands: [ + SaveMessagesToDBCmd, + SavePinsToDBCmd, + ], + + // prettier-ignore + events: [ + MessageCreateEvt, + MessageUpdateEvt, + MessageDeleteEvt, + MessageDeleteBulkEvt, + ], + + onLoad(pluginData) { + const { state, guild } = pluginData; + state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id); + }, +}); diff --git a/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts b/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts new file mode 100644 index 00000000..431acb13 --- /dev/null +++ b/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts @@ -0,0 +1,31 @@ +import { messageSaverCmd } from "../types"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { saveMessagesToDB } from "../saveMessagesToDB"; +import { TextChannel } from "eris"; +import { sendSuccessMessage } from "src/pluginUtils"; + +export const SaveMessagesToDBCmd = messageSaverCmd({ + trigger: "save_messages_to_db", + permission: "can_manage", + source: "guild", + + signature: { + channel: ct.textChannel(), + ids: ct.string({ catchAll: true }), + }, + + async run({ message: msg, args, pluginData }) { + await msg.channel.createMessage("Saving specified messages..."); + const { savedCount, failed } = await saveMessagesToDB(pluginData, args.channel, args.ids.trim().split(" ")); + + if (failed.length) { + sendSuccessMessage( + pluginData, + msg.channel, + `Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`, + ); + } else { + sendSuccessMessage(pluginData, msg.channel, `Saved ${savedCount} messages!`); + } + }, +}); diff --git a/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts b/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts new file mode 100644 index 00000000..ee462244 --- /dev/null +++ b/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts @@ -0,0 +1,35 @@ +import { messageSaverCmd } from "../types"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { saveMessagesToDB } from "../saveMessagesToDB"; +import { sendSuccessMessage } from "src/pluginUtils"; + +export const SavePinsToDBCmd = messageSaverCmd({ + trigger: "save_pins_to_db", + permission: "can_manage", + source: "guild", + + signature: { + channel: ct.textChannel(), + }, + + async run({ message: msg, args, pluginData }) { + await msg.channel.createMessage(`Saving pins from <#${args.channel.id}>...`); + + const pins = await args.channel.getPins(); + const { savedCount, failed } = await saveMessagesToDB( + pluginData, + args.channel, + pins.map(m => m.id), + ); + + if (failed.length) { + sendSuccessMessage( + pluginData, + msg.channel, + `Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`, + ); + } else { + sendSuccessMessage(pluginData, msg.channel, `Saved ${savedCount} messages!`); + } + }, +}); diff --git a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts new file mode 100644 index 00000000..5aaafe5e --- /dev/null +++ b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts @@ -0,0 +1,53 @@ +import { messageSaverEvt } from "../types"; +import { Message } from "eris"; + +export const MessageCreateEvt = messageSaverEvt({ + event: "messageCreate", + allowOutsideOfGuild: false, + + async listener(meta) { + // Only save regular chat messages + if (meta.args.message.type !== 0) { + return; + } + + await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message); + }, +}); + +export const MessageUpdateEvt = messageSaverEvt({ + event: "messageUpdate", + allowOutsideOfGuild: false, + + async listener(meta) { + if (meta.args.message.type !== 0) { + return; + } + + await meta.pluginData.state.savedMessages.saveEditFromMsg(meta.args.message); + }, +}); + +export const MessageDeleteEvt = messageSaverEvt({ + event: "messageDelete", + allowOutsideOfGuild: false, + + async listener(meta) { + const msg = meta.args.message as Message; + if (msg.type != null && msg.type !== 0) { + return; + } + + await meta.pluginData.state.savedMessages.markAsDeleted(msg.id); + }, +}); + +export const MessageDeleteBulkEvt = messageSaverEvt({ + event: "messageDeleteBulk", + allowOutsideOfGuild: false, + + async listener(meta) { + const ids = meta.args.messages.map(m => m.id); + await meta.pluginData.state.savedMessages.markBulkAsDeleted(ids); + }, +}); diff --git a/backend/src/plugins/MessageSaver/saveMessagesToDB.ts b/backend/src/plugins/MessageSaver/saveMessagesToDB.ts new file mode 100644 index 00000000..7dd6e575 --- /dev/null +++ b/backend/src/plugins/MessageSaver/saveMessagesToDB.ts @@ -0,0 +1,35 @@ +import { MessageSaverPluginType } from "./types"; +import { PluginData } from "knub"; +import { TextChannel, Message } from "eris"; + +export async function saveMessagesToDB( + pluginData: PluginData, + channel: TextChannel, + ids: string[], +) { + const failed = []; + for (const id of ids) { + const savedMessage = await pluginData.state.savedMessages.find(id); + if (savedMessage) continue; + + let thisMsg: Message; + + try { + thisMsg = await channel.getMessage(id); + + if (!thisMsg) { + failed.push(id); + continue; + } + + await pluginData.state.savedMessages.createFromMsg(thisMsg, { is_permanent: true }); + } catch (e) { + failed.push(id); + } + } + + return { + savedCount: ids.length - failed.length, + failed, + }; +} diff --git a/backend/src/plugins/MessageSaver/types.ts b/backend/src/plugins/MessageSaver/types.ts new file mode 100644 index 00000000..1eb6909a --- /dev/null +++ b/backend/src/plugins/MessageSaver/types.ts @@ -0,0 +1,18 @@ +import * as t from "io-ts"; +import { BasePluginType, command, eventListener } from "knub"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; + +export const ConfigSchema = t.type({ + can_manage: t.boolean, +}); +export type TConfigSchema = t.TypeOf; + +export interface MessageSaverPluginType extends BasePluginType { + config: TConfigSchema; + state: { + savedMessages: GuildSavedMessages; + }; +} + +export const messageSaverCmd = command(); +export const messageSaverEvt = eventListener(); diff --git a/backend/src/plugins/availablePlugins.ts b/backend/src/plugins/availablePlugins.ts index 14794d80..08a2ee8c 100644 --- a/backend/src/plugins/availablePlugins.ts +++ b/backend/src/plugins/availablePlugins.ts @@ -1,6 +1,7 @@ import { UtilityPlugin } from "./Utility/UtilityPlugin"; import { LocateUserPlugin } from "./LocateUser/LocateUserPlugin"; import { ZeppelinPluginBlueprint } from "./ZeppelinPluginBlueprint"; +import { MessageSaverPlugin } from "./MessageSaver/MessageSaverPlugin"; import { AutoReactionsPlugin } from "./AutoReactions/AutoReactionsPlugin"; import { RemindersPlugin } from "./Reminders/RemindersPlugin"; import { UsernameSaverPlugin } from "./UsernameSaver/UsernameSaverPlugin"; @@ -10,6 +11,7 @@ import { WelcomeMessagePlugin } from "./WelcomeMessage/WelcomeMessagePlugin"; export const guildPlugins: Array> = [ AutoReactionsPlugin, LocateUserPlugin, + MessageSaverPlugin, RemindersPlugin, UsernameSaverPlugin, UtilityPlugin,