diff options
author | Madeline <46743919+MaddyUnderStars@users.noreply.github.com> | 2022-10-01 15:35:07 +1000 |
---|---|---|
committer | Madeline <46743919+MaddyUnderStars@users.noreply.github.com> | 2022-10-01 15:59:30 +1000 |
commit | 070ce373e90399d9df2a8986e0c7728724163da7 (patch) | |
tree | c629c0f7f9cbf785c0a4b9a43a785c8f530f343b /src | |
parent | Better embed handling (diff) | |
download | server-070ce373e90399d9df2a8986e0c7728724163da7.tar.xz |
Slightly better embeds
Diffstat (limited to 'src')
-rw-r--r-- | src/api/util/utility/EmbedHandlers.ts | 182 | ||||
-rw-r--r-- | src/cdn/routes/external.ts | 4 |
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", |