mrmBot-Matrix/utils/imagedetect.js

189 lines
7.6 KiB
JavaScript
Raw Normal View History

import { request } from "undici";
import { getType } from "./image.js";
2019-09-13 20:02:41 +00:00
2020-11-20 21:16:52 +00:00
const tenorURLs = [
"tenor.com",
"www.tenor.com"
];
const giphyURLs = [
"giphy.com",
2022-03-06 05:06:20 +00:00
"www.giphy.com",
"i.giphy.com"
2020-11-20 21:16:52 +00:00
];
2021-04-21 22:37:05 +00:00
const giphyMediaURLs = [ // there could be more of these
2021-05-06 21:12:44 +00:00
"media.giphy.com",
2021-04-21 22:37:05 +00:00
"media0.giphy.com",
"media1.giphy.com",
"media2.giphy.com",
"media3.giphy.com",
"media4.giphy.com"
];
2020-11-20 21:16:52 +00:00
const imgurURLs = [
"imgur.com",
"www.imgur.com",
"i.imgur.com"
];
const gfycatURLs = [
"gfycat.com",
"www.gfycat.com",
"thumbs.gfycat.com",
"giant.gfycat.com"
];
2020-11-20 21:16:52 +00:00
const combined = [...tenorURLs, ...giphyURLs, ...giphyMediaURLs, ...imgurURLs, ...gfycatURLs];
const imageFormats = ["image/jpeg", "image/png", "image/webp", "image/gif", "large"];
const videoFormats = ["video/mp4", "video/webm", "video/mov"];
// gets the proper image paths
const getImage = async (image, image2, video, extraReturnTypes, gifv = false, type = null, link = false) => {
2019-09-13 20:02:41 +00:00
try {
const fileNameSplit = new URL(image).pathname.split("/");
const fileName = fileNameSplit[fileNameSplit.length - 1];
const fileNameNoExtension = fileName.slice(0, fileName.lastIndexOf("."));
2020-10-19 23:48:43 +00:00
const payload = {
url: image2,
path: image,
name: fileNameNoExtension
2020-10-19 23:48:43 +00:00
};
const host = new URL(image2).host;
if (gifv || (link && combined.includes(host))) {
2020-11-20 21:16:52 +00:00
if (tenorURLs.includes(host)) {
// Tenor doesn't let us access a raw GIF without going through their API,
// so we use that if there's a key in the config
if (process.env.TENOR !== "") {
2022-09-06 21:25:13 +00:00
let id;
if (image2.includes("tenor.com/view/")) {
id = image2.split("-").pop();
} else if (image2.endsWith(".gif")) {
const redirect = (await request(image2, { method: "HEAD" })).headers.location;
id = redirect.split("-").pop();
}
const data = await request(`https://tenor.googleapis.com/v2/posts?ids=${id}&media_filter=gif&limit=1&key=${process.env.TENOR}`);
if (data.statusCode === 429) {
if (extraReturnTypes) {
payload.type = "tenorlimit";
return payload;
} else {
return;
}
}
const json = await data.body.json();
2022-08-11 19:49:07 +00:00
if (json.error) throw Error(json.error.message);
payload.path = json.results[0].media_formats.gif.url;
}
2020-11-20 21:16:52 +00:00
} else if (giphyURLs.includes(host)) {
// Can result in an HTML page instead of a GIF
payload.path = `https://media0.giphy.com/media/${image2.split("/")[4].split("-").pop()}/giphy.gif`;
2021-04-21 22:37:05 +00:00
} else if (giphyMediaURLs.includes(host)) {
payload.path = `https://media0.giphy.com/media/${image2.split("/")[4]}/giphy.gif`;
2020-11-20 21:16:52 +00:00
} else if (imgurURLs.includes(host)) {
2021-09-13 18:05:22 +00:00
// Seems that Imgur has a possibility of making GIFs static
2020-10-19 23:48:43 +00:00
payload.path = image.replace(".mp4", ".gif");
} else if (gfycatURLs.includes(host)) {
// iirc Gfycat also seems to sometimes make GIFs static
if (link) {
const data = await request(`https://api.gfycat.com/v1/gfycats/${image.split("/").pop().split(".mp4")[0]}`);
const json = await data.body.json();
if (json.errorMessage) throw Error(json.errorMessage);
payload.path = json.gfyItem.gifUrl;
} else {
payload.path = `https://thumbs.gfycat.com/${image.split("/").pop().split(".mp4")[0]}-size_restricted.gif`;
}
}
2020-10-20 01:24:53 +00:00
payload.type = "image/gif";
} else if (video) {
payload.type = type ?? await getType(payload.path, extraReturnTypes);
if (!payload.type || (!videoFormats.includes(payload.type) && !imageFormats.includes(payload.type))) return;
} else {
payload.type = type ?? await getType(payload.path, extraReturnTypes);
if (!payload.type || !imageFormats.includes(payload.type)) return;
2019-09-13 20:02:41 +00:00
}
return payload;
2019-09-13 20:02:41 +00:00
} catch (error) {
if (error.name === "AbortError") {
throw Error("Timed out");
} else {
throw error;
}
2019-09-13 20:02:41 +00:00
}
};
const checkImages = async (message, extraReturnTypes, video, sticker) => {
let type;
if (sticker && message.stickerItems) {
type = message.stickerItems[0];
} else {
// first check the embeds
if (message.embeds.length !== 0) {
// embeds can vary in types, we check for tenor gifs first
if (message.embeds[0].type === "gifv") {
type = await getImage(message.embeds[0].video.url, message.embeds[0].url, video, extraReturnTypes, true);
// then we check for other image types
} else if ((message.embeds[0].type === "video" || message.embeds[0].type === "image") && message.embeds[0].thumbnail) {
type = await getImage(message.embeds[0].thumbnail.proxy_url, message.embeds[0].thumbnail.url, video, extraReturnTypes);
// finally we check both possible image fields for "generic" embeds
2022-01-15 16:47:41 +00:00
} else if (message.embeds[0].type === "rich" || message.embeds[0].type === "article") {
if (message.embeds[0].thumbnail) {
type = await getImage(message.embeds[0].thumbnail.proxy_url, message.embeds[0].thumbnail.url, video, extraReturnTypes);
} else if (message.embeds[0].image) {
type = await getImage(message.embeds[0].image.proxy_url, message.embeds[0].image.url, video, extraReturnTypes);
}
}
// then check the attachments
} else if (message.attachments.length !== 0 && message.attachments[0].width) {
type = await getImage(message.attachments[0].proxy_url, message.attachments[0].url, video);
}
}
// if the return value exists then return it
return type ?? false;
};
2019-09-13 20:02:41 +00:00
// this checks for the latest message containing an image and returns the url of the image
export default async (client, cmdMessage, interaction, options, extraReturnTypes = false, video = false, sticker = false, singleMessage = false) => {
// we start by determining whether or not we're dealing with an interaction or a message
if (interaction) {
// we can get a raw attachment or a URL in the interaction itself
if (options) {
if (options.image) {
2022-08-11 15:40:10 +00:00
const attachment = interaction.data.resolved.attachments.get(options.image);
const result = await getImage(attachment.proxyUrl, attachment.url, video, attachment.contentType);
if (result !== false) return result;
} else if (options.link) {
const result = await getImage(options.link, options.link, video, extraReturnTypes, false, null, true);
if (result !== false) return result;
}
}
}
if (cmdMessage) {
// check if the message is a reply to another message
if (cmdMessage.messageReference) {
const replyMessage = await client.getMessage(cmdMessage.messageReference.channelID, cmdMessage.messageReference.messageID).catch(() => undefined);
if (replyMessage) {
const replyResult = await checkImages(replyMessage, extraReturnTypes, video, sticker);
if (replyResult !== false) return replyResult;
}
}
// then we check the current message
const result = await checkImages(cmdMessage, extraReturnTypes, video, sticker);
if (result !== false) return result;
}
if (!singleMessage) {
// if there aren't any replies or interaction attachments then iterate over the last few messages in the channel
2022-09-10 22:51:00 +00:00
try {
const messages = await client.getMessages((interaction ? interaction : cmdMessage).channel.id);
// iterate over each message
for (const message of messages) {
const result = await checkImages(message, extraReturnTypes, video, sticker);
if (result === false) {
continue;
} else {
return result;
}
}
} catch {
// no-op
2019-09-13 20:02:41 +00:00
}
}
};