summary refs log tree commit diff
path: root/src/api/util/utility/EmbedHandlers.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/util/utility/EmbedHandlers.ts')
-rw-r--r--src/api/util/utility/EmbedHandlers.ts140
1 files changed, 140 insertions, 0 deletions
diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts
new file mode 100644
index 00000000..d6b69d86
--- /dev/null
+++ b/src/api/util/utility/EmbedHandlers.ts
@@ -0,0 +1,140 @@
+import { Config, Embed, EmbedType } from "@fosscord/util";
+import fetch, { Response } from "node-fetch";
+import * as cheerio from "cheerio";
+import probe from "probe-image-size";
+
+export const DEFAULT_FETCH_OPTIONS: any = {
+	redirect: "follow",
+	follow: 1,
+	headers: {
+		"user-agent":
+			"Mozilla/5.0 (compatible; Fosscord/1.0; +https://github.com/fosscord/fosscord)",
+	},
+	// size: 1024 * 1024 * 5, 	// grabbed from config later
+	compress: true,
+	method: "GET",
+};
+
+export const getProxyUrl = (url: URL, width: number, height: number) => {
+	const { endpointPublic, resizeWidthMax, resizeHeightMax } = Config.get().cdn;
+	width = Math.min(width || 500, resizeWidthMax || width);
+	height = Math.min(height || 500, resizeHeightMax || width);
+	return `${endpointPublic}/external/resize/${encodeURIComponent(url.href)}?width=${width}&height=${height}`;
+};
+
+export const getMetaDescriptions = async (url: URL) => {
+	let response: Response;
+	try {
+		response = await fetch(url, {
+			...DEFAULT_FETCH_OPTIONS,
+			size: Config.get().limits.message.maxEmbedDownloadSize,
+		});
+	}
+	catch (e) {
+		return null;
+	}
+
+	const text = await response.text();
+	const $ = cheerio.load(text);
+
+	return {
+		title: $('meta[property="og:title"]').attr("content"),
+		provider_name: $('meta[property="og:site_name"]').text(),
+		author: $('meta[property="article:author"]').attr("content"),
+		description:
+			$('meta[property="og:description"]').attr("content") ||
+			$('meta[property="description"]').attr("content"),
+		image: $('meta[property="og:image"]').attr("content"),
+		width: parseInt(
+			$('meta[property="og:image:width"]').attr("content") ||
+			"",
+		) || undefined,
+		height: parseInt(
+			$('meta[property="og:image:height"]').attr("content") ||
+			"",
+		) || undefined,
+		url: $('meta[property="og:url"]').attr("content"),
+		youtube_embed: $(`meta[property="og:video:secure_url"]`).attr("content")
+	};
+};
+
+const genericImageHandler = async (url: URL): Promise<Embed | null> => {
+	const metas = await getMetaDescriptions(url);
+	if (!metas) return null;
+
+	const result = await probe(url.href);
+
+	const width = metas.width || result.width;
+	const height = metas.height || result.height;
+
+	return {
+		url: url.href,
+		type: EmbedType.image,
+		thumbnail: {
+			width: width,
+			height: height,
+			url: url.href,
+			proxy_url: getProxyUrl(url, result.width, result.height),
+		}
+	};
+};
+
+export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | null>; } = {
+	// the url does not have a special handler
+	"default": genericImageHandler,
+
+	"giphy.com": genericImageHandler,
+	"media4.giphy.com": genericImageHandler,
+	"tenor.com": genericImageHandler,
+	"c.tenor.com": genericImageHandler,
+	"media.tenor.com": genericImageHandler,
+
+	"www.youtube.com": async (url: URL): Promise<Embed | null> => {
+		const metas = await getMetaDescriptions(url);
+		if (!metas) return null;
+
+		return {
+			video: {
+				// TODO: does this adjust with aspect ratio?
+				width: metas.width,
+				height: metas.height,
+				url: metas.youtube_embed!,
+			},
+			url: url.href,
+			type: EmbedType.video,
+			title: metas.title,
+			thumbnail: {
+				width: metas.width,
+				height: metas.height,
+				url: metas.image,
+				proxy_url: getProxyUrl(new URL(metas.image!), metas.width!, metas.height!),
+			},
+			provider: {
+				url: "https://www.youtube.com",
+				name: "YouTube",
+			},
+			description: metas.description,
+			color: 16711680,
+			author: {
+				name: metas.author,
+				// TODO: author channel url
+			}
+		};
+	},
+
+	// the url is an image from this instance
+	"self": async (url: URL): Promise<Embed | null> => {
+		const result = await probe(url.href);
+
+		return {
+			url: url.href,
+			type: EmbedType.image,
+			thumbnail: {
+				width: result.width,
+				height: result.height,
+				url: url.href,
+				proxy_url: url.href,
+			}
+		};
+	},
+};
\ No newline at end of file