feat: deliver likes as bookmarks, revert announce cc, add OG images
- Likes are now sent as Create/Note with bookmark-style content (🔖) instead of Like activities, ensuring proper display on Mastodon - Announce activities reverted to upstream addressing (to: Public only, no cc:followers) - Add per-post OG image to both plain JSON-LD and Fedify Note/Article objects, derived from the post URL pattern (/og/{date}-{slug}.png) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+27
-38
@@ -14,7 +14,6 @@ import {
|
|||||||
Create,
|
Create,
|
||||||
Hashtag,
|
Hashtag,
|
||||||
Image,
|
Image,
|
||||||
Like,
|
|
||||||
Mention,
|
Mention,
|
||||||
Note,
|
Note,
|
||||||
Video,
|
Video,
|
||||||
@@ -95,24 +94,7 @@ function linkifyMentions(html, resolvedMentions) {
|
|||||||
export function jf2ToActivityStreams(properties, actorUrl, publicationUrl, options = {}) {
|
export function jf2ToActivityStreams(properties, actorUrl, publicationUrl, options = {}) {
|
||||||
const postType = properties["post-type"];
|
const postType = properties["post-type"];
|
||||||
|
|
||||||
if (postType === "like") {
|
// Likes are delivered as bookmarks — fall through to bookmark handling below
|
||||||
// Serve like posts as Note objects for AP content negotiation.
|
|
||||||
// Returning a bare Like activity breaks Mastodon's authorize_interaction
|
|
||||||
// flow because it expects a content object (Note/Article), not an activity.
|
|
||||||
const likeOf = properties["like-of"];
|
|
||||||
const postUrl = resolvePostUrl(properties.url, publicationUrl);
|
|
||||||
return {
|
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
|
||||||
type: "Note",
|
|
||||||
id: postUrl,
|
|
||||||
attributedTo: actorUrl,
|
|
||||||
published: properties.published,
|
|
||||||
url: postUrl,
|
|
||||||
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
|
||||||
cc: [`${actorUrl.replace(/\/$/, "")}/followers`],
|
|
||||||
content: `\u2764\uFE0F <a href="${likeOf}">${likeOf}</a>`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reposts are always public — Mastodon and other implementations expect this
|
// Reposts are always public — Mastodon and other implementations expect this
|
||||||
if (postType === "repost") {
|
if (postType === "repost") {
|
||||||
@@ -156,8 +138,8 @@ export function jf2ToActivityStreams(properties, actorUrl, publicationUrl, optio
|
|||||||
: [followersUrl],
|
: [followersUrl],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (postType === "bookmark") {
|
if (postType === "bookmark" || postType === "like") {
|
||||||
const bookmarkUrl = properties["bookmark-of"];
|
const bookmarkUrl = properties["bookmark-of"] || properties["like-of"];
|
||||||
const commentary = linkifyUrls(properties.content?.html || properties.content || "");
|
const commentary = linkifyUrls(properties.content?.html || properties.content || "");
|
||||||
object.content = commentary
|
object.content = commentary
|
||||||
? `${commentary}<br><br>\u{1F516} <a href="${bookmarkUrl}">${bookmarkUrl}</a>`
|
? `${commentary}<br><br>\u{1F516} <a href="${bookmarkUrl}">${bookmarkUrl}</a>`
|
||||||
@@ -178,6 +160,16 @@ export function jf2ToActivityStreams(properties, actorUrl, publicationUrl, optio
|
|||||||
object.content += `<p>\u{1F517} <a href="${postUrl}">${postUrl}</a></p>`;
|
object.content += `<p>\u{1F517} <a href="${postUrl}">${postUrl}</a></p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OG image for fediverse preview cards
|
||||||
|
const ogMatch = postUrl && postUrl.match(/\/([\w-]+)\/(\d{4})\/(\d{2})\/(\d{2})\/([\w-]+)\/?$/);
|
||||||
|
if (ogMatch) {
|
||||||
|
object.image = {
|
||||||
|
type: "Image",
|
||||||
|
url: `${publicationUrl.replace(/\/$/, "")}/og/${ogMatch[2]}-${ogMatch[3]}-${ogMatch[4]}-${ogMatch[5]}.png`,
|
||||||
|
mediaType: "image/png",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (isArticle) {
|
if (isArticle) {
|
||||||
object.name = properties.name;
|
object.name = properties.name;
|
||||||
if (properties.summary) {
|
if (properties.summary) {
|
||||||
@@ -253,28 +245,16 @@ export function jf2ToAS2Activity(properties, actorUrl, publicationUrl, options =
|
|||||||
const postType = properties["post-type"];
|
const postType = properties["post-type"];
|
||||||
const actorUri = new URL(actorUrl);
|
const actorUri = new URL(actorUrl);
|
||||||
|
|
||||||
if (postType === "like") {
|
// Likes are delivered as bookmarks — fall through to bookmark handling below
|
||||||
const likeOf = properties["like-of"];
|
|
||||||
if (!likeOf) return null;
|
|
||||||
const followersUrl = `${actorUrl.replace(/\/$/, "")}/followers`;
|
|
||||||
return new Like({
|
|
||||||
actor: actorUri,
|
|
||||||
object: new URL(likeOf),
|
|
||||||
to: new URL("https://www.w3.org/ns/activitystreams#Public"),
|
|
||||||
cc: new URL(followersUrl),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reposts are always public — Mastodon and other implementations expect this
|
// Reposts are always public — upstream @rmdes addressing
|
||||||
if (postType === "repost") {
|
if (postType === "repost") {
|
||||||
const repostOf = properties["repost-of"];
|
const repostOf = properties["repost-of"];
|
||||||
if (!repostOf) return null;
|
if (!repostOf) return null;
|
||||||
const followersUrl = `${actorUrl.replace(/\/$/, "")}/followers`;
|
|
||||||
return new Announce({
|
return new Announce({
|
||||||
actor: actorUri,
|
actor: actorUri,
|
||||||
object: new URL(repostOf),
|
object: new URL(repostOf),
|
||||||
to: new URL("https://www.w3.org/ns/activitystreams#Public"),
|
to: new URL("https://www.w3.org/ns/activitystreams#Public"),
|
||||||
cc: new URL(followersUrl),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,8 +316,8 @@ export function jf2ToAS2Activity(properties, actorUrl, publicationUrl, options =
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
if (postType === "bookmark") {
|
if (postType === "bookmark" || postType === "like") {
|
||||||
const bookmarkUrl = properties["bookmark-of"];
|
const bookmarkUrl = properties["bookmark-of"] || properties["like-of"];
|
||||||
const commentary = linkifyUrls(properties.content?.html || properties.content || "");
|
const commentary = linkifyUrls(properties.content?.html || properties.content || "");
|
||||||
noteOptions.content = commentary
|
noteOptions.content = commentary
|
||||||
? `${commentary}<br><br>\u{1F516} <a href="${bookmarkUrl}">${bookmarkUrl}</a>`
|
? `${commentary}<br><br>\u{1F516} <a href="${bookmarkUrl}">${bookmarkUrl}</a>`
|
||||||
@@ -382,6 +362,15 @@ export function jf2ToAS2Activity(properties, actorUrl, publicationUrl, options =
|
|||||||
noteOptions.attachments = fedifyAttachments;
|
noteOptions.attachments = fedifyAttachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OG image for fediverse preview cards
|
||||||
|
const ogMatchF = postUrl && postUrl.match(/\/([\w-]+)\/(\d{4})\/(\d{2})\/(\d{2})\/([\w-]+)\/?$/);
|
||||||
|
if (ogMatchF) {
|
||||||
|
noteOptions.image = new Image({
|
||||||
|
url: new URL(`${publicationUrl.replace(/\/$/, "")}/og/${ogMatchF[2]}-${ogMatchF[3]}-${ogMatchF[4]}-${ogMatchF[5]}.png`),
|
||||||
|
mediaType: "image/png",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Tags: hashtags + Mention for reply addressing + @mentions
|
// Tags: hashtags + Mention for reply addressing + @mentions
|
||||||
const fedifyTags = buildFedifyTags(properties, publicationUrl, postType);
|
const fedifyTags = buildFedifyTags(properties, publicationUrl, postType);
|
||||||
|
|
||||||
@@ -575,7 +564,7 @@ function buildPlainTags(properties, publicationUrl, existing) {
|
|||||||
|
|
||||||
function buildFedifyTags(properties, publicationUrl, postType) {
|
function buildFedifyTags(properties, publicationUrl, postType) {
|
||||||
const tags = [];
|
const tags = [];
|
||||||
if (postType === "bookmark") {
|
if (postType === "bookmark" || postType === "like") {
|
||||||
tags.push(
|
tags.push(
|
||||||
new Hashtag({
|
new Hashtag({
|
||||||
name: "#bookmark",
|
name: "#bookmark",
|
||||||
|
|||||||
Reference in New Issue
Block a user