From 8015784719e499eee4dec3ee20f200461114f542 Mon Sep 17 00:00:00 2001 From: Eunwoo Choi Date: Thu, 31 Aug 2023 23:19:16 +0900 Subject: [PATCH] feat: use webhook for logging (#625) * feat: use webhook for sending log * fix: use property instead of hashmap * fix: ci fail due to webhook URL validation * chore: new url based on regex * feat: send reports to webhook * chore: env example --- .env.demo.local | 13 +++++++------ pages/api/v1/bots/servers.ts | 4 ++-- pages/api/v2/bots/[id]/index.ts | 4 ++-- pages/api/v2/bots/[id]/report.ts | 4 ++-- pages/api/v2/bots/[id]/stats.ts | 4 ++-- .../bots/submits/[id]/[date]/approve.ts | 4 ++-- .../bots/submits/[id]/[date]/deny.ts | 6 +++--- pages/api/v2/servers/[id]/report.ts | 4 ++-- pages/api/v2/users/[id]/report.ts | 4 ++-- utils/DiscordBot.ts | 19 +++++++++++-------- 10 files changed, 35 insertions(+), 31 deletions(-) diff --git a/.env.demo.local b/.env.demo.local index 89bc27c0ff..1a40c6e9b1 100644 --- a/.env.demo.local +++ b/.env.demo.local @@ -17,11 +17,6 @@ DISCORD_CLIENT_INTENTS=32767 GUILD_ID=653083797763522580 REVIEW_GUILD_ID=906537041326637086 -REPORT_CHANNEL_ID=813255797823766568 -LOGGING_CHANNEL_ID=844006379823955978 -STATS_LOGGING_CHANNEL_ID=653227346962153472 -REVIEW_LOG_CHANNEL_ID=906551334063439902 -OPEN_REVIEW_LOG_CHANNEL_ID=1008376563731013643 GITHUB_CLIENT_ID=GH_CLIENT_ID GITHUB_CLIENT_SECRET=GH_CLIENT_SECRET @@ -31,4 +26,10 @@ CSRF_SECRET=CSRF_SECRET DD_TRACE_DEBUG=true DD_TRACE_ENABLED=true -NEXT_PUBLIC_HCAPTCHA_SITEKEY=HCAPTCHA_SITEKEY \ No newline at end of file +NEXT_PUBLIC_HCAPTCHA_SITEKEY=HCAPTCHA_SITEKEY + +LOG_WEBHOOK_URL= +REVIEW_LOG_WEBHOOK_URL= +OPEN_REVIEW_LOG_WEBHOOK_URL= +STATS_LOG_WEBHOOK_URL= +REPORT_WEBHOOK_URL= diff --git a/pages/api/v1/bots/servers.ts b/pages/api/v1/bots/servers.ts index 0993c7437c..2d98f35255 100644 --- a/pages/api/v1/bots/servers.ts +++ b/pages/api/v1/bots/servers.ts @@ -6,7 +6,7 @@ import { get, update } from '@utils/Query' import RequestHandler from '@utils/RequestHandler' import ResponseWrapper from '@utils/ResponseWrapper' import { BotStatUpdate, BotStatUpdateSchema } from '@utils/Yup' -import { getStatsLoggingChannel } from '@utils/DiscordBot' +import { webhookClients } from '@utils/DiscordBot' import { KoreanbotsEndPoints } from '@utils/Constants' import { makeDiscordCodeblock } from '@utils/Tools' @@ -42,7 +42,7 @@ const BotStats = RequestHandler() const d = await update.updateServer(botInfo.id, validated.servers, undefined) if(d===1 || d===2) return ResponseWrapper(res, { code: 403, message: `서버 수를 ${[null, '1만', '100만'][d]} 이상으로 설정하실 수 없습니다. 문의해주세요.`, version: 1 }) get.bot.clear(bot) - await getStatsLoggingChannel().send({ + await webhookClients.internal.statsLog.send({ content: `[BOT/STATS] <@${botInfo.id}> (${botInfo.id})\n${makeDiscordCodeblock(`${botInfo.servers > validated.servers ? '-' : '+'} ${botInfo.servers} -> ${validated.servers} (${botInfo.servers > validated.servers ? '▼' : '▲'}${Math.abs(validated.servers - botInfo.servers)})`, 'diff')}`, embeds: [new EmbedBuilder().setDescription(`${botInfo.name} - <@${botInfo.id}> ([${botInfo.id}](${KoreanbotsEndPoints.URL.bot(botInfo.id)}`)] }) diff --git a/pages/api/v2/bots/[id]/index.ts b/pages/api/v2/bots/[id]/index.ts index 27ce5b9dc4..03075e2200 100644 --- a/pages/api/v2/bots/[id]/index.ts +++ b/pages/api/v2/bots/[id]/index.ts @@ -10,7 +10,7 @@ import { AddBotSubmit, AddBotSubmitSchema, CsrfCaptcha, ManageBot, ManageBotSche import RequestHandler from '@utils/RequestHandler' import { User } from '@types' import { checkUserFlag, diff, inspect, makeDiscordCodeblock, objectDiff, serialize } from '@utils/Tools' -import { discordLog, getBotReviewLogChannel, getMainGuild } from '@utils/DiscordBot' +import { discordLog, getMainGuild, webhookClients } from '@utils/DiscordBot' import { KoreanbotsEndPoints } from '@utils/Constants' const patchLimiter = rateLimit({ @@ -91,7 +91,7 @@ const Bots = RequestHandler() format: 'js' }) const userinfo = await get.user.load(user) - await getBotReviewLogChannel().send({ + await webhookClients.internal.reviewLog.send({ embeds: [ new EmbedBuilder() .setAuthor({ diff --git a/pages/api/v2/bots/[id]/report.ts b/pages/api/v2/bots/[id]/report.ts index 1c570bc623..08d17ddf22 100644 --- a/pages/api/v2/bots/[id]/report.ts +++ b/pages/api/v2/bots/[id]/report.ts @@ -5,7 +5,7 @@ import { get } from '@utils/Query' import RequestHandler from '@utils/RequestHandler' import ResponseWrapper from '@utils/ResponseWrapper' import { ReportSchema, Report} from '@utils/Yup' -import { getReportChannel } from '@utils/DiscordBot' +import { webhookClients } from '@utils/DiscordBot' import { checkToken } from '@utils/Csrf' const limiter = rateLimit({ @@ -38,7 +38,7 @@ const BotReport = RequestHandler().post(limiter) }) if(!validated) return - await getReportChannel().send({ content: `Reported by <@${user}> (${user})\nReported **${bot.name}** <@${bot.id}> (${bot.id})\nCategory ${req.body.category}\nDesc\n\`\`\`${req.body.description}\`\`\``, allowedMentions: { parse: ['users'] }}) + await webhookClients.internal.reportChannel.send({ threadName: `봇-${bot.id}`, content: `Reported by <@${user}> (${user})\nReported **${bot.name}** <@${bot.id}> (${bot.id})\nCategory ${req.body.category}\nDesc\n\`\`\`${req.body.description}\`\`\``, allowedMentions: { parse: ['users'] }}) return ResponseWrapper(res, { code: 200, message: '성공적으로 처리되었습니다.' }) }) diff --git a/pages/api/v2/bots/[id]/stats.ts b/pages/api/v2/bots/[id]/stats.ts index 85c67b47c0..886ed98ab8 100644 --- a/pages/api/v2/bots/[id]/stats.ts +++ b/pages/api/v2/bots/[id]/stats.ts @@ -6,7 +6,7 @@ import { get, update } from '@utils/Query' import RequestHandler from '@utils/RequestHandler' import ResponseWrapper from '@utils/ResponseWrapper' import { BotStatUpdate, BotStatUpdateSchema } from '@utils/Yup' -import { getStatsLoggingChannel } from '@utils/DiscordBot' +import { webhookClients } from '@utils/DiscordBot' import { checkUserFlag, makeDiscordCodeblock } from '@utils/Tools' import { KoreanbotsEndPoints } from '@utils/Constants' import { User, WebhookType } from '@types' @@ -70,7 +70,7 @@ const BotStats = RequestHandler().post(limiter) timestamp: Date.now() }) } - await getStatsLoggingChannel().send({ + await webhookClients.internal.statsLog.send({ content: `[BOT/STATS] <@${botInfo.id}> (${botInfo.id})\n${makeDiscordCodeblock(`${botInfo.servers > validated.servers ? '-' : '+'} ${botInfo.servers} -> ${validated.servers} (${botInfo.servers > validated.servers ? '▼' : '▲'}${Math.abs(validated.servers - botInfo.servers)})`, 'diff')}`, embeds: [new EmbedBuilder().setDescription(`${botInfo.name} - <@${botInfo.id}> ([${botInfo.id}](${KoreanbotsEndPoints.URL.bot(botInfo.id)}))`)] }) diff --git a/pages/api/v2/management/bots/submits/[id]/[date]/approve.ts b/pages/api/v2/management/bots/submits/[id]/[date]/approve.ts index 125c577fa8..f84d28b1b4 100644 --- a/pages/api/v2/management/bots/submits/[id]/[date]/approve.ts +++ b/pages/api/v2/management/bots/submits/[id]/[date]/approve.ts @@ -5,7 +5,7 @@ import tracer from 'dd-trace' import RequestHandler from '@utils/RequestHandler' import ResponseWrapper from '@utils/ResponseWrapper' import { get, update } from '@utils/Query' -import { DiscordBot, getBotReviewLogChannel } from '@utils/DiscordBot' +import { DiscordBot, webhookClients } from '@utils/DiscordBot' import { KoreanbotsEndPoints } from '@utils/Constants' const ApproveBotSubmit = RequestHandler() @@ -21,7 +21,7 @@ const ApproveBotSubmit = RequestHandler() get.bot.clear(req.query.id) const embed = new EmbedBuilder().setTitle('승인').setColor(Colors.Green).setDescription(`[${submit.id}/${submit.date}](${KoreanbotsEndPoints.URL.submittedBot(submit.id, submit.date)})`).setTimestamp() if(req.body.reviewer) embed.addFields({name: '📃 정보', value: `심사자: ${req.body.reviewer}`}) - await getBotReviewLogChannel().send({embeds: [embed]}) + await webhookClients.internal.reviewLog.send({embeds: [embed]}) tracer.trace('botSubmits.approve', span => { span.setTag('id', submit.id) span.setTag('date', submit.date) diff --git a/pages/api/v2/management/bots/submits/[id]/[date]/deny.ts b/pages/api/v2/management/bots/submits/[id]/[date]/deny.ts index d7c5846b60..c7d24de33b 100644 --- a/pages/api/v2/management/bots/submits/[id]/[date]/deny.ts +++ b/pages/api/v2/management/bots/submits/[id]/[date]/deny.ts @@ -5,7 +5,7 @@ import tracer from 'dd-trace' import RequestHandler from '@utils/RequestHandler' import ResponseWrapper from '@utils/ResponseWrapper' import { get, update } from '@utils/Query' -import { DiscordBot, getBotReviewLogChannel, getOpenBotReviewLogChannel } from '@utils/DiscordBot' +import { DiscordBot, webhookClients } from '@utils/DiscordBot' import { BotSubmissionDenyReasonPresetsName, KoreanbotsEndPoints } from '@utils/Constants' const DenyBotSubmit = RequestHandler() @@ -19,10 +19,10 @@ const DenyBotSubmit = RequestHandler() get.botSubmit.clear(JSON.stringify({ id: req.query.id, date: req.query.date })) const embed = new EmbedBuilder().setTitle('거부').setColor(Colors.Red).setDescription(`[${submit.id}/${submit.date}](${KoreanbotsEndPoints.URL.submittedBot(submit.id, submit.date)})`).setTimestamp() if(req.body.reviewer || req.body.reason) embed.addFields({name: '📃 정보', value: `${req.body.reason ? `사유: ${BotSubmissionDenyReasonPresetsName[req.body.reason] || req.body.reason}\n`: ''}${req.body.reviewer ? `심사자: ${req.body.reviewer}` : ''}`}) - await getBotReviewLogChannel().send({embeds: [embed]}) + await webhookClients.internal.reviewLog.send({embeds: [embed]}) const openEmbed = new EmbedBuilder().setTitle('거부').setColor(Colors.Red).setDescription(`<@${submit.id}> (${submit.id})`).setTimestamp() if(req.body.reason) openEmbed.addFields({name: '📃 사유', value: `${req.body.reason ? `${BotSubmissionDenyReasonPresetsName[req.body.reason] || req.body.reason}\n`: '없음'}`}) - await getOpenBotReviewLogChannel().send({embeds: [openEmbed]}) + await webhookClients.internal.openReviewLog.send({embeds: [openEmbed]}) tracer.trace('botSubmits.deny', span => { span.setTag('id', submit.id) span.setTag('date', submit.date) diff --git a/pages/api/v2/servers/[id]/report.ts b/pages/api/v2/servers/[id]/report.ts index e0aa52464f..27aff1db35 100644 --- a/pages/api/v2/servers/[id]/report.ts +++ b/pages/api/v2/servers/[id]/report.ts @@ -5,7 +5,7 @@ import { get } from '@utils/Query' import RequestHandler from '@utils/RequestHandler' import ResponseWrapper from '@utils/ResponseWrapper' import { ReportSchema, Report} from '@utils/Yup' -import { getReportChannel } from '@utils/DiscordBot' +import { webhookClients } from '@utils/DiscordBot' import { checkToken } from '@utils/Csrf' const limiter = rateLimit({ @@ -38,7 +38,7 @@ const ServerReport = RequestHandler().post(limiter) }) if(!validated) return - await getReportChannel().send({content: `Reported by <@${user}> (${user})\nReported **${server.name}** (${server.id})\nCategory ${req.body.category}\nDesc\n\`\`\`${req.body.description}\`\`\``, allowedMentions: { parse: ['users'] }}) + await webhookClients.internal.reportChannel.send({ threadName: `서버-${server.id}`, content: `Reported by <@${user}> (${user})\nReported **${server.name}** (${server.id})\nCategory ${req.body.category}\nDesc\n\`\`\`${req.body.description}\`\`\``, allowedMentions: { parse: ['users'] }}) return ResponseWrapper(res, { code: 200, message: '성공적으로 처리되었습니다.' }) }) diff --git a/pages/api/v2/users/[id]/report.ts b/pages/api/v2/users/[id]/report.ts index a339c2d5c5..12bd2e369b 100644 --- a/pages/api/v2/users/[id]/report.ts +++ b/pages/api/v2/users/[id]/report.ts @@ -5,7 +5,7 @@ import { get } from '@utils/Query' import RequestHandler from '@utils/RequestHandler' import ResponseWrapper from '@utils/ResponseWrapper' import { ReportSchema, Report} from '@utils/Yup' -import { getReportChannel } from '@utils/DiscordBot' +import { webhookClients } from '@utils/DiscordBot' import { checkToken } from '@utils/Csrf' const limiter = rateLimit({ @@ -37,7 +37,7 @@ const UserReport = RequestHandler().post(limiter) }) if(!validated) return - await getReportChannel().send({ content: `Reported by <@${user}> (${user})\nReported **${userInfo.tag === '0' ? userInfo.globalName + ' @' + userInfo.username : userInfo.username + '#' + userInfo.tag} <@${userInfo.id}> (${userInfo.id})\nCategory ${req.body.category}\nDesc\n\`\`\`${req.body.description}\`\`\``, allowedMentions: { parse: ['users'] }}) + await webhookClients.internal.reportChannel.send({ threadName: `유저-${userInfo.id}`, content: `Reported by <@${user}> (${user})\nReported **${userInfo.tag === '0' ? userInfo.globalName + ' @' + userInfo.username : userInfo.username + '#' + userInfo.tag}** <@${userInfo.id}> (${userInfo.id})\nCategory ${req.body.category}\nDesc\n\`\`\`${req.body.description}\`\`\``, allowedMentions: { parse: ['users'] }}) return ResponseWrapper(res, { code: 200, message: '성공적으로 처리되었습니다.' }) }) diff --git a/utils/DiscordBot.ts b/utils/DiscordBot.ts index 05bb571977..c3dea154ba 100644 --- a/utils/DiscordBot.ts +++ b/utils/DiscordBot.ts @@ -8,9 +8,18 @@ export const ServerListDiscordBot = new Discord.Client({ intents: [] }) +const dummyURL = 'https://discord.com/api/webhooks/123123123123123123/asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf' + export const webhookClients = { bot: new Discord.Collection(), - server: new Discord.Collection() + server: new Discord.Collection(), + internal: { + log: new Discord.WebhookClient({url: process.env.LOG_WEBHOOK_URL ?? dummyURL}), + reviewLog: new Discord.WebhookClient({url: process.env.REVIEW_LOG_WEBHOOK_URL ?? dummyURL}), + openReviewLog: new Discord.WebhookClient({url: process.env.OPEN_REVIEW_LOG_WEBHOOK_URL ?? dummyURL}), + statsLog: new Discord.WebhookClient({url: process.env.STATS_LOG_WEBHOOK_URL ?? dummyURL}), + reportChannel: new Discord.WebhookClient({url: process.env.REPORT_WEBHOOK_URL ?? dummyURL}) + } } DiscordBot.on('ready', async () => { @@ -23,15 +32,9 @@ DiscordBot.login(process.env.DISCORD_TOKEN) ServerListDiscordBot.login(process.env.DISCORD_SERVERLIST_TOKEN) export const getMainGuild = () => DiscordBot.guilds.cache.get(process.env.GUILD_ID) -export const getReviewGuild = () => DiscordBot.guilds.cache.get(process.env.REVIEW_GUILD_ID) -export const getReportChannel = (): Discord.TextChannel => DiscordBot.channels.cache.get(process.env.REPORT_CHANNEL_ID) as Discord.TextChannel -export const getLoggingChannel = (): Discord.TextChannel => DiscordBot.channels.cache.get(process.env.LOGGING_CHANNEL_ID) as Discord.TextChannel -export const getStatsLoggingChannel = (): Discord.TextChannel => DiscordBot.channels.cache.get(process.env.STATS_LOGGING_CHANNEL_ID) as Discord.TextChannel -export const getBotReviewLogChannel = (): Discord.TextChannel => DiscordBot.channels.cache.get(process.env.REVIEW_LOG_CHANNEL_ID) as Discord.TextChannel -export const getOpenBotReviewLogChannel = (): Discord.TextChannel => DiscordBot.channels.cache.get(process.env.OPEN_REVIEW_LOG_CHANNEL_ID) as Discord.TextChannel export const discordLog = async (type: string, issuerID: string, embed?: Discord.EmbedBuilder, attachment?: { content: string, format: string}, content?: string): Promise => { - getLoggingChannel().send({ + webhookClients.internal.log.send({ content: `[${type}] <@${issuerID}> (${issuerID})\n${content || ''}`, embeds: [embed && embed.setTitle(type).setTimestamp(new Date())], ...(attachment && { files: [