mirror of
https://github.com/koreanbots/core.git
synced 2025-12-15 14:10:22 +00:00
feat: added logging
This commit is contained in:
parent
e22ed164e2
commit
acd53e7c21
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 })
|
||||
}
|
||||
|
||||
|
||||
@ -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 })
|
||||
})
|
||||
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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
5
types/global.d.ts
vendored
@ -17,3 +17,8 @@ declare module 'yup' {
|
||||
unique(format?: string): this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
declare module 'difflib' {
|
||||
export function unifiedDiff(before: string, after: string): string[]
|
||||
}
|
||||
@ -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',
|
||||
|
||||
@ -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}`)
|
||||
]
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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')) {
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user