diff --git a/packages/skin-database/api/DiscordEventHandler.ts b/packages/skin-database/api/DiscordEventHandler.ts index 20717ee7..059a63b3 100644 --- a/packages/skin-database/api/DiscordEventHandler.ts +++ b/packages/skin-database/api/DiscordEventHandler.ts @@ -4,15 +4,57 @@ import * as Config from "../config"; import SkinModel from "../data/SkinModel"; import * as DiscordUtils from "../discord-bot/utils"; import UserContext from "../data/UserContext"; +import logger from "../logger"; export default class DiscordEventHandler { _clientPromise: Promise; constructor() { + logger.info("DiscordEventHandler: constructing"); const _client = new Discord.Client(); + _client.on("ready", () => { + logger.info("DiscordEventHandler: client ready", { + user: _client.user?.tag, + }); + }); + _client.on("error", (err: any) => { + logger.error("DiscordEventHandler: client error", { + err: err?.message, + stack: err?.stack, + }); + }); + _client.on("disconnect", (event: any) => { + logger.warn("DiscordEventHandler: client disconnect", { event }); + }); + _client.on("reconnecting", () => { + logger.warn("DiscordEventHandler: client reconnecting"); + }); + _client.on("shardError", (err: any) => { + logger.error("DiscordEventHandler: shard error", { + err: err?.message, + stack: err?.stack, + }); + }); + _client.on("messageReactionAdd", (reaction: any, user: any) => { + logger.info("DiscordEventHandler: messageReactionAdd", { + emoji: reaction?.emoji?.name, + msgId: reaction?.message?.id, + channelId: reaction?.message?.channel?.id, + userId: user?.id, + username: user?.username, + userIsBot: user?.bot, + }); + }); this._clientPromise = _client .login(Config.discordToken) - .then(() => _client); + .then(() => _client) + .catch((err) => { + logger.error("DiscordEventHandler: login failed", { + err: err?.message, + stack: err?.stack, + }); + throw err; + }); } async dispose() { @@ -34,6 +76,24 @@ export default class DiscordEventHandler { } async handle(action: ApiAction): Promise { + const actionMd5 = "md5" in action ? action.md5 : undefined; + logger.info("DiscordEventHandler.handle: entry", { + type: action.type, + md5: actionMd5, + }); + try { + await this._handle(action); + } catch (err: any) { + logger.error("DiscordEventHandler.handle: failed", { + type: action.type, + md5: actionMd5, + err: err?.message, + stack: err?.stack, + }); + } + } + + private async _handle(action: ApiAction): Promise { const ctx = new UserContext(); switch (action.type) { case "REVIEW_REQUESTED": @@ -159,19 +219,31 @@ export default class DiscordEventHandler { } private async requestReview(md5: string, ctx: UserContext) { + logger.info("requestReview: entry", { md5 }); const skin = await SkinModel.fromMd5(ctx, md5); if (skin == null) { + logger.warn("requestReview: skin not found", { md5 }); return; } const dest = await this.getChannel(Config.NSFW_SKIN_CHANNEL_ID); const tweetStatus = await skin.getTweetStatus(); + logger.info("requestReview: resolved", { + md5, + tweetStatus, + channelId: Config.NSFW_SKIN_CHANNEL_ID, + }); if (tweetStatus === "UNREVIEWED") { await DiscordUtils.postSkin({ md5, title: (filename) => `Review: ${filename}`, dest, + source: "graphql:request_nsfw_review_for_skin", }); } else { + logger.info("requestReview: skipping post, already reviewed", { + md5, + tweetStatus, + }); // Too much nosie // await DiscordUtils.sendAlreadyReviewed({ md5, dest }); } diff --git a/packages/skin-database/discord-bot/commands/review.ts b/packages/skin-database/discord-bot/commands/review.ts index 5e50a61a..29137a92 100644 --- a/packages/skin-database/discord-bot/commands/review.ts +++ b/packages/skin-database/discord-bot/commands/review.ts @@ -15,6 +15,7 @@ async function reviewSkin(message: Message): Promise { md5, title: (filename) => `Review: ${filename}`, dest: message.channel, + source: "bot:!review", }); } diff --git a/packages/skin-database/discord-bot/utils.ts b/packages/skin-database/discord-bot/utils.ts index 2aa090fb..a8cb5861 100644 --- a/packages/skin-database/discord-bot/utils.ts +++ b/packages/skin-database/discord-bot/utils.ts @@ -29,18 +29,21 @@ export async function postSkin({ md5, title: _title, dest, + source = "unknown", }: { md5: string; title?: (filename: string | null) => string; dest: TextChannel | DMChannel; + source?: string; }) { const ctx = new UserContext(); - console.log("postSkin..."); + const destName = "name" in dest ? dest.name : "DM"; + logger.info("postSkin: entry", { md5, source, dest: destName }); const skin = await SkinModel.fromMd5(ctx, md5); if (skin == null) { console.warn("Could not find skin for md5", { md5, alert: true }); - logger.warn("Could not find skin for md5", { md5, alert: true }); + logger.warn("Could not find skin for md5", { md5, alert: true, source }); return; } const readmeText = await skin.getReadme(); @@ -108,14 +111,62 @@ export async function postSkin({ // @ts-ignore WAT? const msg = await dest.send(embed); + const msgId = Array.isArray(msg) ? msg.map((m) => m.id).join(",") : msg.id; + logger.info("postSkin: sent message", { md5, source, msgId, isArray: Array.isArray(msg) }); if (tweetStatus !== "UNREVIEWED") { + logger.info("postSkin: skipping reactions, not UNREVIEWED", { md5, source, msgId, tweetStatus }); return; } // Don't await - Promise.all([msg.react("👍"), msg.react("👎"), msg.react("🔞")]); + Promise.all([msg.react("👍"), msg.react("👎"), msg.react("🔞")]) + .then(() => logger.info("postSkin: bot reactions added", { md5, source, msgId })) + .catch((err) => + logger.error("postSkin: bot reactions failed", { + md5, + source, + msgId, + err: err?.message, + stack: err?.stack, + }) + ); + + const loggingFilter = async ( + reaction: MessageReaction, + user: User + ): Promise => { + let passes = false; + let err: string | undefined; + try { + passes = await filter(reaction); + } catch (e: any) { + err = e?.message ?? String(e); + } + logger.info("postSkin: reaction observed", { + md5, + source, + msgId, + emoji: reaction.emoji.name, + userId: user?.id, + username: user?.username, + userIsBot: user?.bot, + passes, + filterErr: err, + }); + return passes; + }; + + logger.info("postSkin: awaiting reactions", { md5, source, msgId }); // TODO: Timeout at some point - await msg.awaitReactions(filter, { max: 1 }).then(async (collected) => { + await msg + .awaitReactions(loggingFilter, { max: 1 }) + .then(async (collected) => { + logger.info("postSkin: awaitReactions resolved", { + md5, + source, + msgId, + collectedSize: collected.size, + }); const vote = collected.first(); if (vote == null) { throw new Error("Did not expect vote to be empty");