@takosjp/yurucommu-api 2.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 (44) hide show
  1. package/dist/index.d.ts +5 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +1024 -0
  4. package/dist/lib/api/account.d.ts +4 -0
  5. package/dist/lib/api/account.d.ts.map +1 -0
  6. package/dist/lib/api/actors.d.ts +43 -0
  7. package/dist/lib/api/actors.d.ts.map +1 -0
  8. package/dist/lib/api/auth.d.ts +18 -0
  9. package/dist/lib/api/auth.d.ts.map +1 -0
  10. package/dist/lib/api/communities.d.ts +112 -0
  11. package/dist/lib/api/communities.d.ts.map +1 -0
  12. package/dist/lib/api/dm.d.ts +67 -0
  13. package/dist/lib/api/dm.d.ts.map +1 -0
  14. package/dist/lib/api/fetch.d.ts +31 -0
  15. package/dist/lib/api/fetch.d.ts.map +1 -0
  16. package/dist/lib/api/follow.d.ts +7 -0
  17. package/dist/lib/api/follow.d.ts.map +1 -0
  18. package/dist/lib/api/media.d.ts +24 -0
  19. package/dist/lib/api/media.d.ts.map +1 -0
  20. package/dist/lib/api/moderation.d.ts +35 -0
  21. package/dist/lib/api/moderation.d.ts.map +1 -0
  22. package/dist/lib/api/normalize.d.ts +13 -0
  23. package/dist/lib/api/normalize.d.ts.map +1 -0
  24. package/dist/lib/api/notifications.d.ts +17 -0
  25. package/dist/lib/api/notifications.d.ts.map +1 -0
  26. package/dist/lib/api/posts.d.ts +57 -0
  27. package/dist/lib/api/posts.d.ts.map +1 -0
  28. package/dist/lib/api/recommendations.d.ts +10 -0
  29. package/dist/lib/api/recommendations.d.ts.map +1 -0
  30. package/dist/lib/api/search.d.ts +19 -0
  31. package/dist/lib/api/search.d.ts.map +1 -0
  32. package/dist/lib/api/stories.d.ts +33 -0
  33. package/dist/lib/api/stories.d.ts.map +1 -0
  34. package/dist/lib/api.d.ts +16 -0
  35. package/dist/lib/api.d.ts.map +1 -0
  36. package/dist/lib/fetch-with-timeout.d.ts +7 -0
  37. package/dist/lib/fetch-with-timeout.d.ts.map +1 -0
  38. package/dist/lib/transport.d.ts +11 -0
  39. package/dist/lib/transport.d.ts.map +1 -0
  40. package/dist/social-server.d.ts +34 -0
  41. package/dist/social-server.d.ts.map +1 -0
  42. package/dist/types/index.d.ts +152 -0
  43. package/dist/types/index.d.ts.map +1 -0
  44. package/package.json +30 -0
