feat: discord webhook notifs

This commit is contained in:
diced 2022-08-23 09:38:29 -07:00
parent 4f631fbd0e
commit 1d42d922bd
No known key found for this signature in database
GPG key ID: 370BD1BA142842D1
6 changed files with 214 additions and 3 deletions

View file

@ -112,6 +112,30 @@ export interface ConfigWebsite {
show_files_per_user: boolean;
}
export interface ConfigDiscord {
url: string;
username: string;
avatar_url: string;
upload: ConfigDiscordContent;
shorten: ConfigDiscordContent;
}
export interface ConfigDiscordContent {
content: string;
embed: ConfigDiscordEmbed;
}
export interface ConfigDiscordEmbed {
title?: string;
description?: string;
footer?: string;
color?: number;
thumbnail?: boolean;
timestamp: boolean;
image: boolean;
}
export interface Config {
core: ConfigCore;
uploader: ConfigUploader;
@ -119,4 +143,5 @@ export interface Config {
ratelimit: ConfigRatelimit;
datasource: ConfigDatasource;
website: ConfigWebsite;
discord: ConfigDiscord;
}

View file

@ -83,6 +83,28 @@ export default function readConfig() {
map('WEBSITE_TITLE', 'string', 'website.title'),
map('WEBSITE_SHOW_FILES_PER_USER', 'boolean', 'website.show_files_per_user'),
map('DISCORD_URL', 'string', 'discord.url'),
map('DISCORD_USERNAME', 'string', 'discord.username'),
map('DISCORD_AVATAR_URL', 'string', 'discord.avatar_url'),
map('DISCORD_UPLOAD_CONTENT', 'string', 'discord.upload.content'),
map('DISCORD_UPLOAD_EMBED_TITLE', 'string', 'discord.upload.embed.title'),
map('DISCORD_UPLOAD_EMBED_DESCRIPTION', 'string', 'discord.upload.embed.description'),
map('DISCORD_UPLOAD_EMBED_FOOTER', 'string', 'discord.upload.embed.footer'),
map('DISCORD_UPLOAD_EMBED_COLOR', 'number', 'discord.upload.embed.color'),
map('DISCORD_UPLOAD_EMBED_IMAGE', 'boolean', 'discord.upload.embed.image'),
map('DISCORD_UPLOAD_EMBED_THUMBNAIL', 'boolean', 'discord.upload.embed.thumbnail'),
map('DISCORD_UPLOAD_EMBED_TIMESTAMP', 'boolean', 'discord.upload.embed.timestamp'),
map('DISCORD_SHORTEN_CONTENT', 'string', 'discord.shorten.content'),
map('DISCORD_SHORTEN_EMBED_TITLE', 'string', 'discord.shorten.embed.title'),
map('DISCORD_SHORTEN_EMBED_DESCRIPTION', 'string', 'discord.shorten.embed.description'),
map('DISCORD_SHORTEN_EMBED_FOOTER', 'string', 'discord.shorten.embed.footer'),
map('DISCORD_SHORTEN_EMBED_COLOR', 'number', 'discord.shorten.embed.color'),
map('DISCORD_SHORTEN_EMBED_IMAGE', 'boolean', 'discord.shorten.embed.image'),
map('DISCORD_SHORTEN_EMBED_THUMBNAIL', 'boolean', 'discord.shorten.embed.thumbnail'),
map('DISCORD_SHORTEN_EMBED_TIMESTAMP', 'boolean', 'discord.shorten.embed.timestamp'),
];
const config = {};
@ -111,6 +133,5 @@ export default function readConfig() {
set(config, map.path, parsed);
}
}
return config;
}

View file

