@commonpub/server 2.3.4 → 2.4.0

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.
@@ -0,0 +1,100 @@
1
+ import type { DB, FederatedHubListItem, FederatedHubPostItem } from '../types.js';
2
+ /**
3
+ * List all federated hubs (accepted + not hidden).
4
+ * Used by listHubs() when includeFederated is true.
5
+ */
6
+ export declare function listFederatedHubs(db: DB, filters?: {
7
+ search?: string;
8
+ limit?: number;
9
+ offset?: number;
10
+ }): Promise<{
11
+ items: FederatedHubListItem[];
12
+ total: number;
13
+ }>;
14
+ /**
15
+ * Get a single federated hub by its local ID.
16
+ */
17
+ export declare function getFederatedHub(db: DB, hubId: string): Promise<FederatedHubListItem | null>;
18
+ /**
19
+ * Get a federated hub by its AP actor URI.
20
+ */
21
+ /**
22
+ * Get a federated hub by its AP actor URI.
23
+ * Only returns hubs with status='accepted' (follow handshake completed).
24
+ */
25
+ export declare function getFederatedHubByActorUri(db: DB, actorUri: string): Promise<FederatedHubListItem | null>;
26
+ /**
27
+ * Follow a remote hub Group actor to start mirroring.
28
+ * Returns the created federated hub entry.
29
+ */
30
+ export declare function followRemoteHub(db: DB, actorUri: string, metadata: {
31
+ originDomain: string;
32
+ remoteSlug: string;
33
+ name: string;
34
+ description?: string;
35
+ iconUrl?: string;
36
+ bannerUrl?: string;
37
+ hubType?: string;
38
+ remoteMemberCount?: number;
39
+ remotePostCount?: number;
40
+ url?: string;
41
+ rules?: string;
42
+ categories?: string[];
43
+ }, followActivityUri?: string): Promise<{
44
+ id: string;
45
+ created: boolean;
46
+ }>;
47
+ /**
48
+ * Accept a hub follow — called when we receive Accept(Follow) from the remote Group actor.
49
+ */
50
+ export declare function acceptHubFollow(db: DB, actorUri: string): Promise<boolean>;
51
+ /**
52
+ * Unfollow/cancel a hub mirror.
53
+ */
54
+ export declare function unfollowRemoteHub(db: DB, actorUri: string): Promise<boolean>;
55
+ /**
56
+ * Queue an outbound Follow activity from the local instance actor to a remote Group actor.
57
+ * This is the AP handshake that initiates hub mirroring.
58
+ */
59
+ export declare function sendHubFollow(db: DB, remoteGroupActorUri: string, domain: string): Promise<void>;
60
+ /**
61
+ * Ingest a post from a federated hub (received via Announce from Group actor).
62
+ */
63
+ export declare function ingestFederatedHubPost(db: DB, federatedHubId: string, post: {
64
+ objectUri: string;
65
+ actorUri: string;
66
+ content: string;
67
+ postType?: string;
68
+ isPinned?: boolean;
69
+ remoteLikeCount?: number;
70
+ remoteReplyCount?: number;
71
+ publishedAt?: Date;
72
+ }): Promise<{
73
+ id: string;
74
+ created: boolean;
75
+ }>;
76
+ /**
77
+ * List posts for a federated hub.
78
+ */
79
+ export declare function listFederatedHubPosts(db: DB, federatedHubId: string, opts?: {
80
+ limit?: number;
81
+ offset?: number;
82
+ }): Promise<{
83
+ items: FederatedHubPostItem[];
84
+ total: number;
85
+ }>;
86
+ /**
87
+ * Delete a federated hub post (on receiving Delete activity).
88
+ * Validates actorUri matches the post author for defense in depth.
89
+ */
90
+ export declare function deleteFederatedHubPost(db: DB, objectUri: string, actorUri?: string): Promise<boolean>;
91
+ /**
92
+ * Like a federated hub post (increment local counter).
93
+ * The caller is responsible for queuing the outbound Like activity.
94
+ */
95
+ export declare function likeFederatedHubPost(db: DB, postId: string): Promise<boolean>;
96
+ /**
97
+ * Unlike a federated hub post (decrement local counter).
98
+ */
99
+ export declare function unlikeFederatedHubPost(db: DB, postId: string): Promise<boolean>;
100
+ //# sourceMappingURL=hubMirroring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hubMirroring.d.ts","sourceRoot":"","sources":["../../src/federation/hubMirroring.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,352 @@
1
+ import { eq, and, desc, isNull, sql, ilike } from 'drizzle-orm';
2
+ import { federatedHubs, federatedHubPosts, remoteActors, activities, followRelationships, } from '@commonpub/schema';
3
+ import { buildFollowActivity } from '@commonpub/protocol';
4
+ import { normalizePagination, escapeLike } from '../query.js';
5
+ // --- Federated Hub CRUD ---
6
+ /**
7
+ * List all federated hubs (accepted + not hidden).
8
+ * Used by listHubs() when includeFederated is true.
9
+ */
10
+ export async function listFederatedHubs(db, filters = {}) {
11
+ const conditions = [
12
+ eq(federatedHubs.status, 'accepted'),
13
+ eq(federatedHubs.isHidden, false),
14
+ ];
15
+ if (filters.search) {
16
+ conditions.push(ilike(federatedHubs.name, `%${escapeLike(filters.search)}%`));
17
+ }
18
+ const where = and(...conditions);
19
+ const { limit, offset } = normalizePagination(filters);
20
+ const [rows, countResult] = await Promise.all([
21
+ db
22
+ .select({ hub: federatedHubs })
23
+ .from(federatedHubs)
24
+ .where(where)
25
+ .orderBy(desc(federatedHubs.receivedAt))
26
+ .limit(limit)
27
+ .offset(offset),
28
+ db
29
+ .select({ count: sql `count(*)::int` })
30
+ .from(federatedHubs)
31
+ .where(where),
32
+ ]);
33
+ const total = countResult[0]?.count ?? 0;
34
+ const items = rows.map((row) => ({
35
+ id: row.hub.id,
36
+ name: row.hub.name,
37
+ slug: row.hub.remoteSlug,
38
+ description: row.hub.description,
39
+ hubType: row.hub.hubType,
40
+ iconUrl: row.hub.iconUrl,
41
+ bannerUrl: row.hub.bannerUrl,
42
+ memberCount: row.hub.remoteMemberCount,
43
+ postCount: row.hub.localPostCount,
44
+ originDomain: row.hub.originDomain,
45
+ url: row.hub.url,
46
+ actorUri: row.hub.actorUri,
47
+ receivedAt: row.hub.receivedAt,
48
+ source: 'federated',
49
+ }));
50
+ return { items, total };
51
+ }
52
+ /**
53
+ * Get a single federated hub by its local ID.
54
+ */
55
+ export async function getFederatedHub(db, hubId) {
56
+ const [row] = await db
57
+ .select({ hub: federatedHubs })
58
+ .from(federatedHubs)
59
+ .where(and(eq(federatedHubs.id, hubId), eq(federatedHubs.isHidden, false)))
60
+ .limit(1);
61
+ if (!row)
62
+ return null;
63
+ return {
64
+ id: row.hub.id,
65
+ name: row.hub.name,
66
+ slug: row.hub.remoteSlug,
67
+ description: row.hub.description,
68
+ hubType: row.hub.hubType,
69
+ iconUrl: row.hub.iconUrl,
70
+ bannerUrl: row.hub.bannerUrl,
71
+ memberCount: row.hub.remoteMemberCount,
72
+ postCount: row.hub.localPostCount,
73
+ originDomain: row.hub.originDomain,
74
+ url: row.hub.url,
75
+ actorUri: row.hub.actorUri,
76
+ receivedAt: row.hub.receivedAt,
77
+ source: 'federated',
78
+ };
79
+ }
80
+ /**
81
+ * Get a federated hub by its AP actor URI.
82
+ */
83
+ /**
84
+ * Get a federated hub by its AP actor URI.
85
+ * Only returns hubs with status='accepted' (follow handshake completed).
86
+ */
87
+ export async function getFederatedHubByActorUri(db, actorUri) {
88
+ const [row] = await db
89
+ .select({ hub: federatedHubs })
90
+ .from(federatedHubs)
91
+ .where(and(eq(federatedHubs.actorUri, actorUri), eq(federatedHubs.status, 'accepted')))
92
+ .limit(1);
93
+ if (!row)
94
+ return null;
95
+ return {
96
+ id: row.hub.id,
97
+ name: row.hub.name,
98
+ slug: row.hub.remoteSlug,
99
+ description: row.hub.description,
100
+ hubType: row.hub.hubType,
101
+ iconUrl: row.hub.iconUrl,
102
+ bannerUrl: row.hub.bannerUrl,
103
+ memberCount: row.hub.remoteMemberCount,
104
+ postCount: row.hub.localPostCount,
105
+ originDomain: row.hub.originDomain,
106
+ url: row.hub.url,
107
+ actorUri: row.hub.actorUri,
108
+ receivedAt: row.hub.receivedAt,
109
+ source: 'federated',
110
+ };
111
+ }
112
+ // --- Hub Follow (Mirror Creation) ---
113
+ /**
114
+ * Follow a remote hub Group actor to start mirroring.
115
+ * Returns the created federated hub entry.
116
+ */
117
+ export async function followRemoteHub(db, actorUri, metadata, followActivityUri) {
118
+ const metaFields = {
119
+ name: metadata.name,
120
+ description: metadata.description ?? null,
121
+ iconUrl: metadata.iconUrl ?? null,
122
+ bannerUrl: metadata.bannerUrl ?? null,
123
+ hubType: metadata.hubType ?? 'community',
124
+ remoteMemberCount: metadata.remoteMemberCount ?? 0,
125
+ remotePostCount: metadata.remotePostCount ?? 0,
126
+ url: metadata.url ?? null,
127
+ rules: metadata.rules ?? null,
128
+ categories: metadata.categories ?? null,
129
+ followActivityUri: followActivityUri ?? null,
130
+ updatedAt: new Date(),
131
+ };
132
+ // Try insert first — onConflictDoNothing to detect if it already existed
133
+ const inserted = await db.insert(federatedHubs).values({
134
+ actorUri,
135
+ originDomain: metadata.originDomain,
136
+ remoteSlug: metadata.remoteSlug,
137
+ status: 'pending',
138
+ ...metaFields,
139
+ }).onConflictDoNothing({ target: federatedHubs.actorUri })
140
+ .returning({ id: federatedHubs.id });
141
+ if (inserted.length > 0) {
142
+ return { id: inserted[0].id, created: true };
143
+ }
144
+ // Already existed — update metadata
145
+ const [updated] = await db.update(federatedHubs).set(metaFields)
146
+ .where(eq(federatedHubs.actorUri, actorUri))
147
+ .returning({ id: federatedHubs.id });
148
+ return { id: updated.id, created: false };
149
+ }
150
+ /**
151
+ * Accept a hub follow — called when we receive Accept(Follow) from the remote Group actor.
152
+ */
153
+ export async function acceptHubFollow(db, actorUri) {
154
+ const result = await db
155
+ .update(federatedHubs)
156
+ .set({ status: 'accepted', updatedAt: new Date() })
157
+ .where(and(eq(federatedHubs.actorUri, actorUri), eq(federatedHubs.status, 'pending')))
158
+ .returning({ id: federatedHubs.id });
159
+ return result.length > 0;
160
+ }
161
+ /**
162
+ * Unfollow/cancel a hub mirror.
163
+ */
164
+ export async function unfollowRemoteHub(db, actorUri) {
165
+ const result = await db
166
+ .update(federatedHubs)
167
+ .set({ isHidden: true, status: 'rejected', updatedAt: new Date() })
168
+ .where(eq(federatedHubs.actorUri, actorUri))
169
+ .returning({ id: federatedHubs.id });
170
+ return result.length > 0;
171
+ }
172
+ /**
173
+ * Queue an outbound Follow activity from the local instance actor to a remote Group actor.
174
+ * This is the AP handshake that initiates hub mirroring.
175
+ */
176
+ export async function sendHubFollow(db, remoteGroupActorUri, domain) {
177
+ // Resolve & cache remote actor (delivery worker needs their inbox)
178
+ const { resolveRemoteActor } = await import('./federation.js');
179
+ const resolved = await resolveRemoteActor(db, remoteGroupActorUri).catch(() => null);
180
+ if (!resolved) {
181
+ throw new Error(`Could not resolve remote hub actor ${remoteGroupActorUri}`);
182
+ }
183
+ const localActorUri = `https://${domain}/actor`;
184
+ const followActivity = buildFollowActivity(domain, localActorUri, remoteGroupActorUri);
185
+ // Store follow relationship for the delivery system to find
186
+ await db.insert(followRelationships).values({
187
+ followerActorUri: localActorUri,
188
+ followingActorUri: remoteGroupActorUri,
189
+ status: 'pending',
190
+ }).onConflictDoNothing();
191
+ // Update the federatedHubs record with the Follow activity URI
192
+ await db.update(federatedHubs).set({
193
+ followActivityUri: followActivity.id,
194
+ updatedAt: new Date(),
195
+ }).where(eq(federatedHubs.actorUri, remoteGroupActorUri));
196
+ // Queue Follow for async delivery
197
+ await db.insert(activities).values({
198
+ type: 'Follow',
199
+ actorUri: localActorUri,
200
+ objectUri: remoteGroupActorUri,
201
+ payload: followActivity,
202
+ direction: 'outbound',
203
+ status: 'pending',
204
+ });
205
+ }
206
+ // --- Federated Hub Posts ---
207
+ /**
208
+ * Ingest a post from a federated hub (received via Announce from Group actor).
209
+ */
210
+ export async function ingestFederatedHubPost(db, federatedHubId, post) {
211
+ // Resolve remote actor
212
+ const [actor] = await db
213
+ .select({ id: remoteActors.id })
214
+ .from(remoteActors)
215
+ .where(eq(remoteActors.actorUri, post.actorUri))
216
+ .limit(1);
217
+ // Try insert first — if conflict (already exists), update separately
218
+ const inserted = await db.insert(federatedHubPosts).values({
219
+ federatedHubId,
220
+ objectUri: post.objectUri,
221
+ actorUri: post.actorUri,
222
+ remoteActorId: actor?.id ?? null,
223
+ content: post.content,
224
+ postType: post.postType ?? 'text',
225
+ isPinned: post.isPinned ?? false,
226
+ remoteLikeCount: post.remoteLikeCount ?? 0,
227
+ remoteReplyCount: post.remoteReplyCount ?? 0,
228
+ publishedAt: post.publishedAt ?? null,
229
+ }).onConflictDoNothing({ target: federatedHubPosts.objectUri })
230
+ .returning({ id: federatedHubPosts.id });
231
+ if (inserted.length > 0) {
232
+ // Genuinely new post — increment count
233
+ await db.update(federatedHubs).set({
234
+ localPostCount: sql `${federatedHubs.localPostCount} + 1`,
235
+ lastSyncAt: new Date(),
236
+ }).where(eq(federatedHubs.id, federatedHubId));
237
+ return { id: inserted[0].id, created: true };
238
+ }
239
+ // Already existed — update content
240
+ const [existing] = await db.update(federatedHubPosts).set({
241
+ content: post.content,
242
+ postType: post.postType ?? 'text',
243
+ isPinned: post.isPinned ?? false,
244
+ remoteLikeCount: post.remoteLikeCount ?? 0,
245
+ remoteReplyCount: post.remoteReplyCount ?? 0,
246
+ }).where(eq(federatedHubPosts.objectUri, post.objectUri))
247
+ .returning({ id: federatedHubPosts.id });
248
+ return { id: existing.id, created: false };
249
+ }
250
+ /**
251
+ * List posts for a federated hub.
252
+ */
253
+ export async function listFederatedHubPosts(db, federatedHubId, opts = {}) {
254
+ const { limit, offset } = normalizePagination(opts);
255
+ const where = and(eq(federatedHubPosts.federatedHubId, federatedHubId), isNull(federatedHubPosts.deletedAt));
256
+ const [rows, countResult] = await Promise.all([
257
+ db
258
+ .select({
259
+ post: federatedHubPosts,
260
+ actor: {
261
+ actorUri: remoteActors.actorUri,
262
+ preferredUsername: remoteActors.preferredUsername,
263
+ displayName: remoteActors.displayName,
264
+ avatarUrl: remoteActors.avatarUrl,
265
+ instanceDomain: remoteActors.instanceDomain,
266
+ },
267
+ })
268
+ .from(federatedHubPosts)
269
+ .leftJoin(remoteActors, eq(federatedHubPosts.remoteActorId, remoteActors.id))
270
+ .where(where)
271
+ .orderBy(desc(federatedHubPosts.isPinned), desc(federatedHubPosts.receivedAt))
272
+ .limit(limit)
273
+ .offset(offset),
274
+ db
275
+ .select({ count: sql `count(*)::int` })
276
+ .from(federatedHubPosts)
277
+ .where(where),
278
+ ]);
279
+ const total = countResult[0]?.count ?? 0;
280
+ const items = rows.map((row) => ({
281
+ id: row.post.id,
282
+ federatedHubId: row.post.federatedHubId,
283
+ content: row.post.content,
284
+ postType: row.post.postType,
285
+ isPinned: row.post.isPinned,
286
+ localLikeCount: row.post.localLikeCount,
287
+ localReplyCount: row.post.localReplyCount,
288
+ remoteLikeCount: row.post.remoteLikeCount,
289
+ remoteReplyCount: row.post.remoteReplyCount,
290
+ publishedAt: row.post.publishedAt,
291
+ receivedAt: row.post.receivedAt,
292
+ objectUri: row.post.objectUri,
293
+ author: {
294
+ actorUri: row.actor?.actorUri ?? row.post.actorUri,
295
+ preferredUsername: row.actor?.preferredUsername ?? null,
296
+ displayName: row.actor?.displayName ?? null,
297
+ avatarUrl: row.actor?.avatarUrl ?? null,
298
+ instanceDomain: row.actor?.instanceDomain ?? 'unknown',
299
+ },
300
+ source: 'federated',
301
+ }));
302
+ return { items, total };
303
+ }
304
+ /**
305
+ * Delete a federated hub post (on receiving Delete activity).
306
+ * Validates actorUri matches the post author for defense in depth.
307
+ */
308
+ export async function deleteFederatedHubPost(db, objectUri, actorUri) {
309
+ const conditions = [
310
+ eq(federatedHubPosts.objectUri, objectUri),
311
+ isNull(federatedHubPosts.deletedAt),
312
+ ];
313
+ if (actorUri) {
314
+ conditions.push(eq(federatedHubPosts.actorUri, actorUri));
315
+ }
316
+ const result = await db
317
+ .update(federatedHubPosts)
318
+ .set({ deletedAt: new Date() })
319
+ .where(and(...conditions))
320
+ .returning({ id: federatedHubPosts.id, federatedHubId: federatedHubPosts.federatedHubId });
321
+ if (result.length === 0)
322
+ return false;
323
+ // Decrement local post count
324
+ await db.update(federatedHubs).set({
325
+ localPostCount: sql `GREATEST(${federatedHubs.localPostCount} - 1, 0)`,
326
+ }).where(eq(federatedHubs.id, result[0].federatedHubId));
327
+ return true;
328
+ }
329
+ /**
330
+ * Like a federated hub post (increment local counter).
331
+ * The caller is responsible for queuing the outbound Like activity.
332
+ */
333
+ export async function likeFederatedHubPost(db, postId) {
334
+ const result = await db
335
+ .update(federatedHubPosts)
336
+ .set({ localLikeCount: sql `${federatedHubPosts.localLikeCount} + 1` })
337
+ .where(eq(federatedHubPosts.id, postId))
338
+ .returning({ id: federatedHubPosts.id });
339
+ return result.length > 0;
340
+ }
341
+ /**
342
+ * Unlike a federated hub post (decrement local counter).
343
+ */
344
+ export async function unlikeFederatedHubPost(db, postId) {
345
+ const result = await db
346
+ .update(federatedHubPosts)
347
+ .set({ localLikeCount: sql `GREATEST(${federatedHubPosts.localLikeCount} - 1, 0)` })
348
+ .where(eq(federatedHubPosts.id, postId))
349
+ .returning({ id: federatedHubPosts.id });
350
+ return result.length > 0;
351
+ }
352
+ //# sourceMappingURL=hubMirroring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hubMirroring.js","sourceRoot":"","sources":["../../src/federation/hubMirroring.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"inboxHandlers.d.ts","sourceRoot":"","sources":["../../src/federation/inboxHandlers.ts"],"names":[],"mappings":"AAcA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAqBtC,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,EAAE,CAAC;IACP,MAAM,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,qCAAqC;IACrC,gBAAgB,CAAC,EAAE;QACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;QACjC,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,oEAAoE;IACpE,UAAU,CAAC,EAAE;QACX,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,GAAG,cAAc,CAuzB7E"}
1
+ {"version":3,"file":"inboxHandlers.d.ts","sourceRoot":"","sources":["../../src/federation/inboxHandlers.ts"],"names":[],"mappings":"AAcA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAqBtC,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,EAAE,CAAC;IACP,MAAM,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,qCAAqC;IACrC,gBAAgB,CAAC,EAAE;QACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;QACjC,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,oEAAoE;IACpE,UAAU,CAAC,EAAE;QACX,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,GAAG,cAAc,CAm3B7E"}
@@ -125,6 +125,12 @@ export function createInboxHandlers(opts) {
125
125
  catch { /* non-critical */ }
126
126
  }
