mirror of
https://github.com/spacebarchat/server.git
synced 2024-09-20 01:31:34 +02:00
Slightly better embeds
This commit is contained in:
parent
e18af893f6
commit
070ce373e9
@ -2,6 +2,7 @@ import { Config, Embed, EmbedType } from "@fosscord/util";
|
|||||||
import fetch, { Response } from "node-fetch";
|
import fetch, { Response } from "node-fetch";
|
||||||
import * as cheerio from "cheerio";
|
import * as cheerio from "cheerio";
|
||||||
import probe from "probe-image-size";
|
import probe from "probe-image-size";
|
||||||
|
import imageSize from "image-size";
|
||||||
|
|
||||||
export const DEFAULT_FETCH_OPTIONS: any = {
|
export const DEFAULT_FETCH_OPTIONS: any = {
|
||||||
redirect: "follow",
|
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}`;
|
return `${endpointPublic}/external/resize/${encodeURIComponent(url.href)}?width=${width}&height=${height}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMetaDescriptions = async (url: URL) => {
|
export const getMetaDescriptions = async (text: string) => {
|
||||||
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();
|
|
||||||
const $ = cheerio.load(text);
|
const $ = cheerio.load(text);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -44,7 +33,7 @@ export const getMetaDescriptions = async (url: URL) => {
|
|||||||
description:
|
description:
|
||||||
$('meta[property="og:description"]').attr("content") ||
|
$('meta[property="og:description"]').attr("content") ||
|
||||||
$('meta[property="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(
|
width: parseInt(
|
||||||
$('meta[property="og:image:width"]').attr("content") ||
|
$('meta[property="og:image:width"]').attr("content") ||
|
||||||
"",
|
"",
|
||||||
@ -54,34 +43,82 @@ export const getMetaDescriptions = async (url: URL) => {
|
|||||||
"",
|
"",
|
||||||
) || undefined,
|
) || undefined,
|
||||||
url: $('meta[property="og:url"]').attr("content"),
|
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 doFetch = async (url: URL) => {
|
||||||
|
try {
|
||||||
|
return await fetch(url, {
|
||||||
|
...DEFAULT_FETCH_OPTIONS,
|
||||||
|
size: Config.get().limits.message.maxEmbedDownloadSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const genericImageHandler = async (url: URL): Promise<Embed | null> => {
|
const genericImageHandler = async (url: URL): Promise<Embed | null> => {
|
||||||
const metas = await getMetaDescriptions(url);
|
const response = await doFetch(url);
|
||||||
if (!metas) return null;
|
if (!response) return null;
|
||||||
|
const metas = await getMetaDescriptions(await response.text());
|
||||||
|
|
||||||
const result = await probe(url.href);
|
if (!metas.width || !metas.height) {
|
||||||
|
const result = await probe(url.href);
|
||||||
const width = metas.width || result.width;
|
metas.width = result.width;
|
||||||
const height = metas.height || result.height;
|
metas.height = result.height;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: url.href,
|
url: url.href,
|
||||||
type: EmbedType.image,
|
type: EmbedType.image,
|
||||||
thumbnail: {
|
thumbnail: {
|
||||||
width: width,
|
width: metas.width,
|
||||||
height: height,
|
height: metas.height,
|
||||||
url: url.href,
|
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>; } = {
|
export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | null>; } = {
|
||||||
// the url does not have a special handler
|
// 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,
|
"giphy.com": genericImageHandler,
|
||||||
"media4.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,
|
"c.tenor.com": genericImageHandler,
|
||||||
"media.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> => {
|
"www.youtube.com": async (url: URL): Promise<Embed | null> => {
|
||||||
const metas = await getMetaDescriptions(url);
|
const response = await doFetch(url);
|
||||||
if (!metas) return null;
|
if (!response) return null;
|
||||||
|
const metas = await getMetaDescriptions(await response.text());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
video: {
|
video: {
|
||||||
|
@ -77,6 +77,10 @@ router.get("/resize/:url", async (req: Request, res: Response) => {
|
|||||||
throw new HTTPError("Couldn't fetch website");
|
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)
|
const resizedBuffer = await sharp(buffer)
|
||||||
.resize(parseInt(width as string), parseInt(height as string), {
|
.resize(parseInt(width as string), parseInt(height as string), {
|
||||||
fit: "inside",
|
fit: "inside",
|
||||||
|
Loading…
Reference in New Issue
Block a user