diff --git a/components/Form/Label.tsx b/components/Form/Label.tsx index 3e230be..2c24a2e 100644 --- a/components/Form/Label.tsx +++ b/components/Form/Label.tsx @@ -21,7 +21,7 @@ const Label: React.FC = ({ * )} - {labelDesc} + {labelDesc} )}
diff --git a/components/Form/Selects.tsx b/components/Form/Selects.tsx index b4118aa..90b99ff 100644 --- a/components/Form/Selects.tsx +++ b/components/Form/Selects.tsx @@ -109,7 +109,10 @@ const Select: React.FC = ({ onChange={handleChange} onBlur={handleTouch} noOptionsMessage={() => '검색 결과가 없습니다.'} - value={values.map((el) => ({ label: el, value: el }))} + value={values.map((el) => ({ + label: Object.values(options).find(({ value }) => value === el)?.label || el, + value: el, + }))} components={{ MultiValue, MultiValueRemove, diff --git a/pages/addbot.tsx b/pages/addbot.tsx index da86d1c..beb642d 100644 --- a/pages/addbot.tsx +++ b/pages/addbot.tsx @@ -10,7 +10,7 @@ import HCaptcha from '@hcaptcha/react-hcaptcha' import { get } from '@utils/Query' import { cleanObject, parseCookie, redirectTo } from '@utils/Tools' import { AddBotSubmit, AddBotSubmitSchema } from '@utils/Yup' -import { botCategories, botCategoryDescription, library } from '@utils/Constants' +import { botCategories, botCategoryDescription, botEnforcements, library } from '@utils/Constants' import { getToken } from '@utils/Csrf' import Fetch from '@utils/Fetch' import { ResponseProps, SubmittedBot, Theme, User } from '@types' @@ -57,6 +57,7 @@ const AddBot: NextPage = ({ logged, user, csrfToken, theme }) => { - 어떤 - 기능 - 있나요?`, + enforcements: [], _csrf: csrfToken, _captcha: 'captcha', } @@ -356,6 +357,34 @@ const AddBot: NextPage = ({ logged, user, csrfToken, theme }) => { + +

* = 필수 항목

diff --git a/pages/api/v2/bots/[id]/index.ts b/pages/api/v2/bots/[id]/index.ts index 0804870..b582421 100644 --- a/pages/api/v2/bots/[id]/index.ts +++ b/pages/api/v2/bots/[id]/index.ts @@ -246,28 +246,29 @@ const Bots = RequestHandler() 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, - }, - ], - }) + if (validated.vanity !== bot.vanity) { + 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) if (result === 0) return ResponseWrapper(res, { code: 400 }) @@ -289,6 +290,7 @@ const Bots = RequestHandler() category: JSON.stringify(bot.category), vanity: bot.vanity, banner: bot.banner, + enforcements: JSON.stringify(bot.enforcements), bg: bot.bg, }, { @@ -302,6 +304,7 @@ const Bots = RequestHandler() category: JSON.stringify(validated.category), vanity: validated.vanity, banner: validated.banner, + enforcements: JSON.stringify(validated.enforcements), bg: validated.bg, } ) diff --git a/pages/bots/[id]/edit.tsx b/pages/bots/[id]/edit.tsx index 55bbc2e..4dbf03b 100644 --- a/pages/bots/[id]/edit.tsx +++ b/pages/bots/[id]/edit.tsx @@ -9,9 +9,16 @@ import { ParsedUrlQuery } from 'querystring' import { getJosaPicker } from 'josa' import { get } from '@utils/Query' -import { checkBotFlag, checkUserFlag, cleanObject, makeBotURL, parseCookie, redirectTo } from '@utils/Tools' +import { + checkBotFlag, + checkUserFlag, + cleanObject, + makeBotURL, + parseCookie, + redirectTo, +} from '@utils/Tools' import { ManageBot, getManageBotSchema } from '@utils/Yup' -import { botCategories, botCategoryDescription, library } from '@utils/Constants' +import { botCategories, botCategoryDescription, botEnforcements, library } from '@utils/Constants' import { Bot, Theme, User } from '@types' import { getToken } from '@utils/Csrf' import Fetch from '@utils/Fetch' @@ -82,6 +89,7 @@ const ManageBotPage: NextPage = ({ bot, user, csrfToken, theme } prefix: bot.prefix, library: bot.lib, category: bot.category, + enforcements: bot.enforcements, intro: bot.intro, desc: bot.desc, website: bot.web, @@ -98,8 +106,12 @@ const ManageBotPage: NextPage = ({ bot, user, csrfToken, theme } > {({ errors, touched, values, setFieldTouched, setFieldValue }) => (
-
- +
+

{bot.name}#{bot.tag} @@ -165,7 +177,11 @@ const ManageBotPage: NextPage = ({ bot, user, csrfToken, theme } error={errors.category && touched.category ? (errors.category as string) : null} > ({ label: el, value: el, description: botCategoryDescription[el] }))} + options={botCategories.map((el) => ({ + label: el, + value: el, + description: botCategoryDescription[el], + }))} handleChange={(value) => { setFieldValue( 'category', @@ -273,24 +289,26 @@ const ManageBotPage: NextPage = ({ bot, user, csrfToken, theme } - { - isPerkAvailable && ( - <> + {isPerkAvailable && ( + <> -

신뢰된 봇 특전 설정

- 신뢰된 봇의 혜택을 만나보세요. (커스텀 URL과 배너/배경 이미지는 이용약관 및 가이드라인을 준수해야하며 위반 시 신뢰된 봇 자격이 박탈될 수 있습니다.) +

+ 신뢰된 봇 특전 설정 +

+ + 신뢰된 봇의 혜택을 만나보세요. (커스텀 URL과 배너/배경 이미지는 이용약관 및 + 가이드라인을 준수해야하며 위반 시 신뢰된 봇 자격이 박탈될 수 있습니다.) + - - ) - } + + )} + +

* = 필수 항목 @@ -320,7 +365,6 @@ const ManageBotPage: NextPage = ({ bot, user, csrfToken, theme } 저장 - )} diff --git a/pages/bots/[id]/index.tsx b/pages/bots/[id]/index.tsx index b5bcf8a..b342930 100644 --- a/pages/bots/[id]/index.tsx +++ b/pages/bots/[id]/index.tsx @@ -11,7 +11,7 @@ import { SnowflakeUtil } from 'discord.js' import { ParsedUrlQuery } from 'querystring' import { Bot, ResponseProps, Theme, User } from '@types' -import { git, KoreanbotsEndPoints, reportCats, Status } from '@utils/Constants' +import { botEnforcements, git, KoreanbotsEndPoints, reportCats, Status } from '@utils/Constants' import { get } from '@utils/Query' import Day from '@utils/Day' import { ReportSchema } from '@utils/Yup' @@ -116,13 +116,27 @@ const Bots: NextPage = ({ data, desc, date, user, theme, csrfToken }) 로 문의해주세요.

+ ) : data.enforcements.length > 0 ? ( + +

이 봇은 기능에 제한을 두고 있습니다.

+

+ {data.enforcements.map((i) => ( +

  • {botEnforcements[i].description}
  • + ))} +

    +
    ) : ( '' )}

    - +
    = ({ data, desc, date, user, theme, csrfToken })

    - {(data.state === 'ok' && !checkBotFlag(data.flags, 'private')) && ( + {data.state === 'ok' && !checkBotFlag(data.flags, 'private') && (

    초대하기 diff --git a/types/index.ts b/types/index.ts index a8d7295..ce349da 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,9 +1,12 @@ +import { botEnforcements } from '@utils/Constants' import type { GuildFeature } from 'discord.js' import type { IncomingMessage } from 'http' import type { NextPageContext } from 'next' export type Nullable = T | null +export type ValueOf = T[keyof T] + export interface Bot { id: string name: string @@ -25,11 +28,14 @@ export interface Bot { url: string | null discord: string | null vanity: string | null + enforcements: BotEnforcementKeys[] bg: string banner: string owners: User[] | string[] } +export type BotEnforcementKeys = keyof typeof botEnforcements + export interface RawGuild { id: string name: string diff --git a/utils/Constants.ts b/utils/Constants.ts index 2460d14..fbda298 100644 --- a/utils/Constants.ts +++ b/utils/Constants.ts @@ -120,6 +120,25 @@ export const botCategoryDescription = { 마인크래프트: '게임 "마인크래프트"에 관련된 기능을 다룹니다.', } +export const botEnforcements = { + JOIN_PARTIALLY_ENFORCED: { + label: '서버 참여가 필요한 기능이 있습니다', + description: '봇의 일부 명령어는 봇의 디스코드 서버에 참여해야 사용할 수 있습니다.', + }, + JOIN_ENFORCED: { + label: '서버 참여 없이는 봇의 핵심 기능을 사용할 수 없습니다', + description: '봇의 핵심 기능은 봇의 디스코드 서버에 참여해야 사용할 수 있습니다.', + }, + LICENSE_PARTIALLY_ENFORCED: { + label: '유료 구매가 필요한 기능이 있습니다', + description: '봇의 일부 명령어는 유료 구매가 필요합니다.', + }, + LICENSE_ENFORCED: { + label: '유료 구매 없이는 봇의 핵심 기능을 사용할 수 없습니다', + description: '유료 구매 없이는 봇의 핵심 기능을 사용할 수 없습니다.', + }, +} as const + export const botCategoryIcon = { 관리: 'fas fa-cogs', 뮤직: 'fas fa-music', diff --git a/utils/Query.ts b/utils/Query.ts index 07a49cc..ce26ab4 100644 --- a/utils/Query.ts +++ b/utils/Query.ts @@ -62,6 +62,7 @@ async function getBot(id: string, topLevel = true): Promise { 'bots.status', 'bots.trusted', 'bots.partnered', + 'bots.enforcements', 'bots.discord', 'bots.state', 'bots.vanity', @@ -102,7 +103,7 @@ async function getBot(id: string, topLevel = true): Promise { res.owners = JSON.parse(res.owners) res.banner = res.banner ? camoUrl(res.banner) : null res.bg = res.bg ? camoUrl(res.bg) : null - + res.enforcements = JSON.parse(res.enforcements ?? '[]') if (discordBot.flags.bitfield & UserFlags.BotHTTPInteractions) { res.status = 'online' } else if (botMember) { @@ -448,6 +449,7 @@ async function getBotSubmit(id: string, date: number): Promise { 'id', 'date', 'category', + 'enforcements', 'lib', 'prefix', 'intro', @@ -463,6 +465,7 @@ async function getBotSubmit(id: string, date: number): Promise { .where({ id, date }) if (res.length === 0) return null res[0].category = JSON.parse(res[0].category) + res[0].enforcements = JSON.parse(res[0].enforcements || '[]') res[0].owner = await get.user.load(res[0].owner) return res[0] } @@ -474,6 +477,7 @@ async function getBotSubmits(id: string): Promise { 'id', 'date', 'category', + 'enforcements', 'lib', 'prefix', 'intro', @@ -492,6 +496,7 @@ async function getBotSubmits(id: string): Promise { res = await Promise.all( res.map(async (el) => { el.category = JSON.parse(el.category) + el.enforcements = JSON.parse(el.enforcements) el.owner = owner return el }) @@ -636,6 +641,7 @@ async function submitBot( git: data.git, url: data.url, category: JSON.stringify(data.category), + enforcements: JSON.stringify(data.enforcements), discord: data.discord, state: 0, }) @@ -745,6 +751,7 @@ async function updateBot(id: string, data: ManageBot): Promise { intro: data.intro, desc: data.desc, vanity: data.vanity, + enforcements: JSON.stringify(data.enforcements), banner: data.banner, bg: data.bg, }) @@ -1134,6 +1141,7 @@ async function approveBotSubmission(id: string, date: number) { 'id', 'date', 'category', + 'enforcements', 'lib', 'prefix', 'intro', @@ -1160,6 +1168,7 @@ async function approveBotSubmission(id: string, date: number) { web: data.web, git: data.git, category: data.category, + enforcements: data.enforcements, discord: data.discord, token: sign({ id }), }) diff --git a/utils/Yup.ts b/utils/Yup.ts index 91d984c..7f73e87 100644 --- a/utils/Yup.ts +++ b/utils/Yup.ts @@ -3,6 +3,7 @@ import YupKorean from 'yup-locales-ko' import { ListType } from '@types' import { botCategories, + botEnforcements, library, reportCats, reservedVanityBypass, @@ -174,6 +175,7 @@ export const AddBotSubmitSchema: Yup.SchemaOf = Yup.object({ .min(100, '봇 설명은 최소 100자여야합니다.') .max(1500, '봇 설명은 최대 1500자여야합니다.') .required('봇 설명은 필수 항목입니다.'), + enforcements: Yup.array(Yup.string().oneOf(Object.keys(botEnforcements))), _csrf: Yup.string().required(), _captcha: Yup.string().required(), }) @@ -190,6 +192,7 @@ export interface AddBotSubmit { category: string | string[] intro: string desc: string + enforcements: string[] _csrf: string _captcha: string } @@ -303,6 +306,7 @@ export function getManageBotSchema(perkAvailable = false) { .min(100, '봇 설명은 최소 100자여야합니다.') .max(1500, '봇 설명은 최대 1500자여야합니다.') .required('봇 설명은 필수 항목입니다.'), + enforcements: Yup.array(Yup.string().oneOf(Object.keys(botEnforcements))), _csrf: Yup.string().required(), } @@ -349,6 +353,7 @@ export interface ManageBot { desc: string vanity: string banner: string + enforcements: string[] bg: string _csrf: string }