127
127
  }
128
+ // Check if this Accept is for a hub mirror (remote Group accepted our Follow)
129
+ try {
130
+ const { acceptHubFollow } = await import('./hubMirroring.js');
131
+ await acceptHubFollow(db, actorUri);
132
+ }
133
+ catch { /* non-critical — may not be a hub follow */ }
128
134
  await db.insert(activities).values({
129
135
  type: 'Accept',
130
136
  actorUri,
@@ -536,6 +542,12 @@ export function createInboxHandlers(opts) {
536
542
  .update(federatedContent)
537
543
  .set({ deletedAt: new Date() })
538
544
  .where(and(eq(federatedContent.objectUri, objectId), eq(federatedContent.actorUri, actorUri), isNull(federatedContent.deletedAt)));
545
+ // Also check federated hub posts (pass actorUri for author validation)
546
+ try {
547
+ const { deleteFederatedHubPost } = await import('./hubMirroring.js');
548
+ await deleteFederatedHubPost(db, objectId, actorUri);
549
+ }
550
+ catch { /* non-critical */ }
539
551
  await db.insert(activities).values({
540
552
  type: 'Delete',
541
553
  actorUri,
@@ -714,13 +726,60 @@ export function createInboxHandlers(opts) {
714
726
  }
715
727
  catch { /* invalid URL */ }
716
728
  }
717
- // If not local, try federated content
729
+ // If not local, try federated content boost
718
730
  if (!matched) {
719
731
  await db
720
732
  .update(federatedContent)
721
733
  .set({ localBoostCount: sql `${federatedContent.localBoostCount} + 1` })
722
734
  .where(eq(federatedContent.objectUri, objectUri));
723
735
  }
736
+ // Check if this Announce is from a Group actor we're mirroring (hub post ingestion)
737
+ if (!matched) {
738
+ try {
739
+ // Loop prevention: skip Announces of our own content
740
+ const isOwnContent = new URL(objectUri).hostname === domain;
741
+ if (!isOwnContent) {
742
+ const { getFederatedHubByActorUri, ingestFederatedHubPost } = await import('./hubMirroring.js');
743
+ const mirroredHub = await getFederatedHubByActorUri(db, actorUri);
744
+ if (mirroredHub) {
745
+ // Dereference the announced Note to get its content
746
+ const noteResponse = await fetch(objectUri, {
747
+ headers: { 'Accept': 'application/activity+json, application/ld+json' },
748
+ signal: AbortSignal.timeout(10_000),
749
+ });
750
+ if (noteResponse.ok) {
751
+ const note = await noteResponse.json();
752
+ const noteContent = note.content ?? '';
753
+ // AP spec: attributedTo can be string, object with id, or array
754
+ let noteActorUri = actorUri;
755
+ const attr = note.attributedTo;
756
+ if (typeof attr === 'string') {
757
+ noteActorUri = attr;
758
+ }
759
+ else if (attr && typeof attr === 'object' && !Array.isArray(attr)) {
760
+ noteActorUri = attr.id ?? actorUri;
761
+ }
762
+ else if (Array.isArray(attr) && attr.length > 0) {
763
+ const first = attr[0];
764
+ noteActorUri = typeof first === 'string' ? first : first.id ?? actorUri;
765
+ }
766
+ // Resolve the post author
767
+ await resolveRemoteActor(db, noteActorUri);
768
+ await ingestFederatedHubPost(db, mirroredHub.id, {
769
+ objectUri,
770
+ actorUri: noteActorUri,
771
+ content: noteContent,
772
+ postType: note.cpubPostType ?? 'text',
773
+ publishedAt: note.published ? new Date(note.published) : undefined,
774
+ });
775
+ }
776
+ }
777
+ }
778
+ }
779
+ catch (err) {
780
+ console.warn('[inbox] Hub post ingestion failed:', err instanceof Error ? err.message : err);
781
+ }
782
+ }
724
783
  await db.insert(activities).values({
725
784
  type: 'Announce',
726
785
  actorUri,