diff --git a/src/features/stats.ts b/src/features/stats.ts index 2bbe9400..19523af9 100644 --- a/src/features/stats.ts +++ b/src/features/stats.ts @@ -1,7 +1,9 @@ import fetch from "node-fetch"; import queryString from "query-string"; -import { Client } from "discord.js"; +import { ChannelType, Client } from "discord.js"; import { amplitudeKey } from "../helpers/env"; +import { difference } from "../helpers/sets"; +import { mapAppliedTagsToTagNames } from "../helpers/discord"; type AmplitudeValue = string | number | boolean; type EmitEventData = Record; @@ -32,6 +34,7 @@ const emitEvent = ( }; const message = "message sent"; +const postTagged = "post tagged"; const threadCreated = "thread created"; const threadReplyRemoved = "thread reply removed"; const threadTimeout = "thread timeout"; @@ -39,6 +42,8 @@ const threadResolved = "thread resolved"; const reacted = "reaction added"; export const threadStats = { + postTagged: (tags: string[], channel: string) => + emitEvent(postTagged, { data: { tags, channel } }), threadCreated: (channel: string) => emitEvent(threadCreated, { data: { channel } }), threadReplyRemoved: (channel: string) => @@ -57,6 +62,37 @@ export const threadStats = { }; const stats = (client: Client) => { + client.on("threadUpdate", async (oldThread, newThread) => { + if (oldThread.parent?.type !== ChannelType.GuildForum) { + return; + } + const appliedTags = [ + ...difference( + new Set(newThread.appliedTags), + new Set(oldThread.appliedTags), + ), + ]; + + if (appliedTags.length === 0) { + return; + } + + threadStats.postTagged( + mapAppliedTagsToTagNames(appliedTags, oldThread.parent), + newThread.parentId ?? "0", + ); + }); + client.on("threadCreate", async (thread) => { + if ( + thread.parent?.type === ChannelType.GuildForum && + thread.appliedTags.length > 0 + ) { + threadStats.postTagged( + mapAppliedTagsToTagNames(thread.appliedTags, thread.parent), + thread.parentId ?? "0", + ); + } + }); client.on("messageReactionAdd", async (reaction, user) => { const { message, emoji } = reaction; const { channel } = message; @@ -72,6 +108,7 @@ const stats = (client: Client) => { data: { channel: channelId, emoji: emoji.toString() ?? "n/a", + target: message.author?.id ?? "0", }, userId: user.id, }); diff --git a/src/helpers/discord.ts b/src/helpers/discord.ts index 7303f6ca..b08e455e 100644 --- a/src/helpers/discord.ts +++ b/src/helpers/discord.ts @@ -13,6 +13,7 @@ import { SlashCommandBuilder, APIApplicationCommand, APIApplicationCommandOption, + ForumChannel, } from "discord.js"; import prettyBytes from "pretty-bytes"; @@ -113,6 +114,16 @@ export const quoteAndEscape = (content: string) => { return escapeDisruptiveContent(quoteMessageContent(content)); }; +export const mapAppliedTagsToTagNames = ( + appliedTags: string[], + channel: ForumChannel, +) => { + const { availableTags: tags } = channel; + const tagLookup = new Map(tags.map((e) => [e.id, e])); + + return appliedTags.map((id) => tagLookup.get(id)?.name ?? ""); +}; + // // Types and type helpers for command configs // diff --git a/src/helpers/sets.ts b/src/helpers/sets.ts index a58d4092..e98ac20b 100644 --- a/src/helpers/sets.ts +++ b/src/helpers/sets.ts @@ -1,2 +1,5 @@ +/** + * Difference returns a list of items that are in A but is not present in B. + */ export const difference = (a: Set, b: Set) => new Set(Array.from(a).filter((x) => !b.has(x)));