From 5fb999ec5bc181e467cc55d5579b9898d6d8b170 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Thu, 13 Mar 2025 10:53:24 -0600 Subject: [PATCH 01/19] include branches in invite regex --- src/modules/utility/guildinfo.js | 2 +- src/modules/utility/lookupinvite.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/utility/guildinfo.js b/src/modules/utility/guildinfo.js index 6c57b5d..0572e22 100644 --- a/src/modules/utility/guildinfo.js +++ b/src/modules/utility/guildinfo.js @@ -601,7 +601,7 @@ guildinfo.callback = async function (msg, line, args, {nolocal, debug}) { invite = await hf.bot.requestHandler.request( "GET", `/invites/${guild.instant_invite.replace( - /(https?:\/\/)?discord(\.gg|(app)?.com\/invite)\//, + /(https?:\/\/)?(canary\.|ptb\.)?discord(\.gg|(app)?.com\/invite)\//, "" )}?with_counts=true&with_expiration=true` ); diff --git a/src/modules/utility/lookupinvite.js b/src/modules/utility/lookupinvite.js index bdc5acb..d6a41d3 100644 --- a/src/modules/utility/lookupinvite.js +++ b/src/modules/utility/lookupinvite.js @@ -23,7 +23,7 @@ lookupinvite.addAlias("ii"); lookupinvite.callback = async function (msg, line) { if (!line || line == "") return "Arguments required."; - line = line.replace(/(https?:\/\/)?discord(\.gg|(app)?.com\/invite)\//, ""); + line = line.replace(/(https?:\/\/)?(canary\.|ptb\.)?discord(\.gg|(app)?.com\/invite)\//, ""); if (decodeURIComponent(line).indexOf("../") > -1) return "nuh uh"; From d39bf5f2a4ea1b6186de4edbca16ddceba8b471c Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Thu, 13 Mar 2025 11:18:43 -0600 Subject: [PATCH 02/19] fedimbed: try catch nodeinfo --- src/modules/fedimbed.js | 68 ++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/modules/fedimbed.js b/src/modules/fedimbed.js index fcd4c3c..8ef0778 100644 --- a/src/modules/fedimbed.js +++ b/src/modules/fedimbed.js @@ -30,12 +30,12 @@ const PATH_REGEX = { lemmy: /^\/post\/\d+\/?/, honk: /^\/u\/(.+?)\/h\/(.+?)\/?/, pixelfed: /^\/p\/(.+?)\/(.+?)\/?/, - cohost: /^\/[A-Za-z0-9]+\/post\/\d+-[A-Za-z0-9-]+\/?/, + //cohost: /^\/[A-Za-z0-9]+\/post\/\d+-[A-Za-z0-9-]+\/?/, bluesky: BSKY_POST_REGEX, }; const PLATFORM_COLORS = { - mastodon: 0x2791da, + mastodon: 0x6363ff, pleroma: 0xfba457, akkoma: 0x593196, misskey: 0x99c203, @@ -46,7 +46,7 @@ const PLATFORM_COLORS = { birdsitelive: 0x1da1f2, iceshrimp: 0x8e82f9, // YCbCr interpolated as the accent color is a gradient pixelfed: 0x10c5f8, - cohost: 0x83254f, + //cohost: 0x83254f, bluesky: 0x0085ff, }; @@ -67,6 +67,7 @@ const BSKY_DOMAINS = [ /*const TW_DOMAINS = [ "tw.c7.pm", "tw.counter-strike.gay", + "xcancel.com", "twitter.com", "x.com", "fxtwitter.com", @@ -84,36 +85,41 @@ async function resolvePlatform(url) { const urlObj = new URL(url); if (domainCache.has(urlObj.hostname)) return domainCache.get(urlObj.hostname); - const res = await fetch(urlObj.origin + "/.well-known/nodeinfo", { - headers: {"User-Agent": FRIENDLY_USERAGENT}, - }).then((res) => res.text()); + try { + const res = await fetch(urlObj.origin + "/.well-known/nodeinfo", { + headers: {"User-Agent": FRIENDLY_USERAGENT}, + }).then((res) => res.text()); - if (!res.startsWith("{")) { - logger.error("fedimbed", `No nodeinfo for "${urlObj.hostname}"???`); - domainCache.set(urlObj.hostname, null); - return null; + if (!res.startsWith("{")) { + logger.error("fedimbed", `No nodeinfo for "${urlObj.hostname}"???`); + domainCache.set(urlObj.hostname, null); + return null; + } + + const probe = JSON.parse(res); + + if (!probe?.links) { + logger.error("fedimbed", `No nodeinfo for "${urlObj.hostname}"???`); + domainCache.set(urlObj.hostname, null); + return null; + } + + const nodeinfo = await fetch(probe.links[probe.links.length - 1].href, { + headers: {"User-Agent": FRIENDLY_USERAGENT}, + }).then((res) => res.json()); + + if (!nodeinfo?.software?.name) { + logger.error("fedimbed", `Got nodeinfo for "${urlObj.hostname}", but missing software name.`); + domainCache.set(urlObj.hostname, null); + return null; + } + + domainCache.set(urlObj.hostname, nodeinfo.software.name); + return nodeinfo.software.name; + } catch (err) { + logger.error("fedimbed", `Failed to get nodeinfo for "${url}": ${err}`); + return ""; } - - const probe = JSON.parse(res); - - if (!probe?.links) { - logger.error("fedimbed", `No nodeinfo for "${urlObj.hostname}"???`); - domainCache.set(urlObj.hostname, null); - return null; - } - - const nodeinfo = await fetch(probe.links[probe.links.length - 1].href, { - headers: {"User-Agent": FRIENDLY_USERAGENT}, - }).then((res) => res.json()); - - if (!nodeinfo?.software?.name) { - logger.error("fedimbed", `Got nodeinfo for "${urlObj.hostname}", but missing software name.`); - domainCache.set(urlObj.hostname, null); - return null; - } - - domainCache.set(urlObj.hostname, nodeinfo.software.name); - return nodeinfo.software.name; } const keyId = "https://hf.c7.pm/actor#main-key"; From 9fba9d2c9e63eb33a9826962678e80bf789b1713 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Fri, 14 Mar 2025 17:57:41 -0600 Subject: [PATCH 03/19] fedimbed: much needed cleanup and reply context --- src/modules/fedimbed.js | 461 ++++++++++++++++++++++++---------------- 1 file changed, 279 insertions(+), 182 deletions(-) diff --git a/src/modules/fedimbed.js b/src/modules/fedimbed.js index 8ef0778..8e2fe47 100644 --- a/src/modules/fedimbed.js +++ b/src/modules/fedimbed.js @@ -122,6 +122,14 @@ async function resolvePlatform(url) { } } +function normalizePlatform(platform) { + return platform + .replace("gotosocial", "GoToSocial") + .replace("birdsitelive", '"Twitter" (BirdsiteLive)') + .replace(/^(.)/, (_, c) => c.toUpperCase()) + .replace("Cohost", "cohost"); +} + const keyId = "https://hf.c7.pm/actor#main-key"; const privKey = fs.readFileSync(require.resolve("#root/priv/private.pem")); async function signedFetch(url, options) { @@ -406,7 +414,7 @@ async function bluesky(msg, url, spoiler = false) { if (data.thread.parent) { const reply = data.thread.parent.post; mainEmbed.author = { - name: `Replying to: ${reply.author.displayName} (${reply.author.handle})`, + name: `Replying to: ${reply.author.displayName} (@${reply.author.handle})`, icon_url: "https://cdn.discordapp.com/emojis/1308640078825787412.png", url: `https://bsky.app/profile/${reply.author.handle}/post/${reply.uri.substring( reply.uri.lastIndexOf("/") + 1 @@ -570,104 +578,53 @@ async function bluesky(msg, url, spoiler = false) { }; } -async function processUrl(msg, url, spoiler = false, command = false) { - let canFedi = await hasFlag(msg.guildID, "fedimbed"); - let canBsky = await hasFlag(msg.guildID, "bskyEmbeds"); +async function fetchPost(url, platform, forceMastoAPI = false) { + let urlObj = new URL(url); + let postData; - if (command === true) { - canFedi = true; - canBsky = true; - } - - let invalidUrl = false; - let urlObj; - try { - urlObj = new URL(url); - } catch { - invalidUrl = true; - } - - if (invalidUrl) return {}; - - if (BSKY_DOMAINS.includes(urlObj.hostname.toLowerCase())) { - if (canBsky) { - return await bluesky(msg, url, spoiler); - } else { - return {}; - } - } - if (!canFedi) return {}; - - // some lemmy instances have old reddit frontend subdomains - // but these frontends are just frontends and dont actually expose the API - if (urlObj.hostname.startsWith("old.")) { - urlObj.hostname = urlObj.hostname.replace("old.", ""); - url = urlObj.href; - } - - let platform = (await resolvePlatform(url)) ?? ""; - let color = PLATFORM_COLORS[platform]; - let platformName = platform - .replace("gotosocial", "GoToSocial") - .replace("birdsitelive", '"Twitter" (BirdsiteLive)') - .replace(/^(.)/, (_, c) => c.toUpperCase()) - .replace("Cohost", "cohost"); - - const images = []; - const videos = []; - const audios = []; - let content, - cw, - author, - timestamp, - title, - poll, - emotes = [], - sensitive = false; - - // Fetch post - let rawPostData; - try { - rawPostData = await signedFetch(url, { - headers: { - "User-Agent": FRIENDLY_USERAGENT, - Accept: "application/activity+json", - }, - }).then((res) => res.text()); - } catch (err) { - logger.error("fedimbed", `Failed to signed fetch "${url}", retrying unsigned: ${err}`); - } - if (!rawPostData) { + if (!forceMastoAPI) { + let rawPostData; try { - rawPostData = await fetch(url, { + rawPostData = await signedFetch(url, { headers: { "User-Agent": FRIENDLY_USERAGENT, Accept: "application/activity+json", }, }).then((res) => res.text()); } catch (err) { - logger.error("fedimbed", `Failed to fetch "${url}": ${err}`); + logger.error("fedimbed", `Failed to signed fetch "${url}", retrying unsigned: ${err}`); } - } - - let postData; - if (rawPostData?.startsWith("{")) { - try { - postData = JSON.parse(rawPostData); - } catch (err) { - logger.error("fedimbed", `Failed to decode JSON for "${url}": ${err}\n "${rawPostData}"`); + if (!rawPostData) { + try { + rawPostData = await fetch(url, { + headers: { + "User-Agent": FRIENDLY_USERAGENT, + Accept: "application/activity+json", + }, + }).then((res) => res.text()); + } catch (err) { + logger.error("fedimbed", `Failed to fetch "${url}": ${err}`); + } } - } else { - logger.warn("fedimbed", `Got non-JSON for "${url}": ${rawPostData}`); - } - if (postData?.error) { - logger.error("fedimbed", `Received error for "${url}": ${postData.error}`); + if (rawPostData?.startsWith("{")) { + try { + postData = JSON.parse(rawPostData); + } catch (err) { + logger.error("fedimbed", `Failed to decode JSON for "${url}": ${err}\n "${rawPostData}"`); + } + } else { + logger.warn("fedimbed", `Got non-JSON for "${url}": ${rawPostData}`); + } + + if (postData?.error) { + logger.error("fedimbed", `Received error for "${url}": ${postData.error}`); + } } if (!postData) { - // We failed to get post. - // Assume it was due to AFM or forced HTTP signatures and use MastoAPI + // We failed to get post. (or we forced ourselves to be here) + // Assume it was due to AFM or some other issue and (try to) use MastoAPI (or equivalent) // Follow redirect from /object since we need the ID from /notice if (PATH_REGEX.pleroma.test(urlObj.pathname)) { @@ -754,119 +711,211 @@ async function processUrl(msg, url, spoiler = false, command = false) { if (!postData2) { logger.warn("fedimbed", `Bailing trying to re-embed "${url}": Failed to get post from normal and MastoAPI.`); + return null; } else if (postData2.error) { logger.error( "fedimbed", `Bailing trying to re-embed "${url}", MastoAPI gave us error: ${JSON.stringify(postData2.error)}` ); - } else { - cw = postData2.spoiler_warning ?? postData2.spoiler_text ?? postData2.cw; - content = - postData2.akkoma?.source?.content ?? - postData2.pleroma?.content?.["text/plain"] ?? - postData2.text ?? - postData2.content; - author = { - name: - postData2.account?.display_name ?? - postData2.account?.username ?? - postData2.user?.name ?? - postData2.user?.username, - handle: - postData2.account?.fqn ?? `${postData2.account?.username ?? postData2.user?.username}@${urlObj.hostname}`, - url: postData2.account?.url ?? `${urlObj.origin}/@${postData2.account?.username ?? postData2.user?.username}`, - avatar: postData2.account?.avatar ?? postData2.user?.avatarUrl, - }; - timestamp = postData2.created_at ?? postData2.createdAt; - emotes = postData2.emojis.filter((x) => !x.name.endsWith("#.")).map((x) => ({name: `:${x.name}:`, url: x.url})); - sensitive = postData2.sensitive; + return null; + } - const attachments = postData2.media_attachments ?? postData2.files; - if (attachments) { - for (const attachment of attachments) { - const contentType = await fetch(attachment.url, { - method: "HEAD", - }).then((res) => res.headers.get("Content-Type")); + postData2._fedimbed_mastoapi = true; + return postData2; + } else { + return null; + } + } else { + return postData; + } +} - if (contentType) { - if (contentType.startsWith("image/")) { - images.push({ - url: attachment.url, - desc: attachment.description ?? attachment.comment, - type: contentType, - }); - } else if (contentType.startsWith("video/")) { - videos.push({ - url: attachment.url, - desc: attachment.description ?? attachment.comment, - type: contentType, - }); - } else if (contentType.startsWith("audio/")) { - audios.push({ - url: attachment.url, - desc: attachment.description ?? attachment.comment, - type: contentType, - }); - } - } else { - const type = attachment.type?.toLowerCase(); +async function processUrl(msg, url, spoiler = false, command = false) { + let canFedi = await hasFlag(msg.guildID, "fedimbed"); + let canBsky = await hasFlag(msg.guildID, "bskyEmbeds"); - const fileType = - attachment.pleroma?.mime_type ?? type.indexOf("/") > -1 - ? type - : type + - "/" + - (url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image" - ? "png" - : type == "video" - ? "mp4" - : "mpeg"); - if (type.startsWith("image")) { - images.push({ - url: attachment.url, - desc: attachment.description ?? attachment.comment, - type: fileType, - }); - } else if (type.startsWith("video")) { - videos.push({ - url: attachment.url, - desc: attachment.description ?? attachment.comment, - type: fileType, - }); - } else if (type.startsWith("audio")) { - audios.push({ - url: attachment.url, - desc: attachment.description ?? attachment.comment, - type: fileType, - }); - } - } + if (command === true) { + canFedi = true; + canBsky = true; + } + + let invalidUrl = false; + let urlObj; + try { + urlObj = new URL(url); + } catch { + invalidUrl = true; + } + + if (invalidUrl) return {}; + + if (BSKY_DOMAINS.includes(urlObj.hostname.toLowerCase())) { + if (canBsky) { + return await bluesky(msg, url, spoiler); + } else { + return {}; + } + } + if (!canFedi) return {}; + + // some lemmy instances have old reddit frontend subdomains + // but these frontends are just frontends and dont actually expose the API + if (urlObj.hostname.startsWith("old.")) { + urlObj.hostname = urlObj.hostname.replace("old.", ""); + url = urlObj.href; + } + + let platform = (await resolvePlatform(url)) ?? ""; + let color = PLATFORM_COLORS[platform]; + let platformName = normalizePlatform(platform); + + const images = []; + const videos = []; + const audios = []; + let content, + cw, + author, + timestamp, + title, + poll, + context, + contextUrl, + emotes = [], + sensitive = false; + + // Fetch post + const postData = await fetchPost(url, platform); + + if (postData._fedimbed_mastoapi) { + if (postData.url) { + const realUrlObj = new URL(postData.url); + if (realUrlObj.origin != urlObj.origin) { + platform = await resolvePlatform(postData.url); + color = PLATFORM_COLORS[platform]; + platformName = normalizePlatform(platform); + url = postData.url; + urlObj = realUrlObj; + } + } + + cw = postData.spoiler_warning ?? postData.spoiler_text ?? postData.cw; + content = + postData.akkoma?.source?.content ?? + postData.pleroma?.content?.["text/plain"] ?? + postData.text ?? + postData.content; + author = { + name: + postData.account?.display_name ?? postData.account?.username ?? postData.user?.name ?? postData.user?.username, + handle: postData.account?.fqn ?? `${postData.account?.username ?? postData.user?.username}@${urlObj.hostname}`, + url: postData.account?.url ?? `${urlObj.origin}/@${postData.account?.username ?? postData.user?.username}`, + avatar: postData.account?.avatar ?? postData.user?.avatarUrl, + }; + timestamp = postData.created_at ?? postData.createdAt; + emotes = postData.emojis.filter((x) => !x.name.endsWith("#.")).map((x) => ({name: `:${x.name}:`, url: x.url})); + sensitive = postData.sensitive; + + if (postData.in_reply_to_id) { + // this url is a dummy and will failed if gone to normally + const replyData = await fetchPost( + `https://${urlObj.origin}/@fedimbed_reply_fake_user_sorry/${postData.in_reply_to_id}`, + platform, + true + ); + if (replyData) { + contextUrl = replyData.url; + context = `Replying to: ${ + replyData.account?.display_name ?? + replyData.account?.username ?? + replyData.user?.name ?? + replyData.user?.username + } (${ + replyData.account?.fqn ?? `${replyData.account?.username ?? replyData.user?.username}@${urlObj.hostname}` + })`; + } + } + + const attachments = postData.media_attachments ?? postData.files; + if (attachments) { + for (const attachment of attachments) { + const contentType = await fetch(attachment.url, { + method: "HEAD", + }).then((res) => res.headers.get("Content-Type")); + + if (contentType) { + if (contentType.startsWith("image/")) { + images.push({ + url: attachment.url, + desc: attachment.description ?? attachment.comment, + type: contentType, + }); + } else if (contentType.startsWith("video/")) { + videos.push({ + url: attachment.url, + desc: attachment.description ?? attachment.comment, + type: contentType, + }); + } else if (contentType.startsWith("audio/")) { + audios.push({ + url: attachment.url, + desc: attachment.description ?? attachment.comment, + type: contentType, + }); + } + } else { + const type = attachment.type?.toLowerCase(); + + const fileType = + attachment.pleroma?.mime_type ?? type.indexOf("/") > -1 + ? type + : type + + "/" + + (url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image" ? "png" : type == "video" ? "mp4" : "mpeg"); + if (type.startsWith("image")) { + images.push({ + url: attachment.url, + desc: attachment.description ?? attachment.comment, + type: fileType, + }); + } else if (type.startsWith("video")) { + videos.push({ + url: attachment.url, + desc: attachment.description ?? attachment.comment, + type: fileType, + }); + } else if (type.startsWith("audio")) { + audios.push({ + url: attachment.url, + desc: attachment.description ?? attachment.comment, + type: fileType, + }); } } - if (!spoiler && postData2.sensitive && attachments.length > 0) { - spoiler = true; - } - - if (postData2.poll) { - poll = { - end: new Date(postData2.poll.expires_at), - total: postData2.poll.votes_count, - options: postData2.poll.options.map((o) => ({ - name: o.title, - count: o.votes_count, - })), - }; - } } } + if (!spoiler && postData.sensitive && attachments.length > 0) { + spoiler = true; + } + + if (postData.poll) { + poll = { + end: new Date(postData.poll.expires_at), + total: postData.poll.votes_count, + options: postData.poll.options.map((o) => ({ + name: o.title, + count: o.votes_count, + })), + }; + } } else { if (postData.id) { const realUrlObj = new URL(postData.id); if (realUrlObj.origin != urlObj.origin) { platform = await resolvePlatform(postData.id); color = PLATFORM_COLORS[platform]; - platformName = platform.replace("gotosocial", "GoToSocial").replace(/^(.)/, (_, c) => c.toUpperCase()); + platformName = normalizePlatform(platform); url = postData.id; + urlObj = realUrlObj; } } @@ -882,6 +931,49 @@ async function processUrl(msg, url, spoiler = false, command = false) { emotes = tag.filter((x) => !!x.icon).map((x) => ({name: x.name, url: x.icon.url})); } + if (postData.inReplyTo) { + contextUrl = postData.inReplyTo; + context = "Replying to: "; + + const replyData = await fetchPost(postData.inReplyTo, platform); + if (replyData) { + if (replyData._fedimbed_mastoapi) { + context += `${ + replyData.account?.display_name ?? + replyData.account?.username ?? + replyData.user?.name ?? + replyData.user?.username + } (@${ + replyData.account?.fqn ?? `${replyData.account?.username ?? replyData.user?.username}@${urlObj.hostname}` + })`; + } else { + const authorData = await signedFetch(postData.actor ?? postData.attributedTo, { + headers: { + "User-Agent": FRIENDLY_USERAGENT, + Accept: "application/activity+json", + }, + }) + .then((res) => res.json()) + .catch((err) => { + /*if (platform !== "cohost")*/ logger.error("fedimbed", `Failed to get author for "${url}": ${err}`); + }); + + if (authorData) { + const authorUrlObj = new URL(authorData.url ?? authorData.id); + context += `${authorData.name} (${authorData.preferredUsername}@${authorUrlObj.hostname})`; + } else { + // bootleg author + const authorUrl = replyData.actor ?? replyData.attributedTo; + const authorUrlObj = new URL(authorUrl); + const name = authorUrlObj.pathname.substring(authorUrlObj.pathname.lastIndexOf("/") + 1); + context += `${name}@${authorUrlObj.hostname}`; + } + } + } else { + context += ""; + } + } + // NB: gts doesnt send singular attachments as array const attachments = Array.isArray(postData.attachment) ? postData.attachment : [postData.attachment]; for (const attachment of attachments) { @@ -991,8 +1083,7 @@ async function processUrl(msg, url, spoiler = false, command = false) { }) .then((res) => res.json()) .catch((err) => { - // only posts can be activity+json right now, reduce log spam - if (platform !== "cohost") logger.error("fedimbed", `Failed to get author for "${url}": ${err}`); + /*if (platform !== "cohost")*/ logger.error("fedimbed", `Failed to get author for "${url}": ${err}`); }); if (authorData) { @@ -1004,7 +1095,7 @@ async function processUrl(msg, url, spoiler = false, command = false) { avatar: authorData.icon?.url, }; } else { - // bootleg author, mainly for cohost + // bootleg author const authorUrl = postData.actor ?? postData.attributedTo; const authorUrlObj = new URL(authorUrl); const name = authorUrlObj.pathname.substring(authorUrlObj.pathname.lastIndexOf("/") + 1); @@ -1083,6 +1174,12 @@ async function processUrl(msg, url, spoiler = false, command = false) { name: user, url: author.url, } + : context + ? { + name: context, + url: contextUrl, + icon_url: "https://cdn.discordapp.com/emojis/1308640078825787412.png", + } : null, footer: { text: platformName, From 134f417b917c6f285020a18d274001fd02e948aa Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Fri, 14 Mar 2025 18:17:18 -0600 Subject: [PATCH 04/19] fedimbed: nullcheck postData, misskey mastoapi emoji fix --- src/modules/fedimbed.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/modules/fedimbed.js b/src/modules/fedimbed.js index 8e2fe47..338249a 100644 --- a/src/modules/fedimbed.js +++ b/src/modules/fedimbed.js @@ -786,7 +786,9 @@ async function processUrl(msg, url, spoiler = false, command = false) { // Fetch post const postData = await fetchPost(url, platform); - if (postData._fedimbed_mastoapi) { + if (!postData) { + return {}; + } else if (postData._fedimbed_mastoapi) { if (postData.url) { const realUrlObj = new URL(postData.url); if (realUrlObj.origin != urlObj.origin) { @@ -812,7 +814,9 @@ async function processUrl(msg, url, spoiler = false, command = false) { avatar: postData.account?.avatar ?? postData.user?.avatarUrl, }; timestamp = postData.created_at ?? postData.createdAt; - emotes = postData.emojis.filter((x) => !x.name.endsWith("#.")).map((x) => ({name: `:${x.name}:`, url: x.url})); + emotes = postData.emojis + .filter((x) => !(x.name ?? x.shortcode)?.endsWith("#.")) + .map((x) => ({name: `:${x.name ?? x.shortcode}:`, url: x.url})); sensitive = postData.sensitive; if (postData.in_reply_to_id) { @@ -1487,6 +1491,7 @@ events.add("messageCreate", "fedimbed", async function (msg) { logger.verbose("fedimbed", `Hit "${service}" for "${url}", processing now.`); try { const {response, sendWait} = await processUrl(msg, url, hasSpoiler); + if (!response) return; await msg.channel.createMessage(response).then(() => { if (sendWait) { msg.removeReaction("\uD83D\uDCE4"); @@ -1497,7 +1502,7 @@ events.add("messageCreate", "fedimbed", async function (msg) { } }); } catch (err) { - logger.error("fedimbed", `Error processing "${url}":\n` + err.stack); + logger.error("fedimbed", `Error processing "${url}":\n${err.stack}`); } break; } From ac11f3a46bc947fe53844aa8bfa0e5c38db1d85c Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Fri, 14 Mar 2025 18:52:55 -0600 Subject: [PATCH 05/19] fedimbed: make iceshrimp fallback normal mastoapi api call, copypaste moment --- src/modules/fedimbed.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/modules/fedimbed.js b/src/modules/fedimbed.js index 338249a..c38b409 100644 --- a/src/modules/fedimbed.js +++ b/src/modules/fedimbed.js @@ -661,10 +661,17 @@ async function fetchPost(url, platform, forceMastoAPI = false) { noteId = noteId.split("#")[0]; } logger.verbose("fedimbed", "Misskey post ID: " + noteId); - redirUrl = urlObj.origin + "/api/notes/show/"; - options.method = "POST"; - options.body = JSON.stringify({noteId}); - headers["Content-Type"] = "application/json"; + + // iceshrimp has an entire .NET rewrite and only supports MastoAPI it seems + // *should* be fine for older iceshrimp-js instances + if (platform == "iceshrimp") { + redirUrl = urlObj.origin + "/api/v1/statuses/" + noteId; + } else { + redirUrl = urlObj.origin + "/api/notes/show/"; + options.method = "POST"; + options.body = JSON.stringify({noteId}); + headers["Content-Type"] = "application/json"; + } } else { logger.error("fedimbed", `Missing MastoAPI replacement for "${platform}"`); } @@ -951,7 +958,7 @@ async function processUrl(msg, url, spoiler = false, command = false) { replyData.account?.fqn ?? `${replyData.account?.username ?? replyData.user?.username}@${urlObj.hostname}` })`; } else { - const authorData = await signedFetch(postData.actor ?? postData.attributedTo, { + const authorData = await signedFetch(replyData.actor ?? replyData.attributedTo, { headers: { "User-Agent": FRIENDLY_USERAGENT, Accept: "application/activity+json", From 232a0f011500a14a13d39a11755ce123b0711a86 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Fri, 14 Mar 2025 19:52:52 -0600 Subject: [PATCH 06/19] fedimbed.bluesky: fix quotes with external media --- src/modules/fedimbed.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/modules/fedimbed.js b/src/modules/fedimbed.js index c38b409..98e8abe 100644 --- a/src/modules/fedimbed.js +++ b/src/modules/fedimbed.js @@ -48,6 +48,7 @@ const PLATFORM_COLORS = { pixelfed: 0x10c5f8, //cohost: 0x83254f, bluesky: 0x0085ff, + twitter: 0xff6c60, // Nitter accent color }; const BSKY_DOMAINS = [ @@ -475,6 +476,17 @@ async function bluesky(msg, url, spoiler = false) { videos.push({url: videoUrl, desc: post.embed.alt, type: contentType}); embeds.push({...mainEmbed, fields: [{name: "\u200b", value: `[Video Link](${videoUrl})`}]}); + } else if (post.embed.media.$type === "app.bsky.embed.external#view") { + if (post.embed.external.uri.includes("tenor.com")) { + const url = new URL(post.embed.external.uri); + url.searchParams.delete("hh"); + url.searchParams.delete("ww"); + embeds.push({...mainEmbed, image: {url: url.toString()}}); + } else { + embeds.push(mainEmbed); + } + } else { + embeds.push(mainEmbed); } const quoteData = await blueskyQuoteEmbed(post.embed.record.record); From ad54973479312cb138b0735707fa508f3c1242b5 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Fri, 14 Mar 2025 19:54:21 -0600 Subject: [PATCH 07/19] fedimbed.bluesky: copypaste moment --- src/modules/fedimbed.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/fedimbed.js b/src/modules/fedimbed.js index 98e8abe..30d19cb 100644 --- a/src/modules/fedimbed.js +++ b/src/modules/fedimbed.js @@ -477,8 +477,8 @@ async function bluesky(msg, url, spoiler = false) { embeds.push({...mainEmbed, fields: [{name: "\u200b", value: `[Video Link](${videoUrl})`}]}); } else if (post.embed.media.$type === "app.bsky.embed.external#view") { - if (post.embed.external.uri.includes("tenor.com")) { - const url = new URL(post.embed.external.uri); + if (post.embed.media.external.uri.includes("tenor.com")) { + const url = new URL(post.embed.media.external.uri); url.searchParams.delete("hh"); url.searchParams.delete("ww"); embeds.push({...mainEmbed, image: {url: url.toString()}}); From 1e68c2be12290bc50cf898f083d495d278a051c5 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 16 Mar 2025 12:02:56 -0600 Subject: [PATCH 08/19] main error handling cleanup --- .rgignore | 1 + src/index.js | 32 ++++++++++++-------------------- 2 files changed, 13 insertions(+), 20 deletions(-) create mode 100644 .rgignore diff --git a/.rgignore b/.rgignore new file mode 100644 index 0000000..8fce603 --- /dev/null +++ b/.rgignore @@ -0,0 +1 @@ +data/ diff --git a/src/index.js b/src/index.js index c6ca6de..e09a91b 100644 --- a/src/index.js +++ b/src/index.js @@ -69,7 +69,7 @@ for (const file of fs.readdirSync(resolve(__dirname, "modules"), {withFileTypes: require(`#modules/${file.name}`); logger.info("hf:modules", `Loaded module: "${file.name}"`); } catch (err) { - logger.error("hf:modules", `Failed to load "${file.name}": ${err}`); + logger.error("hf:modules", `Failed to load "${file.name}": ${err.stack}`); } } @@ -89,9 +89,7 @@ bot.on("messageCreate", async (msg) => { await CommandDispatcher(msg); } catch (err) { - const stack = (err?.stack ?? err.message).split("\n"); - const error = stack.shift(); - logger.error("hf:main", `Failed to dispatch command: ${error}\n\t${stack.join("\n\t")}`); + logger.error("hf:main", `Failed to dispatch command: ${err.stack}`); } }); bot.on("messageUpdate", async (msg, oldMsg) => { @@ -101,9 +99,7 @@ bot.on("messageUpdate", async (msg, oldMsg) => { await CommandDispatcher(msg); } } catch (err) { - const stack = (err?.stack ?? err.message).split("\n"); - const error = stack.shift(); - logger.error("hf:main", `Failed to dispatch command update: ${error}\n\t${stack.join("\n\t")}`); + logger.error("hf:main", `Failed to dispatch command update: ${err.stack}`); } }); bot.on("messageReactionAdd", async (msg, reaction, reactor) => { @@ -133,18 +129,14 @@ bot.on("messageReactionAdd", async (msg, reaction, reactor) => { await msg.delete("Command sender requested output deletion."); } catch (err) { - const stack = (err?.stack ?? err.message).split("\n"); - const error = stack.shift(); - logger.error("hf:main", `Failed to self-delete message: ${error}\n\t${stack.join("\n\t")}`); + logger.error("hf:main", `Failed to self-delete message: ${err.stack}`); } }); bot.on("interactionCreate", async (interaction) => { try { await InteractionDispatcher(interaction); } catch (err) { - const stack = (err?.stack ?? err.message).split("\n"); - const error = stack.shift(); - logger.error("hf:main", `Failed to dispatch interaction command: ${error}\n\t${stack.join("\n\t")}`); + logger.error("hf:main", `Failed to dispatch interaction command: ${err.stack}`); } }); @@ -160,7 +152,7 @@ bot.once("ready", async () => { }); } } catch (err) { - logger.error("hf:main", `Failed to send startup message, API probably broken currently.\n${err}`); + logger.error("hf:main", `Failed to send startup message, API probably broken currently.\n${err.stack}`); } bot.on("ready", () => { logger.info("hf:main", "Reconnected to Discord."); @@ -209,23 +201,23 @@ bot.once("ready", async () => { try { await bot.requestHandler.request("PUT", APIEndpoints.COMMANDS(bot.application.id), true, commands); } catch (err) { - logger.error("hf:main", `Failed to update interaction commands, API probably broken currently.\n${err}`); + logger.error("hf:main", `Failed to update interaction commands, API probably broken currently.\n${err.stack}`); } } }); bot.on("error", (err) => { - logger.error("hf:main", "Catching error: " + err); + logger.error("hf:main", `Catching error: ${err.stack}`); }); bot.on("warn", (err) => { - logger.warn("hf:main", "Catching warn: " + err); + logger.warn("hf:main", `Catching warn: ${err}`); }); bot.on("shardDisconnect", (err, id) => { - logger.verbose("hf:shard", `Disconnecting from shard ${id}: ${err}`); + logger.verbose("hf:shard", `Disconnecting from shard ${id}: ${err.stack}`); }); bot.on("shardResume", (id) => { - logger.verbose("hf:shard", "Resuming on shard " + id); + logger.verbose("hf:shard", `Resuming on shard ${id}`); }); bot.on("shardPreReady", (id) => { logger.verbose("hf:shard", `Shard ${id} getting ready`); @@ -234,7 +226,7 @@ bot.on("shardReady", (id) => { logger.verbose("hf:shard", `Shard ${id} ready`); }); bot.on("unknown", (packet, id) => { - logger.verbose("hf:main", `Shard ${id} caught unknown packet: ${JSON.stringify(packet)}`); + logger.verbose("hf:main", `Shard ${id} caught unknown packet:\n ${JSON.stringify(packet)}`); }); instead("spawn", bot.shards, function (args, orig) { From 28bdba1fd4ee63c479fc22a1e906273b863a10a1 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 16 Mar 2025 12:11:50 -0600 Subject: [PATCH 09/19] foxwells: join request logger --- src/modules/foxwells.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/modules/foxwells.js b/src/modules/foxwells.js index 62ad126..e3e079a 100644 --- a/src/modules/foxwells.js +++ b/src/modules/foxwells.js @@ -263,3 +263,33 @@ async function processReaction(_msg, reaction, user) { events.add("messageReactionAdd", "vinboard", processReaction); events.add("messageReactionRemove", "vinboard", processReaction); + +/* join request logger */ +const STAFF_CHANNEL_ID = "662007759738372096"; + +function processJoinRequest(data) { + if (data.status !== "SUBMITTED") return; + if (!data.request) return; + if (data.request.guild_id !== FOXWELLS_GUILD_ID) return; + + const channel = hf.bot.guilds.get(FOXWELLS_GUILD_ID).channels.get(STAFF_CHANNEL_ID); + const userId = data.request.user_id; + + channel.createMessage({ + embeds: [ + { + title: "New join request", + description: `<@${userId}> (\`${userId}\`)`, + fields: data.request.form_responses + .filter((field) => field.field_type !== "TERMS") + .map((field) => ({name: field.label, value: `\`\`\`\n${field.response}\n\`\`\``})), + }, + ], + }); +} + +events.add("unknown", "foxwells_joinrequest", (packet) => { + if (packet.t === "GUILD_JOIN_REQUEST_UPDATE") { + processJoinRequest(packet.d); + } +}); From aca7f96c2699a9c6629c95aa3a1f164fd72af115 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 16 Mar 2025 12:23:19 -0600 Subject: [PATCH 10/19] socketstats command --- src/index.js | 8 ++++++++ src/modules/bot.js | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/index.js b/src/index.js index e09a91b..41f46d1 100644 --- a/src/index.js +++ b/src/index.js @@ -56,6 +56,7 @@ global.hf = { events, timer, database, + event_stats: {}, }; const {formatUsername} = require("#util/misc.js"); @@ -229,6 +230,13 @@ bot.on("unknown", (packet, id) => { logger.verbose("hf:main", `Shard ${id} caught unknown packet:\n ${JSON.stringify(packet)}`); }); +bot.on("rawWS", (packet) => { + if (packet.op === 0 && packet.t != null) { + if (!hf.event_stats[packet.t]) hf.event_stats[packet.t] = 0; + hf.event_stats[packet.t]++; + } +}); + instead("spawn", bot.shards, function (args, orig) { const ret = orig.apply(this, args); const shard = this.get(args[0]); diff --git a/src/modules/bot.js b/src/modules/bot.js index 7352bc5..765a16c 100644 --- a/src/modules/bot.js +++ b/src/modules/bot.js @@ -221,3 +221,17 @@ settings.callback = async function (msg, line, [cmd, key, value]) { } }; hf.registerCommand(settings); + +const socketstats = new Command("socketstats"); +socketstats.category = CATEGORY; +socketstats.helpText = "List the counts of socket events this session"; +socketstats.callback = function () { + let out = "```c\n"; + for (const [event, count] of Object.entries(hf.event_stats)) { + out += `"${event}": ${count}\n`; + } + out += "```"; + + return out; +}; +hf.registerCommand(socketstats); From 3289367e31a30208216d4e6456acc393d6fec739 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 16 Mar 2025 16:00:12 -0600 Subject: [PATCH 11/19] attempt fix --- src/index.js | 2 +- src/modules/foxwells.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 41f46d1..9e85c14 100644 --- a/src/index.js +++ b/src/index.js @@ -215,7 +215,7 @@ bot.on("warn", (err) => { }); bot.on("shardDisconnect", (err, id) => { - logger.verbose("hf:shard", `Disconnecting from shard ${id}: ${err.stack}`); + logger.verbose("hf:shard", `Disconnecting from shard ${id}: ${err?.stack ?? err ?? "no error???"}`); }); bot.on("shardResume", (id) => { logger.verbose("hf:shard", `Resuming on shard ${id}`); diff --git a/src/modules/foxwells.js b/src/modules/foxwells.js index e3e079a..7907663 100644 --- a/src/modules/foxwells.js +++ b/src/modules/foxwells.js @@ -265,7 +265,7 @@ events.add("messageReactionAdd", "vinboard", processReaction); events.add("messageReactionRemove", "vinboard", processReaction); /* join request logger */ -const STAFF_CHANNEL_ID = "662007759738372096"; +/*const STAFF_CHANNEL_ID = "662007759738372096"; function processJoinRequest(data) { if (data.status !== "SUBMITTED") return; @@ -292,4 +292,4 @@ events.add("unknown", "foxwells_joinrequest", (packet) => { if (packet.t === "GUILD_JOIN_REQUEST_UPDATE") { processJoinRequest(packet.d); } -}); +});*/ From 7ef3c95533a7d8b75b8852e5feefda996ff28484 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Fri, 21 Mar 2025 18:34:53 -0600 Subject: [PATCH 12/19] codePreviews: account for C in line selection (github) --- src/modules/codePreviews.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/codePreviews.js b/src/modules/codePreviews.js index 49e9c80..23d374d 100644 --- a/src/modules/codePreviews.js +++ b/src/modules/codePreviews.js @@ -60,12 +60,12 @@ async function processFile(link, originalLink, spoiler = false, linkFile = false fileName = `[${fileName}](<${originalLink}>)`; } - const lineStr = urlObj.hash.match(/#L\d+(-L?\d+)?/)?.[0]; + const lineStr = urlObj.hash.match(/#L\d+(C\d+)?(-L?\d+)?(C\d+)?/)?.[0]; let startLine, endLine; let entireFile = false; if (lineStr) { - const [start, end] = lineStr.match(/\d+/g); + const [start, end] = lineStr.match(/(?<=L-)\d+/g); if (!end) { startLine = endLine = start; } else { From 609a312c7c411558c771b77e2cbd62ac216d37ab Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Fri, 21 Mar 2025 18:37:22 -0600 Subject: [PATCH 13/19] codePreviews: guh --- src/modules/codePreviews.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/codePreviews.js b/src/modules/codePreviews.js index 23d374d..d96961e 100644 --- a/src/modules/codePreviews.js +++ b/src/modules/codePreviews.js @@ -65,7 +65,7 @@ async function processFile(link, originalLink, spoiler = false, linkFile = false let entireFile = false; if (lineStr) { - const [start, end] = lineStr.match(/(?<=L-)\d+/g); + const [start, end] = lineStr.match(/(?<=[L-]{1,2})\d+/g); if (!end) { startLine = endLine = start; } else { From 116abe3bb3323337f265123fa313dfd4624ab703 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 23 Mar 2025 13:52:04 -0600 Subject: [PATCH 14/19] fedimbed: less restrictive regex for mastodon (cause lazy, covers other services) --- src/modules/fedimbed.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/fedimbed.js b/src/modules/fedimbed.js index 30d19cb..c6487cc 100644 --- a/src/modules/fedimbed.js +++ b/src/modules/fedimbed.js @@ -21,8 +21,8 @@ const BSKY_POST_REGEX = /^\/profile\/(did:plc:[a-z0-9]+|(did:web:)?[a-z0-9][a-z0-9.-]+[a-z0-9]*)\/post\/([a-z0-9]+)\/?$/i; const PATH_REGEX = { - mastodon: /^\/@(.+?)\/(\d+)\/?/, - mastodon2: /^\/(.+?)\/statuses\/\d+\/?/, + mastodon: /^\/@(.+?)\/([a-z0-9]+?)\/?/, + mastodon2: /^\/(.+?)\/statuses\/([a-z0-9]+?)\/?/i, pleroma: /^\/objects\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/?/, pleroma2: /^\/notice\/[A-Za-z0-9]+\/?/, misskey: /^\/notes\/[a-z0-9]+\/?/, From 60631bba3747e6a3b7d6270987bbbe28d3459bbf Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 23 Mar 2025 13:53:42 -0600 Subject: [PATCH 15/19] fedimbed: forgot insensitive on one of them --- src/modules/fedimbed.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/fedimbed.js b/src/modules/fedimbed.js index c6487cc..468b192 100644 --- a/src/modules/fedimbed.js +++ b/src/modules/fedimbed.js @@ -21,7 +21,7 @@ const BSKY_POST_REGEX = /^\/profile\/(did:plc:[a-z0-9]+|(did:web:)?[a-z0-9][a-z0-9.-]+[a-z0-9]*)\/post\/([a-z0-9]+)\/?$/i; const PATH_REGEX = { - mastodon: /^\/@(.+?)\/([a-z0-9]+?)\/?/, + mastodon: /^\/@(.+?)\/([a-z0-9]+?)\/?/i, mastodon2: /^\/(.+?)\/statuses\/([a-z0-9]+?)\/?/i, pleroma: /^\/objects\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/?/, pleroma2: /^\/notice\/[A-Za-z0-9]+\/?/, From 2388f0cc47e3284a6ae769a007888d614e0e3298 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Fri, 28 Mar 2025 09:20:37 -0600 Subject: [PATCH 16/19] fedimbed: we love non-standard activitystream implementations --- src/modules/fedimbed.js | 144 ++++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 71 deletions(-) diff --git a/src/modules/fedimbed.js b/src/modules/fedimbed.js index 468b192..c169f61 100644 --- a/src/modules/fedimbed.js +++ b/src/modules/fedimbed.js @@ -997,90 +997,92 @@ async function processUrl(msg, url, spoiler = false, command = false) { } } - // NB: gts doesnt send singular attachments as array - const attachments = Array.isArray(postData.attachment) ? postData.attachment : [postData.attachment]; - for (const attachment of attachments) { - if (attachment.mediaType) { - if (attachment.mediaType.startsWith("video/")) { - videos.push({ - url: attachment.url, - desc: attachment.name ?? attachment.description ?? attachment.comment, - type: attachment.mediaType, - }); - } else if (attachment.mediaType.startsWith("image/")) { - images.push({ - url: attachment.url, - desc: attachment.name ?? attachment.description ?? attachment.comment, - type: attachment.mediaType, - }); - } else if (attachment.mediaType.startsWith("audio/")) { - audios.push({ - url: attachment.url, - desc: attachment.name ?? attachment.description ?? attachment.comment, - type: attachment.mediaType, - }); - } - } else if (attachment.url) { - const contentType = await fetch(attachment.url, { - method: "HEAD", - }).then((res) => res.headers.get("Content-Type")); - - if (contentType) { - if (contentType.startsWith("image/")) { - images.push({ - url: attachment.url, - desc: attachment.name ?? attachment.description ?? attachment.comment, - type: contentType, - }); - } else if (contentType.startsWith("video/")) { + if (postData.attachment) { + // NB: gts doesnt send singular attachments as array + const attachments = Array.isArray(postData.attachment) ? postData.attachment : [postData.attachment]; + for (const attachment of attachments) { + if (attachment.mediaType) { + if (attachment.mediaType.startsWith("video/")) { videos.push({ url: attachment.url, desc: attachment.name ?? attachment.description ?? attachment.comment, - type: contentType, + type: attachment.mediaType, }); - } else if (contentType.startsWith("audio/")) { + } else if (attachment.mediaType.startsWith("image/")) { + images.push({ + url: attachment.url, + desc: attachment.name ?? attachment.description ?? attachment.comment, + type: attachment.mediaType, + }); + } else if (attachment.mediaType.startsWith("audio/")) { audios.push({ url: attachment.url, desc: attachment.name ?? attachment.description ?? attachment.comment, - type: contentType, + type: attachment.mediaType, }); } + } else if (attachment.url) { + const contentType = await fetch(attachment.url, { + method: "HEAD", + }).then((res) => res.headers.get("Content-Type")); + + if (contentType) { + if (contentType.startsWith("image/")) { + images.push({ + url: attachment.url, + desc: attachment.name ?? attachment.description ?? attachment.comment, + type: contentType, + }); + } else if (contentType.startsWith("video/")) { + videos.push({ + url: attachment.url, + desc: attachment.name ?? attachment.description ?? attachment.comment, + type: contentType, + }); + } else if (contentType.startsWith("audio/")) { + audios.push({ + url: attachment.url, + desc: attachment.name ?? attachment.description ?? attachment.comment, + type: contentType, + }); + } + } else { + const type = attachment.type?.toLowerCase(); + + const fileType = + type.indexOf("/") > -1 + ? type + : type + + "/" + + (url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image" ? "png" : type == "video" ? "mp4" : "mpeg"); + if (type.startsWith("image")) { + images.push({ + url: attachment.url, + desc: attachment.name ?? attachment.description ?? attachment.comment, + type: fileType, + }); + } else if (type.startsWith("video")) { + videos.push({ + url: attachment.url, + desc: attachment.name ?? attachment.description ?? attachment.comment, + type: fileType, + }); + } else if (type.startsWith("audio")) { + audios.push({ + url: attachment.url, + desc: attachment.name ?? attachment.description ?? attachment.comment, + type: fileType, + }); + } + } } else { - const type = attachment.type?.toLowerCase(); - - const fileType = - type.indexOf("/") > -1 - ? type - : type + - "/" + - (url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image" ? "png" : type == "video" ? "mp4" : "mpeg"); - if (type.startsWith("image")) { - images.push({ - url: attachment.url, - desc: attachment.name ?? attachment.description ?? attachment.comment, - type: fileType, - }); - } else if (type.startsWith("video")) { - videos.push({ - url: attachment.url, - desc: attachment.name ?? attachment.description ?? attachment.comment, - type: fileType, - }); - } else if (type.startsWith("audio")) { - audios.push({ - url: attachment.url, - desc: attachment.name ?? attachment.description ?? attachment.comment, - type: fileType, - }); - } + logger.warn("fedimbed", `Unhandled attachment structure! ${JSON.stringify(attachment)}`); } - } else { - logger.warn("fedimbed", `Unhandled attachment structure! ${JSON.stringify(attachment)}`); } - } - if (!spoiler && postData.sensitive && attachments.length > 0) { - spoiler = true; + if (!spoiler && postData.sensitive && attachments.length > 0) { + spoiler = true; + } } if (postData.image?.url) { From 1e5c56c0d652ab4e663d0af7763a3d051f17893e Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Fri, 28 Mar 2025 09:32:48 -0600 Subject: [PATCH 17/19] html to markdown fixes --- src/util/html.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/util/html.js b/src/util/html.js index 85bb5ec..092a0f0 100644 --- a/src/util/html.js +++ b/src/util/html.js @@ -32,14 +32,14 @@ function parseHtmlEntities(str) { function htmlToMarkdown(str, images = true, embed = true) { str = str.replaceAll("\\", "\\\\"); - str = str.replace(/]+)?>(.|\n)*?<\/style>/gi, ""); - str = str.replace(/]+)?href="([^"]+?)"(\s+[^>]+)?>(.+?)<\/a>/gi, (_, __, url, ___, text) => { + str = str.replace(/]+)?>(.|\n)*?<\/style>/gi, ""); + str = str.replace(/]+)?href="([^"]+?)"(\s*[^>]+)?>(.+?)<\/a>/gi, (_, __, url, ___, text) => { url = url.replace(/^\/\//, "https://").replace("\\#", "#"); return url == text ? url : `[${text}](${embed ? "" : "<"}${url}${embed ? "" : ">"})`; }); if (images) str = str.replace( - /]+)?src="([^"]+?)"(\s+[^>]+)?(alt|title)="([^"]+?)"(\s+[^>]+)?\/>/gi, + /]+)?src="([^"]+?)"(\s*[^>]+)?(alt|title)="([^"]+?)"(\s*[^>]+)?\/>/gi, `[$5](${embed ? "" : "<"}$2${embed ? "" : ">"})` ); str = str.replace(/<\/?\s*br\s*\/?>/gi, "\n"); @@ -49,7 +49,7 @@ function htmlToMarkdown(str, images = true, embed = true) { ); str = str.replace(/<\/?p>/gi, "\n"); str = str.replace(/
((.|\n)*?)<\/dd>/gi, (_, inner) => "\u3000\u3000" + inner.split("\n").join("\n\u3000\u3000")); - str = str.replace(/]+)?>((.|\n)*?)<\/ol>/gi, (_, __, inner) => { + str = str.replace(/]+)?>((.|\n)*?)<\/ol>/gi, (_, __, inner) => { let index = 0; return inner .replace(/
  • /gi, () => { @@ -59,7 +59,7 @@ function htmlToMarkdown(str, images = true, embed = true) { .replace(/<\/li>/gi, "\n") .replaceAll("\n\n", "\n"); }); - str = str.replace(/]+)?>((.|\n)*?)<\/ul>/gi, (_, __, inner) => { + str = str.replace(/]+)?>((.|\n)*?)<\/ul>/gi, (_, __, inner) => { let index = 0; return inner .replace(/
  • /gi, () => { @@ -69,17 +69,17 @@ function htmlToMarkdown(str, images = true, embed = true) { .replace(/<\/li>/gi, "\n") .replaceAll("\n\n", "\n"); }); - str = str.replace(/<\/?code(\s+[^>]+)?>/gi, "`"); - str = str.replace(/<\/?em(\s+[^>]+)?>/gi, "_"); - str = str.replace(/<\/?i(\s+[^>]+)?>/gi, "_"); - str = str.replace(/<\/?b(\s+[^>]+)?>/gi, "**"); - str = str.replace(/<\/?u(\s+[^>]+)?>/gi, "__"); - str = str.replace(/<\/?s(\s+[^>]+)?>/gi, "~~"); - str = str.replace(/]+)?>/gi, "# "); - str = str.replace(/]+)?>/gi, "## "); - str = str.replace(/]+)?>/gi, "### "); - str = str.replace(/<\/?h4(\s+[^>]+)?>/gi, "**"); - str = str.replace(/<(math|noscript)(\s+[^>]+)?>((.|\n)*?)<\/(math|noscript)>/gi, ""); + str = str.replace(/<\/?code(\s*[^>]+)?>/gi, "`"); + str = str.replace(/<\/?em(\s*[^>]+)?>/gi, "_"); + str = str.replace(/<\/?i(\s*[^>]+)?>/gi, "_"); + str = str.replace(/<\/?b(\s*[^>]+)?>/gi, "**"); + str = str.replace(/<\/?u(\s*[^>]+)?>/gi, "__"); + str = str.replace(/<\/?s(\s*[^>]+)?>/gi, "~~"); + str = str.replace(/]+)?>/gi, "# "); + str = str.replace(/]+)?>/gi, "## "); + str = str.replace(/]+)?>/gi, "### "); + str = str.replace(/<\/?h4(\s*[^>]+)?>/gi, "**"); + str = str.replace(/<(math|noscript)(\s*[^>]+)?>((.|\n)*?)<\/(math|noscript)>/gi, ""); str = str.replace(/<[^>]+?>/gi, ""); str = parseHtmlEntities(str); // whyyyyyyyyyyyy From f86e33ef3eaaddafae1d0a8886ae923abf3cbf42 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Fri, 28 Mar 2025 17:20:01 -0600 Subject: [PATCH 18/19] fedimbed: javascript moment --- src/modules/fedimbed.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/fedimbed.js b/src/modules/fedimbed.js index c169f61..173a9e2 100644 --- a/src/modules/fedimbed.js +++ b/src/modules/fedimbed.js @@ -997,7 +997,7 @@ async function processUrl(msg, url, spoiler = false, command = false) { } } - if (postData.attachment) { + if (postData.attachment != null) { // NB: gts doesnt send singular attachments as array const attachments = Array.isArray(postData.attachment) ? postData.attachment : [postData.attachment]; for (const attachment of attachments) { From 38ef4d1c2fbff37c0b37b7f2ef243e71cde2b944 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sat, 29 Mar 2025 10:05:21 -0600 Subject: [PATCH 19/19] guh --- src/util/html.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/html.js b/src/util/html.js index 092a0f0..be61121 100644 --- a/src/util/html.js +++ b/src/util/html.js @@ -69,6 +69,7 @@ function htmlToMarkdown(str, images = true, embed = true) { .replace(/<\/li>/gi, "\n") .replaceAll("\n\n", "\n"); }); + str = str.replace(/<\/?span(\s*[^>]+)?>/gi, ""); str = str.replace(/<\/?code(\s*[^>]+)?>/gi, "`"); str = str.replace(/<\/?em(\s*[^>]+)?>/gi, "_"); str = str.replace(/<\/?i(\s*[^>]+)?>/gi, "_");