Skip to content
Open
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
66 changes: 66 additions & 0 deletions pages/api/v2/bots/[id]/limit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { NextApiRequest } from 'next'

import { KoreanbotsEndPoints } from '@utils/Constants'
import { checkToken } from '@utils/Csrf'
import { discordLog } from '@utils/DiscordBot'
import { CaptchaVerify, get, update } from '@utils/Query'
import RequestHandler from '@utils/RequestHandler'
import ResponseWrapper from '@utils/ResponseWrapper'
import { checkUserFlag, makeDiscordCodeblock } from '@utils/Tools'
import { ExceedLimit, ExceedLimitScehma } from '@utils/Yup'
import { EmbedBuilder } from 'discord.js'

const BotLimit = RequestHandler().patch(async (req: PostApiRequest, res) => {
const user = await get.Authorization(req.cookies.token)
if (!user) return ResponseWrapper(res, { code: 401 })
const userinfo = await get.user.load(user)
const bot = await get.bot.load(req.query.id)
if (!bot) return ResponseWrapper(res, { code: 404 })
if (!checkUserFlag(userinfo.flags, 'staff')) return ResponseWrapper(res, { code: 403 })

const validated = await ExceedLimitScehma.validate(req.body, { abortEarly: false })
.then((el) => el)
.catch((e) => {
ResponseWrapper(res, { code: 400, errors: e.errors })
return null
})
if (!validated) return
const csrfValidated = checkToken(req, res, validated._csrf)
if (!csrfValidated) return
const captcha = await CaptchaVerify(validated._captcha)
if (!captcha) return

await update.updateServer(
bot.id,
validated.servers,
validated.shards == 0 ? undefined : validated.shards,
true
)
get.user.clear(user)

await discordLog(
'BOT/EXCEED_LIMIT',
userinfo.id,
new EmbedBuilder().setDescription(
`${bot.name} - <@${bot.id}> ([${bot.id}](${KoreanbotsEndPoints.URL.bot(bot.id)}))`
),
null,
makeDiscordCodeblock(
`${bot.servers > validated.servers ? '-' : '+'} ${bot.servers} -> ${validated.servers} (${bot.servers > validated.servers ? '▼' : '▲'}${Math.abs(validated.servers - bot.servers)})\n` +
`${validated.servers >= 1000000 ? '+ 서버수 제한 해제 (1000000+)\n' : ''}` +
`${validated.servers >= 10000 && validated.servers < 1000000 ? '+ 서버수 제한 해제 (10000+)\n' : ''}` +
`${validated.shards >= 200 ? '+ 샤드수 제한 해제\n' : ''}`,
'diff'
)
)
return ResponseWrapper(res, { code: 200 })
})

interface PostApiRequest extends NextApiRequest {
query: {
id: string
}
body: ExceedLimit
}

export default BotLimit
78 changes: 76 additions & 2 deletions pages/bots/[id]/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
parseCookie,
redirectTo,
} from '@utils/Tools'
import { ManageBot, getManageBotSchema } from '@utils/Yup'
import { ExceedLimit, ManageBot, getManageBotSchema } from '@utils/Yup'
import { botCategories, botCategoryDescription, botEnforcements, library } from '@utils/Constants'
import { Bot, Theme, User } from '@types'
import { getToken } from '@utils/Csrf'
Expand Down Expand Up @@ -49,6 +49,7 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
const [adminModal, setAdminModal] = useState(false)
const [transferModal, setTransferModal] = useState(false)
const [deleteModal, setDeleteModal] = useState(false)
const [limitModal, setLimitModal] = useState(false)
const router = useRouter()

