@rmdes/indiekit-endpoint-conversations 2.1.3 → 2.1.5
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/lib/polling/scheduler.js +45 -31
- package/package.json +1 -1
package/lib/polling/scheduler.js
CHANGED
|
@@ -100,10 +100,12 @@ export async function runPollCycle(indiekit, options) {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
|
-
* Backfill empty author.photo fields in conversation_items
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
103
|
+
* Backfill empty author.photo fields in conversation_items.
|
|
104
|
+
* Tries four strategies in order:
|
|
105
|
+
* 1. ap_notifications (actorPhoto)
|
|
106
|
+
* 2. ap_activities (actorAvatar — new field from inbox handler fix)
|
|
107
|
+
* 3. ap_followers (avatar)
|
|
108
|
+
* 4. Live fetch of the actor's ActivityPub profile (icon field)
|
|
107
109
|
*/
|
|
108
110
|
async function backfillMissingAvatars(indiekit, stateCollection) {
|
|
109
111
|
try {
|
|
@@ -118,20 +120,16 @@ async function backfillMissingAvatars(indiekit, stateCollection) {
|
|
|
118
120
|
const state = await stateCollection.findOne({ _id: "poll_cursors" });
|
|
119
121
|
if (state?.avatar_backfill_complete) return;
|
|
120
122
|
|
|
121
|
-
//
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.toArray();
|
|
132
|
-
|
|
133
|
-
if (itemsWithoutPhoto.length === 0) {
|
|
134
|
-
// Mark backfill as complete so we don't query every cycle
|
|
123
|
+
// Get unique actor URLs with empty photos (deduplicate)
|
|
124
|
+
const actorUrls = await itemsCollection.distinct("author.url", {
|
|
125
|
+
$or: [
|
|
126
|
+
{ "author.photo": "" },
|
|
127
|
+
{ "author.photo": null },
|
|
128
|
+
{ "author.photo": { $exists: false } },
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (actorUrls.length === 0) {
|
|
135
133
|
await stateCollection.findOneAndUpdate(
|
|
136
134
|
{ _id: "poll_cursors" },
|
|
137
135
|
{ $set: { avatar_backfill_complete: true } },
|
|
@@ -142,13 +140,12 @@ async function backfillMissingAvatars(indiekit, stateCollection) {
|
|
|
142
140
|
|
|
143
141
|
let updated = 0;
|
|
144
142
|
|
|
145
|
-
for (const
|
|
146
|
-
const actorUrl = item.author?.url;
|
|
143
|
+
for (const actorUrl of actorUrls) {
|
|
147
144
|
if (!actorUrl) continue;
|
|
148
145
|
|
|
149
146
|
let photo = "";
|
|
150
147
|
|
|
151
|
-
// Strategy 1: Check ap_notifications
|
|
148
|
+
// Strategy 1: Check ap_notifications
|
|
152
149
|
if (!photo && notificationsCollection) {
|
|
153
150
|
try {
|
|
154
151
|
const notification = await notificationsCollection.findOne({
|
|
@@ -159,7 +156,7 @@ async function backfillMissingAvatars(indiekit, stateCollection) {
|
|
|
159
156
|
} catch { /* ignore */ }
|
|
160
157
|
}
|
|
161
158
|
|
|
162
|
-
// Strategy 2: Check ap_activities for actorAvatar
|
|
159
|
+
// Strategy 2: Check ap_activities for actorAvatar
|
|
163
160
|
if (!photo && activitiesCollection) {
|
|
164
161
|
try {
|
|
165
162
|
const activity = await activitiesCollection.findOne({
|
|
@@ -178,6 +175,25 @@ async function backfillMissingAvatars(indiekit, stateCollection) {
|
|
|
178
175
|
} catch { /* ignore */ }
|
|
179
176
|
}
|
|
180
177
|
|
|
178
|
+
// Strategy 4: Fetch actor profile from the fediverse (last resort)
|
|
179
|
+
if (!photo) {
|
|
180
|
+
try {
|
|
181
|
+
const resp = await fetch(actorUrl, {
|
|
182
|
+
headers: { Accept: "application/activity+json, application/ld+json" },
|
|
183
|
+
signal: AbortSignal.timeout(5000),
|
|
184
|
+
});
|
|
185
|
+
if (resp.ok) {
|
|
186
|
+
const actor = await resp.json();
|
|
187
|
+
const icon = actor.icon;
|
|
188
|
+
if (typeof icon === "string") {
|
|
189
|
+
photo = icon;
|
|
190
|
+
} else if (icon?.url) {
|
|
191
|
+
photo = icon.url;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch { /* timeout or network error — skip */ }
|
|
195
|
+
}
|
|
196
|
+
|
|
181
197
|
if (photo) {
|
|
182
198
|
await itemsCollection.updateMany(
|
|
183
199
|
{ "author.url": actorUrl, "author.photo": { $in: ["", null] } },
|
|
@@ -189,18 +205,16 @@ async function backfillMissingAvatars(indiekit, stateCollection) {
|
|
|
189
205
|
|
|
190
206
|
if (updated > 0) {
|
|
191
207
|
console.info(
|
|
192
|
-
`[Conversations] Avatar backfill: updated ${updated} actors with photos`,
|
|
208
|
+
`[Conversations] Avatar backfill: updated ${updated}/${actorUrls.length} actors with photos`,
|
|
193
209
|
);
|
|
194
210
|
}
|
|
195
211
|
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
);
|
|
203
|
-
}
|
|
212
|
+
// Mark complete — all actors have been attempted
|
|
213
|
+
await stateCollection.findOneAndUpdate(
|
|
214
|
+
{ _id: "poll_cursors" },
|
|
215
|
+
{ $set: { avatar_backfill_complete: true } },
|
|
216
|
+
{ upsert: true },
|
|
217
|
+
);
|
|
204
218
|
} catch (error) {
|
|
205
219
|
// Non-critical — log and continue
|
|
206
220
|
console.warn("[Conversations] Avatar backfill error:", error.message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-conversations",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.5",
|
|
4
4
|
"description": "Conversation aggregation endpoint for Indiekit. Backend enrichment service that polls Mastodon/Bluesky notifications and serves JF2-compatible data for the interactions page.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|