@rmdes/indiekit-endpoint-activitypub 1.1.5 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/assets/reader.css
CHANGED
|
@@ -296,6 +296,40 @@
|
|
|
296
296
|
max-width: 100%;
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
/* @mentions — styled as subtle pills to distinguish from prose */
|
|
300
|
+
.ap-card__content .h-card,
|
|
301
|
+
.ap-card__content a.u-url.mention {
|
|
302
|
+
color: var(--color-on-offset);
|
|
303
|
+
font-size: var(--font-size-s);
|
|
304
|
+
text-decoration: none;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.ap-card__content a.u-url.mention:hover {
|
|
308
|
+
color: var(--color-primary);
|
|
309
|
+
text-decoration: underline;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* Hashtag mentions — subtle tag styling */
|
|
313
|
+
.ap-card__content a.mention.hashtag {
|
|
314
|
+
color: var(--color-on-offset);
|
|
315
|
+
font-size: var(--font-size-s);
|
|
316
|
+
text-decoration: none;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.ap-card__content a.mention.hashtag:hover {
|
|
320
|
+
color: var(--color-primary);
|
|
321
|
+
text-decoration: underline;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Mastodon's invisible/ellipsis spans for long URLs */
|
|
325
|
+
.ap-card__content .invisible {
|
|
326
|
+
display: none;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.ap-card__content .ellipsis::after {
|
|
330
|
+
content: "…";
|
|
331
|
+
}
|
|
332
|
+
|
|
299
333
|
/* ==========================================================================
|
|
300
334
|
Content Warning
|
|
301
335
|
========================================================================== */
|
|
@@ -50,9 +50,15 @@ export function profilePostController(mountPath, plugin) {
|
|
|
50
50
|
authorizedFetch,
|
|
51
51
|
} = request.body;
|
|
52
52
|
|
|
53
|
-
// Parse profile links (attachments) from form arrays
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
// Parse profile links (attachments) from form arrays.
|
|
54
|
+
// With express.urlencoded({ extended: true }), qs strips the []
|
|
55
|
+
// suffix so the data arrives as request.body.link_name (array).
|
|
56
|
+
const linkNames = [].concat(
|
|
57
|
+
request.body.link_name || request.body["link_name[]"] || [],
|
|
58
|
+
);
|
|
59
|
+
const linkValues = [].concat(
|
|
60
|
+
request.body.link_value || request.body["link_value[]"] || [],
|
|
61
|
+
);
|
|
56
62
|
const attachments = [];
|
|
57
63
|
for (let i = 0; i < linkNames.length; i++) {
|
|
58
64
|
const n = linkNames[i]?.trim();
|
package/lib/inbox-listeners.js
CHANGED
|
@@ -321,6 +321,11 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
321
321
|
const object = await announce.getObject();
|
|
322
322
|
if (!object) return;
|
|
323
323
|
|
|
324
|
+
// Skip non-content objects (Lemmy/PieFed like/create activities
|
|
325
|
+
// that resolve to activity IDs instead of actual Note/Article posts)
|
|
326
|
+
const hasContent = object.content?.toString() || object.name?.toString();
|
|
327
|
+
if (!hasContent) return;
|
|
328
|
+
|
|
324
329
|
// Get booster actor info
|
|
325
330
|
const boosterActor = await announce.getActor();
|
|
326
331
|
const boosterInfo = await extractActorInfo(boosterActor);
|
|
@@ -446,7 +451,9 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
446
451
|
const following = await collections.ap_following.findOne({ actorUrl });
|
|
447
452
|
if (following) {
|
|
448
453
|
try {
|
|
449
|
-
const timelineItem = await extractObjectData(object
|
|
454
|
+
const timelineItem = await extractObjectData(object, {
|
|
455
|
+
actorFallback: actorObj,
|
|
456
|
+
});
|
|
450
457
|
await addTimelineItem(collections, timelineItem);
|
|
451
458
|
} catch (error) {
|
|
452
459
|
// Log extraction errors but don't fail the entire handler
|
package/lib/timeline-store.js
CHANGED
|
@@ -87,6 +87,7 @@ export async function extractActorInfo(actor) {
|
|
|
87
87
|
* @param {object} options - Extraction options
|
|
88
88
|
* @param {object} [options.boostedBy] - Actor info for boosts
|
|
89
89
|
* @param {Date} [options.boostedAt] - Boost timestamp
|
|
90
|
+
* @param {object} [options.actorFallback] - Fedify actor to use when object.getAttributedTo() fails
|
|
90
91
|
* @returns {Promise<object>} Timeline item data
|
|
91
92
|
*/
|
|
92
93
|
export async function extractObjectData(object, options = {}) {
|
|
@@ -127,7 +128,7 @@ export async function extractObjectData(object, options = {}) {
|
|
|
127
128
|
? String(object.published)
|
|
128
129
|
: new Date().toISOString();
|
|
129
130
|
|
|
130
|
-
// Extract author —
|
|
131
|
+
// Extract author — try multiple strategies in order of reliability
|
|
131
132
|
let authorObj = null;
|
|
132
133
|
try {
|
|
133
134
|
if (typeof object.getAttributedTo === "function") {
|
|
@@ -135,10 +136,48 @@ export async function extractObjectData(object, options = {}) {
|
|
|
135
136
|
authorObj = Array.isArray(attr) ? attr[0] : attr;
|
|
136
137
|
}
|
|
137
138
|
} catch {
|
|
138
|
-
//
|
|
139
|
+
// getAttributedTo() failed (Authorized Fetch, unreachable, etc.)
|
|
140
|
+
}
|
|
141
|
+
// If getAttributedTo() returned nothing, use the actor from the wrapping activity
|
|
142
|
+
if (!authorObj && options.actorFallback) {
|
|
143
|
+
authorObj = options.actorFallback;
|
|
144
|
+
}
|
|
145
|
+
// Try direct property access for plain objects
|
|
146
|
+
if (!authorObj) {
|
|
139
147
|
authorObj = object.attribution || object.attributedTo || null;
|
|
140
148
|
}
|
|
141
|
-
|
|
149
|
+
|
|
150
|
+
let author;
|
|
151
|
+
if (authorObj) {
|
|
152
|
+
author = await extractActorInfo(authorObj);
|
|
153
|
+
} else {
|
|
154
|
+
// Last resort: use attributionIds (non-fetching) to get at least a URL
|
|
155
|
+
const attrIds = object.attributionIds;
|
|
156
|
+
if (attrIds && attrIds.length > 0) {
|
|
157
|
+
const authorUrl = attrIds[0].href;
|
|
158
|
+
const parsedUrl = new URL(authorUrl);
|
|
159
|
+
const authorHostname = parsedUrl.hostname;
|
|
160
|
+
// Extract username from common URL patterns:
|
|
161
|
+
// /@username, /users/username, /ap/users/12345/
|
|
162
|
+
const pathname = parsedUrl.pathname;
|
|
163
|
+
let username = "";
|
|
164
|
+
const atPattern = pathname.match(/\/@([^/]+)/);
|
|
165
|
+
const usersPattern = pathname.match(/\/users\/([^/]+)/);
|
|
166
|
+
if (atPattern) {
|
|
167
|
+
username = atPattern[1];
|
|
168
|
+
} else if (usersPattern) {
|
|
169
|
+
username = usersPattern[1];
|
|
170
|
+
}
|
|
171
|
+
author = {
|
|
172
|
+
name: username || authorHostname,
|
|
173
|
+
url: authorUrl,
|
|
174
|
+
photo: "",
|
|
175
|
+
handle: username ? `@${username}@${authorHostname}` : "",
|
|
176
|
+
};
|
|
177
|
+
} else {
|
|
178
|
+
author = { name: "Unknown", url: "", photo: "", handle: "" };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
142
181
|
|
|
143
182
|
// Extract tags/categories
|
|
144
183
|
const category = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
{# Timeline item card partial - reusable across timeline and profile views #}
|
|
2
2
|
|
|
3
|
+
{# Skip empty cards (e.g. Lemmy/PieFed activity IDs with no actual content) #}
|
|
4
|
+
{% set hasCardContent = item.content and (item.content.html or item.content.text) %}
|
|
5
|
+
{% set hasCardTitle = item.name %}
|
|
6
|
+
{% set hasCardMedia = (item.photo and item.photo.length > 0) or (item.video and item.video.length > 0) or (item.audio and item.audio.length > 0) %}
|
|
7
|
+
{% if hasCardContent or hasCardTitle or hasCardMedia %}
|
|
8
|
+
|
|
3
9
|
<article class="ap-card{% if item.type %} ap-card--{{ item.type }}{% endif %}{% if item.inReplyTo %} ap-card--reply{% endif %}">
|
|
4
10
|
{# Boost header if this is a boosted post #}
|
|
5
11
|
{% if item.type == "boost" and item.boostedBy %}
|
|
@@ -167,3 +173,5 @@
|
|
|
167
173
|
<div x-show="error" x-text="error" class="ap-card__action-error" x-transition></div>
|
|
168
174
|
</footer>
|
|
169
175
|
</article>
|
|
176
|
+
|
|
177
|
+
{% endif %}{# end hasCardContent/hasCardTitle/hasCardMedia guard #}
|