summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-10-01 15:35:07 +1000
committerMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-10-01 15:59:30 +1000
commit070ce373e90399d9df2a8986e0c7728724163da7 (patch)
treec629c0f7f9cbf785c0a4b9a43a785c8f530f343b /src
parentBetter embed handling (diff)
downloadserver-070ce373e90399d9df2a8986e0c7728724163da7.tar.xz
Slightly better embeds
Diffstat (limited to 'src')
-rw-r--r--src/api/util/utility/EmbedHandlers.ts182
-rw-r--r--src/cdn/routes/external.ts4
2 files changed, 159 insertions, 27 deletions
diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts
index d6b69d86..a4625b3b 100644
--- a/src/api/util/utility/EmbedHandlers.ts
+++ b/src/api/util/utility/EmbedHandlers.ts
@@ -2,6 +2,7 @@ import { Config, Embed, EmbedType } from "@fosscord/util";
 import fetch, { Response } from "node-fetch";
 import * as cheerio from "cheerio";
 import probe from "probe-image-size";
+import imageSize from "image-size";
 
 export const DEFAULT_FETCH_OPTIONS: any = {
 	redirect: "follow",
@@ -22,19 +23,7 @@ export const getProxyUrl = (url: URL, width: number, height: number) => {
 	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();
+export const getMetaDescriptions = async (text: string) => {
 	const $ = cheerio.load(text);
 
 	return {
@@ -44,7 +33,7 @@ export const getMetaDescriptions = async (url: URL) => {
 		description:
 			$('meta[property="og:description"]').attr("content") ||
 			$('meta[property="description"]').attr("content"),
-		image: $('meta[property="og:image"]').attr("content"),
+		image: $('meta[property="og:image"]').attr("content") || $(`meta[property="twitter:image"]`).attr("content"),
 		width: parseInt(
 			$('meta[property="og:image:width"]').attr("content") ||
 			"",
@@ -54,34 +43,82 @@ export const getMetaDescriptions = async (url: URL) => {
 			"",
 		) || undefined,
 		url: $('meta[property="og:url"]').attr("content"),
-		youtube_embed: $(`meta[property="og:video:secure_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 doFetch = async (url: URL) => {
+	try {
+		return await fetch(url, {
+			...DEFAULT_FETCH_OPTIONS,
+			size: Config.get().limits.message.maxEmbedDownloadSize,
+		});
+	}
+	catch (e) {
+		return null;
+	}
+};
 
-	const result = await probe(url.href);
+const genericImageHandler = async (url: URL): Promise<Embed | null> => {
+	const response = await doFetch(url);
+	if (!response) return null;
+	const metas = await getMetaDescriptions(await response.text());
 
-	const width = metas.width || result.width;
-	const height = metas.height || result.height;
+	if (!metas.width || !metas.height) {
+		const result = await probe(url.href);
+		metas.width = result.width;
+		metas.height = result.height;
+	}
 
 	return {
 		url: url.href,
 		type: EmbedType.image,
 		thumbnail: {
-			width: width,
-			height: height,
+			width: metas.width,
+			height: metas.height,
 			url: url.href,
-			proxy_url: getProxyUrl(url, result.width, result.height),
+			proxy_url: getProxyUrl(new URL(metas.image || url.href), metas.width, metas.height),
 		}
 	};
 };
 
 export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | null>; } = {
 	// the url does not have a special handler
-	"default": genericImageHandler,
+	"default": async (url: URL) => {
+		const response = await doFetch(url);
+		if (!response) return null;
+
+		if (response.headers.get("content-type")?.indexOf("image") !== -1) {
+			// this is an image
+
+			const size = imageSize(await response.buffer());
+
+			return {
+				url: url.href,
+				type: EmbedType.image,
+				image: {
+					width: size.width,
+					height: size.height,
+					url: url.href,
+					proxy_url: getProxyUrl(url, size.width!, size.height!),
+				}
+			};
+		}
+
+		const metas = await getMetaDescriptions(await response.text());
+		return {
+			url: url.href,
+			type: EmbedType.link,
+			title: metas.title,
+			thumbnail: {
+				width: metas.width,
+				height: metas.height,
+				url: metas.image,
+				proxy_url: getProxyUrl(new URL(metas.image!), metas.width!, metas.height!),
+			},
+			description: metas.description,
+		};
+	},
 
 	"giphy.com": genericImageHandler,
 	"media4.giphy.com": genericImageHandler,
@@ -89,9 +126,100 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | null>
 	"c.tenor.com": genericImageHandler,
 	"media.tenor.com": genericImageHandler,
 
+	// TODO: twitter, facebook
+	// have to use their APIs or something because they don't send the metas in initial html
+
+	"open.spotify.com": async (url: URL) => {
+		const response = await doFetch(url);
+		if (!response) return null;
+		const metas = await getMetaDescriptions(await response.text());
+
+		return {
+			url: url.href,
+			type: EmbedType.link,
+			title: metas.title,
+			description: metas.description,
+			thumbnail: {
+				width: 640,
+				height: 640,
+				proxy_url: getProxyUrl(new URL(metas.image!), 640, 640),
+				url: metas.image,
+			},
+			provider: {
+				url: "https://spotify.com",
+				name: "Spotify",
+			}
+		};
+	},
+
+	"pixiv.net": async (url: URL) => { return EmbedHandlers["www.pixiv.net"](url); },
+	"www.pixiv.net": async (url: URL) => {
+		const response = await doFetch(url);
+		if (!response) return null;
+		const metas = await getMetaDescriptions(await response.text());
+
+		// TODO: doesn't show images. think it's a bug in the cdn
+		return {
+			url: url.href,
+			type: EmbedType.image,
+			title: metas.title,
+			description: metas.description,
+			image: {
+				width: metas.width,
+				height: metas.height,
+				url: url.href,
+				proxy_url: getProxyUrl(new URL(metas.image!), metas.width!, metas.height!),
+			},
+			provider: {
+				url: "https://pixiv.net",
+				name: "Pixiv"
+			}
+		};
+	},
+
+	"store.steampowered.com": async (url: URL) => {
+		const response = await doFetch(url);
+		if (!response) return null;
+		const metas = await getMetaDescriptions(await response.text());
+
+		return {
+			url: url.href,
+			type: EmbedType.rich,
+			title: metas.title,
+			description: metas.description,
+			image: {	// TODO: meant to be thumbnail.
+				// isn't this standard across all of steam?
+				width: 460,
+				height: 215,
+				url: metas.image,
+				proxy_url: getProxyUrl(new URL(metas.image!), 460, 215),
+			},
+			provider: {
+				url: "https://store.steampowered.com",
+				name: "Steam"
+			},
+			// TODO: fields for release date
+			// TODO: Video
+		};
+	},
+
+	"reddit.com": async (url: URL) => { return EmbedHandlers["www.reddit.com"](url); },
+	"www.reddit.com": async (url: URL) => {
+		const res = await EmbedHandlers["default"](url);
+		return {
+			...res,
+			color: 16777215,
+			provider: {
+				name: "reddit"
+			}
+		};
+	},
+
+	"youtube.com": async (url: URL) => { return EmbedHandlers["www.youtube.com"](url); },
 	"www.youtube.com": async (url: URL): Promise<Embed | null> => {
-		const metas = await getMetaDescriptions(url);
-		if (!metas) return null;
+		const response = await doFetch(url);
+		if (!response) return null;
+		const metas = await getMetaDescriptions(await response.text());
 
 		return {
 			video: {
diff --git a/src/cdn/routes/external.ts b/src/cdn/routes/external.ts
index 405e665e..33528a7b 100644
--- a/src/cdn/routes/external.ts
+++ b/src/cdn/routes/external.ts
@@ -77,6 +77,10 @@ router.get("/resize/:url", async (req: Request, res: Response) => {
 		throw new HTTPError("Couldn't fetch website");
 	}
 
+	if (response.headers.get("content-type")?.indexOf("image") === -1) {
+		throw new HTTPError("Content type is not image.");
+	}
+
 	const resizedBuffer = await sharp(buffer)
 		.resize(parseInt(width as string), parseInt(height as string), {
 			fit: "inside",