@ -1,6 +1,19 @@
import { Config } from 'lib/config/Config';
import { object, bool, string, number, boolean, array } from 'yup';
const discord_content = object({
content: string().nullable(),
embed: object({
title: string().nullable().default(null),
description: string().nullable().default(null),
footer: string().nullable().default(null),
color: string().nullable().default(null),
thumbnail: boolean().default(false),
image: boolean().default(true),
timestamp: boolean().default(true),
}).nullable().default(null),
}).nullable().default(null);
const validator = object({
core: object({
https: bool().default(false),
@ -54,6 +67,13 @@ const validator = object({
title: string().default('Zipline'),
show_files_per_user: boolean().default(true),
}),
discord: object({
url: string(),
username: string().default('Zipline'),
avatar_url: string().default('https://raw.githubusercontent.com/diced/zipline/9b60147e112ec5b70170500b85c75ea621f41d03/public/zipline.png'),
upload: discord_content,
shorten: discord_content,
}).optional().nullable().default(null),
});
export default function validate(config): Config {
@ -89,7 +109,9 @@ export default function validate(config): Config {
break;
}
}
console.log(validated);
return validated as unknown as Config;
} catch (e) {
if (process.env.ZIPLINE_DOCKER_BUILD) return null;

132
src/lib/discord.ts Normal file
View file

@ -0,0 +1,132 @@
import { Image, Url, User } from '@prisma/client';
import { ConfigDiscordContent } from 'lib/config/Config';
import config from 'lib/config';
import Logger from './logger';
// [user, image, url, route (ex. https://example.com/u/something.png)]
export type Args = [User, Image?, Url?, string?];
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(/{image.id}/gi, args[1].id.toString())
.replace(/{image.mime}/gi, args[1].mimetype)
.replace(/{image.file}/gi, args[1].file)
.replace(/{image.created_at.full_string}/gi, args[1].created_at.toLocaleString())
.replace(/{image.created_at.time_string}/gi, args[1].created_at.toLocaleTimeString())
.replace(/{image.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): ConfigDiscordContent & { url: string } {
return {
content: parse(content.content, args),
embed: content.embed ? {
title: parse(content.embed.title, args),
description: parse(content.embed.description, args),
footer: parse(content.embed.footer, args),
color: content.embed.color,
thumbnail: content.embed.thumbnail,
timestamp: content.embed.timestamp,
image: content.embed.image,
} : null,
url: args[3],
};
}
export async function sendUpload(user: User, image: Image, host: string) {
if (!config.discord.upload) return;
const parsed = parseContent(config.discord.upload, [user, image, null, host]);
const isImage = image.mimetype.startsWith('image/');
const body = {
username: config.discord.username,
avatar_url: config.discord.avatar_url,
content: parsed.content ?? null,
embeds: parsed.embed ? [{
title: parsed.embed.title ?? null,
description: parsed.embed.description ?? null,
url: parsed.url ?? null,
timestamp: parsed.embed.timestamp ? image.created_at.toISOString() : null,
color: parsed.embed.color ?? null,
footer: parsed.embed.footer ? {
text: parsed.embed.footer,
} : null,
thumbnail: isImage && parsed.embed.thumbnail ? {
url: parsed.url,
} : null,
image: isImage && parsed.embed.image ? {
url: parsed.url,
} : null,
}] : null,
};
const res = await fetch(config.discord.url, {
method: 'POST',
body: JSON.stringify(body),
headers: {
'Content-Type': 'application/json',
},
});
if (!res.ok) {
Logger.get('discord').error(`Failed to send upload notification to discord: ${res.status} ${res.statusText}`);
}
return;
}
export async function sendShorten(user: User, url: Url, host: string) {
if (!config.discord.shorten) return;
const parsed = parseContent(config.discord.shorten, [user, null, url, host]);
const body = {
username: config.discord.username,
avatar_url: config.discord.avatar_url,
content: parsed.content ?? null,
embeds: parsed.embed ? [{
title: parsed.embed.title ?? null,
description: parsed.embed.description ?? null,
url: parsed.url ?? null,
timestamp: parsed.embed.timestamp ? url.created_at.toISOString() : null,
color: parsed.embed.color ?? null,
footer: parsed.embed.footer ? {
text: parsed.embed.footer,
} : null,
}] : null,
};
console.log(body);
const res = await fetch(config.discord.url, {
method: 'POST',
body: JSON.stringify(body),
headers: {
'Content-Type': 'application/json',
},
});
if (!res.ok) {
Logger.get('discord').error(`Failed to send url shorten notification to discord: ${res.status} ${res.statusText}`);
}
return;
}

View file

@ -3,6 +3,8 @@ import zconfig from 'lib/config';
import { NextApiReq, NextApiRes, withZipline } from 'lib/middleware/withZipline';
import { createInvisURL, randomChars } from 'lib/util';
import Logger from 'lib/logger';
import config from 'lib/config';
import { sendShorten } from 'lib/discord';
async function handler(req: NextApiReq, res: NextApiRes) {
if (req.method !== 'POST') return res.forbid('no allow');
@ -42,7 +44,11 @@ async function handler(req: NextApiReq, res: NextApiRes) {
if (req.headers.zws) invis = await createInvisURL(zconfig.urls.length, url.id);
Logger.get('url').info(`User ${user.username} (${user.id}) shortenned a url ${url.destination} (${url.id})`);
Logger.get('url').info(`User ${user.username} (${user.id}) shortenned a url ${url.destination} (${url.id})`);
if (config.discord.shorten) {
await sendShorten(user, url, `${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}${zconfig.urls.route}/${req.body.vanity ? req.body.vanity : invis ? invis.invis : url.id}`);
}
return res.json({ url: `${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}${zconfig.urls.route}/${req.body.vanity ? req.body.vanity : invis ? invis.invis : url.id}` });
}

View file

@ -11,6 +11,7 @@ import { randomUUID } from 'crypto';
import sharp from 'sharp';
import { humanTime, parseExpiry } from 'lib/clientUtils';
import { StringValue } from 'ms';
import { sendUpload } from 'lib/discord';
const uploader = multer();
@ -127,6 +128,10 @@ async function handler(req: NextApiReq, res: NextApiRes) {
} else {
response.files.push(`${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}${zconfig.uploader.route === '/' ? '' : zconfig.uploader.route}/${invis ? invis.invis : image.file}`);
}
if (zconfig.discord.upload) {
await sendUpload(user, image, `${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}${zconfig.uploader.route === '/' ? '' : zconfig.uploader.route}/${invis ? invis.invis : image.file}`);
}
}
if (user.administrator && zconfig.ratelimit.admin > 0) {