Rewrite thumbnail/image generation for embeds

This commit is contained in:
Madeline 2023-09-03 14:17:11 +10:00
parent fb7409947c
commit e64c34adea
No known key found for this signature in database
GPG Key ID: 1958E017C36F2E47

View File

@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Config, Embed, EmbedType } from "@spacebar/util"; import { Config, Embed, EmbedImage, EmbedType } from "@spacebar/util";
import * as cheerio from "cheerio"; import * as cheerio from "cheerio";
import crypto from "crypto"; import crypto from "crypto";
import fetch, { RequestInit } from "node-fetch"; import fetch, { RequestInit } from "node-fetch";
@ -35,6 +35,20 @@ export const DEFAULT_FETCH_OPTIONS: RequestInit = {
method: "GET", method: "GET",
}; };
const makeEmbedImage = (
url: string | undefined,
width: number | undefined,
height: number | undefined,
): Required<EmbedImage> | undefined => {
if (!url || !width || !height) return undefined;
return {
url,
width,
height,
proxy_url: getProxyUrl(new URL(url), width, height),
};
};
let hasWarnedAboutImagor = false; let hasWarnedAboutImagor = false;
export const getProxyUrl = ( export const getProxyUrl = (
@ -82,6 +96,15 @@ const getMeta = ($: cheerio.CheerioAPI, name: string): string | undefined => {
return ret.trim().length == 0 ? undefined : ret; return ret.trim().length == 0 ? undefined : ret;
}; };
const tryParseInt = (str: string | undefined) => {
if (!str) return undefined;
try {
return parseInt(str);
} catch (e) {
return undefined;
}
};
export const getMetaDescriptions = (text: string) => { export const getMetaDescriptions = (text: string) => {
const $ = cheerio.load(text); const $ = cheerio.load(text);
@ -94,8 +117,8 @@ export const getMetaDescriptions = (text: string) => {
image: getMeta($, "og:image") || getMeta($, "twitter:image"), image: getMeta($, "og:image") || getMeta($, "twitter:image"),
image_fallback: $(`image`).attr("src"), image_fallback: $(`image`).attr("src"),
video_fallback: $(`video`).attr("src"), video_fallback: $(`video`).attr("src"),
width: parseInt(getMeta($, "og:image:width") || "0"), width: tryParseInt(getMeta($, "og:image:width")),
height: parseInt(getMeta($, "og:image:height") || "0"), height: tryParseInt(getMeta($, "og:image:height")),
url: getMeta($, "og:url"), url: getMeta($, "og:url"),
youtube_embed: getMeta($, "og:video:secure_url"), youtube_embed: getMeta($, "og:video:secure_url"),
@ -120,13 +143,11 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => {
method: "HEAD", method: "HEAD",
}); });
let width: number, height: number, image: string | undefined; let image;
if (type.headers.get("content-type")?.indexOf("image") !== -1) { if (type.headers.get("content-type")?.indexOf("image") !== -1) {
const result = await probe(url.href); const result = await probe(url.href);
width = result.width; image = makeEmbedImage(url.href, result.width, result.height);
height = result.height;
image = url.href;
} else if (type.headers.get("content-type")?.indexOf("video") !== -1) { } else if (type.headers.get("content-type")?.indexOf("video") !== -1) {
// TODO // TODO
return null; return null;
@ -135,22 +156,19 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => {
const response = await doFetch(url); const response = await doFetch(url);
if (!response) return null; if (!response) return null;
const metas = getMetaDescriptions(await response.text()); const metas = getMetaDescriptions(await response.text());
width = metas.width; image = makeEmbedImage(
height = metas.height; metas.image || metas.image_fallback,
image = metas.image || metas.image_fallback; metas.width,
metas.height,
);
} }
if (!width || !height || !image) return null; if (!image) return null;
return { return {
url: url.href, url: url.href,
type: EmbedType.image, type: EmbedType.image,
thumbnail: { thumbnail: image,
width: width,
height: height,
url: url.href,
proxy_url: getProxyUrl(new URL(image), width, height),
},
}; };
}; };
@ -176,13 +194,15 @@ export const EmbedHandlers: {
if (!metas.image) metas.image = metas.image_fallback; if (!metas.image) metas.image = metas.image_fallback;
let image: Required<EmbedImage> | undefined;
if (metas.image && (!metas.width || !metas.height)) { if (metas.image && (!metas.width || !metas.height)) {
const result = await probe(metas.image); const result = await probe(metas.image);
metas.width = result.width; image = makeEmbedImage(metas.image, result.width, result.height);
metas.height = result.height;
} }
if (!metas.image && (!metas.title || !metas.description)) { if (!image && (!metas.title || !metas.description)) {
// we don't have any content to display
return null; return null;
} }
@ -191,24 +211,11 @@ export const EmbedHandlers: {
if (metas.type == "object") embedType = EmbedType.article; // github if (metas.type == "object") embedType = EmbedType.article; // github
if (metas.type == "rich") embedType = EmbedType.rich; if (metas.type == "rich") embedType = EmbedType.rich;
if (metas.width && metas.width < 400) embedType = EmbedType.link;
return { return {
url: url.href, url: url.href,
type: embedType, type: embedType,
title: metas.title, title: metas.title,
thumbnail: { thumbnail: image,
width: metas.width,
height: metas.height,
url: metas.image,
proxy_url: metas.image
? getProxyUrl(
new URL(metas.image),
metas.width,
metas.height,
)
: undefined,
},
description: metas.description, description: metas.description,
}; };
}, },
@ -340,14 +347,7 @@ export const EmbedHandlers: {
type: EmbedType.link, type: EmbedType.link,
title: metas.title, title: metas.title,
description: metas.description, description: metas.description,
thumbnail: { thumbnail: makeEmbedImage(metas.image, 640, 640),
width: 640,
height: 640,
proxy_url: metas.image
? getProxyUrl(new URL(metas.image), 640, 640)
: undefined,
url: metas.image,
},
provider: { provider: {
url: "https://spotify.com", url: "https://spotify.com",
name: "Spotify", name: "Spotify",
@ -369,18 +369,11 @@ export const EmbedHandlers: {
type: EmbedType.image, type: EmbedType.image,
title: metas.title, title: metas.title,
description: metas.description, description: metas.description,
image: { image: makeEmbedImage(
width: metas.width, metas.image || metas.image_fallback,
height: metas.height,
url: url.href,
proxy_url: metas.image
? getProxyUrl(
new URL(metas.image),
metas.width, metas.width,
metas.height, metas.height,
) ),
: undefined,
},
provider: { provider: {
url: "https://pixiv.net", url: "https://pixiv.net",
name: "Pixiv", name: "Pixiv",
@ -437,37 +430,31 @@ export const EmbedHandlers: {
const metas = getMetaDescriptions(await response.text()); const metas = getMetaDescriptions(await response.text());
return { return {
video: { video: makeEmbedImage(
// TODO: does this adjust with aspect ratio? metas.youtube_embed,
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: metas.image
? getProxyUrl(
new URL(metas.image),
metas.width, metas.width,
metas.height, metas.height,
) ),
: undefined, url: url.href,
}, type: metas.youtube_embed ? EmbedType.video : EmbedType.link,
title: metas.title,
thumbnail: makeEmbedImage(
metas.image || metas.image_fallback,
metas.width,
metas.height,
),
provider: { provider: {
url: "https://www.youtube.com", url: "https://www.youtube.com",
name: "YouTube", name: "YouTube",
}, },
description: metas.description, description: metas.description,
color: 16711680, color: 16711680,
author: { author: metas.author
? {
name: metas.author, name: metas.author,
// TODO: author channel url // TODO: author channel url
}, }
: undefined,
}; };
}, },
@ -487,12 +474,7 @@ export const EmbedHandlers: {
url: url.href, url: url.href,
type: EmbedType.rich, type: EmbedType.rich,
title: `xkcd: ${metas.title}`, title: `xkcd: ${metas.title}`,
image: { image: makeEmbedImage(metas.image, width, height),
width,
height,
url: metas.image,
proxy_url: getProxyUrl(new URL(metas.image), width, height),
},
footer: hoverText footer: hoverText
? { ? {
text: hoverText, text: hoverText,