@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.
Files changed (185) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +82 -0
  3. package/migrations/0001_init.sql +495 -0
  4. package/migrations/0002_social_remote_actor_edges.sql +92 -0
  5. package/migrations/0003_activity_remote_object_edges.sql +68 -0
  6. package/migrations/0004_blocklist.sql +26 -0
  7. package/migrations/0005_story_community_scope.sql +13 -0
  8. package/migrations/0006_dm_community_read_status.sql +19 -0
  9. package/migrations/0007_moderation_reports.sql +22 -0
  10. package/migrations/0008_actor_fields_aka.sql +18 -0
  11. package/migrations/0009_object_tags.sql +13 -0
  12. package/migrations/0010_object_recipients_drop_actor_fk.sql +34 -0
  13. package/migrations/0011_drop_remote_actor_fks.sql +205 -0
  14. package/migrations/0012_objects_content_fts.sql +39 -0
  15. package/migrations/0013_efficiency_indexes.sql +13 -0
  16. package/migrations/0014_inbox_actor_created_idx.sql +15 -0
  17. package/migrations/0015_community_bans.sql +16 -0
  18. package/migrations/0016_namespace_takos_oidc_subject.sql +19 -0
  19. package/migrations/0017_mobile_push_registrations.sql +22 -0
  20. package/migrations/README.md +122 -0
  21. package/package.json +75 -0
  22. package/packages/api/LICENSE +16 -0
  23. package/packages/api/package.json +30 -0
  24. package/packages/api/src/index.ts +4 -0
  25. package/packages/api/src/lib/api/account.ts +20 -0
  26. package/packages/api/src/lib/api/actors.ts +149 -0
  27. package/packages/api/src/lib/api/auth.ts +46 -0
  28. package/packages/api/src/lib/api/communities.ts +329 -0
  29. package/packages/api/src/lib/api/dm.test.ts +67 -0
  30. package/packages/api/src/lib/api/dm.ts +236 -0
  31. package/packages/api/src/lib/api/fetch.ts +111 -0
  32. package/packages/api/src/lib/api/follow.ts +30 -0
  33. package/packages/api/src/lib/api/media.ts +100 -0
  34. package/packages/api/src/lib/api/moderation.ts +98 -0
  35. package/packages/api/src/lib/api/normalize.ts +71 -0
  36. package/packages/api/src/lib/api/notifications.test.ts +63 -0
  37. package/packages/api/src/lib/api/notifications.ts +61 -0
  38. package/packages/api/src/lib/api/posts.test.ts +110 -0
  39. package/packages/api/src/lib/api/posts.ts +181 -0
  40. package/packages/api/src/lib/api/recommendations.ts +22 -0
  41. package/packages/api/src/lib/api/search.ts +88 -0
  42. package/packages/api/src/lib/api/stories.ts +80 -0
  43. package/packages/api/src/lib/api.ts +15 -0
  44. package/packages/api/src/lib/fetch-with-timeout.ts +42 -0
  45. package/packages/api/src/lib/transport.ts +40 -0
  46. package/packages/api/src/social-server.ts +47 -0
  47. package/packages/api/src/types/index.ts +185 -0
  48. package/scripts/apply-takosumi-migrations.ts +621 -0
  49. package/src/backend/federation-helpers.ts +36 -0
  50. package/src/backend/index.ts +872 -0
  51. package/src/backend/lib/account-migration.ts +106 -0
  52. package/src/backend/lib/activitypub-actor-cache.ts +238 -0
  53. package/src/backend/lib/activitypub-helpers.ts +131 -0
  54. package/src/backend/lib/activitypub-validators.ts +323 -0
  55. package/src/backend/lib/ap-context.ts +16 -0
  56. package/src/backend/lib/ap-ids.ts +101 -0
  57. package/src/backend/lib/ap-response.ts +30 -0
  58. package/src/backend/lib/ap-signing.ts +87 -0
  59. package/src/backend/lib/ap-verify.ts +670 -0
  60. package/src/backend/lib/auth-lockout.ts +230 -0
  61. package/src/backend/lib/backend-paths.ts +34 -0
  62. package/src/backend/lib/base64.ts +30 -0
  63. package/src/backend/lib/blocklist-purge.ts +109 -0
  64. package/src/backend/lib/blocklist.ts +279 -0
  65. package/src/backend/lib/chunk.ts +33 -0
  66. package/src/backend/lib/client-ip.ts +169 -0
  67. package/src/backend/lib/community-visibility.ts +230 -0
  68. package/src/backend/lib/crypto.ts +424 -0
  69. package/src/backend/lib/delivery/circuit.ts +265 -0
  70. package/src/backend/lib/delivery/metrics.ts +30 -0
  71. package/src/backend/lib/delivery/planner.ts +190 -0
  72. package/src/backend/lib/delivery/queue-batching.ts +626 -0
  73. package/src/backend/lib/delivery/queue-delivery.ts +641 -0
  74. package/src/backend/lib/delivery/queue.ts +576 -0
  75. package/src/backend/lib/delivery/transformers.ts +56 -0
  76. package/src/backend/lib/delivery/types.ts +139 -0
  77. package/src/backend/lib/errors.ts +114 -0
  78. package/src/backend/lib/federation-fetch.ts +296 -0
  79. package/src/backend/lib/feed-cursor.ts +57 -0
  80. package/src/backend/lib/feed-exclude.ts +48 -0
  81. package/src/backend/lib/hex.ts +8 -0
  82. package/src/backend/lib/log-mask.ts +213 -0
  83. package/src/backend/lib/logger.ts +285 -0
  84. package/src/backend/lib/mobile-contract.ts +137 -0
  85. package/src/backend/lib/oauth-providers.ts +324 -0
  86. package/src/backend/lib/oauth-utils.ts +148 -0
  87. package/src/backend/lib/oidc-id-token.ts +151 -0
  88. package/src/backend/lib/parse-helpers.ts +31 -0
  89. package/src/backend/lib/post-visibility.ts +190 -0
  90. package/src/backend/lib/session-actor.ts +61 -0
  91. package/src/backend/lib/ssrf.ts +428 -0
  92. package/src/backend/lib/strip-image-metadata.ts +191 -0
  93. package/src/backend/middleware/bearer-auth.ts +70 -0
  94. package/src/backend/middleware/body-limit.ts +212 -0
  95. package/src/backend/middleware/cache.ts +429 -0
  96. package/src/backend/middleware/csrf.ts +130 -0
  97. package/src/backend/middleware/error-handler.ts +77 -0
  98. package/src/backend/middleware/rate-limit.ts +308 -0
  99. package/src/backend/public.ts +21 -0
  100. package/src/backend/routes/account-teardown.ts +430 -0
  101. package/src/backend/routes/activitypub/handlers/actor-inbox-handlers.ts +354 -0
  102. package/src/backend/routes/activitypub/handlers/inbound-timestamp.ts +29 -0
  103. package/src/backend/routes/activitypub/handlers/inbox-content-handlers.ts +1634 -0
  104. package/src/backend/routes/activitypub/handlers/inbox-follow-handlers.ts +547 -0
  105. package/src/backend/routes/activitypub/handlers/inbox-interaction-handlers.ts +497 -0
  106. package/src/backend/routes/activitypub/handlers/inbox-shared-helpers.ts +262 -0
  107. package/src/backend/routes/activitypub/handlers/user-inbox-handlers.ts +35 -0
  108. package/src/backend/routes/activitypub/inbox-types.ts +74 -0
  109. package/src/backend/routes/activitypub/inbox.ts +1191 -0
  110. package/src/backend/routes/activitypub/outbox.ts +0 -0
  111. package/src/backend/routes/activitypub/query-helpers.ts +227 -0
  112. package/src/backend/routes/activitypub.ts +616 -0
  113. package/src/backend/routes/actors-helpers.ts +487 -0
  114. package/src/backend/routes/actors.ts +1311 -0
  115. package/src/backend/routes/apps.ts +313 -0
  116. package/src/backend/routes/auth-helpers.ts +566 -0
  117. package/src/backend/routes/auth.ts +615 -0
  118. package/src/backend/routes/communities/membership-invites.ts +208 -0
  119. package/src/backend/routes/communities/membership-join.ts +335 -0
  120. package/src/backend/routes/communities/membership-members.ts +539 -0
  121. package/src/backend/routes/communities/membership-requests.ts +296 -0
  122. package/src/backend/routes/communities/membership-shared.ts +364 -0
  123. package/src/backend/routes/communities/messages.ts +479 -0
  124. package/src/backend/routes/communities/routes.ts +624 -0
  125. package/src/backend/routes/communities.ts +21 -0
  126. package/src/backend/routes/dm/contacts.ts +525 -0
  127. package/src/backend/routes/dm/conversations-helpers.ts +197 -0
  128. package/src/backend/routes/dm/conversations.ts +25 -0
  129. package/src/backend/routes/dm/messages.ts +658 -0
  130. package/src/backend/routes/dm/query-helpers.ts +85 -0
  131. package/src/backend/routes/dm/read-archive.ts +228 -0
  132. package/src/backend/routes/dm/requests.ts +222 -0
  133. package/src/backend/routes/dm/typing.ts +81 -0
  134. package/src/backend/routes/dm.ts +15 -0
  135. package/src/backend/routes/follow-helpers.ts +370 -0
  136. package/src/backend/routes/follow.ts +588 -0
  137. package/src/backend/routes/media.ts +692 -0
  138. package/src/backend/routes/mobile.ts +159 -0
  139. package/src/backend/routes/moderation.ts +373 -0
  140. package/src/backend/routes/notifications.ts +757 -0
  141. package/src/backend/routes/posts/delete-cascade.ts +330 -0
  142. package/src/backend/routes/posts/interactions.ts +795 -0
  143. package/src/backend/routes/posts/post-helpers.ts +847 -0
  144. package/src/backend/routes/posts/queries.ts +537 -0
  145. package/src/backend/routes/posts/routes.ts +865 -0
  146. package/src/backend/routes/posts/transformers.ts +161 -0
  147. package/src/backend/routes/posts.ts +17 -0
  148. package/src/backend/routes/recommendations.ts +88 -0
  149. package/src/backend/routes/search.ts +730 -0
  150. package/src/backend/routes/stories/interactions.ts +576 -0
  151. package/src/backend/routes/stories/query-helpers.ts +482 -0
  152. package/src/backend/routes/stories/routes.ts +906 -0
  153. package/src/backend/routes/stories.ts +13 -0
  154. package/src/backend/routes/takos-tools/dm.ts +249 -0
  155. package/src/backend/routes/takos-tools/follows.ts +225 -0
  156. package/src/backend/routes/takos-tools/posts.ts +292 -0
  157. package/src/backend/routes/takos-tools/search.ts +228 -0
  158. package/src/backend/routes/takos-tools/timeline.ts +132 -0
  159. package/src/backend/routes/takos-tools/types.ts +10 -0
  160. package/src/backend/routes/takos-tools-response.ts +178 -0
  161. package/src/backend/routes/takos-tools.ts +153 -0
  162. package/src/backend/routes/timeline.ts +755 -0
  163. package/src/backend/runtime/bun.ts +620 -0
  164. package/src/backend/runtime/cloudflare.ts +202 -0
  165. package/src/backend/runtime/compat-bun/types.ts +44 -0
  166. package/src/backend/runtime/memory-kv.ts +104 -0
  167. package/src/backend/runtime/shared.ts +142 -0
  168. package/src/backend/runtime/types.ts +205 -0
  169. package/src/backend/server.ts +636 -0
  170. package/src/backend/types.ts +143 -0
  171. package/src/db/index.ts +97 -0
  172. package/src/db/schema/actors.ts +129 -0
  173. package/src/db/schema/communities.ts +133 -0
  174. package/src/db/schema/date-utils.ts +17 -0
  175. package/src/db/schema/index.ts +17 -0
  176. package/src/db/schema/messaging.ts +241 -0
  177. package/src/db/schema/mobile.ts +37 -0
  178. package/src/db/schema/posts.ts +150 -0
  179. package/src/db/schema/relations.ts +266 -0
  180. package/src/db/schema/reports.ts +33 -0
  181. package/src/db/schema/social.ts +106 -0
  182. package/src/db/schema/stories.ts +70 -0
  183. package/src/db/schema.ts +15 -0
  184. package/src/plugin/public.ts +7 -0
  185. package/src/runtime/site-worker.ts +10 -0
