mirror of
https://github.com/koreanbots/core.git
synced 2025-12-15 22:10:24 +00:00
Feat: 신뢰된 봇 특전 수정 기능 추가 (#673)
* feat: camofy urls * chore: unify standards * feat: api changes for trusted bot perks * feat: trusted bot perks on edit page * chore: unify * fix: wtf * feat: hide perks menu for non-trusted bots * fix: camo should be proceed at server-side * type: typing issue * fix: conflict * fix: editing page on vanity code * fix: removed reserved page * feat: reserved vanity * fix: for not found * fix: bypass reserved vanity for offical bot * chore: apply prettier * chore: add webhook log for vanity change --------- Co-authored-by: skinmaker1345 <skinmaker1345@gmail.com>
This commit is contained in:
parent
f68da03eaf
commit
b7ca0cfc03
@ -33,3 +33,4 @@ REVIEW_LOG_WEBHOOK_URL=
|
|||||||
OPEN_REVIEW_LOG_WEBHOOK_URL=
|
OPEN_REVIEW_LOG_WEBHOOK_URL=
|
||||||
STATS_LOG_WEBHOOK_URL=
|
STATS_LOG_WEBHOOK_URL=
|
||||||
REPORT_WEBHOOK_URL=
|
REPORT_WEBHOOK_URL=
|
||||||
|
NOTICE_LOG_WEBHOOK_URL=
|
||||||
|
|||||||
@ -18,7 +18,8 @@ const BotCard: React.FC<BotCardProps> = ({ manage = false, bot }) => {
|
|||||||
<div
|
<div
|
||||||
className='relative mx-auto h-full rounded-2xl bg-little-white text-black shadow-xl dark:bg-discord-black dark:text-white'
|
className='relative mx-auto h-full rounded-2xl bg-little-white text-black shadow-xl dark:bg-discord-black dark:text-white'
|
||||||
style={
|
style={
|
||||||
checkBotFlag(bot.flags, 'trusted') && bot.banner
|
(checkBotFlag(bot.flags, 'trusted') || checkBotFlag(bot.flags, 'partnered')) &&
|
||||||
|
bot.banner
|
||||||
? {
|
? {
|
||||||
background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${bot.banner}") center top / cover no-repeat`,
|
background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${bot.banner}") center top / cover no-repeat`,
|
||||||
color: 'white',
|
color: 'white',
|
||||||
|
|||||||
@ -27,7 +27,9 @@ const ServerCard: React.FC<BotCardProps> = ({ type, server }) => {
|
|||||||
<div
|
<div
|
||||||
className='relative mx-auto h-full rounded-2xl bg-little-white text-black shadow-xl dark:bg-discord-black dark:text-white'
|
className='relative mx-auto h-full rounded-2xl bg-little-white text-black shadow-xl dark:bg-discord-black dark:text-white'
|
||||||
style={
|
style={
|
||||||
checkServerFlag(server.flags, 'trusted') && server.banner
|
(checkServerFlag(server.flags, 'trusted') ||
|
||||||
|
checkServerFlag(server.flags, 'partnered')) &&
|
||||||
|
server.banner
|
||||||
? {
|
? {
|
||||||
background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${server.banner}") center top / cover no-repeat`,
|
background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${server.banner}") center top / cover no-repeat`,
|
||||||
color: 'white',
|
color: 'white',
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
import RequestHandler from '@utils/RequestHandler'
|
import RequestHandler from '@utils/RequestHandler'
|
||||||
import { User } from '@types'
|
import { User } from '@types'
|
||||||
import {
|
import {
|
||||||
|
checkBotFlag,
|
||||||
checkUserFlag,
|
checkUserFlag,
|
||||||
diff,
|
diff,
|
||||||
inspect,
|
inspect,
|
||||||
@ -25,7 +26,6 @@ import {
|
|||||||
} from '@utils/Tools'
|
} from '@utils/Tools'
|
||||||
import { discordLog, getMainGuild, webhookClients } from '@utils/DiscordBot'
|
import { discordLog, getMainGuild, webhookClients } from '@utils/DiscordBot'
|
||||||
import { KoreanbotsEndPoints } from '@utils/Constants'
|
import { KoreanbotsEndPoints } from '@utils/Constants'
|
||||||
import { userInfo } from 'os'
|
|
||||||
|
|
||||||
const patchLimiter = rateLimit({
|
const patchLimiter = rateLimit({
|
||||||
windowMs: 2 * 60 * 1000,
|
windowMs: 2 * 60 * 1000,
|
||||||
@ -128,7 +128,11 @@ const Bots = RequestHandler()
|
|||||||
: `${userinfo.username}#${userinfo.tag}`,
|
: `${userinfo.username}#${userinfo.tag}`,
|
||||||
iconURL:
|
iconURL:
|
||||||
KoreanbotsEndPoints.URL.root +
|
KoreanbotsEndPoints.URL.root +
|
||||||
KoreanbotsEndPoints.CDN.avatar(userinfo.id, { format: 'png', size: 256, hash: userinfo.avatar }),
|
KoreanbotsEndPoints.CDN.avatar(userinfo.id, {
|
||||||
|
format: 'png',
|
||||||
|
size: 256,
|
||||||
|
hash: userinfo.avatar,
|
||||||
|
}),
|
||||||
url: KoreanbotsEndPoints.URL.user(userinfo.id),
|
url: KoreanbotsEndPoints.URL.user(userinfo.id),
|
||||||
})
|
})
|
||||||
.setTitle('대기 중')
|
.setTitle('대기 중')
|
||||||
@ -211,7 +215,7 @@ const Bots = RequestHandler()
|
|||||||
const csrfValidated = checkToken(req, res, req.body._csrf)
|
const csrfValidated = checkToken(req, res, req.body._csrf)
|
||||||
if (!csrfValidated) return
|
if (!csrfValidated) return
|
||||||
|
|
||||||
const validated = await ManageBotSchema.validate(req.body, { abortEarly: false })
|
const validated: ManageBot = await ManageBotSchema.validate(req.body, { abortEarly: false })
|
||||||
.then((el) => el)
|
.then((el) => el)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
ResponseWrapper(res, { code: 400, errors: e.errors })
|
ResponseWrapper(res, { code: 400, errors: e.errors })
|
||||||
@ -219,11 +223,52 @@ const Bots = RequestHandler()
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!validated) return
|
if (!validated) return
|
||||||
|
if (
|
||||||
|
!checkBotFlag(bot.flags, 'trusted') &&
|
||||||
|
!checkBotFlag(bot.flags, 'partnered') &&
|
||||||
|
(validated.vanity || validated.banner || validated.bg)
|
||||||
|
)
|
||||||
|
return ResponseWrapper(res, {
|
||||||
|
code: 403,
|
||||||
|
message: '해당 봇은 특전을 이용할 권한이 없습니다.',
|
||||||
|
})
|
||||||
|
if (validated.vanity) {
|
||||||
|
const vanity = await get.bot.load(validated.vanity)
|
||||||
|
if (vanity && vanity.id !== bot.id) {
|
||||||
|
return ResponseWrapper(res, {
|
||||||
|
code: 403,
|
||||||
|
message: '이미 사용중인 한디리 커스텀 URL 입니다.',
|
||||||
|
errors: ['다른 커스텀 URL로 다시 시도해주세요.'],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await webhookClients.internal.noticeLog.send({
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: '한디리 커스텀 URL 변경',
|
||||||
|
description: `봇: ${bot.name} - <@${bot.id}> ([${bot.id}](${KoreanbotsEndPoints.URL.bot(
|
||||||
|
bot.id
|
||||||
|
)}))`,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: '이전',
|
||||||
|
value: bot.vanity || '없음',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '이후',
|
||||||
|
value: validated.vanity || '없음',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
color: Colors.Blue,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
const result = await update.bot(req.query.id, validated)
|
const result = await update.bot(req.query.id, validated)
|
||||||
if (result === 0) return ResponseWrapper(res, { code: 400 })
|
if (result === 0) return ResponseWrapper(res, { code: 400 })
|
||||||
else {
|
else {
|
||||||
get.bot.clear(req.query.id)
|
get.bot.clear(req.query.id)
|
||||||
|
get.bot.clear(bot.vanity)
|
||||||
const embed = new EmbedBuilder().setDescription(
|
const embed = new EmbedBuilder().setDescription(
|
||||||
`${bot.name} - <@${bot.id}> ([${bot.id}](${KoreanbotsEndPoints.URL.bot(bot.id)}))`
|
`${bot.name} - <@${bot.id}> ([${bot.id}](${KoreanbotsEndPoints.URL.bot(bot.id)}))`
|
||||||
)
|
)
|
||||||
@ -237,6 +282,9 @@ const Bots = RequestHandler()
|
|||||||
discord: bot.discord,
|
discord: bot.discord,
|
||||||
intro: bot.intro,
|
intro: bot.intro,
|
||||||
category: JSON.stringify(bot.category),
|
category: JSON.stringify(bot.category),
|
||||||
|
vanity: bot.vanity,
|
||||||
|
banner: bot.banner,
|
||||||
|
bg: bot.bg,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prefix: validated.prefix,
|
prefix: validated.prefix,
|
||||||
@ -247,6 +295,9 @@ const Bots = RequestHandler()
|
|||||||
discord: validated.discord,
|
discord: validated.discord,
|
||||||
intro: validated.intro,
|
intro: validated.intro,
|
||||||
category: JSON.stringify(validated.category),
|
category: JSON.stringify(validated.category),
|
||||||
|
vanity: validated.vanity,
|
||||||
|
banner: validated.banner,
|
||||||
|
bg: validated.bg,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
diffData.forEach((d) => {
|
diffData.forEach((d) => {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { ParsedUrlQuery } from 'querystring'
|
|||||||
import { getJosaPicker } from 'josa'
|
import { getJosaPicker } from 'josa'
|
||||||
|
|
||||||
import { get } from '@utils/Query'
|
import { get } from '@utils/Query'
|
||||||
import { checkUserFlag, cleanObject, makeBotURL, parseCookie, redirectTo } from '@utils/Tools'
|
import { checkBotFlag, checkUserFlag, cleanObject, makeBotURL, parseCookie, redirectTo } from '@utils/Tools'
|
||||||
import { ManageBot, ManageBotSchema } from '@utils/Yup'
|
import { ManageBot, ManageBotSchema } from '@utils/Yup'
|
||||||
import { botCategories, botCategoryDescription, library } from '@utils/Constants'
|
import { botCategories, botCategoryDescription, library } from '@utils/Constants'
|
||||||
import { Bot, Theme, User } from '@types'
|
import { Bot, Theme, User } from '@types'
|
||||||
@ -58,7 +58,7 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
|
|||||||
else return null
|
else return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bot) return <NotFound />
|
if (!bot?.id) return <NotFound />
|
||||||
if (!user)
|
if (!user)
|
||||||
return (
|
return (
|
||||||
<Login>
|
<Login>
|
||||||
@ -70,6 +70,7 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
|
|||||||
!checkUserFlag(user.flags, 'staff')
|
!checkUserFlag(user.flags, 'staff')
|
||||||
)
|
)
|
||||||
return <Forbidden />
|
return <Forbidden />
|
||||||
|
const isPerkAvailable = checkBotFlag(bot.flags, 'trusted') || checkBotFlag(bot.flags, 'partnered')
|
||||||
return (
|
return (
|
||||||
<Container paddingTop className='pb-10 pt-5'>
|
<Container paddingTop className='pb-10 pt-5'>
|
||||||
<NextSeo title={`${bot.name} 수정하기`} description='봇의 정보를 수정합니다.' />
|
<NextSeo title={`${bot.name} 수정하기`} description='봇의 정보를 수정합니다.' />
|
||||||
@ -87,6 +88,9 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
|
|||||||
url: bot.url,
|
url: bot.url,
|
||||||
git: bot.git,
|
git: bot.git,
|
||||||
discord: bot.discord,
|
discord: bot.discord,
|
||||||
|
vanity: isPerkAvailable && bot.vanity,
|
||||||
|
banner: isPerkAvailable && bot.banner,
|
||||||
|
bg: isPerkAvailable && bot.bg,
|
||||||
_csrf: csrfToken,
|
_csrf: csrfToken,
|
||||||
})}
|
})}
|
||||||
validationSchema={ManageBotSchema}
|
validationSchema={ManageBotSchema}
|
||||||
@ -269,6 +273,44 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
|
|||||||
<Markdown text={values.desc} />
|
<Markdown text={values.desc} />
|
||||||
</Segment>
|
</Segment>
|
||||||
</Label>
|
</Label>
|
||||||
|
{
|
||||||
|
isPerkAvailable && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<h2 className='pt-2 text-2xl font-semibold text-koreanbots-green'>신뢰된 봇 특전 설정</h2>
|
||||||
|
<span className='mt-1 text-sm text-gray-400'>신뢰된 봇의 혜택을 만나보세요. (커스텀 URL과 배너/배경 이미지는 이용약관 및 가이드라인을 준수해야하며 위반 시 신뢰된 봇 자격이 박탈될 수 있습니다.)</span>
|
||||||
|
<Label
|
||||||
|
For='vanity'
|
||||||
|
label='한디리 커스텀 URL'
|
||||||
|
labelDesc='고유한 커스텀 URL을 설정해주세요.'
|
||||||
|
error={errors.vanity && touched.vanity ? errors.vanity : null}
|
||||||
|
|
||||||
|
>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
koreanbots.dev/bots/
|
||||||
|
<Input name='vanity' placeholder='koreanbots' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</Label>
|
||||||
|
<Label
|
||||||
|
For='banner'
|
||||||
|
label='배너 URL'
|
||||||
|
labelDesc='봇 목록의 카드에 표시되는 이미지입니다.'
|
||||||
|
error={errors.banner && touched.banner ? errors.banner : null}
|
||||||
|
>
|
||||||
|
<Input name='banner' placeholder='https://koreanbots.dev/logo.png' />
|
||||||
|
</Label>
|
||||||
|
<Label
|
||||||
|
For='bg'
|
||||||
|
label='배경 URL'
|
||||||
|
labelDesc='봇 페이지 배경에 표시되는 이미지입니다.'
|
||||||
|
error={errors.bg && touched.bg ? errors.bg : null}
|
||||||
|
>
|
||||||
|
<Input name='bg' placeholder='https://koreanbots.dev/logo.png' />
|
||||||
|
</Label>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
<Divider />
|
<Divider />
|
||||||
<p className='mb-5 mt-2 text-base'>
|
<p className='mb-5 mt-2 text-base'>
|
||||||
<span className='font-semibold text-red-500'> *</span> = 필수 항목
|
<span className='font-semibold text-red-500'> *</span> = 필수 항목
|
||||||
@ -278,6 +320,7 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
|
|||||||
<i className='far fa-save' /> 저장
|
<i className='far fa-save' /> 저장
|
||||||
</>
|
</>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
@ -574,9 +617,11 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
|
|||||||
export const getServerSideProps = async (ctx: Context) => {
|
export const getServerSideProps = async (ctx: Context) => {
|
||||||
const parsed = parseCookie(ctx.req)
|
const parsed = parseCookie(ctx.req)
|
||||||
const user = await get.Authorization(parsed?.token)
|
const user = await get.Authorization(parsed?.token)
|
||||||
|
const bot = await get.bot.load(ctx.query.id)
|
||||||
|
const spec = await get.botSpec(bot?.id || '', user || '')
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
bot: await get.bot.load(ctx.query.id),
|
bot: { ...bot, banner: spec?.banner || null, bg: spec?.bg || null },
|
||||||
user: await get.user.load(user || ''),
|
user: await get.user.load(user || ''),
|
||||||
csrfToken: getToken(ctx.req, ctx.res),
|
csrfToken: getToken(ctx.req, ctx.res),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
import { NextPage } from 'next'
|
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
|
|
||||||
const Reserved: NextPage = () => {
|
|
||||||
const router = useRouter()
|
|
||||||
router.push('/bots/iu')
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Reserved
|
|
||||||
@ -42,7 +42,8 @@ const Servers: NextPage<ServersProps> = ({ data, desc, date, user, theme }) => {
|
|||||||
const [emojisModal, setEmojisModal] = useState(false)
|
const [emojisModal, setEmojisModal] = useState(false)
|
||||||
const [ownersModal, setOwnersModal] = useState(false)
|
const [ownersModal, setOwnersModal] = useState(false)
|
||||||
const [owners, setOwners] = useState<User[]>(null)
|
const [owners, setOwners] = useState<User[]>(null)
|
||||||
const bg = checkBotFlag(data?.flags, 'trusted') && data?.banner
|
const bg =
|
||||||
|
(checkBotFlag(data?.flags, 'trusted') || checkBotFlag(data?.flags, 'partnered')) && data?.bg
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data)
|
if (data)
|
||||||
@ -143,7 +144,12 @@ const Servers: NextPage<ServersProps> = ({ data, desc, date, user, theme }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className='w-full lg:flex'>
|
<div className='w-full lg:flex'>
|
||||||
<div className='w-full text-center lg:w-2/12'>
|
<div className='w-full text-center lg:w-2/12'>
|
||||||
<ServerIcon id={data.id} size={256} className='w-full rounded-full' hash={data.icon} />
|
<ServerIcon
|
||||||
|
id={data.id}
|
||||||
|
size={256}
|
||||||
|
className='w-full rounded-full'
|
||||||
|
hash={data.icon}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full grow px-5 py-12 text-center lg:w-5/12 lg:text-left'>
|
<div className='w-full grow px-5 py-12 text-center lg:w-5/12 lg:text-left'>
|
||||||
<h1 className='mb-2 mt-3 text-4xl font-bold' style={bg ? { color: 'white' } : {}}>
|
<h1 className='mb-2 mt-3 text-4xl font-bold' style={bg ? { color: 'white' } : {}}>
|
||||||
|
|||||||
@ -89,6 +89,8 @@ export interface BotSpec {
|
|||||||
webhookURL: string | null
|
webhookURL: string | null
|
||||||
webhookStatus: WebhookStatus
|
webhookStatus: WebhookStatus
|
||||||
token: string
|
token: string
|
||||||
|
banner: string | null
|
||||||
|
bg: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerSpec {
|
export interface ServerSpec {
|
||||||
@ -351,7 +353,7 @@ export interface ImageOptions {
|
|||||||
export interface KoreanbotsImageOptions {
|
export interface KoreanbotsImageOptions {
|
||||||
format?: 'webp' | 'png' | 'gif'
|
format?: 'webp' | 'png' | 'gif'
|
||||||
size?: 128 | 256 | 512
|
size?: 128 | 256 | 512
|
||||||
hash?: string;
|
hash?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DiscordImageType {
|
export enum DiscordImageType {
|
||||||
|
|||||||
@ -626,3 +626,10 @@ export const GuildPermissions = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const reservedVanityBypass = [
|
||||||
|
'653534001742741552',
|
||||||
|
'784618064167698472',
|
||||||
|
'653083797763522580',
|
||||||
|
'807561475014262785',
|
||||||
|
]
|
||||||
|
|||||||
@ -57,6 +57,10 @@ export const webhookClients = {
|
|||||||
{ url: process.env.REPORT_WEBHOOK_URL ?? dummyURL },
|
{ url: process.env.REPORT_WEBHOOK_URL ?? dummyURL },
|
||||||
{ allowedMentions: { parse: [] } }
|
{ allowedMentions: { parse: [] } }
|
||||||
),
|
),
|
||||||
|
noticeLog: new Discord.WebhookClient(
|
||||||
|
{ url: process.env.NOTICE_LOG_WEBHOOK_URL ?? dummyURL },
|
||||||
|
{ allowedMentions: { parse: [] } }
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -100,6 +100,8 @@ async function getBot(id: string, topLevel = true): Promise<Bot> {
|
|||||||
res.name = name
|
res.name = name
|
||||||
res.category = JSON.parse(res.category)
|
res.category = JSON.parse(res.category)
|
||||||
res.owners = JSON.parse(res.owners)
|
res.owners = JSON.parse(res.owners)
|
||||||
|
res.banner = res.banner ? camoUrl(res.banner) : null
|
||||||
|
res.bg = res.bg ? camoUrl(res.bg) : null
|
||||||
|
|
||||||
if (discordBot.flags.bitfield & UserFlags.BotHTTPInteractions) {
|
if (discordBot.flags.bitfield & UserFlags.BotHTTPInteractions) {
|
||||||
res.status = 'online'
|
res.status = 'online'
|
||||||
@ -676,7 +678,14 @@ async function submitServer(
|
|||||||
|
|
||||||
async function getBotSpec(id: string, userID: string): Promise<BotSpec | null> {
|
async function getBotSpec(id: string, userID: string): Promise<BotSpec | null> {
|
||||||
const res = await knex('bots')
|
const res = await knex('bots')
|
||||||
.select(['bots.id', 'bots.token', 'bots.webhook_url', 'bots.webhook_status'])
|
.select([
|
||||||
|
'bots.id',
|
||||||
|
'bots.token',
|
||||||
|
'bots.webhook_url',
|
||||||
|
'bots.webhook_status',
|
||||||
|
'bots.banner',
|
||||||
|
'bots.bg',
|
||||||
|
])
|
||||||
.leftJoin('owners_mapping', 'bots.id', 'owners_mapping.target_id')
|
.leftJoin('owners_mapping', 'bots.id', 'owners_mapping.target_id')
|
||||||
.where('owners_mapping.user_id', userID)
|
.where('owners_mapping.user_id', userID)
|
||||||
.andWhere('owners_mapping.type', ObjectType.Bot)
|
.andWhere('owners_mapping.type', ObjectType.Bot)
|
||||||
@ -688,6 +697,8 @@ async function getBotSpec(id: string, userID: string): Promise<BotSpec | null> {
|
|||||||
token: res[0].token,
|
token: res[0].token,
|
||||||
webhookURL: res[0].webhook_url,
|
webhookURL: res[0].webhook_url,
|
||||||
webhookStatus: res[0].webhook_status,
|
webhookStatus: res[0].webhook_status,
|
||||||
|
banner: res[0].banner,
|
||||||
|
bg: res[0].bg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -733,6 +744,9 @@ async function updateBot(id: string, data: ManageBot): Promise<number> {
|
|||||||
category: JSON.stringify(data.category),
|
category: JSON.stringify(data.category),
|
||||||
intro: data.intro,
|
intro: data.intro,
|
||||||
desc: data.desc,
|
desc: data.desc,
|
||||||
|
vanity: data.vanity,
|
||||||
|
banner: data.banner,
|
||||||
|
bg: data.bg,
|
||||||
})
|
})
|
||||||
.where({ id })
|
.where({ id })
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,12 @@
|
|||||||
import urlRegex from 'url-regex-safe'
|
import urlRegex from 'url-regex-safe'
|
||||||
|
const reservedVanityConst = [
|
||||||
|
'koreanbots',
|
||||||
|
'koreanservers',
|
||||||
|
'koreanlist',
|
||||||
|
'kbots',
|
||||||
|
'kodl',
|
||||||
|
'discord',
|
||||||
|
]
|
||||||
|
|
||||||
export const ID = /^[0-9]{17,}$/
|
export const ID = /^[0-9]{17,}$/
|
||||||
export const Vanity = /^[A-Za-z\d-]+$/
|
export const Vanity = /^[A-Za-z\d-]+$/
|
||||||
@ -12,3 +20,4 @@ export const Heading = '<h\\d id="(.+?)">(.*?)<\\/h(\\d)>'
|
|||||||
export const EmojiSyntax = ':(\\w+):'
|
export const EmojiSyntax = ':(\\w+):'
|
||||||
export const ImageTag = /<img\s[^>]*?alt\s*=\s*['"]([^'"]*?)['"][^>]*?>/
|
export const ImageTag = /<img\s[^>]*?alt\s*=\s*['"]([^'"]*?)['"][^>]*?>/
|
||||||
export const markdownImage = /!\[([^\]]*)\]\((.*?)\s*("(?:.*[^"])")?\s*\)/g
|
export const markdownImage = /!\[([^\]]*)\]\((.*?)\s*("(?:.*[^"])")?\s*\)/g
|
||||||
|
export const reservedVanity = new RegExp(`^((?!${reservedVanityConst.join('|')}).)*$`, 'i') // 예약되지 않음을 확인
|
||||||
|
|||||||
36
utils/Yup.ts
36
utils/Yup.ts
@ -1,8 +1,14 @@
|
|||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import YupKorean from 'yup-locales-ko'
|
import YupKorean from 'yup-locales-ko'
|
||||||
import { ListType } from '@types'
|
import { ListType } from '@types'
|
||||||
import { botCategories, library, reportCats, serverCategories } from '@utils/Constants'
|
import {
|
||||||
import { HTTPProtocol, ID, Prefix, Url, Vanity } from '@utils/Regex'
|
botCategories,
|
||||||
|
library,
|
||||||
|
reportCats,
|
||||||
|
reservedVanityBypass,
|
||||||
|
serverCategories,
|
||||||
|
} from '@utils/Constants'
|
||||||
|
import { HTTPProtocol, ID, Prefix, reservedVanity, Url, Vanity } from '@utils/Regex'
|
||||||
|
|
||||||
Yup.setLocale(YupKorean)
|
Yup.setLocale(YupKorean)
|
||||||
Yup.addMethod(Yup.array, 'unique', function (message, mapper = (a) => a) {
|
Yup.addMethod(Yup.array, 'unique', function (message, mapper = (a) => a) {
|
||||||
@ -296,6 +302,29 @@ export const ManageBotSchema: Yup.SchemaOf<ManageBot> = Yup.object({
|
|||||||
.min(100, '봇 설명은 최소 100자여야합니다.')
|
.min(100, '봇 설명은 최소 100자여야합니다.')
|
||||||
.max(1500, '봇 설명은 최대 1500자여야합니다.')
|
.max(1500, '봇 설명은 최대 1500자여야합니다.')
|
||||||
.required('봇 설명은 필수 항목입니다.'),
|
.required('봇 설명은 필수 항목입니다.'),
|
||||||
|
vanity: Yup.string()
|
||||||
|
.matches(Vanity, '커스텀 URL은 영문만 포함할 수 있습니다.')
|
||||||
|
.when('id', {
|
||||||
|
is: (id: string) => reservedVanityBypass.includes(id),
|
||||||
|
then: Yup.string(),
|
||||||
|
otherwise: Yup.string().matches(
|
||||||
|
reservedVanity,
|
||||||
|
'예약어가 포함되었거나 사용할 수 없는 커스텀 URL입니다.'
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.min(2, '커스텀 URL은 최소 2자여야합니다.')
|
||||||
|
.max(32, '커스텀 URL은 최대 32자여야합니다.')
|
||||||
|
.nullable(),
|
||||||
|
banner: Yup.string()
|
||||||
|
.matches(HTTPProtocol, 'http:// 또는 https:// 로 시작해야합니다.')
|
||||||
|
.matches(Url, '올바른 배너 URL을 입력해주세요.')
|
||||||
|
.max(612, 'URL은 최대 612자까지만 가능합니다.')
|
||||||
|
.nullable(),
|
||||||
|
bg: Yup.string()
|
||||||
|
.matches(HTTPProtocol, 'http:// 또는 https:// 로 시작해야합니다.')
|
||||||
|
.matches(Url, '올바른 배경 URL을 입력해주세요.')
|
||||||
|
.max(612, 'URL은 최대 612자까지만 가능합니다.')
|
||||||
|
.nullable(),
|
||||||
_csrf: Yup.string().required(),
|
_csrf: Yup.string().required(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -309,6 +338,9 @@ export interface ManageBot {
|
|||||||
category: string[]
|
category: string[]
|
||||||
intro: string
|
intro: string
|
||||||
desc: string
|
desc: string
|
||||||
|
vanity: string
|
||||||
|
banner: string
|
||||||
|
bg: string
|
||||||
_csrf: string
|
_csrf: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user