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",
|