mirror of
https://github.com/koreanbots/core.git
synced 2025-12-15 14:10:22 +00:00
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:
parent
336300bd9a
commit
59dbf74466
@ -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>
|
||||
|
||||
@ -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 })
|
||||
})
|
||||
|
||||
|
||||
58
pages/api/v2/applications/servers/[id]/index.ts
Normal file
58
pages/api/v2/applications/servers/[id]/index.ts
Normal 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
|
||||
@ -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')
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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='국내 봇을 한 곳에서.' />
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)} />
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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]
|
||||
}
|
||||
|
||||
30
utils/Yup.ts
30
utils/Yup.ts
@ -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
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user