Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.demo.local
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ REVIEW_LOG_WEBHOOK_URL=
OPEN_REVIEW_LOG_WEBHOOK_URL=
STATS_LOG_WEBHOOK_URL=
REPORT_WEBHOOK_URL=
NOTICE_LOG_WEBHOOK_URL=
3 changes: 2 additions & 1 deletion components/BotCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const BotCard: React.FC<BotCardProps> = ({ manage = false, bot }) => {
<div
className='relative mx-auto h-full rounded-2xl bg-little-white text-black shadow-xl dark:bg-discord-black dark:text-white'
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`,
color: 'white',
Expand Down
4 changes: 3 additions & 1 deletion components/ServerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ const ServerCard: React.FC<BotCardProps> = ({ type, server }) => {
<div
className='relative mx-auto h-full rounded-2xl bg-little-white text-black shadow-xl dark:bg-discord-black dark:text-white'
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`,
color: 'white',
Expand Down
57 changes: 54 additions & 3 deletions pages/api/v2/bots/[id]/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import RequestHandler from '@utils/RequestHandler'
import { User } from '@types'
import {
checkBotFlag,
checkUserFlag,
diff,
inspect,
Expand All @@ -25,7 +26,6 @@ import {
} from '@utils/Tools'
import { discordLog, getMainGuild, webhookClients } from '@utils/DiscordBot'
import { KoreanbotsEndPoints } from '@utils/Constants'
import { userInfo } from 'os'

const patchLimiter = rateLimit({
windowMs: 2 * 60 * 1000,
Expand Down Expand Up @@ -128,7 +128,11 @@ const Bots = RequestHandler()
: `${userinfo.username}#${userinfo.tag}`,
iconURL:
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),
})
.setTitle('대기 중')
Expand Down Expand Up @@ -211,19 +215,60 @@ const Bots = RequestHandler()
const csrfValidated = checkToken(req, res, req.body._csrf)
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)
.catch((e) => {
ResponseWrapper(res, { code: 400, errors: e.errors })
return null
})

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)
if (result === 0) return ResponseWrapper(res, { code: 400 })
else {
get.bot.clear(req.query.id)
get.bot.clear(bot.vanity)
const embed = new EmbedBuilder().setDescription(
`${bot.name} - <@${bot.id}> ([${bot.id}](${KoreanbotsEndPoints.URL.bot(bot.id)}))`
)
Expand All @@ -237,6 +282,9 @@ const Bots = RequestHandler()
discord: bot.discord,
intro: bot.intro,
category: JSON.stringify(bot.category),
vanity: bot.vanity,
banner: bot.banner,
bg: bot.bg,
},
{
prefix: validated.prefix,
Expand All @@ -247,6 +295,9 @@ const Bots = RequestHandler()
discord: validated.discord,
intro: validated.intro,
category: JSON.stringify(validated.category),
vanity: validated.vanity,
banner: validated.banner,
bg: validated.bg,
}
)
diffData.forEach((d) => {
Expand Down
51 changes: 48 additions & 3 deletions pages/bots/[id]/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ParsedUrlQuery } from 'querystring'
import { getJosaPicker } from 'josa'

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 { botCategories, botCategoryDescription, library } from '@utils/Constants'
import { Bot, Theme, User } from '@types'
Expand Down Expand Up @@ -58,7 +58,7 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
else return null
}

if (!bot) return <NotFound />
if (!bot?.id) return <NotFound />
if (!user)
return (
<Login>
Expand All @@ -70,6 +70,7 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
!checkUserFlag(user.flags, 'staff')
)
return <Forbidden />
const isPerkAvailable = checkBotFlag(bot.flags, 'trusted') || checkBotFlag(bot.flags, 'partnered')
return (
<Container paddingTop className='pb-10 pt-5'>
<NextSeo title={`${bot.name} 수정하기`} description='봇의 정보를 수정합니다.' />
Expand All @@ -87,6 +88,9 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
url: bot.url,
git: bot.git,
discord: bot.discord,
vanity: isPerkAvailable && bot.vanity,
banner: isPerkAvailable && bot.banner,
bg: isPerkAvailable && bot.bg,
_csrf: csrfToken,
})}
validationSchema={ManageBotSchema}
Expand Down Expand Up @@ -269,6 +273,44 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
<Markdown text={values.desc} />
</Segment>
</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 />
<p className='mb-5 mt-2 text-base'>
<span className='font-semibold text-red-500'> *</span> = 필수 항목
Expand All @@ -278,6 +320,7 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
<i className='far fa-save' /> 저장
</>
</Button>

