Skip to content

Commit 218e790

Browse files
feat: 강제사항 명시 (#687)
* feat: add enforcements field * feat: add alert page * chore: specify enforcement description * fix: do not send vanity log when no changes are made * chore: add license related enforcements * fix: typo * refactor: make botEnforcements more maintainable * refactor: do not filter keys to display enforcements * fix: typo
1 parent 843e413 commit 218e790

File tree

10 files changed

+177
-45
lines changed

10 files changed

+177
-45
lines changed

components/Form/Label.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const Label: React.FC<LabelProps> = ({
2121
<span className='align-text-top text-base font-semibold text-red-500'> *</span>
2222
)}
2323
</h3>
24-
{labelDesc}
24+
<span className='whitespace-pre-line'>{labelDesc}</span>
2525
</div>
2626
)}
2727
<div className={short ? 'col-span-1' : 'col-span-3'}>

components/Form/Selects.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,10 @@ const Select: React.FC<SelectProps> = ({
109109
onChange={handleChange}
110110
onBlur={handleTouch}
111111
noOptionsMessage={() => '검색 결과가 없습니다.'}
112-
value={values.map((el) => ({ label: el, value: el }))}
112+
value={values.map((el) => ({
113+
label: Object.values(options).find(({ value }) => value === el)?.label || el,
114+
value: el,
115+
}))}
113116
components={{
114117
MultiValue,
115118
MultiValueRemove,

pages/addbot.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import HCaptcha from '@hcaptcha/react-hcaptcha'
1010
import { get } from '@utils/Query'
1111
import { cleanObject, parseCookie, redirectTo } from '@utils/Tools'
1212
import { AddBotSubmit, AddBotSubmitSchema } from '@utils/Yup'
13-
import { botCategories, botCategoryDescription, library } from '@utils/Constants'
13+
import { botCategories, botCategoryDescription, botEnforcements, library } from '@utils/Constants'
1414
import { getToken } from '@utils/Csrf'
1515
import Fetch from '@utils/Fetch'
1616
import { ResponseProps, SubmittedBot, Theme, User } from '@types'
@@ -57,6 +57,7 @@ const AddBot: NextPage<AddBotProps> = ({ logged, user, csrfToken, theme }) => {
5757
- 어떤
5858
- 기능
5959
- 있나요?`,
60+
enforcements: [],
6061
_csrf: csrfToken,
6162
_captcha: 'captcha',
6263
}
@@ -356,6 +357,34 @@ const AddBot: NextPage<AddBotProps> = ({ logged, user, csrfToken, theme }) => {
356357
</Segment>
357358
</Label>
358359
<Divider />
360+
<Label
361+
For='enforcements'
362+
label='필수 고지 내용'
363+
labelDesc='내용에 해당하는 경우 필수로 선택해야 합니다.'
364+
required
365+
error={
366+
errors.enforcements && touched.enforcements ? (errors.enforcements as string) : null
367+
}
368+
>
369+
<Selects
370+
options={Object.entries(botEnforcements).map(([k, v]) => ({
371+
label: v.label,
372+
value: k,
373+
}))}
374+
handleChange={(value) => {
375+
setFieldValue(
376+
'enforcements',
377+
value.map((v) => v.value)
378+
)
379+
}}
380+
handleTouch={() => setFieldTouched('enforcements', true)}
381+
values={values.enforcements ?? ([] as string[])}
382+
setValues={(value) => {
383+
setFieldValue('enforcements', value)
384+
}}
385+
/>
386+
</Label>
387+
<Divider />
359388
<p className='mb-5 mt-2 text-base'>
360389
<span className='font-semibold text-red-500'> *</span> = 필수 항목
361390
</p>

pages/api/v2/bots/[id]/index.ts

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -246,28 +246,29 @@ const Bots = RequestHandler()
246246
errors: ['다른 커스텀 URL로 다시 시도해주세요.'],
247247
})
248248
}
249-
250-
await webhookClients.internal.noticeLog.send({
251-
embeds: [
252-
{
253-
title: '한디리 커스텀 URL 변경',
254-
description: `봇: ${bot.name} - <@${bot.id}> ([${bot.id}](${KoreanbotsEndPoints.URL.bot(
255-
bot.id
256-
)}))`,
257-
fields: [
258-
{
259-
name: '이전',
260-
value: bot.vanity || '없음',
261-
},
262-
{
263-
name: '이후',
264-
value: validated.vanity || '없음',
265-
},
266-
],
267-
color: Colors.Blue,
268-
},
269-
],
270-
})
249+
if (validated.vanity !== bot.vanity) {
250+
await webhookClients.internal.noticeLog.send({
251+
embeds: [
252+
{
253+
title: '한디리 커스텀 URL 변경',
254+
description: `봇: ${bot.name} - <@${bot.id}> ([${
255+
bot.id
256+
}](${KoreanbotsEndPoints.URL.bot(bot.id)}))`,
257+
fields: [
258+
{
259+
name: '이전',
260+
value: bot.vanity || '없음',
261+
},
262+
{
263+
name: '이후',
264+
value: validated.vanity || '없음',
265+
},
266+
],
267+
color: Colors.Blue,
268+
},
269+
],
270+
})
271+
}
271272
}
272273
const result = await update.bot(req.query.id, validated)
273274
if (result === 0) return ResponseWrapper(res, { code: 400 })
@@ -289,6 +290,7 @@ const Bots = RequestHandler()
289290
category: JSON.stringify(bot.category),
290291
vanity: bot.vanity,
291292
banner: bot.banner,
293+
enforcements: JSON.stringify(bot.enforcements),
292294
bg: bot.bg,
293295
},
294296
{
@@ -302,6 +304,7 @@ const Bots = RequestHandler()
302304
category: JSON.stringify(validated.category),
303305
vanity: validated.vanity,
304306
banner: validated.banner,
307+
enforcements: JSON.stringify(validated.enforcements),
305308
bg: validated.bg,
306309
}
307310
)

pages/bots/[id]/edit.tsx

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,16 @@ import { ParsedUrlQuery } from 'querystring'
99
import { getJosaPicker } from 'josa'
1010

1111
import { get } from '@utils/Query'
12-
import { checkBotFlag, checkUserFlag, cleanObject, makeBotURL, parseCookie, redirectTo } from '@utils/Tools'
12+
import {
13+
checkBotFlag,
14+
checkUserFlag,
15+
cleanObject,
16+
makeBotURL,
17+
parseCookie,
18+
redirectTo,
19+
} from '@utils/Tools'
1320
import { ManageBot, getManageBotSchema } from '@utils/Yup'
14-
import { botCategories, botCategoryDescription, library } from '@utils/Constants'
21+
import { botCategories, botCategoryDescription, botEnforcements, library } from '@utils/Constants'
1522
import { Bot, Theme, User } from '@types'
1623
import { getToken } from '@utils/Csrf'
1724
import Fetch from '@utils/Fetch'
@@ -82,6 +89,7 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
8289
prefix: bot.prefix,
8390
library: bot.lib,
8491
category: bot.category,
92+
enforcements: bot.enforcements,
8593
intro: bot.intro,
8694
desc: bot.desc,
8795
website: bot.web,
@@ -98,8 +106,12 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
98106
>
99107
{({ errors, touched, values, setFieldTouched, setFieldValue }) => (
100108
<Form>
101-
<div className='text-center md:flex md:text-left'>
102-
<DiscordAvatar userID={bot.id} className='mx-auto rounded-full md:mx-1' hash={bot.avatar}/>
109+
<div className='text-ceznter md:flex md:text-left'>
110+
<DiscordAvatar
111+
userID={bot.id}
112+
className='mx-auto rounded-full md:mx-1'
113+
hash={bot.avatar}
114+
/>
103115
<div className='px-8 py-6 md:w-2/3'>
104116
<h1 className='text-3xl font-bold'>
105117
{bot.name}#{bot.tag}
@@ -165,7 +177,11 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
165177
error={errors.category && touched.category ? (errors.category as string) : null}
166178
>
167179
<Selects
168-
options={botCategories.map((el) => ({ label: el, value: el, description: botCategoryDescription[el] }))}
180+
options={botCategories.map((el) => ({
181+
label: el,
182+
value: el,
183+
description: botCategoryDescription[el],
184+
}))}
169185
handleChange={(value) => {
170186
setFieldValue(
171187
'category',
@@ -273,24 +289,26 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
273289
<Markdown text={values.desc} />
274290
</Segment>
275291
</Label>
276-
{
277-
isPerkAvailable && (
278-
<>
292+
{isPerkAvailable && (
293+
<>
279294
<Divider />
280-
<h2 className='pt-2 text-2xl font-semibold text-koreanbots-green'>신뢰된 봇 특전 설정</h2>
281-
<span className='mt-1 text-sm text-gray-400'>신뢰된 봇의 혜택을 만나보세요. (커스텀 URL과 배너/배경 이미지는 이용약관 및 가이드라인을 준수해야하며 위반 시 신뢰된 봇 자격이 박탈될 수 있습니다.)</span>
295+
<h2 className='pt-2 text-2xl font-semibold text-koreanbots-green'>
296+
신뢰된 봇 특전 설정
297+
</h2>
298+
<span className='mt-1 text-sm text-gray-400'>
299+
신뢰된 봇의 혜택을 만나보세요. (커스텀 URL과 배너/배경 이미지는 이용약관 및
300+
가이드라인을 준수해야하며 위반 시 신뢰된 봇 자격이 박탈될 수 있습니다.)
301+
</span>
282302
<Label
283303
For='vanity'
284304
label='한디리 커스텀 URL'
285305
labelDesc='고유한 커스텀 URL을 설정해주세요.'
286306
error={errors.vanity && touched.vanity ? errors.vanity : null}
287-
288307
>
289308
<div className='flex items-center'>
290309
koreanbots.dev/bots/
291310
<Input name='vanity' placeholder='koreanbots' />
292311
</div>
293-
294312
</Label>
295313
<Label
296314
For='banner'
@@ -308,9 +326,36 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
308326
>
309327
<Input name='bg' placeholder='https://koreanbots.dev/logo.png' />
310328
</Label>
311-
</>
312-
)
313-
}
329+
</>
330+
)}
331+
<Divider />
332+
<Label
333+
For='enforcements'
334+
label='필수 고지 내용'
335+
labelDesc='내용에 해당하는 경우 필수로 선택해야 합니다.'
336+
required
337+
error={
338+
errors.enforcements && touched.enforcements ? (errors.enforcements as string) : null
339+
}
340+
>
341+
<Selects
342+
options={Object.entries(botEnforcements).map(([k, v]) => ({
343+
label: v.label,
344+
value: k,
345+
}))}
346+
handleChange={(value) => {
347+
setFieldValue(
348+
'enforcements',
349+
value.map((v) => v.value)
350+
)
351+
}}
352+
handleTouch={() => setFieldTouched('enforcements', true)}
353+
values={values.enforcements ?? ([] as string[])}
354+
setValues={(value) => {
355+
setFieldValue('enforcements', value)
356+
}}
357+
/>
358+
</Label>
314359
<Divider />
315360
<p className='mb-5 mt-2 text-base'>
316361
<span className='font-semibold text-red-500'> *</span> = 필수 항목
@@ -320,7 +365,6 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
320365
<i className='far fa-save' /> 저장
321366
</>
322367
</Button>
323-
324368
</Form>
325369
)}
326370
</Formik>

pages/bots/[id]/index.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { SnowflakeUtil } from 'discord.js'
1111
import { ParsedUrlQuery } from 'querystring'
1212
import { Bot, ResponseProps, Theme, User } from '@types'
1313

14-
import { git, KoreanbotsEndPoints, reportCats, Status } from '@utils/Constants'
14+
import { botEnforcements, git, KoreanbotsEndPoints, reportCats, Status } from '@utils/Constants'
1515
import { get } from '@utils/Query'
1616
import Day from '@utils/Day'
1717
import { ReportSchema } from '@utils/Yup'
@@ -116,13 +116,27 @@ const Bots: NextPage<BotsProps> = ({ data, desc, date, user, theme, csrfToken })
116116
로 문의해주세요.
117117
</p>
118118
</Message>
119+
) : data.enforcements.length > 0 ? (
120+
<Message type='warning'>
121+
<h2 className='text-lg font-extrabold'>이 봇은 기능에 제한을 두고 있습니다.</h2>
122+
<p>
123+
{data.enforcements.map((i) => (
124+
<li key={i}>{botEnforcements[i].description}</li>
125+
))}
126+
</p>
127+
</Message>
119128
) : (
120129
''
121130
)}
122131
</div>
123132
<div className='w-full lg:flex'>
124133
<div className='w-full text-center lg:w-2/12'>
125-
<DiscordAvatar userID={data.id} size={256} className='w-full rounded-full' hash={data.avatar}/>
134+
<DiscordAvatar
135+
userID={data.id}
136+
size={256}
137+
className='w-full rounded-full'
138+
hash={data.avatar}
139+
/>
126140
</div>
127141
<div className='w-full grow px-5 py-12 text-center lg:w-5/12 lg:text-left'>
128142
<Tag
@@ -158,7 +172,7 @@ const Bots: NextPage<BotsProps> = ({ data, desc, date, user, theme, csrfToken })
158172
</p>
159173
</div>
160174
<div className='w-full lg:w-1/4'>
161-
{(data.state === 'ok' && !checkBotFlag(data.flags, 'private')) && (
175+
{data.state === 'ok' && !checkBotFlag(data.flags, 'private') && (
162176
<LongButton newTab href={`/bots/${router.query.id}/invite`}>
163177
<h4 className='whitespace-nowrap'>
164178
<i className='fas fa-user-plus text-discord-blurple' /> 초대하기

types/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import { botEnforcements } from '@utils/Constants'
12
import type { GuildFeature } from 'discord.js'
23
import type { IncomingMessage } from 'http'
34
import type { NextPageContext } from 'next'
45

56
export type Nullable<T> = T | null
67

8+
export type ValueOf<T> = T[keyof T]
9+
710
export interface Bot {
811
id: string
912
name: string
@@ -25,11 +28,14 @@ export interface Bot {
2528
url: string | null
2629
discord: string | null
2730
vanity: string | null
31+
enforcements: BotEnforcementKeys[]
2832
bg: string
2933
banner: string
3034
owners: User[] | string[]
3135
}
3236

37+
export type BotEnforcementKeys = keyof typeof botEnforcements
38+
3339
export interface RawGuild {
3440
id: string
3541
name: string

utils/Constants.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,25 @@ export const botCategoryDescription = {
120120
마인크래프트: '게임 "마인크래프트"에 관련된 기능을 다룹니다.',
121121
}
122122

123+
export const botEnforcements = {
124+
JOIN_PARTIALLY_ENFORCED: {
125+
label: '서버 참여가 필요한 기능이 있습니다',
126+
description: '봇의 일부 명령어는 봇의 디스코드 서버에 참여해야 사용할 수 있습니다.',
127+
},
128+
JOIN_ENFORCED: {
129+
label: '서버 참여 없이는 봇의 핵심 기능을 사용할 수 없습니다',
130+
description: '봇의 핵심 기능은 봇의 디스코드 서버에 참여해야 사용할 수 있습니다.',
131+
},
132+
LICENSE_PARTIALLY_ENFORCED: {
133+
label: '유료 구매가 필요한 기능이 있습니다',
134+
description: '봇의 일부 명령어는 유료 구매가 필요합니다.',
135+
},
136+
LICENSE_ENFORCED: {
137+
label: '유료 구매 없이는 봇의 핵심 기능을 사용할 수 없습니다',
138+
description: '유료 구매 없이는 봇의 핵심 기능을 사용할 수 없습니다.',
139+
},
140+
} as const
141+
123142
export const botCategoryIcon = {
124143
관리: 'fas fa-cogs',
125144
뮤직: 'fas fa-music',

0 commit comments

Comments
 (0)