feat: added logging

This commit is contained in:
wonderlandpark 2021-05-20 09:52:34 +09:00
parent e22ed164e2
commit acd53e7c21
13 changed files with 128 additions and 13 deletions

View File

@ -4,6 +4,12 @@ const withPWA = require('next-pwa')
const VERSION = require('./package.json').version
const NextConfig = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback.fs = false
}
return config
},
pwa: {
disable: process.env.NODE_ENV !== 'production',
register: false

View File

@ -28,6 +28,7 @@
"csrf": "3.1.0",
"dataloader": "2.0.0",
"dayjs": "1.10.4",
"difflib": "0.2.4",
"discord.js": "12.5.3",
"emoji-mart": "3.0.1",
"erlpack": "0.1.3",

View File

@ -1,5 +1,6 @@
import { NextApiRequest } from 'next'
import rateLimit from 'express-rate-limit'
import { MessageEmbed } from 'discord.js'
import { CaptchaVerify, get, put, remove, update } from '@utils/Query'
import ResponseWrapper from '@utils/ResponseWrapper'
@ -7,7 +8,9 @@ import { checkToken } from '@utils/Csrf'
import { AddBotSubmit, AddBotSubmitSchema, CsrfCaptcha, ManageBot, ManageBotSchema } from '@utils/Yup'
import RequestHandler from '@utils/RequestHandler'
import { User } from '@types'
import { checkUserFlag } from '@utils/Tools'
import { checkUserFlag, diff, inspect, makeDiscordCodeblock, objectDiff, serialize } from '@utils/Tools'
import { discordLog, getBotReviewLogChannel } from '@utils/DiscordBot'
import { KoreanbotsEndPoints } from '@utils/Constants'
const patchLimiter = rateLimit({
windowMs: 2 * 60 * 1000,
@ -73,6 +76,12 @@ const Bots = RequestHandler()
errors: ['봇 신청하시기 위해서는 공식 디스코드 서버에 참가해주셔야합니다.'],
})
get.botSubmits.clear(user)
await discordLog('BOT/SUBMIT', user, new MessageEmbed().setDescription(`[${result.id}/${result.date}](${KoreanbotsEndPoints.URL.submittedBot(result.id, result.date)})`), {
content: inspect(serialize(result)),
format: 'js'
})
await getBotReviewLogChannel().send(new MessageEmbed().setTitle('심사 대기 중').setColor('GREY').setDescription(`[${result.id}/${result.date}](${KoreanbotsEndPoints.URL.submittedBot(result.id, result.date)})`).setFooter(Date.now()))
return ResponseWrapper(res, { code: 200, data: result })
})
.delete(async (req: DeleteApiRequest, res) => {
@ -90,8 +99,13 @@ const Bots = RequestHandler()
if(req.body.name !== bot.name) return ResponseWrapper(res, { code: 400, message: '봇 이름을 입력해주세요.' })
remove.bot(bot.id)
get.user.clear(user)
await discordLog('BOT/DELETE', user, (new MessageEmbed().setDescription(`${bot.name} - <@${bot.id}> ([${bot.id}](${KoreanbotsEndPoints.URL.bot(bot.id)}))`)),
{
content: inspect(bot),
format: 'js'
}
)
return ResponseWrapper(res, { code: 200, message: '성공적으로 삭제했습니다.' })
})
.patch(patchLimiter).patch(async (req: PatchApiRequest, res) => {
const bot = await get.bot.load(req.query.id)
@ -112,11 +126,24 @@ const Bots = RequestHandler()
})
if (!validated) return
console.log(validated)
const result = await update.bot(req.query.id, validated)
if(result === 0) return ResponseWrapper(res, { code: 400 })
else {
get.bot.clear(req.query.id)
const embed = new MessageEmbed().setDescription(`${bot.name} - <@${bot.id}> ([${bot.id}](${KoreanbotsEndPoints.URL.bot(bot.id)}))`)
const diffData = objectDiff(
{ prefix: bot.prefix, library: bot.lib, web: bot.web, git: bot.git, url: bot.url, discord: bot.discord, intro: bot.intro, category: JSON.stringify(bot.category) },
{ prefix: validated.prefix, library: validated.library, web: validated.website, git: validated.git, url: validated.url, discord: validated.discord, intro: validated.intro, category: JSON.stringify(validated.category) }
)
diffData.map(d => {
embed.addField(d[0], makeDiscordCodeblock(diff(d[1][0] || '', d[1][1] || ''), 'diff'))
})
await discordLog('BOT/EDIT', user, embed,
{
content: `--- 설명\n${diff(bot.desc, validated.desc, true)}`,
format: 'diff'
}
)
return ResponseWrapper(res, { code: 200 })
}