async function submitBot(value: ManageBot) {
Expand Down Expand Up @@ -136,7 +137,9 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
{data.message || '오류가 발생했습니다.'}
</h2>
<ul className='list-inside list-disc'>
{data.errors?.map((el, n) => <li key={n}>{el}</li>)}
{data.errors?.map((el, n) => (
<li key={n}>{el}</li>
))}
</ul>
</Message>
</div>
Expand Down Expand Up @@ -664,6 +667,77 @@ const ManageBotPage: NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }
</Segment>
</div>
)}
{checkUserFlag(user.flags, 'staff') && (
<div className='py-4'>
<Divider />
<h2 className='pb-2 text-2xl font-semibold'>한디리 스탭 전용</h2>
<Segment>
<div className='items-center lg:flex'>
<div className='grow py-1'>
<h3 className='text-lg font-semibold'>서버수/샤드수 제한 해제</h3>
<p className='text-gray-400'>10000서버, 1000000서버, 200샤드 제한을 해제합니다.</p>
</div>
<Button
onClick={() => setLimitModal(true)}
className='lg:w-1/8 h-10 bg-koreanbots-blue text-white hover:opacity-80'
>
<i className='fas fa-unlock' /> 제한 해제
</Button>
<Modal
full
header={`${bot.name} 서버/샤드수 제한 해제`}
isOpen={limitModal}
dark={theme === 'dark'}
onClose={() => setLimitModal(false)}
closeIcon
>
<Formik
initialValues={{
_captcha: '',
_csrf: csrfToken,
servers: bot.servers,
shards: bot.shards ?? 0,
}}
onSubmit={async (value) => {
const res = await Fetch(`/bots/${bot.id}/limit`, {
method: 'PATCH',
body: JSON.stringify(cleanObject<ExceedLimit>(value)),
})
if (res.code === 200) {
alert('성공적으로 수정하였습니다.')
router.push(makeBotURL(bot))
} else alert(res.message)
}}
>
{({ values, setFieldValue }) => (
<Form>
<div className='py-4'>
<h2 className='text-md my-1'>설정하실 서버 수를 입력해주세요.</h2>
<Input name='servers' placeholder={String(bot.servers)} type='number' />
<h2 className='text-md my-1'>설정하실 샤드 수를 입력해주세요.</h2>
<Input name='shards' placeholder={String(bot.shards)} type='number' />
</div>
<Captcha
dark={theme === 'dark'}
onVerify={(k) => setFieldValue('_captcha', k)}
/>
<Button
disabled={!values._captcha}
className={`mt-4 bg-koreanbots-blue text-white ${
!values._captcha ? 'opacity-80' : 'hover:opacity-80'
}`}
type='submit'
>
<i className='fas fa-save' /> 저장
</Button>
</Form>
)}
</Formik>
</Modal>
</div>
</Segment>
</div>
)}
</Container>
)
}
Expand Down
55 changes: 31 additions & 24 deletions utils/Query.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import DataLoader from 'dataloader'
import { ActivityType, User as DiscordUser, GuildFeature, GuildMember, UserFlags } from 'discord.js'
import fetch from 'node-fetch'
import { TLRU } from 'tlru'
import DataLoader from 'dataloader'
import { ActivityType, GuildFeature, GuildMember, User as DiscordUser, UserFlags } from 'discord.js'

import {
Bot,
Server,
User,
ListType,
List,
TokenRegister,
BotFlags,
DiscordUserFlags,
SubmittedBot,
BotSpec,
DiscordTokenInfo,
DiscordUserFlags,
List,
ListType,
Nullable,
ObjectType,
RawGuild,
Server,
ServerData,
ServerFlags,
RawGuild,
Nullable,
Webhook,
BotSpec,
ServerSpec,
ObjectType,
SubmittedBot,
TokenRegister,
User,
Webhook,
} from '@types'
import {
botCategories,
Expand All @@ -32,14 +32,14 @@ import {
VOTE_COOLDOWN,
} from './Constants'

import knex from './Knex'
import { Bots, Servers } from './Mongo'
import { DiscordBot, getMainGuild } from './DiscordBot'
import { sign, verify } from './Jwt'
import knex from './Knex'
import { Bots, Servers } from './Mongo'
import { Notification } from './NotificationManager'
import { markdownImage } from './Regex'
import { areArraysEqual, camoUrl, formData, getYYMMDD, serialize } from './Tools'
import { AddBotSubmit, AddServerSubmit, ManageBot, ManageServer } from './Yup'
import { markdownImage } from './Regex'
import { Notification } from './NotificationManager'

export const imageRateLimit = new TLRU<unknown, number>({ maxAgeMs: 60000 })

Expand Down Expand Up @@ -198,7 +198,7 @@ async function fetchServerOwners(id: string): Promise<User[] | null> {
? [
await get._rawUser.load(data.owner),
...(await Promise.all(data.admins.map((el) => get._rawUser.load(el)))),
].filter((el) => el)
].filter((el) => el)
: null
}

Expand Down Expand Up @@ -778,13 +778,20 @@ async function updatedServer(id: string, data: ManageServer) {
/**
* @returns 1 - Limit of 100k servers
* @returns 2 - Limit of 10M servers
* @returns 3 - Limit of 100 shards
* @returns 3 - Limit of 200 shards
*/
async function updateServer(id: string, servers: number, shards: number) {
async function updateServer(
id: string,
servers: number,
shards: number,
force = false
): Promise<number | void> {
const bot = await get.bot.load(id)
if (bot.servers < 10000 && servers >= 10000) return 1
else if (bot.servers < 1000000 && servers >= 1000000) return 2
if (bot.shards < 200 && shards >= 200) return 3
if (!force) {
if (bot.servers < 10000 && servers >= 10000) return 1
else if (bot.servers < 1000000 && servers >= 1000000) return 2
if (bot.shards < 200 && shards >= 200) return 3
}
await knex('bots')
.update({
servers: servers === undefined ? bot.servers : servers,
Expand Down
14 changes: 14 additions & 0 deletions utils/Yup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,4 +459,18 @@ export interface EditBotOwner {
_captcha: string
}

export const ExceedLimitScehma: Yup.SchemaOf<ExceedLimit> = Yup.object({
servers: Yup.number().integer().moreThan(-1, '서버 수는 0보다 커야합니다.').required(),
shards: Yup.number().integer().moreThan(-1, '샤드 수는 0보다 커야합니다.').required(),
_csrf: Yup.string().required(),
_captcha: Yup.string().required(),
})

export interface ExceedLimit {
_csrf: string
_captcha: string
servers: number
shards: number
}

export default Yup