</Form>
)}
</Formik>
Expand Down Expand Up @@ -574,9 +617,11 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
export const getServerSideProps = async (ctx: Context) => {
const parsed = parseCookie(ctx.req)
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 {
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 || ''),
csrfToken: getToken(ctx.req, ctx.res),
},
Expand Down
2 changes: 1 addition & 1 deletion pages/bots/[id]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const Modal = dynamic(() => import('@components/Modal'))
const NSFW = dynamic(() => import('@components/NSFW'))

const Bots: NextPage<BotsProps> = ({ data, desc, date, user, theme, csrfToken }) => {
const bg = checkBotFlag(data?.flags, 'trusted') && data?.banner
const bg = checkBotFlag(data?.flags, 'trusted') && data?.bg
const router = useRouter()
const [nsfw, setNSFW] = useState<boolean>()
const [reportModal, setReportModal] = useState(false)
Expand Down
10 changes: 0 additions & 10 deletions pages/bots/koreanbots.tsx

This file was deleted.

10 changes: 8 additions & 2 deletions pages/servers/[id]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ const Servers: NextPage<ServersProps> = ({ data, desc, date, user, theme }) => {
const [emojisModal, setEmojisModal] = useState(false)
const [ownersModal, setOwnersModal] = useState(false)
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()
useEffect(() => {
if (data)
Expand Down Expand Up @@ -143,7 +144,12 @@ const Servers: NextPage<ServersProps> = ({ data, desc, date, user, theme }) => {
</div>
<div className='w-full lg:flex'>
<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 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' } : {}}>
Expand Down
4 changes: 3 additions & 1 deletion types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ export interface BotSpec {
webhookURL: string | null
webhookStatus: WebhookStatus
token: string
banner: string | null
bg: string | null
}

export interface ServerSpec {
Expand Down Expand Up @@ -351,7 +353,7 @@ export interface ImageOptions {
export interface KoreanbotsImageOptions {
format?: 'webp' | 'png' | 'gif'
size?: 128 | 256 | 512
hash?: string;
hash?: string
}

export enum DiscordImageType {
Expand Down
7 changes: 7 additions & 0 deletions utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,3 +626,10 @@ export const GuildPermissions = {
},
],
}

export const reservedVanityBypass = [
'653534001742741552',
'784618064167698472',
'653083797763522580',
'807561475014262785',
]
4 changes: 4 additions & 0 deletions utils/DiscordBot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export const webhookClients = {
{ url: process.env.REPORT_WEBHOOK_URL ?? dummyURL },
{ allowedMentions: { parse: [] } }
),
noticeLog: new Discord.WebhookClient(
{ url: process.env.NOTICE_LOG_WEBHOOK_URL ?? dummyURL },
{ allowedMentions: { parse: [] } }
),
},
}

Expand Down
16 changes: 15 additions & 1 deletion utils/Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ async function getBot(id: string, topLevel = true): Promise<Bot> {
res.name = name
res.category = JSON.parse(res.category)
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) {
res.status = 'online'
Expand Down Expand Up @@ -676,7 +678,14 @@ async function submitServer(

async function getBotSpec(id: string, userID: string): Promise<BotSpec | null> {
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')
.where('owners_mapping.user_id', userID)
.andWhere('owners_mapping.type', ObjectType.Bot)
Expand All @@ -688,6 +697,8 @@ async function getBotSpec(id: string, userID: string): Promise<BotSpec | null> {
token: res[0].token,
webhookURL: res[0].webhook_url,
webhookStatus: res[0].webhook_status,
banner: res[0].banner,
bg: res[0].bg,
}
}

Expand Down Expand Up @@ -733,6 +744,9 @@ async function updateBot(id: string, data: ManageBot): Promise<number> {
category: JSON.stringify(data.category),
intro: data.intro,
desc: data.desc,
vanity: data.vanity,
banner: data.banner,
bg: data.bg,
})
.where({ id })

Expand Down
9 changes: 9 additions & 0 deletions utils/Regex.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import urlRegex from 'url-regex-safe'
const reservedVanityConst = [
'koreanbots',
'koreanservers',
'koreanlist',
'kbots',
'kodl',
'discord',
]

export const ID = /^[0-9]{17,}$/
export const Vanity = /^[A-Za-z\d-]+$/
Expand All @@ -12,3 +20,4 @@ export const Heading = '<h\\d id="(.+?)">(.*?)<\\/h(\\d)>'
export const EmojiSyntax = ':(\\w+):'
export const ImageTag = /<img\s[^>]*?alt\s*=\s*['"]([^'"]*?)['"][^>]*?>/
export const markdownImage = /!\[([^\]]*)\]\((.*?)\s*("(?:.*[^"])")?\s*\)/g
export const reservedVanity = new RegExp(`^((?!${reservedVanityConst.join('|')}).)*$`, 'i') // 예약되지 않음을 확인
Loading