View File

@ -4,9 +4,12 @@ import RequestHandler from '@utils/RequestHandler'
import { CaptchaVerify, get, update } from '@utils/Query'
import ResponseWrapper from '@utils/ResponseWrapper'
import { checkToken } from '@utils/Csrf'
import { checkUserFlag } from '@utils/Tools'
import { checkUserFlag, diff, makeDiscordCodeblock } from '@utils/Tools'
import { EditBotOwner, EditBotOwnerSchema } from '@utils/Yup'
import { User } from '@types'
import { discordLog } from '@utils/DiscordBot'
import { MessageEmbed } from 'discord.js'
import { KoreanbotsEndPoints } from '@utils/Constants'
const BotOwners = RequestHandler()
.patch(async (req: PostApiRequest, res) => {
@ -33,6 +36,7 @@ const BotOwners = RequestHandler()
if(userFetched.length > 1 && userFetched[0].id !== (bot.owners as User[])[0].id) return ResponseWrapper(res, { code: 400, errors: ['소유자를 이전할 때는 다른 관리자를 포함할 수 없습니다.'] })
await update.botOwners(bot.id, validated.owners)
get.user.clear(user)
await discordLog('BOT/OWNERS', userinfo.id, (new MessageEmbed().setDescription(`${bot.name} - <@${bot.id}> ([${bot.id}](${KoreanbotsEndPoints.URL.bot(bot.id)}))`)), null, makeDiscordCodeblock(diff(JSON.stringify(bot.owners.map(el => el.id)), JSON.stringify(validated.owners)), 'diff'))
return ResponseWrapper(res, { code: 200 })
})

View File

