mirror of
https://github.com/koreanbots/core.git
synced 2025-12-15 14:10:22 +00:00
feat: added vote
This commit is contained in:
parent
9cf3e06709
commit
05b92d1980
35
pages/api/v2/bots/[id]/vote.ts
Normal file
35
pages/api/v2/bots/[id]/vote.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { NextApiRequest } from 'next'
|
||||
|
||||
import { CaptchaVerify, get, put } from '@utils/Query'
|
||||
import RequestHandler from '@utils/RequestHandler'
|
||||
import ResponseWrapper from '@utils/ResponseWrapper'
|
||||
import { checkToken } from '@utils/Csrf'
|
||||
|
||||
const BotVote = RequestHandler()
|
||||
.post(async (req: PostApiRequest, res) => {
|
||||
const user = await get.Authorization(req.cookies.token)
|
||||
if(!user) return ResponseWrapper(res, { code: 401 })
|
||||
const bot = await get.bot.load(req.query.id)
|
||||
if (!bot) return ResponseWrapper(res, { code: 404, message: '존재하지 않는 봇입니다.' })
|
||||
const csrfValidated = checkToken(req, res, req.body._csrf)
|
||||
if (!csrfValidated) return
|
||||
const captcha = await CaptchaVerify(req.body._captcha)
|
||||
if(!captcha) return ResponseWrapper(res, { code: 400, message: '캡챠 검증에 실패하였습니다.' })
|
||||
|
||||
const vote = await put.voteBot(user, bot.id)
|
||||
if(vote === null) return ResponseWrapper(res, { code: 401 })
|
||||
else if(vote === true) return ResponseWrapper(res, { code: 200 })
|
||||
else return ResponseWrapper(res, { code: 429, data: { retryAfter: vote } })
|
||||
})
|
||||
|
||||
interface PostApiRequest extends NextApiRequest {
|
||||
query: {
|
||||
id: string
|
||||
}
|
||||
body: {
|
||||
_captcha: string
|
||||
_csrf: string
|
||||
}
|
||||
}
|
||||
|
||||
export default BotVote
|
||||
@ -3,9 +3,9 @@ import Link from 'next/link'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { Bot, CsrfContext, Theme, User } from '@types'
|
||||
import { Bot, CsrfContext, ResponseProps, Theme, User } from '@types'
|
||||
import { get } from '@utils/Query'
|
||||
import { makeBotURL, parseCookie, checkBotFlag } from '@utils/Tools'
|
||||
import { makeBotURL, parseCookie, checkBotFlag, redirectTo } from '@utils/Tools'
|
||||
|
||||
import { ParsedUrlQuery } from 'querystring'
|
||||
|
||||
@ -13,6 +13,9 @@ import NotFound from 'pages/404'
|
||||
import { getToken } from '@utils/Csrf'
|
||||
import Captcha from '@components/Captcha'
|
||||
import { useState } from 'react'
|
||||
import Fetch from '@utils/Fetch'
|
||||
import Day from '@utils/Day'
|
||||
import { getJosaPicker } from 'josa'
|
||||
|
||||
|
||||
const Container = dynamic(() => import('@components/Container'))
|
||||
@ -23,14 +26,20 @@ const Segment = dynamic(() => import('@components/Segment'))
|
||||
const SEO = dynamic(() => import('@components/SEO'))
|
||||
const Advertisement = dynamic(() => import('@components/Advertisement'))
|
||||
|
||||
const VoteBot: NextPage<VoteBotProps> = ({ data, csrfToken , theme}) => {
|
||||
const VoteBot: NextPage<VoteBotProps> = ({ data, user, csrfToken, theme }) => {
|
||||
const [ votingStatus, setVotingStatus ] = useState(0)
|
||||
const [ result, setResult ] = useState<ResponseProps<{retryAfter?: number}>>(null)
|
||||
const router = useRouter()
|
||||
if(!data?.id) return <NotFound />
|
||||
if(csrfToken !== router.query.csrfToken) {
|
||||
router.push(`/bots/${data.id}`)
|
||||
return <></>
|
||||
}
|
||||
if(!user) {
|
||||
localStorage.redirectTo = window.location.href
|
||||
redirectTo(router, 'login')
|
||||
return <SEO title='새로운 봇 추가하기' description='자신의 봇을 한국 디스코드봇 리스트에 등록하세요.'/>
|
||||
}
|
||||
if((checkBotFlag(data.flags, 'trusted') || checkBotFlag(data.flags, 'partnered')) && data.vanity && data.vanity !== router.query.id) router.push(`/bots/${data.vanity}/vote?csrfToken=${csrfToken}`)
|
||||
return <Container paddingTop className='py-10'>
|
||||
<SEO title={data.name} description={`한국 디스코드봇 리스트에서 ${data.name}에 투표하세요`} image={
|
||||
@ -40,21 +49,31 @@ const VoteBot: NextPage<VoteBotProps> = ({ data, csrfToken , theme}) => {
|
||||
} />
|
||||
<Advertisement />
|
||||
<Link href={makeBotURL(data)}>
|
||||
<a className='text-blue-500 hover:opacity-80'><i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.name}</strong>으로 돌아가기</a>
|
||||
<a className='text-blue-500 hover:opacity-80'><i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.name}</strong>{getJosaPicker('로')(data.name)} 돌아가기</a>
|
||||
</Link>
|
||||
<Segment className='mb-10 py-8'>
|
||||
<div className='text-center'>
|
||||
<DiscordAvatar userID={data.id} className='mx-auto w-52 h-52 bg-white mb-4' />
|
||||
<Tag text={<span><i className='fas fa-heart text-red-600' /> {data.votes}</span>} dark />
|
||||
<h1 className='text-3xl font-bold mt-3'>{data.name}</h1>
|
||||
<h4 className='text-md mt-1'>12시간 뒤에 다시 투표하실 수 있습니다.</h4>
|
||||
<h4 className='text-md mt-1'>12시간 마다 다시 투표하실 수 있습니다.</h4>
|
||||
<div className='inline-block mt-2'>
|
||||
{
|
||||
votingStatus === 0 ? <Button onClick={()=> setVotingStatus(1)}>
|
||||
<><i className='far fa-heart text-red-600'/> 하트 추가</>
|
||||
</Button>
|
||||
: votingStatus === 1 ? <Captcha dark={theme === 'dark'} onVerify={() => setVotingStatus(2)}/>
|
||||
: <h2 className='text-2xl font-bold'>해당 봇에 투표했습니다!</h2>
|
||||
: votingStatus === 1 ? <Captcha dark={theme === 'dark'} onVerify={async (key) => {
|
||||
const res = await Fetch<{ retryAfter: number }|unknown>(`/bots/${data.id}/vote`, { method: 'POST', body: JSON.stringify({ _csrf: csrfToken, _captcha: key }) })
|
||||
setResult(res)
|
||||
setVotingStatus(2)
|
||||
}}
|
||||
/>
|
||||
: result.code === 200 ? <h2 className='text-2xl font-bold'>해당 봇에 투표했습니다!</h2>
|
||||
: result.code === 429 ? <>
|
||||
<h2 className='text-2xl font-bold'>이미 해당 봇에 투표하였습니다.</h2>
|
||||
<h4 className='text-md mt-1'>{Day(+new Date() + result.data?.retryAfter).fromNow()} 다시 투표하실 수 있습니다.</h4>
|
||||
</>
|
||||
: <p>{result.message}</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { Bot, ImageOptions, KoreanbotsImageOptions } from '@types'
|
||||
import { KeyMap } from 'react-hotkeys'
|
||||
import { formatNumber, makeImageURL } from './Tools'
|
||||
|
||||
export const VOTE_COOLDOWN = 12 * 60 * 60 * 1000
|
||||
export const Status = {
|
||||
online: {
|
||||
text: '온라인',
|
||||
|
||||
@ -4,7 +4,7 @@ import DataLoader from 'dataloader'
|
||||
import { User as DiscordUser } from 'discord.js'
|
||||
|
||||
import { Bot, User, ListType, BotList, TokenRegister, BotFlags, DiscordUserFlags, SubmittedBot } from '@types'
|
||||
import { categories, imageSafeHost, SpecialEndPoints } from './Constants'
|
||||
import { categories, imageSafeHost, SpecialEndPoints, VOTE_COOLDOWN } from './Constants'
|
||||
|
||||
import knex from './Knex'
|
||||
import { DiscordBot, getMainGuild } from './DiscordBot'
|
||||
@ -181,6 +181,33 @@ async function getBotSubmits(id: string) {
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userID
|
||||
* @param botID
|
||||
* @returns Timestamp
|
||||
*/
|
||||
async function getBotVote(userID: string, botID: string): Promise<number|null> {
|
||||
const user = await knex('users').select(['votes']).where({ id: userID })
|
||||
if(user.length === 0) return null
|
||||
const data = JSON.parse(user[0].votes)
|
||||
return data[botID] || 0
|
||||
|
||||
|
||||
}
|
||||
|
||||
async function voteBot(userID: string, botID: string): Promise<number|boolean> {
|
||||
const user = await knex('users').select(['votes']).where({ id: userID })
|
||||
if(user.length === 0) return null
|
||||
const date = +new Date()
|
||||
const data = JSON.parse(user[0].votes)
|
||||
const lastDate = data[botID] || 0
|
||||
if(date - lastDate < VOTE_COOLDOWN) return VOTE_COOLDOWN - (date - lastDate)
|
||||
data[botID] = date
|
||||
await knex('bots').where({ id: botID }).increment('votes', 1)
|
||||
await knex('users').where({ id: userID }).update({ votes: JSON.stringify(data) })
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns 1 - Has pending Bots
|
||||
* @returns 2 - Already submitted ID
|
||||
@ -339,8 +366,6 @@ export async function CaptchaVerify(response: string): Promise<boolean> {
|
||||
})
|
||||
}).then(r=> r.json())
|
||||
|
||||
console.log(res)
|
||||
|
||||
return res.success
|
||||
}
|
||||
|
||||
@ -425,6 +450,7 @@ export const get = {
|
||||
(await Promise.all(urls.map(async (url: string) => await getImage(url))))
|
||||
, { cacheMap: new TLRU({ maxStoreSize: 500, maxAgeMs: 3600000 }) }),
|
||||
},
|
||||
botVote: getBotVote,
|
||||
Authorization,
|
||||
BotAuthorization
|
||||
}
|
||||
@ -439,6 +465,7 @@ export const update = {
|
||||
}
|
||||
|
||||
export const put = {
|
||||
voteBot,
|
||||
submitBot
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user