refactor/webhook (#553)

* types: fix type of shortValue

* refactor: move webhook section to dev page

* feat: add tooltip

* refactor: remove warning prop for Label

* refactor: remove webhook field from edit page

* feat: check webhookStatus

* chore: remove unused import

* chore: remove ununsed import

* chore: remove unused import

* chore: add desc about failed webhook

* chore: remove unused import

* feat: remove warning icon when updated

* feat: add webhook field to server

* refactor: remove webhook verification

* fix: endpoint

* feat: add endpoint for /applications/servers/[id]

* feat: remove webhook related props from bot/server

* chore: edit url

* feat: add link to docs

* chore: remove indents
This commit is contained in:
SKINMAKER 2023-04-14 23:57:43 +09:00 committed by GitHub
parent 336300bd9a
commit 59dbf74466
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 209 additions and 144 deletions

View File

@ -1,5 +1,3 @@
import Tooltip from '@components/Tooltip'
const Label: React.FC<LabelProps> = ({
For,
children,
@ -8,9 +6,7 @@ const Label: React.FC<LabelProps> = ({
error = null,
grid = true,
short = false,
required = false,
warning = false,
warningText
required = false
}) => {
return <label
className={grid ? 'grid grid-cols-1 xl:grid-cols-4 gap-2 my-4' : 'inline-flex items-center'}
@ -23,11 +19,6 @@ const Label: React.FC<LabelProps> = ({
{required && (
<span className='align-text-top text-red-500 text-base font-semibold'> *</span>
)}
{warning && (
<Tooltip direction='left' text={warningText}>
<span className='text-red-500 text-base font-semibold pl-1' role='img' aria-label='warning'></span>
</Tooltip>
)}
</h3>
{labelDesc}
</div>

View File

@ -6,7 +6,9 @@ import ResponseWrapper from '@utils/ResponseWrapper'
import { checkToken } from '@utils/Csrf'
import RequestHandler from '@utils/RequestHandler'
import { User } from '@types'
import { User, WebhookStatus } from '@types'
import { parseWebhookURL } from 'discord.js'
import { verifyWebhook } from '@utils/Webhook'
const BotApplications = RequestHandler().patch(async (req: ApiRequest, res) => {
const user = await get.Authorization(req.cookies.token)
@ -24,7 +26,25 @@ const BotApplications = RequestHandler().patch(async (req: ApiRequest, res) => {
const bot = await get.bot.load(req.query.id)
if (!bot) return ResponseWrapper(res, { code: 404, message: '존재하지 않는 봇입니다.' })
if (!(bot.owners as User[]).find(el => el.id === user)) return ResponseWrapper(res, { code: 403 })
await update.updateBotApplication(req.query.id, { webhook: validated.webhook || null })
if(validated.webhookURL) {
const key = await verifyWebhook(validated.webhookURL)
if(key === false) {
return ResponseWrapper(res, { code: 400, message: '웹후크 주소를 검증할 수 없습니다.', errors: ['웹후크 주소가 올바른지 확인해주세요.\n웹후크 주소 검증에 대한 자세한 내용은 API 문서를 참고해주세요.'] })
}
await update.webhook(req.query.id, 'bots', {
url: validated.webhookURL,
status: parseWebhookURL(validated.webhookURL) ? WebhookStatus.Discord : WebhookStatus.HTTP,
failedSince: null,
secret: key,
})
} else {
await update.webhook(req.query.id, 'bots', {
url: null,
status: WebhookStatus.None,
failedSince: null,
secret: null,
})
}
return ResponseWrapper(res, { code: 200 })
})

View File

@ -0,0 +1,58 @@
import { NextApiRequest } from 'next'
import { DeveloperServer, DeveloperServerSchema } from '@utils/Yup'
import { get, update } from '@utils/Query'
import ResponseWrapper from '@utils/ResponseWrapper'
import { checkToken } from '@utils/Csrf'
import RequestHandler from '@utils/RequestHandler'
import { WebhookStatus } from '@types'
import { parseWebhookURL } from 'discord.js'
import { verifyWebhook } from '@utils/Webhook'
const ServerApplications = RequestHandler().patch(async (req: ApiRequest, res) => {
const user = await get.Authorization(req.cookies.token)
if (!user) return ResponseWrapper(res, { code: 401 })
const csrfValidated = checkToken(req, res, req.body._csrf)
if (!csrfValidated) return
const validated = await DeveloperServerSchema.validate(req.body, { abortEarly: false })
.then(el => el)
.catch(e => {
ResponseWrapper(res, { code: 400, errors: e.errors })
return null
})
if (!validated) return
const server = await get.serverData(req.query.id)
if (!server) return ResponseWrapper(res, { code: 404, message: '존재하지 않는 서버입니다.' })
if (![server.owner, ...server.admins].includes(user)) return ResponseWrapper(res, { code: 403 })
if(validated.webhookURL) {
const key = await verifyWebhook(validated.webhookURL)
if(key === false) {
return ResponseWrapper(res, { code: 400, message: '웹후크 주소를 검증할 수 없습니다.', errors: ['웹후크 주소가 올바른지 확인해주세요.\n웹후크 주소 검증에 대한 자세한 내용은 API 문서를 참고해주세요.'] })
}
await update.webhook(req.query.id, 'servers', {
url: validated.webhookURL,
status: parseWebhookURL(validated.webhookURL) ? WebhookStatus.Discord : WebhookStatus.HTTP,
failedSince: null,
secret: key,
})
} else {
await update.webhook(req.query.id, 'servers', {
url: null,
status: WebhookStatus.None,
failedSince: null,
secret: null,
})
}
return ResponseWrapper(res, { code: 200 })
})
interface ApiRequest extends NextApiRequest {
body: DeveloperServer
query: {
id: string
}
}
export default ServerApplications

View File

@ -12,7 +12,6 @@ import { User } from '@types'
import { checkUserFlag, diff, inspect, makeDiscordCodeblock, objectDiff, serialize } from '@utils/Tools'
import { discordLog, getBotReviewLogChannel, getMainGuild } from '@utils/DiscordBot'
import { KoreanbotsEndPoints } from '@utils/Constants'
import { verifyWebhook } from '@utils/Webhook'
const patchLimiter = rateLimit({
windowMs: 2 * 60 * 1000,
@ -29,8 +28,6 @@ const Bots = RequestHandler()
const bot = await get.bot.load(req.query.id)
if (!bot) return ResponseWrapper(res, { code: 404, message: '존재하지 않는 봇입니다.' })
else {
delete bot.webhookURL
delete bot.webhookStatus
return ResponseWrapper(res, { code: 200, data: bot })
}
})
@ -158,18 +155,15 @@ const Bots = RequestHandler()
})
if (!validated) return
const key = validated.webhookURL ? await verifyWebhook(validated.webhookURL) : null
if(key === false) {
return ResponseWrapper(res, { code: 400, message: '웹후크 주소를 검증할 수 없습니다.', errors: ['웹후크 주소가 올바른지 확인해주세요.\n웹후크 주소 검증에 대한 자세한 내용은 API 문서를 참고해주세요.'] })
}
const result = await update.bot(req.query.id, validated, key)
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 EmbedBuilder().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, webhook: bot.webhookURL, 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, webhook: validated.webhookURL, intro: validated.intro, category: JSON.stringify(validated.category) }
{ 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.forEach(d => {
embed.addFields({name: d[0], value: makeDiscordCodeblock(diff(d[1][0] || '', d[1][1] || ''), 'diff')

View File

@ -10,7 +10,6 @@ import RequestHandler from '@utils/RequestHandler'
import { checkUserFlag, diff, inspect, makeDiscordCodeblock, objectDiff, serialize } from '@utils/Tools'
import { DiscordBot, discordLog } from '@utils/DiscordBot'
import { KoreanbotsEndPoints } from '@utils/Constants'
import { verifyWebhook } from '@utils/Webhook'
const patchLimiter = rateLimit({
windowMs: 2 * 60 * 1000,
@ -27,8 +26,6 @@ const Servers = RequestHandler()
const server = await get.server.load(req.query.id)
if (!server) return ResponseWrapper(res, { code: 404, message: '존재하지 않는 서버 입니다.' })
else {
delete server.webhookURL
delete server.webhookStatus
return ResponseWrapper(res, { code: 200, data: server })
}
})
@ -138,12 +135,7 @@ const Servers = RequestHandler()
const invite = await DiscordBot.fetchInvite(validated.invite).catch(() => null)
if(invite?.guild.id !== server.id || invite.expiresAt) return ResponseWrapper(res, { code: 400, message: '올바르지 않은 초대코드입니다.', errors: ['입력하신 초대코드가 올바르지 않습니다. 올바른 초대코드를 입력했는지 다시 한 번 확인해주세요.', '만료되지 않는 초대코드인지 확인해주세요.'] })
const key = validated.webhook ? await verifyWebhook(validated.webhook) : null
if(key === false) {
return ResponseWrapper(res, { code: 400, message: '웹후크 주소를 검증할 수 없습니다.', errors: ['웹후크 주소가 올바른지 확인해주세요.\n웹후크 주소 검증에 대한 자세한 내용은 API 문서를 참고해주세요.'] })
}
const result = await update.server(req.query.id, validated, key)
const result = await update.server(req.query.id, validated)
if(result === 0) return ResponseWrapper(res, { code: 400 })
else {

View File

@ -12,7 +12,7 @@ import { get } from '@utils/Query'
import { checkUserFlag, cleanObject, makeBotURL, parseCookie, redirectTo } from '@utils/Tools'
import { ManageBot, ManageBotSchema } from '@utils/Yup'
import { botCategories, library } from '@utils/Constants'
import { Bot, Theme, User, WebhookStatus } from '@types'
import { Bot, Theme, User } from '@types'
import { getToken } from '@utils/Csrf'
import Fetch from '@utils/Fetch'
@ -75,7 +75,6 @@ const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme })
url: bot.url,
git: bot.git,
discord: bot.discord,
webhookURL: bot.webhookURL,
_csrf: csrfToken
})}
validationSchema={ManageBotSchema}
@ -146,9 +145,6 @@ const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme })
discord.gg/<Input name='discord' placeholder='JEh53MQ' />
</div>
</Label>
<Label For='webhookURL' label='웹훅 링크' labelDesc='봇의 업데이트 알림을 받을 웹훅 링크를 입력해주세요. (웹훅 링크가 유효하지 않을 경우 웹훅이 중지되며, 다시 저장할 경우 작동합니다.)' error={errors.webhookURL && touched.webhookURL ? errors.webhookURL : null} warning={bot.webhookStatus === WebhookStatus.Disabled} warningText='웹훅 링크가 유효하지 않아 웹훅이 중지되었습니다.'>
<Input name='webhookURL' placeholder='https://discord.com/api/webhooks/ID/TOKEN'/>
</Label>
<Divider />
<Label For='intro' label='봇 소개' labelDesc='봇을 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)' error={errors.intro && touched.intro ? errors.intro : null} required>
<Input name='intro' placeholder='국내 봇을 한 곳에서.' />

View File

@ -1,3 +1,4 @@
/* eslint-disable no-mixed-spaces-and-tabs */
import { NextPage, NextPageContext } from 'next'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
@ -5,15 +6,19 @@ import { useState } from 'react'
import useCopyClipboard from 'react-use-clipboard'
import { get } from '@utils/Query'
import { parseCookie, redirectTo } from '@utils/Tools'
import { cleanObject, parseCookie, redirectTo } from '@utils/Tools'
import { getToken } from '@utils/Csrf'
import Fetch from '@utils/Fetch'
import { ParsedUrlQuery } from 'querystring'
import { Bot, BotSpec, ResponseProps, Theme } from '@types'
import { Bot, BotSpec, ResponseProps, Theme, WebhookStatus } from '@types'
import NotFound from 'pages/404'
import Link from 'next/link'
import { Form, Formik } from 'formik'
import { DeveloperBot, DeveloperBotSchema } from '@utils/Yup'
import Input from '@components/Form/Input'
import Tooltip from '@components/Tooltip'
const Button = dynamic(() => import('@components/Button'))
const DeveloperLayout = dynamic(() => import('@components/DeveloperLayout'))
@ -29,13 +34,13 @@ const BotApplication: NextPage<BotApplicationProps> = ({ user, spec, bot, theme,
const [ tokenCopied, setTokenCopied ] = useCopyClipboard(spec?.token, {
successDuration: 1000
})
// async function updateApplication(d: DeveloperBot) {
// const res = await Fetch(`/applications/bots/${bot.id}`, {
// method: 'PATCH',
// body: JSON.stringify(cleanObject(d))
// })
// setData(res)
// }
async function updateApplication(d: DeveloperBot) {
const res = await Fetch(`/applications/bots/${bot.id}`, {
method: 'PATCH',
body: JSON.stringify(cleanObject(d))
})
setData(res)
}
async function resetToken() {
const res = await Fetch<{ token: string }>(`/applications/bots/${bot.id}/reset`, {
@ -102,23 +107,33 @@ const BotApplication: NextPage<BotApplicationProps> = ({ user, spec, bot, theme,
</div>
</Modal>
</div>
{/* <Formik validationSchema={DeveloperBotSchema} initialValues={{
webhook: spec.webhook || '',
<Formik validationSchema={DeveloperBotSchema} initialValues={{
webhookURL: spec.webhookURL || '',
_csrf: csrfToken
}}
onSubmit={(data) => updateApplication(data)}>
onSubmit={updateApplication}>
{({ errors, touched }) => (
<Form>
<div className='mb-2'>
<h3 className='font-bold mb-1'> URL</h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
<Input name='webhook' placeholder='https://webhook.kbots.link' />
{touched.webhook && errors.webhook ? <div className='text-red-500 text-xs font-light mt-1'>{errors.webhook}</div> : null}
<h3 className='font-bold mb-1'>
URL
{(!data || data.code !== 200) && spec.webhookStatus === WebhookStatus.Disabled && (
<Tooltip direction='left' text='웹훅 링크가 유효하지 않아 웹훅이 중지되었습니다.'>
<span className='text-red-500 text-base font-semibold pl-1' role='img' aria-label='warning'></span>
</Tooltip>
)}
</h3>
<p className='text-gray-400 text-sm mb-1'> .<br/>
, .<br/>
<Link href={'/developers/docs/%EC%9B%B9%ED%9B%84%ED%81%AC'}><a className='text-blue-500 hover:text-blue-400 font-semibold'> </a></Link> .
</p>
<Input name='webhookURL' placeholder='https://webhook.koreanbots.dev' />
{touched.webhookURL && errors.webhookURL ? <div className='text-red-500 text-xs font-light mt-1'>{errors.webhookURL}</div> : null}
</div>
<Button type='submit'><i className='far fa-save'/> </Button>
</Form>
)}
</Formik> */}
</Formik>
</div>
</div>
</div>

View File

@ -5,15 +5,19 @@ import { useState } from 'react'
import useCopyClipboard from 'react-use-clipboard'
import { get } from '@utils/Query'
import { parseCookie, redirectTo } from '@utils/Tools'
import { cleanObject, parseCookie, redirectTo } from '@utils/Tools'
import { getToken } from '@utils/Csrf'
import Fetch from '@utils/Fetch'
import { ParsedUrlQuery } from 'querystring'
import { Server, BotSpec, ResponseProps, Theme } from '@types'
import { Server, BotSpec, ResponseProps, Theme, WebhookStatus } from '@types'
import NotFound from 'pages/404'
import Link from 'next/link'
import { DeveloperServer, DeveloperServerSchema } from '@utils/Yup'
import { Form, Formik } from 'formik'
import Tooltip from '@components/Tooltip'
import Input from '@components/Form/Input'
const Button = dynamic(() => import('@components/Button'))
const DeveloperLayout = dynamic(() => import('@components/DeveloperLayout'))
@ -29,13 +33,13 @@ const ServerApplication: NextPage<ServerApplicationProps> = ({ user, spec, serve
const [ tokenCopied, setTokenCopied ] = useCopyClipboard(spec?.token, {
successDuration: 1000
})
// async function updateApplication(d: DeveloperBot) {
// const res = await Fetch(`/applications/bots/${bot.id}`, {
// method: 'PATCH',
// body: JSON.stringify(cleanObject(d))
// })
// setData(res)
// }
async function updateApplication(d: DeveloperServer) {
const res = await Fetch(`/applications/servers/${server.id}`, {
method: 'PATCH',
body: JSON.stringify(cleanObject(d))
})
setData(res)
}
async function resetToken() {
const res = await Fetch<{ token: string }>(`/applications/servers/${server.id}/reset`, {
@ -104,23 +108,33 @@ const ServerApplication: NextPage<ServerApplicationProps> = ({ user, spec, serve
</div>
</Modal>
</div>
{/* <Formik validationSchema={DeveloperBotSchema} initialValues={{
webhook: spec.webhook || '',
_csrf: csrfToken
}}
onSubmit={(data) => updateApplication(data)}>
{({ errors, touched }) => (
<Form>
<div className='mb-2'>
<h3 className='font-bold mb-1'> URL</h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
<Input name='webhook' placeholder='https://webhook.kbots.link' />
{touched.webhook && errors.webhook ? <div className='text-red-500 text-xs font-light mt-1'>{errors.webhook}</div> : null}
</div>
<Button type='submit'><i className='far fa-save'/> </Button>
</Form>
)}
</Formik> */}
<Formik validationSchema={DeveloperServerSchema} initialValues={{
webhookURL: spec.webhookURL || '',
_csrf: csrfToken
}}
onSubmit={updateApplication}>
{({ errors, touched }) => (
<Form>
<div className='mb-2'>
<h3 className='font-bold mb-1'>
URL
{(!data || data.code !== 200) && spec.webhookStatus === WebhookStatus.Disabled && (
<Tooltip direction='left' text='웹훅 링크가 유효하지 않아 웹훅이 중지되었습니다.'>
<span className='text-red-500 text-base font-semibold pl-1' role='img' aria-label='warning'></span>
</Tooltip>
)}
</h3>
<p className='text-gray-400 text-sm mb-1'> .<br/>
, .<br/>
<Link href={'/developers/docs/%EC%9B%B9%ED%9B%84%ED%81%AC'}><a className='text-blue-500 hover:text-blue-400 font-semibold'> </a></Link> .
</p>
<Input name='webhookURL' placeholder='https://webhook.koreanbots.dev' />
{touched.webhookURL && errors.webhookURL ? <div className='text-red-500 text-xs font-light mt-1'>{errors.webhookURL}</div> : null}
</div>
<Button type='submit'><i className='far fa-save'/> </Button>
</Form>
)}
</Formik>
</div>
</div>
</div>

View File

@ -11,7 +11,7 @@ import { get } from '@utils/Query'
import { checkUserFlag, cleanObject, getRandom, makeServerURL, parseCookie, redirectTo } from '@utils/Tools'
import { ManageServer, ManageServerSchema } from '@utils/Yup'
import { serverCategories, ServerIntroList } from '@utils/Constants'
import { Server, Theme, User, WebhookStatus } from '@types'
import { Server, Theme, User } from '@types'
import { getToken } from '@utils/Csrf'
import Fetch from '@utils/Fetch'
@ -57,7 +57,6 @@ const ManageServerPage:NextPage<ManageServerProps> = ({ server, user, owners, cs
intro: server.intro,
desc: server.desc,
category: server.category,
webhookURL: server.webhookURL,
_csrf: csrfToken
})}
validationSchema={ManageServerSchema}
@ -99,9 +98,6 @@ const ManageServerPage:NextPage<ManageServerProps> = ({ server, user, owners, cs
discord.gg/<Input name='invite' placeholder='JEh53MQ' />
</div>
</Label>
<Label For='webhookURL' label='웹훅 링크' labelDesc='봇의 업데이트 알림을 받을 웹훅 링크를 입력해주세요. (웹훅 링크가 유효하지 않을 경우 웹훅이 중지되며, 다시 저장할 경우 작동합니다.)' error={errors.webhookURL && touched.webhookURL ? errors.webhookURL : null} warning={server.webhookStatus === WebhookStatus.Disabled} warningText='웹훅 링크가 유효하지 않아 웹훅이 중지되었습니다.'>
<Input name='webhookURL' placeholder='https://discord.com/api/webhooks/ID/TOKEN'/>
</Label>
<Divider />
<Label For='intro' label='서버 소개' labelDesc='서버를 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)' error={errors.intro && touched.intro ? errors.intro : null} required>
<Input name='intro' placeholder={getRandom(ServerIntroList)} />

View File

@ -24,8 +24,6 @@ export interface Bot {
git: string | null
url: string | null
discord: string | null
webhookURL: string | null
webhookStatus: WebhookStatus
vanity: string | null
bg: string
banner: string
@ -55,8 +53,6 @@ export interface Server {
desc: string
category: ServerCategory[]
invite: string
webhookURL: string | null
webhookStatus: WebhookStatus
vanity: string | null
bg: string | null
banner: string | null
@ -89,7 +85,15 @@ export interface User {
export interface BotSpec {
id: string
webhook: string | null
webhookURL: string | null
webhookStatus: WebhookStatus
token: string
}
export interface ServerSpec {
id: string
webhookURL: string | null
webhookStatus: WebhookStatus
token: string
}

View File

@ -1,9 +1,9 @@
import fetch from 'node-fetch'
import { TLRU } from 'tlru'
import DataLoader from 'dataloader'
import { ActivityType, GuildFeature, GuildMember, parseWebhookURL, User as DiscordUser, UserFlags } from 'discord.js'
import { ActivityType, GuildFeature, GuildMember, User as DiscordUser, UserFlags } from 'discord.js'
import { Bot, Server, User, ListType, List, TokenRegister, BotFlags, DiscordUserFlags, SubmittedBot, DiscordTokenInfo, ServerData, ServerFlags, RawGuild, Nullable, WebhookStatus, Webhook } from '@types'
import { Bot, Server, User, ListType, List, TokenRegister, BotFlags, DiscordUserFlags, SubmittedBot, DiscordTokenInfo, ServerData, ServerFlags, RawGuild, Nullable, Webhook, BotSpec, ServerSpec } from '@types'
import { botCategories, DiscordEnpoints, imageSafeHost, serverCategories, SpecialEndPoints, VOTE_COOLDOWN } from './Constants'
import knex from './Knex'
@ -37,8 +37,6 @@ async function getBot(id: string, topLevel=true):Promise<Bot> {
'trusted',
'partnered',
'discord',
'webhook_url',
'webhook_status',
'state',
'vanity',
'bg',
@ -68,12 +66,8 @@ async function getBot(id: string, topLevel=true):Promise<Bot> {
} else {
res[0].status = null
}
res[0].webhookURL = res[0].webhook_url
res[0].webhookStatus = res[0].webhook_status
delete res[0].trusted
delete res[0].partnered
delete res[0].webhook_url
delete res[0].webhook_status
if (topLevel) {
res[0].owners = await Promise.all(
res[0].owners.map(async (u: string) => await get._rawUser.load(u))
@ -101,8 +95,6 @@ async function getServer(id: string, topLevel=true): Promise<Server> {
'category',
'invite',
'state',
'webhook_url',
'webhook_status',
'vanity',
'bg',
'banner',
@ -120,10 +112,6 @@ async function getServer(id: string, topLevel=true): Promise<Server> {
await knex('servers').update({ name: data.name, owners: JSON.stringify([data.owner, ...data.admins]) })
.where({ id: res[0].id })
}
res[0].webhookURL = res[0].webhook_url
res[0].webhookStatus = res[0].webhook_status
delete res[0].webhook_url
delete res[0].webhook_status
delete res[0].owners
res[0].icon = data?.icon || null
res[0].members = data?.memberCount || null
@ -487,16 +475,26 @@ async function submitServer(userID: string, id: string, data: AddServerSubmit):
return true
}
async function getBotSpec(id: string, userID: string) {
const res = await knex('bots').select(['id', 'token']).where({ id }).andWhere('owners', 'like', `%${userID}%`)
async function getBotSpec(id: string, userID: string): Promise<BotSpec | null> {
const res = await knex('bots').select(['id', 'token', 'webhook_url', 'webhook_status']).where({ id }).andWhere('owners', 'like', `%${userID}%`)
if(!res[0]) return null
return serialize(res[0])
return {
id: res[0].id,
token: res[0].token,
webhookURL: res[0].webhook_url,
webhookStatus: res[0].webhook_status
}
}
async function getServerSpec(id: string, userID: string): Promise<{ id: string, token: string }> {
const res = await knex('servers').select(['id', 'token']).where({ id }).andWhere('owners', 'like', `%${userID}%`)
async function getServerSpec(id: string, userID: string): Promise<ServerSpec | null> {
const res = await knex('servers').select(['id', 'token', 'webhook_url', 'webhook_status']).where({ id }).andWhere('owners', 'like', `%${userID}%`)
if(!res[0]) return null
return serialize(res[0])
return {
id: res[0].id,
token: res[0].token,
webhookURL: res[0].webhook_url,
webhookStatus: res[0].webhook_status
}
}
async function deleteBot(id: string): Promise<boolean> {
@ -510,7 +508,7 @@ async function deleteServer(id: string): Promise<boolean> {
return !!server
}
async function updateBot(id: string, data: ManageBot, webhookSecret: string | null): Promise<number> {
async function updateBot(id: string, data: ManageBot): Promise<number> {
const res = await knex('bots').where({ id })
if(res.length === 0) return 0
await knex('bots').update({
@ -520,10 +518,6 @@ async function updateBot(id: string, data: ManageBot, webhookSecret: string | nu
git: data.git,
url: data.url,
discord: data.discord,
webhook_url: data.webhookURL,
webhook_status: parseWebhookURL(data.webhookURL) ? WebhookStatus.Discord : WebhookStatus.HTTP,
webhook_failed_since: null,
webhook_secret: webhookSecret,
category: JSON.stringify(data.category),
intro: data.intro,
desc: data.desc
@ -532,18 +526,14 @@ async function updateBot(id: string, data: ManageBot, webhookSecret: string | nu
return 1
}
async function updatedServer(id: string, data: ManageServer, webhookSecret: string | null) {
async function updatedServer(id: string, data: ManageServer) {
const res = await knex('servers').where({ id })
if(res.length === 0) return 0
await knex('servers').update({
invite: data.invite,
category: JSON.stringify(data.category),
intro: data.intro,
desc: data.desc,
webhook_url: data.webhookURL,
webhook_status: parseWebhookURL(data.webhookURL) ? WebhookStatus.Discord : WebhookStatus.HTTP,
webhook_failed_since: null,
webhook_secret: webhookSecret,
desc: data.desc
}).where({ id })
return 1
@ -578,12 +568,6 @@ async function updateWebhook(id: string, type: 'bots' | 'servers', value: Partia
return true
}
async function updateBotApplication(id: string, value: { webhook: string }) {
const bot = await knex('bots').update({ webhook: value.webhook }).where({ id })
if(bot !== 1) return false
return true
}
async function updateOwner(id: string, owners: string[]): Promise<void> {
await knex('bots').where({ id }).update({ owners: JSON.stringify(owners) })
get.bot.clear(id)
@ -892,7 +876,6 @@ export const get = {
export const update = {
assignToken,
updateBotApplication,
resetBotToken,
resetServerToken,
updateServer,

View File

@ -28,11 +28,11 @@ export function formatNumber(value: number):string {
if(!value) return '0'
const suffixes = ['', '만', '억', '조','해']
const suffixNum = Math.floor((''+value).length/4)
let shortValue: string|number = parseFloat((suffixNum != 0 ? (value / Math.pow(10000, suffixNum)) : value).toPrecision(2))
let shortValue: number = parseFloat((suffixNum != 0 ? (value / Math.pow(10000, suffixNum)) : value).toPrecision(2))
if (shortValue % 1 != 0) {
shortValue = shortValue.toFixed(1)
shortValue = Number(shortValue.toFixed(1))
}
if(suffixNum === 1 && shortValue < 1) return Number(shortValue) * 10 + '천'
if(suffixNum === 1 && shortValue < 1) return shortValue * 10 + '천'
else if(shortValue === 1000) return '1천'
return shortValue+suffixes[suffixNum]
}

View File

@ -326,11 +326,6 @@ export const ManageBotSchema: Yup.SchemaOf<ManageBot> = Yup.object({
.min(2, '지원 디스코드는 최소 2자여야합니다.')
.max(32, '지원 디스코드는 최대 32자까지만 가능합니다.')
.nullable(),
webhookURL: Yup.string()
.matches(HTTPProtocol, 'http:// 또는 https:// 로 시작해야합니다.')
.matches(Url, '올바른 웹훅 URL을 입력해주세요.')
.max(256, '웹훅 링크는 최대 256자까지만 가능합니다.')
.nullable(),
category: Yup.array(Yup.string().oneOf(botCategories))
.min(1, '최소 한 개의 카테고리를 선택해주세요.')
.unique('카테고리는 중복될 수 없습니다.')
@ -353,7 +348,6 @@ export interface ManageBot {
url: string
git: string
discord: string
webhookURL: string
category: string[]
intro: string
desc: string
@ -378,11 +372,6 @@ export const ManageServerSchema: Yup.SchemaOf<ManageServer> = Yup.object({
.min(100, '서버 설명은 최소 100자여야합니다.')
.max(1500, '서버 설명은 최대 1500자여야합니다.')
.required('서버 설명은 필수 항목입니다.'),
webhookURL: Yup.string()
.matches(HTTPProtocol, 'http:// 또는 https:// 로 시작해야합니다.')
.matches(Url, '올바른 웹훅 URL을 입력해주세요.')
.max(256, '웹훅 링크는 최대 256자까지만 가능합니다.')
.nullable(),
_csrf: Yup.string().required(),
})
@ -391,7 +380,6 @@ export interface ManageServer {
category: string[]
intro: string
desc: string
webhookURL: string
_csrf: string
}
@ -406,7 +394,7 @@ export interface CsrfCaptcha {
}
export const DeveloperBotSchema: Yup.SchemaOf<DeveloperBot> = Yup.object({
webhook: Yup.string()
webhookURL: Yup.string()
.matches(HTTPProtocol, 'http:// 또는 https:// 로 시작해야합니다.')
.matches(Url, '올바른 웹훅 URL을 입력해주세요.')
.max(150, 'URL은 최대 150자까지만 가능합니다.')
@ -415,7 +403,21 @@ export const DeveloperBotSchema: Yup.SchemaOf<DeveloperBot> = Yup.object({
})
export interface DeveloperBot {
webhook: string | null
webhookURL: string | null
_csrf: string
}
export const DeveloperServerSchema: Yup.SchemaOf<DeveloperServer> = Yup.object({
webhookURL: Yup.string()
.matches(HTTPProtocol, 'http:// 또는 https:// 로 시작해야합니다.')
.matches(Url, '올바른 웹훅 URL을 입력해주세요.')
.max(150, 'URL은 최대 150자까지만 가능합니다.')
.nullable(),
_csrf: Yup.string().required(),
})
export interface DeveloperServer {
webhookURL: string | null
_csrf: string
}