@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,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;
|