@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,178 @@
1
+ /**
2
+ * Takos Tools - Response & input helpers
3
+ *
4
+ * Shared helpers extracted from takos-tools.ts for response formatting,
5
+ * input validation, and common data operations.
6
+ */
7
+
8
+ import { and, eq, inArray } from "drizzle-orm";
9
+ import type { Database } from "../../db/index.ts";
10
+ import { actors, bookmarks, follows, likes } from "../../db/index.ts";
11
+ import {
12
+ formatUsername,
13
+ parseLimit,
14
+ safeJsonParse,
15
+ } from "../federation-helpers.ts";
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Types
19
+ // ---------------------------------------------------------------------------
20
+
21
+ export interface ToolResponse {
22
+ success: boolean;
23
+ data?: unknown;
24
+ error?: string;
25
+ }
26
+
27
+ export interface ActorSummary {
28
+ apId: string;
29
+ preferredUsername: string;
30
+ name: string | null;
31
+ iconUrl: string | null;
32
+ }
33
+
34
+ export type Input = Record<string, unknown>;
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Input helpers
38
+ // ---------------------------------------------------------------------------
39
+
40
+ export function toolLimit(
41
+ value: unknown,
42
+ fallback: number,
43
+ max: number,
44
+ ): number {
45
+ const normalized = value == null ? undefined : String(value);
46
+ return parseLimit(normalized, fallback, max);
47
+ }
48
+
49
+ export function requireString(input: Input, key: string): string {
50
+ return String(input[key] || "").trim();
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Response helpers
55
+ // ---------------------------------------------------------------------------
56
+
57
+ export function errAuth(): ToolResponse {
58
+ return { success: false, error: "Authentication required" };
59
+ }
60
+
61
+ export function errRequired(field: string): ToolResponse {
62
+ return { success: false, error: `${field} is required` };
63
+ }
64
+
65
+ export function errNotFound(entity: string): ToolResponse {
66
+ return { success: false, error: `${entity} not found` };
67
+ }
68
+
69
+ export function ok(data: unknown): ToolResponse {
70
+ return { success: true, data };
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // Formatting
75
+ // ---------------------------------------------------------------------------
76
+
77
+ export function formatActorSummary(a: ActorSummary): Record<string, unknown> {
78
+ return {
79
+ ap_id: a.apId,
80
+ username: formatUsername(a.apId),
81
+ preferred_username: a.preferredUsername,
82
+ name: a.name,
83
+ icon_url: a.iconUrl,
84
+ };
85
+ }
86
+
87
+ export const ACTOR_SUMMARY_COLUMNS = {
88
+ apId: actors.apId,
89
+ preferredUsername: actors.preferredUsername,
90
+ name: actors.name,
91
+ iconUrl: actors.iconUrl,
92
+ } as const;
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Data helpers
96
+ // ---------------------------------------------------------------------------
97
+
98
+ /**
99
+ * Determine the conversation partner from a DM object.
100
+ * Returns null when the current actor is not a verified participant (defense in depth).
101
+ */
102
+ export function resolveDmPartner(
103
+ dm: { attributedTo: string; toJson: string | null },
104
+ actorApId: string,
105
+ ): string | null {
106
+ const toRecipients = safeJsonParse<string[]>(dm.toJson, []);
107
+
108
+ if (dm.attributedTo === actorApId) {
109
+ return toRecipients[0] || null;
110
+ }
111
+
112
+ // Defense in depth: verify we're actually a recipient.
113
+ if (!toRecipients.includes(actorApId)) return null;
114
+ return dm.attributedTo;
115
+ }
116
+
117
+ /**
118
+ * Toggle a many-to-one relation (like, bookmark) on a post.
119
+ */
120
+ export async function togglePostRelation(
121
+ db: Database,
122
+ table: typeof likes | typeof bookmarks,
123
+ actorApIdVal: string,
124
+ objectApIdVal: string,
125
+ active: boolean,
126
+ ): Promise<void> {
127
+ if (active) {
128
+ // Upsert: insert if not exists, ignore conflict
129
+ await db
130
+ .insert(table)
131
+ .values({ actorApId: actorApIdVal, objectApId: objectApIdVal })
132
+ .onConflictDoNothing();
133
+ } else {
134
+ await db
135
+ .delete(table)
136
+ .where(
137
+ and(
138
+ eq(table.actorApId, actorApIdVal),
139
+ eq(table.objectApId, objectApIdVal),
140
+ ),
141
+ );
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Fetch a follow-direction list for a given actor (followers or following).
147
+ */
148
+ export async function fetchFollowList(
149
+ db: Database,
150
+ targetApId: string,
151
+ direction: "followers" | "following",
152
+ limit: number,
153
+ ): Promise<ActorSummary[]> {
154
+ const isFollowers = direction === "followers";
155
+ const followRows = await db
156
+ .select()
157
+ .from(follows)
158
+ .where(
159
+ and(
160
+ eq(
161
+ isFollowers ? follows.followingApId : follows.followerApId,
162
+ targetApId,
163
+ ),
164
+ eq(follows.status, "accepted"),
165
+ ),
166
+ )
167
+ .limit(limit);
168
+
169
+ const relatedIds = followRows.map((f) =>
170
+ isFollowers ? f.followerApId : f.followingApId,
171
+ );
172
+ if (relatedIds.length === 0) return [];
173
+
174
+ return db
175
+ .select(ACTOR_SUMMARY_COLUMNS)
176
+ .from(actors)
177
+ .where(inArray(actors.apId, relatedIds));
178
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Takos Tools Endpoint
3
+ *
4
+ * Provides tool endpoints for AI agent integration.
5
+ * POST /.takos/tools/:name - Execute a tool
6
+ *
7
+ * Each tool category is implemented in a dedicated module under ./takos-tools/.
8
+ */
9
+
10
+ import { Hono } from "hono";
11
+ import type { Env, Variables } from "../types.ts";
12
+ import type { ToolResponse } from "./takos-tools-response.ts";
13
+ import { logger } from "../lib/logger.ts";
14
+
15
+ // Handler modules
16
+ import {
17
+ handleGetTrending,
18
+ handleGetUserProfile,
19
+ handleSearchPosts,
20
+ handleSearchUsers,
21
+ } from "./takos-tools/search.ts";
22
+ import {
23
+ handleBookmarkPost,
24
+ handleCreatePost,
25
+ handleDeletePost,
26
+ handleLikePost,
27
+ } from "./takos-tools/posts.ts";
28
+ import {
29
+ handleFollowUser,
30
+ handleGetFollowList,
31
+ handleUnfollowUser,
32
+ } from "./takos-tools/follows.ts";
33
+ import {
34
+ handleGetDmMessages,
35
+ handleGetDmThreads,
36
+ handleSendDm,
37
+ } from "./takos-tools/dm.ts";
38
+ import {
39
+ handleGetNotifications,
40
+ handleGetTimeline,
41
+ } from "./takos-tools/timeline.ts";
42
+
43
+ type HonoEnv = { Bindings: Env; Variables: Variables };
44
+
45
+ const takosTools = new Hono<HonoEnv>();
46
+ const log = logger.child({ component: "takos.tools" });
47
+
48
+ // Feature flag gate (fail-close).
49
+ takosTools.use("*", async (c, next) => {
50
+ if (c.env.ENABLE_TAKOS_TOOLS !== "true") {
51
+ return c.notFound();
52
+ }
53
+ await next();
54
+ });
55
+
56
+ interface ToolRequest {
57
+ input: Record<string, unknown>;
58
+ context?: {
59
+ user_id?: string;
60
+ session_id?: string;
61
+ };
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Route
66
+ // ---------------------------------------------------------------------------
67
+
68
+ takosTools.post("/:name", async (c) => {
69
+ const toolName = c.req.param("name");
70
+ const actor = c.get("actor");
71
+
72
+ let body: ToolRequest;
73
+ try {
74
+ body = await c.req.json();
75
+ } catch {
76
+ return c.json(
77
+ { success: false, error: "Invalid JSON body" } as ToolResponse,
78
+ 400,
79
+ );
80
+ }
81
+
82
+ const input = body.input || {};
83
+
84
+ try {
85
+ switch (toolName) {
86
+ // ------ Search tools ------
87
+ case "yurucommu_search_users":
88
+ return await handleSearchUsers(c, input, actor);
89
+ case "yurucommu_search_posts":
90
+ return await handleSearchPosts(c, input, actor);
91
+ case "yurucommu_get_trending":
92
+ return await handleGetTrending(c, input, actor);
93
+ case "yurucommu_get_user_profile":
94
+ return await handleGetUserProfile(c, input, actor);
95
+
96
+ // ------ Post tools ------
97
+ case "yurucommu_create_post":
98
+ return await handleCreatePost(c, input, actor);
99
+ case "yurucommu_delete_post":
100
+ return await handleDeletePost(c, input, actor);
101
+ case "yurucommu_like_post":
102
+ return await handleLikePost(c, input, actor);
103
+ case "yurucommu_bookmark_post":
104
+ return await handleBookmarkPost(c, input, actor);
105
+
106
+ // ------ Follow tools ------
107
+ case "yurucommu_follow_user":
108
+ return await handleFollowUser(c, input, actor);
109
+ case "yurucommu_unfollow_user":
110
+ return await handleUnfollowUser(c, input, actor);
111
+ case "yurucommu_get_followers":
112
+ return await handleGetFollowList(c, input, actor, "followers");
113
+ case "yurucommu_get_following":
114
+ return await handleGetFollowList(c, input, actor, "following");
115
+
116
+ // ------ DM tools ------
117
+ case "yurucommu_send_dm":
118
+ return await handleSendDm(c, input, actor);
119
+ case "yurucommu_get_dm_threads":
120
+ return await handleGetDmThreads(c, input, actor);
121
+ case "yurucommu_get_dm_messages":
122
+ return await handleGetDmMessages(c, input, actor);
123
+
124
+ // ------ Timeline tools ------
125
+ case "yurucommu_get_timeline":
126
+ return await handleGetTimeline(c, input, actor);
127
+ case "yurucommu_get_notifications":
128
+ return await handleGetNotifications(c, input, actor);
129
+
130
+ default:
131
+ return c.json(
132
+ { success: false, error: `Unknown tool: ${toolName}` },
133
+ 404,
134
+ );
135
+ }
136
+ } catch (error) {
137
+ log.error("Takos tool failed", {
138
+ event: "takos.tools.failed",
139
+ toolName,
140
+ error,
141
+ });
142
+ // Return a GENERIC message — never echo the raw Error.message to the client.
143
+ // A D1/Drizzle failure can carry SQL fragments / column names / constraint
144
+ // text; the detail is logged server-side above. Mirrors media.ts and the
145
+ // global onError handler (which both return fixed strings).
146
+ return c.json(
147
+ { success: false, error: "Internal error" } as ToolResponse,
148
+ 500,
149
+ );
150
+ }
151
+ });
152
+
153
+ export default takosTools;