@ -12,6 +12,7 @@ const limiter = rateLimit({
windowMs: 5 * 60 * 1000,
max: 3,
statusCode: 429,
skipFailedRequests: true,
handler: (_req, res) => ResponseWrapper(res, { code: 429 }),
keyGenerator: (req) => req.headers['x-forwarded-for'] as string,
skip: (_req, res) => {

View File

@ -1,15 +1,20 @@
import { NextApiRequest } from 'next'
import rateLimit from 'express-rate-limit'
import { MessageEmbed } from 'discord.js'
import { get, update } from '@utils/Query'
import RequestHandler from '@utils/RequestHandler'
import ResponseWrapper from '@utils/ResponseWrapper'
import { BotStatUpdate, BotStatUpdateSchema } from '@utils/Yup'
import { discordLog } from '@utils/DiscordBot'
import { makeDiscordCodeblock } from '@utils/Tools'
import { KoreanbotsEndPoints } from '@utils/Constants'
const limiter = rateLimit({
windowMs: 3 * 60 * 1000,
max: 3,
statusCode: 429,
skipFailedRequests: true,
handler: (_req, res) => ResponseWrapper(res, { code: 429 }),
keyGenerator: (req) => req.headers.authorization,
skip: (req, res) => {
@ -37,6 +42,8 @@ const BotStats = RequestHandler().post(limiter)
if(botInfo.id !== bot) return ResponseWrapper(res, { code: 403 })
const d = await update.updateServer(botInfo.id, validated.servers)
if(d===1 || d===2) return ResponseWrapper(res, { code: 403, message: `서버 수를 ${[null, '1만', '100만'][d]} 이상으로 설정하실 수 없습니다. 문의해주세요.` })
get.bot.clear(req.query.id)
await discordLog('BOT/STATS', botInfo.id, (new MessageEmbed().setDescription(`${botInfo.name} - <@${botInfo.id}> ([${botInfo.id}](${KoreanbotsEndPoints.URL.bot(botInfo.id)}))`)), null, makeDiscordCodeblock(`${botInfo.servers > validated.servers ? '-' : '+'} ${botInfo.servers} -> ${validated.servers} (${botInfo.servers > validated.servers ? '▼' : '▲'}${Math.abs(validated.servers - botInfo.servers)})`, 'diff'))
return ResponseWrapper(res, { code: 200, message: '성공적으로 업데이트 했습니다.'})
})

5
types/global.d.ts vendored
View File

@ -17,3 +17,8 @@ declare module 'yup' {
unique(format?: string): this
}
}
declare module 'difflib' {
export function unifiedDiff(before: string, after: string): string[]
}

View File

@ -133,7 +133,7 @@ export const reportCats = [
export const imageSafeHost = [
'koreanbots.dev',
'githubusercontent.com',
'cdn.discordapp.com'
'cdn.discordapp.com'
]
export const MessageColor = {
@ -188,8 +188,14 @@ export const DiscordEnpoints = {
}
export const KoreanbotsEndPoints = {
CDN: class CDN {
static avatar (id: string, options: KoreanbotsImageOptions) { return makeImageURL(`/api/image/discord/avatars/${id}`, options) }
CDN: class {
static root = '/api/image'
static avatar (id: string, options: KoreanbotsImageOptions) { return makeImageURL(`${this.root}/discord/avatars/${id}`, options) }
},
URL: class {
static root = process.env.KOREANBOTS_URL || 'https://koreanbots.dev'
static bot (id: string) { return `${this.root}/bots/${id}` }
static submittedBot(id: string, date: number) { return `${this.root}/pendingBots/${id}/${date}` }
},
baseAPI: '/api/v2',
login: '/api/auth/discord',

View File

@ -1,9 +1,12 @@
import * as Discord from 'discord.js'
const DiscordBot = new Discord.Client()
export const DiscordBot = new Discord.Client()
const guildID = '653083797763522580'
const reportChannelID = '813255797823766568'
const loggingChannelID = '844006379823955978'
const botReviewLogChannelID = '844044961975500840'
DiscordBot.on('ready', async () => {
console.log('I\'m Ready')
@ -13,6 +16,17 @@ DiscordBot.on('ready', async () => {
DiscordBot.login(process.env.DISCORD_TOKEN)
const getMainGuild = () => DiscordBot.guilds.cache.get(guildID)
const getReportChannel = (): Discord.TextChannel => DiscordBot.channels.cache.get(reportChannelID) as Discord.TextChannel
export { DiscordBot, getMainGuild, getReportChannel }
export const getMainGuild = () => DiscordBot.guilds.cache.get(guildID)
export const getReportChannel = (): Discord.TextChannel => getMainGuild().channels.cache.get(reportChannelID) as Discord.TextChannel
export const getLoggingChannel = (): Discord.TextChannel => getMainGuild().channels.cache.get(loggingChannelID) as Discord.TextChannel
export const getBotReviewLogChannel = (): Discord.TextChannel => getMainGuild().channels.cache.get(botReviewLogChannelID) as Discord.TextChannel
export const discordLog = async (type: string, issuerID: string, embed?: Discord.MessageEmbed, attachment?: { content: string, format: string}, content?: string): Promise<void> => {
getLoggingChannel().send({
content: `[${type}] <@${issuerID}> (${issuerID})\n${content || ''}`,
embed: embed && embed.setTitle(type).setTimestamp(new Date()),
...(attachment && { files: [
attachment && new Discord.MessageAttachment(Buffer.from(attachment.content), `${type.toLowerCase().replace(/\//g, '-')}-${issuerID}-${Date.now()}.${attachment.format}`)
]
})
})
}

View File

@ -215,7 +215,7 @@ async function voteBot(userID: string, botID: string): Promise<number|boolean> {
* @returns 4 - Discord not Joined
* @returns obj - Success
*/
async function submitBot(id: string, data: AddBotSubmit):Promise<number|SubmittedBot> {
async function submitBot(id: string, data: AddBotSubmit):Promise<1|2|3|4|SubmittedBot> {
const submits = await knex('submitted').select(['id']).where({ state: 0 }).andWhere('owners', 'LIKE', `%${id}%`)
if(submits.length > 1) return 1
const botId = data.id

View File

@ -1,4 +1,5 @@
import { NextApiRequest, NextApiResponse } from 'next'
import * as Sentry from '@sentry/nextjs'
import nc from 'next-connect'
import rateLimit from 'express-rate-limit'
@ -22,6 +23,11 @@ const RequestHandler = () =>
onNoMatch(_req, res) {
return ResponseWrapper(res, { code: 405 })
},
onError(err, _req, res) {
console.error(err)
Sentry.captureException(err)
return ResponseWrapper(res, { code: 500 })
}
})
.use(limiter)

View File

@ -1,11 +1,13 @@
import { NextRouter } from 'next/router'
import { inspect as utilInspect } from 'util'
import { createHmac } from 'crypto'
import { Readable } from 'stream'
import cookie from 'cookie'
import * as difflib from 'difflib'
import { BotFlags, ImageOptions, UserFlags } from '@types'
import Logger from '@utils/Logger'
import { BASE_URLs, KoreanbotsEndPoints, Oauth } from '@utils/Constants'
import { NextRouter } from 'next/router'
export function handlePWA(): boolean {
let displayMode = 'browser'
@ -56,6 +58,30 @@ export function serialize<T>(data: T): T {
return JSON.parse(JSON.stringify(data))
}
export function diff(original: string, current: string, header=false, sep='\n', join?: string) {
return difflib.unifiedDiff(original.split(sep), current.split(sep)).slice(header ? 2 : 3).join(join ?? sep)
}
export function objectDiff(original: Record<string, string>, current: Record<string, string>): [string, (string|null)[] ][] {
const obj: Record<string, string[]> = {}
Object.entries(original).forEach(k =>
obj[k[0]] = [ k[1] ]
)
Object.entries(current).forEach(k => {
if(!obj[k[0]]) obj[k[0]] = []
obj[k[0]][1] = k[1]
})
return Object.entries(obj).filter(k => k[1][0] !== k[1][1])
}
export function makeDiscordCodeblock(content: string, lang?: string): string {
return `\`\`\`${lang || ''}\n${content.replace(/```/g, '\\`\\`\\`')}\n\`\`\``
}
export function inspect(object: unknown) {
return utilInspect(object, { depth: Infinity, maxArrayLength: Infinity, maxStringLength: Infinity})
}
export function supportsWebP() {
const elem = document.createElement('canvas')
if (elem.getContext && elem.getContext('2d')) {

View File

@ -3663,6 +3663,13 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
difflib@0.2.4:
version "0.2.4"
resolved "https://registry.yarnpkg.com/difflib/-/difflib-0.2.4.tgz#b5e30361a6db023176d562892db85940a718f47e"
integrity sha1-teMDYabbAjF21WKJLbhZQKcY9H4=
dependencies:
heap ">= 0.2.0"
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@ -4812,6 +4819,11 @@ he@1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
"heap@>= 0.2.0":
version "0.2.6"
resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.6.tgz#087e1f10b046932fc8594dd9e6d378afc9d1e5ac"
integrity sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=
hmac-drbg@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"