feat: camo images in bot desc

This commit is contained in:
wonderlandpark 2021-04-09 14:17:33 +09:00
parent ef160e89aa
commit 57569bd298
6 changed files with 50 additions and 10 deletions

View File

@ -36,7 +36,7 @@ const TextArea = dynamic(() => import('@components/Form/TextArea'))
const Modal = dynamic(() => import('@components/Modal'))
const NSFW = dynamic(() => import('@components/NSFW'))
const Bots: NextPage<BotsProps> = ({ data, date, user, theme, csrfToken }) => {
const Bots: NextPage<BotsProps> = ({ data, desc, date, user, theme, csrfToken }) => {
const bg = checkBotFlag(data?.flags, 'trusted') && data?.banner
const router = useRouter()
const [ nsfw, setNSFW ] = useState<boolean>()
@ -305,7 +305,7 @@ const Bots: NextPage<BotsProps> = ({ data, date, user, theme, csrfToken }) => {
}
<div className='markdown-body pt-4 w-full'>
<Segment className='my-4'>
<Markdown text={data.desc}/>
<Markdown text={desc}/>
</Segment>
<Advertisement />
</div>
@ -320,10 +320,12 @@ const Bots: NextPage<BotsProps> = ({ data, date, user, theme, csrfToken }) => {
export const getServerSideProps = async (ctx: Context) => {
const parsed = parseCookie(ctx.req)
const data = await get.bot.load(ctx.query.id) ?? { id: '' }
const desc = await get.botDescSafe(data.id)
const user = await get.Authorization(parsed?.token)
return {
props: {
data,
desc,
date: SnowflakeUtil.deconstruct(data.id ?? '0').date.toJSON(),
user: await get.user.load(user || ''),
csrfToken: getToken(ctx.req, ctx.res)
@ -335,6 +337,7 @@ export default Bots
interface BotsProps {
data: Bot
desc: string
date: Date
user: User
theme: Theme

View File

@ -122,6 +122,12 @@ export const reportCats = [
'기타',
]
export const imageSafeHost = [
'koreanbots.dev',
'githubusercontent.com',
'cdn.discordapp.com'
]
export const MessageColor = {
success: 'bg-green-200 text-green-800',
error: 'bg-red-200 text-red-800',
@ -131,7 +137,8 @@ export const MessageColor = {
export const BASE_URLs = {
api: 'https://discord.com/api',
cdn: 'https://cdn.discordapp.com'
cdn: 'https://cdn.discordapp.com',
camo: 'https://camo.koreanbots.dev'
}
export const BotBadgeType = (data: Bot) => {

View File

@ -4,13 +4,14 @@ import DataLoader from 'dataloader'
import { User as DiscordUser } from 'discord.js'
import { Bot, User, ListType, BotList, TokenRegister, BotFlags, DiscordUserFlags, SubmittedBot } from '@types'
import { categories, SpecialEndPoints } from './Constants'
import { categories, imageSafeHost, SpecialEndPoints } from './Constants'
import knex from './Knex'
import { DiscordBot, getMainGuild } from './DiscordBot'
import { sign, verify } from './Jwt'
import { formData, serialize } from './Tools'
import { camoUrl, formData, serialize } from './Tools'
import { AddBotSubmit, ManageBot } from './Yup'
import { markdownImage } from './Regex'
export const imageRateLimit = new TLRU<unknown, number>({ maxAgeMs: 60000 })
@ -43,7 +44,6 @@ async function getBot(id: string, owners=true):Promise<Bot> {
.orWhere({ vanity: id, trusted: true })
.orWhere({ vanity: id, partnered: true })
if (res[0]) {
const discordBot = await get.discord.user.load(res[0].id)
await getMainGuild()?.members?.fetch(res[0].id).catch(e=> e)
if(!discordBot) return null
@ -353,6 +353,17 @@ export const get = {
async (ids: string[]) =>
(await Promise.all(ids.map(async (el: string) => await getBot(el, false)))).map(row => serialize(row))
, { cacheMap: new TLRU({ maxStoreSize: 50, maxAgeMs: 60000 }) }),
botDescSafe: async (id: string) => {
const bot = await get.bot.load(id)
return bot?.desc.replace(markdownImage, (matches: string, alt: string|undefined, link: string|undefined, description: string|undefined): string => {
try {
const url = new URL(link)
return `![${alt || description || ''}](${imageSafeHost.find(el => url.host.endsWith(el)) ? link : camoUrl(link) })`
} catch {
return matches
}
}) || null
},
user: new DataLoader(
async (ids: string[]) =>
(await Promise.all(ids.map(async (el: string) => await getUser(el)))).map(row => serialize(row))

View File

@ -11,3 +11,4 @@ export const Emoji =
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

View File

@ -5,7 +5,7 @@ import { KoreanbotsEmoji } from './Constants'
export const anchorHeader = {
type: 'output',
regex: Heading,
replace: function (__match: string, id:string, title:string, level:number) {
replace: function (__match: string, id:string, title:string, level:number): string {
// github anchor style
const href = id.replace(ImageTag, '$1').replace(/"/gi, '')
@ -33,7 +33,7 @@ export const twemoji = {
export const customEmoji = {
type: 'output',
regex: EmojiSyntax,
replace: function(__match: string, name: string) {
replace: function(__match: string, name: string): string {
const result = KoreanbotsEmoji.find(el => el.short_names.includes(name))
if(!name || !result) return `:${name}:`
return `<img class="emoji special" draggable="false" alt="${name}" src="${result.imageUrl}"/>`

View File

@ -1,8 +1,9 @@
import { createHmac } from 'crypto'
import { Readable } from 'stream'
import cookie from 'cookie'
import { BotFlags, ImageOptions, UserFlags } from '@types'
import { KoreanbotsEndPoints, Oauth } from './Constants'
import { BASE_URLs, KoreanbotsEndPoints, Oauth } from './Constants'
import { NextRouter } from 'next/router'
export function formatNumber(value: number):string {
@ -121,4 +122,21 @@ export function cleanObject<T extends Record<any, any>>(obj: T): T {
return obj
}
export { anchorHeader, twemoji, customEmoji } from './ShowdownExtensions'
export function camoUrl(url: string): string {
return BASE_URLs.camo + `/${HMAC(url)}/${toHex(url)}`
}
export function HMAC(value: string, secret=process.env.CAMO_SECRET):string|null {
try {
return createHmac('sha1', secret).update(value, 'utf8').digest('hex')
}
catch {
return null
}
}
export function toHex(value: string): string {
return Buffer.from(value).toString('hex')
}
export * from './ShowdownExtensions'