diff --git a/src/lib/discord.ts b/src/lib/discord.ts index a41f868..de687b7 100644 --- a/src/lib/discord.ts +++ b/src/lib/discord.ts @@ -2,53 +2,21 @@ import { Image, Url, User } from '@prisma/client'; import config from 'lib/config'; import { ConfigDiscordContent } from 'lib/config/Config'; import Logger from './logger'; - -// [user, image, url, route (ex. https://example.com/r/something.png)] -export type Args = [User, Image?, Url?, string?]; +import { parseString, ParseValue } from './utils/parser'; const logger = Logger.get('discord'); -function parse(str: string, args: Args) { - if (!str) return null; - - str = str - .replace(/{user\.admin}/gi, args[0].administrator ? 'yes' : 'no') - .replace(/{user\.id}/gi, args[0].id.toString()) - .replace(/{user\.name}/gi, args[0].username) - .replace(/{link}/gi, args[3]); - - if (args[1]) - str = str - .replace(/{file\.id}/gi, args[1].id.toString()) - .replace(/{file\.mime}/gi, args[1].mimetype) - .replace(/{file\.file}/gi, args[1].file) - .replace(/{file\.created_at.full_string}/gi, args[1].created_at.toLocaleString()) - .replace(/{file\.created_at.time_string}/gi, args[1].created_at.toLocaleTimeString()) - .replace(/{file\.created_at.date_string}/gi, args[1].created_at.toLocaleDateString()); - - if (args[2]) - str = str - .replace(/{url\.id}/gi, args[2].id.toString()) - .replace(/{url\.vanity}/gi, args[2].vanity ? args[2].vanity : 'none') - .replace(/{url\.destination}/gi, args[2].destination) - .replace(/{url\.created_at.full_string}/gi, args[2].created_at.toLocaleString()) - .replace(/{url\.created_at.time_string}/gi, args[2].created_at.toLocaleTimeString()) - .replace(/{url\.created_at.date_string}/gi, args[2].created_at.toLocaleDateString()); - - return str; -} - export function parseContent( content: ConfigDiscordContent, - args: Args + args: ParseValue ): ConfigDiscordContent & { url: string } { return { - content: parse(content.content, args), + content: parseString(content.content, args), embed: content.embed ? { - title: parse(content.embed.title, args), - description: parse(content.embed.description, args), - footer: parse(content.embed.footer, args), + title: parseString(content.embed.title, args), + description: parseString(content.embed.description, args), + footer: parseString(content.embed.footer, args), color: content.embed.color, thumbnail: content.embed.thumbnail, timestamp: content.embed.timestamp, @@ -59,10 +27,16 @@ export function parseContent( }; } -export async function sendUpload(user: User, image: Image, host: string) { +export async function sendUpload(user: User, image: Image, raw_link: string, link: string) { if (!config.discord.upload) return; - const parsed = parseContent(config.discord.upload, [user, image, null, host]); + const parsed = parseContent(config.discord.upload, { + file: image, + user, + link, + raw_link, + }); + const isImage = image.mimetype.startsWith('image/'); const body = JSON.stringify({ @@ -118,10 +92,14 @@ export async function sendUpload(user: User, image: Image, host: string) { return; } -export async function sendShorten(user: User, url: Url, host: string) { +export async function sendShorten(user: User, url: Url, link: string) { if (!config.discord.shorten) return; - const parsed = parseContent(config.discord.shorten, [user, null, url, host]); + const parsed = parseContent(config.discord.shorten, { + url, + user, + link, + }); const body = JSON.stringify({ username: config.discord.username, diff --git a/src/lib/utils/client.ts b/src/lib/utils/client.ts index f504adc..ef0c59d 100644 --- a/src/lib/utils/client.ts +++ b/src/lib/utils/client.ts @@ -1,4 +1,3 @@ -import type { Image, User } from '@prisma/client'; import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration'; import dayjsRelativeTime from 'dayjs/plugin/relativeTime'; @@ -6,21 +5,6 @@ import ms, { StringValue } from 'ms'; dayjs.extend(duration); dayjs.extend(dayjsRelativeTime); -export function parse(str: string, image: Image, user: User) { - if (!str) return null; - - return str - .replace(/{user\.admin}/gi, user.administrator ? 'yes' : 'no') - .replace(/{user\.id}/gi, user.id.toString()) - .replace(/{user\.name}/gi, user.username) - .replace(/{image\.id}/gi, image.id.toString()) - .replace(/{image\.mime}/gi, image.mimetype) - .replace(/{image\.file}/gi, image.file) - .replace(/{image\.created_at.full_string}/gi, image.created_at.toLocaleString()) - .replace(/{image\.created_at.time_string}/gi, image.created_at.toLocaleTimeString()) - .replace(/{image\.created_at.date_string}/gi, image.created_at.toLocaleDateString()); -} - export function randomChars(length: number) { const charset = 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890'; diff --git a/src/lib/utils/parser.ts b/src/lib/utils/parser.ts new file mode 100644 index 0000000..0361ae5 --- /dev/null +++ b/src/lib/utils/parser.ts @@ -0,0 +1,143 @@ +import type { Image, User, Url } from '@prisma/client'; + +export type ParseValue = { + file?: Image; + url?: Url; + user?: User; + + link?: string; + raw_link?: string; +}; + +export function parseString(str: string, value: ParseValue) { + str = str.replace(/\{link\}/gi, value.link).replace(/\{raw_link\}/gi, value.raw_link); + + const re = /\{(?file|url|user)\.(?\w+)(::(?\w+))?\}/gi; + let matches: RegExpMatchArray; + + while ((matches = re.exec(str))) { + const getV = value[matches.groups.type]; + if (!getV) { + str = replaceCharsFromString(str, '{unknown_type}', matches.index, re.lastIndex); + re.lastIndex = matches.index; + continue; + } + + if (matches.groups.prop in ['password', 'avatar']) { + str = replaceCharsFromString(str, '{unknown_property}', matches.index, re.lastIndex); + re.lastIndex = matches.index; + continue; + } + + const v = getV[matches.groups.prop]; + + if (v === undefined) { + str = replaceCharsFromString(str, '{unknown_property}', matches.index, re.lastIndex); + re.lastIndex = matches.index; + continue; + } + + if (matches.groups.mod) { + str = replaceCharsFromString(str, modifier(matches.groups.mod, v), matches.index, re.lastIndex); + re.lastIndex = matches.index; + continue; + } + + str = replaceCharsFromString(str, v, matches.index, re.lastIndex); + re.lastIndex = matches.index; + } + + return str; +} + +function modifier(mod: string, value: any): string { + mod = mod.toLowerCase(); + + if (value instanceof Date) { + switch (mod) { + case 'locale': + return value.toLocaleString(); + case 'time': + return value.toLocaleTimeString(); + case 'date': + return value.toLocaleDateString(); + case 'unix': + return Math.floor(value.getTime() / 1000).toString(); + case 'iso': + return value.toISOString(); + case 'utc': + return value.toUTCString(); + case 'year': + return value.getFullYear().toString(); + case 'month': + return (value.getMonth() + 1).toString(); + case 'day': + return value.getDate().toString(); + case 'hour': + return value.getHours().toString(); + case 'minute': + return value.getMinutes().toString(); + case 'second': + return value.getSeconds().toString(); + default: + return '{unknown_date_modifier}'; + } + } else if (typeof value === 'string') { + switch (mod) { + case 'upper': + return value.toUpperCase(); + case 'lower': + return value.toLowerCase(); + case 'title': + return value.charAt(0).toUpperCase() + value.slice(1); + case 'length': + return value.length.toString(); + case 'reverse': + return value.split('').reverse().join(''); + case 'base64': + return btoa(value); + case 'hex': + return toHex(value); + default: + return '{unknown_str_modifier}'; + } + } else if (typeof value === 'number') { + switch (mod) { + case 'comma': + return value.toLocaleString(); + case 'hex': + return value.toString(16); + case 'octal': + return value.toString(8); + case 'binary': + return value.toString(2); + default: + return '{unknown_int_modifier}'; + } + } else if (typeof value === 'boolean') { + switch (mod) { + case 'yesno': + return value ? 'Yes' : 'No'; + case 'onoff': + return value ? 'On' : 'Off'; + case 'truefalse': + return value ? 'True' : 'False'; + default: + return '{unknown_bool_modifier}'; + } + } + + return '{unknown_modifier}'; +} + +function replaceCharsFromString(str: string, replace: string, start: number, end: number): string { + return str.slice(0, start) + replace + str.slice(end); +} + +function toHex(str: string): string { + let hex = ''; + for (let i = 0; i < str.length; i++) { + hex += '' + str.charCodeAt(i).toString(16); + } + return hex; +} diff --git a/src/pages/api/upload.ts b/src/pages/api/upload.ts index d17228d..552ba54 100644 --- a/src/pages/api/upload.ts +++ b/src/pages/api/upload.ts @@ -191,7 +191,10 @@ async function handler(req: NextApiReq, res: NextApiRes) { file, `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}/r/${ invis ? invis.invis : file.file - }` + }`, + `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}${ + zconfig.uploader.route === '/' ? '' : zconfig.uploader.route + }/${invis ? invis.invis : file.file}` ); } @@ -325,7 +328,10 @@ async function handler(req: NextApiReq, res: NextApiRes) { image, `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}/r/${ invis ? invis.invis : image.file - }` + }`, + `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}${ + zconfig.uploader.route === '/' ? '' : zconfig.uploader.route + }/${invis ? invis.invis : image.file}` ); } diff --git a/src/pages/view/[id].tsx b/src/pages/view/[id].tsx index b035d37..b3e99bf 100644 --- a/src/pages/view/[id].tsx +++ b/src/pages/view/[id].tsx @@ -1,7 +1,7 @@ import { Box, Button, Modal, PasswordInput } from '@mantine/core'; import exts from 'lib/exts'; import prisma from 'lib/prisma'; -import { parse } from 'lib/utils/client'; +import { parseString } from 'lib/utils/parser'; import { GetServerSideProps } from 'next'; import Head from 'next/head'; import { useRouter } from 'next/router'; @@ -63,9 +63,14 @@ export default function EmbeddedFile({ image, user, pass, prismRender }) { {image.embed && ( <> {user.embedSiteName && ( - + + )} + {user.embedTitle && ( + )} - {user.embedTitle && } )} diff --git a/src/server/index.ts b/src/server/index.ts index 6074dba..bc3a95f 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -21,6 +21,7 @@ import rawRoute from './routes/raw'; import uploadsRoute, { uploadsRouteOnResponse } from './routes/uploads'; import urlsRoute, { urlsRouteOnResponse } from './routes/urls'; import { IncomingMessage } from 'http'; +import { parseString } from '../lib/utils/parser'; const dev = process.env.NODE_ENV === 'development'; const logger = Logger.get('server'); @@ -38,6 +39,19 @@ start(); async function start() { logger.debug('Starting server'); + // const a = parseString( + // '{file.name::upper} {file.mimetype} {file.test} {file.created_at::unix} {file.v::yesno} {file.int::hex}', + // // @ts-ignore + // { + // name: 'test', + // mimetype: 'image/png', + // created_at: new Date(), + // int: 123123123123123, + // v: false, + // } + // ); + // console.log(a); + // plugins server .register(loggerPlugin)