@takosjp/yurucommu-core 3.0.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.
- package/LICENSE +16 -0
- package/README.md +82 -0
- package/migrations/0001_init.sql +495 -0
- package/migrations/0002_social_remote_actor_edges.sql +92 -0
- package/migrations/0003_activity_remote_object_edges.sql +68 -0
- package/migrations/0004_blocklist.sql +26 -0
- package/migrations/0005_story_community_scope.sql +13 -0
- package/migrations/0006_dm_community_read_status.sql +19 -0
- package/migrations/0007_moderation_reports.sql +22 -0
- package/migrations/0008_actor_fields_aka.sql +18 -0
- package/migrations/0009_object_tags.sql +13 -0
- package/migrations/0010_object_recipients_drop_actor_fk.sql +34 -0
- package/migrations/0011_drop_remote_actor_fks.sql +205 -0
- package/migrations/0012_objects_content_fts.sql +39 -0
- package/migrations/0013_efficiency_indexes.sql +13 -0
- package/migrations/0014_inbox_actor_created_idx.sql +15 -0
- package/migrations/0015_community_bans.sql +16 -0
- package/migrations/0016_namespace_takos_oidc_subject.sql +19 -0
- package/migrations/0017_mobile_push_registrations.sql +22 -0
- package/migrations/README.md +122 -0
- package/package.json +75 -0
- package/packages/api/LICENSE +16 -0
- package/packages/api/package.json +30 -0
- package/packages/api/src/index.ts +4 -0
- package/packages/api/src/lib/api/account.ts +20 -0
- package/packages/api/src/lib/api/actors.ts +149 -0
- package/packages/api/src/lib/api/auth.ts +46 -0
- package/packages/api/src/lib/api/communities.ts +329 -0
- package/packages/api/src/lib/api/dm.test.ts +67 -0
- package/packages/api/src/lib/api/dm.ts +236 -0
- package/packages/api/src/lib/api/fetch.ts +111 -0
- package/packages/api/src/lib/api/follow.ts +30 -0
- package/packages/api/src/lib/api/media.ts +100 -0
- package/packages/api/src/lib/api/moderation.ts +98 -0
- package/packages/api/src/lib/api/normalize.ts +71 -0
- package/packages/api/src/lib/api/notifications.test.ts +63 -0
- package/packages/api/src/lib/api/notifications.ts +61 -0
- package/packages/api/src/lib/api/posts.test.ts +110 -0
- package/packages/api/src/lib/api/posts.ts +181 -0
- package/packages/api/src/lib/api/recommendations.ts +22 -0
- package/packages/api/src/lib/api/search.ts +88 -0
- package/packages/api/src/lib/api/stories.ts +80 -0
- package/packages/api/src/lib/api.ts +15 -0
- package/packages/api/src/lib/fetch-with-timeout.ts +42 -0
- package/packages/api/src/lib/transport.ts +40 -0
- package/packages/api/src/social-server.ts +47 -0
- package/packages/api/src/types/index.ts +185 -0
- package/scripts/apply-takosumi-migrations.ts +621 -0
- package/src/backend/federation-helpers.ts +36 -0
- package/src/backend/index.ts +872 -0
- package/src/backend/lib/account-migration.ts +106 -0
- package/src/backend/lib/activitypub-actor-cache.ts +238 -0
- package/src/backend/lib/activitypub-helpers.ts +131 -0
- package/src/backend/lib/activitypub-validators.ts +323 -0
- package/src/backend/lib/ap-context.ts +16 -0
- package/src/backend/lib/ap-ids.ts +101 -0
- package/src/backend/lib/ap-response.ts +30 -0
- package/src/backend/lib/ap-signing.ts +87 -0
- package/src/backend/lib/ap-verify.ts +670 -0
- package/src/backend/lib/auth-lockout.ts +230 -0
- package/src/backend/lib/backend-paths.ts +34 -0
- package/src/backend/lib/base64.ts +30 -0
- package/src/backend/lib/blocklist-purge.ts +109 -0
- package/src/backend/lib/blocklist.ts +279 -0
- package/src/backend/lib/chunk.ts +33 -0
- package/src/backend/lib/client-ip.ts +169 -0
- package/src/backend/lib/community-visibility.ts +230 -0
- package/src/backend/lib/crypto.ts +424 -0
- package/src/backend/lib/delivery/circuit.ts +265 -0
- package/src/backend/lib/delivery/metrics.ts +30 -0
- package/src/backend/lib/delivery/planner.ts +190 -0
- package/src/backend/lib/delivery/queue-batching.ts +626 -0
- package/src/backend/lib/delivery/queue-delivery.ts +641 -0
- package/src/backend/lib/delivery/queue.ts +576 -0
- package/src/backend/lib/delivery/transformers.ts +56 -0
- package/src/backend/lib/delivery/types.ts +139 -0
- package/src/backend/lib/errors.ts +114 -0
- package/src/backend/lib/federation-fetch.ts +296 -0
- package/src/backend/lib/feed-cursor.ts +57 -0
- package/src/backend/lib/feed-exclude.ts +48 -0
- package/src/backend/lib/hex.ts +8 -0
- package/src/backend/lib/log-mask.ts +213 -0
- package/src/backend/lib/logger.ts +285 -0
- package/src/backend/lib/mobile-contract.ts +137 -0
- package/src/backend/lib/oauth-providers.ts +324 -0
- package/src/backend/lib/oauth-utils.ts +148 -0
- package/src/backend/lib/oidc-id-token.ts +151 -0
- package/src/backend/lib/parse-helpers.ts +31 -0
- package/src/backend/lib/post-visibility.ts +190 -0
- package/src/backend/lib/session-actor.ts +61 -0
- package/src/backend/lib/ssrf.ts +428 -0
- package/src/backend/lib/strip-image-metadata.ts +191 -0
- package/src/backend/middleware/bearer-auth.ts +70 -0
- package/src/backend/middleware/body-limit.ts +212 -0
- package/src/backend/middleware/cache.ts +429 -0
- package/src/backend/middleware/csrf.ts +130 -0
- package/src/backend/middleware/error-handler.ts +77 -0
- package/src/backend/middleware/rate-limit.ts +308 -0
- package/src/backend/public.ts +21 -0
- package/src/backend/routes/account-teardown.ts +430 -0
- package/src/backend/routes/activitypub/handlers/actor-inbox-handlers.ts +354 -0
- package/src/backend/routes/activitypub/handlers/inbound-timestamp.ts +29 -0
- package/src/backend/routes/activitypub/handlers/inbox-content-handlers.ts +1634 -0
- package/src/backend/routes/activitypub/handlers/inbox-follow-handlers.ts +547 -0
- package/src/backend/routes/activitypub/handlers/inbox-interaction-handlers.ts +497 -0
- package/src/backend/routes/activitypub/handlers/inbox-shared-helpers.ts +262 -0
- package/src/backend/routes/activitypub/handlers/user-inbox-handlers.ts +35 -0
- package/src/backend/routes/activitypub/inbox-types.ts +74 -0
- package/src/backend/routes/activitypub/inbox.ts +1191 -0
- package/src/backend/routes/activitypub/outbox.ts +0 -0
- package/src/backend/routes/activitypub/query-helpers.ts +227 -0
- package/src/backend/routes/activitypub.ts +616 -0
- package/src/backend/routes/actors-helpers.ts +487 -0
- package/src/backend/routes/actors.ts +1311 -0
- package/src/backend/routes/apps.ts +313 -0
- package/src/backend/routes/auth-helpers.ts +566 -0
- package/src/backend/routes/auth.ts +615 -0
- package/src/backend/routes/communities/membership-invites.ts +208 -0
- package/src/backend/routes/communities/membership-join.ts +335 -0
- package/src/backend/routes/communities/membership-members.ts +539 -0
- package/src/backend/routes/communities/membership-requests.ts +296 -0
- package/src/backend/routes/communities/membership-shared.ts +364 -0
- package/src/backend/routes/communities/messages.ts +479 -0
- package/src/backend/routes/communities/routes.ts +624 -0
- package/src/backend/routes/communities.ts +21 -0
- package/src/backend/routes/dm/contacts.ts +525 -0
- package/src/backend/routes/dm/conversations-helpers.ts +197 -0
- package/src/backend/routes/dm/conversations.ts +25 -0
- package/src/backend/routes/dm/messages.ts +658 -0
- package/src/backend/routes/dm/query-helpers.ts +85 -0
- package/src/backend/routes/dm/read-archive.ts +228 -0
- package/src/backend/routes/dm/requests.ts +222 -0
- package/src/backend/routes/dm/typing.ts +81 -0
- package/src/backend/routes/dm.ts +15 -0
- package/src/backend/routes/follow-helpers.ts +370 -0
- package/src/backend/routes/follow.ts +588 -0
- package/src/backend/routes/media.ts +692 -0
- package/src/backend/routes/mobile.ts +159 -0
- package/src/backend/routes/moderation.ts +373 -0
- package/src/backend/routes/notifications.ts +757 -0
- package/src/backend/routes/posts/delete-cascade.ts +330 -0
- package/src/backend/routes/posts/interactions.ts +795 -0
- package/src/backend/routes/posts/post-helpers.ts +847 -0
- package/src/backend/routes/posts/queries.ts +537 -0
- package/src/backend/routes/posts/routes.ts +865 -0
- package/src/backend/routes/posts/transformers.ts +161 -0
- package/src/backend/routes/posts.ts +17 -0
- package/src/backend/routes/recommendations.ts +88 -0
- package/src/backend/routes/search.ts +730 -0
- package/src/backend/routes/stories/interactions.ts +576 -0
- package/src/backend/routes/stories/query-helpers.ts +482 -0
- package/src/backend/routes/stories/routes.ts +906 -0
- package/src/backend/routes/stories.ts +13 -0
- package/src/backend/routes/takos-tools/dm.ts +249 -0
- package/src/backend/routes/takos-tools/follows.ts +225 -0
- package/src/backend/routes/takos-tools/posts.ts +292 -0
- package/src/backend/routes/takos-tools/search.ts +228 -0
- package/src/backend/routes/takos-tools/timeline.ts +132 -0
- package/src/backend/routes/takos-tools/types.ts +10 -0
- package/src/backend/routes/takos-tools-response.ts +178 -0
- package/src/backend/routes/takos-tools.ts +153 -0
- package/src/backend/routes/timeline.ts +755 -0
- package/src/backend/runtime/bun.ts +620 -0
- package/src/backend/runtime/cloudflare.ts +202 -0
- package/src/backend/runtime/compat-bun/types.ts +44 -0
- package/src/backend/runtime/memory-kv.ts +104 -0
- package/src/backend/runtime/shared.ts +142 -0
- package/src/backend/runtime/types.ts +205 -0
- package/src/backend/server.ts +636 -0
- package/src/backend/types.ts +143 -0
- package/src/db/index.ts +97 -0
- package/src/db/schema/actors.ts +129 -0
- package/src/db/schema/communities.ts +133 -0
- package/src/db/schema/date-utils.ts +17 -0
- package/src/db/schema/index.ts +17 -0
- package/src/db/schema/messaging.ts +241 -0
- package/src/db/schema/mobile.ts +37 -0
- package/src/db/schema/posts.ts +150 -0
- package/src/db/schema/relations.ts +266 -0
- package/src/db/schema/reports.ts +33 -0
- package/src/db/schema/social.ts +106 -0
- package/src/db/schema/stories.ts +70 -0
- package/src/db/schema.ts +15 -0
- package/src/plugin/public.ts +7 -0
- package/src/runtime/site-worker.ts +10 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import type { Database } from "../../../../db/index.ts";
|
|
2
|
+
import { and, eq, sql } from "drizzle-orm";
|
|
3
|
+
import type { BatchItem } from "drizzle-orm/batch";
|
|
4
|
+
import {
|
|
5
|
+
activities,
|
|
6
|
+
announces,
|
|
7
|
+
follows,
|
|
8
|
+
inbox as inboxTable,
|
|
9
|
+
likes,
|
|
10
|
+
objects,
|
|
11
|
+
} from "../../../../db/index.ts";
|
|
12
|
+
import { isLocal } from "../../../federation-helpers.ts";
|
|
13
|
+
import type { Activity } from "../inbox-types.ts";
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Atomic multi-statement commit (mirrors posts/interactions.ts `runBatch`)
|
|
17
|
+
//
|
|
18
|
+
// D1 has no interactive transactions, but both the D1 and libsql drivers
|
|
19
|
+
// expose `db.batch([...])`, which commits a list of prepared statements
|
|
20
|
+
// atomically. The shared `Database` union aliases the abstract
|
|
21
|
+
// `BaseSQLiteDatabase` base (which does not surface `batch`), so we narrow to
|
|
22
|
+
// the concrete batch surface here rather than weakening the shared type.
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
type BatchStatement = BatchItem<"sqlite">;
|
|
26
|
+
interface BatchableDb {
|
|
27
|
+
batch(
|
|
28
|
+
statements: readonly [BatchStatement, ...BatchStatement[]],
|
|
29
|
+
): Promise<unknown>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function runBatch(
|
|
33
|
+
db: Database,
|
|
34
|
+
statements: readonly [BatchStatement, ...BatchStatement[]],
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
await (db as unknown as BatchableDb).batch(statements);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Shared helpers used by multiple inbox handler files
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/** Upsert an activity record and create an inbox entry for a local actor. */
|
|
44
|
+
export async function upsertActivityAndNotify(
|
|
45
|
+
db: Database,
|
|
46
|
+
activityId: string,
|
|
47
|
+
type: string,
|
|
48
|
+
actorApId: string,
|
|
49
|
+
objectApIdValue: string,
|
|
50
|
+
rawActivity: Activity,
|
|
51
|
+
recipientApId: string,
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
const now = new Date().toISOString();
|
|
54
|
+
|
|
55
|
+
await db
|
|
56
|
+
.insert(activities)
|
|
57
|
+
.values({
|
|
58
|
+
apId: activityId,
|
|
59
|
+
type,
|
|
60
|
+
actorApId,
|
|
61
|
+
objectApId: objectApIdValue,
|
|
62
|
+
rawJson: JSON.stringify(rawActivity),
|
|
63
|
+
})
|
|
64
|
+
.onConflictDoNothing();
|
|
65
|
+
|
|
66
|
+
// Idempotent like the activities insert above: a replayed delivery (remote
|
|
67
|
+
// retry) must not create a duplicate inbox/notification row or 500 on the
|
|
68
|
+
// (actor, activity) unique constraint.
|
|
69
|
+
await db
|
|
70
|
+
.insert(inboxTable)
|
|
71
|
+
.values({
|
|
72
|
+
actorApId: recipientApId,
|
|
73
|
+
activityApId: activityId,
|
|
74
|
+
read: 0,
|
|
75
|
+
createdAt: now,
|
|
76
|
+
})
|
|
77
|
+
.onConflictDoNothing();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Interaction table descriptor used by generic helpers.
|
|
82
|
+
* Both `likes` and `announces` share the same column shape.
|
|
83
|
+
*/
|
|
84
|
+
type InteractionTable = typeof likes | typeof announces;
|
|
85
|
+
|
|
86
|
+
const INTERACTION_TABLES: Record<"like" | "announce", InteractionTable> = {
|
|
87
|
+
like: likes,
|
|
88
|
+
announce: announces,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Find a follow by activityApId and return it (or null). */
|
|
92
|
+
export async function findFollowByActivityId(
|
|
93
|
+
db: Database,
|
|
94
|
+
activityApIdValue: string,
|
|
95
|
+
): Promise<{
|
|
96
|
+
followerApId: string;
|
|
97
|
+
followingApId: string;
|
|
98
|
+
status: string;
|
|
99
|
+
} | null> {
|
|
100
|
+
const row = await db
|
|
101
|
+
.select({
|
|
102
|
+
followerApId: follows.followerApId,
|
|
103
|
+
followingApId: follows.followingApId,
|
|
104
|
+
status: follows.status,
|
|
105
|
+
})
|
|
106
|
+
.from(follows)
|
|
107
|
+
.where(eq(follows.activityApId, activityApIdValue))
|
|
108
|
+
.get();
|
|
109
|
+
return row ?? null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Delete a follow using its compound key. Returns the deleted rows (with their
|
|
114
|
+
* prior status) so callers can gate denormalized count updates on a row
|
|
115
|
+
* actually being removed AND on whether it had been counted (status
|
|
116
|
+
* 'accepted'). `.returning()` yields the deleted rows across both D1 and
|
|
117
|
+
* libsql backends.
|
|
118
|
+
*/
|
|
119
|
+
export async function deleteFollowByCompoundKey(
|
|
120
|
+
db: Database,
|
|
121
|
+
followerApId: string,
|
|
122
|
+
followingApId: string,
|
|
123
|
+
): Promise<Array<{ status: string }>> {
|
|
124
|
+
return await db
|
|
125
|
+
.delete(follows)
|
|
126
|
+
.where(
|
|
127
|
+
and(
|
|
128
|
+
eq(follows.followerApId, followerApId),
|
|
129
|
+
eq(follows.followingApId, followingApId),
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
.returning({ status: follows.status });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Lookup the owner of an object and, if local, notify via upsertActivityAndNotify.
|
|
137
|
+
* Returns the attributedTo value, or null if the object was not found.
|
|
138
|
+
*/
|
|
139
|
+
export async function notifyLocalObjectOwner(
|
|
140
|
+
db: Database,
|
|
141
|
+
objectApIdValue: string,
|
|
142
|
+
activityId: string,
|
|
143
|
+
activityType: string,
|
|
144
|
+
actorApIdValue: string,
|
|
145
|
+
rawActivity: Activity,
|
|
146
|
+
baseUrl: string,
|
|
147
|
+
): Promise<string | null> {
|
|
148
|
+
const obj = await db
|
|
149
|
+
.select({ attributedTo: objects.attributedTo })
|
|
150
|
+
.from(objects)
|
|
151
|
+
.where(eq(objects.apId, objectApIdValue))
|
|
152
|
+
.get();
|
|
153
|
+
if (!obj) return null;
|
|
154
|
+
|
|
155
|
+
if (isLocal(obj.attributedTo, baseUrl)) {
|
|
156
|
+
await upsertActivityAndNotify(
|
|
157
|
+
db,
|
|
158
|
+
activityId,
|
|
159
|
+
activityType,
|
|
160
|
+
actorApIdValue,
|
|
161
|
+
objectApIdValue,
|
|
162
|
+
rawActivity,
|
|
163
|
+
obj.attributedTo,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return obj.attributedTo;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Map interaction kind to the corresponding count column on `objects`. */
|
|
170
|
+
const COUNT_FIELDS: Record<"like" | "announce", "likeCount" | "announceCount"> =
|
|
171
|
+
{
|
|
172
|
+
like: "likeCount",
|
|
173
|
+
announce: "announceCount",
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Generic undo for Like or Announce: delete by direct object ID or fallback
|
|
178
|
+
* to the activityId lookup, then RECOMPUTE the count field.
|
|
179
|
+
* Returns true if the deletion was handled, false otherwise.
|
|
180
|
+
*
|
|
181
|
+
* #COUNTER-SYM (crash-retry convergence): the edge delete and the counter
|
|
182
|
+
* maintenance MUST commit together. Previously the edge was deleted in one
|
|
183
|
+
* statement and the counter decremented (`- 1`) in a SEPARATE statement; under
|
|
184
|
+
* the claim/processed re-dispatch model an interruption between the two left
|
|
185
|
+
* the edge gone but the counter un-decremented, and the peer's retry matched 0
|
|
186
|
+
* rows so the decrement was SKIPPED → a permanent OVER-count. Group the delete
|
|
187
|
+
* and the counter update into a single atomic `db.batch`, and derive the
|
|
188
|
+
* counter from `COUNT(*)` of the edge table (idempotent recompute, mirrors the
|
|
189
|
+
* inbound insert path in handleInteraction) rather than a blind `- 1`, so a
|
|
190
|
+
* retry after a mid-write crash CONVERGES to the true edge count.
|
|
191
|
+
*/
|
|
192
|
+
export async function undoInteraction(
|
|
193
|
+
db: Database,
|
|
194
|
+
kind: "like" | "announce",
|
|
195
|
+
countField: "likeCount" | "announceCount",
|
|
196
|
+
directObjectId: string | undefined,
|
|
197
|
+
activityId: string | null,
|
|
198
|
+
actor: string,
|
|
199
|
+
): Promise<boolean> {
|
|
200
|
+
const table = INTERACTION_TABLES[kind];
|
|
201
|
+
const cf = countField ?? COUNT_FIELDS[kind];
|
|
202
|
+
|
|
203
|
+
if (directObjectId) {
|
|
204
|
+
// Delete the edge and recompute the counter from the remaining edge rows
|
|
205
|
+
// atomically. A duplicate Undo (or an Undo of an interaction we never
|
|
206
|
+
// recorded) deletes 0 rows and the recompute is a no-op against the
|
|
207
|
+
// already-correct edge set, so the count cannot drift in either direction.
|
|
208
|
+
await runBatch(db, [
|
|
209
|
+
db
|
|
210
|
+
.delete(table)
|
|
211
|
+
.where(
|
|
212
|
+
and(eq(table.actorApId, actor), eq(table.objectApId, directObjectId)),
|
|
213
|
+
),
|
|
214
|
+
db
|
|
215
|
+
.update(objects)
|
|
216
|
+
.set({
|
|
217
|
+
[cf]: sql`(SELECT COUNT(*) FROM ${table} WHERE ${table.objectApId} = ${directObjectId})`,
|
|
218
|
+
})
|
|
219
|
+
.where(eq(objects.apId, directObjectId)),
|
|
220
|
+
]);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!activityId) return false;
|
|
225
|
+
|
|
226
|
+
// Resolve the edge by its activity ID, then commit the delete + recompute in
|
|
227
|
+
// one batch keyed on the resolved objectApId.
|
|
228
|
+
const record = await db
|
|
229
|
+
.select({
|
|
230
|
+
actorApId: table.actorApId,
|
|
231
|
+
objectApId: table.objectApId,
|
|
232
|
+
})
|
|
233
|
+
.from(table)
|
|
234
|
+
.where(eq(table.activityApId, activityId))
|
|
235
|
+
.get();
|
|
236
|
+
if (record) {
|
|
237
|
+
// Bind the undo to the VERIFIED signer. The activity id is public, so a
|
|
238
|
+
// resolved edge whose owner != the signing actor is a cross-actor forgery
|
|
239
|
+
// (a remote attacker undoing someone else's like/announce by id). The
|
|
240
|
+
// directObjectId branch above already keys its delete on `actor`; mirror it.
|
|
241
|
+
if (record.actorApId !== actor) return false;
|
|
242
|
+
await runBatch(db, [
|
|
243
|
+
db
|
|
244
|
+
.delete(table)
|
|
245
|
+
.where(
|
|
246
|
+
and(
|
|
247
|
+
eq(table.actorApId, record.actorApId),
|
|
248
|
+
eq(table.objectApId, record.objectApId),
|
|
249
|
+
),
|
|
250
|
+
),
|
|
251
|
+
db
|
|
252
|
+
.update(objects)
|
|
253
|
+
.set({
|
|
254
|
+
[cf]: sql`(SELECT COUNT(*) FROM ${table} WHERE ${table.objectApId} = ${record.objectApId})`,
|
|
255
|
+
})
|
|
256
|
+
.where(eq(objects.apId, record.objectApId)),
|
|
257
|
+
]);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Inbox Handlers - Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all ActivityPub user inbox handlers from domain-specific modules:
|
|
5
|
+
* - inbox-follow-handlers: Follow/Accept/Reject/Undo
|
|
6
|
+
* - inbox-content-handlers: Create/Delete/Update/Move
|
|
7
|
+
* - inbox-interaction-handlers: Like/Announce/Add/Remove/Block/Flag
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Follow/Accept/Reject/Undo
|
|
11
|
+
export {
|
|
12
|
+
handleAccept,
|
|
13
|
+
handleFollow,
|
|
14
|
+
handleReject,
|
|
15
|
+
handleUndo,
|
|
16
|
+
} from "./inbox-follow-handlers.ts";
|
|
17
|
+
|
|
18
|
+
// Create/Delete/Update/Move + CreateStory
|
|
19
|
+
export {
|
|
20
|
+
handleCreate,
|
|
21
|
+
handleCreateStory,
|
|
22
|
+
handleDelete,
|
|
23
|
+
handleMove,
|
|
24
|
+
handleUpdate,
|
|
25
|
+
} from "./inbox-content-handlers.ts";
|
|
26
|
+
|
|
27
|
+
// Like/Announce/Add/Remove/Block/Flag
|
|
28
|
+
export {
|
|
29
|
+
handleAdd,
|
|
30
|
+
handleAnnounce,
|
|
31
|
+
handleBlock,
|
|
32
|
+
handleFlag,
|
|
33
|
+
handleLike,
|
|
34
|
+
handleRemove,
|
|
35
|
+
} from "./inbox-interaction-handlers.ts";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import type { Env, Variables } from "../../types.ts";
|
|
3
|
+
|
|
4
|
+
export type ActivityContext = Context<{ Bindings: Env; Variables: Variables }>;
|
|
5
|
+
|
|
6
|
+
export type ActivityObject = {
|
|
7
|
+
id?: string;
|
|
8
|
+
type?: string | string[];
|
|
9
|
+
object?: string;
|
|
10
|
+
inReplyTo?: string;
|
|
11
|
+
to?: string[];
|
|
12
|
+
cc?: string[];
|
|
13
|
+
conversation?: string;
|
|
14
|
+
content?: string;
|
|
15
|
+
summary?: string | null;
|
|
16
|
+
attachment?: unknown;
|
|
17
|
+
tag?: unknown;
|
|
18
|
+
overlays?: unknown;
|
|
19
|
+
endTime?: string;
|
|
20
|
+
displayDuration?: string;
|
|
21
|
+
published?: string;
|
|
22
|
+
room?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type Activity = {
|
|
26
|
+
id?: string;
|
|
27
|
+
type?: string;
|
|
28
|
+
actor?: string;
|
|
29
|
+
object?: string | ActivityObject;
|
|
30
|
+
target?: string | ActivityObject;
|
|
31
|
+
room?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type RemoteActor = {
|
|
35
|
+
id: string;
|
|
36
|
+
type?: string;
|
|
37
|
+
preferredUsername?: string;
|
|
38
|
+
name?: string;
|
|
39
|
+
summary?: string;
|
|
40
|
+
icon?: { url?: string };
|
|
41
|
+
inbox?: string;
|
|
42
|
+
outbox?: string;
|
|
43
|
+
publicKey?: { id?: string; publicKeyPem?: string };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type StoryOverlay = {
|
|
47
|
+
type?: string;
|
|
48
|
+
position?: {
|
|
49
|
+
x?: number;
|
|
50
|
+
y?: number;
|
|
51
|
+
width?: number;
|
|
52
|
+
height?: number;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// AS2 `type` may be a string or an array; this matches a name against either
|
|
57
|
+
// shape so `=== "Note"` comparisons keep working once a remote sends an array.
|
|
58
|
+
export function typeIncludes(
|
|
59
|
+
type: string | string[] | undefined,
|
|
60
|
+
name: string,
|
|
61
|
+
): boolean {
|
|
62
|
+
return Array.isArray(type) ? type.includes(name) : type === name;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getActivityObject(activity: Activity): ActivityObject | null {
|
|
66
|
+
if (!activity.object || typeof activity.object === "string") return null;
|
|
67
|
+
return activity.object;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getActivityObjectId(activity: Activity): string | null {
|
|
71
|
+
if (!activity.object) return null;
|
|
72
|
+
if (typeof activity.object === "string") return activity.object;
|
|
73
|
+
return activity.object.id || null;
|
|
74
|
+
}
|