summary refs log tree commit diff
diff options
context:
space:
mode:
authorMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-10-03 23:38:53 +1100
committerMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-10-03 23:38:53 +1100
commite8815f1fd595858e85f1c41df9f4cbe51f968760 (patch)
tree0900328585d42d12b477ffec22f26f42de068aac
parentMake imagor optional (diff)
downloadserver-e8815f1fd595858e85f1c41df9f4cbe51f968760.tar.xz
Twitter embeds
-rw-r--r--src/api/util/handlers/Message.ts12
-rw-r--r--src/api/util/utility/EmbedHandlers.ts95
-rw-r--r--src/util/entities/Config.ts6
3 files changed, 102 insertions, 11 deletions
diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts
index 09b86fc2..d4f0e995 100644
--- a/src/api/util/handlers/Message.ts
+++ b/src/api/util/handlers/Message.ts
@@ -192,19 +192,21 @@ export async function postHandleMessage(message: Message) {
 		const url = new URL(link);
 
 		// bit gross, but whatever!
-		const { endpointPublic } = Config.get().cdn;
-		const handler = url.hostname == new URL(endpointPublic!).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"];
+		const endpointPublic = Config.get().cdn.endpointPublic || "http://127.0.0.1";	// lol
+		const handler = url.hostname == new URL(endpointPublic).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"];
 
 		try {
 			const res = await handler(url);
 			if (!res) continue;
-			embed = res;
+			// tried to use shorthand but types didn't like me L
+			if (Array.isArray(res))
+				data.embeds.push(...res)
+			else
+				data.embeds.push(res);
 		}
 		catch (e) {
 			continue;
 		}
-
-		data.embeds.push(embed);
 	}
 
 	await Promise.all([
diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts
index 65fecd4d..831cab9a 100644
--- a/src/api/util/utility/EmbedHandlers.ts
+++ b/src/api/util/utility/EmbedHandlers.ts
@@ -16,7 +16,7 @@ export const DEFAULT_FETCH_OPTIONS: any = {
 	method: "GET",
 };
 
-export const getProxyUrl = (url: URL, width: number, height: number) => {
+export const getProxyUrl = (url: URL, width: number, height: number): string => {
 	const { endpointPublic, resizeWidthMax, resizeHeightMax, imagorServerUrl } = Config.get().cdn;
 	const secret = Config.get().security.jwtSecret;	// maybe shouldn't use this?
 	width = Math.min(width || 500, resizeWidthMax || width);
@@ -116,7 +116,7 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => {
 	};
 };
 
-export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | null>; } = {
+export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed[] | null>; } = {
 	// the url does not have a special handler
 	"default": async (url: URL) => {
 		const type = await fetch(url, {
@@ -168,6 +168,89 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | null>
 	// TODO: twitter, facebook
 	// have to use their APIs or something because they don't send the metas in initial html
 
+	"twitter.com": (url: URL) => { return EmbedHandlers["www.twitter.com"](url); },
+	"www.twitter.com": async (url: URL) => {
+		const token = Config.get().external.twitter;
+		if (!token) return null; // todo move to config
+
+		if (!url.href.includes("/status/")) return null;	// TODO;
+		const id = url.pathname.split("/")[3];	// super bad lol
+		if (!parseInt(id)) return null;
+		const endpointUrl = `https://api.twitter.com/2/tweets/${id}`
+			+ `?expansions=author_id,attachments.media_keys`
+			+ `&media.fields=url,width,height`
+			+ `&tweet.fields=created_at,public_metrics`
+			+ `&user.fields=profile_image_url`;
+
+
+		const response = await fetch(endpointUrl, {
+			...DEFAULT_FETCH_OPTIONS,
+			headers: {
+				authorization: `Bearer ${token}`,
+			}
+		});
+		const json = await response.json();
+		if (json.errors) return null;
+		const author = json.includes.users[0];
+		const text = json.data.text;
+		const created_at = new Date(json.data.created_at);
+		const metrics = json.data.public_metrics;
+		let media = json.includes.media?.filter((x: any) => x.type == "photo") as any[];	// TODO: video
+
+		const embed: Embed = {
+			type: EmbedType.rich,
+			url: `${url.origin}${url.pathname}`,
+			description: text,
+			author: {
+				url: `https://twitter.com/${author.username}`,
+				name: `${author.name} (@${author.username})`,
+				proxy_icon_url: getProxyUrl(new URL(author.profile_image_url), 400, 400),
+				icon_url: author.profile_image_url,
+			},
+			timestamp: created_at,
+			fields: [
+				{ inline: true, name: "Likes", value: metrics.like_count.toString() },
+				{ inline: true, name: "Retweet", value: metrics.retweet_count.toString() },
+			],
+			color: 1942002,
+			footer: {
+				text: "Twitter",
+				proxy_icon_url: getProxyUrl(new URL("https://abs.twimg.com/icons/apple-touch-icon-192x192.png"), 192, 192),
+				icon_url: "https://abs.twimg.com/icons/apple-touch-icon-192x192.png"
+			},
+			// Discord doesn't send this?
+			// provider: {
+			// 	name: "Twitter",
+			// 	url: "https://twitter.com"
+			// },
+		};
+
+		if (media) {
+			embed.image = {
+				width: media[0].width,
+				height: media[0].height,
+				url: media[0].url,
+				proxy_url: getProxyUrl(new URL(media[0].url), media[0].width, media[0].height)
+			};
+			media.shift();
+		}
+
+		return embed;
+
+		// TODO: Client won't merge these into a single embed, for some reason.
+		// return [embed, ...media.map((x: any) => ({
+		// 	// generate new embeds for each additional attachment
+		// 	type: EmbedType.rich,
+		// 	url: url.href,
+		// 	image: {
+		// 		width: x.width,
+		// 		height: x.height,
+		// 		url: x.url,
+		// 		proxy_url: getProxyUrl(new URL(x.url), x.width, x.height)
+		// 	}
+		// }))];
+	},
+
 	"open.spotify.com": async (url: URL) => {
 		const response = await doFetch(url);
 		if (!response) return null;
@@ -191,7 +274,7 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | null>
 		};
 	},
 
-	"pixiv.net": async (url: URL) => { return EmbedHandlers["www.pixiv.net"](url); },
+	"pixiv.net": (url: URL) => { return EmbedHandlers["www.pixiv.net"](url); },
 	"www.pixiv.net": async (url: URL) => {
 		const response = await doFetch(url);
 		if (!response) return null;
@@ -242,7 +325,7 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | null>
 		};
 	},
 
-	"reddit.com": async (url: URL) => { return EmbedHandlers["www.reddit.com"](url); },
+	"reddit.com": (url: URL) => { return EmbedHandlers["www.reddit.com"](url); },
 	"www.reddit.com": async (url: URL) => {
 		const res = await EmbedHandlers["default"](url);
 		return {
@@ -254,7 +337,7 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | null>
 		};
 	},
 
-	"youtube.com": async (url: URL) => { return EmbedHandlers["www.youtube.com"](url); },
+	"youtube.com": (url: URL) => { return EmbedHandlers["www.youtube.com"](url); },
 	"www.youtube.com": async (url: URL): Promise<Embed | null> => {
 		const response = await doFetch(url);
 		if (!response) return null;
@@ -304,4 +387,4 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | null>
 			}
 		};
 	},
-};
\ No newline at end of file
+};;
\ No newline at end of file
diff --git a/src/util/entities/Config.ts b/src/util/entities/Config.ts
index 42006fc9..2b75a049 100644
--- a/src/util/entities/Config.ts
+++ b/src/util/entities/Config.ts
@@ -209,6 +209,9 @@ export interface ConfigValue {
 		traceSampleRate: number;
 		environment: string;
 	};
+	external: {
+		twitter: string | null;
+	}
 }
 
 export const DefaultConfigOptions: ConfigValue = {
@@ -418,4 +421,7 @@ export const DefaultConfigOptions: ConfigValue = {
 		traceSampleRate: 1.0,
 		environment: hostname(),
 	},
+	external: {
+		twitter: null,
+	}
 };