package/dist/index.js ADDED
@@ -0,0 +1,1024 @@
1
+ // src/lib/fetch-with-timeout.ts
2
+ var DEFAULT_REQUEST_TIMEOUT_MS = 15000;
3
+ var UPLOAD_REQUEST_TIMEOUT_MS = 60000;
4
+ async function fetchWithTimeout(input, init = {}) {
5
+ const { timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, signal, ...rest } = init;
6
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
7
+ return fetch(input, { ...rest, signal });
8
+ }
9
+ const controller = new AbortController;
10
+ const abortFromSignal = () => controller.abort(signal?.reason);
11
+ if (signal?.aborted) {
12
+ controller.abort(signal.reason);
13
+ } else if (signal) {
14
+ signal.addEventListener("abort", abortFromSignal, { once: true });
15
+ }
16
+ const timeoutId = globalThis.setTimeout(() => {
17
+ controller.abort();
18
+ }, timeoutMs);
19
+ try {
20
+ return await fetch(input, {
21
+ ...rest,
22
+ signal: controller.signal
23
+ });
24
+ } finally {
25
+ globalThis.clearTimeout(timeoutId);
26
+ if (signal) {
27
+ signal.removeEventListener("abort", abortFromSignal);
28
+ }
29
+ }
30
+ }
31
+
32
+ // src/lib/transport.ts
33
+ class DefaultSelfHostedTransport {
34
+ credentials = "include";
35
+ resolveUrl(path) {
36
+ return path;
37
+ }
38
+ getAuthHeaders(_path) {
39
+ return {};
40
+ }
41
+ }
42
+ var activeTransportResolver = () => new DefaultSelfHostedTransport;
43
+ function setYurucommuApiTransportResolver(resolver) {
44
+ activeTransportResolver = resolver;
45
+ }
46
+ function setYurucommuApiTransport(transport) {
47
+ activeTransportResolver = () => transport;
48
+ }
49
+ function clearYurucommuApiTransport() {
50
+ activeTransportResolver = () => new DefaultSelfHostedTransport;
51
+ }
52
+ function getYurucommuApiTransport() {
53
+ return activeTransportResolver();
54
+ }
55
+
56
+ // src/lib/api/fetch.ts
57
+ class ApiError extends Error {
58
+ status;
59
+ constructor(status, message) {
60
+ super(message);
61
+ this.status = status;
62
+ this.name = "ApiError";
63
+ }
64
+ }
65
+ async function extractErrorMessage(res, fallback) {
66
+ try {
67
+ const data = await res.json();
68
+ const err = data.error;
69
+ const message = typeof err === "string" ? err : err?.message;
70
+ return message || fallback;
71
+ } catch {
72
+ return res.statusText || fallback;
73
+ }
74
+ }
75
+ async function assertOk(res, fallback) {
76
+ if (!res.ok) {
77
+ const message = await extractErrorMessage(res, fallback);
78
+ throw new ApiError(res.status, message);
79
+ }
80
+ }
81
+ function apiFetch(url, options = {}) {
82
+ const transport = getYurucommuApiTransport();
83
+ const apiUrl = transport.resolveUrl(url);
84
+ const headers = new Headers(options.headers);
85
+ const authHeaders = transport.getAuthHeaders(url);
86
+ for (const [key, value] of Object.entries(authHeaders)) {
87
+ if (!headers.has(key)) {
88
+ headers.set(key, value);
89
+ }
90
+ }
91
+ return fetchWithTimeout(apiUrl, {
92
+ ...options,
93
+ headers,
94
+ credentials: options.credentials ?? transport.credentials
95
+ });
96
+ }
97
+ function createApiMethod(method) {
98
+ return async (url, body, options = {}) => {
99
+ const headers = new Headers(options.headers);
100
+ if (body) {
101
+ headers.set("Content-Type", "application/json");
102
+ }
103
+ return await apiFetch(url, {
104
+ method,
105
+ headers,
106
+ body: body ? JSON.stringify(body) : undefined,
107
+ ...options
108
+ });
109
+ };
110
+ }
111
+ var apiPost = createApiMethod("POST");
112
+ var apiPut = createApiMethod("PUT");
113
+ var apiPatch = createApiMethod("PATCH");
114
+ var apiDelete = createApiMethod("DELETE");
115
+
116
+ // src/lib/api/account.ts
117
+ async function fetchAlsoKnownAs(identifier) {
118
+ const res = await apiFetch(`/api/actors/${encodeURIComponent(identifier)}`);
119
+ if (!res.ok)
120
+ return [];
121
+ const data = await res.json();
122
+ return Array.isArray(data.also_known_as) ? data.also_known_as.filter((a) => typeof a === "string") : [];
123
+ }
124
+ async function setAlsoKnownAs(aliases) {
125
+ const res = await apiPut("/api/actors/me", { also_known_as: aliases });
126
+ await assertOk(res, "Failed to update aliases");
127
+ }
128
+ async function moveAccount(target) {
129
+ const res = await apiPost("/api/actors/me/move", { target });
130
+ await assertOk(res, "Failed to move account");
131
+ }
132
+
133
+ // src/lib/api/auth.ts
134
+ async function login(password) {
135
+ const res = await apiPost("/api/auth/login", { password });
136
+ return await res.json();
137
+ }
138
+ async function logout() {
139
+ await apiPost("/api/auth/logout");
140
+ }
141
+ async function fetchAccounts() {
142
+ const res = await apiFetch("/api/auth/accounts");
143
+ await assertOk(res, "Failed to fetch accounts");
144
+ return await res.json();
145
+ }
146
+ async function switchAccount(apId) {
147
+ const res = await apiPost("/api/auth/switch", { ap_id: apId });
148
+ await assertOk(res, "Failed to switch account");
149
+ }
150
+ async function createAccount(username, name) {
151
+ const res = await apiPost("/api/auth/accounts", { username, name });
152
+ await assertOk(res, "Failed to create account");
153
+ const data = await res.json();
154
+ return data.account;
155
+ }
156
+
157
+ // src/lib/api/normalize.ts
158
+ function formatUsernameFromApId(apId, preferred) {
159
+ try {
160
+ const url = new URL(apId);
161
+ const match = apId.match(/\/(users|groups)\/([^/]+)$/);
162
+ if (match)
163
+ return `${match[2]}@${url.host}`;
164
+ if (preferred)
165
+ return `${preferred}@${url.host}`;
166
+ } catch {}
167
+ return null;
168
+ }
169
+ function normalizeActor(actor) {
170
+ if (!actor || !actor.ap_id)
171
+ return actor;
172
+ const rawUsername = actor.username?.trim();
173
+ const formatted = rawUsername || formatUsernameFromApId(actor.ap_id, actor.preferred_username) || actor.preferred_username || actor.username || actor.ap_id;
174
+ const preferred = actor.preferred_username?.trim() || (formatted.includes("@") ? formatted.split("@")[0] : formatted);
175
+ return {
176
+ ...actor,
177
+ username: formatted,
178
+ preferred_username: preferred
179
+ };
180
+ }
181
+ var normalizePost = (post) => ({
182
+ ...post,
183
+ author: normalizeActor(post.author)
184
+ });
185
+ var normalizeStory = (story) => ({
186
+ ...story,
187
+ author: normalizeActor(story.author)
188
+ });
189
+ var normalizeActorStories = (stories) => ({
190
+ ...stories,
191
+ actor: normalizeActor(stories.actor),
192
+ stories: (stories.stories || []).map(normalizeStory)
193
+ });
194
+ var normalizeNotification = (notification) => ({
195
+ ...notification,
196
+ actor: normalizeActor(notification.actor)
197
+ });
198
+
199
+ // src/lib/api/actors.ts
200
+ async function fetchActor(identifier) {
201
+ const res = await apiFetch(`/api/actors/${encodeURIComponent(identifier)}`);
202
+ await assertOk(res, "Actor not found");
203
+ const data = await res.json();
204
+ return normalizeActor(data.actor);
205
+ }
206
+ async function updateProfile(data) {
207
+ const res = await apiPut("/api/actors/me", data);
208
+ await assertOk(res, "Failed to update profile");
209
+ }
210
+ async function fetchActorPosts(identifier, options) {
211
+ const params = new URLSearchParams;
212
+ if (options?.limit)
213
+ params.set("limit", String(options.limit));
214
+ if (options?.before)
215
+ params.set("before", options.before);
216
+ const query = params.toString() ? `?${params}` : "";
217
+ const res = await apiFetch(`/api/actors/${encodeURIComponent(identifier)}/posts${query}`);
218
+ await assertOk(res, "Failed to fetch actor posts");
219
+ const data = await res.json();
220
+ return {
221
+ posts: (data.posts || []).map(normalizePost),
222
+ nextCursor: data.next_cursor ?? null,
223
+ hasMore: data.has_more ?? false
224
+ };
225
+ }
226
+ function followListQuery(options) {
227
+ const params = new URLSearchParams;
228
+ if (options?.limit)
229
+ params.set("limit", String(options.limit));
230
+ if (options?.offset)
231
+ params.set("offset", String(options.offset));
232
+ return params.toString() ? `?${params}` : "";
233
+ }
234
+ async function fetchFollowers(identifier, options) {
235
+ const res = await apiFetch(`/api/actors/${encodeURIComponent(identifier)}/followers${followListQuery(options)}`);
236
+ await assertOk(res, "Failed to fetch followers");
237
+ const data = await res.json();
238
+ return {
239
+ actors: (data.followers || []).map(normalizeActor),
240
+ hasMore: data.has_more ?? false,
241
+ total: data.total ?? 0
242
+ };
243
+ }
244
+ async function fetchFollowing(identifier, options) {
245
+ const res = await apiFetch(`/api/actors/${encodeURIComponent(identifier)}/following${followListQuery(options)}`);
246
+ await assertOk(res, "Failed to fetch following");
247
+ const data = await res.json();
248
+ return {
249
+ actors: (data.following || []).map(normalizeActor),
250
+ hasMore: data.has_more ?? false,
251
+ total: data.total ?? 0
252
+ };
253
+ }
254
+ async function fetchBlockedUsers() {
255
+ const res = await apiFetch("/api/actors/me/blocked");
256
+ await assertOk(res, "Failed to fetch blocked users");
257
+ const data = await res.json();
258
+ return (data.blocked || []).map(normalizeActor);
259
+ }
260
+ async function blockUser(apId) {
261
+ const res = await apiPost("/api/actors/me/blocked", { ap_id: apId });
262
+ await assertOk(res, "Failed to block user");
263
+ }
264
+ async function unblockUser(apId) {
265
+ const res = await apiDelete("/api/actors/me/blocked", { ap_id: apId });
266
+ await assertOk(res, "Failed to unblock user");
267
+ }
268
+ async function fetchMutedUsers() {
269
+ const res = await apiFetch("/api/actors/me/muted");
270
+ await assertOk(res, "Failed to fetch muted users");
271
+ const data = await res.json();
272
+ return (data.muted || []).map(normalizeActor);
273
+ }
274
+ async function muteUser(apId) {
275
+ const res = await apiPost("/api/actors/me/muted", { ap_id: apId });
276
+ await assertOk(res, "Failed to mute user");
277
+ }
278
+ async function unmuteUser(apId) {
279
+ const res = await apiDelete("/api/actors/me/muted", { ap_id: apId });
280
+ await assertOk(res, "Failed to unmute user");
281
+ }
282
+ async function deleteAccount() {
283
+ const res = await apiPost("/api/actors/me/delete");
284
+ await assertOk(res, "Failed to delete account");
285
+ }
286
+
287
+ // src/lib/api/follow.ts
288
+ async function follow(targetApId) {
289
+ const res = await apiPost("/api/follow", { target_ap_id: targetApId });
290
+ await assertOk(res, "Failed to follow");
291
+ return await res.json();
292
+ }
293
+ async function unfollow(targetApId) {
294
+ const res = await apiDelete("/api/follow", { target_ap_id: targetApId });
295
+ await assertOk(res, "Failed to unfollow");
296
+ }
297
+ async function acceptFollowRequest(requesterApId) {
298
+ const res = await apiPost("/api/follow/accept", {
299
+ requester_ap_id: requesterApId
300
+ });
301
+ await assertOk(res, "Failed to accept");
302
+ }
303
+ async function rejectFollowRequest(requesterApId) {
304
+ const res = await apiPost("/api/follow/reject", {
305
+ requester_ap_id: requesterApId
306
+ });
307
+ await assertOk(res, "Failed to reject");
308
+ }
309
+
310
+ // src/lib/api/posts.ts
311
+ async function fetchTimeline(options) {
312
+ const params = new URLSearchParams;
313
+ if (options?.limit)
314
+ params.set("limit", String(options.limit));
315
+ if (options?.before)
316
+ params.set("before", options.before);
317
+ if (options?.community)
318
+ params.set("community", options.community);
319
+ const query = params.toString() ? `?${params}` : "";
320
+ const res = await apiFetch(`/api/timeline${query}`);
321
+ await assertOk(res, "Failed to load timeline");
322
+ const data = await res.json();
323
+ return {
324
+ posts: (data.posts || []).map(normalizePost),
325
+ nextCursor: data.next_cursor ?? null,
326
+ hasMore: data.has_more ?? false
327
+ };
328
+ }
329
+ async function fetchPost(apId) {
330
+ const res = await apiFetch(`/api/posts/${encodeURIComponent(apId)}`);
331
+ await assertOk(res, "Post not found");
332
+ const data = await res.json();
333
+ return normalizePost(data.post);
334
+ }
335
+ async function fetchReplies(postApId, options) {
336
+ const params = new URLSearchParams;
337
+ if (options?.before)
338
+ params.set("before", options.before);
339
+ const query = params.toString() ? `?${params}` : "";
340
+ const res = await apiFetch(`/api/posts/${encodeURIComponent(postApId)}/replies${query}`);
341
+ const data = await res.json();
342
+ return {
343
+ replies: (data.replies || []).map(normalizePost),
344
+ nextCursor: data.next_cursor ?? null,
345
+ hasMore: data.has_more ?? false
346
+ };
347
+ }
348
+ async function createPost(data) {
349
+ const res = await apiPost("/api/posts", data);
350
+ await assertOk(res, "Failed to create post");
351
+ const result = await res.json();
352
+ return normalizePost(result.post);
353
+ }
354
+ async function editPost(apId, data) {
355
+ const res = await apiPatch(`/api/posts/${encodeURIComponent(apId)}`, data);
356
+ await assertOk(res, "Failed to edit post");
357
+ const result = await res.json();
358
+ return { content: result.post.content, summary: result.post.summary };
359
+ }
360
+ async function deletePost(apId) {
361
+ const res = await apiDelete(`/api/posts/${encodeURIComponent(apId)}`);
362
+ await assertOk(res, "Failed to delete post");
363
+ }
364
+ async function likePost(apId) {
365
+ const res = await apiPost(`/api/posts/${encodeURIComponent(apId)}/like`);
366
+ await assertOk(res, "Failed to like");
367
+ }
368
+ async function unlikePost(apId) {
369
+ const res = await apiDelete(`/api/posts/${encodeURIComponent(apId)}/like`);
370
+ await assertOk(res, "Failed to unlike");
371
+ }
372
+ async function repostPost(apId) {
373
+ const res = await apiPost(`/api/posts/${encodeURIComponent(apId)}/repost`);
374
+ await assertOk(res, "Failed to repost");
375
+ }
376
+ async function unrepostPost(apId) {
377
+ const res = await apiDelete(`/api/posts/${encodeURIComponent(apId)}/repost`);
378
+ await assertOk(res, "Failed to unrepost");
379
+ }
380
+ async function bookmarkPost(apId) {
381
+ const res = await apiPost(`/api/posts/${encodeURIComponent(apId)}/bookmark`);
382
+ await assertOk(res, "Failed to bookmark");
383
+ }
384
+ async function unbookmarkPost(apId) {
385
+ const res = await apiDelete(`/api/posts/${encodeURIComponent(apId)}/bookmark`);
386
+ await assertOk(res, "Failed to unbookmark");
387
+ }
388
+ async function fetchBookmarks(options) {
389
+ const params = new URLSearchParams;
390
+ if (options?.limit)
391
+ params.set("limit", String(options.limit));
392
+ if (options?.before)
393
+ params.set("before", options.before);
394
+ const query = params.toString() ? `?${params}` : "";
395
+ const res = await apiFetch(`/api/bookmarks${query}`);
396
+ const data = await res.json();
397
+ return {
398
+ posts: (data.posts ?? []).map(normalizePost),
399
+ nextCursor: data.next_cursor ?? null,
400
+ hasMore: data.has_more ?? false
401
+ };
402
+ }
403
+
404
+ // src/lib/api/communities.ts
405
+ var normalizeCommunityMessage = (message) => ({
406
+ ...message,
407
+ sender: normalizeActor(message.sender)
408
+ });
409
+ async function fetchCommunities() {
410
+ const res = await apiFetch("/api/communities");
411
+ const data = await res.json();
412
+ return data.communities || [];
413
+ }
414
+ async function fetchCommunity(identifier) {
415
+ const res = await apiFetch(`/api/communities/${encodeURIComponent(identifier)}`);
416
+ await assertOk(res, "Community not found");
417
+ const data = await res.json();
418
+ return data.community;
419
+ }
420
+ async function createCommunity(data) {
421
+ const res = await apiPost("/api/communities", data);
422
+ await assertOk(res, "Failed to create community");
423
+ const result = await res.json();
424
+ return result.community;
425
+ }
426
+ async function joinCommunity(identifier, options) {
427
+ const body = options?.inviteId ? { invite_id: options.inviteId } : undefined;
428
+ const res = await apiPost(`/api/communities/${encodeURIComponent(identifier)}/join`, body);
429
+ const data = await res.json().catch(() => ({}));
430
+ if (!res.ok) {
431
+ throw new ApiError(res.status, data.error || "Failed to join community");
432
+ }
433
+ const status = data.status === "pending" || data.status === "invite_required" ? data.status : "joined";
434
+ return { status };
435
+ }
436
+ async function leaveCommunity(identifier) {
437
+ const res = await apiPost(`/api/communities/${encodeURIComponent(identifier)}/leave`);
438
+ await assertOk(res, "Failed to leave community");
439
+ }
440
+ async function fetchCommunityMessages(identifier, options) {
441
+ const params = new URLSearchParams;
442
+ if (options?.limit)
443
+ params.set("limit", String(options.limit));
444
+ if (options?.before)
445
+ params.set("before", options.before);
446
+ const query = params.toString() ? `?${params}` : "";
447
+ const res = await apiFetch(`/api/communities/${encodeURIComponent(identifier)}/messages${query}`);
448
+ await assertOk(res, "Failed to fetch messages");
449
+ const data = await res.json();
450
+ return {
451
+ messages: (data.messages || []).map(normalizeCommunityMessage),
452
+ hasMore: data.has_more ?? false
453
+ };
454
+ }
455
+ async function sendCommunityMessage(identifier, content) {
456
+ const res = await apiPost(`/api/communities/${encodeURIComponent(identifier)}/messages`, { content });
457
+ await assertOk(res, "Failed to send message");
458
+ const data = await res.json();
459
+ return normalizeCommunityMessage(data.message);
460
+ }
461
+ async function fetchCommunityMembers(identifier) {
462
+ const res = await apiFetch(`/api/communities/${encodeURIComponent(identifier)}/members?limit=500`);
463
+ await assertOk(res, "Failed to fetch members");
464
+ const data = await res.json();
465
+ return (data.members || []).map(normalizeActor);
466
+ }
467
+ async function fetchCommunityJoinRequests(identifier) {
468
+ const res = await apiFetch(`/api/communities/${encodeURIComponent(identifier)}/requests`);
469
+ await assertOk(res, "Failed to fetch join requests");
470
+ const data = await res.json();
471
+ return (data.requests || []).map(normalizeActor);
472
+ }
473
+ async function acceptCommunityJoinRequest(identifier, actorApId) {
474
+ const res = await apiPost(`/api/communities/${encodeURIComponent(identifier)}/requests/accept`, { actor_ap_id: actorApId });
475
+ await assertOk(res, "Failed to accept join request");
476
+ }
477
+ async function rejectCommunityJoinRequest(identifier, actorApId) {
478
+ const res = await apiPost(`/api/communities/${encodeURIComponent(identifier)}/requests/reject`, { actor_ap_id: actorApId });
479
+ await assertOk(res, "Failed to reject join request");
480
+ }
481
+ async function fetchCommunityInvites(identifier) {
482
+ const res = await apiFetch(`/api/communities/${encodeURIComponent(identifier)}/invites`);
483
+ await assertOk(res, "Failed to fetch invites");
484
+ const data = await res.json();
485
+ return (data.invites || []).map((invite) => ({
486
+ ...invite,
487
+ invited_by: normalizeActor(invite.invited_by)
488
+ }));
489
+ }
490
+ async function createCommunityInvite(identifier, options) {
491
+ const res = await apiPost(`/api/communities/${encodeURIComponent(identifier)}/invites`, options);
492
+ await assertOk(res, "Failed to create invite");
493
+ return await res.json();
494
+ }
495
+ async function revokeCommunityInvite(identifier, inviteId) {
496
+ const res = await apiDelete(`/api/communities/${encodeURIComponent(identifier)}/invites/${encodeURIComponent(inviteId)}`);
497
+ await assertOk(res, "Failed to revoke invite");
498
+ }
499
+ async function updateCommunitySettings(identifier, settings) {
500
+ const res = await apiPatch(`/api/communities/${encodeURIComponent(identifier)}/settings`, settings);
501
+ await assertOk(res, "Failed to update community settings");
502
+ }
503
+ async function removeCommunityMember(identifier, actorApId) {
504
+ const res = await apiDelete(`/api/communities/${encodeURIComponent(identifier)}/members/${encodeURIComponent(actorApId)}`);
505
+ await assertOk(res, "Failed to remove member");
506
+ }
507
+ async function updateCommunityMemberRole(identifier, actorApId, role) {
508
+ const res = await apiPatch(`/api/communities/${encodeURIComponent(identifier)}/members/${encodeURIComponent(actorApId)}`, { role });
509
+ await assertOk(res, "Failed to update member role");
510
+ }
511
+ async function deleteCommunityMessage(identifier, messageId) {
512
+ const res = await apiDelete(`/api/communities/${encodeURIComponent(identifier)}/messages/${encodeURIComponent(messageId)}`);
513
+ await assertOk(res, "Failed to delete message");
514
+ }
515
+
516
+ // src/lib/api/dm.ts
517
+ var normalizeDmMessage = (message) => ({
518
+ ...message,
519
+ sender: normalizeActor(message.sender)
520
+ });
521
+ var normalizeDmRequest = (request) => ({
522
+ ...request,
523
+ sender: normalizeActor(request.sender)
524
+ });
525
+ async function fetchDMContacts() {
526
+ const res = await apiFetch("/api/dm/contacts");
527
+ await assertOk(res, "Failed to load conversations");
528
+ const data = await res.json();
529
+ return {
530
+ mutual_followers: (data.mutual_followers || []).map(normalizeActor),
531
+ communities: (data.communities || []).map(normalizeActor),
532
+ request_count: data.request_count || 0
533
+ };
534
+ }
535
+ async function fetchDMUnreadCount() {
536
+ const res = await apiFetch("/api/dm/unread/count");
537
+ const data = await res.json();
538
+ return {
539
+ total: data.total || 0,
540
+ dm: data.dm || 0,
541
+ community: data.community || 0
542
+ };
543
+ }
544
+ async function fetchDMRequests() {
545
+ const res = await apiFetch("/api/dm/requests");
546
+ await assertOk(res, "Failed to load message requests");
547
+ const data = await res.json();
548
+ return (data.requests || []).map(normalizeDmRequest);
549
+ }
550
+ async function rejectDMRequest(senderApId, block) {
551
+ const res = await apiPost("/api/dm/requests/reject", {
552
+ sender_ap_id: senderApId,
553
+ block
554
+ });
555
+ await assertOk(res, "Failed to reject request");
556
+ }
557
+ async function fetchUserDMMessages(userApId, options) {
558
+ const params = new URLSearchParams;
559
+ if (options?.limit)
560
+ params.set("limit", String(options.limit));
561
+ if (options?.before)
562
+ params.set("before", options.before);
563
+ const query = params.toString() ? `?${params}` : "";
564
+ const res = await apiFetch(`/api/dm/user/${encodeURIComponent(userApId)}/messages${query}`);
565
+ const data = await res.json();
566
+ return {
567
+ messages: (data.messages || []).map(normalizeDmMessage),
568
+ conversation_id: data.conversation_id ?? null,
569
+ hasMore: data.has_more ?? false
570
+ };
571
+ }
572
+ async function sendUserDMMessage(userApId, content) {
573
+ const res = await apiPost(`/api/dm/user/${encodeURIComponent(userApId)}/messages`, { content });
574
+ await assertOk(res, "Failed to send message");
575
+ const data = await res.json();
576
+ return {
577
+ message: normalizeDmMessage(data.message),
578
+ conversation_id: data.conversation_id
579
+ };
580
+ }
581
+ async function sendUserDMTyping(userApId) {
582
+ const res = await apiPost(`/api/dm/user/${encodeURIComponent(userApId)}/typing`);
583
+ await assertOk(res, "Failed to send typing");
584
+ }
585
+ async function fetchUserDMTyping(userApId) {
586
+ const res = await apiFetch(`/api/dm/user/${encodeURIComponent(userApId)}/typing`);
587
+ await assertOk(res, "Failed to fetch typing");
588
+ const data = await res.json();
589
+ return {
590
+ is_typing: !!data.is_typing,
591
+ last_typed_at: data.last_typed_at ?? null
592
+ };
593
+ }
594
+ async function markDMAsRead(userApId) {
595
+ const res = await apiPost(`/api/dm/user/${encodeURIComponent(userApId)}/read`);
596
+ await assertOk(res, "Failed to mark as read");
597
+ }
598
+ async function archiveDMConversation(userApId) {
599
+ const res = await apiPost(`/api/dm/user/${encodeURIComponent(userApId)}/archive`);
600
+ await assertOk(res, "Failed to archive conversation");
601
+ }
602
+ async function unarchiveDMConversation(userApId) {
603
+ const res = await apiDelete(`/api/dm/user/${encodeURIComponent(userApId)}/archive`);
604
+ await assertOk(res, "Failed to unarchive conversation");
605
+ }
606
+ async function fetchArchivedDMConversations() {
607
+ const res = await apiFetch("/api/dm/archived");
608
+ await assertOk(res, "Failed to load archived conversations");
609
+ const data = await res.json();
610
+ return (data.archived || []).map((c) => {
611
+ const a = normalizeActor(c);
612
+ return {
613
+ ...a,
614
+ type: "user",
615
+ last_message: null,
616
+ unread_count: 0
617
+ };
618
+ });
619
+ }
620
+ async function markCommunityAsRead(communityApId) {
621
+ const res = await apiPost(`/api/dm/community/${encodeURIComponent(communityApId)}/read`);
622
+ await assertOk(res, "Failed to mark as read");
623
+ }
624
+ async function fetchDMContact(apId) {
625
+ const res = await apiFetch(`/api/dm/contact/${encodeURIComponent(apId)}`);
626
+ if (res.status === 404)
627
+ return null;
628
+ await assertOk(res, "Failed to resolve contact");
629
+ const data = await res.json();
630
+ return data.contact ? normalizeActor(data.contact) : null;
631
+ }
632
+
633
+ // src/lib/api/notifications.ts
634
+ async function fetchNotifications(options) {
635
+ const params = new URLSearchParams;
636
+ if (options?.limit)
637
+ params.set("limit", options.limit.toString());
638
+ if (options?.type && options.type !== "all")
639
+ params.set("type", options.type);
640
+ if (options?.before)
641
+ params.set("before", options.before);
642
+ if (options?.archived)
643
+ params.set("archived", "true");
644
+ const query = params.toString() ? `?${params}` : "";
645
+ const res = await apiFetch(`/api/notifications${query}`);
646
+ await assertOk(res, "Failed to load notifications");
647
+ const data = await res.json();
648
+ return {
649
+ notifications: (data.notifications || []).map(normalizeNotification),
650
+ hasMore: data.has_more ?? false,
651
+ nextCursor: data.next_cursor ?? null
652
+ };
653
+ }
654
+ async function fetchUnreadCount() {
655
+ const res = await apiFetch("/api/notifications/unread/count");
656
+ const data = await res.json();
657
+ return data.count || 0;
658
+ }
659
+ async function markNotificationsRead(ids) {
660
+ const res = await apiPost("/api/notifications/read", { ids });
661
+ await assertOk(res, "Failed to mark as read");
662
+ }
663
+ async function archiveNotifications(ids) {
664
+ const res = await apiPost("/api/notifications/archive", { ids });
665
+ await assertOk(res, "Failed to archive");
666
+ }
667
+ async function unarchiveNotifications(ids) {
668
+ const res = await apiDelete("/api/notifications/archive", { ids });
669
+ await assertOk(res, "Failed to unarchive");
670
+ }
671
+ async function archiveAllNotifications() {
672
+ const res = await apiPost("/api/notifications/archive/all", {});
673
+ await assertOk(res, "Failed to archive all");
674
+ const data = await res.json();
675
+ return data.archived_count ?? 0;
676
+ }
677
+
678
+ // src/lib/api/search.ts
679
+ function pageParams(query, opts) {
680
+ const params = new URLSearchParams({ q: query });
681
+ if (opts?.sort)
682
+ params.set("sort", opts.sort);
683
+ if (opts?.offset)
684
+ params.set("offset", String(opts.offset));
685
+ if (opts?.limit)
686
+ params.set("limit", String(opts.limit));
687
+ return params.toString();
688
+ }
689
+ async function searchActors(query, opts) {
690
+ const res = await apiFetch(`/api/search/actors?${pageParams(query, opts)}`);
691
+ const data = await res.json();
692
+ return {
693
+ items: (data.actors || []).map(normalizeActor),
694
+ hasMore: data.has_more ?? false
695
+ };
696
+ }
697
+ async function searchRemote(query) {
698
+ const res = await apiFetch(`/api/search/remote?q=${encodeURIComponent(query)}`);
699
+ const data = await res.json();
700
+ return (data.actors || []).map(normalizeActor);
701
+ }
702
+ async function searchPosts(query, opts) {
703
+ const res = await apiFetch(`/api/search/posts?${pageParams(query, opts)}`);
704
+ const data = await res.json();
705
+ return {
706
+ items: (data.posts || []).map(normalizePost),
707
+ hasMore: data.has_more ?? false
708
+ };
709
+ }
710
+ async function searchHashtag(tag, opts) {
711
+ const params = new URLSearchParams;
712
+ if (opts?.sort)
713
+ params.set("sort", opts.sort);
714
+ if (opts?.offset)
715
+ params.set("offset", String(opts.offset));
716
+ if (opts?.limit)
717
+ params.set("limit", String(opts.limit));
718
+ const qs = params.toString();
719
+ const res = await apiFetch(`/api/search/hashtag/${encodeURIComponent(tag)}${qs ? `?${qs}` : ""}`);
720
+ const data = await res.json();
721
+ return {
722
+ items: (data.posts || []).map(normalizePost),
723
+ hasMore: data.has_more ?? false
724
+ };
725
+ }
726
+ async function fetchTrendingHashtags(limit = 10) {
727
+ const res = await apiFetch(`/api/search/hashtags/trending?limit=${limit}`);
728
+ const data = await res.json();
729
+ return data.trending || [];
730
+ }
731
+
732
+ // src/lib/api/media.ts
733
+ var allowedMimeTypes = [
734
+ "image/jpeg",
735
+ "image/png",
736
+ "image/gif",
737
+ "image/webp",
738
+ "video/mp4",
739
+ "video/webm"
740
+ ];
741
+ var maxImageFileSize = 20 * 1024 * 1024;
742
+ var maxVideoFileSize = 40 * 1024 * 1024;
743
+ var filenameRegex = /^[\w\-. ]+$/;
744
+
745
+ class FileValidationError extends Error {
746
+ code;
747
+ constructor(message, code) {
748
+ super(message);
749
+ this.code = code;
750
+ this.name = "FileValidationError";
751
+ }
752
+ }
753
+ function validateFile(file) {
754
+ if (!allowedMimeTypes.includes(file.type)) {
755
+ throw new FileValidationError(`Invalid file type: ${file.type}. Allowed types: ${allowedMimeTypes.join(", ")}`, "INVALID_TYPE");
756
+ }
757
+ const maxFileSize = file.type.startsWith("video/") ? maxVideoFileSize : maxImageFileSize;
758
+ if (file.size > maxFileSize) {
759
+ const sizeMB = (file.size / (1024 * 1024)).toFixed(2);
760
+ const maxMB = maxFileSize / (1024 * 1024);
761
+ throw new FileValidationError(`File too large: ${sizeMB}MB. Maximum size: ${maxMB}MB`, "FILE_TOO_LARGE");
762
+ }
763
+ if (!filenameRegex.test(file.name)) {
764
+ throw new FileValidationError(`Invalid filename: ${file.name}. Filename can only contain letters, numbers, dots, hyphens, underscores, and spaces.`, "INVALID_FILENAME");
765
+ }
766
+ }
767
+ async function uploadMedia(file) {
768
+ validateFile(file);
769
+ const formData = new FormData;
770
+ formData.append("file", file);
771
+ const res = await apiFetch("/api/media/upload", {
772
+ method: "POST",
773
+ body: formData,
774
+ timeoutMs: UPLOAD_REQUEST_TIMEOUT_MS
775
+ });
776
+ await assertOk(res, "Failed to upload");
777
+ return res.json();
778
+ }
779
+
780
+ // src/lib/api/stories.ts
781
+ async function fetchStories(community) {
782
+ const qs = community ? `?community=${encodeURIComponent(community)}` : "";
783
+ const res = await apiFetch(`/api/stories${qs}`);
784
+ await assertOk(res, "Failed to fetch stories");
785
+ const data = await res.json();
786
+ return (data.actor_stories || []).map(normalizeActorStories);
787
+ }
788
+ async function createStory(story) {
789
+ const res = await apiPost("/api/stories", story);
790
+ await assertOk(res, "Failed to create story");
791
+ const data = await res.json();
792
+ return normalizeStory(data.story);
793
+ }
794
+ async function deleteStory(apId) {
795
+ const res = await apiPost("/api/stories/delete", { ap_id: apId });
796
+ await assertOk(res, "Failed to delete story");
797
+ }
798
+ async function markStoryViewed(apId) {
799
+ const res = await apiPost("/api/stories/view", { ap_id: apId });
800
+ await assertOk(res, "Failed to mark story as viewed");
801
+ }
802
+ async function voteOnStory(apId, optionIndex) {
803
+ const res = await apiPost("/api/stories/vote", {
804
+ ap_id: apId,
805
+ option_index: optionIndex
806
+ });
807
+ await assertOk(res, "Failed to vote on story");
808
+ return await res.json();
809
+ }
810
+ async function likeStory(apId) {
811
+ const res = await apiPost(`/api/stories/${encodeURIComponent(apId)}/like`);
812
+ await assertOk(res, "Failed to like story");
813
+ return await res.json();
814
+ }
815
+ async function unlikeStory(apId) {
816
+ const res = await apiDelete(`/api/stories/${encodeURIComponent(apId)}/like`);
817
+ await assertOk(res, "Failed to unlike story");
818
+ return await res.json();
819
+ }
820
+ async function shareStory(apId) {
821
+ const res = await apiPost(`/api/stories/${encodeURIComponent(apId)}/share`);
822
+ await assertOk(res, "Failed to share story");
823
+ return await res.json();
824
+ }
825
+
826
+ // src/lib/api/recommendations.ts
827
+ async function fetchRecommendedUsers() {
828
+ const res = await apiFetch("/api/recommendations/users");
829
+ if (!res.ok)
830
+ return [];
831
+ const data = await res.json();
832
+ return (data.users || []).map((u) => ({
833
+ ...normalizeActor(u),
834
+ mutual_count: u.mutual_count
835
+ }));
836
+ }
837
+
838
+ // src/lib/api/moderation.ts
839
+ async function fetchBlockedDomains() {
840
+ const res = await apiFetch("/api/moderation/domains");
841
+ await assertOk(res, "Failed to fetch blocked domains");
842
+ const data = await res.json();
843
+ return data.domains || [];
844
+ }
845
+ async function blockDomain(domain, reason) {
846
+ const res = await apiPost("/api/moderation/domains", { domain, reason });
847
+ await assertOk(res, "Failed to block domain");
848
+ }
849
+ async function unblockDomain(domain) {
850
+ const res = await apiDelete("/api/moderation/domains", { domain });
851
+ await assertOk(res, "Failed to unblock domain");
852
+ }
853
+ async function fetchBlockedActors() {
854
+ const res = await apiFetch("/api/moderation/actors");
855
+ await assertOk(res, "Failed to fetch blocked actors");
856
+ const data = await res.json();
857
+ return data.actors || [];
858
+ }
859
+ async function blockActor(apId, reason) {
860
+ const res = await apiPost("/api/moderation/actors", { ap_id: apId, reason });
861
+ await assertOk(res, "Failed to block actor");
862
+ }
863
+ async function unblockActor(apId) {
864
+ const res = await apiDelete("/api/moderation/actors", { ap_id: apId });
865
+ await assertOk(res, "Failed to unblock actor");
866
+ }
867
+ async function fetchReports(options) {
868
+ const query = options?.onlyOpen ? "?status=open" : "";
869
+ const res = await apiFetch(`/api/moderation/reports${query}`);
870
+ await assertOk(res, "Failed to fetch reports");
871
+ const data = await res.json();
872
+ return data.reports || [];
873
+ }
874
+ async function resolveReport(id, reopen = false) {
875
+ const res = await apiPost(`/api/moderation/reports/${encodeURIComponent(id)}/resolve`, { reopen });
876
+ await assertOk(res, "Failed to resolve report");
877
+ }
878
+ async function reportContent(input) {
879
+ const res = await apiPost("/api/moderation/reports/outbound", {
880
+ target_actor_ap_id: input.targetActorApId,
881
+ post_ap_id: input.postApId,
882
+ reason: input.reason
883
+ });
884
+ await assertOk(res, "Failed to submit report");
885
+ }
886
+ // src/social-server.ts
887
+ async function fetchCurrentActor() {
888
+ const res = await apiFetch("/api/auth/me");
889
+ if (res.status === 401 || res.status === 403)
890
+ return null;
891
+ await assertOk(res, "Failed to load current user");
892
+ const data = await res.json();
893
+ return data.actor ?? null;
894
+ }
895
+ async function fetchSocialServerDiscovery() {
896
+ const res = await apiFetch("/.well-known/social-server");
897
+ await assertOk(res, "Failed to load social server discovery");
898
+ return await res.json();
899
+ }
900
+ export {
901
+ voteOnStory,
902
+ validateFile,
903
+ uploadMedia,
904
+ updateProfile,
905
+ updateCommunitySettings,
906
+ updateCommunityMemberRole,
907
+ unrepostPost,
908
+ unmuteUser,
909
+ unlikeStory,
910
+ unlikePost,
911
+ unfollow,
912
+ unbookmarkPost,
913
+ unblockUser,
914
+ unblockDomain,
915
+ unblockActor,
916
+ unarchiveNotifications,
917
+ unarchiveDMConversation,
918
+ switchAccount,
919
+ shareStory,
920
+ setYurucommuApiTransportResolver,
921
+ setYurucommuApiTransport,
922
+ setAlsoKnownAs,
923
+ sendUserDMTyping,
924
+ sendUserDMMessage,
925
+ sendCommunityMessage,
926
+ searchRemote,
927
+ searchPosts,
928
+ searchHashtag,
929
+ searchActors,
930
+ revokeCommunityInvite,
931
+ resolveReport,
932
+ repostPost,
933
+ reportContent,
934
+ removeCommunityMember,
935
+ rejectFollowRequest,
936
+ rejectDMRequest,
937
+ rejectCommunityJoinRequest,
938
+ normalizeStory,
939
+ normalizePost,
940
+ normalizeNotification,
941
+ normalizeActorStories,
942
+ normalizeActor,
943
+ muteUser,
944
+ moveAccount,
945
+ maxVideoFileSize,
946
+ maxImageFileSize,
947
+ markStoryViewed,
948
+ markNotificationsRead,
949
+ markDMAsRead,
950
+ markCommunityAsRead,
951
+ logout,
952
+ login,
953
+ likeStory,
954
+ likePost,
955
+ leaveCommunity,
956
+ joinCommunity,
957
+ getYurucommuApiTransport,
958
+ follow,
959
+ fetchUserDMTyping,
960
+ fetchUserDMMessages,
961
+ fetchUnreadCount,
962
+ fetchTrendingHashtags,
963
+ fetchTimeline,
964
+ fetchStories,
965
+ fetchSocialServerDiscovery,
966
+ fetchReports,
967
+ fetchReplies,
968
+ fetchRecommendedUsers,
969
+ fetchPost,
970
+ fetchNotifications,
971
+ fetchMutedUsers,
972
+ fetchFollowing,
973
+ fetchFollowers,
974
+ fetchDMUnreadCount,
975
+ fetchDMRequests,
976
+ fetchDMContacts,
977
+ fetchDMContact,
978
+ fetchCurrentActor,
979
+ fetchCommunityMessages,
980
+ fetchCommunityMembers,
981
+ fetchCommunityJoinRequests,
982
+ fetchCommunityInvites,
983
+ fetchCommunity,
984
+ fetchCommunities,
985
+ fetchBookmarks,
986
+ fetchBlockedUsers,
987
+ fetchBlockedDomains,
988
+ fetchBlockedActors,
989
+ fetchArchivedDMConversations,
990
+ fetchAlsoKnownAs,
991
+ fetchActorPosts,
992
+ fetchActor,
993
+ fetchAccounts,
994
+ extractErrorMessage,
995
+ editPost,
996
+ deleteStory,
997
+ deletePost,
998
+ deleteCommunityMessage,
999
+ deleteAccount,
1000
+ createStory,
1001
+ createPost,
1002
+ createCommunityInvite,
1003
+ createCommunity,
1004
+ createAccount,
1005
+ clearYurucommuApiTransport,
1006
+ bookmarkPost,
1007
+ blockUser,
1008
+ blockDomain,
1009
+ blockActor,
1010
+ assertOk,
1011
+ archiveNotifications,
1012
+ archiveDMConversation,
1013
+ archiveAllNotifications,
1014
+ apiPut,
1015
+ apiPost,
1016
+ apiPatch,
1017
+ apiFetch,
1018
+ apiDelete,
1019
+ allowedMimeTypes,
1020
+ acceptFollowRequest,
1021
+ acceptCommunityJoinRequest,
1022
+ FileValidationError,
1023
+ ApiError
1024
+ };