@@ -0,0 +1,197 @@
1
+ // Shared helpers for DM conversations
2
+
3
+ import type { Context } from "hono";
4
+ import { and, eq, inArray, isNotNull, or } from "drizzle-orm";
5
+ import type { Database } from "../../../db/index.ts";
6
+ import { objectRecipients, objects } from "../../../db/index.ts";
7
+ import type { Env, Variables } from "../../types.ts";
8
+ import { safeJsonParse } from "../../federation-helpers.ts";
9
+ import { chunkForInClause } from "../../lib/chunk.ts";
10
+
11
+ /**
12
+ * Indexed lookup of the object AP-IDs a given actor was addressed on as a DM
13
+ * recipient.
14
+ *
15
+ * DM writes (dm/messages.ts, takos-tools/dm.ts, inbox-content-handlers.ts)
16
+ * record every recipient in the `object_recipients` link table with
17
+ * `type = 'to'` and `recipient_ap_id = <recipient>`, backed by the
18
+ * `object_recipients_recipient_created_idx` index. Selecting object AP-IDs from
19
+ * that table on an equality of `recipient_ap_id` is index-served, whereas the
20
+ * equivalent `to_json LIKE '%"<apId>"%'` substring scan over the `objects`
21
+ * table cannot use an index and degrades to a full-table scan.
22
+ *
23
+ * Returned as a Drizzle subquery so callers can use it inside `inArray(
24
+ * objects.apId, ...)` — the same recipient-membership semantics (the `to`
25
+ * audience contains the actor) without an unindexable `to_json` substring scan.
26
+ */
27
+ export function recipientObjectIds(db: Database, recipientApId: string) {
28
+ return db
29
+ .select({ objectApId: objectRecipients.objectApId })
30
+ .from(objectRecipients)
31
+ .where(
32
+ and(
33
+ eq(objectRecipients.recipientApId, recipientApId),
34
+ eq(objectRecipients.type, "to"),
35
+ ),
36
+ );
37
+ }
38
+
39
+ export type HonoEnv = { Bindings: Env; Variables: Variables };
40
+
41
+ export {
42
+ type ActorInfo,
43
+ formatActorSummary as formatActorProfile,
44
+ loadActorInfoMap as buildActorInfoMap,
45
+ } from "../actors-helpers.ts";
46
+
47
+ /** Extract the other participant's AP ID from a DM object. */
48
+ export function getOtherParticipant(
49
+ obj: { attributedTo: string; toJson: string },
50
+ actorApId: string,
51
+ ): string {
52
+ if (obj.attributedTo === actorApId) {
53
+ return safeJsonParse<string[]>(obj.toJson, [])[0] || "";
54
+ }
55
+ return obj.attributedTo;
56
+ }
57
+
58
+ /**
59
+ * Drizzle where clause for DM objects involving a given actor (author OR
60
+ * recipient).
61
+ *
62
+ * Recipient membership is resolved through the indexed `object_recipients`
63
+ * link via {@link recipientObjectIds} (`inArray(objects.apId, ...)`) instead of
64
+ * an unindexable `to_json LIKE '%"<apId>"%'` substring scan. The semantics are
65
+ * identical — a DM where the actor authored it (`attributed_to`) or was a `to`
66
+ * recipient — but the query is index-served. `db` is required to build the
67
+ * recipient subquery.
68
+ */
69
+ export function dmWhereForActor(db: Database, actorApId: string) {
70
+ return and(
71
+ eq(objects.visibility, "direct"),
72
+ eq(objects.type, "Note"),
73
+ isNotNull(objects.conversation),
74
+ or(
75
+ eq(objects.attributedTo, actorApId),
76
+ inArray(objects.apId, recipientObjectIds(db, actorApId)),
77
+ ),
78
+ );
79
+ }
80
+
81
+ /** Sort comparator: descending by time string, with fallback. */
82
+ export function byTimeDesc(a: string | null, b: string | null): number {
83
+ return (b || "").localeCompare(a || "");
84
+ }
85
+
86
+ /** Decode and validate the :encodedApId route param. Returns null on failure after sending 400. */
87
+ export function parseOtherApId(c: Context<HonoEnv>): string | null {
88
+ const raw = c.req.param("encodedApId");
89
+ if (!raw) {
90
+ c.status(400);
91
+ return null;
92
+ }
93
+ const apId = decodeURIComponent(raw);
94
+ if (!apId) {
95
+ c.status(400);
96
+ return null;
97
+ }
98
+ return apId;
99
+ }
100
+
101
+ type DmObject = {
102
+ conversation: string | null;
103
+ attributedTo: string;
104
+ toJson: string;
105
+ published: string;
106
+ content?: string | null;
107
+ };
108
+
109
+ /**
110
+ * Group DM objects by conversation, keeping only the first (most recent) per conversation.
111
+ * `filterFn` controls which conversations to include.
112
+ */
113
+ export function groupConversations(
114
+ dmObjects: DmObject[],
115
+ actorApId: string,
116
+ filterFn: (conversationId: string) => boolean,
117
+ ): Map<
118
+ string,
119
+ {
120
+ conversation: string;
121
+ otherApId: string;
122
+ lastMessageAt: string;
123
+ lastContent: string | null;
124
+ lastSender: string;
125
+ }
126
+ > {
127
+ const map = new Map<
128
+ string,
129
+ {
130
+ conversation: string;
131
+ otherApId: string;
132
+ lastMessageAt: string;
133
+ lastContent: string | null;
134
+ lastSender: string;
135
+ }
136
+ >();
137
+
138
+ for (const obj of dmObjects) {
139
+ if (
140
+ !obj.conversation ||
141
+ !filterFn(obj.conversation) ||
142
+ map.has(obj.conversation)
143
+ )
144
+ continue;
145
+
146
+ const otherApId = getOtherParticipant(obj, actorApId);
147
+ if (!otherApId) continue;
148
+
149
+ map.set(obj.conversation, {
150
+ conversation: obj.conversation,
151
+ otherApId,
152
+ lastMessageAt: obj.published,
153
+ lastContent: obj.content ?? null,
154
+ lastSender: obj.attributedTo,
155
+ });
156
+ }
157
+
158
+ return map;
159
+ }
160
+
161
+ /** Find the set of conversation IDs where the actor has sent at least one message. */
162
+ export async function findRepliedConversations(
163
+ db: Database,
164
+ conversationIds: string[],
165
+ actorApId: string,
166
+ ): Promise<Set<string | null>> {
167
+ if (conversationIds.length === 0) return new Set();
168
+
169
+ // Chunk the IN(...) lookup: the caller passes every conversation in the
170
+ // contact list (up to 2000) and D1 caps a query at 100 bound parameters.
171
+ // Chunks are disjoint id slices, so unioning the replied-conversation sets is
172
+ // collision-free.
173
+ const result = new Set<string | null>();
174
+ for (const ids of chunkForInClause(conversationIds)) {
175
+ const replies = await db
176
+ .selectDistinct({
177
+ conversation: objects.conversation,
178
+ })
179
+ .from(objects)
180
+ .where(
181
+ and(
182
+ inArray(objects.conversation, ids),
183
+ eq(objects.attributedTo, actorApId),
184
+ ),
185
+ );
186
+ for (const r of replies) result.add(r.conversation);
187
+ }
188
+ return result;
189
+ }
190
+
191
+ /** Collect unique values from a map's entries via accessor function. */
192
+ export function uniqueValues<V>(
193
+ map: Map<string, V>,
194
+ accessor: (v: V) => string,
195
+ ): string[] {
196
+ return [...new Set(Array.from(map.values()).map(accessor))];
197
+ }
@@ -0,0 +1,25 @@
1
+ // Direct Messages - AP Native
2
+ // DMs are Note objects with visibility='direct' and to=[recipient]
3
+ // Threading via conversation field
4
+ //
5
+ // Router entry: composes the current DM sub-modules.
6
+
7
+ import { Hono } from "hono";
8
+ import type { HonoEnv } from "./conversations-helpers.ts";
9
+
10
+ import contacts from "./contacts.ts";
11
+ import requests from "./requests.ts";
12
+ import typing from "./typing.ts";
13
+ import readArchive from "./read-archive.ts";
14
+
15
+ // -- Routes --
16
+
17
+ const dm = new Hono<HonoEnv>();
18
+
19
+ // Mount sub-routers
20
+ dm.route("/", contacts);
21
+ dm.route("/", requests);
22
+ dm.route("/", typing);
23
+ dm.route("/", readArchive);
24
+
25
+ export default dm;