diff --git a/backend/src/plugins/LocateUser/commands/FollowCmd.ts b/backend/src/plugins/LocateUser/commands/FollowCmd.ts index d0e41a79..5d44a3e6 100644 --- a/backend/src/plugins/LocateUser/commands/FollowCmd.ts +++ b/backend/src/plugins/LocateUser/commands/FollowCmd.ts @@ -13,7 +13,7 @@ export const FollowCmd = locateUserCommand({ signature: { member: ct.resolvedMember(), - reminder: ct.string({ required: false, rest: true }), + reminder: ct.string({ required: false, catchAll: true }), duration: ct.delay({ option: true, shortcut: "d" }), active: ct.bool({ option: true, shortcut: "a" }), diff --git a/backend/src/plugins/UsernameSaver/UsernameSaverPlugin.ts b/backend/src/plugins/UsernameSaver/UsernameSaverPlugin.ts new file mode 100644 index 00000000..592258ea --- /dev/null +++ b/backend/src/plugins/UsernameSaver/UsernameSaverPlugin.ts @@ -0,0 +1,21 @@ +import { zeppelinPlugin } from "../ZeppelinPluginBlueprint"; +import { UsernameHistory } from "src/data/UsernameHistory"; +import { Queue } from "src/Queue"; +import { UsernameSaverPluginType } from "./types"; +import { MessageCreateEvt } from "./events/MessageCreateEvt"; +import { VoiceChannelJoinEvt } from "./events/VoiceChannelJoinEvt"; + +export const UsernameSaverPlugin = zeppelinPlugin()("username_saver", { + // prettier-ignore + events: [ + MessageCreateEvt, + VoiceChannelJoinEvt, + ], + + onLoad(pluginData) { + const { state, guild } = pluginData; + + state.usernameHistory = new UsernameHistory(); + state.updateQueue = new Queue(); + }, +}); diff --git a/backend/src/plugins/UsernameSaver/events/MessageCreateEvt.ts b/backend/src/plugins/UsernameSaver/events/MessageCreateEvt.ts new file mode 100644 index 00000000..14c6aded --- /dev/null +++ b/backend/src/plugins/UsernameSaver/events/MessageCreateEvt.ts @@ -0,0 +1,11 @@ +import { usernameEvent } from "../types"; +import { updateUsername } from "../updateUsername"; + +export const MessageCreateEvt = usernameEvent({ + event: "messageCreate", + + async listener(meta) { + if (meta.args.message.author.bot) return; + meta.pluginData.state.updateQueue.add(() => updateUsername(meta.pluginData, meta.args.message.author)); + }, +}); diff --git a/backend/src/plugins/UsernameSaver/events/VoiceChannelJoinEvt.ts b/backend/src/plugins/UsernameSaver/events/VoiceChannelJoinEvt.ts new file mode 100644 index 00000000..e26ea435 --- /dev/null +++ b/backend/src/plugins/UsernameSaver/events/VoiceChannelJoinEvt.ts @@ -0,0 +1,11 @@ +import { usernameEvent } from "../types"; +import { updateUsername } from "../updateUsername"; + +export const VoiceChannelJoinEvt = usernameEvent({ + event: "voiceChannelJoin", + + async listener(meta) { + if (meta.args.member.bot) return; + meta.pluginData.state.updateQueue.add(() => updateUsername(meta.pluginData, meta.args.member.user)); + }, +}); diff --git a/backend/src/plugins/UsernameSaver/types.ts b/backend/src/plugins/UsernameSaver/types.ts new file mode 100644 index 00000000..eeb078e7 --- /dev/null +++ b/backend/src/plugins/UsernameSaver/types.ts @@ -0,0 +1,12 @@ +import { BasePluginType, eventListener } from "knub"; +import { UsernameHistory } from "src/data/UsernameHistory"; +import { Queue } from "src/Queue"; + +export interface UsernameSaverPluginType extends BasePluginType { + state: { + usernameHistory: UsernameHistory; + updateQueue: Queue; + }; +} + +export const usernameEvent = eventListener(); diff --git a/backend/src/plugins/UsernameSaver/updateUsername.ts b/backend/src/plugins/UsernameSaver/updateUsername.ts new file mode 100644 index 00000000..6b5129a9 --- /dev/null +++ b/backend/src/plugins/UsernameSaver/updateUsername.ts @@ -0,0 +1,12 @@ +import { User } from "eris"; +import { PluginData } from "knub"; +import { UsernameSaverPluginType } from "./types"; + +export async function updateUsername(pluginData: PluginData, user: User) { + if (!user) return; + const newUsername = `${user.username}#${user.discriminator}`; + const latestEntry = await pluginData.state.usernameHistory.getLastEntry(user.id); + if (!latestEntry || newUsername !== latestEntry.username) { + await pluginData.state.usernameHistory.addEntry(user.id, newUsername); + } +} diff --git a/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts b/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts new file mode 100644 index 00000000..b34e5659 --- /dev/null +++ b/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts @@ -0,0 +1,29 @@ +import { zeppelinPlugin } from "../ZeppelinPluginBlueprint"; +import { PluginOptions } from "knub"; +import { WelcomeMessagePluginType, ConfigSchema } from "./types"; +import { GuildLogs } from "src/data/GuildLogs"; +import { GuildMemberAddEvt } from "./events/GuildMemberAddEvt"; + +const defaultOptions: PluginOptions = { + config: { + send_dm: false, + send_to_channel: null, + message: "", + }, +}; + +export const WelcomeMessagePlugin = zeppelinPlugin()("welcome_message", { + configSchema: ConfigSchema, + defaultOptions, + + // prettier-ignore + events: [ + GuildMemberAddEvt, + ], + + onLoad(pluginData) { + const { state, guild } = pluginData; + + state.logs = new GuildLogs(guild.id); + }, +}); diff --git a/backend/src/plugins/WelcomeMessage/events/GuildMemberAddEvt.ts b/backend/src/plugins/WelcomeMessage/events/GuildMemberAddEvt.ts new file mode 100644 index 00000000..8e33080e --- /dev/null +++ b/backend/src/plugins/WelcomeMessage/events/GuildMemberAddEvt.ts @@ -0,0 +1,51 @@ +import { welcomeEvent } from "../types"; +import { renderTemplate } from "src/templateFormatter"; +import { stripObjectToScalars, createChunkedMessage } from "src/utils"; +import { LogType } from "src/data/LogType"; +import { TextChannel } from "eris"; + +export const GuildMemberAddEvt = welcomeEvent({ + event: "guildMemberAdd", + + async listener(meta) { + const pluginData = meta.pluginData; + const member = meta.args.member; + + const config = pluginData.config.get(); + if (!config.message) return; + if (!config.send_dm && !config.send_to_channel) return; + + const formatted = await renderTemplate(config.message, { + member: stripObjectToScalars(member, ["user"]), + }); + + if (config.send_dm) { + const dmChannel = await member.user.getDMChannel(); + if (!dmChannel) return; + + try { + await createChunkedMessage(dmChannel, formatted); + } catch (e) { + pluginData.state.logs.log(LogType.BOT_ALERT, { + body: `Failed send a welcome DM to {userMention(member)}`, + member: stripObjectToScalars(member), + }); + } + } + + if (config.send_to_channel) { + const channel = meta.args.guild.channels.get(config.send_to_channel); + if (!channel || !(channel instanceof TextChannel)) return; + + try { + await createChunkedMessage(channel, formatted); + } catch (e) { + pluginData.state.logs.log(LogType.BOT_ALERT, { + body: `Failed send a welcome message for {userMention(member)} to {channelMention(channel)}`, + member: stripObjectToScalars(member), + channel: stripObjectToScalars(channel), + }); + } + } + }, +}); diff --git a/backend/src/plugins/WelcomeMessage/types.ts b/backend/src/plugins/WelcomeMessage/types.ts new file mode 100644 index 00000000..eef61be9 --- /dev/null +++ b/backend/src/plugins/WelcomeMessage/types.ts @@ -0,0 +1,20 @@ +import * as t from "io-ts"; +import { BasePluginType, eventListener } from "knub"; +import { tNullable } from "src/utils"; +import { GuildLogs } from "src/data/GuildLogs"; + +export const ConfigSchema = t.type({ + send_dm: t.boolean, + send_to_channel: tNullable(t.string), + message: t.string, +}); +export type TConfigSchema = t.TypeOf; + +export interface WelcomeMessagePluginType extends BasePluginType { + config: TConfigSchema; + state: { + logs: GuildLogs; + }; +} + +export const welcomeEvent = eventListener(); diff --git a/backend/src/plugins/availablePlugins.ts b/backend/src/plugins/availablePlugins.ts index 16d5849c..14376e34 100644 --- a/backend/src/plugins/availablePlugins.ts +++ b/backend/src/plugins/availablePlugins.ts @@ -2,12 +2,16 @@ import { UtilityPlugin } from "./Utility/UtilityPlugin"; import { LocateUserPlugin } from "./LocateUser/LocateUserPlugin"; import { ZeppelinPluginBlueprint } from "./ZeppelinPluginBlueprint"; import { RemindersPlugin } from "./Reminders/RemindersPlugin"; +import { UsernameSaverPlugin } from "./UsernameSaver/UsernameSaverPlugin"; +import { WelcomeMessagePlugin } from "./WelcomeMessage/WelcomeMessagePlugin"; // prettier-ignore export const guildPlugins: Array> = [ LocateUserPlugin, RemindersPlugin, + UsernameSaverPlugin, UtilityPlugin, + WelcomeMessagePlugin, ]; export const globalPlugins = [];