fedimbed.bluesky: finish quotes

This commit is contained in:
Cynthia Foxwell 2025-04-18 14:28:16 -06:00
parent 19b1bdcbbe
commit 3c1b2c2ec2
Signed by: Cynosphere
SSH key fingerprint: SHA256:H3SM8ufP/uxqLwKSH7xY89TDnbR9uOHzjLoBr0tlajk

View file

@ -317,7 +317,7 @@ function getStatsBluesky(post) {
} }
async function blueskyQuoteEmbed(quote) { async function blueskyQuoteEmbed(quote) {
const embeds = []; const images = [];
const videos = []; const videos = [];
let hidden = false; let hidden = false;
@ -341,76 +341,36 @@ async function blueskyQuoteEmbed(quote) {
} }
} }
const mainEmbed = { const components = [];
color: PLATFORM_COLORS.bluesky,
url: `https://bsky.app/profile/${quote.author.handle}/post/${quote.uri.substring(quote.uri.lastIndexOf("/") + 1)}`, const header = {
author: {name: "Quoted Post", icon_url: "https://cdn.discordapp.com/emojis/1308640087759654922.png"}, type: 9,
title: `${quote.author.displayName} (@${quote.author.handle})`, components: [
thumbnail: { {
type: 10,
content: `### <:i:1308640087759654922> Quoted Post\n## ${quote.author.displayName}\n[@${
quote.author.handle
}](https://bsky.app/profile/${quote.author.did})\n${
quote.value.facets?.length ?? 0 > 0
? processBlueskyFacets(quote.value.text, quote.value.facets)
: quote.value.text
}`.trim(),
},
],
accessory: {
type: 11,
media: {
url: quote.author.avatar, url: quote.author.avatar,
}, },
description: quote.value.text,
footer: {
text: `${getStatsBluesky(quote)}\nBluesky`,
icon_url: "https://bsky.app/static/apple-touch-icon.png",
}, },
timestamp: quote.value.createdAt,
}; };
components.push(header);
/*if (quote.author.handle.endsWith(".ap.brid.gy")) {
const handle = quote.author.handle.replace(".ap.brid.gy", "");
const split = handle.split(".");
const username = split.shift();
const domain = split.join(".");
const authorUrl = `https://${domain}/users/${username}`;
const authorData = await signedFetch(authorUrl, {
headers: {
"User-Agent": FRIENDLY_USERAGENT,
Accept: "application/activity+json",
},
})
.then((res) => res.json())
.catch((err) => {
logger.error("fedimbed", `Failed to get author for "${authorUrl}": ${err}`);
});
if (authorData) {
const platform = (await resolvePlatform(authorUrl)) ?? "<no nodeinfo>";
let color = PLATFORM_COLORS[platform.toLowerCase()];
let platformName = normalizePlatform(platform);
const crawled = await getCrawledData(authorUrl, color, platformName);
if (!color && crawled?.color) {
color = crawled.color;
}
if (crawled?.platformName) {
platformName = crawled.platformName;
}
platformName += " [via Bluesky via Bridgy]";
mainEmbed.footer = {
text: platformName,
icon_url: crawled?.icon,
};
mainEmbed.title = `${authorData.name} (${authorData.preferredUsername}@${domain})`;
mainEmbed.color = color;
mainEmbed.thumbnail.url = authorData?.icon?.url ?? quote.author.avatar;
}
}*/
if (quote.value.facets?.length > 0) {
mainEmbed.description = processBlueskyFacets(mainEmbed.description, quote.value.facets);
}
if (quote.embeds?.[0]) { if (quote.embeds?.[0]) {
const embed = quote.embeds[0]; const embed = quote.embeds[0];
switch (embed.$type) { switch (embed.$type) {
case "app.bsky.embed.images#view": { case "app.bsky.embed.images#view": {
embeds.push( images.push(...embed.images.map((image) => ({media: {url: image.fullsize}, description: image.alt})));
...embed.images.map((image) => ({...mainEmbed, image: {url: image.fullsize, description: image.alt}}))
);
break; break;
} }
case "app.bsky.embed.video#view": { case "app.bsky.embed.video#view": {
@ -418,32 +378,18 @@ async function blueskyQuoteEmbed(quote) {
const domain = lookup.service.find((service) => service.id === "#atproto_pds").serviceEndpoint; const domain = lookup.service.find((service) => service.id === "#atproto_pds").serviceEndpoint;
const videoUrl = `${domain}/xrpc/com.atproto.sync.getBlob?did=${quote.author.did}&cid=${embed.cid}`; const videoUrl = `${domain}/xrpc/com.atproto.sync.getBlob?did=${quote.author.did}&cid=${embed.cid}`;
const contentType = await fetch(videoUrl, { videos.push({media: {url: videoUrl}, description: embed.alt});
method: "HEAD",
}).then((res) => res.headers.get("Content-Type"));
videos.push({url: videoUrl, desc: embed.alt, type: contentType});
embeds.push({...mainEmbed, fields: [{name: "\u200b", value: `[Video Link](${videoUrl})`}]});
break; break;
} }
case "app.bsky.embed.recordWithMedia#view": { case "app.bsky.embed.recordWithMedia#view": {
if (embed.media.$type === "app.bsky.embed.images#view") { if (embed.media.$type === "app.bsky.embed.images#view") {
embeds.push( images.push(...embed.media.images.map((image) => ({media: {url: image.fullsize}, description: image.alt})));
...embed.media.images.map((image) => ({...mainEmbed, image: {url: image.fullsize, description: image.alt}}))
);
} else if (embed.media.$type === "app.bsky.embed.video#view") { } else if (embed.media.$type === "app.bsky.embed.video#view") {
const lookup = await fetch(`https://plc.directory/${quote.author.did}`).then((res) => res.json()); const lookup = await fetch(`https://plc.directory/${quote.author.did}`).then((res) => res.json());
const domain = lookup.service.find((service) => service.id === "#atproto_pds").serviceEndpoint; const domain = lookup.service.find((service) => service.id === "#atproto_pds").serviceEndpoint;
const videoUrl = `${domain}/xrpc/com.atproto.sync.getBlob?did=${quote.author.did}&cid=${embed.media.cid}`; const videoUrl = `${domain}/xrpc/com.atproto.sync.getBlob?did=${quote.author.did}&cid=${embed.media.cid}`;
const contentType = await fetch(videoUrl, { videos.push({media: {url: videoUrl}, description: embed.alt});
method: "HEAD",
}).then((res) => res.headers.get("Content-Type"));
videos.push({url: videoUrl, desc: embed.alt, type: contentType});
embeds.push({...mainEmbed, fields: [{name: "\u200b", value: `[Video Link](${videoUrl})`}]});
} }
break; break;
} }
@ -452,22 +398,37 @@ async function blueskyQuoteEmbed(quote) {
const url = new URL(embed.external.uri); const url = new URL(embed.external.uri);
url.searchParams.delete("hh"); url.searchParams.delete("hh");
url.searchParams.delete("ww"); url.searchParams.delete("ww");
embeds.push({...mainEmbed, image: {url: url.toString()}}); images.push({media: {url: url.toString()}});
} else {
embeds.push(mainEmbed);
} }
break; break;
} }
default: {
embeds.push(mainEmbed);
break;
} }
} }
} else {
embeds.push(mainEmbed);
}
return {embeds, videos, adult, hidden, spoiler, tags}; const footer = {
type: 10,
content: `${getStatsBluesky(quote)}\n<t:${Math.floor(new Date(quote.value.createdAt).getTime() / 1000)}:F>`,
};
if (images.length > 0) {
components.push({
type: 12,
items: images,
});
}
for (const video of videos) {
components.push({
type: 12,
items: [video],
});
}
components.push(footer);
const url = `https://bsky.app/profile/${quote.author.did}/post/${quote.uri.substring(
quote.uri.lastIndexOf("/") + 1
)}`;
return {components, url, adult, hidden, spoiler, tags};
} }
async function bluesky(msg, url, spoiler = false) { async function bluesky(msg, url, spoiler = false) {
@ -529,6 +490,76 @@ async function bluesky(msg, url, spoiler = false) {
} }
} }
const videos = [];
const images = [];
let quoteData;
if (post.embed) {
switch (post.embed.$type) {
case "app.bsky.embed.images#view": {
images.push(post.embed.images.map((image) => ({media: {url: image.fullsize}, description: image.alt})));
break;
}
case "app.bsky.embed.video#view": {
const lookup = await fetch(`https://plc.directory/${post.author.did}`).then((res) => res.json());
const domain = lookup.service.find((service) => service.id === "#atproto_pds").serviceEndpoint;
const videoUrl = `${domain}/xrpc/com.atproto.sync.getBlob?did=${post.author.did}&cid=${post.embed.cid}`;
videos.push({media: {url: videoUrl}, description: post.embed.alt});
break;
}
case "app.bsky.embed.record#view": {
hasQuote = true;
const quote = post.embed.record;
quoteData = await blueskyQuoteEmbed(quote);
if (quoteData.adult) adult = true;
if (quoteData.hidden) hidden = true;
if (quoteData.spoiler) spoiler = true;
if (quoteData.tags.length > 0) tags.push(...quoteData.tags);
break;
}
case "app.bsky.embed.recordWithMedia#view": {
hasQuote = true;
if (post.embed.media.$type === "app.bsky.embed.images#view") {
images.push(post.embed.media.images.map((image) => ({media: {url: image.fullsize}, description: image.alt})));
} else if (post.embed.media.$type === "app.bsky.embed.video#view") {
const lookup = await fetch(`https://plc.directory/${post.author.did}`).then((res) => res.json());
const domain = lookup.service.find((service) => service.id === "#atproto_pds").serviceEndpoint;
const videoUrl = `${domain}/xrpc/com.atproto.sync.getBlob?did=${post.author.did}&cid=${post.embed.media.cid}`;
videos.push({media: {url: videoUrl}, description: post.embed.alt});
} else if (post.embed.media.$type === "app.bsky.embed.external#view") {
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");
}
}
const quoteData = await blueskyQuoteEmbed(post.embed.record.record);
if (quoteData.adult) adult = true;
if (quoteData.hidden) hidden = true;
if (quoteData.spoiler) spoiler = true;
if (quoteData.tags.length > 0) tags.push(...quoteData.tags);
break;
}
case "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");
images.push({media: {url: url.toString()}});
}
break;
}
}
}
const warnings = []; const warnings = [];
if (hidden) { if (hidden) {
warnings.push(":warning: Post marked as hidden"); warnings.push(":warning: Post marked as hidden");
@ -545,11 +576,6 @@ async function bluesky(msg, url, spoiler = false) {
} }
} }
const videos = [];
const images = [];
//const embeds = [];
//let sendWait = false;
const warningText = { const warningText = {
type: 10, type: 10,
content: `## ${warnings.join("\n## ")}`, content: `## ${warnings.join("\n## ")}`,
@ -595,169 +621,9 @@ async function bluesky(msg, url, spoiler = false) {
const footer = { const footer = {
type: 10, type: 10,
content: `${getStatsBluesky(post)}\n-# Bluesky \u2022 <t:${Math.floor( content: `${getStatsBluesky(post)}\n<t:${Math.floor(new Date(post.record.createdAt).getTime() / 1000)}:F>`,
new Date(post.record.createdAt).getTime() / 1000
)}:F>`,
}; };
/*if (post.author.handle.endsWith(".ap.brid.gy")) {
const handle = post.author.handle.replace(".ap.brid.gy", "");
const split = handle.split(".");
const username = split.shift();
const domain = split.join(".");
const authorUrl = `https://${domain}/@${username}`;
const authorData = await signedFetch(authorUrl, {
headers: {
"User-Agent": FRIENDLY_USERAGENT,
Accept: "application/activity+json",
},
})
.then((res) => res.json())
.catch((err) => {
logger.error("fedimbed", `Failed to get author for "${authorUrl}": ${err}`);
});
if (authorData) {
const platform = (await resolvePlatform(authorUrl)) ?? "<no nodeinfo>";
let color = PLATFORM_COLORS[platform];
let platformName = normalizePlatform(platform);
const crawled = await getCrawledData(authorUrl, color, platformName);
if (!color && crawled?.color) {
color = crawled.color;
}
if (crawled?.platformName) {
platformName = crawled.platformName;
}
platformName += " [via Bluesky via Bridgy]";
mainEmbed.footer = {
text: platformName,
icon_url: crawled?.icon,
};
mainEmbed.title = `${authorData.name} (${authorData.preferredUsername}@${domain})`;
mainEmbed.color = color;
mainEmbed.thumbnail.url = authorData?.icon?.url ?? post.author.avatar;
}
}*/
if (post.embed) {
switch (post.embed.$type) {
case "app.bsky.embed.images#view": {
images.push(post.embed.images.map((image) => ({media: {url: image.fullsize}, description: image.alt})));
break;
}
case "app.bsky.embed.video#view": {
const lookup = await fetch(`https://plc.directory/${post.author.did}`).then((res) => res.json());
const domain = lookup.service.find((service) => service.id === "#atproto_pds").serviceEndpoint;
const videoUrl = `${domain}/xrpc/com.atproto.sync.getBlob?did=${post.author.did}&cid=${post.embed.cid}`;
videos.push({media: {url: videoUrl}, description: post.embed.alt});
break;
}
case "app.bsky.embed.record#view": {
hasQuote = true;
const quote = post.embed.record;
const quoteData = await blueskyQuoteEmbed(quote);
if (quoteData.videos.length > 0) videos.push(...quoteData.videos);
//embeds.push(mainEmbed, ...quoteData.embeds);
if (quoteData.adult) adult = true;
if (quoteData.hidden) hidden = true;
if (quoteData.spoiler) spoiler = true;
if (quoteData.tags.length > 0) tags.push(...quoteData.tags);
break;
}
case "app.bsky.embed.recordWithMedia#view": {
//hasQuote = true;
if (post.embed.media.$type === "app.bsky.embed.images#view") {
images.push(post.embed.media.images.map((image) => ({media: {url: image.fullsize}, description: image.alt})));
} else if (post.embed.media.$type === "app.bsky.embed.video#view") {
const lookup = await fetch(`https://plc.directory/${post.author.did}`).then((res) => res.json());
const domain = lookup.service.find((service) => service.id === "#atproto_pds").serviceEndpoint;
const videoUrl = `${domain}/xrpc/com.atproto.sync.getBlob?did=${post.author.did}&cid=${post.embed.media.cid}`;
videos.push({media: {url: videoUrl}, description: post.embed.alt});
//embeds.push({...mainEmbed, fields: [{name: "\u200b", value: `[Video Link](${videoUrl})`}]});
} else if (post.embed.media.$type === "app.bsky.embed.external#view") {
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()}});
}
}
const quoteData = await blueskyQuoteEmbed(post.embed.record.record);
if (quoteData.videos.length > 0) videos.push(...quoteData.videos);
//embeds.push(...quoteData.embeds);
if (quoteData.adult) adult = true;
if (quoteData.hidden) hidden = true;
if (quoteData.spoiler) spoiler = true;
if (quoteData.tags.length > 0) tags.push(...quoteData.tags);
break;
}
case "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");
images.push({media: {url: url.toString()}});
}
break;
}
}
}
/*if (videos.length > 0) {
sendWait = true;
if (msg instanceof Message) await msg.addReaction("\uD83D\uDCE4");
}
const files = [];
if (videos.length > 0) {
for (const attachment of videos) {
const size = await fetch(attachment.url, {
method: "HEAD",
headers: {
"User-Agent": FRIENDLY_USERAGENT,
},
}).then((res) => Number(res.headers.get("Content-Length")));
let limit = getUploadLimit(guild);
if (msg.attachmentSizeLimit != null) limit = msg.attachmentSizeLimit;
if (size <= limit) {
const file = await fetch(attachment.url, {
headers: {
"User-Agent": FRIENDLY_USERAGENT,
},
})
.then((res) => res.arrayBuffer())
.then((buf) => Buffer.from(buf));
files.push({
filename:
(spoiler ? "SPOILER_" : "") +
(attachment.type.indexOf("/") > -1
? attachment.type.replace("/", ".").replace("quicktime", "mov")
: attachment.type + "." + (url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? "mp4")),
file,
description: attachment.desc,
});
}
}
}*/
if (images.length > 0) { if (images.length > 0) {
container.components.push({ container.components.push({
type: 12, type: 12,
@ -774,6 +640,22 @@ async function bluesky(msg, url, spoiler = false) {
if (quoteOnly && !hasQuote) return {}; if (quoteOnly && !hasQuote) return {};
if (quoteData) container.components.push({type: 14}, ...quoteData.components);
const buttons = {
type: 1,
components: [
{
type: 2,
style: 5,
label: "Post",
url,
},
],
};
if (quoteData?.url) buttons.components.push({type: 2, style: 5, label: "Quoted Post", url: quoteData.url});
container.components.push(buttons);
return { return {
response: { response: {
flags: 1 << 15, flags: 1 << 15,